[
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n*.cmd eol=crlf\n*.bat eol=crlf\ninit.d/windivert.filter.examples/** eol=crlf\nfiles/** binary\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue-warning.md",
    "content": "---\nname: bugs\nabout: do not write lame questions\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nIssues - это место для обращений к разработчику.\nDiscussions - место для обсуждения вопросов между пользователями.\nНе тратьте время разработчика на ерунду. Вам не будут здесь объяснять как скопировать, что \"писать в\", почему сразу же закрывается окно, почему не открывается сайт или госуслуги. Здесь не место обсуждению любых шаманств, т.е. манипуляций без понимания, в стиле 4pda.\nПишите только конкретные проблемы на техническом уровне в оригинальном zapret (не в сборках), которые вы заметили, и которые являются или могут являться багами в софте.\nИли если вы считаете, что ваше обращение обосновано, технически грамотно и по адресу.\nВсе, что будет нарушать эти критерии, может быть молча удалено, закрыто или перенесено в дискуссии на усмотрение разработчика.\nЕсли сомневаетесь - пишите лучше сразу в дискуссии.\n\nПрочитайте для начала docs/quick_start.md или docs/quick_start_windows.md.\nТам объясняется для кого этот софт, какие требуются знания, почему это не простая волшебная таблетка.\n\nВирусов здесь нет. У вас либо чья-то сборка, либо ваш антивирус давно пора отправить на покой. Антивирусы в основном жалуются на upx и windivert, которые убраны НЕ будут. upx - это паковщик для сокращения требуемого места на openwrt, windivert - замена iptables для windows, потенциальный инструмент хакера или компонент зловредной программы, но сам по себе вирусом не является. Не согласны - удаляйте софт. За агрессивные наезды \"почему автор распространяет вирусы\" молча схватите бан.\n\n\nHere is the place for bugs only. All questions, especially user-like questions (non-technical) go to Discussions.\nThere're also no viruses here. All virus claims and everyting non-technical and non-bugs will be instantly deleted, closed or moved to Discussions.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\nrun-name: ${{ startsWith(github.ref, 'refs/tags/v') && format('Release {0}', github.ref_name) || null }}\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - v[0-9]+*\n    # branches:\n    #   - master\n    # paths:\n    #   - 'ip2net/**'\n    #   - 'mdig/**'\n    #   - 'nfq/**'\n    #   - 'tpws/**'\n\njobs:\n  build-linux:\n    name: Linux ${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - arch: arm64\n            tool: aarch64-unknown-linux-musl\n          - arch: arm\n            tool: armv6-unknown-linux-musleabi\n          - arch: mips64\n            tool: mips64-unknown-linux-musl\n          - arch: mipselsf\n            tool: mipsel-unknown-linux-muslsf\n          - arch: mipssf\n            tool: mips-unknown-linux-muslsf\n          - arch: ppc\n            tool: powerpc-unknown-linux-musl\n          - arch: x86\n            tool: i586-unknown-linux-musl\n          - arch: x86_64\n            tool: x86_64-unknown-linux-musl\n          - arch: lexra\n            tool: mips-linux\n            dir: rsdk-4.6.4-5281-EB-3.10-0.9.33-m32ub-20141001\n            env:\n              CFLAGS: '-march=5281'\n              LDFLAGS: '-lgcc_eh'\n            repo: 'bol-van/build'\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          path: zapret\n\n      - name: Set up build tools\n        env:\n          ARCH: ${{ matrix.arch }}\n          TOOL: ${{ matrix.tool }}\n          REPO: ${{ matrix.arch == 'lexra' && matrix.repo || 'bol-van/musl-cross' }}\n          DIR: ${{ matrix.arch == 'lexra' && matrix.dir || matrix.tool }}\n        run: |\n          if [[ \"$ARCH\" == lexra ]]; then\n            sudo dpkg --add-architecture i386\n            sudo apt update -qq\n            sudo apt install -y libcap-dev libc6:i386 zlib1g:i386\n            URL=https://github.com/$REPO/raw/refs/heads/master/$DIR.txz\n          else\n            sudo apt update -qq\n            sudo apt install -y libcap-dev\n            URL=https://github.com/$REPO/releases/download/latest/$TOOL.tar.xz\n          fi\n          mkdir -p $HOME/tools\n          wget -qO- $URL | tar -C $HOME/tools -xJ || exit 1\n          [[ -d \"$HOME/tools/$DIR/bin\" ]] && echo \"$HOME/tools/$DIR/bin\" >> $GITHUB_PATH\n\n      - name: Build\n        env:\n          ARCH: ${{ matrix.arch }}\n          TARGET: ${{ matrix.tool }}\n          CFLAGS: ${{ matrix.env.CFLAGS != '' && matrix.env.CFLAGS || null }}\n          LDFLAGS: ${{ matrix.env.LDFLAGS != '' && matrix.env.LDFLAGS || null }}\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          DEPS_DIR=$GITHUB_WORKSPACE/deps\n          export CC=\"$TARGET-gcc\"\n          export LD=$TARGET-ld\n          export AR=$TARGET-ar\n          export NM=$TARGET-nm\n          export STRIP=$TARGET-strip\n          export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig\n          export STAGING_DIR=$RUNNER_TEMP\n          OPTIMIZE=-Oz\n          case \"$ARCH\" in\n            lexra)\n              OPTIMIZE=-Os\n              ;;\n            arm)\n              CPU=\"-mcpu=arm1176jzf-s -mthumb\"\n              ;;\n          esac\n\n          # netfilter libs\n          wget -qO- https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj\n          wget -qO- https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj\n          wget -qO- https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj\n\n          for i in libmnl libnfnetlink libnetfilter_queue ; do\n            (\n              cd $i-*\n              CFLAGS=\"$OPTIMIZE $CPU -flto=auto $CFLAGS\" \\\n              ./configure --prefix= --host=$TARGET --enable-static --disable-shared --disable-dependency-tracking\n              make install -j$(nproc) DESTDIR=$DEPS_DIR\n            )\n            sed -i \"s|^prefix=.*|prefix=$DEPS_DIR|g\" $DEPS_DIR/lib/pkgconfig/$i.pc\n          done\n\n          # zlib\n          gh api repos/madler/zlib/releases/latest --jq '.tag_name' |\\\n            xargs -I{} wget -qO- https://github.com/madler/zlib/archive/refs/tags/{}.tar.gz | tar -xz\n          (\n            cd zlib-*\n            CFLAGS=\"$OPTIMIZE $CPU -flto=auto $CFLAGS\" \\\n            ./configure --prefix= --static\n            make install -j$(nproc) DESTDIR=$DEPS_DIR\n          )\n\n          # headers\n          # wget https://git.alpinelinux.org/aports/plain/main/bsd-compat-headers/queue.h && \\\n          # wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/plain/libcap/include/sys/capability.h && \\\n          install -Dm644 -t $DEPS_DIR/include/sys /usr/include/x86_64-linux-gnu/sys/queue.h /usr/include/sys/capability.h\n\n          # zapret\n          OPTIMIZE=$OPTIMIZE \\\n          CFLAGS=\"-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -static-libgcc -static $CPU -I$DEPS_DIR/include $CFLAGS\" \\\n          LDFLAGS=\"-L$DEPS_DIR/lib $LDFLAGS\" \\\n          make -C zapret -j$(nproc)\n          tar -C zapret/binaries/my -cJf zapret-linux-$ARCH.tar.xz .\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: zapret-linux-${{ matrix.arch }}\n          path: zapret-*.tar.xz\n          if-no-files-found: error\n\n  build-macos:\n    name: macOS\n    runs-on: macos-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Build zapret\n        run: |\n          export CFLAGS=\"-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}\"\n          make mac -j$(sysctl -n hw.logicalcpu)\n          tar -C binaries/my -cJf zapret-mac-x64.tar.xz .\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: zapret-mac-x64\n          path: zapret-*.tar.xz\n          if-no-files-found: error\n\n  build-freebsd:\n    name: FreeBSD ${{ matrix.arch }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - target: x86_64\n            arch: x86_64\n          # - target: i386\n          #   arch: x86\n    container:\n      image: empterdose/freebsd-cross-build:11.4\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install packages\n        run: apk add tar xz\n\n      - name: Build zapret\n        env:\n          TARGET: ${{ matrix.target }}\n          ARCH: ${{ matrix.arch }}\n        run: |\n          export CFLAGS=\"-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}\"\n          settarget $TARGET-freebsd11 make bsd -j$(nproc)\n          tar -C binaries/my -cJf zapret-freebsd-$ARCH.tar.xz .\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: zapret-freebsd-${{ matrix.arch }}\n          path: zapret-*.tar.xz\n          if-no-files-found: error\n\n  build-windows:\n    name: Windows ${{ matrix.arch }}\n    runs-on: windows-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [ x86_64, x86 ]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          path: zapret\n\n      - name: Set up MinGW\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: ${{ matrix.arch == 'x86_64' && 'MINGW64' || 'MINGW32' }}\n          install: >-\n            ${{ matrix.arch == 'x86_64' && 'mingw-w64-x86_64-toolchain' || 'mingw-w64-i686-toolchain' }}\n\n      - name: Build ip2net, mdig\n        shell: msys2 {0}\n        run: |\n          export CFLAGS=\"-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}\"\n          mkdir -p output\n          cd zapret\n          mingw32-make -C ip2net win\n          mingw32-make -C mdig win\n          cp -a {ip2net/ip2net,mdig/mdig}.exe ../output\n\n      - name: Restore psmisc from cache\n        id: cache-restore-psmisc\n        uses: actions/cache/restore@v4\n        with:\n          path: ${{ github.workspace }}/psmisc\n          key: psmisc-${{ matrix.arch }}\n\n      - name: Set up Cygwin\n        env:\n          PACKAGES: ${{ steps.cache-restore-psmisc.outputs.cache-hit != 'true' && 'cygport gettext-devel libiconv-devel libncurses-devel' || null }}\n        uses: cygwin/cygwin-install-action@v4\n        with:\n          platform: ${{ matrix.arch }}\n          site: ${{ matrix.arch == 'x86_64' && 'http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215' || null }}\n          check-sig: ${{ matrix.arch == 'x86_64' && 'false' || null }}\n          packages: >-\n            gcc-core\n            make\n            zlib-devel\n            zip\n            unzip\n            wget\n            ${{ env.PACKAGES }}\n\n      - name: Build psmisc\n        if: steps.cache-restore-psmisc.outputs.cache-hit != 'true'\n        env:\n          URL: https://mirrors.kernel.org/sourceware/cygwin/x86_64/release/psmisc\n        shell: C:\\cygwin\\bin\\bash.exe -eo pipefail '{0}'\n        run: >-\n          export MAKEFLAGS=-j$(nproc) &&\n          mkdir -p psmisc && cd psmisc &&\n          wget -qO- ${URL} | grep -Po 'href=\\\"\\Kpsmisc-(\\d+\\.)+\\d+.+src\\.tar\\.xz(?=\\\")' | xargs -I{} wget -O- ${URL}/{} | tar -xJ &&\n          cd psmisc-*.src &&\n          echo CYGCONF_ARGS+=\\\" --disable-dependency-tracking --disable-nls\\\" >> psmisc.cygport &&\n          cygport psmisc.cygport prep compile install\n\n      - name: Save psmisc to cache\n        if: steps.cache-restore-psmisc.outputs.cache-hit != 'true'\n        uses: actions/cache/save@v4\n        with:\n          path: ${{ github.workspace }}/psmisc\n          key: psmisc-${{ matrix.arch }}\n\n      - name: Build winws\n        env:\n          TARGET: ${{ matrix.arch == 'x86_64' && 'cygwin' || 'cygwin32' }}\n        shell: C:\\cygwin\\bin\\bash.exe -eo pipefail '{0}'\n        run: >-\n          export MAKEFLAGS=-j$(nproc) &&\n          export CFLAGS=\"-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}\" &&\n          cd zapret &&\n          make -C nfq ${TARGET} &&\n          cp -a nfq/winws.exe ../output\n\n      - name: Create zip\n        env:\n          BITS: ${{ matrix.arch == 'x86_64' && '64' || '32' }}\n          DIR: ${{ matrix.arch == 'x86_64' && 'x64' || 'x86' }}\n        shell: C:\\cygwin\\bin\\bash.exe -e '{0}'\n        run: >-\n          cp -a -t output psmisc/psmisc-*.src/psmisc-*/inst/usr/bin/killall.exe /usr/bin/cygwin1.dll &&\n          wget -O WinDivert.zip https://github.com/basil00/WinDivert/releases/download/v2.2.2/WinDivert-2.2.2-A.zip &&\n          unzip -j WinDivert.zip \"*/${DIR}/WinDivert.dll\" \"*/${DIR}/WinDivert${BITS}.sys\" -d output &&\n          zip zapret-win-${{ matrix.arch }}.zip -j output/*\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: zapret-win-${{ matrix.arch }}\n          path: zapret-*.zip\n          if-no-files-found: error\n\n  build-android:\n    name: Android ${{ matrix.abi }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - abi: armeabi-v7a\n            target: armv7a-linux-androideabi\n          - abi: arm64-v8a\n            target: aarch64-linux-android\n          - abi: x86\n            target: i686-linux-android\n          - abi: x86_64\n            target: x86_64-linux-android\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          path: zapret\n\n      - name: Build\n        env:\n          ABI: ${{ matrix.abi }}\n          API: 21\n          TARGET: ${{ matrix.target }}\n          GH_TOKEN: ${{ github.token }}\n        run: |\n          DEPS_DIR=$GITHUB_WORKSPACE/deps\n          export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64\n          export CC=\"$TOOLCHAIN/bin/clang --target=$TARGET$API\"\n          export AR=$TOOLCHAIN/bin/llvm-ar\n          export AS=$CC\n          export LD=$TOOLCHAIN/bin/ld\n          export RANLIB=$TOOLCHAIN/bin/llvm-ranlib\n          export STRIP=$TOOLCHAIN/bin/llvm-strip\n          export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig\n          case \"$ABI\" in\n            armeabi-v7a)\n              CPU=\"-mthumb\"\n              ;;\n            arm64-v8a)\n              PAGESIZE=\"-Wl,-z,max-page-size=16384\"\n              ;;\n          esac\n\n          # netfilter libs\n          wget -qO- https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj\n          wget -qO- https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj\n          wget -qO- https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj\n          patch -p1 -d libnetfilter_queue-* -i ../zapret/.github/workflows/libnetfilter_queue-android.patch\n\n          for i in libmnl libnfnetlink libnetfilter_queue ; do\n            (\n              cd $i-*\n              CFLAGS=\"$CPU -Os -flto=auto -Wno-implicit-function-declaration\" \\\n              ./configure --prefix= --host=$TARGET --enable-static --disable-shared --disable-dependency-tracking\n              make install -j$(nproc) DESTDIR=$DEPS_DIR\n            )\n            sed -i \"s|^prefix=.*|prefix=$DEPS_DIR|g\" $DEPS_DIR/lib/pkgconfig/$i.pc\n          done\n\n          # zapret\n          CFLAGS=\"$CPU -DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -I$DEPS_DIR/include\" \\\n          LDFLAGS=\"-L$DEPS_DIR/lib $PAGESIZE\" \\\n          make -C zapret android -j$(nproc)\n\n          # strip unwanted ELF sections to prevent warnings on old Android versions\n          gh api repos/termux/termux-elf-cleaner/releases/latest --jq '.tag_name' |\\\n            xargs -I{} wget -O elf-cleaner https://github.com/termux/termux-elf-cleaner/releases/download/{}/termux-elf-cleaner\n          chmod +x elf-cleaner\n          ./elf-cleaner --api-level $API zapret/binaries/my/*\n          zip zapret-android-$ABI.zip -j zapret/binaries/my/*\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: zapret-android-${{ matrix.abi }}\n          path: zapret-*.zip\n          if-no-files-found: error\n\n  release:\n    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')\n    needs: [ build-linux, build-windows, build-macos, build-freebsd, build-android ]\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    env:\n      repo_dir: zapret-${{ github.ref_name }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          path: ${{ env.repo_dir }}\n\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        id: bins\n        with:\n          path: ${{ env.repo_dir }}/binaries\n          pattern: zapret-*\n\n      - name: Install upx\n        uses: crazy-max/ghaction-upx@v3\n        with:\n          install-only: true\n          version: v4.2.4\n\n      - name: Prepare binaries\n        shell: bash\n        run: |\n          cd ${{ steps.bins.outputs.download-path }}\n          run_upx() {\n            upx --best --lzma $@ || true\n          }\n          run_dir() {\n            for f in $dir/* ; do\n              # extract binaries\n              case $f in\n                *.tar.xz )\n                  tar -C $dir -xvf $f && rm $f\n                  if [[ $dir == *-linux-x86_64 ]]; then\n                    tar -C $dir -czvf $dir/tpws_wsl.tgz tpws\n                    run_upx $dir/*\n                  elif [[ $dir =~ linux ]] && [[ $dir != *-linux-mips64 ]] && [[ $dir != *-linux-lexra ]]; then\n                    run_upx $dir/*\n                  fi\n                  ;;\n                *.zip )\n                  unzip $f -d $dir && rm $f\n                  if [[ $dir =~ win ]]; then\n                    chmod -x $dir/*\n                  fi\n                  ;;\n              esac\n            done\n            mv $dir $1\n          }\n          for dir in * ; do\n            if [ -d $dir ]; then\n              echo \"Processing $dir\"\n              case $dir in\n                *-android-arm64-v8a )   run_dir android-arm64 ;;\n                *-android-armeabi-v7a ) run_dir android-arm ;;\n                *-android-x86 )         run_dir android-x86 ;;\n                *-android-x86_64 )      run_dir android-x86_64 ;;\n                *-freebsd-x86_64 )      run_dir freebsd-x86_64 ;;\n                *-linux-arm )           run_dir linux-arm ;;\n                *-linux-arm64 )         run_dir linux-arm64 ;;\n                *-linux-mips64 )        run_dir linux-mips64 ;;\n                *-linux-mipselsf )      run_dir linux-mipsel ;;\n                *-linux-mipssf )        run_dir linux-mips ;;\n                *-linux-ppc )           run_dir linux-ppc ;;\n                *-linux-x86 )           run_dir linux-x86 ;;\n                *-linux-x86_64 )        run_dir linux-x86_64 ;;\n                *-linux-lexra )         run_dir linux-lexra ;;\n                *-mac-x64 )             run_dir mac64 ;;\n                *-win-x86 )             run_dir windows-x86 ;;\n                *-win-x86_64 )          run_dir windows-x86_64 ;;\n              esac\n            fi\n          done\n          ls -lhR\n\n      - name: Create release bundles\n        run: |\n          rm -rf ${{ env.repo_dir }}/.git*\n          find ${{ env.repo_dir }}/binaries -type f -exec sha256sum {} \\; >sha256sum.txt\n          tar --owner=0 --group=0 -czf ${{ env.repo_dir }}.tar.gz ${{ env.repo_dir }}\n          zip -qr ${{ env.repo_dir }}.zip ${{ env.repo_dir }}\n          (\n            cd ${{ env.repo_dir }}\n            rm -rf binaries/{android*,freebsd*,mac*,win*,x86_64/tpws_wsl.tgz} \\\n                   init.d/{openrc,macos,pfsense,runit,s6,systemd,windivert.filter.examples} \\\n                   tpws nfq ip2net mdig docs files/huawei Makefile\n          )\n          tar --owner=0 --group=0 -czf ${{ env.repo_dir }}-openwrt-embedded.tar.gz ${{ env.repo_dir }}\n\n      - name: Upload release assets\n        uses: softprops/action-gh-release@v2\n        with:\n          fail_on_unmatched_files: true\n          prerelease: false\n          draft: false\n          body: |\n            ### zapret ${{ github.ref_name }}\n          files: |\n            zapret*.tar.gz\n            zapret*.zip\n            sha256sum.txt\n"
  },
  {
    "path": ".github/workflows/libnetfilter_queue-android.patch",
    "content": "--- a/src/extra/pktbuff.c\n+++ b/src/extra/pktbuff.c\n@@ -14,7 +14,7 @@\n #include <string.h> /* for memcpy */\n #include <stdbool.h>\n \n-#include <netinet/if_ether.h>\n+#include <linux/if_ether.h>\n #include <netinet/ip.h>\n #include <netinet/tcp.h>\n \n--- a/src/nlmsg.c\n+++ b/src/nlmsg.c\n@@ -21,7 +21,7 @@\n \n #include <linux/netfilter/nfnetlink_queue.h>\n \n-#include <libnetfilter_queue/libnetfilter_queue.h>\n+// #include <libnetfilter_queue/libnetfilter_queue.h>\n \n #include \"internal.h\"\n \n--- a/src/extra/tcp.c\n+++ b/src/extra/tcp.c\n@@ -139,12 +139,16 @@ void nfq_tcp_compute_checksum_ipv6(struc\n  *  (union is compatible to any of its members)\n  *  This means this part of the code is -fstrict-aliasing safe now.\n  */\n+#ifndef __ANDROID__\n union tcp_word_hdr {\n \tstruct tcphdr hdr;\n \tuint32_t  words[5];\n };\n+#endif\n \n+#ifndef tcp_flag_word\n #define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words[3])\n+#endif\n \n /**\n  * nfq_pkt_snprintf_tcp_hdr - print tcp header into one buffer in a humnan\n"
  },
  {
    "path": ".gitignore",
    "content": "/config\nip2net/ip2net\nmdig/mdig\nnfq/dvtws\nnfq/nfqws\nnfq/winws.exe\nnfq/WinDivert*\ntpws/tpws\nbinaries/my/\nipset/zapret-ip*.txt\nipset/zapret-ip*.gz\nipset/zapret-hosts*.txt\nipset/zapret-hosts*.gz\n"
  },
  {
    "path": "Makefile",
    "content": "DIRS := nfq tpws ip2net mdig\nDIRS_MAC := tpws ip2net mdig\nTGT := binaries/my\n\nall:\tclean\n\t@mkdir -p \"$(TGT)\"; \\\n\tfor dir in $(DIRS); do \\\n\t\tfind \"$$dir\" -type f  \\( -name \"*.c\" -o -name \"*.h\" -o -name \"*akefile\" \\) -exec chmod -x {} \\; ; \\\n\t\t$(MAKE) -C \"$$dir\" || exit; \\\n\t\tfor exe in \"$$dir/\"*; do \\\n\t\t\tif [ -f \"$$exe\" ] && [ -x \"$$exe\" ]; then \\\n\t\t\t\tmv -f \"$$exe\" \"${TGT}\" ; \\\n\t\t\t\tln -fs \"../${TGT}/$$(basename \"$$exe\")\" \"$$exe\" ; \\\n\t\t\tfi \\\n\t\tdone \\\n\tdone\n\nsystemd: clean\n\t@mkdir -p \"$(TGT)\"; \\\n\tfor dir in $(DIRS); do \\\n\t\tfind \"$$dir\" -type f  \\( -name \"*.c\" -o -name \"*.h\" -o -name \"*akefile\" \\) -exec chmod -x {} \\; ; \\\n\t\t$(MAKE) -C \"$$dir\" systemd || exit; \\\n\t\tfor exe in \"$$dir/\"*; do \\\n\t\t\tif [ -f \"$$exe\" ] && [ -x \"$$exe\" ]; then \\\n\t\t\t\tmv -f \"$$exe\" \"${TGT}\" ; \\\n\t\t\t\tln -fs \"../${TGT}/$$(basename \"$$exe\")\" \"$$exe\" ; \\\n\t\t\tfi \\\n\t\tdone \\\n\tdone\n\nandroid: clean\n\t@mkdir -p \"$(TGT)\"; \\\n\tfor dir in $(DIRS); do \\\n\t\tfind \"$$dir\" -type f  \\( -name \"*.c\" -o -name \"*.h\" -o -name \"*akefile\" \\) -exec chmod -x {} \\; ; \\\n\t\t$(MAKE) -C \"$$dir\" android || exit; \\\n\t\tfor exe in \"$$dir/\"*; do \\\n\t\t\tif [ -f \"$$exe\" ] && [ -x \"$$exe\" ]; then \\\n\t\t\t\tmv -f \"$$exe\" \"${TGT}\" ; \\\n\t\t\t\tln -fs \"../${TGT}/$$(basename \"$$exe\")\" \"$$exe\" ; \\\n\t\t\tfi \\\n\t\tdone \\\n\tdone\n\nbsd:\tclean\n\t@mkdir -p \"$(TGT)\"; \\\n\tfor dir in $(DIRS); do \\\n\t\tfind \"$$dir\" -type f  \\( -name \"*.c\" -o -name \"*.h\" -o -name \"*akefile\" \\) -exec chmod -x {} \\; ; \\\n\t\t$(MAKE) -C \"$$dir\" bsd || exit; \\\n\t\tfor exe in \"$$dir/\"*; do \\\n\t\t\tif [ -f \"$$exe\" ] && [ -x \"$$exe\" ]; then \\\n\t\t\t\tmv -f \"$$exe\" \"${TGT}\" ; \\\n\t\t\t\tln -fs \"../${TGT}/$$(basename \"$$exe\")\" \"$$exe\" ; \\\n\t\t\tfi \\\n\t\tdone \\\n\tdone\n\nmac:\tclean\n\t@mkdir -p \"$(TGT)\"; \\\n\tfor dir in $(DIRS_MAC); do \\\n\t\tfind \"$$dir\" -type f  \\( -name \"*.c\" -o -name \"*.h\" -o -name \"*akefile\" \\) -exec chmod -x {} \\; ; \\\n\t\t$(MAKE) -C \"$$dir\" mac || exit; \\\n\t\tfor exe in \"$$dir/\"*; do \\\n\t\t\tif [ -f \"$$exe\" ] && [ -x \"$$exe\" ]; then \\\n\t\t\t\tmv -f \"$$exe\" \"${TGT}\" ; \\\n\t\t\t\tln -fs \"../${TGT}/$$(basename \"$$exe\")\" \"$$exe\" ; \\\n\t\t\tfi \\\n\t\tdone \\\n\tdone\n\nclean:\n\t@[ -d \"$(TGT)\" ] && rm -rf \"$(TGT)\" ; \\\n\tfor dir in $(DIRS); do \\\n\t\t$(MAKE) -C \"$$dir\" clean; \\\n\tdone\n"
  },
  {
    "path": "blockcheck.sh",
    "content": "#!/bin/sh\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=${ZAPRET_BASE:-\"$EXEDIR\"}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\nZAPRET_CONFIG_DEFAULT=\"$ZAPRET_BASE/config.default\"\n\nCURL=${CURL:-curl}\n\n[ -f \"$ZAPRET_CONFIG\" ] || {\n\t[ -f \"$ZAPRET_CONFIG_DEFAULT\" ] && {\n\t\tZAPRET_CONFIG_DIR=\"$(dirname \"$ZAPRET_CONFIG\")\"\n\t\t[ -d \"$ZAPRET_CONFIG_DIR\" ] || mkdir -p \"$ZAPRET_CONFIG_DIR\"\n\t\tcp \"$ZAPRET_CONFIG_DEFAULT\" \"$ZAPRET_CONFIG\"\n\t}\n}\n[ -f \"$ZAPRET_CONFIG\" ] && . \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/dialog.sh\"\n. \"$ZAPRET_BASE/common/elevate.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/virt.sh\"\n\nDOMAINS_DEFAULT=${DOMAINS_DEFAULT:-rutracker.org}\nQNUM=${QNUM:-59780}\nSOCKS_PORT=${SOCKS_PORT:-1993}\nTPWS_UID=${TPWS_UID:-1}\nTPWS_GID=${TPWS_GID:-3003}\nNFQWS=${NFQWS:-${ZAPRET_BASE}/nfq/nfqws}\nDVTWS=${DVTWS:-${ZAPRET_BASE}/nfq/dvtws}\nWINWS=${WINWS:-${ZAPRET_BASE}/nfq/winws}\nTPWS=${TPWS:-${ZAPRET_BASE}/tpws/tpws}\nMDIG=${MDIG:-${ZAPRET_BASE}/mdig/mdig}\nDESYNC_MARK=0x10000000\nIPFW_RULE_NUM=${IPFW_RULE_NUM:-1}\nIPFW_DIVERT_PORT=${IPFW_DIVERT_PORT:-59780}\nCURL_MAX_TIME=${CURL_MAX_TIME:-2}\nCURL_MAX_TIME_QUIC=${CURL_MAX_TIME_QUIC:-$CURL_MAX_TIME}\nCURL_MAX_TIME_DOH=${CURL_MAX_TIME_DOH:-2}\nMIN_TTL=${MIN_TTL:-1}\nMAX_TTL=${MAX_TTL:-12}\nMIN_AUTOTTL_DELTA=${MIN_AUTOTTL_DELTA:-1}\nMAX_AUTOTTL_DELTA=${MAX_AUTOTTL_DELTA:-5}\nUSER_AGENT=${USER_AGENT:-Mozilla}\nHTTP_PORT=${HTTP_PORT:-80}\nHTTPS_PORT=${HTTPS_PORT:-443}\nQUIC_PORT=${QUIC_PORT:-443}\nUNBLOCKED_DOM=${UNBLOCKED_DOM:-iana.org}\nPARALLEL_OUT=/tmp/zapret_parallel\nSIM_SUCCESS_RATE=${SIM_SUCCESS_RATE:-10}\n\nHDRTEMP=/tmp/zapret-hdr\n\nNFT_TABLE=blockcheck\n\nDNSCHECK_DNS=${DNSCHECK_DNS:-8.8.8.8 1.1.1.1 77.88.8.1}\nDNSCHECK_DOM=${DNSCHECK_DOM:-pornhub.com ej.ru rutracker.org www.torproject.org bbc.com}\nDOH_SERVERS=${DOH_SERVERS:-\"https://cloudflare-dns.com/dns-query https://dns.google/dns-query https://dns.quad9.net/dns-query https://dns.adguard.com/dns-query https://common.dot.dns.yandex.net/dns-query\"}\nDNSCHECK_DIG1=/tmp/dig1.txt\nDNSCHECK_DIG2=/tmp/dig2.txt\nDNSCHECK_DIGS=/tmp/digs.txt\n\nIPSET_FILE=/tmp/blockcheck_ipset.txt\n\nunset PF_STATUS\nPF_RULES_SAVE=/tmp/pf-zapret-save.conf\n\nunset ALL_PROXY\n\nkillwait()\n{\n\t# $1 - signal (-9, -2, ...)\n\t# $2 - pid\n\tkill $1 $2\n\t# suppress job kill message\n\twait $2 2>/dev/null\n}\n\nexitp()\n{\n\tlocal A\n\n\t[ \"$BATCH\" = 1 ] || {\n\t\techo\n\t\techo press enter to continue\n\t\tread A\n\t}\n\texit $1\n}\n\npf_is_avail()\n{\n\t[ -c /dev/pf ]\n}\npf_status()\n{\n\tpfctl -qsi  | sed -nre \"s/^Status: ([^ ]+).*$/\\1/p\"\n}\npf_is_enabled()\n{\n\t[ \"$(pf_status)\" = Enabled ]\n}\npf_save()\n{\n\tPF_STATUS=0\n\tpf_is_enabled && PF_STATUS=1\n\t[ \"$UNAME\" = \"OpenBSD\" ] && pfctl -sr >\"$PF_RULES_SAVE\"\n}\npf_restore()\n{\n\t[ -n \"$PF_STATUS\" ] || return\n\tcase \"$UNAME\" in\n\t\tOpenBSD)\n\t\t\tif [ -f \"$PF_RULES_SAVE\" ]; then\n\t\t\t\tpfctl -qf \"$PF_RULES_SAVE\"\n\t\t\telse\n\t\t\t\techo | pfctl -qf -\n\t\t\tfi\n\t\t\t;;\n\t\tDarwin)\n\t\t\t# it's not possible to save all rules in the right order. hard to reorder. if not ordered pf will refuse to load conf.\n\t\t\tpfctl -qf /etc/pf.conf\n\t\t\t;;\n\tesac\n\tif [ \"$PF_STATUS\" = 1 ]; then\n\t\tpfctl -qe\n\telse\n\t\tpfctl -qd\n\tfi\n}\npf_clean()\n{\n\trm -f \"$PF_RULES_SAVE\"\n}\nopf_dvtws_anchor()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\t# $3 - ip list\n\tlocal iplist family=inet\n\t[ \"$IPV\" = 6 ] && family=inet6\n\tmake_comma_list iplist \"$3\"\n\techo \"set reassemble no\"\n\t[ \"$1\" = tcp ] && echo \"pass in quick $family proto $1 from {$iplist} port $2 flags SA/SA divert-packet port $IPFW_DIVERT_PORT no state\"\n\techo \"pass in  quick $family proto $1 from {$iplist} port $2 no state\"\n\techo \"pass out quick $family proto $1 to   {$iplist} port $2 divert-packet port $IPFW_DIVERT_PORT no state\"\n\techo \"pass\"\n}\nopf_prepare_dvtws()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\t# $3 - ip list\n\topf_dvtws_anchor $1 $2 \"$3\" | pfctl -qf -\n\tpfctl -qe\n}\n\ncleanup()\n{\n\tcase \"$UNAME\" in\n\t\tOpenBSD)\n\t\t    pf_clean\n\t\t    ;;\n\tesac\n}\n\nIPT()\n{\n\t$IPTABLES -C \"$@\" >/dev/null 2>/dev/null || $IPTABLES -I \"$@\"\n}\nIPT_DEL()\n{\n\t$IPTABLES -C \"$@\" >/dev/null 2>/dev/null && $IPTABLES -D \"$@\"\n}\nIPT_ADD_DEL()\n{\n\ton_off_function IPT IPT_DEL \"$@\"\n}\nIPFW_ADD()\n{\n\tipfw -qf add $IPFW_RULE_NUM \"$@\"\n}\nIPFW_DEL()\n{\n\tipfw -qf delete $IPFW_RULE_NUM 2>/dev/null\n}\nipt6_has_raw()\n{\n\tip6tables -nL -t raw >/dev/null 2>/dev/null\n}\nipt6_has_frag()\n{\n\tip6tables -A OUTPUT -m frag 2>/dev/null || return 1\n\tip6tables -D OUTPUT -m frag 2>/dev/null\n}\nipt_has_nfq()\n{\n\t# cannot just check /proc/net/ip_tables_targets because of iptables-nft or modules not loaded yet\n\tiptables -A OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null || return 1\n\tiptables -D OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null\n\treturn 0\n}\nnft_has_nfq()\n{\n\tlocal res=1\n\tnft delete table ${NFT_TABLE}_test 2>/dev/null\n\tnft add table ${NFT_TABLE}_test 2>/dev/null && {\n\t\tnft add chain ${NFT_TABLE}_test test\n\t\tnft add rule ${NFT_TABLE}_test test queue num $QNUM bypass 2>/dev/null && res=0\n\t\tnft delete table ${NFT_TABLE}_test\n\t}\n\treturn $res\n}\n\ndoh_resolve()\n{\n\t# $1 - ip version 4/6\n\t# $2 - hostname\n\t# $3 - doh server URL. use $DOH_SERVER if empty\n\t\"$MDIG\" --family=$1 --dns-make-query=$2 | \"$CURL\" --max-time $CURL_MAX_TIME_DOH -s --data-binary @- -H \"Content-Type: application/dns-message\" \"${3:-$DOH_SERVER}\" | \"$MDIG\" --dns-parse-query\n}\ndoh_find_working()\n{\n\tlocal doh\n\n\t[ -n \"$DOH_SERVER\" ] && return 0\n\techo \"* searching working DoH server\"\n\tDOH_SERVER=\n\tfor doh in $DOH_SERVERS; do\n\t\techo -n \"$doh : \"\n\t\tif doh_resolve 4 iana.org $doh >/dev/null 2>/dev/null; then\n\t\t\techo OK\n\t\t\tDOH_SERVER=\"$doh\"\n\t\t\treturn 0\n\t\telse\n\t\t\techo FAIL\n\t\tfi\n\tdone\n\techo all DoH servers failed\n\treturn 1\n}\n\nmdig_vars()\n{\n\t# $1 - ip version 4/6\n\t# $2 - hostname\n\n\thostvar=$(echo $2 | sed -e 's/[\\./?&#@%*$^:~=!()+-]/_/g')\n\tcachevar=DNSCACHE_${hostvar}_$1\n\tcountvar=${cachevar}_COUNT\n\teval count=\\$${countvar}\n}\nmdig_cache()\n{\n\t# $1 - ip version 4/6\n\t# $2 - hostname\n\tlocal hostvar cachevar countvar count ip ips\n\tmdig_vars \"$@\"\n\t[ -n \"$count\" ] || {\n\t\t# windows version of mdig outputs 0D0A line ending. remove 0D.\n\t\tif [ \"$SECURE_DNS\" = 1 ]; then\n\t\t\tips=\"$(echo $2 | doh_resolve $1 $2 | tr -d '\\r' | xargs)\"\n\t\telse\n\t\t\tips=\"$(echo $2 | \"$MDIG\" --family=$1 | tr -d '\\r' | xargs)\"\n\t\tfi\n\t\t[ -n \"$ips\" ] || return 1\n\t\tcount=0\n\t\tfor ip in $ips; do\n\t\t\teval ${cachevar}_$count=$ip\n\t\t\tcount=$(($count+1))\n\t\tdone\n\t\teval $countvar=$count\n\t}\n\treturn 0\n}\nmdig_resolve()\n{\n\t# $1 - ip version 4/6\n\t# $2 - var to receive result\n\t# $3 - hostname, possibly with uri : rutracker.org/xxx/xxxx\n\tlocal hostvar cachevar countvar count n sdom\n\n\tsplit_by_separator \"$3\" / sdom\n\tmdig_vars \"$1\" \"$sdom\"\n\tif [ -n \"$count\" ]; then\n\t\tn=$(random 0 $(($count-1)))\n\t\teval $2=\\$${cachevar}_$n\n\t\treturn 0\n\telse\n\t\tmdig_cache \"$1\" \"$sdom\" && mdig_resolve \"$1\" \"$2\" \"$sdom\"\n\tfi\n}\nmdig_resolve_all()\n{\n\t# $1 - ip version 4/6\n\t# $2 - var to receive result\n\t# $3 - hostname\n\n\tlocal hostvar cachevar countvar count ip__ ips__ n sdom\n\n\tsplit_by_separator \"$3\" / sdom\n\tmdig_vars \"$1\" \"$sdom\"\n\tif [ -n \"$count\" ]; then\n\t\tn=0\n\t\twhile [ \"$n\" -le $count ]; do\n\t\t\teval ip__=\\$${cachevar}_$n\n\t\t\tif [ -n \"$ips__\" ]; then\n\t\t\t\tips__=\"$ips__ $ip__\"\n\t\t\telse\n\t\t\t\tips__=\"$ip__\"\n\t\t\tfi\n\t\t\tn=$(($n + 1))\n\t\tdone\n\t\teval $2=\"\\$ips__\"\n\t\treturn 0\n\telse\n\t\tmdig_cache \"$1\" \"$sdom\" && mdig_resolve_all \"$1\" \"$2\" \"$sdom\"\n\tfi\n}\n\nnetcat_setup()\n{\n\t[ -n \"$NCAT\" ] || {\n\t\tif exists ncat; then\n\t\t\tNCAT=ncat\n\t\telif exists nc; then\n\t\t\t# busybox netcat does not support any required options\n\t\t\tis_linked_to_busybox nc && return 1\n\t\t\tNCAT=nc\n\t\telse\n\t\t\treturn 1\n\t\tfi\n\t}\n\treturn 0\n\t\n}\nnetcat_test()\n{\n\t# $1 - ip\n\t# $2 - port\n\tlocal cmd\n\tnetcat_setup && {\n\t\tcmd=\"$NCAT -z -w 2 $1 $2\"\n\t\techo $cmd\n\t\t$cmd 2>&1\n\t}\n}\n\ntpws_can_fix_seg()\n{\n\t# fix-seg requires kernel 4.6+\n\t\"$TPWS\" --port 1 --dry-run --fix-seg >/dev/null 2>/dev/null\n}\n\ncheck_system()\n{\n\techo \\* checking system\n\n\tUNAME=$(uname)\n\tSUBSYS=\n\tFIX_SEG=\n\tlocal s\n\n\t# can be passed FWTYPE=iptables to override default nftables preference\n\tcase \"$UNAME\" in\n\t\tLinux)\n\t\t\tPKTWS=\"$NFQWS\"\n\t\t\tPKTWSD=nfqws\n\t\t\tif [ -x \"$TPWS\" ] ; then\n\t\t\t\tif tpws_can_fix_seg ; then\n\t\t\t\t\techo tpws supports --fix-seg on this system\n\t\t\t\t\tFIX_SEG='--fix-seg'\n\t\t\t\telse\n\t\t\t\t\techo tpws does not support --fix-seg on this system\n\t\t\t\tfi\n\t\t\tfi\n\t\t\tlinux_fwtype\n\t\t\t[ \"$FWTYPE\" = iptables -o \"$FWTYPE\" = nftables ] || {\n\t\t\t\techo firewall type $FWTYPE not supported in $UNAME\n\t\t\t\texitp 5\n\t\t\t}\n\t\t\t;;\n\t\tFreeBSD)\n\t\t\tPKTWS=\"$DVTWS\"\n\t\t\tPKTWSD=dvtws\n\t\t\tFWTYPE=ipfw\n\t\t\t[ -f /etc/platform ] && read SUBSYS </etc/platform\n\t\t\t;;\n\t\tOpenBSD)\n\t\t\tPKTWS=\"$DVTWS\"\n\t\t\tPKTWSD=dvtws\n\t\t\tFWTYPE=opf\n\t\t\t;;\n\t\tDarwin)\n\t\t\tPKTWS=\"$DVTWS\"\n\t\t\tPKTWSD=dvtws\n\t\t\tFWTYPE=mpf\n\t\t\t;;\n\t\tCYGWIN*)\n\t\t\tUNAME=CYGWIN\n\t\t\tPKTWS=\"$WINWS\"\n\t\t\tPKTWSD=winws\n\t\t\tFWTYPE=windivert\n\t\t\t# ts fooling requires timestamps. they are disabled by default in windows.\n\t\t\techo enabling tcp timestamps\n\t\t\tnetsh interface tcp set global timestamps=enabled >/dev/null\n\t\t\t;;\n\t\t*)\n\t\t\techo $UNAME not supported\n\t\t\texitp 5\n\tesac\n\techo $UNAME${SUBSYS:+/$SUBSYS} detected\n\techo -n 'kernel: '\n\tif [ -f \"/proc/version\" ]; then\n\t\tcat /proc/version\n\telse\n\t\tuname -a\n\tfi\n\t[ -f /etc/os-release ] && {\n\t\t. /etc/os-release\n\t\t[ -n \"$PRETTY_NAME\" ] && echo \"distro: $PRETTY_NAME\"\n\t\t[ -n \"$OPENWRT_RELEASE\" ] && echo \"openwrt release: $OPENWRT_RELEASE\"\n\t\t[ -n \"$OPENWRT_BOARD\" ] && echo \"openwrt board: $OPENWRT_BOARD\"\n\t\t[ -n \"$OPENWRT_ARCH\" ] && echo \"openwrt arch: $OPENWRT_ARCH\"\n\t}\n\techo firewall type is $FWTYPE\n\techo CURL=$CURL\n\t\"$CURL\" --version\n}\n\nzp_already_running()\n{\n\tcase \"$UNAME\" in\n\t\tCYGWIN)\n\t\t\twin_process_exists $PKTWSD || win_process_exists goodbyedpi\n\t\t\t;;\n\t\t*)\n\t\t\tprocess_exists $PKTWSD || process_exists tpws\n\tesac\n}\ncheck_already()\n{\n\techo \\* checking already running DPI bypass processes\n\tif zp_already_running; then\n\t\techo \"!!! WARNING. some dpi bypass processes already running !!!\"\n\t\techo \"!!! WARNING. blockcheck requires all DPI bypass methods disabled !!!\"\n\t\techo \"!!! WARNING. pls stop all dpi bypass instances that may interfere with blockcheck !!!\"\n\tfi\n}\n\nfreebsd_module_loaded()\n{\n\t# $1 - module name\n\tkldstat -qm \"${1}\"\n}\nfreebsd_modules_loaded()\n{\n\t# $1,$2,$3, ... - module names\n\twhile [ -n \"$1\" ]; do\n\t\tfreebsd_module_loaded $1 || return 1\n\t\tshift\n\tdone\n\treturn 0\n}\n\ncheck_prerequisites()\n{\n\techo \\* checking prerequisites\n\t\n\t[ \"$SKIP_PKTWS\" = 1 -o \"$UNAME\" = Darwin -o -x \"$PKTWS\" ] && [ \"$SKIP_TPWS\" = 1 -o \"$UNAME\" = CYGWIN -o -x \"$TPWS\" ] && [ -x \"$MDIG\" ] || {\n\t\tlocal target\n\t\tcase $UNAME in\n\t\t\tDarwin)\n\t\t\t\ttarget=\"mac\"\n\t\t\t\t;;\n\t\t\tOpenBSD)\n\t\t\t\ttarget=\"bsd\"\n\t\t\t\t;;\n\t\tesac\n\t\techo $PKTWS or $TPWS or $MDIG is not available. run \\\"$ZAPRET_BASE/install_bin.sh\\\" or \\`make -C \\\"$ZAPRET_BASE\\\" $target\\`\n\t\texitp 6\n\t}\n\n\tlocal prog progs=\"$CURL\"\n\t[ \"$SKIP_PKTWS\" = 1 ] || {\n\t\tcase \"$UNAME\" in\n\t\t\tLinux)\n\t\t\t\tcase \"$FWTYPE\" in\n\t\t\t\t\tiptables)\n\t\t\t\t\t\tipt_has_nfq || {\n\t\t\t\t\t\t\techo NFQUEUE iptables or ip6tables target is missing. pls install modules.\n\t\t\t\t\t\t\texitp 6\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprogs=\"$progs iptables ip6tables\"\n\t\t\t\t\t\t;;\n\t\t\t\t\tnftables)\n\t\t\t\t\t\tnft_has_nfq || {\n\t\t\t\t\t\t\techo nftables queue support is not available. pls install modules.\n\t\t\t\t\t\t\texitp 6\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprogs=\"$progs nft\"\n\t\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t\t;;\n\t\t\tFreeBSD)\n\t\t\t\tfreebsd_modules_loaded ipfw ipdivert || {\n\t\t\t\t\techo ipfw or ipdivert kernel module not loaded\n\t\t\t\t\t\texitp 6\n\t\t\t\t}\n\t\t\t\t[ \"$(sysctl -qn net.inet.ip.fw.enable)\" = 0 -o \"$(sysctl -qn net.inet6.ip6.fw.enable)\" = 0 ] && {\n\t\t\t\t\techo ipfw is disabled. use : ipfw enable firewall\n\t\t\t\t\texitp 6\n\t\t\t\t}\n\t\t\t\tpf_is_avail && {\n\t\t\t\t\tpf_save\n\t\t\t\t\t[ \"$SUBSYS\" = \"pfSense\" ] && {\n\t\t\t\t\t\t# pfsense's ipfw may not work without these workarounds\n\t\t\t\t\t\tsysctl net.inet.ip.pfil.outbound=ipfw,pf 2>/dev/null\n\t\t\t\t\t\tsysctl net.inet.ip.pfil.inbound=ipfw,pf 2>/dev/null\n\t\t\t\t\t\tsysctl net.inet6.ip6.pfil.outbound=ipfw,pf 2>/dev/null\n\t\t\t\t\t\tsysctl net.inet6.ip6.pfil.inbound=ipfw,pf 2>/dev/null\n\t\t\t\t\t\tpfctl -qd\n\t\t\t\t\t\tpfctl -qe\n\t\t\t\t\t\tpf_restore\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tprogs=\"$progs ipfw\"\n\t\t\t\t;;\n\t\t\tOpenBSD|Darwin)\n\t\t\t\tpf_is_avail || {\n\t\t\t\t\techo pf is not available\n\t\t\t\t\texitp 6\n\t\t\t\t}\n\t\t\t\tpf_save\n\t\t\t\tprogs=\"$progs pfctl\"\n\t\t\t\t;;\n\t\tesac\n\t}\n\n\tcase \"$UNAME\" in\n\t\tCYGWIN)\n\t\t\tSKIP_TPWS=1\n\t\t\t;;\n\tesac\n\n\tfor prog in $progs; do\n\t\texists $prog || {\n\t\t\techo $prog does not exist. please install\n\t\t\texitp 6\n\t\t}\n\tdone\n\n\tif exists nslookup; then\n\t\tLOOKUP=nslookup\n\telif exists host; then\n\t\tLOOKUP=host\n\telse\n\t\techo nslookup or host does not exist. please install\n\t\texitp 6\n\tfi\n}\n\n\ncurl_translate_code()\n{\n\t# $1 - code\n\tprintf $1\n\tcase $1 in\n\t\t0) printf \": ok\"\n\t\t;;\n\t\t1) printf \": unsupported protocol\"\n\t\t;;\n\t\t2) printf \": early initialization code failed\"\n\t\t;;\n\t\t3) printf \": the URL was not properly formatted\"\n\t\t;;\n\t\t4) printf \": feature not supported by libcurl\"\n\t\t;;\n\t\t5) printf \": could not resolve proxy\"\n\t\t;;\n\t\t6) printf \": could not resolve host\"\n\t\t;;\n\t\t7) printf \": could not connect\"\n\t\t;;\n\t\t8) printf \": invalid server reply\"\n\t\t;;\n\t\t9) printf \": remote access denied\"\n\t\t;;\n\t\t27) printf \": out of memory\"\n\t\t;;\n\t\t28) printf \": operation timed out\"\n\t\t;;\n\t\t35) printf \": SSL connect error\"\n\t\t;;\n\tesac\n}\ncurl_supports_tls13()\n{\n\tlocal r\n\t\"$CURL\" --tlsv1.3 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null\n\t# return code 2 = init failed. likely bad command line options\n\t[ $? = 2 ] && return 1\n\t# curl can have tlsv1.3 key present but ssl library without TLS 1.3 support\n\t# this is online test because there's no other way to trigger library incompatibility case\n\t\"$CURL\" --tlsv1.3 --max-time 1 -Is -o /dev/null https://iana.org 2>/dev/null\n\tr=$?\n\t[ $r != 4 -a $r != 35 ]\n}\n\ncurl_supports_tlsmax()\n{\n\t# supported only in OpenSSL and LibreSSL\n\t\"$CURL\" --version | grep -Fq -e OpenSSL -e LibreSSL -e BoringSSL -e GnuTLS -e quictls || return 1\n\t# supported since curl 7.54\n\t\"$CURL\" --tls-max 1.2 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null\n\t# return code 2 = init failed. likely bad command line options\n\t[ $? != 2 ]\n}\n\ncurl_supports_connect_to()\n{\n\t\"$CURL\" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null\n\t[ \"$?\" != 2 ]\n}\n\ncurl_supports_http3()\n{\n\t# if it has http3 : curl: (3) HTTP/3 requested for non-HTTPS URL\n\t# otherwise : curl: (2) option --http3-only: is unknown\n\t\"$CURL\" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 --http3-only http://127.0.0.1:65535 2>/dev/null\n\t[ \"$?\" != 2 ]\n}\n\nhdrfile_http_code()\n{\n\t# $1 - hdr file\n\tsed -nre '1,1 s/^HTTP\\/1\\.[0,1] ([0-9]+) .*$/\\1/p' \"$1\"\n}\nhdrfile_location()\n{\n\t# $1 - hdr file\n\n\t# some DPIs return CRLF line ending\n\ttr -d '\\015' <\"$1\" | sed -nre 's/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ \t]*([^ \t]*)[ \t]*$/\\1/p'\n}\n\ncurl_with_subst_ip()\n{\n\t# $1 - domain\n\t# $2 - port\n\t# $3 - ip\n\t# $4+ - curl params\n\tlocal ip=\"$3\"\n\tcase \"$ip\" in\n\t\t*:*) ip=\"[$ip]\" ;;\n\tesac\n\tlocal connect_to=\"--connect-to $1::$ip${2:+:$2}\" arg\n\tshift ; shift ; shift;\n\t[ \"$CURL_VERBOSE\" = 1 ] && arg=\"-v\"\n\t[ \"$CURL_CMD\" = 1 ] && echo $CURL ${arg:+$arg }$connect_to \"$@\"\n\tALL_PROXY=\"$ALL_PROXY\" \"$CURL\" ${arg:+$arg }$connect_to \"$@\"\n}\ncurl_with_dig()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\t# $3 - port\n\t# $4+ - curl params\n\tlocal dom=\"$2\" port=$3\n\tlocal sdom suri ip\n\n\tsplit_by_separator \"$dom\" / sdom suri\n\tmdig_resolve $1 ip $sdom\n\tshift ; shift ; shift\n\tif [ -n \"$ip\" ]; then\n\t\tcurl_with_subst_ip \"$sdom\" \"$port\" \"$ip\" \"$@\"\n\telse\n\t\treturn 6\n\tfi\n}\ncurl_probe()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\t# $3 - port\n\t# $4 - subst ip\n\t# $5+ - curl params\n\tlocal ipv=$1 dom=\"$2\" port=$3 subst=$4\n\tshift; shift; shift; shift\n\tif [ -n \"$subst\" ]; then\n\t\tcurl_with_subst_ip \"$dom\" $port $subst \"$@\"\n\telse\n\t\tcurl_with_dig $ipv \"$dom\" $port \"$@\"\n\tfi\n}\ncurl_test_http()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\t# $3 - subst ip\n\t# $4 - \"detail\" - detail info\n\n\tlocal code loc hdrt=\"${HDRTEMP}_${!:-$$}.txt\" dom=\"$(tolower \"$2\")\"\n\tcurl_probe $1 \"$2\" $HTTP_PORT \"$3\" -SsD \"$hdrt\" -A \"$USER_AGENT\" --max-time $CURL_MAX_TIME $CURL_OPT \"http://$2\" -o /dev/null 2>&1 || {\n\t\tcode=$?\n\t\trm -f \"$hdrt\"\n\t\treturn $code\n\t}\n\tif [ \"$4\" = \"detail\" ] ; then\n\t\thead -n 1 \"$hdrt\"\n\t\tgrep \"^[lL]ocation:\" \"$hdrt\"\n\telse\n\t\tcode=$(hdrfile_http_code \"$hdrt\")\n\t\t[ \"$code\" = 301 -o \"$code\" = 302 -o \"$code\" = 307 -o \"$code\" = 308 ] && {\n\t\t\tloc=$(hdrfile_location \"$hdrt\")\n\t\t\tsplit_by_separator \"$dom\" / dom\n\t\t\ttolower \"$loc\" | grep -qE \"^https?://.*$dom(/|$)\" ||\n\t\t\ttolower \"$loc\" | grep -vqE '^https?://' || {\n\t\t\t\techo suspicious redirection $code to : $loc\n\t\t\t\trm -f \"$hdrt\"\n\t\t\t\treturn 254\n\t\t\t}\n\t\t}\n\tfi\n\trm -f \"$hdrt\"\n\t[ \"$code\" = 400 ] && {\n\t\t# this can often happen if the server receives fake packets it should not receive\n\t\techo http code $code. likely the server receives fakes.\n\t\treturn 254\n\t}\n\treturn 0\n}\ncurl_test_https_tls12()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\t# $3 - subst ip\n\n\t# do not use tls 1.3 to make sure server certificate is not encrypted\n\tcurl_probe $1 $2 $HTTPS_PORT \"$3\" $HTTPS_HEAD -Ss -A \"$USER_AGENT\" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.2 $TLSMAX12 \"https://$2\" -o /dev/null 2>&1\n}\ncurl_test_https_tls13()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\t# $3 - subst ip\n\n\t# force TLS1.3 mode\n\tcurl_probe $1 $2 $HTTPS_PORT \"$3\" $HTTPS_HEAD -Ss -A \"$USER_AGENT\" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.3 $TLSMAX13 \"https://$2\" -o /dev/null 2>&1\n}\n\ncurl_test_http3()\n{\n\t# $1 - ip version : 4/6\n\t# $2 - domain name\n\n\t# force QUIC only mode without tcp\n\tcurl_with_dig $1 $2 $QUIC_PORT $HTTPS_HEAD -Ss -A \"$USER_AGENT\" --max-time $CURL_MAX_TIME_QUIC --http3-only $CURL_OPT \"https://$2\" -o /dev/null 2>&1\n}\n\nipt_aux_scheme()\n{\n\t# $1 - 1 - add , 0 - del\n\t# $2 - tcp/udp\n\t# $3 - port\n\n\t# to avoid possible INVALID state drop\n\t[ \"$2\" = tcp ] && IPT_ADD_DEL $1 INPUT -p $2 --sport $3 ! --syn -j ACCEPT\n\n\tlocal icmp_filter=\"-p icmp -m icmp --icmp-type\"\n\t[ \"$IPV\" = 6 ] && icmp_filter=\"-p icmpv6 -m icmp6 --icmpv6-type\"\n\tIPT_ADD_DEL $1 INPUT $icmp_filter time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP \n\n\t# for strategies with incoming packets involved (autottl)\n\tIPT_ADD_DEL $1 OUTPUT -p $2 --dport $3 -m conntrack --ctstate INVALID -j ACCEPT\n\tif [ \"$IPV\" = 6 -a -n \"$IP6_DEFRAG_DISABLE\" ]; then\n\t\t# the only way to reliable disable ipv6 defrag. works only in 4.16+ kernels\n\t\tIPT_ADD_DEL $1 OUTPUT -t raw -p $2 -m frag -j CT --notrack\n\telif [ \"$IPV\" = 4 ]; then\n\t\t# enable fragments\n\t\tIPT_ADD_DEL $1 OUTPUT -f -j ACCEPT\n\tfi\n\t# enable everything generated by nfqws (works only in OUTPUT, not in FORWARD)\n\t# raw table may not be present\n\tIPT_ADD_DEL $1 OUTPUT -t raw -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CT --notrack\n}\nipt_scheme()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\t# $3 - ip list\n\n\tlocal ip\n\n\t$IPTABLES -t mangle -N blockcheck_output 2>/dev/null\n\t$IPTABLES -t mangle -F blockcheck_output\n\tIPT OUTPUT -t mangle -j blockcheck_output\n\n\t# prevent loop\n\t$IPTABLES -t mangle -A blockcheck_output -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j RETURN\n\t$IPTABLES -t mangle -A blockcheck_output ! -p $1 -j RETURN\n\t$IPTABLES -t mangle -A blockcheck_output -p $1 ! --dport $2 -j RETURN\n\n\tfor ip in $3; do\n\t\t$IPTABLES -t mangle -A blockcheck_output -d $ip -j CONNMARK --or-mark $DESYNC_MARK\n\t\t$IPTABLES -t mangle -A blockcheck_output -d $ip -j NFQUEUE --queue-num $QNUM\n\tdone\n\n\tipt_aux_scheme 1 $1 $2\n}\nnft_scheme()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\t# $3 - ip list\n\n\tlocal iplist ipver=$IPV\n\t[ \"$IPV\" = 6 ] || ipver=\n\tmake_comma_list iplist $3\n\n\tnft add table inet $NFT_TABLE\n\tnft \"add chain inet $NFT_TABLE postnat { type filter hook postrouting priority 102; }\"\n\tnft \"add rule inet $NFT_TABLE postnat meta nfproto ipv${IPV} $1 dport $2 mark and $DESYNC_MARK == 0 ip${ipver} daddr {$iplist} ct mark set ct mark or $DESYNC_MARK queue num $QNUM\"\n\t# for strategies with incoming packets involved (autottl)\n\tnft \"add chain inet $NFT_TABLE prenat { type filter hook prerouting priority -102; }\"\n\t# enable everything generated by nfqws (works only in OUTPUT, not in FORWARD)\n\tnft \"add chain inet $NFT_TABLE predefrag { type filter hook output priority -402; }\"\n\tnft \"add rule inet $NFT_TABLE predefrag meta nfproto ipv${IPV} mark and $DESYNC_MARK !=0 notrack\"\n\t[ \"$IPV\" = 4 ] && {\n\t\tnft \"add rule inet $NFT_TABLE prenat icmp type time-exceeded ct mark and $DESYNC_MARK != 0 drop\"\n\t\tnft \"add rule inet $NFT_TABLE prenat icmp type time-exceeded ct state invalid drop\"\n\t}\n\t[ \"$IPV\" = 6 ] && {\n\t\tnft \"add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct mark and $DESYNC_MARK != 0 drop\"\n\t\tnft \"add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct state invalid drop\"\n\t}\n}\n\npktws_ipt_prepare()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\t# $3 - ip list\n\n\tlocal ip\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tipt_scheme $1 $2 \"$3\"\n\t\t\t;;\n\t\tnftables)\n\t\t\tnft_scheme $1 $2 \"$3\"\n\t\t\t;;\n\t\tipfw)\n\t\t\t# disable PF to avoid interferences\n\t\t\tpf_is_avail && pfctl -qd\n\t\t\tfor ip in $3; do\n\t\t\t\tIPFW_ADD divert $IPFW_DIVERT_PORT $1 from me to $ip $2 proto ip${IPV} out not diverted\n\t\t\tdone\n\t\t\t;;\n\t\topf)\n\t\t\topf_prepare_dvtws $1 $2 \"$3\"\n\t\t\t;;\n\t\twindivert)\n\t\t\tWF=\"--wf-l3=ipv${IPV} --wf-${1}=$2\"\n\t\t\trm -f \"$IPSET_FILE\"\n\t\t\tfor ip in $3; do\n\t\t\t\techo $ip >>\"$IPSET_FILE\"\n\t\t\tdone\n\t\t\t;;\n\n\tesac\n}\npktws_ipt_unprepare()\n{\n\t# $1 - tcp/udp\n\t# $2 - port\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tipt_aux_scheme 0 $1 $2\n\t\t\tIPT_DEL OUTPUT -t mangle -j blockcheck_output\n\t\t\t$IPTABLES -t mangle -F blockcheck_output 2>/dev/null\n\t\t\t$IPTABLES -t mangle -X blockcheck_output 2>/dev/null\n\t\t\t;;\n\t\tnftables)\n\t\t\tnft delete table inet $NFT_TABLE 2>/dev/null\n\t\t\t;;\n\t\tipfw)\n\t\t\tIPFW_DEL\n\t\t\tpf_is_avail && pf_restore\n\t\t\t;;\n\t\topf)\n\t\t\tpf_restore\n\t\t\t;;\n\t\twindivert)\n\t\t\tunset WF\n\t\t\trm -f \"$IPSET_FILE\"\n\t\t\t;;\n\tesac\n}\n\npktws_ipt_prepare_tcp()\n{\n\t# $1 - port\n\t# $2 - ip list\n\n\tlocal ip iplist ipver\n\n\tpktws_ipt_prepare tcp $1 \"$2\"\n\n\t# for autottl mode\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\t$IPTABLES -N blockcheck_input -t mangle 2>/dev/null\n\t\t\t$IPTABLES -F blockcheck_input -t mangle 2>/dev/null\n\t\t\tIPT INPUT -t mangle -j blockcheck_input\n\t\t\t$IPTABLES -t mangle -A blockcheck_input ! -p tcp -j RETURN\n\t\t\t$IPTABLES -t mangle -A blockcheck_input -p tcp ! --sport $1 -j RETURN\n\t\t\t$IPTABLES -t mangle -A blockcheck_input -p tcp ! --tcp-flags SYN,ACK SYN,ACK -j RETURN\n\t\t\tfor ip in $2; do\n\t\t\t\t$IPTABLES -A blockcheck_input -t mangle -s $ip -j NFQUEUE --queue-num $QNUM\n\t\t\tdone\n\t\t\t;;\n\t\tnftables)\n\t\t\tipver=$IPV\n\t\t\t[ \"$IPV\" = 6 ] || ipver=\n\t\t\tmake_comma_list iplist $2\n\t\t\tnft \"add rule inet $NFT_TABLE prenat meta nfproto ipv${IPV} tcp sport $1 tcp flags & (syn | ack) == (syn | ack) ip${ipver} saddr {$iplist} queue num $QNUM\"\n\t\t\t;;\n\t\tipfw)\n\t\t\tfor ip in $2; do\n\t\t\t\tIPFW_ADD divert $IPFW_DIVERT_PORT tcp from $ip $1 to me proto ip${IPV} tcpflags syn,ack in not diverted\n\t\t\tdone\n\t\t\t;;\n\tesac\n}\npktws_ipt_unprepare_tcp()\n{\n\t# $1 - port\n\t\n\tpktws_ipt_unprepare tcp $1\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tIPT_DEL INPUT -t mangle -j blockcheck_input\n\t\t\t$IPTABLES -t mangle -F blockcheck_input 2>/dev/null\n\t\t\t$IPTABLES -t mangle -X blockcheck_input 2>/dev/null\n\t\t\t;;\n\tesac\n}\npktws_ipt_prepare_udp()\n{\n\t# $1 - port\n\t# $2 - ip list\n\n\tpktws_ipt_prepare udp $1 \"$2\"\n}\npktws_ipt_unprepare_udp()\n{\n\t# $1 - port\n\t\n\tpktws_ipt_unprepare udp $1\n}\n\npktws_start()\n{\n\tcase \"$UNAME\" in\n\t\tLinux)\n\t\t\t\"$NFQWS\" --uid $TPWS_UID:$TPWS_GID --dpi-desync-fwmark=$DESYNC_MARK --qnum=$QNUM \"$@\" >/dev/null &\n\t\t\t;;\n\t\tFreeBSD|OpenBSD)\n\t\t\t\"$DVTWS\" --port=$IPFW_DIVERT_PORT \"$@\" >/dev/null &\n\t\t\t;;\n\t\tCYGWIN)\n\t\t\t\"$WINWS\" $WF --ipset=\"$IPSET_FILE\" \"$@\" >/dev/null &\n\t\t\t;;\n\tesac\n\tPID=$!\n\t# give some time to initialize\n\tminsleep\n}\ntpws_start()\n{\n\tlocal uid\n\t[ -n \"$HAVE_ROOT\" ] && uid=\"--uid $TPWS_UID:$TPWS_GID\"\n\t\"$TPWS\" $uid --socks --bind-addr=127.0.0.1 --port=$SOCKS_PORT \"$@\" >/dev/null &\n\tPID=$!\n\t# give some time to initialize\n\tminsleep\n}\nws_kill()\n{\n\t[ -z \"$PID\" ] || {\n\t\tkillwait -9 $PID 2>/dev/null\n\t\tPID=\n\t}\n}\n\ncheck_domain_port_block()\n{\n\t# $1 - domain\n\t# $2 - port\n\tlocal ip ips\n\techo\n\techo \\* port block tests ipv$IPV $1:$2\n\tif netcat_setup; then\n\t\tmdig_resolve_all $IPV ips $1\n\t\tif [ -n \"$ips\" ]; then\n\t\t\tfor ip in $ips; do\n\t\t\t\tif netcat_test $ip $2; then\n\t\t\t\t\techo $ip connects\n\t\t\t\telse\n\t\t\t\t\techo $ip does not connect. netcat code $?\n\t\t\t\tfi\n\t\t\tdone\n\t\telse\n\t\t\techo \"ipv${IPV} $1 does not resolve\"\n\t\tfi\n\telse\n\t\techo suitable netcat not found. busybox nc is not supported. pls install nmap ncat or openbsd netcat.\n\tfi\n}\n\ncurl_test()\n{\n\t# $1 - test function\n\t# $2 - domain\n\t# $3 - subst ip\n\t# $4 - param of test function\n\tlocal code=0 n=0 p pids\n\n\tif [ \"$PARALLEL\" = 1 ]; then\n\t\trm -f \"${PARALLEL_OUT}\"*\n\t\tfor n in $(seq -s ' ' 1 $REPEATS); do\n\t\t\t$1 \"$IPV\" $2 $3 \"$4\" >\"${PARALLEL_OUT}_$n\" &\n\t\t\tpids=\"${pids:+$pids }$!\"\n\t\tdone\n\t\tn=1\n\t\tfor p in $pids; do\n\t\t\t[ $REPEATS -gt 1 ] && printf \"[attempt $n] \"\n\t\t\tif wait $p; then\n\t\t\t\t[ $REPEATS -gt 1 ] && echo 'AVAILABLE'\n\t\t\telse\n\t\t\t\tcode=$?\n\t\t\t\tcat \"${PARALLEL_OUT}_$n\"\n\t\t\tfi\n\t\t\tn=$(($n+1))\n\t\tdone\n\t\trm -f \"${PARALLEL_OUT}\"*\n\telse\n\t\twhile [ $n -lt $REPEATS ]; do\n\t\t\tn=$(($n+1))\n\t\t\t[ $REPEATS -gt 1 ] && printf \"[attempt $n] \"\n\t\t\tif $1 \"$IPV\" $2 $3 \"$4\" ; then\n\t\t\t\t[ $REPEATS -gt 1 ] && echo 'AVAILABLE'\n\t\t\telse\n\t\t\t\tcode=$?\n\t\t\t\t[ \"$SCANLEVEL\" = quick ] && break\n\t\t\tfi\n\t\tdone\n\tfi\n\t[ \"$4\" = detail ] || {\n\t\tif [ $code = 254 ]; then\n\t\t\techo \"UNAVAILABLE\"\n\t\telif [ $code = 0 ]; then\n\t\t\techo '!!!!! AVAILABLE !!!!!'\n\t\telse\n\t\t\techo \"UNAVAILABLE code=$code\"\n\t\tfi\n\t}\n\treturn $code\n}\nws_curl_test()\n{\n\n\t# $1 - ws start function\n\t# $2 - test function\n\t# $3 - domain\n\t# $4,$5,$6, ... - ws params\n\tlocal code ws_start=$1 testf=$2 dom=\"$3\"\n\n\t[ \"$SIMULATE\" = 1 ] && {\n\t\tn=$(random 0 99)\n\t\tif [ \"$n\" -lt \"$SIM_SUCCESS_RATE\" ]; then\n\t\t\techo \"SUCCESS\"\n\t\t\treturn 0\n\t\telse\n\t\t\techo \"FAILED\"\n\t\t\treturn 7\n\t\tfi\n\t}\n\tshift\n\tshift\n\tshift\n\t$ws_start \"$@\"\n\tcurl_test $testf \"$dom\"\n\tcode=$?\n\tws_kill\n\treturn $code\n}\ntpws_curl_test()\n{\n\t# $1 - test function\n\t# $2 - domain\n\t# $3,$4,$5, ... - tpws params\n\techo - $1 ipv$IPV $2 : tpws $3 $4 $5 $6 $7 $8 $9${TPWS_EXTRA:+ $TPWS_EXTRA}${TPWS_EXTRA_1:+ \"$TPWS_EXTRA_1\"}${TPWS_EXTRA_2:+ \"$TPWS_EXTRA_2\"}${TPWS_EXTRA_3:+ \"$TPWS_EXTRA_3\"}${TPWS_EXTRA_4:+ \"$TPWS_EXTRA_4\"}${TPWS_EXTRA_5:+ \"$TPWS_EXTRA_5\"}${TPWS_EXTRA_6:+ \"$TPWS_EXTRA_6\"}${TPWS_EXTRA_7:+ \"$TPWS_EXTRA_7\"}${TPWS_EXTRA_8:+ \"$TPWS_EXTRA_8\"}${TPWS_EXTRA_9:+ \"$TPWS_EXTRA_9\"}\n\tlocal ALL_PROXY=\"socks5://127.0.0.1:$SOCKS_PORT\"\n\tws_curl_test tpws_start \"$@\"${TPWS_EXTRA:+ $TPWS_EXTRA}${TPWS_EXTRA_1:+ \"$TPWS_EXTRA_1\"}${TPWS_EXTRA_2:+ \"$TPWS_EXTRA_2\"}${TPWS_EXTRA_3:+ \"$TPWS_EXTRA_3\"}${TPWS_EXTRA_4:+ \"$TPWS_EXTRA_4\"}${TPWS_EXTRA_5:+ \"$TPWS_EXTRA_5\"}${TPWS_EXTRA_6:+ \"$TPWS_EXTRA_6\"}${TPWS_EXTRA_7:+ \"$TPWS_EXTRA_7\"}${TPWS_EXTRA_8:+ \"$TPWS_EXTRA_8\"}${TPWS_EXTRA_9:+ \"$TPWS_EXTRA_9\"}\n\tlocal testf=$1 dom=\"$2\" strategy code=$?\n\t[ \"$code\" = 0 ] && {\n\t\tshift; shift;\n\t\tstrategy=\"$@\"\n\t\tstrategy_append_extra_tpws\n\t\treport_append \"$dom\" \"$testf ipv${IPV}\" \"tpws ${WF:+$WF }$strategy\"\n\t}\n\treturn $code\n}\npktws_curl_test()\n{\n\t# $1 - test function\n\t# $2 - domain\n\t# $3,$4,$5, ... - nfqws/dvtws params\n\tlocal testf=$1 dom=\"$2\" strategy code\n\n\tshift; shift;\n\techo - $testf ipv$IPV $dom : $PKTWSD ${WF:+$WF }${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+\"$PKTWS_EXTRA_PRE_1\" }${PKTWS_EXTRA_PRE_2:+\"$PKTWS_EXTRA_PRE_2\" }${PKTWS_EXTRA_PRE_3:+\"$PKTWS_EXTRA_PRE_3\" }${PKTWS_EXTRA_PRE_4:+\"$PKTWS_EXTRA_PRE_4\" }${PKTWS_EXTRA_PRE_5:+\"$PKTWS_EXTRA_PRE_5\" }${PKTWS_EXTRA_PRE_6:+\"$PKTWS_EXTRA_PRE_6\" }${PKTWS_EXTRA_PRE_7:+\"$PKTWS_EXTRA_PRE_7\" }${PKTWS_EXTRA_PRE_8:+\"$PKTWS_EXTRA_PRE_8\" }${PKTWS_EXTRA_PRE_9:+\"$PKTWS_EXTRA_PRE_9\" }$@${PKTWS_EXTRA:+ $PKTWS_EXTRA}${PKTWS_EXTRA_1:+ \"$PKTWS_EXTRA_1\"}${PKTWS_EXTRA_2:+ \"$PKTWS_EXTRA_2\"}${PKTWS_EXTRA_3:+ \"$PKTWS_EXTRA_3\"}${PKTWS_EXTRA_4:+ \"$PKTWS_EXTRA_4\"}${PKTWS_EXTRA_5:+ \"$PKTWS_EXTRA_5\"}${PKTWS_EXTRA_6:+ \"$PKTWS_EXTRA_6\"}${PKTWS_EXTRA_7:+ \"$PKTWS_EXTRA_7\"}${PKTWS_EXTRA_8:+ \"$PKTWS_EXTRA_8\"}${PKTWS_EXTRA_9:+ \"$PKTWS_EXTRA_9\"}\n\tws_curl_test pktws_start $testf \"$dom\" ${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+\"$PKTWS_EXTRA_PRE_1\" }${PKTWS_EXTRA_PRE_2:+\"$PKTWS_EXTRA_PRE_2\" }${PKTWS_EXTRA_PRE_3:+\"$PKTWS_EXTRA_PRE_3\" }${PKTWS_EXTRA_PRE_4:+\"$PKTWS_EXTRA_PRE_4\" }${PKTWS_EXTRA_PRE_5:+\"$PKTWS_EXTRA_PRE_5\" }${PKTWS_EXTRA_PRE_6:+\"$PKTWS_EXTRA_PRE_6\" }${PKTWS_EXTRA_PRE_7:+\"$PKTWS_EXTRA_PRE_7\" }${PKTWS_EXTRA_PRE_8:+\"$PKTWS_EXTRA_PRE_8\" }${PKTWS_EXTRA_PRE_9:+\"$PKTWS_EXTRA_PRE_9\" }\"$@\"${PKTWS_EXTRA:+ $PKTWS_EXTRA}${PKTWS_EXTRA_1:+ \"$PKTWS_EXTRA_1\"}${PKTWS_EXTRA_2:+ \"$PKTWS_EXTRA_2\"}${PKTWS_EXTRA_3:+ \"$PKTWS_EXTRA_3\"}${PKTWS_EXTRA_4:+ \"$PKTWS_EXTRA_4\"}${PKTWS_EXTRA_5:+ \"$PKTWS_EXTRA_5\"}${PKTWS_EXTRA_6:+ \"$PKTWS_EXTRA_6\"}${PKTWS_EXTRA_7:+ \"$PKTWS_EXTRA_7\"}${PKTWS_EXTRA_8:+ \"$PKTWS_EXTRA_8\"}${PKTWS_EXTRA_9:+ \"$PKTWS_EXTRA_9\"}\n\n\tcode=$?\n\t[ \"$code\" = 0 ] && {\n\t\tstrategy=\"$@\"\n\t\tstrategy_append_extra_pktws\n\t\treport_append \"$dom\" \"$testf ipv${IPV}\" \"$PKTWSD ${WF:+$WF }$strategy\"\n\t}\n\treturn $code\n}\n\nstrategy_append_extra_pktws()\n{\n\tstrategy=\"${strategy:+${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+\"$PKTWS_EXTRA_PRE_1\" }${PKTWS_EXTRA_PRE_2:+\"$PKTWS_EXTRA_PRE_2\" }${PKTWS_EXTRA_PRE_3:+\"$PKTWS_EXTRA_PRE_3\" }${PKTWS_EXTRA_PRE_4:+\"$PKTWS_EXTRA_PRE_4\" }${PKTWS_EXTRA_PRE_5:+\"$PKTWS_EXTRA_PRE_5\" }${PKTWS_EXTRA_PRE_6:+\"$PKTWS_EXTRA_PRE_6\" }${PKTWS_EXTRA_PRE_7:+\"$PKTWS_EXTRA_PRE_7\" }${PKTWS_EXTRA_PRE_8:+\"$PKTWS_EXTRA_PRE_8\" }${PKTWS_EXTRA_PRE_9:+\"$PKTWS_EXTRA_PRE_9\" }$strategy${PKTWS_EXTRA:+ $PKTWS_EXTRA}${PKTWS_EXTRA_1:+ \"$PKTWS_EXTRA_1\"}${PKTWS_EXTRA_2:+ \"$PKTWS_EXTRA_2\"}${PKTWS_EXTRA_3:+ \"$PKTWS_EXTRA_3\"}${PKTWS_EXTRA_4:+ \"$PKTWS_EXTRA_4\"}${PKTWS_EXTRA_5:+ \"$PKTWS_EXTRA_5\"}${PKTWS_EXTRA_6:+ \"$PKTWS_EXTRA_6\"}${PKTWS_EXTRA_7:+ \"$PKTWS_EXTRA_7\"}${PKTWS_EXTRA_8:+ \"$PKTWS_EXTRA_8\"}${PKTWS_EXTRA_9:+ \"$PKTWS_EXTRA_9\"}}\"\n}\nstrategy_append_extra_tpws()\n{\n\tstrategy=\"${strategy:+${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+\"$PKTWS_EXTRA_PRE_1\" }${PKTWS_EXTRA_PRE_2:+\"$PKTWS_EXTRA_PRE_2\" }${PKTWS_EXTRA_PRE_3:+\"$PKTWS_EXTRA_PRE_3\" }${PKTWS_EXTRA_PRE_4:+\"$PKTWS_EXTRA_PRE_4\" }${PKTWS_EXTRA_PRE_5:+\"$PKTWS_EXTRA_PRE_5\" }${PKTWS_EXTRA_PRE_6:+\"$PKTWS_EXTRA_PRE_6\" }${PKTWS_EXTRA_PRE_7:+\"$PKTWS_EXTRA_PRE_7\" }${PKTWS_EXTRA_PRE_8:+\"$PKTWS_EXTRA_PRE_8\" }${PKTWS_EXTRA_PRE_9:+\"$PKTWS_EXTRA_PRE_9\" }$strategy${TPWS_EXTRA:+ $TPWS_EXTRA}${TPWS_EXTRA_1:+ \"$TPWS_EXTRA_1\"}${TPWS_EXTRA_2:+ \"$TPWS_EXTRA_2\"}${TPWS_EXTRA_3:+ \"$TPWS_EXTRA_3\"}${TPWS_EXTRA_4:+ \"$TPWS_EXTRA_4\"}${TPWS_EXTRA_5:+ \"$TPWS_EXTRA_5\"}${TPWS_EXTRA_6:+ \"$TPWS_EXTRA_6\"}${TPWS_EXTRA_7:+ \"$TPWS_EXTRA_7\"}${TPWS_EXTRA_8:+ \"$TPWS_EXTRA_8\"}${TPWS_EXTRA_9:+ \"$TPWS_EXTRA_9\"}}\"\n}\n\nxxxws_curl_test_update()\n{\n\t# $1 - xxx_curl_test function\n\t# $2 - test function\n\t# $3 - domain\n\t# $4,$5,$6, ... - nfqws/dvtws params\n\tlocal code xxxf=$1 testf=$2 dom=\"$3\"\n\tshift\n\tshift\n\tshift\n\t$xxxf $testf \"$dom\" \"$@\"\n\tcode=$?\n\t[ $code = 0 ] && strategy=\"${strategy:-$@}\"\n\treturn $code\n}\npktws_curl_test_update()\n{\n\txxxws_curl_test_update pktws_curl_test \"$@\"\n}\ntpws_curl_test_update()\n{\n\txxxws_curl_test_update tpws_curl_test \"$@\"\n}\n\nreport_append()\n{\n\t# $1 - domain\n\t# $2 - test function + ipver\n\t# $3 - value\n\tlocal hashstr hash hashvar hashcountvar val ct\n\n\t# save resources if only one domain\n\t[ \"$DOMAINS_COUNT\" -gt 1 ] && {\n\t\thashstr=\"$2 : $3\"\n\t\thash=\"$(echo -n \"$hashstr\" | md5f)\"\n\t\thashvar=RESHASH_${hash}\n\t\thashcountvar=${hashvar}_COUNTER\n\n\t\tNRESHASH=${NRESHASH:-0}\n\n\t\teval val=\"\\$$hashvar\"\n\t\tif [ -n \"$val\" ]; then\n\t\t\teval ct=\"\\$$hashcountvar\"\n\t\t\tct=$(($ct + 1))\n\t\t\teval $hashcountvar=\"\\$ct\"\n\t\telse\n\t\t\teval $hashvar=\\\"$hashstr\\\"\n\t\t\teval $hashcountvar=1\n\t\t\teval RES_$NRESHASH=\"\\$hash\"\n\t\t\tNRESHASH=$(($NRESHASH+1))\n\t\tfi\n\t}\n\n\tNREPORT=${NREPORT:-0}\n\teval REPORT_${NREPORT}=\\\"$2 $1 : $3\\\"\n\tNREPORT=$(($NREPORT+1))\n}\nreport_print()\n{\n\tlocal n=0 s\n\tNREPORT=${NREPORT:-0}\n\twhile [ $n -lt $NREPORT ]; do\n\t\teval s=\\\"\\${REPORT_$n}\\\"\n\t\techo $s\n\t\tn=$(($n+1))\n\tdone\n}\nresult_intersection_print()\n{\n\tlocal n=0 hash hashvar hashcountvar ct val\n\twhile : ; do\n\t\teval hash=\\\"\\$RES_$n\\\"\n\t\t[ -n \"$hash\" ] || break\n\t\thashvar=RESHASH_${hash}\n\t\thashcountvar=${hashvar}_COUNTER\n\t\teval ct=\\\"\\$$hashcountvar\\\"\n\t\t[ \"$ct\" = \"$DOMAINS_COUNT\" ] && {\n\t\t\teval val=\\\"\\$$hashvar\\\"\n\t\t\techo \"$val\"\n\t\t}\n\t\tn=$(($n + 1))\n\tdone\n}\nreport_strategy()\n{\n\t# $1 - test function\n\t# $2 - domain\n\t# $3 - daemon\n\techo\n\tif [ -n \"$strategy\" ]; then\n\t\t# trim spaces at the end\n\t\tstrategy=\"$(echo \"$strategy\" | xargs)\"\n\t\techo \"!!!!! $1: working strategy found for ipv${IPV} $2 : $3 $strategy !!!!!\"\n\t\techo\n\t\treturn 0\n\telse\n\t\techo \"$1: $3 strategy for ipv${IPV} $2 not found\"\n\t\techo\n\t\treport_append \"$2\" \"$1 ipv${IPV}\" \"$3 not working\"\n\t\treturn 1\n\tfi\n}\ntest_has_fakedsplit()\n{\n\tcontains \"$1\" fakedsplit || contains \"$1\" fakeddisorder\n}\ntest_has_split()\n{\n\tcontains \"$1\" multisplit || contains \"$1\" multidisorder || test_has_fakedsplit \"$1\"\n}\ntest_has_hostfakesplit()\n{\n\tcontains \"$1\" hostfakesplit\n}\ntest_has_fake()\n{\n\t[ \"$1\" = fake ] || starts_with \"$1\" fake,\n}\nwarn_fool()\n{\n\tcase \"$1\" in\n\t\tmd5sig) echo 'WARNING ! although md5sig fooling worked it will not work on all sites. it typically works only on linux servers.'\n\t\t\t[ \"$2\" = \"fakedsplit\" -o \"$2\" = \"fakeddisorder\" ] && \\\n\t\t\t\techo \"WARNING ! fakedsplit/fakeddisorder with md5sig fooling and low split position causes MTU overflow with multi-segment TLS (kyber)\"\n\t\t\t;;\n\t\tdatanoack) echo 'WARNING ! although datanoack fooling worked it may break NAT and may only work with external IP. Additionally it may require nftables to work correctly.' ;;\n\t\tts) echo 'WARNING ! although ts fooling worked it will not work without timestamps being enabled in the client OS. In windows timestamps are DISABLED by default.'\n\tesac\n}\npktws_curl_test_update_vary()\n{\n\t# $1 - test function\n\t# $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $3 - domain\n\t# $4 - desync mode\n\t# $5,$6,... - strategy\n\n\tlocal testf=$1 sec=$2 domain=$3 desync=$4 proto splits= pos fake ret=1\n\tlocal fake1=- fake2=- fake3=- fake4=-\n\t\n\tshift; shift; shift; shift\n\t\n\tproto=http\n\t[ \"$sec\" = 0 ] || proto=tls\n\ttest_has_fake $desync && {\n\t\tfake1=\"--dpi-desync-fake-$proto=0x00000000\"\n\t\t[ \"$sec\" = 0 ] || {\n\t\t\tfake2='--dpi-desync-fake-tls=0x00000000 --dpi-desync-fake-tls=! --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid'\n\t\t\t# this splits actual fake to '1603' and modified standard fake from offset 2\n\t\t\tfake3='--dpi-desync-fake-tls=0x1603 --dpi-desync-fake-tls=!+2 --dpi-desync-fake-tls-mod=rnd,dupsid,rndsni --dpi-desync-fake-tcp-mod=seq'\n\t\t\tfake4='--dpi-desync-fake-tls-mod=rnd,dupsid,rndsni,padencap'\n\t\t}\n\t}\n\tif test_has_fakedsplit $desync ; then\n\t\tsplits=\"method+2 midsld\"\n\t\t[ \"$sec\" = 0 ] || splits=\"1 midsld\"\n\t\t# do not send fake first\n\t\tfake1='--dpi-desync-fakedsplit-mod=altorder=1'\n\telif test_has_split $desync ; then\n\t\tsplits=\"method+2 midsld\"\n\t\t[ \"$sec\" = 0 ] || splits=\"1 midsld 1,midsld\"\n\tfi\n\ttest_has_hostfakesplit $desync && {\n\t\tfake1=\"--dpi-desync-hostfakesplit-mod=altorder=1\"\n\t\tfake2=\"--dpi-desync-hostfakesplit-midhost=midsld\"\n\t\tfake3=\"--dpi-desync-hostfakesplit-mod=altorder=1 --dpi-desync-hostfakesplit-midhost=midsld\"\n\t}\n\tfor fake in '' \"$fake1\" \"$fake2\" \"$fake3\" \"$fake4\" ; do\n\t\t[ \"$fake\" = \"-\" ] && continue\n\t\tif [ -n \"$splits\" ]; then\n\t\t\tfor pos in $splits ; do\n\t\t\t\tpktws_curl_test_update $testf \"$domain\" --dpi-desync=$desync \"$@\" --dpi-desync-split-pos=$pos $fake && {\n\t\t\t\t\t[ \"$SCANLEVEL\" = force ] || return 0\n\t\t\t\t\tret=0\n\t\t\t\t}\n\t\t\tdone\n\t\telse\n\t\t\tpktws_curl_test_update $testf \"$domain\" --dpi-desync=$desync \"$@\" $fake && {\n\t\t\t\t[ \"$SCANLEVEL\" = force ] || return 0\n\t\t\t\tret=0\n\t\t\t}\n\t\tfi\n\tdone\n\n\treturn $ret\n}\n\npktws_check_domain_http_bypass_()\n{\n\t# $1 - test function\n\t# $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $3 - domain\n\n\tlocal ok ttls attls s f f2 e desync pos fooling frag sec=\"$2\" delta orig splits\n\tlocal need_split need_disorder need_fakedsplit need_hostfakesplit need_fakeddisorder need_fake need_wssize\n\tlocal splits_http='method+2 midsld method+2,midsld'\n\tlocal splits_tls='2 1 sniext+1 sniext+4 host+1 midsld 1,midsld 1,sniext+1,host+1,midsld-2,midsld,midsld+2,endhost-1'\n\n\t[ \"$sec\" = 0 ] && {\n\t\tfor s in '--hostcase' '--hostspell=hoSt' '--hostnospace' '--domcase' '--methodeol'; do\n\t\t\tpktws_curl_test_update $1 $3 $s && [ \"$SCANLEVEL\" = quick ] && return\n\t\tdone\n\t}\n\n\tttls=$(seq -s ' ' $MIN_TTL $MAX_TTL)\n\tattls=$(seq -s ' ' $MIN_AUTOTTL_DELTA $MAX_AUTOTTL_DELTA)\n\tneed_wssize=1\n\tfor e in '' '--wssize 1:6'; do\n\t\tneed_split=\n\t\tneed_disorder=\n\n\t\t[ -n \"$e\" ] && {\n\t\t\tpktws_curl_test_update $1 $3 $e && [ \"$SCANLEVEL\" = quick ] && return\n\t\t}\n\n\t\tfor desync in multisplit multidisorder; do\n\t\t\tok=0\n\t\t\tsplits=\"$splits_http\"\n\t\t\t[ \"$sec\" = 0 ] || splits=\"$splits_tls\"\n\t\t\tfor pos in $splits; do\n\t\t\t\tpktws_curl_test_update $1 $3 --dpi-desync=$desync --dpi-desync-split-pos=$pos $e && {\n\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\tok=1\n\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t[ \"$SCANLEVEL\" = force ] || break\n\t\t\t\t}\n\t\t\tdone\n\t\t\t[ \"$ok\" = 1 -a \"$SCANLEVEL\" != force ] || {\n\t\t\t\tcase $desync in\n\t\t\t\t\tmultisplit)\n\t\t\t\t\t\tneed_split=1\n\t\t\t\t\t\t;;\n\t\t\t\t\tmultidisorder)\n\t\t\t\t\t\tneed_disorder=1\n\t\t\t\t\t\t;;\n\t\t\t\tesac\n\t\t\t}\n\t\tdone\n\n\t\tneed_fakedsplit=1\n\t\tneed_hostfakesplit=1\n\t\tneed_fakeddisorder=1\n\t\tneed_fake=1\n\t\tfor desync in fake ${need_split:+fakedsplit fake,multisplit fake,fakedsplit hostfakesplit fake,hostfakesplit} ${need_disorder:+fakeddisorder fake,multidisorder fake,fakeddisorder}; do\n\t\t\t[ \"$need_fake\" = 0 ] && test_has_fake \"$desync\" && continue\n\t\t\t[ \"$need_fakedsplit\" = 0 ] && contains \"$desync\" fakedsplit && continue\n\t\t\t[ \"$need_hostfakesplit\" = 0 ] && contains \"$desync\" hostfakesplit && continue\n\t\t\t[ \"$need_fakeddisorder\" = 0 ] && contains \"$desync\" fakeddisorder && continue\n\t\t\tok=0\n\t\t\tfor ttl in $ttls; do\n\t\t\t\t# orig-ttl=1 with start/cutoff limiter drops empty ACK packet in response to SYN,ACK. it does not reach DPI or server.\n\t\t\t\t# missing ACK is transmitted in the first data packet of TLS/HTTP proto\n\t\t\t\tfor f in '' '--orig-ttl=1 --orig-mod-start=s1 --orig-mod-cutoff=d1'; do\n\t\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=$ttl $f $e && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tok=1\n\t\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = force ] || break\n\t\t\t\t\t}\n\t\t\t\tdone\n\t\t\t\t[ \"$ok\" = 1 ] && break\n\t\t\tdone\n\t\t\t# only skip tests if TTL succeeded. do not skip if TTL failed but fooling succeeded\n\t\t\t[ $ok = 1 -a \"$SCANLEVEL\" != force ] && {\n\t\t\t\t[ \"$desync\" = fake ] && need_fake=0\n\t\t\t\t[ \"$desync\" = fakedsplit ] && need_fakedsplit=0\n\t\t\t\t[ \"$desync\" = hostfakesplit ] && need_hostfakesplit=0\n\t\t\t\t[ \"$desync\" = fakeddisorder ] && need_fakeddisorder=0\n\t\t\t}\n\t\t\tf=\n\t\t\t[ \"$UNAME\" = \"OpenBSD\" ] || f=\"badsum\"\n\t\t\tf=\"$f badseq datanoack ts md5sig\"\n\t\t\t[ \"$IPV\" = 6 ] && f=\"$f hopbyhop hopbyhop2\"\n\t\t\tfor fooling in $f; do\n\t\t\t\tok=0\n\t\t\t\tf2=\n\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling $e && {\n\t\t\t\t\twarn_fool $fooling $desync\n\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\tneed_wssize=0\n\t\t\t\t\tok=1\n\t\t\t\t}\n\t\t\t\t[ \"$fooling\" = badseq ] && {\n\t\t\t\t\t[ \"$ok\" = 1 -a \"$SCANLEVEL\" != force ] && continue\n\t\t\t\t\t# --dpi-desync-badseq-increment=0 leaves modified by default ack increment\n\t\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling --dpi-desync-badseq-increment=0 $e && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t[ \"$fooling\" = md5sig ] && {\n\t\t\t\t\t[ \"$ok\" = 1 -a \"$SCANLEVEL\" != force ] && continue\n\t\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling --dup=1 --dup-cutoff=n2 --dup-fooling=md5sig $e && {\n\t\t\t\t\t\twarn_fool $fooling $desync\n\t\t\t\t\t\techo \"HINT ! To avoid possible 1 sec server response delay use --dup-ttl or --dup-autottl and block ICMP time exceeded\"\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tdone\n\t\tdone\n\n\t\t[ \"$IPV\" = 6 ] && {\n\t\t\tf=\"hopbyhop ${need_split:+hopbyhop,multisplit} ${need_disorder:+hopbyhop,multidisorder} destopt ${need_split:+destopt,multisplit} ${need_disorder:+destopt,multidisorder}\"\n\t\t\t[ -n \"$IP6_DEFRAG_DISABLE\" ] && f=\"$f ipfrag1 ${need_split:+ ipfrag1,multisplit} ${need_disorder:+ ipfrag1,multidisorder}\"\n\t\t\tfor desync in $f; do\n\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync $e && {\n\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\tneed_wssize=0\n\t\t\t\t}\n\t\t\tdone\n\t\t}\n\n\t\t[ \"$need_split\" = 1 ] && {\n\t\t\t# relative markers can be anywhere, even in subsequent packets. first packet can be MTU-full.\n\t\t\t# make additional split pos \"10\" to guarantee enough space for seqovl and likely to be before midsld,sniext,...\n\t\t\t# method is always expected in the beginning of the first packet\n\t\t\tf=\"method+2 method+2,midsld\"\n\t\t\t[ \"$sec\" = 0 ] || f=\"10 10,sniext+1 10,sniext+4 10,midsld\"\n\t\t\tfor pos in $f; do\n\t\t\t\tpktws_curl_test_update $1 $3 --dpi-desync=multisplit --dpi-desync-split-pos=$pos --dpi-desync-split-seqovl=1 $e && {\n\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\tneed_wssize=0\n\t\t\t\t}\n\t\t\tdone\n\t\t\t[ \"$sec\" != 0 ] && pktws_curl_test_update $1 $3 --dpi-desync=multisplit --dpi-desync-split-pos=2 --dpi-desync-split-seqovl=336 --dpi-desync-split-seqovl-pattern=\"$ZAPRET_BASE/files/fake/tls_clienthello_iana_org.bin\" $e && {\n\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\tneed_wssize=0\n\t\t\t}\n\t\t}\n\t\t[ \"$need_disorder\" = 1 ] && {\n\t\t\tif [ \"$sec\" = 0 ]; then\n\t\t\t\tfor pos in 'method+1 method+2' 'midsld-1 midsld' 'method+1 method+2,midsld'; do\n\t\t\t\t\tf=\"$(extract_arg 1 $pos)\"\n\t\t\t\t\tf2=\"$(extract_arg 2 $pos)\"\n\t\t\t\t\tpktws_curl_test_update $1 $3 --dpi-desync=multidisorder --dpi-desync-split-pos=$f2 --dpi-desync-split-seqovl=$f $e && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t}\n\t\t\t\tdone\n\t\t\telse\n\t\t\t\tfor pos in '1 2' 'sniext sniext+1' 'sniext+3 sniext+4' 'midsld-1 midsld' '1 2,midsld'; do\n\t\t\t\t\tf=$(extract_arg 1 $pos)\n\t\t\t\t\tf2=$(extract_arg 2 $pos)\n\t\t\t\t\tpktws_curl_test_update $1 $3 --dpi-desync=multidisorder --dpi-desync-split-pos=$f2 --dpi-desync-split-seqovl=$f $e && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_wssize=0\n\t\t\t\t\t}\n\t\t\t\tdone\n\t\t\tfi\n\t\t}\n\n\t\tneed_fakedsplit=1\n\t\tneed_fakeddisorder=1\n\t\tneed_hostfakesplit=1\n\t\tneed_fake=1\n\t\tfor desync in fake ${need_split:+fakedsplit fake,multisplit fake,fakedsplit hostfakesplit fake,hostfakesplit} ${need_disorder:+fakeddisorder fake,multidisorder fake,fakeddisorder}; do\n\t\t\t[ \"$need_fake\" = 0 ] && test_has_fake \"$desync\" && continue\n\t\t\t[ \"$need_fakedsplit\" = 0 ] && contains \"$desync\" fakedsplit && continue\n\t\t\t[ \"$need_hostfakesplit\" = 0 ] && contains \"$desync\" hostfakesplit && continue\n\t\t\t[ \"$need_fakeddisorder\" = 0 ] && contains \"$desync\" fakeddisorder && continue\n\t\t\tok=0\n\t\t\t# orig-ttl=1 with start/cutoff limiter drops empty ACK packet in response to SYN,ACK. it does not reach DPI or server.\n\t\t\t# missing ACK is transmitted in the first data packet of TLS/HTTP proto\n\t\t\tfor delta in $attls; do\n\t\t\t\tfor f in '' '--orig-ttl=1 --orig-mod-start=s1 --orig-mod-cutoff=d1'; do\n\t\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=1 --dpi-desync-autottl=-$delta $f $e && ok=1\n\t\t\t\t\t[ \"$ok\" = 1 -a \"$SCANLEVEL\" != force ] && break\n\t\t\t\tdone\n\t\t\tdone\n\t\t\t[ \"$SCANLEVEL\" = force ] && {\n\t\t\t\tfor orig in 1 2 3; do\n\t\t\t\t\tfor delta in $attls; do\n\t\t\t\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync ${orig:+--orig-autottl=+$orig} --dpi-desync-ttl=1 --dpi-desync-autottl=-$delta $e && ok=1\n\t\t\t\t\tdone\n\t\t\t\t\t[ \"$ok\" = 1 -a \"$SCANLEVEL\" != force ] && break\n\t\t\t\tdone\n\t\t\t}\n\t\t\t[ \"$ok\" = 1 ] &&\n\t\t\t{\n\t\t\t\techo \"WARNING ! although autottl worked it requires testing on multiple domains to find out reliable delta\"\n\t\t\t\techo \"WARNING ! if a reliable delta cannot be found it's a good idea not to use autottl\"\n\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\tneed_wssize=0\n\t\t\t\t[ \"$SCANLEVEL\" = force ] || {\n\t\t\t\t\t[ \"$desync\" = fake ] && need_fake=0\n\t\t\t\t\t[ \"$desync\" = fakedsplit ] && need_fakedsplit=0\n\t\t\t\t\t[ \"$desync\" = hostfakesplit ] && need_hostfakesplit=0\n\t\t\t\t\t[ \"$desync\" = fakeddisorder ] && need_fakeddisorder=0\n\t\t\t\t}\n\t\t\t}\t\t\t\n\t\tdone\n\n\t\ts=\"http_iana_org.bin\"\n\t\t[ \"$sec\" = 0 ] || s=\"tls_clienthello_iana_org.bin\"\n\t\tfor desync in syndata ${need_split:+syndata,multisplit} ${need_disorder:+syndata,multidisorder} ; do\n\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync $e && [ \"$SCANLEVEL\" = quick ] && return\n\t\t\tpktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fake-syndata=\"$ZAPRET_BASE/files/fake/$s\" $e && [ \"$SCANLEVEL\" = quick ] && return\n\t\tdone\n\n\t\t# do not do wssize test for http and TLS 1.3. it's useless\n\t\t[ \"$sec\" = 1 ] || break\n\t\t[ \"$SCANLEVEL\" = force -o \"$need_wssize\" = 1 ] || break\n\tdone\n}\npktws_check_domain_http_bypass()\n{\n\t# $1 - test function\n\t# $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $3 - domain\n\n\tlocal strategy\n\tpktws_check_domain_http_bypass_ \"$@\"\n\tstrategy_append_extra_pktws\n\treport_strategy $1 $3 $PKTWSD\n}\n\npktws_check_domain_http3_bypass_()\n{\n\t# $1 - test function\n\t# $2 - domain\n\n\tlocal f desync frag tests rep fake\n\n\tfor fake in '' \"--dpi-desync-fake-quic=$ZAPRET_BASE/files/fake/quic_initial_www_google_com.bin\"; do\n\t\tfor rep in '' 2 5 10 20; do\n\t\t\tpktws_curl_test_update $1 $2 --dpi-desync=fake ${fake:+\"$fake\" }${rep:+--dpi-desync-repeats=$rep} && [ \"$SCANLEVEL\" != force ] && {\n\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\tbreak\n\t\t\t}\n\t\tdone\n\tdone\n\n\t[ \"$IPV\" = 6 ] && {\n\t\tf=\"hopbyhop destopt\"\n\t\t[ -n \"$IP6_DEFRAG_DISABLE\" ] && f=\"$f ipfrag1\"\n\t\tfor desync in $f; do\n\t\t\tpktws_curl_test_update $1 $2 --dpi-desync=$desync && [ \"$SCANLEVEL\" = quick ] && return\n\t\tdone\n\t}\n\n\t# OpenBSD has checksum issues with fragmented packets\n\t[ \"$UNAME\" != \"OpenBSD\" ] && [ \"$IPV\" = 4 -o -n \"$IP6_DEFRAG_DISABLE\" ] && {\n\t\tfor frag in 8 16 24 32 40 64; do\n\t\t\ttests=\"ipfrag2\"\n\t\t\t[ \"$IPV\" = 6 ] && tests=\"$tests hopbyhop,ipfrag2 destopt,ipfrag2\"\n\t\t\tfor desync in $tests; do\n\t\t\t\tpktws_curl_test_update $1 $2 --dpi-desync=$desync --dpi-desync-ipfrag-pos-udp=$frag && [ \"$SCANLEVEL\" = quick ] && return\n\t\t\tdone\n\t\tdone\n\t}\n\t\n}\npktws_check_domain_http3_bypass()\n{\n\t# $1 - test function\n\t# $2 - domain\n\n\tlocal strategy\n\tpktws_check_domain_http3_bypass_ \"$@\"\n\tstrategy_append_extra_pktws\n\treport_strategy $1 $2 $PKTWSD\n}\nwarn_mss()\n{\n\t[ -n \"$1\" ] && echo 'WARNING ! although mss worked it may not work on all sites and will likely cause significant slowdown. it may only be required for TLS1.2, not TLS1.3'\n\treturn 0\n}\nfix_seg()\n{\n\t# $1 - split-pos\n\t[ -n \"$FIX_SEG\" ] && contains \"$1\" , && echo \"$FIX_SEG\"\n}\n\ntpws_check_domain_http_bypass_()\n{\n\t# $1 - test function\n\t# $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $3 - domain\n\n\tlocal s mss s2 s3 oobdis pos sec=\"$2\"\n\tlocal splits_tls='2 1 sniext+1 sniext+4 host+1 midsld 1,midsld 1,sniext+1,host+1,midsld,endhost-1'\n\tlocal splits_http='method+2 midsld method+2,midsld'\n\n\t# simulteneous oob and disorder works properly only in linux. other systems retransmit oob byte without URG tcp flag and poison tcp stream.\n\t[ \"$UNAME\" = Linux ] && oobdis='--oob --disorder'\n\tif [ \"$sec\" = 0 ]; then\n\t\tfor s in '--hostcase' '--hostspell=hoSt' '--hostdot' '--hosttab' '--hostnospace' '--domcase' ; do\n\t\t\ttpws_curl_test_update $1 $3 $s && [ \"$SCANLEVEL\" = quick ] && return\n\t\tdone\n\t\tfor s in 1024 2048 4096 8192 16384 ; do\n\t\t\ttpws_curl_test_update $1 $3 --hostpad=$s && [ \"$SCANLEVEL\" != force ] && {\n\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\tbreak\n\t\t\t}\n\t\tdone\n\t\tfor s2 in '' '--hostcase' '--oob' '--disorder' ${oobdis:+\"$oobdis\"}; do\n\t\t\tfor s in $splits_http ; do\n\t\t\t\ttpws_curl_test_update $1 $3 --split-pos=$s $(fix_seg $s) $s2 && [ \"$SCANLEVEL\" != force ] && {\n\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\tdone\n\t\tdone\n\t\tfor s in  '--methodspace' '--unixeol' '--methodeol'; do\n\t\t\ttpws_curl_test_update $1 $3 $s && [ \"$SCANLEVEL\" = quick ] && return\n\t\tdone\n\telse\n\t\tlocal need_mss=1\n\t\tfor mss in '' 88; do\n\t\t\ts3=${mss:+--mss=$mss}\n\t\t\tfor s2 in '' '--oob' '--disorder' ${oobdis:+\"$oobdis\"}; do\n\t\t\t\tfor pos in $splits_tls; do\n\t\t\t\t\ttpws_curl_test_update $1 $3 --split-pos=$pos $(fix_seg $pos) $s2 $s3 && warn_mss $s3 && [ \"$SCANLEVEL\" != force ] && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_mss=0\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\tdone\n\t\t\tdone\n\t\t\tfor s in '' '--oob' '--disorder' ${oobdis:+\"$oobdis\"}; do\n\t\t\t\tfor s2 in '--tlsrec=midsld' '--tlsrec=sniext+1 --split-pos=midsld' '--tlsrec=sniext+4 --split-pos=midsld' \"--tlsrec=sniext+1 --split-pos=1,midsld $FIX_SEG\" \"--tlsrec=sniext+4 --split-pos=1,midsld $FIX_SEG\" ; do\n\t\t\t\t\ttpws_curl_test_update $1 $3 $s2 $s $s3 && warn_mss $s3 && [ \"$SCANLEVEL\" != force ] && {\n\t\t\t\t\t\t[ \"$SCANLEVEL\" = quick ] && return\n\t\t\t\t\t\tneed_mss=0\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\tdone\n\t\t\tdone\n\t\t\t# only linux supports mss\n\t\t\t[ \"$UNAME\" = Linux -a \"$sec\" = 1 ] || break\n\t\t\t[ \"$SCANLEVEL\" = force -o \"$need_mss\" = 1 ] || break\n\t\tdone\n\tfi\n}\ntpws_check_domain_http_bypass()\n{\n\t# $1 - test function\n\t# $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $3 - domain\n\n\tlocal strategy\n\ttpws_check_domain_http_bypass_ \"$@\"\n\tstrategy_append_extra_tpws\n\treport_strategy $1 $3 tpws\n}\n\ncheck_dpi_ip_block()\n{\n\t# $1 - test function\n\t# $2 - domain\n\n\tlocal blocked_dom=\"$2\"\n\tlocal blocked_ip blocked_ips unblocked_ip\n\n\techo \n\techo \"- IP block tests (requires manual interpretation)\"\n\n\techo \"> testing $UNBLOCKED_DOM on it's original ip\"\n\tif curl_test $1 $UNBLOCKED_DOM; then\n\t\tmdig_resolve $IPV unblocked_ip $UNBLOCKED_DOM\n\t\t[ -n \"$unblocked_ip\" ] || {\n\t\t\techo $UNBLOCKED_DOM does not resolve. tests not possible.\n\t\t\treturn 1\n\t\t}\n\n\t\techo \"> testing $blocked_dom on $unblocked_ip ($UNBLOCKED_DOM)\"\n\t\tcurl_test $1 $blocked_dom $unblocked_ip detail\n\n\t\tmdig_resolve_all $IPV blocked_ips $blocked_dom\n\t\tfor blocked_ip in $blocked_ips; do\n\t\t\techo \"> testing $UNBLOCKED_DOM on $blocked_ip ($blocked_dom)\"\n\t\t\tcurl_test $1 $UNBLOCKED_DOM $blocked_ip detail\n\t\tdone\n\telse\n\t\techo $UNBLOCKED_DOM is not available. skipping this test.\n\tfi\n}\n\ncurl_has_reason_to_continue()\n{\n\t# $1 - curl return code\n\tfor c in 1 2 3 4 6 27 ; do\n\t\t[ $1 = $c ] && return 1\n\tdone\n\treturn 0\n}\n\ncheck_domain_prolog()\n{\n\t# $1 - test function\n\t# $2 - port\n\t# $3 - domain\n\n\tlocal code\n\n\t[ \"$SIMULATE\" = 1 ] && return 0\n\n\techo\n\techo \\* $1 ipv$IPV $3\n\n\techo \"- checking without DPI bypass\"\n\tcurl_test $1 $3 && {\n\t\treport_append \"$3\" \"$1 ipv${IPV}\" \"working without bypass\"\n\t\t[ \"$SCANLEVEL\" = force ] || return 1\n\t}\n\tcode=$?\n\tcurl_has_reason_to_continue $code || {\n\t\treport_append \"$3\" \"$1 ipv${IPV}\" \"test aborted, no reason to continue. curl code $(curl_translate_code $code)\"\n\t\treturn 1\n\t}\n\treturn 0\n}\ncheck_domain_http_tcp()\n{\n\t# $1 - test function\n\t# $2 - port\n\t# $3 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk\n\t# $4 - domain\n\n\tlocal ips\n\n\t# in case was interrupted before\n\tpktws_ipt_unprepare_tcp $2\n\tws_kill\n\n\tcheck_domain_prolog $1 $2 $4 || return\n\n\t[ \"$SKIP_IPBLOCK\" = 1 ] || check_dpi_ip_block $1 $4\n\n\t[ \"$SKIP_TPWS\" = 1 ] || {\n\t\techo\n\t\ttpws_check_domain_http_bypass $1 $3 $4\n\t}\n\n\t[ \"$SKIP_PKTWS\" = 1 ] || {\n\t\techo\n\t        echo preparing $PKTWSD redirection\n\t\tmdig_resolve_all $IPV ips $4\n\t\tpktws_ipt_prepare_tcp $2 \"$ips\"\n\n\t\tpktws_check_domain_http_bypass $1 $3 $4\n\n\t\techo clearing $PKTWSD redirection\n\t\tpktws_ipt_unprepare_tcp $2\n\t}\n}\ncheck_domain_http_udp()\n{\n\t# $1 - test function\n\t# $2 - port\n\t# $3 - domain\n\n\tlocal ips\n\n\t# in case was interrupted before\n\tpktws_ipt_unprepare_udp $2\n\tws_kill\n\n\tcheck_domain_prolog $1 $2 $3 || return\n\n\t[ \"$SKIP_PKTWS\" = 1 ] || {\n\t\techo\n\t        echo preparing $PKTWSD redirection\n\t\tmdig_resolve_all $IPV ips $3\n\t\tpktws_ipt_prepare_udp $2 \"$ips\"\n\n\t\tpktws_check_domain_http3_bypass $1 $3\n\n\t\techo clearing $PKTWSD redirection\n\t\tpktws_ipt_unprepare_udp $2\n\t}\n}\n\n\ncheck_domain_http()\n{\n\t# $1 - domain\n\tcheck_domain_http_tcp curl_test_http $HTTP_PORT 0 $1\n}\ncheck_domain_https_tls12()\n{\n\t# $1 - domain\n\tcheck_domain_http_tcp curl_test_https_tls12 $HTTPS_PORT 1 $1\n}\ncheck_domain_https_tls13()\n{\n\t# $1 - domain\n\tcheck_domain_http_tcp curl_test_https_tls13 $HTTPS_PORT 2 $1\n}\ncheck_domain_http3()\n{\n\t# $1 - domain\n\tcheck_domain_http_udp curl_test_http3 $QUIC_PORT $1\n}\n\nconfigure_ip_version()\n{\n\tif [ \"$IPV\" = 6 ]; then\n\t\tLOCALHOST=::1\n\t\tLOCALHOST_IPT=[${LOCALHOST}]\n\t\tIPVV=6\n\telse\n\t\tIPTABLES=iptables\n\t\tLOCALHOST=127.0.0.1\n\t\tLOCALHOST_IPT=$LOCALHOST\n\t\tIPVV=\n\tfi\n\tIPTABLES=ip${IPVV}tables\n}\nconfigure_curl_opt()\n{\n\t# wolfssl : --tlsv1.x mandates exact ssl version, tls-max not supported\n\t# openssl : --tlsv1.x means \"version equal or greater\", tls-max supported\n\tTLSMAX12=\n\tTLSMAX13=\n\tcurl_supports_tlsmax && {\n\t\tTLSMAX12=\"--tls-max 1.2\"\n\t\tTLSMAX13=\"--tls-max 1.3\"\n\t}\n\tTLS13=\n\tcurl_supports_tls13 && TLS13=1\n\tHTTP3=\n\tcurl_supports_http3 && HTTP3=1\n\n\tHTTPS_HEAD=-I\n\t[ \"$CURL_HTTPS_GET\" = 1 ] && HTTPS_HEAD=\n}\n\nlinux_ipv6_defrag_can_be_disabled()\n{\n\tlinux_min_version 4 16\n}\n\nconfigure_defrag()\n{\n\tIP6_DEFRAG_DISABLE=\n\n\t[ \"$IPVS\" = 4 ] && return\n\n\t[ \"$UNAME\" = \"Linux\" ] && {\n\t\tlinux_ipv6_defrag_can_be_disabled || {\n\t\t\techo \"WARNING ! ipv6 defrag can only be effectively disabled in linux kernel 4.16+\"\n\t\t\techo \"WARNING ! ipv6 ipfrag tests are disabled\"\n\t\t\techo\n\t\t\treturn\n\t\t}\n\t}\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tif ipt6_has_raw ; then\n\t\t\t\tif ipt6_has_frag; then\n\t\t\t\t\tIP6_DEFRAG_DISABLE=1\n\t\t\t\telse\n\t\t\t\t\techo \"WARNING ! ip6tables does not have '-m frag' module, ipv6 ipfrag tests are disabled\"\n\t\t\t\t\techo\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\techo \"WARNING ! ip6tables raw table is not available, ipv6 ipfrag tests are disabled\"\n\t\t\t\techo\n\t\t\tfi\n\t\t\t[ -n \"$IP6_DEFRAG_DISABLE\" ] && {\n\t\t\t\tlocal ipexe=\"$(readlink -f $(whichq ip6tables))\"\n\t\t\t\tif contains \"$ipexe\" nft; then\n\t\t\t\t\techo \"WARNING ! ipv6 ipfrag tests may have no effect if ip6tables-nft is used. current ip6tables point to : $ipexe\"\n\t\t\t\telse\n\t\t\t\t\techo \"WARNING ! ipv6 ipfrag tests may have no effect if ip6table_raw kernel module is not loaded with parameter : raw_before_defrag=1\"\n\t\t\t\tfi\n\t\t\t\techo\n\t\t\t}\n\t\t\t;;\n\t\t*)\n\t\t\tIP6_DEFRAG_DISABLE=1\n\t\t\t;;\n\tesac\n}\n\nask_params()\n{\n\techo\n\techo NOTE ! this test should be run with zapret or any other bypass software disabled, without VPN\n\techo\n\t\n\tcurl_supports_connect_to || {\n\t\techo \"installed curl does not support --connect-to option. pls install at least curl 7.49\"\n\t\techo \"current curl version:\"\n\t\t\"$CURL\" --version\n\t\texitp 1\n\t}\n\n\tlocal dom\n\t[ -n \"$DOMAINS\" ] || {\n\t\tDOMAINS=\"$DOMAINS_DEFAULT\"\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo \"specify domain(s) to test. multiple domains are space separated. URIs are supported (rutracker.org/forum/index.php)\"\n\t\t\tprintf \"domain(s) (default: $DOMAINS) : \"\n\t\t\tread dom\n\t\t\t[ -n \"$dom\" ] && DOMAINS=\"$dom\"\n\t\t}\n\t}\n\tDOMAINS_COUNT=\"$(echo \"$DOMAINS\" | wc -w | trim)\"\n\n\tlocal IPVS_def=4\n\t[ -n \"$IPVS\" ] || {\n\t\t# yandex public dns\n\t\tpingtest 6 2a02:6b8::feed:0ff && IPVS_def=46\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\tprintf \"ip protocol version(s) - 4, 6 or 46 for both (default: $IPVS_def) : \"\n\t\t\tread IPVS\n\t\t}\n\t\t[ -n \"$IPVS\" ] || IPVS=$IPVS_def\n\t\t[ \"$IPVS\" = 4 -o \"$IPVS\" = 6 -o \"$IPVS\" = 46 ] || {\n\t\t\techo 'invalid ip version(s). should be 4, 6 or 46.'\n\t\t\texitp 1\n\t\t}\n\t}\n\t[ \"$IPVS\" = 46 ] && IPVS=\"4 6\"\n\n\tconfigure_curl_opt\n\n\t[ -n \"$ENABLE_HTTP\" ] || {\n\t\tENABLE_HTTP=1\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo\n\t\t\task_yes_no_var ENABLE_HTTP \"check http\"\n\t\t}\n\t}\n\n\t[ -n \"$ENABLE_HTTPS_TLS12\" ] || {\n\t\tENABLE_HTTPS_TLS12=1\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo\n\t\t\task_yes_no_var ENABLE_HTTPS_TLS12 \"check https tls 1.2\"\n\t\t}\n\t}\n\n\t[ -n \"$ENABLE_HTTPS_TLS13\" ] || {\n\t\tENABLE_HTTPS_TLS13=0\n\t\tif [ -n \"$TLS13\" ]; then\n\t\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\t\techo\n\t\t\t\techo \"TLS 1.3 uses encrypted ServerHello. DPI cannot check domain name in server response.\"\n\t\t\t\techo \"This can allow more bypass strategies to work.\"\n\t\t\t\techo \"What works for TLS 1.2 will also work for TLS 1.3 but not vice versa.\"\n\t\t\t\techo \"Most sites nowadays support TLS 1.3 but not all. If you can't find a strategy for TLS 1.2 use this test.\"\n\t\t\t\techo \"TLS 1.3 only strategy is better than nothing.\"\n\t\t\t\task_yes_no_var ENABLE_HTTPS_TLS13 \"check https tls 1.3\"\n\t\t\t}\n\t\telse\n\t\t\techo\n\t\t\techo \"installed curl version does not support TLS 1.3 . tests disabled.\"\n\t\tfi\n\t}\n\n\t[ -n \"$ENABLE_HTTP3\" ] || {\n\t\tENABLE_HTTP3=0\n\t\tif [ -n \"$HTTP3\" ]; then\n\t\t\tENABLE_HTTP3=1\n\t\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\t\techo\n\t\t\t\techo \"make sure target domain(s) support QUIC or result will be negative in any case\"\n\t\t\t\task_yes_no_var ENABLE_HTTP3 \"check http3 QUIC\"\n\t\t\t}\n\t\telse\n\t\t\techo\n\t\t\techo \"installed curl version does not support http3 QUIC. tests disabled.\"\n\t\tfi\n\t}\n\n\t[ -n \"$REPEATS\" ] || {\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo\n\t\t\techo \"sometimes ISPs use multiple DPIs or load balancing. bypass strategies may work unstable.\"\n\t\t\tprintf \"how many times to repeat each test (default: 1) : \"\n\t\t\tread REPEATS\n\t\t}\n\t\tREPEATS=$((0+${REPEATS:-1}))\n\t\t[ \"$REPEATS\" = 0 ] && {\n\t\t\techo invalid repeat count\n\t\t\texitp 1\n\t\t}\n\t}\n\t[ -z \"$PARALLEL\" -a $REPEATS -gt 1 ] && {\n\t\tPARALLEL=0\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo\n\t\t\techo \"parallel scan can greatly increase speed but may also trigger DDoS protection and cause false result\"\n\t\t\task_yes_no_var PARALLEL \"enable parallel scan\"\n\t\t}\n\t}\n\tPARALLEL=${PARALLEL:-0}\n\n\t[ -n \"$SCANLEVEL\" ] || {\n\t\tSCANLEVEL=standard\n\t\t[ \"$BATCH\" = 1 ] || {\n\t\t\techo\n\t\t\techo quick    - scan as fast as possible to reveal any working strategy\n\t\t\techo standard - do investigation what works on your DPI\n\t\t\techo force    - scan maximum despite of result\n\t\t\task_list SCANLEVEL \"quick standard force\" \"$SCANLEVEL\"\n\t\t\t# disable tpws checks by default in quick mode\n\t\t\t[ \"$SCANLEVEL\" = quick -a -z \"$SKIP_TPWS\" -a \"$UNAME\" != Darwin ] && SKIP_TPWS=1\n\t\t}\n\t}\n\n\techo\n\n\tconfigure_defrag\n}\n\n\n\nping_with_fix()\n{\n\tlocal ret\n\t$PING $2 $1 >/dev/null 2>/dev/null\n\tret=$?\n\t# can be because of unsupported -4 option\n\tif [ \"$ret\" = 2 -o \"$ret\" = 64 ]; then\n\t\tping $2 $1 >/dev/null\n\telse\n\t\treturn $ret\n\tfi\n}\n\npingtest()\n{\n\t# $1 - ip version : 4 or 6\n\t# $2 - domain or ip\n\n\t# ping command can vary a lot. some implementations have -4/-6 options. others don.t\n\t# WARNING ! macos ping6 command does not have timeout option. ping6 will fail\n\n\tlocal PING=ping ret\n\tif [ \"$1\" = 6 ]; then\n\t\tif exists ping6; then\n\t\t\tPING=ping6\n\t\telse\n\t\t\tPING=\"ping -6\"\n\t\tfi\n\telse\n\t\tif [ \"$UNAME\" = Darwin -o \"$UNAME\" = FreeBSD -o \"$UNAME\" = OpenBSD ]; then\n\t\t\t# ping by default pings ipv4, ping6 only pings ipv6\n\t\t\t# in FreeBSD -4/-6 options are supported, in others not\n\t\t\tPING=ping\n\t\telse\n\t\t\t# this can be linux or cygwin\n\t\t\t# in linux it's not possible for sure to figure out if it supports -4/-6. only try and check for result code=2 (invalid option)\n\t\t\tPING=\"ping -4\"\n\t\tfi\n\tfi\n\tcase \"$UNAME\" in\n\t\tDarwin)\n\t\t\t$PING -c 1 -t 1 $2 >/dev/null 2>/dev/null\n\t\t\t# WARNING ! macos ping6 command does not have timeout option. ping6 will fail. but without timeout is not an option.\n\t\t\t;;\n\t\tOpenBSD)\n\t\t\t$PING -c 1 -w 1 $2 >/dev/null\n\t\t\t;;\n\t\tCYGWIN)\n\t\t\tif starts_with \"$(which ping)\" /cygdrive; then\n\t\t\t\t# cygwin does not have own ping by default. use windows PING.\n\t\t\t\t$PING -n 1 -w 1000 $2 >/dev/null\n\t\t\telse\n\t\t\t\tping_with_fix $2 '-c 1 -w 1'\n\t\t\tfi\n\t\t\t;;\n\t\t*)\n\t\t\tping_with_fix $2 '-c 1 -W 1'\n\t\t\t;;\n\tesac\n}\ndnstest()\n{\n\t# $1 - dns server. empty for system resolver\n\t\"$LOOKUP\" iana.org $1 >/dev/null 2>/dev/null\n}\nfind_working_public_dns()\n{\n\tlocal dns\n\tfor dns in $DNSCHECK_DNS; do\n\t\tpingtest 4 $dns && dnstest $dns && {\n\t\t\tPUBDNS=$dns\n\t\t\treturn 0\n\t\t}\n\tdone\n\treturn 1\n}\nlookup4()\n{\n\t# $1 - domain\n\t# $2 - DNS\n\tcase \"$LOOKUP\" in\n\t\tnslookup)\n\t\t\tif is_linked_to_busybox nslookup; then\n\t\t\t\tnslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^.*:[^0-9]*(([0-9]{1,3}\\.){3}[0-9]{1,3}).*$/\\1/p'\n\t\t\telse\n\t\t\t\tnslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^[^0-9]*(([0-9]{1,3}\\.){3}[0-9]{1,3}).*$/\\1/p'\n\t\t\tfi\n\t\t\t;;\n\t\thost)\n\t\t\thost -t A $1 $2 | grep \"has address\" | grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}'\n\t\t\t;;\n\tesac\n}\ncheck_dns_spoof()\n{\n\t# $1 - domain\n\t# $2 - public DNS\n\n\t# windows version of mdig outputs 0D0A line ending. remove 0D.\n\techo $1 | \"$MDIG\" --family=4 | tr -d '\\r' >\"$DNSCHECK_DIG1\"\n\tlookup4 $1 $2 >\"$DNSCHECK_DIG2\"\n\t# check whether system resolver returns anything other than public DNS\n\tgrep -qvFf \"$DNSCHECK_DIG2\" \"$DNSCHECK_DIG1\"\n}\ncheck_dns_cleanup()\n{\n\trm -f \"$DNSCHECK_DIG1\" \"$DNSCHECK_DIG2\" \"$DNSCHECK_DIGS\" 2>/dev/null\n}\ncheck_dns_()\n{\n\tlocal C1 C2 dom\n\n\tDNS_IS_SPOOFED=0\n\n\t[ \"$SKIP_DNSCHECK\" = 1 ] && return 0\n\n\techo \\* checking DNS\n\n\t[ -f \"$DNSCHECK_DIGS\" ] && rm -f \"$DNSCHECK_DIGS\"\n\n\tdnstest || {\n\t\techo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access.\n\t\treturn 1\n\t}\n\techo system DNS is working\n\n\tif find_working_public_dns ; then\n\t\techo comparing system resolver to public DNS : $PUBDNS\n\t\tfor dom in $DNSCHECK_DOM; do\n\t\t\tif check_dns_spoof \"$dom\" $PUBDNS ; then\n\t\t\t\techo $dom : MISMATCH\n\t\t\t\techo -- system resolver :\n\t\t\t\tcat \"$DNSCHECK_DIG1\"\n\t\t\t\techo -- $PUBDNS :\n\t\t\t\tcat \"$DNSCHECK_DIG2\"\n\t\t\t\tcheck_dns_cleanup\n\t\t\t\techo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!!\n\t\t\t\techo -- DNS CHANGE OR DNSCRYPT MAY BE REQUIRED\n\t\t\t\tDNS_IS_SPOOFED=1\n\t\t\t\treturn 1\n\t\t\telse\n\t\t\t\techo $dom : OK\n\t\t\t\tcat \"$DNSCHECK_DIG1\" >>\"$DNSCHECK_DIGS\"\n\t\t\tfi\n\t\tdone\n\telse\n\t\techo no working public DNS was found. looks like public DNS blocked.\n\t\tfor dom in $DNSCHECK_DOM; do echo $dom; done | \"$MDIG\" --threads=10 --family=4 >\"$DNSCHECK_DIGS\"\n\tfi\n\n\techo \"checking resolved IP uniqueness for : $DNSCHECK_DOM\"\n\techo \"censor's DNS can return equal result for multiple blocked domains.\"\n\tC1=$(wc -l <\"$DNSCHECK_DIGS\")\n\tC2=$(sort -u \"$DNSCHECK_DIGS\" | wc -l)\n\t[ \"$C1\" -eq 0 ] &&\n\t{\n\t\techo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access.\n\t\tcheck_dns_cleanup\n\t\treturn 1\n\t}\n\t[ \"$C1\" = \"$C2\" ] ||\n\t{\n\t\techo system dns resolver has returned equal IPs for some domains checked above \\($C1 total, $C2 unique\\)\n\t\techo non-unique IPs :\n\t\tsort \"$DNSCHECK_DIGS\" | uniq -d\n\t\techo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!!\n\t\techo -- DNSCRYPT MAY BE REQUIRED\n\t\tcheck_dns_cleanup\n\t\tDNS_IS_SPOOFED=1\n\t\treturn 1\n\t}\n\techo all resolved IPs are unique\n\techo -- DNS looks good\n\techo -- NOTE this check is Russia targeted. In your country other domains may be blocked.\n\tcheck_dns_cleanup\n\treturn 0\n}\n\ncheck_dns()\n{\n\tlocal r\n\tcheck_dns_\n\tr=$?\n\t[ \"$DNS_IS_SPOOFED\" = 1 ] && SECURE_DNS=${SECURE_DNS:-1}\n\t[ \"$SECURE_DNS\" = 1 ] && {\n\t\tdoh_find_working || {\n\t\t\techo could not find working DoH server. exiting.\n\t\t\texitp 7\n\t\t}\n\t}\n\treturn $r\n}\n\nunprepare_all()\n{\n\t# make sure we are not in a middle state that impacts connectivity\n\tws_kill\n\twait\n\t[ -n \"$IPV\" ] && {\n\t\tpktws_ipt_unprepare_tcp $HTTP_PORT\n\t\tpktws_ipt_unprepare_tcp $HTTPS_PORT\n\t\tpktws_ipt_unprepare_udp $QUIC_PORT\n\t}\n\tcleanup\n\trm -f \"${HDRTEMP}\"* \"${PARALLEL_OUT}\"*\n}\nsigint()\n{\n\techo\n\techo terminating...\n\tunprepare_all\n\texitp 1\n}\nsigint_cleanup()\n{\n\tcleanup\n\texit 1\n}\nsigsilent()\n{\n\t# must not write anything here to stdout\n\tunprepare_all\n\texit 1\n}\n\nfsleep_setup\nfix_sbin_path\ncheck_system\ncheck_already\n# no divert sockets in MacOS\n[ \"$UNAME\" = \"Darwin\" ] && SKIP_PKTWS=1\n[ \"$UNAME\" != CYGWIN  -a \"$SKIP_PKTWS\" != 1 ] && require_root\ncheck_prerequisites\ntrap sigint_cleanup INT\ncheck_dns\ncheck_virt\nask_params\ntrap - INT\n\nPID=\nNREPORT=\nunset WF\ntrap sigint INT\ntrap sigsilent PIPE\ntrap sigsilent HUP\nfor dom in $DOMAINS; do\n\tfor IPV in $IPVS; do\n\t\tconfigure_ip_version\n\t\t[ \"$ENABLE_HTTP\" = 1 ] && {\n\t\t\t[ \"$SKIP_IPBLOCK\" = 1 ] || check_domain_port_block $dom $HTTP_PORT\n\t\t\tcheck_domain_http $dom\n\t\t}\n\t\t[ \"$ENABLE_HTTPS_TLS12\" = 1 -o \"$ENABLE_HTTPS_TLS13\" = 1 ] && [ \"$SKIP_IPBLOCK\" != 1 ] && check_domain_port_block $dom $HTTPS_PORT\n\t\t[ \"$ENABLE_HTTPS_TLS12\" = 1 ] && check_domain_https_tls12 $dom\n\t\t[ \"$ENABLE_HTTPS_TLS13\" = 1 ] && check_domain_https_tls13 $dom\n\t\t[ \"$ENABLE_HTTP3\" = 1 ] && check_domain_http3 $dom\n\tdone\ndone\ntrap - HUP\ntrap - PIPE\ntrap - INT\n\ncleanup\n\necho\necho \\* SUMMARY\nreport_print\n[ \"$DOMAINS_COUNT\" -gt 1 ] && {\n\techo\n\techo \\* COMMON\n\tresult_intersection_print\n\techo\n\t[ \"$SCANLEVEL\" = force ] || {\n\t\techo \"blockcheck optimizes test sequence. To save time some strategies can be skipped if their test is considered useless.\"\n\t\techo \"That's why COMMON intersection can miss strategies that would work for all domains.\"\n\t\techo \"Use \\\"force\\\" scan level to test all strategies and generate trustable intersection.\"\n\t\techo \"Current scan level was \\\"$SCANLEVEL\\\"\".\n\t}\n}\necho\necho \"Please note this SUMMARY does not guarantee a magic pill for you to copy/paste and be happy.\"\necho \"Understanding how strategies work is very desirable.\"\necho \"This knowledge allows to understand better which strategies to prefer and which to avoid if possible, how to combine strategies.\"\necho \"Blockcheck does it's best to prioritize good strategies but it's not bullet-proof.\"\necho \"It was designed not as magic pill maker but as a DPI bypass test tool.\"\n\nexitp 0\n"
  },
  {
    "path": "common/base.sh",
    "content": "which()\n{\n\t# on some systems 'which' command is considered deprecated and not installed by default\n\t# 'command -v' replacement does not work exactly the same way. it outputs shell aliases if present\n\t# $1 - executable name\n\tlocal IFS=:\n\t[ \"$1\" != \"${1#/}\" ] && [ -x \"$1\" ] && {\n\t\techo \"$1\"\n\t\treturn 0\n\t}\n\tfor p in $PATH; do\n\t    [ -x \"$p/$1\" ] && {\n\t\techo \"$p/$1\"\n\t\treturn 0\n\t    }\n\tdone\n\treturn 1\n}\nexists()\n{\n\twhich \"$1\" >/dev/null 2>/dev/null\n}\nexistf()\n{\n\ttype \"$1\" >/dev/null 2>/dev/null\n}\nwhichq()\n{\n\twhich $1 2>/dev/null\n}\nexist_all()\n{\n\twhile [ -n \"$1\" ]; do\n\t\texists \"$1\" || return 1\n\t\tshift\n\tdone\n\treturn 0\n}\non_off_function()\n{\n\t# $1 : function name on\n\t# $2 : function name off\n\t# $3 : 0 - off, 1 - on\n\tlocal F=\"$1\"\n\t[ \"$3\" = \"1\" ] || F=\"$2\"\n\tshift\n\tshift\n\tshift\n\t\"$F\" \"$@\"\n}\ncontains()\n{\n\t# check if substring $2 contains in $1\n\t[ \"${1#*$2}\" != \"$1\" ]\n}\nstarts_with()\n{\n\t# $1 : what\n\t# $2 : starts with\n\tcase \"$1\" in\n\t\t\"$2\"*)\n\t\t\treturn 0\n\t\t\t;;\n\tesac\n\treturn 1\n}\nextract_arg()\n{\n\t# $1 - arg number\n\t# $2,$3,... - args\n        local n=$1\n        while [ -n \"$1\" ]; do\n                shift\n                [ $n -eq 1 ] && { echo \"$1\"; return 0; }\n                n=$(($n-1))\n        done\n        return 1\n}\nfind_str_in_list()\n{\n\t# $1 - string\n\t# $2 - space separated values\n\tlocal v\n\t[ -n \"$1\" ] && {\n\t\tfor v in $2; do\n\t\t\t[ \"$v\" = \"$1\" ] && return 0\n\t\tdone\n\t}\n\treturn 1\n}\nend_with_newline()\n{\n\tlocal c=\"$(tail -c 1)\"\n\t[ \"$c\" = \"\" ]\n}\ntrim()\n{\n\tawk '{gsub(/^ +| +$/,\"\")}1'\n}\nsplit_by_separator()\n{\n\t# $1 - string\n\t# $2 - separator\n\t# $3 - var name to get \"before\" part\n\t# $4 - var name to get \"after\" part\n\tlocal before=\"${1%%$2*}\"\n\tlocal after=\"${1#*$2}\"\n\t[ \"$after\" = \"$1\" ] && after=\n\t[ -n \"$3\" ] && eval $3=\"\\$before\"\n\t[ -n \"$4\" ] && eval $4=\"\\$after\"\n}\ntolower()\n{\n\techo \"$@\" | tr 'A-Z' 'a-z'\n}\n\ndir_is_not_empty()\n{\n\t# $1 - directory\n\tlocal n\n\t[ -d \"$1\" ] || return 1\n\tn=$(ls \"$1\" | wc -c | xargs)\n\t[ \"$n\" != 0 ]\n}\n\nappend_separator_list()\n{\n\t# $1 - var name to receive result\n\t# $2 - separator\n\t# $3 - quoter\n\t# $4,$5,... - elements\n\tlocal _var=\"$1\" sep=\"$2\" quo=\"$3\" i\n\n\teval i=\"\\$$_var\"\n\tshift; shift; shift\n\twhile [ -n \"$1\" ]; do\n\t\tif [ -n \"$i\" ] ; then\n\t\t\ti=\"$i$sep$quo$1$quo\"\n\t\telse\n\t\t\ti=\"$quo$1$quo\"\n\t\tfi\n\t\tshift\n\tdone\n\teval $_var=\"\\$i\"\n}\nmake_separator_list()\n{\n\teval $1=''\n\tappend_separator_list \"$@\"\n}\nmake_comma_list()\n{\n\t# $1 - var name to receive result\n\t# $2,$3,... - elements\n\tlocal var=\"$1\"\n\tshift\n\tmake_separator_list $var , '' \"$@\"\n}\nmake_quoted_comma_list()\n{\n\t# $1 - var name to receive result\n\t# $2,$3,... - elements\n\tlocal var=\"$1\"\n\tshift\n\tmake_separator_list $var , '\"' \"$@\"\n}\nunique()\n{\n\tlocal i\n\tfor i in \"$@\"; do echo $i; done | sort -u | xargs\n}\n\nis_linked_to_busybox()\n{\n\tlocal IFS F P\n\t\n\tIFS=:\n\tfor path in $PATH; do\n\t\tF=$path/$1\n\t\tP=\"$(readlink $F)\"\n\t\tif [ -z \"$P\" ] && [ -x $F ] && [ ! -L $F ]; then return 1; fi\n\t\t[ \"${P%busybox*}\" != \"$P\" ] && return\n\tdone\n}\nget_dir_inode()\n{\n\tlocal dir=\"$1\"\n\t[ -L \"$dir\" ] && dir=$(readlink \"$dir\")\n\tls -id \"$dir\" | awk '{print $1}'\n}\n\nlinux_min_version()\n{\n\t# $1 - major ver\n\t# $2 - minor ver\n\tlocal V1=$(sed -nre 's/^Linux version ([0-9]+)\\.[0-9]+.*$/\\1/p' /proc/version)\n\tlocal V2=$(sed -nre 's/^Linux version [0-9]+\\.([0-9]+).*$/\\1/p' /proc/version)\n\t[ -n \"$V1\" -a -n \"$V2\" ] && [ \"$V1\" -gt \"$1\" -o \"$V1\" -eq \"$1\" -a \"$V2\" -ge \"$2\" ]\n}\nlinux_get_subsys()\n{\n\tlocal INIT=\"$(sed 's/\\x0/\\n/g' /proc/1/cmdline | head -n 1)\"\n\n\t[ -L \"$INIT\" ] && INIT=$(readlink \"$INIT\")\n\tINIT=\"$(basename \"$INIT\")\"\n\tif [ -f \"/etc/openwrt_release\" ] && [ \"$INIT\" = \"procd\" ] ; then\n\t\tSUBSYS=openwrt\n\telif [ -x \"/bin/ndm\" ] ; then\n\t\tSUBSYS=keenetic\n\telse\n\t\t# generic linux\n\t\tSUBSYS=\n\tfi\n}\nopenwrt_fw3()\n{\n\t[ ! -x /sbin/fw4 -a -x /sbin/fw3 ]\n}\nopenwrt_fw4()\n{\n\t[ -x /sbin/fw4 ]\n}\nopenwrt_fw3_integration()\n{\n\t[ \"$FWTYPE\" = iptables ] && openwrt_fw3\n}\n\ncreate_dev_stdin()\n{\n\t[ -e /dev/stdin ] || ln -s /proc/self/fd/0 /dev/stdin\n}\n\ncall_for_multiple_items()\n{\n\t# $1 - function to get an item\n\t# $2 - variable name to put result into\n\t# $3 - space separated parameters to function $1\n\n\tlocal i item items\n\tfor i in $3; do\n\t\t$1 item $i\n\t\t[ -n \"$item\" ] && {\n\t\t\tif [ -n \"$items\" ]; then\n\t\t\t\titems=\"$items $item\"\n\t\t\telse\n\t\t\t\titems=\"$item\"\n\t\t\tfi\n\t\t}\n\tdone\n\teval $2=\\\"$items\\\"\n}\n\nfix_sbin_path()\n{\n\tlocal IFS=':'\n\tprintf \"%s\\n\" $PATH | grep -Fxq '/usr/sbin' || PATH=\"/usr/sbin:$PATH\"\n\tprintf \"%s\\n\" $PATH | grep -Fxq '/sbin' || PATH=\"/sbin:$PATH\"\n\texport PATH\n}\n\n# it can calculate floating point expr\ncalc()\n{\n\tLC_ALL=C awk \"BEGIN { print $*}\";\n}\n\nfsleep_setup()\n{\n    [ -n \"$FSLEEP\" ] || {\n\tif sleep 0.001 2>/dev/null; then\n\t\tFSLEEP=1\n\telif busybox usleep 1 2>/dev/null; then\n\t\tFSLEEP=2\n\telse\n\t\tlocal errtext=\"$(read -t 0.001 2>&1)\"\n\t\tif [ -z \"$errtext\" ]; then\n\t\t\tFSLEEP=3\n\t\t# newer openwrt has ucode with system function that supports timeout in ms\n\t\telif ucode -e \"system(['sleep','1'], 1)\" 2>/dev/null; then\n\t\t\tFSLEEP=4\n\t\t# older openwrt may have lua and nixio lua module\n\t\telif lua -e 'require \"nixio\".nanosleep(0,1)' 2>/dev/null ; then\n\t\t\tFSLEEP=5\n\t\telse\n\t\t\tFSLEEP=0\n\t\tfi\n\tfi\n    }\n}\nmsleep()\n{\n    # $1 - milliseconds\n    case \"$FSLEEP\" in\n\t1)\n\t\tsleep $(calc $1/1000)\n\t\t;;\n\t2)\n\t\tbusybox usleep $(calc $1*1000)\n\t\t;;\n\t3)\n\t\tread -t $(calc $1/1000)\n\t\t;;\n\t4)\n\t\tucode -e \"system(['sleep','2147483647'], $1)\"\n\t\t;;\n\t5)\n\t\tlua -e \"require 'nixio'.nanosleep($(($1/1000)),$(calc $1%1000*1000000))\"\n\t\t;;\n    \t*)\n\t\tsleep $((($1+999)/1000))\n    esac\n}\nminsleep()\n{\n\tmsleep 100\n}\n\nreplace_char()\n{\n\tlocal a=\"$1\"\n\tlocal b=\"$2\"\n\tshift; shift\n\techo \"$@\" | tr \"$a\" \"$b\"\n}\n\nreplace_str()\n{\n\tlocal a=$(echo \"$1\" | sed 's/\\//\\\\\\//g')\n\tlocal b=$(echo \"$2\" | sed 's/\\//\\\\\\//g')\n\tshift; shift\n\techo \"$@\" | sed \"s/$a/$b/g\"\n}\n\nsetup_md5()\n{\n\t[ -n \"$MD5\" ] && return\n\tMD5=md5sum\n\texists $MD5 || MD5=md5\n}\n\nmd5f()\n{\n\tsetup_md5\n\t$MD5 | cut -d ' ' -f1\n}\n\nsetup_random()\n{\n\t[ -n \"$RCUT\" ] && return\n\tRCUT=\"cut -c 1-17\"\n\t# some shells can operate with 32 bit signed int\n\t[ $((0x100000000)) = 0 ] && RCUT=\"cut -c 1-9\"\n}\n\nrandom()\n{\n\t# $1 - min, $2 - max\n\tlocal r rs\n\tsetup_random\n\tif [ -c /dev/urandom ]; then\n\t\tread rs </dev/urandom\n\telse\n\t\trs=\"$RANDOM$RANDOM$(date)\"\n\tfi\n\t# shells use signed int64\n\tr=1$(echo $rs | md5f | sed 's/[^0-9]//g' | $RCUT)\n\techo $(( ($r % ($2-$1+1)) + $1 ))\n}\n\nshell_name()\n{\n\t[ -n \"$SHELL_NAME\" ] || {\n\t\t[ -n \"$UNAME\" ] || UNAME=\"$(uname)\"\n\n\t\tif [ \"$UNAME\" = \"Linux\" ]; then\n\t\t\tSHELL_NAME=\"$(readlink /proc/$$/exe)\"\n\t\t\tSHELL_NAME=\"$(basename \"$SHELL_NAME\")\"\n\t\telse\n\t\t\tSHELL_NAME=$(ps -p $$ -o comm=)\n\t\tfi\n\n\t\t[ -n \"$SHELL_NAME\" ] || SHELL_NAME=\"$(basename \"$SHELL\")\"\n\t}\n}\n\nprocess_exists()\n{\n\tif exists pgrep; then\n\t\tpgrep ^$1$ >/dev/null\n\telif exists pidof; then\n\t\tpidof $1 >/dev/null\n\telse\n\t\treturn 1\n\tfi \n}\n\nwin_process_exists()\n{\n\ttasklist /NH /FI \"IMAGENAME eq ${1}.exe\" | grep -q \"^${1}.exe\"\n}\n\nalloc_num()\n{\n\t# $1 - source var name\n\t# $2 - target var name\n\t# $3 - min\n\t# $4 - max\n\t\n\tlocal v\n\teval v=\"\\$$2\"\n\t# do not replace existing value\n\t[ -n \"$v\" ] && return\n\teval v=\"\\$$1\"\n\t[ -n \"$v\" ] || v=$3\n\teval $2=\"$v\"\n\tv=$((v + 1))\n\t[ $v -gt $4 ] && v=$3\n\teval $1=\"$v\"\n}\n\nstd_ports()\n{\n\tTPWS_PORTS_IPT=$(replace_char - : $TPWS_PORTS)\n\tNFQWS_PORTS_TCP_IPT=$(replace_char - : $NFQWS_PORTS_TCP)\n\tNFQWS_PORTS_TCP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_TCP_KEEPALIVE)\n\tNFQWS_PORTS_UDP_IPT=$(replace_char - : $NFQWS_PORTS_UDP)\n\tNFQWS_PORTS_UDP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_UDP_KEEPALIVE)\n}\n\nhas_bad_ws_options()\n{\n\t# $1 - nfqws/tpws opts\n\n\tcontains \"$1\" \"--ipset\" && {\n\t\techo\n\t\techo \"WARNING !!! --ipset parameter is present\"\n\t\techo \"It's OK if you only specialize already redirected traffic and also process the rest.\"\n\t\techo \"If you redirect port X to process several IPs from the list and do nothing with the rest - IT'S VERY INEFFECTIVE !\"\n\t\techo \"Kernel ipsets should be used instead. Write custom scripts and filter IPs in kernel.\"\n\t\techo\n\t}\n\t\n\treturn 1\n}\ncheck_bad_ws_options()\n{\n\t# $1 - 0 = stop, 1 = start\n\t# $2 - nfqws/tpws options\n\tif [ \"$1\" = 1 ] && has_bad_ws_options \"$2\"; then\n\t\techo \"!!! REFUSING TO USE BAD OPTIONS : $2\"\n\t\thelp_bad_ws_options\n\t\treturn 1\n\telse\n\t\treturn 0\n\tfi\n}\nhelp_bad_ws_options()\n{\n\techo \"WARNING ! BAD options detected\"\n}\n"
  },
  {
    "path": "common/custom.sh",
    "content": "custom_runner()\n{\n\t# $1 - function name\n\t# $2+ - params\n\n\t[ \"$DISABLE_CUSTOM\" = 1 ] && return 0\n\n\tlocal n script FUNC=$1\n\n\tshift\n\n\t[ -d \"$CUSTOM_DIR/custom.d\" ] && {\n\t\tdir_is_not_empty \"$CUSTOM_DIR/custom.d\" && {\n\t\t\tfor script in \"$CUSTOM_DIR/custom.d/\"*; do\n\t\t\t\t[ -f \"$script\" ] || continue\n\t\t\t\tunset -f $FUNC\n\t\t\t\t. \"$script\"\n\t\t\t\texistf $FUNC && $FUNC \"$@\"\n\t\t\tdone\n\t\t}\n\t}\n}\n\nalloc_tpws_port()\n{\n\t# $1 - target var name\n\talloc_num NUMPOOL_TPWS_PORT $1 910 979\n}\nalloc_qnum()\n{\n\t# $1 - target var name\n\talloc_num NUMPOOL_QNUM $1 65400 65499\n}\nalloc_dnum()\n{\n\t# alloc daemon number\n\t# $1 - target var name\n\talloc_num NUMPOOL_DNUM $1 1000 1999\n}\n"
  },
  {
    "path": "common/dialog.sh",
    "content": "read_yes_no()\n{\n\t# $1 - default (Y/N)\n\tlocal A\n\tread A\n\t[ -z \"$A\" ] || ([ \"$A\" != \"Y\" ] && [ \"$A\" != \"y\" ] && [ \"$A\" != \"N\" ] && [ \"$A\" != \"n\" ]) && A=$1\n\t[ \"$A\" = \"Y\" ] || [ \"$A\" = \"y\" ] || [ \"$A\" = \"1\" ]\n}\nask_yes_no()\n{\n\t# $1 - default (Y/N or 0/1)\n\t# $2 - text\n\tlocal DEFAULT=$1\n\t[ \"$1\" = \"1\" ] && DEFAULT=Y\n\t[ \"$1\" = \"0\" ] && DEFAULT=N\n\t[ -z \"$DEFAULT\" ] && DEFAULT=N\n\tprintf \"$2 (default : $DEFAULT) (Y/N) ? \"\n\tread_yes_no $DEFAULT\n}\nask_yes_no_var()\n{\n\t# $1 - variable name for answer : 0/1\n\t# $2 - text\n\tlocal DEFAULT\n\teval DEFAULT=\"\\$$1\"\n\tif ask_yes_no \"$DEFAULT\" \"$2\"; then\n\t\teval $1=1\n\telse\n\t\teval $1=0\n\tfi\n}\nask_list()\n{\n\t# $1 - mode var\n\t# $2 - space separated value list\n\t# $3 - (optional) default value\n\tlocal M_DEFAULT\n\teval M_DEFAULT=\"\\$$1\"\n\tlocal M_DEFAULT_VAR=\"$M_DEFAULT\"\n\tlocal M=\"\" m\n\n\t[ -n \"$3\" ] && { find_str_in_list \"$M_DEFAULT\" \"$2\" || M_DEFAULT=\"$3\" ;}\n\n\tn=1\n\tfor m in $2; do\n\t\techo $n : $m\n\t\tn=$(($n+1))\n\tdone\n\tprintf \"your choice (default : $M_DEFAULT) : \"\n\tread m\n\t[ -n \"$m\" ] && M=$(echo $2 | cut -d ' ' -f$m 2>/dev/null)\n\t[ -z \"$M\" ] && M=\"$M_DEFAULT\"\n\techo selected : $M\n\teval $1=\"\\\"$M\\\"\"\n\n\t[ \"$M\" != \"$M_DEFAULT_VAR\" ]\n}\n"
  },
  {
    "path": "common/elevate.sh",
    "content": "require_root()\n{\n\tlocal exe preserve_env\n\techo \\* checking privileges\n\t[ $(id -u) -ne \"0\" ] && {\n\t\techo root is required\n\t\texe=\"$EXEDIR/$(basename \"$0\")\"\n\t\texists sudo && {\n\t\t\techo elevating with sudo\n\t\t\texec sudo -E sh \"$exe\"\n\t\t}\n\t\texists su && {\n\t\t\techo elevating with su\n\t\t\tcase \"$UNAME\" in\n\t\t\t\tLinux)\n\t\t\t\t\tpreserve_env=\"--preserve-environment\"\n\t\t\t\t\t;;\n\t\t\t\tFreeBSD|OpenBSD|Darwin)\n\t\t\t\t\tpreserve_env=\"-m\"\n\t\t\t\t\t;;\n\t\t\tesac\n\t\t\texec su $preserve_env root -c \"sh \\\"$exe\\\"\"\n\t\t}\n\t\techo su or sudo not found\n\t\texitp 2\n\t}\n\tHAVE_ROOT=1\n}\n"
  },
  {
    "path": "common/fwtype.sh",
    "content": "linux_ipt_avail()\n{\n\texists iptables && exists ip6tables\n}\nlinux_maybe_iptables_fwtype()\n{\n\tlinux_ipt_avail && FWTYPE=iptables\n}\nlinux_nft_avail()\n{\n\texists nft\n}\nlinux_fwtype()\n{\n\t[ -n \"$FWTYPE\" ] && return\n\n\tFWTYPE=unsupported\n\n\tlinux_get_subsys\n\tif [ \"$SUBSYS\" = openwrt ] ; then\n\t\t# linux kernel is new enough if fw4 is there\n\t\tif [ -x /sbin/fw4 ] && linux_nft_avail ; then\n\t\t\tFWTYPE=nftables\n\t\telse\n\t\t\tlinux_maybe_iptables_fwtype\n\t\tfi\n\telse\n\t\tSUBSYS=\n\t\t# generic linux\n\t\t# flowtable is implemented since kernel 4.16\n\t\tif linux_nft_avail && linux_min_version 4 16; then\n\t\t\tFWTYPE=nftables\n\t\telse\n\t\t\tlinux_maybe_iptables_fwtype\n\t\tfi\n\tfi\n\n\texport FWTYPE\n}\n\nget_fwtype()\n{\n\t[ -n \"$FWTYPE\" ] && return\n\n\tlocal UNAME=\"$(uname)\"\n\n\tcase \"$UNAME\" in\n\t\tLinux)\n\t\t\tlinux_fwtype\n\t\t\t;;\n\t\tFreeBSD)\n\t\t\tif exists ipfw ; then\n\t\t\t\tFWTYPE=ipfw\n\t\t\telse\n\t\t\t\tFWTYPE=unsupported\n\t\t\tfi\n\t\t\t;;\n\t\t*)\n\t\t\tFWTYPE=unsupported\n\t\t\t;;\n\tesac\n\n\texport FWTYPE\n}\n"
  },
  {
    "path": "common/installer.sh",
    "content": "GET_LIST_PREFIX=/ipset/get_\n\nSYSTEMD_DIR=/lib/systemd\n[ -d \"$SYSTEMD_DIR\" ] || SYSTEMD_DIR=/usr/lib/systemd\n[ -d \"$SYSTEMD_DIR\" ] && SYSTEMD_SYSTEM_DIR=\"$SYSTEMD_DIR/system\"\n\nINIT_SCRIPT=/etc/init.d/zapret\n\n\nexitp()\n{\n\techo\n\techo press enter to continue\n\tread A\n\texit $1\n}\n\nextract_var_def()\n{\n\t# $1 - var name\n\t# this sed script parses single or multi line shell var assignments with optional ' or \" enclosure\n\tsed -n \\\n\"/^$1=\\\"/ {\n:s1\n/\\\".*\\\"/ {\n p\n b\n}\nN\nt c1\nb s1\n:c1\n}\n/^$1='/ {\n:s2\n/'.*'/ {\n p\n b\n}\nN\nt c2\nb s2\n:c2\n}\n/^$1=/p\n\"\n}\nreplace_var_def()\n{\n\t# $1 - var name\n\t# $2 - new val\n\t# $3 - conf file\n\t# this sed script replaces single or multi line shell var assignments with optional ' or \" enclosure\n\tlocal repl\n\tif [ -z \"$2\" ]; then\n\t\trepl=\"#$1=\"\n\telif contains \"$2\" \" \"; then\n\t\trepl=\"$1=\\\"$2\\\"\"\n\telse\n\t\trepl=\"$1=$2\"\n\tfi\n\tlocal script=\\\n\"/^#*[[:space:]]*$1=\\\"/ {\n:s1\n/\\\".*\\\"/ {\n c\\\\\n$repl\n b\n}\nN\nt c1\nb s1\n:c1\n}\n/^#*[[:space:]]*$1='/ {\n:s2\n/'.*'/ {\n c\\\\\n$repl\n b\n}\nN\nt c2\nb s2\n:c2\n}\n/^#*[[:space:]]*$1=/c\\\\\n$repl\"\n\t# there's incompatibility with -i option on MacOS/BSD and busybox/GNU\n\tif [ \"$UNAME\" = \"Linux\" ]; then\n\t\tsed -i -e \"$script\" \"$3\"\n\telse\n\t\tsed -i '' -e \"$script\" \"$3\"\n\tfi\n}\n\nparse_var_checked()\n{\n\t# $1 - file name\n\t# $2 - var name\n\n\tlocal tmp=\"/tmp/zvar-pid-$$.sh\"\n\tlocal v\n\tcat \"$1\" | extract_var_def \"$2\" >\"$tmp\"\n\t. \"$tmp\"\n\trm -f \"$tmp\"\n\teval v=\"\\$$2\"\n\t# trim\n\tv=\"$(echo \"$v\" | trim)\"\n\teval $2=\\\"\"$v\"\\\"\n}\nparse_vars_checked()\n{\n\t# $1 - file name\n\t# $2,$3,... - var names\n\tlocal f=\"$1\"\n\tshift\n\twhile [ -n \"$1\" ]; do\n\t\tparse_var_checked \"$f\" $1\n\t\tshift\n\tdone\t\n}\nedit_file()\n{\n\t# $1 - file name\n\tlocal ed=\"$EDITOR\"\n\t[ -n \"$ed\" ] || {\n\t\tfor e in mcedit nano vim vi; do\n\t\t\texists \"$e\" && {\n\t\t\t\ted=\"$e\"\n\t\t\t\tbreak\n\t\t\t}\n\t\tdone\n\t}\n\t[ -n \"$ed\" ] && \"$ed\" \"$1\"\n}\necho_var()\n{\n\tlocal v\n\teval v=\"\\$$1\"\n\tif find_str_in_list $1 \"$EDITVAR_NEWLINE_VARS\"; then\n\t\techo \"$1=\\\"\"\n\t\techo \"$v\\\"\" | tr '\\n' ' ' | tr -d '\\r' | sed -e 's/^ *//' -e 's/ *$//' -e \"s/$EDITVAR_NEWLINE_DELIMETER /$EDITVAR_NEWLINE_DELIMETER\\n/g\"\n\telse\n\t\tif contains \"$v\" \" \"; then\n\t\t\techo $1=\\\"$v\\\"\n\t\telse\n\t\t\techo $1=$v\n\t\tfi\n\tfi\n}\nedit_vars()\n{\n\t# $1,$2,... - var names\n\tlocal n=1 var tmp=\"/tmp/zvars-pid-$$.txt\"\n\trm -f \"$tmp\"\n\twhile : ; do\n\t\teval var=\"\\${$n}\"\n\t\t[ -n \"$var\" ] || break\n\t\techo_var $var >> \"$tmp\"\n\t\tn=$(($n+1))\n\tdone\n\tedit_file \"$tmp\" && parse_vars_checked \"$tmp\" \"$@\"\n\trm -f \"$tmp\"\n}\n\nlist_vars()\n{\n\twhile [ -n \"$1\" ] ; do\n\t\techo_var $1\n\t\tshift\n\tdone\n\techo\n}\n\nopenrc_test()\n{\n\texists rc-update || return 1\n\t# some systems do not usse openrc-init but launch openrc from inittab\n\t[ \"$INIT\" = \"openrc-init\" ] || grep -qE \"sysinit.*openrc\" /etc/inittab 2>/dev/null\n}\ncheck_system()\n{\n\t# $1 - nonempty = do not fail on unknown rc system\n\n\techo \\* checking system\n\n\tSYSTEM=\n\tSUBSYS=\n\tSYSTEMCTL=\"$(whichq systemctl)\"\n\n\tget_fwtype\n\tOPENWRT_FW3=\n\tOPENWRT_FW4=\n\n\tlocal info\n\tUNAME=$(uname)\n\tif [ \"$UNAME\" = \"Linux\" ]; then\n\t\t# do not use 'exe' because it requires root\n\t\tlocal INIT=\"$(sed 's/\\x0/\\n/g' /proc/1/cmdline | head -n 1)\"\n\t\t[ -L \"$INIT\" ] && INIT=$(readlink \"$INIT\")\n\t\tINIT=\"$(basename \"$INIT\")\"\n\t\t# some distros include systemctl without systemd\n\t\tif [ -d \"$SYSTEMD_DIR\" ] && [ -x \"$SYSTEMCTL\" ] && [ \"$INIT\" = \"systemd\" ]; then\n\t\t\tSYSTEM=systemd\n\t\t\t[ -f \"$EXEDIR/init.d/sysv/functions\" ] && . \"$EXEDIR/init.d/sysv/functions\"\n\t\telif [ -f \"/etc/openwrt_release\" ] && exists opkg || exists apk && exists uci && [ \"$INIT\" = \"procd\" ] ; then\n\t\t\tSYSTEM=openwrt\n\t\t\tOPENWRT_PACKAGER=opkg\n\t\t\tOPENWRT_PACKAGER_INSTALL=\"opkg install\"\n\t\t\tOPENWRT_PACKAGER_UPDATE=\"opkg update\"\n\t\t\texists apk && {\n\t\t\t\tOPENWRT_PACKAGER=apk\n\t\t\t\tOPENWRT_PACKAGER_INSTALL=\"apk add\"\n\t\t\t\tOPENWRT_PACKAGER_UPDATE=\n\t\t\t}\n\t\t\tinfo=\"package manager $OPENWRT_PACKAGER\\n\"\n\t\t\tif openwrt_fw3 ; then\n\t\t\t\tOPENWRT_FW3=1\n\t\t\t\tinfo=\"${info}firewall fw3\"\n\t\t\t\tif is_ipt_flow_offload_avail; then\n\t\t\t\t\tinfo=\"$info. hardware flow offloading requires iptables.\"\n\t\t\t\telse\n\t\t\t\t\tinfo=\"$info. flow offloading unavailable.\"\n\t\t\t\tfi\n\t\t\telif openwrt_fw4; then\n\t\t\t\tOPENWRT_FW4=1\n\t\t\t\tinfo=\"${info}firewall fw4. flow offloading requires nftables.\"\n\t\t\tfi\n\t\t\t[ -f \"$EXEDIR/init.d/openwrt/functions\" ] && . \"$EXEDIR/init.d/openwrt/functions\"\n\t\telif openrc_test; then\n\t\t\tSYSTEM=openrc\n\t\t\t[ -f \"$EXEDIR/init.d/sysv/functions\" ] && . \"$EXEDIR/init.d/sysv/functions\"\n\t\telse\n\t\t\techo system is not either systemd, openrc or openwrt based\n\t\t\techo easy installer can set up config settings but can\\'t configure auto start\n\t\t\techo you have to do it manually. check readme.md for manual setup info.\n\t\t\tif [ -n \"$1\" ] || ask_yes_no N \"do you want to continue\"; then\n\t\t\t    SYSTEM=linux\n\t\t\telse\n\t\t\t    exitp 5\n\t\t\tfi\n\t\t\t[ -f \"$EXEDIR/init.d/sysv/functions\" ] && . \"$EXEDIR/init.d/sysv/functions\"\n\t\tfi\n\t\tlinux_get_subsys\n\telif [ \"$UNAME\" = \"Darwin\" ]; then\n\t\tSYSTEM=macos\n\t\t[ -f \"$EXEDIR/init.d/macos/functions\" ] && . \"$EXEDIR/init.d/macos/functions\"\n\telse\n\t\techo easy installer only supports Linux and MacOS. check readme.md for supported systems and manual setup info.\n\t\texitp 5\n\tfi\n\techo system is based on $SYSTEM\n\t[ -n \"$info\" ] && printf \"${info}\\n\"\n}\n\nget_free_space_mb()\n{\n    df -m \"$1\" | awk '/[0-9]%/{print $(NF-2)}'\n}\nget_ram_kb()\n{\n    grep MemTotal /proc/meminfo | awk '{print $2}'\n}\nget_ram_mb()\n{\n    local R=$(get_ram_kb)\n    echo $(($R/1024))\n}\n\ncrontab_del()\n{\n\texists crontab || return\n\n\techo \\* removing crontab entry\n\n\tCRONTMP=/tmp/cron.tmp\n\tcrontab -l >$CRONTMP 2>/dev/null\n\tif grep -q \"$GET_LIST_PREFIX\" $CRONTMP; then\n\t\techo removing following entries from crontab :\n\t\tgrep \"$GET_LIST_PREFIX\" $CRONTMP\n\t\tgrep -v \"$GET_LIST_PREFIX\" $CRONTMP >$CRONTMP.2\n\t\tcrontab $CRONTMP.2\n\t\trm -f $CRONTMP.2\n\tfi\n\trm -f $CRONTMP\n}\ncrontab_del_quiet()\n{\n\texists crontab || return\n\n\tCRONTMP=/tmp/cron.tmp\n\tcrontab -l >$CRONTMP 2>/dev/null\n\tif grep -q \"$GET_LIST_PREFIX\" $CRONTMP; then\n\t\tgrep -v \"$GET_LIST_PREFIX\" $CRONTMP >$CRONTMP.2\n\t\tcrontab $CRONTMP.2\n\t\trm -f $CRONTMP.2\n\tfi\n\trm -f $CRONTMP\n}\ncrontab_add()\n{\n\t# $1 - hour min\n\t# $2 - hour max\n\t[ -x \"$GET_LIST\" ] &&\t{\n\t\techo \\* adding crontab entry\n\n\t\tif exists crontab; then\n\t\t\tCRONTMP=/tmp/cron.tmp\n\t\t\tcrontab -l >$CRONTMP 2>/dev/null\n\t\t\tif grep -q \"$GET_LIST_PREFIX\" $CRONTMP; then\n\t\t\t\techo some entries already exist in crontab. check if this is corrent :\n\t\t\t\tgrep \"$GET_LIST_PREFIX\" $CRONTMP\n\t\t\telse\n\t\t\t\tend_with_newline <\"$CRONTMP\" || echo >>\"$CRONTMP\"\n\t\t\t\techo \"$(random 0 59) $(random $1 $2) */2 * * $GET_LIST\" >>$CRONTMP\n\t\t\t\tcrontab $CRONTMP\n\t\t\tfi\n\t\t\trm -f $CRONTMP\n\t\telse\n\t\t\techo '!!! CRON IS ABSENT !!! LISTS AUTO UPDATE WILL NOT WORK !!!'\n\t\tfi\n\t}\n}\ncron_ensure_running()\n{\n\t# if no crontabs present in /etc/cron openwrt init script does not launch crond. this is default\n\t[ \"$SYSTEM\" = \"openwrt\" ] && {\n\t\t/etc/init.d/cron enable\n\t\t/etc/init.d/cron start\n\t}\n}\n\n\nservice_start_systemd()\n{\n\techo \\* starting zapret service\n\n\t\"$SYSTEMCTL\" start zapret || {\n\t\techo could not start zapret service\n\t\texitp 30\n\t}\n}\nservice_stop_systemd()\n{\n\techo \\* stopping zapret service\n\n\t\"$SYSTEMCTL\" daemon-reload\n\t\"$SYSTEMCTL\" disable zapret\n\t\"$SYSTEMCTL\" stop zapret\n}\nservice_remove_systemd()\n{\n\techo \\* removing zapret service\n\n\trm -f \"$SYSTEMD_SYSTEM_DIR/zapret.service\"\n\t\"$SYSTEMCTL\" daemon-reload\n}\ntimer_remove_systemd()\n{\n\techo \\* removing zapret-list-update timer\n\n\t\"$SYSTEMCTL\" daemon-reload\n\t\"$SYSTEMCTL\" disable zapret-list-update.timer\n\t\"$SYSTEMCTL\" stop zapret-list-update.timer\n\trm -f \"$SYSTEMD_SYSTEM_DIR/zapret-list-update.service\" \"$SYSTEMD_SYSTEM_DIR/zapret-list-update.timer\"\n\t\"$SYSTEMCTL\" daemon-reload\n}\n\ninstall_sysv_init()\n{\n\t# $1 - \"0\"=disable\n\techo \\* installing init script\n\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\t\"$INIT_SCRIPT\" stop\n\t\t\"$INIT_SCRIPT\" disable\n\t}\n\tln -fs \"$INIT_SCRIPT_SRC\" \"$INIT_SCRIPT\"\n\t[ \"$1\" != \"0\" ] && \"$INIT_SCRIPT\" enable\n}\ninstall_openrc_init()\n{\n\t# $1 - \"0\"=disable\n\techo \\* installing init script\n\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\t\"$INIT_SCRIPT\" stop\n\t\trc-update del zapret\n\t}\n\tln -fs \"$INIT_SCRIPT_SRC\" \"$INIT_SCRIPT\"\n\t[ \"$1\" != \"0\" ] && rc-update add zapret\n}\nservice_remove_openrc()\n{\n\techo \\* removing zapret service\n\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\trc-update del zapret\n\t\t\"$INIT_SCRIPT\" stop\n\t}\n\trm -f \"$INIT_SCRIPT\"\n}\nservice_start_sysv()\n{\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\techo \\* starting zapret service\n\t\t\"$INIT_SCRIPT\" start || {\n\t\t\techo could not start zapret service\n\t\t\texitp 30\n\t\t}\n\t}\n}\nservice_stop_sysv()\n{\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\techo \\* stopping zapret service\n\t\t\"$INIT_SCRIPT\" stop\n\t}\n}\nservice_remove_sysv()\n{\n\techo \\* removing zapret service\n\n\t[ -x \"$INIT_SCRIPT\" ] && {\n\t\t\"$INIT_SCRIPT\" disable\n\t\t\"$INIT_SCRIPT\" stop\n\t}\n\trm -f \"$INIT_SCRIPT\"\n}\n\ncheck_kmod()\n{\n\t[ -f \"/lib/modules/$(uname -r)/$1.ko\" ]\n}\ncheck_package_exists_openwrt()\n{\n\t[ -n \"$($OPENWRT_PACKAGER list $1)\" ]\n}\ncheck_package_openwrt()\n{\n\tcase $OPENWRT_PACKAGER in\n\t\topkg)\n\t\t\t[ -n \"$(opkg list-installed $1)\" ] && return 0\n\t\t\tlocal what=\"$(opkg whatprovides $1 | tail -n +2 | head -n 1)\"\n\t\t\t[ -n \"$what\" ] || return 1\n\t\t\t[ -n \"$(opkg list-installed $what)\" ]\n\t\t\t;;\n\t\tapk)\n\t\t\tapk info -e $1\n\t\t\t;;\n\tesac\n}\ncheck_packages_openwrt()\n{\n\tfor pkg in $@; do\n\t\tcheck_package_openwrt $pkg || return\n\tdone\n}\n\ninstall_openwrt_iface_hook()\n{\n\techo \\* installing ifup hook\n\t\n\tln -fs \"$OPENWRT_IFACE_HOOK\" /etc/hotplug.d/iface\n}\nremove_openwrt_iface_hook()\n{\n\techo \\* removing ifup hook\n\t\n\trm -f /etc/hotplug.d/iface/??-zapret\n}\nopenwrt_fw_section_find()\n{\n\t# $1 - fw include postfix\n\t# echoes section number\n\t\n\ti=0\n\twhile true\n\tdo\n\t\tpath=$(uci -q get firewall.@include[$i].path)\n\t\t[ -n \"$path\" ] || break\n\t\t[ \"$path\" = \"$OPENWRT_FW_INCLUDE$1\" ] && {\n\t \t\techo $i\n\t \t\treturn 0\n\t\t}\n\t\ti=$(($i+1))\n\tdone\n\treturn 1\n}\nopenwrt_fw_section_del()\n{\n\t# $1 - fw include postfix\n\n\tlocal id=\"$(openwrt_fw_section_find $1)\"\n\t[ -n \"$id\" ] && {\n\t\tuci delete firewall.@include[$id] && uci commit firewall\n\t\trm -f \"$OPENWRT_FW_INCLUDE$1\"\n\t}\n}\nopenwrt_fw_section_add()\n{\n\topenwrt_fw_section_find ||\n\t{\n\t\tuci add firewall include >/dev/null || return\n\t\techo -1\n\t}\n}\nopenwrt_fw_section_configure()\n{\n\tlocal id=\"$(openwrt_fw_section_add $1)\"\n\t[ -z \"$id\" ] ||\n\t ! uci set firewall.@include[$id].path=\"$OPENWRT_FW_INCLUDE\" ||\n\t ! uci set firewall.@include[$id].reload=\"1\" ||\n\t ! uci commit firewall &&\n\t{\n\t\techo could not add firewall include\n\t\texitp 50\n\t}\n}\ninstall_openwrt_firewall()\n{\n\techo \\* installing firewall script $1\n\t\n\techo \"linking : $FW_SCRIPT_SRC => $OPENWRT_FW_INCLUDE\"\n\tln -fs \"$FW_SCRIPT_SRC\" \"$OPENWRT_FW_INCLUDE\"\n\t\n\topenwrt_fw_section_configure $1\n}\nrestart_openwrt_firewall()\n{\n\techo \\* restarting firewall\n\n\tlocal FW=fw4\n\t[ -n \"$OPENWRT_FW3\" ] && FW=fw3\n\texists $FW && $FW -q restart || {\n\t\techo could not restart firewall $FW\n\t}\n}\nremove_openwrt_firewall()\n{\n\techo \\* removing firewall script\n\t\n\topenwrt_fw_section_del\n\t# from old zapret versions. now we use single include\n\topenwrt_fw_section_del 6\n}\n\nclear_ipset()\n{\n\techo \"* clearing ipset(s)\"\n\n\t# free some RAM\n\t\"$IPSET_DIR/create_ipset.sh\" clear\n}\n\n\nservice_install_macos()\n{\n\techo \\* installing zapret service\n\n\tln -fs \"$ZAPRET_BASE/init.d/macos/zapret.plist\" /Library/LaunchDaemons\n}\nservice_start_macos()\n{\n\techo \\* starting zapret service\n\n\t\"$INIT_SCRIPT_SRC\" start\n}\nservice_stop_macos()\n{\n\techo \\* stopping zapret service\n\n\t\"$INIT_SCRIPT_SRC\" stop\n}\nservice_remove_macos()\n{\n\techo \\* removing zapret service\n\n\trm -f /Library/LaunchDaemons/zapret.plist\n\tzapret_stop_daemons\n}\n\nremove_macos_firewall()\n{\n\techo \\* removing zapret PF hooks\n\n\tpf_anchors_clear\n\tpf_anchors_del\n\tpf_anchor_root_del\n\tpf_anchor_root_reload\n}\n\nsedi()\n{\n\t# MacOS doesnt support -i without parameter. busybox doesnt support -i with parameter.\n\t# its not possible to put \"sed -i ''\" to a variable and then use it\n\tif [ \"$SYSTEM\" = \"macos\" ]; then\n\t\tsed -i '' \"$@\"\n\telse\n\t\tsed -i \"$@\"\n\tfi\n}\n\nwrite_config_var()\n{\n\t# $1 - mode var\n\tlocal M\n\teval M=\"\\$$1\"\n\t# replace / => \\/\n\t#M=${M//\\//\\\\\\/}\n\tM=$(echo $M | sed 's/\\//\\\\\\//g' | trim)\n\tgrep -q \"^[[:space:]]*$1=\\|^#*[[:space:]]*$1=\" \"$ZAPRET_CONFIG\" || {\n\t\t# var does not exist in config. add it\n\t\techo $1= >>\"$ZAPRET_CONFIG\"\n\t}\n\treplace_var_def $1 \"$M\" \"$ZAPRET_CONFIG\"\n}\n\nno_prereq_exit()\n{\n\techo could not install prerequisites\n\texitp 6\n}\ncheck_prerequisites_linux()\n{\n\techo \\* checking prerequisites\n\n\tlocal s cmd PKGS UTILS req=\"curl curl\"\n\tlocal APTGET DNF YUM PACMAN ZYPPER EOPKG APK\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\treq=\"$req iptables iptables ip6tables iptables ipset ipset\"\n\t\t\t;;\n\t\tnftables)\n\t\t\treq=\"$req nft nftables\"\n\t\t\t;;\n\tesac\n\n\tPKGS=$(for s in $req; do echo $s; done |\n\t\twhile read cmd; do\n\t\t\tread pkg\n\t\t\texists $cmd || echo $pkg\n\t\tdone | sort -u | xargs)\n\tUTILS=$(for s in $req; do echo $s; done |\n\t\twhile read cmd; do\n\t\t\tread pkg\n\t\t\techo $cmd\n\t\tdone | sort -u | xargs)\n\n\tif [ -z \"$PKGS\" ] ; then\n\t\techo required utilities exist : $UTILS\n\telse\n\t\techo \\* installing prerequisites\n\n\t\techo packages required : $PKGS\n\n\t\tAPTGET=$(whichq apt-get)\n\t\tDNF=$(whichq dnf)\n\t\tYUM=$(whichq yum)\n\t\tPACMAN=$(whichq pacman)\n\t\tZYPPER=$(whichq zypper)\n\t\tEOPKG=$(whichq eopkg)\n\t\tAPK=$(whichq apk)\n\t\tif [ -x \"$APTGET\" ] ; then\n\t\t\t\"$APTGET\" update\n\t\t\t\"$APTGET\" install -y --no-install-recommends $PKGS dnsutils || no_prereq_exit\n\t\telif [ -x \"$DNF\" ] ; then\n\t\t\t\"$DNF\" -y install $PKGS || no_prereq_exit\n\t\telif [ -x \"$YUM\" ] ; then\n\t\t\t\"$YUM\" -y install $PKGS || no_prereq_exit\n\t\telif [ -x \"$PACMAN\" ] ; then\n\t\t\t\"$PACMAN\" -Syy\n\t\t\t\"$PACMAN\" --noconfirm -S $PKGS || no_prereq_exit\n\t\telif [ -x \"$ZYPPER\" ] ; then\n\t\t\t\"$ZYPPER\" --non-interactive install $PKGS || no_prereq_exit\n\t\telif [ -x \"$EOPKG\" ] ; then\n\t\t\t\"$EOPKG\" -y install $PKGS || no_prereq_exit\n\t\telif [ -x \"$APK\" ] ; then\n\t\t\t\"$APK\" update\n\t\t\t# for alpine\n\t\t\t[ \"$FWTYPE\" = iptables ] && [ -n \"$($APK list ip6tables)\" ] && PKGS=\"$PKGS ip6tables\"\n\t\t\t\"$APK\" add $PKGS || no_prereq_exit\n\t\telse\n\t\t\techo supported package manager not found\n\t\t\techo you must manually install : $UTILS\n\t\t\texitp 5\n\t\tfi\n\tfi\n}\n\nremovable_pkgs_openwrt()\n{\n\tlocal pkg PKGS2\n\t[ -n \"$OPENWRT_FW4\" ] && PKGS2=\"$PKGS2 iptables-zz-legacy iptables ip6tables-zz-legacy ip6tables\"\n\t[ -n \"$OPENWRT_FW3\" ] && PKGS2=\"$PKGS2 nftables-json nftables-nojson nftables\"\n\tPKGS=\n\tfor pkg in $PKGS2; do\n\t\tcheck_package_exists_openwrt $pkg && PKGS=\"${PKGS:+$PKGS }$pkg\"\n\tdone\n\tPKGS=\"ipset iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra iptables-mod-u32 ip6tables-mod-nat ip6tables-extra kmod-nft-queue gzip coreutils-sort coreutils-sleep curl $PKGS\"\n}\n\nopenwrt_fix_broken_apk_uninstall_scripts()\n{\n\t# at least in early snapshots with apk removing gnu gzip, sort, ... does not restore links to busybox\n\t# system may become unusable\n\texists sort || { echo fixing missing sort; ln -fs /bin/busybox /usr/bin/sort; }\n\texists gzip || { echo fixing missing gzip; ln -fs /bin/busybox /bin/gzip; }\n\texists sleep || { echo fixing missing sleep; ln -fs /bin/busybox /bin/sleep; }\n}\n\nremove_extra_pkgs_openwrt()\n{\n\tlocal PKGS\n\techo \\* remove dependencies\n\tremovable_pkgs_openwrt\n\techo these packages may have been installed by install_easy.sh : $PKGS\n\task_yes_no N \"do you want to remove them\" && {\n\t\tcase $OPENWRT_PACKAGER in\n\t\t\topkg)\n\t\t\t\topkg remove --autoremove $PKGS\n\t\t\t\t;;\n\t\t\tapk)\n\t\t\t\tapk del $PKGS\n\t\t\t\topenwrt_fix_broken_apk_uninstall_scripts\n\t\t\t\t;;\n\t\tesac\n\t}\n}\n\ncheck_prerequisites_openwrt()\n{\n\techo \\* checking prerequisites\n\n\tlocal PKGS=\"curl\" UPD=0 local pkg_iptables\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tpkg_iptables=iptables\n\t\t\tcheck_package_exists_openwrt iptables-zz-legacy && pkg_iptables=iptables-zz-legacy\n\t\t\tPKGS=\"$PKGS ipset $pkg_iptables iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra iptables-mod-u32\"\n\t\t\tcheck_package_exists_openwrt ip6tables-zz-legacy && pkg_iptables=ip6tables-zz-legacy\n\t\t\t[ \"$DISABLE_IPV6\" = 1 ] || PKGS=\"$PKGS $pkg_iptables ip6tables-mod-nat ip6tables-extra\"\n\t\t\t;;\n\t\tnftables)\n\t\t\tPKGS=\"$PKGS nftables kmod-nft-nat kmod-nft-offload kmod-nft-queue\"\n\t\t\t;;\n\tesac\n\n\tif check_packages_openwrt $PKGS ; then\n\t\techo everything is present\n\telse\n\t\techo \\* installing prerequisites\n\n\t\t$OPENWRT_PACKAGER_UPDATE\n\t\tUPD=1\n\t\t$OPENWRT_PACKAGER_INSTALL $PKGS || {\n\t\t\techo could not install prerequisites\n\t\t\texitp 6\n\t\t}\n\tfi\n\t\n\tis_linked_to_busybox gzip && {\n\t\techo\n\t\techo your system uses default busybox gzip. its several times slower than GNU gzip.\n\t\techo ip/host list scripts will run much faster with GNU gzip\n\t\techo installer can install GNU gzip but it requires about 100 Kb space\n\t\tif ask_yes_no N \"do you want to install GNU gzip\"; then\n\t\t\t[ \"$UPD\" = \"0\" ] && {\n\t\t\t\t$OPENWRT_PACKAGER_UPDATE\n\t\t\t\tUPD=1\n\t\t\t}\n\t\t\t$OPENWRT_PACKAGER_INSTALL --force-overwrite gzip\n\t\tfi\n\t}\n\tis_linked_to_busybox sort && {\n\t\techo\n\t\techo your system uses default busybox sort. its much slower and consumes much more RAM than GNU sort\n\t\techo ip/host list scripts will run much faster with GNU sort\n\t\techo installer can install GNU sort but it requires about 100 Kb space\n\t\tif ask_yes_no N \"do you want to install GNU sort\"; then\n\t\t\t[ \"$UPD\" = \"0\" ] && {\n\t\t\t\t$OPENWRT_PACKAGER_UPDATE\n\t\t\t\tUPD=1\n\t\t\t}\n\t\t\t$OPENWRT_PACKAGER_INSTALL --force-overwrite coreutils-sort\n\t\tfi\n\t}\n\t[ \"$FSLEEP\" = 0 ] && is_linked_to_busybox sleep && {\n\t\techo\n\t\techo no methods of sub-second sleep were found.\n\t\techo if you want to speed up blockcheck install coreutils-sleep. it requires about 40 Kb space\n\t\tif ask_yes_no N \"do you want to install COREUTILS sleep\"; then\n\t\t\t[ \"$UPD\" = \"0\" ] && {\n\t\t\t\t$OPENWRT_PACKAGER_UPDATE\n\t\t\t\tUPD=1\n\t\t\t}\n\t\t\t$OPENWRT_PACKAGER_INSTALL --force-overwrite coreutils-sleep\n\t\t\tfsleep_setup\n\t\tfi\n\t}\n}\n\n\n\nselect_ipv6()\n{\n\tlocal T=N\n\n\t[ \"$DISABLE_IPV6\" != '1' ] && T=Y\n\tlocal old6=$DISABLE_IPV6\n\techo\n\tif ask_yes_no $T \"enable ipv6 support\"; then\n\t\tDISABLE_IPV6=0\n\telse\n\t\tDISABLE_IPV6=1\n\tfi\n\t[ \"$old6\" != \"$DISABLE_IPV6\" ] && write_config_var DISABLE_IPV6\n}\nselect_fwtype()\n{\n\techo\n\t[ $(get_ram_mb) -le 400 ] && {\n\t\techo WARNING ! you are running a low RAM system\n\t\techo WARNING ! nft requires lots of RAM to load huge ip sets, much more than ipsets require\n\t\techo WARNING ! if you need large lists it may be necessary to fall back to iptables+ipset firewall\n\t}\n\techo select firewall type :\n\task_list FWTYPE \"iptables nftables\" \"$FWTYPE\"\n\t# always write config var to prevent auto discovery every time\n\twrite_config_var FWTYPE\n}\n\ndry_run_tpws_()\n{\n\tlocal TPWS=\"$ZAPRET_BASE/tpws/tpws\"\n\techo verifying tpws options\n\t\"$TPWS\" --dry-run ${WS_USER:+--user=$WS_USER} \"$@\"\n}\ndry_run_nfqws_()\n{\n\tlocal NFQWS=\"$ZAPRET_BASE/nfq/nfqws\"\n\techo verifying nfqws options\n\t\"$NFQWS\" --dry-run ${WS_USER:+--user=$WS_USER} \"$@\"\n}\ndry_run_tpws()\n{\n\t[ \"$TPWS_ENABLE\" = 1 ] || return 0\n\tlocal opt=\"$TPWS_OPT\" port=${TPPORT_SOCKS:-988}\n\tfilter_apply_hostlist_target opt\n\tdry_run_tpws_ --port=$port $opt\n}\ndry_run_tpws_socks()\n{\n\t[ \"$TPWS_SOCKS_ENABLE\" = 1 ] || return 0\n\tlocal opt=\"$TPWS_SOCKS_OPT\" port=${TPPORT:-987}\n\tfilter_apply_hostlist_target opt\n\tdry_run_tpws_ --port=$port --socks $opt\n}\ndry_run_nfqws()\n{\n\t[ \"$NFQWS_ENABLE\" = 1 ] || return 0\n\tlocal opt=\"$NFQWS_OPT\" qn=${QNUM:-200}\n\tfilter_apply_hostlist_target opt\n\tdry_run_nfqws_ --qnum=$qn $opt\n}\n"
  },
  {
    "path": "common/ipt.sh",
    "content": "std_ports\nipt_connbytes=\"-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes\"\nIPSET_EXCLUDE=\"-m set ! --match-set nozapret\"\nIPSET_EXCLUDE6=\"-m set ! --match-set nozapret6\"\nIPBAN_EXCLUDE=\"-m set ! --match-set ipban\"\nIPBAN_EXCLUDE6=\"-m set ! --match-set ipban6\"\n\nipt()\n{\n\tiptables $FW_EXTRA_PRE -C \"$@\" $FW_EXTRA_POST >/dev/null 2>/dev/null || iptables $FW_EXTRA_PRE -I \"$@\" $FW_EXTRA_POST\n}\nipta()\n{\n\tiptables $FW_EXTRA_PRE -C \"$@\" $FW_EXTRA_POST >/dev/null 2>/dev/null || iptables $FW_EXTRA_PRE -A \"$@\" $FW_EXTRA_POST\n}\nipt_del()\n{\n\tiptables $FW_EXTRA_PRE -C \"$@\" $FW_EXTRA_POST >/dev/null 2>/dev/null && iptables $FW_EXTRA_PRE -D \"$@\" $FW_EXTRA_POST\n}\nipt_add_del()\n{\n\ton_off_function ipt ipt_del \"$@\"\n}\nipta_add_del()\n{\n\ton_off_function ipta ipt_del \"$@\"\n}\nipt6()\n{\n\tip6tables -C \"$@\" >/dev/null 2>/dev/null || ip6tables -I \"$@\"\n}\nipt6a()\n{\n\tip6tables -C \"$@\" >/dev/null 2>/dev/null || ip6tables -A \"$@\"\n}\nipt6_del()\n{\n\tip6tables -C \"$@\" >/dev/null 2>/dev/null && ip6tables -D \"$@\"\n}\nipt6_add_del()\n{\n\ton_off_function ipt6 ipt6_del \"$@\"\n}\nipt6a_add_del()\n{\n\ton_off_function ipt6a ipt6_del \"$@\"\n}\n\nis_ipt_flow_offload_avail()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tgrep -q FLOWOFFLOAD 2>/dev/null /proc/net/ip$1_tables_targets\n}\n\nfilter_apply_ipset_target4()\n{\n\t# $1 - var name of ipv4 iptables filter\n\tif [ \"$MODE_FILTER\" = \"ipset\" ]; then\n\t\teval $1=\"\\\"\\$$1 -m set --match-set zapret dst\\\"\"\n\tfi\n}\nfilter_apply_ipset_target6()\n{\n\t# $1 - var name of ipv6 iptables filter\n\tif [ \"$MODE_FILTER\" = \"ipset\" ]; then\n\t\teval $1=\"\\\"\\$$1 -m set --match-set zapret6 dst\\\"\"\n\tfi\n}\nfilter_apply_ipset_target()\n{\n\t# $1 - var name of ipv4 iptables filter\n\t# $2 - var name of ipv6 iptables filter\n\tfilter_apply_ipset_target4 $1\n\tfilter_apply_ipset_target6 $2\n}\n\nreverse_nfqws_rule_stream()\n{\n\tsed -e 's/-o /-i /g' -e 's/--dport /--sport /g' -e 's/--dports /--sports /g' -e 's/ dst$/ src/' -e 's/ dst / src /g' -e 's/--connbytes-dir=original/--connbytes-dir=reply/g' -e \"s/-m mark ! --mark $DESYNC_MARK\\/$DESYNC_MARK//g\"\n}\nreverse_nfqws_rule()\n{\n\techo \"$@\" | reverse_nfqws_rule_stream\n}\n\nprepare_tpws_fw4()\n{\n\t# otherwise linux kernel will treat 127.0.0.0/8 as \"martian\" ip and refuse routing to it\n\t# NOTE : kernels <3.6 do not have this feature. consider upgrading or change DNAT to REDIRECT and do not bind to 127.0.0.0/8\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\tiptables -N input_rule_zapret 2>/dev/null\n\t\tipt input_rule_zapret -d $TPWS_LOCALHOST4 -j RETURN\n\t\tipta input_rule_zapret -d 127.0.0.0/8 -j DROP\n\t\tipt INPUT ! -i lo -j input_rule_zapret\n\n\t\tprepare_route_localnet\n\t}\n}\nunprepare_tpws_fw4()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\tunprepare_route_localnet\n\n\t\tipt_del INPUT ! -i lo -j input_rule_zapret\n\t\tiptables -F input_rule_zapret 2>/dev/null\n\t\tiptables -X input_rule_zapret 2>/dev/null\n\t}\n}\nunprepare_tpws_fw()\n{\n\tunprepare_tpws_fw4\n}\n\nipt_mark_filter()\n{\n\t[ -n \"$FILTER_MARK\" ] && echo \"-m mark --mark $FILTER_MARK/$FILTER_MARK\"\n}\n\nipt_print_op()\n{\n\tif [ \"$1\" = \"1\" ]; then\n\t\techo \"Inserting ip$4tables rule for $3 : $2\"\n\telse\n\t\techo \"Deleting ip$4tables rule for $3 : $2\"\n\tfi\n}\n\n_fw_tpws4()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - tpws port\n\t# $4 - lan interface names space separated\n\t# $5 - wan interface names space separated\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i rule\n\n\t\t[ \"$1\" = 1 ] && prepare_tpws_fw4\n\n\t\tipt_print_op $1 \"$2\" \"tpws (port $3)\"\n\n\t\trule=\"$(ipt_mark_filter) $2 $IPSET_EXCLUDE dst $IPBAN_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$3\"\n\t\tfor i in $4 ; do\n\t\t\tipt_add_del $1 PREROUTING -t nat -i $i $rule\n\t \tdone\n\n\t\trule=\"-m owner ! --uid-owner $WS_USER $rule\"\n\t\tif [ -n \"$5\" ]; then\n\t\t\tfor i in $5; do\n\t\t\t\tipt_add_del $1 OUTPUT -t nat -o $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt_add_del $1 OUTPUT -t nat $rule\n\t\tfi\n\t}\n}\n_fw_tpws6()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv6\n\t# $3 - tpws port\n\t# $4 - lan interface names space separated\n\t# $5 - wan interface names space separated\n\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i rule DNAT6\n\n\t\tipt_print_op $1 \"$2\" \"tpws (port $3)\" 6\n\n\t\trule=\"$(ipt_mark_filter) $2 $IPSET_EXCLUDE6 dst $IPBAN_EXCLUDE6 dst\"\n\t\tfor i in $4 ; do\n\t\t\t_dnat6_target $i DNAT6\n\t\t\t[ -n \"$DNAT6\" -a \"$DNAT6\" != \"-\" ] && ipt6_add_del $1 PREROUTING -t nat -i $i $rule -j DNAT --to [$DNAT6]:$3\n\t \tdone\n\n\t\trule=\"-m owner ! --uid-owner $WS_USER $rule -j DNAT --to [::1]:$3\"\n\t\tif [ -n \"$5\" ]; then\n\t\t\tfor i in $5; do\n\t\t\t\tipt6_add_del $1 OUTPUT -t nat -o $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt6_add_del $1 OUTPUT -t nat $rule\n\t\tfi\n\t}\n}\nfw_tpws()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - iptable filter for ipv6\n\t# $4 - tpws port\n\tfw_tpws4 $1 \"$2\" $4\n\tfw_tpws6 $1 \"$3\" $4\n}\n\n\n_fw_nfqws_post4()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - queue number\n\t# $4 - wan interface names space separated\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i\n\n\t\tipt_print_op $1 \"$2\" \"nfqws postrouting (qnum $3)\"\n\n\t\trule=\"$(ipt_mark_filter) -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK $2 $IPSET_EXCLUDE dst -j NFQUEUE --queue-num $3 --queue-bypass\"\n\t\tif [ -n \"$4\" ] ; then\n\t\t\tfor i in $4; do\n\t\t\t\tipt_add_del $1 POSTROUTING -t mangle -o $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt_add_del $1 POSTROUTING -t mangle $rule\n\t\tfi\n\t}\n}\n_fw_nfqws_post6()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv6\n\t# $3 - queue number\n\t# $4 - wan interface names space separated\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i\n\n\t\tipt_print_op $1 \"$2\" \"nfqws postrouting (qnum $3)\" 6\n\n\t\trule=\"$(ipt_mark_filter) -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK $2 $IPSET_EXCLUDE6 dst -j NFQUEUE --queue-num $3 --queue-bypass\"\n\t\tif [ -n \"$4\" ] ; then\n\t\t\tfor i in $4; do\n\t\t\t\tipt6_add_del $1 POSTROUTING -t mangle -o $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt6_add_del $1 POSTROUTING -t mangle $rule\n\t\tfi\n\t}\n}\nfw_nfqws_post()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - iptable filter for ipv6\n\t# $4 - queue number\n\tfw_nfqws_post4 $1 \"$2\" $4\n\tfw_nfqws_post6 $1 \"$3\" $4\n}\n\n_fw_nfqws_pre4()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - queue number\n\t# $4 - wan interface names space separated\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i\n\n\t\tipt_print_op $1 \"$2\" \"nfqws input+forward (qnum $3)\"\n\n\t\trule=\"$2 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $3 --queue-bypass\"\n\t\tif [ -n \"$4\" ] ; then\n\t\t\tfor i in $4; do\n\t\t\t\t# iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there\n\t\t\t\tipt_add_del $1 INPUT -t mangle -i $i $rule\n\t\t\t\tipt_add_del $1 FORWARD -t mangle -i $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt_add_del $1 INPUT -t mangle $rule\n\t\t\tipt_add_del $1 FORWARD -t mangle $rule\n\t\tfi\n\t}\n}\n_fw_nfqws_pre6()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv6\n\t# $3 - queue number\n\t# $4 - wan interface names space separated\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$2\" ] || {\n\t\tlocal i\n\n\t\tipt_print_op $1 \"$2\" \"nfqws input+forward (qnum $3)\" 6\n\n\t\trule=\"$2 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $3 --queue-bypass\"\n\t\tif [ -n \"$4\" ] ; then\n\t\t\tfor i in $4; do\n\t\t\t\t# iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there\n\t\t\t\tipt6_add_del $1 INPUT -t mangle -i $i $rule\n\t\t\t\tipt6_add_del $1 FORWARD -t mangle -i $i $rule\n\t\t\tdone\n\t\telse\n\t\t\tipt6_add_del $1 INPUT -t mangle $rule\n\t\t\tipt6_add_del $1 FORWARD -t mangle $rule\n\t\tfi\n\t}\n}\nfw_nfqws_pre()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - iptable filter for ipv4\n\t# $3 - iptable filter for ipv6\n\t# $4 - queue number\n\tfw_nfqws_pre4 $1 \"$2\" $4\n\tfw_nfqws_pre6 $1 \"$3\" $4\n}\n\n\nfw_reverse_nfqws_rule4()\n{\n\tfw_nfqws_pre4 $1 \"$(reverse_nfqws_rule \"$2\")\" $3\n}\nfw_reverse_nfqws_rule6()\n{\n\tfw_nfqws_pre6 $1 \"$(reverse_nfqws_rule \"$2\")\" $3\n}\nfw_reverse_nfqws_rule()\n{\n\t# ensure that modes relying on incoming traffic work\n\t# $1 - 1 - add, 0 - del\n\t# $2 - rule4\n\t# $3 - rule6\n\t# $4 - queue number\n\tfw_reverse_nfqws_rule4 $1 \"$2\" $4\n\tfw_reverse_nfqws_rule6 $1 \"$3\" $4\n}\n\nipt_first_packets()\n{\n\t# $1 - packet count\n\t[ -n \"$1\" -a \"$1\" != keepalive ] && [ \"$1\" -ge 1 ] && echo \"$ipt_connbytes 1:$1\"\n}\nipt_do_nfqws_in_out()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - tcp,udp\n\t# $3 - ports\n\t# $4 - PKT_OUT. special value : 'keepalive'\n\t# $5 - PKT_IN\n\tlocal f4 f6 first_packets_only\n\t[ -n \"$3\" ] || return\n\t[ -n \"$4\" -a \"$4\" != 0 ] &&\n\t{\n\t\tfirst_packets_only=\"$(ipt_first_packets $4)\"\n\t\tf4=\"-p $2 -m multiport --dports $3 $first_packets_only\"\n\t\tf6=$f4\n\t\tfilter_apply_ipset_target f4 f6\n\t\tfw_nfqws_post $1 \"$f4\" \"$f6\" $QNUM\n\t}\n\t[ -n \"$5\" -a \"$5\" != 0 ] &&\n\t{\n\t\tfirst_packets_only=\"$(ipt_first_packets $5)\"\n\t\tf4=\"-p $2 -m multiport --dports $3  $first_packets_only\"\n\t\tf6=$f4\n\t\tfilter_apply_ipset_target f4 f6\n\t\tfw_reverse_nfqws_rule $1 \"$f4\" \"$f6\" $QNUM\n\t}\n}\n\nzapret_do_firewall_standard_tpws_rules_ipt()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tlocal f4 f6\n\n\t[ \"$TPWS_ENABLE\" = 1 -a -n \"$TPWS_PORTS\" ] && {\n\t\tf4=\"-p tcp -m multiport --dports $TPWS_PORTS_IPT\"\n\t\tf6=$f4\n\t\tfilter_apply_ipset_target f4 f6\n\t\tfw_tpws $1 \"$f4\" \"$f6\" $TPPORT\n\t}\n}\nzapret_do_firewall_standard_nfqws_rules_ipt()\n{\n\t# $1 - 1 - add, 0 - del\n\n\t[ \"$NFQWS_ENABLE\" = 1 ] && {\n\t\tipt_do_nfqws_in_out $1 tcp \"$NFQWS_PORTS_TCP_IPT\" \"$NFQWS_TCP_PKT_OUT\" \"$NFQWS_TCP_PKT_IN\"\n\t\tipt_do_nfqws_in_out $1 tcp \"$NFQWS_PORTS_TCP_KEEPALIVE_IPT\" keepalive \"$NFQWS_TCP_PKT_IN\"\n\t\tipt_do_nfqws_in_out $1 udp \"$NFQWS_PORTS_UDP_IPT\" \"$NFQWS_UDP_PKT_OUT\" \"$NFQWS_UDP_PKT_IN\"\n\t\tipt_do_nfqws_in_out $1 udp \"$NFQWS_PORTS_UDP_KEEPALIVE_IPT\" keepalive \"$NFQWS_UDP_PKT_IN\"\n\t}\n}\nzapret_do_firewall_standard_rules_ipt()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tzapret_do_firewall_standard_tpws_rules_ipt $1\n\tzapret_do_firewall_standard_nfqws_rules_ipt $1\n}\n\nzapret_do_firewall_rules_ipt()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tzapret_do_firewall_standard_rules_ipt $1\n\tcustom_runner zapret_custom_firewall $1\n\tzapret_do_icmp_filter $1\n}\n\nzapret_do_icmp_filter()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tlocal FW_EXTRA_PRE= FW_EXTRA_POST=\n\n\t[ \"$FILTER_TTL_EXPIRED_ICMP\" = 1 ] && {\n\t\t[ \"$DISABLE_IPV4\" = 1 ] || {\n\t\t\tipt_add_del $1 POSTROUTING -t mangle -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CONNMARK --or-mark $DESYNC_MARK\n\t\t\tipt_add_del $1 INPUT -p icmp -m icmp --icmp-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP\n\t\t\tipt_add_del $1 FORWARD -p icmp -m icmp --icmp-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP\n\t\t}\n\t\t[ \"$DISABLE_IPV6\" = 1 ] || {\n\t\t\tipt6_add_del $1 POSTROUTING -t mangle -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CONNMARK --or-mark $DESYNC_MARK\n\t\t\tipt6_add_del $1 INPUT -p icmpv6 -m icmp6 --icmpv6-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP\n\t\t\tipt6_add_del $1 FORWARD -p icmpv6 -m icmp6 --icmpv6-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP\n\t\t}\n\t}\n}\n\nzapret_do_firewall_ipt()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tif [ \"$1\" = 1 ]; then\n\t\techo Applying iptables\n\telse\n\t\techo Clearing iptables\n\tfi\n\n\t# always create ipsets. ip_exclude ipset is required\n\t[ \"$1\" = 1 ] && create_ipset no-update\n\n\tzapret_do_firewall_rules_ipt \"$@\"\n\n\tif [ \"$1\" = 1 ] ; then\n\t\texistf flow_offloading_exempt && flow_offloading_exempt\n\telse\n\t\texistf flow_offloading_unexempt && flow_offloading_unexempt\n\t\tunprepare_tpws_fw\n\tfi\n\n\treturn 0\n}\n"
  },
  {
    "path": "common/linux_daemons.sh",
    "content": "standard_mode_tpws_socks()\n{\n\t# $1 - 1 - run, 0 - stop\n\tlocal opt\n\t[ \"$TPWS_SOCKS_ENABLE\" = 1 ] && {\n\t\topt=\"--port=$TPPORT_SOCKS $TPWS_SOCKS_OPT\"\n\t\tfilter_apply_hostlist_target opt\n\t\tdo_tpws_socks $1 2 \"$opt\"\n\t}\n}\nstandard_mode_tpws()\n{\n\t# $1 - 1 - run, 0 - stop\n\tlocal opt\n\t[ \"$TPWS_ENABLE\" = 1 ] && check_bad_ws_options $1 \"$TPWS_OPT\" && {\n\t\topt=\"--port=$TPPORT $TPWS_OPT\"\n\t\tfilter_apply_hostlist_target opt\n\t\tdo_tpws $1 1 \"$opt\"\n\t}\n}\nstandard_mode_nfqws()\n{\n\t# $1 - 1 - run, 0 - stop\n\tlocal opt\n\t[ \"$NFQWS_ENABLE\" = 1 ] && check_bad_ws_options $1 \"$NFQWS_OPT\" && {\n\t\topt=\"--qnum=$QNUM $NFQWS_OPT\"\n\t\tfilter_apply_hostlist_target opt\n\t\tdo_nfqws $1 3 \"$opt\"\n\t}\n}\nstandard_mode_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tstandard_mode_tpws_socks $1\n\tstandard_mode_tpws $1\n\tstandard_mode_nfqws $1\n}\nzapret_do_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tstandard_mode_daemons $1\n\tcustom_runner zapret_custom_daemons $1\n\n\treturn 0\n}\nzapret_run_daemons()\n{\n\tzapret_do_daemons 1 \"$@\"\n}\nzapret_stop_daemons()\n{\n\tzapret_do_daemons 0 \"$@\"\n}\n"
  },
  {
    "path": "common/linux_fw.sh",
    "content": "set_conntrack_liberal_mode()\n{\n\t[ -n \"$SKIP_CONNTRACK_LIBERAL_MODE\" ] || sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=$1\n}\nzapret_do_firewall()\n{\n\tlinux_fwtype\n\n\t[ \"$1\" = 1 -a -n \"$INIT_FW_PRE_UP_HOOK\" ] && $INIT_FW_PRE_UP_HOOK\n\t[ \"$1\" = 0 -a -n \"$INIT_FW_PRE_DOWN_HOOK\" ] && $INIT_FW_PRE_DOWN_HOOK\n\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\tzapret_do_firewall_ipt \"$@\"\n\t\t\t;;\n\t\tnftables)\n\t\t\tzapret_do_firewall_nft \"$@\"\n\t\t\t;;\n\tesac\n\n\t# russian DPI sends RST,ACK with wrong ACK.\n\t# this is sometimes treated by conntrack as invalid and connbytes fw rules do not pass RST packet to nfqws.\n\t# switch on liberal mode on zapret firewall start and switch off on zapret firewall stop\n\t# this is only required for processing incoming bad RSTs. incoming rules are only applied in autohostlist mode\n\t# calling this after firewall because conntrack module can be not loaded before applying conntrack firewall rules\n\t[ \"$MODE_FILTER\" = \"autohostlist\" ] && set_conntrack_liberal_mode $1\n\t\n\t[ \"$1\" = 1 -a -n \"$INIT_FW_POST_UP_HOOK\" ] && $INIT_FW_POST_UP_HOOK\n\t[ \"$1\" = 0 -a -n \"$INIT_FW_POST_DOWN_HOOK\" ] && $INIT_FW_POST_DOWN_HOOK\n\n\treturn 0\n}\nzapret_apply_firewall()\n{\n\tzapret_do_firewall 1 \"$@\"\n}\nzapret_unapply_firewall()\n{\n\tzapret_do_firewall 0 \"$@\"\n}\n"
  },
  {
    "path": "common/linux_iphelper.sh",
    "content": "# there's no route_localnet for ipv6\n# the best we can is to route to link local of the incoming interface\n# OUTPUT - can DNAT to ::1\n# PREROUTING - can't DNAT to ::1. can DNAT to link local of -i interface or to any global addr\n# not a good idea to expose tpws to the world (bind to ::)\n\n# max wait time for the link local ipv6 on the LAN interface\nLINKLOCAL_WAIT_SEC=${LINKLOCAL_WAIT_SEC:-5}\n\nget_ipv6_linklocal()\n{\n # $1 - interface name. if empty - any interface\n if exists ip ; then\n    local dev\n    [ -n \"$1\" ] && dev=\"dev $1\"\n    ip addr show $dev | sed -e 's/^.*inet6 \\([^ ]*\\)\\/[0-9]* scope link.*$/\\1/;t;d' | head -n 1\n else\n    ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\\/[0-9]* Scope:Link.*$/\\1/;t;d' | head -n 1\n fi\n}\nget_ipv6_global()\n{\n # $1 - interface name. if empty - any interface\n if exists ip ; then\n    local dev\n    [ -n \"$1\" ] && dev=\"dev $1\"\n    ip addr show $dev | sed -e 's/^.*inet6 \\([^ ]*\\)\\/[0-9]* scope global.*$/\\1/;t;d' | head -n 1\n else\n    ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\\/[0-9]* Scope:Global.*$/\\1/;t;d' | head -n 1\n fi\n}\n\niface_is_up()\n{\t\n\t# $1 - interface name\n\t[ -f /sys/class/net/$1/operstate ] || return\n\tlocal state\n\tread state </sys/class/net/$1/operstate\n\t[ \"$state\" != \"down\" ]\n}\nwait_ifup()\n{\n\t# $1 - interface name\n\tlocal ct=0\n\twhile\n\t\tiface_is_up $1 && return\n\t\t[ \"$ct\" -ge \"$IFUP_WAIT_SEC\" ] && break\n\t\techo waiting for ifup of $1 for another $(($IFUP_WAIT_SEC - $ct)) seconds ...\n\t\tct=$(($ct+1))\n\t\tsleep 1\n\tdo :; done\n\tfalse\n}\n\n_dnat6_target()\n{\n\t# $1 - interface name\n\t# $2 - var to store target ip6\n\t# get target ip address for DNAT. prefer link locals\n\t# tpws should be as inaccessible from outside as possible\n\t# link local address can appear not immediately after ifup\n\t# DNAT6_TARGET=- means attempt was made but address was not found (to avoid multiple re-attempts)\n\n\tlocal DNAT6_TARGET DVAR=DNAT6_TARGET_$1\n\tDVAR=$(echo $DVAR | sed 's/[^a-zA-Z0-9_]/_/g')\n\teval DNAT6_TARGET=\"\\$$DVAR\"\n\t[ -n \"$2\" ] && eval $2=''\n\t[ -n \"$DNAT6_TARGET\" ] || {\n\t\tlocal ct=0\n\t\twhile\n\t\t\tDNAT6_TARGET=$(get_ipv6_linklocal $1)\n\t\t\t[ -n \"$DNAT6_TARGET\" ] && break\n\t\t\t[ \"$ct\" -ge \"$LINKLOCAL_WAIT_SEC\" ] && break\n\t\t\techo $1: waiting for the link local for another $(($LINKLOCAL_WAIT_SEC - $ct)) seconds ...\n\t\t\tct=$(($ct+1))\n\t\t\tsleep 1\n\t\tdo :; done\n\n\t\t[ -n \"$DNAT6_TARGET\" ] || {\n\t\t    \techo $1: no link local. getting global\n\t\t\tDNAT6_TARGET=$(get_ipv6_global $1)\n\t\t\t[ -n \"$DNAT6_TARGET\" ] || {\n\t\t\t\techo $1: could not get any address\n\t\t\t\tDNAT6_TARGET=-\n\t\t\t}\n\t\t}\n\t\teval $DVAR=\"$DNAT6_TARGET\"\n\t}\n\t[ -n \"$2\" ] && eval $2=\"$DNAT6_TARGET\"\n}\n\n_set_route_localnet()\n{\n\t# $1 - 1 = enable, 0 = disable\n\t# $2,$3,... - interface names\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\tlocal enable=\"$1\"\n\t\tshift\n\t\twhile [ -n \"$1\" ]; do\n\t\t\tsysctl -q -w net.ipv4.conf.$1.route_localnet=\"$enable\"\n\t\t\tshift\n\t\tdone\n\t}\n}\nprepare_route_localnet()\n{\n\tset_route_localnet 1 \"$@\"\n}\nunprepare_route_localnet()\n{\n\tset_route_localnet 0 \"$@\"\n}\n\nget_uevent_devtype()\n{\n\tlocal DEVTYPE INTERFACE IFINDEX OF_NAME OF_FULLNAME OF_COMPATIBLE_N\n\t[ -f \"/sys/class/net/$1/uevent\" ] && {\n\t\t. \"/sys/class/net/$1/uevent\"\n\t\techo -n $DEVTYPE\n\t}\n}\nresolve_lower_devices()\n{\n\t# $1 - bridge interface name\n\t[ -d \"/sys/class/net/$1\" ] && {\n\t\tfind \"/sys/class/net/$1\" -follow -maxdepth 1 -name \"lower_*\" |\n\t\t{\n\t\t\tlocal l lower lowers\n\t\t\twhile read lower; do\n\t\t\t\tlower=\"$(basename \"$lower\")\"\n\t\t\t\tl=\"${lower#lower_*}\"\n\t\t\t\t[  \"$l\" != \"$lower\" ] && append_separator_list lowers ' ' '' \"$l\"\n\t\t\tdone\n\t\t\tprintf \"$lowers\"\n\t\t}\n\t}\n}\n\ndefault_route_interfaces6()\n{\n\tsed -nre 's/^00000000000000000000000000000000 00 [0-9a-f]{32} [0-9a-f]{2} [0-9a-f]{32} [0-9a-f]{8} [0-9a-f]{8} [0-9a-f]{8} [0-9a-f]{8} +(.*)$/\\1/p' /proc/net/ipv6_route | grep -v '^lo$' | sort -u | xargs\n}\n\ndefault_route_interfaces4()\n{\n\tsed -nre 's/^([^\\t]+)\\t00000000\\t[0-9A-F]{8}\\t[0-9A-F]{4}\\t[0-9]+\\t[0-9]+\\t[0-9]+\\t00000000.*$/\\1/p' /proc/net/route | sort -u | xargs\n}\n"
  },
  {
    "path": "common/list.sh",
    "content": "HOSTLIST_MARKER=\"<HOSTLIST>\"\nHOSTLIST_NOAUTO_MARKER=\"<HOSTLIST_NOAUTO>\"\n\nfind_hostlists()\n{\n\t[ -n \"$HOSTLIST_BASE\" ] || HOSTLIST_BASE=\"$ZAPRET_BASE/ipset\"\n\n\tHOSTLIST=\"$HOSTLIST_BASE/zapret-hosts.txt.gz\"\n\t[ -f \"$HOSTLIST\" ] || HOSTLIST=\"$HOSTLIST_BASE/zapret-hosts.txt\"\n\t[ -f \"$HOSTLIST\" ] || HOSTLIST=\n\n\tHOSTLIST_USER=\"$HOSTLIST_BASE/zapret-hosts-user.txt.gz\"\n\t[ -f \"$HOSTLIST_USER\" ] || HOSTLIST_USER=\"$HOSTLIST_BASE/zapret-hosts-user.txt\"\n\t[ -f \"$HOSTLIST_USER\" ] || HOSTLIST_USER=\n\n\tHOSTLIST_EXCLUDE=\"$HOSTLIST_BASE/zapret-hosts-user-exclude.txt.gz\"\n\t[ -f \"$HOSTLIST_EXCLUDE\" ] || HOSTLIST_EXCLUDE=\"$HOSTLIST_BASE/zapret-hosts-user-exclude.txt\"\n\t[ -f \"$HOSTLIST_EXCLUDE\" ] || HOSTLIST_EXCLUDE=\n\n\tHOSTLIST_AUTO=\"$HOSTLIST_BASE/zapret-hosts-auto.txt\"\n\tHOSTLIST_AUTO_DEBUGLOG=\"$HOSTLIST_BASE/zapret-hosts-auto-debug.log\"\n}\n\nfilter_apply_hostlist_target()\n{\n\t# $1 - var name of tpws or nfqws params\n\n\tlocal v parm parm1 parm2 parm3 parm4 parm5 parm6 parm7 parm8 parmNA\n\teval v=\"\\$$1\"\n\tif contains \"$v\" \"$HOSTLIST_MARKER\" || contains \"$v\" \"$HOSTLIST_NOAUTO_MARKER\"; then\n\t\t[ \"$MODE_FILTER\" = hostlist -o \"$MODE_FILTER\" = autohostlist ] &&\n\t\t{\n\t\t\tfind_hostlists\n\t\t\tparm1=\"${HOSTLIST_USER:+--hostlist=$HOSTLIST_USER}\"\n\t\t\tparm2=\"${HOSTLIST:+--hostlist=$HOSTLIST}\"\n\t\t\tparm3=\"${HOSTLIST_EXCLUDE:+--hostlist-exclude=$HOSTLIST_EXCLUDE}\"\n\t\t\t[ \"$MODE_FILTER\" = autohostlist ] &&\n\t\t\t{\n\t\t\t\tparm4=\"--hostlist-auto=$HOSTLIST_AUTO\"\n\t\t\t\tparm5=\"${AUTOHOSTLIST_FAIL_THRESHOLD:+--hostlist-auto-fail-threshold=$AUTOHOSTLIST_FAIL_THRESHOLD}\"\n\t\t\t\tparm6=\"${AUTOHOSTLIST_FAIL_TIME:+--hostlist-auto-fail-time=$AUTOHOSTLIST_FAIL_TIME}\"\n\t\t\t\tparm7=\"${AUTOHOSTLIST_RETRANS_THRESHOLD:+--hostlist-auto-retrans-threshold=$AUTOHOSTLIST_RETRANS_THRESHOLD}\"\n\t\t\t\tparm8=\"--hostlist=$HOSTLIST_AUTO\"\n\t\t\t}\n\t\t\tparm=\"$parm1${parm2:+ $parm2}${parm3:+ $parm3}${parm4:+ $parm4}${parm5:+ $parm5}${parm6:+ $parm6}${parm7:+ $parm7}\"\n\t\t\tparmNA=\"$parm1${parm2:+ $parm2}${parm3:+ $parm3}${parm8:+ $parm8}\"\n\t\t}\n\t\tv=\"$(replace_str $HOSTLIST_NOAUTO_MARKER \"$parmNA\" \"$v\")\"\n\t\tv=\"$(replace_str $HOSTLIST_MARKER \"$parm\" \"$v\")\"\n\t\t[ \"$MODE_FILTER\" = autohostlist -a \"$AUTOHOSTLIST_DEBUGLOG\" = 1 ] && {\n\t\t\tv=\"$v --hostlist-auto-debug=$HOSTLIST_AUTO_DEBUGLOG\"\n\t\t}\n\t\teval $1=\\\"\"$v\"\\\"\n\tfi\n}\n"
  },
  {
    "path": "common/nft.sh",
    "content": "[ -n \"$ZAPRET_NFT_TABLE\" ] || ZAPRET_NFT_TABLE=zapret\nnft_connbytes=\"ct original packets\"\n\n# required for : nft -f -\ncreate_dev_stdin\nstd_ports\n\nnft_create_table()\n{\n\tnft add table inet $ZAPRET_NFT_TABLE\n}\nnft_del_table()\n{\n\tnft delete table inet $ZAPRET_NFT_TABLE 2>/dev/null\n}\nnft_list_table()\n{\n\tnft -t list table inet $ZAPRET_NFT_TABLE\n}\n\nnft_create_set()\n{\n\t# $1 - set name\n\t# $2 - params\n\tnft create set inet $ZAPRET_NFT_TABLE $1 \"{ $2 }\" 2>/dev/null\n}\nnft_del_set()\n{\n\t# $1 - set name\n\tnft delete set inet $ZAPRET_NFT_TABLE $1\n}\nnft_flush_set()\n{\n\t# $1 - set name\n\tnft flush set inet $ZAPRET_NFT_TABLE $1\n}\nnft_set_exists()\n{\n\t# $1 - set name\n\tnft -t list set inet $ZAPRET_NFT_TABLE $1 2>/dev/null >/dev/null\n}\nnft_flush_chain()\n{\n\t# $1 - chain name\n\tnft flush chain inet $ZAPRET_NFT_TABLE $1\n}\n\nnft_del_all_chains_from_table()\n{\n\t# $1 - table_name with or without family\n\n\t# delete all chains with possible references to each other\n\t# cannot just delete all in the list because of references\n\t# avoid infinite loops\n\tlocal chains deleted=1 error=1\n\twhile [ -n \"$deleted\" -a -n \"$error\" ]; do\n\t\tchains=$(nft -t list table $1 2>/dev/null | sed -nre \"s/^[ \t]*chain ([^ ]+) \\{/\\1/p\" | xargs)\n\t\t[ -n \"$chains\" ] || break\n\t\tdeleted=\n\t\terror=\n\t\tfor chain in $chains; do\n\t\t\tif nft delete chain $1 $chain 2>/dev/null; then\n\t\t\t\tdeleted=1\n\t\t\telse\n\t\t\t\terror=1\n\t\t\tfi\n\t\tdone\n\tdone\n}\n\nnft_create_chains()\n{\ncat << EOF | nft -f -\n\tadd chain inet $ZAPRET_NFT_TABLE dnat_output { type nat hook output priority -101; }\n\tflush chain inet $ZAPRET_NFT_TABLE dnat_output\n\tadd chain inet $ZAPRET_NFT_TABLE dnat_pre { type nat hook prerouting priority -101; }\n\tflush chain inet $ZAPRET_NFT_TABLE dnat_pre\n\tadd chain inet $ZAPRET_NFT_TABLE forward { type filter hook forward priority -1; }\n\tflush chain inet $ZAPRET_NFT_TABLE forward\n\tadd chain inet $ZAPRET_NFT_TABLE input { type filter hook input priority -1; }\n\tflush chain inet $ZAPRET_NFT_TABLE input\n\tadd chain inet $ZAPRET_NFT_TABLE flow_offload\n\tflush chain inet $ZAPRET_NFT_TABLE flow_offload\n\tadd chain inet $ZAPRET_NFT_TABLE localnet_protect\n\tflush chain inet $ZAPRET_NFT_TABLE localnet_protect\n\tadd rule inet  $ZAPRET_NFT_TABLE localnet_protect ip daddr $TPWS_LOCALHOST4 return comment \"route_localnet allow access to tpws\"\n\tadd rule inet  $ZAPRET_NFT_TABLE localnet_protect ip daddr 127.0.0.0/8 drop comment \"route_localnet remote access protection\"\n\tadd rule inet  $ZAPRET_NFT_TABLE input iif != lo jump localnet_protect\n\tadd chain inet $ZAPRET_NFT_TABLE postrouting\n\tflush chain inet $ZAPRET_NFT_TABLE postrouting\n\tadd chain inet $ZAPRET_NFT_TABLE postrouting_hook { type filter hook postrouting priority 99; }\n\tflush chain inet $ZAPRET_NFT_TABLE postrouting_hook\n\tadd rule inet  $ZAPRET_NFT_TABLE postrouting_hook mark and $DESYNC_MARK == 0 jump postrouting\n\tadd chain inet $ZAPRET_NFT_TABLE postnat\n\tflush chain inet $ZAPRET_NFT_TABLE postnat\n\tadd chain inet $ZAPRET_NFT_TABLE postnat_hook { type filter hook postrouting priority 101; }\n\tflush chain inet $ZAPRET_NFT_TABLE postnat_hook\n\tadd rule inet  $ZAPRET_NFT_TABLE postnat_hook mark and $DESYNC_MARK == 0 jump postnat\n\tadd chain inet $ZAPRET_NFT_TABLE prerouting { type filter hook prerouting priority -99; }\n\tflush chain inet $ZAPRET_NFT_TABLE prerouting\n\tadd chain inet $ZAPRET_NFT_TABLE prenat { type filter hook prerouting priority -101; }\n\tflush chain inet $ZAPRET_NFT_TABLE prenat\n\tadd chain inet $ZAPRET_NFT_TABLE predefrag { type filter hook output priority -401; }\n\tflush chain inet $ZAPRET_NFT_TABLE predefrag\n\tadd chain inet $ZAPRET_NFT_TABLE predefrag_nfqws\n\tflush chain inet $ZAPRET_NFT_TABLE predefrag_nfqws\n\tadd rule inet $ZAPRET_NFT_TABLE predefrag mark and $DESYNC_MARK !=0 jump predefrag_nfqws comment \"nfqws generated : avoid drop by INVALID conntrack state\"\n\tadd rule inet $ZAPRET_NFT_TABLE predefrag_nfqws mark and $DESYNC_MARK_POSTNAT !=0 notrack comment \"postnat traffic\"\n\tadd rule inet $ZAPRET_NFT_TABLE predefrag_nfqws ip frag-off & 0x1fff != 0 notrack comment \"ipfrag\"\n\tadd rule inet $ZAPRET_NFT_TABLE predefrag_nfqws exthdr frag exists notrack comment \"ipfrag\"\n\tadd rule inet $ZAPRET_NFT_TABLE predefrag_nfqws tcp flags ! syn,rst,ack notrack comment \"datanoack\"\n\tadd set inet $ZAPRET_NFT_TABLE lanif { type ifname; }\n\tadd set inet $ZAPRET_NFT_TABLE wanif { type ifname; }\n\tadd set inet $ZAPRET_NFT_TABLE wanif6 { type ifname; }\n\tadd map inet $ZAPRET_NFT_TABLE link_local { type ifname : ipv6_addr; }\n\nEOF\n\t[ -n \"$POSTNAT_ALL\" ] && {\n\t\tnft_flush_chain predefrag_nfqws\n\t\tnft_add_rule predefrag_nfqws notrack comment \\\"do not track nfqws generated packets to avoid nat tampering and defragmentation\\\"\n\t}\n\t[ \"$FILTER_TTL_EXPIRED_ICMP\" = 1 ] && {\n\t\tif is_postnat; then\n\t\t\t# can be caused by untracked nfqws-generated packets\n\t\t\tnft_add_rule prerouting icmp type time-exceeded ct state invalid drop\n\t\telse\n\t\t\tnft_add_rule postrouting_hook mark and $DESYNC_MARK != 0 ct mark set ct mark or $DESYNC_MARK comment \\\"nfqws related : prevent ttl expired socket errors\\\"\n\t\tfi\n\t\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\t\tnft_add_rule prerouting icmp type time-exceeded ct mark and $DESYNC_MARK != 0 drop comment \\\"nfqws related : prevent ttl expired socket errors\\\"\n\t\t}\n\t\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\t\tnft_add_rule prerouting icmpv6 type time-exceeded ct mark and $DESYNC_MARK != 0 drop comment \\\"nfqws related : prevent ttl expired socket errors\\\"\n\t\t}\n\t}\n}\nnft_del_chains()\n{\n\t# do not delete all chains because of additional user hooks\n\t# they must be inside zapret table to use nfsets\n\n\t# these chains are newer. do not fail all because chains are not present\ncat << EOF | nft -f - 2>/dev/null\n\tdelete chain inet $ZAPRET_NFT_TABLE postrouting_hook\n\tdelete chain inet $ZAPRET_NFT_TABLE postnat_hook\nEOF\n\ncat << EOF | nft -f - 2>/dev/null\n\tdelete chain inet $ZAPRET_NFT_TABLE dnat_output\n\tdelete chain inet $ZAPRET_NFT_TABLE dnat_pre\n\tdelete chain inet $ZAPRET_NFT_TABLE forward\n\tdelete chain inet $ZAPRET_NFT_TABLE input\n\tdelete chain inet $ZAPRET_NFT_TABLE postrouting\n\tdelete chain inet $ZAPRET_NFT_TABLE postnat\n\tdelete chain inet $ZAPRET_NFT_TABLE prerouting\n\tdelete chain inet $ZAPRET_NFT_TABLE prenat\n\tdelete chain inet $ZAPRET_NFT_TABLE predefrag\n\tdelete chain inet $ZAPRET_NFT_TABLE predefrag_nfqws\n\tdelete chain inet $ZAPRET_NFT_TABLE flow_offload\n\tdelete chain inet $ZAPRET_NFT_TABLE localnet_protect\nEOF\n# unfortunately this approach breaks udp desync of the connection initiating packet (new, first one)\n#\tdelete chain inet $ZAPRET_NFT_TABLE predefrag\n}\nnft_del_flowtable()\n{\n\tnft delete flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null\n}\nnft_create_or_update_flowtable()\n{\n\t# $1 = flags ('offload' for hw offload)\n\t# $2,$3,$4,... - interfaces\n\t# can be called multiple times to add interfaces. interfaces can only be added , not removed\n\tlocal flags=$1 devices makelist\n\tshift\n\t# warning ! nft versions at least up to 1.0.1 do not allow interface names starting with digit in flowtable and do not allow quoting\n\t# warning ! openwrt fixes this in post-21.x snapshots with special nft patch\n\t# warning ! in traditional linux distros nft is unpatched and will fail with quoted interface definitions if unfixed\n\t[ -n \"$flags\" ] && flags=\"flags $flags;\"\n\tfor makelist in make_quoted_comma_list make_comma_list; do\n\t\t$makelist devices \"$@\"\n\t\t[ -n \"$devices\" ] && devices=\"devices={$devices};\"\n\t\tnft add flowtable inet $ZAPRET_NFT_TABLE ft \"{ hook ingress priority -1; $flags $devices }\" && break\n\tdone\n}\nnft_flush_ifsets()\n{\ncat << EOF | nft -f -  2>/dev/null\n\tflush set inet $ZAPRET_NFT_TABLE lanif\n\tflush set inet $ZAPRET_NFT_TABLE wanif\n\tflush set inet $ZAPRET_NFT_TABLE wanif6\n\tflush map inet $ZAPRET_NFT_TABLE link_local\nEOF\n}\nnft_flush_link_local()\n{\n\tnft flush map inet $ZAPRET_NFT_TABLE link_local 2>/dev/null\n}\nnft_list_ifsets()\n{\n\tnft list set inet $ZAPRET_NFT_TABLE lanif\n\tnft list set inet $ZAPRET_NFT_TABLE wanif\n\tnft list set inet $ZAPRET_NFT_TABLE wanif6\n\tnft list map inet $ZAPRET_NFT_TABLE link_local\n\tnft list flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null\n}\n\nnft_create_firewall()\n{\n\tnft_create_table\n\tnft_del_flowtable\n\tnft_flush_link_local\n\tnft_create_chains\n}\nnft_del_firewall()\n{\n\tnft_del_chains\n\tnft_del_flowtable\n\tnft_flush_link_local\n\t# leave ifsets and ipsets because they may be used by custom rules\n}\n\nnft_add_rule()\n{\n\t# $1 - chain\n\t# $2,$3,... - rule(s)\n\tlocal chain=\"$1\"\n\tshift\n\tnft add rule inet $ZAPRET_NFT_TABLE $chain $FW_EXTRA_PRE \"$@\"\n}\nnft_insert_rule()\n{\n\t# $1 - chain\n\t# $2,$3,... - rule(s)\n\tlocal chain=\"$1\"\n\tshift\n\tnft insert rule inet $ZAPRET_NFT_TABLE $chain $FW_EXTRA_PRE \"$@\"\n}\nnft_add_set_element()\n{\n\t# $1 - set or map name\n\t# $2 - element\n\t[ -z \"$2\" ] || nft add element inet $ZAPRET_NFT_TABLE $1 \"{ $2 }\"\n}\nnft_add_set_elements()\n{\n\t# $1 - set or map name\n\t# $2,$3,... - element(s)\n\tlocal set=\"$1\" elements\n\tshift\n\tmake_comma_list elements \"$@\"\n\tnft_add_set_element $set \"$elements\"\n}\nnft_reverse_nfqws_rule()\n{\n\techo \"$@\" | sed -e 's/oifname /iifname /g' -e 's/dport /sport /g' -e 's/daddr /saddr /g' -e 's/ct original /ct reply /g' -e \"s/mark and $DESYNC_MARK == 0//g\"\n}\nnft_clean_nfqws_rule()\n{\n\techo \"$@\" | sed -e \"s/mark and $DESYNC_MARK == 0//g\" -e \"s/oifname @wanif6//g\" -e \"s/oifname @wanif//g\"\n}\nnft_add_nfqws_flow_exempt_rule()\n{\n\t# $1 - rule (must be all filters in one var)\n\tlocal FW_EXTRA_POST= FW_EXTRA_PRE=\n\tnft_add_rule flow_offload $(nft_clean_nfqws_rule $1) return comment \\\"direct flow offloading exemption\\\"\n\t# do not need this because of oifname @wanif/@wanif6 filter in forward chain\n\t#nft_add_rule flow_offload $(nft_reverse_nfqws_rule $1) return comment \\\"reverse flow offloading exemption\\\"\n}\nnft_add_flow_offload_exemption()\n{\n\t# \"$1\" - rule for ipv4\n\t# \"$2\" - rule for ipv6\n\t# \"$3\" - comment\n\tlocal FW_EXTRA_POST= FW_EXTRA_PRE=\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$1\" ] || nft_add_rule flow_offload oifname @wanif $1 ip daddr != @nozapret return comment \\\"$3\\\"\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$2\" ] || nft_add_rule flow_offload oifname @wanif6 $2 ip6 daddr != @nozapret6 return comment \\\"$3\\\"\n}\n\nnft_apply_flow_offloading()\n{\n\t# ft can be absent\n\tnft_add_rule flow_offload meta l4proto \"{ tcp, udp }\" flow add @ft 2>/dev/null && {\n\t\tnft_add_rule flow_offload meta l4proto \"{ tcp, udp }\" counter comment \\\"if offload works here must not be too much traffic\\\"\n\t\t# allow only outgoing packets to initiate flow offload\n\t\tnft_add_rule forward oifname @wanif jump flow_offload\n\t\tnft_add_rule forward oifname @wanif6 jump flow_offload\n\t}\n}\n\n\n\nnft_filter_apply_ipset_target4()\n{\n\t# $1 - var name of ipv4 nftables filter\n\tif [ \"$MODE_FILTER\" = \"ipset\" ]; then\n\t\teval $1=\"\\\"\\$$1 ip daddr @zapret\\\"\"\n\tfi\n}\nnft_filter_apply_ipset_target6()\n{\n\t# $1 - var name of ipv6 nftables filter\n\tif [ \"$MODE_FILTER\" = \"ipset\" ]; then\n\t\teval $1=\"\\\"\\$$1 ip6 daddr @zapret6\\\"\"\n\tfi\n}\nnft_filter_apply_ipset_target()\n{\n\t# $1 - var name of ipv4 nftables filter\n\t# $2 - var name of ipv6 nftables filter\n\tnft_filter_apply_ipset_target4 $1\n\tnft_filter_apply_ipset_target6 $2\n}\n\nnft_mark_filter()\n{\n\t[ -n \"$FILTER_MARK\" ] && echo \"mark and $FILTER_MARK != 0\"\n}\n\nnft_script_add_ifset_element()\n{\n\t# $1 - set name\n\t# $2 - space separated elements\n\tlocal elements\n\t[ -n \"$2\" ] && {\n\t\tmake_quoted_comma_list elements $2\n\t\tscript=\"${script}\nadd element inet $ZAPRET_NFT_TABLE $1 { $elements }\"\n\t}\n}\nnft_fill_ifsets()\n{\n\t# $1 - space separated lan interface names\n\t# $2 - space separated wan interface names\n\t# $3 - space separated wan6 interface names\n\t# 4,5,6 is needed for pppoe+openwrt case. looks like it's not easily possible to resolve ethernet device behind a pppoe interface\n\t# $4 - space separated lan physical interface names (optional)\n\t# $5 - space separated wan physical interface names (optional)\n\t# $6 - space separated wan6 physical interface names (optional)\n\n\tlocal script i j ALLDEVS devs b\n\n\t# if large sets exist nft works very ineffectively\n\t# looks like it analyzes the whole table blob to find required data pieces\n\t# calling all in one shot helps not to waste cpu time many times\n\n\tscript=\"flush set inet $ZAPRET_NFT_TABLE wanif\nflush set inet $ZAPRET_NFT_TABLE wanif6\nflush set inet $ZAPRET_NFT_TABLE lanif\"\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || nft_script_add_ifset_element wanif \"$2\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || nft_script_add_ifset_element wanif6 \"$3\"\n\tnft_script_add_ifset_element lanif \"$1\"\n\n\techo \"$script\" | nft -f -\n\n\tcase \"$FLOWOFFLOAD\" in\n\t\tsoftware)\n\t\t\tALLDEVS=$(unique $1 $2 $3)\n\t\t\t# unbound flowtable may cause error in older nft version\n\t\t\tnft_create_or_update_flowtable '' $ALLDEVS 2>/dev/null\n\t\t\t;;\n\t\thardware)\n\t\t\tALLDEVS=$(unique $1 $2 $3 $4 $5 $6)\n\t\t\t# first create unbound flowtable. may cause error in older nft version\n\t\t\tnft_create_or_update_flowtable 'offload' 2>/dev/null\n\t\t\t# then add elements. some of them can cause error because unsupported\n\t\t\tfor i in $ALLDEVS; do\n\t\t\t\t# bridge members must be added instead of the bridge itself\n\t\t\t\t# some members may not support hw offload. example : lan1 lan2 lan3 support, wlan0 wlan1 - not\n\t\t\t\tb=\n\t\t\t\tdevs=$(resolve_lower_devices $i)\n\t\t\t\tfor j in $devs; do\n\t\t\t\t\t# do not display error if addition failed\n\t\t\t\t\tnft_create_or_update_flowtable 'offload' $j && b=1 2>/dev/null\n\t\t\t\tdone\n\t\t\t\t[ -n \"$b\" ] || {\n\t\t\t\t\t# no lower devices added ? try to add interface itself\n\t\t\t\t\tnft_create_or_update_flowtable 'offload' $i 2>/dev/null\n\t\t\t\t}\n\t\t\tdone\n\t\t\t;;\n\tesac\n}\n\nnft_only()\n{\n\tlinux_fwtype\n\n\tcase \"$FWTYPE\" in\n\t\tnftables)\n\t\t\t\"$@\"\n\t\t\t;;\n\tesac\n}\n\n\nnft_print_op()\n{\n\techo \"Inserting nftables ipv$3 rule for $2 : $1\"\n}\n_nft_fw_tpws4()\n{\n\t# $1 - filter ipv4\n\t# $2 - tpws port\n\t# $3 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\"\n\t\tlocal mark_filter=$(nft_mark_filter)\n\t\tnft_print_op \"$filter\" \"tpws (port $2)\" 4\n\t\tnft_insert_rule dnat_output skuid != $WS_USER ${3:+oifname @wanif} $mark_filter $filter ip daddr != @nozapret ip daddr != @ipban $FW_EXTRA_POST dnat ip to $TPWS_LOCALHOST4:$port\n\t\tnft_insert_rule dnat_pre iifname @lanif $mark_filter $filter ip daddr != @nozapret ip daddr != @ipban $FW_EXTRA_POST dnat ip to $TPWS_LOCALHOST4:$port\n\t\tprepare_route_localnet\n\t}\n}\n_nft_fw_tpws6()\n{\n\t# $1 - filter ipv6\n\t# $2 - tpws port\n\t# $3 - lan interface names space separated\n\t# $4 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\" DNAT6 i\n\t\tlocal mark_filter=$(nft_mark_filter)\n\t\tnft_print_op \"$filter\" \"tpws (port $port)\" 6\n\t\tnft_insert_rule dnat_output skuid != $WS_USER ${4:+oifname @wanif6} $mark_filter $filter ip6 daddr != @nozapret6 ip6 daddr != @ipban6 $FW_EXTRA_POST dnat ip6 to [::1]:$port\n\t\t[ -n \"$3\" ] && {\n\t\t\tnft_insert_rule dnat_pre $mark_filter $filter ip6 daddr != @nozapret6 ip6 daddr != @ipban6 $FW_EXTRA_POST dnat ip6 to iifname map @link_local:$port\n\t\t\tfor i in $3; do\n\t\t\t\t_dnat6_target $i DNAT6\n\t\t\t\t# can be multiple tpws processes on different ports\n\t\t\t\t[ -n \"$DNAT6\" -a \"$DNAT6\" != '-' ] && nft_add_set_element link_local \"$i : $DNAT6\"\n\t\t\tdone\n\t\t}\n\t}\n}\nnft_fw_tpws()\n{\n\t# $1 - filter ipv4\n\t# $2 - filter ipv6\n\t# $3 - tpws port\n\n\tnft_fw_tpws4 \"$1\" $3\n\tnft_fw_tpws6 \"$2\" $3\n}\nis_postnat()\n{\n\t[ \"$POSTNAT\" != 0 -o \"$POSTNAT_ALL\" = 1 ]\n}\nget_postchain()\n{\n\tif is_postnat ; then\n\t\techo -n postnat\n\telse\n\t\techo -n postrouting\n\tfi\n}\nget_prechain()\n{\n\tif is_postnat ; then\n\t\techo -n prenat\n\telse\n\t\techo -n prerouting\n\tfi\n}\n_nft_fw_nfqws_post4()\n{\n\t# $1 - filter ipv4\n\t# $2 - queue number\n\t# $3 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\" rule chain=$(get_postchain) setmark\n\t\tnft_print_op \"$filter\" \"nfqws postrouting (qnum $port)\" 4\n\t\trule=\"${3:+oifname @wanif} $(nft_mark_filter) $filter ip daddr != @nozapret\"\n\t\tis_postnat && setmark=\"meta mark set meta mark or $DESYNC_MARK_POSTNAT\"\n\t\tnft_insert_rule $chain $rule $setmark $CONNMARKER $FW_EXTRA_POST queue num $port bypass\n\t\tnft_add_nfqws_flow_exempt_rule \"$rule\"\n\t}\n}\n_nft_fw_nfqws_post6()\n{\n\t# $1 - filter ipv6\n\t# $2 - queue number\n\t# $3 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\" rule chain=$(get_postchain) setmark\n\t\tnft_print_op \"$filter\" \"nfqws postrouting (qnum $port)\" 6\n\t\trule=\"${3:+oifname @wanif6} $(nft_mark_filter) $filter ip6 daddr != @nozapret6\"\n\t\tis_postnat && setmark=\"meta mark set meta mark or $DESYNC_MARK_POSTNAT\"\n\t\tnft_insert_rule $chain $rule $setmark $CONNMARKER $FW_EXTRA_POST queue num $port bypass\n\t\tnft_add_nfqws_flow_exempt_rule \"$rule\"\n\t}\n}\nnft_fw_nfqws_post()\n{\n\t# $1 - filter ipv4\n\t# $2 - filter ipv6\n\t# $3 - queue number\n\n\tnft_fw_nfqws_post4 \"$1\" $3\n\tnft_fw_nfqws_post6 \"$2\" $3\n}\n\n_nft_fw_nfqws_pre4()\n{\n\t# $1 - filter ipv4\n\t# $2 - queue number\n\t# $3 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV4\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\" rule\n\t\tnft_print_op \"$filter\" \"nfqws prerouting (qnum $port)\" 4\n\t\trule=\"${3:+iifname @wanif} $filter ip saddr != @nozapret\"\n\t\tnft_insert_rule $(get_prechain) $rule $CONNMARKER $FW_EXTRA_POST queue num $port bypass\n\t}\n}\n_nft_fw_nfqws_pre6()\n{\n\t# $1 - filter ipv6\n\t# $2 - queue number\n\t# $3 - not-empty if wan interface filtering required\n\n\t[ \"$DISABLE_IPV6\" = \"1\" -o -z \"$1\" ] || {\n\t\tlocal filter=\"$1\" port=\"$2\" rule\n\t\tnft_print_op \"$filter\" \"nfqws prerouting (qnum $port)\" 6\n\t\trule=\"${3:+iifname @wanif6} $filter ip6 saddr != @nozapret6\"\n\t\tnft_insert_rule $(get_prechain) $rule $CONNMARKER $FW_EXTRA_POST queue num $port bypass\n\t}\n}\nnft_fw_nfqws_pre()\n{\n\t# $1 - filter ipv4\n\t# $2 - filter ipv6\n\t# $3 - queue number\n\n\tnft_fw_nfqws_pre4 \"$1\" $3\n\tnft_fw_nfqws_pre6 \"$2\" $3\n}\n\nnft_fw_nfqws_both4()\n{\n\t# $1 - filter ipv4\n\t# $2 - queue number\n\tnft_fw_nfqws_post4 \"$@\"\n\tnft_fw_nfqws_pre4 \"$(nft_reverse_nfqws_rule $1)\" $2\n}\nnft_fw_nfqws_both6()\n{\n\t# $1 - filter ipv6\n\t# $2 - queue number\n\tnft_fw_nfqws_post6 \"$@\"\n\tnft_fw_nfqws_pre6 \"$(nft_reverse_nfqws_rule $1)\" $2\n}\nnft_fw_nfqws_both()\n{\n\t# $1 - filter ipv4\n\t# $2 - filter ipv6\n\t# $3 - queue number\n\tnft_fw_nfqws_both4 \"$1\" \"$3\"\n\tnft_fw_nfqws_both6 \"$2\" \"$3\"\n}\n\nzapret_reload_ifsets()\n{\n\tnft_only nft_create_table ; nft_fill_ifsets_overload\n\treturn 0\n}\nzapret_list_ifsets()\n{\n\tnft_only nft_list_ifsets\n\treturn 0\n}\nzapret_list_table()\n{\n\tnft_only nft_list_table\n\treturn 0\n}\n\n\n\nnft_fw_reverse_nfqws_rule4()\n{\n\tnft_fw_nfqws_pre4 \"$(nft_reverse_nfqws_rule \"$1\")\" $2\n}\nnft_fw_reverse_nfqws_rule6()\n{\n\tnft_fw_nfqws_pre6 \"$(nft_reverse_nfqws_rule \"$1\")\" $2\n}\nnft_fw_reverse_nfqws_rule()\n{\n\t# ensure that modes relying on incoming traffic work\n\t# $1 - rule4\n\t# $2 - rule6\n\t# $3 - queue number\n\tnft_fw_reverse_nfqws_rule4 \"$1\" $3\n\tnft_fw_reverse_nfqws_rule6 \"$2\" $3\n}\n\nnft_first_packets()\n{\n\t# $1 - packet count\n\t[ -n \"$1\" -a \"$1\" != keepalive ] && [ \"$1\" -ge 1 ] &&\n\t{\n\t\tif [ \"$1\" = 1 ] ; then\n\t\t\techo \"$nft_connbytes 1\"\n\t\telse\n\t\t\techo \"$nft_connbytes 1-$1\"\n\t\tfi\n\t}\n}\n\nnft_apply_nfqws_in_out()\n{\n\t# $1 - tcp,udp\n\t# $2 - ports\n\t# $3 - PKT_OUT. special value : 'keepalive'\n\t# $4 - PKT_IN\n\tlocal f4 f6 first_packets_only\n\t[ -n \"$2\" ] || return\n\t[ -n \"$3\" -a \"$3\" != 0 ] &&\n\t{\n\t\tfirst_packets_only=\"$(nft_first_packets $3)\"\n\t\tf4=\"$1 dport {$2} $first_packets_only\"\n\t\tf6=$f4\n\t\tnft_filter_apply_ipset_target f4 f6\n\t\tnft_fw_nfqws_post \"$f4\" \"$f6\" $QNUM\n\t}\n\t[ -n \"$4\" -a \"$4\" != 0 ] &&\n\t{\n\t\tfirst_packets_only=\"$(nft_first_packets $4)\"\n\t\tf4=\"$1 dport {$2} $first_packets_only\"\n\t\tf6=$f4\n\t\tnft_filter_apply_ipset_target f4 f6\n\t\tnft_fw_reverse_nfqws_rule \"$f4\" \"$f6\" $QNUM\n\t}\n}\n\nzapret_apply_firewall_standard_tpws_rules_nft()\n{\n\tlocal f4 f6\n\n\t[ \"$TPWS_ENABLE\" = 1 -a -n \"$TPWS_PORTS\" ] && {\n\t\tf4=\"tcp dport {$TPWS_PORTS}\"\n\t\tf6=$f4\n\t\tnft_filter_apply_ipset_target f4 f6\n\t\tnft_fw_tpws \"$f4\" \"$f6\" $TPPORT\n\t}\n}\nzapret_apply_firewall_standard_nfqws_rules_nft()\n{\n\t[ \"$NFQWS_ENABLE\" = 1 ] && {\n\t\tnft_apply_nfqws_in_out tcp \"$NFQWS_PORTS_TCP\" \"$NFQWS_TCP_PKT_OUT\" \"$NFQWS_TCP_PKT_IN\"\n\t\tnft_apply_nfqws_in_out tcp \"$NFQWS_PORTS_TCP_KEEPALIVE\" keepalive \"$NFQWS_TCP_PKT_IN\"\n\t\tnft_apply_nfqws_in_out udp \"$NFQWS_PORTS_UDP\" \"$NFQWS_UDP_PKT_OUT\" \"$NFQWS_UDP_PKT_IN\"\n\t\tnft_apply_nfqws_in_out udp \"$NFQWS_PORTS_UDP_KEEPALIVE\" keepalive \"$NFQWS_UDP_PKT_IN\"\n\t}\n}\nzapret_apply_firewall_standard_rules_nft()\n{\n\tzapret_apply_firewall_standard_tpws_rules_nft\n\tzapret_apply_firewall_standard_nfqws_rules_nft\n}\n\nzapret_apply_firewall_rules_nft()\n{\n\tzapret_apply_firewall_standard_rules_nft\n\tcustom_runner zapret_custom_firewall_nft\n}\n\nzapret_apply_firewall_nft()\n{\n\techo Applying nftables\n\n\tcreate_ipset no-update\n\tnft_create_firewall\n\tnft_fill_ifsets_overload\n\n\tzapret_apply_firewall_rules_nft\n\n\t[ \"$FLOWOFFLOAD\" = 'software' -o \"$FLOWOFFLOAD\" = 'hardware' ] && nft_apply_flow_offloading\n\n\treturn 0\n}\nzapret_unapply_firewall_nft()\n{\n\techo Clearing nftables\n\n\tunprepare_route_localnet\n\tnft_del_firewall\n\tcustom_runner zapret_custom_firewall_nft_flush\n\treturn 0\n}\nzapret_do_firewall_nft()\n{\n\t# $1 - 1 - add, 0 - del\n\n\tif [ \"$1\" = 0 ] ; then\n\t\tzapret_unapply_firewall_nft\n\telse\n\t\tzapret_apply_firewall_nft\n\tfi\n\n\treturn 0\n}\n\n# ctmark is not available in POSTNAT mode\nCONNMARKER=\n[ \"$FILTER_TTL_EXPIRED_ICMP\" = 1 ] && is_postnat && CONNMARKER=\"ct mark set ct mark or $DESYNC_MARK\"\n"
  },
  {
    "path": "common/pf.sh",
    "content": "PF_MAIN=\"/etc/pf.conf\"\nPF_ANCHOR_DIR=\"/etc/pf.anchors\"\nPF_ANCHOR_ZAPRET=\"$PF_ANCHOR_DIR/zapret\"\nPF_ANCHOR_ZAPRET_V4=\"$PF_ANCHOR_DIR/zapret-v4\"\nPF_ANCHOR_ZAPRET_V6=\"$PF_ANCHOR_DIR/zapret-v6\"\n\nstd_ports\n\npf_anchor_root_reload()\n{\n\techo reloading PF root anchor\n\tpfctl -qf \"$PF_MAIN\"\n}\n\npf_anchor_root()\n{\n\tlocal patch\n\t[ -f \"$PF_MAIN\" ] && {\n\t\tgrep -q '^rdr-anchor \"zapret\"$' \"$PF_MAIN\" || {\n\t\t\techo patching rdr-anchor in $PF_MAIN\n\t\t\tpatch=1\n\t\t\tsed -i '' -e '/^rdr-anchor \"com\\.apple\\/\\*\"$/i \\\nrdr-anchor \"zapret\"\n' $PF_MAIN\n\t\t}\n\t\tgrep -q '^anchor \"zapret\"$' \"$PF_MAIN\" || {\n\t\t\techo patching anchor in $PF_MAIN\n\t\t\tpatch=1\n\t\t\tsed -i '' -e '/^anchor \"com\\.apple\\/\\*\"$/i \\\nanchor \"zapret\"\n' $PF_MAIN\n\t\t}\n\t\tgrep -q \"^set limit table-entries\" \"$PF_MAIN\" || {\n\t\t\techo patching table-entries limit\n\t\t\tpatch=1\n\t\t\tsed -i '' -e '/^scrub-anchor \"com\\.apple\\/\\*\"$/i \\\nset limit table-entries 5000000\n' $PF_MAIN\n\t\t}\n\n\t\tgrep -q '^anchor \"zapret\"$' \"$PF_MAIN\" &&\n\t\tgrep -q '^rdr-anchor \"zapret\"$' \"$PF_MAIN\" &&\n\t\tgrep -q '^set limit table-entries' \"$PF_MAIN\" && {\n\t\t\tif [ -n \"$patch\" ]; then\n\t\t\t\techo successfully patched $PF_MAIN\n\t\t\t\tpf_anchor_root_reload\n\t\t\telse\n\t\t\t\techo successfully checked zapret anchors in $PF_MAIN\n\t\t\tfi\n\t\t\treturn 0\n\t\t}\n\t}\n\techo ----------------------------------\n\techo Automatic $PF_MAIN patching failed. You must apply root anchors manually in your PF config.\n\techo rdr-anchor \\\"zapret\\\"\n\techo anchor \\\"zapret\\\"\n\techo ----------------------------------\n\treturn 1\n}\npf_anchor_root_del()\n{\n\tsed -i '' -e '/^anchor \"zapret\"$/d' -e '/^rdr-anchor \"zapret\"$/d' -e '/^set limit table-entries/d' \"$PF_MAIN\"\n}\n\npf_anchor_zapret()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\tif [ -f \"$ZIPLIST_EXCLUDE\" ]; then\n\t\t\techo \"table <nozapret> persist file \\\"$ZIPLIST_EXCLUDE\\\"\"\n\t\telse\n\t\t\techo \"table <nozapret> persist\"\n\t\tfi\n\t}\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\tif [ -f \"$ZIPLIST_EXCLUDE6\" ]; then\n\t\t\techo \"table <nozapret6> persist file \\\"$ZIPLIST_EXCLUDE6\\\"\"\n\t\telse\n\t\t\techo \"table <nozapret6> persist\"\n\t\tfi\n\t}\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || echo \"rdr-anchor \\\"/zapret-v4\\\" inet to !<nozapret>\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || echo \"rdr-anchor \\\"/zapret-v6\\\" inet6 to !<nozapret6>\"\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || echo \"anchor \\\"/zapret-v4\\\" inet to !<nozapret>\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || echo \"anchor \\\"/zapret-v6\\\" inet6 to !<nozapret6>\"\n}\npf_anchor_zapret_tables()\n{\n\t# $1 - variable to receive applied table names\n\t# $2/$3 $4/$5 ...  table_name/table_file\n\tlocal tblv=$1\n\tlocal _tbl\n\n\tshift\n\t[ \"$MODE_FILTER\" = \"ipset\" ] &&\n\t{\n\t\twhile [ -n \"$1\" ] && [ -n \"$2\" ] ; do\n\t\t\t[ -f \"$2\" ] && {\n\t\t\t\techo \"table <$1> file \\\"$2\\\"\"\n\t\t\t\t_tbl=\"$_tbl<$1> \"\n\t\t\t}\n\t\t\tshift\n\t\t\tshift\n\t\tdone\n\t}\n\t[ -n \"$_tbl\" ] || _tbl=\"any\"\n\n\teval $tblv=\"\\\"\\$_tbl\\\"\"\n}\npf_nat_reorder_rules()\n{\n\t# this is dirty hack to move rdr above route-to\n        # use only first word as a key and preserve order within a single key\n\tsort -srfk 1,1\n}\n\npf_anchor_zapret_v4_tpws()\n{\n\t# $1 - tpws listen port\n\t# $2 - rdr ports\n\n\tlocal rule port=\"{$2}\"\n\tfor lan in $IFACE_LAN; do\n\t\tfor t in $tbl; do\n\t\t\t echo \"rdr on $lan inet proto tcp from any to $t port $port -> 127.0.0.1 port $1\"\n\t\tdone\n\tdone\n\techo \"rdr on lo0 inet proto tcp from !127.0.0.0/8 to any port $port -> 127.0.0.1 port $1\"\n\tfor t in $tbl; do\n\t\trule=\"route-to (lo0 127.0.0.1) inet proto tcp from !127.0.0.0/8 to $t port $port user { >root }\"\n\t\tif [ -n \"$IFACE_WAN\" ] ; then\n\t\t\tfor wan in $IFACE_WAN; do\n\t\t\t\techo \"pass out on $wan $rule\"\n\t\t\tdone\n\t\telse\n\t\t\techo \"pass out $rule\"\n\t\tfi\n\tdone\n}\n\npf_anchor_zapret_v4()\n{\n\tlocal tbl port\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\t{\n\t\t\tpf_anchor_zapret_tables tbl zapret-user \"$ZIPLIST_USER\" zapret \"$ZIPLIST\"\n\t\t\tcustom_runner zapret_custom_firewall_v4\n\t\t\t[ \"$TPWS_ENABLE\" = 1 -a -n \"$TPWS_PORTS\" ] && pf_anchor_zapret_v4_tpws $TPPORT \"$TPWS_PORTS_IPT\"\n\t\t} | pf_nat_reorder_rules\n\t}\n}\npf_anchor_zapret_v6_tpws()\n{\n\t# $1 - tpws listen port\n\t# $2 - rdr ports\n\n\tlocal rule LL_LAN port=\"{$2}\"\n\n\t# LAN link local is only for router\n\tfor lan in $IFACE_LAN; do\n\t\tLL_LAN=$(get_ipv6_linklocal $lan)\n\t\t[ -n \"$LL_LAN\" ] && {\n\t\t\tfor t in $tbl; do\n\t\t\t\techo \"rdr on $lan inet6 proto tcp from any to $t port $port -> $LL_LAN port $1\"\n\t\t\tdone\n\t\t}\n\tdone\n\techo \"rdr on lo0 inet6 proto tcp from !::1 to any port $port -> fe80::1 port $1\"\n\tfor t in $tbl; do\n\t\trule=\"route-to (lo0 fe80::1) inet6 proto tcp from !::1 to $t port $port user { >root }\"\n\t\tif [ -n \"${IFACE_WAN6:-$IFACE_WAN}\" ] ; then\n\t\t\tfor wan in ${IFACE_WAN6:-$IFACE_WAN}; do\n\t\t\t\techo \"pass out on $wan $rule\"\n\t\t\tdone\n\t\telse\n\t\t\techo \"pass out $rule\"\n\t\tfi\n\tdone\n}\npf_anchor_zapret_v6()\n{\n\tlocal tbl port\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\t{\n\t\t\tpf_anchor_zapret_tables tbl zapret-user \"$ZIPLIST_USER\" zapret \"$ZIPLIST\"\n\t\t\tcustom_runner zapret_custom_firewall_v6\n\t\t\t[ \"$TPWS_ENABLE\" = 1 -a -n \"$TPWS_PORTS_IPT\" ] && pf_anchor_zapret_v6_tpws $TPPORT \"$TPWS_PORTS_IPT\"\n\t\t} | pf_nat_reorder_rules\n\t}\n}\n\npf_anchors_create()\n{\n\twait_lan_ll\n\tpf_anchor_zapret >\"$PF_ANCHOR_ZAPRET\"\n\tpf_anchor_zapret_v4 >\"$PF_ANCHOR_ZAPRET_V4\"\n\tpf_anchor_zapret_v6 >\"$PF_ANCHOR_ZAPRET_V6\"\n}\npf_anchors_del()\n{\n\trm -f \"$PF_ANCHOR_ZAPRET\" \"$PF_ANCHOR_ZAPRET_V4\" \"$PF_ANCHOR_ZAPRET_V6\"\n}\npf_anchors_load()\n{\n\techo loading zapret anchor from \"$PF_ANCHOR_ZAPRET\"\n\tpfctl -qa zapret -f \"$PF_ANCHOR_ZAPRET\" || {\n\t\techo error loading zapret anchor\n\t\treturn 1\n\t}\n\tif [ \"$DISABLE_IPV4\" = \"1\" ]; then\n\t\techo clearing zapret-v4 anchor\n\t\tpfctl -qa zapret-v4 -F all 2>/dev/null\n\telse\n\t\techo loading zapret-v4 anchor from \"$PF_ANCHOR_ZAPRET_V4\"\n\t\tpfctl -qa zapret-v4 -f \"$PF_ANCHOR_ZAPRET_V4\" || {\n\t\t\techo error loading zapret-v4 anchor\n\t\t\treturn 1\n\t\t}\n\tfi\n\tif [ \"$DISABLE_IPV6\" = \"1\" ]; then\n\t\techo clearing zapret-v6 anchor\n\t\tpfctl -qa zapret-v6 -F all 2>/dev/null\n\telse\n\t\techo loading zapret-v6 anchor from \"$PF_ANCHOR_ZAPRET_V6\"\n\t\tpfctl -qa zapret-v6 -f \"$PF_ANCHOR_ZAPRET_V6\" || {\n\t\t\techo error loading zapret-v6 anchor\n\t\t\treturn 1\n\t\t}\n\tfi\n\techo successfully loaded PF anchors\n\treturn 0\n}\npf_anchors_clear()\n{\n\techo clearing zapret anchors\n\tpfctl -qa zapret-v4 -F all 2>/dev/null\n\tpfctl -qa zapret-v6 -F all 2>/dev/null\n\tpfctl -qa zapret -F all 2>/dev/null\n}\npf_enable()\n{\n\techo enabling PF\n\tpfctl -qe\n}\npf_table_reload()\n{\n\techo reloading zapret tables\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || pfctl -qTl -a zapret-v4 -f \"$PF_ANCHOR_ZAPRET_V4\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || pfctl -qTl -a zapret-v6 -f \"$PF_ANCHOR_ZAPRET_V6\"\n\tpfctl -qTl -a zapret -f \"$PF_ANCHOR_ZAPRET\"\n}\n"
  },
  {
    "path": "common/virt.sh",
    "content": "get_virt()\n{\n\tlocal vm s v UNAME\n\tUNAME=$(uname)\n\tcase \"$UNAME\" in\n\t\tLinux)\n\t\t\tif exists systemd-detect-virt; then\n\t\t\t\tvm=$(systemd-detect-virt --vm)\n\t\t\telif [ -f /sys/class/dmi/id/product_name ]; then\n\t\t\t\tread s </sys/class/dmi/id/product_name\n\t\t\t\tfor v in KVM QEMU VMware VMW VirtualBox Xen Bochs Parallels BHYVE Hyper-V; do\n\t\t\t\t\tcase \"$s\" in\n\t\t\t\t\t\t\"$v\"*)\n\t\t\t\t\t\tvm=$v\n\t\t\t\t\t\tbreak\n\t\t    \t\t\t;;\n\t\t\t\t\tesac\n\t\t\t\tdone\n\t\t\tfi\n\t\t\t;;\n\tesac\n\techo \"$vm\" | awk '{print tolower($0)}'\n}\ncheck_virt()\n{\n\techo \\* checking virtualization\n\tlocal vm=\"$(get_virt)\"\n\tif [ -n \"$vm\" ]; then\n\t\tif [ \"$vm\" = \"none\" ]; then\n\t\t\techo running on bare metal\n\t\telse\n\t\t\techo \"!!! WARNING. $vm virtualization detected !!!\"\n\t\t\techo '!!! WARNING. vmware and virtualbox are known to break most of the DPI bypass techniques when network is NATed using internal hypervisor NAT !!!'\n\t\t\techo '!!! WARNING. if this is your case make sure you are bridged not NATed !!!'\n\t\tfi\n\telse\n\t\techo cannot detect\n\tfi\n}\n"
  },
  {
    "path": "config.default",
    "content": "# this file is included from init scripts\n# change values here\n\n# can help in case /tmp has not enough space\n#TMPDIR=/opt/zapret/tmp\n\n# redefine user for zapret daemons. required on Keenetic\n#WS_USER=nobody\n\n# override firewall type : iptables,nftables,ipfw\n#FWTYPE=iptables\n# nftables only : set this to 0 to use pre-nat mode. default is post-nat.\n# pre-nat mode disables some bypass techniques for forwarded traffic but allows to see client IP addresses in debug log\n#POSTNAT=0\n\n# options for ipsets\n# maximum number of elements in sets. also used for nft sets\nSET_MAXELEM=522288\n# too low hashsize can cause memory allocation errors on low RAM systems , even if RAM is enough\n# too large hashsize will waste lots of RAM\nIPSET_OPT=\"hashsize 262144 maxelem $SET_MAXELEM\"\n# dynamically generate additional ip. $1 = ipset/nfset/table name\n#IPSET_HOOK=\"/etc/zapret.ipset.hook\"\n\n# options for ip2net. \"-4\" or \"-6\" auto added by ipset create script\nIP2NET_OPT4=\"--prefix-length=22-30 --v4-threshold=3/4\"\nIP2NET_OPT6=\"--prefix-length=56-64 --v6-threshold=5\"\n# options for auto hostlist\nAUTOHOSTLIST_RETRANS_THRESHOLD=3\nAUTOHOSTLIST_FAIL_THRESHOLD=3\nAUTOHOSTLIST_FAIL_TIME=60\n# 1 = debug autohostlist positives to ipset/zapret-hosts-auto-debug.log\nAUTOHOSTLIST_DEBUGLOG=0\n\n# number of parallel threads for domain list resolves\nMDIG_THREADS=30\n\n# ipset/*.sh can compress large lists\nGZIP_LISTS=1\n# command to reload ip/host lists after update\n# comment or leave empty for auto backend selection : ipset or ipfw if present\n# on BSD systems with PF no auto reloading happens. you must provide your own command\n# set to \"-\" to disable reload\n#LISTS_RELOAD=\"pfctl -f /etc/pf.conf\"\n\n# mark bit used by nfqws to prevent loop\nDESYNC_MARK=0x40000000\nDESYNC_MARK_POSTNAT=0x20000000\n\n# do not pass outgoing traffic to tpws/nfqws not marked with this bit\n# this setting allows to write your own rules to limit traffic that should be fooled\n# for example based on source IP or incoming interface name\n# no filter if not defined\n#FILTER_MARK=0x10000000\n\nTPWS_SOCKS_ENABLE=0\n# tpws socks listens on this port on localhost and LAN interfaces\nTPPORT_SOCKS=987\n# use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir\n# hostlist markers are replaced to empty string if MODE_FILTER does not satisfy\n# <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list\nTPWS_SOCKS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\n\"\n\nTPWS_ENABLE=0\nTPWS_PORTS=80,443\n# use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir\n# hostlist markers are replaced to empty string if MODE_FILTER does not satisfy\n# <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list\nTPWS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\n\"\n\nNFQWS_ENABLE=0\n# redirect outgoing traffic with connbytes limiter applied in both directions.\nNFQWS_PORTS_TCP=80,443\nNFQWS_PORTS_UDP=443\n# PKT_OUT means connbytes dir original\n# PKT_IN means connbytes dir reply\n# this is --dpi-desync-cutoff=nX kernel mode implementation for linux. it saves a lot of CPU.\nNFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_TCP_PKT_IN=3\nNFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_UDP_PKT_IN=0\n# redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter\n# normally it's needed only for stateless DPI that matches every packet in a single TCP session\n# typical example are plain HTTP keep alives\n# this mode can be very CPU consuming. enable with care !\n#NFQWS_PORTS_TCP_KEEPALIVE=80\n#NFQWS_PORTS_UDP_KEEPALIVE=\n# use <HOSTLIST> and <HOSTLIST_NOAUTO> placeholders to engage standard hostlists and autohostlist in ipset dir\n# hostlist markers are replaced to empty string if MODE_FILTER does not satisfy\n# <HOSTLIST_NOAUTO> appends ipset/zapret-hosts-auto.txt as normal list\nNFQWS_OPT=\"\n--filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig <HOSTLIST> --new\n--filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig <HOSTLIST> --new\n--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 <HOSTLIST_NOAUTO>\n\"\n\n# none,ipset,hostlist,autohostlist\nMODE_FILTER=none\n\n# donttouch,none,software,hardware\nFLOWOFFLOAD=donttouch\n\n# openwrt: specify networks to be treated as LAN. default is \"lan\"\n#OPENWRT_LAN=\"lan lan2 lan3\"\n# openwrt: specify networks to be treated as WAN. default wans are interfaces with default route\n#OPENWRT_WAN4=\"wan vpn\"\n#OPENWRT_WAN6=\"wan6 vpn6\"\n\n# for routers based on desktop linux and macos. has no effect in openwrt.\n# CHOOSE LAN and optinally WAN/WAN6 NETWORK INTERFACES\n# or leave them commented if its not router\n# it's possible to specify multiple interfaces like this : IFACE_LAN=\"eth0 eth1 eth2\"\n# if IFACE_WAN6 is not defined it take the value of IFACE_WAN\n#IFACE_LAN=eth0\n#IFACE_WAN=eth1\n#IFACE_WAN6=\"ipsec0 wireguard0 he_net\"\n\n# should start/stop command of init scripts apply firewall rules ?\n# not applicable to openwrt with firewall3+iptables\nINIT_APPLY_FW=1\n# firewall apply hooks\n#INIT_FW_PRE_UP_HOOK=\"/etc/firewall.zapret.hook.pre_up\"\n#INIT_FW_POST_UP_HOOK=\"/etc/firewall.zapret.hook.post_up\"\n#INIT_FW_PRE_DOWN_HOOK=\"/etc/firewall.zapret.hook.pre_down\"\n#INIT_FW_POST_DOWN_HOOK=\"/etc/firewall.zapret.hook.post_down\"\n\n# do not work with ipv4\n#DISABLE_IPV4=1\n# do not work with ipv6\nDISABLE_IPV6=1\n\n# drop icmp time exceeded messages for nfqws tampered connections\n# in POSTNAT mode this can interfere with default mtr/traceroute in tcp or udp mode. use source port not redirected to nfqws\n# set to 0 if you are not expecting connection breakage due to icmp in response to TCP SYN or UDP\nFILTER_TTL_EXPIRED_ICMP=1\n\n# select which init script will be used to get ip or host list\n# possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh\n# comment if not required\n#GETLIST=\n"
  },
  {
    "path": "docs/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2016-2024 bol-van\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "docs/bsd.en.md",
    "content": "## Table of contents\n\n- [Table of contents](#table-of-contents)\n- [Supported versions](#supported-versions)\n- [BSD features](#bsd-features)\n- [FreeBSD](#freebsd)\n  - [`dvtws` quick start](#dvtws-quick-start)\n  - [PF in FreeBSD](#pf-in-freebsd)\n  - [`pfsense`](#pfsense)\n- [OpenBSD](#openbsd)\n- [MacOS](#macos)\n  - [MacOS easy install](#macos-easy-install)\n\n## Supported versions\n\nFreeBSD 11.x+ , OpenBSD 6.x+, partially MacOS Sierra+\n\nOlder versions may work or not.\n\n## BSD features\n\nBSD does not have NFQUEUE. Similar mechanism - divert sockets. In BSD compiling\nthe source from nfq directory result in `dvtws` binary instead of `nfqws`.\n`dvtws` shares most of the code with `nfqws` and offers almost identical\nparameters.\n\nFreeBSD has 3 firewalls: IPFilter, ipfw and Packet Filter (PF). OpenBSD has\nonly PF.\n\nTo compile sources:\n\n- FreeBSD: `make`\n- OpenBSD: `make bsd`\n- MacOS: `make mac`\n\nCompile all programs:\n```\nmake -C /opt/zapret\n```\n\nDivert sockets are internal type sockets in the BSD kernel. They have no\nrelation to network addresses or network packet exchange. They are identified\nby a port number `1..65535`. Its like queue number in NFQUEUE. Traffic can be\ndiverted to a divert socket using firewall rule. If nobody listens on the\nspecified divert port packets are dropped. Its similar to NFQUEUE without\n`--queue-bypass`.\n\n`ipset/*.sh` scripts work with ipfw lookup tables if ipfw is present.\n\nipfw table is analog to linux `ipset`. Unlike ipsets ipfw tables share v4 an v6\naddresses and subnets.\n\n- If ipfw is absent scripts check LISTS_RELOAD config variable.\n- If its present then scripts execute a command from LISTS_RELOAD.\n- If LISTS_RELOAD=- scripts do not load tables even if ipfw exists.\n\nPF can load ip tables from a file. To use this feature with `ipset/*.sh` scripts disable gzip file creation\nusing `GZIP_LISTS=0` directive in the `/opt/zapret/config` file.\n\nBSD kernel doesn't implement splice syscall. tpws uses regular recv/send\noperations with data copying to user space. Its slower but not critical.\n\n`tpws` uses nonblocking sockets with linux specific epoll feature. In BSD systems\nepoll is emulated by epoll-shim library on top of kqueue.\n\n`dvtws` uses some programming HACKs, assumptions and knowledge of discovered\nbugs and limitations. BSD systems have many limitations, version specific\nfeatures and bugs in low level networking, especially for ipv6. Many years have\npassed but BSD code still has 15-20 year artificial limiters in the code. `dvtws`\nuses additinal divert socket(s) for layer 3 packet injection if raw sockets do\nnot allow it. It works for the moment but who knows. Such a usage is not very\ndocumented.\n\n`mdig` and `ip2net` are fully compatible with BSD.\n\n\n## FreeBSD\n\nDivert sockets require special kernel module `ipdivert`.\nWrite the following to config files:\n\n`/boot/loader.conf` (create if absent):\n```\nipdivert_load=\"YES\"\nnet.inet.ip.fw.default_to_accept=1\n```\n\n`/etc/rc.conf`:\n```\nfirewall_enable=\"YES\"\nfirewall_script=\"/etc/rc.firewall.my\"\n```\n\n`/etc/rc.firewall.my`:\n```\nipfw -q -f flush\n```\n\nLater you will add ipfw commands to `/etc/rc.firewall.my` to be reapplied after reboot.\nYou can also run zapret daemons from there. Start them with `--daemon` options, for example\n```\npkill ^dvtws$\n/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\nTo restart firewall and daemons run : `/etc/rc.d/ipfw restart`\n\nAssume `LAN=\"em1\"`, `WAN=\"em0\"`.\n\n`tpws` transparent mode quick start.\n\nFor all traffic:\n```\nipfw delete 100\nipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon\nipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon\nipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\nipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\nProcess only table zapret with the exception of table nozapret:\n```\nipfw delete 100\nipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\nipfw add 100 fwd 127.0.0.1,988 tcp from me to table\\(zapret\\) 80,443 proto ip4 xmit em0 not uid daemon\nipfw add 100 fwd ::1,988 tcp from me to table\\(zapret\\) 80,443 proto ip6 xmit em0 not uid daemon\nipfw add 100 allow tcp from any to table\\(nozapret\\) 80,443 recv em1\nipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\nipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\nTables zapret, nozapret, ipban are created by `ipset/*.sh` scripts the same way as in Linux.\nIts a good idea to update tables periodically:\n```\n crontab -e\n```\n\nWrite the line:\n```\n0 12 */2 * * /opt/zapret/ipset/get_config.sh\n```\n\nWhen using `ipfw`, `tpws` does not require special permissions for transparent\nmode. However without root its not possible to bind to ports less than 1024 and\nchange UID/GID. Without changing UID tpws will run into recursive loop, and\nthat's why its necessary to write ipfw rules with the right UID. Redirecting to\nports greater than or equal to 1024 is dangerous. If tpws is not running any\nunprivileged process can listen to that port and intercept traffic.\n\n### `dvtws` quick start\n\nFor all traffic:\n```\nipfw delete 100\nipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0\n# required for autottl mode only\nipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0\n/opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\nProcess only table zapret with the exception of table nozapret:\n```\nipfw delete 100\nipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\nipfw add 100 divert 989 tcp from any to table\\(zapret\\) 80,443 out not diverted not sockarg xmit em0\n# required for autottl mode only\nipfw add 100 divert 989 tcp from table\\(zapret\\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0\n/opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\nReinjection loop avoidance. FreeBSD artificially ignores sockarg for ipv6 in\nthe kernel. This limitation is coming from the ipv6 early age. Code is still in\n\"testing\" state. 10-20 years. Everybody forgot about it. `dvtws` sends ipv6\nforged frames using another divert socket (HACK). they can be filtered out\nusing 'diverted'. ipv4 frames are filtered using 'sockarg'.\n\n### PF in FreeBSD\n\nThe setup is similar to OpenBSD, but there are important nuances.\n1. PF support is disabled by default in FreeBSD. Use parameter `--enable-pf`.\n2. It's not possible to redirect to `::1`. Need to redirect to the link-local\n   address of the incoming interface. Look for fe80:... address in ifconfig and\n   use it for redirection target.\n3. pf.conf syntax is a bit different from OpenBSD.\n4. How to set maximum table size : sysctl net.pf.request_maxcount=2000000\n5. `divert-to` is broken. Loop avoidance scheme does not work.\n   This makes `dvtws` unusable with pf.\n   Someone posted kernel patch but 14-RELEASE is still broken.\n\n`/etc/pf.conf`:\n```\nrdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988\nrdr pass on em1 inet  proto tcp to port {80,443} -> 127.0.0.1 port 988\n```\n\nThen:\n```\n/opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force\n```\n\nIts not clear how to do rdr-to outgoing traffic. I could not make route-to\nscheme work.\n\n\n### `pfsense`\n\n`pfsense` is based on FreeBSD. Binaries from `binaries/freebsd-x64` are\ncompiled in FreeBSD 11 and should work. Use `install_bin.sh`. pfsense uses pf\nfirewall which does not support divert. Fortunately ipfw and ipdivert modules\nare present and can be kldload-ed. In older versions it's also necessary to\nchange firewall order using sysctl commands. In newer versions those sysctl\nparameters are absent but the system behaves as required without them.\nSometimes pf may limit `dvtws` abilities. It scrubs ip fragments disabling `dvtws`\nipfrag2 desync mode.\n\nThere's autostart script example in `init.d/pfsense`. It should be placed to\n`/usr/local/etc/rc.d` and edited. Write your ipfw rules and daemon start\ncommands.\ncurl is present by default. You can use it to download `tar.gz` release directly from github.\nOr you can copy files using sftp.\n\nCopy zip with zapret files to `/opt` and unpack there as it's done in other\nsystems. In this case run `dvtws` as `/opt/zapret/nfq/dvtws`. Or just copy\n`dvtws` to `/usr/local/sbin`. As you wish. `ipset` scripts are working, cron is\npresent. It's possible to renew lists.\n\nIf you dont like poverty of default repos its possible to enable FreeBSD repo.\nChange `no` to `yes` in `/usr/local/etc/pkg/repos/FreeBSD.conf` and `/usr/local/etc/pkg/repos/pfSense.conf`.\n\n`/usr/local/etc/rc.d/zapret.sh` (chmod 755)\n```\n#!/bin/sh\n\nkldload ipfw\nkldload ipdivert\n\n# for older pfsense versions. newer do not have these sysctls\nsysctl net.inet.ip.pfil.outbound=ipfw,pf\nsysctl net.inet.ip.pfil.inbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.outbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.inbound=ipfw,pf\n\nipfw delete 100\nipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0\npkill ^dvtws$\ndvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n\n# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state\npfctl -d ; pfctl -e\n```\n\nI could not make tpws work from ipfw. Looks like there's some conflict between\ntwo firewalls. Only PF redirection works. PF does not allow to freely add and\ndelete rules. Only anchors can be reloaded. To make an anchor work it must be\nreferred from the main ruleset. But its managed by pfsense scripts.\n\nOne possible solution would be to modify `/etc/inc/filter.inc` as follows:\n```\n    .................\n    /* MOD */\n    $natrules .= \"# ZAPRET redirection\\n\";\n    $natrules .= \"rdr-anchor \\\"zapret\\\"\\n\";\n\n    $natrules .= \"# TFTP proxy\\n\";\n    $natrules .= \"rdr-anchor \\\"tftp-proxy/*\\\"\\n\";\n    .................\n```\n\nWrite the anchor code to `/etc/zapret.anchor`:\n```\nrdr pass on em1 inet  proto tcp to port {80,443} -> 127.0.0.1 port 988\nrdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988\n```\nReplace `fe80::20c:29ff:5ae3:4821` with your link local address of the LAN\ninterface or remove the line if ipv6 is not needed.\n\nAutostart `/usr/local/etc/rc.d/zapret.sh`:\n```\npfctl -a zapret -f /etc/zapret.anchor\npkill ^tpws$\ntpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2\n```\n\nAfter reboot check that anchor is created and referred from the main ruleset:\n```\n[root@pfSense /]# pfctl -s nat\nno nat proto carp all\nnat-anchor \"natearly/*\" all\nnat-anchor \"natrules/*\" all\n...................\nno rdr proto carp all\nrdr-anchor \"zapret\" all\nrdr-anchor \"tftp-proxy/*\" all\nrdr-anchor \"miniupnpd\" all\n[root@pfSense /]# pfctl -s nat -a zapret\nrdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988\nrdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988\nrdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988\nrdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988\n```\n\nAlso there's a way to add redirect in the pfsense UI and start `tpws` from cron using `@reboot` prefix.\nThis way avoids modification of pfsense code.\n\n## OpenBSD\n\nIn OpenBSD default `tpws` bind is ipv6 only. To bind to ipv4 specify\n`--bind-addr=0.0.0.0`.\n\nUse `--bind-addr=0.0.0.0 --bind-addr=::` to achieve the same default bind as in\nothers OSes.\n\n`tpws` for forwarded traffic only (OLDER OS versions):\n\n`/etc/pf.conf`:\n```\npass in quick on em1 inet  proto tcp to port {80,443} rdr-to 127.0.0.1 port 988\npass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988\n```\n\nThen:\n```\npfctl -f /etc/pf.conf\ntpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf\n```\n\nIts not clear how to do rdr-to outgoing traffic. I could not make route-to\nscheme work. rdr-to support is done using /dev/pf, that's why transparent mode\nrequires root.\n\n`tpws` for forwarded traffic only (NEWER OS versions):\n\n```\npass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989\npass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989\n```\n\nThen:\n```\npfctl -f /etc/pf.conf\ntpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\ntpws must be bound exactly to diverted IPs, not `0.0.0.0` or `::`.\n\nIt's also not clear how to divert connections from local system.\n\n\n`dvtws` for all traffic:\n\n`/etc/pf.conf`:\n```\npass in  quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 proto tcp from port {80,443} no state\npass out quick on em0 proto tcp to   port {80,443} divert-packet port 989\n```\n\nThen:\n```\npfctl -f /etc/pf.conf\n./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\n`dwtws` only for table zapret with the exception of table nozapret :\n\n`/etc/pf.conf`:\n```\nset limit table-entries 2000000\ntable <zapret> file \"/opt/zapret/ipset/zapret-ip.txt\"\ntable <zapret-user> file \"/opt/zapret/ipset/zapret-ip-user.txt\"\ntable <nozapret> file \"/opt/zapret/ipset/zapret-ip-exclude.txt\"\npass out quick on em0 inet  proto tcp to   <nozapret> port {80,443}\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret>  port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret-user>  port {80,443} divert-packet port 989 no state\ntable <zapret6> file \"/opt/zapret/ipset/zapret-ip6.txt\"\ntable <zapret6-user> file \"/opt/zapret/ipset/zapret-ip-user6.txt\"\ntable <nozapret6> file \"/opt/zapret/ipset/zapret-ip-exclude6.txt\"\npass out quick on em0 inet6 proto tcp to   <nozapret6> port {80,443}\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6> port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6-user> port {80,443} divert-packet port 989 no state\n```\n\nThen:\n```\npfctl -f /etc/pf.conf\n./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\ndivert-packet automatically adds the reverse rule. By default also incoming\ntraffic will be passwed to `dvtws`. This is highly undesired because it is waste\nof cpu resources and speed limiter. The trick with \"no state\" and \"in\" rules\nallows to bypass auto reverse rule.\n\n`dvtws` in OpenBSD sends all fakes through a divert socket because raw sockets\nhave critical artificial limitations. Looks like pf automatically prevent\nreinsertion of diverted frames. Loop problem does not exist.\n\nOpenBSD forcibly recomputes tcp checksum after divert. Thats why most likely\ndpi-desync-fooling=badsum will not work. `dvtws` will warn if you specify this\nparameter.\n\n`ipset` scripts do not reload PF by default. To enable reload specify command in\n`/opt/zapret/config`:\n```\nLISTS_RELOAD=\"pfctl -f /etc/pf.conf\"\n```\n\nNewer `pfctl` versions can reload tables only:\n```\npfctl -Tl -f /etc/pf.conf\n```\n\nBut OpenBSD 6.8 `pfctl` is old enough and does not support that. Newer FreeBSD do.\n\nDon't forget to disable gzip compression:\n```\nGZIP_LISTS=0\n```\n\nIf some list files do not exist and have references in pf.conf it leads to\nerror. You need to exclude those tables from pf.conf and referencing them\nrules. After configuration is done you can put `ipset` script:\n```\n crontab -e\n```\n\nThen write the line:\n```\n0 12 */2 * * /opt/zapret/ipset/get_config.sh\n```\n\n## MacOS\n\nInitially, the kernel of this OS was based on BSD. That's why it is still BSD\nbut a lot was modified by Apple. As usual a mass commercial project priorities\ndiffer from their free counterparts. Apple guys do what they want.\n\nMacOS used to have ipfw but it was removed later and replaced by PF. It looks\nlike divert sockets are internally replaced with raw. Its possible to request a\ndivert socket but it behaves exactly as raw socket with all its BSD inherited +\napple specific bugs and feature. The fact is that divert-packet in\n`/etc/pf.conf` does not work. pfctl binary does not contain the word `divert`.\n\n`dvtws` does compile but is useless.\n\nAfter some efforts `tpws` works. Apple has removed some important stuff from\ntheir newer SDKs (DIOCNATLOOK) making them undocumented and unsupported.\n\nWith important definitions copied from an older SDK it was possible to make\ntransparent mode working again. But this is not guaranteed to work in the\nfuture versions.\n\nAnother MacOS unique feature is root requirement while polling `/dev/pf`.\n\nBy default tpws drops root. Its necessary to specify `--user=root` to stay with\nroot.\n\nIn other aspects PF behaves very similar to FreeBSD and shares the same pf.conf\nsyntax.\n\nIn MacOS redirection works both for passthrough and outgoing traffic. Outgoing\nredirection requires route-to rule. Because tpws is forced to run as root to\navoid loop its necessary to exempt root from the redirection. That's why DPI\nbypass will not work for local requests from root.\n\nIf you do ipv6 routing you have to get rid of \"secured\" ipv6 address\nassignment.\n\n\"secured\" addresses are designed to be permanent and not related to the MAC\naddress.\n\nAnd they really are. Except for link-locals.\n\nIf you just reboot the system link-locals will not change. But next day they\nwill change.\n\nNot necessary to wait so long. Just change the system time to tomorrow and reboot.\nLink-locals will change (at least they change in vmware guest). Looks like its a kernel bug.\nLink locals should not change. Its useless and can be harmful. Cant use LL as a gateway.\n\nThe easiest solution is to disable \"secured\" addresses.\n\nOutgoing connections prefer randomly generated temporary addressesas like in other systems.\n\nPut the string `net.inet6.send.opmode=0` to `/etc/sysctl.conf`.  If not present\n- create it.\n\nThen reboot the system.\n\nIf you dont like this solution you can assign an additional static ipv6 address\nfrom `fc00::/7` range with `/128` prefix to your LAN interface and use it as\nthe gateway address.\n\n`tpws` transparent mode only for outgoing connections.\n\n`/etc/pf.conf`:\n```\nrdr pass on lo0 inet  proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988\npass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }\npass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }\n```\n\nThen:\n```\npfctl -ef /etc/pf.conf\n/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force\n```\n\n`tpws` transparent mode for both passthrough and outgoing connections. en1 - LAN.\n\n```\nifconfig en1 | grep fe80\n        inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8\n```\n\n`/etc/pf.conf`:\n```\nrdr pass on en1 inet  proto tcp from any to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988\nrdr pass on lo0 inet  proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988\npass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }\npass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }\n```\n\nThen:\n```\npfctl -ef /etc/pf.conf\n/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force\n```\n\nBuild from source : `make -C /opt/zapret mac`\n\n`ipset/*.sh` scripts work.\n\n\n### MacOS easy install\n\n`install_easy.sh` supports MacOS\n\nShipped precompiled binaries are built for 64-bit MacOS with\n`-mmacosx-version-min=10.8` option. They should run on all supported MacOS\nversions. If no - its easy to build your own. Running `make` automatically\ninstalls developer tools.\n\n**WARNING**:\n**Internet sharing is not supported!**\n\nRouting is supported but only manually configured through PF. If you enable\ninternet sharing tpws stops functioning. When you disable internet sharing you\nmay lose web site access.\n\nTo fix:\n```\npfctl -f /etc/pf.conf\n```\n\nIf you need internet sharing use `tpws` socks mode.\n\n`launchd` is used for autostart (`/Library/LaunchDaemons/zapret.plist`)\n\nControl script: `/opt/zapret/init.d/macos/zapret`\n\nThe following commands fork with both tpws and firewall (if `INIT_APPLY_FW=1` in config)\n```\n/opt/zapret/init.d/macos/zapret start\n/opt/zapret/init.d/macos/zapret stop\n/opt/zapret/init.d/macos/zapret restart\n```\n\nWork with `tpws` only:\n```\n/opt/zapret/init.d/macos/zapret start-daemons\n/opt/zapret/init.d/macos/zapret stop-daemons\n/opt/zapret/init.d/macos/zapret restart-daemons\n```\n\nWork with PF only:\n```\n/opt/zapret/init.d/macos/zapret start-fw\n/opt/zapret/init.d/macos/zapret stop-fw\n/opt/zapret/init.d/macos/zapret restart-fw\n```\n\nReloading PF tables:\n```\n/opt/zapret/init.d/macos/zapret reload-fw-tables\n```\n\nInstaller configures `LISTS_RELOAD` in the config so `ipset *.sh` scripts\nautomatically reload PF tables. Installer creates cron job for `ipset\n/get_config.sh`, as in OpenWRT.\n\nstart-fw script automatically patches `/etc/pf.conf` inserting there `zapret`\nanchors. Auto patching requires pf.conf with apple anchors preserved. If your\n`pf.conf` is highly customized and patching fails you will see the warning. Do\nnot ignore it.\n\nIn that case you need to manually insert \"zapret\" anchors to your `pf.conf`\n(keeping the right rule type ordering):\n```\nrdr-anchor \"zapret\"\nanchor \"zapret\"\nunistall_easy.sh unpatches pf.conf\n```\nstart-fw creates 3 anchor files in `/etc/pf.anchors` :\nzapret,zapret-v4,zapret-v6.\n\n- Last 2 are referenced by anchor `zapret`.\n- Tables `nozapret`,`nozapret6` belong to anchor `zapret`.\n- Tables `zapret`,`zapret-user` belong to anchor `zapret-v4`.\n- Tables `zapret6`,`apret6-user` belong to anchor `zapret-v6`.\n\nIf an ip version is disabled then corresponding anchor is empty and is not\nreferenced from the anchor `zapret`. Tables are only created for existing list\nfiles in the `ipset` directory.\n"
  },
  {
    "path": "docs/bsd.md",
    "content": "# Настройка BSD-подобных систем\n\n* [Поддерживаемые версии](#поддерживаемые-версии)\n* [Особенности BSD систем](#особенности-bsd-систем)\n    * [Отсутствие nfqueue](#отсутствие-nfqueue)\n    * [Типы Firewall](#типы-firewall)\n    * [Сборка](#сборка)\n    * [Divert сокеты](#divert-сокеты)\n    * [Lookup Tables](#lookup-tables)\n    * [Загрузка ip таблиц из файла](#загрузка-ip-таблиц-из-файла)\n    * [Отсутствие splice](#отсутствие-splice)\n    * [mdig и ip2net](#mdig-и-ip2net)\n* [FreeBSD](#freebsd)\n    * [Подгрузка ipdivert](#подгрузка-ipdivert)\n    * [Авто-восстановление правил ipfw и работа в фоне](#авто-восстановление-правил-ipfw-и-работа-в-фоне)\n    * [tpws в прозрачном режиме](#tpws-в-прозрачном-режиме)\n    * [Запуск dvtws](#запуск-dvtws)\n    * [PF в FreeBSD](#pf-в-freebsd)\n    * [pfsense](#pfsense)\n* [OpenBSD](#openbsd)\n    * [tpws bind на ipv4](#tpws-bind-на-ipv4)\n    * [tpws для проходящего трафика (старые системы)](#tpws-для-проходящего-трафика-старая-схема-не-работает-в-новых-версиях))\n    * [tpws для проходящего трафика (новые системы)](#tpws-для-проходящего-трафика-новые-системы))\n    * [Запуск dvtws](#запуск-dvtws)\n    * [Проблемы с badsum](#проблемы-с-badsum)\n    * [Особенность отправки fake пакетов](#особенность-отправки-fake-пакетов)\n    * [Перезагрузка PF таблиц](#перезагрузка-pf-таблиц)\n* [MacOS](#macos)\n    * [Введение](#введение)\n    * [dvtws бесполезен](#dvtws-бесполезен)\n    * [tpws](#tpws)\n    * [Проблема link-local адреса](#проблема-link-local-адреса)\n    * [Сборка](#сборка)\n    * [Простая установка](#простая-установка)\n    * [Вариант Custom](#вариант-custom)\n\n\n## Поддерживаемые версии\n**FreeBSD** 11.x+ , **OpenBSD** 6.x+, частично **MacOS Sierra** +\n\n> [!CAUTION]\n> На более старых может собираться, может не собираться, может работать или не\n> работать. На **FreeBSD** 10 собирается и работает `dvtws`. С `tpws` есть\n> проблемы из-за слишком старой версии компилятора clang. Вероятно, будет\n> работать, если обновить компилятор. Возможна прикрутка к последним версиям\n> pfsense без веб интерфейса в ручном режиме через консоль.\n\n\n## Особенности BSD систем\n\n### Отсутствие nfqueue\nВ **BSD** нет `nfqueue`. Похожий механизм - divert sockets. Из каталога\n[`nfq/`](../nfq/) под **BSD** собирается `dvtws` вместо `nfqws`. Он разделяет с\n`nfqws` большую часть кода и почти совпадает по параметрам командной строки.\n\n### Типы Firewall\n**FreeBSD** содержит 3 фаервола : **IPFilter**, **ipfw** и **Packet Filter (PF\nв дальнейшем)**. **OpenBSD** содержит только **PF**.\n\n### Сборка\nПод **FreeBSD** `tpws` и `dvtws` собираются через `make`.\n\nПод **OpenBSD**:\n```sh\nmake bsd\n```\n\nПод **MacOS**:\n```sh\nmake mac\n```\n\n**FreeBSD** make распознает BSDmakefile, **OpenBSD** и **MacOS** - нет. Поэтому\nтам используется отдельный target в Makefile. Сборка всех исходников:\n```sh\nmake -C /opt/zapret\n```\n\n### Divert сокеты\nDivert сокет это внутренний тип сокета ядра **BSD**. Он не привязывается ни к\nкакому сетевому адресу, не участвует в обмене данными через сеть и\nидентифицируется по номеру порта `1..65535`. Аналогия с номером очереди\n`NFQUEUE`. На divert сокеты заворачивается трафик посредством правил ipfw или\nPF. Если в фаерволе есть правило divert, но на divert порту никто не слушает,\nто пакеты дропаются. Это поведение аналогично правилам `NFQUEUE` без параметра\n`--queue-bypass`. На **FreeBSD** divert сокеты могут быть только ipv4, хотя на\nних принимаются и ipv4, и ipv6 фреймы. На **OpenBSD** divert сокеты создаются\nотдельно для ipv4 и ipv6 и работают только с одной версией `ip` каждый. На\n**MacOS** похоже, что divert сокеты из ядра вырезаны. См подробнее раздел про\n**MacOS**. Отсылка в divert сокет работает аналогично отсылке через raw socket\nна linux. Передается полностью IP фрейм, начиная с ip заголовка. Эти особенности\nучитываются в `dvtws`.\n\n### Lookup Tables\nСкрипты [`ipset/*.sh`](../ipset/) при наличии ipfw работают с ipfw lookup\ntables. Это прямой аналог ipset. lookup tables не разделены на v4 и v6. Они\nмогут содержать v4 и v6 адреса и подсети одновременно. Если ipfw отсутствует,\nто действие зависит от переменной `LISTS_RELOAD` в config. Если она задана, то\nвыполняется команда из `LISTS_RELOAD`. В противном случае не делается ничего.\nЕсли `LISTS_RELOAD=-`, то заполнение таблиц отключается даже при наличии ipfw.\n\n### Загрузка ip таблиц из файла\nPF может загружать ip таблицы из файла. Чтобы использовать эту возможность\nследует отключить сжатие gzip для листов через параметр файла config:\n`GZIP_LISTS=0`.\n\n### Отсутствие splice\n**BSD** не содержит системного вызова splice. `tpws` работает через переброску\nданных в user mode в оба конца. Это медленнее, но не критически. Управление\nасинхронными сокетами в `tpws` основано на linux-specific механизме epoll. В\n**BSD** для его эмуляции используется epoll-shim - прослойка для эмуляции epoll\nна базе kqueue.\n\n### mdig и ip2net\nmdig и ip2net полностью работоспособны в **BSD**. В них нет ничего\nсистемо-зависимого.\n\n\n## FreeBSD\n\n### Подгрузка ipdivert\nDivert сокеты требуют специального модуля ядра - `ipdivert`.\n\n- Поместите следующие строки в `/boot/loader.conf` (создать, если отсутствует):\n```\nipdivert_load=\"YES\"\nnet.inet.ip.fw.default_to_accept=1\n```\n\n`/etc/rc.conf`:\n```\nfirewall_enable=\"YES\"\nfirewall_script=\"/etc/rc.firewall.my\"\n```\n\n`/etc/rc.firewall.my`:\n```sh\n$ ipfw -q -f flush\n```\n\n### Авто-восстановление правил ipfw и работа в фоне\nВ `/etc/rc.firewall.my` можно дописывать правила ipfw, чтобы они\nвосстанавливались после перезагрузки. Оттуда же можно запускать и демоны\nzapret, добавив в параметры `--daemon`. Например так:\n```sh\n$ pkill ^dvtws$\n$ /opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\nДля перезапуска фаервола и демонов достаточно будет сделать:\n```sh\n$ /etc/rc.d/ipfw restart\n```\n\n### tpws в прозрачном режиме\nКраткая инструкция по запуску `tpws` в прозрачном режиме.\n\n> [!NOTE]  \n> Предполагается, что интерфейс LAN называется `em1`, WAN - `em0`.\n\n#### Весь трафик\n```sh\n$ ipfw delete 100\n$ ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon\n$ ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon\n$ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\n$ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n$ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\n#### Трафик только на таблицу zapret, за исключением таблицы nozapret\n\n```sh\n$ ipfw delete 100\n$ ipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\n$ ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\\(zapret\\) 80,443 proto ip4 xmit em0 not uid daemon\n$ ipfw add 100 fwd ::1,988 tcp from me to table\\(zapret\\) 80,443 proto ip6 xmit em0 not uid daemon\n$ ipfw add 100 allow tcp from any to table\\(nozapret\\) 80,443 recv em1\n$ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\n$ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n$ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\n> [!NOTE]  \n> Таблицы zapret, nozapret, ipban создаются скриптами из ipset по аналогии с\n> Linux. Обновление скриптов можно забить в cron под root:\n> ```sh\n> $ crontab -e\n> ```\n>\n> ```\n> <...>\n> 0 12 */2 * * /opt/zapret/ipset/get_config.sh\n> ```\n\n> [!CAUTION]  \n> При использовании ipfw `tpws` не требует повышенных привилегий для реализации\n> прозрачного режима. Однако, без рута невозможен bind на порты `< 1024` и\n> смена UID/GID. Без смены UID будет рекурсия, поэтому правила ipfw нужно\n> создавать с учетом UID, под которым работает `tpws`. Переадресация на порты\n> `>= 1024` может создать угрозу перехвата трафика непривилегированным\n> процессом, если вдруг `tpws` не запущен.\n\n\n### Запуск dvtws\n\n#### Весь трафик\n```sh\n$ ipfw delete 100\n$ ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0\n# required for autottl mode only\n$ ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0\n$ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\n#### Трафик только на таблицу zapret, за исключением таблицы nozapret\n\n```sh\n$ ipfw delete 100\n$ ipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\n$ ipfw add 100 divert 989 tcp from any to table\\(zapret\\) 80,443 out not diverted not sockarg xmit em0\n# required for autottl mode only\n$ ipfw add 100 divert 989 tcp from table\\(zapret\\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0\n$ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\n\n### PF в FreeBSD\nНастройка аналогична **OpenBSD**, но есть важные нюансы.\n\n- В **FreeBSD** поддержка PF в `tpws` отключена по умолчанию. Чтобы ее\n  включить, нужно использовать параметр `--enable-pf`.\n- Нельзя сделать ipv6 rdr на `::1`. Нужно делать на link-local адрес входящего\n  интерфейса. Смотрите через `ifconfig` адрес `fe80:...` и добавляете в правило.\n- Синтаксис `pf.conf` немного отличается. Более новая версия PF.\n- Лимит на количество элементов таблиц задается так:\n  ```sh\n  $ sysctl net.pf.request_maxcount=2000000\n  ```\n- Сломан divert-to. Он работает, но не работает механизм предотвращения\n  зацикливаний. Кто-то уже написал патч, но в `14-RELEASE` проблема все еще\n  есть. Следовательно, на данный момент работа `dvtws` через PF невозможна.\n\n  `/etc/pf.conf`:\n  ```\n  rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988\n  rdr pass on em1 inet  proto tcp to port {80,443} -> 127.0.0.1 port 988\n  ```\n\n  ```sh\n  $ /opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force\n  ```\n\n> [!NOTE]  \n > В PF не выходит делать rdr-to с той же системы, где работает proxy.\n > Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется.\n > Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС.\n > Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**.\n\n\n### pfsense\n\n#### Описание\npfsense основан на **FreeBSD** и использует фаервол PF, имеющий проблемы с\ndivert. К счастью, модули ipfw и ipdivert присутствуют в поставке последних\nверсий pfsense. Их можно подгрузить через `kldload`.\n\nВ некоторых более старых версиях pfsense требуется изменить порядок фаерволов\nчерез `sysctl`, сделав ipfw первым. В более новых эти параметры `sysctl`\nотсутствуют, но система работает как надо и без них. В некоторых случаях\nфаервол PF может ограничивать возможности `dvtws`, в частности в области\nфрагментации ip.\n\nПрисутствуют по умолчанию правила scrub для реассемблинга фрагментов.\n\nБинарики из [`binaries/freebsd-x64`](../binaries/freebsd-x64) собраны под\n**FreeBSD 11**. Они должны работать и на последующих версиях **FreeBSD**,\nвключая pfsense. Можно пользоваться `install_bin.sh`.\n\n#### Автозапуск\nПример скрипта автозапуска лежит в [`init.d/pfsense`](../init.d/pfsense). Его\nследует поместить в `/usr/local/etc/rc.d` и отредактировать на предмет правил\nipfw и запуска демонов. Есть встроенный редактор `edit` как более приемлемая\nальтернатива `vi`.\n\n> [!NOTE]  \n> Поскольку `git` отсутствует, копировать файлы удобнее всего через `ssh`.\n> `curl` присутствует по умолчанию. Можно скопировать zip с файлами zapret и\n> распаковать в `/opt`, как это делается на других системах. Тогда `dvtws`\n> нужно запускать как `/opt/zapret/nfq/dvtws`. Либо скопировать только `dvtws`\n> в `/usr/local/sbin`. Как вам больше нравится.\n\n> [!NOTE]  \n> Скрипты ipset работают, крон есть. Можно сделать автообновление листов.\n\n> [!NOTE]  \n> Если вас напрягает бедность имеющегося репозитория, можно включить\n> репозиторий от **FreeBSD**, который по умолчанию выключен.\n>\n> Поменяйте `no` на `yes` в `/usr/local/etc/pkg/repos/FreeBSD.conf`\n>\n> Можно установить весь привычный софт, включая `git`, чтобы напрямую скачивать\n> zapret с github.\n\n`/usr/local/etc/rc.d/zapret.sh`  (chmod `755`):\n```sh\n#!/bin/sh\n\nkldload ipfw\nkldload ipdivert\n\n# for older pfsense versions. newer do not have these sysctls\nsysctl net.inet.ip.pfil.outbound=ipfw,pf\nsysctl net.inet.ip.pfil.inbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.outbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.inbound=ipfw,pf\n\nipfw delete 100\nipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0\npkill ^dvtws$\ndvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n\n# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state\npfctl -d ; pfctl -e\n```\n\n#### Проблемы tpws\nЧто касается `tpws`, то видимо имеется некоторый конфликт двух фаерволов, и\nправила fwd в ipfw не работают. Работает перенаправление средствами PF как\nописано в разделе по **FreeBSD**. В PF можно изменять правила только целыми\nблоками - якорями (anchors). Нельзя просто так добавить или удалить что-то. Но\nчтобы какой-то anchor был обработан, на него должна быть ссылка из основного\nнабора правил. Его трогать нельзя, иначе порушится весь фаервол. Поэтому\nпридется править код скриптов pfsense.\n\n1. Поправьте `/etc/inc/filter.inc` следующим образом:\n```\n\t<...>\n\t/* MOD */\n\t$natrules .= \"# ZAPRET redirection\\n\";\n\t$natrules .= \"rdr-anchor \\\"zapret\\\"\\n\";\n\n\t$natrules .= \"# TFTP proxy\\n\";\n\t$natrules .= \"rdr-anchor \\\"tftp-proxy/*\\\"\\n\";\n\t<...>\n```\n\n2. Напишите файл с содержимым anchor-а (например, `/etc/zapret.anchor`):\n```\nrdr pass on em1 inet  proto tcp to port {80,443} -> 127.0.0.1 port 988\nrdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988\n```\n\n`fe80::20c:29ff:5ae3:4821` замените на ваш link local адрес LAN интерфейса,\nлибо уберите строчку, если ipv6 не нужен.\n\n3. Добавьте в автозапуск `/usr/local/etc/rc.d/zapret.sh`:\n```sh\n$ pfctl -a zapret -f /etc/zapret.anchor\n$ pkill ^tpws$\n$ tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2\n```\n\n4. После перезагрузки проверьте, что правила создались:\n```sh\n$ pfctl -s nat\nno nat proto carp all\nnat-anchor \"natearly/*\" all\nnat-anchor \"natrules/*\" all\n<...>\nno rdr proto carp all\nrdr-anchor \"zapret\" all\nrdr-anchor \"tftp-proxy/*\" all\nrdr-anchor \"miniupnpd\" all\n\n$ pfctl -s nat -a zapret\nrdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988\nrdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988\nrdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988\nrdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988\n```\n\n> [!NOTE]  \n> Так же есть более элегантный способ запуска `tpws` через @reboot в cron и\n> правило перенаправления в UI. Это позволит не редактировать код pfsense.\n\n\n## OpenBSD\n\n### tpws bind на ipv4\nВ `tpws` bind по умолчанию только на ipv6. Для bind на ipv4 нужно указать `--bind-addr=0.0.0.0`.\nИспользуйте `--bind-addr=0.0.0.0 --bind-addr=::` для достижения того же результата, как в других ОС по умолчанию.\nНо лучше все же так не делать, а сажать на определенные внутренние адреса или интерфейсы.\n\n\n### tpws для проходящего трафика (старая схема не работает в новых версиях)\n\nВ этом варианте tpws обращается явно к редиректору pf и пытается от него получить оригинальный адрес назначения.\nКак показывает практика, это не работает на новых версиях OpenBSD. Возвращается ошибка ioctl.\nПоследняя проверенная версия, где это работает, - 6.8 . Между 6.8 и 7.4 разработчики сломали этот механизм.\n\n`/etc/pf.conf`:\n```\npass in quick on em1 inet  proto tcp to port {80,443} rdr-to 127.0.0.1 port 988\npass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988\n```\n\n```sh\n$ pfctl -f /etc/pf.conf\n$ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf\n```\n\n> [!NOTE]\n> В PF не выходит делать rdr-to с той же системы, где работает proxy.\n> Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется.\n> Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС.\n> Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**.\n\n### tpws для проходящего трафика (новые системы)\n\nВ новых версиях предлагается использовать divert-to вместо rdr-to.\nМинимально проверенная версия, где это работает, 7.4. Может работать или не работать на более старых - исследование не проводилось.\n\n`/etc/pf.conf`:\n```\npass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989\npass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989\n```\n\ntpws должен иметь бинд на точно такой адрес, который указан в правилах pf. `0.0.0.0` или `::` не работает.\n\n```sh\n$ pfctl -f /etc/pf.conf\n$ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n```\n\n> [!NOTE]\n> Так же не понятно как делать divert с самой системы, где работает tpws.\n\n### Запуск dvtws\n\n#### Весь трафик\n`/etc/pf.conf`:\n```\npass in  quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 proto tcp from port {80,443} no state\npass out quick on em0 proto tcp to   port {80,443} divert-packet port 989 no state\n```\n\n```sh\n$ pfctl -f /etc/pf.conf\n$ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\n#### Трафик только на таблицу zapret, за исключением таблицы nozapret\n\n`/etc/pf.conf`:\n```\nset limit table-entries 2000000\ntable <zapret> file \"/opt/zapret/ipset/zapret-ip.txt\"\ntable <zapret-user> file \"/opt/zapret/ipset/zapret-ip-user.txt\"\ntable <nozapret> file \"/opt/zapret/ipset/zapret-ip-exclude.txt\"\npass out quick on em0 inet  proto tcp to   <nozapret> port {80,443}\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret>  port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret-user>  port {80,443} divert-packet port 989 no state\ntable <zapret6> file \"/opt/zapret/ipset/zapret-ip6.txt\"\ntable <zapret6-user> file \"/opt/zapret/ipset/zapret-ip-user6.txt\"\ntable <nozapret6> file \"/opt/zapret/ipset/zapret-ip-exclude6.txt\"\npass out quick on em0 inet6 proto tcp to   <nozapret6> port {80,443}\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6> port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6-user> port {80,443} divert-packet port 989 no state\n```\n\n```sh\n$ pfctl -f /etc/pf.conf\n$ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n```\n\n\n### Проблемы с badsum\n**OpenBSD** принудительно пересчитывает tcp checksum после divert, поэтому\nскорее всего `dpi-desync-fooling=badsum` у вас не заработает. При использовании\nэтого параметра `dvtws` предупредит о возможной проблеме.\n\n\n### Особенность отправки fake пакетов\nВ **OpenBSD** `dvtws` все фейки отсылает через divert socket, поскольку эта\nвозможность через raw sockets заблокирована. Видимо PF автоматически\nпредотвращает повторный заворот diverted фреймов, поэтому проблемы зацикливания\nнет.\n\ndivert-packet автоматически вносит обратное правило для перенаправления. Трюк с\nno state и in правилом позволяет обойти эту проблему, чтобы напрасно не гнать\nмассивный трафик через `dvtws`.\n\n\n### Перезагрузка PF таблиц\nСкрипты из ipset не перезагружают таблицы в PF по умолчанию.\n\nЧтобы они это делали, добавьте параметр в `/opt/zapret/config`:\n```\nLISTS_RELOAD=\"pfctl -f /etc/pf.conf\"\n```\n\nБолее новые версии `pfctl` понимают команду перезагрузить только таблицы. Но это не относится к **OpenBSD**. В новых **FreeBSD** есть.\n```sh\n$ pfctl -Tl -f /etc/pf.conf\n```\n\n> [!IMPORTANT]  \n> Не забудьте выключить сжатие gzip: `GZIP_LISTS=0`\n\n> [!IMPORTANT]  \n> Если в вашей конфигурации какого-то файла листа нет, то его необходимо\n> исключить из правил PF. Если вдруг листа нет, и он задан в pf.conf, будет\n> ошибка перезагрузки фаервола.\n\n> [!NOTE]\n> После настройки обновление листов можно поместить в cron:\n> ```sh\n> $ crontab -e\n> ```\n>\n> ```\n> <...>\n> 0 12 */2 * * /opt/zapret/ipset/get_config.sh\n> ```\n\n\n## MacOS\n\n### Введение\nИначально ядро этой ОС \"darwin\" основывалось на **BSD**, потому в ней много\nпохожего на другие версии **BSD**. Однако, как и в других массовых коммерческих\nпроектах, приоритеты смещаются в сторону от оригинала. Яблочники что хотят, то\nи творят.\n\n\n### dvtws бесполезен\nРаньше был ipfw, потом его убрали, заменили на PF. Есть сомнения, что divert\nсокеты в ядре остались. Попытка создать divert socket не выдает ошибок, но\nполученный сокет ведет себя точно так же, как raw, со всеми его унаследованными\nкосяками + еще яблочно специфическими. В PF divert-packet не работает. Простой\ngrep бинарика `pfctl` показывает, что там нет слова \"divert\", а в других\nверсиях **BSD** оно есть. `dvtws` собирается, но совершенно бесполезен.\n\n\n### tpws\n`tpws` удалось адаптировать, он работоспособен. Получение адреса назначения для\nпрозрачного прокси в PF (`DIOCNATLOOK`) убрали из заголовков в новых SDK,\nсделав фактически недокументированным.\n\nВ `tpws` перенесены некоторые определения из более старых версий яблочных SDK.\nС ними удалось завести прозрачный режим. Однако, что будет в следующих версиях\nугадать сложно. Гарантий нет. Еще одной особенностью PF в **MacOS** является\nпроверка на рута в момент обращения к `/dev/pf`, чего нет в остальных **BSD**.\n`tpws` по умолчанию сбрасывает рутовые привилегии. Необходимо явно указать\nпараметр `--user=root`. В остальном PF себя ведет похоже на **FreeBSD**.\nСинтаксис `pf.conf` тот же.\n\n> [!IMPORTANT]  \n> На **MacOS** работает редирект как с проходящего трафика, так и с локальной\n> системы через route-to. Поскольку `tpws` вынужден работать под root, для\n> исключения рекурсии приходится пускать исходящий от root трафик напрямую.\n> Отсюда имеем недостаток - **обход DPI для рута работать НЕ будет**.\n\n#### Работа в прозрачном режиме только для исходящих запросов\n`/etc/pf.conf`:\n```\nrdr pass on lo0 inet  proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988\npass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }\npass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }\n```\n\n```sh\n$ pfctl -ef /etc/pf.conf\n$ /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force\n```\n\n#### Работа в прозрачном режиме\n> [!NOTE]\n> Предполагается, что имя LAN интерфейса - `en1`\n\n```sh\n$ ifconfig en1 | grep fe80\n        inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8\n```\n\n`/etc/pf.conf`:\n```\nrdr pass on en1 inet  proto tcp from any to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988\nrdr pass on lo0 inet  proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988\nrdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988\npass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root }\npass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root }\n```\n\n```sh\n$ pfctl -ef /etc/pf.conf\n$ /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force\n```\n\n\n### Проблема link-local адреса\nЕсли вы пользуетесь **MaсOS** в качестве ipv6 роутера, то нужно будет\nрешить вопрос с регулярно изменяемым link-local адресом. С некоторых версий\n**MacOS** использует по умолчанию постоянные \"secured\" ipv6 адреса вместо\nгенерируемых на базе MAC адреса.\n\nВсе замечательно, но есть одна проблема. Постоянными остаются только global\nscope адреса. Link locals периодически меняются. Смена завязана на системное\nвремя. Перезагрузки адрес не меняют, Но если перевести время на день вперед и\nперезагрузиться - link local станет другим (по крайней мере в vmware это так).\nИнформации по вопросу крайне мало, но тянет на баг. Не должен меняться link\nlocal. Скрывать link local не имеет смысла, а динамический link local нельзя\nиспользовать в качестве адреса шлюза. Проще всего отказаться от \"secured\"\nадресов. Для этого поместите строчку `net.inet6.send.opmode=0` в\n`/etc/sysctl.conf` и перезагрузите систему.\n\nВсе равно для исходящих соединений будут использоваться temporary адреса, как и\nв других системах. Или вам идея не по вкусу, можно прописать дополнительный\nстатический ipv6 из диапазона маски `fc00::/7` - выберите любой с длиной\nпрефикса `128`. Это можно сделать в системных настройках, создав дополнительный\nадаптер на базе того же сетевого интерфейса, отключить в нем ipv4 и вписать\nстатический ipv6. Он добавится к автоматически настраеваемым.\n\n\n### Сборка\n```sh\n$ make -C /opt/zapret mac\n```\n\n\n### Простая установка\nВ **MacOS** поддерживается `install_easy.sh`\n\nВ комплекте идут бинарики, собраные под 64-bit с опцией\n`-mmacosx-version-min=10.8`. Они должны работать на всех поддерживаемых версиях\n**MacOS**. Если вдруг не работают - можно собрать свои. Developer tools\nставятся автоматом при запуске `make`.\n\n> [!WARNING]  \n> Internet sharing средствами системы **не поддерживается**!\n>\n> Поддерживается только роутер, настроенный своими силами через PF. Если вы\n> вдруг включили шаринг, а потом выключили, то доступ к сайтам может пропасть\n> совсем.\n>\n> Лечение:\n> ```sh\n> $ pfctl -f /etc/pf.conf\n> ```\n>\n> Если вам нужен шаринг интернета, лучше отказаться от прозрачного режима и\n> использовать socks прокси.\n\nДля автостарта используется launchd (`/Library/LaunchDaemons/zapret.plist`)\nУправляющий скрипт : `/opt/zapret/init.d/macos/zapret`\n\nСледующие команды работают с `tpws` и фаерволом одновременно (если\n`INIT_APPLY_FW=1` в config)\n\n```sh\n$ /opt/zapret/init.d/macos/zapret start\n$ /opt/zapret/init.d/macos/zapret stop\n$ /opt/zapret/init.d/macos/zapret restart\n```\n\nРабота только с tpws:\n```sh\n$ /opt/zapret/init.d/macos/zapret start-daemons\n$ /opt/zapret/init.d/macos/zapret stop-daemons\n$ /opt/zapret/init.d/macos/zapret restart-daemons\n```\n\nРабота только с PF:\n```sh\n$ /opt/zapret/init.d/macos/zapret start-fw\n$ /opt/zapret/init.d/macos/zapret stop-fw\n$ /opt/zapret/init.d/macos/zapret restart-fw\n```\n\nПерезагрузка всех IP таблиц из файлов:\n```sh\n$ /opt/zapret/init.d/macos/zapret reload-fw-tables\n```\n\n> [!NOTE]  \n> Инсталятор настраивает `LISTS_RELOAD` в config, так что скрипты\n> [`ipset/*.sh`](../ipset/) автоматически перезагружают IP таблицы в PF.\n\n> [!NOTE]  \n> Автоматически создается cron job на\n> [`ipset/get_config.sh`](../ipset/get_config.sh), по аналогии с openwrt.\n\nПри start-fw скрипт автоматически модицифирует `/etc/pf.conf`, вставляя туда\nanchors \"zapret\". Модификация расчитана на `pf.conf`, в котором сохранены\nдефолтные anchors от apple. Если у вас измененный `pf.conf` и модификация не\nудалась, об этом будет предупреждение. Не игнорируйте его. В этом случае вам\nнужно вставить в свой `pf.conf` (в соответствии с порядком типов правил):\n```\nrdr-anchor \"zapret\"\nanchor \"zapret\"\n```\n\n> [!NOTE]  \n> При деинсталяции через `uninstall_easy.sh` модификации `pf.conf` убираются.\n\n> [!NOTE]  \n> start-fw создает 3 файла anchors в `/etc/pf.anchors`: `zapret`, `zapret-v4`,\n> `zapret-v6`. Последние 2 подключаются из anchor \"zapret\".\n\n> [!NOTE]  \n> Таблицы `nozapret` и `nozapret6` принадлежат anchor \"zapret\".\n>\n> Таблицы `zapret` и `zapret-user` в anchor \"zapret-v4\".\n>\n> Таблицы `zapret6` и `zapret6-user` в anchor \"zapret-v6\".\n>\n> Если какая-то версия протокола отключена - соответствующий anchor пустой и не\n> упоминается в anchor \"zapret\". Таблицы и правила создаются только на те\n> листы, которые фактически есть в директории ipset.\n\n\n### Вариант Custom\nТак же как и в других системах, поддерживаемых в простом инсталяторе, можно\nсоздавать свои custom скрипты.\n\nРасположение: `/opt/zapret/init.d/macos/custom`\n\n`zapret_custom_daemons()` получает в `$1`: `0` или `1`. `0` = stop, `1` = start\n\ncustom firewall отличается от linux варианта. Вместо заполнения `iptables` вам\nнужно сгенерировать правила для `zapret-v4` и `zapret-v6` anchors и выдать их в\nstdout. Это делается в функциях `zapret_custom_firewall_v4()` и\n`zapret_custom_firewall_v6()`. Определения таблиц заполняются основным скриптом\n\\- вам это делать не нужно. Можно ссылаться на таблицы `zapret` и `zapret-user`\nв v4, `zapret6` и `zapret6-user`.\n\nCм. пример [в файле](../init.d/macos/custom.d.examples/50-extra-tpws).\n"
  },
  {
    "path": "docs/bsdfw.txt",
    "content": "WAN=em0  LAN=em1\n\nFreeBSD IPFW  :\n\nipfw delete 100\nipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon\nipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon\nipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\nipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n\nipfw delete 100\nipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\nipfw add 100 fwd 127.0.0.1,988 tcp from me to table\\(zapret\\) 80,443 proto ip4 xmit em0 not uid daemon\nipfw add 100 fwd ::1,988 tcp from me to table\\(zapret\\) 80,443 proto ip6 xmit em0 not uid daemon\nipfw add 100 allow tcp from any to table\\(nozapret\\) 80,443 recv em1\nipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1\nipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1\n\n/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n\n\nipfw delete 100\nipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0\n; required for autottl mode\nipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0\n; udp\nipfw add 100 divert 989 udp from any to any 443 out not diverted xmit em0\n\nipfw delete 100\nipfw add 100 allow tcp from me to table\\(nozapret\\) 80,443\nipfw add 100 divert 989 tcp from any to table\\(zapret\\) 80,443 out not diverted xmit em0\n\n/opt/zapret/nfq/dvtws --port=989 --debug --dpi-desync=split\n\n\nsample ipfw NAT setup :\n\nWAN=em0\nLAN=em1\nipfw -q flush\nipfw -q nat 1 config if $WAN unreg_only reset\nipfw -q add 10 allow ip from any to any via $LAN\nipfw -q add 20 allow ip from any to any via lo0\nipfw -q add 300 nat 1 ip4 from any to any in recv $WAN\nipfw -q add 301 check-state\nipfw -q add 350 skipto 390 tcp from any to any out xmit $WAN setup keep-state\nipfw -q add 350 skipto 390 udp from any to any out xmit $WAN keep-state\nipfw -q add 360 allow all from any to me in recv $WAN\nipfw -q add 390 nat 1 ip4 from any to any out xmit $WAN \nipfw -q add 10000 allow ip from any to any\n\nForwarding :\nsysctl net.inet.ip.forwarding=1\nsysctl net.inet6.ip6.forwarding=1\n\n\nOpenBSD PF :\n\n(OLD OpenBSD versions)\n\n; dont know how to rdr-to from local system. doesn't seem to work. only works for routed traffic.\n\n/etc/pf.conf\npass in quick on em1 inet  proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 \npass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 \npfctl -f /etc/pf.conf\n/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf\n\n(NEW OpenBSD versions)\n\n; dont know how to divert-to from local system\n\n/etc/pf.conf\npass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989\npass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989\npfctl -f /etc/pf.conf\n/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1\n\n\n; dvtws works both for routed and local\n\npass in  quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 proto tcp from port {80,443} no state\npass out quick on em0 proto tcp to   port {80,443} divert-packet port 989 no state\npfctl -f /etc/pf.conf\n./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2\n\n; dvtws with table limitations : to zapret,zapret6 but not to nozapret,nozapret6\n; reload tables : pfctl -f /etc/pf.conf\nset limit table-entries 2000000\ntable <zapret> file \"/opt/zapret/ipset/zapret-ip.txt\"\ntable <zapret-user> file \"/opt/zapret/ipset/zapret-ip-user.txt\"\ntable <nozapret> file \"/opt/zapret/ipset/zapret-ip-exclude.txt\"\npass out quick on em0 inet  proto tcp to   <nozapret> port {80,443}\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret>  port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet  proto tcp from <zapret-user>  port {80,443} no state\npass out quick on em0 inet  proto tcp to   <zapret-user>  port {80,443} divert-packet port 989 no state\ntable <zapret6> file \"/opt/zapret/ipset/zapret-ip6.txt\"\ntable <zapret6-user> file \"/opt/zapret/ipset/zapret-ip-user6.txt\"\ntable <nozapret6> file \"/opt/zapret/ipset/zapret-ip-exclude6.txt\"\npass out quick on em0 inet6 proto tcp to   <nozapret6> port {80,443}\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6> port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6> port {80,443} divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} flags SA/SA divert-packet port 989 no state\npass in  quick on em0 inet6 proto tcp from <zapret6-user>  port {80,443} no state\npass out quick on em0 inet6 proto tcp to   <zapret6-user> port {80,443} divert-packet port 989 no state\n"
  },
  {
    "path": "docs/changes.txt",
    "content": "v1\n\nInitial release\n\nv2\n\nnfqws : command line options change. now using standard getopt.\nnfqws : added options for window size changing and \"Host:\" case change\nISP support : tested on mns.ru and beeline (corbina)\ninit scripts : rewritten init scripts for simple choise of ISP\ncreate_ipset : now using 'ipset restore', it works much faster\nreadme : updated. now using UTF-8 charset.\n\nv3\n\ntpws : added transparent proxy (supports TPROXY and DNAT).\n       can help when ISP tracks whole HTTP session, not only the beginning\nipset : added zapret-hosts-user.txt which contain user defined host names to be resolved\n        and added to zapret ip list\nISP support : dom.ru support via TPROXY/DNAT\nISP support : successfully tested sknt.ru on 'domru' configuration\n  other configs will probably also work, but cannot test\ncompile : openwrt compile howto\n\nv4\n\ntpws : added ability to insert extra space after http method : \"GET /\" => \"GET  /\"\nISP support : TKT support\n\nv5\n\nnfqws : ipv6 support in nfqws\n\nv6\n\nipset : added \"get_antizapret.sh\"\n\nv7\n\ntpws : added ability to insert \".\" after Host: name\n\nv8\n\nopenwrt init : removed hotplug.d/firewall because of race conditions. now only use /etc/firewall.user\n\nv9\n\nipban : added ipban ipset. place domains banned by ip to zapret-hosts-user-ipban.txt\n\tthese IPs must be soxified for both http and https\nISP support : tiera support\nISP support : added DNS filtering to ubuntu and debian scripts\n\nv10\n\ntpws : added split-pos option. split every message at specified position\n\nv11\n\nipset : scripts optimizations\n\nv12\n\nnfqws : fix wrong tcp checksum calculation if packet length is odd and platform is big-endian\n\nv13\n\nadded binaries\n\nv14\n\nchange get_antizapret script to work with https://github.com/zapret-info/z-i/raw/master/dump.csv\nfilter out 192.168.*, 127.*, 10.* from blocked ips\n\nv15\n\nadded --hostspell option to nfqws and tpws\nISP support : beeline now catches \"host\" but other spellings still work\nopenwrt/LEDE : changed init script to work with procd\ntpws, nfqws : minor cosmetic fixes\n\nv16\n\ntpws: split-http-req=method : split inside method name, not after\nISP support : mns.ru changed split pos to 3 (got redirect page with HEAD req : curl -I ej.ru)\n\nv17\n\nISP support : athome moved from nfqws to tpws because of instability and http request hangs\ntpws : added options unixeol,methodeol,hosttab\n\nv18\n\ntpws,nfqws : added hostnospace option\n\nv19\n\ntpws : added hostlist option\n\nv20\n\nadded ip2net. ip2net groups ips from iplist into subnets and reduces ipset size twice\n\nv21\n\nadded mdig. get_reestr.sh is *real* again\n\nv22\n\ntotal review of init script logic\ndropped support of older debian 7 and ubuntu 12/14 systems\ninstall_bin.sh : auto binaries preparation\ndocs: readme review. some new topics added, others deleted\ndocs: VPN setup with policy based routing using wireguard\ndocs: wireguard modding guide\n\nv23\n\nmajor init system rewrite\nopenwrt : separate firewall include /etc/firewall.zapret\ninstall_easy.sh : easy setup on openwrt, debian, ubuntu, centos, fedora, opensuse\n\nv24\n\nseparate config from init scripts\ngzip support in ipset/*.sh and tpws\n\nv25\n\ninit : move to native systemd units\nuse links to units, init scripts and firewall includes, no more copying\n\nv26\n\nipv6 support\ntpws : advanced bind options\n\nv27\n\ntpws : major connection code rewrite. originally it was derived from not top quality example , with many bugs and potential problems.\nnext generation connection code uses nonblocking sockets. now its in EXPERIMENTAL state.\n\nv28\n\ntpws : added socks5 support\nipset : major RKN getlist rewrite. added antifilter.network support\n\nv29\n\nnfqws : DPI desync attack\nip exclude system\n\nv30\n\nnfqws : DPI desync attack modes : fake,rst\n\nv31\n\nnfqws : DPI desync attack modes : disorder,disorder2,split,split2.\nnfqws : DPI desync fooling mode : badseq.  multiple modes supported\n\nv32\n\ntpws : multiple binds\ninit scripts : run only one instance of tpws in any case\n\nv33\n\nopenwrt : flow offloading support\nconfig : MODE refactoring\n\nv34\n\nnfqws : dpi-desync 2 mode combos\nnfqws : dpi-desync without parameter no more supported. previously it meant \"fake\"\nnfqws : custom fake http request and tls client hello\n\nv35\n\nlimited FreeBSD and OpenBSD support\n\nv36\n\nfull FreeBSD and OpenBSD support\n\nv37\n\nlimited MacOS support\n\nv38\n\nMacOS easy install\n\nv39\n\nnfqws: conntrack, wssize\n\nv40\n\ninit scripts : IFACE_LAN, IFACE_WAN now accept multiple interfaces\ninit scripts : openwrt uses now OPENWRT_LAN parameter to override incoming interfaces for tpws\n\nv41\n\ninstall_easy : openrc support\n\nv42\n\nblockcheck.sh\n\nv43\n\nnfqws: UDP desync with conntrack support (any-protocol only for now)\n\nv44\n\nnfqws: ipfrag\n\nv45\n\nnfqws: hop-by-hop ipv6 desync and fooling\n\nv46\n\nbig startup script refactoring to support nftables and new openwrt snapshot builds with firewall4\n\nv47\n\nnfqws: QUIC initial decryption\nnfqws: udplen, fakeknown dpi desync modes\n\nv48\n\nnfqws, tpws : multiple --hostlist and --hostlist-exclude support\nlaunch system, ipset : no more list merging. all lists are passed separately to nfqws and tpws\nnfqws : udplen fooling supports packet shrinking (negative increment value)\n\nv49\n\nQUIC support integrated to the main system and setup\n\nv50\n\nDHT protocol support.\nDPI desync mode 'tamper' for DHT.\nHEX string support in addition to binary files.\n\nv51\n\ntpws --tlsrec attack.\n\nv52\n\nautohostlist mode\n\nv53\n\nnfqws: tcp session reassemble for TLS ClientHello\n\nv54\n\ntpws: out of band send when splitting (--oob)\nnfqws: autottl\nnfqws: datanoack fooling\nnftables: use POSTNAT path for tcp redirections to allow NAT-breaking strategies. use additional mark bit DESYNC_MARK_POSTNAT.\n\nv55\n\ntpws: incompatible oob parameter change. it doesn't take oob byte anymore. instead it takes optional protocol filter - http or tls.\n  the same is done with disorder. oob byte can be specified in parameter --oob-data.\nblockcheck: quick mode, strategy order optimizations, QUIC protocol support\nnfqws: syndata desync mode\n\nv56\n\ntpws: mss fooling\ntpws: multi thread resolver. eliminates blocks related to hostname resolve.\n\nv57\n\ntpws: --nosplice option\nnfqws: postnat fixes\nnfqws: --dpi-desync-start option\nnfqws: packet delay for kyber TLS and QUIC\nnfqws: --dpi-desync-retrans obsolete\nnfqws: --qnum is mandatory, no more default queue 0\n\nv58\n\nwinws\n\nv59\n\ntpws: --split-tls\ntpws: --tlsrec=sniext\nnfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls. multi segment TLS support for split.\nblockcheck: mdig dns cache\n\nv60\n\nblockcheck: port block test, partial ip block test\nnfqws: seqovl split/disorder modes\n\nv61\n\nC code cleanups\ndvtws: do not use raw sockets. use divert.\nnfqws,tpws: detect TLS 1.2 ClientHello from very old libraries with SSL 3.0 version in record layer\nnfqws,tpws: debug log to file and syslog\ntpws: --connect-bind-addr option\ntpws: log local endpoint (including source port number) for remote leg\n\nv62:\n\ntpws: connection close logic rewrite. tcp user timeout parameters for local and remote leg.\nnfqws: multi-strategy\n\nv63:\n\ntpws: multi-strategy\n\nv64:\n\nblockcheck: warn if dpi bypass software is already running\nblockcheck: TPWS_EXTRA, NFQWS_EXTRA\ninit.d: multiple custom scripts\n\nv65:\n\ninit.d: dynamic number allocation for dnum,tpws_port,qnum\ninit.d: FW_EXTRA_PRE, FW_EXTRA_POST\ninit.d: zapret_custom_firewall_nft_flush\nnfqws,tpws: l7proto and client ip:port info in autohostlist debug log\nnfqws,tpws: user mode ipset filter support\nnfqws,tpws: l7proto filter support\ntpws: fixed MSS apply in transparent mode\nnfqws: fixed autottl apply if desync profile changed\ntpws,nfqws: fixed 100% cpu hang on gzipped list with comments\nipset: get_refilter_ipsum.sh , get_refilter_domain.sh\n\nv66:\n\ninit.d: rewrite traffic interception and daemon launch parameters in config file. this break compatibility with old versions.\ninit.d: openwrt-minimal : tpws launch for low storage openwrt devices\n\nv67:\n\nmdig: --dns-make-query, --dns-parse-query for side-channel resolving (DoH)\nblockcheck: use DoH resolvers if DNS spoof is detected\nblockcheck: restring fooling to testing domain's IPs\nnfqws,tpws: internal hostlist deduplication to save RAM\nnfqws,tpws: hostlist/ipset auto reload on file change. no more HUP.\nnfqws,tpws: --filter-tcp, --filter-udp take comma separated port range list\nnfqws,tpws: @<config_file> - read config from a file\nconfig: <HOSTLIST_NOAUTO> marker\nbinaries: remove zapret-winws. add win32.\nblockcheck, install_easy.sh: preserve user environment variables during elevation\nblockcheck: do not require root if SKIP_PKTWS=1\n\nv68:\n\ndocs : move russian version to markdown\nnfqws,tpws: use alternate $ sign for $<config_file>\nrepo: binaries removed from repo. git actions binaries build in releases.\nuninstall_easy.sh: offer to remove dependencies in openwrt\ninstall_easy.sh: allow to download lists in autohostlist filter mode\n\nv69:\n\nnfqws, tpws: multisplit/multidisorder support.\nnfqws: name change split->fakedsplit, disorder->fakeddisorder. compat : old names are synonyms\nnfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls deprecated. compat : these parameters add split point to multisplit.\nnfqws: --dpi-desync=split2|disorder2 deprecated. compat: they are now synonyms for multisplit/multidisorder\nnfqws: cancel seqovl if MTU is exceeded (linux only). cancel seqovl for disorder if seqovl>=first_part_size.\nnfqws: fixed splits in multiple TLS segments.\ntpws: --split-http-req,--split-tls deprecated. compat : these parameters add split point to multisplit.\ntpws: --tlsrec now takes pos markers. compat : old names are converted to pos markers\ntpws: --tlsrec-pos deprecated. compat : sets absolute pos marker\nnfqws,tpws: chown autohostlist, autohostlist debug log and debug log files after options parse\nnfqws,tpws: set EXEDIR env var to use in @config (won't work for stadalone winws without /bin/sh)\ndvtws: set random/increasing ip_id value in generated packets\nmdig: fixed parsing of DNS reply in windows (stdin is opened as text, not binary)\ntpws: support compile for android NDK api level >= 21 (Android 5.0)\ntpws: --fix-seg segmentation fixer\nrepo: build for android NDK api level 21 (Android 5.0)\ninstall_easy: support for APK package manager in openwrt\nblockcheck: removed ignore CA question\nblockcheck: removed IGNORE_CA, CURL_VERBOSE\nblockcheck: added CURL_OPT\nblockcheck: new strategies support\nblockcheck: test sequence rework\nblockcheck: view all working strategies in summary\n\nv69.1:\n\ninit.d: keenetic udp fix custom\ntpws: fixed incorrect hostlist checks\n\nv69.2:\n\nnfqws,tpws: --skip\nnfqws: --methodeol\ninit.d: do not use pgrep in sysv for busybox compat\n\nv69.3\n\nnfqws,tpws: fixed ipsets and hostlists\nall progs: version numbers for github, build date/time for self built\nrepo: light release for openwrt and embedded systems\nrepo: sha256sum\n\nv69.4\n\nnfqws: fakedsplit/fakeddisorder fakes for both split segments\nnfqws: --dpi-desync-fakedsplit-pattern\n\nv69.5\n\nnfqws,tpws: --dry-run\ninstall_easy: check tpws and nfqws options validity\n\nv69.6\n\nnfqws: set NETLINK_NO_ENOBUFS to fix possible nfq recv errors\ninit.d: unify custom scripts for linux\ninit.d: new custom scripts : 20-fw-extra, 50-wg4all\n\nv69.7\n\nnfqws,tpws: --comment\nnfqws: trash flood warning\nwinws: exclude empty outgoing ack packets in windivert filter\n\nv69.8\n\nwinws: accept empty outgoing RST and FIN packets for conntrack needs\nrepo: lexra build\n\nv69.9\n\ninit.d: exclude ipban from tpws redirection\nmacos: fix install_easy\nmacos: fix national decimal separator in sleep\nipset: scripts maintenance\n\nv70\n\nblockcheck: override all dialog questions and enable batch mode\nblockcheck: parallel attempts\nnfqws: weaken wireguard initiation recognition. use len=148 and data[0]=1 signature\nnfqws: apply split+seqovl only to the first reasm fragment\ninstall_easy: dnf packager support\nnfqws,tpws: hostlist/ipset track not only file mod time but also file size\nnfqws,tpws,ipset: return lists reload on HUP\nnfqws,blockcheck: --dpi-desync-fake-tls-mod\n\nv70.1\n\nnfqws: --dpi-desync-fake-tls-mod=dupsid\nnfqws,tpws: test accessibility of list files after privs drop\nnfqws,tpws: --version\n\nv70.4\n\nnfqws,tpws: ^ prefix in hostlist to disable subdomain matches\nnfqws,tpws: optional systemd notify support. compile using 'make systemd'\nnfqws,tpws: systemd instance templates for nfqws and tpws\nnfqws,tpws: separate droproot from dropcaps\ntpws: detect WSL 1 and warn about non-working options\n\nv70.5\n\nnfqws: multiple --dpi-desync-fake-xxx\nnfqws: support of inter-packet fragmented QUIC CRYPTO\n\nv70.6\n\nnfqws: detect Discord Voice IP discovery packets\nnfqws: detect STUN message packets\nnfqws: change SNI to specified value tls mod : --dpi-desync-fake-tls-mod sni=<sni>\nnfqws: update default TLS ClientHello fake. firefox 136.0.4 finger, no kyber, SNI=microsoft.com\nnfqws: multiple mods for multiple TLS fakes\ninit.d: remove 50-discord\nblockcheck: use tpws --fix-seg on linux for multiple splits\n\nv71\n\nnfqws,tpws: debug tls version, alpn, ech\nnfqws: --dpi-desync-fake-tls=! means default tls fake\nnfqws: --dup*\nnfqws: --orig*\nnfqws: ipcache of hop count and host names\nnfqws: --ctrack-disable\nnfqws: --synack-split\nnfqws: --autottl=- or --autottl=0:0-0 disable autottl. previous \"0\" does not work anymore.\ntpws: ipcache of host names\nnfqws,tpws: set 1024 repeat limit to fakes and dups\nnfqws,tpws: do more before daemonize\nnfqws,tpws: accept multiple gids in --gid\nnfqws,tpws: display \"android\" in version string if built for android\ninit.d: remove --ipset parameter prohibition\ninit.d, blockcheck: drop time exceeded icmp for nfqws-related connections\nblockcheck: some dup and orig-ttl mods\nblockcheck: PKTWS_EXTRA_PRE\nblockcheck: report test function and domain every test\n\nv71.1\n\nnfqws,tpws: much faster ipset implementation. move from hash to avl tree\nnfqws,tpws: check list files accessibility with dropped privs in --dry-run mode\nnfqws,tpws: --debug=android for NDK builds\nnfqws,tpws: use initgroups instead of setgroups if --user specified\nnfqws: --filter-ssid (linux-only)\ninstall_easy: stop if running embedded release on traditional linux system (some files missing)\ninstall_bin: add \"read elf\" arch detection method\nbinaries: renamed arch dirs in binaries\n\nv71.1.1\n\nnfqws: use wireless ext in case nl80211 does not return SSID\n\nv71.2\n\nnfqws: apply udp desync to replayed packets with non-zero reasm offset (except fake)\nblockcheck: display curl version and kernel version\ninstall_bin: stop if no binaries found. display help text.\nwinws: increase buffers for port filter\ntpws: tpws no more opens /dev/pf in OpenBSD by default. requires --enable-pf like in FreeBSD. this is migration from rdr-to to divert-to redirection scheme.\ninstall_easy: warn if --ipset parameter is specified\n\nv71.3\n\ninit.d: FILTER_MARK\nnfqws: ts fooling\nblockcheck: test ts fooling\nblockcheck: auto enable tcp timestamps in windows\ntpws,nfqws: special handling of IP-like hostnames. strip :port from hostname:port\n\nv71.4\n\nnfqws,tpws: fix possible crashes or high memory usage if hostlist has duplicate hostnames\ninit.d: custom scripts 50-discord-media, 50-stun4all\ninit.d: windivert filters for discord media, stun, wireguard\nreadme: hardware problems description\n\nv72\n\nwinws: --wf-raw-part\nnfqws: --dpi-desync=hostfakesplit\nnfqws: --dpi-desync-fake-tcp-mod=seq\nnfqws: --dpi-desync-fake-tls=!+offset , --dpi-desync-fake-xxx=[+offset]@filename\nnfqws: --dpi-desync-fakedsplit-mod=altorder  for fakedsplit/fakeddisorder\nnfqws: --dpi-desync-hostfakesplit-mod=altorder\nnfqws: fakedsplit/fakeddisorder normalize fake split pattern offset to reasm offset\nnfqws: optimize ipv4 ip_id. apply ip_id on all OS the same way.\nblockcheck: print PRETTY_NAME and some OPENWRT_xxx from /etc/os-release\nblockcheck: new strategies\nblockcheck: curl test simulation : SIMULATE=1\n\nv72.1\n\nnfqws: --ip-id=seq|seqgroup|rnd|zero\nblockcheck: MIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA\ninit.d: 50-quic4all custom\n\n72.2\n\nnfqws: --wssize-forced-cutoff\nnfqws: --orig-tcp-flags, --dup-tcp-flags, --dpi-desync-tcp-flags\nnfqws: --dup-ip-id\n\n72.3\n\nblockcheck: support URIs\nblockcheck: CURL_HTTPS_GET=1 suppresses -I curl option for https (HEAD -> GET)\n\n72.4\n\nblockcheck: fix broken dns cache\n\n72.5\n\nnfqws: fix broken l7proto profile rediscovery\nnfqws: backport from nfqws2 nl80211 ssid discovery fix for newer kernels\n\n72.6\n\nipset: remove zapret-info based scripts because it's gone\nblockcheck: fix tpws test regression\n\n72.7\n\nnfqws,tpws: memleak fix\nmdig: --eagain, --eagain-delay\n\n72.8\n\nnfqws: fix breaking tcp if ts fooling is enabled but no timestamps present\n\n72.9\n\nblockcheck: fix detection of http redirection if domain/URI specified\ninstall_easy: fix writing of ask_list variables\n\n72.10\n\n* nfqws2: fix broken wifi ssid update\n* minor AI fixes\n\n72.12\n\n* github: reduce binaries size\n* github: use 16K page size for android arm64 build\n* nfqws: join fragments in quic CRYPTO reconstruction. allow intersections.\n"
  },
  {
    "path": "docs/compile/build_howto_openwrt.txt",
    "content": "How to compile native programs for use in openwrt\n-------------------------------------------------\n\n1) Install required packages to the host system :\n\ndebian,ubuntu : apt install build-essential patch libncurses-dev python3-distutils unzip gawk wget git\nfedora: dnf install make patch gcc g++ ncurses-devel git perl\n\nOther packages may be required on your distribution. Look for the errors.\n\n2) Download latest SDK for your target platform from https://downloads.openwrt.org\n\nexamples :\n\ncurl -o - https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz | tar -Jxv\ncd openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64\n\ncurl -o - https://downloads.openwrt.org/snapshots/targets/x86/64/openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64.tar.zst | tar --zstd -xv\ncd openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64\n\n3) Install required libs\n\n./scripts/feeds update base packages\n./scripts/feeds install libnetfilter-queue zlib libcap\n\n4) Prepare openwrt package definitions\n\ncp -R /opt/zapret/docs/compile/openwrt/. .\ncp -R /opt/zapret/tpws package/zapret/tpws\ncp -R /opt/zapret/nfq package/zapret/nfqws\ncp -R /opt/zapret/mdig package/zapret/mdig\ncp -R /opt/zapret/ip2net package/zapret/ip2net\nrm -f package/zapret/tpws/tpws/tpws package/zapret/nfqws/nfq/nfqws package/zapret/mdig/mdig/mdig package/zapret/ip2net/ip2net/ip2net\n\n5) Prepare .config\n\nmake defconfig\n\nIf you only need bins without packages comment 'CONFIG_AUTOREMOVE=y' line in .config\n\n6) Compile\n\ndynamic build : make package/{tpws,nfqws,mdig,ip2net}/compile\nstatic build :  make CFLAGS=-static package/{tpws,nfqws,mdig,ip2net}/compile\n\n7) Get result\n\nexecutables only : build_dir/target/<progname>\nipk or apk packages : bin/packages/*/base\n\n8) Installing to openwrt to use with zapret\n\nzapret with or without binaries should be already installed in /opt/zapret.\nInstall ipk's or apk's with all compiled progs using opkg or apk.\nBins are placed to /opt/zapret/binaries/my.\nOr copy binaries there manually and set chmod 755 to them.\nRun install_bin.sh or install_easy.sh. They will use bins in 'my' folder.\n"
  },
  {
    "path": "docs/compile/build_howto_unix.txt",
    "content": "debian,ubuntu :\n\napt install make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev\nmake -C /opt/zapret systemd\n\nFreeBSD :\n\nmake -C /opt/zapret\n\nOpenBSD :\n\nmake -C /opt/zapret bsd\n\nMacOS :\n\nmake -C /opt/zapret mac\n"
  },
  {
    "path": "docs/compile/build_howto_windows.txt",
    "content": "Windows x64\n\n1) Download latest cygwin for windows 7\n\ncurl -O https://www.cygwin.com/setup-x86_64.exe\nsetup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215\n\n2) During setup install packages : make gcc-core zlib-devel\n\n3) Run Cygwin.bat\n\n4) cd to %ZAPRET_BASE%/nfq\n\ncd C:/Users/user/Downloads/zapret/nfq\n\n5) Compile\n\nmake cygwin64\n\nuse winws.exe\n\n6) Take windivert.dll and windivert64.sys here : https://reqrypt.org/download\nChoose version 2.2.2 for Windows 10 and 2.2.0 for Windows 7.\n\n7) Copy cygwin1.dll, winws.exe, windivert.dll and windivert64.sys to one folder.\n\n8) Run winws.exe from cmd.exe running as administrator.\nwinws will not run from cygwin shell with cygwin1.dll copy in it's folder.\nwinws will not run without cygwin1.dll outside of cygwin shell.\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/ip2net/Makefile",
    "content": "#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=ip2net\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/ip2net\n\tSECTION:=net\n\tCATEGORY:=Network\n\tTITLE:=ip2net\n\tSUBMENU:=Zapret\nendef\n\ndefine Build/Prepare\n\tmkdir -p $(PKG_BUILD_DIR)\n\t$(CP) ./ip2net/* $(PKG_BUILD_DIR)/\nendef\n\ndefine Build/Compile\n\t$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)\nendef\n\ndefine Package/ip2net/install\n\t$(INSTALL_DIR) $(1)/opt/zapret/binaries/my\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/ip2net $(1)/opt/zapret/binaries/my\nendef\n\n$(eval $(call BuildPackage,ip2net))\n\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/ip2net/readme.txt",
    "content": "Copy \"ip2net\" folder here !\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/mdig/Makefile",
    "content": "#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=mdig\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/mdig\n\tSECTION:=net\n\tCATEGORY:=Network\n\tTITLE:=mdig\n\tSUBMENU:=Zapret\nendef\n\ndefine Build/Prepare\n\tmkdir -p $(PKG_BUILD_DIR)\n\t$(CP) ./mdig/* $(PKG_BUILD_DIR)/\nendef\n\ndefine Build/Compile\n\t$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)\nendef\n\ndefine Package/mdig/install\n\t$(INSTALL_DIR) $(1)/opt/zapret/binaries/my\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/mdig $(1)/opt/zapret/binaries/my\nendef\n\n$(eval $(call BuildPackage,mdig))\n\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/mdig/readme.txt",
    "content": "Copy \"mdig\" folder here !\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/nfqws/Makefile",
    "content": "#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=nfqws\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/nfqws\n\tSECTION:=net\n\tCATEGORY:=Network\n\tTITLE:=nfqws\n\tSUBMENU:=Zapret\n\tDEPENDS:=+libnetfilter-queue +lmnl +libcap +zlib\nendef\n\ndefine Build/Prepare\n\tmkdir -p $(PKG_BUILD_DIR)\n\t$(CP) ./nfq/* $(PKG_BUILD_DIR)/\nendef\n\ndefine Build/Compile\n\t$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)\nendef\n\ndefine Package/nfqws/install\n\t$(INSTALL_DIR) $(1)/opt/zapret/binaries/my\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/binaries/my\nendef\n\n$(eval $(call BuildPackage,nfqws))\n\n\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/nfqws/readme.txt",
    "content": "Copy \"nfq\" folder here !\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/tpws/Makefile",
    "content": "#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=tpws\nPKG_RELEASE:=1\n\ninclude $(INCLUDE_DIR)/package.mk\n\ndefine Package/tpws\n\tSECTION:=net\n\tCATEGORY:=Network\n\tTITLE:=tpws\n\tSUBMENU:=Zapret\n \tDEPENDS:=+zlib +libcap\nendef\n\ndefine Build/Prepare\n\tmkdir -p $(PKG_BUILD_DIR)\n\t$(CP) ./tpws/* $(PKG_BUILD_DIR)/\nendef\n\ndefine Build/Compile\n\t$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)\nendef\n\ndefine Package/tpws/install\n\t$(INSTALL_DIR) $(1)/opt/zapret/binaries/my\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/binaries/my\nendef\n\n$(eval $(call BuildPackage,tpws))\n\n"
  },
  {
    "path": "docs/compile/openwrt/package/zapret/tpws/readme.txt",
    "content": "Copy \"tpws\" folder here !\n"
  },
  {
    "path": "docs/iptables.txt",
    "content": "For window size changing :\n\niptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass\n\nFor dpi desync attack :\n\niptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I POSTROUTING -p tcp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I POSTROUTING -p udp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n\n# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI\nsysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 \niptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:12 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I PREROUTING -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n\n\nFor TPROXY :\n\nsysctl -w net.ipv4.ip_forward=1\niptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n\nip -f inet rule add fwmark 1 lookup 100\nip -f inet route add local default dev lo table 100\n# prevent loop\niptables  -t filter -I INPUT -p tcp --dport 988  -j REJECT\niptables  -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1\niptables  -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988\n\niptables  -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1\niptables  -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988\n\nFor DNAT :\n\n# run tpws as user \"tpws\". its required to avoid loops.\nsysctl -w net.ipv4.conf.eth1.route_localnet=1\niptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.127:988\niptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988\n\n\nReset all iptable rules :\n\niptables -F\niptables -X\niptables -t nat -F\niptables -t nat -X\niptables -t mangle -F\niptables -t mangle -X\niptables -t raw -F\niptables -t raw -X\n\nReset iptable policies :\n\niptables -P INPUT ACCEPT\niptables -P FORWARD ACCEPT\niptables -P OUTPUT ACCEPT\niptables -t mangle -P POSTROUTING ACCEPT\niptables -t mangle -P PREROUTING ACCEPT\niptables -t mangle -P INPUT ACCEPT\niptables -t mangle -P FORWARD ACCEPT\niptables -t mangle -P OUTPUT ACCEPT\niptables -t raw -P PREROUTING ACCEPT\niptables -t raw -P OUTPUT ACCEPT\n"
  },
  {
    "path": "docs/nftables.txt",
    "content": "nftables test cheat sheet\nsimplified rules to test nfqws and tpws\n\n\nFor DNAT :\n\n# run tpws as user \"tpws\". its required to avoid loops.\n\nnft delete table inet ztest\nnft create table inet ztest\nnft add chain inet ztest pre \"{type nat hook prerouting priority dstnat;}\"\nnft add rule inet ztest pre tcp dport \"{80,443}\" redirect to :988\nnft add chain inet ztest out \"{type nat hook output priority -100;}\"\nnft add rule inet ztest out tcp dport \"{80,443}\" skuid != tpws redirect to :988\n\n\nFor dpi desync attack :\n\nnft delete table inet ztest\nnft create table inet ztest\nnft add chain inet ztest post \"{type filter hook postrouting priority mangle;}\"\nnft add rule inet ztest post meta mark and 0x40000000 == 0 tcp dport \"{80,443}\" ct original packets 1-6 queue num 200 bypass\nnft add rule inet ztest post meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass\n\n# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI\nsysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 \nnft add chain inet ztest pre \"{type filter hook prerouting priority filter;}\"\nnft add rule inet ztest pre tcp sport \"{80,443}\" ct reply packets 1-3 queue num 200 bypass\n\n\nshow rules   : nft list table inet ztest\ndelete table : nft delete table inet ztest\n"
  },
  {
    "path": "docs/nftables_notes.txt",
    "content": "nftables - это технология, пришедшая на замену iptables.\nВ ней собрали все, что относилось к различным iptables. А их немало. iptables, ip6tables, ebtables, arptables, ipset.\nВесь код из разрозненных, но похожих компонент, собрали в одно целое с единым синтаксисом.\nДобавили различные конструкции языка, позволяющие писать правила более лаконично, не повторяя одни и те же команды с небольшими различиями.\nНа nftables можно сделать почти все, что можно было сделать на iptables. Есть то, что можно сделать на nftables, но нельзя на iptables.\nЕсть и наоборот.\n\nК сожалению, не обошлось и без боли.\n\nГлавная боль N1. Очень серьезная, актуальная для openwrt, и решения не видно.\n\nipset-ы позволяли загонять пересекающиеся интервалы ip адресов или подсетей.\nnftables sets это не позволяют. Любое пересечение вызывает ошибку.\nЕсть auto-merge, но это работает только в user mode в процессе nft, при условии, что весь блок адресов загоняется одной командой\nи нет пересечений с уже имеющимся контентом в set.\nЭто не было бы критической проблемой, поскольку скрипты zapret и так загоняют ipset целиком.\nПроблема в катастрофическом расходе памяти при операции загона больших интервальных листов, то есть с подсетями и диапазонами.\nЧтобы загнать 100000 ipv4 записей, едва хватает 300 Mb памяти устройства.\nПри успехе операции в ядре список столько не занимает, но суть дела это не меняет.\nДля традиционных linux систем это не проблема, но почти все роутеры загнутся.\nПриемлемого решения не просматривается.\nСделать записи непересекающимися в листах - задача непростая. Потребуется переписать алгоритм auto-merge из nft,\nно с пониженным расходом памяти.\nЗагонять записи по одному отдельными вызовами nft, игнорируя ошибки, займет вечность.\nЗагонять блоком отдельных команд, игнорируя ошибки, - nft такого не умеет. Похоже, при любой ошибке происходит откат всего скрипта.\nК тому же при таком подходе будут неточности в итоговом результате.\nSwap позволяет немного сгладить проблему, но лишь незначительно.\nСкажем, если вдруг list загоняется без ошибок с 300 Mb памяти и с падением на 256 Mb, то swap спасает.\nЕсли памяти становится 200 Mb, то swap уже не спасет. Все равно вызывается OOM killer, заодно убивая и другие процессы, кроме nft,\nа это уже совсем плохо. Может быть убито что-то важное.\n\nБоль N2, не смертельная, но тоже не айс.\n\nКакие-то нерациональные алгоритмы разбора таблиц в nft.\nНапример, есть 1 большой set на 100000 элементов и 1 маленький на 2 элемента.\nЧтобы просто пролистать мелкий set или добавить туда еще что-то nft будет мусолить несколько секунд.\nЧто он делает за это время ? Тащит из ядра огромный блоб, в котором все в куче, и разбирает его, чтобы выделить искомую мелочь ?\nВ какой-то мере удается это сгладить, объединяя несколько команд в единый скрипт.\n\nБоль N3\n\nСистема nftables построена на виртуальной машине. Правила, которые вы пишите, переводятся в псевдокод VM.\nЧтобы потом их показать , nft декомпилирует код и переводит в читаемый язык.\nЭто довольно сложно, и регулярно случаются баги, связанные с неверным отображением.\n\nКроме этого, часто встречаются и баги парсера.\nНапример, все версии nft вплоть до 1.0.1 имеют баг, который не разрешает названия интерфейсов в кавычках в\nопределении flowtable. Без кавычек нельзя вставить интерфейсы , имя которых начинается с цифры.\nOpenWRT решает эту проблему отдельным патчем в snapshot версии, но на традиционных системах и в openwrt 21.x- его нет.\nПочему бы не наплевать на интерфейсы, начинающиеся с цифры ? Потому что для openwrt 6to4-6to4, 6in4-he-net - обычное явление.\nНа текущий момент этой проблемы в openwrt уже нет, если использовать актуальную версию.\n\nНо тем не менее, хоть nft и давно перешел отметку 1.0, всякая мелочь, особенно на не совсем стандартных правилах,\nрегулярно всплывает. Потому чем новее у вас будет версия nft, тем больше там выловлено проблем.\nЗдесь обновления важны, чтобы потом не мучаться из-за давно исправленного велосипеда.\n\nБоль N4\n\nНевозможно , не копаясь в других таблицах и хуках, ничего не зная об их содержании, предотвратить DROP или REJECT.\nНельзя написать такое правило, которое что-то важное ACCEPTнет, игнорируя остальные хуки во всех таблицах.\nЕсли у вас есть какой-то фаервол, и он что-то дропает, то как от этого отказаться, если надо временно что-то принять ?\nЭто особенность netfilter, он так работает, но в iptables есть лишь стандартные таблицы с их хуками, куда можно\nвставить ACCEPT. Здесь хуков может быть сколько угодно в каких угодно таблицах.\nЭта проблема частично ломает кайф от независимого управления таблицами.\n\n\nПлюс N1, главный\n\niptables хороши, когда ими пользуется кто-то один. Иначе это проходной двор.\nКогда есть система управления фаерволом, то приходится как-то к ней прикручиваться, чтобы не нарушить ее работу\nи управлять правилами синхронно. Нужно уметь внести и удалить отдельные правила когда это нужно, не трогая все остальное.\nНекоторые системы управления фаерволом вообще не предполагают, чтобы кто-то еще лез в iptables, и это очень сильно портит жизнь.\nУ iptables есть предопределенный набор хуков netfilter с фиксированным приоритетом.\nВ nftables хуков можно создать неограниченное количество с выбранным приоритетом, управляя ими независимо в отдельных таблицах.\nСистема управления фаерволом может работать в одной таблице (fw4 в случае openwrt) и не трогать все остальное.\nzapret может работать в другой таблице и не трогать систему управления фаерволом. Они друг другу не мешают.\nЭто снимает множество боли.\nНо есть и исключение. nfset-ы - аналог ipset-ов - нельзя использовать из другой таблицы. Потому если вам нужен ipset,\nсоздаваемый zapret скриптами, вам понадобится писать правила в той же таблице. Но нет никакой необходимости влезать в цепочки zapret.\nСоздаете свои цепочки и хуки и делаете в них что угодно.\n\nПлюс N2\n\nВозможность выбора приоритета хука позволяет легко решить проблему хаотической и принудительной дефрагментацией L3 ipv6,\nбез танцев с загрузкой модулей ядра со специальными параметрами или перекомпиляцией nftables-nft.\nЭто же позволяет перехватить трафик после SNAT/MASQUERADE, что на iptables невозможно.\nАтаки на проходящий трафик, ломающие NAT, крайне затруднены на iptables.\n\nПлюс N3\n\nНаличие множеств (anonymous/named sets) позволяет не писать кучу однообразных правил там, где в iptables их пришлось бы написать.\n\nПлюс N4\n\nЕсли у вас есть nftables, то там наверняка есть уже все или почти все.\nНет кучи разных модулей ядра и .so плагинов для iptables user-mode процесса.\nОтдельные модули ядра есть, но их меньше, чем в iptables, и openwrt их делит на меньшее число пакетов, большинство из которых\nи так ставятся по умолчанию.  user-mode процесс nft и вовсе неделим. EXE-шник + lib.\n\nПлюс N5\n\nПишут, что nftables работают быстрее. Но это не точно и зависит от много чего.\nВ целом - чем меньше правил, тем выше скорость. Но в nftables правил можно писать меньше, значит скорость тоже может быть выше.\nУ разработчиков есть идея перевести backend nftables на BPF, а это наверняка будет существенно быстрее.\n\n\nВыводы\n\nБез больших листов все почти прекрасно. Но большие ip листы убивают все. Не для домашних это роутеров.\nА ipset-ы к nftables не прикрутить.\nЗато есть возможность задействовать более продвинутые атаки, конфликтующие с NAT, которые на iptables могут быть невозможны.\nДелать нечего. Openwrt отошел от iptables. С этим придется как-то жить.\nПоэтому пришлось сделать для openwrt поддержку и iptables, и nftables (только с версии openwrt 21.xx, в более старых будут проблемы).\niptables можно задействовать на любой openwrt версии.\nЕсли используется fw3, применяется старый механизм интеграции в fw3.\nЕсли он не используется, то правилами iptables управляем как в традиционных linux системах - то есть с возможностью\nзапуска и остановки, а скрипт запуска вносит в том числе и правила iptables.\n\nНа новых openwrt возможно снести nftables и firewall4 и установить firewall3 и iptables.\nЕсли вам никак без больших ip листов на слабой системе, это может быть единственным спасением.\n"
  },
  {
    "path": "docs/quick_start.md",
    "content": "# Быстрая настройка Linux/OpenWrt\n\n> [!CAUTION]  \n> Не пишите в issue вопросы типа \"как скопировать файл\", \"как скачать\", \"как\n> запустить\" и т.п. То есть все, что касается базовых навыков обращения с ОС\n> Linux. Эти вопросы будут закрывать сразу. Если у вас подобные вопросы\n> возникают, рекомендую не использовать данный софт или искать помощь где-то в\n> другом месте. То же самое могу сказать тем, кто хочет нажать 1 кнопку, чтобы\n> все заработало, и совсем не хочет читать и изучать. Увы, такое не подвезли и\n> не подвезут. Ищите другие более простые методы обхода. Этот метод **не для\n> рядового пользователя**.\n\n\n## Вступление\nСпециально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться\nв простыню [readme.md](readme.md).\n\nОбход DPI является хакерской методикой. Под этим словом понимается метод,\nкоторому оказывается активное противодействие и поэтому автоматически не\nгарантирована работоспособность в любых условиях и на любых ресурсах, требуется\nнастройка под специфические условия у вашего провайдера. Условия могут меняться\nсо временем, и методика может начинать или переставать работать, может\nпотребоваться повторный анализ ситуации. Могут обнаруживаться отдельные\nресурсы, которые заблокированы иначе, и которые не работают или перестали\nработать. Могут и сломаться отдельные не заблокированные ресурсы. Поэтому очень\nжелательно иметь знания в области сетей, чтобы иметь возможность\nпроанализировать техническую ситуацию. Не будет лишним иметь обходные каналы\nпроксирования трафика на случай, если обход DPI не помогает.\n\nВариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный.\nНет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой.\nТакие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети.\nЕсли даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация.\n\nБудем считать, что у вас есть система на базе традиционного **linux** или\n**openwrt**. Если у вас традиционный linux - задача обойти блокировки только на\nэтой системе, если openwrt - обойти блокировки для подключенных устройств. Это\nнаиболее распространенный случай.\n\n\n## Настройка\n> [!TIP]  \n> Чтобы процедура установки сработала в штатном режиме на openwrt, нужно\n> рассчитывать на свободное место около 1-2 Mb для установки самого zapret и\n> необходимых дополнительных пакетов. Если места мало и нет возможности его\n> увеличить за счет `extroot`, возможно придется отказаться от варианта простой\n> установки и прикручивать в ручном режиме без имеющихся скриптов запуска.\n> Можно использовать [облегченный `tpws` вариант](../init.d/openwrt-minimal),\n> либо попробовать засунуть требуемые zapret дополнительные пакеты в сжатый\n> образ `squashfs` с помощью `image builder` и перешить этим вариантом роутер.\n\n1. Скачайте последний [tar.gz релиз](https://github.com/bol-van/zapret/releases) в /tmp, распакуйте его, затем удалите архив.\n   Для openwrt и прошивок используйте вариант `openwrt-embedded`.\n   Для экономия места в /tmp можно качать через curl в stdout и сразу распаковывать.\n   Пример под openwrt для версии zapret 71.4 (для других URL отличается) :\n   ```\n   $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx\n   $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx\n   ```\n   Пример под традиционный linux для версии zapret 71.4 (для других URL отличается) :\n   ```\n   $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx\n   $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx\n   ```\n   curl должен быть предварительно установлен. Но он в любом случае понадобится далее.\n   Вариант с wget будет работать только если установленный wget поддерживает https.\n\n2. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и\n   сам zapret. Гарантированно уберет zapret скрипт `uninstall_easy.sh`.\n\n3. Если вы работаете в виртуальной машине, необходимо использовать соединение с\n   сетью в режиме bridge. NAT **не** подходит.\n\n4. Выполните однократные действия по установке требуемых пакетов в ОС и\n   настройке исполняемых файлов правильной архитектуры:\n   ```sh\n   $ install_bin.sh\n   $ install_prereq.sh\n   ```\n\n   > Вас могут спросить о типе фаервола (iptables/nftables) и использовании\n   > ipv6. Это нужно для установки правильных пакетов в ОС, чтобы не\n   > устанавливать лишнее.\n\n5. Запустите `blockcheck.sh`. Скрипт вначале проверяет DNS. Если выводятся\n   сообщения о подмене адресов, то нужно будет решить проблему с DNS.\n   `blockcheck.sh` перейдет в этом случае на DoH и будет пытаться получить и\n   использовать реальные IP адреса. Но если вы не настроите решение этой\n   проблемы, обход будет работать только для тех программ или ОС, которые сами\n   реализуют механизмы SecureDNS. Для других программ обход работать не будет.\n\n   > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо\n   > заменой DNS серверов от провайдера на публичные (`1.1.1.1`, `8.8.8.8`),\n   > либо в случае перехвата провайдером обращений к сторонним серверам - через\n   > специальные средства шифрования DNS запросов, такие как `dnscrypt`, `DoT`,\n   > `DoH`.\n   >\n   > Еще один эффективный вариант - использовать ресолвер от yandex\n   > (`77.88.8.88`) на нестандартном порту `1253`. Многие провайдеры не\n   > анализируют обращения к DNS на нестандартных портах.\n   >\n   > Проверить работает ли этот вариант можно так:\n   > ```sh\n   > $ dig -p 53 @77.88.8.88 rutracker.org\n   > $ dig -p 1253 @77.88.8.88 rutracker.org\n   > ```\n   >\n   > Если DNS действительно подменяется, и ответ на эти 2 команды разный,\n   > значит метод вероятно работает.\n   >\n   > В openwrt DNS на нестандартном порту можно прописать в `/etc/config/dhcp`\n   > таким способом:\n   >\n   > ```\n   > config dnsmasq\n   > \t<...>\n   > \tlist server '77.88.8.88#1253'\n   > ```\n   >\n   > Если настройки IP и DNS получаются автоматически от провайдера, в\n   > `/etc/config/network` найдите секцию интерфейса `wan` и сделайте так:\n   >\n   > ```\n   > config interface 'wan'\n   > \t<...>\n   > \toption peerdns '0'\n   > ```\n   >\n   > ```sh\n   > $ /etc/init.d/network restart\n   > $ /etc/init.d/dnsmasq restart\n   > ```\n   >\n   > Если это не подходит, можно перенаправлять обращения на UDP и TCP порты\n   > `53` вашего DNS сервера на `77.88.8.88:1253` средствами\n   > `iptables`/`nftables`. В `/etc/resolv.conf` нельзя прописать DNS на\n   > нестандартном порту.\n\n6. `blockcheck.sh` позволяет выявить рабочую стратегию обхода блокировок. По\n   результатам скрипта нужно понять какой вариант будете использовать : `nfqws`\n   или `tpws` и запомнить найденные стратегии для дальнейшего применения.\n\n   Следует понимать, что скрипт проверяет доступность только конкретного\n   домена, который вы вводите в начале, конкретной программой curl.\n   У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой.\n   Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber).\n   От этого может зависеть работоспособность стратегий.\n   Обычно остальные домены блокированы подобным образом, **но не факт**.\n   Бывают специальные блокировки. Некоторые параметры требуют тюнинга под \"общий знаменатель\".\n   В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне\n   желательно**, но это требует понимания как работают стратегии. zapret **не может\n   пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите\n   их через пробел.\n\n   > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у\n   > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на\n   > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и\n   > включаются в работу хаотичным образом или образом, зависящим от\n   > направления (IP сервера). скрипт не всегда может выдать вам в итогах\n   > оптимальную стратегию, которую надо просто переписать в настройки. В\n   > некоторых случаях надо реально думать что происходит, анализируя результат\n   > на разных стратегиях.\n   >\n   > Далее, имея понимание что работает на http, https, quic нужно\n   > сконструировать параметры запуска `tpws` и/или `nfqws` с использованием\n   > мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии).\n   >\n   > Если кратко, то обычно параметры конструируются так:\n   > ```sh\n   > \"--filter-udp=443 'параметры для quic' <HOSTLIST_NOAUTO> --new\n   > --filter-tcp=80,443 'объединенные параметры для http и https' <HOSTLIST>\"\n   > ```\n   >\n   > Или так:\n   > ```sh\n   > \"--filter-udp=443 'параметры для quic' <HOSTLIST_NOAUTO> --new\n   > --filter-tcp=80 'параметры для http' <HOSTLIST> --new\n   > --filter-tcp=443 'параметры для https' <HOSTLIST>\"\n   > ```\n   >\n   > `<HOSTLIST>` и `<HOSTLIST_NOAUTO>` так и пишутся. Их не надо на что-то\n   > заменять. Это сделают скрипты запуска, если вы выбрали режим фильтрации по\n   > хостлистам, и уберут в противном случае. Если для какого-то протокола надо\n   > дурить все без стандартного хостлиста - просто уберите оттуда `<HOSTLIST>`\n   > и `<HOSTLIST_NOAUTO>`. Можно писать свои параметры `--hostlist` и\n   > `--hostlist-exclude` для дополнительных хостлистов или в профилях\n   > специализаций под конкретный ресурс. В последнем случае стандартный\n   > хостлист там не нужен. Следует избегать указания собственных параметров\n   > `--hostlist` на листы из директории ipset. Эта логика включена в\n   > `<HOSTLIST>` и `<HOSTLIST_NOAUTO>`. Отличие `<HOSTLIST_NOAUTO>` в том, что\n   > стандартный автолист по этому профилю используется как обычный, то есть\n   > без автоматического добавления доменов. Однако, добавления в других\n   > профилях автоматически отражаются во всех остальных.\n   >\n   > Если стратегии отличаются по версии ip протокола, и вы не можете их\n   > объединить, фильтр пишется так:\n   > ```sh\n   > \"--filter-l3=ipv4 --filter-udp=443 'параметры для quic ipv4' <HOSTLIST_NOAUTO> --new\n   > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' <HOSTLIST> --new\n   > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' <HOSTLIST> --new\n   > --filter-l3=ipv6 --filter-udp=443 'параметры для quic ipv6' <HOSTLIST_NOAUTO> --new\n   > --filter-l3=ipv6 --filter-tcp=80 'параметры для http ipv6' <HOSTLIST> --new\n   > --filter-l3=ipv6 --filter-tcp=443 'параметры для https ipv6' <HOSTLIST>\"\n   > ```\n   >\n   > Но здесь совсем \"копи-пастный\" вариант. Чем больше вы объедините стратегий и\n   > сократите их общее количество, тем будет лучше.\n   >\n   > Если вам не нужно дурение отдельных протоколов, лучше всего будет убрать\n   > лишние порты из системы перехвата трафика через параметры `TPWS_PORTS`,\n   > `NFQWS_PORTS_TCP`, `NFQWS_PORTS_UDP` и убрать соответствующие им профили\n   > мультистратегии.\n   >\n   > | Протокол | Порт | Примечание |\n   > |---|---|---|\n   > | `tcp` | `80` | `http` соединение |\n   > | `tcp` | `443` | `https` соединение |\n   > | `udp` | `443` | `quic` соединение |\n   >\n   > Если используются методы нулевой фазы десинхронизации (`--mss`,\n   > `--wssize`, `--dpi-desync=syndata`) и режим фильтрации `hostlist`, то все\n   > параметры, относящиеся к этим методам, следует помещать в отдельные\n   > профили мультистратегии, которые получат управление до определения имени\n   > хоста. Необходимо понимать алгоритм работы мультистратегий. Самым надежным\n   > вариантом будет дублирование этих параметров на 2 профиля. Какой-нибудь\n   > сработает в зависимости от параметра `MODE_FILTER`.\n   >\n   > ```sh\n   > \"--filter-tcp=80 'параметры для http' <HOSTLIST> --new\n   > --filter-tcp=443 'параметры для https' --wssize 1:6 <HOSTLIST> --new\n   > --filter-tcp=443 --wssize 1:6\"\n   > ```\n   >\n   > В этом примере `wssize` будет применяться всегда к порту tcp `443` вне\n   > зависимости от параметра `MODE_FILTER`. Хостлист будет игнорироваться,\n   > если таковой имеется. К http применять `wssize` вредно и бессмысленно.\n   >\n   > Иногда требуется дописать к стратегиям свои собственные параметры.\n   > Например, нужно изменить количество повторов фейков или задать свой фейк.\n   > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`.\n   >\n   > ```PKTWS_EXTRA=\"--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin\" ./blockcheck.sh```\n   >\n   > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный\n   > костяк проверки, на который вы можете навешивать свои кустомизации.\n\n7. Запустите скрипт облегченной установки - `install_easy.sh` Выберите `nfqws`\n   и/или `tpws`, затем согласитесь на редактирование параметров. Откроется\n   редактор, куда впишите созданную на предыдущем этапе стратегию.\n\n8. На все остальные вопросы `install_easy.sh` отвечайте согласно выводимой\n   аннотации.\n\n9. Удалите директорию из /tmp, откуда производилась установка.\n\n## Полное удаление\n\n1. Прогоните `/opt/zapret/uninstall_easy.sh`.\n2. Cогласитесь на удаление зависимостей в openwrt.\n3. Удалите каталог `/opt/zapret`.\n\n## Итог\nЭто минимальная инструкция, чтобы быстро сориентироваться с чего начать.\nОднако, это не гарантированное решение и в некоторых случаях вы не обойдетесь\nбез знаний и основного \"талмуда\". Подробности и полное техническое описание\nрасписаны в [README](readme.md).\n\nЕсли ломаются отдельные **не заблокированные** ресурсы, следует вносить их в\nисключения, либо пользоваться ограничивающим `ipset` или хост листом. Лучше\nподбирать такие стратегии, которые вызывают минимальные поломки. Есть стратегии\nдовольно безобидные, а есть сильно ломающие, которые подходят только для\nточечного пробития отдельных ресурсов, когда ничего лучше нет. Хорошая\nстратегия может сильно ломать из-за плохо подобранных ограничителей для фейков\n\\- ttl, fooling.\n"
  },
  {
    "path": "docs/quick_start_windows.md",
    "content": "# Быстрая настройка Windows\n\nСпециально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню [readme.md](./readme.md).\n> [!CAUTION]  \n> Как обычно, компьютерная грамотность ложится полностью на вас.\n> Вы должны уметь работать с консолью windows и иметь минимальные навыки обращения с командными файлами `bat`, `cmd`.\n> Если грамотность отсутствует и возникает куча _\"как?\"_ на базовых вещах, значит эта программа не для вас.\n> Разработчик не будет отвечать на вопросы из серии школы компьютерной грамотности.\n> Если вы все-таки хотите продолжать, задавайте вопросы в дискуссиях на github или на форумах.\n> Возможно, кто-то вам поможет. Но не надо писать issue на github. Они будут закрываться сразу.\n\n## Немного разъяснений\n\nОбход DPI является хакерской методикой. Под этим словом понимается метод, которому оказывается активное противодействие и поэтому\nавтоматически не гарантирована работоспособность в любых условиях и на любых ресурсах,\nтребуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем,\nи методика может начинать или переставать работать, может потребоваться повторный анализ ситуации.\nМогут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать.\nМогут и сломаться отдельные незаблокированные ресурсы.\nПоэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию.\nНе будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает.\n\nВариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный.\nНет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой.\nТакие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети.\nЕсли даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация.\n\nОсобо осторожным нужно быть со сторонними сборками. Там могут быть вирусы. Не в каждой сборке, но уже были замечены скаммеры.\nВидео на ютубе как просто обойти блокировку, прилагающийся архив, в котором какая-то ерунда, написанная на питоне, скачивающая зловред.\n\nБудем считать, что у вас есть windows 7 или выше. Задача - обойти блокировки с самой системы.\n\n## Я ЧТО-ТО ДЕЛАЛ, НЕ ПОМОГЛО, КАК ТЕПЕРЬ ЭТО УДАЛИТЬ\n\nЕсли вы не устанавливали zapret как службу или запланированную задачу (а это требует редактирования cmd файлов),\nдостаточно закрыть окно с winws и запустить windivert_delete.cmd.\nАльтернатива - перезагрузить компьютер.\nПосле чего можно удалить папку с zapret. На этом деинсталляция закончена.\nЕсли же вы устанавливали zapret как службу, то вы наверняка знаете как ее удалить.\n\nЕсли вдруг среди того, на что вы нажимали, есть слова \"general\", \"alt\", \".bat\", \"автозапуск\", или же есть файлы, которые отсутствуют\nв оригинальных репозиториях, то это сборка. Вы не получите ответа как ее удалить от разработчика zapret.\nСпрашивайте самих сборщиков. Разработчик не предоставляет простого решения, этим занимаются сборщики, но они и сами отвечают за свои продукты.\n\n## Настройка\n\n1. Скачайте и распакуйте архив https://github.com/bol-van/zapret-win-bundle/archive/refs/heads/master.zip.\n\n2. Если у вас Windows 7 x64, однократно запустите `win7/install_win7.cmd`. Батник заменит файлы windivert на совместимую с Windows 7 версию.\n\n   > Для 32-битных систем Windows нет готового полного варианта.\n\n   > На windows 11 arm64 выполните `arm64/install_arm64.cmd` от имени администратора и перезагрузите компьютер.\n   > Читайте [docs/windows.md](./windows.md)\n   >\n   > Имейте в виду, что антивирусы могут плохо реагировать на windivert.\n   > cygwin так же имеет внушительный список несовместимостей с антивирусами, хотя современные антивирусы\n   > более-менее научились с ним дружить.\n   > Если проблема имеет место , используйте исключения. Если не помогает - отключайте антивирус совсем.\n\n3. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret.\n\n4. Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. nat не подходит\n\n5. Запустите `blockcheck\\blockcheck.cmd`.  blockcheck в начале проверяет **DNS**.\n   Если выводятся сообщения о подмене адресов, то нужно будет решить проблему с **DNS**.\n   blockcheck перейдет в этом случае на **DoH** _(DNS over HTTPS)_ и будет пытаться получить и использовать реальные IP адреса.\n   Но если вы не настроите решение этой проблемы, обход будет работать только для тех программ,\n   которые сами реализуют механизмы SecureDNS. Для других программ обход работать не будет.\n\n   > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо заменой DNS серверов\n   > от провайдера на публичные (`1.1.1.1`, `8.8.8.8`), либо в случае перехвата провайдером обращений\n   > к сторонним серверам - через специальные средства шифрования DNS запросов, такие как [dnscrypt](https://www.dnscrypt.org/), **DoT** _(DNS over TLS)_, **DoH**.\n   > В современных броузерах чаще всего DoH включен по умолчанию, но curl будет использовать обычный системный DNS.\n   > win11 поддерживает системные DoH из коробки. Они не настроены по умолчанию.\n   > В последних билдах win10 существует неофициальный обходной путь для включения DoH.\n   > Для остальных систем нужно стороннее решение, работающие по принципу DNS proxy.\n   >\n   > Тут все разжевано как и где это включается : https://hackware.ru/?p=13707\n\n6. blockcheck позволяет выявить рабочую стратегию обхода блокировок.\n   Лог скрипта будет сохранен в `blockcheck\\blockcheck.log`.\n   Запомните/перепишите найденные стратегии.\n\n   Следует понимать, что скрипт проверяет доступность только конкретного\n   домена, который вы вводите в начале, конкретной программой curl.\n   У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой.\n   Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber).\n   От этого может зависеть работоспособность стратегий.\n   Обычно остальные домены блокированы подобным образом, **но не факт**.\n   Бывают специальные блокировки. Некоторые параметры требуют тюнинга под \"общий знаменатель\".\n   В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне\n   желательно**, но это требует понимания как работают стратегии. zapret **не может\n   пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите их через пробел.\n\n   > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у\n   > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на\n   > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и\n   > включаются в работу хаотичным образом или образом, зависящим от\n   > направления (IP сервера). скрипт не всегда может выдать вам в итогах\n   > оптимальную стратегию, которую надо просто переписать в настройки. В\n   > некоторых случаях надо реально думать что происходит, анализируя результат\n   > на разных стратегиях.\n   >\n   > Далее, имея понимание что работает на http, https, quic, нужно сконструировать параметры запуска winws\n   > с использованием мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии).\n   >\n   > Прежде всего вам нужно собрать фильтр перехватываемого трафика. Это делается через параметры\n   > `--wf-l3`, `--wf-tcp`, `--wf-udp`.\n   > `--wf-l3` относится к версии ip протокола - ipv4 или ipv6.\n   > `--wf-tcp` и `--wf-udp` содержат перечень портов или диапазонов портов через запятую.\n   > \n   > Пример стандартного фильтра для перехвата http, https, quic : `--wf-tcp=80,443` `--wf-udp=443`\n   > \n   > Фильтр должен быть минимально необходимым. Перехват лишнего трафика приведет только к бессмысленному расходованию ресурсов процессора и замедлению интернета.\n   >\n   > Если кратко по мультистратегии, то обычно параметры конструируются так :\n   > ```\n   > --filter-udp=443 'параметры для quic' --new\n   > --filter-tcp=80,443 'объединенные параметры для http и https'\n   > ```\n   >\n   > Или так :\n   > ```\n   > --filter-udp=443 'параметры для quic' --new\n   > --filter-tcp=80 'параметры для http' --new\n   > --filter-tcp=443 'параметры для https'\n   > ```\n   >\n   > Если стратегии отличаются по версии ip протокола, и вы не можете их объединить, фильтр пишется так :\n   > ```\n   > --filter-l3=ipv4 --filter-udp=443 \"параметры для quic ipv4\" --new\n   > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' --new\n   > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' --new\n   > --filter-l3=ipv6 --filter-udp=443 \"параметры для quic ipv6\" --new\n   > --filter-l3=ipv6 --filter-tcp=80 \"параметры для http ipv6\" --new\n   > --filter-l3=ipv6 --filter-tcp=443 \"параметры для https ipv6\"\n   > ```\n   >\n   > Но здесь совсем _\"копи-пастный\"_ вариант.\n   > Чем больше вы обьедините стратегий и сократите их общее количество, тем будет лучше.\n   >\n   > Если вам не нужно дурение отдельных протоколов, лучше всего будет их убрать из системы перехвата трафика через\n   > параметры `--wf-*` и убрать соответствующие им профили мультистратегии.\n   > tcp 80 - http, tcp 443 - https, udp 443 - quic.\n   >\n   > Если используются методы нулевой фазы десинхронизации (`--mss`, `--wssize`, `--dpi-desync=syndata`) и фильтрация hostlist,\n   > то все параметры, относящиеся к этим методам, следует помещать в отдельные профили мультистратегии, которые получат\n   > управление до определения имени хоста. Необходимо понимать алгоритм работы мультистратегий.\n   >\n   > ```\n   > --filter-tcp=80 'параметры для http' --new\n   > --filter-tcp=443 'параметры для https' --hostlist=d:/users/user/temp/list.txt --new\n   > --filter-tcp=443 --wssize 1:6\n   > ```\n   >\n   > autohostlist профиль приоритетен, поэтому wssize нужно писать туда :\n   >\n   > ```\n   > --filter-tcp=80 'параметры для http' --new\n   > --filter-tcp=443 'параметры для https' --wssize 1:6 --hostlist-auto=d:/users/user/temp/autolist.txt\n   > ```\n   >\n   > В этих примерах wssize будет применяться всегда к порту tcp 443, а хостлист будет игнорироваться.\n   > К http применять wssize вредно и бессмысленно.\n   >\n   > Иногда требуется дописать к стратегиям свои собственные параметры.\n   > Например, нужно изменить количество повторов фейков или задать свой фейк.\n   > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`. Пользуйтесь шеллом `cygwin/cygwin-admin.cmd`.\n   >\n   > ```PKTWS_EXTRA=\"--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin\" blockcheck```\n   >\n   > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный\n   > костяк проверки, на который вы можете навешивать свои кустомизации.\n\n7. Протестируйте найденные стратегии на winws. Winws следует брать из zapret-winws.\n   Для этого откройте командную строку windows от имени администратора в директории zapret-winws.\n   Проще всего это сделать через `_CMD_ADMIN.cmd`. Он сам поднимет права и зайдет в нужную директорию.\n\n8. Обеспечьте удобную загрузку обхода блокировок.\n\n   > Есть 2 варианта. Ручной запуск через ярлык или автоматический при старте системы, вне контекста текущего пользователя.\n   > Последний вариант разделяется на запуск через планировщик задач и через службы windows.\n   >\n   > Если хотите ручной запуск, скопируйте `preset_russia.cmd` в `preset_my.cmd` (`<ваше_название>.cmd`) и адаптируйте его под ваши параметра запуска.\n   > Потом можно создать ярлык на рабочем столе на `preset_my.cmd`. Не забудьте, что требуется запускать от имени администратора.\n   >\n   > Но лучше будет сделать неинтерактивный автоматический запуск вместе с системой.\n   > В zapret-winws есть командные файлы `task_*`, предназначенные для управления задачами планировщика.\n   > Там следует поменять содержимое переменной `WINWS1` на свои параметры запуска winws. Пути с пробелами нужно экранировать кавычками с обратным слэшем : `\\\"`.\n   > После создания задач запустите их. Проверьте, что обход встает после перезагрузки windows.\n   >\n   > Аналогично настраиваются и службы windows. Смотрите `service_*.cmd`\n\n9. Если ломаются отдельные незаблокированные ресурсы, нужно пользоваться ограничивающим\n   ipset или хост листом. Читайте основной талмуд [readme.md](./readme.md) ради подробностей.\n   Но еще лучше будет подбирать такие стратегии, которые ломают минимум.\n   Есть стратегии довольно безобидные, а есть сильно ломающие, которые подходят только для точечного пробития отдельных ресурсов,\n   когда ничего лучше нет. Хорошая стратегия может сильно ломать из-за плохо подобранных ограничителей для фейков - ttl, fooling.\n\n> [!CAUTION]  \n> Это минимальная инструкция, чтобы сориентироваться с чего начать. Однако, это - не панацея.\n> В некоторых случаях вы не обойдетесь без знаний и основного \"талмуда\".\n\nПодробности и полное техническое описание расписаны в [readme.md](./readme.md)\n"
  },
  {
    "path": "docs/readme.en.md",
    "content": "# SCAMMER WARNING\n\nThis software is free and open source under [MIT license](./LICENSE.txt).\nIf anyone demands you to download this software only from their webpage, telegram channel, forces you to delete links, videos, makes copyright claims, you are dealing with scammers.\nHowever, [donations](#donations) are welcome.\n\n# Multilanguage/Мультиязычный README\n___\n[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.en.md)\n[![ru](https://img.shields.io/badge/lang-ru-green.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.md)\n\n***\n\n- [What is it for](#what-is-it-for)\n- [How it works](#how-it-works)\n- [How to put this into practice in the linux system](#how-to-put-this-into-practice-in-the-linux-system)\n- [When it will not work](#when-it-will-not-work)\n- [nfqws](#nfqws)\n  - [DPI desync attack](#dpi-desync-attack)\n  - [Fakes](#fakes)\n  - [Fake mods](#fake-mods)\n  - [TCP segmentation](#tcp-segmentation)\n  - [Sequence numbers overlap](#sequence-numbers-overlap)\n  - [IP_ID assignment](#ip_id-assignment)\n  - [ipv6 specific modes](#ipv6-specific-modes)\n  - [Original modding](#original-modding)\n  - [Duplicates](#duplicates)\n  - [Server reply reaction](#server-reply-reaction)\n  - [SYNDATA mode](#syndata-mode)\n  - [DPI desync combos](#dpi-desync-combos)\n  - [IP cache](#ip-cache)\n  - [CONNTRACK](#conntrack)\n  - [Reassemble](#reassemble)\n  - [UDP support](#udp-support)\n  - [IP fragmentation](#ip-fragmentation)\n  - [Multiple strategies](#multiple-strategies)\n  - [WIFI filtering](#wifi-filtering)\n  - [Virtual machines](#virtual-machines)\n  - [IPTABLES for nfqws](#iptables-for-nfqws)\n  - [NFTABLES for nfqws](#nftables-for-nfqws)\n  - [Flow offloading](#flow-offloading)\n  - [Server side fooling](#server-side-fooling)\n- [tpws](#tpws)\n  - [TCP segmentation in tpws](#tcp-segmentation-in-tpws)\n  - [TLSREC](#tlsrec)\n  - [MSS](#mss)\n  - [Other tamper options](#other-tamper-options)\n  - [Supplementary options](#supplementary-options)\n  - [Multiple strategies](#multiple-strategies-1)\n  - [IPTABLES for tpws](#iptables-for-tpws)\n  - [NFTABLES for tpws](#nftables-for-tpws)\n- [Ways to get a list of blocked IP](#ways-to-get-a-list-of-blocked-ip)\n- [Domain name filtering](#domain-name-filtering)\n- [**autohostlist** mode](#autohostlist-mode)\n- [Choosing parameters](#choosing-parameters)\n- [Screwing to the firewall control system or your launch system](#screwing-to-the-firewall-control-system-or-your-launch-system)\n- [Installation](#installation)\n  - [Checking ISP](#checking-isp)\n  - [desktop linux system](#desktop-linux-system)\n  - [OpenWRT](#openwrt)\n  - [Android](#android)\n  - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos)\n  - [Windows (WSL)](#windows-wsl)\n  - [Other devices](#other-devices)\n- [Donations](#donations)\n***\n\n## What is it for\n\nA stand-alone (without 3rd party servers) DPI circumvention tool.\nMay allow to bypass http(s) website blocking or speed shaping, resist signature tcp/udp protocol discovery.\n\nThe project is mainly aimed at the Russian audience.\nSome features of the project are russian reality specific (such as getting list of sites\nblocked by Roskomnadzor), but most others are common.\n\nMainly OpenWRT targeted but also supports traditional Linux, FreeBSD, OpenBSD, Windows, partially MacOS.\n\nMost features are also supported in Windows.\n\n## How it works\n\nIn the simplest case you are dealing with passive DPI. Passive DPI can read passthrough traffic,\ninject its own packets, but cannot drop packets.\n\nIf the request is prohibited the passive DPI will inject its own RST packet and optionally http redirect packet.\n\nIf fake packets from DPI are only sent to client, you can use iptables commands to drop them if you can write\ncorrect filter rules. This requires manual in-deep traffic analysis and tuning for specific ISP.\n\nThis is how we bypass the consequences of a ban trigger.\n\nIf the passive DPI sends an RST packet also to the server, there is nothing you can do about it.\nYour task is to prevent ban trigger from firing up. Iptables alone will not work.\nThis project is aimed at preventing the ban rather than eliminating its consequences.\n\nTo do that send what DPI does not expect and what breaks its algorithm of recognizing requests and blocking them.\n\nSome DPIs cannot recognize the http request if it is divided into TCP segments.\nFor example, a request of the form `GET / HTTP / 1.1 \\ r \\ nHost: kinozal.tv ......`\nwe send in 2 parts: first go `GET`, then `/ HTTP / 1.1 \\ r \\ nHost: kinozal.tv .....`.\n\nOther DPIs stumble when the `Host:` header is written in another case: for example, `host:`.\n\nSometimes work adding extra space after the method: `GET /` => `GET  /`\nor adding a dot at the end of the host name: `Host: kinozal.tv.`\n\nThere is also more advanced magic for bypassing DPI at the packet level.\n\n## How to put this into practice in the linux system\n\nIn short, the options can be classified according to the following scheme:\n\n1. Passive DPI not sending RST to the server. ISP tuned iptables commands can help.\nThis option is out of the scope of the project. If you do not allow ban trigger to fire, then you won’t have to\ndeal with its consequences.\n2. Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy.\n3. Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets.\n\nFor options 2 and 3, **tpws** and **nfqws** programs are implemented, respectively.\nYou need to run them with the necessary parameters and redirect certain traffic with iptables or nftables.\n\n## When it will not work\n\n* If DNS server returns false responses. ISP can return false IP addresses or not return anything\nwhen blocked domains are queried. If this is the case change DNS to public ones, such as 8.8.8.8 or 1.1.1.1.Sometimes ISP hijacks queries to any DNS server. Dnscrypt or dns-over-tls help.\n* If blocking is done by IP.\n* If a connection passes through a filter capable of reconstructing a TCP connection, and which\nfollows all standards. For example, we are routed to squid. Connection goes through the full OS tcpip stack. This project targets DPI only, not full OS stack and not server applications.\n\n## nfqws\n\nThis program is a packet modifier and a NFQUEUE queue handler.\nFor BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see [bsd.en.md](./bsd.en.md)).\nnfqws takes the following parameters:\n\n```\n @<config_file>                                            ; read file for options. must be the only argument. other options are ignored.\n\n --debug=0|1\n --dry-run                                                 ; verify parameters and exit with code 0 if successful\n --version                                                 ; print version and exit\n --comment                                                 ; any text (ignored)\n --qnum=<nfqueue_number>\n --daemon                                                  ; daemonize\n --pidfile=<filename>                                      ; write pid to file\n --user=<username>                                         ; drop root privs\n --uid=uid[:gid1,gid2,...]                                 ; drop root privs\n --bind-fix4                                               ; apply outgoing interface selection fix for generated ipv4 packets\n --bind-fix6                                               ; apply outgoing interface selection fix for generated ipv6 packets\n --wsize=<window_size>[:<scale_factor>]                    ; set window size. 0 = do not modify. OBSOLETE !\n --wssize=<window_size>[:<scale_factor>]                   ; set window size for server. 0 = do not modify. default scale_factor = 0.\n --wssize-cutoff=[n|d|s]N                                  ; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n --wssize-forced-cutoff=0|1                                ; 1(default)=auto cutoff wssize on known protocol\n --ctrack-timeouts=S:E:F[:U]                               ; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default 60:300:60:60\n --ctrack-disable=[0|1]                                    ; 1 or no argument disables conntrack\n --ipcache-lifetime=<int>                                  ; time in seconds to keep cached hop count and domain name (default 7200). 0 = no expiration\n --ipcache-hostname=[0|1]                                  ; 1 or no argument enables ip->hostname caching\n --hostcase                                                ; change Host: => host:\n --hostspell                                               ; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n --hostnospace                                             ; remove space after Host: and add it to User-Agent: to preserve packet size\n --domcase                                                 ; mix domain case : Host: TeSt.cOm\n --methodeol                                               ; add '\\n' before method and remove space after Host:\n --synack-split=[syn|synack|acksyn]                        ; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN\n --orig-ttl=<int>                                          ; set TTL for original packets\n --orig-ttl6=<int>                                         ; set ipv6 hop limit for original packets. by default ttl value is used\n --orig-autottl=[<delta>[:<min>[-<max>]]|-]                ; auto ttl mode for both ipv4 and ipv6. default: +5:3-64. \"0:0-0\" or \"-\" disables autottl.\n --orig-autottl6=[<delta>[:<min>[-<max>]]|-]               ; overrides --orig-autottl for ipv6 only\n --orig-tcp-flags-set=<int|0xHEX|flaglist>                 ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n --orig-tcp-flags-unset=<int|0xHEX|flaglist>               ; unset these tcp flags (flags &= ~value)\n --orig-mod-start=[n|d|s]N                                 ; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n --orig-mod-cutoff=[n|d|s]N                                ; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n --dup=<int>                                               ; duplicate original packets. send N dups before original.\n --dup-replace=[0|1]                                       ; 1 or no argument means do not send original, only dups\n --dup-ttl=<int>                                           ; set TTL for dups\n --dup-ttl6=<int>                                          ; set ipv6 hop limit for dups. by default ttl value is used\n --dup-autottl=[<delta>[:<min>[-<max>]]|-]                 ; auto ttl mode for both ipv4 and ipv6. default: -1:3-64. \"0:0-0\" or \"-\" disables autottl.\n --dup-autottl6=[<delta>[:<min>[-<max>]]|-]                ; overrides --dup-autottl for ipv6 only\n --dup-tcp-flags-set=<int|0xHEX|flaglist>                  ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n --dup-tcp-flags-unset=<int|0xHEX|flaglist>                ; unset these tcp flags (flags &= ~value)\n --dup-fooling=<mode>[,<mode>]                             ; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack hopbyhop hopbyhop2\n --dup-ts-increment=<int|0xHEX>                            ; ts fooling TSval signed increment for dup. default -600000\n --dup-badseq-increment=<int|0xHEX>                        ; badseq fooling seq signed increment for dup. default -10000\n --dup-badack-increment=<int|0xHEX>                        ; badseq fooling ackseq signed increment for dup. default -66000\n --dup-ip-id=same|zero|seq|rnd                             ; ipv4 ip_id mode for dupped packets\n --dup-start=[n|d|s]N                                      ; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n --dup-cutoff=[n|d|s]N                                     ; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n --ip-id=zero|seq|seqgroup|rnd                             ; ipv4 ip_id assignment scheme\n --dpi-desync=[<mode0>,]<mode>[,<mode2>]                   ; try to desync dpi state. modes : synack fake fakeknown rst rstack hopbyhop destopt ipfrag1 multisplit multidisorder fakedsplit hostfakesplit fakeddisorder ipfrag2 udplen tamper\n --dpi-desync-fwmark=<int|0xHEX>                           ; override fwmark for desync packet. default = 0x40000000 (1073741824)\n --dpi-desync-ttl=<int>                                    ; set ttl for desync packet\n --dpi-desync-ttl6=<int>                                   ; set ipv6 hop limit for desync packet. by default ttl value is used.\n --dpi-desync-autottl=[<delta>[:<min>[-<max>]]|-]          ; auto ttl mode for both ipv4 and ipv6. default: -1:3-20. \"0:0-0\" or \"-\" disables autottl.\n --dpi-desync-autottl6=[<delta>[:<min>[-<max>]]|-]         ; overrides --dpi-desync-autottl for ipv6 only\n --dpi-desync-tcp-flags-set=<int|0xHEX|flaglist>           ; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n --dpi-desync-tcp-flags-unset=<int|0xHEX|flaglist>         ; unset these tcp flags (flags &= ~value)\n --dpi-desync-fooling=<mode>[,<mode>]                      ; can use multiple comma separated values. modes : none md5sig ts badseq badsum datanoack hopbyhop hopbyhop2\n --dpi-desync-repeats=<N>                                  ; send every desync packet N times\n --dpi-desync-skip-nosni=0|1                               ; 1(default)=do not act on ClientHello without SNI (ESNI ?)\n --dpi-desync-split-pos=N|-N|marker+N|marker-N             ; comma separated list of split positions\n                                                           ; markers: method,host,endhost,sld,endsld,midsld,sniext\n                                                           ; full list is only used by multisplit and multidisorder\n                                                           ; fakedsplit/fakeddisorder use first l7-protocol-compatible parameter if present, first abs value otherwise\n --dpi-desync-split-seqovl=N|-N|marker+N|marker-N          ; use sequence overlap before first sent original split segment\n --dpi-desync-split-seqovl-pattern=[+ofs]@<filename>|0xHEX ; pattern for the fake part of overlap\n --dpi-desync-fakedsplit-pattern=[+ofs]@<filename>|0xHEX   ; fake pattern for fakedsplit/fakeddisorder\n --dpi-desync-fakedsplit-mod=mod[,mod]                     ; mods can be none,altorder=0|1|2|3 + 0|8|16\n --dpi-desync-hostfakesplit-midhost=marker+N|marker-N      ; additionally split real hostname at specified marker. must be within host..endhost or won't be splitted.\n --dpi-desync-hostfakesplit-mod=mod[,mod]                  ; can be none, host=<hostname>, altorder=0|1\n --dpi-desync-ipfrag-pos-tcp=<8..9216>                     ; ip frag position starting from the transport header. multiple of 8, default 32.\n --dpi-desync-ipfrag-pos-udp=<8..9216>                     ; ip frag position starting from the transport header. multiple of 8, default 8.\n --dpi-desync-ts-increment=<int|0xHEX>                     ; ts fooling TSval signed increment. default -600000\n --dpi-desync-badseq-increment=<int|0xHEX>                 ; badseq fooling seq signed increment. default -10000\n --dpi-desync-badack-increment=<int|0xHEX>                 ; badseq fooling ackseq signed increment. default -66000\n --dpi-desync-any-protocol=0|1                             ; 0(default)=desync only http and tls  1=desync any nonempty data packet\n --dpi-desync-fake-tcp-mod=mod[,mod]                       ; comma separated list of tcp fake mods. available mods : none,seq\n --dpi-desync-fake-http=[+ofs]@<filename>|0xHEX            ; file containing fake http request\n --dpi-desync-fake-tls=[+ofs]@<filename>|0xHEX|![+offset]  ; file containing fake TLS ClientHello (for https). '!' = standard fake\n --dpi-desync-fake-tls-mod=mod[,mod]                       ; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=<sni>,dupsid,padencap\n --dpi-desync-fake-unknown=[+ofs]@<filename>|0xHEX         ; file containing unknown protocol fake payload\n --dpi-desync-fake-syndata=[+ofs]@<filename>|0xHEX         ; file containing SYN data payload\n --dpi-desync-fake-quic=[+ofs]@<filename>|0xHEX            ; file containing fake QUIC Initial\n --dpi-desync-fake-wireguard=[+ofs]@<filename>|0xHEX       ; file containing fake wireguard handshake initiation\n --dpi-desync-fake-dht=[+ofs]@<filename>|0xHEX             ; file containing fake DHT (d1..e)\n --dpi-desync-fake-discord=[+ofs]@<filename>|0xHEX         ; file containing fake Discord voice connection initiation packet (IP Discovery)\n --dpi-desync-fake-stun=[+ofs]@<filename>|0xHEX            ; file containing fake STUN message\n --dpi-desync-fake-unknown-udp=[+ofs]@<filename>|0xHEX     ; file containing unknown udp protocol fake payload\n --dpi-desync-udplen-increment=<int>                       ; increase or decrease udp packet length by N bytes (default 2). negative values decrease length.\n --dpi-desync-udplen-pattern=[+ofs]@<filename>|0xHEX       ; udp tail fill pattern\n --dpi-desync-start=[n|d|s]N                               ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n --dpi-desync-cutoff=[n|d|s]N                              ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n --hostlist=<filename>                                     ; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply if not prefixed with `^`, gzip supported, multiple hostlists allowed)\n --hostlist-domains=<domain_list>                          ; comma separated fixed domain list\n --hostlist-exclude=<filename>                             ; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply if not prefixed with `^`, gzip supported, multiple hostlists allowed)\n --hostlist-exclude-domains=<domain_list>                  ; comma separated fixed domain list\n --hostlist-auto=<filename>                                ; detect DPI blocks and build hostlist automatically\n --hostlist-auto-fail-threshold=<int>                      ; how many failed attempts cause hostname to be added to auto hostlist (default : 3)\n --hostlist-auto-fail-time=<int>                           ; all failed attemps must be within these seconds (default : 60)\n --hostlist-auto-retrans-threshold=<int>                   ; how many request retransmissions cause attempt to fail (default : 3)\n --hostlist-auto-debug=<logfile>                           ; debug auto hostlist positives\n --new                                                     ; begin new strategy (new profile)\n --skip                                                    ; do not use this profile\n --filter-l3=ipv4|ipv6                                     ; L3 protocol filter. multiple comma separated values allowed.\n --filter-tcp=[~]port1[-port2]|*                           ; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. comma separated list supported.\n --filter-udp=[~]port1[-port2]|*                           ; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list supported.\n --filter-l7=<proto>                                       ; L6-L7 protocol filter. multiple comma separated values allowed. proto: http tls quic wireguard dht discord stun unknown\n --filter-ssid=ssid1[,ssid2,ssid3,...]                     ; per profile wifi SSID filter\n --ipset=<filename>                                        ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n --ipset-ip=<ip_list>                                      ; comma separated fixed subnet list\n --ipset-exclude=<filename>                                ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n --ipset-exclude-ip=<ip_list>                              ; comma separated fixed subnet list\n```\n\nMany parameters dealing with binary data support loading from hex string prefixed by \"0x\" or from a file.\nFilename can be \"as is\" or prefixed with \"@\". If there's \"+number\" prefix before \"@\" it means offset of data inside the file.\nOffset must be less that data size.\n\n### DPI desync attack\n\nThe idea is to take original message, modify it, add additional fake information in such a way that the server OS accepts original data only\nbut DPI cannot recostruct original message or sees what it cannot identify as a prohibited request.\n\nThere's a set of instruments to achieve that goal.\nIt can be fake packets that reach DPI but do not reach server or get rejected by server, TCP segmentation or IP fragmentation.\nThere're attacks based on TCP sequence numbers. Methods can be combined in many ways.\n\n### Fakes\n\nFakes are separate generated by nfqws packets carrying false information for DPI. They must either not reach the server or be rejected by it. Otherwise TCP connection or data stream would be broken. There're multiple ways to solve this task.\n\n* **md5sig** does not work on all servers. It typically works only on Linux servers. MD5 tcp option requires additional space in TCP header\n  and can cause MTU overflow during fakedsplit/fakeddisorder on low positions when multisegment query (TLS kyber) is transmitted.\n  `nfqws` cannot redistribute data between original TCP segments. The error displayed is 'message too long'.\n* **badsum** doesn't work if your device is behind NAT which does not pass invalid packets.\n  The most common Linux NAT router configuration does not pass them. Most home routers are Linux based.\n  The default sysctl configuration `net.netfilter.nf_conntrack_checksum=1` causes conntrack to verify tcp and udp checksums\n  and set INVALID state for packets with invalid checksum.\n  Typically, iptables rules include a rule for dropping packets with INVALID state in the FORWARD chain.\n  The combination of these factors does not allow badsum packets to pass through the router.\n  In openwrt mentioned sysctl is set to 0 from the box, in other routers its often left in the default \"1\" state.\n  For nfqws to work properly through the router set `net.netfilter.nf_conntrack_checksum=0` on the router.\n  System never verifies checksums of locally generated packets so nfqws will always work on the router itself.\n  If you are behind another NAT, such as a ISP, and it does not pass invalid packages, there is nothing you can do about it.\n  But usually ISPs pass badsum.\n  Some adapters/switches/drivers enable hardware filtering of rx badsum not allowing it to pass to the OS.\n  This behavior was observed on a Mediatek MT7621 based device.\n  Tried to modify mediatek ethernet driver with no luck, likely hardware enforced limitation.\n  However the device allowed to send badsum packets, problem only existed for passthrough traffic from clients.\n* **badseq** packets will be dropped by server, but DPI also can ignore them.\n  default badseq increment is set to -10000 because some DPIs drop packets outside of the small tcp window.\n  But this also can cause troubles when `--dpi-desync-any-protocol` is enabled.\n  To be 100% sure fake packet cannot fit to server tcp window consider setting badseq increment to 0x80000000\n* **TTL** looks like the best option, but it requires special tuning for each ISP. If DPI is further than local ISP websites\n  you can cut access to them. Manual IP exclude list is required. Its possible to use md5sig with ttl.\n  This way you cant hurt anything, but good chances it will help to open local ISP websites.\n  If automatic solution cannot be found then use `zapret-hosts-user-exclude.txt`.\n  Some router stock firmwares fix outgoing TTL. Without switching this option off TTL fooling will not work.\n* **hopbyhop** is ipv6 only. This fooling adds empty extension header `hop-by-hop options` or two headers in case of `hopbyhop2`.\n  Packets with two hop-by-hop headers violate RFC and discarded by all operating systems.\n  All OS accept packets with one hop-by-hop header.\n  Some ISPs/operators drop ipv6 packets with hop-by-hop options. Fakes will not be processed by the server either because\n  ISP drops them or because there are two same headers.\n  DPIs may still anaylize packets with one or two hop-by-hop headers.\n* **datanoack** sends tcp fakes without ACK flag. Servers do not accept this but DPI may accept.\n  This mode may break NAT and may not work with iptables if masquerade is used, even from the router itself.\n  Works with nftables properly. Likely requires external IP address (some ISPs pass these packets through their NAT).\n* Manipulate **tcp flags** with `--dpi-desync-tcp-flags-set` and `--dpi-desync-tcp-flags-unset`.\n  Invalid tcp flags combination may cause server to drop the packet but DPI can accept it.\n  For example, set SYN in fakes. This may not work with all servers.\n  `datanoack` can be replaced to `--dpi-desync-tcp-flags-unset=ACK`.\n* **ts** adds to TSval ts increment value (-600000 by default). Servers discard packets with TSval in some range.\n  Practical tests suggest increment between -100 and -0x80000000.\n  Timestamps are generated by client OS. In linux timestamps are enabled by default. In windows by default timestamps are disabled.\n  They can be enabled with this command : `netsh interface tcp set global timestamps=enabled` .\n  ts fooling requires that timestamps are enabled. They must be enabled on every client OS.\n  TSecr is left unmodified.\n* **autottl** tries to automatically guess hop count to the server and compute TTL by adding some delta value that can be positive or negative.\n  Positive deltas must be preceeded by unary `+` sign. Deltas without any unary sign are treated negative for old versions compatibility reasons.\n  This tech relies on well known TTL default values used by OS : 64,128,255.\n  nfqws needs first incoming packet to see it's TTL. You must redirect it too.\n  If resulting value TTL is outside the range (min,max) then its normalized to min or max.\n  If delta is negative and TTL is longer than guessed hop count or delta is positive and TTL is shorter than guessed hop count\n  then autottl fails and falls back to the fixed value.\n  This can help if multiple DPIs exists on backbone channels, not just near the ISP.\n  Can fail if inbound and outbound paths are not symmetric.\n\n\n`--dpi-desync-fooling` takes multiple comma separated values.\n\n\nMultiple parameters `--dpi-desync-fake-???` are supported except for the `--dpi-desync-fake-syndata`.\nFakes are sent in the specified order. `--dpi-desync-repeats` resends each fake.\nResulting order would be : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....`\n\n\n### FAKE mods\n\nBy default all tcp fakes are sent with the same sequence as original packet.\nThis can be changed by `--dpi-desync-fake-tcp-mod=seq`. In the latter case all fakes are sent as if they would be tcp segments of a single fake.\n\n**nfqws** has built-in TLS fake. It can be customized with `--dpi-desync-fake-tls` option.\nCustomized fake data can be anything - valid TLS Client Hello or arbitrary data.\nIt's possible to use TLS Client Hello with any fingerprint and any SNI.\n\n**nfqws** can do some modifications of valid TLS Client Hello fakes in runtime with `--dpi-desync-fake-tls-mod` option.\n\n * `none`. Do not do any mods.\n * `rnd`. Randomize `random` and `session id` fields. Applied on every request.\n * `rndsni`. Randomize SNI. If SNI >=7 symbols random SLD is applied with known TLD. Otherwise filled with random symbols. Applied only once at startup.\n * `dupsid`. Copy `session ID` from original TLS Client Hello. Takes precedence over `rnd`. Applied on every request.\n * `sni=<sni>`. Set specified SNI value. Changes TLS fake length, fixes lengths in TLS structure. Applied once at startup before `rndsni`.\n * `padencap`. Padding extension is extended by original TLS Client Hello size (including multi packet variation with kyber). Padding extension is added to the end if not present, otherwise it must be the last extension. All lengths are increased. Fake size is not changed. Can be useful if DPI does not analyze sequence numbers properly. Applied on every request.\n\nBy default if custom fake is not defined `rnd,rndsni,dupsid` mods are applied. If defined - `none`.\nThis behaviour is compatible with previous versions with addition of `dupsid`.\n\nIf multiple TLS fakes are present each one takes the last mod.\nIf a mod is specified after fake it replaces previous mod.\nThis way it's possible to use different mods for every TLS fake.\n\nIf a mod is set to non-TLS fake it causes error. Use `--dpi-desync-fake-tls-mod=none'.\n\nExample : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none'\n\n### TCP segmentation\n\n * `multisplit`. split request at specified in `--dpi-desync-split-pos` positions\n * `multidisorder`. same as `multisplit` but send in reverse order\n * `fakedsplit`. sequental one position split with fake mix\n * `hostfakesplit` (altorder=0). fake host part of the request : before host, random fake host, real host (optionally split this part), random fake host repeat, after host\n * `hostfakesplit` (altorder=1). fake host part of the request : before host, random fake host, after host, real host (optionally split this part)\n * `fakedsplit`. reverse one position split with fake mix\n\n`--dpi-desync-fakedsplit-mod=altorder=N` specifies number which influence to the presence of individual fakes in `fakedsplit`/`fakeddisorder`.\n\n`fakedsplit` TCP segments of multi-packet messages with split pos :\n\n * `altorder=0`. fake 1st segment, 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment\n * `altorder=1`. 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment\n * `altorder=2`. 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment\n * `altorder=3`. 1st segment, fake 2nd segment, 2nd segment\n\n`fakeddisorder` TCP segments of multi-packet messages with split pos :\n\n * `altorder=0`. fake 2nd segment, 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment\n * `altorder=1`. 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment\n * `altorder=2`. 2nd segment, fake 1st segment, 1st segment, fake 1st segment\n * `altorder=3`. 1st segment, fake 1st segment, 1st segment\n\n`fakedsplit`/`fakeddisorder` TCP segments of multi-packet messages without split pos :\n\n * `altorder=0`. fake, original, fake\n * `altorder=8`. original, fake\n * `altorder=16`. original\n\nResulting `altorder=N` is the sum of two `altorder` parts mentioned above.\n\n`--dpi-desync-fakedsplit-pattern` defines data payload of fakes in `fakedsplit`/`fakeddisorder`. By default pattern is simple `0x00`.\nOffset of split part + offset of current packet in multi-packet message define offset in the pattern.\n\nPositions are defined by markers.\n\n* **Absolute positive marker** - numeric offset inside one packet or group of packets starting from the start\n* **Absolute negative marker** - numeric offset inside one packet or group of packets starting from the next byte after the end\n* **Relative marker** - positive or negative offset relative to a logical position within a packet or group of packets\n\nRelative positions :\n\n* **method** - HTTP method start ('GET', 'POST', 'HEAD', ...). Method is usually always at position 0 but can shift because of `--methodeol` fooling. If fooled position can become 1 or 2.\n* **host** - hostname start in a known protocol (http, TLS)\n* **endhost** - the byte next to the last hostname's byte\n* **sld** - second level domain start in the hostname\n* **endsld** - the byte next to the last SLD byte\n* **midsld** - middle of SLD\n* **sniext** - start of the data field in the SNI TLS extension. Any extension has 2-byte type and length fields followed by data field.\n\nMarker list example : `100,midsld,sniext+1,endhost-2,-10`.\n\nWhen splitting all markers are resolved to absolute offsets. If a relative position is absent in the current protocol its dropped. Then all resolved offsets are normalized to the current packet offset in multi packet group (multi-packet TLS with kyber, for example). Positions outside of the current packet are dropped. Remaining positions are sorted and deduplicated.\n\nIn `multisplit`or `multidisorder` case split is cancelled if no position remained.\n\n`fakedsplit` и `fakeddisorder` use only one split position. It's searched from the  `--dpi-desync-split-pos` list by a special alorightm.\nFirst relative markers are searched. If no suitable found absolute markers are searched. If nothing found position 1 is used.\n\nFor example, `--dpi-desync-split-pos=method+2,midsld,5` means `method+2` for http, `midsld` for TLS and 5 for others.\n\n`--dpi-desync-fakedsplit-mod=altorder=N` switches `fakedsplit` to alternate segment ordering.\n\n`hostfakesplit` only fakes hostname part of the request making it hard to destinguish between real and fake host names.\nIt works for tcp protocols with host : TLS and HTTP. Real hostname can be additionally split using `--dpi-desync-hostfakesplit-midhost` marker.\nFor example, `--dpi-desync-hostfakesplit-midhost=midsld`. Position must be within host range or split won't happen.\nMulti-packet queries are supported if hostname part is not already split. If it is fooling is cancelled.\n\nBy default fake host names are generated randomly on the fly using `[0-9a-z]` pattern. If host length is >= 7 dot is placed to simulate 3-char TLD and last 3 chars are replaces with a random known 3-char TLD.\nIt's possible to set fake host template : `--dpi-desync-hostfakesplit-mod=host=<hostname>`.\nTemplate hostname will be expanded to the left to original hostname size with random characters from `[0-9a-z]` pattern : \"www.networksolutions.com\" -> \"h8xmdba4tv7a8.google.com\".\nIf original hostname size is less than template size it will be cut : \"habr.com\" -> \"ogle.com\".\nIf original hostname size is larger than template size by one, dot will be appended to the left : \"www.xxx.com\" => \".google.com\"..\nThat's why it's a good idea to use short hostnames in template : \"ya.ru\", \"vk.com\", \"x.com\".\n\n`--dpi-desync-hostfakesplit-mod=altorder=1` switches `hostfakesplit` to alternate segment ordering. `altorder=1` sends the whole request with faked host sequentally, then real host segment.\n\n### Sequence numbers overlap\n\n`seqovl` adds to one of the original segment `seqovl` bytes to the beginning and decreases sequence number. For `split` - to the first segment, for `disorder` - to the beginning of the penultimate segment sent (second in the original sequence).\n\nIn `split` mode this creates partially in-window packet. OS receives only in-window part.\nIn `disorder` mode OS receives fake and real part of the second segment but does not pass received data to the socket until first segment is received. First segment overwrites fake part of the second segment. Then OS passes original data to the socket.\nAll unix OS except Solaris preserve last received data. This is not the case for Windows servers and `disorder` with `seqovl` will not work.\nDisorder requires `seqovl` to be less than split position. Otherwise `seqovl` is not possible and will be cancelled.\nMethod allows to avoid separate fakes. Fakes and real data are mixed.\n\n### IP_ID assignment\n\nSome DPIs check ipv4 ip_id value. OS normally increment ip_id value every packet. Some anti-DPI software may send fakes or tcp segments with the same ip_id as original causing block trigger.\n\nSequental ip_id will be broken in case of sending fakes or additional tcp segments because OS knows nothing about them. But still there are options how to assignt ip_id to generated packets.\n\n`ip-id` parameter sets ip_id assignment scheme for a desync profile :\n\n * `seq` (default) : increment ip_id for every next packet. in `multidisorder` case increase ip_id for the number of tcp segments then decrease by 1 every packet.\n * `seqgroup` : same as `seq` but send fake replacements with the same ip_id as original parts. related only to fake tcp segments with the same size and same sequence as originals.\n * `rnd` : assign random ip_id\n * `zero` : always set zero. Linux and BSD will send zero, Windows will replace zero with it's own counter.\n\nipv6 header lacks ip_id field, `ip-id` parameter ignored for ipv6.\n\n### ipv6 specific modes\n\n`hopbyhop`, `destopt` and `ipfrag1` desync modes (they're not the same as `hopbyhop` fooling !) are ipv6 only. One `hop-by-hop`,\n`destination options` or `fragment` header is added to all desynced packets.\nExtra header increases packet size and can't be applied to the maximum size packets.\nIf it's not possible to send modified packet original one will be sent.\nThe idea here is that DPI sees 0 in the next header field of the main ipv6 header and does not\nwalk through the extension header chain until transport header is found.\n`hopbyhop`, `destopt`, `ipfrag1` modes can be used with any second phase mode except `ipfrag1+ipfrag2`.\nFor example, `hopbyhop,multisplit` means split original tcp packet into several pieces and add hop-by-hop header to each.\nWith `hopbyhop,ipfrag2` header sequence will be : `ipv6,hop-by-hop,fragment,tcp/udp`.\n`ipfrag1` mode may not always work without special preparations. See \"IP Fragmentation\" notices.\n\n### Original modding\n\nParameters `--orig-ttl` and `--orig-ttl6` allow to set TTL on original packets.\nAll further packet manipulations, e.g. segmentation, take modded original as data source and inherit modded TTL.\n\n`--orig-autottl` and `--orig-autottl6` work the same way as `dpi-desync-autottl`, but on original packets.\nDelta should have unary `+` sign to produce TTL longer than guessed hop count. Otherwise nothing will reach the server.\nExample : `--orig-autottl=+5:3-64`.\n\n`--orig-mod-start` and `--orig-mod-cutoff` specify start and end conditions for original modding. The work the same way as\n`--dpi-desync-start` and `--dpi-desync-cutoff`.\n\nThis function can be useful when DPI hunts for fakes and blocks suspicious connections.\nDPI can compute TTL difference between packets and fire block trigger if it exceedes some threshold.\n\n### Duplicates\n\nDuplicates are copies of original packets which are sent before them. Duplicates are enabled by `--dup=N`, where N is dup count.\n`--dup-replace` disables sending of original.\n\nDups are sent only when original would also be sent without reconstruction.\nFor example, if TCP segmentation happens, original is actually dropped and is being replaced by artificially constructed new packets.\nDups are not sent in this case.\n\nAll dup fooling modes are available : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`.\nYou decide whether these packets need to reach the server and in what form, according to the intended strategy.\n\n`--dup-autottl` and `--dup-autottl6` work the same way as `dpi-desync-autottl`.\nDelta can be preceeded by unary `+` or `-` sign.\nExample : `--dup-autottl=-2:3-64`.\n\n`--dup-start` and `--dup-cutoff` specify start and end conditions for dupping. The work the same way as\n`--dpi-desync-start` and `--dpi-desync-cutoff`.\n\nThis function can help if DPI compares some characteristics of fake and original packets and block connection if they differ some way.\nFooled duplicates can convince DPI that the whole session has an anomaly.\nFor example, all connection is protected by MD5 signature, not individual packets.\n\n### Server reply reaction\n\nThere are DPIs that analyze responses from the server, particularly the certificate from the ServerHello that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server with ACK sequence number corresponding to the length of the ClientHello+1.\nIn the disorder variant, a selective acknowledgement (SACK) usually arrives first, then a full ACK.\nIf, instead of ACK or SACK, there is an RST packet with minimal delay, DPI cuts you off at the request stage.\nIf the RST is after a full ACK after a delay of about ping to the server, then probably DPI acts on the server response. The DPI may be satisfied with good ClientHello and stop monitoring the TCP session without checking ServerHello. Then you were lucky. 'fake' option could work.\nIf it does not stop monitoring and persistently checks the ServerHello, `--wssize` parameter may help (see [CONNTRACK](#conntrack)).\nOtherwise it is hardly possible to overcome this without the help of the server.\nThe best solution is to enable TLS 1.3 support on the server. TLS 1.3 sends the server certificate in encrypted form.\nThis is recommendation to all admins of blocked sites. Enable TLS 1.3. You will give more opportunities to overcome DPI.\n\n### SYNDATA mode\n\nNormally SYNs come without data payload. If it's present it's ignored by all major OS if TCP fast open (TFO) is not involved, but may not be ignored by DPI.\nOriginal connections with TFO are not touched because otherwise they would be definitely broken.\nWithout extra parameter payload is 16 zero bytes.\n\n### DPI desync combos\n\n`--dpi-desync` takes up to 3 comma separated modes.\n\n* 0 phase modes work during the connection establishement : `synack`, `syndata` `--wsize`, `--wssize`. [hostlist](#multiple-strategies) filters are applicable only if [`--ipcache-hostname`](#ip-cache) is enabled.\n* In the 1st phase fakes are sent before original data  : `fake`, `rst`, `rstack`.\n* In the 2nd phase original data is sent in a modified way (for example `fakedsplit` or `ipfrag2`).\n\nModes must be specified in phase ascending order.\n\n### IP cache\n\n`ipcache` is the structure in the process memory that stores some information by IP address and interface name key.\nThis information can be used as missing data. Currently it's used in the following cases :\n\n1. IP,interface => hop count . This is used to apply autottl at 0 phase since the first session packet. If the record is absent autottl will not be applied immediately. Second time it will be applied immediately using cached hop count.\n\n2. IP => hostname . Hostname is cached to be used in 0 phase strategies. Mode is disabled by default and can be enabled by `ipcache-hostname` parameter.\nThis tech is experimental. There's no one-to-one correspondence between IP and domain name. Multiple domains can resolve to the same IP.\nIf collision happens hostname is replaced. On CDNs a domain can resolve to different IPs over time. `--ipcache-lifetime` limits how long cached record is valid. It's 2 hours by default.\nBe prepared for unexpected results that can be explained only by reading debug logs.\n\nSIGUSR2 forces process to output it's ipcache to stdout.\n\n### CONNTRACK\n\nnfqws is equipped with minimalistic connection tracking system (conntrack)\nIt's used if some specific DPI circumvention methods are involved and helps to reassemble multi-packet requests.\n\nConntrack can track connection phase : SYN,ESTABLISHED,FIN , packet counts in both directions , sequence numbers.\n\nIt can be fed with unidirectional or bidirectional packets.\n\nA SYN or SYN,ACK packet creates an entry in the conntrack table.\n\nThat's why iptables redirection must start with the first packet although can be cut later using connbytes filter.\n\nFirst seen UDP packet creates UDP stream. It defines the stream direction. Then all packets with the same\n`src_ip,src_port,dst_ip,dst_port` are considered to belong to the same UDP stream. UDP stream exists till inactivity timeout.\n\nA connection is deleted from the table as soon as it's no more required to satisfy nfqws needs or when a timeout happens.\n\nThere're 3 timeouts for each connection state. They can be changed in `--ctrack-timeouts` parameter.\n\n`--wssize` changes tcp window size for the server to force it to send split replies.\nIn order for this to affect all server operating systems, it is necessary to change the window size in each outgoing packet\nbefore sending the message, the answer to which must be split (for example, TLS ClientHello).\nThat's why conntrack is required to know when to stop applying low window size.\n\nIf you do not stop and set the low wssize all the time, the speed will drop catastrophically.\nLinux can overcome this using connbytes filter but other OS may not include similar filter.\n\nIn http(s) case wssize stops after the first http request or TLS ClientHello unless `--wssize-forced-cutoff=0` is specified.\n\nIf you deal with a non-http(s) protocol you need `--wssize-cutoff`. It sets the threshold where wssize stops.\n\nThreshold can be prefixed with 'n' (packet number starting from 1), 'd' (data packet number starting from 1), \n's' (relative sequence number - sent by client bytes + 1).\n\nIf a http request or TLS ClientHello packet is detected wssize stops immediately ignoring wssize-cutoff option.\nThis action is called \"forced wssize cutoff\" and can disabled using `--wssize-forced-cutoff=0`.\n\nIf your protocol is prone to long inactivity, you should increase ESTABLISHED phase timeout using `--ctrack-timeouts`.\n\nDefault timeout is low - only 5 mins.\n\nDon't forget that nfqws feeds with redirected packets. If you have limited redirection with connbytes\nESTABLISHED entries can remain in the table until dropped by timeout.\n\nTo diagnose conntrack state send SIGUSR1 signal to nfqws : `killall -SIGUSR1 nfqws`.\n\nnfqws will dump current conntrack table to stdout.\n\nTypically, in a SYN packet, client sends TCP extension **scaling factor** in addition to window size.\nscaling factor is the power of two by which the window size is multiplied : 0=>1, 1=>2, 2=>4, ..., 8=>256, ...\n\nThe wssize parameter specifies the scaling factor after a colon.\n\nScaling factor can only decrease, increase is blocked to prevent the server from exceeding client's window size.\n\nTo force a TLS server to fragment ServerHello message to avoid hostname detection on DPI use `--wssize=1:6`\n\nThe main rule is to set scale_factor as much as possible so that after recovery the final window size\nbecomes the possible maximum. If you set `scale_factor` 64:0, it will be very slow.\n\nOn the other hand, the server response must not be large enough for the DPI to find what it is looking for.\n\n`--wssize` is not applied in desync profiles with hostlist filter because it works since the connection initiation when it's not yet possible\nto extract the host name. But it works with auto hostlist profiles.\n\n`--wssize` may slow down sites and/or increase response time. It's desired to use another methods if possible.\n\n`--dpi-desync-cutoff` allows you to set the threshold at which it stops applying dpi-desync.\nCan be prefixed with 'n', 'd', 's' symbol the same way as `--wssize-cutoff`.\nUseful with `--dpi-desync-any-protocol=1`.\nIf the connection falls out of the conntrack and `--dpi-desync-cutoff` is set, `dpi desync` will not be applied.\n\nSet conntrack timeouts appropriately.\n\n### Reassemble\n\nnfqws supports reassemble of TLS and QUIC ClientHello.\nThey can consist of multiple packets if kyber crypto is used (default starting from chromium 124).\nChromium randomizes TLS fingerprint. SNI can be in any packet or in-between.\nStateful DPIs usually reassemble all packets in the request then apply block decision.\nIf nfqws receives a partial ClientHello it begins reassemble session. Packets are delayed until it's finished.\nThen they go through desync using fully reassembled message.\nOn any error reassemble is cancelled and all delayed packets are sent immediately without desync.\n\nThere is special support for all tcp split options for multi segment TLS. Split position is treated as message-oriented, not packet oriented. For example, if your client sends TLS ClientHello with size 2000 and SNI is at 1700, desync mode is `fake,multisplit`, then fake is sent first, then original first segment and the last splitted segment. 3 segments total.\n\n### UDP support\n\nUDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level.\nOnly desync modes `fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1`,`ipfrag2`,`udplen` and `tamper` are applicable.\n`fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1` are 1st phase modes, others - 2nd phase.\nAs always it's possible to combine one mode from 1st phase with one mode from 2nd phase but not possible to mix same phase modes.\n\n`udplen` increases udp payload size by `--dpi-desync-udplen-increment` bytes. Padding is filled with zeroes by default but can be overriden with a pattern.\nThis option can resist DPIs that track outgoing UDP packet sizes.\nRequires that application protocol does not depend on udp payload size.\n\nQUIC initial packets are recognized. Decryption and hostname extraction is supported so `--hostlist` parameter will work.\nWireguard handshake initiation, DHT, STUN and [Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery) packets are also recognized.\nFor other protocols desync use `--dpi-desync-any-protocol`.\n\nConntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`.\n\nFake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently.\nBy default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`.\n\n### IP fragmentation\n\nModern network can be very hostile to IP fragmentation. Fragmented packets are often not delivered or refragmented/reassembled on the way. \nFrag position is set independently for tcp and udp. By default 24 and 8, must be multiple of 8.\nOffset starts from the transport header.\n\ntcp fragments are almost always filtered. It's absolutely not suitable for arbitrary websites.\nudp fragments have good chances to survive but not everywhere. It's good to assume success rate on QUIC between 50..75%.\nLikely more with your VPS. Sometimes filtered by DDoS protection.\n\nThere are important nuances when working with fragments in Linux.\n\nipv4 : Linux allows to send ipv4 fragments but standard firewall rules in OUTPUT chain can cause raw send to fail.\n\nipv6 : There's no way for an application to reliably send fragments without defragmentation by conntrack.\nSometimes it works, sometimes system defragments packets.\nLooks like kernels <4.16 have no simple way to solve this problem. Unloading of `nf_conntrack` module\nand its dependency `nf_defrag_ipv6` helps but this severely impacts functionality.\nKernels 4.16+ exclude from defragmentation untracked packets.\nSee `blockcheck.sh` code for example.\n\nSometimes it's required to load `ip6table_raw` kernel module with parameter `raw_before_defrag=1`.\nIn openwrt module parameters are specified after module names separated by space in files located in `/etc/modules.d`.\n\nIn traditional linux check whether `iptables-legacy` or `iptables-nft` is used. If legacy create the file\n`/etc/modprobe.d/ip6table_raw.conf` with the following content :\n```\noptions ip6table_raw raw_before_defrag=1\n```\nIn some linux distros its possible to change current ip6tables using this command: `update-alternatives --config ip6tables`.\nIf you want to stay with `nftables-nft` you need to patch and recompile your version.\nIn `nft.c` find :\n```\n\t\t\t{\n\t\t\t\t.name\t= \"PREROUTING\",\n\t\t\t\t.type\t= \"filter\",\n\t\t\t\t.prio\t= -300,\t/* NF_IP_PRI_RAW */\n\t\t\t\t.hook\t= NF_INET_PRE_ROUTING,\n\t\t\t},\n\t\t\t{\n\t\t\t\t.name\t= \"OUTPUT\",\n\t\t\t\t.type\t= \"filter\",\n\t\t\t\t.prio\t= -300,\t/* NF_IP_PRI_RAW */\n\t\t\t\t.hook\t= NF_INET_LOCAL_OUT,\n\t\t\t},\n```\nand replace -300 to -450.\n\nIt must be done manually, `blockcheck.sh` cannot auto fix this for you.\n\nOr just move to `nftables`. You can create hooks with any priority there.\n\nLooks like there's no way to do ipfrag using iptables for forwarded traffic if NAT is present.\n`MASQUERADE` is terminating target, after it `NFQUEUE` does not work.\nnfqws sees packets with internal network source address. If fragmented NAT does not process them.\nThis results in attempt to send packets to internet with internal IP address.\nYou need to use nftables instead with hook priority 101 or higher.\n\n### Multiple strategies\n\n**nfqws** can apply different strategies to different requests. It's done with multiple desync profiles.\nProfiles are delimited by the `--new` parameter. First profile is created automatically and does not require `--new`.\nEach profile has a filter. By default it's empty and profile matches any packet.\nFilter can have hard parameters : ip version, ipset and tcp/udp port range.\nHard parameters are always identified unambiguously even on zero-phase when hostname and L7 are unknown yet.\nHostlists can also act as a filter. They can be combined with hard parameters.\nWhen a packet comes profiles are matched from the first to the last until first filter condition match.\nHard filter is matched first. If it does not match verification goes to the next profile.\nIf a profile matches hard filter , L7 filter and has autohostlist it's selected immediately.\nIf a profile matches hard filter , L7 filter and has normal hostlist(s) and hostname is unknown yet verification goes to the next profile.\nOtherwise profile hostlist(s) are checked for the hostname. If it matches profile is selected.\nOtherwise verification goes to the next profile.\n\nIt's possible that before knowing L7 and hostname connection is served by one profile and after\nthis information is revealed it's switched to another profile.\nIf you use 0-phase desync methods think carefully what can happen during strategy switch.\nUse `--debug` logging to understand better what **nfqws** does.\n\nProfiles are numbered from 1 to N. There's last empty profile in the chain numbered 0.\nIt's used when no filter matched.\n\nIMPORTANT : multiple strategies exist only for the case when it's not possible to combine all to one strategy.\nCopy-pasting blockcheck results of different websites to multiple strategies lead to the mess.\nThis way you may never unblock all resources and only confuse yourself.\n\nIMPORTANT : user-mode ipset implementation was not designed as a kernel version replacement. Kernel version is much more effective.\nIt's for the systems that lack ipset support : Windows and Linux without nftables and ipset kernel modules (Android, for example).\n\n### WIFI filtering\n\nWifi interface name is not related to connected SSID.\nIt's possible to connect interface to different SSIDs.\nThey may require different strategies. How to solve this problem ?\n\nYou can run and stop nfqws instances manually. But you can also automate this.\nWindows version `winws` has global filter `--ssid-filter`.\nIt connects or disconnects `winws` depending on connected SSIDs.\nRouting is not take into account. This approach is possible because windivert can have multiple handlers with intersecting filter.\nIf SSID changes one `winws` connects and others disconnect.\n\n`winws` solution is hard to implement in Linux because one nfqueue can have only one handler and it's impossible to pass same traffic to multiple queues.\nOne must connect when others have already disconnected.\nInstead, `nfqws` has per-profile `--filter-ssid` parameter. Like `--ssid-filter` it takes comma separated SSID list.\n`nfqws` maintains ifname->SSID list which is updated not faster than once a second.\nWhen a packet comes incoming or outgoing interface name is matched to the SSID and then used in profile selection algorithm.\n\nSSID info is taken the same way as `iw dev <ifname> info` does (nl80211).\nUnfortunately it's broken since kernel 5.19 and still unfixed in 6.14.\nIn the latter case `iwgetid` way is used (wireless extensions).\nWireless extensions are deprecated. Some kernels can be built without wext support.\nBefore using `--filter-ssid` check that any of the mentioned commands can return SSID.\n\n### Virtual machines\n\nMost of nfqws packet magic does not work from VMs powered by virtualbox and vmware when network is NATed.\nHypervisor forcibly changes TTL and does not forward fake packets.\nSet up bridge networking.\n\n### IPTABLES for nfqws\n\n> [!CAUTION]\n> Starting from Linux kernel 6.17 there's CONFIG_NETFILTER_XTABLES_LEGACY parameter which is not set by default. Many distributions will likely not turn it on making iptables-legacy non working. This is part of iptables deprecation. However iptables-nft still works because their backend is based on nftables.\n\nThis is the common way to redirect some traffic to nfqws :\n\n```\niptables -t mangle -I POSTROUTING -o <wan_interface> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\nThis variant works if DPI is stateful and does not track all packets separately in search for \"bad requests\". If it's stateless you have to redirect all outgoing plain http packets.\n\n```\niptables -t mangle -I POSTROUTING -o <wan_interface> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I POSTROUTING -o <wan_interface> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\nmark bit is used to prevent loops. **nfqws** sets this mark in each injected packet.\nIt's also necessary for correct injected packet ordering and for deadlock prevention.\n\n`autottl` requires incoming `SYN,ACK` packet or first reply packet (it's usually the same). \n\n`autohostlist` needs incoming `RST` and `http redirect`.\n\nIt's possible to build tcp flags and u32 based filter but connbytes is easier.\n\n`\niptables -t mangle -I PREROUTING -i <wan_interface> -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n`\n\nFor QUIC :\n\n```\niptables -t mangle -I POSTROUTING -o <wan_interface> -p udp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\n6 packets cover possible retransmissions of quic initials and feed `autohostlist` mode.\n\n### NFTABLES for nfqws\n\nThis is the start configuration :\n\n```\nIFACE_WAN=wan\n\nnft create table inet ztest\n\nnft add chain inet ztest post \"{type filter hook postrouting priority mangle;}\"\nnft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport \"{80,443}\" ct original packets 1-6 queue num 200 bypass\nnft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass\n\n# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI\nsysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 \nnft add chain inet ztest pre \"{type filter hook prerouting priority filter;}\"\nnft add rule inet ztest pre iifname $IFACE_WAN tcp sport \"{80,443}\" ct reply packets 1-3 queue num 200 bypass\n```\n\nTo engage `datanoack` or `ipfrag` for passthrough traffic special POSTNAT configuration is required. Generated packets must be marked as **notrack** in the early stage to avoid being invalidated by linux conntrack.\n\n```\nIFACE_WAN=wan\n\nnft create table inet ztest\n\nnft add chain inet ztest postnat \"{type filter hook postrouting priority srcnat+1;}\"\nnft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport \"{80,443}\" ct original packets 1-6 queue num 200 bypass\nnft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass\n\nnft add chain inet ztest predefrag \"{type filter hook output priority -401;}\"\nnft add rule inet ztest predefrag \"mark & 0x40000000 != 0x00000000 notrack\"\n```\n\nDelete nftable :\n\n```\nnft delete table inet ztest\n```\n\n### Flow offloading\n\nIf your device supports flow offloading (hardware acceleration) iptables and nftables may not work. With offloading enabled packets bypass standard netfilter flow. It must be either disabled or selectively controlled.\n\nNewer linux kernels have software flow offloading (SFO). The story is the same with SFO.\n\nIn `iptables` flow offloading is controlled by openwrt proprietary extension `FLOWOFFLOAD`. Newer `nftables` implement built-in offloading support.\n\nFlow offloading does not interfere with **tpws** and `OUTPUT` traffic. It only breaks nfqws that fools `FORWARD` traffic.\n\n### Server side fooling\n\nIt's also possible.\nnfqws is intended for client side attacks. That's why it recognizes direct and reply traffic based on role in connection establishement.\nIf it sees SYN then source IP is client IP. If it sees SYN,ACK then source ip is server IP.\nFor UDP client address is considered as source IP of the first seen packet of src_ip,src_port,dst_ip,dst_port tuple.\n\nThis does not work correctly on the server side. Client traffic is reply traffic, server traffic is direct traffic.\n\n`--wsize` works in any case. It can be used on both client and server.\nOther techs work only if nfqws treats traffic as direct traffic.\nTo apply them to server originated traffic disable conntrack by `--ctrack-disable` parameter.\nIf a packet is not found in conntrack it's treated as direct and techs like `multidisorder` will be applied.\n\nMost of the protocols will not be recognized because protocol recognition system only reacts to client packets.\nTo make things working use `--dpi-desync-any-protocol` with connbytes or packet payload limiter.\nstart/cutoff are unavailable because they are conntrack based.\n\n`--synack-split` removes standard SYN,ACK packet and replaces it with one SYN packet, SYN then ACK separate packets or ACK then SYN separate packets.\nClient sends SYN,ACK in reply which usually only server does.\nThis makes some DPI's to treat connection establishement roles wrong. They stop to block.\nSee [split handshake](https://nmap.org/misc/split-handshake.pdf).\n\nOn server side traffic should be redirected to nfqws using source port numbers and original connbytes direction.\n\n\n## tpws\n\ntpws is transparent proxy.\n\n```\n @<config_file>                          ; read file for options. must be the only argument. other options are ignored.\n\n --debug=0|1|2|syslog|@<filename>        ; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n --debug-level=0|1|2                     ; specify debug level for syslog and @<filename>\n --dry-run                               ; verify parameters and exit with code 0 if successful\n --version                                      ; print version and exit\n --bind-addr=<v4_addr>|<v6_addr>         ; for v6 link locals append %interface_name : fe80::1%br-lan\n --bind-iface4=<interface_name>          ; bind to the first ipv4 addr of interface\n --bind-iface6=<interface_name>          ; bind to the first ipv6 addr of interface\n --bind-linklocal=no|unwanted|prefer|force\n                                         ; no : bind only to global ipv6\n                                         ; unwanted (default) : prefer global address, then LL\n                                         ; prefer : prefer LL, then global\n                                         ; force : LL only\n --bind-wait-ifup=<sec>                  ; wait for interface to appear and up\n --bind-wait-ip=<sec>                    ; after ifup wait for ip address to appear up to N seconds\n --bind-wait-ip-linklocal=<sec>          ; accept only link locals first N seconds then any\n --bind-wait-only                        ; wait for bind conditions satisfaction then exit. return code 0 if success.\n --connect-bind-addr=<v4_addr>|<v6_addr> ; address for outbound connections. for v6 link locals append %%interface_name\n --port=<port>                           ; port number to listen on\n --socks                                 ; implement socks4/5 proxy instead of transparent proxy\n --local-rcvbuf=<bytes>                  ; SO_RCVBUF for local legs\n --local-sndbuf=<bytes>                  ; SO_SNDBUF for local legs\n --remote-rcvbuf=<bytes>                 ; SO_RCVBUF for remote legs\n --remote-sndbuf=<bytes>                 ; SO_SNDBUF for remote legs\n --nosplice                              ; do not use splice to transfer data between sockets\n --skip-nodelay                          ; do not set TCP_NODELAY for outgoing connections. incompatible with split.\n --local-tcp-user-timeout=<seconds>      ; set tcp user timeout for local leg (default : 10, 0 = system default)\n --remote-tcp-user-timeout=<seconds>     ; set tcp user timeout for remote leg (default : 20, 0 = system default)\n --fix-seg=<int>                         ; recover failed TCP segmentation at the cost of slowdown. wait up to N msec.\n --ipcache-lifetime=<int>                ; time in seconds to keep cached domain name (default 7200). 0 = no expiration\n --ipcache-hostname=[0|1]                ; 1 or no argument enables ip->hostname caching\n --no-resolve                            ; disable socks5 remote dns\n --resolver-threads=<int>                ; number of resolver worker threads\n --maxconn=<max_connections>             ; max number of local legs\n --maxfiles=<max_open_files>             ; max file descriptors (setrlimit). min requirement is (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode.\n                                         ; its worth to make a reserve with 1.5 multiplier. by default maxfiles is (X*connections)*1.5+16\n --max-orphan-time=<sec>                 ; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n\n --new                                   ; begin new strategy (new profile)\n --skip                                  ; do not use this profile\n --filter-l3=ipv4|ipv6                   ; L3 protocol filter. multiple comma separated values allowed.\n --filter-tcp=[~]port1[-port2]|*         ; TCP port filter. ~ means negation. comma separated list supported.\n --filter-l7=[http|tls|unknown]          ; L6-L7 protocol filter. multiple comma separated values allowed.\n --ipset=<filename>                      ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n --ipset-ip=<ip_list>                    ; comma separated fixed subnet list\n --ipset-exclude=<filename>              ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n --ipset-exclude-ip=<ip_list>            ; comma separated fixed subnet list\n\n --hostlist=<filename>                   ; only act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed)\n --hostlist-domains=<domain_list>        ; comma separated fixed domain list\n --hostlist-exclude=<filename>           ; do not act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed)\n --hostlist-exclude-domains=<domain_list> ; comma separated fixed domain list\n --hostlist-auto=<filename>              ; detect DPI blocks and build hostlist automatically\n --hostlist-auto-fail-threshold=<int>    ; how many failed attempts cause hostname to be added to auto hostlist (default : 3)\n --hostlist-auto-fail-time=<int>         ; all failed attemps must be within these seconds (default : 60)\n --hostlist-auto-debug=<logfile>         ; debug auto hostlist positives\n\n --split-pos=N|-N|marker+N|marker-N      ; comma separated list of split positions\n                                         ; markers: method,host,endhost,sld,endsld,midsld,sniext  \n --split-any-protocol                    ; split not only http and TLS\n --disorder[=http|tls]                   ; when splitting simulate sending second fragment first\n --oob[=http|tls]                        ; when splitting send out of band byte. default is HEX 0x00.\n --oob-data=<char>|0xHEX                 ; override default 0x00 OOB byte.\n --hostcase                              ; change Host: => host:\n --hostspell                             ; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n --hostdot                               ; add \".\" after Host: name\n --hosttab                               ; add tab after Host: name\n --hostnospace                           ; remove space after Host:\n --hostpad=<bytes>                       ; add dummy padding headers before Host:\n --domcase                               ; mix domain case after Host: like this : TeSt.cOm\n --methodspace                           ; add extra space after method\n --methodeol                             ; add end-of-line before method\n --unixeol                               ; replace 0D0A to 0A\n --tlsrec=N|-N|marker+N|marker-N         ; make 2 TLS records. split at specified logical part. don't split if SNI is not present.\n --tlsrec-pos=<pos>                      ; make 2 TLS records. split at specified pos\n --mss=<int>                             ; set client MSS. forces server to split messages but significantly decreases speed !\n --tamper-start=[n]<pos>                 ; start tampering only from specified outbound stream position. byte pos or block number ('n'). default is 0.\n --tamper-cutoff=[n]<pos>                ; do not tamper anymore after specified outbound stream position. byte pos or block number ('n'). default is unlimited.\n --daemon                                ; daemonize\n --pidfile=<filename>                    ; write pid to file\n --user=<username>                       ; drop root privs\n --uid=uid[:gid1,gid2,...]               ; drop root privs\n```\n\n### TCP segmentation in tpws\n\n**tpws** like **nfqws** supports multiple splits. Split [markers](#tcp-segmentation) are specified in `--split-pos` parameter.\n\nOn the socket level there's no guaranteed way to force OS to send pieces of data in separate packets. OS has a send buffer for each socket. If `TCP_NODELAY` socket option is enabled and send buffer is empty OS will likely send data immediately. If send buffer is not empty OS will coalesce it with new data and send in one packet if possible.\n\nIn practice outside of massive transmissions it's usually enough to enable `TCP_NODELAY` and use separate `send()` calls to force custom TCP segmentation. But if there're too many split segments Linux can combined some pieces and break desired behaviour. BSD and Windows are more predictable in this case. That's why it's not recommended to use too many splits. Tests revealed that 8+ can become problematic.\n\nSince linux kernel 4.6 **tpws** can recognize TCP segmentation failures and warn about them. `--fix-seg` can fix segmentation failures at the cost of some slowdown. It waits for several msec until all previous data is sent. This breaks async processing model and slows down every other connection going through **tpws**. Thus it's not recommended on highly loaded systems. But can be compromise for home systems.\n\nIf you're attempting to split massive transmission with `--split-any-protocol` option it will definitely cause massive segmentation failures. Do not do that without `--tamper-start` and `--tamper-cutoff` limiters.\n\n**tpws** works on socket level and receives in one shot long requests (TLS with kyber) that should normally require several TCP packets. It tampers entire received block without knowing how much packets it will take. OS will do additional segmenation to meet MTU.\n\n`--disorder` sends every odd packet with TTL=1. Server receives even packets fastly. Then client OS retransmits odd packets with normal TTL and server receives them. In case of 6 segments server and DPI will see them in this order : `2 4 6 1 3 5`. This way of disorder causes some delays. Default retransmission timeout in Linux is 200 ms.\n\n`--oob` sends one out-of-band byte in the end of the first split segment.\n\n`--oob` and `--disorder` can be combined only in Linux. Others OS do not handle this correctly.\n\n### TLSREC\n\n`--tlsrec` allow to split TLS ClientHello into 2 TLS records in one TCP segment. It accepts single pos marker.\n\n`--tlsrec` breaks significant number of sites. Crypto libraries on servers usually accept fine modified ClientHello but middleboxes such as CDNs and ddos guards - not always. Use of `--tlsrec` without filters is discouraged.\n\n### MSS\n\n`--mss` sets TCP_MAXSEG socket option. Client sets this value in MSS TCP option in the SYN packet.\nServer replies with it's own MSS in SYN,ACK packet. Usually servers lower their packet sizes but they still don't fit to supplied MSS. The greater MSS client sets the bigger server's packets will be.\nIf it's enough to split TLS 1.2 ServerHello, it may fool DPI that checks certificate domain name.\nThis scheme may significantly lower speed. Hostlist filter is possible only in socks mode if client uses remote resolving (firefox `network.proxy.socks_remote_dns`) or if `ipcache-hostname` is enabled.\n`--mss` is not required for TLS1.3. If TLS1.3 is negotiable then MSS make things only worse. Use only if nothing better is available. Works only in Linux, not BSD or MacOS.\n\n### Other tamper options\n\n`--hostpad=<bytes>` adds padding headers before `Host:` with specified number of bytes. If `<bytes>` is too large headers are split by 2K. Padding more that 64K is not supported and not accepted by http servers.\n\nIt's useful against stateful DPI's that reassemble only limited amount of data. Increase padding `<bytes>` until website works. If minimum working `<bytes>` is close to MTU then it's likely DPI is not reassembling packets. Then it's better to use regular split instead of `--hostpad`.\n\n### Supplementary options\n\n**tpws** can bind to multiple interfaces and IP addresses (up to 32).\n\nPort number is always the same.\n\nParameters `--bind-iface*` and `--bind-addr` create new bind.\n\nOther parameters `--bind-*` are related to the last bind.\n\nlink local ipv6 (`fe80::/8`) mode selection :\n\n```\n--bind-iface6 --bind-linklocal=no : first selects private address fc00::/7, then global address\n--bind-iface6 --bind-linklocal=unwanted : first selects private address fc00::/7, then global address, then LL\n--bind-iface6 --bind-linklocal=prefer : first selects LL, then private address fc00::/7, then global address\n--bind-iface6 --bind-linklocal=force : select only LL\n```\n\nTo bind to all ipv4 specify `--bind-addr \"0.0.0.0\"`, all ipv6 - `::`. \n\n`--bind-addr=\"\"` - mean bind to all ipv4 and ipv6.\n\nIf no binds are specified default bind to all ipv4 and ipv6 addresses is created.\n\nTo bind to a specific link local address do : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name`\n\nThe `--bind-wait*` parameters can help in situations where you need to get IP from the interface, but it is not there yet, it is not raised\nor not configured.\n\nIn different systems, ifup events are caught in different ways and do not guarantee that the interface has already received an IP address of a certain type.\n\nIn the general case, there is no single mechanism to hang oneself on an event of the type \"link local address appeared on the X interface.\"\n\nTo bind to a specific ip when its interface may not be configured yet do : `--bind-addr=192.168.5.3 --bind-wait-ip=20`\n\nIt's possible to bind to any nonexistent address in transparent mode but in socks mode address must exist.\n\nIn socks proxy mode no additional system privileges are required. Connections to local IPs of the system where **tpws** runs are prohibited.\ntpws supports remote dns resolving (curl : `--socks5-hostname`  firefox : `socks_remote_dns=true`) , but does it in blocking mode.\n\n**tpws** uses async sockets for all activities. Domain names are resolved in multi threaded pool.\nResolving does not freeze other connections. But if there're too many requests resolving delays may increase.\nNumber of resolver threads is choosen automatically proportinally to `--maxconn` and can be overriden using `--resolver-threads`.\nTo disable hostname resolve use `--no-resolve` option.\n\n### Multiple strategies\n\n**tpws** like **nfqws** supports multiple strategies. They work mostly like with **nfqws** with minimal differences.\n`filter-udp` is absent because **tpws** does not support udp. 0-phase desync methods (`--mss`) can work with hostlist in socks modes with remote hostname resolve.\nThis is the point where you have to plan profiles carefully. If you use `--mss` and hostlist filters, behaviour can be different depending on remote resolve feature enabled or not.\nUse `--mss` both in hostlist profile and profile without hostlist.\nUse `curl --socks5` and `curl --socks5-hostname` to issue two kinds of proxy queries.\nSee `--debug` output to test your setup.\n\n### IPTABLES for tpws\n\nUse the following rules to redirect TCP connections to 'tpws' :\n```\niptables -t nat -I OUTPUT -o <wan_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988\niptables -t nat -I PREROUTING -i <lan_interface> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988\n```\n\nFirst rule redirects outgoing from the same system traffic, second redirects passthrough traffic.\n\nDNAT to localhost works only in the **OUTPUT** chain and does not work in the **PREROUTING** chain without setting this sysctl :\n\n`sysctl -w net.ipv4.conf.<lan_interface>.route_localnet=1`\n\nIt's also possible to use `-j REDIRECT --to-port 988` instead of DNAT but in the latter case transparent proxy must listen on all IP addresses or on a LAN interface address. It's not too good to listen on all IP and it's not trivial to get specific IP in a shell script. `route_localnet` has it's own security impact if not protected by additional rules. You open `127.0.0.0/8` subnet to the net.\n\nThis is how to open only single `127.0.0.127` address :\n```\niptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT\niptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP\n```\n\nOwner filter is required to avoid redirection loops. **tpws** must be run with `--user tpws` parameter.\n\nip6tables work almost the same with minor differences. ipv6 addresses should be enclosed in square brackets :\n```\nip6tables -t nat -I OUTPUT -o <wan_interface> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988\n```\n\nThere's no `route_localnet` for ipv6. DNAT to localhost (`::1`) is possible only in **OUTPUT** chain. In **PREROUTING** chain DNAT is possible to any global address or link local address of the interface where packet came from.\n\n### NFTABLES for tpws\n\nBase nftables scheme :\n```\nIFACE_WAN=wan\nIFACE_LAN=br-lan\n\nsysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1\n\nnft create table inet ztest\n\nnft create chain inet ztest localnet_protect\nnft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return\nnft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop\nnft create chain inet ztest input \"{type filter hook input priority filter - 1;}\"\nnft add rule inet ztest input iif != \"lo\" jump localnet_protect\n\nnft create chain inet ztest dnat_output \"{type nat hook output priority dstnat;}\"\nnft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988\nnft create chain inet ztest dnat_pre \"{type nat hook prerouting priority dstnat;}\"\nnft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988\n```\n\nDelete nftable :\n```\nnft delete table inet ztest\n```\n\n\n## Ways to get a list of blocked IP\n\nnftables can't work with ipsets. Native nf sets require lots of RAM to load large ip lists with subnets and intervals.\nIn case you're on a low RAM system and need large lists it may be required to fall back to iptables+ipset.\n\n1. Enter the blocked domains to `ipset/zapret-hosts-user.txt` and run `ipset/get_user.sh`\nAt the output, you get `ipset/zapret-ip-user.txt` with IP addresses.\n\n2. `ipset/get_reestr_*.sh`. Russian specific\n\n3. `ipset/get_antifilter_*.sh`. Russian specific\n\n4. `ipset/get_config.sh`. This script calls what is written into the GETLIST variable from the config file.\n\nIf the variable is not defined, then only lists for ipsets nozapret/nozapret6 are resolved.\n\nSo, if you're not russian, the only way for you is to manually add blocked domains.\nOr write your own `ipset/get_iran_blocklist.sh` , if you know where to download this one.\n\nOn routers, it is not recommended to call these scripts more than once in 2 days to minimize flash memory writes.\n\n`ipset/create_ipset.sh` executes forced ipset update.\nWith `no-update` parameter `create_ipset.sh` creates ipset but populate it only if it was actually created.\n\nIt's useful when multiple subsequent calls are possible to avoid wasting of cpu time redoing the same job.\n\nIpset loading is resource consuming. Its a good idea to call create_ipset without `no-update` parameter\n\nonly once a several days. Use it with `no-update` option in other cases.\n\nipset scripts automatically call ip2net utility.\nip2net helps to reduce ip list size by combining IPs to subnets. Also it cuts invalid IPs from the list.\nStored lists are already processed by ip2net. They are error free and ready for loading.\n\n`create_ipset.sh` supports loading ip lists from gzip files. First it looks for the filename with the \".gz\" extension,\nsuch as `zapret-ip.txt.gz`, if not found it falls back to the original name `zapret-ip.txt`.\n\nSo your own get_iran_blockslist.sh can use \"zz\" function to produce gz. Study how other russian `get_XXX.sh` work.\n\nGzipping helps saving a lot of precious flash space on embedded systems.\n\nUser lists are not gzipped because they are not expected to be very large.\n\nYou can add a list of domains to `ipset/zapret-hosts-user-ipban.txt`. Their ip addresses will be placed\nin a separate ipset \"ipban\". It can be used to route connections to transparent proxy \"redsocks\" or VPN.\n\nIPV6: if ipv6 is enabled, then additional txt's are created with the same name, but with a \"6\" at the end before the extension.\n\n`zapret-ip.txt` => `zapret-ip6.txt`\n\nThe ipsets zapret6 and ipban6 are created.\n\nIP EXCLUSION SYSTEM. All scripts resolve `zapret-hosts-user-exclude.txt` file, creating `zapret-ip-exclude.txt` and `zapret-ip-exclude6.txt`.\n\nThey are the source for ipsets nozapret/nozapret6. All rules created by init scripts are created with these ipsets in mind.\nThe IPs placed in them are not involved in the process.\nzapret-hosts-user-exclude.txt can contain domains, ipv4 and ipv6 addresses or subnets.\n\nFreeBSD. `ipset/*.sh` scripts also work in FreeBSD. Instead of ipset they create ipfw lookup tables with the same names as in Linux.\nipfw tables can store both ipv4 and ipv6 addresses and subnets. There's no 4 and 6 separation.\n\nLISTS_RELOAD config parameter defines a custom lists reloading command.\nIts useful on BSD systems with PF.\nLISTS_RELOAD=-  disables reloading ip list backend.\n\n## Domain name filtering\n\nAn alternative to ipset is to use **tpws** or **nfqws** with a list(s) of domains.\nBoth **tpws** and **nfqws** take any number of include (`--hostlist`) and exclude (`--hostlist-exclude`) domain lists.\nAll lists of the same type are combined internally leaving only 2 lists : include and exclude.\n\nExclude list is checked first. Fooling is cancelled if domain belongs to exclude list.\nIf include list is present and domain does not belong to that list fooling is also cancelled.\nEmpty list means absent list. Otherwise fooling goes on.\n\nLaunch system looks for 2 include lists :\n\n`ipset/zapret-hosts-users.txt.gz` or `ipset/zapret-hosts-users.txt`\n\n`ipset/zapret-hosts.txt.gz` or `ipset/zapret-hosts.txt`\n\nand 1 exclude list\n\n`ipset/zapret-hosts-users-exclude.txt.gz` or `ipset/zapret-hosts-users-exclude.txt`\n\nIf `MODE_FILTER=hostlist` all present lists are passed to **nfqws** or **tpws**.\nIf all include lists are empty it works like no include lists exist at all.\nIf you need \"all except\" mode you dont have to delete zapret-hosts-users.txt. Just make it empty.\n\nSubdomains auto apply. For example, \"ru\" in the list affects \"\\*.ru\" .\n`^` prefix symbol disables subdomain match.\n\n**tpws** and **nfqws** automatically reload lists if their modification time or file size is changed.\nHUP signal forcibly reloads all lists.\n\nWhen filtering by domain name, daemons should run without filtering by ipset.\nWhen using large regulator lists estimate the amount of RAM on the router !\n\n## **autohostlist** mode\n\nThis mode analyzes both client requests and server replies.\nIf a host is not in any list and a situation similar to block occurs host is automatically added to the special list both in memory and file.\nUse exclude hostlist to prevent autohostlist triggering.\nIf it did happen - delete the undesired record from the file.\n\nIn case of nfqws it's required to redirect both incoming and outgoing traffic to the queue.\nIt's strongly recommended to use connbytes filter or nfqws will process gigabytes of incoming traffic.\nFor the same reason it's not recommended to use autohostlist mode in BSDs. BSDs do not support connbytes or similar mechanism.\n\n**nfqws** и **tpws** detect the folowing situations :\n1) [nfqws] Multiple retransmissions of the first request inside a TCP session having host.\n2) [nfqws,tpws] RST in response to the first request.\n3) [nfqws,tpws] HTTP redirect in response to the first http request with 2nd level domain diferent from the original.\n4) [tpws] Client closes connection after first request without having server reply (no reponse from server, timeout).\n\nTo minimize false positives there's fail counter. If in specific time occurs more than specified number of fails\nthe host is added to the list. Then DPI bypass strategy start to apply immediately.\n\nFor the user autohostlist mode looks like this.\nWhen for the first time user visits a blocked website it sees block page, connection reset\nor browser hangs until timeout, then display a error.\nUser presses multiple times F5 causing browser to retry attempts.\nAfter some retries a website opens and next time works as expected.\n\nWith autohostlist mode it's possible to use bypass strategies that break lots of sites.\nIf a site does not behave like blocked no fooling applies.\nOtherwise it's nothing to lose.\n\nHowever false positives still can occur in case target website is behaving abnormally\n(may be due to DDoS attack or server malfunction). If it happens bypass strategy\nmay start to break the website. This situation can only be controlled manually.\nRemove undesired domain from the autohostlist file.\nUse exclude hostlist to prevent further auto additions.\n\nIt's possible to use one auto hostlist with multiple processes. All processes check for file modification time.\nIf a process modified autohostlist, all others will reread it automatically.\nAll processes must run with the same uid.\n\nIf zapret scripts are used then autohostlist is `ipset/zapret-hosts-auto.txt`\nand exlude list is `ipset/zapret-hosts-user-exclude.txt`. autohostlist mode\nincludes hostlist mode. You can use `ipset/zapret-hosts-user.txt`.\n\n\n## Choosing parameters\n\nThe file `/opt/zapret/config` is used by various components of the system and contains basic settings.\nIt needs to be viewed and edited if necessary.\n\nWhich firewall type use on linux systems : `nftables` or `iptables`.\nOn traditional systems `nftables` is selected by default if `nft` is installed.\nOn openwrt by default `nftables` is selected on `firewall4` based systems.\n\n`FWTYPE=iptables`\n\nWith `nftables` post-NAT scheme is used by default. It allows more DPI attacks on forwarded traffic.\nIt's possible to use `iptables`-like pre-NAT scheme. **nfqws** will see client source IPs and display them in logs.\n\n`#POSTNAT=0`\n\nThere'are 3 standard options configured separately and independently : `tpws-socks`, **tpws**, **nfqws**.\nThey can be used alone or combined. Custom scripts in `init.d/{sysv,openwrt,macos}/custom.d` are always applied.\n\n`tpws-socks` requires daemon parameter configuration but does not require traffic interception.\nOther standard options require also traffic interception.\nEach standard option launches single daemon instance. Strategy differiences are managed using multi-profile scheme.\nMain rule for interception is \"intercept required minumum\". Everything else only wastes CPU resources and slows down connection.\n\n`--ipset` option is prohibited intentionally to disallow easy to use but ineffective user-mode filtering.\nUse kernel ipsets instead. It may require custom scripts.\n\nTo use standard updatable hostlists from the `ipset` dir use `<HOSTLIST>` placeholder. It's automatically replaced\nwith hostlist parameters if `MODE_FILTER` variable enables hostlists and is removed otherwise.\nStandard hostlists are expected in final (fallback) strategies closing groups of filter parameters.\nDon't use `<HOSTLIST>` in highly specialized profiles. Use your own filter or hostlist(s).\n`<HOSTLIST_NOAUTO>` marker uses standard autohostlist as usual hostlist thus disabling auto additions in this profile.\nIf any other profile adds something this profile accepts the change automatically.\n\nChange loop prevention mark bit\n\n`DESYNC_MARK=0x40000000`\n\nChange postnat scheme mark bit\n\n`DESYNC_MARK_POSTNAT=0x20000000`\n\nIf uncommented pass to zapret only packets marked with this bit\n\n`#FILTER_MARK=0x10000000`\n\nBit must be set in your own rules.\n* iptables - in mangle PREROUTING and mangle OUTPUT before zapret rules (iptables -I _after_ zapret rules application).\n* nftables - in output and prerouting hooks with priority -102 or lower.\n\nMark criterias can be any. For example, source IP or source interface name.\n\n**tpws** socks proxy mode switch\n\n`TPWS_SOCKS_ENABLE=0`\n\nListening tcp port for **tpws** proxy mode.\n\n`TPPORT_SOCKS=987`\n\n**tpws** socks mode parameters\n\n```\nTPWS_SOCKS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\"\n\"\n```\n\n**tpws** transparent mode switch\n\n`TPWS_ENABLE=0`\n\n**tpws** transparent mode target ports\n\n`TPWS_PORTS=80,443`\n\n**tpws** transparent mode parameters\n\n```\nTPWS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\"\n\"\n```\n\n**nfqws** enable switch\n\n`NFQWS_ENABLE=0`\n\n**nfqws** port targets for `connbytes`-limited interception. `connbytes` allows to intercept only starting packets from connections.\nThis is more effective kernel-mode alternative to `nfqws --dpi-desync-cutoff=nX`.\n\n```\nNFQWS_PORTS_TCP=80,443\nNFQWS_PORTS_UDP=443\n```\n\nHow many starting packets should be intercepted to nfqws in each direction\n\n```\nNFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_TCP_PKT_IN=3\nNFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_UDP_PKT_IN=0\n```\n\nThere's kind of traffic that requires interception of entire outgoing stream.\nTypically it's support for plain http keepalives and stateless DPI.\nThis mode of interception significantly increases CPU utilization. Use with care and only if required.\nHere you specify port numbers for unlimited interception.\nIt's advised also to remove these ports from `connbytes`-limited interception list.\n\n```\n#NFQWS_PORTS_TCP_KEEPALIVE=80\n#NFQWS_PORTS_UDP_KEEPALIVE=\n```\n\n**nfqws** parameters\n\n```\nNFQWS_OPT=\"\n--filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig <HOSTLIST> --new\n--filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig <HOSTLIST> --new\n--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 <HOSTLIST_NOAUTO>\n\"\n```\n\n\nHost filtering mode :\n```\nnone - apply fooling to all hosts\nipset - limit fooling to hosts from ipset zapret/zapret6\nhostlist - limit fooling to hosts from hostlist\nautohostlist - hostlist mode + blocks auto detection\n```\n\n`MODE_FILTER=none`\n\n\nflow offloading control (if supported)\n\n```\ndonttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading\nnone : always disable system flow offloading setting and dont configure selective flow offloading\nsoftware : always disable system flow offloading setting and configure selective software flow offloading\nhardware : always disable system flow offloading setting and configure selective hardware flow offloading\n```\n\n`FLOWOFFLOAD=donttouch`\n\nThe GETLIST parameter tells the install_easy.sh installer which script to call\nto update the list of blocked ip or hosts.\nIts called via `get_config.sh` from scheduled tasks (crontab or systemd timer).\nPut here the name of the script that you will use to update the lists.\nIf not, then the parameter should be commented out.\n\nYou can individually disable ipv4 or ipv6. If the parameter is commented out or not equal to \"1\",\nuse of the protocol is permitted.\n\n```\n#DISABLE_IPV4=1\nDISABLE_IPV6=1\n```\n\nThe number of threads for mdig multithreaded DNS resolver (1..100).\nThe more of them, the faster, but will your DNS server be offended by hammering ?\n\n`MDIG_THREADS=30`\n\ntemp directory. Used by ipset/*.sh scripts for large lists processing.\n/tmp by default. Can be reassigned if /tmp is tmpfs and RAM is low.\nTMPDIR=/opt/zapret/tmp\n\nipset and nfset options :\n\n```\nSET_MAXELEM=262144\nIPSET_OPT=\"hashsize 262144 maxelem 2097152\n```\n\nKernel automatically increases hashsize if ipset is too large for the current hashsize.\nThis procedure requires internal reallocation and may require additional memory.\nOn low RAM systems it can cause errors.\nDo not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs.\n\nip2net options. separate for ipv4 and ipv6.\n\n```\nIP2NET_OPT4=\"--prefix-length=22-30 --v4-threshold=3/4\"\nIP2NET_OPT6=\"--prefix-length=56-64 --v6-threshold=5\"\n```\n\nautohostlist mode tuning.\n\n```\nAUTOHOSTLIST_RETRANS_THRESHOLD=3\nAUTOHOSTLIST_FAIL_THRESHOLD=2\nAUTOHOSTLIST_FAIL_TIME=60\nAUTOHOSTLIST_DEBUG=0\n```\n\nEnable gzip compression for large lists. Used by ipset/*.sh scripts.\n\n`GZIP_LISTS=1`\n\nCommand to reload ip/host lists after update.\nComment or leave empty for auto backend selection : ipset or ipfw if present.\nOn BSD systems with PF no auto reloading happens. You must provide your own command.\nNewer FreeBSD versions support table only reloading : `pfctl -Tl -f /etc/pf.conf`\nSet to \"-\" to disable reload.\n\n`LISTS_RELOAD=\"pfctl -f /etc/pf.conf\"`\n\nIn openwrt there's default network `lan`. Only traffic coming from this network is redirected to tpws by default.\nTo override this behaviour set the following variable :\n\n`OPENWRT_LAN=\"lan lan2 lan3\"`\n\nIn openwrt wan interfaces are those having default route. Separately for ipv4 and ipv6.\nThis can be redefined :\n```\nOPENWRT_WAN4=\"wan4 vpn\"\nOPENWRT_WAN6=\"wan6 vpn6\"\n```\n\nThe `INIT_APPLY_FW=1` parameter enables the init script to independently apply iptables rules.\nWith other values or if the parameter is commented out, the rules will not be applied.\nThis is useful if you have a firewall management system, in the settings of which you should tie the rules.\nNot applicable to `OpenWRT` if used with `firewall3+iptables`.\n\n`FILTER_TTL_EXPIRED_ICMP=1` blocks icmp time exceeded messages in response to connections handled by nfqws.\nLinux closes socket if it receives this icmp in response to SYN packet. Similar mechanism exists for datagram sockets.\nIt's better to disable this if you do not expect problems caused by icmp.\n\nThe following settings are not relevant for openwrt :\n\nIf your system works as a router, then you need to enter the names of the internal and external interfaces:\n```\nIFACE_LAN=eth0\nIFACE_WAN=eth1\nIFACE_WAN6=\"henet ipsec0\"\n```\nMultiple interfaces are space separated. IF IFACE_WAN6 is omitted then IFACE_WAN value is taken.\n\nIMPORTANT: configuring routing, masquerade, etc. not a zapret task.\nOnly modes that intercept transit traffic are enabled.\nIt's possible to specify multiple interfaces like this : `IFACE_LAN=\"eth0 eth1 eth2\"`\n\n\n## Screwing to the firewall control system or your launch system\n\nIf you use some kind of firewall management system, then it may conflict with an existing startup script.\nWhen re-applying the rules, it could break the iptables settings from the zapret.\nIn this case, the rules for iptables should be screwed to your firewall separately from running tpws or nfqws.\n\nThe following calls allow you to apply or remove iptables rules separately:\n\n```\n /opt/zapret/init.d/sysv/zapret start_fw\n /opt/zapret/init.d/sysv/zapret stop_fw\n /opt/zapret/init.d/sysv/zapret restart_fw\n```\n\nAnd you can start or stop the demons separately from the firewall:\n\n```\n /opt/zapret/init.d/sysv/zapret start_daemons\n /opt/zapret/init.d/sysv/zapret stop_daemons\n /opt/zapret/init.d/sysv/zapret restart_daemons\n```\n\nnftables nearly eliminate conflicts betweeen firewall control systems because they allow\nseparate tables and netfilter hooks. `zapret` nf table is used for zapret purposes.\nIf your system does not touch it everything will likely be OK.\n\nSome additional nftables-only calls exist :\n\nLookup `lanif`, `wanif`, `wanif6` and `flow table` interface sets.\n```\n /opt/zapret/init.d/sysv/zapret list_ifsets\n```\n\nRenew `lanif`, `wanif`, `wanif6` and `flow table` interface sets.\nTaken from `IFACE_LAN`, `IFACE_WAN` config variables on traditional Linux systems.\nAutoselected on `OpenWRT`. `lanif` can be extended using `OPENWRT_LAN` config variable.\n```\n /opt/zapret/init.d/sysv/zapret reload_ifsets\n```\n\nCalls `nft -t list table inet zapret`.\n```\n /opt/zapret/init.d/sysv/zapret list_table\n```\n\nIt's also possible to hook with your script to any stage of zapret firewall processing.\nThe following settings are available in the zapret config file :\n\n```\nINIT_FW_PRE_UP_HOOK=\"/etc/firewall.zapret.hook.pre_up\"\nINIT_FW_POST_UP_HOOK=\"/etc/firewall.zapret.hook.post_up\"\nINIT_FW_PRE_DOWN_HOOK=\"/etc/firewall.zapret.hook.pre_down\"\nINIT_FW_POST_DOWN_HOOK=\"/etc/firewall.zapret.hook.post_down\"\n```\n\nHooks are extremely useful if you need nftables sets populated by zapret scripts.\nnfsets can only belong to one table. You have to write rule there and synchorize them with zapret scripts.\n\n## Installation\n\n### Checking ISP\n\nBefore running zapret you must discover working bypass strategy.\n`blockcheck.sh` automates this process. It first checks DNS then tries many strategies finding the working ones.\nNote that DNS check is mostly Russia targeted. It checks several pre-defined blocked in Russia domains and\nverifies system DNS answers with public DNS answers. Because ISP can block public DNS or redirect any DNS queries\nto their servers `blockcheck.sh` also checks that all returned answers are unique. Usually if DNS is blocked\nISP returns single ip for all blocked domains to redirect you to their \"access denied\" page.\nDoH servers are used automatically for checks if DNS spoof is detected.\n`blockcheck.sh` works on all systems supported by `zapret`.\n\n### desktop linux system\n\nSimple install works on most modern linux distributions with systemd or openrc, OpenWRT and MacOS.\nRun `install_easy.sh` and answer its questions.\n\n### OpenWRT\n\n`install_easy.sh` works on openwrt but there're additional challenges.\nThey are mainly about possibly low flash free space.\nSimple install will not work if it has no space to install itself and required packages from the repo.\n\nAnother challenge would be to bring zapret to the router. You can download zip from github and use it.\nInstall openssh-sftp-server and unzip to openwrt and use sftp to transfer the file.\nIt's also not too hard to use 'nc' (netcat) for file transfer.\n\nThe best way to start is to put zapret dir to `/tmp` and run `/tmp/zapret/install_easy.sh` from there.\nAfter installation remove `/tmp/zapret` to free RAM.\n\nThe absolute minimum for openwrt is 64/8 system, 64/16 is comfortable, 128/extroot is recommended.\n\nFor low storage openwrt see `init.d/openwrt-minimal`.\n\n### Android\n\nIts not possible to use **nfqws** and **tpws** in transparent proxy mode without root privileges. Without root **tpws** can run in `--socks` mode.\n\nAndroid has NFQUEUE and **nfqws** should work.\n\nThere's no `ipset` support unless you run custom kernel. In common case task of bringing up `ipset` on android is ranging from \"not easy\" to \"almost impossible\", unless you find working kernel image for your device.\n\nAlthough linux binaries work it's recommended to use Android specific ones. They have no problems with user names, local time, DNS, ...\nIts recommended to use gid 3003 (AID_INET), otherwise **tpws** will not have inet access.\n\nExample : `--uid 1:3003`\n\nIn iptables use : `! --uid-owner 1` instead of `! --uid-owner tpws`.\n\n**nfqws** should be executed with `--uid 1`. Otherwise on some devices or firmwares kernel may partially hang. Looks like processes with certain uids can be suspended. With buggy chineese cellular interface driver this can lead to device hang.\n\nWrite your own shell script with iptables and **tpws**, run it using your root manager.\nAutorun scripts are here :\n\nmagisk  : `/data/adb/service.d`\n\nsupersu : `/system/su.d`\n\nHow to run **tpws** on root-less android.\nYou can't write to `/system`, `/data`, can't run from sd card.\nSelinux prevents running executables in `/data/local/tmp` from apps.\nUse adb and adb shell.\n\n```\nmkdir /data/local/tmp/zapret\nadb push tpws /data/local/tmp/zapret\nchmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws\nchcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws\n```\n\nNow its possible to run `/data/local/tmp/zapret/tpws` from any app such as tasker.\n\n### FreeBSD, OpenBSD, MacOS\n\nsee [BSD documentation](./bsd.en.md)\n\n### Windows (WSL)\n\nsee [Windows documentation](./windows.en.md)\n\n### Other devices\n\nAuthor's goal does not include easy supporting as much devices as possibles.\nPlease do not ask for easy supporting firmwares. It requires a lot of work and owning lots of devices. Its counterproductive.\nAs a devices owner its easier for you and should not be too hard if firmware is open.\nMost closed stock firmwares are not designed for custom usage and sometimes actively prevent it.\nIn the latter case you have to hack into it and reverse engineer. Its not easy.\nBinaries are universal. They can run on almost all firmwares.\nYou will need :\n * root shell access. true sh shell, not microtik-like console\n * startup hook\n * r/w partition to store binaries and startup script with executable permission (+x)\n * **tpws** can be run almost anywhere but **nfqws** require kernel support for NFQUEUE. Its missing in most firmwares.\n * too old 2.6 kernels are unsupported and can cause errors. newer 2.6 kernels are OK.\nIf binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws.\n\nFirst manually debug your scenario. Run iptables + daemon and check if its what you want.\nWrite your own script with iptables magic and run required daemon from there. Put it to startup.\nDont ask me how to do it. Its different for all firmwares and requires studying.\nFind manual or reverse engineer yourself.\nCheck for race conditions. Firmware can clear or modify iptables after your startup script.\nIf this is the case then run another script in background and add some delay there.\n\n## Donations\n\nAre welcome here :\n\nUSDT ERC `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` \n\nUSDT TRC `TEzAAtn4VhndqEaAyuCM78xh5W2gCjwWEo`\n\nBTC  `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve`\n\nETH  `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E`\n"
  },
  {
    "path": "docs/readme.md",
    "content": "# ВНИМАНИЕ, остерегайтесь мошенников\n\nzapret является свободным и open source.\nВсякий, кто понуждает вас скачивать zapret только с его ресурса, требует удалить ссылки, видео, файлы, обосновывая эти требования авторскими правами, сам нарушает [лицензию](./LICENSE.txt).\nОднако, это не исключает [добровольные пожертвования](#поддержать-разработчика).\n\n# zapret2\n\nЭта версия zapret более не развивается и находится в режиме EOL (End-Of-Life). Никаких новых функций больше не будет. Только багфиксы.\n\n[Актуальная версия - zapret 2](https://github.com/bol-van/zapret2)\n\n# Multilanguage README\n\n[![en](https://img.shields.io/badge/lang-en-red.svg)](./readme.en.md)\n[![ru](https://img.shields.io/badge/lang-ru-green.svg)](./readme.md)\n\n***\n\n  - [Зачем это нужно](#зачем-это-нужно)\n  - [Быстрый старт](#быстрый-старт)\n  - [Как это работает](#как-это-работает)\n  - [Что сейчас происходит в России](#что-сейчас-происходит-в-россии)\n  - [Как это реализовать на практике в системе linux](#как-это-реализовать-на-практике-в-системе-linux)\n  - [Когда это работать не будет](#когда-это-работать-не-будет)\n  - [nfqws](#nfqws)\n    - [АТАКА ДЕСИНХРОНИЗАЦИИ DPI](#атака-десинхронизации-dpi)\n    - [ФЕЙКИ](#фейки)\n    - [МОДИФИКАЦИЯ ФЕЙКОВ](#модификация-фейков)\n    - [TCP СЕГМЕНТАЦИЯ](#tcp-сегментация)\n    - [ПЕРЕКРЫТИЕ SEQUENCE NUMBERS](#перекрытие-sequence-numbers)\n    - [НАЗНАЧЕНИЕ IP_ID](#назначение-ip_id)\n    - [СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6](#специфические-режимы-ipv6)\n    - [МОДИФИКАЦИЯ ОРИГИНАЛА](#модификация-оригинала)\n    - [ДУБЛИКАТЫ](#дубликаты)\n    - [КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ](#комбинирование-методов-десинхронизации)\n    - [КЭШ IP](#кэш-ip)\n    - [РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА](#реакция-dpi-на-ответ-сервера)\n    - [РЕЖИМ SYNACK](#режим-synack)\n    - [РЕЖИМ SYNDATA](#режим-syndata)\n    - [ВИРТУАЛЬНЫЕ МАШИНЫ](#виртуальные-машины)\n    - [CONNTRACK](#conntrack)\n    - [РЕАССЕМБЛИНГ](#реассемблинг)\n    - [ПОДДЕРЖКА UDP](#поддержка-udp)\n    - [IP ФРАГМЕНТАЦИЯ](#ip-фрагментация)\n    - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии)\n    - [ФИЛЬТРАЦИЯ ПО WIFI](#фильтрация-по-wifi)\n    - [IPTABLES ДЛЯ NFQWS](#iptables-для-nfqws)\n    - [NFTABLES ДЛЯ NFQWS](#nftables-для-nfqws)\n    - [FLOW OFFLOADING](#flow-offloading)\n    - [ОСОБЕННОСТИ ЖЕЛЕЗОК](#особенности-железок)\n    - [ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА](#дурение-со-стороны-сервера)\n  - [tpws](#tpws)\n    - [TCP СЕГМЕНТАЦИЯ В TPWS](#tcp-сегментация-в-tpws)\n    - [TLSREC](#tlsrec)\n    - [MSS](#mss)\n    - [ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ](#другие-параметры-дурения)\n    - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии-1)\n    - [СЛУЖЕБНЫЕ ПАРАМЕТРЫ](#служебные-параметры)\n    - [IPTABLES ДЛЯ TPWS](#iptables-для-tpws)\n    - [NFTABLES ДЛЯ TPWS](#nftables-для-tpws)\n  - [ip2net](#ip2net)\n  - [mdig](#mdig)\n  - [Способы получения списка заблокированных IP](#способы-получения-списка-заблокированных-ip)\n  - [Фильтрация по именам доменов](#фильтрация-по-именам-доменов)\n  - [Режим фильтрации autohostlist](#режим-фильтрации-autohostlist)\n  - [Проверка провайдера](#проверка-провайдера)\n  - [Выбор параметров](#выбор-параметров)\n  - [Прикручивание к системе управления фаерволом или своей системе запуска](#прикручивание-к-системе-управления-фаерволом-или-своей-системе-запуска)\n  - [Вариант custom](#вариант-custom)\n  - [Простая установка](#простая-установка)\n  - [Установка под systemd](#установка-под-systemd)\n  - [Простая установка на openwrt](#простая-установка-на-openwrt)\n  - [Установка на openwrt в режиме острой нехватки места на диске](#установка-на-openwrt-в-режиме-острой-нехватки-места-на-диске)\n  - [Android](#android)\n  - [Мобильные модемы и роутеры huawei](#мобильные-модемы-и-роутеры-huawei)\n  - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos)\n  - [Windows](#windows)\n  - [Другие прошивки](#другие-прошивки)\n  - [Обход блокировки через сторонний хост](#обход-блокировки-через-сторонний-хост)\n  - [Почему стоит вложиться в покупку VPS](#почему-стоит-вложиться-в-покупку-vps)\n  - [Поддержать разработчика](#поддержать-разработчика)\n***\n\n## Зачем это нужно\n\nАвтономное средство противодействия DPI, которое не требует подключения каких-либо сторонних серверов. Может помочь\nобойти блокировки или замедление сайтов HTTP(S), сигнатурный анализ TCP и UDP протоколов, например, с целью блокировки\nVPN.\n\nПроект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под OpenWrt. Поддерживаются\nтрадиционные Linux-системы, FreeBSD, OpenBSD, частично macOS. В некоторых случаях возможна самостоятельная прикрутка\nрешения к различным прошивкам.\n\nБольшая часть функционала работает на Windows.\n\n## Быстрый старт\n\n- [Linux/openWrt](./quick_start.md)\n- [Windows](./quick_start_windows.md)\n\n## Как это работает\n\nВ самом простейшем случае вы имеете дело с пассивным DPI. Пассивный DPI может читать трафик из потока, может инжектить\nсвои пакеты, но не может блокировать проходящие пакеты. Если запрос \"плохой\", пассивный DPI инжектит пакет RST,\nопционально дополняя его пакетом HTTP redirect. Если фейк пакет инжектится только для клиента, в этом случае можно\nобойтись командами iptables для дропа RST и/или редиректа на заглушку по определённым условиям, которые нужно подбирать\nдля каждого провайдера индивидуально. Так мы обходим последствия срабатывания триггера запрета. Если пассивный DPI\nнаправляет пакет RST в том числе и серверу, то вы ничего с этим не сможете сделать. Ваша задача — не допустить\nсрабатывания триггера запрета. Одними iptables уже не обойтись. Этот проект нацелен именно на предотвращение\nсрабатывания запрета, а не ликвидацию его последствий.\n\nАктивный DPI ставится в разрез провода и может дропать пакеты по любым критериям, в том числе распознавать TCP-потоки и\nблокировать любые пакеты, принадлежащие потоку.\n\nКак не допустить срабатывания триггера запрета? Послать то, на что DPI не рассчитывает и что ломает ему алгоритм\nраспознавания запросов и их блокировки.\n\nНекоторые DPI не могут распознать HTTP-запрос, если он разделен на TCP-сегменты. Например, запрос\nвида `GET / HTTP/1.1\\r\\nHost: kinozal.tv......`\nмы посылаем двумя частями: сначала идет `GET`, затем `/ HTTP/1.1\\r\\nHost: kinozal.tv.....`. Другие DPI спотыкаются, когда\nзаголовок `Host:` пишется в другом регистре: например, `host:`. Кое-где работает добавление дополнительного пробела\nпосле метода: `GET /` → `GET /`\nили добавление точки в конце имени хоста: `Host: kinozal.tv.`\n\nСуществует и более продвинутая магия, направленная на преодоление DPI на пакетном уровне.\n\nПодробнее про DPI:\\\nhttps://habr.com/ru/post/335436 или https://web.archive.org/web/20230331233644/https://habr.com/ru/post/335436/ \\\nhttps://geneva.cs.umd.edu/papers/geneva_ccs19.pdf\n\n## Что сейчас происходит в России\n\nРаньше, до внедрения повсеместных систем ТСПУ, использовался зоопарк различных DPI у провайдеров. Какие-то были\nактивными, какие-то пассивными. Сейчас время простых iptables окончательно ушло. Везде активный DPI ТСПУ, но кое-где\nмогут оставаться невыключенными дополнительные старые DPI из зоопарка. В этом случае приходится обходить сразу несколько\nDPI. Все больше становится внереестровых блокировок, о которых вы узнаете только по факту недоступности чего-либо, в\nсписках этого нет. Применяются блокировки некоторых диапазонов ip адресов (автономный обход невозможен)\nи протоколов (VPN). На некоторых диапазонах IP используется более строгий фильтр, распознающий попытки обмана через\nсегментацию. Должно быть это связано с некоторыми сервисами, которые пытаются таким образом обмануть DPI.\n\n## Как это реализовать на практике в системе linux\n\nЕсли кратко, то варианты можно классифицировать по следующей схеме :\n\n1) Пассивный DPI, не отправляющий RST серверу. Помогут индивидуально настраиваемые под провайдера команды iptables. На\n   rutracker в разделе \"обход блокировок - другие способы\" по этому вопросу существует отдельная тема. В данном проекте\n   не рассматривается. Если вы не допустите срабатывание триггера запрета, то и не придется бороться с его\n   последствиями.\n2) Модификация TCP соединения на уровне потока. Реализуется через proxy или transparent proxy.\n3) Модификация TCP соединения на уровне пакетов. Реализуется через обработчик очереди NFQUEUE и raw сокеты.\n\nДля вариантов 2 и 3 реализованы программы tpws и nfqws соответственно. Чтобы они работали, необходимо их запустить с\nнужными параметрами и перенаправить на них определенный трафик средствами iptables или nftables.\n\n## Когда это работать не будет\n\n* Если подменяется DNS. С этой проблемой легко справиться.\n* Если блокировка осуществляется по IP.\n* Если соединение проходит через фильтр, способный реконструировать TCP соединение, и который следует всем стандартам.\n  Например, нас заворачивают на squid. Соединение идет через полноценный стек tcpip операционной системы.\n  Проект нацелен на обман DPI, который в силу ограниченности ресурсов и большого трафика вынужден интерпретировать его лишь ограниченно.\n  Обмануть полноценный стек ОС и полноценные серверные приложения не получится.\n\n## nfqws\n\nЭта программа - модификатор пакетов и обработчик очереди NFQUEUE. Для BSD систем существует адаптированный вариант -\ndvtws, собираемый из тех же исходников (см. [документация BSD](./bsd.md)).\n\n```\n@<config_file>|$<config_file>                             ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются.\n\n--debug=0|1                                               ; 1=выводить отладочные сообщения\n--dry-run                                                 ; проверить опции командной строки и выйти. код 0 - успешная проверка.\n--version                                                 ; вывести версию и выйти\n--comment                                                 ; любой текст (игнорируется)\n--daemon                                                  ; демонизировать прогу\n--pidfile=<file>                                          ; сохранить PID в файл\n--user=<username>                                         ; менять uid процесса\n--uid=uid[:gid]                                           ; менять uid процесса\n--qnum=N                                                  ; номер очереди N\n--bind-fix4                                               ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv4 пакетов\n--bind-fix6                                               ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv6 пакетов\n--ctrack-timeouts=S:E:F[:U]                               ; таймауты внутреннего conntrack в состояниях SYN, ESTABLISHED, FIN, таймаут udp. по умолчанию 60:300:60:60\n--ctrack-disable=[0|1]                                    ; 1 или отсутствие аргумента отключает conntrack\n--ipcache-lifetime=<int>                                  ; время жизни записей кэша IP в секундах. 0 - без ограничений.\n--ipcache-hostname=[0|1]                                  ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы\n--wsize=<winsize>[:<scale_factor>]                        ; менять tcp window size на указанный размер в SYN,ACK. если не задан scale_factor, то он не меняется (устарело !)\n--wssize=<winsize>[:<scale_factor>]                       ; менять tcp window size на указанный размер в исходящих пакетах. scale_factor по умолчанию 0. (см. conntrack !)\n--wssize-cutoff=[n|d|s]N                                  ; изменять server window size в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N\n--wssize-forced-cutoff=0|1                                ; 1(default)=автоматически отключать wssize в случае обнаружения известного протокола\n--synack-split=[syn|synack|acksyn]                        ; выполнить tcp split handshake. вместо SYN,ACK отсылать только SYN, SYN+ACK или ACK+SYN\n--orig-ttl=<int>                                          ; модифицировать TTL оригинального пакета\n--orig-ttl6=<int>                                         ; модифицировать ipv6 hop limit оригинальных пакетов.  если не указано, используется значение --orig-ttl\n--orig-autottl=[<delta>[:<min>[-<max>]]|-]                ; режим auto ttl для ipv4 и ipv6. по умолчанию: +5:3-64. \"0:0-0\" или \"-\" отключает функцию\n--orig-autottl6=[<delta>[:<min>[-<max>]]|-]               ; переопределение предыдущего параметра для ipv6\n--orig-tcp-flags-set=<int|0xHEX|flaglist>                 ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n--orig-tcp-flags-unset=<int|0xHEX|flaglist>               ; удалять указанные tcp флаги (flags &= ~value)\n--orig-mod-start=[n|d|s]N                                 ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N\n--orig-mod-cutoff=[n|d|s]N                                ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N\n--dup=<int>                                               ; высылать N дубликатов до оригинала\n--dup-replace=[0|1]                                       ; 1 или отсутствие аргумента блокирует отправку оригинала. отправляются только дубликаты.\n--dup-ttl=<int>                                           ; модифицировать TTL дубликатов\n--dup-ttl6=<int>                                          ; модифицировать ipv6 hop limit дубликатов. если не указано, используется значение --dup-ttl\n--dup-autottl=[<delta>[:<min>[-<max>]]|-]                 ; режим auto ttl для ipv4 и ipv6. по умолчанию: +1:3-64. \"0:0-0\" или \"-\" отключает функцию\n--dup-autottl6=[<delta>[:<min>[-<max>]]|-]                ; переопределение предыдущего параметра для ipv6\n--dup-tcp-flags-set=<int|0xHEX|flaglist>                  ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n--dup-tcp-flags-unset=<int|0xHEX|flaglist>                ; удалять указанные tcp флаги (flags &= ~value)\n--dup-fooling=<fooling>                                   ; дополнительные методики как сделать, чтобы дубликат не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n--dup-ts-increment=<int|0xHEX>                            ; инкремент TSval для ts. по умолчанию -600000\n--dup-badseq-increment=<int|0xHEX>                        ; инкремент sequence number для badseq. по умолчанию -10000\n--dup-badack-increment=<int|0xHEX>                        ; инкремент ack sequence number для badseq. по умолчанию -66000\n--dup-ip-id=same|zero|seq|rnd                             ; режим назначения ip_id для пакетов dup\n--dup-start=[n|d|s]N                                      ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N\n--dup-cutoff=[n|d|s]N                                     ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N\n--hostcase                                                ; менять регистр заголовка \"Host:\" по умолчанию на \"host:\".\n--hostnospace                                             ; убрать пробел после \"Host:\" и переместить его в конец значения \"User-Agent:\" для сохранения длины пакета\n--methodeol                                               ; добавить перевод строки в unix стиле ('\\n') перед методом и убрать пробел из Host: : \"GET / ... Host: domain.com\" => \"\\nGET  / ... Host:domain.com\"\n--hostspell=HoST                                          ; точное написание заголовка Host (можно \"HOST\" или \"HoSt\"). автоматом включает --hostcase\n--domcase                                                 ; домен после Host: сделать таким : TeSt.cOm\n--ip-id=seq|seqgroup|rnd|zero                             ; режим назначения ip_id для генерированных пакетов\n--dpi-desync=[<mode0>,]<mode>[,<mode2]                    ; атака по десинхронизации DPI. mode : synack syndata fake fakeknown rst rstack hopbyhop destopt ipfrag1 multisplit multidisorder fakedsplit hostfakesplit fakeddisorder ipfrag2 udplen tamper\n--dpi-desync-fwmark=<int|0xHEX>                           ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000\n--dpi-desync-ttl=<int>                                    ; установить ttl для десинхронизирующих пакетов\n--dpi-desync-ttl6=<int>                                   ; установить ipv6 hop limit для десинхронизирующих пакетов. если не указано, используется значение --dpi-desync-ttl\n--dpi-desync-autottl=[<delta>[:<min>[-<max>]]|-]          ; режим auto ttl для ipv4 и ipv6. по умолчанию: 1:3-20. \"0:0-0\" или \"-\" отключает функцию\n--dpi-desync-autottl6=[<delta>[:<min>[-<max>]]|-]         ; переопределение предыдущего параметра для ipv6\n--dpi-desync-tcp-flags-set=<int|0xHEX|flaglist>           ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\n--dpi-desync-tcp-flags-unset=<int|0xHEX|flaglist>         ; удалять указанные tcp флаги (flags &= ~value)\n--dpi-desync-fooling=<fooling>                            ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n--dpi-desync-repeats=<N>                                  ; посылать каждый генерируемый в nfqws пакет N раз (не влияет на остальные пакеты)\n--dpi-desync-skip-nosni=0|1                               ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI\n--dpi-desync-split-pos=N|-N|marker+N|marker-N             ; список через запятую маркеров для tcp сегментации в режимах split и disorder\n--dpi-desync-split-seqovl=N|-N|marker+N|marker-N          ; единичный маркер, определяющий величину перекрытия sequence в режимах split и disorder. для split поддерживается только положительное число.\n--dpi-desync-split-seqovl-pattern=[+ofs]@<filename>|0xHEX ; чем заполнять фейковую часть overlap\n--dpi-desync-fakedsplit-pattern=[+ofs]@<filename>|0xHEX   ; чем заполнять фейки в fakedsplit/fakeddisorder\n--dpi-desync-fakedsplit-mod=mod[,mod]                     ; может быть none, altorder=0|1|2|3 + 0|8|16\n--dpi-desync-hostfakesplit-midhost=marker+N|marker-N      ; маркер дополнительного разреза сегмента с оригинальным хостом. должен попадать в пределы хоста.\n--dpi-desync-hostfakesplit-mod=mod[,mod]                  ; может быть none, host=<hostname>, altorder=0|1\n--dpi-desync-ipfrag-pos-tcp=<8..9216>                     ; позиция ip фрагментации tcp, начиная с транспортного заголовка. должно быть кратно 8, по умолчанию - 32.\n--dpi-desync-ipfrag-pos-udp=<8..9216>                     ; позиция ip фрагментации udp, начиная с транспортного заголовка. должно быть кратно 8, по умолчанию - 8.\n--dpi-desync-ts-increment=<int|0xHEX>                     ; инкремент TSval для ts. по умолчанию -600000\n--dpi-desync-badseq-increment=<int|0xHEX>                 ; инкремент sequence number для badseq. по умолчанию -10000\n--dpi-desync-badack-increment=<int|0xHEX>                 ; инкремент ack sequence number для badseq. по умолчанию -66000\n--dpi-desync-any-protocol=0|1                             ; 0(default)=работать только по http request и tls clienthello  1=по всем непустым пакетам данных\n--dpi-desync-fake-tcp-mod=mod[,mod]                       ; список через запятую режимов runtime модификации tcp фейков (любых) : none, seq\n--dpi-desync-fake-http=[+ofs]@<filename>|0xHEX\t          ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному www.iana.org\n--dpi-desync-fake-tls=[+ofs]@<filename>|0xHEX|![+offset]  ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному. '!' = стандартный фейк\n--dpi-desync-fake-tls-mod=mod[,mod]                       ; список через запятую режимов runtime модификации фейков : none,rnd,rndsni,sni=<sni>,dupsid,padencap\n--dpi-desync-fake-unknown=[+ofs]@<filename>|0xHEX         ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт\n--dpi-desync-fake-syndata=[+ofs]@<filename>|0xHEX         ; файл, содержащий фейковый пейлоад пакета SYN для режима десинхронизации syndata\n--dpi-desync-fake-quic=[+ofs]@<filename>|0xHEX            ; файл, содержащий фейковый QUIC Initial\n--dpi-desync-fake-wireguard=[+ofs]@<filename>|0xHEX       ; файл, содержащий фейковый wireguard handshake initiation\n--dpi-desync-fake-dht=[+ofs]@<filename>|0xHEX             ; файл, содержащий фейковый пейлоад DHT протокола для dpi-desync=fake, на замену стандартным нулям 64 байт\n--dpi-desync-fake-discord=[+ofs]@<filename>|0xHEX         ; файл, содержащий фейковый пейлоад Discord протокола нахождения IP адреса для голосовых чатов для dpi-desync=fake, на замену стандартным нулям 64 байт\n--dpi-desync-fake-stun=[+ofs]@<filename>|0xHEX            ; файл, содержащий фейковый пейлоад STUN протокола для dpi-desync=fake, на замену стандартным нулям 64 байт\n--dpi-desync-fake-unknown-udp=[+ofs]@<filename>|0xHEX     ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт\n--dpi-desync-udplen-increment=<int>                       ; на сколько увеличивать длину udp пейлоада в режиме udplen\n--dpi-desync-udplen-pattern=[+ofs]@<filename>|0xHEX       ; чем добивать udp пакет в режиме udplen. по умолчанию - нули\n--dpi-desync-start=[n|d|s]N                               ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N\n--dpi-desync-cutoff=[n|d|s]N                              ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N\n--hostlist=<filename>                                     ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'.\n                                                          ; в файле должен быть хост на каждой строке.\n                                                          ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска.\n                                                          ; при изменении времени модификации файла он перечитывается автоматически по необходимости\n                                                          ; список может быть запакован в gzip. формат автоматически распознается и разжимается\n                                                          ; списков может быть множество. пустой общий лист = его отсутствие\n                                                          ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello.\n--hostlist-domains=<domain_list>                          ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов.\n--hostlist-exclude=<filename>                             ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам.\n--hostlist-exclude-domains=<domain_list>                  ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов.\n--hostlist-auto=<filename>                                ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика)\n--hostlist-auto-fail-threshold=<int>                      ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3)\n--hostlist-auto-fail-time=<int>                           ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60)\n--hostlist-auto-retrans-threshold=<int>                   ; сколько ретрансмиссий запроса считать блокировкой (по умолчанию: 3)\n--hostlist-auto-debug=<logfile>                           ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты.\n--new                                                     ; начало новой стратегии (новый профиль)\n--skip                                                    ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров.\n--filter-l3=ipv4|ipv6                                     ; фильтр версии ip для текущей стратегии\n--filter-tcp=[~]port1[-port2]|*                           ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. установка фильтра tcp и неустановка фильтра udp запрещает udp. поддерживается список через запятую.\n--filter-udp=[~]port1[-port2]|*                           ; фильтр портов udp для текущей стратегии. ~ означает инверсию. установка фильтра udp и неустановка фильтра tcp запрещает tcp. поддерживается список через запятую.\n--filter-l7=<proto>                                       ; фильтр протокола L6-L7. поддерживается несколько значений через запятую. proto : http tls quic wireguard dht discord stun unknown\n--filter-ssid=ssid1[,ssid2,ssid3,...]                     ; фильтр по имени wifi сети (только для linux)\n--ipset=<filename>                                        ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая.\n--ipset-ip=<ip_list>                                      ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей.\n--ipset-exclude=<filename>                                ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая.\n--ipset-exclude-ip=<ip_list>                              ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей.\n```\n\n`--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл. Может быть важен порядок следования\nопций. `--debug` лучше всего указывать в самом начале. Опции анализируются последовательно. Если ошибка будет при\nпроверке опции, а до анализа `--debug` еще дело не дошло, то сообщения не будут выведены в файл или syslog. При\nлогировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. Так что\nфайл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. Но имейте в виду, что если вы\nзапускаете процесс под root, то будет сменен UID на не-root. В начале на лог файл меняется owner, иначе запись будет\nневозможна. Если вы потом удалите файл, и у процесса не будет прав на создание файла в его директории, лог больше не\nбудет вестись. Вместо удаления лучше использовать truncate. В шелле это можно сделать через команду \": >filename\"\n\nМногие параметры, загружающие двоичные данные из файлов, поддерживают загрузку из hex-строки или из файла.\nhex строка начинается с \"0x\". Имя файла можно писать как есть или использовать префикс \"@\".\nЕсли перед префиксом \"@\" указано \"+<число>\", то это означает смещение полезных данных внутри файла.\nФайл может загружаться целиком с нулевой позиции, к нему могут применяться модификации, требующие полного файла (TLS),\nно передача пойдет с позиции offset. offset должен быть меньше длины файла. Если к блоку данных применяется мод,\nкоторый уменьшает размер данных, и offset окажется не меньше новой длины данных, будет ошибка.\n\n### АТАКА ДЕСИНХРОНИЗАЦИИ DPI\n\nСуть ее в следующем. Берется оригинальный запрос, модифицируется, добавляется поддельная информация (фейки)\nтаким образом, чтобы ОС сервера передала серверному процессу оригинальный запрос в неизменном виде, а DPI увидел другое.\nТо, что он блокировать не станет. Сервер видит одно, DPI - другое. DPI не понимает, что передается запрещенный запрос и не блокирует его.\n\nЕсть арсенал возможностей, чтобы достичь такого результата.\nЭто может быть передача фейк пакетов, чтобы они дошли до DPI, но не дошли до сервера. Может использоваться фрагментация на уровне TCP (сегментация) или на уровне IP.\nЕсть атаки, основанные на игре с tcp sequence numbers или с перепутыванием порядка следования tcp сегментов.\nМетоды могут сочетаться в различных вариантах.\n\n### ФЕЙКИ\n\nФейки - это отдельные сгенерированные nfqws пакеты, несущие ложную информацию для DPI.\nОни либо не должны дойти до сервера, либо могут дойти, но должны быть им отброшены.\nИначе получается слом tcp соединения или нарушение целостности передаваемого потока, что гарантированно приводит к поломке ресурса.\nЕсть ряд методов для решения этой задачи.\n\n* `md5sig` добавляет TCP опцию **MD5 signature**. Работает не на всех серверах. Пакеты с md5 обычно отбрасывают только linux.\n  Требуется значительное увеличение длины tcp пакета, чтобы вместить tcp option. При обработке многосегментных запросов (TLS Kyber)\n  первый пакет идет полный под MTU. При fakedsplit/fakeddisorder на небольших позициях отдельные tcp сегменты достаточно велики, чтобы внедрение\n  md5 tcp option вызвало переполнение MTU и ошибку отправки \"message too long\". `nfqws` не умеет перераспределять данные между tcp сегментами,\n  поэтому надо или отказываться от kyber, или увеличивать сплит-позицию, или отказываться от fakedsplit/fakeddisorder.\n* `badsum` портит контрольную сумму TCP. Не сработает, если ваше устройство за NAT, который не пропускает пакеты с инвалидной суммой. Наиболее\n  распространенная настройка NAT роутера в Linux их не пропускает. На Linux построено большинство домашних роутеров.\n  Непропускание обеспечивается так : настройка ядра sysctl по умолчанию\n  `net.netfilter.nf_conntrack_checksum=1` заставляет conntrack проверять tcp и udp чексуммы входящих пакетов и\n  выставлять state INVALID для пакетов с инвалидной суммой. Обычно в правилах iptables вставляется правило для дропа\n  пакетов с состоянием INVALID в цепочке FORWARD. Совместное сочетание этих факторов приводит к непрохождению badsum\n  через такой роутер. В OpenWrt из коробки `net.netfilter.nf_conntrack_checksum=0`, в других роутерах часто нет, и не\n  всегда это можно изменить. Чтобы nfqws мог работать через роутер, нужно на нем выставить указанное значение sysctl в 0.\n  nfqws на самом роутере будет работать и без этой настройки, потому что чексумма локально созданных пакетов не\n  проверяется никогда. Если роутер за другим NAT, например провайдерским, и он не пропускает invalid packets вы ничего\n  не сможете с этим сделать. Но обычно провайдеры все же пропускают badsum. На некоторых адаптерах/свитчах/драйверах\n  принудительно включен rx-checksum offload, badsum пакеты отсекаются еще до получения в ОС. В этом случае если что-то и\n  можно сделать, то только модифицировать драйвер, что представляется задачей крайне нетривиальной. Установлено, что так\n  себя ведут некоторые роутеры на базе mediatek. badsum пакеты уходят с клиентской ОС, но роутером не видятся в br-lan\n  через tcpdump. При этом если nfqws выполняется на самом роутере, обход может работать. badsum нормально уходят с\n  внешнего интерфейса.\n* `badseq` увеличивает TCP sequence number на определенное значение, выводя его тем самым из TCP window.\n  Такие пакеты будут наверняка отброшены принимающим узлом, но так же и DPI, если он ориентируется на sequence\n  numbers. По умолчанию смещение seq выбирается -10000. Практика показала, что некоторые DPI не пропускают seq вне\n  определенного окна. Однако, такое небольшое смещение может вызвать проблемы при существенной потоковой передаче и\n  потере пакетов. Если вы используете `--dpi-desync-any-protocol`, может понадобиться установить badseq increment\n  0x80000000. Это обеспечит надежную гарантию, что поддельный пакет не вклинится в tcp window на сервере. Так же было\n  замечено, что badseq ломает логику некоторых DPI при анализе http, вызывая зависание соединения. Причем на тех же DPI\n  TLS с badseq работает нормально.\n* `TTL` казалось бы - лучший вариант, но он требует индивидуальной настройки под каждого провайдера. Если DPI находится\n  дальше локальных сайтов провайдера, то вы можете отрезать себе доступ к ним. Ситуация усугубляется наличием ТСПУ на\n  магистралах, что вынуждает делать TTL достаточно высоким, увеличивая риск пробоя фейка до сервера. Необходим ip\n  exclude list, заполняемый вручную. Вместе с ttl можно применять md5sig. Это ничего не испортит, зато дает неплохой\n  шанс работы сайтов, до которых \"плохой\" пакет дойдет по TTL. Если не удается найти автоматическое решение,\n  воспользуйтесь файлом `zapret-hosts-user-exclude.txt`. Некоторые стоковые прошивки роутеров фиксируют исходящий TTL,\n  без отключения этой опции через них работать не будет. КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при\n  котором обход еще работает. Это и будет номер хопа вашего DPI.\n* `hopbyhop` относится только к ipv6. Добавляется ipv6 extenstion header `hop-by-hop options`. В варианте `hopbyhop2`\n  добавляются 2 хедера, что является нарушением стандарта и гарантированно отбрасывается стеком протоколов во всех ОС.\n  Один хедер hop-by-hop принимается всеми ОС, однако на некоторых каналах/провайдерах такие пакеты могут фильтроваться и\n  не доходить. Расчет идет на то, что DPI проанализирует пакет с hop-by-hop, но он либо не дойдет до адресата в силу\n  фильтров провайдера, либо будет отброшен сервером, потому что хедера два.\n* `datanoack` высылает фейки со снятым tcp флагом ACK. Сервера такое не принимают, а DPI может принять. Эта техника\n  может ломать NAT и не всегда работает с iptables, если используется masquerade, даже с локальной системы (почти всегда\n  на роутерах ipv4). На системах c iptables без masquerade и на nftables работает без ограничений. Экспериментально\n  выяснено, что многие провайдерские NAT не отбрасывают эти пакеты, потому работает даже с внутренним провайдерским IP.\n  Но linux NAT оно не пройдет, так что за домашним роутером эта техника скорее всего не сработает, но может сработать с него.\n  Может сработать и через роутер, если подключение по проводу, и на роутере включено аппаратное ускорение.\n* Манипуляция tcp флагами с помощью `--dpi-desync-tcp-flags-set` и `--dpi-desync-tcp-flags-unset`. Можно сделать инвалидное\n  сочетание флагов, которое сервер не примет, а DPI - примет. Например, установить SYN в фейках. Но это может работать не на всех серверах.\n  `datanoack` может быть заменен `--dpi-desync-tcp-flags-unset=ACK`.\n  Пакеты с инвалидными флагами могут отбрасываться, проходя через NAT.\n* `ts` прибавляет к значению TSval таймштампа tcp значение ts increment (по умолчанию -600000). Сервера отбрасывают пакеты\n  с TSval в определенных пределах. По практическим тестам инкремент должен быть где-то от -100 до -0x80000000.\n  timestamps генерирует клиентская ОС. В linux таймштампы включены по умолчанию, в windows выключены по умолчанию.\n  Можно включить через команду `netsh interface tcp set global timestamps=enabled`.\n  ts fooling требует, чтобы таймштампы были включены, иначе работать не будет. Включать надо на каждом клиентском устройстве.\n  TSecr оставляется без изменений. Так же требуется, чтобы сервер понимал timestamps, но это в большинстве случаев так.\n* `autottl`. Суть режима в автоматическом определении TTL, чтобы пакет почти наверняка прошел DPI и немного не дошел до\n  сервера (`--dpi-desync-autottl`). Или наоборот - TTL едва хватило, чтобы он все-таки дошел до сервера (см `--dup-autottl`, `--orig-autottl`).\n  Берутся базовые значения TTL 64,128,255, смотрится входящий пакет (да, требуется направить первый входящий пакет на nfqws !).\n  Вычисляется длина пути, прибавляется `delta`. delta может быть положительной или отрицательной.\n  Чтобы задать положительную дельту, нужно указать унарный знак **+** перед числом.\n  В случае его отсутствия или при наличии унарного знака **-** дельта считается отрицательной.\n  Если TTL вне диапазона min,max, то берутся значения min,max, чтобы вписаться в\n  диапазон. Если при этом дельта отрицательная и полученный TTL больше длины пути или дельта положительная и полученный TTL меньше длины пути,\n  то автоматизм не сработал и берутся фиксированные значения : `--dpi-desync-ttl`, `--orig-ttl`, `--dup-ttl`.\n  Техника позволяет решить вопрос, когда вся сеть перегорожена шлагбаумами (DPI, ТСПУ) везде где только\n  можно, включая магистралов. Но потенциально может давать сбои. Например, при асимметрии входящего и исходящего канала\n  до конкретного сервера. Некоторые сервера выдают нестандартный TTL (google), потому на них получается полная ерунда.\n  Если не учитывать подобные исключения, то на каких-то провайдерах эта техника будет работать неплохо, на других доставит больше проблем,\n  чем пользы. Где-то может потребоваться тюнинг параметров. Лучше использовать с дополнительным ограничителем.\n\nРежимы дурения могут сочетаться в любых комбинациях. `--dpi-desync-fooling` берет множество значений через запятую.\n\nВозможно задание множества фейков через повторение параметров `--dpi-desync-fake-???`, кроме `--dpi-desync-fake-syndata`.\nФейки будут отосланы в указанном порядке. `--dpi-desync-repeats` повторяет каждый отосланный фейк.\nИтоговый порядок будет такой : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....`\n\n### МОДИФИКАЦИЯ ФЕЙКОВ\n\nЛюбые tcp фейки отправляются с исходным sequence по умолчанию, даже если их несколько.\nЕсли задать `--dpi-desync-fake-tcp-mod=seq`, то несколько фейков будут отправлены с увеличением sequence number таким образом,\nкак будто они являются tcp сегментами одного фейка.\n\nВ nfqws зашит базовый вариант фейка для TLS. Его можно переопределить опцией `--dpi-desync-fake-tls`.\nПереопределение фейков дает возможность использовать любые данные в качестве фейка для TLS.\nМожно использовать фейковый Client Hello с любым фингерпринтом и с любым SNI.\n\nНекоторые модификации можно делать в процессе выполнения с помощью `--dpi-desync-fake-tls-mod`.\nЧасть из них работает при обработке каждого TLS Client Hello и может подстраиваться под отправляемые данные.\nМодификации требуют наличия полного валидного TLS Client Hello в качестве фейка, они не работают с произвольными данными.\n\n * `none`. Не применять никакие модификации.\n * `rnd`. Рандомизировать поля `random` и `session id`. Выполняется на каждый запрос.\n * `dupsid`. Копировать `session ID` из передаваемого TLS Client Hello. Имеет приоритет над `rnd`. Выполняется на каждый запрос.\n * `rndsni`. Рандомизировать SNI. Если SNI >=7 символов, применяется случайный домен 2 уровня с известным TLD, иначе заполняется случайными символами без точки. Выполняется один раз при старте.\n * `sni=<sni>`. Заменить sni на указанное значение. Макс длина SNI - 63 байта. Общая длина TLS фейка и длины в структуре TLS Client Hello меняются. Выполняется один раз при старте. Если сочетается с `rndsni`, выполняется до него.\n * `padencap`. Расширяется padding extension на размер передаваемого TLS Client Hello (включая многопакетный вариант с kyber). Если padding отсутствует, он добавляется в конец. Если присутствует - требуется, чтобы padding шел последним extension. Правятся все длины, чтобы создать видимость включения передаваемого TLS Client Hello в padding extension. Размер фейка не изменяется. Расчет идет на DPI, который не анализирует sequence numbers должным образом. Выполняется на каждый запрос.\n\nПо умолчанию если не задан собственный фейк для TLS используются модификации `rnd,rndsni,dupsid`. Если фейк задан, используется `none`.\nЭто соответствует поведению программы более старых версий с добавлением функции `dupsid`.\n\nЕсли задан режим модификации и имеется множество TLS фейков, к каждому из них применяется последний режим модификации.\nЕсли режим модификации задан после фейка, то он замещает предыдущий режим.\nТаким образом можно использовать разные режимы модификации для разных фейков.\nПри невозможности модифицировать фейк на этапе запуска программа завершается с ошибкой.\n\nЕсли сначала идет TLS фейк, для него задан режим однократной модификации, затем идет не TLS фейк, то будет ошибка.\nНужно использовать `--dpi-desync-fake-tls-mod=none'.\n\nПример : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none'\n\n### TCP СЕГМЕНТАЦИЯ\n\n * `multisplit`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях.\n * `multidisorder`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях и отправляем в обратном порядке.\n * `fakedsplit`. различные варианты замешивания фейков и оригиналов в прямом порядке\n * `fakeddisorder`. различные варианты замешивания фейков и оригиналов в обратном порядке\n * `hostfakesplit` (altorder=0). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал хоста (+ опционально нарезка маркером midhost), фейк хоста, оригинал после хоста\n * `hostfakesplit` (altorder=1). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал после хоста, оригинал хоста (+опционально нарезка маркером midhost)\n * `fakeddisorder`. аналогично `fakedsplit`, только в обратном порядке : фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1 части.\n\nДля `fakedsplit` и `fakeddisorder` предусмотрены вариации порядка следования сегментов.\nПараметр `--dpi-desync-fakedsplit-mod=altorder=N` задает число, влияющее на наличие отдельных фейков :\n\nРежимы altorder для `fakedsplit` для части многопакетного запроса, где есть сплит-позиция :\n * `altorder=0`. фейк 1-й части, 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части\n * `altorder=1`. 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части\n * `altorder=2`. 1 часть, фейк 2-й части, 2 часть, фейк 2-й части\n * `altorder=3`. 1 часть, фейк 2-й части, 2 часть\n\nРежимы altorder для `fakeddisorder` для части многопакетного запроса, где есть сплит-позиция :\n * `altorder=0`. фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части\n * `altorder=1`. 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части\n * `altorder=2`. 2 часть, фейк 1-й части, 1 часть, фейк 1-й части\n * `altorder=3`. 2 часть, фейк 1-й части, 1 часть\n\nРежимы altorder для `fakedsplit` и `fakeddisorder` для части многопакетного запроса, где нет сплит-позиции :\n * `altorder=0`. фейк, оригинал, фейк\n * `altorder=8`. оригинал, фейк\n * `altorder=16`. оригинал\n\nИтоговое число `altorder=N` вычисляется как сумма чисел из этих двух групп. По умолчанию `altorder=0`.\n\nСодержимое фейков в `fakedsplit`/`fakeddisorder` определяется параметром `--dpi-desync-fakedsplit-pattern` (по умолчанию 0x00).\nДанные фейков берутся из паттерна со смещением, соответствующим смещению отсылаемых частей, учитывая смещения пакетов в многопакетных запросах.\nРазмеры фейков соответствуют длинам отсылаемых частей.\nЦель этих режимов - максимально усложнить выявление оригинальных данных среди фейков.\n\nИспользование `fakedsplit` или `fakeddisorder` на TLS kyber с md5sig fooling может привести к ошибкам \"message too long\", если позиция сплита мала,\nпоскольку будет превышение MTU из-за md5 tcp option.\n\nРежим 'hostfakesplit' имеет задачу минимального вмешательства фейком - как раз по той части запроса, на основании которой DPI принимает решение о блокировке. Конкретно - имени хоста.\nПо умолчанию фейк хоста генерируется каждый раз случайно из набора `[a-z0-9]`. При длине более 7 символов за 3 символа до конца ставится точка, имитируя TLD, а последние 3 символа заполняются одним из нескольких известных TLD.\n\nМожно переопределить шаблон генерации с помощью `--dpi-desync-hostfakesplit-mod=host=<hostname>`. В последнем случае справа всегда будет указанный hostname.\nСлева он будет дополнен до размера оригинального хоста как поддомен со случайными символами. Пример : \"www.networksolutions.com\" -> \"h8xmdba4tv7a8.google.com\".\nЕсли размер оригинального хоста меньше шаблона, шаблон будет порезан : \"habr.com\" -> \"ogle.com\".\nЕсли размер оригинального хоста больше шаблона на 1, получится инвалидный пустой поддомен : \"www.xxx.com\" => \".google.com\".\nПоэтому стоит использовать максимально короткие хосты из разрешенных : \"ya.ru\", \"vk.com\".\n\n`--dpi-desync-hostfakesplit-mod=altorder=1` позволяет сменить порядок следования частей на альтернативный вариант.\n`altorder=1` шлет фрагменты в таком порядке, чтобы при последовательной сборке сегментов на DPI он получил полностью собранный оригинал запроса с подмененным хостом.\nРеальный хост идет отдельным сегментом уже после. То есть в этом варианте применяется разновидность disorder. Сервер принимает фрагменты с нарушенным порядком sequence.\n\nОпционально можно разрезать оригинальный хост. Например, `--dpi-desync-hostfakesplit-midhost=midsld`. Позиция нарезки должна попадать внутрь хоста.\nМногопакетные запросы поддерживаются только, если исходная нарезка пакетов не включает позиции имени хоста. В последнем случае дурение отменяется.\n\nВариант `fakedsplit` имеет несколько альтернативных порядков нарезки - от 0 до 3. Режим задается в параметре `--dpi-desync-fakedsplit-mod=altorder=N`.\nКаждый следующий altorder убирает часть фейков.\n\nДля определения позиций нарезки используются маркеры.\n\n* **Абсолютный положительный маркер** - числовое смещение внутри пакета или группы пакетов от начала.\n* **Абсолютный отрицательный маркер** - числовое смещение внутри пакета или группы пакетов от следующего за концом байта. -1 указывает на последний байт.\n* **Относительный маркер** - положительное или отрицательное смещение относительно логической позиции внутри пакета или группы пакетов.\n\nОтносительные позиции :\n\n* **method** - начало метода HTTP ('GET', 'POST', 'HEAD', ...). Метод обычно всегда находится на позиции 0, но может сместиться из-за `--methodeol`. Тогда позиция может стать 1 или 2.\n* **host** - начало имени хоста в известном протоколе (http, TLS)\n* **endhost** - байт, следующий за последним байтом имени хоста\n* **sld** - начало домена 2 уровня в имени хоста\n* **endsld** - байт, следующий за последним байтом домена 2 уровня в имени хоста\n* **midsld** - середина домена 2 уровня в имени хоста\n* **sniext** - начало поля данных SNI extension в TLS. Любой extension состоит из 2-байтовых полей type и length, за ними идет поле данных.\n\nПример списка маркеров : `100,midsld,sniext+1,endhost-2,-10`.\n\nПри разбиении пакета первым делом происходит ресолвинг маркеров - нахождение всех указанных относительных позиций и применение смещений.\nЕсли относительная позиция отсутствует в текущем протоколе, такие позиции не применяются и отбрасываются.\nДальше происходит нормализация позиций относительно смещения текущего пакета в группе пакетов (многопакетные запросы TLS с kyber, например).\nВыкидываются все позиции, выходящие за пределы текущего пакета. Оставшиеся сортируются в порядке возрастания и удаляются дубли.\nВ вариантах `multisplit` и `multidisorder` если не осталось ни одной позиции, разбиение не происходит.\n\nВарианты `fakedsplit` и `fakeddisorder` применяют только одну позицию сплита. Ее поиск среди списка `--dpi-desync-split-pos` осуществляется особым образом.\nСначала сверяются все относительные маркеры. Если среди них найден подходящий, применяется он. В противном случае сверяются все абсолютные маркеры.\nЕсли и среди них ничего не найдено, применяется позиция 1.\n\nНапример, можно написать `--dpi-desync-split-pos=method+2,midsld,5`. Если протокол http, разбиение будет на позиции `method+2`.\nЕсли протокол TLS - на позиции `midsld`. Если протокол неизвестен и включено `--dpi-desync-any-protocol`, разбиение будет на позиции 5.\nЧтобы все было однозначнее, можно использовать разные профили для разных протоколов и указывать только одну позицию, которая точно есть в этом протоколе.\n\n### ПЕРЕКРЫТИЕ SEQUENCE NUMBERS\n\n`seqovl` добавляет в начало одного из TCP сегментов `seqovl` байт со смещенным в минус sequence number на величину `seqovl`.\nДля `split` - в начало первого сегмента, для `disorder` - в начало предпоследнего отсылаемого сегмента (второго в оригинальном порядке следования).\n\nВ случае `split` расчет идет на то, что предыдущий отсыл, если он был, уже попал в сокет серверного приложения, поэтому новая пришедшая часть лишь частично находится в\nпределах текущего окна (in-window). Спереди фейковая часть отбрасывается, а оставшаяся часть содержит оригинал и\nначинается с начала window, поэтому попадает в сокет. Серверное приложение получает все, что реально отсылает клиент,\nотбрасывая фейковую out-of-window часть. Но DPI не может этого понять, поэтому у него происходит sequence десинхронизация.\nОбязательно, чтобы первый сегмент вместе с `seqovl` не превысили длину MTU. Эта ситуация распознается автоматически в Linux, и `seqovl` отменяется.\nВ остальных системах ситуация не распознается, и это приведет к поломке соединения. Поэтому выбирайте первую позицию сплита и `seqovl` таким образом, чтобы MTU не был превышен в любом случае.\nИначе дурение может не работать или работать хаотично.\n\nДля `disorder` overlap идет на предпоследнюю отсылаемую часть пакета. \nДля простоты будем считать, что разбиение идет на 2 части, шлются они в порядке \"2 1\" при оригинальном порядке \"1 2\".\nОбязательно, чтобы `seqovl` был меньше позиции первого сплита, иначе все отосланное будет передано в сокет сразу же, включая фейк, ломая протокол прикладного уровня.\nТакая ситуация легко обнаруживается программой, и `seqovl` отменяется. Увеличение размера пакета невозможно в принципе.\nПри соблюдении условия 2-я часть пакета является полностью in-window, поэтому серверная ОС принимает ее целиком, включая фейк.\nНо поскольку начальная часть данных из 1 пакета еще не принята, то фейк и реальные данные остаются в памяти ядра, не отправляясь в серверное приложение.\nКак только приходит 1-я часть пакета, она переписывает фейковую часть в памяти ядра.\nЯдро получает данные из 1 и 2 части, поэтому далее идет отправка в сокет приложения.\nТаково поведение всех unix ОС, кроме solaris - оставлять последние принятые данные.\nWindows оставляет старые данные, поэтому disorder с seqovl будет приводить к зависаниям соединения\nпри работе с Windows серверами. Solaris практически мертв, windows серверов очень немного.\nМожно использовать листы при необходимости.\nМетод позволяет обойтись без fooling и TTL. Фейки перемешаны с реальным данными.\n`fakedsplit/fakeddisorder` по-прежнему добавляют дополнительные отдельные фейки.\n\n`seqovl` в варианте `split` может быть только абсолютным положительным значением, поскольку применяется только в первому пакету.\nВ варианте `disorder` допустимо применение всех вариантов маркеров.\nОни автоматически нормализуются к текущему пакету в серии. Можно сплитать на `midsld` и делать seqovl на `midsld-1`.\n\n### НАЗНАЧЕНИЕ IP_ID\n\nНекоторые DPI секут поле ipv4 заголовка ip_id. Защита заключается в распознавании нехарактерного для разных ОС порядка назначения ip_id,\nно характерного для некоторого anti-DPI софта. Обычно ОС инкрементируют ip_id для каждого следующего пакета.\nНапример, на ТСПУ повторение ненулевых ip_id фейка и не фейка вызывает триггер блока на диапазонах IP `googlevideo.com`.\n\nЕсли отсылаются фейки или дополнительные tcp сегменты, то в любом случае последовательность будет нарушена, поскольку ОС ничего не будет знать о всунутых фейках\nи не увеличит свой счетчик ip_id на количество фейков или дополнительных tcp сегментов.\nЧтобы сохранить последовательность, потребовалось бы перехватывать все соединение до конца, что очень затратно по ресурсам.\nПоэтому после отработки серии генерированных пакетов ip_id возвращается к тому значению, о котором знает ОС.\n\nПараметр `ip-id` относится к профилю и задает режим назначения ip_id при отсылке генерированных в nfqws пакетов.\n\n * `seq` (по умолчанию) : взять последний ip_id реального пакета. последующие генерированыне пакеты получают увеличенные на 1 ip_id, кроме случая `multidisorder`.\n для `multidisorder` в пределах сегментов, где есть сплит-позиции, значение ip_id увеличивается на количество частей, затем уменьшается на 1 с каждой отосланной частью.\n * `seqgroup` : то же, что и `seq`, но фейки того же размера, что и оригинальные сегменты, маскирующиеся под оригинал получают те же ip_id.\n * `rnd` : всем генерированным пакетам назначать случайный ip_id\n * `zero` : всем генерированным пакетам назначать ip_id=0 . в этом случае Linux и BSD отошлют 0, Windows назначит последовательные ip_id всем пакетам (тем самым автоматически решается проблема сбоя счетчика пакетов).\n\nВ заголовках ipv6 поле ip_id отсутствует, параметр игнорируется для ipv6.\n\n### СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6\n\nРежимы десинхронизации `hopbyhop`, `destopt` и `ipfrag1` (не путать с fooling !) относятся только к ipv6 и заключается\nв добавлении хедера `hop-by-hop options`, `destination options` или `fragment` во все пакеты, попадающие под десинхронизацию.\nЗдесь надо обязательно понимать, что добавление хедера увеличивает размер пакета, потому не может быть применено\nк пакетам максимального размера. Это имеет место при передаче больших сообщений.\nВ случае невозможности отослать пакет дурение будет отменено, пакет будет выслан в оригинале.\nРасчет идет на то, что DPI увидит 0 в поле next header основного заголовка `ipv6` и не будет скакать по\nextension хедерам в поисках транспортного хедера. Таким образом не поймет, что это tcp или udp, и пропустит пакет\nбез анализа. Возможно, какие-то DPI на это купятся.\nМожет сочетаться с любыми режимами 2-й фазы, кроме варианта `ipfrag1+ipfrag2`.\nНапример, `hopbyhop,multisplit` означает разбить tcp пакет на несколько сегментов, в каждый из них добавить hop-by-hop.\nПри `hopbyhop,ipfrag2` последовательность хедеров будет : `ipv6,hop-by-hop`,`fragment`,`tcp/udp`.\nРежим `ipfrag1` может срабатывать не всегда без специальной подготовки. См. раздел `IP фрагментация`.\n\n### МОДИФИКАЦИЯ ОРИГИНАЛА\n\nПараметры `--orig-ttl` и `--orig-ttl6` позволяют изменить TTL оригинальных пакетов.\nЕсли дальнейшие манипуляции связаны с оригиналом, например, идет TCP сегментация, то исходными\nданными являются измененные оригинальные пакеты. То есть в данном примере TCP сегменты пойдут с измененным TTL.\n\nВариант `--orig-autottl` и `--orig-autottl6` работает аналогично `dpi-desync-autottl`, но по оригинальным пакетам.\nДельту стоит указывать положительную с унарным знаком `+`, иначе оригинал не дойдет до сервера, и вы вообще ничего не получите.\nПример : `--orig-autottl=+5:3-64`.\n\n`--orig-mod-start` и `--orig-mod-cutoff` задают ограничитель по началу и концу модификации оригинала.\nСхема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`.\n\nФункция может быть полезна, когда DPI охотится за фейками и блокирует соединение при наличии подозрительных признаков,\nв частности, измененный TTL у фейка относительно оригинала.\n\n### ДУБЛИКАТЫ\n\nДубликаты - это копии оригинальных пакетов, высылаемые перед ними. Включаются параметром `--dup=N`, где N - количество дублей,\nне включающее оригинал. `--dup-replace` отключает отсылку оригинала.\n\nОтсылка дублей имеет место только в тех случаях, когда высылается и оригинал без реконструкции.\nНапример, если случилась TCP сегментация, то оригинал фактически дропается и заменяется искусственно сконструированными сегментами.\nДубли высланы не будут. Это же касается изменения состава хедеров ipv6, режима tamper для DHT и других.\n\nВозможно применение всех вариантов дурения, как и для desync : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`. Нужно ли, чтобы эти пакеты доходили до сервера и в каком виде, решаете вы согласно задуманной стратегии.\n\nВариант `--dup-autottl` и `--dup-autottl6` работает аналогично `dpi-desync-autottl`, но по дублям.\nДельту можно указывать положительную с унарным знаком `+`, а можно и отрицательную. Зависит от вашей задумки.\nПример : `--dup-autottl=-2:3-64`.\n\n`--dup-start` и `--dup-cutoff` задают ограничитель по началу и концу применения стратегии дубликатов.\nСхема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`.\n\nФункция может помочь, когда DPI сечет разницу в характеристиках фейков и оригинала.\nДубликатами можно попытаться заставить DPI принять , что весь сеанс идет аномальным.\nНапример, у нас имеется TCP сеанс с MD5 сразу с первого SYN пакета. Значит последующие MD5 будут восприниматься нормально.\n\n### КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ\n\nВ параметре dpi-desync можно указать до 3 режимов через запятую.\n\n* 0 фаза - предполагает работу на этапе установления соединения : `synack`, `syndata`, `--wsize`, `--wssize`. На эту фазу не действуют фильтры по [hostlist](#множественные-стратегии), кроме случая, описанного [далее](#кэш-ip).\n* 1 фаза - отсылка чего-либо до оригинального пакета данных : `fake`, `rst`, `rstack`.\n* 2 фаза - отсылка в модифицированном виде оригинального пакета данных (например, `fakedsplit` или `ipfrag2`).\n\nРежимы требуют указания в порядке возрастания номеров фаз.\n\n### КЭШ IP\n\nipcache представляет собой структуру в памяти процесса, позволяющую по ключу IP адреса и имени интерфейса запоминать некоторую информацию,\nкоторую впоследствии можно извлечь и использовать как недостающие данные. На текущий момент это применяются в следующих ситуациях :\n\n1. IP,interface => hop count . Кэшируется количество хопов до сервера для последующего применения в autottl прямо с первого пакета, когда еще ответа не было. Пока записи в кэше нет, autottl не будет применен сразу. При повторном запросе до истечения времени жизни записи autottl будет применение сразу.\n\n2. IP => hostname . Кэшируется имя хоста, вне привязки к интерфейсу, для последующего применения в стратегиях нулевой фазы. Режим отключен по умолчанию и включается через параметры `ipcache-hostname`.\nДанная техника является экспериментальной. Ее проблема в том, что как такового нет однозначного соответствия между доменом и IP. Множество доменов могут ссылаться на тот же IP адрес.\nПри коллизии происходит замещение имени хоста на последний вариант.\nДомен может скакать по разным IP на CDN. Сейчас один адрес, через час - другой. Эта проблема решается через время жизни записей кэша : `--ipcache-lifetime`. По умолчанию 2 часа.\nОднако, может случиться и так, что в вашем случае применение техники несет больше пользы, чем проблем. Будьте готовы к непонятному на первый взгляд поведению, которое может быть исследовано только через `--debug` лог.\n\nПри подаче сигнала SIGUSR2 процесс выводит содержимое ipcache на консоль.\n\n### РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА\n\nЕсть DPI, которые анализируют ответы от сервера, в частности сертификат из ServerHello, где прописаны домены.\nПодтверждением доставки ClientHello является ACK пакет от сервера с номером ACK sequence, соответствующим длине ClientHello+1.\nВ варианте disorder обычно приходит сперва частичное подтверждение (SACK), потом полный ACK.\nЕсли вместо ACK или SACK идет RST пакет с минимальной задержкой, то DPI вас отсекает еще на этапе вашего запроса.\nЕсли RST идет после полного ACK спустя задержку, равную примерно пингу до сервера,\nтогда вероятно DPI реагирует на ответ сервера.\nDPI может отстать от потока, если ClientHello его удовлетворил и не проверять ServerHello.\nТогда вам повезло. Вариант fake может сработать.\nЕсли же он не отстает и упорно проверяет ServerHello, то можно попробовать заставить сервер высылать ServerHello частями\nчерез параметр `--wssize` (см. conntrack).\nЕсли и это не помогает, то сделать с этим что-либо вряд ли возможно без помощи со стороны сервера.\nЛучшее решение - включить на сервере поддержку TLS 1.3. В нем сертификат сервера передается в зашифрованном виде.\nЭто рекомендация ко всем админам блокируемых сайтов. Включайте TLS 1.3. Так вы дадите больше возможностей преодолеть DPI.\n\n### РЕЖИМ SYNACK\n\nВ документации по geneva это называется \"TCB turnaround\". Попытка ввести DPI в заблуждение относительно\nролей клиента и сервера.\n\nПоскольку режим нарушает работу NAT, техника может сработать только если между атакующим устройством\nи DPI нет NAT. Атака не сработает через NAT роутер, но может сработать с него.\nДля реализации атаки на проходящий трафик требуются nftables и схема [POSTNAT](#nftables-для-nfqws).\n\n### РЕЖИМ SYNDATA\n\nТут все просто. Добавляются данные в пакет SYN. Все ОС их игнорируют, если не используется TCP fast open (TFO),\nа DPI может воспринять, не разобравшись есть там TFO или нет.\nОригинальные соединения с TFO не трогаются, поскольку это их точно сломает.\nБез уточняющего параметра добавляются 16 нулевых байтов.\n\n### ВИРТУАЛЬНЫЕ МАШИНЫ\n\nИзнутри VM от virtualbox и vmware в режиме NAT не работают многие техники пакетной магии nfqws.\nПринудительно заменяется ttl, не проходят фейк пакеты. Необходимо настроить сеть в режиме bridge.\n\n### CONNTRACK\n\nnfqws оснащен ограниченной реализацией слежения за состоянием tcp соединений (conntrack).\nОн включается для реализации некоторых методов противодействия DPI.\nconntrack способен следить за фазой соединения : SYN,ESTABLISHED,FIN, количеством пакетов в каждую сторону,\nsequence numbers. conntrack способен \"кормиться\" пакетами в обе или только в одну сторону.\nСоединение попадает в таблицу при обнаружении пакетов с выставленными флагами SYN или SYN,ACK.\nПоэтому если необходим conntrack, в правилах перенаправления iptables соединение должно идти на nfqws с самого первого\nпакета, хотя затем может обрываться по фильтру connbytes.\nДля UDP инициатором попадания в таблицу является первый UDP пакет. Он же и определяет направление потока.\nСчитается, что первый UDP пакет исходит от клиента к серверу. Далее все пакеты с совпадающими\n`src_ip,src_port,dst_ip,dst_port` считаются принадлежащими этому потоку до истечения времени неактивности.\nconntrack - простенький, он не писался с учетом всевозможных атак на соединение, он не проверяет\nпакеты на валидность sequence numbers или чексумму. Его задача - лишь обслуживание нужд nfqws, он обычно\nкормится только исходящим трафиком, потому нечувствителен к подменам со стороны внешней сети.\nСоединение удаляется из таблицы, как только отпадает нужда в слежении за ним или по таймауту неактивности.\nСуществуют отдельные таймауты на каждую фазу соединения. Они могут быть изменены параметром `--ctrack-timeouts`.\n\n`--wssize` позволяет изменить с клиента размер tcp window для сервера, чтобы он послал следующие ответы разбитыми на части.\nЧтобы это подействовало на все серверные ОС, необходимо менять window size в каждом исходящем с клиента пакете до отсылки сообщения,\nответ на которое должен быть разбит (например, TLS ClientHello). Именно поэтому и необходим conntrack, чтобы\nзнать когда надо остановиться. Если не остановиться и все время устанавливать низкий wssize, скорость упадет катастрофически.\nВ linux это может быть купировано через connbytes, но в BSD системах такой возможности нет.\nВ случае http(s) останавливаемся сразу после отсылки первого http запроса или TLS ClientHello.\nЕсли вы имеете дело с не http(s), то вам потребуется параметр `--wssize-cutoff`. Он устанавливает предел, с которого действие\nwssize прекращается. Префикс d перед номером означает учитывать только пакеты с data payload, префикс s - relative sequence number,\nпроще говоря количество переданных клиентом байтов + 1.\nЕсли проскочит пакет с http request или TLS ClientHello, действие wssize прекращается сразу же, не дожидаясь wssize-cutoff,\nесли не указан параметр `--wssize-forced-cutoff=0`.\nЕсли ваш протокол склонен к долгому бездействию, следует увеличить таймаут фазы ESTABLISHED через параметр `--ctrack-timeouts`.\nТаймаут по умолчанию низкий - всего 5 минут.\nНе забывайте, что nfqws кормится приходящими на него пакетами. Если вы ограничили поступление пакетов через connbytes,\nто в таблице могут остаться повисшие соединения в фазе ESTABLISHED, которые отвалятся только по таймауту.\nДля диагностики состояния conntrack пошлите сигнал SIGUSR1 процессу nfqws : `killall -SIGUSR1 nfqws`.\nТекущая таблица будет выведена nfqws в stdout.\n\nОбычно в SYN пакете клиент отсылает кроме window size еще и TCP extension `scaling factor`.\n**scaling factor** представляет из себя степень двойки, на которую умножается window size : 0=>1, 1=>2, 2=>4, ..., 8=>256, ...\nВ параметре wssize scaling factor указывается через двоеточие.\nScaling factor может только снижаться, увеличение заблокировано, чтобы не допустить превышение размера окна со стороны сервера.\nДля принуждения сервера к фрагментации ServerHello, чтобы избежать просекание имени сервера из сертификата сервера на DPI,\nлучше всего использовать `--wssize=1:6`. Основное правило - делать `scale_factor` как можно больше, чтобы после восстановления\nwindow size итоговый размер окна стал максимально возможным. Если вы сделаете 64:0, будет очень медленно.\nС другой стороны нельзя допустить, чтобы ответ сервера стал достаточно большим, чтобы DPI нашел там искомое.\n\n`--wssize` не работает в профилях с хостлистами, поскольку он действует с самого начала соединения, когда еще нельзя\nпринять решение о попадании в лист. Однако, профиль с auto hostlist может содержать --wssize.\n`--wssize` может замедлять скорость и/или увеличивать время ответа сайтов, поэтому если есть другие работающие способы\nобхода DPI, лучше применять их.\n\n`--dpi-desync-cutoff` позволяет задать предел, при достижении которого прекращается применение dpi-desync.\nДоступны префиксы n,d,s по аналогии с `--wssize-cutoff`.\nПолезно совместно с `--dpi-desync-any-protocol=1`.\nНа склонных к бездействию соединениях следует изменить таймауты conntrack.\nЕсли соединение выпало из conntrack и задана опция `--dpi-desync-cutoff`, `dpi desync` применяться не будет.\n\n### РЕАССЕМБЛИНГ\n\nnfqws поддерживает реассемблинг некоторых видов запросов.\nНа текущий момент это TLS и QUIC ClientHello. Они бывают длинными, если в chrome включить пост-квантовую\nкриптографию tls-kyber, и занимают, как правило, 2 или 3 пакета. kyber включен по умолчанию, начиная с chromium 124.\nchrome рандомизирует фингерпринт TLS. SNI может оказаться как в начале, так и в конце, то есть\nпопасть в любой пакет. stateful DPI обычно реассемблирует запрос целиком, и только потом\nпринимает решение о блокировке.\nВ случае получения TLS или QUIC пакета с частичным ClientHello начинается процесс сборки, а пакеты\nзадерживаются и не отсылаются до ее окончания. По окончании сборки пакеты проходит через десинхронизацию\nна основании полностью собранного ClientHello.\nПри любой ошибке в процессе сборки задержанные пакеты немедленно отсылаются в сеть, а десинхронизация отменяется.\n\nЕсть специальная поддержка всех вариантов tcp сплита для многосегментного TLS.\nЕсли указать позицию сплита больше длины первого пакета, то разбивка происходит не обязательно первого пакета, а того,\nна который пришлась итоговая позиция.\nЕсли, допустим, клиент послал TLS ClientHello длиной 2000, SNI начинается с 1700,\nи заданы опции `fake,multisplit`, то перед первым пакетом идет fake, затем первый пакет в оригинале,\nа последний пакет разбивается на 2 сегмента. В итоге имеем фейк в начале и 3 реальных сегмента.\n\n### ПОДДЕРЖКА UDP\n\nАтаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip.\n\nДля UDP действуют только режимы десинхронизации `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`, `ipfrag2`, `udplen`, `tamper`.\nРежимами первой фазы являются `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`. Второй фазы - `ipfrag2`, `udplen`, `tamper`.\nКак обычно, возможно сочетание режимов первой и второй фазы, но не двух режимов одной фазы.\n\n`udplen` увеличивает размер udp пакета на указанное в `--dpi-desync-udplen-increment` количество байтов.\nПаддинг заполняется нулями по умолчанию, но можно задать свой паттерн.\nПредназначено для обмана DPI, ориентирующегося на размеры пакетов.\nМожет сработать, если пользовательский протокол не привязан жестко к размеру udp пейлоада.\nРежим tamper означает модификацию пакетов известных протоколов особенным для протокола образом.\nНа текущий момент работает только с DHT.\nПоддерживается определение пакетов QUIC Initial с расшифровкой содержимого и имени хоста, то есть параметр\n`--hostlist` будет работать.\nОпределяются пакеты wireguard handshake initiation, DHT (начинается с 'd1', кончается 'e'), STUN и\n[Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery).\nДля десинхронизации других протоколов обязательно указывать `--dpi-desync-any-protocol`.\nРеализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp\nможно изменить 4-м параметром в `--ctrack-timeouts`.\nАтака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов.\nПо умолчанию fake наполнение - 64 нуля. Можно указать файл в `--dpi-desync-fake-unknown-udp`.\n\n### IP ФРАГМЕНТАЦИЯ\n\nСовременная сеть практически не пропускает фрагментированные tcp на уровне ip.\nНа udp с этим дело получше, поскольку некоторые udp протоколы могут опираться на этот механизм (IKE старых версий).\nОднако, кое-где бывает, что режут и фрагментированный udp.\nРоутеры на базе linux могут самопроизвольно собирать или перефрагментировать пакеты.\nПозиция фрагментации задается отдельно для tcp и udp. По умолчанию 24 и 8 соответственно, должна быть кратна 8.\nСмещение считается с транспортного заголовка.\n\nСуществует ряд моментов вокруг работы с фрагментами на Linux, без понимания которых может ничего не получиться.\n\nipv4 : Linux дает отсылать ipv4 фрагменты, но стандартные настройки iptables в цепочке OUTPUT могут вызывать ошибки отправки.\n\nipv6 : Нет способа для приложения гарантированно отослать фрагменты без дефрагментации в conntrack.\nНа разных системах получается по-разному. Где-то нормально уходят, где-то пакеты дефрагментируются.\nДля ядер <4.16 похоже, что нет иного способа решить эту проблему, кроме как выгрузить модуль `nf_conntrack`,\nкоторый подтягивает зависимость `nf_defrag_ipv6`. Он то как раз и выполняет дефрагментацию.\nДля ядер 4.16+ ситуация чуть лучше. Из дефрагментации исключаются пакеты в состоянии NOTRACK.\nЧтобы не загромождать описание, смотрите пример решения этой проблемы в `blockcheck.sh`.\n\nИногда требуется подгружать модуль `ip6table_raw` с параметром `raw_before_defrag=1`.\nВ OpenWrt параметры модулей указываются через пробел после их названий в файлах `/etc/modules.d`.\nВ традиционных системах посмотрите используется ли `iptables-legacy` или `iptables-nft`. Если legacy, то нужно создать файл\n`/etc/modprobe.d/ip6table_raw.conf` с содержимым :\n```\noptions ip6table_raw raw_before_defrag=1\n```\nВ некоторых традиционных дистрибутивах можно изменить текущий ip6tables через : update-alternatives --config ip6tables\nЕсли вы хотите оставаться на iptables-nft, вам придется пересобрать патченную версию. Патч совсем небольшой.\nВ `nft.c` найдите фрагмент:\n```\n        {\n            .name\t= \"PREROUTING\",\n            .type\t= \"filter\",\n            .prio\t= -300,\t/* NF_IP_PRI_RAW */\n            .hook\t= NF_INET_PRE_ROUTING,\n        },\n        {\n            .name\t= \"OUTPUT\",\n            .type\t= \"filter\",\n            .prio\t= -300,\t/* NF_IP_PRI_RAW */\n            .hook\t= NF_INET_LOCAL_OUT,\n        },\n```\nи замените везде -300 на -450.\n\nЭто нужно сделать вручную, никакой автоматики в `blockcheck.sh` нет.\n\nЛибо можно раз и навсегда избавиться от этой проблемы, используя `nftables`. Там можно создать `netfilter hook`\nс любым приоритетом. Используйте приоритет -401 и ниже.\n\nПри использовании iptables и NAT, похоже, что нет способа прицепить обработчик очереди после NAT.\nПакет попадает в nfqws с source адресом внутренней сети, затем фрагментируется и уже не обрабатывается NAT.\nТак и уходит во внешнюю сеть с src ip 192.168.x.x. Следовательно, метод не срабатывает.\nВидимо единственный рабочий метод - отказаться от iptables и использовать nftables.\nХук должен быть с приоритетом 101 или выше.\n\n### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ\n\n**nfqws** способен по-разному реагировать на различные запросы и применять разные стратегии дурения.\nЭто реализовано посредством поддержки множества профилей дурения.\nПрофили разделяются в командной строке параметром `--new`. Первый профиль создается автоматически.\nДля него не нужно `--new`. Каждый профиль имеет фильтр. По умолчанию он пуст, то есть профиль удовлетворяет\nлюбым условиям.\nФильтр может содержать жесткие параметры: версия ip протокола, ipset и порты tcp/udp.\nОни всегда однозначно идентифицируются даже на нулевой фазе десинхронизации, когда еще хост и L7 неизвестны.\nВ качестве мягкого фильтра могут выступать хост-листы и протокол прикладного уровня (l7).\nL7 протокол становится известен обычно после первого пакета с данными.\nПри поступлении запроса идет проверка профилей в порядке от первого до последнего до\nдостижения первого совпадения с фильтром.\nЖесткие параметры фильтра сверяются первыми. При несовпадении идет сразу же переход к следующему профилю.\nЕсли какой-то профиль удовлетворяет жесткому фильтру и L7 фильтру и содержит авто-хостлист, он выбирается сразу.\nЕсли профиль удовлетворяет жесткому фильтру и L7 фильтру, для него задан хостлист, и у нас еще нет имени хоста,\nидет переход к следующему профилю. В противном случае идет проверка по хостлистам этого профиля.\nЕсли имя хоста удовлетворяет листам, выбирается этот профиль. Иначе идет переход к следующему.\nМожет так случиться, что до получения имени хоста или узнавания L7 протокола соединение идет по одному профилю,\nа при выяснении этих параметров профиль меняется на лету. Это может произойти даже дважды - при выяснении L7\nи имени хоста. Чаще всего это выяснение совмещается в одно действие, поскольку по одному пакету, как правило, узнается и L7, и хост.\nПоэтому если у вас есть параметры дурения нулевой фазы, тщательно продумывайте что может произойти при переключении стратегии.\nСмотрите debug log, чтобы лучше понять что делает nfqws.\nНумерация профилей идет с 1 до N. Последним в цепочке создается пустой профиль с номером 0.\nОн используется, когда никакие условия фильтров не совпали.\n\n> [!IMPORTANT]\n> Множественные стратегии создавались только для случаев, когда невозможно обьединить\n> имеющиеся стратегии для разных ресурсов. Копирование стратегий из blockcheck для разных сайтов\n> во множество профилей без понимания как они работают приведет к нагромождению параметров, которые все равно\n> не покроют все возможные заблокированные ресурсы. Вы только увязните в этой каше.\n\n> [!IMPORTANT]\n> user-mode реализация ipset создавалась не как удобная замена *nix версии, реализованной в ядре.\n> Вариант в ядре работает гораздо эффективнее. Это создавалось для систем без поддержки ipset в ядре.\n> Конкретно - Windows и ядра Linux, собранные без nftables и ipset модулей ядра. Например, в android нет ipset.\n\n### ФИЛЬТРАЦИЯ ПО WIFI\n\nИмя wifi сети никак не связано с сетевым интерфейсом адаптера wifi.\nИнтерфейс один, подключиться можно к любой сети. Для разных сетей разные стратегии.\nСтратегия от сети A не работает или ломает сеть B. Что делать ?\n\nМожно вручную запускать и снимать инстансы nfqws. Но можно поступить иначе.\nВ windows версии winws есть глобальный фильтр `--ssid-filter`.\nОн включает или отключает инстанс winws в зависимости от подключенности любого адаптера к конкретной wifi сети.\nПри этом не учитывается маршрутизация. Такой подход возможен потому, что к windivert можно прицепить несколько инстансов winws на пересекающихся фильтрах.\nПри смене wifi сети одни будут включаться, другие выключаться.\n\nДля linux применяется иное решение. Фильтр `--filter-ssid` относится к конкретному профилю.\nНевозможно повесить несколько инстансов nfqws на одну и ту же очередь или направить один и тот же трафик на несколько очередей.\nПодключение и отключение от очереди разных инстансов сопряжено со сложностями синхронизации между ними.\nПоэтому обрабатывать трафик должен один инстанс, и он должен уметь работать с разными wifi сетями.\nЭто и реализовано в параметре `--filter-ssid`. Он берет список имен wifi сетей (SSID) через запятую аналогично `--ssid-filter` для winws.\nПри выборе профиля имеет значение куда идет конкретный обрабатываемый пакет. На какой интерфейс. Или с какого интерфейса пакет пришел, если он считается входящим.\nПоэтому даже если у вас часть трафика идет на одну сеть, часть на другую, а часть вообще не идет по wifi, то все это можно настроить.\n\nСканируются все wifi интерфейсы, составляется список interface->SSID. Он обновляется по мере поступления\nпакетов, но не чаще 1 раза в секунду.\n\n### IPTABLES ДЛЯ NFQWS\n\n> [!CAUTION]\n> Начиная с ядер Linux 6.17 присутствует параметр конфигурации ядра CONFIG_NETFILTER_XTABLES_LEGACY, который по умолчанию в дистрибутиве может быть \"not set\". Отсутствие этой настройки выключает iptables-legacy. Это часть процесса депрекации iptables. Тем не менее iptables-nft будут работать, поскольку используют backend nftables.\n\niptables для задействования атаки на первые пакеты данных в tcp соединении :\n\n```\niptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\nЭтот вариант применяем, когда DPI не следит за всеми запросами http внутри keep-alive сессии.\nЕсли следит, направляем только первый пакет от https и все пакеты от http :\n\n```\niptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\niptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\nmark нужен, чтобы сгенерированный поддельный пакет не попал опять к нам на обработку. nfqws выставляет fwmark при его отсылке.\nХотя nfqws способен самостоятельно различать помеченные пакеты, фильтр в iptables по mark нужен при использовании connbytes,\nчтобы не допустить изменения порядка следования пакетов. Процессинг очереди - процесс отложенный.\nЕсли ядро имеет пакеты на отсылку вне очереди - оно их отправляет незамедлительно.\nИзменение правильного порядка следования пакетов при десинхронизации ломает всю идею.\nТак же были замечены дедлоки при достаточно большой отсылке пакетов из nfqws и отсутствии mark фильтра.\nПроцесс может зависнуть. Поэтому наличие фильтра по mark в ip/nf tables можно считать обязательным.\n\nПочему `--connbytes 1:6` :\n* 1 - для работы методов десинхронизации 0-й фазы и корректной работы conntrack\n* 2 - иногда данные идут в 3-м пакете 3-way handshake\n* 3 - стандартная ситуация приема одного пакета запроса\n* 4-6 - на случай ретрансмиссии или запроса длиной в несколько пакетов (TLSClientHello с kyber, например)\n\nДля режима autottl необходимо перенаправление входящего `SYN,ACK` пакета или первого пакета соединения (что обычно есть то же самое).\nДля режима autohostlist необходимы входящие RST и http redirect.\nМожно построить фильтр на tcp flags для выделения `SYN,ACK` и модуле u32 для поиска характерных паттернов http redirect,\nно проще использовать connbytes для выделения нескольких начальных входящих пакетов.\n\n`\niptables -t mangle -I PREROUTING -i <внешний интерфейс> -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:3 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n`\n\nДля quic :\n\n```\niptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p udp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\n```\n\n6 пакетов берется, чтобы покрыть случаи возможных ретрансмиссий quic initial в случае плохой связи или если сервер плохо себя чувствует, а приложение настаивает именно на quic, не переходя на tcp.\nА так же для работы autohostlist по quic. Однако, autohostlist для quic не рекомендуется.\n\n### NFTABLES ДЛЯ NFQWS\n\nМожно начать с базовой конфигурации.\n\n```\nIFACE_WAN=wan\n\nnft create table inet ztest\n\nnft add chain inet ztest post \"{type filter hook postrouting priority mangle;}\"\nnft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport \"{80,443}\" ct original packets 1-6 queue num 200 bypass\nnft add rule inet ztest post oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass\n\n# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI\nsysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 \nnft add chain inet ztest pre \"{type filter hook prerouting priority filter;}\"\nnft add rule inet ztest pre iifname $IFACE_WAN tcp sport \"{80,443}\" ct reply packets 1-3 queue num 200 bypass\n```\n\nДля задействования IP фрагментации и `datanoack` на проходящие пакеты требуется особая конфигурация цепочек, перенаправляющая пакеты после NAT.\nВ скриптах zapret эта схема называется `POSTNAT`, и она возможна только на nftables.\nСгенерированные nfqws пакеты требуется на раннем этапе помечать как **notrack**, чтобы они не были испорчены NAT.\n\n```\nIFACE_WAN=wan\n\nnft create table inet ztest\n\nnft add chain inet ztest postnat \"{type filter hook postrouting priority srcnat+1;}\"\nnft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 tcp dport \"{80,443}\" ct original packets 1-6 queue num 200 bypass\nnft add rule inet ztest postnat oifname $IFACE_WAN meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass\n\nnft add chain inet ztest predefrag \"{type filter hook output priority -401;}\"\nnft add rule inet ztest predefrag \"mark & 0x40000000 != 0x00000000 notrack\"\n```\n\nУдаление тестовой таблицы :\n\n```\nnft delete table inet ztest\n```\n\n### FLOW OFFLOADING\n\nЕсли ваше устройство поддерживает аппаратное ускорение (flow offloading, hardware nat, hardware acceleration), то\niptables могут не работать. При включенном offloading пакет не проходит по обычному пути netfilter. Необходимо или его\nотключить, или выборочно им управлять.\n\nВ новых ядрах присутствует software flow offloading (SFO).\nПакеты, проходящие через SFO, так же проходят мимо большей части механизмов iptables. При включенном SFO работает\nDNAT/REDIRECT (tpws). Эти соединения исключаются из offloading. Однако, остальные соединения идут через SFO, потому\nNFQUEUE будет срабатывать только до помещения соединения в flowtable. Практически это означает, что почти весь функционал nfqws работать не будет.\nOffload включается через специальный target в iptables `FLOWOFFLOAD` или через flowtable в nftables.\n\nНе обязательно пропускать весь трафик через offload.\ntpws и так обходит offload \"by design\", а для отработки nfqws достаточно первых нескольких пакетов в tcp соединении или udp сеансе.\nПока сеанс не направлен на offload, он процессится обычным образом через полноценный netfilter.\nКак только срабатывает правило offload по любому входящему или исходящему пакету, весь сеанс окончательно уходит из netfilter в offload.\nПоэтому скрипты zapret берут правила для NFQUEUE, что они создали, и из них создают exemption правила, которые не дают раньше времени попасть сеансу в offload, а потом его \"отпускают\".\nПри этом входящим пакетам не дают начать offload, триггером выступают только исходящие пакеты.\nЭта схема обеспечивает практически нулевой негативный эффект на скорость, одновременно покрывая нужды nfqws и упрощая правила таблиц.\n\nOpenWrt не предусматривает выборочного управления offload, поэтому скрипты zapret поддерживают свою систему выборочного управления.\n\niptables target `FLOWOFFLOAD` - это проприетарное изобретение OpenWrt.\nУправление offload в nftables реализовано в базовом ядре linux без патчей.\nnftables - единственный способ включения offload на классическом Linux.\n\n### ОСОБЕННОСТИ ЖЕЛЕЗОК\n\nНа устройствах mediatek замечены 2 проблемы.\n\nДрайвер mediatek ethernet отбрасывает tcp и udp пакеты с неверной чексуммой на аппаратном уровне, это не отключается.\nКак следствие не будет работать fooling badsum через роутер, но будет с него.\n\nДругая проблема mediatek, затрагивающая как ethernet, так и wireless, проявляется на udp, когда включен offload rx-gro-list.\nПока отсутствует nfqueue, все хорошо. Как только nfqueue появляется, часть пакетов выпадает.\nОсобенно заметно это проявляется на дурении QUIC с kyber.\n\n<details>\n  <summary><b>shell код лечения</b></summary>\n\n```\nappend_separator_list()\n{\n    # $1 - var name to receive result\n    # $2 - separator\n    # $3 - quoter\n    # $4,$5,... - elements\n    local _var=\"$1\" sep=\"$2\" quo=\"$3\" i\n\n    eval i=\"\\$$_var\"\n    shift; shift; shift\n    while [ -n \"$1\" ]; do\n        if [ -n \"$i\" ] ; then\n            i=\"$i$sep$quo$1$quo\"\n        else\n            i=\"$quo$1$quo\"\n        fi\n        shift\n    done\n    eval $_var=\"\\$i\"\n}\nresolve_lower_devices()\n{\n    # $1 - bridge interface name\n    [ -d \"/sys/class/net/$1\" ] && {\n        find \"/sys/class/net/$1\" -follow -maxdepth 1 -name \"lower_*\" |\n        {\n            local l lower lowers\n            while read lower; do\n                lower=\"$(basename \"$lower\")\"\n                l=\"${lower#lower_*}\"\n                [  \"$l\" != \"$lower\" ] && append_separator_list lowers ' ' '' \"$l\"\n            done\n            printf \"$lowers\"\n        }\n    }\n}\n\n# it breaks nfqueue\nlans=$(resolve_lower_devices br-lan)\nfor int in $lans; do\n    ethtool -K $int rx-gro-list off\ndone\n```\n</details>\n\nЭтот код нужно вызывать после вставания интерфейса LAN, когда все bridge members уже занесены в bridge.\nМожно использовать хук в `/etc/hotplug.d/iface`. Должен быть установлен `ethtool`.\n\nПроблемы mediatek были подтверждены на MT7621 (TP-Link Archer C6U v1) и MT7981 (Xiaomi AX3000T).\nДругие чипсеты могут быть так же подвержены проблеме, а могут и не быть. Более широкой статистики нет.\n\n\n### ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА\n\nЭто тоже возможно.\nnfqws рассчитан на атаку со стороны клиента, поэтому он распознает прямой и обратный трафик на основании роли в установлении tcp соединения.\nЕсли проходит SYN, то source IP - это клиент. Если проходит SYN,ACK , то source IP - это сервер.\nДля UDP клиентом считается source IP первого прошедшего пакета по двум связкам ip-port.\nНа сервере трафиком клиента будет считаться принятый трафик, а трафиком сервера - исходящий.\n\n`--wsize` работает в любом случае, он может использоваться как на клиенте, так и на сервере.\nОстальные техники работают только если nfqws считает трафик трафиком клиента.\nПоэтому для их применения по исходящему с сервера трафику conntrack нужно выключить параметром `--ctrack-disable`.\nЕсли пакет не найден в conntrack, по нему идет работа как по пакету клиента.\n\nБольшинство протоколов опознаваться не будет, потому что система их опознавания рассчитана на содержание пакетов от клиента.\nЧтобы задействовать техники типа `fake` или `multisplit` нужно использовать `--dpi-desync-any-protocol` с ограничителем connbytes или\nс ограничителем на основании содержания пакета или его заголовков.\nstart/cutoff недоступны, поскольку завязаны на conntrack.\n\nТехника `synack-split` позволяет разбить tcp сегмент SYN,ACK на отдельные части с SYN и с ACK.\nВ ответ на это клиент шлет SYN,ACK , что обычно характеризует сервер.\nУ некоторых DPI от этого может ломаться алгоритм, и они перестают блокировать запрещенный контент.\nЗдесь [подробное описание](https://nmap.org/misc/split-handshake.pdf) что есть split handshake.\n\nПеренаправление трафика обычно идет по номеру source портов и направлению original.\noriginal - это исходящий с системы трафик, reply - входящий.\n\n\n## tpws\n\ntpws - это transparent proxy.\n```\n@<config_file>|$<config_file>                     ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются.\n\n--debug=0|1|2|syslog|@<filename>                  ; 0,1,2 = логирование на косоль : 0=тихо, 1(default)=подробно, 2=отладка.\n--debug-level=0|1|2                               ; указать уровень логирования для syslog и @<filename>\n--dry-run                                         ; проверить опции командной строки и выйти. код 0 - успешная проверка.\n--version                                         ; вывести версию и выйти\n\n--daemon                                          ; демонизировать прогу\n--pidfile=<file>                                  ; сохранить PID в файл\n--user=<username>                                 ; менять uid процесса\n--uid=uid[:gid]                                   ; менять uid процесса\n--bind-addr                                       ; на каком адресе слушать. может быть ipv4 или ipv6 адрес\n                                                  ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan\n--bind-linklocal=no|unwanted|prefer|force         ; no : биндаться только на global ipv6\n                                                  ; unwanted (default) : предпочтительно global, если нет - LL\n                                                  ; prefer : предпочтительно LL, если нет - global\n                                                  ; force : биндаться только на LL\n--bind-iface4=<iface>                             ; слушать на первом ipv4 интерфейса iface\n--bind-iface6=<iface>                             ; слушать на первом ipv6 интерфейса iface\n--bind-wait-ifup=<sec>                            ; ждать до N секунд появления и поднятия интерфейса\n--bind-wait-ip=<sec>                              ; ждать до N секунд получения IP адреса (если задан --bind-wait-ifup - время идет после поднятия интерфейса)\n--bind-wait-ip-linklocal=<sec>\n                                                  ; имеет смысл только при задании --bind-wait-ip\n                                                  ; --bind-linklocal=unwanted\t: согласиться на LL после N секунд\n                                                  ; --bind-linklocal=prefer\t: согласиться на global address после N секунд\n--bind-wait-only                                  ; подождать все бинды и выйти. результат 0 в случае успеха, иначе не 0.\n--connect-bind-addr                               ; с какого адреса подключаться во внешнюю сеть. может быть ipv4 или ipv6 адрес\n                                                  ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan\n                                                  ; опция может повторяться для v4 и v6 адресов\n                                                  ; опция не отменяет правил маршрутизации ! выбор интерфейса определяется лишь правилами маршрутизации, кроме случая v6 link local.\n--socks                                           ; вместо прозрачного прокси реализовать socks4/5 proxy\n--no-resolve                                      ; запретить ресолвинг имен через socks5\n--resolve-threads                                 ; количество потоков ресолвера\n--port=<port>                                     ; на каком порту слушать\n--maxconn=<max_connections>                       ; максимальное количество соединений от клиентов к прокси\n--maxfiles=<max_open_files>                       ; макс количество файловых дескрипторов (setrlimit). мин требование (X*connections+16), где X=6 в tcp proxy mode, X=4 в режиме тамперинга.\n                                                  ; стоит сделать запас с коэффициентом как минимум 1.5. по умолчанию maxfiles (X*connections)*1.5+16\n--max-orphan-time=<sec>                           ; если вы запускаете через tpws торрент-клиент с множеством раздач, он пытается установить очень много исходящих соединений,\n                                                  ; большая часть из которых отваливается по таймауту (юзера сидят за NAT, firewall, ...)\n                                                  ; установление соединения в linux может длиться очень долго. локальный конец отвалился, перед этим послав блок данных,\n                                                  ; tpws ждет подключения удаленного конца, чтобы отослать ему этот блок, и зависает надолго.\n                                                  ; настройка позволяет сбрасывать такие подключения через N секунд, теряя блок данных. по умолчанию 5 сек. 0 означает отключить функцию\n                                                  ; эта функция не действует на успешно подключенные ранее соединения\n\n--local-rcvbuf=<bytes>                            ; SO_RCVBUF для соединений client-proxy\n--local-sndbuf=<bytes>                            ; SO_SNDBUF для соединений client-proxy\n--remote-rcvbuf=<bytes>                           ; SO_RCVBUF для соединений proxy-target\n--remote-sndbuf=<bytes>                           ; SO_SNDBUF для соединений proxy-target\n--nosplice                                        ; не использовать splice на linux системах\n--skip-nodelay                                    ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split.\n--local-tcp-user-timeout=<seconds>                ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение)\n--remote-tcp-user-timeout=<seconds>               ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение)\n--fix-seg=<int>                                   ; исправлять неудачи tcp сегментации ценой задержек для всех клиентов и замедления. ждать до N мс. по умолчанию 30 мс.\n--ipcache-lifetime=<int>                          ; время жизни записей кэша IP в секундах. 0 - без ограничений.\n--ipcache-hostname=[0|1]                          ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы\n\n--split-pos=N|-N|marker+N|marker-N                ; список через запятую маркеров для tcp сегментации\n--split-any-protocol                              ; применять сегментацию к любым пакетам. по умолчанию - только к известным протоколам (http, TLS)\n--disorder[=http|tls]                             ; путем манипуляций с сокетом вынуждает отправлять первым второй сегмент разделенного запроса\n--oob[=http|tls]                                  ; отправить байт out-of-band data (OOB) в конце первой части сплита\n--oob-data=<char>|0xHEX                           ; переопределить байт OOB. по умолчанию 0x00.\n--hostcase                                        ; менять регистр заголовка \"Host:\". по умолчанию на \"host:\".\n--hostspell=HoST                                  ; точное написание заголовка Host (можно \"HOST\" или \"HoSt\"). автоматом включает --hostcase\n--hostdot                                         ; добавление точки после имени хоста : \"Host: kinozal.tv.\"\n--hosttab                                         ; добавление табуляции после имени хоста : \"Host: kinozal.tv\\t\"\n--hostnospace                                     ; убрать пробел после \"Host:\"\n--hostpad=<bytes>                                 ; добавить паддинг-хедеров общей длиной <bytes> перед Host:\n--domcase                                         ; домен после Host: сделать таким : TeSt.cOm\n--methodspace                                     ; добавить пробел после метода : \"GET /\" => \"GET  /\"\n--methodeol                                       ; добавить перевод строки перед методом  : \"GET /\" => \"\\r\\nGET  /\"\n--unixeol                                         ; конвертировать 0D0A в 0A и использовать везде 0A\n--tlsrec=N|-N|marker+N|marker-N                   ; разбивка TLS ClientHello на 2 TLS records на указанной позиции. Минимальное смещение - 6.\n--mss=<int>                                       ; установить MSS для клиента. может заставить сервер разбивать ответы, но существенно снижает скорость\n--tamper-start=[n]<pos>                           ; начинать дурение только с указанной байтовой позиции или номера блока исходяшего потока (считается позиция начала принятого блока)\n--tamper-cutoff=[n]<pos>                          ; закончить дурение на указанной байтовой позиции или номере блока исходящего потока (считается позиция начала принятого блока)\n--hostlist=<filename>                             ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'.\n                                                  ; в файле должен быть хост на каждой строке.\n                                                  ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска.\n                                                  ; при изменении времени модификации файла он перечитывается автоматически по необходимости\n                                                  ; список может быть запакован в gzip. формат автоматически распознается и разжимается\n                                                  ; списков может быть множество. пустой общий лист = его отсутствие\n                                                  ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello.\n--hostlist-domains=<domain_list>                  ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов.\n--hostlist-exclude=<filename>                     ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам.\n--hostlist-exclude-domains=<domain_list>          ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов.\n--hostlist-auto=<filename>                        ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика)\n--hostlist-auto-fail-threshold=<int>              ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3)\n--hostlist-auto-fail-time=<int>                   ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60)\n--hostlist-auto-debug=<logfile>                   ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты.\n--new                                             ; начало новой стратегии (новый профиль)\n--skip                                            ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров.\n--filter-l3=ipv4|ipv6                             ; фильтр версии ip для текущей стратегии\n--filter-tcp=[~]port1[-port2]|*                   ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. поддерживается список через запятую.\n--filter-l7=[http|tls|quic|wireguard|dht|unknown] ; фильтр протокола L6-L7. поддерживается несколько значений через запятую.\n--ipset=<filename>                                ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая.\n--ipset-ip=<ip_list>                              ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей.\n--ipset-exclude=<filename>                        ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая.\n--ipset-exclude-ip=<ip_list>                      ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей.\n```\n\n### TCP СЕГМЕНТАЦИЯ В TPWS\n\ntpws, как и nfqws, поддерживает множественную сегментацию запросов. Сплит позиции задаются в `--split-pos`.\nУказываются маркеры через запятую. Описание маркеров см в разделе [nfqws](#tcp-сегментация).\n\nНа прикладном уровне в общем случае нет гарантированного средства заставить ядро выплюнуть\nблок данных, порезанным в определенном месте. ОС держит буфер отсылки (SNDBUF) у каждого сокета.\nЕсли у сокета включена опция TCP_NODELAY и буфер пуст, то каждый send приводит к отсылке\nотдельного ip пакета или группы пакетов, если блок не вмещается в один ip пакет.\nОднако, если в момент send уже имеется неотосланный буфер, то ОС присоединит данные к нему,\nникакой отсылки отдельным пакетом не будет. Но в этом случае и так нет никакой гарантии,\nчто какой-то блок сообщения пойдет в начале пакета, на что собственно и заточены DPI.\nРазбиение будет производиться согласно MSS, который зависит от MTU исходящего интерфейса.\nТаким образом DPI, смотрящие в начало поля данных TCP пакета, будут поломаны в любом случае.\nПротокол http относится к запрос-ответным протоколам. Новое сообщение посылается только тогда,\nкогда сервер получил запрос и полностью вернул ответ. Значит запрос фактически был не только отослан,\nно и принят другой стороной, а следовательно буфер отсылки пуст, и следующие 2 send приведут\nк отсылке сегментов данных разными ip пакетами.\n\nТаким образом tpws обеспечивает сплит только за счет раздельных вызовов send, и это обычно работает надежно,\nесли разбивать не на слишком много частей и не на слишком мелкие подряд следующие части.\nВ последнем случае Linux все же может обьединить некоторые части, что приведет к несоответствию реальной сегментации\nуказанным сплит позициям. Другие ОС в этом вопросе ведут себя более предсказуемо. Спонтанного обьединения замечено не было.\nПоэтому не стоит злоупотреблять сплитами и в особенности мелкими соседними пакетами.\n\nКак показывается практика, проблемы могут начаться , если количество сплитов более одного.\nНа каких-то системах наблюдался стабильный результат до 8 сплитов, на других проблемы уже начинались после 2 сплитов.\nОдин сплит работает стабильно, если не является частью массивной потоковой передачи.\nПри неудаче сегментации будет выводиться сообщение `WARNING ! segmentation failed`.\nЕсли вы его видите, это повод снизить количество сплит позиций.\nЕсли это не вариант, для ядер Linux >=4.6 есть параметр `--fix-seg`. Он позволяет подождать завершение отсылки перед отправкой следующей части.\nНо этот вариант ломает модель асинхронной обработки событий. Пока идет ожидание, все остальные соединения не обрабатываются\nи кратковременно подвисают. На практике это может быть совсем небольшое ожидание - менее 10 мс.\nВыполняется оно только , если происходит split, и в ожидании есть реальная необходимость.\nВ высоконагруженных системах данный вариант не рекомендуется. Но для домашнего использования может подойти, и вы эти задержки даже не заметите.\n\nЕсли вы пытаетесь сплитнуть массивную передачу с `--split-any-protocol`, когда информация поступает быстрее отсылки,\nто без `--fix-seg` ошибки сегментации будут сыпаться сплошным потоком.\nРабота по массивному потоку без ограничителей `--tamper-start` и `--tamper-cutoff` обычно лишена смысла.\n\ntpws работает на уровне сокетов, поэтому длинный запрос, не вмещающийся в 1 пакет (TLS с kyber), он получает целым блоком.\nНа каждую сплит часть он делает отдельный вызов `send()`. Но ОС не сможет отослать данные в одном пакете, если размер превысит MTU.\nВ случае слишком большого сегмента ОС дополнительно его порежет на более мелкие. Результат должен быть аналогичен nfqws.\n\n`--disorder` заставляет слать каждый 2-й пакет с TTL=1, начиная с первого.\nК серверу приходят все четные пакеты сразу. На остальные ОС делает ретрансмиссию, и они приходят потом.\nЭто само по себе создает дополнительную задержку (200 мс в linux для первой ретрансмиссии).\nИным способом сделать disorder в сокет варианте не представляется возможным.\nИтоговый порядок для 6 сегментов получается `2 4 6 1 3 5`.\n\n`--oob` высылает 1 байт out-of-band data после первого сплит сегмента. `oob` в каждом сегменте сплита показал себя ненадежным.\nСервер получает oob в сокет.\n\nСочетание `oob` и `disorder` возможно только в Linux. Остальные ОС не умеют с таким справляться. Флаг URG теряется при ретрансмиссиях.\nСервер получает oob в сокет. Сочетание этих параметров в ос, кроме Linux, вызывает ошибку на этапе запуска.\n\n### TLSREC\n\n`--tlsrec` позволяют внутри одного tcp сегмента разрезать TLS ClientHello на 2 TLS records. Можно использовать стандартный\nмеханизм маркеров для задания относительных позиций.\n\n`--tlsrec` ломает значительное количество сайтов. Криптобиблиотеки (openssl, ...) на оконечных http серверах\nбез проблем принимают разделенные tls сегменты, но мидлбоксы - не всегда. К мидлбоксам можно отнести CDN\nили системы ddos-защиты. Поэтому применение `--tlsrec` без ограничителей вряд ли целесообразно.\nВ РФ `--tlsrec` обычно не работает с TLS 1.2, потому что цензор парсит сертификат сервера из ServerHello.\nРаботает только с TLS 1.3, поскольку там эта информация шифруется.\nВпрочем, сейчас сайтов, не поддерживающих TLS 1.3, осталось немного.\n\n### MSS\n\n`--mss` устанавливает опцию сокета TCP_MAXSEG. Клиент выдает это значение в tcp опциях SYN пакета.\nСервер в ответ в SYN,ACK выдает свой MSS. На практике сервера обычно снижают размеры отсылаемых ими пакетов, но они\nвсе равно не вписываются в низкий MSS, указанный клиентом. Обычно чем больше указал клиент, тем больше\nшлет сервер. На TLS 1.2 если сервер разбил заброс так, чтобы домен из сертификата не попал в первый пакет,\nэто может обмануть DPI, секущий ответ сервера.\nСхема может значительно снизить скорость и сработать не на всех сайтах.\n\nС фильтром по hostlist совместимо только в [некоторых случаях](#множественные-стратегии-1), когда возможно узнать имя хоста на момент применения дурения.\n\nПрименяя данную опцию к сайтам TLS1.3, если броузер тоже поддерживает TLS1.3, то вы делаете только хуже.\nНо нет способа автоматически узнать когда надо применять, когда нет, поскольку MSS идет только в\n3-way handshake еще до обмена данными, а версию TLS можно узнать только по ответу сервера, который\nможет привести к реакции DPI.\nИспользовать только когда нет ничего лучше или для отдельных ресурсов.\nДля http использовать смысла нет, поэтому заводите отдельный desync profile с фильтром по порту 443.\nРаботает только на Linux, не работает на BSD и MacOS.\n\n### ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ\n\nПараметр `--hostpad=<bytes>` добавляет паддинг-хедеров перед `Host:` на указанное количество байтов.\nЕсли размер `<bytes>` слишком большой, то идет разбивка на разные хедеры по 2K.\nОбщий буфер приема http запроса - 64K, больший паддинг не поддерживается, да и http сервера\nтакое уже не принимают.\nПолезно против DPI, выполняющих реассемблинг TCP с ограниченным буфером.\nЕсли техника работает, то после некоторого количества bytes http запрос начнет проходить до сайта.\nЕсли при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции TCP сегментации.\nЕсли все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения.\n\n### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ\n\nРаботают аналогично **nfqws**, кроме некоторых моментов.\nНет параметра `--filter-udp`, поскольку **tpws** udp не поддерживает.\nМетоды нулевой фазы (`--mss`) могут работать по хостлисту только в двух случаях:\nесли используется режим socks и удаленный ресолвинг хостов через прокси, либо используется система [кэша IP](#кэш-ip) для запоминания соответствия IP->hostname.\nРаботоспособность вашей настройки в одном и том же режиме может зависеть от того,\nприменяет ли клиент удаленный ресолвинг. Это может быть неочевидно. В одной программе работает, в другой - нет.\n\nЕсли вы используете профиль с хостлистом , и вам нужен mss всегда, укажите mss в профиле с хостлистом,\nсоздайте еще один профиль без хостлиста, если его еще нет, и в нем еще раз укажите mss.\nТогда при любом раскладе будет выполняться mss.\n\nЕсли вам нужен mss по хостлисту, указывайте `--mss` только в профиле с хостлистом и убедитесь в наличии любого из необходимых условий работы в таком режиме.\n\nИспользуйте `curl --socks5` и `curl --socks5-hostname` для проверки вашей стратегии.\nСмотрите вывод `--debug`, чтобы убедиться в правильности настроек.\n\n### СЛУЖЕБНЫЕ ПАРАМЕТРЫ\n\n`--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл.\nМожет быть важен порядок следования опций. `--debug` лучше всего указывать в самом начале.\nОпции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа `--debug` еще дело не дошло,\nто сообщения не будут выведены в файл или syslog.\n`--debug=0|1|2` позволяют сразу в одном параметре включить логирование на консоль и указать уровень.\nСохранено для совместимости с более старыми версиями. Для выбора уровня в режиме syslog или file используйте\nотдельный параметр `--debug-level`. Если в этих режимах `--debug` не указывать уровень через `--debug-level`, то\nавтоматически назначается уровень 1.\nПри логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается.\nТак что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог.\nНо имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root.\nВ начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл,\nи у процесса не будет прав на создание файла в его директории, лог больше не будет вестись.\nВместо удаления лучше использовать truncate.\nВ шелле это можно сделать через команду \": >filename\"\n\ntpws может биндаться на множество интерфейсов и IP адресов (до 32 шт).\nПорт всегда только один.\nПараметры `--bind-iface*` и `--bind-addr` создают новый бинд.\nОстальные параметры `--bind-*` относятся к последнему бинду.\nДля бинда на все ipv4 укажите `--bind-addr \"0.0.0.0\"`, на все ipv6 - `\"::\"`. `--bind-addr=\"\"` - биндаемся на все ipv4 и ipv6.\nВыбор режима использования link local ipv6 адресов (`fe80::/8`) :\n```\n--bind-iface6 --bind-linklocal=no : сначала приватный адрес fc00::/7, затем глобальный адрес\n--bind-iface6 --bind-linklocal=unwanted : сначала приватный адрес fc00::/7, затем глобальный адрес, затем link local.\n--bind-iface6 --bind-linklocal=prefer : сначала link local, затем приватный адрес fc00::/7, затем глобальный адрес.\n--bind-iface6 --bind-linklocal=force : только link local\n```\nЕсли не указано ни одного бинда, то создается бинд по умолчанию на все адреса всех интерфейсов.\nДля бинда на конкретный link-local address делаем так : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name`\nПараметры `--bind-wait*` могут помочь в ситуациях, когда нужно взять IP с интерфейса, но его еще нет, он не поднят\nили не сконфигурирован.\nВ разных системах события ifup ловятся по-разному и не гарантируют, что интерфейс уже получил IP адрес определенного типа.\nВ общем случае не существует единого механизма повеситься на событие типа \"на интерфейсе X появился link local address\".\nДля бинда на известный ip, когда еще интерфейс не сконфигурирован, нужно делать так: `--bind-addr=192.168.5.3 --bind-wait-ip=20`\nВ режиме transparent бинд возможен на любой несуществующий адрес, в режиме socks - только на существующий.\n\nПараметры rcvbuf и sndbuf позволяют установить setsockopt SO_RCVBUF SO_SNDBUF для локального и удаленного соединения.\n\n`--skip-nodelay` может быть полезен, когда tpws используется без дурения, чтобы привести MTU к MTU системы, на которой работает tpws.\nЭто может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения\nподозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз.\n\n`--local-tcp-user-timeout` и `--remote-tcp-user-timeout` устанавливают значение таймаута в секундах\nдля соединений клиент-прокси и прокси-сервер. Этот таймаут соответствует опции сокета linux\nTCP_USER_TIMEOUT. Под таймаутом подразумевается время, в течение которого буферизированные данные\nне переданы или на переданные данные не получено подтверждение (ACK) от другой стороны.\nЭтот таймаут никак не касается времени отсутствия какой-либо передачи через сокет лишь потому,\nчто данных для передачи нет. Полезно для сокращения время закрытия подвисших соединений.\nПоддерживается только на Linux и MacOS.\n\nРежим `--socks` не требует повышенных привилегий (кроме бинда на привилегированные порты 1..1023).\nПоддерживаются версии socks 4 и 5 без авторизации. Версия протокола распознается автоматически.\nПодключения к IP того же устройства, на котором работает tpws, включая localhost, запрещены.\nsocks5 позволяет удаленно ресолвить хосты (curl : --socks5-hostname  firefox : socks_remote_dns=true).\ntpws поддерживает эту возможность асинхронно, не блокируя процессинг других соединений, используя\nмногопоточный пул ресолверов. Количество потоков определяется автоматически в зависимости от `--maxconn`,\nно можно задать и вручную через параметр `--resolver-threads`.\nЗапрос к socks выставляется на паузу, пока домен не будет преобразован в ip адрес в одном из потоков\nресолвера. Ожидание может быть более длинным, если все потоки заняты.\nЕсли задан параметр `--no-resolve`, то подключения по именам хостов запрещаются, а пул ресолверов не создается.\nТем самым экономятся ресурсы.\n\n### IPTABLES ДЛЯ TPWS\n\nДля перенаправления tcp соединения на transparent proxy используются команды следующего вида :\n\n```\niptables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988\niptables -t nat -I PREROUTING -i <внутренний_интерфейс> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988\n```\n\nПервая команда для соединений с самой системы, вторая - для проходящих через роутер соединений.\n\nDNAT на localhost работает в цепочке OUTPUT, но не работает в цепочке PREROUTING без включения параметра\nroute_localnet :\n\n`sysctl -w net.ipv4.conf.<внутренний_интерфейс>.route_localnet=1`\n\nМожно использовать `-j REDIRECT --to-port 988` вместо DNAT, однако в этом случае процесс transparent proxy должен\nслушать на ip адресе входящего интерфейса или на всех адресах. Слушать на всех - не есть хорошо с точки зрения\nбезопасности. Слушать на одном (локальном) можно, но в случае автоматизированного скрипта придется его узнавать, потом\nдинамически вписывать в команду. В любом случае требуются дополнительные усилия. Использование route_localnet тоже имеет\nпотенциальные проблемы с безопасностью. Вы делаете доступным все, что висит на `127.0.0.0/8` для локальной подсети <\nвнутренний_интерфейс>. Службы обычно привязываются к `127.0.0.1`, поэтому можно средствами iptables запретить входящие\nна `127.0.0.1` не с интерфейса lo, либо повесить tpws на любой другой IP из `127.0.0.0/8`, например на `127.0.0.127`,\nи разрешить входящие не с lo только на этот IP.\n\n```\niptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT\niptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP\n```\n\nФильтр по owner необходим для исключения рекурсивного перенаправления соединений от самого tpws. tpws запускается под\nпользователем **tpws**, для него задается исключающее правило.\n\nip6tables работают почти точно так же, как и ipv4, но есть ряд важных нюансов. В DNAT следует брать адрес --to в\nквадратные скобки. Например :\n\n`ip6tables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988`\n\nПараметра route_localnet не существует для ipv6. DNAT на localhost (::1) возможен только в цепочке OUTPUT. В цепочке\nPREROUTING DNAT возможен на любой global address или на link local address того же интерфейса, откуда пришел пакет.\nNFQUEUE работает без изменений.\n\n### NFTABLES ДЛЯ TPWS\n\nБазовая конфигурация :\n\n```\nIFACE_WAN=wan\nIFACE_LAN=br-lan\n\nsysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1\n\nnft create table inet ztest\n\nnft create chain inet ztest localnet_protect\nnft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return\nnft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop\nnft create chain inet ztest input \"{type filter hook input priority filter - 1;}\"\nnft add rule inet ztest input iif != \"lo\" jump localnet_protect\n\nnft create chain inet ztest dnat_output \"{type nat hook output priority dstnat;}\"\nnft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988\nnft create chain inet ztest dnat_pre \"{type nat hook prerouting priority dstnat;}\"\nnft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988\n```\n\nУдаление таблицы :\n```\nnft delete table inet ztest\n```\n\n## ip2net\n\nУтилита ip2net предназначена для преобразования ipv4 или ipv6 списка ip в список подсетей\nс целью сокращения размера списка. Входные данные берутся из stdin, выходные выдаются в `stdout`.\n\n```\n-4                             ; лист - ipv4 (по умолчанию)\n-6                             ; лист - ipv6\n--prefix-length=min[-max]      ; диапазон рассматриваемых длин префиксов. например : 22-30 (ipv4), 56-64 (ipv6)\n--v4-threshold=mul/div         ; ipv4 : включать подсети, в которых заполнено по крайней мере mul/div адресов. например : 3/4\n--v6-threshold=N               ; ipv6 : минимальное количество ip для создания подсети\n```\nВ списке могут присутствовать записи вида ip/prefix и ip1-ip2. Такие записи выкидываются в stdout без изменений.\nОни принимаются командой ipset. ipset умеет для листов hash:net из ip1-ip2 делать оптимальное покрытие ip/prefix.\nipfw из FreeBSD понимает ip/prefix, но не понимает ip1-ip2.\nip2net фильтрует входные данные, выкидывая неправильные IP адреса.\n\nВыбирается подсеть, в которой присутствует указанный минимум адресов.\nДля ipv4 минимум задается как процент от размера подсети (mul/div. например, 3/4), для ipv6 минимум задается напрямую.\n\nРазмер подсети выбирается следующим алгоритмом:\nСначала в указанном диапазоне длин префиксов ищутся подсети, в которых количество адресов - максимально.\nЕсли таких сетей найдено несколько, берется наименьшая сеть (префикс больше).\nНапример, заданы параметры v6_threshold=2 prefix_length=32-64, имеются следующие ipv6 :\n```\n1234:5678:aaaa::5\n1234:5678:aaaa::6\n1234:5678:aaac::5\nРезультат будет :\n1234:5678:aaa8::/45\n```\nЭти адреса так же входят в подсеть /32. Однако, нет смысла проходиться ковровой бомбардировкой,\nкогда те же самые адреса вполне влезают в /45 и их ровно столько же.\nЕсли изменить v6_threshold=4, то результат будет:\n```\n1234:5678:aaaa::5\n1234:5678:aaaa::6\n1234:5678:aaac::5\n```\nТо есть ip не объединятся в подсеть, потому что их слишком мало.\nЕсли изменить `prefix_length=56-64`, результат будет:\n```\n1234:5678:aaaa::/64\n1234:5678:aaac::5\n```\n\nТребуемое процессорное время для вычислений сильно зависит от ширины диапазона длин префиксов, размера искомых подсетей и длины листа.\nЕсли ip2net думает слишком долго, не используйте слишком большие подсети и уменьшите диапазон длин префиксов.\nУчтите, что арифметика mul/div - целочисленная. При превышении разрядной сетки 32 bit результат непредсказуем.\nНе надо делать такое: 5000000/10000000. 1/2 - гораздо лучше.\n\n## mdig\n\nПрограмма предназначена для многопоточного ресолвинга больших листов через системный DNS.\nОна берет из stdin список доменов и выводит в stdout результат ресолвинга. Ошибки выводятся в stderr.\n\n```\n--threads=<threads_number>\t; количество потоков. по умолчанию 1.\n--family=<4|6|46>              ; выбор семейства IP адресов : ipv4, ipv6, ipv4+ipv6\n--verbose                      ; дебаг-лог на консоль\n--stats=N                      ; выводить статистику каждые N доменов\n--log-resolved=<file>          ; сохранять успешно отресолвленные домены в файл\n--log-failed=<file>            ; сохранять неудачно отресолвленные домены в файл\n--dns-make-query=<domain>      ; вывести в stdout бинарный DNS запрос по домену. если --family=6, запрос будет AAAA, иначе A.\n--dns-parse-query              ; распарсить бинарный DNS ответ и выдать все ivp4 и ipv6 адреса из него в stdout\n```\n\nПараметры `--dns-make-query` и `--dns-parse-query` позволяют провести ресолвинг одного домена через произвольный канал.\nНапример, следующим образом можно выполнить DoH запрос, используя лишь mdig и curl :\n```\nmdig --family=6 --dns-make-query=rutracker.org | curl --data-binary @- -H \"Content-Type: application/dns-message\" https://cloudflare-dns.com/dns-query | mdig --dns-parse-query\n```\n\n## Способы получения списка заблокированных IP\n\n!!! nftables не могут работать с ipset-ами. Собственный аналогичный механизм требует огромного количество RAM\n!!! для загрузки больших листов.  Например, для загона 100K записей в nfset не хватает даже 256 Mb.\n!!! Если вам нужны большие листы на домашних роутерах, откатывайтесь на iptables+ipset.\n\n1) Внесите заблокированные домены в `ipset/zapret-hosts-user.txt` и запустите `ipset/get_user.sh`\n   На выходе получите `ipset/zapret-ip-user.txt` с IP адресами.\n\nCкрипты с названием get_reestr_* оперируют дампом реестра заблокированных сайтов :\n\n2) `ipset/get_reestr_resolve.sh` получает список доменов от rublacklist и дальше их ресолвит в ip адреса\n   в файл ipset/zapret-ip.txt.gz. В этом списке есть готовые IP адреса, но судя во всему они там в точности в том виде,\n   что вносит в реестр РосКомПозор. Адреса могут меняться, позор не успевает их обновлять, а провайдеры редко\n   банят по IP : вместо этого они банят http запросы с \"нехорошим\" заголовком \"Host:\" вне зависимости\n   от IP адреса. Поэтому скрипт ресолвит все сам, хотя это и занимает много времени.\n   Используется мультипоточный ресолвер mdig (собственная разработка).\n\n3) `ipset/get_reestr_preresolved.sh`. то же самое, что и 2), только берется уже заресолвленый список\n   со стороннего ресурса.\n\n4) `ipset/get_reestr_preresolved_smart.sh`. то же самое, что и 3), с добавлением всего диапазона некоторых\n   автономных систем (прыгающие IP адреса из cloudflare, facebook, ...) и некоторых поддоменов блокируемых сайтов\n\nCкрипты с названием `get_antifilter_*` оперируют списками адресов и масок подсетей с сайтов antifilter.network и antifilter.download :\n\n5) `ipset/get_antifilter_ip.sh`. получает лист https://antifilter.download/list/ip.lst.\n\n6) `ipset/get_antifilter_ipsmart.sh`. получает лист https://antifilter.network/download/ipsmart.lst.\n   умная суммаризация отдельных адресов из ip.lst по маскам от /32 до /22\n\n7) `ipset/get_antifilter_ipsum.sh`. получает лист https://antifilter.download/list/ipsum.lst.\n   суммаризация отдельных адресов из ip.lst по маске /24\n\n8) `ipset/get_antifilter_ipresolve.sh`. получает лист https://antifilter.download/list/ipresolve.lst.\n   пре-ресолвленный список, аналогичный получаемый при помощи get_reestr_resolve. только ipv4.\n\n9) `ipset/get_antifilter_allyouneed.sh`. получает лист https://antifilter.download/list/allyouneed.lst.\n   Суммарный список префиксов, созданный из ipsum.lst и subnet.lst.\n\n10) `ipset/get_refilter_ipsum.sh`.\n    Список берется отсюда : https://github.com/1andrevich/Re-filter-lists\n\nВсе варианты рассмотренных скриптов автоматически создают и заполняют ipset.\nВарианты 2-10 дополнительно вызывают вариант 1.\n\n11) `ipset/get_config.sh`. этот скрипт вызывает то, что прописано в переменной GETLIST из файла config\n    Если переменная не определена, то ресолвятся лишь листы для ipset nozapret/nozapret6.\n\nЛисты РКН все время изменяются. Возникают новые тенденции. Требования к RAM могут меняться.\nПоэтому необходима нечастая, но все же регулярная ревизия что же вообще у вас происходит на роутере.\nИли вы можете узнать о проблеме лишь когда у вас начнет постоянно пропадать wifi, и вам придется\nего перезагружать каждые 2 часа (метод кувалды).\n\nСамые щадящие варианты по RAM - `get_antifilter_allyouneed.sh`, `get_antifilter_ipsum.sh`, `get_refilter_*.sh`.\n\nЛисты `zapret-ip.txt` и `zapret-ipban.txt` сохраняются в сжатом виде в файлы .gz.\nЭто позволяет снизить их размер во много раз и сэкономить место на роутере.\nОтключить сжатие листов можно параметром конфига GZIP_LISTS=0.\n\nНа роутерах не рекомендуется вызывать эти скрипты чаще раза за 2 суток, поскольку сохранение идет\nлибо во внутреннюю флэш память роутера, либо в случае extroot - на флэшку.\nВ обоих случаях слишком частая запись может убить флэшку, но если это произойдет с внутренней\nфлэш памятью, то вы просто убьете роутер.\n\nПринудительное обновление `ipset` выполняет скрипт `ipset/create_ipset.sh`.\nЕсли передан параметр `no-update`, скрипт не обновляет `ipset`, а только создает его при его отсутствии и заполняет.\nЭто полезно, когда могут случиться несколько последовательных вызовов скрипта. Нет смысла несколько раз перезаполнять\n`ipset`, это длительная операция на больших листах. Листы можно обновлять раз в несколько суток, и только тогда\nвызывать `create_ipset` без параметра `no-update`. Во всех остальных случаях стоит применять `no-update`.\n\nСписок РКН уже достиг внушительных размеров в сотни тысяч IP адресов. Поэтому для оптимизации `ipset`\nприменяется утилита `ip2net`. Она берет список отдельных IP адресов и пытается интеллектуально создать из него подсети для сокращения\nколичества адресов. `ip2net` отсекает неправильные записи в листах, гарантируя отсутствие ошибок при их загрузке.\n`ip2net` написан на языке C, поскольку операция ресурсоемкая. Иные способы роутер может не потянуть.\n\nМожно внести список доменов в `ipset/zapret-hosts-user-ipban.txt`. Их ip адреса будут помещены\nв отдельный ipset `ipban`. Он может использоваться для принудительного завертывания всех\nсоединений на прозрачный proxy `redsocks` или на VPN.\n\n**IPV6** : если включен ipv6, то дополнительно создаются листы с таким же именем, но с \"6\" на конце перед расширением.\n`zapret-ip.txt` => `zapret-ip6.txt`\nСоздаются ipset-ы zapret6 и ipban6.\nЛисты с antifilter не содержат список ipv6 адресов.\n\n**СИСТЕМА ИСКЛЮЧЕНИЯ IP**. Все скрипты ресолвят файл `zapret-hosts-user-exclude.txt`, создавая `zapret-ip-exclude.txt` и `zapret-ip-exclude6.txt`.\nОни загоняются в ipset-ы nozapret и nozapret6. Все правила, создаваемые init скриптами, создаются с учетом этих ipset.\nПомещенные в них IP не участвуют в процессе.\n`zapret-hosts-user-exclude.txt` может содержать домены, ipv4 и ipv6 адреса или подсети.\n\n**FreeBSD**. Скрипты ipset/*.sh работают так же на FreeBSD. Вместо ipset они создают lookup таблицы ipfw с аналогичными именами.\nipfw таблицы в отличие от ipset могут содержать как ipv4, так и ipv6 адреса и подсети в одной таблице, поэтому разделения нет.\n\nПараметр конфига LISTS_RELOAD задает произвольную команду для перезагрузки листов.\nЭто особенно полезно на BSD системах с PF.\nLISTS_RELOAD=-  отключает перезагрузку листов.\n\n## Фильтрация по именам доменов\n\nАльтернативой ipset является использование tpws или nfqws со списком доменов.\nОба демона принимают неограниченное количество листов include (`--hostlist`) и exclude (`--hostlist-exclude`).\nПрежде всего проверяются exclude листы. При вхождении в них происходит отказ от дурения.\nДалее при наличии include листов проверяется домен на вхождение в них. При невхождении в список отказ от дурения.\nЕсли все include листы пустые, это приравнивается к отсутствию include листов. Ограничение перестает работать.\nВ иных случаях происходит дурение.\nНет ни одного списка - дурение всегда.\nЕсть только exclude список - дурение всех, кроме.\nЕсть только include список - дурение только их.\nЕсть оба - дурение только include, кроме exclude.\n\nВ системе запуска это обыграно следующим образом.\nПрисутствуют 2 include списка :\n`ipset/zapret-hosts-user.txt.gz` или `ipset/zapret-hosts-user.txt`,\n`ipset/zapret-hosts.txt.gz` или `ipset/zapret-hosts.txt`\nи 1 exclude список\n`ipset/zapret-hosts-user-exclude.txt.gz` или `ipset/zapret-hosts-user-exclude.txt`\n\nПри режимах фильтрации `MODE_FILTER=hostlist` или `MODE_FILTER=autohostlist` система запуска передает **nfqws** или **tpws** все листы, файлы которых присутствуют.\nПередача происходит через замену маркеров `<HOSTLIST>` и `<HOSTLIST_NOAUTO>` на реальные параметры `--hostlist`, `--hostlist-exclude`, `--hostlist-auto`.\nЕсли вдруг листы include присутствуют, но все они пустые, то работа аналогична отсутствию include листа.\nФайл есть, но несмотря на это дурится все, кроме exclude.\nЕсли вам нужен именно такой режим - не обязательно удалять `zapret-hosts-users.txt`. Достаточно сделать его пустым.\n\nПоддомены учитываются автоматически. Например, строчка \"ru\" вносит в список \"\\*.ru\". Строчка \"\\*.ru\" в списке не сработает.\nМожно использовать символ `^` в начале хоста, чтобы отказаться от автоматического учета поддоменов.\n\nСписок доменов РКН может быть получен скриптами\n```\nipset/get_reestr_hostlist.sh\nipset/get_antizapret_domains.sh\nipset/get_reestr_resolvable_domains.sh\nipset/get_refilter_domains.sh\n```\nОн кладется в `ipset/zapret-hosts.txt.gz`.\n\nПри изменении времени модификации или размера файлов списки перечитываются автоматически.\nПосле неатомарных операций изменения можно послать tpws/nfqws сигнал HUP для принудительной перечитки всех листов.\n\nПри фильтрации по именам доменов демон должен запускаться без фильтрации по ipset.\ntpws и nfqws решают нужно ли применять дурение в зависимости от хоста, полученного из протокола прикладного уровня (http, tls, quic).\nПри использовании больших списков, в том числе списка РКН, оцените объем RAM на роутере !\nЕсли после запуска демона RAM под завязку или случаются oom, значит нужно отказаться от таких больших списков.\n\n## Режим фильтрации autohostlist\n\nЭтот режим позволяет проанализировать как запросы со стороны клиента, так и ответы от сервера.\nЕсли хост еще не находится ни в каких листах и обнаруживается ситуация, похожая на блокировку,\nпроисходит автоматическое добавление хоста в список `autohostlist` как в памяти, так и в файле.\n**nfqws** или **tpws** сами ведут этот файл.\nЧтобы какой-то хост не смог попась в `autohostlist` используйте `hostlist-exclude`.\nЕсли он все-же туда попал - удалите запись из файла вручную. Процессы автоматически перечитают файл.\n**tpws**/**nfqws** сами назначают владельцем файла юзера, под которым они работают после сброса привилегий,\nчтобы иметь возможность обновлять лист.\n\nВ случае **nfqws** данный режим требует перенаправления в том числе и входящего трафика.\nКрайне рекомендовано использовать ограничитель `connbytes`, чтобы **nfqws** не обрабатывал гигабайты.\nПо этой же причине не рекомендуется использование режима на BSD системах. Там нет фильтра `connbytes`.\n\nНа linux системах при использовании nfqws и фильтра connbytes может понадобиться :\n`sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1`\nБыло замечено, что некоторые DPI в России возвращают RST с неверным ACK. Это принимается tcp/ip стеком\nlinux, но через раз приобретает статус INVALID в conntrack. Поэтому правила с `connbytes` срабатывают\nчерез раз, не пересылая RST пакет **nfqws**.\n\nКак вообще могут вести себя DPI, получив \"плохой запрос\" и приняв решение о блокировке:\n\n1) Зависание: просто отмораживается, блокируя прохождение пакетов по TCP каналу.\n2) RST: отправляет RST клиенту и/или серверу\n3) Редирект: (только для http) отправляет редирект на сайт-заглушку\n4) Подмена сертификата: (только для https) полный перехват TLS сеанса с попыткой всунуть что-то\n   свое клиенту. Применяется нечасто, поскольку броузеры на такое ругаются.\n\n**nfqws** и **tpws** могут сечь варианты 1-3, 4 они не распознают.\nВ силу специфики работы с отдельными пакетами или с TCP каналом tpws и nfqws распознают эти ситуации\nпо-разному.\nЧто считается ситуацией, похожей на блокировку :\n1) **nfqws** Несколько ретрансмиссий первого запроса в TCP сеансе, в котором имеется host.\n2) **nfqws,tpws** RST, пришедший в ответ на первый запрос с хостом.\n3) **nfqws,tpws** HTTP редирект, пришедший в ответ на первый запрос с хостом, на глобальный адрес\n   с доменом 2 уровня, не совпадающим с доменом 2 уровня оригинального запроса.\n4) **tpws** закрытие соединения клиентом после отправки первого запроса с хостом, если не было на него\n   ответа со стороны сервера. Это обычно случается по таймауту, когда нет ответа (случай \"зависание\").\n\nЧтобы снизить вероятность ложных срабатываний, имеется счетчик ситуаций, похожих на блокировку.\nЕсли за определенное время произойдет более определенного их количества, хост считается заблокированным\nи заносится в `autohostlist`. По нему сразу же начинает работать стратегия по обходу блокировки.\nЕсли в процессе счета вебсайт отвечает без признаков блокировки, счетчик сбрасывается.\nВероятно, это был временный сбой сайта.\n\nНа практике работа с данным режимом выглядит так.\nПервый раз пользователь заходит на сайт и получает заглушку, сброс соединения или броузер подвисает,\nвываливаясь по таймауту с сообщением о невозможности загрузить страницу.\nНадо долбить F5, принуждая броузер повторять попытки. После некоторой попытки сайт\nначинает работать, и дальше он будет работать всегда.\n\nС этим режимом можно использовать техники обхода, ломающие значительное количество сайтов.\nЕсли сайт не ведет себя как заблокированный, значит обход применен не будет.\nВ противном случае терять все равно нечего.\nОднако, могут быть временные сбои сервера, приводящие к ситуации, аналогичной блокировке.\nМогут происходить ложные срабатывания. Если такое произошло, стратегия может начать ломать\nнезаблокированный сайт. Эту ситуацию, увы, придется вам контролировать вручную.\nЗаносите такие домены в `ipset/zapret-hosts-user-exclude.txt`, чтобы избежать повторения.\nЧтобы впоследствии разобраться почему домен был занесен в лист, можно включить `autohostlist debug log`.\nОн полезен тем, что работает без постоянного просмотра вывода **nfqws** в режиме debug.\nВ лог заносятся только основные события, ведущие к занесению хоста в лист.\nПо логу можно понять как избежать ложных срабатываний и подходит ли вообще вам этот режим.\n\nМожно использовать один `autohostlist` с множеством процессов. Все процессы проверяют время модификации файла.\nЕсли файл был изменен в другом процессе, происходит его перечитывание.\nВсе процессы должны работать под одним uid, чтобы были права доступа на файл.\n\nСкрипты `zapret` ведут `autohostlist` в `ipset/zapret-hosts-auto.txt`.\n`install_easy.sh` при апгрейде `zapret` сохраняет этот файл.\nРежим `autohostlist` включает в себя режим `hostlist`.\nМожно вести `ipset/zapret-hosts-user.txt`, `ipset/zapret-hosts-user-exclude.txt`.\n\n## Проверка провайдера\n\nПеред настройкой нужно провести исследование какую бяку устроил вам ваш провайдер.\n\nНужно выяснить не подменяет ли он DNS и какой метод обхода DPI работает.\nВ этом вам поможет скрипт `blockcheck.sh`.\n\nЕсли DNS подменяется, но провайдер не перехватывает обращения к сторонним DNS, поменяйте DNS на публичный.\nНапример: 8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1, 9.9.9.9\nЕсли DNS подменяется и провайдер перехватывает обращения к сторонним DNS, настройте `dnscrypt`.\nЕще один эффективный вариант - использовать ресолвер от yandex 77.88.8.88 на нестандартном порту 1253.\nМногие провайдеры не анализируют обращения к DNS на нестандартных портах.\n`blockcheck` если видит подмену DNS автоматически переключается на DoH сервера.\n\nСледует прогнать `blockcheck` по нескольким заблокированным сайтам и выявить общий характер блокировок.\nРазные сайты могут быть заблокированы по-разному, нужно искать такую технику, которая работает на большинстве.\nЧтобы записать вывод `blockcheck.sh` в файл, выполните: `./blockcheck.sh | tee /tmp/blockcheck.txt`.\n\nПроанализируйте какие методы дурения DPI работают, в соответствии с ними настройте `/opt/zapret/config`.\n\nИмейте в виду, что у провайдеров может быть несколько DPI или запросы могут идти через разные каналы\nпо методу балансировки нагрузки. Балансировка может означать, что на разных ветках разные DPI или\nони находятся на разных хопах. Такая ситуация может выражаться в нестабильности работы обхода.\nДернули несколько раз curl. То работает, то connection reset или редирект. `blockcheck.sh` выдает\nстранноватые результаты. То split работает на 2-м. хопе, то на 4-м. Достоверность результата вызывает сомнения.\nВ этом случае задайте несколько повторов одного и того же теста. Тест будет считаться успешным только,\nесли все попытки пройдут успешно.\n\nПри использовании `autottl` следует протестировать как можно больше разных доменов. Эта техника\nможет на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах\nона стабильна, на третьих полный хаос, и проще отказаться.\n\n`Blockcheck` имеет 3 уровня сканирования.\n* `quick` - максимально быстро найти хоть что-то работающее.\n* `standard` дает возможность провести исследование как и на что реагирует DPI в плане методов обхода.\n* `force` дает максимум проверок даже в случаях, когда ресурс работает без обхода или с более простыми стратегиями.\n\nЕсть ряд других параметров, которые не будут спрашиваться в диалоге, но которые можно переопределить через\nпеременные.\n\n```\nCURL - замена программы curl\nCURL_MAX_TIME - время таймаута curl в секундах\nCURL_MAX_TIME_QUIC - время таймаута curl для quic. если не задано, используется значение CURL_MAX_TIME\nCURL_MAX_TIME_DOH - время таймаута curl для DoH серверов\nCURL_CMD=1 - показывать команды curl\nCURL_OPT - дополнительные параметры curl. `-k` - игнор сертификатов. `-v` - подробный вывод протокола\nCURL_HTTPS_GET=1 - использовать метод GET вместо HEAD для https\nDOMAINS - список тестируемых доменов через пробел\nIPVS=4|6|46 - тестируемые версии ip протокола\nENABLE_HTTP=0|1 - включить тест plain http\nENABLE_HTTPS_TLS12=0|1 - включить тест https TLS 1.2\nENABLE_HTTPS_TLS13=0|1 - включить тест https TLS 1.3\nENABLE_HTTP3=0|1 - включить тест QUIC\nREPEATS - количество попыток тестирования\nPARALLEL=0|1 - включить параллельные попытки. может обидеть сайт из-за долбежки и привести к неверному результату\nSCANLEVEL=quick|standard|force - уровень сканирования\nBATCH=1 - пакетный режим без вопросов и ожидания ввода в консоли\nHTTP_PORT, HTTPS_PORT, QUIC_PORT - номера портов для соответствующих протоколов\nSKIP_DNSCHECK=1 - отказ от проверки DNS\nSKIP_IPBLOCK=1 - отказ от тестов блокировки по порту или IP\nSKIP_TPWS=1 - отказ от тестов tpws\nSKIP_PKTWS=1 - отказ от тестов nfqws/dvtws/winws\nPKTWS_EXTRA, TPWS_EXTRA - дополнительные параметры nfqws/dvtws/winws и tpws, указываемые после основной стратегии\nPKTWS_EXTRA_1 .. PKTWS_EXTRA_9, TPWS_EXTRA_1 .. TPWS_EXTRA_9 - отдельно дополнительные параметры, содержащие пробелы\nPKTWS_EXTRA_PRE - дополнительные параметры для nfqws/dvtws/winws, указываемые перед основной стратегией\nPKTWS_EXTRA_PRE_1 .. PKTWS_EXTRA_PRE_9 - отдельно дополнительные параметры, содержащие пробелы\nSECURE_DNS=0|1 - принудительно выключить или включить DoH\nDOH_SERVERS - список URL DoH через пробел для автоматического выбора работающего сервера\nDOH_SERVER - конкретный DoH URL, отказ от поиска\nUNBLOCKED_DOM - незаблокированный домен, который используется для тестов IP block\nMIN_TTL,MAX_TTL - пределы тестов с TTL. MAX_TTL=0 отключает тесты.\nMIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA - пределы тестов с autottl по дельте. MAX_AUTOTTL_DELTA=0 отключает тесты.\nSIMULATE=1 - включить режим симуляции для отладки логики скрипта. отключаются реальные запросы через curl, заменяются рандомным результатом.\nSIM_SUCCESS_RATE=<percent> - вероятность успеха симуляции в процентах\n```\n\nПример запуска с переменными:\\\n`SECURE_DNS=1 SKIP_TPWS=1 CURL_MAX_TIME=1 CURL=/tmp/curl ./blockcheck.sh`\n\n**СКАН ПОРТОВ**\\\nЕсли в системе присутствует совместимый `netcat` (ncat от nmap или openbsd ncat. в OpenWrt по умолчанию нет),\nто выполняется сканирование портов http или https всех IP адресов домена.\nЕсли ни один IP не отвечает, то результат очевиден. Можно останавливать сканирование.\nАвтоматически оно не остановится, потому что netcat-ы недостаточно подробно информируют о причинах ошибки.\nЕсли доступна только часть IP, то можно ожидать хаотичных сбоев, т.к. подключение идет к случайному адресу\nиз списка.\n\n**ПРОВЕРКА НА ЧАСТИЧНЫЙ IP block**\\\nПод частичным блоком подразумевается ситуация, когда коннект на порты есть, но по определенному транспортному\nили прикладному протоколу всегда идет реакция DPI вне зависимости от запрашиваемого домена.\nЭта проверка так же не выдаст автоматического вердикта/решения, потому что может быть очень много вариаций.\nВместо этого анализ происходящего возложен на самого пользователя или тех, кто будет читать лог.\nСуть этой проверки в попытке дернуть неблокированный IP с блокированным доменом и наоборот, анализируя\nпри этом реакцию DPI. Реакция DPI обычно проявляется в виде таймаута (зависание запроса), connection reset\nили http redirect на заглушку. Любой другой вариант скорее всего говорит об отсутствии реакции DPI.\nВ частности, любые http коды, кроме редиректа, ведущего именно на заглушку, а не куда-то еще.\nНа TLS - ошибки handshake без задержек.\nОшибка сертификата может говорить как о реакции DPI с MiTM атакой (подмена сертификата), так и\nо том, что принимающий сервер неблокированного домена все равно принимает ваш TLS `handshake` с чужим доменом,\nпытаясь при этом выдать сертификат без запрошенного домена. Требуется дополнительный анализ.\nЕсли на заблокированный домен есть реакция на всех IP адресах, значит есть блокировка по домену.\nЕсли на неблокированный домен есть реакция на IP адресах блокированного домена, значит имеет место блок по IP.\nСоответственно, если есть и то, и другое, значит есть и блок по IP, и блок по домену.\nНеблокированный домен первым делом проверяется на доступность на оригинальном адресе.\nПри недоступности тест отменяется, поскольку он будет неинформативен.\n\nЕсли выяснено, что есть частичный блок по IP на DPI, то скорее всего все остальные тесты будут провалены\nвне зависимости от стратегий обхода. Но бывают и некоторые исключения. Например, пробитие через `ipv6\noption headers`. Или сделать так, чтобы он не мог распознать протокол прикладного уровня.\nДальнейшие тесты могут быть не лишены смысла.\n\n**ПРИМЕРЫ БЛОКИРОВКИ ТОЛЬКО ПО ДОМЕНУ БЕЗ БЛОКА ПО IP**\n\n```\n> testing iana.org on it's original\n!!!!! AVAILABLE !!!!!\n> testing rutracker.org on 192.0.43.8 (iana.org)\ncurl: (28) Operation timed out after 1002 milliseconds with 0 bytes received\n> testing iana.org on 172.67.182.196 (rutracker.org)\nHTTP/1.1 409 Conflict\n> testing iana.org on 104.21.32.39 (rutracker.org)\nHTTP/1.1 409 Conflict\n\n> testing iana.org on it's original ip\n!!!!! AVAILABLE !!!!!\n> testing rutracker.org on 192.0.43.8 (iana.org)\ncurl: (28) Connection timed out after 1001 milliseconds\n> testing iana.org on 172.67.182.196 (rutracker.org)\ncurl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure\n> testing iana.org on 104.21.32.39 (rutracker.org)\ncurl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure\n\n> testing iana.org on it's original ip\n!!!!! AVAILABLE !!!!!\n> testing rutracker.org on 192.0.43.8 (iana.org)\nHTTP/1.1 307 Temporary Redirect\nLocation: https://www.gblnet.net/blocked.php\n> testing iana.org on 172.67.182.196 (rutracker.org)\nHTTP/1.1 409 Conflict\n> testing iana.org on 104.21.32.39 (rutracker.org)\nHTTP/1.1 409 Conflict\n\n> testing iana.org on it's original ip\n!!!!! AVAILABLE !!!!!\n> testing rutracker.org on 192.0.43.8 (iana.org)\ncurl: (35) Recv failure: Connection reset by peer\n> testing iana.org on 172.67.182.196 (rutracker.org)\ncurl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure\n> testing iana.org on 104.21.32.39 (rutracker.org)\ncurl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure\n```\n\n\n**ПРИМЕР ПОЛНОГО IP БЛОКА ИЛИ БЛОКА TCP ПОРТА ПРИ ОТСУТСТВИИ БЛОКА ПО ДОМЕНУ**\n\n```\n* port block tests ipv4 startmail.com:80\n  ncat -z -w 1 145.131.90.136 80\n  145.131.90.136 does not connect. netcat code 1\n  ncat -z -w 1 145.131.90.152 80\n  145.131.90.152 does not connect. netcat code 1\n\n* curl_test_http ipv4 startmail.com\n- checking without DPI bypass\n  curl: (28) Connection timed out after 2002 milliseconds\n  UNAVAILABLE code=28\n\n- IP block tests (requires manual interpretation)\n\n> testing iana.org on it's original ip\n!!!!! AVAILABLE !!!!!\n> testing startmail.com on 192.0.43.8 (iana.org)\nHTTP/1.1 302 Found\nLocation: https://www.iana.org/\n> testing iana.org on 145.131.90.136 (startmail.com)\ncurl: (28) Connection timed out after 2002 milliseconds\n> testing iana.org on 145.131.90.152 (startmail.com)\ncurl: (28) Connection timed out after 2002 milliseconds\n```\n\n## Выбор параметров\n\nФайл `/opt/zapret/config` используется различными компонентами системы и содержит основные настройки.\nЕго нужно просмотреть и при необходимости отредактировать.\n\nНа linux системах можно выбрать использовать `iptables` или `nftables`.\nПо умолчанию на традиционных linux выбирается `nftables`, если установлен nft.\nНа OpenWrt по умолчанию выбирается `nftables` на новых версиях с firewall4.\n\n`FWTYPE=iptables`\n\nНа `nftables` можно отключить стандартную схему перехвата трафика после NAT и перейти на перехват до NAT.\nЭто сделает невозможным применение некоторых методов дурения на проходящем трафике как в случае с `iptables`.\nnfqws начнет получать адреса пакетов из локальной сети и отображать их в логах.\n\n`POSTNAT=0`\n\nСуществует 3 стандартных опции запуска, настраиваемых раздельно и независимо: `tpws-socks`, **tpws**, **nfqws**.\nИх можно использовать как по отдельности, так и вместе. Например, вам надо сделать комбинацию\nиз методов, доступных только в **tpws** и только в **nfqws**. Их можно задействовать вместе.\n**tpws** будет прозрачно локализовывать трафик на системе и применять свое дурение, **nfqws** будет дурить трафик,\nисходящий с самой системы после обработки на **tpws**.\nА можно на эту же систему повесить без параметров socks proxy, чтобы получать доступ к обходу блокировок через прокси.\nТаким образом, все 3 режима вполне могут задействоваться вместе.\nТак же безусловно и независимо, в добавок к стандартным опциям, применяются все custom скрипты в `init.d/{sysv,openwrt,macos}/custom.d`.\n\nОднако, при комбинировании tpws и nfqws с пересечением по L3/L4 протоколам не все так просто , как может показаться на первый взгляд.\nПервым всегда работает tpws, за ним - nfqws. На nfqws попадает уже \"задуренный\" трафик от tpws.\nПолучается, что дурилка дурит дурилку, и дурилка не срабатывает, потому что ее задурили.\nВот такой веселый момент. nfqws перестает распознавать протоколы и применять методы.\nНекоторые методы дурения от tpws nfqws в состоянии распознать и отработать корректно, но большинство - нет.\nРешение - использование `--dpi-desync-any-protocol` в nfqws и работа как с неизвестным протоколом.\nКомбинирование tpws и nfqws является продвинутым вариантом, требующим глубокого понимания происходящего.\nОчень желательно проанализировать действия nfqws по `--debug` логу. Все ли так, как вы задумали.\n\nОдновременное использование tpws и nfqws без пересечения по L3/L4 (то есть nfqws - udp, tpws - tcp или nfqws - port 443, tpws - port 80 или nfqws - ipv4, tpws - ipv6) проблем не представляет.\n\n`tpws-socks` требует настройки параметров **tpws**, но не требует перехвата трафика.\nОстальные опции требуют раздельно настройки перехвата трафика и опции самих демонов.\nКаждая опция предполагает запуск одного инстанса соответствующего демона. Все различия методов дурения\nдля `http`, `https`, `quic` и т.д. должны быть отражены через схему мультистратегий.\nВ этом смысле настройка похожа на вариант `winws` на Windows, а перенос конфигов не должен представлять больших сложностей.\n\nОсновное правило настройки перехвата - перехватывайте только необходимый минимум.\nЛюбой перехват лишнего - это бессмысленная нагрузка на вашу систему.\nОпции демонов `--ipset` использовать нужно с умом. Не стоит перехватывать весь трафик, чтобы потом по параметру --ipset\nвыделить лишь горстку IP. Это будет работать, но очень неэффективно с точки зрения нагрузки на систему.\nИспользуйте `ipset`-ы режима ядра. При необходимости пишите и задействуйте `custom scripts`.\nНо если у вас и так идет работа по всем IP, и нужно написать небольшую специализацию по IP, то --ipset вполне уместен.\n\nНастройки демонов можно для удобства писать на нескольких строках, используя двойные или одинарные кавычки.\nЧтобы задействовать стандартные обновляемые хост-листы из каталога `ipset`, используйте маркер <HOSTLIST>.\nОн будет заменен на параметры, соответствующие режиму MODE_FILTER, и будут подставлены реально существующие файлы.\nЕсли MODE_FILTER не предполагает стандартного хостлиста, <HOSTLIST> будет заменен на пустую строку.\nСтандартные хостлисты следует вставлять в финальных стратегиях (стратегиях по умолчанию), закрывающих цепочки по\nгруппе параметров фильтра. Таких мест может быть несколько.\nНе нужно использовать <HOSTLIST> в узких специализациях и в тех профилях, по которым точно не будет проходить\nтрафик с известными протоколами, откуда поддерживается извлечение имени хоста (`http`, `tls`, `quic`).\n<HOSTLIST_NOAUTO> - это вариация, при которой стандартный автолист используется как обычный.\nТо есть на этом профиле не происходит автоматическое добавление заблокированных доменов.\nНо если на другом профиле что-то будет добавлено, то этот профиль примет изменения автоматически.\n\n***Изменение бита mark для предотвращения зацикливания***\\\n`DESYNC_MARK=0x40000000`\n\n***Изменение бита mark для пометки пакетов, проходящих по POSTNAT схеме (только nftables)***\\\n`DESYNC_MARK_POSTNAT=0x20000000`\n\n***Если раскоментировано, пометка пакетов, которые должны быть обработаны zapret.***\\\n`#FILTER_MARK=0x10000000`\n\nБит должен быть установлен вашими собственными правилами.\n* Для iptables - в цепочках mangle PREROUTING и mangle OUTPUT перед правилами zapret (iptables -I _после_ применения правил zapret).\n* Для nftables - в хуках output и prerouting с приоритетом -102 или ниже.\n\nКритерии пометки любые. Например, IP адрес или интерфейс источника. Это ответ на вопрос \"как мне сделать, чтобы телик не ходил через zapret или чтобы через него ходил только мой комп\".\n\n***Включение стандартной опции tpws в режиме socks***\\\n`TPWS_SOCKS_ENABLE=0`\n\n***На каком порту будет слушать tpws socks. прослушивается только localhost и LAN***\\\n`TPPORT_SOCKS=987`\n\n***Параметры tpws для режима socks***\n```\nTPWS_SOCKS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\"\n```\n\n***Включение стандартной опции tpws в прозрачном режиме***\\\n`TPWS_ENABLE=0`\n\n***Какие tcp порты следует перенаправлять на tpws***\\\n`TPWS_PORTS=80,443`\n\n***Параметры tpws для прозрачного режима***\n```\nTPWS_OPT=\"\n--filter-tcp=80 --methodeol <HOSTLIST> --new\n--filter-tcp=443 --split-pos=1,midsld --disorder <HOSTLIST>\"\n```\n\n***Включение стандартной опции nfqws***\\\n`NFQWS_ENABLE=0`\n\n***Какие tcp и udp порты следует перенаправлять на nfqws с использованием connbytes ограничителя***\n\nconnbytes позволяет из каждого соединения перенаправить только заданное количество начальных пакетов по каждому направлению - на вход и на выход.\nЭто более эффективная kernel-mode замена параметра nfqws `--dpi-desync-cutoff=nX`.\n```\nNFQWS_PORTS_TCP=80,443\nNFQWS_PORTS_UDP=443\n```\n\n***Сколько начальных входящих и исходящих пакетов нужно перенаправлять на nfqws по каждому направлению***\n```\nNFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_TCP_PKT_IN=3\nNFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD))\nNFQWS_UDP_PKT_IN=0\n```\n\n***Задать порты для перенаправления на nfqws без connbytes ограничителя***\\\nЕсть трафик, исходящий сеанс для которого необходимо перенаправлять весь без ограничителей.\nТипичное применение - поддержка http keepalives на stateless DPI.\nЭто существенно нагружает процессор. Использовать только если понимаете зачем. Чаще всего это не нужно.\nВходящий трафик ограничивается по connbytes через параметры PKT_IN.\nЕсли указываете здесь какие-то порты, желательно их убрать из версии с connbytes ограничителем\n```\nNFQWS_PORTS_TCP_KEEPALIVE=80\nNFQWS_PORTS_UDP_KEEPALIVE=\n```\n\n***Параметры nfqws***\n```\nNFQWS_OPT=\"\n--filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig <HOSTLIST> --new\n--filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig <HOSTLIST> --new\n--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 <HOSTLIST_NOAUTO>\n```\n\n***Режим фильтрации хостов:***\n```\nnone - применять дурение ко всем хостам\nipset - ограничить дурение ipset-ом zapret/zapret6\nhostlist - ограничить дурение списком хостов из файла\nautohostlist - режим hostlist + распознавание блокировок и ведение автоматического листа\n```\n`MODE_FILTER=none`\n\n***Настройка системы управления выборочным traffic offload (только если поддерживается)***\n```\ndonttouch: выборочное управление отключено, используется системная настройка, простой инсталлятор выключает системную настройку, если она не совместима с выбранным режимом\nnone: выборочное управление отключено, простой инсталлятор выключает системную настройку\nsoftware: выборочное управление включено в режиме software, простой инсталлятор выключает системную настройку\nhardware: выборочное управление включено в режиме hardware, простой инсталлятор выключает системную настройку\n```\n`FLOWOFFLOAD=donttouch`\n\nПараметр `GETLIST` указывает инсталлятору `install_easy.sh` какой скрипт дергать\nдля обновления списка заблокированных ip или хостов.\nОн же вызывается через `get_config.sh` из запланированных заданий (crontab или systemd timer).\nПоместите сюда название скрипта, который будете использовать для обновления листов.\nЕсли не нужно, то параметр следует закомментировать.\n\nМожно индивидуально отключить ipv4 или ipv6. Если параметр закомментирован или не равен \"1\",\nиспользование протокола разрешено.\n```\nDISABLE_IPV4=1\nDISABLE_IPV6=1\n```\n\nКоличество потоков для многопоточного DNS ресолвера mdig (1..100).\nЧем их больше, тем быстрее, но не обидится ли на долбежку ваш DNS сервер?\\\n`MDIG_THREADS=30`\n\nМесто для хранения временных файлов. При скачивании огромных реестров в `/tmp` места может не хватить.\nЕсли файловая система на нормальном носителе (не встроенная память роутера), то можно\nуказать место на флэшке или диске.\n`TMPDIR=/opt/zapret/tmp`\n\n***Опции для создания ipset-ов и nfset-ов***\n\n```\nSET_MAXELEM=262144\nIPSET_OPT=\"hashsize 262144 maxelem 2097152\"\n```\n\nХук, позволяющий внести ip адреса динамически. $1 = имя таблицы\\\nАдреса выводятся в stdout. В случае nfset автоматически решается проблема возможного пересечения интервалов.\\\n`IPSET_HOOK=\"/etc/zapret.ipset.hook\"`\n\n***ПРО РУГАНЬ в dmesg по поводу нехватки памяти.***\n\nМожет так случиться, что памяти в системе достаточно, но при попытке заполнить огромный `ipset`\nядро начинает громко ругаться, `ipset` заполняется не полностью.\\\nВероятная причина в том, что превышается `hashsize`, заданный при создании `ipset` (create_ipset.sh).\nПроисходит переаллокация списка, не находится непрерывных фрагментов памяти нужной длины.\nЭто лечится увеличением `hashsize`. Но чем больше `hashsize`, тем больше занимает `ipset` в памяти.\nЗадавать слишком большой `hashsize` для недостаточно больших списков нецелесообразно.\n\n***Опции для вызова ip2net. Отдельно для листов ipv4 и ipv6.***\n\n```\nIP2NET_OPT4=\"--prefix-length=22-30 --v4-threshold=3/4\"\nIP2NET_OPT6=\"--prefix-length=56-64 --v6-threshold=5\"\n```\n\n***Настройка режима autohostlist.***\n\nПри увеличении AUTOHOSTLIST_RETRANS_THRESHOLD и использовании nfqws следует пересмотреть значения параметров\nNFQWS_TCP_PKT_OUT и NFQWS_UDP_PKT_OUT. Все ретрансмиссии должны быть получены nfqws, иначе триггер \"зависание запроса\" не сработает.\n\n```\nAUTOHOSTLIST_RETRANS_THRESHOLD=3\nAUTOHOSTLIST_FAIL_THRESHOLD=3\nAUTOHOSTLIST_FAIL_TIME=60\nAUTOHOSTLIST_DEBUG=0\n```\n\n***Включить или выключить сжатие больших листов в скриптах ipset/\\*.sh.***\n\n`GZIP_LISTS=1`\n\n***Команда для перезагрузки ip таблиц фаервола.***\n\nЕсли не указано или пустое, выбирается автоматически ipset или ipfw при их наличии.\nНа BSD системах с PF нет автоматической загрузки. Там нужно указать команду явно: `pfctl -f /etc/pf.conf`\nНа более новых pfctl (есть в новых FreeBSD, нет в OpenBSD 6.8) можно дать команду загрузки только таблиц: `pfctl -Tl -f /etc/pf.conf`\n\"-\" означает отключение загрузки листов даже при наличии поддерживаемого backend.\n```\nLISTS_RELOAD=\"pfctl -f /etc/pf.conf\"\nLISTS_RELOAD=-\n```\n\nВ OpenWrt существует сеть по умолчанию 'lan'. Только трафик с этой сети будет перенаправлен на tpws.\nНо возможно задать другие сети или список сетей:\\\n`OPENWRT_LAN=\"lan lan2 lan3\"`\n\nВ OpenWrt в качестве wan берутся интерфейсы, имеющие default route. Отдельно для ipv4 и ipv6.\nЭто можно переопределить:\n```\nOPENWRT_WAN4=\"wan4 vpn\"\nOPENWRT_WAN6=\"wan6 vpn6\"\n```\n\nПараметр `INIT_APPLY_FW=1` разрешает init скрипту самостоятельно применять правила iptables.\\\nПри иных значениях или если параметр закомментирован, правила применены не будут.\\\nЭто полезно, если у вас есть система управления фаерволом, в настройки которой и следует прикрутить правила.\\\nНа OpenWrt неприменимо при использовании firewall3+iptables.\n\n`FILTER_TTL_EXPIRED_ICMP=1` включает механизмы блокировки пакетов icmp time exceeded, высылаемые роутерами по пути следования пакета в ответ на исчерпание TTL/HL.\nВ linux соединение обрывается системой, если в ответ на первый пакет (для tcp - SYN) пришел такой icmp. Аналогичная схема имеется и в datagram сокетах.\nБлокировка icmp идет исключительно за счет средств iptables/nftables.\nЧтобы не трогать весь трафик, в режиме PRENAT используется connmark для пометки сеансов, над которыми поработал nfqws. В режиме POSTNAT так сделать нельзя,\nпоэтому помечаются все сеансы, заворачиваемые на nfqws.\nНастройку лучше отключить, если вы не ожидаете проблем от icmp, тк в этом случае будет меньше ненужных вмешательств в трафик.\n\n***Следующие настройки не актуальны для openwrt:***\n\nЕсли ваша система работает как роутер, то нужно вписать названия внутренних и внешних интерфейсов:\n```\nIFACE_LAN=eth0\nIFACE_WAN=eth1\nIFACE_WAN6=\"henet ipsec0\"\n```\nНесколько интерфейсов могут быть вписаны через пробел.\nЕсли IFACE_WAN6 не задан, то берется значение IFACE_WAN.\n\n> [!IMPORTANT]\n> Настройка маршрутизации, маскарада и т.д. не входит в задачу zapret.\n> Включаются только режимы, обеспечивающие перехват транзитного трафика.\n> Возможно определить несколько интерфейсов следующим образом:\n\n`IFACE_LAN=\"eth0 eth1 eth2\"`\n\n## Прикручивание к системе управления фаерволом или своей системе запуска\n\nЕсли вы используете какую-то систему управления фаерволом, то она может вступать в конфликт\nс имеющимся скриптом запуска. При повторном применении правил она могла бы поломать настройки iptables от zapret.\nВ этом случае правила для iptables должны быть прикручены к вашему фаерволу отдельно от запуска tpws или nfqws.\n\n_Следующие вызовы позволяют применить или убрать правила iptables отдельно:_\n\n```\n/opt/zapret/init.d/sysv/zapret start_fw\n/opt/zapret/init.d/sysv/zapret stop_fw\n/opt/zapret/init.d/sysv/zapret restart_fw\n```\n\n_А так можно запустить или остановить демоны отдельно от фаервола:_\n\n```\n/opt/zapret/init.d/sysv/zapret start_daemons\n/opt/zapret/init.d/sysv/zapret stop_daemons\n/opt/zapret/init.d/sysv/zapret restart_daemons\n```\n\n`nftables` сводят практически на нет конфликты между разными системами управления, поскольку позволяют\nиспользовать независимые таблицы и хуки. Используется отдельная nf-таблица \"zapret\".\nЕсли ваша система ее не будет трогать, скорее всего все будет нормально.\n\n_Для `nftables` предусмотрено несколько дополнительных вызовов:_\n\nПосмотреть set-ы интерфейсов, относящихся к lan, wan и wan6. По ним идет завертывание трафика.\nА так же таблицу flow table с именами интерфейсов ingress hook.\\\n`/opt/zapret/init.d/sysv/zapret list_ifsets`\n\nОбновить set-ы интерфейсов, относящихся к lan, wan и wan6.\nДля традиционных linux список интерфейсов берется из переменных конфига IFACE_LAN, IFACE_WAN.\nДля OpenWrt определяется автоматически. Множество lanif может быть расширено параметром OPENWRT_LAN.\nВсе интерфейсы lan и wan так же добавляются в ingress hook от flow table.\\\n`/opt/zapret/init.d/sysv/zapret reload_ifsets`\n\nПросмотр таблицы без содержимого set-ов. Вызывает `nft -t list table inet zapret`\\\n`/opt/zapret/init.d/sysv/zapret list_table`\n\n_Так же возможно прицепиться своим скриптом к любой стадии применения и снятия фаервола со стороны zapret скриптов:_\n```\nINIT_FW_PRE_UP_HOOK=\"/etc/firewall.zapret.hook.pre_up\"\nINIT_FW_POST_UP_HOOK=\"/etc/firewall.zapret.hook.post_up\"\nINIT_FW_PRE_DOWN_HOOK=\"/etc/firewall.zapret.hook.pre_down\"\nINIT_FW_POST_DOWN_HOOK=\"/etc/firewall.zapret.hook.post_down\"\n```\n\nЭти настройки доступны в config.\nМожет быть полезно, если вам нужно использовать nftables set-ы, например `ipban`/`ipban6`.\nnfset-ы принадлежат только одной таблице, следовательно вам придется писать правила для таблицы zapret,\nа значит нужно синхронизироваться с применением/снятием правил со стороны zapret скриптов.\n\n## Вариант custom\n\ncustom скрипты - это маленькие shell программы, управляющие нестандартными режимами применения zapret\nили частными случаями, которые не могут быть интегрированы в основную часть без загромождения и замусоривания кода.\nДля применения custom следует помещать файлы в следующие директории в зависимости от вашей системы:\n```\n/opt/zapret/init.d/sysv/custom.d\n/opt/zapret/init.d/openwrt/custom.d\n/opt/zapret/init.d/macos/custom.d\n```\nДиректория будет просканирована в алфавитном порядке, и каждый скрипт будет применен.\n\nВ `init.d` имеется `custom.d.examples.linux`, в `init.d/macos` - `custom.d.examples`.\nЭто готовые скрипты, которые можно копировать в `custom.d`. Их можно взять за основу для написания собственных.\n\n***Для linux пишется код в функции***\n```\nzapret_custom_daemons\nzapret_custom_firewall\nzapret_custom_firewall_nft\nzapret_custom_firewall_nft_flush\n```\n\n***Для macos***\n```\nzapret_custom_daemons\nzapret_custom_firewall_v4\nzapret_custom_firewall_v6\n```\n\nzapret_custom_daemons поднимает демоны **nfqws**/**tpws** в нужном вам количестве и с нужными вам параметрами.\nВ первом параметре передается код операции: 1 = запуск, 0 = останов.\nСхема запуска демонов в OpenWrt отличается - используется procd.\nПоэтому логика останова отсутствует за ненадобностью, останов никогда не вызывается.\n\nzapret_custom_firewall поднимает и убирает правила `iptables`.\nВ первом параметре передается код операции: 1 = запуск, 0 = останов.\n\nzapret_custom_firewall_nft поднимает правила nftables.\nЛогика останова отсутствует за ненадобностью. Стандартные цепочки zapret удаляются автоматически.\nОднако, sets и правила из ваших собственных цепочек не удаляются.\nИх нужно подчистить в zapret_custom_firewall_nft_flush.\nЕсли set-ов и собственных цепочек у вас нет, функцию можно не определять или оставить пустой.\n\nЕсли вам не нужны iptables или nftables - можете не писать соответствующую функцию.\n\nВ linux можно использовать локальные переменные `FW_EXTRA_PRE` и `FW_EXTRA_POST`.\\\n`FW_EXTRA_PRE` добавляет код к правилам ip/nf tables до кода, генерируемого функциями-хелперами.\\\n`FW_EXTRA_POST` добавляет код после.\n\nВ linux функции-хелперы добавляют правило в начало цепочек, то есть перед уже имеющимися.\nПоэтому специализации должны идти после более общих вариантов.\nДля macos правило обратное. Там правила добавляются в конец.\nПо этой же причине фаервол в Linux сначала применяется в стандартном режиме, потом custom,\nа в MacOS сначала custom, потом стандартный режим.\n\nВ macos firewall-функции ничего сами никуда не заносят. Их задача - лишь выдать текст в stdout,\nсодержащий правила для pf-якоря. Остальное сделает обертка.\n\nОсобо обратите внимание на номер демона в функциях `run_daemon` , `do_daemon`, `do_tpws`, `do_tpws_socks`, `do_nfqws` ,\nномера портов **tpws** и очередей **nfqueue**.\nОни должны быть уникальными во всех скриптах. При накладке будет ошибка.\nПоэтому используйте функции динамического получения этих значений из пула.\n\n`custom` скрипты могут использовать переменные из `config`. Можно помещать в `config` свои переменные\nи задействовать их в скриптах.\nМожно использовать функции-хелперы. Они являются частью общего пространства функций shell.\nПолезные функции можно взять из примеров скриптов. Так же смотрите `common/*.sh`.\nИспользуя хелпер функции, вы избавитесь от необходимости учитывать все возможные случаи\nтипа наличия/отсутствия ipv6, является ли система роутером, имена интерфейсов, ...Хелперы это учитывают. Вам нужно сосредоточиться лишь на фильтрах `{ip,nf}tables` и параметрах демонов.\n\n## Простая установка\n\n`install_easy.sh` автоматизирует ручные варианты процедур установки.\nОн поддерживает OpenWrt, linux системы на базе systemd или openrc и MacOS.\n\nДля более гибкой настройки перед запуском инсталлятора следует выполнить раздел \"Выбор параметров\".\n\nЕсли система запуска поддерживается, но используется не поддерживаемый инсталлятором менеджер пакетов\nили названия пакетов не соответствуют прописанным в инсталлятор, пакеты нужно установить вручную.\nВсегда требуется curl. `ipset` - только для режима `iptables`, для `nftables` - не нужен.\n\nДля совсем обрезанных дистрибутивов (alpine) требуется отдельно установить `iptables` и `ip6tables`, либо `nftables`.\n\nВ комплекте идут статические бинарники для большинства архитектур. Какой-то из них подойдет\nс вероятностью 99%. Но если у вас экзотическая система, инсталлятор попробует собрать бинарники сам\nчерез make. Для этого нужны gcc, make и необходимые **-dev** пакеты. Можно форсировать режим\nкомпиляции следующим вызовом:\n\n`install_easy.sh make`\n\nПод OpenWrt все уже сразу готово для использования системы в качестве роутера.\nИмена интерфейсов WAN и LAN известны из настроек системы.\nПод другими системами роутер вы настраиваете самостоятельно. Инсталлятор в это не вмешивается.\nИнсталлятор в зависимости от выбранного режима может спросить LAN и WAN интерфейсы.\nНужно понимать, что заворот проходящего трафика на **tpws** в прозрачном режиме происходит до выполнения маршрутизации,\nследовательно возможна фильтрация по LAN и невозможна по WAN.\nРешение о завороте на **tpws** локального исходящего трафика принимается после выполнения маршрутизации,\nследовательно ситуация обратная: LAN не имеет смысла, фильтрация по WAN возможна.\nЗаворот на **nfqws** происходит всегда после маршрутизации, поэтому к нему применима только фильтрация по WAN.\nВозможность прохождения трафика в том или ином направлении настраивается вами в процессе конфигурации роутера.\n\nДеинсталляция выполняется через `uninstall_easy.sh`. После выполнения деинсталляции можно удалить каталог `/opt/zapret`.\n\n## Установка под systemd\n\nЕсли вам нравится systemd и хочется максимально под него заточиться, можно отказаться от скриптов запуска zapret\nи поднимать инстансы `tpws` и `nfqws` как отдельные юниты systemd. При этом вам придется вручную написать правила iptables/nftables\nи каким-то образом их поднимать. Например, написать дополнительный systemd unit для этого.\nТак же требуется собрать бинарники особым образом через `make systemd`.\n\nВ комплекте zapret есть шаблоны `init.d/systemd/{nfqws@.service,tpws@.service}`.\nКраткий перечень команд для их использования приведен в комментариях в этих файлах.\n\n## Простая установка на openwrt\n\nРаботает только если у вас на роутере достаточно места.\n\nКопируем zapret на роутер в `/tmp`.\n\nЗапускаем установщик:\\\n`sh /tmp/zapret/install_easy.sh`\n\nОн скопирует в `/opt/zapret` только необходимый минимум файлов.\n\nПосле успешной установки можно удалить zapret из tmp для освобождения RAM:\\\n`rm -r /tmp/zapret`\n\nДля более гибкой настройки перед запуском инсталлятора следует выполнить раздел \"Выбор параметров\".\n\nСистема простой инсталяции заточена на любое умышленное или неумышленное изменение прав доступа на файлы.\nУстойчива к репаку под windows. После копирования в `/opt` права будут принудительно восстановлены.\n\n\n## Установка на openwrt в режиме острой нехватки места на диске\n\nТребуется около 120-200 кб на диске. Придется отказаться от всего, кроме **tpws**.\n\n**Инструкция для openwrt 22 и выше с nftables**\n\nНикаких зависимостей устанавливать не нужно.\n\n***Установка:***\n\n1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt.\n2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`.\n3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws`\n4) Отредактируйте `/etc/config/tpws`\n* Если не нужен ipv6, отредактируйте `/etc/nftables.d/90-tpws.nft` и закомментируйте строки с редиректом ipv6.\n5) `/etc/init.d/tpws enable`\n6) `/etc/init.d/tpws start`\n7) `fw4 restart`\n\n***Полное удаление:***\n\n1) `/etc/init.d/tpws disable`\n2) `/etc/init.d/tpws stop`\n3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws /usr/bin/tpws`\n4) `fw4 restart`\n\n**Инструкция для openwrt 21 и ниже с iptables**\n\n***Установите зависимости:***\n1) `opkg update`\n2) `opkg install iptables-mod-extra`\n* только для IPV6: `opkg install ip6tables-mod-nat`\n\nУбедитесь, что в `/etc/firewall.user` нет ничего значимого.\nЕсли есть - не следуйте слепо инструкции. Объедините код или создайте свой `firewall include` в `/etc/config/firewall`.\n\n***Установка:***\n\n1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt.\n2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`.\n3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws`\n4) Отредактируйте `/etc/config/tpws`\n* Если не нужен ipv6, отредактируйте /etc/firewall.user и установите там DISABLE_IPV6=1.\n5) `/etc/init.d/tpws enable`\n6) `/etc/init.d/tpws start`\n7) `fw3 restart`\n\n***Полное удаление:***\n\n1) `/etc/init.d/tpws disable`\n2) `/etc/init.d/tpws stop`\n3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws`\n4) `touch /etc/firewall.user`\n5) `fw3 restart`\n\n\n## Android\n\nБез рута забудьте про nfqws и tpws в режиме transparent proxy. tpws будет работать только в режиме `--socks`.\n\nЯдра Android имеют поддержку NFQUEUE. nfqws работает.\n\nВ стоковых ядрах нет поддержки ipset. В общем случае сложность задачи по поднятию ipset варьируется от\n\"не просто\" до \"почти невозможно\". Если только вы не найдете готовое собранное ядро под ваш девайс.\n\ntpws будет работать в любом случае, он не требует чего-либо особенного.\n\nХотя linux варианты под Android работают, рекомендуется использовать специально собранные под bionic бинарники.\nУ них не будет проблем с DNS, с локальным временем и именами юзеров и групп.\\\nРекомендуется использовать gid 3003 (AID_INET). Иначе можете получить permission denied на создание сокета.\nНапример: `--uid 1:3003`\\\nВ iptables укажите: `! --uid-owner 1` вместо `! --uid-owner tpws`.\\\nНапишите шелл скрипт с iptables и tpws, запускайте его средствами вашего рут менеджера.\nСкрипты автозапуска лежат тут:\\\nmagisk : /data/adb/service.d\\\nsupersu: /system/su.d\n\n**nfqws** может иметь такой глюк. При запуске с uid по умолчанию (0x7FFFFFFF) при условии работы на сотовом интерфейсе\nи отключенном кабеле внешнего питания система может частично виснуть. Перестает работать тач и кнопки,\nно анимация на экране может продолжаться. Если экран был погашен, то включить его кнопкой power невозможно.\nИзменение UID на низкий (--uid 1 подойдет) позволяет решить эту проблему.\nГлюк был замечен на android 8.1 на девайсе, основанном на платформе mediatek.\n\nОтвет на вопрос куда поместить tpws на android без рута, чтобы потом его запускать из приложений.\nФайл заливаем через adb shell в /data/local/tmp/, лучше всего в субфолдер.\n```\nmkdir /data/local/tmp/zapret\nadb push tpws /data/local/tmp/zapret\nchmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws\nchcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws\n```\nКак найти стратегию обхода сотового оператора: проще всего раздать инет на комп.\nДля этого подойдет любая поддерживаемая ОС. Подключите android через USB кабель к компу и включите режим модема.\nПрогоните стандартную процедуру blockcheck. При переносе правил на телефон уменьшить TTL на 1,\nесли правила с TTL присутствуют в стратегии. Если проверялось на windows, убрать параметры `--wf-*`.\n\nРабота blockcheck в android shell не поддерживается, но имея рута можно развернуть rootfs какого-нибудь дистрибутива linux.\nЭто лучше всего делать с компа через adb shell.\nЕсли компа нет, то развертка chroot - единственный вариант, хотя и неудобный.\nПодойдет что-то легковесное, например, alpine или даже OpenWrt.\nЕсли это не эмулятор android, то универсальная архитектура - arm (любой вариант).\nЕсли вы точно знаете, что ОС у вас 64-разрядная, то лучше вместо arm - arm64.\nВыяснить архитектуру можно командой `uname -a`.\n\n```\nmount --bind /dev /data/linux/dev\nmount --bind /proc /data/linux/proc\nmount --bind /sys /data/linux/sys\nchroot /data/linux\n```\n\nПервым делом вам нужно будет один раз настроить DNS. Сам он не заведется.\n\n`echo nameserver 1.1.1.1 >/etc/resolv.conf`\n\nДалее нужно средствами пакетного менеджера установить iptables-legacy. Обязательно **НЕ** iptables-nft,\nкоторый, как правило, присутствует по умолчанию. В ядре android нет nftables.\\\n`ls -la $(which iptables)`\\\nЛинк должен указывать на legacy вариант.\nЕсли нет, значит устанавливайте нужные пакеты вашего дистрибутива, и убеждайтесь в правильности ссылок.\\\n`iptables -S`\\\nТак можно проверить, что ваш `iptables` увидел то, что туда насовал android. `iptables-nft` выдаст ошибку.\nДалее качаем zapret в `/opt/zapret`. Обычные действия с `install_prereq.sh`, `install_bin.sh`, `blockcheck.sh`.\n\nУчтите, что стратегии обхода сотового оператора и домашнего wifi вероятно будут разные.\nВыделить сотового оператора легко через параметр iptables `-o <имя интерфейса>`. Имя может быть, например, `ccmni0`.\nЕго легко увидеть через `ifconfig`.\nWifi сеть - обычно `wlan0`.\n\nПереключать blockcheck между оператором и wifi можно вместе со всем инетом - включив или выключив wifi.\nЕсли найдете стратегию для wifi и впишите ее в автостарт, то при подключении к другому wifi\nона может не сработать или вовсе что-то поломать, потому подумайте стоит ли.\nМожет быть лучше сделать скрипты типа \"запустить обход домашнего wifi\", \"снять обход домашнего wifi\",\nи пользоваться ими по необходимости из терминала.\nНо домашний wifi лучше все-же обходить на роутере.\n\n\n## Мобильные модемы и роутеры huawei\n\nУстройства типа E3372, E8372, E5770 разделяют общую идеологию построения системы.\nИмеются 2 вычислительных ядра. Одно ядро выполняет vxworks, другое - linux.\nНа 4pda имеются модифицированные прошивки с telnet и adb. Их и нужно использовать.\n\nДальнейшие утверждения проверены на E8372. На других может быть аналогично или похоже.\nПрисутствуют дополнительные аппаратные блоки для offload-а сетевых функций.\nНе весь трафик идет через linux. Исходящий трафик с самого модема проходит\nцепочку OUTPUT нормально, на FORWARD =>wan часть пакетов выпадает из tcpdump.\n\ntpws работает обычным образом.\n\n`nfqueue` поломан, можно собрать фиксящий модуль https://github.com/im-0/unfuck-nfqueue-on-e3372h,\nиспользуя исходники с huawei open source. Исходники содержат тулчейн и полусобирающееся,\nнеактуальное ядро. Конфиг можно взять с рабочего модема из `/proc/config.gz`.\nС помощью этих исходников умельцы могут собрать модуль `unfuck_nfqueue.ko`.\nПосле его применения NFQUEUE и nfqws для arm работают нормально.\n\nЧтобы избежать проблемы с offload-ом при использовании nfqws, следует комбинировать tpws в режиме tcp proxy и nfqws.\nПравила NFQUEUE пишутся для цепочки OUTPUT.\nconnbytes придется опускать, поскольку модуля в ядре нет. Но это не смертельно.\n\nСкрипт автозапуска - `/system/etc/autorun.sh`. Создайте свой скрипт настройки zapret,\nзапускайте из конца autorun.sh через \"&\". Скрипт должен в начале делать sleep 5, чтобы дождаться\nподнятия сети и iptables от huawei.\n\n> [!WARNING]\n> На этом модеме происходят хаотические сбросы соединений tcp по непонятным причинам.\n> Выглядит это так, если запускать curl с самого модема:\n```\ncurl www.ru\ncurl: (7) Failed to connect to www.ru port 80: Host is unreachable\n```\nВозникает ошибка сокета EHOSTUNREACH (errno -113). То же самое видно в tpws.\nВ броузере не подгружаются части веб страниц, картинки, стили.\nВ tcpdump на внешнем интерфейсе eth_x виден только единственный и безответный SYN пакет, без сообщений ICMP.\nОС каким-то образом узнает о невозможности установить TCP соединение и выдает ошибку.\nЕсли выполнять подключение с клиента, то SYN пропадают, соединение не устанавливается.\nОС клиента проводит ретрансмиссию, и с какого-то раза подключение удается.\nПоэтому без tcp проксирования в этой ситуации сайты тупят, но загружаются, а с проксированием\nподключение выполняется, но вскоре сбрасывается без каких-либо данных, и броузеры не пытаются установить\nего заново. Поэтому качество броузинга с tpws может быть хуже, но дело не в tpws.\nЧастота сбросов заметно возрастает, если запущен торент клиент, имеется много tcp соединений.\nОднако, причина не в переполнении таблицы conntrack. Увеличение лимитов и очистка conntrack не помогают.\nПредположительно эта особенность связана с обработкой пакетов сброса соединения в hardware offload.\nТочного ответа на вопрос у меня нет. Если вы знаете - поделитесь, пожалуйста.\nЧтобы не ухудшать качество броузинга, можно фильтровать заворот на tpws по ip фильтру.\nПоддержка ipset отсутствует. Значит, все, что можно сделать - создать индивидуальные правила\nна небольшое количество хостов.\n\nНекоторые наброски скриптов присутствуют в [files/huawei](./../files/huawei/). _Не готовое решение!_ Смотрите, изучайте, приспосабливайте.\\\nЗдесь можно скачать готовые полезные статические бинарники для arm, включая curl : https://github.com/bol-van/bins\n\n\n## FreeBSD, OpenBSD, MacOS\n\nОписано в [документации BSD](./bsd.md)\n\n## Windows\n\nОписано в [документации Windows](./windows.md)\n\n\n## Другие прошивки\n\nДля статических бинарников не имеет значения на чем они запущены: PC, android, приставка, роутер, любой другой девайс.\nПодойдет любая прошивка, дистрибутив linux. Статические бинарники запустятся на всем.\nИм нужно только ядро с необходимыми опциями сборки или модулями.\nНо кроме бинарников в проекте используются еще и скрипты, в которых задействуются некоторые\nстандартные программы.\n\nОсновные причины почему нельзя просто так взять и установить эту систему на что угодно:\n* отсутствие доступа к девайсу через shell\n* отсутствие рута\n* отсутствие раздела r/w для записи и энергонезависимого хранения файлов\n* отсутствие возможности поставить что-то в автозапуск\n* отсутствие cron\n* неотключаемый flow offload или другая проприетарщина в netfilter\n* недостаток модулей ядра или опций его сборки\n* недостаток модулей iptables (/usr/lib/iptables/lib*.so)\n* недостаток стандартных программ (типа ipset, curl) или их кастрированность (облегченная замена)\n* кастрированный или нестандартный шелл sh\n\nЕсли в вашей прошивке есть все необходимое, то вы можете адаптировать zapret под ваш девайс в той или иной степени.\nМожет быть у вас не получится поднять все части системы, однако вы можете хотя бы попытаться\nподнять tpws и завернуть на него через -j REDIRECT весь трафик на порт 80.\nЕсли вам есть куда записать tpws, есть возможность выполнять команды при старте, то как минимум\nэто вы сделать сможете. Скорее всего поддержка REDIRECT в ядре есть. Она точно есть на любом роутере,\nна других устройствах под вопросом. NFQUEUE, ipset на большинстве прошивок отсутствуют из-за ненужности.\n\nПересобрать ядро или модули для него будет скорее всего достаточно трудно.\nДля этого вам необходимо будет по крайней мере получить исходники вашей прошивки.\nUser mode компоненты могут быть привнесены относительно безболезненно, если есть место куда их записать.\nСпециально для девайсов, имеющих область r/w, существует проект entware.\nНекоторые прошивки даже имеют возможность его облегченной установки через веб интерфейс.\nentware содержит репозиторий user-mode компонент, которые устанавливаются в /opt.\nС их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра.\n\nМожно попытаться использовать sysv init script таким образом, как это описано в разделе\n\"Прикручивание к системе управления фаерволом или своей системе запуска\".\nВ случае ругани на отсутствие каких-то базовых программ, их следует восполнить посредством entware.\nПеред запуском скрипта путь к дополнительным программам должен быть помещен в PATH.\n\n_Подробное описание настроек для других прошивок выходит за рамки данного проекта._\n\nOpenWrt является одной из немногих относительно полноценных linux систем для embedded devices.\nОна характеризуется следующими вещами, которые и послужили основой выбора именно этой прошивки:\n* полный root доступ к девайсу через shell. на заводских прошивках чаще всего отсутствует, на многих альтернативных есть\n* корень r/w. это практически уникальная особенность OpenWrt. заводские и большинство альтернативных прошивок\n  построены на базе squashfs root (r/o), а конфигурация хранится в специально отформатированной области\n  встроенной памяти, называемой nvram.  не имеющие r/w корня системы сильно кастрированы. они не имеют\n  возможности доустановки ПО из репозитория без специальных вывертов и заточены в основном\n  на чуть более продвинутого, чем обычно, пользователя и управление имеющимся функционалом через веб интерфейс,\n  но функционал фиксированно ограничен. альтернативные прошивки, как правило, могут монтировать r/w раздел\n  в какую-то область файловой системы, заводские обычно могут монтировать лишь флэшки, подключенные к USB,\n  и не факт, что есть поддержка unix файловых системы. может быть поддержка только fat и ntfs.\n* возможность выноса корневой файловой системы на внешний носитель (extroot) или создания на нем оверлея (overlay)\n* наличие менеджера пакетов opkg и репозитория софта\n* flow offload предсказуемо, стандартно и выборочно управляем, а так же отключаем\n* в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно.\n* в репозитории есть все модули iptables, их можно доустановить через opkg\n* в репозитории есть огромное количество стандартных программ и дополнительного софта\n* наличие SDK, позволяющего собрать недостающее\n\n\n## Обход блокировки через сторонний хост\n\nЕсли не работает автономный обход, приходится перенаправлять трафик через сторонний хост.\nПредлагается использовать прозрачный редирект через socks5 посредством `iptables+redsocks`, либо `iptables+iproute+vpn`.\nНастройка варианта с redsocks на OpenWrt описана в [redsocks.txt](./redsocks.txt).\nНастройка варианта с `iproute+wireguard` - в [wireguard_iproute_openwrt.txt](./wireguard_iproute_openwrt.txt).\n\n\n## Почему стоит вложиться в покупку VPS\n\nVPS — это виртуальный сервер. Существует огромное множество датацентров, предлагающих данную услугу.\nНа VPS могут выполняться какие угодно задачи. От простого веб-сайта до навороченной системы собственной разработки.\nМожно использовать VPS и для поднятия собственного VPN или прокси.\nСама широта возможных способов применения и распространенность услуги сводят к минимуму возможности\nрегуляторов по бану сервисов такого типа. Да, если введут белые списки, то решение загнется, но это будет уже другая\nреальность, в которой придется изобретать иные решения.\nПока этого не сделали, никто не будет банить хостинги просто потому, что они предоставляют хостинг услуги.\nВы, как индивидуум, скорее всего, никому не нужны. Подумайте чем вы отличаетесь от известного VPN провайдера.\nVPN-провайдер предоставляет _простую_ и _доступную_ услугу по обходу блокировок для масс.\nЭтот факт делает его первоочередной целью блокировки. РКН направит уведомление, после отказа сотрудничать\nзаблокирует VPN. Предоплаченная сумма пропадет.\nУ регуляторов нет и никогда не будет ресурсов для тотальной проверки каждого сервера в сети.\nВозможен китайский расклад, при котором DPI выявляет VPN-протоколы и динамически банит IP серверов,\nпредоставляющих нелицензированный VPN. Но имея знания, голову, вы всегда можете обфусцировать\nVPN трафик или применить другие типы VPN, более устойчивые к анализу на DPI, или просто менее широкоизвестные,\nа следовательно с меньшей вероятностью обнаруживаемые регулятором.\nУ вас есть свобода делать на вашем VPS все что вы захотите, адаптируясь к новым условиям.\nДа, это потребует знаний. Вам выбирать учиться и держать ситуацию под контролем, когда вам ничего запретить\nне могут, или покориться системе.\n\nVPS можно приобрести в множестве мест. Существуют специализированные на поиске предложений VPS порталы.\\\nНапример, [вот этот](https://vps.today).\nДля персонального VPN сервера обычно достаточно самой минимальной конфигурации, но с безлимитным трафиком или\nс большим лимитом по трафику (терабайты). Важен и тип VPS. OpenVZ подойдёт для OpenVPN, но\nвы не поднимете на нем WireGuard, IPsec, то есть все, что требует kernel mode.\nДля kernel mode требуется тип виртуализации, предполагающий запуск полноценного экземпляра ОС linux\nвместе с ядром. Подойдут KVM, Xen, Hyper-V, VMware.\n\nПо цене можно найти предложения, которые будут дешевле готовой VPN услуги, но при этом вы сам хозяин в своей лавке\nи не рискуете попасть под бан регулятора, разве что «заодно» — под ковровую бомбардировку с баном миллионов IP.\nКроме того, если вам совсем все кажется сложным, прочитанное вызывает ступор и вы точно знаете, что ничего\nиз описанного сделать не сможете, то вы сможете хотя бы использовать динамическое перенаправление портов SSH\nдля получения шифрованного SOCKS-прокси и прописать его в браузер. Знания linux не нужны совсем.\nЭто вариант наименее напряжный для чайников, хотя и не самый удобный в использовании.\n\n## Поддержать разработчика\n\nUSDT ERC `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` \n\nUSDT TRC `TEzAAtn4VhndqEaAyuCM78xh5W2gCjwWEo`\n\nBTC  `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve`\n\nETH  `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E`\n"
  },
  {
    "path": "docs/redsocks.txt",
    "content": "Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему.\nЕсли вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего\nпонимания смысла, то маловероятно, что у вас что-то заработает. Не тратьте свое время напрасно.\nЦель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать.\n\n\nПрозрачный выборочный заворот tcp соединений на роутере через socks\n\nTor поддерживает \"из коробки\" режим transparent proxy. Это можно использовать в теории, но практически - только на роутерах с 128 мб памяти и выше. И тор еще и тормозной.\nДругой вариант напрашивается, если у вас есть доступ к какой-нибудь unix системе с SSH, где сайты не блокируются. Например, у вас есть VPS вне России.\nПонятийно требуются следующие шаги :\n1) Выделять IP, на которые надо проксировать трафик. У нас уже имеется ipset \"zapret\", технология создания которого отработана.\n2) Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks.\n3) Установить transparent соксификатор. Redsocks прекрасно подошел на эту роль.\n4) Завернуть через iptables или nftables трафик с порта назначения 443 и на ip адреса из ipset/nfset 'zapret' на соксификатор\nТоже самое сделать с ipset/nfset 'ipban' для всех tcp портов.\nБуду рассматривать систему на базе openwrt, где уже установлена система обхода dpi \"zapret\".\nЕсли вам не нужны функции обхода DPI, его можно не включать. Обновление фильтра от этого не зависит.\n\n\n* Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks\n\nТ.к. дефолтный dropbear клиент не поддерживает создание socks, то для начала придется заменить dropbear ssh client на openssh : пакеты openssh-client и openssh-client-utils.\nУстанавливать их нужно с опцией opkg --force-overwrite, поскольку они перепишут ssh клиент от dropbear.\nПосле установки пакетов расслабим неоправданно жестокие права : chmod 755 /etc/ssh.\nСледует создать пользователя, под которым будем крутить ssh client. Допустим, это будет 'proxy'.\nСначала установить пакеты shadow-useradd и shadow-su.\n------------------\nuseradd -s /bin/false -d /home/proxy proxy\nmkdir -p /home/proxy\nchown proxy:proxy /home/proxy\n------------------\nСгенерируем ключ RSA для доступа к ssh серверу.\n------------------\nsu -s /bin/ash proxy\ncd\nmkdir -m 700 .ssh\ncd .ssh\nssh-keygen\nls\nexit\n------------------\nДолжны получиться файлы id_rsa и id_rsa.pub.\nСтрочку из id_rsa.pub следует добавить на ssh сервер в файл $HOME/.ssh/authorized_keys.\nБолее подробно о доступе к ssh через авторизацию по ключам : https://beget.com/ru/articles/ssh_by_key\nПредположим, ваш ssh сервер - vps.mydomain.com, пользователь называется 'proxy'.\nПроверить подключение можно так : ssh -N -D 1098 -l proxy vps.mydomain.com.\nСделайте это под пользователем \"proxy\", поскольку при первом подключении ssh спросит о правильности hostkey.\nСоединение может отвалиться в любой момент, поэтому нужно зациклить запуск ssh.\nДля этого лучший вариант - использовать procd - упрощенная замена systemd на openwrt версий BB и выше.\n--- /etc/init.d/socks_vps ---\n#!/bin/sh /etc/rc.common\nSTART=50\nSTOP=50\nUSE_PROCD=1\nUSERNAME=proxy\nCOMMAND=\"ssh -N -D 1098 -l proxy vps.mydomain.com\"\nstart_service() {\n    procd_open_instance\n    procd_set_param user $USERNAME\n    procd_set_param respawn 10 10 0\n    procd_set_param command $COMMAND\n    procd_close_instance\n}\n-----------------------------\nЭтому файлу нужно дать права : chmod +x /etc/init.d/socks_vps\nЗапуск : /etc/init.d/socks_vps start\nОстанов : /etc/init.d/socks_vps stop\nВключить автозагрузку : /etc/init.d/socks_vps enable\nПроверка : curl -4 --socks5 127.0.0.1:1098 https://rutracker.org\n\n\n* Организовать прозрачную соксификацию\n\nУстановить пакет redsocks.\nКонфиг :\n-- /etc/redsocks.conf : ---\nbase {\n\tlog_debug = off;\n\tlog_info = on;\n\tlog = \"syslog:local7\";\n\tdaemon = on;\n\tuser = nobody;\n\tgroup = nogroup;\n\tredirector = iptables;\n}\nredsocks {\n        local_ip = 127.0.0.127;\n        local_port = 1099;\n        ip = 127.0.0.1;\n        port = 1098;\n        type = socks5;\n}\n---------------------------\n\nПосле чего перезапускаем : /etc/init.d/redsocks restart\nСмотрим появился ли листенер : netstat -tnlp | grep 1099\n\nВ zapret для перенаправления DNAT на интерфейс lo используется 127.0.0.127.\nКо всем остальным адресам из 127.0.0.0/8 DNAT может быть заблокирован. Читайте readme.txt про route_localnet.\n\n* Завертывание соединений через iptables\n\n!! Версии OpenWRT до 21.02 включительно используют iptables + fw3. Более новые перешили на nftables по умолчанию.\n!! В новых OpenWRT можно снести firewall4 и nftables, заменив их на firewall3 + iptables\n!! Инструкция относится только к openwrt, где используется iptables.\n\nБудем завертывать любые tcp соединения на ip из ipset \"ipban\" и https на ip из ipset \"zapret\", за исключением ip из ipset \"nozapret\".\n\n--- /etc/firewall.user -----\nSOXIFIER_PORT=1099\n\n. /opt/zapret/init.d/openwrt/functions\n\ncreate_ipset no-update\n\nnetwork_find_wan4_all wan_iface\nfor ext_iface in $wan_iface; do\n    network_get_device ext_device $ext_iface\n    ipt OUTPUT -t nat -o $ext_device -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT\n    ipt OUTPUT -t nat -o $ext_device -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT\ndone\n\nprepare_route_localnet\n\nipt prerouting_lan_rule -t nat -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT\nipt prerouting_lan_rule -t nat -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT\n----------------------------\n\nВнести параметр \"reload\" в указанное место :\n--- /etc/config/firewall ---\nconfig include\n        option path '/etc/firewall.user'\n        option reload '1'\n----------------------------\n\nПерезапуск firewall : /etc/init.d/firewall restart\n\n\n* Завертывание соединений через nftables\n\n!! Только для версий OpenWRT старше 21.02\n\nnftables не могут использовать ipset. Вместо ipset существует аналог - nfset.\nnfset является частью таблицы nftable и принадлежит только к ней. Адресация nfset из другой nftable невозможна.\nСкрипты ipset/* в случае nftables используют nfset-ы в таблице zapret.\nЧтобы использовать эти nfset-ы в своих правилах, необходимо синхронизироваться с их созданием и вносить свои цепочки в nftable \"zapret\".\nДля этого существуют хуки - скрипты, вызываемые из zapret на определенных стадиях инициализации фаервола.\n\nРаскомментируйте в /opt/zapret/config строчку\nINIT_FW_POST_UP_HOOK=\"/etc/firewall.zapret.hook.post_up\"\n\nСоздайте файл /etc/firewall.zapret.hook.post_up и присвойте ему chmod 755.\n\n--- /etc/firewall.zapret.hook.post_up ---\n#!/bin/sh\n\nSOXIFIER_PORT=1099\n\n. /opt/zapret/init.d/openwrt/functions\n\ncat << EOF | nft -f - 2>/dev/null\n delete chain inet $ZAPRET_NFT_TABLE my_output\n delete chain inet $ZAPRET_NFT_TABLE my_prerouting\nEOF\n\nprepare_route_localnet\n\ncat << EOF | nft -f -\n add chain inet $ZAPRET_NFT_TABLE my_output { type nat hook output priority -102; }\n flush chain inet $ZAPRET_NFT_TABLE my_output\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT\n\n add chain inet $ZAPRET_NFT_TABLE my_prerouting { type nat hook prerouting priority -102; }\n flush chain inet $ZAPRET_NFT_TABLE my_prerouting\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT\nEOF\n----------------------------\n\nПерезапуск firewall : /etc/init.d/zapret restart_fw\n\n\n* Проверка\n\nВсе, теперь можно проверять :\n/etc/init.d/redsocks stop\ncurl -4 https://rutracker.org\n# должно обломаться с надписью \"Connection refused\". если не обламывается - значит ip адрес rutracker.org не в ipset,\n# либо не сработали правила фаервола. например, из-за не установленных модулей ipt\n/etc/init.d/redsocks start\ncurl -4 https://rutracker.org\n# должно выдать страницу\n"
  },
  {
    "path": "docs/windows.en.md",
    "content": "### tpws\n\nUsing `WSL` (Windows subsystem for Linux) it's possible to run `tpws` in socks mode under rather new builds of\nwindows 10 and windows server.\nIts not required to install any linux distributions as suggested in most articles.\ntpws is static binary. It doesn't need a distribution.\n\nInstall `WSL` : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all`\n\nFrom release copy `binaries/linux-x86_64/tpws_wsl.tgz` to the target system.\nRun : `wsl --import tpws \"%USERPROFILE%\\tpws\" tpws_wsl.tgz`\n\nRun tpws : `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <fooling_options>`\n\nConfigure socks as `127.0.0.1:1080` in a browser or another program.\n\nCleanup : `wsl --unregister tpws`\n\nTested in windows 10 build 19041 (20.04) with WSL1.\n\n`--oob` , `--mss` and `--disorder` do not work.\nRST detection in autohostlist scheme may not work.\nWSL may glitch with splice. `--nosplice` may be required.\n\n\n### winws\n\n`winws` is `nfqws` version for windows. It's based on `windivert`. Most functions are working.\nLarge ip filters (ipsets) are not possible. Forwarded traffic and connection sharing are not supported.\nAdministrator rights are required.\n\nWorking with packet filter consists of two parts\n\n1. In-kernel packet selection and passing selected packets to a packet filter in user mode.\nIn *nix it's done by `iptables`, `nftables`, `pf`, `ipfw`.\n2. User mode packet filter processes packets and does DPI bypass magic.\n\nWindows does not have part 1. No `iptables` exist. That's why 3rd party packet redirector is used.\nIt's called `windivert`. It works starting from `windows 7`. Kernel driver is signed but it may require to disable secure boot\nor update windows 7. Read below for windows 7 windivert signing info.\n\nTask of `iptables` is done inside `winws` through `windivert` filters. `Windivert` has it's own [filter language](https://reqrypt.org/windivert-doc.html#filter_language).\n`winws` can automate filter construction using simple ip version and port filter. Raw filters are also supported.\n\n```\n --wf-iface=<int>[:<int>]                       ; numeric network interface and subinterface indexes\n --wf-l3=ipv4|ipv6                              ; L3 protocol filter. multiple comma separated values allowed.\n --wf-tcp=[~]port1[-port2]                      ; TCP port filter. ~ means negation. multiple comma separated values allowed.\n --wf-udp=[~]port1[-port2]                      ; UDP port filter. ~ means negation. multiple comma separated values allowed.\n --wf-raw-part=<filter>|@<filename>             ; partial raw windivert filter string or filename\n --wf-filter-lan=0|1                            ; add excluding filter for non-global IP (default : 1)\n --wf-raw=<filter>|@<filename>                  ; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part\n --wf-save=<filename>                           ; save windivert filter string to a file and exit\n --ssid-filter=ssid1[,ssid2,ssid3,...]          ; enable winws only if any of specified wifi SSIDs connected\n --nlm-filter=net1[,net2,net3,...]              ; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted.\n --nlm-list[=all]                               ; list Network List Manager (NLM) networks. connected only or all.                           \n```\n\n`--wf-l3`, `--wf-tcp`, `--wf-udp` can take multiple comma separated arguments.\n\nInterface indexes can be discovered using this command : `netsh int ip show int`\n\nIf you can't find index this way use `winws --debug` to see index there. Subinterface index is almost always 0 and you can omit it.\n\n`--wf-raw-part` specifies partial windivert filter. Multiple filter parts are supported. They can also be combined with `--wf-tcp`,`--wf-udp`.\n\n`--wf-raw` specifies full windivert filter that replaces `--wf-tcp`,`--wf-udp`,`--wf-raw-part`.\n\nKernel filtering with windivert language is much more effective than passing massive amount of traffic to winws. Use it if possible to save CPU resources.\n\nMultiple `winws` processes are allowed. However, it's discouraged to intersect their filters.\n\n`--ssid-filter` allows to enable `winws` only if specified wifi networks are connected. `winws` auto detects SSID appearance and disappearance.\nSSID names must be written in the same case as the system sees them. This option does not analyze routing and does not detect where traffic actually goes.\nIf multiple connections are available, the only thing that triggers `winws` operation is wifi connection presence. That's why it's a good idea to add also `--wf-iface` filter to not break ethernet, for example.\n\n`--nlm-filter` is like `--ssid-filter` but works with names or GUIDs from Network List Manager. NLM names are those you see in Control Panel \"Network and Sharing Center\".\nNLM networks are adapter independent. Usually MAC address of the default router is used to distinugish networks. NLM works with any type of adapters : ethernet, wifi, vpn and others.\nThat's why NLM is more universal than `ssid-filter`.\n\n`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`.\nIf you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`.\n`Cygwin` is required for `blockcheck.sh` support but `winws` itself can be run standalone without cygwin.\n\nHow to get `windows 7` and `winws` compatible `cygwin` :\n```\ncurl -O https://www.cygwin.com/setup-x86_64.exe\nsetup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215\n```\nYou must choose to install `curl`. To compile from sources install `gcc-core`,`make`,`zlib-devel`.\nMake from directory `nfq` using `make cygwin64` or `make cygwin32` for 64 and 32 bit versions.\n\n`winws` requires `cygwin1.dll`, `windivert.dll`, `windivert64.sys` or `windivert32.sys`.\nYou can take them from `binaries/windows-x86_64` or `binaries/windows-x86`.\n\nThere's no `arm64` signed `windivert` driver and no `cygwin`.\nBut it's possible to use unsigned driver version in test mode and user mode components with x64 emulation.\nx64 emulation requires `windows 11` and not supported in `windows 10`.\n\n### windows 7 windivert signing\n\nRequirements for windows driver signing have changed in 2021.\nOfficial free updates of windows 7 ended in 2020.\nAfter 2020 for the years paid updates were available (ESU).\nOne of the updates from ESU enables signatures used in windivert 2.2.2-A.\nThere are several options :\n\n1. Take `windivert64.sys` and `windivert.dll` version `2.2.0-C` or `2.2.0-D` from [here](https://reqrypt.org/download).\nReplace these 2 files in every location they are present.\nIn `zapret-win-bundle` they are in `zapret-winws` и `blockcheck/zapret/nfq` folders.\nHowever this option still requires 10+ year old patch that enables SHA256 signatures.\nIf you're using win bundle you can simply run `win7\\install_win7.cmd`\n\n3. [Hack ESU](https://hackandpwn.com/windows-7-esu-patching)\n\n4. Use `UpdatePack7R2` from simplix : https://blog.simplix.info\nIf you are in Russia or Belarus temporary change region in Control Panel.\n\n### blockcheck\n\n`blockcheck.sh` is written in posix shell and uses some standard posix utilites.\nWindows does not have them. To execute `blockcheck.sh` use `cygwin` command prompt run as administrator.\nIt's not possible to use `WSL`. It's not the same as `cygwin`.\nFirst run once `install_bin.sh` then `blockcheck.sh`.\n\nBackslashes in windows paths shoud be doubled. Or use cygwin path notation.\n```\ncd \"C:\\\\Users\\\\vasya\"\ncd \"C:/Users/vasya\"\ncd \"/cygdrive/c/Users/vasya\"\n```\n`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`.\nIf you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`.\n\n`Cygwin` is required only for `blockcheck.sh`. Standalone `winws` can be run without it.\n\nTo simplify things it's advised to use `zapret-win-bundle`.\n\n### zapret-win-bundle\n\nTo make your life easier there's ready to use [bundle](https://github.com/bol-van/zapret-win-bundle) with `cygwin`,`blockcheck` and `winws`.\n\n* `/zapret-winws` - standalone version of `winws` for everyday use. does not require any other folders.\n* `/zapret-winws/_CMD_ADMIN.cmd` - open `cmd` as administrator in the current folder\n* `/blockcheck/blockcheck.cmd` - run `blockcheck` with logging to `blockcheck/blockcheck.log`\n* `/cygwin/cygwin.cmd` - run `cygwin` shell as current user\n* `/cygwin/cygwin-admin.cmd` - run `cygwin` shell as administrator\n\nThere're aliases in cygwin shell for `winws`,`blockcheck`,`ip2net`,`mdig`. No need to mess with paths.\nIt's possible to send signals to `winws` using standard unix utilites : `pidof,kill,killall,pgrep,pkill`.\n`Cygwin` shares common process list per `cygwin1.dll` copy. If you run a `winws` from `zapret-winws`\nyou won't be able to `kill` it because this folder contain its own copy of `cygwin1.dll`.\n\nIt's possible to use `cygwin` shell to make `winws` debug log. Use `tee` command like this :\n\n```\nwinws --debug --wf-tcp=80,443 | tee winws.log\nunix2dos winws.log\n```\n\n`winws.log` will be in `cygwin/home/<username>`. `unix2dos` helps with `windows 7` notepad. It's not necessary in `Windows 10` and later.\n\nBecause 32-bit systems are rare nowadays `zapret-win-bundle` exists only for `Windows x64/arm64`.\n\n### auto start\n\nTo start `winws` with windows use windows task scheduler. There are `task_*.cmd` batch files in `binaries/windows-x86-64/zapret-winws`.\nThey create, remove, start and stop scheduled task `winws1`. They must be run as administrator.\n\nEdit `task_create.cmd` and write your `winws` parameters to `%WINWS1%` variable. If you need multiple `winws` instances\nclone the code in all cmd files to support multiple tasks `winws1,winws2,winws3,...`.\n\nTasks can also be controlled from GUI `taskschd.msc`.\n\nAlso you can use windows services the same way with `service_*.cmd`.\n\n### Windows Server\n\nwinws is linked against wlanapi.dll which is absent by default.\nTo solve this problem run power shell as administrator and execute command `Install-WindowsFeature -Name Wireless-Networking`.\nThen reboot the system.\n"
  },
  {
    "path": "docs/windows.md",
    "content": "﻿# Windows\n\n## tpws\n\nЗапуск tpws возможен только в Linux варианте под **WSL** _(Windows Subsystem for Linux)_.\nНативного варианта под Windows нет, поскольку он использует epoll, которого под windows не существует.\n\ntpws в режиме socks можно запускать под более-менее современными билдами windows 10 и windows server\nс установленным WSL. Совсем не обязательно устанавливать дистрибутив убунту, как вам напишут почти в каждой\nстатье про WSL, которую вы найдете в сети. tpws - статический бинарик, ему дистрибутив не нужен.\n\nУстановить WSL : \n `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all`\n\nИз релиза скопировать на целевую систему `binaries/linux-x86_64/tpws_wsl.tgz`.\n\nВыполнить :\n `wsl --import tpws \"%USERPROFILE%\\tpws\" tpws_wsl.tgz`\n\nЗапустить :\n `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <параметры_дурения>`\n\nПрописать socks `127.0.0.1:1080` в браузер или другую программу.\n\nУдаление : `wsl --unregister tpws`\n\n\n> [!NOTE]\n> Проверено на windows 10 build 19041 (20.04) под WSL1. На WSL2 эти команды могут не сработать.\nЕсли у вас есть WSL2, значит у вас есть работающая виртуалка с linux.\nЕсли вы умеете с ней обращаться, tpws на ней запустить возможно без всяких проблем.\n\n\nВозможные проблемы: \n- Не работают функции `--oob` и `--mss` из-за ограничений реализации WSL.\n`--disorder` не работает из-за особенностей tcp/ip стека windows.\n\n- Может не срабатывать детект RST в autohostlist.\n\n- WSL может глючить со splice, приводя к зацикливанию процесса. Может потребоваться `--nosplice`.\n\n- Не поддерживается tcp user timeout.\nЧтобы избавиться от сообщений об ошибке добавляйте :\n `--local-tcp-user-timeout=0 --remote-tcp-user-timeout=0`.\nЭти сообщения только информативные, на работу они не влияют.\n\n## winws\n\nЭто вариант пакетного фильтра nfqws для Windows, построенный на базе windivert.\nВсе функции работоспособны, однако функционал ipset в ядре отсутствует. Он реализован в user mode. Фильтры по большому количеству IP адресов невозможны.\nРабота с проходящим трафиком, например в случае \"расшаривания\" соединения, невозможна.\nДля работы с windivert требуются права администратора.\nСпецифические для unix параметры, такие как `--uid`, `--user` и тд, исключены. Все остальные параметры аналогичны nfqws и dvtws.\n\nРабота с пакетным фильтром основана на двух действиях :\n1) Выделение перенаправляемого трафика в режиме ядра и передача его пакетному фильтру в user mode.\n2) Собственно обработка перенаправленных пакетов в пакетном фильтре.\n\nВ windows отсутствуют встроенные средства для перенаправления трафика, такие как _iptables_, _nftables_, _pf_ или _ipfw_.\nПоэтому используется сторонний драйвер ядра windivert. Он работает, начиная с windows 7. На системах с включенным\nsecure boot могут быть проблемы из-за подписи драйвера. В этом случае отключите `secureboot` или включите режим `testsigning`.\nНа windows 7, вероятно, будут проблемы с загрузкой windivert. Читайте ниже соответствующий раздел.\n\nЗадача _iptables_ в **winws** решается внутренними средствами через фильтры windivert.\nУ windivert существует собственный язык фильтров, похожий на язык фильтров wireshark.\n[Документация по фильтрам windivert.](https://reqrypt.org/windivert-doc.html#filter_language)\nЧтобы не писать сложные фильтры вручную, предусмотрены различные упрощенные варианты автоматического построения фильтров.\n\n```\n --wf-iface=<int>[.<int>]               ; числовые индексы интерфейса и суб-интерфейса\n --wf-l3=ipv4|ipv6                      ; фильтр L3 протоколов. по умолчанию включены ipv4 и ipv6.\n --wf-tcp=[~]port1[-port2]              ; фильтр портов для tcp. ~ означает отрицание\n --wf-udp=[~]port1[-port2]              ; фильтр портов для udp. ~ означает отрицание\n --wf-raw-part=<filter>|@<filename>     ; частичный windivert фильтр из параметра или из файла. имени файла предшествует символ @. может быть множество частей. сочетается с --wf-tcp,--wf-udp.\n --wf-filter-lan=0|1                    ; отфильтровывать адреса назначения, не являющиеся глобальными inet адресами ipv4 или ipv6. по умолчанию - 1.\n --wf-raw=<filter>|@<filename>          ; полный windivert фильтр из параметра или из файла. имени файла предшествует символ @. замещает --wf-raw-part,--wf-tcp,--wf-udp.\n --wf-save=<filename>                   ; сохранить сконструированный фильтр windivert в файл для последующей правки вручную\n --ssid-filter=ssid1[,ssid2,ssid3,...]  ; включать winws только когда подключена любая из указанных wifi сетей\n --nlm-filter=net1[,net2,net3,...]      ; включать winws только когда подключена любая из указанных сетей NLM\n --nlm-list[=all]                       ; вывести список сетей NLM. по умолчанию только подключенных, all - всех.\n ```\n\nПараметры `--wf-l3`, `--wf-tcp`, `--wf-udp` могут брать несколько значений через запятую.\n\nНомера интерфейсов можно узнать так : `netsh int ip show int`.\nНекоторых типы соединений там не увидеть. В этом случае запускайте **winws** с параметром `--debug` и смотрите IfIdx там.\nSubInterface используется windivert, но практически всегда **0**, его можно не указывать. Вероятно, он нужен в редких случаях.\n\nКонструктор стандартных фильтров автоматически включает входящие tcp пакеты с tcp synack и tcp rst для корректной работы функций\nautottl и autohostlist. При включении autohostlist так же перенаправляются пакеты данных с http redirect с кодами 302 и 307.\nЕсли не указаное иное, добавляется фильтр на исключение не-интернет адресов ipv4 и ipv6.\nДля сложных нестандартных сценариев могут потребоваться свои фильтры. Полный фильтр --wf-raw замещает все остальное.\nЧастичные фильтры `--wf-raw-part` совместимы друг с другом и `--wf-tcp` и `--wf-udp`. Они позволяют исключить написание\nгромоздких полных фильтров, сосредоточившись лишь на добавлении какого-то особенного пейлоада.\n`--wf-save` позволяет записать итоговый windivert фильтр в файл. Максимальный размер фильтра - **16 Kb**.\n\nФильтрация windivert производится в ядре. Это несравнимо легче по ресурсам, чем перенаправлять пакеты в пространство user mode,\nчтобы winws принимал решение. Поэтому пользуйтесь по максимуму возможностями windivert.\nНапример, если вам нужно дурить wireguard на все порты, вам придется перенаправить все порты на winws. Или же написать windivert фильтр, который отсечет wireguard по содержимому пакета.\nРазница в нагрузке на процессор колоссальна. В первом случае - до 100% одного ядра cpu в зависимости от объема исходящего udp трафика (привет, торрент и uTP), во втором - близко к 0.\nКроме нагрузки на процессор еще можете порезать себе скорость, тк одно ядро не будет справляться с обработкой вашего гигабитного интернета. А на старых ноутах еще и получите самолетный вой системы охлаждения, приводящий к ее износу.\n\nМожно запускать несколько процессов **winws** с разными стратегиями. Однако, не следует делать пересекающиеся фильтры.\n\nВ `--ssid-filter` можно через запятую задать неограниченное количество имен wifi сетей (**SSID**). Если задана хотя бы одна сеть,\nто winws включается только, если подключен указанный **SSID**. Если **SSID** исчезает, winws отключается. Если **SSID** появляется снова,\nwinws включается. Это нужно, чтобы можно было применять раздельное дурение к каждой отдельной wifi сети.\nНазвания сетей должны быть написаны в том регистре, в котором их видит система. Сравнение идет с учетом регистра!\nПри этом нет никаких проверок куда реально идет трафик. Если одновременно подключен, допустим, ethernet, \nи трафик идет туда, то дурение включается и выключается просто по факту наличия wifi сети, на которую трафик может и не идти.\nИ это может сломать дурение на ethernet. Поэтому полезно так же будет добавить фильтр `--wf-iface` на индекс интерфейса wifi адаптера, \nчтобы не трогать другой трафик.\n\n`--nlm-filter` аналогичен `--ssid-filter`, но работает с именами или GUIDами сетей Network List Manager (NLM).\nЭто те сети, которые вы видите в панели управления в разделе \"Центр управления сетями и общим доступом\".\nПод сетью подразумевается не конкретный адаптер, а именно сетевое окружение конкретного подключения.\nОбычно проверяется mac адрес шлюза. К сети можно подключиться через любой адаптер, и она останется той же самой.\nЕсли подключиться, допустим, к разными роутерам по кабелю, то будут разные сети.\nА если к одному роутеру через 2 разных сетевых карточки на том же компе - будет одна сеть.\nNLM абстрагирует типы сетевых адаптеров. Он работает как с wifi, так и с ethernet и любыми другими.\nПоэтому это более универсальный метод, чем **SSID** фильтр.\nОднако, есть и неприятная сторона. В windows 7 вы легко могли ткнуть на иконку сети и выбрать тип : private или public.\nТам же вы могли посмотреть список сетей и объединить их. Чтобы, допустим, вы могли подключаться по кабелю и wifi\nк одному роутеру, и система эти подключения воспринимала как одну сеть.\nВ следующих версиях windows они эти возможности сильно порезали. Похоже нет встроенных средств полноценно управлять\nnetwork locations в win10/11. Кое-что есть в **powershell**.\nМожно поковыряться напрямую в реестре здесь : \n`HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList`\nНужно менять ProfileGUID в `Signatures\\Unmanaged`. Имена можно поменять в Profiles.\nЕсть кое-какие сторонние утилиты. Кое-что находится, позволяющее посмотреть и удалить network profiles, но не объединить.\nФакт, что в ms они это сильно испортили. Движок network list все тот же, и он способен на все то, что было в win7.\nМожно не бороться с этой проблемой, а просто указывать через запятую те названия сетей или GUIDы, которые выбрала система.\nИли если у вас только wifi, то использовать `--ssid-filter`. Там хотя бы есть гарантия, что **SSID** соответствуют реальности,\nа система их не назвала как-то по-своему.\n\nЕсли в путях присутствуют национальные символы, то при вызове winws из `cmd` или `bat` кодировку нужно использовать **OEM**.\nДля русского языка это 866. Пути с пробелами нужно брать в кавычки.\nПри использовании опции @<config_file> кодировка в файле должна быть **UTF-8** без **BOM mark**.\n\nСуществует неочевидный момент, касаемый запуска **winws** из cygwin shell\\`а. Если в директории, где находится winws, находится\nкопия `cygwin1.dll`, **winws** не запустится.\nЕсли нужен запуск под cygwin, то следует удалить или переместить `cygwin1.dll` из `binaries/windows-x86_64`. Это нужно для работы blockcheck.\nИз cygwin шелла можно посылать winws сигналы через `kill` точно так же, как в `*nix`.\n\nКак получить совместимый с windows 7 и winws cygwin :\n\n`curl -O https://www.cygwin.com/setup-x86_64.exe`\n\n`setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215`\n\n> [!IMPORTANT]\n> Следует выбрать установку curl.\n\nДля сборки из исходников требуется _gcc-core_,_make_,_zlib-devel_.\nСобирать из директории nfq командой `make cygwin64` или `make cygwin32` для 64 и 32 битных версий соответственно.\n**winws** требует `cygwin1.dll`, `windivert.dll`, `windivert64.sys` или `windivert32.sys`.\nИх можно взять из `binaries/win64` и `binaries/win32`.\n\nДля _arm64_ windows нет подписанного драйвера windivert и нет cygwin.\nОднако, эмуляция x64 windows 11 позволяет использовать все, кроме WinDivert64.sys без изменений.\nНо при этом надо заменить WinDivert64.sys на неподписанную _arm64_ версию и установить режим testsigning.\n\n## Windows 7 и windivert\n\nТребования к подписи драйверов windows изменились в 2021 году.\nОфициальные бесплатные обновления windows 7 закончились в 2020.\nПосле этого несколько лет продолжали идти платные обновления по программе **ESU**.\nИменно в этих **ESU** обновлениях находится обновление ядра windows 7, позволяющее загрузить драйвер\n_windivert 2.2.2-A_, который идет в поставке zapret.\nПоэтому варианты следующие :\n\n1) Взять `windivert64.sys` и `windivert.dll` версии _2.2.0-C_ или _2.2.0-D_ отсюда : https://reqrypt.org/download\nи заменить эти 2 файла.\nВ [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle) есть отдельных 2 места, где находится **winws** : [_zapret-winws_](https://github.com/bol-van/zapret-win-bundle/tree/master/zapret-winws) и [_blockcheck/zapret/nfq_](https://github.com/bol-van/zapret-win-bundle/tree/master/blockcheck).\nНадо менять в обоих местах.\nАльтернативный вариант при использовании win bundle - запустить `win7\\install_win7.cmd`\n\n> [!NOTE]\n> Этот вариант проверен и должен работать. Тем не менее патч 10 летней давности, который включает SHA256 сигнатуры, все еще необходим.\n\n2) Взломать **ESU** :\nhttps://hackandpwn.com/windows-7-esu-patching/\nhttp://www.bifido.net/tweaks-and-scripts/8-extended-security-updates-installer.html\nи обновить систему\n\n3) Использовать готовый патчер-взламыватель ESU - \"BypassESU\". Его надо искать по названию.\n\n4) Использовать UpdatePack7R2 от simplix : https://blog.simplix.info\n> [!WARNING]\n> Но с этим паком есть проблема. Автор из Украины, он очень обиделся на русских.\n> Если в панели управления стоит регион RU или BY, появляется неприятный диалог.\n> Чтобы эту проблему обойти, можно поставить временно любой другой регион, потом вернуть.\n> Так же нет никаких гарантий, что автор не насовал туда какой-то зловредный код.\n> Использовать на свой страх и риск.\n\nБолее безопасный вариант - скачать последнюю нормальную довоенную версию : 22.2.10\nhttps://nnmclub.to/forum/viewtopic.php?t=1530323\nЕе достаточно, чтобы _windivert 2.2.2-A_ заработал на windows 7.\n\n## blockcheck\n\n`blockcheck.sh` написан на _posix shell_ и требует некоторых стандартных утилит _posix_. В windows, естественно, этого нет.\nПотому просто так запустить `blockcheck.sh` невозможно.\nДля этого требуется скачать и установить _cygwin_ так , как описано в предыдущем разделе.\nСледует запустить от имени администратора _cygwin shell_ через `cygwin.bat`.\nВ нем нужно пройти в директорию с zapret.\nОбратные слэши путей windows нужно удваивать, менять на прямые слэши, либо использовать отображение на unix path.\nКорректные варианты : \n- `cd C:\\\\Users\\\\vasya`\n- `cd C:/Users/vasya`\n- `cd /cygdrive/c/Users/vasya`\n\nСуществует неочевидный момент, каcаемый запуска **winws** из _cygwin_ шелла. Если в директории, где находится **winws**, есть копия `cygwin1.dll`, **winws** не запустится. Нужно переименовать файл `cygwin1.dll`.\nДалее все как в _*nix_ : 1 раз `./install_bin.sh` , затем `./blockcheck.sh`.\nWSL использовать нельзя, это не то же самое.\n\n_cygwin_ для обычной работы **winws** не нужен.\n\nОднако, хотя такой способ и работает, использование **winws** сильно облегчает [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle).\nТам нет проблемы с `cygwin.dll`.\n\n## Zapret-win-bundle\n\nМожно не возиться с _cygwin_, а взять готовый пакет, включающий в себя _cygwin_ и _blockcheck_ : https://github.com/bol-van/zapret-win-bundle\nТам сделан максимум удобств для сосредоточения на самом zapret, исключая возню с установкой _cygwin_,\nзаходами в директории, запусками под администратором и прочими сугубо техническими моментами, в которых могут быть\nошибки и непонимания, а новичок без базиса знаний может и вовсе запутаться.\n\n`/zapret-winws` - здесь все, что нужно для запуска winws в повседневном рабочем режиме. остальное не нужно.\\\n`/zapret-winws/_CMD_ADMIN.cmd` - получить командную строку cmd в этой директории от имени администратора для тестирования **winws**\nс параметрами, вводимыми вручную\\\n`/blockcheck/blockcheck.cmd` - достаточно кликнуть по нему, чтобы пошел _blockcheck_ с записью лога в `blockcheck/blockcheck.log`\\\n`/cygwin/cygwin.cmd` - запуск среды _cygwin bash_ под текущим пользователем\\\n`/cygwin/cygwin-admin.cmd` - запуск среды _cygwin bash_ под администратором\n\nВ среде _cygwin_ уже настроены alias-ы на winws,blockcheck,ip2net,mdig. С путями возиться не нужно!\n\n> [!TIP]\n> Из cygwin можно не только тестировать winws, но и посылать сигналы.\n> Доступны команды:\n>-  `pidof`\n>-  `kill`\n>-  `killall`\n>-  `pgrep`\n>-  `pkill`\n\nНо важно понимать, что таким образом не выйдет посылать сигналы **winws**, запущенному из _zapret-winws_,\nпоскольку там свой `cygwin1.dll`, и они не разделяют общее пространство процессов unix.\n_zapret-winws_ - это отдельный комплект для повседневного использования, не требующий что-то еще, но и не связанный со _средой cygwin_.\nСпециально для посылки сигналов winws в _zapret-winws_ присутствует killall.exe.\n\nСреду cygwin можно использовать для записи в файл дебаг-лога winws. Для этого пользуйтесь командой tee.\n`winws --debug --wf-tcp=80,443 | tee winws.log`\n`winws.log` будет в `cygwin/home/<имя_пользователя>`\nЕсли у вас windows 7, то блокнот не поймет переводы строк в стиле unix. Воспользуйтесь командой\n`unix2dos winws.log`\n\n> [!CAUTION]\n> Поскольку 32-битные windows мало востребованы, _zapret-win-bundle_ существует только в варианте для windows _x64/arm64_.\n\n## Автозапуск winws\n\nДля запуска **winws** вместе с windows есть 2 варианта. Планировщик задач или службы windows.\n\nМожно создавать задачи и управлять ими через консольную программу schtasks.\nВ директории `binaries/windows-x86_64/winws` подготовлены файлы `task_*.cmd` .\nВ них реализовано создание, удаление, старт и стоп одной копии процесса winws с параметрами из переменной `%WINWS1%`.\nИсправьте параметры на нужную вам стратегию. Если для разных фильтров применяется разная стратегия, размножьте код\nдля задач _winws1_,_winws2_,_winws3_,_..._\n\nАналогично настраивается вариант запуска через службы windows. Смотрите `service_*.cmd`.\n\nВсе батники требуется запускать от имени администратора.\n\nУправлять задачами можно так же из графической программы управления планировщиком `taskschd.msc`\n\n## Особенности Windows Server\n\nwinws слинкован с wlanapi.dll, который по умолчанию не установлен в windows server.\nДля решения этой проблемы запустите power shell под администратором и выполните команду `Install-WindowsFeature -Name Wireless-Networking`.\nПосле чего перезагрузите систему.\n"
  },
  {
    "path": "docs/wireguard_iproute_openwrt.txt",
    "content": "﻿Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему.\r\nЕсли вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего\r\nпонимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно.\r\nЦель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать.\r\n\r\n\r\nЕсть возможность поднять свой VPN сервер ? Не хотим использовать redsocks ?\r\nХотим завертывать на VPN только часть трафика ?\r\nНапример, из ipset zapret только порт tcp:443, из ipban - весь трафик, не только tcp ?\r\nДа, с VPN такое возможно.\r\nОпишу понятийно как настраивается policy based routing в openwrt на примере wireguard.\r\nВместо wireguard можно использовать openvpn или любой другой. Но wireguard прекрасен сразу несколькими вещами.\r\nГлавная из которых - в разы большая скорость, даже немного превышающая ipsec.\r\nВедь openvpn основан на tun, а tun - всегда в разы медленнее решения в kernel mode,\r\nи если для PC оно может быть не так актуально, для soho роутеров - более чем.\r\nWireguard может дать 50 mbps там, где openvpn еле тащит 10.\r\nНо есть и дополнительное требование. Wireguard работает в ядре, значит ядро должно\r\nбыть под вашим контролем. vps на базе openvz не подойдет. Нужен xen, kvm,\r\nлюбой другой вариант, где загружается ваше собственное ядро, а не используется\r\nобщее, разделяемое на множество vps.\r\n\r\nПонятийно необходимо выполнить следующие шаги :\r\n1) Поднять vpn сервер.\r\n2) Настроить vpn клиент. Результат этого шага - получение поднятого интерфейса vpn.\r\nБудь то wireguard, openvpn или любой другой тип vpn.\r\n3) Создать такую схему маршрутизации, при которой пакеты, помечаемые особым mark,\r\nпопадают на vpn, а остальные идут обычным способом.\r\n4) Создать правила, выставляющие mark для всего трафика, который необходимо рулить на vpn.\r\nКритерии могут быть любые, ограниченные лишь возможностями iptables и вашим воображением.\r\n\r\nБудем считать наш vpn сервер находится на ip 91.15.68.202.\r\nВешать его будем на udp порт 12345. На этот же порт будем вешать и клиентов.\r\nСервер работает под debian 9 или выше. Клиент работает под openwrt.\r\nДля vpn отведем подсеть 192.168.254.0/24.\r\n\r\n--- Если нет своего сервера ---\r\n\r\nНо есть конфиг от VPN провайдера или от друга \"Васи\", который захотел с вами поделиться.\r\nТогда вам не надо настраивать сервер, задача упрощается. Делается невозможным вариант настройки\r\nбез masquerade (см ниже).\r\nИз конфига вытаскиваете приватный ключ своего пира и публичный ключ сервера, ip/host/port сервера,\r\nиспользуете их в настройках openwrt вместо сгенеренных самостоятельно.\r\n\r\n--- Поднятие сервера ---\r\n\r\nWireguard был включен в ядро linux с версии 5.6.\r\nЕсли у вас ядро >=5.6, то достаточно установить пакет wireguard-tools. Он содержит user-mode компоненты wireguard.\r\nПосмотрите, возможно в вашем дистрибутиве ядро по умолчанию более старое, но в репозитории\r\nимеются бэкпорты новых версий. Лучше будет обновить ядро из репозитория.\r\n\r\nВ репозитории может быть пакет wireguard-dkms. Это автоматизированное средство сборки\r\nwireguard с исходников, в том числе модуль ядра. Можно пользоваться им.\r\n\r\nИначе вам придется собрать wireguard самому. Ядро должно быть не ниже 3.10.\r\nНа сервере должны быть установлены заголовки ядра (linux-headers-...) и компилятор gcc.\r\n\r\n# git clone --depth 1 https://git.zx2c4.com/wireguard-linux-compat\r\n# cd wireguard-linux-compat/src\r\n# make\r\n# strip --strip-debug wireguard.ko\r\n# sudo make install\r\n\r\nwireguard основан на понятии криптороутинга. Каждый пир (сервер - тоже пир)\r\nимеет пару открытый/закрытый ключ. Закрытый ключ остается у пира,\r\nоткрытый прописывается у его партнера. Каждый пир авторизует другого\r\nпо знанию приватного ключа, соответствующего прописанному у него публичному ключу.\r\nПротокол построен таким образом, что на все неправильные udp пакеты не следует ответа.\r\nНе знаешь приватный ключ ? Не смог послать правильный запрос ? Долбись сколько влезет,\r\nя тебе ничего не отвечу. Это защищает от активного пробинга со стороны DPI и просто\r\nэкономит ресурсы.\r\nЗначит первым делом нужно создать 2 пары ключей : для сервера и для клиента.\r\nwg genkey генерит приватный ключ, wg pubkey получает из него публичный ключ.\r\n\r\n# wg genkey\r\noAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0=\r\n# echo oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= | wg pubkey\r\nbCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=\r\n# wg genkey\r\nOKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM=\r\n# echo OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= | wg pubkey\r\nEELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg=\r\n\r\nПишем конфиг\r\n--/etc/wireguard/wgvps.conf-------------------\r\n[Interface]\r\nPrivateKey = OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM=\r\nListenPort = 12345\r\n\r\n[Peer]\r\n#Endpoint =\r\nPublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=\r\nAllowedIPs = 192.168.254.3\r\nPersistentKeepalive=20\r\n----------------------------------------------\r\n\r\nWireguard - минималистичный vpn. В нем нет никаких средств для автоконфигурации ip.\r\nВсе придется прописывать руками.\r\nВ wgvps.conf должны быть перечислены все пиры с их публичными ключами,\r\nа так же прописаны допустимые для них ip адреса.\r\nНазначим нашему клиенту 192.168.254.3. Сервер будет иметь ip 192.168.254.1.\r\nEndpoint должен быть прописан хотя бы на одном пире.\r\nЕсли endpoint настроен для пира, то wireguard будет периодически пытаться к нему подключиться.\r\nВ схеме клиент/сервер у сервера можно не прописывать endpoint-ы пиров, что позволит\r\nменять ip и быть за nat. Endpoint пира настраивается динамически после успешной фазы\r\nпроверки ключа.\r\n\r\nВключаем маршрутизацию :\r\n# echo net.ipv4.ip_forward = 1 >>/etc/sysctl.conf\r\n# sysctl -p\r\n\r\nИнтерфейс конфигурируется стандартно для дебианоподобных систем :\r\n\r\n--/etc/network/interfaces.d/wgvps-------------\r\nauto wgvps\r\niface wgvps inet static\r\n        address 192.168.254.1\r\n        netmask 255.255.255.0\r\n        pre-up ip link add $IFACE type wireguard\r\n        pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf\r\n        post-up iptables -t nat -A POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE\r\n        post-up iptables -A FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu\r\n        post-down iptables -D FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu\r\n        post-down iptables -t nat -D POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE\r\n        post-down ip link del $IFACE\r\n----------------------------------------------\r\n\r\nПоднятие через ifup wgvps, опускание через ifdown wgvps.\r\nПри поднятии интерфейса заодно настраивается nat. eth0 здесь означает интерфейс vpn сервера с инетовским ip адресом.\r\nЕсли у вас какая-то система управления фаерволом, то надо настройку nat прикручивать туда.\r\nПример написан для простейшего случая, когда никаких ограничений нет, таблицы iptables пустые.\r\nЧтобы посмотреть текущие настройки wireguard, запустите 'wg' без параметров.\r\n\r\n\r\n--- Поднятие клиента ---\r\n\r\n# opkg update\r\n# opkg install wireguard-tools\r\n\r\nДобавляем записи в конфиги.\r\n\r\n--/etc/config/network--------------------------\r\nconfig interface 'wgvps'\r\n\toption proto 'wireguard'\r\n\toption auto '1'\r\n\toption private_key 'oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0='\r\n\toption listen_port '12345'\r\n\toption metric '9'\r\n\toption mtu '1420'\r\n\r\nconfig wireguard_wgvps\r\n\toption public_key 'EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg='\r\n\tlist allowed_ips '0.0.0.0/0'\r\n\toption endpoint_host '91.15.68.202'\r\n\toption endpoint_port '12345'\r\n\toption route_allowed_ips '0'\r\n\toption persistent_keepalive '20'\r\n\r\nconfig interface 'wgvps_ip'\r\n\toption proto 'static'\r\n\toption ifname '@wgvps'\r\n\tlist ipaddr '192.168.254.3/24'\r\n\r\nconfig route\r\n\toption interface 'wgvps'\r\n\toption target '0.0.0.0/0'\r\n\toption table '100'\r\n\t\r\nconfig rule\r\n\toption mark '0x800/0x800'\r\n\toption priority '100'\r\n\toption lookup '100'\r\n------------------------------------------------\r\n\r\n--/etc/config/firewall--------------------------\r\nconfig zone\r\n\toption name 'tunvps'\r\n\toption output 'ACCEPT'\r\n\toption input 'REJECT'\r\n\toption masq '1'\r\n\toption mtu_fix '1'\r\n\toption forward 'REJECT'\r\n\toption network 'wgvps wgvps_ip'\r\n\r\nconfig forwarding\r\n\toption dest 'tunvps'\r\n\toption src 'lan'\r\n\r\nconfig rule\r\n\toption name 'Allow-ICMP-tunvps'\r\n\toption src 'tunvps'\r\n\toption proto 'icmp'\r\n\toption target 'ACCEPT'\r\n\t\r\nconfig rule\r\n\toption target 'ACCEPT'\r\n\toption src 'wan'\r\n\toption proto 'udp'\r\n\toption family 'ipv4'\r\n\toption src_port '12345'\r\n\toption src_ip '91.15.68.202'\r\n\toption name 'WG-VPS'\r\n------------------------------------------------\r\n\r\nЧто тут было сделано :\r\n*) Настроен интерфейс wireguard. Указан собственный приватный ключ.\r\n*) Настроен пир-партнер с указанием его публичного ключа и endpoint (ip:port нашего сервера)\r\n   такая настройка заставит периодически долбиться на сервер по указанному ip\r\n   route_allowed_ip '0'  запрещает автоматическое создание маршрута\r\n   allowed_ips '0.0.0.0/0' разрешает пакеты с любым адресом источника.\r\n    ведь мы собираемся подключаться к любым ip в инете\r\n   persistent_keepalive '20'  помогает исключить дропание mapping на nat-е, если мы сидим за ним,\r\n    да и вообще полезная вещь, чтобы не было подвисших пиров\r\n*) Статическая конфигурация ip интерфейса wgvps.\r\n*) Маршрут default route на wgvps в отдельной таблице маршрутизации с номером 100. Аналог команды ip route add .. table 100\r\n*) Правило использовать таблицу 100 при выставлении в mark бита 0x800. Аналог команды ip rule.\r\n*) Отдельная зона фаервола для VPN - 'tunvps'. В принципе ее можно не создавать, можете приписать интерфейс к зоне wan.\r\n   Но в случае с отдельной зоной можно настроить особые правила на подключения с vpn сервера в сторону клиента.\r\n*) Разрешение форвардинга между локалкой за роутером и wgvps.\r\n*) Разрешение принимать icmp от vpn сервера, включая пинги. ICMP жизненно важны для правильного функционирования ip сети !\r\n*) И желательно проткнуть дырку в фаерволе, чтобы принимать пакеты wireguard со стороны инетовского ip vpn сервера.\r\n   Конечно, оно скорее всего заработает и так, потому что первый пакет пойдет от клиента к серверу и тем самым создаст\r\n   запись в conntrack. Все дальнейшие пакеты в обе стороны подпадут под состояние ESTABLISHED и будут пропущены.\r\n   Запись будет поддерживаться за счет периодических запросов keep alive. Но если вы вдруг уберете keep alive или\r\n   выставите таймаут, превышающий udp таймаут в conntrack, то могут начаться ошибки, висы и переподключения.\r\n   Если же в фаерволе проткнута дырка, то пакеты от сервера не будут заблокированы ни при каких обстоятельствах.\r\n   \r\n# /etc/init.d/firewall restart\r\n# ifup wgvps\r\n# ifconfig wgvps\r\n# ping 192.168.254.1\r\n\r\nЕсли все хорошо, должны ходить пинги.\r\nС сервера не помешает :\r\n# ping 192.168.254.3\r\n\r\n\r\n--- Подготовка zapret ---\r\n\r\nВыполните install_easy.sh. Он настроит режим обхода DPI. Если обход DPI не нужен - не включайте tpws и nfqws.\r\nТак же инсталятор заресолвит домены из ipset/zapret-hosts-user-ipban.txt и внесет крон-джоб для периодического обновления ip.\r\n\r\nЕсли вы используете в своих правилах ipset zapret, то он ресолвится и обновляется только, если выбран режим фильтрации обхода DPI по ipset.\r\nПо сути он вам нужен исключительно, если обход DPI не помогает. Например, удается как-то пробить http, но не удается пробить https.\r\nИ при этом вы хотите, чтобы на VPN направлялись только ip из скачанного ip листа, в добавок к заресолвленному ipset/zapret-hosts-user.txt.\r\nИменно этот случай и рассмотрен в данном примере. Если это не так, то убирайте правила с портом 443 из нижеприведенных правил iptables/nftables.\r\nЕсли не хотите ограничиваться листом, и хотите направлять все на порт 443, то уберите фильтры из правил iptables/nftables,\r\nсвязанные с ipset/nfset \"zapret\".\r\n\r\nФильтрация по именам доменов (MODE_FILTER=hostlist) невозможна средствами iptables/nftables. Она производится исключительно в tpws и nfqws\r\nпо результатам анализа протокола прикладного уровня, иногда достаточно сложного, связанного с дешифровкой пакета (QUIC).\r\nСкачиваются листы с именами доменов, не ip адресами. ipset/zapret-hosts-user.txt не ресолвится, а используется как hostlist.\r\nПотому вам нельзя расчитывать на ipset zapret.\r\nТем не менее при выборе этого режима фильтрации , либо вовсе при ее отсутствии (MODE_FILTER=none), ipset/zapret-hosts-user-ipban.txt\r\nвсе равно ресолвится. Вы всегда можете расчитывать на ipset/nfset \"ipban\", \"nozapret\".\r\n\r\n\"nozapret\" - это ipset/nfset, связанный с системой исключения ip. Сюда загоняется все из ipset/zapret-hosts-user-exclude.txt после ресолвинга.\r\nЕго учет крайне желателен, чтобы вдруг из скачанного листа не просочились записи, например, 192.168.0.0/16 и не заставили лезть туда через VPN.\r\nХотя скрипты получения листов и пытаются отсечь IP локалок, но так будет намного надежнее.\r\n\r\n--- Маркировка трафика ---\r\n\r\nЗавернем на vpn все из ipset zapret на tcp:443 и все из ipban.\r\nOUTPUT относится к исходящим с роутера пакетам, PREROUTING - ко всем остальным.\r\nЕсли с самого роутера ничего заруливать не надо, можно опустить часть, отвечающую за OUTPUT.\r\n\r\n--/etc/firewall.user----------------------------\r\n. /opt/zapret/init.d/openwrt/functions\r\n\r\ncreate_ipset no-update\r\n\r\nnetwork_find_wan4_all wan_iface\r\nfor ext_iface in $wan_iface; do\r\n\tnetwork_get_device DEVICE $ext_iface\r\n\tipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\n\tipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\ndone\r\n\r\nnetwork_get_device DEVICE lan\r\nipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\nipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\n------------------------------------------------\r\n\r\n# /etc/init.d/firewall restart\r\n\r\nЧтобы правила обновлялись в процессе поднятия интерфейсов в системе, нужно внести параметр \"reload\" в указанное место :\r\n--- /etc/config/firewall ---\r\nconfig include\r\n        option path '/etc/firewall.user'\r\n        option reload '1'\r\n----------------------------\r\n\r\n\r\n--- Маркировка трафика nftables ---\r\n\r\nВ новых openwrt по умолчанию установлен nftables, iptables отсутствует.\r\nЕсть вариант снести nftables + fw4 и заменить их на iptables + fw3.\r\nВеб интерфейс luci понимает прозрачно и fw3, и fw4. Однако, при установке iptables и fw3 новые пакеты\r\nбудут устанавливаться без сжатия squashfs. Убедитесь, что у вас достаточно места.\r\nЛибо сразу настраивайте образ через image builder.\r\n\r\nФаервол fw4 работает в одноименной nftable - \"inet fw4\". \"inet\" означает, что таблица принимает и ipv4, и ipv6.\r\nПоскольку для маркировки трафика используется nfset, принадлежащий таблице zapret, цепочки необходимо помещать в ту же таблицу.\r\nДля синхронизации лучше всего использовать хук\r\nINIT_FW_POST_UP_HOOK=\"/etc/firewall.zapret.hook.post_up\"\r\nПараметр нужно раскоментировать в /opt/zapret/config. Далее надо создать указанный файл и дать ему chmod 755.\r\n\r\n--/etc/firewall.zapret.hook.post_up----------------------------\r\n#!/bin/sh\r\n\r\nZAPRET_NFT_TABLE=zapret\r\n\r\ncat << EOF | nft -f - 2>/dev/null\r\n delete chain inet $ZAPRET_NFT_TABLE my_output\r\n delete chain inet $ZAPRET_NFT_TABLE my_prerouting\r\nEOF\r\n\r\ncat << EOF | nft -f -\r\n add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; }\r\n flush chain inet $ZAPRET_NFT_TABLE my_output\r\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800\r\n\r\n add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; }\r\n flush chain inet $ZAPRET_NFT_TABLE my_prerouting\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800\r\nEOF\r\n------------------------------------------------\r\n\r\n# /etc/init.d/zapret restart_fw\r\n\r\nПроверка правил :\r\n# /etc/init.d/zapret list_table\r\nили\r\n# nft -t list table inet zapret\r\n\r\nДолжны быть цепочки my_prerouting и my_output.\r\n\r\nПроверка заполнения nfsets :\r\n# nft list set inet zapret zapret\r\n# nft list set inet zapret ipban\r\n# nft list set inet zapret nozapret\r\n\r\nПроверка заполнения множеств lanif, wanif, wanif6, link_local :\r\n# /etc/init.d/zapret list_ifsets\r\n\r\nДолжны присутствовать имена интерфейсов во множествах lanif, wanif.\r\nwanif6 заполняется только при включении ipv6.\r\nlink_local нужен только для tpws при включении ipv6.\r\n\r\n--- По поводу двойного NAT ---\r\n\r\nВ описанной конфигурации nat выполняется дважды : на роутере-клиенте происходит замена адреса источника из LAN\r\nна 192.168.254.3 и на сервере замена 192.168.254.3 на внешний адрес сервера в инете.\r\nЗачем так делать ? Исключительно для простоты настройки. Или на случай, если сервер wireguard не находится под вашим контролем.\r\nДелать для вас нижеописанные настройки никто не будет с вероятностью, близкой к 100%.\r\nЕсли сервер wireguard - ваш, и вы готовы чуток еще поднапрячься и не хотите двойного nat,\r\nто можете вписать в /etc/config/firewall \"masq '0'\", на сервер дописать маршрут до вашей подсети lan.\r\nЧтобы не делать это для каждого клиента, можно отвести под всех клиентов диапазон 192.168.0.0-192.168.127.255\r\nи прописать его одним маршрутом.\r\n\r\n--/etc/network/interfaces.d/wgvps-------------\r\n        post-up ip route add dev $IFACE 192.168.0.0/17\r\n        post-down ip route del dev $IFACE 192.168.0.0/17\r\n----------------------------------------------\r\n\r\nТак же необходимо указать wireguard дополнительные разрешенные ip для peer :\r\n\r\n--/etc/wireguard/wgvps.conf-------------------\r\n[Peer]\r\nPublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4=\r\nAllowedIPs = 192.168.254.3, 192.168.2.0/24\r\n----------------------------------------------\r\n\r\nВсем клиентам придется назначать различные диапазоны адресов в lan и индивидуально прописывать AllowedIPs\r\nдля каждого peer.\r\n\r\n# ifdown wgvps ; ifup wgvps\r\n\r\nНа клиенте разрешим форвард icmp, чтобы работал пинг и корректно определялось mtu.\r\n\r\n--/etc/config/firewall--------------------------\r\nconfig rule\r\n\toption name 'Allow-ICMP-tunvps'\r\n\toption src 'tunvps'\r\n\toption dest 'lan'\r\n\toption proto 'icmp'\r\n\toption target 'ACCEPT'\r\n------------------------------------------------\r\n\r\nСуществуют еще два неочевидных нюанса.\r\n\r\nПервый из них касается пакетов с самого роутера (цепочка OUTPUT).\r\nАдрес источника выбирается по особому алгоритму, если программа явно его не задала, еще до этапа iptables.\r\nОн берется с интерфейса, куда бы пошел пакет при нормальном раскладе.\r\nОбратная маршрутизация с VPN станет невозможной, да и wireguard такие пакеты порежет, поскольку они не вписываются в AllowedIPs.\r\nНикаким мистическим образом автоматом source address не поменяется.\r\nВ прошлом варианте настройки проблема решалось через маскарад. Сейчас же маскарада нет.\r\nПотому все же придется его делать в случае, когда пакет изначально направился бы через wan,\r\nа мы его завертываем на VPN. Помечаем такие пакеты марком 0x1000.\r\nЕсли вам не актуальны исходящие с самого роутера, то можно ничего не менять.\r\n\r\nДругой нюанс связан с обработкой проброшенных на vps портов, соединения по которым приходят как входящие с интерфейса wgvps.\r\nПредставьте себе, что вы пробросили порт 2222. Кто-то подключается с адреса 1.2.3.4. Вам приходит пакет SYN 1.2.3.4:51723=>192.168.2.2:2222.\r\nПо правилам маршрутизации он пойдет в локалку. 192.168.2.2 его обработает, ответит пакетом ACK 192.168.2.2:2222=>1.2.3.4:51723.\r\nЭтот пакет придет на роутер. И куда он дальше пойдет ? Если он не занесен в ipban, то согласно правилам машрутизации\r\nон пойдет по WAN интерфейсу, а не по исходному wgvps.\r\nЧтобы решить эту проблему, необходимо воспользоваться CONNMARK. Существуют 2 отдельных марка : fwmark и connmark.\r\nconnmark относится к соединению, fwmark - к пакету. Трэкингом соединений занимается conntrack.\r\nПосмотреть его таблицу можно командой \"conntrack -L\". Там же найдете connmark : mark=xxxx.\r\nКак только видим приходящий с wgvps пакет с новым соединением, отмечаем его connmark как 0x800/0x800.\r\nПри этом fwmark не меняется, иначе бы пакет тут же бы завернулся обратно на wgvps согласно ip rule.\r\nЕсли к нам приходит пакет с какого-то другого интерфейса, то восстанавливаем его connmark в fwmark по маске 0x800.\r\nИ теперь он подпадает под правило ip rule, заворачиваясь на wgvps, что и требовалось.\r\n\r\nАльтернативное решение - использовать на VPSке для проброса портов не только DNAT, но и SNAT/MASQUERADE. Тогда source address\r\nбудет заменен на 192.168.254.1. Он по таблице маршрутизации пойдет на wgvps. Но в этом случае клиентские программы,\r\nна которые осуществляется проброс портов, не будут видеть реальный IP подключенца.\r\n\r\n--/etc/firewall.user----------------------------\r\n. /opt/zapret/init.d/openwrt/functions\r\n\r\ncreate_ipset no-update\r\n\r\nnetwork_find_wan4_all wan_iface\r\nfor ext_iface in $wan_iface; do\r\n\tnetwork_get_device DEVICE $ext_iface\r\n\tipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\n\tipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\n\tipt OUTPUT -t mangle -o $DEVICE -j MARK --set-mark 0x1000/0x1000\r\ndone\r\n\r\nnetwork_get_device DEVICE lan\r\nipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\nipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800\r\n\r\n# do masquerade for OUTPUT to ensure correct outgoing address\r\nipt postrouting_tunvps_rule -t nat -m mark --mark 0x1000/0x1000 -j MASQUERADE\r\n\r\n# incoming from wgvps\r\nnetwork_get_device DEVICE wgvps\r\nipt PREROUTING -t mangle ! -i $DEVICE -j CONNMARK --restore-mark --nfmask 0x800 --ctmask 0x800\r\nipt PREROUTING -t mangle -i $DEVICE -m conntrack --ctstate NEW -j CONNMARK --set-mark 0x800/0x800\r\n------------------------------------------------\r\n\r\n# /etc/init.d/firewall restart\r\n\r\nВариант nftables :\r\n\r\n--/etc/firewall.zapret.hook.post_up----------------------------\r\n#!/bin/sh\r\n\r\nZAPRET_NFT_TABLE=zapret\r\nDEVICE=wgvps\r\n\r\ncat << EOF | nft -f - 2>/dev/null\r\n delete chain inet $ZAPRET_NFT_TABLE my_output\r\n delete chain inet $ZAPRET_NFT_TABLE my_prerouting\r\n delete chain inet $ZAPRET_NFT_TABLE my_nat\r\nEOF\r\n\r\ncat << EOF | nft -f -\r\n add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; }\r\n flush chain inet $ZAPRET_NFT_TABLE my_output\r\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta mark set mark or 0x1000\r\n\r\n add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; }\r\n flush chain inet $ZAPRET_NFT_TABLE my_prerouting\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname $DEVICE ct state new ct mark set ct mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname != $DEVICE meta mark set ct mark and 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800\r\n add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800\r\n\r\n add chain inet $ZAPRET_NFT_TABLE my_nat { type nat hook postrouting priority 100 ; }\r\n flush chain inet $ZAPRET_NFT_TABLE my_nat\r\n add rule inet $ZAPRET_NFT_TABLE my_nat oifname $DEVICE mark and 0x1000 == 0x1000 masquerade\r\nEOF\r\n------------------------------------------------\r\n\r\n# /etc/init.d/zapret restart_fw\r\n\r\nК сожалению, здесь возможности nftables немного хромают. Полноценного эквивалента CONNMARK --restore-mark --nfmask\r\nне существует. Оригинал iptables предполагал копирование одного бита 0x800 из connmark в mark.\r\nЛучшее, что можно сделать в nftables, это копирование одного бита с занулением всех остальных.\r\nСложные выражения типа \"meta mark set mark and ~0x800 or (ct mark and 0x800)\" nft не понимает.\r\nОб этом же говорит попытка перевода через iptables-translate.\r\n\r\nСейчас уже можно с vpn сервера пингануть ip адрес внутри локалки клиента. Пинги должны ходить.\r\n\r\nОтсутствие двойного NAT значительно облегчает проброс портов с внешнего IP vpn сервера в локалку какого-либо клиента.\r\nДля этого надо выполнить 2 действия : добавить разрешение в фаервол на клиенте и сделать dnat на сервере.\r\nПример форварда портов 5001 и 5201 на 192.168.2.2 :\r\n\r\n--/etc/config/firewall--------------------------\r\nconfig rule\r\n        option target 'ACCEPT'\r\n        option src 'tunvps'\r\n        option dest 'lan'\r\n        option proto 'tcp udp'\r\n        option dest_port '5001 5201'\r\n        option dest_ip '192.168.2.2'\r\n        option name 'IPERF'\r\n------------------------------------------------\r\n\r\n# /etc/init.d/firewall restart\r\n# /etc/init.d/zapret restart_fw\r\n\r\n--/etc/network/interfaces.d/wgvps-------------\r\n\tpost-up iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2\r\n\tpost-up iptables -t nat -A PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2\r\n\tpost-down iptables -t nat -D PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2\r\n\tpost-down iptables -t nat -D PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2\r\n----------------------------------------------\r\n\r\n# ifdown wgvps ; ifup wgvps\r\n\r\nПример приведен для iperf и iperf3, чтобы показать как пробрасывать несколько портов tcp+udp с минимальным количеством команд.\r\nПроброс tcp и udp порта так же необходим для полноценной работы bittorrent клиента, чтобы работали входящие.\r\n\r\n--- Как мне отправлять на vpn весь трафик с bittorrent ? ---\r\n\r\nМожно поступить так : посмотрите порт в настройках torrent клиента, убедитесь, что не поставлено \"случайный порт\",\r\nдобавьте на роутер правило маркировки по порту источника.\r\nНо мне предпочтительно иное решение. На windows есть замечательная возможность\r\nпрописать правило установки поля качества обслуживания в заголовках ip пакетов в зависимости от процесса-источника.\r\nДля windows 7/2008R2 необходимо будет установить ключик реестра и перезагрузить комп :\r\n# reg add HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Tcpip\\QoS /v \"Do not use NLA\" /t REG_SZ /d \"1\"\r\nРедактировать политику можно в : gpedit.msc -> Computer Configuration -> Windows Settings -> Policy-based QoS\r\nНа win 10 ключик реестра больше не работает, правила qos в gpedit применяются только для профиля домена.\r\nНеобходимо пользоваться командой powershell New-NetQosPolicy. Гуглите хелп по ней. Пример :\r\n# powershell New-NetQosPolicy -Name \"torrent\" -AppPathNameMatchCondition \"qbittorrent.exe\" -DSCPAction 1\r\nОднозначно требуется проверка в wireshark или netmon успешности установки поля dscp. Если там по-прежнему 0x00,\r\nзначит что-то не сработало. 0x04 означает DSCP=1 (dscp находится в старших 6 битах).\r\n\r\nНа роутере в фаер прописываем правило :\r\n\r\n--/etc/config/firewall--------------------------\r\nconfig rule\r\n\toption target 'MARK'\r\n\toption src 'lan'\r\n\toption proto 'all'\r\n\toption extra '-m dscp --dscp 1'\r\n\toption name 'route-dscp-1'\r\n\toption set_mark '0x0800/0x0800'\r\n------------------------------------------------\r\n\r\n# /etc/init.d/firewall restart\r\n\r\nТеперь все с полем dscp \"1\" идет на vpn. Клиент сам решает какой трафик ему нужно забрасывать\r\nна vpn, перенастраивать роутер не нужно.\r\nНа linux клиенте проще всего будет выставлять dscp в iptables по номеру порта источника :\r\n\r\n--/etc/rc.local---------------------------------\r\niptables -A OUTPUT -t mangle -p tcp --sport 23444 -j DSCP --set-dscp 1\r\niptables -A OUTPUT -t mangle -p udp --sport 23444 -j DSCP --set-dscp 1\r\n------------------------------------------------\r\n\r\nможно привязываться к pid процесса, но тогда нужно перенастраивать iptables при каждом перезапуске\r\nторент клиента, это требует рута, и все становится очень неудобно.\r\n\r\n\r\n--- Автоматизация проброса портов через miniupnd ---\r\n\r\nДа, его тоже можно использовать на vps. Только как всегда есть нюансы.\r\n\r\nminiupnpd поддерживает 3 протокола IGD : upnp,nat-pmp и pcp.\r\nupnp и pcp работают через мультикаст, который не пройдет через wgvps.\r\nnat-pmp работает через посылку специальных сообщений на udp:5351 на default gateway.\r\nОбычно их обслуживает miniupnpd на роутере. При создании lease miniupnpd добавляет\r\nправила для проброса портов в цепочку iptables MINIUPNPD, при потери lease - убирает.\r\n\r\nudp:5351 можно перенаправить на vpn сервер через DNAT, чтобы их обрабатывал miniupnpd там.\r\nНо вы должны иметь однозначный критерий перенаправления.\r\nЕсли вы решили завернуть на vpn все, то проблем нет. Пробрасываем udp:5351 безусловно.\r\nЕсли у вас идет перенаправление только с торрент, то необходимо к условию перенаправления\r\nдобавить условия, выделяющие torrent трафик из прочего. Или по dscp, или по sport.\r\nЧтобы запросы от остальных программ обрабатывались miniupnpd на роутере.\r\nЕсли какая-то программа создаст lease не там, где нужно, то входящий трафик до нее не дойдет.\r\n\r\nНа роутере стоит запретить протокол upnp, чтобы торрент клиент не удовлетворился запросом,\r\nобслуженным по upnp на роутере, и пытался использовать nat-pmp.\r\n\r\n--/etc/config/upnp--------------------------\r\nconfig upnpd 'config'\r\n\t.....\r\n        option enable_upnp '0'\r\n------------------------------------------------\r\n\r\n/etc/init.d/miniupnpd restart\r\n\r\nДелаем проброс порта на роутере.\r\nДля простоты изложения будем считать, что на vpn у нас завернут весь трафик.\r\nЕсли это не так, то следует добавить фильтр в \"config redirect\".\r\nЗаодно выделяем диапазон портов для торрент клиентов.\r\nПорт в торент клиенте следует прописать какой-то из этого диапазона.\r\n\r\n------------------------------------------------\r\nconfig redirect\r\n        option enabled '1'\r\n        option target 'DNAT'\r\n        option src 'lan'\r\n        option dest 'tunvps'\r\n        option proto 'udp'\r\n        option src_dport '5351'\r\n        option dest_ip '192.168.254.1'\r\n        option dest_port '5351'\r\n        option name 'NAT-PMP'\r\n        option reflection '0'\r\nconfig rule\r\n        option enabled '1'\r\n        option target 'ACCEPT'\r\n        option src 'tunvps'\r\n        option dest 'lan'\r\n        option name 'tunvps-torrent'\r\n        option dest_port '28000-28009'\r\n------------------------------------------------\r\n\r\n/etc/init.d/firewall reload\r\n\r\n\r\nНа сервере :\r\n\r\napt install miniupnpd\r\n\r\n--- /etc/miniupnpd/miniupnpd.conf --------\r\nenable_natpmp=yes\r\nenable_upnp=no\r\nlease_file=/var/log/upnp.leases\r\nsystem_uptime=yes\r\nclean_ruleset_threshold=10\r\nclean_ruleset_interval=600\r\nforce_igd_desc_v1=no\r\nlistening_ip=192.168.254.1/16\r\next_ifname=eth0\r\n------------------------------------------\r\n\r\nsystemctl restart miniupnpd\r\n\r\nlistening_ip прописан именно таким образом, чтобы обозначить диапазон разрешенных IP.\r\nС других IP он не будет обрабатывать запросы на редирект.\r\nВ ext_ifname впишите название inet интерфейса на сервере.\r\n\r\nЗапускаем торрент клиент. Попутно смотрим в tcpdump весь путь udp:5351 до сервера и обратно.\r\nСмотрим syslog сервера на ругань от miniupnpd.\r\nЕсли все ок, то можем проверить редиректы : iptables -t nat -nL MINIUPNPD\r\nС какого-нибудь другого хоста (не vpn сервер, не ваше подключение) можно попробовать telnet-нуться на проброшенный порт.\r\nДолжно установиться соединение. Или качайте торент и смотрите в пирах флаг \"I\" (incoming).\r\nЕсли \"I\" есть и по ним идет закачка, значит все в порядке.\r\n\r\nОСОБЕННОСТЬ НОВЫХ DEBIAN : по умолчанию используются iptables-nft. miniupnpd работает с iptables-legacy.\r\nЛЕЧЕНИЕ : update-alternatives --set iptables /usr/sbin/iptables-legacy\r\n"
  },
  {
    "path": "files/huawei/E8372/run-zapret-hostlist",
    "content": "#!/system/bin/busybox sh\n\n# download hostlist from http(s) (need curl, its absent by default),\n# feed it to zapret. save flash write cycles\n\nu=\"https://your.host.com/censorship/hoslist.txt\"\n\nSCRIPT=$(readlink -f \"$0\")\nEXEDIR=$(dirname \"$SCRIPT\")\n\nd=/data/censorship\n[ -d $d ] || mkdir $d\nf=$d/hostlist.txt\nt=/hostlist.txt\n\ncurl -k --fail --max-time 10 -o \"$t\" \"$u\" && {\n if [ -s \"$t\" ]; then\n  m1=$(md5sum \"$t\" | cut -d ' ' -f 1)\n  m2=$(md5sum \"$f\" | cut -d ' ' -f 1)\n  echo $m1 $m2\n  if [ -z \"$m2\" ] || [ \"$m1\" != \"$m2\" ]; then\n   echo updating hostlist\n   cp -f \"$t\" \"$f\"\n  else\n   echo hostlist was not changed. keeping old copy\n  fi\n else\n  echo downloaded hostlist is empty. disabling zapret\n  rm \"$f\"\n fi\n}\n\nrm -f \"$t\"\n\"$EXEDIR/unzapret\"\n[ -s \"$f\" ] && exec \"$EXEDIR/zapret\" \"--hostlist=$f\"\n"
  },
  {
    "path": "files/huawei/E8372/run-zapret-ip",
    "content": "#!/system/bin/busybox sh\n\n# download hostlist from http(s) (need curl, its absent by default),\n# resolve to ip list, feed to zapret-ip. save flash write cycles\n\nu=\"https://your.host.com/censorship/hoslist.txt\"\n\nSCRIPT=$(readlink -f \"$0\")\nEXEDIR=$(dirname \"$SCRIPT\")\n\nd=/data/censorship\n[ -d $d ] || mkdir $d\nf=$d/hostlist.txt\nt=/hostlist.txt\ni=/iplist.txt\n\ncurl -k --fail --max-time 10 -o \"$t\" \"$u\" && {\n if [ -s \"$t\" ]; then\n  m1=$(md5sum \"$t\" | cut -d ' ' -f 1)\n  m2=$(md5sum \"$f\" | cut -d ' ' -f 1)\n  echo $m1 $m2\n  if [ -z \"$m2\" ] || [ \"$m1\" != \"$m2\" ]; then\n   echo updating hostlist\n   cp -f \"$t\" \"$f\"\n  else\n   echo hostlist was not changed. keeping old copy\n  fi\n else\n  echo downloaded hostlist is empty. disabling zapret\n  rm \"$f\"\n fi\n}\n\nrm -f \"$t\"\n\"$EXEDIR/unzapret-ip\"\n[ -s \"$f\" ] && {\n mdig --threads=10 --family=4 <\"$f\" >\"$i\"\n [ -s \"$i\" ] && exec \"$EXEDIR/zapret-ip\" \"$i\"\n}\n"
  },
  {
    "path": "files/huawei/E8372/unzapret",
    "content": "#!/system/bin/busybox sh\n\nrule=\"PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1\"\niptables -C $rule 2>/dev/null && iptables -D $rule\nkillall tpws\n\nrule=\"OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443  -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\"\niptables -C $rule 2>/dev/null && iptables -D $rule\nkillall nfqws\n"
  },
  {
    "path": "files/huawei/E8372/unzapret-ip",
    "content": "#!/system/bin/busybox sh\n\nrule=\"PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws\"\niptables -C $rule 2>/dev/null && iptables -D $rule\niptables -F tpws -t nat\niptables -X tpws -t nat\nkillall tpws\n\nrule=\"OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443  -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\"\niptables -C $rule 2>/dev/null && iptables -D $rule\nkillall nfqws\n"
  },
  {
    "path": "files/huawei/E8372/zapret",
    "content": "#!/system/bin/busybox sh\n\n# $1 - additional parameters for nfqws\n\ninsmod /online/modules/unfuck_nfqueue.ko  2>/dev/null\n\nrule=\"PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1\"\niptables -C $rule 2>/dev/null || iptables -I $rule\n\ntpws --uid 1:3003 --port=1 --daemon\n\nrule=\"OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443  -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\"\niptables -C $rule 2>/dev/null || iptables -I $rule\n\nnfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon $1\n"
  },
  {
    "path": "files/huawei/E8372/zapret-ip",
    "content": "#!/system/bin/busybox sh\n\n# $1 - ip list file. create individual rules for tpws redirection. ipset is not available\n\n[ -z \"$1\" ] && {\n echo need iplist file as parameter\n exit 1\n}\n\ninsmod /online/modules/unfuck_nfqueue.ko  2>/dev/null\n\ntpws --maxconn=1024 --uid 1:3003 --port=1 --daemon\n\n\nREDIR=\"-j REDIRECT --to-port 1\"\n\niptables -F tpws -t nat\niptables -X tpws -t nat\niptables -N tpws -t nat\niptables -A tpws -t nat -d 192.168.0.0/16 -j RETURN\n\nwhile read ip; do\n echo redirecting $ip\n iptables -A tpws -t nat -d $ip -p tcp $REDIR\ndone <\"$1\"\n\n\nrule=\"PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws\"\niptables -C $rule 2>/dev/null || iptables -I $rule\n\nnfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon\n\nrule=\"OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443  -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass\"\niptables -C $rule 2>/dev/null || iptables -I $rule\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/10-keenetic-udp-fix",
    "content": "# This script fixes keenetic issue with nfqws generated udp packets\n# Keenetic uses proprietary ndmmark and does not masquerade without this mark\n# If not masqueraded packets go to WAN with LAN IP and get dropped by ISP\n\n# It's advised to set IFACE_WAN in config\n\nzapret_custom_firewall()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal wan wanif rule\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || {\n\t\t# use IFACE_WAN if defined. if not - search for interfaces with default route.\n\t\twanif=${IFACE_WAN:-$(sed -nre 's/^([^\\t]+)\\t00000000\\t[0-9A-F]{8}\\t[0-9A-F]{4}\\t[0-9]+\\t[0-9]+\\t[0-9]+\\t00000000.*$/\\1/p' /proc/net/route | sort -u | xargs)}\n\t\tfor wan in $wanif; do\n\t\t\trule=\"-o $wan -p udp -m mark --mark $DESYNC_MARK/$DESYNC_MARK\"\n\t\t\tipt_print_op $1 \"$rule\" \"keenetic udp fix\"\n\t\t\tipt_add_del $1 POSTROUTING -t nat $rule -j MASQUERADE\n\t\tdone\n\t}\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/20-fw-extra",
    "content": "# this custom script runs standard mode with extra firewall rules\n\n# config: use TPWS_ENABLE_OVERRIDE, NFQWS_ENABLE_OVERRIDE to enable standard mode daemons\n# standard and override switches cannot be enabled simultaneously !\n\nTPWS_ENABLE_OVERRIDE=${TPWS_ENABLE_OVERRIDE:-0}\nNFQWS_ENABLE_OVERRIDE=${NFQWS_ENABLE_OVERRIDE:-0}\n\n# config: some if these values must be set in config. not setting any of these makes this script meaningless.\n# pre vars put ipt/nft code to the rule beginning\n#FW_EXTRA_PRE_TPWS_IPT=\n#FW_EXTRA_PRE_TPWS_NFT=\n#FW_EXTRA_PRE_NFQWS_IPT=\"-m mark --mark 0x10000000/0x10000000\"\n#FW_EXTRA_PRE_NFQWS_NFT=\"mark and 0x10000000 != 0\"\n# post vars put ipt/nft code to the rule end\n#FW_EXTRA_POST_TPWS_IPT=\n#FW_EXTRA_POST_TPWS_NFT=\n#FW_EXTRA_POST_NFQWS_IPT=\n#FW_EXTRA_POST_NFQWS_NFT=\n\ncheck_std_intersect()\n{\n\t[ \"$TPWS_ENABLE_OVERRIDE\" = 1 -a \"$TPWS_ENABLE\" = 1 ] && {\n\t\techo \"ERROR ! both TPWS_ENABLE_OVERRIDE and TPWS_ENABLE are enabled\"\n\t\treturn 1\n\t}\n\t[ \"$NFQWS_ENABLE_OVERRIDE\" = 1 -a \"$NFQWS_ENABLE\" = 1 ] && {\n\t\techo \"ERROR ! both NFQWS_ENABLE_OVERRIDE and NFQWS_ENABLE are enabled\"\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tcheck_std_intersect || return\n\n\tlocal TPWS_SOCKS_ENABLE=0 TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE\n\tstandard_mode_daemons \"$1\"\n}\nzapret_custom_firewall()\n{\n        # $1 - 1 - run, 0 - stop\n\n\tcheck_std_intersect || return\n\n\tlocal FW_EXTRA_PRE FW_EXTRA_POST TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE\n\tFW_EXTRA_PRE=\"$FW_EXTRA_PRE_TPWS_IPT\" FW_EXTRA_POST=\"$FW_EXTRA_POST_TPWS_IPT\"\n\tzapret_do_firewall_standard_tpws_rules_ipt $1\n\tFW_EXTRA_PRE=\"$FW_EXTRA_PRE_NFQWS_IPT\" FW_EXTRA_POST=\"$FW_EXTRA_POST_NFQWS_IPT\"\n\tzapret_do_firewall_standard_nfqws_rules_ipt $1\n}\nzapret_custom_firewall_nft()\n{\n        # stop logic is not required\n\n\tcheck_std_intersect || return\n\n\tlocal FW_EXTRA_PRE FW_EXTRA_POST TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE\n\tFW_EXTRA_PRE=\"$FW_EXTRA_PRE_TPWS_NFT\" FW_EXTRA_POST=\"$FW_EXTRA_POST_TPWS_NFT\"\n\tzapret_apply_firewall_standard_tpws_rules_nft\n\tFW_EXTRA_PRE=\"$FW_EXTRA_PRE_NFQWS_NFT\" FW_EXTRA_POST=\"$FW_EXTRA_POST_NFQWS_NFT\"\n\tzapret_apply_firewall_standard_nfqws_rules_nft\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-dht4all",
    "content": "# this custom script runs desync to DHT packets with udp payload length >=5 , without ipset/hostlist filtering\n# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22)\n\n# can override in config :\nNFQWS_OPT_DESYNC_DHT=\"${NFQWS_OPT_DESYNC_DHT:---dpi-desync=tamper}\"\n\nalloc_dnum DNUM_DHT4ALL\nalloc_qnum QNUM_DHT4ALL\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_DHT4ALL $NFQWS_OPT_DESYNC_DHT\"\n\tdo_nfqws $1 $DNUM_DHT4ALL \"$opt\"\n}\nzapret_custom_firewall()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tlocal f uf4 uf6\n\tlocal first_packet_only=\"$ipt_connbytes 1:1\"\n\n\tf='-p udp -m u32 --u32'\n\tuf4='0>>22&0x3C@4>>16=13:0xFFFF&&0>>22&0x3C@8>>16=0x6431:0x6432'\n\tuf6='44>>16=13:0xFFFF&&48>>16=0x6431:0x6432'\n\tfw_nfqws_post $1 \"$f $uf4 $first_packet_only\"  \"$f $uf6 $first_packet_only\" $QNUM_DHT4ALL\n}\nzapret_custom_firewall_nft()\n{\n\t# stop logic is not required\n\n        local f\n        local first_packet_only=\"$nft_connbytes 1\"\n\n        f=\"udp length ge 13 meta l4proto udp @ih,0,16 0x6431-0x6432\"\n        nft_fw_nfqws_post \"$f $first_packet_only\" \"$f $first_packet_only\" $QNUM_DHT4ALL\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-discord-media",
    "content": "# this custom script runs desync to all discord media packets\n# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22)\n\n# can override in config :\nNFQWS_OPT_DESYNC_DISCORD_MEDIA=\"${NFQWS_OPT_DESYNC_DISCORD_MEDIA:---dpi-desync=fake --dpi-desync-repeats=2}\"\nDISCORD_MEDIA_PORT_RANGE=\"${DISCORD_MEDIA_PORT_RANGE:-50000-50099}\"\n\nalloc_dnum DNUM_DISCORD_MEDIA\nalloc_qnum QNUM_DISCORD_MEDIA\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_DISCORD_MEDIA $NFQWS_OPT_DESYNC_DISCORD_MEDIA\"\n\tdo_nfqws $1 $DNUM_DISCORD_MEDIA \"$opt\"\n}\nzapret_custom_firewall()\n{\n        # $1 - 1 - run, 0 - stop\n\n\tlocal DISABLE_IPV6=1\n\tlocal port_range=$(replace_char - : $DISCORD_MEDIA_PORT_RANGE)\n\tlocal f=\"-p udp --dport $port_range -m u32 --u32\"\n\t# this is simplified test to skip writing monstrous rule. instead of checking 64 bytes for zeroes only check 2 dwords for zero\n\tfw_nfqws_post $1 \"$f 0>>22&0x3C@4>>16=0x52&&0>>22&0x3C@8=0x00010046&&0>>22&0x3C@16=0&&0>>22&0x3C@76=0\" '' $QNUM_DISCORD_MEDIA\n}\nzapret_custom_firewall_nft()\n{\n        # stop logic is not required\n\n\tlocal DISABLE_IPV6=1\n\tlocal f=\"udp dport $DISCORD_MEDIA_PORT_RANGE udp length == 82 @ih,0,32 0x00010046 @ih,64,128 0x00000000000000000000000000000000 @ih,192,128 0x00000000000000000000000000000000 @ih,320,128 0x00000000000000000000000000000000 @ih,448,128 0x00000000000000000000000000000000\"\n\tnft_fw_nfqws_post \"$f\" '' $QNUM_DISCORD_MEDIA\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-nfqws-ipset",
    "content": "# this custom script demonstrates how to launch extra nfqws instance limited by ipset\n\n# can override in config :\nNFQWS_MY1_OPT=\"${NFQWS_MY1_OPT:---filter-udp=* --dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-any-protocol --new --filter-tcp=* --dpi-desync=multisplit}\"\nNFQWS_MY1_SUBNETS4=\"${NFQWS_MY1_SUBNETS4:-173.194.0.0/16 108.177.0.0/17 74.125.0.0/16 64.233.160.0/19 172.217.0.0/16}\"\nNFQWS_MY1_SUBNETS6=\"${NFQWS_MY1_SUBNETS6:-2a00:1450::/29}\"\nNFQWS_MY1_PORTS_TCP=${NFQWS_MY1_PORTS_TCP:-$NFQWS_PORTS_TCP}\nNFQWS_MY1_PORTS_UDP=${NFQWS_MY1_PORTS_UDP:-$NFQWS_PORTS_UDP}\nNFQWS_MY1_TCP_PKT_OUT=${NFQWS_MY1_TCP_PKT_OUT:-$NFQWS_TCP_PKT_OUT}\nNFQWS_MY1_UDP_PKT_OUT=${NFQWS_MY1_UDP_PKT_OUT:-$NFQWS_UDP_PKT_OUT}\nNFQWS_MY1_TCP_PKT_IN=${NFQWS_MY1_TCP_PKT_IN:-$NFQWS_TCP_PKT_IN}\nNFQWS_MY1_UDP_PKT_IN=${NFQWS_MY1_UDP_PKT_IN:-$NFQWS_UDP_PKT_IN}\n\nNFQWS_MY1_IPSET_SIZE=${NFQWS_MY1_IPSET_SIZE:-4096}\nNFQWS_MY1_IPSET_OPT=\"${NFQWS_MY1_IPSET_OPT:-hash:net hashsize 8192 maxelem $NFQWS_MY1_IPSET_SIZE}\"\n\nalloc_dnum DNUM_NFQWS_MY1\nalloc_qnum QNUM_NFQWS_MY1\nNFQWS_MY1_NAME4=my1nfqws4\nNFQWS_MY1_NAME6=my1nfqws6\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_NFQWS_MY1 $NFQWS_MY1_OPT\"\n\tdo_nfqws $1 $DNUM_NFQWS_MY1 \"$opt\"\n}\n\nzapret_custom_firewall()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tlocal f4 f6 subnet\n\tlocal NFQWS_MY1_PORTS_TCP=$(replace_char - : $NFQWS_MY1_PORTS_TCP)\n\tlocal NFQWS_MY1_PORTS_UDP=$(replace_char - : $NFQWS_MY1_PORTS_UDP)\n\n\t[ \"$1\" = 1 -a \"$DISABLE_IPV4\" != 1 ] && {\n\t\tipset create $NFQWS_MY1_NAME4 $NFQWS_MY1_IPSET_OPT family inet 2>/dev/null\n\t\tipset flush $NFQWS_MY1_NAME4\n\t\tfor subnet in $NFQWS_MY1_SUBNETS4; do\n\t\t\techo add $NFQWS_MY1_NAME4 $subnet\n\t\tdone | ipset -! restore\n\t}\n\t[ \"$1\" = 1 -a \"$DISABLE_IPV6\" != 1 ] && {\n\t\tipset create $NFQWS_MY1_NAME6 $NFQWS_MY1_IPSET_OPT family inet6 2>/dev/null\n\t\tipset flush $NFQWS_MY1_NAME6\n\t\tfor subnet in $NFQWS_MY1_SUBNETS6; do\n\t\t\techo add $NFQWS_MY1_NAME6 $subnet\n\t\tdone | ipset -! restore\n\t}\n\n\t[ -n \"$NFQWS_MY1_PORTS_TCP\" ] && {\n\t\t[ -n \"$NFQWS_MY1_TCP_PKT_OUT\" -a \"$NFQWS_MY1_TCP_PKT_OUT\" != 0 ] && {\n\t\t\tf4=\"-p tcp -m multiport --dports $NFQWS_MY1_PORTS_TCP $ipt_connbytes 1:$NFQWS_MY1_TCP_PKT_OUT -m set --match-set\"\n\t\t\tf6=\"$f4 $NFQWS_MY1_NAME6 dst\"\n\t\t\tf4=\"$f4 $NFQWS_MY1_NAME4 dst\"\n\t\t\tfw_nfqws_post $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t\t[ -n \"$NFQWS_MY1_TCP_PKT_IN\" -a \"$NFQWS_MY1_TCP_PKT_IN\" != 0 ] && {\n\t\t\tf4=\"-p tcp -m multiport --sports $NFQWS_MY1_PORTS_TCP $ipt_connbytes 1:$NFQWS_MY1_TCP_PKT_IN -m set --match-set\"\n\t\t\tf6=\"$f4 $NFQWS_MY1_NAME6 src\"\n\t\t\tf4=\"$f4 $NFQWS_MY1_NAME4 src\"\n\t\t\tfw_nfqws_pre $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t}\n\t[ -n \"$NFQWS_MY1_PORTS_UDP\" ] && {\n\t\t[ -n \"$NFQWS_MY1_UDP_PKT_OUT\" -a \"$NFQWS_MY1_UDP_PKT_OUT\" != 0 ] && {\n\t\t\tf4=\"-p udp -m multiport --dports $NFQWS_MY1_PORTS_UDP $ipt_connbytes 1:$NFQWS_MY1_UDP_PKT_OUT -m set --match-set\"\n\t\t\tf6=\"$f4 $NFQWS_MY1_NAME6 dst\"\n\t\t\tf4=\"$f4 $NFQWS_MY1_NAME4 dst\"\n\t\t\tfw_nfqws_post $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t\t[ -n \"$NFQWS_MY1_UDP_PKT_IN\" -a \"$NFQWS_MY1_UDP_PKT_IN\" != 0 ] && {\n\t\t\tf4=\"-p udp -m multiport --sports $NFQWS_MY1_PORTS_UDP $ipt_connbytes 1:$NFQWS_MY1_UDP_PKT_IN -m set --match-set\"\n\t\t\tf6=\"$f4 $NFQWS_MY1_NAME6 src\"\n\t\t\tf4=\"$f4 $NFQWS_MY1_NAME4 src\"\n\t\t\tfw_nfqws_pre $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t}\n\n\t[ \"$1\" = 1 ] || {\n\t\tipset destroy $NFQWS_MY1_NAME4 2>/dev/null\n\t\tipset destroy $NFQWS_MY1_NAME6 2>/dev/null\n\t}\n}\n\nzapret_custom_firewall_nft()\n{\n\tlocal f4 f6 subnets\n\tlocal first_packets_only=\"$nft_connbytes 1-$NFQWS_MY1_PKT_OUT\"\n\n\t[ \"$DISABLE_IPV4\" != 1 ] && {\n\t        make_comma_list subnets $NFQWS_MY1_SUBNETS4\n\t\tnft_create_set $NFQWS_MY1_NAME4 \"type ipv4_addr; size $NFQWS_MY1_IPSET_SIZE; auto-merge; flags interval;\"\n\t\tnft_flush_set $NFQWS_MY1_NAME4\n\t\tnft_add_set_element $NFQWS_MY1_NAME4 \"$subnets\"\n\t}\n\t[ \"$DISABLE_IPV6\" != 1 ] && {\n\t        make_comma_list subnets $NFQWS_MY1_SUBNETS6\n\t\tnft_create_set $NFQWS_MY1_NAME6 \"type ipv6_addr; size $NFQWS_MY1_IPSET_SIZE; auto-merge; flags interval;\"\n\t\tnft_flush_set $NFQWS_MY1_NAME6\n\t\tnft_add_set_element $NFQWS_MY1_NAME6 \"$subnets\"\n\t}\n\n\t[ -n \"$NFQWS_MY1_PORTS_TCP\" ] && {\n\t\t[ -n \"$NFQWS_MY1_TCP_PKT_OUT\" -a \"$NFQWS_MY1_TCP_PKT_OUT\" != 0 ] && {\n\t\t\tf4=\"tcp dport {$NFQWS_MY1_PORTS_TCP} $(nft_first_packets $NFQWS_MY1_TCP_PKT_OUT)\"\n\t\t\tf6=\"$f4 ip6 daddr @$NFQWS_MY1_NAME6\"\n\t\t\tf4=\"$f4 ip daddr @$NFQWS_MY1_NAME4\"\n\t\t\tnft_fw_nfqws_post $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t\t[ -n \"$NFQWS_MY1_TCP_PKT_IN\" -a \"$NFQWS_MY1_TCP_PKT_IN\" != 0 ] && {\n\t\t\tf4=\"tcp sport {$NFQWS_MY1_PORTS_TCP} $(nft_first_packets $NFQWS_MY1_TCP_PKT_IN)\"\n\t\t\tf6=\"$f4 ip6 saddr @$NFQWS_MY1_NAME6\"\n\t\t\tf4=\"$f4 ip saddr @$NFQWS_MY1_NAME4\"\n\t\t\tnft_fw_nfqws_pre $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t}\n\t[ -n \"$NFQWS_MY1_PORTS_UDP\" ] && {\n\t\t[ -n \"$NFQWS_MY1_UDP_PKT_OUT\" -a \"$NFQWS_MY1_UDP_PKT_OUT\" != 0 ] && {\n\t\t\tf4=\"udp dport {$NFQWS_MY1_PORTS_UDP} $(nft_first_packets $NFQWS_MY1_UDP_PKT_OUT)\"\n\t\t\tf6=\"$f4 ip6 daddr @$NFQWS_MY1_NAME6\"\n\t\t\tf4=\"$f4 ip daddr @$NFQWS_MY1_NAME4\"\n\t\t\tnft_fw_nfqws_post $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t\t[ -n \"$NFQWS_MY1_UDP_PKT_IN\" -a \"$NFQWS_MY1_UDP_PKT_IN\" != 0 ] && {\n\t\t\tf4=\"udp sport {$NFQWS_MY1_PORTS_UDP} $(nft_first_packets $NFQWS_MY1_UDP_PKT_IN)\"\n\t\t\tf6=\"$f4 ip6 saddr @$NFQWS_MY1_NAME6\"\n\t\t\tf4=\"$f4 ip saddr @$NFQWS_MY1_NAME4\"\n\t\t\tnft_fw_nfqws_pre $1 \"$f4\" \"$f6\" $QNUM_NFQWS_MY1\n\t\t}\n\t}\n}\n\n\nzapret_custom_firewall_nft_flush()\n{\n\t# this function is called after all nft fw rules are deleted\n\t# however sets are not deleted. it's desired to clear sets here.\n\n\tnft_del_set $NFQWS_MY1_NAME4 2>/dev/null\n\tnft_del_set $NFQWS_MY1_NAME6 2>/dev/null\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-quic4all",
    "content": "# this custom script runs desync to all IETF QUIC initials\n# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22)\n\n# can override in config :\nNFQWS_OPT_DESYNC_QUIC=\"${NFQWS_OPT_DESYNC_QUIC:---dpi-desync=fake --dpi-desync-repeats=2}\"\n\nalloc_dnum DNUM_QUIC4ALL\nalloc_qnum QNUM_QUIC4ALL\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_QUIC4ALL $NFQWS_OPT_DESYNC_QUIC\"\n\tdo_nfqws $1 $DNUM_QUIC4ALL \"$opt\"\n}\nzapret_custom_firewall()\n{\n        # $1 - 1 - run, 0 - stop\n\n\tlocal f='-p udp -m u32 --u32'\n\tfw_nfqws_post $1 \"$f 0>>22&0x3C@4>>16=264:65535&&0>>22&0x3C@8>>28=0xC&&0>>22&0x3C@9=0x00000001\" \"$f 44>>16=264:65535&&48>>28=0xC&&49=0x00000001\" $QNUM_QUIC4ALL\n}\nzapret_custom_firewall_nft()\n{\n        # stop logic is not required\n\n\tlocal f=\"udp length >= 264 @ih,0,4 0xC @ih,8,32 0x00000001\"\n\tnft_fw_nfqws_post \"$f\" \"$f\" $QNUM_QUIC4ALL\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-stun4all",
    "content": "# this custom script runs desync to all stun packets\n# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22)\n\n# can override in config :\nNFQWS_OPT_DESYNC_STUN=\"${NFQWS_OPT_DESYNC_STUN:---dpi-desync=fake --dpi-desync-repeats=2}\"\n\nalloc_dnum DNUM_STUN4ALL\nalloc_qnum QNUM_STUN4ALL\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_STUN4ALL $NFQWS_OPT_DESYNC_STUN\"\n\tdo_nfqws $1 $DNUM_STUN4ALL \"$opt\"\n}\nzapret_custom_firewall()\n{\n        # $1 - 1 - run, 0 - stop\n\n\tlocal f='-p udp -m u32 --u32'\n\tfw_nfqws_post $1 \"$f 0>>22&0x3C@4>>16=28:65535&&0>>22&0x3C@12=0x2112A442&&0>>22&0x3C@8&0xC0000003=0\" \"$f 44>>16=28:65535&&52=0x2112A442&&48&0xC0000003=0\" $QNUM_STUN4ALL\n}\nzapret_custom_firewall_nft()\n{\n        # stop logic is not required\n\n\tlocal f=\"udp length >= 28 @ih,32,32 0x2112A442 @ih,0,2 0 @ih,30,2 0\"\n\tnft_fw_nfqws_post \"$f\" \"$f\" $QNUM_STUN4ALL\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-tpws-ipset",
    "content": "# this custom script demonstrates how to launch extra tpws instance limited by ipset\n\n# can override in config :\nTPWS_MY1_OPT=\"${TPWS_MY1_OPT:---oob --split-pos=midsld}\"\nTPWS_MY1_PORTS=${TPWS_MY1_PORTS:-$TPWS_PORTS}\nTPWS_MY1_SUBNETS4=\"${TPWS_MY1_SUBNETS4:-142.250.0.0/15 64.233.160.0/19 172.217.0.0/16 173.194.0.0/16 108.177.0.0/17 74.125.0.0/16 209.85.128.0/17 216.58.192.0/19}\"\nTPWS_MY1_SUBNETS6=\"${TPWS_MY1_SUBNETS6:-2607:F8B0::/32 2a00:1450:4000::/37}\"\n\nTPWS_MY1_IPSET_SIZE=${TPWS_MY1_IPSET_SIZE:-4096}\nTPWS_MY1_IPSET_OPT=\"${TPWS_MY1_IPSET_OPT:-hash:net hashsize 8192 maxelem $TPWS_MY1_IPSET_SIZE}\"\n\nalloc_dnum DNUM_TPWS_MY1\nalloc_tpws_port PORT_TPWS_MY1\nTPWS_MY1_NAME4=my1tpws4\nTPWS_MY1_NAME6=my1tpws6\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tlocal opt=\"--port=$PORT_TPWS_MY1 $TPWS_MY1_OPT\"\n\tdo_tpws $1 $DNUM_TPWS_MY1 \"$opt\"\n}\n\nzapret_custom_firewall()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tlocal f4 f6 subnet\n\tlocal PORTS_IPT=$(replace_char - : $TPWS_MY1_PORTS)\n\n\t[ \"$1\" = 1 -a \"$DISABLE_IPV4\" != 1 ] && {\n\t\tipset create $TPWS_MY1_NAME4 $TPWS_MY1_IPSET_OPT family inet 2>/dev/null\n\t\tipset flush $TPWS_MY1_NAME4\n\t\tfor subnet in $TPWS_MY1_SUBNETS4; do\n\t\t\techo add $TPWS_MY1_NAME4 $subnet\n\t\tdone | ipset -! restore\n\t}\n\t[ \"$1\" = 1 -a \"$DISABLE_IPV6\" != 1 ] && {\n\t\tipset create $TPWS_MY1_NAME6 $TPWS_MY1_IPSET_OPT family inet6 2>/dev/null\n\t\tipset flush $TPWS_MY1_NAME6\n\t\tfor subnet in $TPWS_MY1_SUBNETS6; do\n\t\t\techo add $TPWS_MY1_NAME6 $subnet\n\t\tdone | ipset -! restore\n\t}\n\n\tf4=\"-p tcp -m multiport --dports $PORTS_IPT -m set --match-set\"\n\tf6=\"$f4 $TPWS_MY1_NAME6 dst\"\n\tf4=\"$f4 $TPWS_MY1_NAME4 dst\"\n\tfw_tpws $1 \"$f4\" \"$f6\" $PORT_TPWS_MY1\n\n\t[ \"$1\" = 1 ] || {\n\t\tipset destroy $TPWS_MY1_NAME4 2>/dev/null\n\t\tipset destroy $TPWS_MY1_NAME6 2>/dev/null\n\t}\n}\n\nzapret_custom_firewall_nft()\n{\n\tlocal f4 f6 subnets\n\n\t[ \"$DISABLE_IPV4\" != 1 ] && {\n\t        make_comma_list subnets $TPWS_MY1_SUBNETS4\n\t\tnft_create_set $TPWS_MY1_NAME4 \"type ipv4_addr; size $TPWS_MY1_IPSET_SIZE; auto-merge; flags interval;\"\n\t\tnft_flush_set $TPWS_MY1_NAME4\n\t\tnft_add_set_element $TPWS_MY1_NAME4 \"$subnets\"\n\t}\n\t[ \"$DISABLE_IPV6\" != 1 ] && {\n\t        make_comma_list subnets $TPWS_MY1_SUBNETS6\n\t\tnft_create_set $TPWS_MY1_NAME6 \"type ipv6_addr; size $TPWS_MY1_IPSET_SIZE; auto-merge; flags interval;\"\n\t\tnft_flush_set $TPWS_MY1_NAME6\n\t\tnft_add_set_element $TPWS_MY1_NAME6 \"$subnets\"\n\t}\n\n\tf4=\"tcp dport {$TPWS_MY1_PORTS}\"\n\tf6=\"$f4 ip6 daddr @$TPWS_MY1_NAME6\"\n\tf4=\"$f4 ip daddr @$TPWS_MY1_NAME4\"\n\tnft_fw_tpws \"$f4\" \"$f6\" $PORT_TPWS_MY1\n}\n\nzapret_custom_firewall_nft_flush()\n{\n\t# this function is called after all nft fw rules are deleted\n\t# however sets are not deleted. it's desired to clear sets here.\n\n\tnft_del_set $TPWS_MY1_NAME4 2>/dev/null\n\tnft_del_set $TPWS_MY1_NAME6 2>/dev/null\n}\n"
  },
  {
    "path": "init.d/custom.d.examples.linux/50-wg4all",
    "content": "# this custom script runs desync to all wireguard handshake initiation packets\n# NOTE: this works for original wireguard and may not work for 3rd party implementations such as xray\n# NOTE: @ih requires nft 1.0.1+ and updated kernel version. it's confirmed to work on 5.15 (openwrt 23) and not work on 5.10 (openwrt 22)\n\n# can override in config :\nNFQWS_OPT_DESYNC_WG=\"${NFQWS_OPT_DESYNC_WG:---dpi-desync=fake --dpi-desync-repeats=2}\"\n\nalloc_dnum DNUM_WG4ALL\nalloc_qnum QNUM_WG4ALL\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - add, 0 - stop\n\n\tlocal opt=\"--qnum=$QNUM_WG4ALL $NFQWS_OPT_DESYNC_WG\"\n\tdo_nfqws $1 $DNUM_WG4ALL \"$opt\"\n}\n# size = 156 (8 udp header + 148 payload) && payload starts with 0x01000000\nzapret_custom_firewall()\n{\n        # $1 - 1 - run, 0 - stop\n\n\tlocal f='-p udp -m u32 --u32'\n\tfw_nfqws_post $1 \"$f 0>>22&0x3C@4>>16=0x9c&&0>>22&0x3C@8=0x01000000\" \"$f 44>>16=0x9c&&48=0x01000000\" $QNUM_WG4ALL\n}\nzapret_custom_firewall_nft()\n{\n        # stop logic is not required\n\n\tlocal f=\"udp length 156 @ih,0,32 0x01000000\"\n\tnft_fw_nfqws_post \"$f\" \"$f\" $QNUM_WG4ALL\n}\n"
  },
  {
    "path": "init.d/macos/custom.d/.keep",
    "content": ""
  },
  {
    "path": "init.d/macos/custom.d.examples/50-extra-tpws",
    "content": "# this script is an example describing how to run tpws on a custom port\n\nTPWS_OPT_EXTRA=${TPWS_OPT_EXTRA:---split-pos=2}\nDPORTS_EXTRA=${DPORTS_EXTRA:-20443,20444,30000-30009}\n\nalloc_dnum DNUM_EXTRA_TPWS\nalloc_tpws_port TPPORT_EXTRA_TPWS\n\nzapret_custom_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\tlocal opt=\"--user=root --port=$TPPORT_EXTRA_TPWS\"\n\ttpws_apply_binds opt\n\topt=\"$opt $TPWS_OPT_EXTRA\"\n\tfilter_apply_hostlist_target opt\n\tdo_daemon $1 $DNUM_EXTRA_TPWS \"$TPWS\" \"$opt\"\n}\n\n# custom firewall functions echo rules for zapret-v4 and zapret-v6 anchors\n# they come after automated table definitions. so you can use <zapret> <zapret6> <zapret-user> ...\n\nzapret_custom_firewall_v4()\n{\n\tpf_anchor_zapret_v4_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA)\n}\nzapret_custom_firewall_v6()\n{\n\tpf_anchor_zapret_v6_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA)\n}\n"
  },
  {
    "path": "init.d/macos/functions",
    "content": "# init script functions library for macos\n\nZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/pf.sh\"\n. \"$ZAPRET_BASE/common/list.sh\"\n. \"$ZAPRET_BASE/common/custom.sh\"\nCUSTOM_DIR=\"$ZAPRET_RW/init.d/macos\"\n\nIPSET_DIR=$ZAPRET_BASE/ipset\n. \"$IPSET_DIR/def.sh\"\n\nPIDDIR=/var/run\n[ -n \"$TPPORT\" ] || TPPORT=988\n[ -n \"$TPPORT_SOCKS\" ] || TPPORT=987\n[ -n \"$WS_USER\" ] || WS_USER=daemon\nTPWS_WAIT=\"--bind-wait-ifup=30 --bind-wait-ip=30\"\nTPWS_WAIT_SOCKS6=\"$TPWS_WAIT --bind-wait-ip-linklocal=30\"\n[ -n \"$TPWS\" ] || TPWS=\"$ZAPRET_BASE/tpws/tpws\"\n\nCUSTOM_SCRIPT=\"$ZAPRET_BASE/init.d/macos/custom\"\n[ -f \"$CUSTOM_SCRIPT\" ] && . \"$CUSTOM_SCRIPT\"\n\nrun_daemon()\n{\n\t# $1 - daemon number : 1,2,3,...\n\t# $2 - daemon\n\t# $3 - daemon args\n\t# use $PIDDIR/$DAEMONBASE$1.pid as pidfile\n\tlocal DAEMONBASE=\"$(basename \"$2\")\"\n\tlocal PIDFILE=\"$PIDDIR/$DAEMONBASE$1.pid\"\n\tlocal ARGS=\"--daemon --pidfile=$PIDFILE $3\"\n\t[ -f \"$PIDFILE\" ] && pgrep -qF \"$PIDFILE\" && {\n\t\techo Already running $1: $2\n\t\treturn 0\n\t}\n\techo \"Starting daemon $1: $2 $ARGS\"\n\t\"$2\" $ARGS\n}\nstop_daemon()\n{\n\t# $1 - daemon number : 1,2,3,...\n\t# $2 - daemon\n\t# use $PIDDIR/$DAEMONBASE$1.pid as pidfile\n\n\tlocal PID\n\tlocal DAEMONBASE=\"$(basename \"$2\")\"\n\tlocal PIDFILE=\"$PIDDIR/$DAEMONBASE$1.pid\"\n\t[ -f \"$PIDFILE\" ] && read PID <\"$PIDFILE\"\n\t[ -n \"$PID\" ] && {\n\t\techo \"Stopping daemon $1: $2 (PID=$PID)\"\n\t\tkill $PID\n\t\trm -f \"$PIDFILE\"\n\t}\n\treturn 0\n}\ndo_daemon()\n{\n\t# $1 - 1 - run, 0 - stop\n\ton_off_function run_daemon stop_daemon \"$@\"\n}\n\ntpws_apply_binds()\n{\n\tlocal o\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || o=\"--bind-addr=127.0.0.1\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\tfor i in lo0 $IFACE_LAN; do\n\t\t\to=\"$o --bind-iface6=$i --bind-linklocal=force $TPWS_WAIT\"\n\t\tdone\n\t}\n\teval $1=\"\\\"\\$$1 $o\\\"\"\n}\ntpws_apply_socks_binds()\n{\n\tlocal o\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || o=\"--bind-addr=127.0.0.1\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-addr=::1\"\n\t\n\tfor lan in $IFACE_LAN; do\n\t    [ \"$DISABLE_IPV4\" = \"1\" ] || o=\"$o --bind-iface4=$lan $TPWS_WAIT\"\n\t    [ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6\"\n\tdone\n\teval $1=\"\\\"\\$$1 $o\\\"\"\n}\n\nwait_interface_ll()\n{\n\techo waiting for an ipv6 link local address on $1 ...\n\t\"$TPWS\" --bind-wait-only --bind-iface6=$1 --bind-linklocal=force $TPWS_WAIT\n}\nwait_lan_ll()\n{\n\t[ \"$DISABLE_IPV6\" != \"1\" ] && {\n\t\tfor lan in $IFACE_LAN; do\n\t\t\twait_interface_ll $lan >&2 || {\n\t\t\t\techo \"wait interface failed on $lan\"\n\t\t\t\treturn 1\n\t\t\t}\n\t\tdone\n\t}\n\treturn 0\n}\nget_ipv6_linklocal()\n{\n\tifconfig $1 | sed -nEe 's/^.*inet6 (fe80:[a-f0-9:]+).*/\\1/p'\n}\n\n\nzapret_do_firewall()\n{\n\t# $1 - 1 - add, 0 - del\n\n\t[ \"$1\" = 1 -a -n \"$INIT_FW_PRE_UP_HOOK\" ] && $INIT_FW_PRE_UP_HOOK\n\t[ \"$1\" = 0 -a -n \"$INIT_FW_PRE_DOWN_HOOK\" ] && $INIT_FW_PRE_DOWN_HOOK\n\n\tif [ \"$1\" = \"1\" ] ; then\n\t\tpf_anchor_root || return 1\n\t\tpf_anchors_create\n\t\tpf_anchors_load || return 1\n\t\tpf_enable\n\telse\n\t\tpf_anchors_clear\n\tfi\n\n\t[ \"$1\" = 1 -a -n \"$INIT_FW_POST_UP_HOOK\" ] && $INIT_FW_POST_UP_HOOK\n\t[ \"$1\" = 0 -a -n \"$INIT_FW_POST_DOWN_HOOK\" ] && $INIT_FW_POST_DOWN_HOOK\n\n\treturn 0\n}\nzapret_apply_firewall()\n{\n\tzapret_do_firewall 1 \"$@\"\n}\nzapret_unapply_firewall()\n{\n\tzapret_do_firewall 0 \"$@\"\n}\nzapret_restart_firewall()\n{\n\tzapret_unapply_firewall \"$@\"\n\tzapret_apply_firewall \"$@\"\n}\n\n\nstandard_mode_daemons()\n{\n\tlocal opt\n\n\tif [ \"$1\" = \"1\" ] && [ \"$DISABLE_IPV4\" = \"1\" ] && [ \"$DISABLE_IPV6\" = \"1\" ] ; then\n\t\techo \"both ipv4 and ipv6 are disabled. nothing to do\"\n\telse\n\t\t[ \"$TPWS_ENABLE\" = 1 ] && check_bad_ws_options $1 \"$TPWS_OPT\" && {\n\t\t\topt=\"--user=root --port=$TPPORT\"\n\t\t\ttpws_apply_binds opt\n\t\t\topt=\"$opt $TPWS_OPT\"\n\t\t\tfilter_apply_hostlist_target opt\n\t\t\tdo_daemon $1 1 \"$TPWS\" \"$opt\"\n\t\t}\n\t\t[ \"$TPWS_SOCKS_ENABLE\" = 1 ] && {\n\t\t\topt=\"--socks --user=$WS_USER --port=$TPPORT_SOCKS\"\n\t\t\ttpws_apply_socks_binds opt\n\t\t\topt=\"$opt $TPWS_SOCKS_OPT\"\n\t\t\tfilter_apply_hostlist_target opt\n\t\t\tdo_daemon $1 2 \"$TPWS\" \"$opt\"\n\t\t}\n\tfi\n}\n\nzapret_do_daemons()\n{\n\t# $1 - 1 - run, 0 - stop\n\n\tstandard_mode_daemons $1\n\tcustom_runner zapret_custom_daemons $1\n\n\treturn 0\n}\nzapret_run_daemons()\n{\n\tzapret_do_daemons 1 \"$@\"\n}\nzapret_stop_daemons()\n{\n\tzapret_do_daemons 0 \"$@\"\n}\nzapret_restart_daemons()\n{\n\tzapret_stop_daemons \"$@\"\n\tzapret_run_daemons \"$@\"\n}\n"
  },
  {
    "path": "init.d/macos/zapret",
    "content": "#!/bin/sh\n\nEXEDIR=\"$(dirname \"$0\")\"\nZAPRET_BASE=\"$EXEDIR/../..\"\nZAPRET_BASE=\"$(cd \"$ZAPRET_BASE\"; pwd)\"\n\n. \"$EXEDIR/functions\"\n\ncase \"$1\" in\n\tstart)\n\t\tzapret_run_daemons\n\t\t[ \"$INIT_APPLY_FW\" != \"1\" ] || zapret_apply_firewall\n\t\t;;\n\tstop)\n\t\t[ \"$INIT_APPLY_FW\" != \"1\" ] || zapret_unapply_firewall\n\t\tzapret_stop_daemons\n\t\t;;\n\trestart)\n\t\t\"$0\" stop\n\t\t\"$0\" start\n\t\t;;\n\n\tstart-fw|start_fw)\n\t\tzapret_apply_firewall\n\t\t;;\n\tstop-fw|stop_fw)\n\t\tzapret_unapply_firewall\n\t\t;;\n\trestart-fw|stop_fw)\n\t\tzapret_restart_firewall\n\t\t;;\n\treload-fw-tables|reload_fw_tables)\n\t\tpf_table_reload\n\t\t;;\n\t\n\tstart-daemons|start_daemons)\n\t\tzapret_run_daemons\n\t\t;;\n\tstop-daemons|stop_daemons)\n\t\tzapret_stop_daemons\n\t\t;;\n\trestart-daemons|restart_daemons)\n\t\tzapret_restart_daemons\n\t\t;;\n\t\t\n  *)\n\tN=\"$SCRIPT/$NAME\"\n\techo \"Usage: $N {start|stop|start-fw|stop-fw|restart-fw|reload-fw-tables|start-daemons|stop-daemons|restart-daemons}\" >&2\n\texit 1\n\t;;\nesac\n"
  },
  {
    "path": "init.d/macos/zapret.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>zapret</string>\n    <key>LaunchOnlyOnce</key>\n    <false/>\n    <key>ProgramArguments</key>\n    <array>\n        <string>/opt/zapret/init.d/macos/zapret</string>\n        <string>start</string>\n    </array>\n    <key>RunAtLoad</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "init.d/openrc/zapret",
    "content": "#!/sbin/openrc-run\n\n# zapret openrc to sysv adapter\n# on some systems (alpine) for unknown reason non-openrc-run scripts are not started from /etc/init.d\n\nEXEDIR=$(dirname \"$RC_SERVICE\")\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=\"$EXEDIR/../..\"\nZAPRET_INIT=\"$ZAPRET_BASE/init.d/sysv/zapret\"\n\nextra_commands=\"start_fw stop_fw restart_fw start_daemons stop_daemons restart_daemons reload_ifsets list_ifsets list_table\"\ndescription=\"extra commands :\"\ndescription_stop_fw=\"Stop zapret firewall\"\ndescription_start_fw=\"Start zapret firewall\"\ndescription_restart_fw=\"Restart zapret firewall\"\ndescription_reload_ifsets=\"Reload interface lists (nftables only)\"\ndescription_list_ifsets=\"Display interface lists (nftables only)\"\ndescription_list_table=\"Display zapret nftable (nftables only)\"\ndescription_stop_daemons=\"Stop zapret daemons only\"\ndescription_start_daemons=\"Start zapret daemons only\"\ndescription_restart_daemons=\"Restart zapret firewall only\"\n\ndepend() {\n\trc-service -e networking && need networking\n}\nstart()\n{\n\t\"$ZAPRET_INIT\" start\n}\nstop()\n{\n\t\"$ZAPRET_INIT\" stop\n}\nstart_fw()\n{\n\t\"$ZAPRET_INIT\" start_fw\n}\nstop_fw()\n{\n\t\"$ZAPRET_INIT\" stop_fw\n}\nrestart_fw()\n{\n\t\"$ZAPRET_INIT\" restart_fw\n}\nstart_daemons()\n{\n\t\"$ZAPRET_INIT\" start_daemons\n}\nstop_daemons()\n{\n\t\"$ZAPRET_INIT\" stop_daemons\n}\nrestart_daemons()\n{\n\t\"$ZAPRET_INIT\" restart_daemons\n}\nreload_ifsets()\n{\n\t\"$ZAPRET_INIT\" reload_ifsets\n}\nlist_ifsets()\n{\n\t\"$ZAPRET_INIT\" list_ifsets\n}\nlist_table()\n{\n\t\"$ZAPRET_INIT\" list_table\n}\n"
  },
  {
    "path": "init.d/openwrt/90-zapret",
    "content": "#!/bin/sh\n\nZAPRET=/etc/init.d/zapret\n\ncheck_lan()\n{\n\tIS_LAN=\n\t[ -n \"$OPENWRT_LAN\" ] || OPENWRT_LAN=lan\n\tfor lan in $OPENWRT_LAN; do\n\t\t[ \"$INTERFACE\" = \"$lan\" ] && {\n\t\t\tIS_LAN=1\n\t\t\tbreak\n\t\t}\n\tdone\n}\ncheck_need_to_reload_tpws6()\n{\n\t# tpws6 dnat target nft map can only be reloaded within firewall apply procedure\n\t# interface ifsets (wanif, wanif6, lanif) can be reloaded independently\n\tcheck_lan\n\tRELOAD_TPWS6=\n\t[ \"$ACTION\" = \"ifup\" -a \"$DISABLE_IPV6\" != 1 -a -n \"$IS_LAN\" ] && \\\n\t\tif [ \"$TPWS_ENABLE\" = 1 ] || dir_is_not_empty \"$CUSTOM_DIR/custom.d\"; then RELOAD_TPWS6=1; fi\n}\n\n\n[ -n \"$INTERFACE\" ] && [ \"$ACTION\" = ifup -o \"$ACTION\" = ifdown ] && [ -x \"$ZAPRET\" ] && \"$ZAPRET\" enabled && {\n\tSCRIPT=$(readlink \"$ZAPRET\")\n\tif [ -n \"$SCRIPT\" ]; then\n\t\tEXEDIR=$(dirname \"$SCRIPT\")\n\t\tZAPRET_BASE=$(readlink -f \"$EXEDIR/../..\")\n\telse\n\t\tZAPRET_BASE=/opt/zapret\n\tfi\n\tZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\n\tZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/openwrt\"\n\t. \"$ZAPRET_CONFIG\"\n\t. \"$ZAPRET_BASE/common/base.sh\"\n\t. \"$ZAPRET_BASE/common/fwtype.sh\"\n\n\tcheck_need_to_reload_tpws6\n\t[ -n \"$RELOAD_TPWS6\" ] && {\n\t\tlogger -t zapret restarting daemons due to $ACTION of $INTERFACE to update tpws6 dnat target\n\t\t\"$ZAPRET\" restart_daemons\n\t}\n\tlinux_fwtype\n\tcase \"$FWTYPE\" in\n\t\tnftables)\n\t\t\tif [ -n \"$RELOAD_TPWS6\" ] ; then\n\t\t\t\tlogger -t zapret reloading nftables due to $ACTION of $INTERFACE to update tpws6 dnat target\n\t\t\t\t\"$ZAPRET\" restart_fw\n\t\t\telse\n\t\t\t\tlogger -t zapret reloading nftables ifsets due to $ACTION of $INTERFACE\n\t\t\t\t\"$ZAPRET\" reload_ifsets\n\t\t\tfi\n\t\t\t;;\n\t\tiptables)\n\t\t\topenwrt_fw3 || {\n\t\t\t\tlogger -t zapret reloading iptables due to $ACTION of $INTERFACE\n\t\t\t\t\"$ZAPRET\" restart_fw\n\t\t\t}\n\t\t\t;;\n\tesac\n}\n"
  },
  {
    "path": "init.d/openwrt/custom.d/.keep",
    "content": ""
  },
  {
    "path": "init.d/openwrt/firewall.zapret",
    "content": "SCRIPT=$(readlink /etc/init.d/zapret)\nif [ -n \"$SCRIPT\" ]; then\n EXEDIR=$(dirname \"$SCRIPT\")\n ZAPRET_BASE=$(readlink -f \"$EXEDIR/../..\")\nelse\n ZAPRET_BASE=/opt/zapret\nfi\n\n. \"$ZAPRET_BASE/init.d/openwrt/functions\"\n\nzapret_apply_firewall\n"
  },
  {
    "path": "init.d/openwrt/functions",
    "content": ". /lib/functions/network.sh\n\nZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/linux_iphelper.sh\"\n. \"$ZAPRET_BASE/common/ipt.sh\"\n. \"$ZAPRET_BASE/common/nft.sh\"\n. \"$ZAPRET_BASE/common/linux_fw.sh\"\n. \"$ZAPRET_BASE/common/linux_daemons.sh\"\n. \"$ZAPRET_BASE/common/list.sh\"\n. \"$ZAPRET_BASE/common/custom.sh\"\nCUSTOM_DIR=\"$ZAPRET_RW/init.d/openwrt\"\n\n[ -n \"$QNUM\" ] || QNUM=200\n[ -n \"$TPPORT\" ] || TPPORT=988\n[ -n \"$TPPORT_SOCKS\" ] || TPPORT_SOCKS=987\n[ -n \"$WS_USER\" ] || WS_USER=daemon\n[ -n \"$DESYNC_MARK\" ] || DESYNC_MARK=0x40000000\n[ -n \"$DESYNC_MARK_POSTNAT\" ] || DESYNC_MARK_POSTNAT=0x20000000\n[ -n \"$OPENWRT_LAN\" ] || OPENWRT_LAN=lan\n\nTPWS_LOCALHOST4=127.0.0.127\n\nIPSET_CR=\"$ZAPRET_BASE/ipset/create_ipset.sh\"\n\n# can be multiple ipv6 outgoing interfaces\n# uplink from isp, tunnelbroker, vpn, ...\n# want them all. who knows what's the real one that blocks sites\n# dont want any manual configuration - want to do it automatically\n# standard network_find_wan[6] return only the first\n# we use low level function from network.sh to avoid this limitation\n# it can change theoretically and stop working\n\nnetwork_find_wan4_all()\n{\n\tif [ -n \"$OPENWRT_WAN4\" ]; then\n\t\teval $1=\"\\$OPENWRT_WAN4\"\n\telse\n\t\t__network_ifstatus \"$1\" \"\" \"[@.route[@.target='0.0.0.0' && !@.table]].interface\" \"\" 10 2>/dev/null && return\n\t\tnetwork_find_wan $1\n\tfi\n}\nnetwork_find_wan_all()\n{\n\tnetwork_find_wan4_all \"$@\"\n}\nnetwork_find_wan6_all()\n{\n\tif [ -n \"$OPENWRT_WAN6\" ]; then\n\t\teval $1=\"\\$OPENWRT_WAN6\"\n\telse\n\t\t__network_ifstatus \"$1\" \"\" \"[@.route[@.target='::' && !@.table]].interface\" \"\" 10 2>/dev/null && return\n\t\tnetwork_find_wan6 $1\n\tfi\n}\nnetwork_find_wanX_devices()\n{\n\t# $1 - ip version: 4 or 6\n\t# $2 - variable to put result to\n\tlocal ifaces\n\tnetwork_find_wan${1}_all ifaces\n\tcall_for_multiple_items network_get_device $2 \"$ifaces\"\n}\n\n\ndnat6_target()\n{\n\t# $1 - lan network name\n\t# $2 - var to store target ip6\n\n\tnetwork_is_up $1 || {\n\t\t[ -n \"$2\" ] && eval $2=''\n\t\treturn\n\t}\n\n\tlocal DEVICE\n\tnetwork_get_device DEVICE $1\n\n\t_dnat6_target $DEVICE $2\n}\n\nset_route_localnet()\n{\n\t# $1 - 1 = enable, 0 = disable\n\n\tlocal DLAN\n\tcall_for_multiple_items network_get_device DLAN \"$OPENWRT_LAN\"\n\t_set_route_localnet $1 $DLAN\n}\n\n\nfw_nfqws_prepost_x()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - filter\n\t# $3 - queue number\n\t# $4 - 4/6\n\t# $5 - post/pre\n\n\tlocal ifaces DWAN\n\tnetwork_find_wan${4}_all ifaces\n\tcall_for_multiple_items network_get_device DWAN \"$ifaces\"\n\n\t[ -n \"$DWAN\" ] && _fw_nfqws_${5}${4} $1 \"$2\" $3 \"$(unique $DWAN)\"\n}\nfw_nfqws_post4()\n{\n\tfw_nfqws_prepost_x  $1 \"$2\" $3 4 post\n}\nfw_nfqws_post6()\n{\n\tfw_nfqws_prepost_x  $1 \"$2\" $3 6 post\n}\nfw_nfqws_pre4()\n{\n\tfw_nfqws_prepost_x  $1 \"$2\" $3 4 pre\n}\nfw_nfqws_pre6()\n{\n\tfw_nfqws_prepost_x  $1 \"$2\" $3 6 pre\n}\nfw_tpws_x()\n{\n\t# $1 - 1 - add, 0 - del\n\t# $2 - filter\n\t# $3 - tpws port\n\t# $4 - ip version : 4 or 6\n\n\tlocal ifaces DLAN DWAN\n\n\tcall_for_multiple_items network_get_device DLAN \"$OPENWRT_LAN\"\n\n\tnetwork_find_wan${4}_all ifaces\n\tcall_for_multiple_items network_get_device DWAN \"$ifaces\"\n\n\t[ -n \"$DWAN\" ] && _fw_tpws${4} $1 \"$2\" $3 \"$DLAN\" \"$(unique $DWAN)\"\n}\nfw_tpws4()\n{\n\tfw_tpws_x $1 \"$2\" $3 4\n}\nfw_tpws6()\n{\n\tfw_tpws_x $1 \"$2\" $3 6\n}\n\n\ncreate_ipset()\n{\n\techo \"Creating ip list table (firewall type $FWTYPE)\"\n\t\"$IPSET_CR\" \"$@\"\n}\n\nlist_nfqws_rules()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tip$1tables -S POSTROUTING -t mangle | \\\n\t\tgrep -E \"NFQUEUE --queue-num $QNUM --queue-bypass|NFQUEUE --queue-num $(($QNUM+1)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+2)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+3)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+10)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+11)) --queue-bypass\" | \\\n\t\tsed -re 's/^-A POSTROUTING (.*) -j NFQUEUE.*$/\\1/' -e \"s/-m mark ! --mark $DESYNC_MARK\\/$DESYNC_MARK//\"\n}\napply_flow_offloading_enable_rule()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tlocal i off='-j FLOWOFFLOAD'\n\t[ \"$FLOWOFFLOAD\" = \"hardware\" ] && off=\"$off --hw\"\n\ti=\"forwarding_rule_zapret -m comment --comment zapret_traffic_offloading_enable -m conntrack --ctstate RELATED,ESTABLISHED $off\"\n\techo enabling ipv${1:-4} flow offloading : $i\n\tip$1tables -A $i\n}\napply_flow_offloading_exempt_rule()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tlocal i v\n\tv=$1\n\tshift\n\ti=\"forwarding_rule_zapret $@ -m comment --comment zapret_traffic_offloading_exemption -j RETURN\"\n\techo applying ipv${v:-4} flow offloading exemption : $i\n\tip${v}tables -A $i\n}\nflow_offloading_unexempt_v()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tlocal DWAN\n\tnetwork_find_wanX_devices ${1:-4} DWAN\n\tfor i in $DWAN; do ipt$1_del FORWARD -o $i -j forwarding_rule_zapret ; done\n\tip$1tables -F forwarding_rule_zapret 2>/dev/null\n\tip$1tables -X forwarding_rule_zapret 2>/dev/null\n}\nflow_offloading_exempt_v()\n{\n\t# $1 = '' for ipv4, '6' for ipv6\n\tis_ipt_flow_offload_avail $1 || return 0\n\n\tflow_offloading_unexempt_v $1\n\n\t[ \"$FLOWOFFLOAD\" = 'software' -o \"$FLOWOFFLOAD\" = 'hardware' ] && {\n\t\tip$1tables -N forwarding_rule_zapret\n\n\t\t# remove outgoing interface\n\t\tlist_nfqws_rules $1 | sed -re 's/-o +[^ ]+//g' |\n\t\twhile read rule; do\n\t\t\tapply_flow_offloading_exempt_rule \"$1\" $rule\n\t\tdone\n\t\n\t\tapply_flow_offloading_enable_rule $1\n\n\t\t# only outgoing to WAN packets trigger flow offloading\n\t\tlocal DWAN\n\t\tnetwork_find_wanX_devices ${1:-4} DWAN\n\t\tfor i in $DWAN; do ipt$1 FORWARD -o $i -j forwarding_rule_zapret; done\n\t}\n\treturn 0\n}\nflow_offloading_exempt()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || flow_offloading_exempt_v\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || flow_offloading_exempt_v 6\n}\nflow_offloading_unexempt()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || flow_offloading_unexempt_v\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || flow_offloading_unexempt_v 6\n}\n\n\n\nnft_fill_ifsets_overload()\n{\n\tlocal ifaces DLAN DWAN DWAN6 PDLAN PDWAN PDWAN6\n\n\tcall_for_multiple_items network_get_device DLAN \"$OPENWRT_LAN\"\n\tcall_for_multiple_items network_get_physdev PDLAN \"$OPENWRT_LAN\"\n\n\tnetwork_find_wan4_all ifaces\n\tcall_for_multiple_items network_get_device DWAN \"$ifaces\"\n\tcall_for_multiple_items network_get_physdev PDWAN \"$ifaces\"\n\n\tnetwork_find_wan6_all ifaces\n\tcall_for_multiple_items network_get_device DWAN6 \"$ifaces\"\n\tcall_for_multiple_items network_get_physdev PDWAN6 \"$ifaces\"\n\n\tnft_fill_ifsets \"$DLAN\" \"$DWAN\" \"$DWAN6\" \"$PDLAN\" \"$PDWAN\" \"$PDWAN6\"\n}\n\nnft_fw_tpws4()\n{\n\t_nft_fw_tpws4 \"$1\" $2 always_apply_wan_filter\n}\nnft_fw_tpws6()\n{\n\tlocal DLAN\n\tcall_for_multiple_items network_get_device DLAN \"$OPENWRT_LAN\"\n\t_nft_fw_tpws6 \"$1\" $2 \"$DLAN\" always_apply_wan_filter\n}\nnft_fw_nfqws_post4()\n{\n\t_nft_fw_nfqws_post4 \"$1\" $2 always_apply_wan_filter\n}\nnft_fw_nfqws_post6()\n{\n\t_nft_fw_nfqws_post6 \"$1\" $2 always_apply_wan_filter\n}\nnft_fw_nfqws_pre4()\n{\n\t_nft_fw_nfqws_pre4 \"$1\" $2 always_apply_wan_filter\n}\nnft_fw_nfqws_pre6()\n{\n\t_nft_fw_nfqws_pre6 \"$1\" $2 always_apply_wan_filter\n}\n"
  },
  {
    "path": "init.d/openwrt/zapret",
    "content": "#!/bin/sh /etc/rc.common\n\nUSE_PROCD=1\n# after network\nSTART=21\n\nmy_extra_command() {\n\tlocal cmd=\"$1\"\n\tlocal help=\"$2\"\n\n\tlocal extra=\"$(printf \"%-16s%s\" \"${cmd}\" \"${help}\")\"\n\tEXTRA_HELP=\"${EXTRA_HELP}\t${extra}\n\"\n\tEXTRA_COMMANDS=\"${EXTRA_COMMANDS} ${cmd}\"\n}\nmy_extra_command stop_fw \"Stop zapret firewall (noop in iptables+fw3 case)\"\nmy_extra_command start_fw \"Start zapret firewall (noop in iptables+fw3 case)\"\nmy_extra_command restart_fw \"Restart zapret firewall (noop in iptables+fw3 case)\"\nmy_extra_command reload_ifsets \"Reload interface lists (nftables only)\"\nmy_extra_command list_ifsets \"Display interface lists (nftables only)\"\nmy_extra_command list_table \"Display zapret nftable (nftables only)\"\nmy_extra_command stop_daemons \"Stop zapret daemons only (=stop in iptables+fw3 case)\"\nmy_extra_command start_daemons \"Start zapret daemons only (=start in iptables+fw3 case)\"\nmy_extra_command restart_daemons \"Restart zapret firewall only (=restart in iptables+fw3 case)\"\n\nSCRIPT=$(readlink /etc/init.d/zapret)\nif [ -n \"$SCRIPT\" ]; then\n EXEDIR=$(dirname \"$SCRIPT\")\n ZAPRET_BASE=$(readlink -f \"$EXEDIR/../..\")\nelse\n ZAPRET_BASE=/opt/zapret\nfi\n\n. \"$ZAPRET_BASE/init.d/openwrt/functions\"\n\n\n# !!!!! in old openwrt 21.x- with iptables firewall rules are configured separately\n# !!!!! in new openwrt >21.x with nftables firewall is configured here\n\nPIDDIR=/var/run\n\n[ -n \"$NFQWS\" ] || NFQWS=\"$ZAPRET_BASE/nfq/nfqws\"\nNFQWS_OPT_BASE=\"--user=$WS_USER --dpi-desync-fwmark=$DESYNC_MARK\"\n\n[ -n \"$TPWS\" ] || TPWS=\"$ZAPRET_BASE/tpws/tpws\"\nTPWS_OPT_BASE=\"--user=$WS_USER\"\nTPWS_OPT_BASE4=\"--bind-addr=$TPWS_LOCALHOST4\"\nTPWS_OPT_BASE6=\"--bind-addr=::1\"\nTPWS_WAIT=\"--bind-wait-ifup=30 --bind-wait-ip=30\"\nTPWS_WAIT_SOCKS6=\"$TPWS_WAIT --bind-wait-ip-linklocal=30\"\nTPWS_OPT_BASE6_PRE=\"--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3\"\n\nrun_daemon()\n{\n\t# $1 - daemon string id or number. can use 1,2,3,...\n\t# $2 - daemon\n\t# $3 - daemon args\n\t# use $PIDDIR/$DAEMONBASE$1.pid as pidfile\n\tlocal DAEMONBASE=\"$(basename \"$2\")\"\n\techo \"Starting daemon $1: $2 $3\"\n\tprocd_open_instance\n\tprocd_set_param command $2 $3\n\tprocd_set_param pidfile $PIDDIR/$DAEMONBASE$1.pid\n\tprocd_close_instance\n}\n\nrun_tpws()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] && [ \"$DISABLE_IPV6\" = \"1\" ] && return 0\n\n\tlocal OPT=\"$TPWS_OPT_BASE\"\n\tlocal DEVICE\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || OPT=\"$OPT $TPWS_OPT_BASE4\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\tOPT=\"$OPT $TPWS_OPT_BASE6\"\n\t\tfor lan in $OPENWRT_LAN; do\n\t\t    network_get_device DEVICE $lan\n\t\t    [ -n \"$DEVICE\" ] && OPT=\"$OPT --bind-iface6=$DEVICE $TPWS_OPT_BASE6_PRE\"\n\t\tdone\n\t}\n\trun_daemon $1 \"$TPWS\" \"$OPT $2\"\n}\ndo_tpws()\n{\n\t[ \"$1\" = 0 ] || { shift; run_tpws \"$@\"; }\n}\nrun_tpws_socks()\n{\n\t[ \"$DISABLE_IPV4\" = \"1\" ] && [ \"$DISABLE_IPV6\" = \"1\" ] && return 0\n\n\tlocal opt=\"$TPWS_OPT_BASE --socks\"\n\n\ttpws_apply_socks_binds opt\n\trun_daemon $1 \"$TPWS\" \"$opt $2\"\n}\ndo_tpws_socks()\n{\n\t[ \"$1\" = 0 ] || { shift; run_tpws_socks \"$@\"; }\n}\ntpws_apply_socks_binds()\n{\n\tlocal o\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || o=\"--bind-addr=127.0.0.1\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-addr=::1\"\n\t\n\tfor lan in $OPENWRT_LAN; do\n\t\tnetwork_get_device DEVICE $lan\n\t\t[ -n \"$DEVICE\" ] || continue\n\t\t[ \"$DISABLE_IPV4\" = \"1\" ] || o=\"$o --bind-iface4=$DEVICE $TPWS_WAIT\"\n\t\t[ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-iface6=$DEVICE --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6\"\n\tdone\n\teval $1=\"\\\"\\$$1 $o\\\"\"\n}\n\nrun_nfqws()\n{\n\trun_daemon $1 \"$NFQWS\" \"$NFQWS_OPT_BASE $2\"\n}\ndo_nfqws()\n{\n\t[ \"$1\" = 0 ] || { shift; run_nfqws \"$@\"; }\n}\n\nstart_daemons_procd()\n{\n\tstandard_mode_daemons 1\n\tcustom_runner zapret_custom_daemons 1\n\n\treturn 0\n}\nstart_daemons()\n{\n\trc_procd start_daemons_procd \"$@\"\n}\nstop_daemons()\n{\n\tlocal svc=\"$(basename ${basescript:-$initscript})\"\n\tprocd_running \"$svc\" \"$1\" && procd_kill \"$svc\" \"$1\"\n}\nrestart_daemons()\n{\n\tstop_daemons\n\tstart_daemons\n}\n\nstart_fw()\n{\n\tzapret_apply_firewall\n}\nstop_fw()\n{\n\tzapret_unapply_firewall\n}\nrestart_fw()\n{\n\tstop_fw\n\tstart_fw\n}\nreload_ifsets()\n{\n\tzapret_reload_ifsets\n}\nlist_ifsets()\n{\n\tzapret_list_ifsets\n}\nlist_table()\n{\n\tzapret_list_table\n}\n\nstart_service()\n{\n\tstart_daemons_procd\n\t[ \"$INIT_APPLY_FW\" != \"1\" ] || {\n\t\tlinux_fwtype\n\t\topenwrt_fw3_integration || start_fw\n\t}\n}\n\nstop_service()\n{\n\t# this procedure is called from stop()\n\t# stop() already stop daemons\n\t[ \"$INIT_APPLY_FW\" != \"1\" ] || {\n\t\tlinux_fwtype\n\t\topenwrt_fw3_integration || stop_fw\n\t}\n}\n"
  },
  {
    "path": "init.d/openwrt-minimal/readme.txt",
    "content": "Minimal tpws startup script for low storage openwrt.\n\n--- openwrt with NFTABLES (22+)\n\nMake sure you are running openwrt with nftables, not iptables.\nNo opkg dependencies required !\n\n* install :\n\nCopy everything from tpws directory to the root of the router.\nCopy tpws binary for your architecture to /usr/bin/tpws\nSet proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws\nEDIT /etc/config/tpws\nIf you don't want ipv6 : edit /etc/nftables.d and comment lines with ipv6 redirect\n/etc/init.d/tpws enable\n/etc/init.d/tpws start\nfw4 restart\n\n* full uninstall :\n\n/etc/init.d/tpws disable\n/etc/init.d/tpws stop\nrm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws\nfw4 restart\n\n--- openwrt with IPTABLES (21-)\n\nMake sure you are running openwrt with iptables, not nftables.\nMake sure you do not have anything valuable in /etc/firewall.user.\nIf you have - do not blindly follow instruction in firewall.user part.\nMerge the code instead or setup your own firewall include in /etc/config/firewall.\n\nopkg update\nopkg install iptables-mod-extra\nIPV6 ONLY : opkg install ip6tables-mod-nat\n\n* install :\n\nCopy everything from tpws directory to the root of the router.\nCopy tpws binary for your architecture to /usr/bin/tpws\nSet proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws\nEDIT /etc/config/tpws\nIf you don't want ipv6 : edit /etc/firewall.user and set DISABLE_IPV6=1\n/etc/init.d/tpws enable\n/etc/init.d/tpws start\nfw3 restart\n\n* full uninstall :\n\n/etc/init.d/tpws disable\n/etc/init.d/tpws stop\nrm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws\ntouch /etc/firewall.user\nfw3 restart\n"
  },
  {
    "path": "init.d/openwrt-minimal/tpws/etc/config/tpws",
    "content": "config global defaults\n\toption user daemon\n\toption tpws /usr/bin/tpws\n\nconfig tpws\n\toption port 900\n\toption opt '--split-pos=2 --oob'\n\toption enabled 1\nconfig tpws\n\toption port 901\n\toption opt '--split-tls=sni --disorder'\n\toption enabled 0\n"
  },
  {
    "path": "init.d/openwrt-minimal/tpws/etc/firewall.user",
    "content": "DISABLE_IPV6=0\nTP_PORT=900\nTP_USER=daemon\n\nEXCLUDE4=\"10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 127.0.0.0/8\"\nEXCLUDE6=\"fc00::/7 fe80::/10 ::1\"\nIPTS=\"iptables ip6tables\"\n[ \"$DISABLE_IPV6\" = 1 ] && IPTS=iptables\n\nexists()\n{\n\twhich \"$1\" >/dev/null 2>/dev/null\n}\n\nipt()\n{\n\t$IPTABLES -C \"$@\" >/dev/null 2>/dev/null || $IPTABLES -I \"$@\"\n}\n\nredirect_port()\n{\n\tipt tpws -t nat -p tcp --dport $1 -j REDIRECT --to-port $2\n}\n\nredirect()\n{\n\tredirect_port 80 $TP_PORT\n\tredirect_port 443 $TP_PORT\n}\n\nfor IPTABLES in $IPTS; do\n\t$IPTABLES -t nat -N tpws 2>/dev/null\n\t$IPTABLES -t nat -F tpws\n\tredirect\ndone\n\nfor net in $EXCLUDE4; do\n\tiptables -t nat -I tpws -d $net -j RETURN\ndone\n[ \"$DISABLE_IPV6\" = 1 ] || {\n\tfor net in $EXCLUDE6; do\n\t\tip6tables -t nat -I tpws -d $net -j RETURN\n\tdone\n}\n\nfor IPTABLES in $IPTS; do\n\tipt PREROUTING -t nat -j tpws\n\tipt OUTPUT -t nat -m owner ! --uid-owner $TP_USER -j tpws\ndone\n"
  },
  {
    "path": "init.d/openwrt-minimal/tpws/etc/init.d/tpws",
    "content": "#!/bin/sh /etc/rc.common\n\nTPWS_DEFAULT=/usr/bin/tpws\nTPWS_USER_DEFAULT=daemon\n\nSTART=99\nSTOP=01\nUSE_PROCD=1\n\ntpws_instance()\n{\n\tconfig_get \"$@\"\n\n\tlocal enabled port opt\n\n\tconfig_get_bool enabled \"$1\" enabled 0\n\t[ \"$enabled\" -eq 1 ] || return 1\n\n\tconfig_get port \"$1\" port\n\tconfig_get opt \"$1\" opt\n\n\tlocal COMMAND=\"$TPWS --user=$TPWS_USER --port=$port $opt\"\n\tprocd_open_instance\n\tprocd_set_param command $COMMAND\n\tprocd_close_instance\n}\n\nstart_service()\n{\n\tconfig_load tpws\n\tconfig_get TPWS_USER defaults user $TPWS_USER_DEFAULT\n\tconfig_get TPWS defaults tpws $TPWS_DEFAULT\n\tconfig_foreach tpws_instance tpws\n}\n"
  },
  {
    "path": "init.d/openwrt-minimal/tpws/etc/nftables.d/90-tpws.nft",
    "content": "set tpws_exclude4 {\n\ttype ipv4_addr; flags interval; auto-merge;\n        elements = { 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,127.0.0.0/8 }\n}\nset tpws_exclude6 {\n\ttype ipv6_addr; flags interval; auto-merge;\n        elements = { fc00::/7, fe80::/10, ::1 }\n}\nchain tpws_pre {\n\ttype nat hook prerouting priority dstnat; policy accept;\n\ttcp dport {80,443} ip daddr != @tpws_exclude4 redirect to :900\n\ttcp dport {80,443} ip6 daddr != @tpws_exclude6 redirect to :900\n}\nchain tpws_out {\n\ttype nat hook output priority -100; policy accept;\n\ttcp dport {80,443} skuid != daemon ip daddr != @tpws_exclude4 redirect to :900\n\ttcp dport {80,443} skuid != daemon ip6 daddr != @tpws_exclude6 redirect to :900\n}\n"
  },
  {
    "path": "init.d/pfsense/zapret.sh",
    "content": "#!/bin/sh\n\n# this file should be placed to /usr/local/etc/rc.d and chmod 755\n\n# prepare system\n\nkldload ipfw\nkldload ipdivert\n\n# for older pfsense versions. newer do not have these sysctls\nsysctl net.inet.ip.pfil.outbound=ipfw,pf\nsysctl net.inet.ip.pfil.inbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.outbound=ipfw,pf\nsysctl net.inet6.ip6.pfil.inbound=ipfw,pf\n\n# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state\npfctl -d ; pfctl -e\n\n# add ipfw rules and start daemon\n\nipfw delete 100\nipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg\npkill ^dvtws$\ndvtws --daemon --port 989 --dpi-desync=multisplit\n"
  },
  {
    "path": "init.d/runit/zapret/finish",
    "content": "#!/bin/sh\n/opt/zapret/init.d/sysv/zapret stop\n"
  },
  {
    "path": "init.d/runit/zapret/run",
    "content": "#!/bin/sh\n/opt/zapret/init.d/sysv/zapret start\nexec chpst -b zapret sleep infinity\n"
  },
  {
    "path": "init.d/s6/zapret/down",
    "content": "#!/bin/execlineb -P\nexec /opt/zapret/init.d/sysv/zapret stop\n"
  },
  {
    "path": "init.d/s6/zapret/type",
    "content": "oneshot\n"
  },
  {
    "path": "init.d/s6/zapret/up",
    "content": "#!/bin/execlineb -P\nexec /opt/zapret/init.d/sysv/zapret start\n"
  },
  {
    "path": "init.d/systemd/nfqws@.service",
    "content": "# Example systemd service unit for nfqws. Adjust for your installation.\n\n# WARNING ! This unit requires to compile nfqws using `make systemd`\n# WARNING ! This makefile target enables special systemd notify support.\n\n# PREPARE\n# install build depends\n# make -C /opt/zapret systemd\n# cp nfqws@service /lib/systemd/system\n# systemctl daemon-reload\n\n# MANAGE INSTANCE\n# prepare /etc/zapret/nfqws1.conf with nfqws parameters\n# systemctl start nfqws@nfqws1\n# systemctl status nfqws@nfqws1\n# systemctl restart nfqws@nfqws1\n# systemctl enable nfqws@nfqws1\n# systemctl disable nfqws@nfqws1\n# systemctl stop nfqws@nfqws1\n\n# DELETE\n# rm /lib/systemd/system/nfqws@.service\n# systemctl daemon-reload\n\n\n[Unit]\nAfter=network.target\n\n[Service]\nType=notify\nRestart=on-failure\n\nExecSearchPath=/opt/zapret/binaries/my\nExecStart=nfqws @${CONFIG_DIR}/${INSTANCE}.conf\nEnvironment=CONFIG_DIR=/etc/zapret\nEnvironment=INSTANCE=%i\n\nRestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET\n\nLockPersonality=true\nMemoryDenyWriteExecute=true\nPrivateDevices=true\nPrivateMounts=true\nPrivateTmp=true\nProcSubset=pid\nProtectClock=true\nProtectControlGroups=true\nProtectHome=true\nProtectHostname=true\nProtectKernelLogs=true\nProtectKernelModules=true\nProtectKernelTunables=true\nProtectProc=invisible\nProtectSystem=full\nRemoveIPC=true\nRestrictNamespaces=true\nRestrictRealtime=true\nRestrictSUIDSGID=true\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "init.d/systemd/tpws@.service",
    "content": "# Example systemd service unit for tpws. Adjust for your installation.\n\n# WARNING ! This unit requires to compile tpws using `make systemd`\n# WARNING ! This makefile target enables special systemd notify support.\n\n# PREPARE\n# install build depends\n# make -C /opt/zapret systemd\n# cp tpws@service /lib/systemd/system\n# systemctl daemon-reload\n\n# MANAGE INSTANCE\n# prepare /etc/zapret/tpws1.conf with tpws parameters\n# systemctl start tpws@tpws1\n# systemctl status tpws@tpws1\n# systemctl restart tpws@tpws1\n# systemctl enable tpws@tpws1\n# systemctl disable tpws@tpws1\n# systemctl stop tpws@tpws1\n\n# DELETE\n# rm /lib/systemd/system/tpws@.service\n# systemctl daemon-reload\n\n\n[Unit]\nAfter=network.target\n\n[Service]\nType=notify\nRestart=on-failure\n\nExecSearchPath=/opt/zapret/binaries/my\nExecStart=tpws @${CONFIG_DIR}/${INSTANCE}.conf\nEnvironment=CONFIG_DIR=/etc/zapret\nEnvironment=INSTANCE=%i\n\nRestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET\n\nLockPersonality=true\nMemoryDenyWriteExecute=true\nPrivateDevices=true\nPrivateMounts=true\nPrivateTmp=true\nProcSubset=pid\nProtectClock=true\nProtectControlGroups=true\nProtectHome=true\nProtectHostname=true\nProtectKernelLogs=true\nProtectKernelModules=true\nProtectProc=invisible\nProtectSystem=full\nRemoveIPC=true\nRestrictNamespaces=true\nRestrictRealtime=true\nRestrictSUIDSGID=true\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "init.d/systemd/zapret-list-update.service",
    "content": "[Unit]\nDescription=zapret ip/host list update\n\n[Service]\nRestart=no\nIgnoreSIGPIPE=no\nKillMode=control-group\nGuessMainPID=no\nRemainAfterExit=no\nExecStart=/opt/zapret/ipset/get_config.sh\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "init.d/systemd/zapret-list-update.timer",
    "content": "[Unit]\nDescription=zapret ip/host list update timer\n\n[Timer]\nOnCalendar=*-*-2,4,6,8,10,12,14,16,18,20,22,24,26,28,30 00:00:00\nRandomizedDelaySec=86400\nPersistent=true\nUnit=zapret-list-update.service\n\n[Install]\nWantedBy=timers.target\n"
  },
  {
    "path": "init.d/systemd/zapret.service",
    "content": "[Unit]\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=forking\nRestart=no\nTimeoutSec=30sec\nIgnoreSIGPIPE=no\nKillMode=none\nGuessMainPID=no\nRemainAfterExit=no\nExecStart=/opt/zapret/init.d/sysv/zapret start\nExecStop=/opt/zapret/init.d/sysv/zapret stop\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "init.d/sysv/custom.d/.keep",
    "content": ""
  },
  {
    "path": "init.d/sysv/functions",
    "content": "# init script functions library for desktop linux systems\n\nZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/linux_iphelper.sh\"\n. \"$ZAPRET_BASE/common/ipt.sh\"\n. \"$ZAPRET_BASE/common/nft.sh\"\n. \"$ZAPRET_BASE/common/linux_fw.sh\"\n. \"$ZAPRET_BASE/common/linux_daemons.sh\"\n. \"$ZAPRET_BASE/common/list.sh\"\n. \"$ZAPRET_BASE/common/custom.sh\"\nCUSTOM_DIR=\"$ZAPRET_RW/init.d/sysv\"\n\n\nuser_exists()\n{\n\tid -u $1 >/dev/null 2>/dev/null\n}\nuseradd_compat()\n{\n\t# $1 - username\n\t# skip for readonly systems\n\t[ -w \"/etc\" ] && {\n\t\tif exists useradd ; then\n\t\t\tuseradd --no-create-home --system --shell /bin/false $1\n\t\telif is_linked_to_busybox adduser ; then\n\t\t\t# some systems may miss nogroup group in /etc/group\n\t\t\t# adduser fails if it's absent and no group is specified\n\t\t\taddgroup nogroup 2>/dev/null\n\t\t\t# busybox has special adduser syntax\n\t\t\tadduser -S -H -D $1\n\t\telif exists adduser; then\n\t\t\tadduser --no-create-home --system --disabled-login $1\n\t\tfi\n\t}\n\tuser_exists $1\n}\nprepare_user()\n{\n\t# $WS_USER is required to prevent redirection of the traffic originating from TPWS itself\n\t# otherwise infinite loop will occur\n\t# also its good idea not to run tpws as root\n\tuser_exists $WS_USER || {\n\t\t# fallback to daemon if we cant add WS_USER\n\t\tuseradd_compat $WS_USER || {\n\t\t\tfor user in daemon nobody; do\n\t\t\t\tuser_exists $user && {\n\t\t\t\t\tWS_USER=$user\n\t\t\t\t\treturn 0\n\t\t\t\t}\n\t\t\tdone\n\t\t\treturn 1\n\t\t}\n\t}\n}\n\n# this complex user selection allows to survive in any locked/readonly/minimalistic environment\n[ -n \"$WS_USER\" ] || WS_USER=tpws\nif prepare_user; then\n USEROPT=\"--user=$WS_USER\"\nelse\n WS_USER=1\n USEROPT=\"--uid $WS_USER:$WS_USER\"\nfi\n\nPIDDIR=/var/run\nIPSET_CR=\"$ZAPRET_BASE/ipset/create_ipset.sh\"\n\n[ -n \"$DESYNC_MARK\" ] || DESYNC_MARK=0x40000000\n[ -n \"$DESYNC_MARK_POSTNAT\" ] || DESYNC_MARK_POSTNAT=0x20000000\n\n[ -n \"$QNUM\" ] || QNUM=200\n[ -n \"$NFQWS\" ] || NFQWS=\"$ZAPRET_BASE/nfq/nfqws\"\nNFQWS_OPT_BASE=\"$USEROPT --dpi-desync-fwmark=$DESYNC_MARK\"\n\n[ -n \"$TPPORT\" ] || TPPORT=988\n[ -n \"$TPPORT_SOCKS\" ] || TPPORT_SOCKS=987\n[ -n \"$TPWS\" ] || TPWS=\"$ZAPRET_BASE/tpws/tpws\"\nTPWS_LOCALHOST4=127.0.0.127\n\nTPWS_OPT_BASE=\"$USEROPT\"\nTPWS_OPT_BASE4=\"--bind-addr=$TPWS_LOCALHOST4\"\nTPWS_OPT_BASE6=\"--bind-addr=::1\"\nTPWS_WAIT=\"--bind-wait-ifup=30 --bind-wait-ip=30\"\nTPWS_WAIT_SOCKS6=\"$TPWS_WAIT --bind-wait-ip-linklocal=30\"\n# first wait for lan to ifup, then wait for bind-wait-ip-linklocal seconds for link local address and bind-wait-ip for any ipv6 as the worst case\nTPWS_OPT_BASE6_PRE=\"--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3\"\n\ndnat6_target()\n{\n\t_dnat6_target \"$@\"\n}\nset_route_localnet()\n{\n\t_set_route_localnet $1 $IFACE_LAN\n}\n\nfw_nfqws_post4()\n{\n\t_fw_nfqws_post4  $1 \"$2\" $3 \"$IFACE_WAN\"\n}\nfw_nfqws_post6()\n{\n\t_fw_nfqws_post6  $1 \"$2\" $3 \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nfw_nfqws_pre4()\n{\n\t_fw_nfqws_pre4  $1 \"$2\" $3 \"$IFACE_WAN\"\n}\nfw_nfqws_pre6()\n{\n\t_fw_nfqws_pre6  $1 \"$2\" $3 \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nfw_tpws4()\n{\n\t_fw_tpws4 $1 \"$2\" $3 \"$IFACE_LAN\" \"$IFACE_WAN\"\n}\nfw_tpws6()\n{\n\t_fw_tpws6 $1 \"$2\" $3 \"$IFACE_LAN\" \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nnft_fw_tpws4()\n{\n\t_nft_fw_tpws4 \"$1\" $2 \"$IFACE_WAN\"\n}\nnft_fw_tpws6()\n{\n\t_nft_fw_tpws6 \"$1\" $2 \"$IFACE_LAN\" \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nnft_fw_nfqws_post4()\n{\n\t_nft_fw_nfqws_post4 \"$1\" $2 \"$IFACE_WAN\"\n}\nnft_fw_nfqws_post6()\n{\n\t_nft_fw_nfqws_post6 \"$1\" $2 \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nnft_fw_nfqws_pre4()\n{\n\t_nft_fw_nfqws_pre4 \"$1\" $2 \"$IFACE_WAN\"\n}\nnft_fw_nfqws_pre6()\n{\n\t_nft_fw_nfqws_pre6 \"$1\" $2 \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\nnft_fill_ifsets_overload()\n{\n\tnft_fill_ifsets \"$IFACE_LAN\" \"$IFACE_WAN\" \"${IFACE_WAN6:-$IFACE_WAN}\"\n}\n\n\nrun_daemon()\n{\n\t# $1 - daemon number : 1,2,3,...\n\t# $2 - daemon\n\t# $3 - daemon args\n\t# use $PIDDIR/$DAEMONBASE$1.pid as pidfile\n\n\tlocal DAEMONBASE=\"$(basename \"$2\")\"\n\tlocal PID= PIDFILE=$PIDDIR/$DAEMONBASE$1.pid\n\techo \"Starting daemon $1: $2 $3\"\n\n\t[ -f \"$PIDFILE\" ] && {\n\t\tread PID <\"$PIDFILE\"\n\t\t[ -d \"/proc/$PID\" ] || PID=\n\t}\n\n\tif [ -n \"$PID\" ]; then\n\t\techo already running\n\telse\n\t\t\"$2\" $3 >/dev/null &\n\t\tPID=$!\n\t\tif [ -n \"$PID\" ]; then\n\t\t\techo $PID >$PIDFILE\n\t\telse\n\t\t\techo could not start daemon $1 : $2 $3\n\t\t\tfalse\n\t\tfi\n\tfi\n}\nstop_daemon()\n{\n\t# $1 - daemon number : 1,2,3,...\n\t# $2 - daemon\n\t# use $PIDDIR/$DAEMONBASE$1.pid as pidfile\n\tlocal DAEMONBASE=\"$(basename \"$2\")\"\n\tlocal PID PIDFILE=$PIDDIR/$DAEMONBASE$1.pid\n\techo \"Stopping daemon $1: $2\"\n\tif [ -f \"$PIDFILE\" ]; then\n\t\tread PID <\"$PIDFILE\"\n\t\tkill $PID\n\t\trm -f \"$PIDFILE\"\n\telse\n\t\techo no pidfile : $PIDFILE\n\tfi\n}\ndo_daemon()\n{\n\t# $1 - 1 - run, 0 - stop\n\ton_off_function run_daemon stop_daemon \"$@\"\n}\n\n\ndo_tpws()\n{\n\t# $1 : 1 - run, 0 - stop\n\t# $2 : daemon number\n\t# $3 : daemon args\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] && [ \"$DISABLE_IPV6\" = \"1\" ] && return 0\n\n\tlocal OPT=\"$TPWS_OPT_BASE\"\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || OPT=\"$OPT $TPWS_OPT_BASE4\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || {\n\t\tOPT=\"$OPT $TPWS_OPT_BASE6\"\n\t\tfor lan in $IFACE_LAN; do\n\t\t\tOPT=\"$OPT --bind-iface6=$lan $TPWS_OPT_BASE6_PRE\"\n\t\tdone\n\t}\n\n\tdo_daemon $1 $2 \"$TPWS\" \"$OPT $3\"\n}\ndo_tpws_socks()\n{\n\t# $1 : 1 - run, 0 - stop\n\t# $2 : daemon number\n\t# $3 : daemon args\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] && [ \"$DISABLE_IPV6\" = \"1\" ] && return 0\n\n\tlocal opt=\"$TPWS_OPT_BASE --socks\"\n\n\ttpws_apply_socks_binds opt\n\n\tdo_daemon $1 $2 \"$TPWS\" \"$opt $3\"\n}\n\ndo_nfqws()\n{\n\t# $1 : 1 - run, 0 - stop\n\t# $2 : daemon number\n\t# $3 : daemon args\n\n\tdo_daemon $1 $2 \"$NFQWS\" \"$NFQWS_OPT_BASE $3\"\n}\n\ntpws_apply_socks_binds()\n{\n\tlocal o\n\n\t[ \"$DISABLE_IPV4\" = \"1\" ] || o=\"--bind-addr=127.0.0.1\"\n\t[ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-addr=::1\"\n\t\n\tfor lan in $IFACE_LAN; do\n\t    [ \"$DISABLE_IPV4\" = \"1\" ] || o=\"$o --bind-iface4=$lan $TPWS_WAIT\"\n\t    [ \"$DISABLE_IPV6\" = \"1\" ] || o=\"$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6\"\n\tdone\n\teval $1=\"\\\"\\$$1 $o\\\"\"\n}\n\n\ncreate_ipset()\n{\n\techo \"Creating ip list table (firewall type $FWTYPE)\"\n\t\"$IPSET_CR\" \"$@\"\n}\n"
  },
  {
    "path": "init.d/sysv/zapret",
    "content": "#!/bin/sh\n### BEGIN INIT INFO\n# Provides:\t\tzapret\n# Required-Start:\t$local_fs $network\n# Required-Stop:\t$local_fs $network\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n### END INIT INFO\n\nSCRIPT=$(readlink -f \"$0\")\nEXEDIR=$(dirname \"$SCRIPT\")\nZAPRET_BASE=$(readlink -f \"$EXEDIR/../..\")\n. \"$EXEDIR/functions\"\n\nNAME=zapret\nDESC=anti-zapret\n\ndo_start()\n{\n\tzapret_run_daemons\n\t[ \"$INIT_APPLY_FW\" != \"1\" ] || { zapret_apply_firewall; }\n}\ndo_stop()\n{\n\tzapret_stop_daemons\n\t[ \"$INIT_APPLY_FW\" != \"1\" ] || zapret_unapply_firewall\n}\n\ncase \"$1\" in\n\tstart)\n\t\tdo_start\n\t\t;;\n\n\tstop)\n\t\tdo_stop\n\t\t;;\n\n\trestart)\n\t\tdo_stop\n\t\tdo_start\n\t\t;;\n\n\tstart-fw|start_fw)\n\t\tzapret_apply_firewall\n\t\t;;\n\tstop-fw|stop_fw)\n\t\tzapret_unapply_firewall\n\t\t;;\n\n\trestart-fw|restart_fw)\n\t\tzapret_unapply_firewall\n\t\tzapret_apply_firewall\n\t\t;;\n\t\n\tstart-daemons|start_daemons)\n\t\tzapret_run_daemons\n\t\t;;\n\tstop-daemons|stop_daemons)\n\t\tzapret_stop_daemons\n\t\t;;\n\trestart-daemons|restart_daemons)\n\t\tzapret_stop_daemons\n\t\tzapret_run_daemons\n\t\t;;\n\n\treload-ifsets|reload_ifsets)\t\n\t\tzapret_reload_ifsets\n\t\t;;\n\tlist-ifsets|list_ifsets)\t\n\t\tzapret_list_ifsets\n\t\t;;\n\tlist-table|list_table)\t\n\t\tzapret_list_table\n\t\t;;\n\t\t\n  *)\n\techo \"Usage: $SCRIPT {start|stop|restart|start-fw|stop-fw|restart-fw|start-daemons|stop-daemons|restart-daemons|reload-ifsets|list-ifsets|list-table}\" >&2\n\texit 1\n\t;;\nesac\n\nexit 0\n"
  },
  {
    "path": "init.d/windivert.filter.examples/README.txt",
    "content": "﻿Цель этих фильтров - отсекать полезную нагрузку в режиме ядра, не насилуя процессор перенаправлением целого потока на winws.\r\nЗадействуются через `winws --wf-raw-part=@filename`. Может быть несколько частичных фильтров. Они могут сочетаться с --wf-tcp и --wf-udp.\r\nОднако, язык фильтров windivert не содержит операций с битовыми полями, сдвигов и побитовой логики.\r\nПоэтому фильтры получились более слабыми, способными передавать неправильную нагрузку.\r\nДофильтрация производится силами winws.\r\n\r\nОписание языка фильтров : https://reqrypt.org/windivert-doc.html#filter_language\r\nПример инстанса для пробития медиапотоков в discord : `winws --wf-raw-part=@windivert_part.discord_media.txt --wf-raw-part=@windivert_part.stun.txt --filter-l7=stun,discord --dpi-desync=fake`\r\n\r\n\r\nThese filters are invoked using `winws --wf-raw-part=@filename`. Multiple filter parts are supported. They can be combined with --wf-tcp and --wf-udp.\r\nFilters are kernel mode and save great amount of CPU.\r\nHowever windivert cannot filter by bit fields, lacks shift and bitwise logic operations.\r\nFilters are relaxed and can pass wrong payloads. Finer filtering is done by winws.\r\n"
  },
  {
    "path": "init.d/windivert.filter.examples/windivert_part.discord_media.txt",
    "content": "  outbound and ip and\r\n  udp.DstPort>=50000 and udp.DstPort<=50099 and\r\n  udp.PayloadLength=74 and\r\n  udp.Payload32[0]=0x00010046 and\r\n  udp.Payload32[2]=0 and\r\n  udp.Payload32[3]=0 and\r\n  udp.Payload32[4]=0 and\r\n  udp.Payload32[5]=0 and\r\n  udp.Payload32[6]=0 and\r\n  udp.Payload32[7]=0 and\r\n  udp.Payload32[8]=0 and\r\n  udp.Payload32[9]=0 and\r\n  udp.Payload32[10]=0 and\r\n  udp.Payload32[11]=0 and\r\n  udp.Payload32[12]=0 and\r\n  udp.Payload32[13]=0 and\r\n  udp.Payload32[14]=0 and\r\n  udp.Payload32[15]=0 and\r\n  udp.Payload32[16]=0 and\r\n  udp.Payload32[17]=0"
  },
  {
    "path": "init.d/windivert.filter.examples/windivert_part.quic_initial_ietf.txt",
    "content": "  outbound and\r\n  udp.PayloadLength>=256 and\r\n  udp.Payload[0]>=0xC0 and udp.Payload[0]<0xD0 and\r\n  udp.Payload[1]=0 and udp.Payload16[1]=0 and udp.Payload[4]=1\r\n"
  },
  {
    "path": "init.d/windivert.filter.examples/windivert_part.stun.txt",
    "content": "  outbound and\r\n  udp.PayloadLength>=20 and\r\n  udp.Payload32[1]=0x2112A442 and udp.Payload[0]<0x40"
  },
  {
    "path": "init.d/windivert.filter.examples/windivert_part.wireguard.txt",
    "content": "  outbound and\r\n  udp.PayloadLength=148 and\r\n  udp.Payload[0]=0x01"
  },
  {
    "path": "install_bin.sh",
    "content": "#!/bin/sh\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nBINS=binaries\nBINDIR=\"$EXEDIR/$BINS\"\n\nZAPRET_BASE=${ZAPRET_BASE:-\"$EXEDIR\"}\n. \"$ZAPRET_BASE/common/base.sh\"\n\n\nread_elf_arch()\n{\n\t# $1 - elf file\n\n\tlocal arch=$(dd if=\"$1\" count=2 bs=1 skip=18 2>/dev/null | hexdump -e '2/1 \"%02x\"')\n\tlocal bit=$(dd if=\"$1\" count=1 bs=1 skip=4 2>/dev/null | hexdump -e '1/1 \"%02x\"')\n\techo $bit$arch\n}\n\nselect_test_method()\n{\n\tlocal f ELF\n\n\tTEST=run\n\n\t# ash and dash try to execute invalid executables as a script. they interpret binary garbage with possible negative consequences\n\t# bash and zsh do not do this\n\tif exists bash; then\n\t\tTEST=bash\n\telif exists zsh && [ \"$UNAME\" != CYGWIN ] ; then\n\t\tTEST=zsh\n\telif [ \"$UNAME\" != Darwin -a \"$UNAME\" != CYGWIN ]; then\n\t\tif exists hexdump && exists dd; then\n\t\t\t# macos does not use ELF\n\t\t\tTEST=elf\n\t\t\tELF=\n\t\t\tELF_ARCH=\n\t\t\tfor f in /bin/sh /system/bin/sh; do\n\t\t\t\t[ -x \"$f\" ] && {\n\t\t\t\t\tELF=$f\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\tdone\n\t\t\t[ -n \"$ELF\" ] && ELF_ARCH=$(read_elf_arch \"$ELF\")\n\t\t\t[ -n \"$ELF_ARCH\" ] && return\n\t\tfi\n\n\t\t# find does not use its own shell exec\n\t\t# it uses execvp(). in musl libc it does not call shell, in glibc it DOES call /bin/sh\n\t\t# that's why prefer bash or zsh if present. otherwise it's our last chance\n\t\tif exists find; then\n\t\t\tTEST=find\n\t\t\tFIND=find\n\t\telif exists busybox; then\n\t\t\tbusybox find /jGHUa3fh1A 2>/dev/null\n\t\t\t# 127 - command not found\n\t\t\t[ \"$?\" = 127 ] || {\n\t\t\t\tTEST=find\n\t\t\t\tFIND=\"busybox find\"\n\t\t\t}\n\t\tfi\n\tfi\n\n}\n\ndisable_antivirus()\n{\n\t# $1 - dir\n\t[ \"$UNAME\" = Darwin ] && find \"$1\" -maxdepth 1 -type f -perm +111 -exec xattr -d com.apple.quarantine {} \\; 2>/dev/null\n}\n\ncheck_dir()\n{\n\tlocal dir=\"$BINDIR/$1\"\n\tlocal exe=\"$dir/ip2net\"\n\tlocal out\n\tif [ -f \"$exe\" ]; then\n\t\tif [ -x \"$exe\" ]; then\n\t\t\tdisable_antivirus \"$dir\"\n\t\t\tcase $TEST in\n\t\t\t\tbash)\n\t\t\t\t\tout=$(echo 0.0.0.0 | bash -c \"\\\"$exe\"\\\" 2>/dev/null)\n\t\t\t\t\t[ -n \"$out\" ]\n\t\t\t\t\t;;\n\t\t\t\tzsh)\n\t\t\t\t\tout=$(echo 0.0.0.0 | zsh -c \"\\\"$exe\\\"\" 2>/dev/null)\n\t\t\t\t\t[ -n \"$out\" ]\n\t\t\t\t\t;;\n\t\t\t\telf)\n\t\t\t\t\tout=$(read_elf_arch \"$exe\")\n\t\t\t\t\t[ \"$ELF_ARCH\" = \"$out\" ] && {\n\t\t\t\t\t\t# exec test to verify it actually works. no illegal instruction or crash.\n\t\t\t\t\t\tout=$(echo 0.0.0.0 | \"$exe\" 2>/dev/null)\n\t\t\t\t\t\t[ -n \"$out\" ]\n\t\t\t\t\t}\n\t\t\t\t\t;;\n\t\t\t\tfind)\n\t\t\t\t\tout=$(echo 0.0.0.0 | $FIND \"$dir\" -maxdepth 1 -name ip2net -exec {} \\; 2>/dev/null)\n\t\t\t\t\t[ -n \"$out\" ]\n\t\t\t\t\t;;\n\t\t\t\trun)\n\t\t\t\t\tout=$(echo 0.0.0.0 | \"$exe\" 2>/dev/null)\n\t\t\t\t\t[ -n \"$out\" ]\n\t\t\t\t\t;;\n\t\t\t\t*)\n\t\t\t\t\tfalse\n\t\t\t\t\t;;\n\t\t\tesac\n\t\telse\n\t\t\techo >&2 \"$exe is not executable. set proper chmod.\"\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo >&2 \"$exe is absent\"\n\t\treturn 2\n\tfi\n}\n\n# link or copy executables. uncomment either ln or cp, comment other\nccp()\n{\n\tlocal F=\"$(basename \"$1\")\"\n\t[ -d \"$ZAPRET_BASE/$2\" ] || mkdir \"$ZAPRET_BASE/$2\"\n\t[ -f \"$ZAPRET_BASE/$2/$F\" ] && rm -f \"$ZAPRET_BASE/$2/$F\"\n\tln -fs \"../$BINS/$1\" \"$ZAPRET_BASE/$2\" && echo linking : \"../$BINS/$1\" =\\> \"$ZAPRET_BASE/$2\"\n\t#cp -f \"../$BINS/$1\" \"$ZAPRET_BASE/$2\" && echo copying : \"../$BINS/$1\" =\\> \"$ZAPRET_BASE/$2\"\n}\n\n\nUNAME=$(uname)\n\n[ \"$1\" = getarch ] ||\nif [ ! -d \"$BINDIR\" ] || ! dir_is_not_empty \"$BINDIR\" ]; then\n\techo \"no binaries found\"\n\tcase $UNAME in\n\t\tLinux)\n\t\t\techo \"you need to download release from github or build binaries from source\"\n\t\t\techo \"building from source requires debian/ubuntu packages : make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev\"\n\t\t\techo \"libsystemd-dev required only on systemd based systems\"\n\t\t\techo \"on distributions with other package manager find dev package analogs\"\n\t\t\techo \"to compile on systems with systemd : make systemd\"\n\t\t\techo \"to compile on other systems : make\"\n\t\t\t;;\n\t\tDarwin)\n\t\t\techo \"you need to download release from github or build binaries from source\"\n\t\t\techo \"to compile : make mac\"\n\t\t\t;;\n\t\tFreeBSD)\n\t\t\techo \"you need to download release from github or build binaries from source\"\n\t\t\techo \"to compile : make\"\n\t\t\t;;\n\t\tOpenBSD)\n\t\t\techo \"to compile : make bsd\"\n\t\t\t;;\n\t\tCYGWIN*)\n\t\t\techo \"you need to download release from github or build binaries from source\"\n\t\t\techo \"to compile : read docs\"\n\t\t\techo \"to make things easier use zapret-win-bundle\"\n\t\t\t;;\n\tesac\n\texit 1\nfi\n\nunset PKTWS\ncase $UNAME in\n\tLinux)\n\t\tARCHLIST=\"my linux-x86_64 linux-x86 linux-arm64 linux-arm linux-mips64 linux-mipsel linux-mips linux-lexra linux-ppc\"\n\t\tPKTWS=nfqws\n\t\t;;\n\tDarwin)\n\t\tARCHLIST=\"my mac64\"\n\t\t;;\n\tFreeBSD)\n\t\tARCHLIST=\"my freebsd-x86_64\"\n\t\tPKTWS=dvtws\n\t\t;;\n\tCYGWIN*)\n\t\tUNAME=CYGWIN\n\t\tARCHLIST=\"windows-x86_64 windows-x86\"\n\t\tPKTWS=winws\n\t\t;;\n\t*)\n\t\tARCHLIST=\"my\"\nesac\n\nselect_test_method\n\nif [ \"$1\" = \"getarch\" ]; then\n\tfor arch in $ARCHLIST\n\tdo\n\t\t[ -d \"$BINDIR/$arch\" ] || continue\n\t\tif check_dir $arch; then\n\t \t\techo $arch\n\t \t\texit 0\n\t \tfi\n\tdone\nelse\n\techo \"using arch detect method : $TEST${ELF_ARCH:+ $ELF_ARCH}\"\n\n\tfor arch in $ARCHLIST\n\tdo\n\t\t[ -d \"$BINDIR/$arch\" ] || continue\n\t\tif check_dir $arch; then\n\t\t\techo $arch is OK\n\t\t\techo installing binaries ...\n\t\t\tccp $arch/ip2net ip2net\n\t\t\tccp $arch/mdig mdig\n\t\t\t[ -n \"$PKTWS\" ] && ccp $arch/$PKTWS nfq\n\t\t\t[ \"$UNAME\" = CYGWIN ] || ccp $arch/tpws tpws\n\t \t\texit 0\n\t\telse\n\t\t\techo $arch is NOT OK\n\t\tfi\n\tdone\n\techo no compatible binaries found\nfi\n\nexit 1\n"
  },
  {
    "path": "install_easy.sh",
    "content": "#!/bin/sh\n\n# automated script for easy installing zapret\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=${ZAPRET_BASE:-\"$EXEDIR\"}\nZAPRET_TARGET=${ZAPRET_TARGET:-/opt/zapret}\nZAPRET_TARGET_RW=${ZAPRET_RW:-\"$ZAPRET_TARGET\"}\nZAPRET_TARGET_CONFIG=\"$ZAPRET_TARGET_RW/config\"\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\nZAPRET_CONFIG_DEFAULT=\"$ZAPRET_BASE/config.default\"\nIPSET_DIR=\"$ZAPRET_BASE/ipset\"\n\n[ -f \"$ZAPRET_CONFIG\" ] || {\n\tZAPRET_CONFIG_DIR=\"$(dirname \"$ZAPRET_CONFIG\")\"\n\t[ -d \"$ZAPRET_CONFIG_DIR\" ] || mkdir -p \"$ZAPRET_CONFIG_DIR\"\n\tcp \"$ZAPRET_CONFIG_DEFAULT\" \"$ZAPRET_CONFIG\"\n}\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/elevate.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/dialog.sh\"\n. \"$ZAPRET_BASE/common/ipt.sh\"\n. \"$ZAPRET_BASE/common/installer.sh\"\n. \"$ZAPRET_BASE/common/virt.sh\"\n. \"$ZAPRET_BASE/common/list.sh\"\n\nGET_LIST=\"$IPSET_DIR/get_config.sh\"\n\ncheck_readonly_system()\n{\n\tlocal RO\n\techo \\* checking readonly system\n        case $SYSTEM in\n\t\tsystemd)\n\t\t\t[ -w \"$SYSTEMD_SYSTEM_DIR\" ] || RO=1\n\t\t\t;;\n\t\topenrc)\n\t\t\t[ -w \"$(dirname \"$INIT_SCRIPT\")\" ] || RO=1\n\t\t\t;;\n\tesac\n\t[ -z \"$RO\" ] || {\n\t\techo '!!! READONLY SYSTEM DETECTED !!!'\n\t\techo '!!! WILL NOT BE ABLE TO CONFIGURE STARTUP !!!'\n\t\techo '!!! MANUAL STARTUP CONFIGURATION IS REQUIRED !!!'\n\t\task_yes_no N \"do you want to continue\" || exitp 5\n\t}\n}\n\ncheck_source()\n{\n\tlocal bad=0\n\n\techo \\* checking source files\n\tcase $SYSTEM in\n\t\tsystemd)\n\t\t\t[ -f \"$EXEDIR/init.d/systemd/zapret.service\" ] || bad=1\n\t\t\t;;\n\t\topenrc)\n\t\t\t[ -f \"$EXEDIR/init.d/openrc/zapret\" ] || bad=1\n\t\t\t;;\n\t\tmacos)\n\t\t\t[ -f \"$EXEDIR/init.d/macos/zapret\" ] || bad=1\n\t\t\t;;\n       esac\n       [ \"$bad\" = 1 ] && {\n               echo 'some critical files are missing'\n               echo 'are you sure you are not using embedded release ? you need full version for traditional systems'\n               exitp 5\n       }\n}\n\ncheck_bins()\n{\n\techo \\* checking executables\n\n\tfix_perms_bin_test \"$EXEDIR\"\n\tlocal arch=\"$(get_bin_arch)\"\n\tlocal make_target\n\tlocal cf=\"-march=native\"\n\t[ \"$FORCE_BUILD\" = \"1\" ] && {\n\t\techo forced build mode\n\t\tif [ \"$arch\" = \"my\" ]; then\n\t\t\techo already compiled\n\t\telse\n\t\t\tarch=\"\"\n\t\tfi\n\t}\n\tif [ -n \"$arch\" ] ; then\n\t\techo found architecture \"\\\"$arch\\\"\"\n\telif [ -f \"$EXEDIR/Makefile\" ] && exists make; then\n\t\techo trying to compile\n\t\tcase $SYSTEM in\n\t\t\tmacos)\n\t\t\t\tmake_target=mac\n\t\t\t\tcf=\n\t\t\t\t;;\n\t\t\tsystemd)\n\t\t\t\tmake_target=systemd\n\t\t\t\t;;\n\t\tesac\n\t\tCFLAGS=\"${cf:+$cf }${CFLAGS}\" OPTIMIZE=-O2 make -C \"$EXEDIR\" $make_target || {\n\t\t\techo could not compile\n\t\t\tmake -C \"$EXEDIR\" clean\n\t\t\texitp 8\n\t\t}\n\t\techo compiled\n\telse\n\t\techo build tools not found\n\t\texitp 8\n\tfi\n}\n\ncall_install_bin()\n{\n\tsh \"$EXEDIR/install_bin.sh\" $1\n}\nget_bin_arch()\n{\n\tcall_install_bin getarch\n}\n\ninstall_binaries()\n{\n\techo \\* installing binaries\n\n\tcall_install_bin || {\n\t\techo compatible binaries not found\n\t\texitp 8\n\t}\n}\n\nws_opt_validate()\n{\n\t# ПРИМЕЧАНИЕ ДЛЯ РАСПРОСТРАНИТЕЛЕЙ КОПИПАСТЫ\n\t# ЭТОТ КОД СДЕЛАН СПЕЦИАЛЬНО ДЛЯ ВАС, ЧТОБЫ ВЫ НЕ ПОСТИЛИ В СЕТЬ ПЛОХИЕ РЕЦЕПТЫ\n\t# ЕСЛИ ВАМ ХОЧЕТСЯ ЕГО УДАЛИТЬ И НАПИСАТЬ ИНСТРУКЦИЮ КАК ЕГО УДАЛЯТЬ, ВЫ ДЕЛАЕТЕ ХРЕНОВУЮ УСЛУГУ. НАПИШИТЕ ЛУЧШЕ custom script.\n\t# custom script - ЭТО ФАЙЛИК, КОТОРЫЙ ДОСТАТОЧНО СКОПИРОВАТЬ В НУЖНУЮ ДИРЕКТОРИЮ, ЧТОБЫ ОН СДЕЛАЛ ТОЖЕ САМОЕ, НО ЭФФЕКТИВНО.\n\t# ФИЛЬТРАЦИЯ ПО IPSET В ЯДРЕ НЕСРАВНИМО ЭФФЕКТИВНЕЕ, ЧЕМ ПЕРЕКИДЫВАТЬ ВСЕ ПАКЕТЫ В nfqws И ТАМ ФИЛЬТРОВАТЬ\n\t# --ipset СУЩЕСТВУЕТ ТОЛЬКО ДЛЯ ВИНДЫ И LINUX СИСТЕМ БЕЗ ipset (НАПРИМЕР, Android).\n\t# И ТОЛЬКО ПО ЭТОЙ ПРИЧИНЕ ОНО НЕ ВЫКИНУТО ПОЛНОСТЬЮ ИЗ LINUX ВЕРСИИ\n\thas_bad_ws_options \"$1\" && {\n\t\thelp_bad_ws_options\n\t\treturn 1\n\t}\n\treturn 0\n}\ntpws_opt_validate()\n{\n\tws_opt_validate \"$1\" || return 1\n\tdry_run_tpws || {\n\t\techo invalid tpws options\n\t\treturn 1\n\t}\n}\ntpws_socks_opt_validate()\n{\n\t# --ipset allowed here\n\tdry_run_tpws_socks || {\n\t\techo invalid tpws options\n\t\treturn 1\n\t}\n}\nnfqws_opt_validate()\n{\n\tws_opt_validate \"$1\" || return 1\n\tdry_run_nfqws || {\n\t\techo invalid nfqws options\n\t\treturn 1\n\t}\n}\n\nselect_mode_group()\n{\n\t# $1 - ENABLE var name\n\t# $2 - ask text\n\t# $3 - vars\n\t# $4 - validator func\n\t# $5 - validator func param var\n\n\tlocal enabled var v edited bad Y param\n\n\techo\n\task_yes_no_var $1 \"$2\"\n\twrite_config_var $1\n\teval enabled=\\$$1\n\t[ \"$enabled\" = 1 ] && {\n\t\techo\n\t\twhile  : ; do\n\t\t\tlist_vars $3\n\t\t\tbad=0; Y=N\n\t\t\t[ -n \"$4\" ] && {\n\t\t\t\teval param=\"\\$$5\"\n\t\t\t\t$4 \"$param\"; bad=$?\n\t\t\t\t[ \"$bad\" = 1 ] && Y=Y\n\t\t\t}\n\t\t\task_yes_no $Y \"do you want to edit the options\" || {\n\t\t\t\t[ \"$bad\" = 1 ] && {\n\t\t\t\t\techo installer will not allow to use bad options. exiting.\n\t\t\t\t\texitp 3\n\t\t\t\t}\n\t\t\t\t[ -n \"$edited\" ] && {\n\t\t\t\t\tfor var in $3; do\n\t\t\t\t\t\twrite_config_var $var\n\t\t\t\t\tdone\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tedit_vars $3\n\t\t\tedited=1\n\t\t\techo ..edited..\n\t\tdone\n\t}\n}\n\nselect_mode_tpws_socks()\n{\n\tlocal EDITVAR_NEWLINE_DELIMETER=\"--new\" EDITVAR_NEWLINE_VARS=\"TPWS_SOCKS_OPT\"\n\tselect_mode_group TPWS_SOCKS_ENABLE \"enable tpws socks mode on port $TPPORT_SOCKS ?\" \"TPPORT_SOCKS TPWS_SOCKS_OPT\" tpws_socks_opt_validate TPWS_SOCKS_OPT\n}\nselect_mode_tpws()\n{\n\tlocal EDITVAR_NEWLINE_DELIMETER=\"--new\" EDITVAR_NEWLINE_VARS=\"TPWS_OPT\"\n\tselect_mode_group TPWS_ENABLE \"enable tpws transparent mode ?\" \"TPWS_PORTS TPWS_OPT\" tpws_opt_validate TPWS_OPT\n}\nselect_mode_nfqws()\n{\n\tlocal EDITVAR_NEWLINE_DELIMETER=\"--new\" EDITVAR_NEWLINE_VARS=\"NFQWS_OPT\"\n\tselect_mode_group NFQWS_ENABLE \"enable nfqws ?\" \"NFQWS_PORTS_TCP NFQWS_PORTS_UDP NFQWS_TCP_PKT_OUT NFQWS_TCP_PKT_IN NFQWS_UDP_PKT_OUT NFQWS_UDP_PKT_IN NFQWS_PORTS_TCP_KEEPALIVE NFQWS_PORTS_UDP_KEEPALIVE NFQWS_OPT\" nfqws_opt_validate NFQWS_OPT\n}\n\nselect_mode_mode()\n{\n\tselect_mode_tpws_socks\n\tselect_mode_tpws\n\t[ \"$UNAME\" = Linux ] && select_mode_nfqws\n\n\techo\n\techo \"current custom scripts in $CUSTOM_DIR/custom.d:\"\n\t[ -d \"$CUSTOM_DIR/custom.d\" ] && ls \"$CUSTOM_DIR/custom.d\"\n\techo \"Make sure this is ok\"\n\techo\n}\n\nselect_mode_filter()\n{\n\tlocal filter=\"none ipset hostlist autohostlist\"\n\techo\n\techo select filtering :\n\task_list MODE_FILTER \"$filter\" none && write_config_var MODE_FILTER\n}\n\nselect_mode()\n{\n\tselect_mode_filter\n\tselect_mode_mode\n\tselect_mode_iface\n}\n\nselect_getlist()\n{\n\tif [ \"$MODE_FILTER\" = \"ipset\" -o \"$MODE_FILTER\" = \"hostlist\" -o \"$MODE_FILTER\" = \"autohostlist\" ]; then\n\t\tlocal D=N\n\t\t[ -n \"$GETLIST\" ] && D=Y\n\t\techo\n\t\tif ask_yes_no $D \"do you want to auto download ip/host list\"; then\n\t\t\tif [ \"$MODE_FILTER\" = \"hostlist\" -o \"$MODE_FILTER\" = \"autohostlist\" ] ; then\n\t\t\t\tGETLISTS=\"get_refilter_domains.sh get_antizapret_domains.sh get_reestr_resolvable_domains.sh\"\n\t\t\t\tGETLIST_DEF=\"get_antizapret_domains.sh\"\n\t\t\telse\n\t\t\t\tGETLISTS=\"get_user.sh get_refilter_ipsum.sh get_antifilter_ip.sh get_antifilter_ipsmart.sh get_antifilter_ipsum.sh get_antifilter_ipresolve.sh get_antifilter_allyouneed.sh get_reestr_preresolved.sh get_reestr_preresolved_smart.sh\"\n\t\t\t\tGETLIST_DEF=\"get_antifilter_allyouneed.sh\"\n\t\t\tfi\n\t\t\task_list GETLIST \"$GETLISTS\" \"$GETLIST_DEF\" && write_config_var GETLIST\n\t\t\treturn\n\t\tfi\n\tfi\n\tGETLIST=\"\"\n\twrite_config_var GETLIST\n}\n\nask_config()\n{\n\tselect_mode\n\tselect_getlist\n}\n\nask_config_offload()\n{\n\t[ \"$FWTYPE\" = nftables ] || is_ipt_flow_offload_avail && {\n\t\techo\n\t\techo flow offloading can greatly increase speed on slow devices and high speed links \\(usually 150+ mbits\\)\n\t\tif [ \"$SYSTEM\" = openwrt ]; then\n\t\t\techo unfortuantely its not compatible with most nfqws options. nfqws traffic must be exempted from flow offloading.\n\t\t\techo donttouch = disable system flow offloading setting if nfqws mode was selected, dont touch it otherwise and dont configure selective flow offloading\n\t\t\techo none = always disable system flow offloading setting and dont configure selective flow offloading\n\t\t\techo software = always disable system flow offloading setting and configure selective software flow offloading\n\t\t\techo hardware = always disable system flow offloading setting and configure selective hardware flow offloading\n\t\telse\n\t\t\techo offloading is applicable only to forwarded traffic. it has no effect on outgoing traffic\n\t\t\techo hardware flow offloading is available only on specific supporting hardware. most likely will not work on a generic system\n\t\tfi\n\t\techo offloading breaks traffic shaper\n\t\techo select flow offloading :\n\t\tlocal options=\"none software hardware\"\n\t\tlocal default=\"none\"\n\t\t[ \"$SYSTEM\" = openwrt ] && {\n\t\t\toptions=\"donttouch none software hardware\"\n\t\t\tdefault=\"donttouch\"\n\t\t}\n\t\task_list FLOWOFFLOAD \"$options\" $default && write_config_var FLOWOFFLOAD\n\t}\n}\n\nask_config_tmpdir()\n{\n\t# ask tmpdir change for low ram systems with enough free disk space\n\t[ -n \"$GETLIST\" ] && [ $(get_free_space_mb \"$EXEDIR/tmp\") -ge 128 ] && [ $(get_ram_mb) -le 400 ] && {\n\t\techo\n\t\techo /tmp in openwrt is tmpfs. on low RAM systems there may be not enough RAM to store downloaded files\n\t\techo default tmpfs has size of 50% RAM\n\t\techo \"RAM  : $(get_ram_mb) Mb\"\n\t\techo \"DISK : $(get_free_space_mb \"$EXEDIR/tmp\") Mb\"\n\t\techo select temp file location\n\t\t[ -z \"$TMPDIR\" ] && TMPDIR=/tmp\n\t\task_list TMPDIR \"/tmp $EXEDIR/tmp\" && {\n\t\t    [ \"$TMPDIR\" = \"/tmp\" ] && TMPDIR=\n\t\t    write_config_var TMPDIR\n\t\t}\n\t}\n}\n\nnft_flow_offload()\n{\n\t[ \"$UNAME\" = Linux -a \"$FWTYPE\" = nftables ] && [ \"$FLOWOFFLOAD\" = software -o \"$FLOWOFFLOAD\" = hardware ]\n}\n\nask_iface()\n{\n\t# $1 - var to ask\n\t# $2 - additional name for empty string synonim\n\n\tlocal ifs i0 def new\n\teval def=\"\\$$1\"\n\n\t[ -n \"$2\" ] && i0=\"$2 \"\n\tcase $SYSTEM in\n\t\tmacos)\n\t\t\tifs=\"$(ifconfig -l)\"\n\t\t\t;;\n\t\t*)\n\t\t\tifs=\"$(ls /sys/class/net)\"\n\t\t\t;;\n\tesac\n\t[ -z \"$def\" ] && eval $1=\"$2\"\n\task_list $1 \"$i0$ifs\" && {\n\t\teval new=\"\\$$1\"\n\t\t[ \"$new\" = \"$2\" ] && eval $1=\"\"\n\t\twrite_config_var $1\n\t}\n}\nask_iface_lan()\n{\n\techo LAN interface :\n\tlocal opt\n\tnft_flow_offload || opt=NONE\n\task_iface IFACE_LAN $opt\n}\nask_iface_wan()\n{\n\techo WAN interface :\n\tlocal opt\n\tnft_flow_offload || opt=ANY\n\task_iface IFACE_WAN $opt\n}\n\nselect_mode_iface()\n{\n\t# openwrt has its own interface management scheme\n\t# filter just creates ip tables, no daemons involved\n\t# nfqws sits in POSTROUTING chain and unable to filter by incoming interface\n\t# tpws redirection works in PREROUTING chain\n\t# in tpws-socks mode IFACE_LAN specifies additional bind interface for the socks listener\n\t# it's not possible to instruct tpws to route outgoing connection to an interface (OS routing table decides)\n\t# custom mode can also benefit from interface names (depends on custom script code)\n\n\t[ \"$SYSTEM\" = \"openwrt\" ] && return\n\n\task_iface_lan\n\task_iface_wan\n}\n\ndefault_files()\n{\n\t# $1 - ro location\n\t# $2 - rw location (can be equal to $1)\n\t[ -d \"$2/ipset\" ] || mkdir -p \"$2/ipset\"\n\t[ -f \"$2/ipset/zapret-hosts-user-exclude.txt\" ] || cp \"$1/ipset/zapret-hosts-user-exclude.txt.default\" \"$2/ipset/zapret-hosts-user-exclude.txt\"\n\t[ -f \"$2/ipset/zapret-hosts-user.txt\" ] || echo nonexistent.domain >> \"$2/ipset/zapret-hosts-user.txt\"\n\t[ -f \"$2/ipset/zapret-hosts-user-ipban.txt\" ] || touch \"$2/ipset/zapret-hosts-user-ipban.txt\"\n\tfor dir in openwrt sysv macos; do\n\t\t[ -d \"$1/init.d/$dir\" ] && {\n\t\t\t[ -d \"$2/init.d/$dir\" ] || mkdir -p \"$2/init.d/$dir\"\n\t\t\t[ -d \"$2/init.d/$dir/custom.d\" ] || mkdir -p \"$2/init.d/$dir/custom.d\"\n\t\t}\n\tdone\n}\ncopy_all()\n{\n\tlocal dir\n\n\tcp -R \"$1\" \"$2\"\n\t[ -d \"$2/tmp\" ] || mkdir \"$2/tmp\"\n}\ncopy_openwrt()\n{\n\tlocal ARCH=\"$(get_bin_arch)\"\n\tlocal BINDIR=\"$1/binaries/$ARCH\"\n\tlocal file\n\n\t[ -d \"$2\" ] || mkdir -p \"$2\"\n\n\tmkdir \"$2/tpws\" \"$2/nfq\" \"$2/ip2net\" \"$2/mdig\" \"$2/binaries\" \"$2/binaries/$ARCH\" \"$2/init.d\" \"$2/tmp\" \"$2/files\"\n\tcp -R \"$1/files/fake\" \"$2/files\"\n\tcp -R \"$1/common\" \"$1/ipset\" \"$2\"\n\tcp -R \"$1/init.d/openwrt\" \"$1/init.d/custom.d.examples.linux\" \"$2/init.d\"\n\tcp \"$1/config\" \"$1/config.default\" \"$1/install_easy.sh\" \"$1/uninstall_easy.sh\" \"$1/install_bin.sh\" \"$1/install_prereq.sh\" \"$1/blockcheck.sh\" \"$2\"\n\tcp \"$BINDIR/tpws\" \"$BINDIR/nfqws\" \"$BINDIR/ip2net\" \"$BINDIR/mdig\" \"$2/binaries/$ARCH\"\n}\n\nfix_perms_bin_test()\n{\n\t[ -d \"$1\" ] || return\n\tfind \"$1/binaries\" -name ip2net ! -perm -111 -exec chmod +x {} \\;\n}\nfix_perms()\n{\n\t[ -d \"$1\" ] || return\n\tfind \"$1\" -type d -exec chmod 755 {} \\;\n\tfind \"$1\" -type f -exec chmod 644 {} \\;\n\tlocal chow\n\tcase \"$UNAME\" in\n\t\tLinux)\n\t\t\tchow=root:root\n\t\t\t;;\n\t\t*)\n\t\t\tchow=root:wheel\n\tesac\n\tchown -R $chow \"$1\"\n\tfind \"$1/binaries\" '(' -name tpws -o -name dvtws -o -name nfqws -o -name ip2net -o -name mdig ')' -exec chmod 755 {} \\;\n\tfor f in \\\ninstall_bin.sh \\\nblockcheck.sh \\\ninstall_easy.sh \\\ninstall_prereq.sh \\\nfiles/huawei/E8372/zapret-ip \\\nfiles/huawei/E8372/unzapret-ip \\\nfiles/huawei/E8372/run-zapret-hostlist \\\nfiles/huawei/E8372/unzapret \\\nfiles/huawei/E8372/zapret \\\nfiles/huawei/E8372/run-zapret-ip \\\nipset/get_exclude.sh \\\nipset/clear_lists.sh \\\nipset/get_refilter_domains.sh \\\nipset/get_refilter_ipsum.sh \\\nipset/get_antifilter_ipresolve.sh \\\nipset/get_reestr_resolvable_domains.sh \\\nipset/get_config.sh \\\nipset/get_reestr_preresolved.sh \\\nipset/get_user.sh \\\nipset/get_antifilter_allyouneed.sh \\\nipset/get_reestr_resolve.sh \\\nipset/create_ipset.sh \\\nipset/get_reestr_hostlist.sh \\\nipset/get_ipban.sh \\\nipset/get_antifilter_ipsum.sh \\\nipset/get_antifilter_ipsmart.sh \\\nipset/get_antizapret_domains.sh \\\nipset/get_reestr_preresolved_smart.sh \\\nipset/get_antifilter_ip.sh \\\ninit.d/pfsense/zapret.sh \\\ninit.d/macos/zapret \\\ninit.d/runit/zapret/run \\\ninit.d/runit/zapret/finish \\\ninit.d/openrc/zapret \\\ninit.d/sysv/zapret \\\ninit.d/openwrt/zapret \\\ninit.d/openwrt-minimal/tpws/etc/init.d/tpws \\\nuninstall_easy.sh \\\n\t; do chmod 755 \"$1/$f\" 2>/dev/null ; done\n}\n\n\n_backup_settings()\n{\n\tlocal i=0\n\tfor f in \"$@\"; do\n\t\t# safety check\n\t\t[ -z \"$f\" -o \"$f\" = \"/\" ] && continue\n\n\t\t[ -f \"$ZAPRET_TARGET/$f\" ] && cp -f \"$ZAPRET_TARGET/$f\" \"/tmp/zapret-bkp-$i\"\n\t\t[ -d \"$ZAPRET_TARGET/$f\" ] && cp -rf \"$ZAPRET_TARGET/$f\" \"/tmp/zapret-bkp-$i\"\n\t\ti=$(($i+1))\n\tdone\n}\n_restore_settings()\n{\n\tlocal i=0\n\tfor f in \"$@\"; do\n\t\t# safety check\n\t\t[ -z \"$f\" -o \"$f\" = \"/\" ] && continue\n\n\t\t[ -f \"/tmp/zapret-bkp-$i\" ] && {\n\t\t\tmv -f \"/tmp/zapret-bkp-$i\" \"$ZAPRET_TARGET/$f\" || rm -f \"/tmp/zapret-bkp-$i\"\n\t\t}\n\t\t[ -d \"/tmp/zapret-bkp-$i\" ] && {\n\t\t\t[ -d \"$ZAPRET_TARGET/$f\" ] && rm -r \"$ZAPRET_TARGET/$f\"\n\t\t\tmv -f \"/tmp/zapret-bkp-$i\" \"$ZAPRET_TARGET/$f\" || rm -r \"/tmp/zapret-bkp-$i\"\n\t\t}\n\t\ti=$(($i+1))\n\tdone\n}\nbackup_restore_settings()\n{\n\t# $1 - 1 - backup, 0 - restore\n\tlocal mode=$1\n\ton_off_function _backup_settings _restore_settings $mode \"config\" \"init.d/sysv/custom.d\" \"init.d/openwrt/custom.d\" \"init.d/macos/custom.d\" \"ipset/zapret-hosts-user.txt\" \"ipset/zapret-hosts-user-exclude.txt\" \"ipset/zapret-hosts-user-ipban.txt\" \"ipset/zapret-hosts-auto.txt\"\n}\n\nconfig_is_obsolete()\n{\n\t[ -f \"$1\" ] && grep -qE \"^[[:space:]]*NFQWS_OPT_DESYNC=|^[[:space:]]*MODE_HTTP=|^[[:space:]]*MODE_HTTPS=|^[[:space:]]*MODE_QUIC=|^[[:space:]]*MODE=\" \"$1\"\n}\n\ncheck_location()\n{\n\t# $1 - copy function\n\n\techo \\* checking location\n\t# use inodes in case something is linked\n\tif [ -d \"$ZAPRET_TARGET\" ] && [ $(get_dir_inode \"$EXEDIR\") = $(get_dir_inode \"$ZAPRET_TARGET\") ]; then\n\t\tconfig_is_obsolete \"$ZAPRET_CONFIG\" && {\n\t\t\techo config file $ZAPRET_CONFIG is obsolete. cannot continue.\n\t\t\texitp 3\n\t\t}\n\t\tdefault_files \"$ZAPRET_TARGET\" \"$ZAPRET_RW\"\n\telse\n\t\tlocal obsolete=0 rwdir=0\n\t\tconfig_is_obsolete \"$ZAPRET_TARGET_CONFIG\" && obsolete=1\n\t\t[ $(get_dir_inode \"$ZAPRET_BASE\") = $(get_dir_inode \"$ZAPRET_RW\") ] || rwdir=1\n\t\t[ $rwdir = 1 -a $obsolete = 1 ] && {\n                 \techo config file in custom ZAPRET_RW directory is obsolete : $ZAPRET_TARGET_CONFIG\n\t\t\techo you need to edit or delete it to continue. also check for obsolete custom scripts.\n\t\t\texitp 3\n\t\t}\n\t\techo\n\t\techo easy install is supported only from default location : $ZAPRET_TARGET\n\t\techo currently its run from $EXEDIR\n\t\tif ask_yes_no N \"do you want the installer to copy it for you\"; then\n\t\t\tlocal keep=N\n\t\t\tif [ -d \"$ZAPRET_TARGET\" ]; then\n\t\t\t\techo\n\t\t\t\techo installer found existing $ZAPRET_TARGET\n\t\t\t\techo directory needs to be replaced. config and custom scripts can be kept or replaced with clean version\n\t\t\t\tif ask_yes_no N \"do you want to delete all files there and copy this version\"; then\n\t\t\t\t\techo\n\t\t\t\t\tif [ $obsolete = 1 ] ; then\n\t\t\t\t\t\techo obsolete config is detected : $ZAPRET_TARGET_RW\n\t\t\t\t\t\task_yes_no N \"impossible to keep config, custom scripts and user lists. do you want to delete them ?\" || {\n\t\t\t\t\t\t\techo refused to delete config in $ZAPRET_TARGET. exiting\n\t\t\t\t\t\t\texitp 3\n\t\t\t\t\t\t}\n\t\t\t\t\telif [ $rwdir != 1 ]; then\n\t\t\t\t\t\task_yes_no Y \"keep config, custom scripts and user lists\" && keep=Y\n\t\t\t\t\t\t[ \"$keep\" = \"Y\" ] && backup_restore_settings 1\n\t\t\t\t\tfi\n\t\t\t\t\trm -r \"$ZAPRET_TARGET\"\n\t\t\t\telse\n\t\t\t\t\techo refused to overwrite $ZAPRET_TARGET. exiting\n\t\t\t\t\texitp 3\n\t\t\t\tfi\n\t\t\tfi\n\t\t\tlocal B=\"$(dirname \"$ZAPRET_TARGET\")\"\n\t\t\t[ -d \"$B\" ] || mkdir -p \"$B\"\n\t\t\t$1 \"$EXEDIR\" \"$ZAPRET_TARGET\"\n\t\t\tfix_perms \"$ZAPRET_TARGET\"\n\t\t\t[ \"$keep\" = \"Y\" ] && backup_restore_settings 0\n\t\t\techo relaunching itself from $ZAPRET_TARGET\n\t\t\texec \"$ZAPRET_TARGET/$(basename \"$0\")\"\n\t\telse\n\t\t\techo copying aborted. exiting\n\t\t\texitp 3\n\t\tfi\n\tfi\n\techo running from $EXEDIR\n}\n\n\nservice_install_systemd()\n{\n\techo \\* installing zapret service\n\n\tif [ -w \"$SYSTEMD_SYSTEM_DIR\" ] ; then\n\t\trm -f \"$INIT_SCRIPT\"\n\t\tcp -f \"$EXEDIR/init.d/systemd/zapret.service\" \"$SYSTEMD_SYSTEM_DIR\"\n\t\t\"$SYSTEMCTL\" daemon-reload\n\t\t\"$SYSTEMCTL\" enable zapret || {\n\t\t\techo could not enable systemd service\n\t\t\texitp 20\n\t\t}\n\telse\n\t\techo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!'\n\tfi\n}\n\ntimer_install_systemd()\n{\n\techo \\* installing zapret-list-update timer\n\n\tif [ -w \"$SYSTEMD_SYSTEM_DIR\" ] ; then\n\t\t\"$SYSTEMCTL\" disable zapret-list-update.timer\n\t\t\"$SYSTEMCTL\" stop zapret-list-update.timer\n\t\tcp -f \"$EXEDIR/init.d/systemd/zapret-list-update.service\" \"$SYSTEMD_SYSTEM_DIR\"\n\t\tcp -f \"$EXEDIR/init.d/systemd/zapret-list-update.timer\" \"$SYSTEMD_SYSTEM_DIR\"\n\t\t\"$SYSTEMCTL\" daemon-reload\n\t\t\"$SYSTEMCTL\" enable zapret-list-update.timer || {\n\t\t\techo could not enable zapret-list-update.timer\n\t\t\texitp 20\n\t\t}\n\t\t\"$SYSTEMCTL\" start zapret-list-update.timer || {\n\t\t\techo could not start zapret-list-update.timer\n\t\t\texitp 30\n\t\t}\n\telse\n\t\techo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!'\n\tfi\n}\n\ndownload_list()\n{\n\t[ -x \"$GET_LIST\" ] &&\t{\n\t\techo \\* downloading blocked ip/host list\n\n\t\t# can be txt or txt.gz\n\t\t\"$IPSET_DIR/clear_lists.sh\"\n\t\t\"$GET_LIST\"\n\t}\n}\n\n\ndnstest()\n{\n\t# $1 - dns server. empty for system resolver\n\tnslookup w3.org $1 >/dev/null 2>/dev/null\n}\ncheck_dns()\n{\n\techo \\* checking DNS\n\n\tdnstest || {\n\t\techo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access.\n\t\treturn 1\n\t}\n\techo system DNS is working\n\treturn 0\n}\n\n\ninstall_systemd()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/sysv/zapret\"\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/sysv\"\n\n\tcheck_bins\n\trequire_root\n\tcheck_readonly_system\n\tcheck_location copy_all\n\tcheck_dns\n\tcheck_virt\n\tservice_stop_systemd\n\tselect_fwtype\n\tcheck_prerequisites_linux\n\tinstall_binaries\n\tselect_ipv6\n\task_config_offload\n\task_config\n\tservice_install_systemd\n\tdownload_list\n\t# in case its left from old version of zapret\n\tcrontab_del_quiet\n\t# now we use systemd timers\n\ttimer_install_systemd\n\tservice_start_systemd\n}\n\n_install_sysv()\n{\n\t# $1 - install init script\n\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/sysv\"\n\n\tcheck_bins\n\trequire_root\n\tcheck_readonly_system\n\tcheck_location copy_all\n\tcheck_dns\n\tcheck_virt\n\tservice_stop_sysv\n\tselect_fwtype\n\tcheck_prerequisites_linux\n\tinstall_binaries\n\tselect_ipv6\n\task_config_offload\n\task_config\n\t$1\n\tdownload_list\n\tcrontab_del_quiet\n\t# desktop system. more likely up at daytime\n\tcrontab_add 10 22\n\tservice_start_sysv\n}\n\ninstall_sysv()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/sysv/zapret\"\n\t_install_sysv install_sysv_init\n}\n\ninstall_openrc()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/openrc/zapret\"\n\t_install_sysv install_openrc_init\n}\n\n\ninstall_linux()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/sysv/zapret\"\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/sysv\"\n\n\tcheck_bins\n\trequire_root\n\tcheck_location copy_all\n\tcheck_dns\n\tcheck_virt\n\tselect_fwtype\n\tcheck_prerequisites_linux\n\tinstall_binaries\n\tselect_ipv6\n\task_config_offload\n\task_config\n\tdownload_list\n\tcrontab_del_quiet\n\t# desktop system. more likely up at daytime\n\tcrontab_add 10 22\n\n\techo\n\techo '!!! WARNING. YOUR SETUP IS INCOMPLETE !!!'\n\techo you must manually add to auto start : $INIT_SCRIPT_SRC start\n\techo make sure it\\'s executed after your custom/firewall iptables configuration\n\techo \"if your system uses sysv init : ln -fs $INIT_SCRIPT_SRC /etc/init.d/zapret ; chkconfig zapret on\"\n}\n\n\ndeoffload_openwrt_firewall()\n{\n\techo \\* checking flow offloading\n\n\t[ \"$FWTYPE\" = \"nftables\" ] || is_ipt_flow_offload_avail || {\n\t\techo unavailable\n\t\treturn\n\t}\n\n\tlocal fo=$(uci -q get firewall.@defaults[0].flow_offloading)\n\n\tif [ \"$fo\" = \"1\" ] ; then\n\t\tlocal mod=0\n\t\tprintf \"system wide flow offloading detected. \"\n\t\tcase $FLOWOFFLOAD in\n\t\t\tdonttouch)\n\t\t\t\tif [ \"$NFQWS_ENABLE\" = \"1\" ]; then\n\t\t\t\t\techo its incompatible with nfqws tcp data tampering. disabling\n\t\t\t\t\tuci set firewall.@defaults[0].flow_offloading=0\n\t\t\t\t\tmod=1\n\t\t\t\telse\n\t\t\t\t\tif dir_is_not_empty \"$CUSTOM_DIR/custom.d\" ; then\n\t\t\t\t\t\techo\n\t\t\t\t\t\techo !!! CUSTOM SCRIPTS ARE PRESENT !!! only you can decide whether flow offloading is compatible.\n\t\t\t\t\t\techo !!! CUSTOM SCRIPTS ARE PRESENT !!! if they use nfqws they will not work. you have to disable system-wide offloading.\n\t\t\t\t\telse\n\t\t\t\t\t\techo its compatible with selected options. not disabling\n\t\t\t\t\tfi\n\t\t\t\tfi\n\t\t\t;;\n\t\t*)\n\t\t\techo zapret will disable system wide offloading setting and add selective rules if required\n\t\t\tuci set firewall.@defaults[0].flow_offloading=0\n\t\t\tmod=1\n\t\tesac\n\t\t[ \"$mod\" = \"1\" ] && uci commit firewall\n\telse\n\t\techo system wide software flow offloading disabled. ok\n\tfi\n}\n\n\n\ninstall_openwrt()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/openwrt/zapret\"\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/openwrt\"\n\tFW_SCRIPT_SRC=\"$EXEDIR/init.d/openwrt/firewall.zapret\"\n\tOPENWRT_FW_INCLUDE=/etc/firewall.zapret\n\tOPENWRT_IFACE_HOOK=\"$EXEDIR/init.d/openwrt/90-zapret\"\n\n\tcheck_bins\n\trequire_root\n\tcheck_location copy_openwrt\n\tinstall_binaries\n\tcheck_dns\n\tcheck_virt\n\n\tlocal FWTYPE_OLD=$FWTYPE\n\n\techo \\* stopping current firewall rules/daemons\n\t\"$INIT_SCRIPT_SRC\" stop_fw\n\t\"$INIT_SCRIPT_SRC\" stop_daemons\n\n\tselect_fwtype\n\tselect_ipv6\n\tcheck_prerequisites_openwrt\n\task_config\n\task_config_tmpdir\n\task_config_offload\n\t# stop and reinstall sysv init\n\tinstall_sysv_init\n\t[ \"$FWTYPE_OLD\" != \"$FWTYPE\" -a \"$FWTYPE_OLD\" = iptables -a -n \"$OPENWRT_FW3\" ] && remove_openwrt_firewall\n\t# free some RAM\n\tclear_ipset\n\tdownload_list\n\tcrontab_del_quiet\n\t# router system : works 24/7. night is the best time\n\tcrontab_add 0 6\n\tcron_ensure_running\n\tinstall_openwrt_iface_hook\n\t# in case of nftables or iptables without fw3 sysv init script also controls firewall\n\t[ -n \"$OPENWRT_FW3\" -a \"$FWTYPE\" = iptables ] && install_openwrt_firewall\n\tservice_start_sysv\n\tdeoffload_openwrt_firewall\n\trestart_openwrt_firewall\n}\n\n\n\nremove_pf_zapret_hooks()\n{\n\techo \\* removing zapret PF hooks\n\n\tpf_anchors_clear\n}\n\nmacos_fw_reload_trigger_clear()\n{\n\tLISTS_RELOAD=\n\twrite_config_var LISTS_RELOAD\n}\nmacos_fw_reload_trigger_set()\n{\n\tLISTS_RELOAD=\"$INIT_SCRIPT_SRC reload-fw-tables\"\n\twrite_config_var LISTS_RELOAD\n}\n\ninstall_macos()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/macos/zapret\"\n\tCUSTOM_DIR=\"$ZAPRET_RW/init.d/macos\"\n\n\t# compile before root\n\tcheck_bins\n\trequire_root\n\tcheck_location copy_all\n\tservice_stop_macos\n\tremove_pf_zapret_hooks\n\tinstall_binaries\n\tcheck_dns\n\tselect_ipv6\n\task_config\n\tservice_install_macos\n\tmacos_fw_reload_trigger_clear\n\t# gzip lists are incompatible with PF\n\tGZIP_LISTS=0 write_config_var GZIP_LISTS\n\tdownload_list\n\tmacos_fw_reload_trigger_set\n\tcrontab_del_quiet\n\t# desktop system. more likely up at daytime\n\tcrontab_add 10 22\n\tservice_start_macos\n}\n\n\n# build binaries, do not use precompiled\n[ \"$1\" = \"make\" ] && FORCE_BUILD=1\n\numask 0022\nfix_sbin_path\nfsleep_setup\ncheck_system\ncheck_source\n\ncase $SYSTEM in\n\tsystemd)\n\t\tinstall_systemd\n\t\t;;\n\topenrc)\n\t\tinstall_openrc\n\t\t;;\n\tlinux)\n\t\tinstall_linux\n\t\t;;\n\topenwrt)\n\t\tinstall_openwrt\n\t\t;;\n\tmacos)\n\t\tinstall_macos\n\t\t;;\nesac\n\n\nexitp 0\n"
  },
  {
    "path": "install_prereq.sh",
    "content": "#!/bin/sh\n\n# install prerequisites\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=${ZAPRET_BASE:-\"$EXEDIR\"}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\nZAPRET_CONFIG_DEFAULT=\"$ZAPRET_BASE/config.default\"\n\n[ -f \"$ZAPRET_CONFIG\" ] || {\n\tZAPRET_CONFIG_DIR=\"$(dirname \"$ZAPRET_CONFIG\")\"\n\t[ -d \"$ZAPRET_CONFIG_DIR\" ] || mkdir -p \"$ZAPRET_CONFIG_DIR\"\n\tcp \"$ZAPRET_CONFIG_DEFAULT\" \"$ZAPRET_CONFIG\"\n}\n\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/elevate.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/dialog.sh\"\n. \"$ZAPRET_BASE/common/installer.sh\"\n. \"$ZAPRET_BASE/common/ipt.sh\"\n\numask 0022\nfix_sbin_path\nfsleep_setup\ncheck_system accept_unknown_rc\n[ $UNAME = \"Linux\" ] || {\n\techo no prerequisites required for $UNAME\n\texitp 0\n}\nrequire_root\n\ncase $UNAME in\n\tLinux)\n\t\tselect_fwtype\n\t\tcase $SYSTEM in\n\t\t\topenwrt)\n\t\t\t\tselect_ipv6\n\t\t\t\tcheck_prerequisites_openwrt\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\tcheck_prerequisites_linux\n\t\t\t\t;;\n\t\tesac\n\t\t;;\nesac\n\nexitp 0\n"
  },
  {
    "path": "ip2net/Makefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto\nCFLAGS_BSD = -Wno-address-of-packed-member\nCFLAGS_WIN = -static\nLIBS = \nLIBS_WIN = -lws2_32\nSRC_FILES = ip2net.c qsort.c\n\nall: ip2net\n\nip2net: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS)\n\nsystemd: ip2net\n\nandroid: ip2net\n\nbsd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS)\n\nmac: $(SRC_FILES)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2neta $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS) $(LDFLAGS)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2netx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS) $(LDFLAGS)\n\tstrip ip2neta ip2netx\n\tlipo -create -output ip2net ip2netx ip2neta\n\trm -f ip2netx ip2neta\n\nwin: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o ip2net $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS)\n\nclean:\n\trm -f ip2net *.o\n"
  },
  {
    "path": "ip2net/ip2net.c",
    "content": "// group ipv4/ipv6 list from stdout into subnets\n// each line must contain either ip or ip/bitcount\n// valid ip/bitcount and ip1-ip2 are passed through without modification\n// ips are groupped into subnets\n\n// can be compiled in mingw. msvc not supported because of absent getopt\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#ifdef _WIN32\n#undef _WIN32_WINNT\n#define _WIN32_WINNT 0x600\n#include <winsock2.h>\n#include <ws2ipdef.h>\n#include <ws2tcpip.h>\n#else\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#endif\n#include <getopt.h>\n#include \"qsort.h\"\n\n#define ALLOC_STEP 16384\n\n// minimum subnet fill percent is  PCTMULT/PCTDIV  (for example 3/4)\n#define DEFAULT_PCTMULT\t3\n#define DEFAULT_PCTDIV\t4\n// subnet search range in \"zero bit count\"\n// means search start from /(32-ZCT_MAX) to /(32-ZCT_MIN)\n#define DEFAULT_V4_ZCT_MAX 10 //   /22\n#define DEFAULT_V4_ZCT_MIN 2  //   /30\n#define DEFAULT_V6_ZCT_MAX 72 //   /56\n#define DEFAULT_V6_ZCT_MIN 64 //   /64\n// must be no less than N ipv6 in subnet\n#define DEFAULT_V6_THRESHOLD\t5\n\nstatic int ucmp(const void * a, const void * b, void *arg)\n{\n\tif (*(uint32_t*)a < *(uint32_t*)b)\n\t\treturn -1;\n\telse if (*(uint32_t*)a > *(uint32_t*)b)\n\t\treturn 1;\n\telse\n\t\treturn 0;\n}\nstatic uint32_t mask_from_bitcount(uint32_t zct)\n{\n\treturn zct<32 ? ~((1u << zct) - 1) : 0;\n}\n// make presorted array unique. return number of unique items.\n// 1,1,2,3,3,0,0,0 (ct=8) => 1,2,3,0 (ct=4)\nstatic uint32_t unique(uint32_t *pu, uint32_t ct)\n{\n\tuint32_t i, j, u;\n\tfor (i = j = 0; j < ct; i++)\n\t{\n\t\tu = pu[j++];\n\t\tfor (; j < ct && pu[j] == u; j++);\n\t\tpu[i] = u;\n\t}\n\treturn i;\n}\n\n\n\n#if defined(__GNUC__) && !defined(__llvm__)\n__attribute__((optimize (\"no-strict-aliasing\")))\n#endif\nstatic int cmp6(const void * a, const void * b, void *arg)\n{\n\t// this function is critical for sort performance\n\t// on big endian systems cpu byte order is equal to network byte order\n\t// no conversion required. it's possible to improve speed by using big size compares\n\t// on little endian systems byte conversion also gives better result than byte comparision\n\t// 64-bit archs often have cpu command to reverse byte order\n\t// assume that a and b are properly aligned\n\n#if defined(__BYTE_ORDER__) && ((__BYTE_ORDER__==__ORDER_BIG_ENDIAN__) || (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__))\n\n\tuint64_t aa,bb;\n#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__\n\taa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[0]);\n\tbb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[0]);\n#else\n\taa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[0];\n\tbb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[0];\n#endif\n\tif (aa < bb)\n\t\treturn -1;\n\telse if (aa > bb)\n\t\treturn 1;\n\telse\n\t{\n#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__\n\t\taa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[1]);\n\t\tbb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[1]);\n#else\n\t\taa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[1];\n\t\tbb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[1];\n#endif\n\t\treturn aa < bb ? -1 : aa > bb ? 1 : 0;\n\t}\n\t\n#else\n\t// fallback case\n\tfor (uint8_t i = 0; i < sizeof(((struct in6_addr *)0)->s6_addr); i++)\n\t{\n\t\tif (((struct in6_addr *)a)->s6_addr[i] < ((struct in6_addr *)b)->s6_addr[i])\n\t\t\treturn -1;\n\t\telse if (((struct in6_addr *)a)->s6_addr[i] > ((struct in6_addr *)b)->s6_addr[i])\n\t\t\treturn 1;\n\t}\n\treturn 0;\n#endif\n}\n\n// make presorted array unique. return number of unique items.\nstatic uint32_t unique6(struct in6_addr *pu, uint32_t ct)\n{\n\tuint32_t i, j, k;\n\tfor (i = j = 0; j < ct; i++)\n\t{\n\t\tfor (k = j++; j < ct && !memcmp(pu + j, pu + k, sizeof(struct in6_addr)); j++);\n\t\tpu[i] = pu[k];\n\t}\n\treturn i;\n}\nstatic void mask_from_bitcount6_make(uint32_t zct, struct in6_addr *a)\n{\n\tif (zct >= 128)\n\t\tmemset(a->s6_addr,0x00,16);\n\telse\n\t{\n\t\tint32_t n = (127 - zct) >> 3;\n\t\tmemset(a->s6_addr,0xFF,n);\n\t\tmemset(a->s6_addr+n,0x00,16-n);\n\t\ta->s6_addr[n] = ~((1u << (zct & 7)) - 1);\n\t}\n}\nstatic struct in6_addr ip6_mask[129];\nstatic void mask_from_bitcount6_prepare(void)\n{\n\tfor (int zct=0;zct<=128;zct++) mask_from_bitcount6_make(zct, ip6_mask+zct);\n}\nstatic inline const struct in6_addr *mask_from_bitcount6(uint32_t zct)\n{\n\treturn ip6_mask+zct;\n}\n\n\n/*\n// this is \"correct\" solution for strict aliasing feature\n// but I don't like this style of coding\n// write what I don't mean to force smart optimizer to do what it's best\n// it produces better code sometimes but not on all compilers/versions/archs\n// sometimes it even generates real memcpy calls (mips32,arm32)\n// so I will not do it\n\nstatic void ip6_and(const struct in6_addr *a, const struct in6_addr *b, struct in6_addr *result)\n{\n\tuint64_t a_addr[2], b_addr[2];\n\tmemcpy(a_addr, a->s6_addr, 16);\n\tmemcpy(b_addr, b->s6_addr, 16);\n\ta_addr[0] &= b_addr[0];\n\ta_addr[1] &= b_addr[1];\n\tmemcpy(result->s6_addr, a_addr, 16);\n}\n*/\n\n// YES, from my point of view C should work as a portable assembler. It must do what I instruct it to do.\n// that's why I disable strict aliasing for this function. I observed gcc can miscompile with O2/O3 setting if inlined and not coded \"correct\"\n// result = a & b\n// assume that a and b are properly aligned\n#if defined(__GNUC__) && !defined(__llvm__)\n__attribute__((optimize (\"no-strict-aliasing\")))\n#endif\nstatic void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result)\n{\n#ifdef __SIZEOF_INT128__\n\t// gcc and clang have 128 bit int types on some 64-bit archs. take some advantage\n\t*((unsigned __int128*)result->s6_addr) = *((unsigned __int128*)a->s6_addr) & *((unsigned __int128*)b->s6_addr);\n#else\n\t((uint64_t*)result->s6_addr)[0] = ((uint64_t*)a->s6_addr)[0] & ((uint64_t*)b->s6_addr)[0];\n\t((uint64_t*)result->s6_addr)[1] = ((uint64_t*)a->s6_addr)[1] & ((uint64_t*)b->s6_addr)[1];\n#endif\n}\n\nstatic void rtrim(char *s)\n{\n\tif (s)\n\t\tfor (char *p = s + strlen(s) - 1; p >= s && (*p == '\\n' || *p == '\\r' || *p == ' ' || *p == '\\t'); p--) *p = '\\0';\n}\n\n\nstatic struct params_s\n{\n\tbool ipv6;\n\tuint32_t pctmult, pctdiv; // for v4\n\tuint32_t zct_min, zct_max; // for v4 and v6\n\tuint32_t v6_threshold; // for v6\n} params;\n\n\nstatic void exithelp(void)\n{\n\tprintf(\n\t\t\" -4\\t\\t\\t\\t; ipv4 list (default)\\n\"\n\t\t\" -6\\t\\t\\t\\t; ipv6 list\\n\"\n\t\t\" --prefix-length=min[-max]\\t; consider prefix lengths from 'min' to 'max'. examples : 22-30 (ipv4), 56-64 (ipv6)\\n\"\n\t\t\" --v4-threshold=mul/div\\t\\t; ipv4 only : include subnets with more than mul/div ips. example : 3/4\\n\"\n\t\t\" --v6-threshold=N\\t\\t; ipv6 only : include subnets with more than N v6 ips. example : 5\\n\"\n\t);\n\texit(1);\n}\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH)\n#define PRINT_VER printf(\"github version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#else\n#define PRINT_VER printf(\"self-built version %s %s\\n\\n\", __DATE__, __TIME__)\n#endif\n\nenum opt_indices {\n\tIDX_HELP,\n\tIDX_H,\n\tIDX_4,\n\tIDX_6,\n\tIDX_PREFIX_LENGTH,\n\tIDX_V4_THRESHOLD,\n\tIDX_V6_THRESHOLD,\n\tIDX_LAST,\n};\n\nstatic const struct option long_options[] = {\n\t[IDX_HELP] = {\"help\", no_argument, 0, 0},\n\t[IDX_H] = {\"h\", no_argument, 0, 0},\n\t[IDX_4] = {\"4\", no_argument, 0, 0},\n\t[IDX_6] = {\"6\", no_argument, 0, 0},\n\t[IDX_PREFIX_LENGTH] = {\"prefix-length\", required_argument, 0, 0},\n\t[IDX_V4_THRESHOLD] = {\"v4-threshold\", required_argument, 0, 0},\n\t[IDX_V6_THRESHOLD] = {\"v6-threshold\", required_argument, 0, 0},\n\t[IDX_LAST] = {NULL, 0, NULL, 0},\n};\n\nstatic void parse_params(int argc, char *argv[])\n{\n\tint option_index = 0;\n\tint v, i;\n\tuint32_t plen1=-1, plen2=-1;\n\n\tmemset(&params, 0, sizeof(params));\n\tparams.pctmult = DEFAULT_PCTMULT;\n\tparams.pctdiv = DEFAULT_PCTDIV;\n\tparams.v6_threshold = DEFAULT_V6_THRESHOLD;\n\n\twhile ((v = getopt_long_only(argc, argv, \"\", long_options, &option_index)) != -1)\n\t{\n\t\tif (v) exithelp();\n\t\tswitch (option_index)\n\t\t{\n\t\tcase IDX_HELP:\n\t\tcase IDX_H:\n\t\t\tPRINT_VER;\n\t\t\texithelp();\n\t\t\tbreak;\n\t\tcase IDX_4:\n\t\t\tparams.ipv6 = false;\n\t\t\tbreak;\n\t\tcase IDX_6:\n\t\t\tparams.ipv6 = true;\n\t\t\tbreak;\n\t\tcase IDX_PREFIX_LENGTH:\n\t\t\ti = sscanf(optarg,\"%u-%u\",&plen1,&plen2);\n\t\t\tif (i == 1) plen2 = plen1;\n\t\t\tif (i<=0 || plen2<plen1 || !plen1 || !plen2)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"invalid parameter for prefix-length : %s\\n\", optarg);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_V4_THRESHOLD:\n\t\t\ti = sscanf(optarg, \"%u/%u\", &params.pctmult, &params.pctdiv);\n\t\t\tif (i!=2 || params.pctdiv<2 || params.pctmult<1 || params.pctmult>=params.pctdiv)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"invalid parameter for v4-threshold : %s\\n\", optarg);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_V6_THRESHOLD:\n\t\t\ti = sscanf(optarg, \"%u\", &params.v6_threshold);\n\t\t\tif (i != 1 || params.v6_threshold<1)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"invalid parameter for v6-threshold : %s\\n\", optarg);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (plen1 != -1 && ((!params.ipv6 && (plen1>31 || plen2>31)) || (params.ipv6 && (plen1>127 || plen2>127))))\n\t{\n\t\tfprintf(stderr, \"invalid parameter for prefix-length\\n\");\n\t\texit(1);\n\t}\n\tparams.zct_min = params.ipv6 ? plen2==-1 ? DEFAULT_V6_ZCT_MIN : 128-plen2 : plen2==-1 ? DEFAULT_V4_ZCT_MIN : 32-plen2;\n\tparams.zct_max = params.ipv6 ? plen1==-1 ? DEFAULT_V6_ZCT_MAX : 128-plen1 : plen1==-1 ? DEFAULT_V4_ZCT_MAX : 32-plen1;\n}\n\n\nint main(int argc, char **argv)\n{\n\tchar str[256],d;\n\tuint32_t ipct = 0, iplist_size = 0, pos = 0, p, zct, ip_ct, pos_end;\n\n\tparse_params(argc, argv);\n\n\tif (params.ipv6) // ipv6\n\t{\n\t\tchar *s;\n\t\tstruct in6_addr a, *iplist = NULL, *iplist_new;\n\n\t\twhile (fgets(str, sizeof(str), stdin))\n\t\t{\n\t\t\trtrim(str);\n\t\t\td = 0;\n\t\t\tif ((s = strchr(str, '/')) || (s = strchr(str, '-')))\n\t\t\t{\n\t\t\t\td = *s;\n\t\t\t\t*s = '\\0';\n\t\t\t}\n\t\t\tif (inet_pton(AF_INET6, str, &a))\n\t\t\t{\n\t\t\t\tif (d=='/')\n\t\t\t\t{\n\t\t\t\t\t// we have subnet ip6/y\n\t\t\t\t\t// output it as is\n\t\t\t\t\tif (sscanf(s + 1, \"%u\", &zct)==1 && zct!=128)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (zct<128) printf(\"%s/%u\\n\", str, zct);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (d=='-')\n\t\t\t\t{\n\t\t\t\t\tif (inet_pton(AF_INET6, s+1, &a)) printf(\"%s-%s\\n\", str, s+1);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (ipct >= iplist_size)\n\t\t\t\t{\n\t\t\t\t\tiplist_size += ALLOC_STEP;\n\t\t\t\t\tiplist_new = (struct in6_addr*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size));\n\t\t\t\t\tif (!iplist_new)\n\t\t\t\t\t{\n\t\t\t\t\t\tfree(iplist);\n\t\t\t\t\t\tfprintf(stderr, \"out of memory\\n\");\n\t\t\t\t\t\treturn 100;\n\t\t\t\t\t}\n\t\t\t\t\tiplist = iplist_new;\n\t\t\t\t}\n\t\t\t\tiplist[ipct++] = a;\n\t\t\t}\n\t\t}\n\t\tgnu_quicksort(iplist, ipct, sizeof(*iplist), cmp6, NULL);\n\t\tipct = unique6(iplist, ipct);\n\t\tmask_from_bitcount6_prepare();\n\n\t\t/*\n\t\tfor(uint32_t i=0;i<ipct;i++)\n\t\t if (inet_ntop(AF_INET6,iplist+i,str,sizeof(str)))\n\t\t  printf(\"%s\\n\",str);\n\t\tprintf(\"\\n\");\n\t\t*/\n\t\twhile (pos < ipct)\n\t\t{\n\t\t\tconst struct in6_addr *mask;\n\t\t\tstruct in6_addr ip_start, ip;\n\t\t\tuint32_t ip_ct_best = 0, zct_best = 0;\n\n\t\t\tpos_end = pos + 1;\n\t\t\t// find smallest network with maximum ip coverage with no less than ip6_subnet_threshold addresses\n\t\t\tfor (zct = params.zct_max; zct >= params.zct_min; zct--)\n\t\t\t{\n\t\t\t\tmask = mask_from_bitcount6(zct);\n\t\t\t\tip6_and(iplist + pos, mask, &ip_start);\n\t\t\t\tfor (p = pos + 1, ip_ct = 1; p < ipct; p++, ip_ct++)\n\t\t\t\t{\n\t\t\t\t\tip6_and(iplist + p, mask, &ip);\n\t\t\t\t\tif (memcmp(&ip_start, &ip, sizeof(ip)))\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (ip_ct == 1) break;\n\t\t\t\tif (ip_ct >= params.v6_threshold)\n\t\t\t\t{\n\t\t\t\t\t// network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets\n\t\t\t\t\tif (!ip_ct_best || ip_ct == ip_ct_best)\n\t\t\t\t\t{\n\t\t\t\t\t\tip_ct_best = ip_ct;\n\t\t\t\t\t\tzct_best = zct;\n\t\t\t\t\t\tpos_end = p;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (zct_best)\n\t\t\t\t// network was found\n\t\t\t\tip6_and(iplist + pos, mask_from_bitcount6(zct_best), &ip_start);\n\t\t\telse\n\t\t\t\tip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip\n\t\t\tinet_ntop(AF_INET6, &ip_start, str, sizeof(str));\n\t\t\tprintf(zct_best ? \"%s/%u\\n\" : \"%s\\n\", str, 128 - zct_best);\n\n\t\t\tpos = pos_end;\n\t\t}\n\n\t\tfree(iplist);\n\t}\n\telse // ipv4\n\t{\n\t\tuint32_t u1,u2,u3,u4, u11,u22,u33,u44, ip;\n\t\tuint32_t *iplist = NULL, *iplist_new, i;\n\n\t\twhile (fgets(str, sizeof(str), stdin))\n\t\t{\n\t\t\tif ((i = sscanf(str, \"%u.%u.%u.%u-%u.%u.%u.%u\", &u1, &u2, &u3, &u4, &u11, &u22, &u33, &u44)) >= 8 && \n\t\t\t\t!(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00) &&\n\t\t\t\t!(u11 & 0xFFFFFF00) && !(u22 & 0xFFFFFF00) && !(u33 & 0xFFFFFF00) && !(u44 & 0xFFFFFF00))\n\t\t\t{\n\t\t\t\tprintf(\"%u.%u.%u.%u-%u.%u.%u.%u\\n\", u1, u2, u3, u4, u11, u22, u33, u44);\n\t\t\t}\n\t\t\telse\n\t\t\tif ((i = sscanf(str, \"%u.%u.%u.%u/%u\", &u1, &u2, &u3, &u4, &zct)) >= 4 &&\n\t\t\t\t!(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00))\n\t\t\t{\n\t\t\t\tif (i == 5 && zct != 32)\n\t\t\t\t{\n\t\t\t\t\t// we have subnet x.x.x.x/y\n\t\t\t\t\t// output it as is if valid, ignore otherwise\n\t\t\t\t\tif (zct < 32)\n\t\t\t\t\t\tprintf(\"%u.%u.%u.%u/%u\\n\", u1, u2, u3, u4, zct);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tip = u1 << 24 | u2 << 16 | u3 << 8 | u4;\n\t\t\t\t\tif (ipct >= iplist_size)\n\t\t\t\t\t{\n\t\t\t\t\t\tiplist_size += ALLOC_STEP;\n\t\t\t\t\t\tiplist_new = (uint32_t*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size));\n\t\t\t\t\t\tif (!iplist_new)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfree(iplist);\n\t\t\t\t\t\t\tfprintf(stderr, \"out of memory\\n\");\n\t\t\t\t\t\t\treturn 100;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tiplist = iplist_new;\n\t\t\t\t\t}\n\t\t\t\t\tiplist[ipct++] = ip;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tgnu_quicksort(iplist, ipct, sizeof(*iplist), ucmp, NULL);\n\t\tipct = unique(iplist, ipct);\n\n\t\twhile (pos < ipct)\n\t\t{\n\t\t\tuint32_t mask, ip_start, ip_end, subnet_ct;\n\t\t\tuint32_t ip_ct_best = 0, zct_best = 0;\n\n\t\t\t// find smallest network with maximum ip coverage with no less than mul/div percent addresses\n\t\t\tfor (zct = params.zct_max; zct >= params.zct_min; zct--)\n\t\t\t{\n\t\t\t\tmask = mask_from_bitcount(zct);\n\t\t\t\tip_start = iplist[pos] & mask;\n\t\t\t\tsubnet_ct = ~mask + 1;\n\t\t\t\tif (iplist[pos] > (ip_start + subnet_ct*(params.pctdiv - params.pctmult) / params.pctdiv))\n\t\t\t\t\tcontinue; // ip is higher than (1-PCT). definitely coverage is not enough. skip searching\n\t\t\t\tip_end = ip_start | ~mask;\n\t\t\t\tfor (p=pos+1, ip_ct=1; p < ipct && iplist[p] <= ip_end; p++) ip_ct++; // count ips within subnet range\n\t\t\t\tif (ip_ct == 1) break;\n\t\t\t\tif (ip_ct >= (subnet_ct*params.pctmult / params.pctdiv))\n\t\t\t\t{\n\t\t\t\t\t// network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets\n\t\t\t\t\tif (!ip_ct_best || ip_ct == ip_ct_best)\n\t\t\t\t\t{\n\t\t\t\t\t\tip_ct_best = ip_ct;\n\t\t\t\t\t\tzct_best = zct;\n\t\t\t\t\t\tpos_end = p;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (zct_best)\n\t\t\t\tip_start = iplist[pos] & mask_from_bitcount(zct_best);\n\t\t\telse\n\t\t\t\tip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip\n\n\t\t\tu1 = ip_start >> 24;\n\t\t\tu2 = (ip_start >> 16) & 0xFF;\n\t\t\tu3 = (ip_start >> 8) & 0xFF;\n\t\t\tu4 = ip_start & 0xFF;\n\t\t\tprintf(zct_best ? \"%u.%u.%u.%u/%u\\n\" : \"%u.%u.%u.%u\\n\", u1, u2, u3, u4, 32 - zct_best);\n\n\t\t\tpos = pos_end;\n\t\t}\n\n\t\tfree(iplist);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "ip2net/qsort.c",
    "content": "/* Copyright (C) 1991-2018 Free Software Foundation, Inc.\n   This file is part of the GNU C Library.\n   Written by Douglas C. Schmidt (schmidt@ics.uci.edu).\n\n   The GNU C Library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Lesser General Public\n   License as published by the Free Software Foundation; either\n   version 2.1 of the License, or (at your option) any later version.\n\n   The GNU C Library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Lesser General Public License for more details.\n\n   You should have received a copy of the GNU Lesser General Public\n   License along with the GNU C Library; if not, see\n   <http://www.gnu.org/licenses/>.  */\n\n/* If you consider tuning this algorithm, you should consult first:\n   Engineering a sort function; Jon Bentley and M. Douglas McIlroy;\n   Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.  */\n\n//#include <alloca.h>\n#include <limits.h>\n#include <stdlib.h>\n//#include <string.h>\n#include \"qsort.h\"\n\n/* Byte-wise swap two items of size SIZE. */\n#define SWAP(a, b, size)\t\t\t\t\t\t      \\\n  do\t\t\t\t\t\t\t\t\t      \\\n    {\t\t\t\t\t\t\t\t\t      \\\n      size_t __size = (size);\t\t\t\t\t\t      \\\n      char *__a = (a), *__b = (b);\t\t\t\t\t      \\\n      do\t\t\t\t\t\t\t\t      \\\n\t{\t\t\t\t\t\t\t\t      \\\n\t  char __tmp = *__a;\t\t\t\t\t\t      \\\n\t  *__a++ = *__b;\t\t\t\t\t\t      \\\n\t  *__b++ = __tmp;\t\t\t\t\t\t      \\\n\t} while (--__size > 0);\t\t\t\t\t\t      \\\n    } while (0)\n\n/* Discontinue quicksort algorithm when partition gets below this size.\n   This particular magic number was chosen to work best on a Sun 4/260. */\n#define MAX_THRESH 4\n\n/* Stack node declarations used to store unfulfilled partition obligations. */\ntypedef struct\n  {\n    char *lo;\n    char *hi;\n  } stack_node;\n\n/* The next 4 #defines implement a very fast in-line stack abstraction. */\n/* The stack needs log (total_elements) entries (we could even subtract\n   log(MAX_THRESH)).  Since total_elements has type size_t, we get as\n   upper bound for log (total_elements):\n   bits per byte (CHAR_BIT) * sizeof(size_t).  */\n#define STACK_SIZE\t(CHAR_BIT * sizeof(size_t))\n#define PUSH(low, high)\t((void) ((top->lo = (low)), (top->hi = (high)), ++top))\n#define\tPOP(low, high)\t((void) (--top, (low = top->lo), (high = top->hi)))\n#define\tSTACK_NOT_EMPTY\t(stack < top)\n\n\n/* Order size using quicksort.  This implementation incorporates\n   four optimizations discussed in Sedgewick:\n\n   1. Non-recursive, using an explicit stack of pointer that store the\n      next array partition to sort.  To save time, this maximum amount\n      of space required to store an array of SIZE_MAX is allocated on the\n      stack.  Assuming a 32-bit (64 bit) integer for size_t, this needs\n      only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).\n      Pretty cheap, actually.\n\n   2. Chose the pivot element using a median-of-three decision tree.\n      This reduces the probability of selecting a bad pivot value and\n      eliminates certain extraneous comparisons.\n\n   3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving\n      insertion sort to order the MAX_THRESH items within each partition.\n      This is a big win, since insertion sort is faster for small, mostly\n      sorted array segments.\n\n   4. The larger of the two sub-partitions is always pushed onto the\n      stack first, with the algorithm then concentrating on the\n      smaller partition.  This *guarantees* no more than log (total_elems)\n      stack size is needed (actually O(1) in this case)!  */\n\nvoid\ngnu_quicksort (void *const pbase, size_t total_elems, size_t size,\n\t    __gnu_compar_d_fn_t cmp, void *arg)\n{\n  char *base_ptr = (char *) pbase;\n\n  const size_t max_thresh = MAX_THRESH * size;\n\n  if (total_elems == 0)\n    /* Avoid lossage with unsigned arithmetic below.  */\n    return;\n\n  if (total_elems > MAX_THRESH)\n    {\n      char *lo = base_ptr;\n      char *hi = &lo[size * (total_elems - 1)];\n      stack_node stack[STACK_SIZE];\n      stack_node *top = stack;\n\n      PUSH (NULL, NULL);\n\n      while (STACK_NOT_EMPTY)\n        {\n          char *left_ptr;\n          char *right_ptr;\n\n\t  /* Select median value from among LO, MID, and HI. Rearrange\n\t     LO and HI so the three values are sorted. This lowers the\n\t     probability of picking a pathological pivot value and\n\t     skips a comparison for both the LEFT_PTR and RIGHT_PTR in\n\t     the while loops. */\n\n\t  char *mid = lo + size * ((hi - lo) / size >> 1);\n\n\t  if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)\n\t    SWAP (mid, lo, size);\n\t  if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)\n\t    SWAP (mid, hi, size);\n\t  else\n\t    goto jump_over;\n\t  if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)\n\t    SWAP (mid, lo, size);\n\tjump_over:;\n\n\t  left_ptr  = lo + size;\n\t  right_ptr = hi - size;\n\n\t  /* Here's the famous ``collapse the walls'' section of quicksort.\n\t     Gotta like those tight inner loops!  They are the main reason\n\t     that this algorithm runs much faster than others. */\n\t  do\n\t    {\n\t      while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)\n\t\tleft_ptr += size;\n\n\t      while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)\n\t\tright_ptr -= size;\n\n\t      if (left_ptr < right_ptr)\n\t\t{\n\t\t  SWAP (left_ptr, right_ptr, size);\n\t\t  if (mid == left_ptr)\n\t\t    mid = right_ptr;\n\t\t  else if (mid == right_ptr)\n\t\t    mid = left_ptr;\n\t\t  left_ptr += size;\n\t\t  right_ptr -= size;\n\t\t}\n\t      else if (left_ptr == right_ptr)\n\t\t{\n\t\t  left_ptr += size;\n\t\t  right_ptr -= size;\n\t\t  break;\n\t\t}\n\t    }\n\t  while (left_ptr <= right_ptr);\n\n          /* Set up pointers for next iteration.  First determine whether\n             left and right partitions are below the threshold size.  If so,\n             ignore one or both.  Otherwise, push the larger partition's\n             bounds on the stack and continue sorting the smaller one. */\n\n          if ((size_t) (right_ptr - lo) <= max_thresh)\n            {\n              if ((size_t) (hi - left_ptr) <= max_thresh)\n\t\t/* Ignore both small partitions. */\n                POP (lo, hi);\n              else\n\t\t/* Ignore small left partition. */\n                lo = left_ptr;\n            }\n          else if ((size_t) (hi - left_ptr) <= max_thresh)\n\t    /* Ignore small right partition. */\n            hi = right_ptr;\n          else if ((right_ptr - lo) > (hi - left_ptr))\n            {\n\t      /* Push larger left partition indices. */\n              PUSH (lo, right_ptr);\n              lo = left_ptr;\n            }\n          else\n            {\n\t      /* Push larger right partition indices. */\n              PUSH (left_ptr, hi);\n              hi = right_ptr;\n            }\n        }\n    }\n\n  /* Once the BASE_PTR array is partially sorted by quicksort the rest\n     is completely sorted using insertion sort, since this is efficient\n     for partitions below MAX_THRESH size. BASE_PTR points to the beginning\n     of the array to sort, and END_PTR points at the very last element in\n     the array (*not* one beyond it!). */\n\n#define min(x, y) ((x) < (y) ? (x) : (y))\n\n  {\n    char *const end_ptr = &base_ptr[size * (total_elems - 1)];\n    char *tmp_ptr = base_ptr;\n    char *thresh = min(end_ptr, base_ptr + max_thresh);\n    char *run_ptr;\n\n    /* Find smallest element in first threshold and place it at the\n       array's beginning.  This is the smallest array element,\n       and the operation speeds up insertion sort's inner loop. */\n\n    for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)\n      if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)\n        tmp_ptr = run_ptr;\n\n    if (tmp_ptr != base_ptr)\n      SWAP (tmp_ptr, base_ptr, size);\n\n    /* Insertion sort, running from left-hand-side up to right-hand-side.  */\n\n    run_ptr = base_ptr + size;\n    while ((run_ptr += size) <= end_ptr)\n      {\n\ttmp_ptr = run_ptr - size;\n\twhile ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)\n\t  tmp_ptr -= size;\n\n\ttmp_ptr += size;\n        if (tmp_ptr != run_ptr)\n          {\n            char *trav;\n\n\t    trav = run_ptr + size;\n\t    while (--trav >= run_ptr)\n              {\n                char c = *trav;\n                char *hi, *lo;\n\n                for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)\n                  *hi = *lo;\n                *hi = c;\n              }\n          }\n      }\n  }\n}\n"
  },
  {
    "path": "ip2net/qsort.h",
    "content": "#pragma once\n\n// GNU qsort is 2x faster than musl\n\ntypedef int (*__gnu_compar_d_fn_t) (const void *, const void *, void *);\nvoid gnu_quicksort (void *const pbase, size_t total_elems, size_t size, __gnu_compar_d_fn_t cmp, void *arg);\n"
  },
  {
    "path": "ipset/antifilter.helper",
    "content": "get_antifilter()\n{\n # $1 - list url\n # $2 - target file\n local ZIPLISTTMP=\"$TMPDIR/zapret-ip.txt\"\n\n [ \"$DISABLE_IPV4\" != \"1\" ] && {\n  curl --fail --max-time 150 --connect-timeout 20 --max-filesize 41943040 -k -L \"$1\" | cut_local >\"$ZIPLISTTMP\" &&\n  {\n   dlsize=$(LC_ALL=C LANG=C wc -c \"$ZIPLISTTMP\" | xargs | cut -f 1 -d ' ')\n   if [ $dlsize -lt 102400 ]; then\n    echo list file is too small. can be bad.\n    exit 2\n   fi\n   ip2net4 <\"$ZIPLISTTMP\" | zz \"$2\"\n   rm -f \"$ZIPLISTTMP\"\n  }\n }\n}\n"
  },
  {
    "path": "ipset/clear_lists.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nrm -f \"$ZIPLIST\"* \"$ZIPLIST6\"* \"$ZIPLIST_USER\" \"$ZIPLIST_USER6\" \"$ZIPLIST_IPBAN\"* \"$ZIPLIST_IPBAN6\"* \"$ZIPLIST_USER_IPBAN\" \"$ZIPLIST_USER_IPBAN6\" \"$ZIPLIST_EXCLUDE\" \"$ZIPLIST_EXCLUDE6\" \"$ZHOSTLIST\"*\n"
  },
  {
    "path": "ipset/create_ipset.sh",
    "content": "#!/bin/sh\n\n# create ipset or ipfw table from resolved ip's\n# $1=no-update    - do not update ipset, only create if its absent\n# $1=clear        - clear ipset\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\n\n. \"$EXEDIR/def.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/nft.sh\"\n\nIPSET_CMD=\"$TMPDIR/ipset_cmd.txt\"\nIPSET_SAVERAM_CHUNK_SIZE=20000\nIPSET_SAVERAM_MIN_FILESIZE=131072\n\nNFSET_TEMP=\"$TMPDIR/nfset_temp.txt\"\nNFSET_SAVERAM_MIN_FILESIZE=16384\nNFSET_SAVERAM_CHUNK_SIZE=1000\n\nIPSET_HOOK_TEMP=\"$TMPDIR/ipset_hook.txt\"\n\nwhile [ -n \"$1\" ]; do\n\t[ \"$1\" = \"no-update\" ] && NO_UPDATE=1\n\t[ \"$1\" = \"clear\" ] && DO_CLEAR=1\n\tshift\ndone\n\n\nfile_extract_lines()\n{\n\t# $1 - filename\n\t# $2 - from line (starting with 0)\n\t# $3 - line count\n\t# awk \"{ err=1 } NR < $(($2+1)) { next } { print; err=0 } NR == $(($2+$3)) { exit err } END {exit err}\" \"$1\"\n\t$AWK \"NR < $(($2+1)) { next } { print } NR == $(($2+$3)) { exit }\" \"$1\"\n}\nipset_restore_chunked()\n{\n\t# $1 - filename\n\t# $2 - chunk size\n\tlocal pos lines\n\t[ -f \"$1\" ] || return\n\tlines=$(wc -l <\"$1\")\n\tpos=$lines\n\twhile [ \"$pos\" -gt \"0\" ]; do\n\t\tpos=$((pos-$2))\n\t\t[ \"$pos\" -lt \"0\" ] && pos=0\n\t\tfile_extract_lines \"$1\" $pos $2 | ipset -! restore\n\t\tsed -i \"$(($pos+1)),$ d\" \"$1\"\n\tdone\n}\n\n\nipset_get_script()\n{\n\t# $1 - ipset name\n\tsed -nEe \"s/^.+$/add $1 &/p\"\n}\nipset_get_script_from_file()\n{\n\t# $1 - filename\n\t# $2 - ipset name\n\tzzcat \"$1\" | sort -u | ipset_get_script $2\n}\nipset_restore()\n{\n\t# $1 - ipset name\n\t# $2 - filename\n\n\tzzexist \"$2\" || return\n\tlocal fsize=$(zzsize \"$2\")\n\tlocal svram=0\n\t# do not saveram small files. file can also be gzipped\n\t[ \"$SAVERAM\" = \"1\" ] && [ \"$fsize\" -ge \"$IPSET_SAVERAM_MIN_FILESIZE\" ] && svram=1\n\n\tlocal T=\"Adding to ipset $1 \"\n\t[ \"$svram\" = \"1\" ] && T=\"$T (saveram)\"\n\tT=\"$T : $f\"\n\techo $T\n\n\tif [ \"$svram\" = \"1\" ]; then\n\t\tipset_get_script_from_file \"$2\" \"$1\" >\"$IPSET_CMD\"\n\t\tipset_restore_chunked \"$IPSET_CMD\" $IPSET_SAVERAM_CHUNK_SIZE\n\t\trm -f \"$IPSET_CMD\"\n\telse\n\t\tipset_get_script_from_file \"$2\" \"$1\" | ipset -! restore\n\tfi\n}\ncreate_ipset()\n{\n\tif [ \"$1\" -eq \"6\" ]; then\n\t\tFAMILY=inet6\n\telse\n\t\tFAMILY=inet\n\tfi\n\tipset create $2 $3 $4 family $FAMILY 2>/dev/null || {\n\t\t[ \"$NO_UPDATE\" = \"1\" ] && return 0\n\t}\n\tipset flush $2\n\t[ \"$DO_CLEAR\" = \"1\" ] || {\n\t\tfor f in \"$5\" \"$6\" ; do\n\t\t\tipset_restore \"$2\" \"$f\"\n\t\tdone\n\t\t[ -n \"$IPSET_HOOK\" ] && $IPSET_HOOK $2 | ipset_get_script $2 | ipset -! restore\n\t}\n\treturn 0\n}\n\nnfset_get_script_multi()\n{\n\t# $1 - set name\n\t# $2,$3,... - filenames\n\n\t# all in one shot. this allows to merge overlapping ranges\n\t# good but eats lots of RAM\n\n\tlocal set=$1 nonempty N=1 f\n\t\n\tshift\n\t# first we need to make sure at least one element exists or nft will fail\n\twhile :\n\tdo\n\t\teval f=\\$$N\n\t\t[ -n \"$f\" ] || break\n\t\tnonempty=$(zzexist \"$f\" && zzcat \"$f\" 2>/dev/null | head -n 1)\n\t\t[ -n \"$nonempty\" ] && break\n\t\tN=$(($N+1))\n\tdone\n\n\t[ -n \"$nonempty\" ] && {\n\t\techo \"add element inet $ZAPRET_NFT_TABLE $set {\"\n\t\twhile [ -n \"$1\" ]; do\n\t\t\tzzexist \"$1\" && zzcat \"$1\" | sed -nEe \"s/^.+$/&,/p\"\n\t\t\tshift\n\t\tdone\n\t\techo \"}\"\n\t}\n}\nnfset_restore()\n{\n\t# $1 - set name\n\t# $2,$3,... - filenames\n\n\techo \"Adding to nfset $1 : $2 $3 $4 $5\"\n\tlocal hookfile\n\t[ -n \"$IPSET_HOOK\" ] && {\n\t\t$IPSET_HOOK $1 >\"$IPSET_HOOK_TEMP\"\n\t\t[ -s \"$IPSET_HOOK_TEMP\" ] && hookfile=$IPSET_HOOK_TEMP\n\t}\n\tnfset_get_script_multi \"$@\" $hookfile | nft -f -\n\trm -f \"$IPSET_HOOK_TEMP\"\n}\ncreate_nfset()\n{\n\t# $1 - family\n\t# $2 - set name\n\t# $3 - maxelem\n\t# $4,$5 - list files\n\n\tlocal policy\n\t[ $SAVERAM = \"1\" ] && policy=\"policy memory;\"\n\tnft_create_set $2 \"type ipv${1}_addr; size $3; flags interval; auto-merge; $policy\" || {\n\t\t[ \"$NO_UPDATE\" = \"1\" ] && return 0\n\t\tnft flush set inet $ZAPRET_NFT_TABLE $2\n\t}\n\t[ \"$DO_CLEAR\" = \"1\" ] || {\n\t\tnfset_restore $2 $4 $5\n\t}\n\treturn 0\n}\n\nadd_ipfw_table()\n{\n\t# $1 - table name\n\tsed -nEe \"s/^.+$/table $1 add &/p\" | ipfw -q /dev/stdin\n}\npopulate_ipfw_table()\n{\n\t# $1 - table name\n\t# $2 - ip list file\n\tzzexist \"$2\" || return\n\tzzcat \"$2\" | sort -u | add_ipfw_table $1\n}\ncreate_ipfw_table()\n{\n\t# $1 - table name\n\t# $2 - table options\n\t# $3,$4, ... - ip list files. can be v4,v6 or mixed\n\n\tlocal name=$1\n\tipfw table \"$name\" create $2 2>/dev/null || {\n\t\t[ \"$NO_UPDATE\" = \"1\" ] && return 0\n\t}\n\tipfw -q table $1 flush\n\tshift\n\tshift\n\t[ \"$DO_CLEAR\" = \"1\" ] || {\n\t\twhile [ -n \"$1\" ]; do\n\t\t\techo \"Adding to ipfw table $name : $1\"\n\t\t\tpopulate_ipfw_table $name \"$1\"\n\t\t\tshift\n\t\tdone\n\t\t[ -n \"$IPSET_HOOK\" ] && $IPSET_HOOK $name | add_ipfw_table $name\n\t}\n\treturn 0\n}\n\nprint_reloading_backend()\n{\n\t# $1 - backend name\n\tlocal s=\"reloading $1 backend\"\n\tif [ \"$NO_UPDATE\" = 1 ]; then\n\t\ts=\"$s (no-update)\"\n\telif [ \"$DO_CLEAR\" = 1 ]; then\n\t\ts=\"$s (clear)\"\n\telse\n\t\ts=\"$s (forced-update)\"\n\tfi\n\techo $s\n}\n\n\noom_adjust_high\nget_fwtype\n\nif [ -n \"$LISTS_RELOAD\" ] ; then\n\tif [ \"$LISTS_RELOAD\" = \"-\" ] ; then\n\t\techo not reloading ip list backend\n\t\ttrue\n\telse\n\t\techo executing custom ip list reload command : $LISTS_RELOAD\n\t\t$LISTS_RELOAD\n\t\t[ -n \"$IPSET_HOOK\" ] && $IPSET_HOOK\n\tfi\nelse\n\tcase \"$FWTYPE\" in\n\t\tiptables)\n\t\t\t# ipset seem to buffer the whole script to memory\n\t\t\t# on low RAM system this can cause oom errors\n\t\t\t# in SAVERAM mode we feed script lines in portions starting from the end, while truncating source file to free /tmp space\n\t\t\t# only /tmp is considered tmpfs. other locations mean tmpdir was redirected to a disk\n\t\t\tSAVERAM=0\n\t\t\t[ \"$TMPDIR\" = \"/tmp\" ] && {\n\t\t\t\tRAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}')\n\t\t\t\t[ \"$RAMSIZE\" -lt \"110000\" ] && SAVERAM=1\n\t\t\t}\n\t\t\tprint_reloading_backend ipset\n\t\t\t[ \"$DISABLE_IPV4\" != \"1\" ] && {\n\t\t\t\tcreate_ipset 4 $ZIPSET hash:net \"$IPSET_OPT\" \"$ZIPLIST\" \"$ZIPLIST_USER\"\n\t\t\t\tcreate_ipset 4 $ZIPSET_IPBAN hash:net \"$IPSET_OPT\" \"$ZIPLIST_IPBAN\" \"$ZIPLIST_USER_IPBAN\"\n\t\t\t\tcreate_ipset 4 $ZIPSET_EXCLUDE hash:net \"$IPSET_OPT_EXCLUDE\" \"$ZIPLIST_EXCLUDE\"\n\t\t\t}\n\t\t\t[ \"$DISABLE_IPV6\" != \"1\" ] && {\n\t\t\t\tcreate_ipset 6 $ZIPSET6 hash:net \"$IPSET_OPT\" \"$ZIPLIST6\" \"$ZIPLIST_USER6\"\n\t\t\t\tcreate_ipset 6 $ZIPSET_IPBAN6 hash:net \"$IPSET_OPT\" \"$ZIPLIST_IPBAN6\" \"$ZIPLIST_USER_IPBAN6\"\n\t\t\t\tcreate_ipset 6 $ZIPSET_EXCLUDE6 hash:net \"$IPSET_OPT_EXCLUDE\" \"$ZIPLIST_EXCLUDE6\"\n\t\t\t}\n\t\t\ttrue\n\t\t\t;;\n\t\tnftables)\n\t\t\tnft_create_table && {\n\t\t\t\tSAVERAM=0\n\t\t\t\tRAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}')\n\t\t\t\t[ \"$RAMSIZE\" -lt \"420000\" ] && SAVERAM=1\n\t\t\t\tprint_reloading_backend \"nftables set\"\n\t\t\t\t[ \"$DISABLE_IPV4\" != \"1\" ] && {\n\t\t\t\t\tcreate_nfset 4 $ZIPSET $SET_MAXELEM \"$ZIPLIST\" \"$ZIPLIST_USER\"\n\t\t\t\t\tcreate_nfset 4 $ZIPSET_IPBAN $SET_MAXELEM \"$ZIPLIST_IPBAN\" \"$ZIPLIST_USER_IPBAN\"\n\t\t\t\t\tcreate_nfset 4 $ZIPSET_EXCLUDE $SET_MAXELEM_EXCLUDE \"$ZIPLIST_EXCLUDE\"\n\t\t\t\t}\n\t\t\t\t[ \"$DISABLE_IPV6\" != \"1\" ] && {\n\t\t\t\t\tcreate_nfset 6 $ZIPSET6 $SET_MAXELEM \"$ZIPLIST6\" \"$ZIPLIST_USER6\"\n\t\t\t\t\tcreate_nfset 6 $ZIPSET_IPBAN6 $SET_MAXELEM \"$ZIPLIST_IPBAN6\" \"$ZIPLIST_USER_IPBAN6\"\n\t\t\t\t\tcreate_nfset 6 $ZIPSET_EXCLUDE6 $SET_MAXELEM_EXCLUDE \"$ZIPLIST_EXCLUDE6\"\n\t\t\t\t}\n\t\t\t\ttrue\n\t\t\t}\n\t\t\t;;\n\t\tipfw)\n\t\t\tprint_reloading_backend \"ipfw table\"\n\t\t\tif [ \"$DISABLE_IPV4\" != \"1\" ] && [ \"$DISABLE_IPV6\" != \"1\" ]; then\n\t\t\t\tcreate_ipfw_table $ZIPSET \"$IPFW_TABLE_OPT\" \"$ZIPLIST\" \"$ZIPLIST_USER\" \"$ZIPLIST6\" \"$ZIPLIST_USER6\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_IPBAN \"$IPFW_TABLE_OPT\" \"$ZIPLIST_IPBAN\" \"$ZIPLIST_USER_IPBAN\" \"$ZIPLIST_IPBAN6\" \"$ZIPLIST_USER_IPBAN6\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_EXCLUDE \"$IPFW_TABLE_OPT_EXCLUDE\" \"$ZIPLIST_EXCLUDE\" \"$ZIPLIST_EXCLUDE6\"\n\t\t\telif [ \"$DISABLE_IPV4\" != \"1\" ]; then\n\t\t\t\tcreate_ipfw_table $ZIPSET \"$IPFW_TABLE_OPT\" \"$ZIPLIST\" \"$ZIPLIST_USER\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_IPBAN \"$IPFW_TABLE_OPT\" \"$ZIPLIST_IPBAN\" \"$ZIPLIST_USER_IPBAN\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_EXCLUDE \"$IPFW_TABLE_OPT_EXCLUDE\" \"$ZIPLIST_EXCLUDE\"\n\t\t\telif [ \"$DISABLE_IPV6\" != \"1\" ]; then\n\t\t\t\tcreate_ipfw_table $ZIPSET \"$IPFW_TABLE_OPT\" \"$ZIPLIST6\" \"$ZIPLIST_USER6\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_IPBAN \"$IPFW_TABLE_OPT\" \"$ZIPLIST_IPBAN6\" \"$ZIPLIST_USER_IPBAN6\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_EXCLUDE \"$IPFW_TABLE_OPT_EXCLUDE\" \"$ZIPLIST_EXCLUDE6\"\n\t\t\telse\n\t\t\t\tcreate_ipfw_table $ZIPSET \"$IPFW_TABLE_OPT\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_IPBAN \"$IPFW_TABLE_OPT\"\n\t\t\t\tcreate_ipfw_table $ZIPSET_EXCLUDE \"$IPFW_TABLE_OPT_EXCLUDE\"\n\t\t\tfi\n\t\t\ttrue\n\t\t\t;;\n\t\t*)\n\t\t\techo no supported ip list backend found\n\t\t\ttrue\n\t\t\t;;\n\t\tesac\n\nfi\n"
  },
  {
    "path": "ipset/def.sh",
    "content": "EXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=${ZAPRET_BASE:-\"$(cd \"$EXEDIR/..\"; pwd)\"}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\nIPSET_RW_DIR=\"$ZAPRET_RW/ipset\"\n\n[ -f \"$ZAPRET_CONFIG\" ] && . \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n\n[ -z \"$TMPDIR\" ] && TMPDIR=/tmp\n[ -z \"$GZIP_LISTS\" ] && GZIP_LISTS=1\n\n[ -z \"$SET_MAXELEM\" ] && SET_MAXELEM=262144\n[ -z \"$IPSET_OPT\" ] && IPSET_OPT=\"hashsize 262144 maxelem $SET_MAXELEM\"\n[ -z \"$SET_MAXELEM_EXCLUDE\" ] && SET_MAXELEM_EXCLUDE=65536\n[ -z \"$IPSET_OPT_EXCLUDE\" ] && IPSET_OPT_EXCLUDE=\"hashsize 1024 maxelem $SET_MAXELEM_EXCLUDE\"\n\n[ -z \"$IPFW_TABLE_OPT\" ] && IPFW_TABLE_OPT=\"algo addr:radix\"\n[ -z \"$IPFW_TABLE_OPT_EXCLUDE\" ] && IPFW_TABLE_OPT_EXCLUDE=\"algo addr:radix\"\n\nZIPSET=zapret\nZIPSET6=zapret6\nZIPSET_EXCLUDE=nozapret\nZIPSET_EXCLUDE6=nozapret6\nZIPLIST=\"$IPSET_RW_DIR/zapret-ip.txt\"\nZIPLIST6=\"$IPSET_RW_DIR/zapret-ip6.txt\"\nZIPLIST_EXCLUDE=\"$IPSET_RW_DIR/zapret-ip-exclude.txt\"\nZIPLIST_EXCLUDE6=\"$IPSET_RW_DIR/zapret-ip-exclude6.txt\"\nZIPLIST_USER=\"$IPSET_RW_DIR/zapret-ip-user.txt\"\nZIPLIST_USER6=\"$IPSET_RW_DIR/zapret-ip-user6.txt\"\nZUSERLIST=\"$IPSET_RW_DIR/zapret-hosts-user.txt\"\nZHOSTLIST=\"$IPSET_RW_DIR/zapret-hosts.txt\"\n\nZIPSET_IPBAN=ipban\nZIPSET_IPBAN6=ipban6\nZIPLIST_IPBAN=\"$IPSET_RW_DIR/zapret-ip-ipban.txt\"\nZIPLIST_IPBAN6=\"$IPSET_RW_DIR/zapret-ip-ipban6.txt\"\nZIPLIST_USER_IPBAN=\"$IPSET_RW_DIR/zapret-ip-user-ipban.txt\"\nZIPLIST_USER_IPBAN6=\"$IPSET_RW_DIR/zapret-ip-user-ipban6.txt\"\nZUSERLIST_IPBAN=\"$IPSET_RW_DIR/zapret-hosts-user-ipban.txt\"\nZUSERLIST_EXCLUDE=\"$IPSET_RW_DIR/zapret-hosts-user-exclude.txt\"\n\n\n[ -n \"$IP2NET\" ] || IP2NET=\"$ZAPRET_BASE/ip2net/ip2net\"\n[ -n \"$MDIG\" ] || MDIG=\"$ZAPRET_BASE/mdig/mdig\"\n[ -z \"$MDIG_THREADS\" ] && MDIG_THREADS=30\n\n\n\n# BSD grep is damn slow with -f option. prefer GNU grep (ggrep) if present\n# MacoS in cron does not include /usr/local/bin to PATH\nif [ -x /usr/local/bin/ggrep ] ; then\n GREP=/usr/local/bin/ggrep\nelif [ -x /usr/local/bin/grep ] ; then\n GREP=/usr/local/bin/grep\nelif exists ggrep; then\n GREP=$(whichq ggrep)\nelse\n GREP=$(whichq grep)\nfi\n\n# GNU awk is faster\nif exists gawk; then\n AWK=gawk\nelse\n AWK=awk\nfi\n\ngrep_supports_b()\n{\n # \\b does not work with BSD grep\n $GREP --version 2>&1 | $GREP -qE \"BusyBox|GNU\"\n}\nget_ip_regex()\n{\n REG_IPV4='((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\/([0-9]|[12][0-9]|3[012]))?'\n REG_IPV6='[0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}|:)+(\\/([0-9][0-9]?|1[01][0-9]|12[0-8]))?'\n # good but too slow\n # REG_IPV6='([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,7}:(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}(/[0-9]+)?|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})(/[0-9]+)?|:((:[0-9a-fA-F]{1,4}){1,7}|:)(/([0-9][0-9]?|1[01][0-9]|12[0-8]))?'\n# grep_supports_b && {\n#  REG_IPV4=\"\\b$REG_IPV4\\b\"\n#  REG_IPV6=\"\\b$REG_IPV6\\b\"\n# }\n}\n\nip2net4()\n{\n if [ -x \"$IP2NET\" ]; then\n  \"$IP2NET\" -4 $IP2NET_OPT4\n else\n  sort -u\n fi\n}\nip2net6()\n{\n if [ -x \"$IP2NET\" ]; then\n  \"$IP2NET\" -6 $IP2NET_OPT6\n else\n  sort -u\n fi\n}\n\nzzexist()\n{\n [ -f \"$1.gz\" ] || [ -f \"$1\" ]\n}\nzztest()\n{\n gzip -t \"$1\" 2>/dev/null\n}\nzzcat()\n{\n if [ -f \"$1.gz\" ]; then\n \tgunzip -c \"$1.gz\"\n elif [ -f \"$1\" ]; then\n\tif zztest \"$1\"; then\n \t\tgunzip -c \"$1\"\n\telse\n\t \tcat \"$1\"\n\tfi\n fi\n}\nzz()\n{\n if [ \"$GZIP_LISTS\" = \"1\" ]; then\n  gzip -c >\"$1.gz\"\n  rm -f \"$1\"\n else\n  cat >\"$1\"\n  rm -f \"$1.gz\"\n fi\n}\nzzsize()\n{\n local f=\"$1\"\n [ -f \"$1.gz\" ] && f=\"$1.gz\"\n if [ -f \"$f\" ]; then\n  wc -c <\"$f\" | xargs\n else\n  printf 0\n fi\n}\nzzcopy()\n{\n local is_gz=0\n zztest \"$1\" && is_gz=1\n if [ \"$GZIP_LISTS\" = 1 -a $is_gz = 1 ]; then\n  cp \"$1\" \"${2}.gz\"\n elif [ \"$GZIP_LISTS\" != 1 -a $is_gz != 1 ]; then\n  cp \"$1\" \"$2\"\n else\n  zzcat \"$1\" | zz \"$2\"\n fi\n}\n\ndigger()\n{\n # $1 - family (4|6)\n # $2 - s=enable mdig stats\n if [ -x \"$MDIG\" ]; then\n  local cmd\n  [ \"$2\" = \"s\" ] && cmd=--stats=1000\n  \"$MDIG\" --family=$1 --threads=$MDIG_THREADS $cmd\n else\n  local A=A\n  [ \"$1\" = \"6\" ] && A=AAAA\n  dig $A +short +time=8 +tries=2 -f - | $GREP -E '^[^;].*[^\\.]$'\n fi\n}\nfiledigger()\n{\n # $1 - hostlist\n # $2 - family (4|6)\n >&2 echo digging $(wc -l <\"$1\" | xargs) ipv$2 domains : \"$1\"\n zzcat \"$1\" | digger $2 s\n}\nflush_dns_cache()\n{\n echo clearing all known DNS caches\n\n if exists killall; then\n  killall -HUP dnsmasq 2>/dev/null\n  # MacOS\n  killall -HUP mDNSResponder 2>/dev/null\n elif exists pkill; then\n  pkill -HUP ^dnsmasq$\n else\n  echo no mass killer available ! cant flush dnsmasq\n fi\n \n if exists rndc; then\n  rndc flush\n fi\n\n if exists systemd-resolve; then\n  systemd-resolve --flush-caches\n fi\n\n}\ndnstest()\n{\n local ip=\"$(echo w3.org | digger 46)\"\n [ -n \"$ip\" ]\n}\ndnstest_with_cache_clear()\n{\n flush_dns_cache\n if dnstest ; then\n    echo DNS is working\n    return 0\n else\n    echo \"! DNS is not working\"\n    return 1\n fi\n}\n\n\ncut_local()\n{\n  $GREP -vE '^192\\.168\\.|^127\\.|^10\\.'\n}\ncut_local6()\n{\n  $GREP -vE '^::|^fc..:|^fd..:|^fe8.:|^fe9.:|^fea.:|^feb.:|^FC..:|^FD..:|^FE8.:|^FE9.:|^FEA.:|^FEB.:'\n}\n\noom_adjust_high()\n{\n\t[ -f /proc/$$/oom_score_adj ] && {\n\t\techo setting high oom kill priority\n\t\techo -n 100 >/proc/$$/oom_score_adj\n\t}\n}\n\ngetexclude()\n{\n oom_adjust_high\n dnstest_with_cache_clear || return\n [ -f \"$ZUSERLIST_EXCLUDE\" ] && {\n  [ \"$DISABLE_IPV4\" != \"1\" ] && filedigger \"$ZUSERLIST_EXCLUDE\" 4 | sort -u > \"$ZIPLIST_EXCLUDE\"\n  [ \"$DISABLE_IPV6\" != \"1\" ] && filedigger \"$ZUSERLIST_EXCLUDE\" 6 | sort -u > \"$ZIPLIST_EXCLUDE6\"\n }\n return 0\n}\n\n_get_ipban()\n{\n [ -f \"$ZUSERLIST_IPBAN\" ] && {\n  [ \"$DISABLE_IPV4\" != \"1\" ] && filedigger \"$ZUSERLIST_IPBAN\" 4 | cut_local | sort -u > \"$ZIPLIST_USER_IPBAN\"\n  [ \"$DISABLE_IPV6\" != \"1\" ] && filedigger \"$ZUSERLIST_IPBAN\" 6 | cut_local6 | sort -u > \"$ZIPLIST_USER_IPBAN6\"\n }\n}\ngetuser()\n{\n getexclude || return\n [ -f \"$ZUSERLIST\" ] && {\n  [ \"$DISABLE_IPV4\" != \"1\" ] && filedigger \"$ZUSERLIST\" 4 | cut_local | sort -u > \"$ZIPLIST_USER\"\n  [ \"$DISABLE_IPV6\" != \"1\" ] && filedigger \"$ZUSERLIST\" 6 | cut_local6 | sort -u > \"$ZIPLIST_USER6\"\n }\n _get_ipban\n return 0\n}\ngetipban()\n{\n getexclude || return\n _get_ipban\n return 0\n}\n\nhup_zapret_daemons()\n{\n echo forcing zapret daemons to reload their hostlist\n if exists killall; then\n  killall -HUP tpws nfqws dvtws 2>/dev/null\n elif exists pkill; then\n  pkill -HUP ^tpws$\n  pkill -HUP ^nfqws$\n  pkill -HUP ^dvtws$\n else\n  echo no mass killer available ! cant HUP zapret daemons\n fi\n}\n"
  },
  {
    "path": "ipset/get_antifilter_allyouneed.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser && {\n . \"$IPSET_DIR/antifilter.helper\"\n get_antifilter https://antifilter.download/list/allyouneed.lst \"$ZIPLIST\"\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_antifilter_ip.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser && {\n . \"$IPSET_DIR/antifilter.helper\"\n get_antifilter https://antifilter.download/list/ip.lst \"$ZIPLIST\"\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_antifilter_ipresolve.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser && {\n . \"$IPSET_DIR/antifilter.helper\"\n get_antifilter https://antifilter.download/list/ipresolve.lst \"$ZIPLIST\"\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_antifilter_ipsmart.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser && {\n . \"$IPSET_DIR/antifilter.helper\"\n get_antifilter https://antifilter.network/download/ipsmart.lst \"$ZIPLIST\"\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_antifilter_ipsum.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser && {\n . \"$IPSET_DIR/antifilter.helper\"\n get_antifilter https://antifilter.download/list/ipsum.lst \"$ZIPLIST\"\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_antizapret_domains.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\n# useful in case ipban set is used in custom scripts\nFAIL=\ngetipban || FAIL=1\n\"$IPSET_DIR/create_ipset.sh\"\n[ -n \"$FAIL\" ] && exit\n\nZURL=https://antizapret.prostovpn.org:8443/domains-export.txt\nZDOM=\"$TMPDIR/zapret.txt\"\n\n\ncurl -H \"Accept-Encoding: gzip\" -k --fail --max-time 600 --connect-timeout 5 --retry 3 --max-filesize 251658240 \"$ZURL\" | gunzip - >\"$ZDOM\" ||\n{\n echo domain list download failed   \n exit 2\n}\n\ndlsize=$(LC_ALL=C LANG=C wc -c \"$ZDOM\" | xargs | cut -f 1 -d ' ')\nif test $dlsize -lt 102400; then\n echo list file is too small. can be bad.\n exit 2\nfi\n\nsort -u \"$ZDOM\" | zz \"$ZHOSTLIST\"\n\nrm -f \"$ZDOM\"\n\nhup_zapret_daemons\n\nexit 0\n"
  },
  {
    "path": "ipset/get_config.sh",
    "content": "#!/bin/sh\n# run script specified in config\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n[ -f \"$IPSET_DIR/../config\" ] && . \"$IPSET_DIR/../config\"\n\n[ -z \"$GETLIST\" ] && GETLIST=get_ipban.sh\n[ -x \"$IPSET_DIR/$GETLIST\" ] && exec \"$IPSET_DIR/$GETLIST\"\n"
  },
  {
    "path": "ipset/get_exclude.sh",
    "content": "#!/bin/sh\n# resolve user host list\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetexclude\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_ipban.sh",
    "content": "#!/bin/sh\n# resolve only ipban user host list\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetipban\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_reestr_preresolved.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nTMPLIST=\"$TMPDIR/list.txt\"\n\nBASEURL=\"https://raw.githubusercontent.com/bol-van/rulist/main\"\nURL4=\"$BASEURL/reestr_resolved4.txt\"\nURL6=\"$BASEURL/reestr_resolved6.txt\"\n#IPB4=\"$BASEURL/reestr_ipban4.txt\"\n#IPB6=\"$BASEURL/reestr_ipban6.txt\"\n\ndl()\n{\n  # $1 - url\n  # $2 - file\n  # $3 - minsize\n  # $4 - maxsize\n  curl -H \"Accept-Encoding: gzip\" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o \"$TMPLIST\" \"$1\" ||\n  {\n   echo list download failed : $1\n   exit 2\n  }\n  dlsize=$(LC_ALL=C LANG=C wc -c \"$TMPLIST\" | xargs | cut -f 1 -d ' ')\n  if test $dlsize -lt $3; then\n   echo list is too small : $dlsize bytes. can be bad.\n   exit 2\n  fi\n  zzcopy \"$TMPLIST\" \"$2\"\n  rm -f \"$TMPLIST\"\n}\n\ngetuser && {\n [ \"$DISABLE_IPV4\" != \"1\" ] && {\n\tdl \"$URL4\" \"$ZIPLIST\" 4096 4194304\n#\tdl \"$IPB4\" \"$ZIPLIST_IPBAN\" 8192 1048576\n }\n [ \"$DISABLE_IPV6\" != \"1\" ] && {\n\tdl \"$URL6\" \"$ZIPLIST6\" 2048 4194304\n#\tdl \"$IPB6\" \"$ZIPLIST_IPBAN6\" 128 1048576\n }\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_reestr_preresolved_smart.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nTMPLIST=\"$TMPDIR/list.txt\"\n\nBASEURL=\"https://raw.githubusercontent.com/bol-van/rulist/main\"\nURL4=\"$BASEURL/reestr_smart4.txt\"\nURL6=\"$BASEURL/reestr_smart6.txt\"\n#IPB4=\"$BASEURL/reestr_ipban4.txt\"\n#IPB6=\"$BASEURL/reestr_ipban6.txt\"\n\ndl()\n{\n  # $1 - url\n  # $2 - file\n  # $3 - minsize\n  # $4 - maxsize\n  curl -H \"Accept-Encoding: gzip\" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o \"$TMPLIST\" \"$1\" ||\n  {\n   echo list download failed : $1\n   exit 2\n  }\n  dlsize=$(LC_ALL=C LANG=C wc -c \"$TMPLIST\" | xargs | cut -f 1 -d ' ')\n  if test $dlsize -lt $3; then\n   echo list is too small : $dlsize bytes. can be bad.\n   exit 2\n  fi\n  zzcopy \"$TMPLIST\" \"$2\"\n  rm -f \"$TMPLIST\"\n}\n\ngetuser && {\n [ \"$DISABLE_IPV4\" != \"1\" ] && {\n\tdl \"$URL4\" \"$ZIPLIST\" 4096 4194304\n#\tdl \"$IPB4\" \"$ZIPLIST_IPBAN\" 8192 1048576\n }\n [ \"$DISABLE_IPV6\" != \"1\" ] && {\n\tdl \"$URL6\" \"$ZIPLIST6\" 2048 4194304\n#\tdl \"$IPB6\" \"$ZIPLIST_IPBAN6\" 128 1048576\n }\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_reestr_resolvable_domains.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nTMPLIST=\"$TMPDIR/list_nethub.txt\"\n\nBASEURL=\"https://raw.githubusercontent.com/bol-van/rulist/main\"\nURL=\"$BASEURL/reestr_hostname_resolvable.txt\"\n#IPB4=\"$BASEURL/reestr_ipban4.txt\"\n#IPB6=\"$BASEURL/reestr_ipban6.txt\"\n\ndl()\n{\n  # $1 - url\n  # $2 - file\n  # $3 - minsize\n  # $4 - maxsize\n  curl -H \"Accept-Encoding: gzip\" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o \"$TMPLIST\" \"$1\" ||\n  {\n   echo list download failed : $1\n   exit 2\n  }\n  dlsize=$(LC_ALL=C LANG=C wc -c \"$TMPLIST\" | xargs | cut -f 1 -d ' ')\n  if test $dlsize -lt $3; then\n   echo list is too small : $dlsize bytes. can be bad.\n   exit 2\n  fi\n  zzcopy \"$TMPLIST\" \"$2\"\n  rm -f \"$TMPLIST\"\n}\n\ndl \"$URL\" \"$ZHOSTLIST\" 65536 67108864\n\nhup_zapret_daemons\n\n#[ \"$DISABLE_IPV4\" != \"1\" ] && dl \"$IPB4\" \"$ZIPLIST_IPBAN\" 8192 1048576\n#[ \"$DISABLE_IPV6\" != \"1\" ] && dl \"$IPB6\" \"$ZIPLIST_IPBAN6\" 128 1048576\n\ngetipban\n\"$IPSET_DIR/create_ipset.sh\"\n\nexit 0\n"
  },
  {
    "path": "ipset/get_refilter_domains.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nTMPLIST=\"$TMPDIR/list.txt\"\n\nURL=\"https://github.com/1andrevich/Re-filter-lists/releases/latest/download/domains_all.lst\"\n\ndl()\n{\n  # $1 - url\n  # $2 - file\n  # $3 - minsize\n  # $4 - maxsize\n  curl -L -H \"Accept-Encoding: gzip\" -k --fail --max-time 60 --connect-timeout 10 --retry 4 --max-filesize $4 -o \"$TMPLIST\" \"$1\" ||\n  {\n   echo list download failed : $1\n   exit 2\n  }\n  dlsize=$(LC_ALL=C LANG=C wc -c \"$TMPLIST\" | xargs | cut -f 1 -d ' ')\n  if test $dlsize -lt $3; then\n   echo list is too small : $dlsize bytes. can be bad.\n   exit 2\n  fi\n  zzcopy \"$TMPLIST\" \"$2\"\n  rm -f \"$TMPLIST\"\n}\n\n# useful in case ipban set is used in custom scripts\nFAIL=\ngetipban || FAIL=1\n\"$IPSET_DIR/create_ipset.sh\"\n[ -n \"$FAIL\" ] && exit\n\ndl \"$URL\" \"$ZHOSTLIST\" 32768 4194304\n\nhup_zapret_daemons\n\nexit 0\n"
  },
  {
    "path": "ipset/get_refilter_ipsum.sh",
    "content": "#!/bin/sh\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\nTMPLIST=\"$TMPDIR/list.txt\"\n\nURL=\"https://github.com/1andrevich/Re-filter-lists/releases/latest/download/ipsum.lst\"\n\ndl()\n{\n  # $1 - url\n  # $2 - file\n  # $3 - minsize\n  # $4 - maxsize\n  curl -L -H \"Accept-Encoding: gzip\" -k --fail --max-time 60 --connect-timeout 10 --retry 4 --max-filesize $4 -o \"$TMPLIST\" \"$1\" ||\n  {\n   echo list download failed : $1\n   exit 2\n  }\n  dlsize=$(LC_ALL=C LANG=C wc -c \"$TMPLIST\" | xargs | cut -f 1 -d ' ')\n  if test $dlsize -lt $3; then\n   echo list is too small : $dlsize bytes. can be bad.\n   exit 2\n  fi\n  zzcopy \"$TMPLIST\" \"$2\"\n  rm -f \"$TMPLIST\"\n}\n\ngetuser && {\n [ \"$DISABLE_IPV4\" != \"1\" ] && {\n \tdl \"$URL\" \"$ZIPLIST\" 32768 4194304\n }\n}\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/get_user.sh",
    "content": "#!/bin/sh\n# resolve user host list\n\nIPSET_DIR=\"$(dirname \"$0\")\"\nIPSET_DIR=\"$(cd \"$IPSET_DIR\"; pwd)\"\n\n. \"$IPSET_DIR/def.sh\"\n\ngetuser\n\n\"$IPSET_DIR/create_ipset.sh\"\n"
  },
  {
    "path": "ipset/zapret-hosts-user-exclude.txt.default",
    "content": "127.0.0.0/8\n10.0.0.0/8\n172.16.0.0/12\n192.168.0.0/16\n169.254.0.0/16\n100.64.0.0/10\n::1\nfc00::/7\nfe80::/10\n"
  },
  {
    "path": "mdig/Makefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 $(OPTIMIZE)\nCFLAGS_BSD = -Wno-address-of-packed-member\nCFLAGS_WIN = -static\nLIBS = -lpthread\nLIBS_ANDROID =\nLIBS_WIN = -lws2_32\nSRC_FILES = *.c\n\nall: mdig\n\nmdig: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS)\n\nsystemd: mdig\n\nandroid: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS_ANDROID) $(LDFLAGS)\n\nbsd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS)\n\nmac: $(SRC_FILES)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdiga $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdigx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS)\n\tstrip mdiga mdigx\n\tlipo -create -output mdig mdigx mdiga\n\trm -f mdigx mdiga\n\nwin: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o mdig $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS)\n\nclean:\n\trm -f mdig *.o\n"
  },
  {
    "path": "mdig/mdig.c",
    "content": "// multi thread dns resolver\n// domain list <stdin\n// ip list >stdout\n// errors, verbose >stderr\n// transparent for valid ip or ip/subnet of allowed address family\n\n#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <stdint.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdbool.h>\n#include <pthread.h>\n#include <getopt.h>\n#ifdef _WIN32\n#undef _WIN32_WINNT\n#define _WIN32_WINNT 0x600\n#include <winsock2.h>\n#include <ws2ipdef.h>\n#include <ws2tcpip.h>\n#include <fcntl.h>\n#else\n#include <unistd.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#endif\n#include <time.h>\n\n#define RESOLVER_EAGAIN_ATTEMPTS\t10\n#define RESOLVER_EAGAIN_DELAY\t\t500\n\nstatic void trimstr(char *s)\n{\n\tchar *p;\n\tfor (p = s + strlen(s) - 1; p >= s && (*p == '\\n' || *p == '\\r' || *p == ' ' || *p == '\\t'); p--) *p = '\\0';\n}\n\nstatic const char* eai_str(int r)\n{\n\tswitch (r)\n\t{\n\tcase EAI_NONAME:\n\t\treturn \"EAI_NONAME\";\n\tcase EAI_AGAIN:\n\t\treturn \"EAI_AGAIN\";\n#ifdef EAI_ADDRFAMILY\n\tcase EAI_ADDRFAMILY:\n\t\treturn \"EAI_ADDRFAMILY\";\n#endif\n#ifdef EAI_NODATA\n\tcase EAI_NODATA:\n\t\treturn \"EAI_NODATA\";\n#endif\n\tcase EAI_BADFLAGS:\n\t\treturn \"EAI_BADFLAGS\";\n\tcase EAI_FAIL:\n\t\treturn \"EAI_FAIL\";\n\tcase EAI_MEMORY:\n\t\treturn \"EAI_MEMORY\";\n\tcase EAI_FAMILY:\n\t\treturn \"EAI_FAMILY\";\n\tcase EAI_SERVICE:\n\t\treturn \"EAI_SERVICE\";\n\tcase EAI_SOCKTYPE:\n\t\treturn \"EAI_SOCKTYPE\";\n#ifdef EAI_SYSTEM\n\tcase EAI_SYSTEM:\n\t\treturn \"EAI_SYSTEM\";\n#endif\n\tdefault:\n\t\treturn \"UNKNOWN\";\n\t}\n}\n\nstatic bool dom_valid(char *dom)\n{\n\tif (!dom || *dom=='.') return false;\n\tfor (; *dom; dom++)\n\t\tif (!(*dom == '.' || *dom == '-' || *dom == '_' || (*dom >= '0' && *dom <= '9') || (*dom >= 'a' && *dom <= 'z') || (*dom >= 'A' && *dom <= 'Z')))\n\t\t\treturn false;\n\treturn true;\n}\n\nstatic void invalid_domain_beautify(char *dom)\n{\n\tfor (int i = 0; *dom && i < 64; i++, dom++)\n\t\tif (*dom < 0x20 || (*dom & 0x80)) *dom = '?';\n\tif (*dom) *dom = 0;\n}\n\n#define FAMILY4 1\n#define FAMILY6 2\nstatic struct\n{\n\tchar verbose;\n\tchar family;\n\tint threads, eagain, eagain_delay;\n\ttime_t start_time;\n\tpthread_mutex_t flock;\n\tpthread_mutex_t slock; // stats lock\n\tint stats_every, stats_ct, stats_ct_ok; // stats\n\tFILE *F_log_resolved, *F_log_failed;\n} glob;\n\n// get next domain. return 0 if failure\nstatic char interlocked_get_dom(char *dom, size_t size)\n{\n\tchar *s;\n\tpthread_mutex_lock(&glob.flock);\n\ts = fgets(dom, size, stdin);\n\tpthread_mutex_unlock(&glob.flock);\n\tif (!s) return 0;\n\ttrimstr(s);\n\treturn 1;\n}\nstatic void interlocked_fprintf(FILE *stream, const char * format, ...)\n{\n\tif (stream)\n\t{\n\t\tva_list args;\n\t\tva_start(args, format);\n\t\tpthread_mutex_lock(&glob.flock);\n\t\tvfprintf(stream, format, args);\n\t\tpthread_mutex_unlock(&glob.flock);\n\t\tva_end(args);\n\t}\n}\n\n#define ELOG(format, ...) interlocked_fprintf(stderr,  \"[%d] \" format \"\\n\", tid, ##__VA_ARGS__)\n#define VLOG(format, ...) {if (glob.verbose) ELOG(format, ##__VA_ARGS__);}\n\nstatic void print_addrinfo(struct addrinfo *ai)\n{\n\tchar str[64];\n\twhile (ai)\n\t{\n\t\tswitch (ai->ai_family)\n\t\t{\n\t\tcase AF_INET:\n\t\t\tif (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str)))\n\t\t\t\tinterlocked_fprintf(stdout, \"%s\\n\", str);\n\t\t\tbreak;\n\t\tcase AF_INET6:\n\t\t\tif (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str)))\n\t\t\t\tinterlocked_fprintf(stdout, \"%s\\n\", str);\n\t\t\tbreak;\n\t\t}\n\t\tai = ai->ai_next;\n\t}\n}\n\nstatic void stat_print(int ct, int ct_ok)\n{\n\tif (glob.stats_every > 0)\n\t{\n\t\ttime_t tm = time(NULL)-glob.start_time;\n\t\tinterlocked_fprintf(stderr, \"mdig stats : %02u:%02u:%02u : domains=%d success=%d error=%d\\n\", (unsigned int)(tm/3600), (unsigned int)((tm/60)%60), (unsigned int)(tm%60), ct, ct_ok, ct - ct_ok);\n\t}\n}\n\nstatic void stat_plus(bool is_ok)\n{\n\tint ct, ct_ok;\n\tif (glob.stats_every > 0)\n\t{\n\t\tpthread_mutex_lock(&glob.slock);\n\t\tct = ++glob.stats_ct;\n\t\tct_ok = glob.stats_ct_ok += is_ok;\n\t\tpthread_mutex_unlock(&glob.slock);\n\n\t\tif (!(ct % glob.stats_every)) stat_print(ct, ct_ok);\n\t}\n}\n\nstatic uint16_t GetAddrFamily(const char *saddr)\n{\n\tstruct in_addr a4;\n\tstruct in6_addr a6;\n\n\tif (inet_pton(AF_INET, saddr, &a4))\n\t\treturn AF_INET;\n\telse if (inet_pton(AF_INET6, saddr, &a6))\n\t\treturn AF_INET6;\n\treturn 0;\n}\n\nstatic void *t_resolver(void *arg)\n{\n\tint tid = (int)(size_t)arg;\n\tint i, r;\n\tchar dom[256];\n\tbool is_ok;\n\tstruct addrinfo hints, *result;\n\tstruct timespec ts_eagain = { .tv_sec = glob.eagain_delay/1000, .tv_nsec=glob.eagain_delay%1000*1000000 };\n\n\tVLOG(\"started\");\n\n\n\tmemset(&hints, 0, sizeof(struct addrinfo));\n\thints.ai_family = (glob.family == FAMILY4) ? AF_INET : (glob.family == FAMILY6) ? AF_INET6 : AF_UNSPEC;\n\thints.ai_socktype = SOCK_DGRAM;\n\n\twhile (interlocked_get_dom(dom, sizeof(dom)))\n\t{\n\t\tis_ok = false;\n\t\tif (*dom)\n\t\t{\n\t\t\tuint16_t family;\n\t\t\tchar *s_mask, s_ip[sizeof(dom)];\n\n\t\t\tstrncpy(s_ip, dom, sizeof(s_ip));\n\t\t\ts_mask = strchr(s_ip, '/');\n\t\t\tif (s_mask) *s_mask++ = 0;\n\t\t\tfamily = GetAddrFamily(s_ip);\n\t\t\tif (family)\n\t\t\t{\n\t\t\t\tif ((family == AF_INET && (glob.family & FAMILY4)) || (family == AF_INET6 && (glob.family & FAMILY6)))\n\t\t\t\t{\n\t\t\t\t\tunsigned int mask=0;\n\t\t\t\t\tbool mask_needed = false;\n\t\t\t\t\tif (s_mask)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (sscanf(s_mask, \"%u\", &mask)==1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (family)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase AF_INET: is_ok = mask <= 32; mask_needed = mask < 32; break;\n\t\t\t\t\t\t\tcase AF_INET6: is_ok = mask <= 128; mask_needed = mask < 128; break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tis_ok = true;\n\t\t\t\t\tif (is_ok)\n\t\t\t\t\t\tinterlocked_fprintf(stdout, mask_needed ? \"%s/%u\\n\" : \"%s\\n\", s_ip, mask);\n\t\t\t\t\telse\n\t\t\t\t\t\tVLOG(\"bad ip/subnet %s\", dom);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tVLOG(\"wrong address family %s\", s_ip);\n\t\t\t}\n\t\t\telse if (dom_valid(dom))\n\t\t\t{\n\t\t\t\tVLOG(\"resolving %s\", dom);\n\t\t\t\tfor (i = 0; i < glob.eagain; i++)\n\t\t\t\t{\n\t\t\t\t\tif ((r = getaddrinfo(dom, NULL, &hints, &result)))\n\t\t\t\t\t{\n\t\t\t\t\t\tVLOG(\"failed to resolve %s : result %d (%s)\", dom, r, eai_str(r));\n\t\t\t\t\t\tif (r == EAI_AGAIN)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnanosleep(&ts_eagain, NULL);\n\t\t\t\t\t\t\tcontinue; // temporary failure. should retry\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tprint_addrinfo(result);\n\t\t\t\t\t\tfreeaddrinfo(result);\n\t\t\t\t\t\tis_ok = true;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (glob.verbose)\n\t\t\t{\n\t\t\t\tchar dom2[sizeof(dom)];\n\t\t\t\tstrcpy(dom2,dom);\n\t\t\t\tinvalid_domain_beautify(dom2);\n\t\t\t\tVLOG(\"invalid domain : %s\", dom2);\n\t\t\t}\n\t\t\tinterlocked_fprintf(is_ok ? glob.F_log_resolved : glob.F_log_failed,\"%s\\n\",dom);\n\t\t}\n\t\tstat_plus(is_ok);\n\t}\n\tVLOG(\"ended\");\n\treturn NULL;\n}\n\nstatic int run_threads(void)\n{\n\tint i, thread;\n\tpthread_t *t;\n\n\tglob.stats_ct = glob.stats_ct_ok = 0;\n\ttime(&glob.start_time);\n\tif (pthread_mutex_init(&glob.flock, NULL) != 0)\n\t{\n\t\tfprintf(stderr, \"mutex init failed\\n\");\n\t\treturn 10;\n\t}\n\tif (pthread_mutex_init(&glob.slock, NULL) != 0)\n\t{\n\t\tfprintf(stderr, \"mutex init failed\\n\");\n\t\tpthread_mutex_destroy(&glob.flock);\n\t\treturn 10;\n\t}\n\tt = (pthread_t*)malloc(sizeof(pthread_t)*glob.threads);\n\tif (!t)\n\t{\n\t\tfprintf(stderr, \"out of memory\\n\");\n\t\tpthread_mutex_destroy(&glob.slock);\n\t\tpthread_mutex_destroy(&glob.flock);\n\t\treturn 11;\n\t}\n\tfor (thread = 0; thread < glob.threads; thread++)\n\t{\n\t\tif (pthread_create(t + thread, NULL, t_resolver, (void*)(size_t)thread))\n\t\t{\n\t\t\tinterlocked_fprintf(stderr, \"failed to create thread #%d\\n\", thread);\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (i = 0; i < thread; i++)\n\t{\n\t\tpthread_join(t[i], NULL);\n\t}\n\tfree(t);\n\tstat_print(glob.stats_ct, glob.stats_ct_ok);\n\tpthread_mutex_destroy(&glob.slock);\n\tpthread_mutex_destroy(&glob.flock);\n\treturn thread ? 0 : 12;\n}\n\n// slightly patched musl code\nsize_t dns_mk_query_blob(uint8_t op, const char *dname, uint8_t class, uint8_t type, uint8_t *buf, size_t buflen)\n{\n\tint i, j;\n\tuint16_t id;\n\tstruct timespec ts;\n\tsize_t l = strnlen(dname, 255);\n\tsize_t n;\n\n\tif (l && dname[l-1]=='.') l--;\n\tif (l && dname[l-1]=='.') return 0;\n\tn = 17+l+!!l;\n\tif (l>253 || buflen<n || op>15u) return 0;\n\n\t/* Construct query template - ID will be filled later */\n\tmemset(buf, 0, n);\n\tbuf[2] = (op<<3) | 1;\n\tbuf[5] = 1;\n\tmemcpy((char *)buf+13, dname, l);\n\tfor (i=13; buf[i]; i=j+1)\n\t{\n\t\tfor (j=i; buf[j] && buf[j] != '.'; j++);\n\t\tif (j-i-1u > 62u) return 0;\n\t\tbuf[i-1] = j-i;\n\t}\n\tbuf[i+1] = type;\n\tbuf[i+3] = class;\n\n\t/* Make a reasonably unpredictable id */\n\tclock_gettime(CLOCK_REALTIME, &ts);\n\tid = (uint16_t)ts.tv_nsec + (uint16_t)(ts.tv_nsec>>16);\n\tbuf[0] = id>>8;\n\tbuf[1] = id;\n\n\treturn n;\n}\nint dns_make_query(const char *dom, char family)\n{\n\tuint8_t q[280];\n\tsize_t l = dns_mk_query_blob(0, dom, 1, family == FAMILY6 ? 28 : 1, q, sizeof(q));\n\tif (!l)\n\t{\n\t\tfprintf(stderr, \"could not make DNS query\\n\");\n\t\treturn 1;\n\t}\n#ifdef _WIN32\n\t_setmode(_fileno(stdout), _O_BINARY);\n#endif\n\tif (fwrite(q,l,1,stdout)!=1)\n\t{\n\t\tfprintf(stderr, \"could not write DNS query blob to stdout\\n\");\n\t\treturn 10;\n\t}\n\treturn 0;\n}\n\nbool dns_parse_print(const uint8_t *a, size_t len)\n{\n\t// check of minimum header length and response flag\n\tuint16_t k, dlen, qcount = a[4]<<8 | a[5], acount = a[6]<<8 | a[7];\n\tchar s_ip[40];\n\n\tif (len<12 || !(a[2]&0x80)) return false;\n\ta+=12; len-=12;\n\tfor(k=0;k<qcount;k++)\n\t{\n\t\twhile (len && *a)\n\t\t{\n\t\t\tif ((*a+1)>len) return false;\n\t\t\t// skip to next label\n\t\t\tlen -= *a+1; a += *a+1;\n\t\t}\n\t\tif (len<5) return false;\n\t\t// skip zero length label, type, class\n\t\ta+=5; len-=5;\n\t}\n\tfor(k=0;k<acount;k++)\n\t{\n\t\t// 11 higher bits indicate pointer\n\t\tif (len<12 || (*a & 0xC0)!=0xC0) return false;\n\t\tdlen = a[10]<<8 | a[11];\n\t\tif (len<(dlen+12)) return false;\n\t\tif (a[4]==0 && a[5]==1 && a[2]==0) // IN class and higher byte of type = 0\n\t\t{\n\t\t\tswitch(a[3])\n\t\t\t{\n\t\t\t\tcase 1: // A\n\t\t\t\t\tif (dlen!=4) break;\n\t\t\t\t\tif (inet_ntop(AF_INET, a+12, s_ip, sizeof(s_ip)))\n\t\t\t\t\t\tprintf(\"%s\\n\", s_ip);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 28: // AAAA\n\t\t\t\t\tif (dlen!=16) break;\n\t\t\t\t\tif (inet_ntop(AF_INET6, a+12, s_ip, sizeof(s_ip)))\n\t\t\t\t\t\tprintf(\"%s\\n\", s_ip);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tlen -= 12+dlen; a += 12+dlen;\n\t}\n\treturn true;\n}\nint dns_parse_query()\n{\n\tuint8_t a[8192];\n\tsize_t l;\n#ifdef _WIN32\n\t_setmode(_fileno(stdin), _O_BINARY);\n#endif\n\tl = fread(a,1,sizeof(a),stdin);\n\tif (!l || ferror(stdin))\n\t{\n\t\tfprintf(stderr, \"could not read DNS reply blob from stdin\\n\");\n\t\treturn 10;\n\t}\n\tif (!dns_parse_print(a,l))\n\t{\n\t\tfprintf(stderr, \"could not parse DNS reply blob\\n\");\n\t\treturn 11;\n\t}\n\treturn 0;\n}\n\n\nstatic void exithelp(void)\n{\n\tprintf(\n\t\t\" --family=<4|6|46>\\t\\t; ipv4, ipv6, ipv4+ipv6\\n\"\n\t\t\" --threads=<threads_number>\\n\"\n\t\t\" --eagain=<eagain_retries>\\t; how many times to retry if EAI_AGAIN received. default %u\\n\"\n\t\t\" --eagain-delay=<ms>\\t\\t; time in msec to wait between EAI_AGAIN attempts. default %u\\n\"\n\t\t\" --verbose\\t\\t\\t; print query progress to stderr\\n\"\n\t\t\" --stats=N\\t\\t\\t; print resolve stats to stderr every N domains\\n\"\n\t\t\" --log-resolved=<file>\\t\\t; log successfully resolved domains to a file\\n\"\n\t\t\" --log-failed=<file>\\t\\t; log failed domains to a file\\n\"\n\t\t\" --dns-make-query=<domain>\\t; output to stdout binary blob with DNS query. use --family to specify ip version.\\n\"\n\t\t\" --dns-parse-query\\t\\t; read from stdin binary DNS answer blob and parse it to ipv4/ipv6 addresses\\n\",\n\t\tRESOLVER_EAGAIN_ATTEMPTS,\n\t\tRESOLVER_EAGAIN_DELAY\n\t);\n\texit(1);\n}\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH)\n#define PRINT_VER printf(\"github version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#else\n#define PRINT_VER printf(\"self-built version %s %s\\n\\n\", __DATE__, __TIME__)\n#endif\n\nenum opt_indices {\n\tIDX_HELP,\n\tIDX_EAGAIN,\n\tIDX_EAGAIN_DELAY,\n\tIDX_THREADS,\n\tIDX_FAMILY,\n\tIDX_VERBOSE,\n\tIDX_STATS,\n\tIDX_LOG_RESOLVED,\n\tIDX_LOG_FAILED,\n\tIDX_DNS_MAKE_QUERY,\n\tIDX_DNS_PARSE_QUERY,\n\tIDX_LAST,\n};\n\nstatic const struct option long_options[] = {\n\t[IDX_HELP] = {\"help\", no_argument, 0, 0},\n\t[IDX_THREADS] = {\"threads\", required_argument, 0, 0},\n\t[IDX_EAGAIN] = {\"eagain\", required_argument, 0, 0},\n\t[IDX_EAGAIN_DELAY] = {\"eagain-delay\", required_argument, 0, 0},\n\t[IDX_FAMILY] = {\"family\", required_argument, 0, 0},\n\t[IDX_VERBOSE] = {\"verbose\", no_argument, 0, 0},\n\t[IDX_STATS] = {\"stats\", required_argument, 0, 0},\n\t[IDX_LOG_RESOLVED] = {\"log-resolved\", required_argument, 0, 0},\n\t[IDX_LOG_FAILED] = {\"log-failed\", required_argument, 0, 0},\n\t[IDX_DNS_MAKE_QUERY] = {\"dns-make-query\", required_argument, 0, 0},\n\t[IDX_DNS_PARSE_QUERY] = {\"dns-parse-query\", no_argument, 0, 0},\n\t[IDX_LAST] = {NULL, 0, NULL, 0},\n};\n\nint main(int argc, char **argv)\n{\n\tint r, v, option_index = 0;\n\tchar fn1[256],fn2[256];\n\tchar dom[256];\n\n\tmemset(&glob, 0, sizeof(glob));\n\t*fn1 = *fn2 = *dom = 0;\n\tglob.family = FAMILY4;\n\tglob.threads = 1;\n\tglob.eagain = RESOLVER_EAGAIN_ATTEMPTS;\n\tglob.eagain_delay = RESOLVER_EAGAIN_DELAY;\n\twhile ((v = getopt_long_only(argc, argv, \"\", long_options, &option_index)) != -1)\n\t{\n\t\tif (v) exithelp();\n\t\tswitch (option_index)\n\t\t{\n\t\tcase IDX_HELP:\n\t\t\tPRINT_VER;\n\t\t\texithelp();\n\t\t\tbreak;\n\t\tcase IDX_THREADS:\n\t\t\tglob.threads = atoi(optarg);\n\t\t\tif (glob.threads <= 0 || glob.threads > 100)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"thread number must be within 1..100\\n\");\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_EAGAIN:\n\t\t\tglob.eagain = atoi(optarg);\n\t\t\tif (glob.eagain <= 0 || glob.eagain > 1000)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"eagain must be within 1..1000\\n\");\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_EAGAIN_DELAY:\n\t\t\tglob.eagain_delay = atoi(optarg);\n\t\t\tif (glob.eagain_delay < 0 || glob.eagain_delay > 100000)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"eagain-delay must be within 0..100000\\n\");\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_FAMILY:\n\t\t\tif (!strcmp(optarg, \"4\"))\n\t\t\t\tglob.family = FAMILY4;\n\t\t\telse if (!strcmp(optarg, \"6\"))\n\t\t\t\tglob.family = FAMILY6;\n\t\t\telse if (!strcmp(optarg, \"46\"))\n\t\t\t\tglob.family = FAMILY4 | FAMILY6;\n\t\t\telse\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"ip family must be 4,6 or 46\\n\");\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_VERBOSE:\n\t\t\tglob.verbose = '\\1';\n\t\t\tbreak;\n\t\tcase IDX_STATS:\n\t\t\tglob.stats_every = optarg ? atoi(optarg) : 0;\n\t\t\tbreak;\n\t\tcase IDX_LOG_RESOLVED:\n\t\t\tstrncpy(fn1,optarg,sizeof(fn1));\n\t\t\tfn1[sizeof(fn1)-1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_LOG_FAILED:\n\t\t\tstrncpy(fn2,optarg,sizeof(fn2));\n\t\t\tfn2[sizeof(fn2)-1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_DNS_MAKE_QUERY:\n\t\t\tstrncpy(dom,optarg,sizeof(dom));\n\t\t\tdom[sizeof(dom)-1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_DNS_PARSE_QUERY:\n\t\t\treturn dns_parse_query();\n\t\t}\n\t}\n\n#ifdef _WIN32\n\tWSADATA wsaData;\n\tif (WSAStartup(MAKEWORD(2, 2), &wsaData))\n\t{\n\t\tfprintf(stderr,\"WSAStartup failed\\n\");\n\t\treturn 4;\n\t}\n#endif\n\n\tif (*dom) return dns_make_query(dom, glob.family);\n\n\tif (*fn1)\n\t{\n\t\tglob.F_log_resolved = fopen(fn1,\"wt\");\n\t\tif (!glob.F_log_resolved)\n\t\t{\n\t\t\tfprintf(stderr,\"failed to create %s\\n\",fn1);\n\t\t\tr=5; goto ex;\n\t\t}\n\t}\n\tif (*fn2)\n\t{\n\t\tglob.F_log_failed = fopen(fn2,\"wt\");\n\t\tif (!glob.F_log_failed)\n\t\t{\n\t\t\tfprintf(stderr,\"failed to create %s\\n\",fn2);\n\t\t\tr=5; goto ex;\n\t\t}\n\t}\n\n\tr = run_threads();\n\nex:\n\tif (glob.F_log_resolved) fclose(glob.F_log_resolved);\n\tif (glob.F_log_failed) fclose(glob.F_log_failed);\n\n\treturn r;\n}\n"
  },
  {
    "path": "nfq/BSDmakefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto -Wno-address-of-packed-member\nLIBS = -lz\nSRC_FILES = *.c crypto/*.c\n\nall: dvtws\n\ndvtws: $(SRC_FILES)\n\t$(CC) $(CFLAGS) -o dvtws $(SRC_FILES) $(LIBS) $(LDFLAGS)\n\nclean:\n\trm -f dvtws\n"
  },
  {
    "path": "nfq/Makefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -ffunction-sections -fdata-sections\nCFLAGS_SYSTEMD = -DUSE_SYSTEMD\nCFLAGS_BSD = -Wno-address-of-packed-member\nCFLAGS_CYGWIN = -Wno-address-of-packed-member -static\nLDFLAGS += -flto=auto\nLDFLAGS_ANDROID = -Wl,--gc-sections -llog\nLDFLAGS_BSD = -Wl,--gc-sections\nLDFLAGS_LINUX = -Wl,--gc-sections\nLDFLAGS_MAC = -Wl,-dead_strip\nLDFLAGS_WIN = -Wl,--gc-sections\nLIBS_LINUX = -lz -lnetfilter_queue -lnfnetlink -lmnl\nLIBS_SYSTEMD = -lsystemd\nLIBS_BSD = -lz\nLIBS_CYGWIN = -lz -Lwindows/windivert -Iwindows -lwlanapi -lole32 -loleaut32\nLIBS_CYGWIN32 = -lwindivert32\nLIBS_CYGWIN64 = -lwindivert64\nRES_CYGWIN32 = windows/res/32/winmanifest.o windows/res/32/winicon.o\nRES_CYGWIN64 = windows/res/64/winmanifest.o windows/res/64/winicon.o\nSRC_FILES = *.c crypto/*.c\n\nall: nfqws\n\nnfqws: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_LINUX)\n\nsystemd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LIBS_SYSTEMD) $(LDFLAGS) $(LDFLAGS_LINUX)\n\nandroid: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_ANDROID)\n\nbsd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o dvtws $(SRC_FILES) $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_BSD)\n\nmac: $(SRC_FILES)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsa $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_MAC)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_MAC)\n\tstrip dvtwsa dvtwsx\n\tlipo -create -output dvtws dvtwsx dvtwsa\n\trm -f dvtwsx dvtwsa\n\ncygwin64:\n\t$(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN64) $(RES_CYGWIN64) $(LDFLAGS) $(LDFLAGS_WIN)\ncygwin32:\n\t$(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN32) $(RES_CYGWIN32) $(LDFLAGS) $(LDFLAGS_WIN)\ncygwin: cygwin64\n\nclean:\n\trm -f nfqws dvtws winws.exe\n"
  },
  {
    "path": "nfq/checksum.c",
    "content": "#define _GNU_SOURCE\n#include \"checksum.h\"\n#include <netinet/in.h>\n\n//#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))\n//#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))\n\nstatic uint16_t from64to16(uint64_t x)\n{\n\tuint32_t u = (uint32_t)(uint16_t)x + (uint16_t)(x>>16) + (uint16_t)(x>>32) + (uint16_t)(x>>48);\n\treturn (uint16_t)u + (uint16_t)(u>>16);\n}\n\n// this function preserves data alignment requirements (otherwise it will be damn slow on mips arch)\n// and uses 64-bit arithmetics to improve speed\n// taken from linux source code\nstatic uint16_t do_csum(const uint8_t * buff, size_t len)\n{\n\tuint8_t odd;\n\tsize_t count;\n\tuint64_t result,w,carry=0;\n\tuint16_t u16;\n\n\tif (!len) return 0;\n\todd = (uint8_t)(1 & (size_t)buff);\n\tif (odd)\n\t{\n\t\t// any endian compatible\n\t\tu16 = 0;\n\t\t*((uint8_t*)&u16+1) = *buff;\n\t\tresult = u16;\n\t\tlen--;\n\t\tbuff++;\n\t}\n\telse\n\t\tresult = 0;\n\tcount = len >> 1;\t\t/* nr of 16-bit words.. */\n\tif (count)\n\t{\n\t\tif (2 & (size_t) buff)\n\t\t{\n\t\t\tresult += *(uint16_t *) buff;\n\t\t\tcount--;\n\t\t\tlen -= 2;\n\t\t\tbuff += 2;\n\t\t}\n\t\tcount >>= 1;\t\t/* nr of 32-bit words.. */\n\t\tif (count)\n\t\t{\n\t\t\tif (4 & (size_t) buff)\n\t\t\t{\n\t\t\t\tresult += *(uint32_t *) buff;\n\t\t\t\tcount--;\n\t\t\t\tlen -= 4;\n\t\t\t\tbuff += 4;\n\t\t\t}\n\t\t\tcount >>= 1;\t/* nr of 64-bit words.. */\n\t\t\tif (count)\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tw = *(uint64_t *) buff;\n\t\t\t\t\tcount--;\n\t\t\t\t\tbuff += 8;\n\t\t\t\t\tresult += carry;\n\t\t\t\t\tresult += w;\n\t\t\t\t\tcarry = (w > result);\n\t\t\t\t} while (count);\n\t\t\t\tresult += carry;\n\t\t\t\tresult = (result & 0xffffffff) + (result >> 32);\n\t\t\t}\n\t\t\tif (len & 4)\n\t\t\t{\n\t\t\t\tresult += *(uint32_t *) buff;\n\t\t\t\tbuff += 4;\n\t\t\t}\n\t\t}\n\t\tif (len & 2)\n\t\t{\n\t\t\tresult += *(uint16_t *) buff;\n\t\t\tbuff += 2;\n\t\t}\n\t}\n\tif (len & 1)\n\t{\n\t\t// any endian compatible\n\t\tu16 = 0;\n\t\t*(uint8_t*)&u16 = *buff;\n\t\tresult += u16;\n\t}\n\tu16 = from64to16(result);\n\tif (odd) u16 = ((u16 >> 8) & 0xff) | ((u16 & 0xff) << 8);\n\treturn u16;\n}\n\nuint16_t csum_partial(const void *buff, size_t len)\n{\n\treturn do_csum(buff,len);\n}\n\nuint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum)\n{\n\treturn ~from64to16((uint64_t)saddr + daddr + sum + htonl(len+proto));\n}\n\nuint16_t ip4_compute_csum(const void *buff, size_t len)\n{\n\treturn ~from64to16(do_csum(buff,len));\n}\nvoid ip4_fix_checksum(struct ip *ip)\n{\n\tip->ip_sum = 0;\n\tip->ip_sum = ip4_compute_csum(ip, ip->ip_hl<<2);\n}\n\nuint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum)\n{\n\tuint64_t a = (uint64_t)sum + htonl(len+proto) +\n\t\t\t*(uint32_t*)saddr + *((uint32_t*)saddr+1) + *((uint32_t*)saddr+2) + *((uint32_t*)saddr+3) + \n\t\t\t*(uint32_t*)daddr + *((uint32_t*)daddr+1) + *((uint32_t*)daddr+2) + *((uint32_t*)daddr+3);\n\treturn ~from64to16(a);\n}\n\n\nvoid tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr)\n{\n\ttcp->th_sum = 0;\n\ttcp->th_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_TCP,csum_partial(tcp,len));\n}\nvoid tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr)\n{\n\ttcp->th_sum = 0;\n\ttcp->th_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_TCP,csum_partial(tcp,len));\n}\nvoid tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr)\n{\n\tif (ip)\n\t\ttcp4_fix_checksum(tcp, len, &ip->ip_src, &ip->ip_dst);\n\telse if (ip6hdr)\n\t\ttcp6_fix_checksum(tcp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst);\n}\n\nvoid udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr)\n{\n\tudp->uh_sum = 0;\n\tudp->uh_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_UDP,csum_partial(udp,len));\n}\nvoid udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr)\n{\n\tudp->uh_sum = 0;\n\tudp->uh_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_UDP,csum_partial(udp,len));\n}\nvoid udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr)\n{\n\tif (ip)\n\t\tudp4_fix_checksum(udp, len, &ip->ip_src, &ip->ip_dst);\n\telse if (ip6hdr)\n\t\tudp6_fix_checksum(udp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst);\n}\n"
  },
  {
    "path": "nfq/checksum.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/types.h>\n#include <netinet/in.h>\n\n#define __FAVOR_BSD\n#include <netinet/ip6.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <netinet/udp.h>\n\nuint16_t csum_partial(const void *buff, size_t len);\nuint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum);\nuint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum);\n\nuint16_t ip4_compute_csum(const void *buff, size_t len);\nvoid ip4_fix_checksum(struct ip *ip);\n\nvoid tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr);\nvoid tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr);\nvoid tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr);\n\nvoid udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr);\nvoid udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr);\nvoid udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr);\n"
  },
  {
    "path": "nfq/conntrack.c",
    "content": "#include \"conntrack.h\"\n#include \"darkmagic.h\"\n#include <arpa/inet.h>\n#include <stdio.h>\n\n#undef uthash_nonfatal_oom\n#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)\n\nstatic bool oom = false;\nstatic void ut_oom_recover(void *elem)\n{\n\toom = true;\n}\n\nstatic const char *connstate_s[]={\"SYN\",\"ESTABLISHED\",\"FIN\"};\n\nstatic void connswap(const t_conn *c, t_conn *c2)\n{\n\tmemset(c2,0,sizeof(*c2));\n\tc2->l3proto = c->l3proto;\n\tc2->l4proto = c->l4proto;\n\tc2->src = c->dst;\n\tc2->dst = c->src;\n\tc2->sport = c->dport;\n\tc2->dport = c->sport;\n}\n\nvoid ConntrackClearHostname(t_ctrack *track)\n{\n\tfree(track->hostname);\n\ttrack->hostname = NULL;\n\ttrack->hostname_is_ip = false;\n}\nstatic void ConntrackClearTrack(t_ctrack *track)\n{\n\tConntrackClearHostname(track);\n\tReasmClear(&track->reasm_orig);\n\trawpacket_queue_destroy(&track->delayed);\n}\n\nstatic void ConntrackFreeElem(t_conntrack_pool *elem)\n{\n\tConntrackClearTrack(&elem->track);\n\tfree(elem);\n}\n\nstatic void ConntrackPoolDestroyPool(t_conntrack_pool **pp)\n{\n\tt_conntrack_pool *elem, *tmp;\n\tHASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); }\n}\nvoid ConntrackPoolDestroy(t_conntrack *p)\n{\n\tConntrackPoolDestroyPool(&p->pool);\n}\n\nvoid ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp)\n{\n\tp->timeout_syn = timeout_syn;\n\tp->timeout_established = timeout_established;\n\tp->timeout_fin = timeout_fin;\n\tp->timeout_udp= timeout_udp;\n\tp->t_purge_interval = purge_interval;\n\ttime(&p->t_last_purge);\n\tp->pool = NULL;\n}\n\nvoid ConntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr)\n{\n\tmemset(c,0,sizeof(*c));\n\tif (ip)\n\t{\n\t\tc->l3proto = IPPROTO_IP;\n\t\tc->dst.ip = bReverse ? ip->ip_src : ip->ip_dst;\n\t\tc->src.ip = bReverse ? ip->ip_dst : ip->ip_src;\n\t}\n\telse if (ip6)\n\t{\n\t\tc->l3proto = IPPROTO_IPV6;\n\t\tc->dst.ip6 = bReverse ? ip6->ip6_src : ip6->ip6_dst;\n\t\tc->src.ip6 = bReverse ? ip6->ip6_dst : ip6->ip6_src;\n\t}\n\telse\n\t\tc->l3proto = -1;\n\textract_ports(tcphdr, udphdr, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport);\n}\n\n\nstatic t_conntrack_pool *ConntrackPoolSearch(t_conntrack_pool *p, const t_conn *c)\n{\n\tt_conntrack_pool *t;\n\tHASH_FIND(hh, p, c, sizeof(*c), t);\n\treturn t;\n}\n\nstatic void ConntrackInitTrack(t_ctrack *t)\n{\n\tmemset(t,0,sizeof(*t));\n\tt->scale_orig = t->scale_reply = SCALE_NONE;\n\ttime(&t->t_start);\n\trawpacket_queue_init(&t->delayed);\n}\nstatic void ConntrackReInitTrack(t_ctrack *t)\n{\n\tConntrackClearTrack(t);\n\tConntrackInitTrack(t);\n}\n\nstatic t_conntrack_pool *ConntrackNew(t_conntrack_pool **pp, const t_conn *c)\n{\n\tt_conntrack_pool *ctnew;\n\tif (!(ctnew = malloc(sizeof(*ctnew)))) return NULL;\n\tctnew->conn = *c;\n\toom = false;\n\tHASH_ADD(hh, *pp, conn, sizeof(*c), ctnew);\n\tif (oom) { free(ctnew); return NULL; }\n\tConntrackInitTrack(&ctnew->track);\n\treturn ctnew;\n}\n\n// non-tcp packets are passed with tcphdr=NULL but len_payload filled\nstatic void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr *tcphdr, uint32_t len_payload)\n{\n\tuint8_t scale;\n\n\tif (bReverse)\n\t{\n\t\tt->pcounter_reply++;\n\t\tt->pdcounter_reply+=!!len_payload;\n\t\t\n\t}\n\telse\n\t{\n\t\tt->pcounter_orig++;\n\t\tt->pdcounter_orig+=!!len_payload;\n\t}\n\n\tif (tcphdr)\n\t{\n\t\tif (tcp_syn_segment(tcphdr))\n\t\t{\n\t\t\tif (t->state!=SYN) ConntrackReInitTrack(t); // erase current entry\n\t\t\tt->seq0 = ntohl(tcphdr->th_seq);\n\t\t}\n\t\telse if (tcp_synack_segment(tcphdr))\n\t\t{\n\t\t\t// ignore SA dups\n\t\t\tuint32_t seq0 = ntohl(tcphdr->th_ack)-1;\n\t\t\tif (t->state!=SYN && t->seq0!=seq0)\n\t\t\t\tConntrackReInitTrack(t); // erase current entry\n\t\t\tif (!t->seq0) t->seq0 = seq0;\n\t\t\tt->ack0 = ntohl(tcphdr->th_seq);\n\t\t}\n\t\telse if (tcphdr->th_flags & (TH_FIN|TH_RST))\n\t\t{\n\t\t\tt->state = FIN;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (t->state==SYN) \n\t\t\t{\n\t\t\t\tt->state=ESTABLISHED;\n\t\t\t\tif (!bReverse && !t->ack0) t->ack0 = ntohl(tcphdr->th_ack)-1;\n\t\t\t}\n\t\t}\n\t\tscale = tcp_find_scale_factor(tcphdr);\n\t\tif (bReverse)\n\t\t{\n\t\t\tt->pos_orig = t->seq_last = ntohl(tcphdr->th_ack);\n\t\t\tt->ack_last = ntohl(tcphdr->th_seq);\n\t\t\tt->pos_reply = t->ack_last + len_payload;\n\t\t\tt->winsize_reply = ntohs(tcphdr->th_win);\n\t\t\tif (scale!=SCALE_NONE) t->scale_reply = scale;\n\t\t\t\n\t\t}\n\t\telse\n\t\t{\n\t\t\tt->seq_last = ntohl(tcphdr->th_seq);\n\t\t\tt->pos_orig = t->seq_last + len_payload;\n\t\t\tt->pos_reply = t->ack_last = ntohl(tcphdr->th_ack);\n\t\t\tt->winsize_orig = ntohs(tcphdr->th_win);\n\t\t\tif (scale!=SCALE_NONE) t->scale_orig = scale;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (bReverse)\n\t\t{\n\t\t\tt->ack_last=t->pos_reply;\n\t\t\tt->pos_reply+=len_payload;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tt->seq_last=t->pos_orig;\n\t\t\tt->pos_orig+=len_payload;\n\t\t}\n\t}\n\n\ttime(&t->t_last);\n}\n\nstatic bool ConntrackPoolDoubleSearchPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse)\n{\n\tt_conn conn,connswp;\n\tt_conntrack_pool *ctr;\n\n\tConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr);\n\tif ((ctr=ConntrackPoolSearch(*pp,&conn)))\n\t{\n\t\tif (bReverse) *bReverse = false;\n\t\tif (ctrack) *ctrack = &ctr->track;\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\tconnswap(&conn,&connswp);\n\t\tif ((ctr=ConntrackPoolSearch(*pp,&connswp)))\n\t\t{\n\t\t\tif (bReverse) *bReverse = true;\n\t\t\tif (ctrack) *ctrack = &ctr->track;\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\nbool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse)\n{\n\treturn ConntrackPoolDoubleSearchPool(&p->pool, ip, ip6, tcphdr, udphdr, ctrack, bReverse);\n}\n\nstatic bool ConntrackPoolFeedPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse)\n{\n\tt_conn conn, connswp;\n\tt_conntrack_pool *ctr;\n\tbool b_rev;\n\n\tConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr);\n\tif ((ctr=ConntrackPoolSearch(*pp,&conn)))\n\t{\n\t\tConntrackFeedPacket(&ctr->track, (b_rev=false), tcphdr, len_payload);\n\t\tgoto ok;\n\t}\n\telse\n\t{\n\t\tconnswap(&conn,&connswp);\n\t\tif ((ctr=ConntrackPoolSearch(*pp,&connswp)))\n\t\t{\n\t\t\tConntrackFeedPacket(&ctr->track, (b_rev=true), tcphdr, len_payload);\n\t\t\tgoto ok;\n\t\t}\n\t}\n\tb_rev = tcphdr && tcp_synack_segment(tcphdr);\n\tif ((tcphdr && tcp_syn_segment(tcphdr)) || b_rev || udphdr)\n\t{\n\t\tif ((ctr=ConntrackNew(pp, b_rev ? &connswp : &conn)))\n\t\t{\n\t\t\tConntrackFeedPacket(&ctr->track, b_rev, tcphdr, len_payload);\n\t\t\tgoto ok;\n\t\t}\n\t}\n\treturn false;\nok:\n\tif (ctrack) *ctrack = &ctr->track;\n\tif (bReverse) *bReverse = b_rev;\n\treturn true;\n}\nbool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse)\n{\n\treturn ConntrackPoolFeedPool(&p->pool,ip,ip6,tcphdr,udphdr,len_payload,ctrack,bReverse);\n}\n\nstatic bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr)\n{\n\tt_conn conn, connswp;\n\tt_conntrack_pool *t;\n\tConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr);\n\tif (!(t=ConntrackPoolSearch(*pp,&conn)))\n\t{\n\t\tconnswap(&conn,&connswp);\n\t\tt=ConntrackPoolSearch(*pp,&connswp);\n\t}\n\tif (!t) return false;\n\tHASH_DEL(*pp, t); ConntrackFreeElem(t);\n\treturn true;\n}\nbool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr)\n{\n\treturn ConntrackPoolDropPool(&p->pool,ip,ip6,tcphdr,udphdr);\n}\n\nvoid ConntrackPoolPurge(t_conntrack *p)\n{\n\ttime_t tidle, tnow = time(NULL);\n\tt_conntrack_pool *t, *tmp;\n\n\tif ((tnow - p->t_last_purge)>=p->t_purge_interval)\n\t{\n\t\tHASH_ITER(hh, p->pool , t, tmp) {\n\t\t\ttidle = tnow - t->track.t_last;\n\t\t\tif (\tt->track.b_cutoff ||\n\t\t\t\t(t->conn.l4proto==IPPROTO_TCP && (\n\t\t\t\t\t(t->track.state==SYN && tidle>=p->timeout_syn) ||\n\t\t\t\t\t(t->track.state==ESTABLISHED && tidle>=p->timeout_established) ||\n\t\t\t\t\t(t->track.state==FIN && tidle>=p->timeout_fin))\n\t\t\t\t) || (t->conn.l4proto==IPPROTO_UDP && tidle>=p->timeout_udp)\n\t\t\t)\n\t\t\t{\n\t\t\t\tHASH_DEL(p->pool, t); ConntrackFreeElem(t); \n\t\t\t}\n\t\t}\n\t\tp->t_last_purge = tnow;\n\t}\n}\n\nstatic void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsize)\n{\n\tif (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0;\n}\n\nvoid ConntrackPoolDump(const t_conntrack *p)\n{\n\tt_conntrack_pool *t, *tmp;\n\tchar sa1[40],sa2[40];\n\ttime_t tnow = time(NULL);\n\tHASH_ITER(hh, p->pool, t, tmp) {\n\t\ttaddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1));\n\t\ttaddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2));\n\t\tprintf(\"%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu \",\n\t\t\tproto_name(t->conn.l4proto),\n\t\t\tsa1, t->conn.sport, sa2, t->conn.dport,\n\t\t\tt->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : \"-\",\n\t\t\t(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),\n\t\t\t(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,\n\t\t\t(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply);\n\t\tif (t->conn.l4proto==IPPROTO_TCP)\n\t\t\tprintf(\"seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d\",\n\t\t\t\tt->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0,\n\t\t\t\tt->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0,\n\t\t\t\tt->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig,\n\t\t\t\tt->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply);\n\t\telse\n\t\t\tprintf(\"rseq=%u pos_orig=%u rack=%u pos_reply=%u\",\n\t\t\t\tt->track.seq_last, t->track.pos_orig,\n\t\t\t\tt->track.ack_last, t->track.pos_reply);\n\t\tprintf(\" req_retrans=%u cutoff=%u wss_cutoff=%u desync_cutoff=%u dup_cutoff=%u orig_cutoff=%u hostname=%s l7proto=%s\\n\",\n\t\t\tt->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.b_dup_cutoff, t->track.b_orig_mod_cutoff, t->track.hostname, l7proto_str(t->track.l7proto));\n\t};\n}\n\n\nvoid ReasmClear(t_reassemble *reasm)\n{\n\tfree(reasm->packet);\n\treasm->packet = NULL;\n\treasm->size = reasm->size_present = 0;\n}\nbool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start)\n{\n\treasm->packet = malloc(size_requested);\n\tif (!reasm->packet) return false;\n\treasm->size = size_requested;\n\treasm->size_present = 0;\n\treasm->seq = seq_start;\n\treturn true;\n}\nbool ReasmResize(t_reassemble *reasm, size_t new_size)\n{\n\tuint8_t *p = realloc(reasm->packet, new_size);\n\tif (!p) return false;\n\treasm->packet = p;\n\treasm->size = new_size;\n\tif (reasm->size_present > new_size) reasm->size_present = new_size;\n\treturn true;\n}\nbool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len)\n{\n\tif (reasm->seq!=seq) return false; // fail session if out of sequence\n\t\n\tsize_t szcopy;\n\tszcopy = reasm->size - reasm->size_present;\n\tif (len<szcopy) szcopy = len;\n\tmemcpy(reasm->packet + reasm->size_present, payload, szcopy);\n\treasm->size_present += szcopy;\n\treasm->seq += (uint32_t)szcopy;\n\n\treturn true;\n}\nbool ReasmHasSpace(t_reassemble *reasm, size_t len)\n{\n\treturn (reasm->size_present+len)<=reasm->size;\n}\n"
  },
  {
    "path": "nfq/conntrack.h",
    "content": "#pragma once\n\n\n// this conntrack is not bullet-proof\n// its designed to satisfy dpi desync needs only\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <ctype.h>\n#include <sys/types.h>\n#include <time.h>\n#include <netinet/in.h>\n\n#define __FAVOR_BSD\n#include <netinet/ip.h>\n#include <netinet/ip6.h>\n#include <netinet/tcp.h>\n#include <netinet/udp.h>\n\n#include \"packet_queue.h\"\n#include \"protocol.h\"\n\n//#define HASH_BLOOM 20\n#define HASH_NONFATAL_OOM 1\n#undef HASH_FUNCTION\n#define HASH_FUNCTION HASH_BER\n#include \"uthash.h\"\n\n#define RETRANS_COUNTER_STOP ((uint8_t)-1)\n\ntypedef union {\n\tstruct in_addr ip;\n\tstruct in6_addr ip6;\n} t_addr;\ntypedef struct\n{\n\tt_addr src, dst;\n\tuint16_t sport,dport;\n\tuint8_t\tl3proto; // IPPROTO_IP, IPPROTO_IPV6\n\tuint8_t l4proto; // IPPROTO_TCP, IPPROTO_UDP\n} t_conn;\n\n// this structure helps to reassemble continuous packets streams. it does not support out-of-orders\ntypedef struct {\n\tuint8_t *packet;\t\t// allocated for size during reassemble request. requestor must know the message size.\n\tuint32_t seq;\t\t\t// current seq number. if a packet comes with an unexpected seq - it fails reassemble session.\n\tsize_t size;\t\t\t// expected message size. success means that we have received exactly 'size' bytes and have them in 'packet'\n\tsize_t size_present;\t\t// how many bytes already stored in 'packet'\n} t_reassemble;\n\n// SYN - SYN or SYN/ACK received\n// ESTABLISHED - any except SYN or SYN/ACK received\n// FIN - FIN or RST received\ntypedef enum {SYN=0, ESTABLISHED, FIN} t_connstate;\n\ntypedef struct\n{\n\tbool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache\n\n\tstruct desync_profile *dp;\t\t// desync profile cache\n\tbool dp_search_complete;\n\n\t// common state\n\ttime_t t_start, t_last;\n\tuint64_t pcounter_orig, pcounter_reply;\t// packet counter\n\tuint64_t pdcounter_orig, pdcounter_reply; // data packet counter (with payload)\n\tuint32_t pos_orig, pos_reply;\t\t// TCP: seq_last+payload, ack_last+payload  UDP: sum of all seen payload lenghts including current\n\tuint32_t seq_last, ack_last;\t\t// TCP: last seen seq and ack  UDP: sum of all seen payload lenghts NOT including current\n\n\t// tcp only state, not used in udp\n\tt_connstate state;\n\tuint32_t seq0, ack0;\t\t\t// starting seq and ack\n\tuint16_t winsize_orig, winsize_reply;\t// last seen window size\n\tuint8_t scale_orig, scale_reply;\t// last seen window scale factor. SCALE_NONE if none\n\t\n\tuint8_t req_retrans_counter;\t\t// number of request retransmissions\n\tbool req_seq_present,req_seq_finalized,req_seq_abandoned;\n\tuint32_t req_seq_start,req_seq_end;\t// sequence interval of the request (to track retransmissions)\n\n\tuint8_t incoming_ttl, desync_autottl, orig_autottl, dup_autottl;\n\tbool b_autottl_discovered;\n\n\tbool b_cutoff;\t\t\t\t// mark for deletion\n\tbool b_wssize_cutoff, b_desync_cutoff, b_dup_cutoff, b_orig_mod_cutoff;\n\n\tuint16_t ip_id;\n\n\tt_l7proto l7proto;\n\tbool l7proto_discovered;\n\tchar *hostname;\n\tbool hostname_is_ip;\n\tbool hostname_discovered;\n\tbool hostname_ah_check;\t\t\t// should perform autohostlist checks\n\n\tt_reassemble reasm_orig;\n\tstruct rawpacket_tailhead delayed;\n} t_ctrack;\n\ntypedef struct\n{\n\tt_ctrack track;\n\tUT_hash_handle hh;\t// makes this structure hashable\n\tt_conn conn;\t\t// key\n} t_conntrack_pool;\ntypedef struct\n{\n\t// inactivity time to purge an entry in each connection state\n\tuint32_t timeout_syn,timeout_established,timeout_fin,timeout_udp;\n\ttime_t t_purge_interval, t_last_purge;\n\tt_conntrack_pool *pool;\n} t_conntrack;\n\nvoid ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp);\nvoid ConntrackPoolDestroy(t_conntrack *p);\nbool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse);\n// do not create, do not update. only find existing\nbool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse);\nbool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr);\nvoid CaonntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr);\nvoid ConntrackPoolDump(const t_conntrack *p);\nvoid ConntrackPoolPurge(t_conntrack *p);\nvoid ConntrackClearHostname(t_ctrack *track);\n\nbool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start);\nbool ReasmResize(t_reassemble *reasm, size_t new_size);\nvoid ReasmClear(t_reassemble *reasm);\n// false means reassemble session has failed and we should ReasmClear() it\nbool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len);\n// check if it has enough space to buffer 'len' bytes\nbool ReasmHasSpace(t_reassemble *reasm, size_t len);\ninline static bool ReasmIsEmpty(t_reassemble *reasm) {return !reasm->size;}\ninline static bool ReasmIsFull(t_reassemble *reasm) {return !ReasmIsEmpty(reasm) && (reasm->size==reasm->size_present);}\n"
  },
  {
    "path": "nfq/crypto/aes-gcm.c",
    "content": "#include \"aes-gcm.h\"\n\nint aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len)\n{\n\tint ret = 0;\n\tgcm_context ctx;\n\n\tgcm_initialize();\n\n\tif (!(ret = gcm_setkey(&ctx, key, (const uint)key_len)))\n\t{\n\t\tret = gcm_crypt_and_tag(&ctx, mode, iv, iv_len, adata, adata_len, input, output, input_length, atag, atag_len);\n\t\tgcm_zero_ctx(&ctx);\n\t}\n\n\treturn ret;\n}\n"
  },
  {
    "path": "nfq/crypto/aes-gcm.h",
    "content": "#pragma once\n\n#include \"gcm.h\"  \n\n// mode : AES_ENCRYPT, AES_DECRYPT\nint aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len);\n"
  },
  {
    "path": "nfq/crypto/aes.c",
    "content": "/******************************************************************************\n*\n* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL\n*\n* This is a simple and straightforward implementation of the AES Rijndael\n* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus\n* of this work was correctness & accuracy.  It is written in 'C' without any\n* particular focus upon optimization or speed. It should be endian (memory\n* byte order) neutral since the few places that care are handled explicitly.\n*\n* This implementation of Rijndael was created by Steven M. Gibson of GRC.com.\n*\n* It is intended for general purpose use, but was written in support of GRC's\n* reference implementation of the SQRL (Secure Quick Reliable Login) client.\n*\n* See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html\n*\n* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE\n* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.\n*\n*******************************************************************************/\n\n#include \"aes.h\"\n\nstatic int aes_tables_inited = 0;   // run-once flag for performing key\n\t\t\t\t\t\t\t\t\t// expasion table generation (see below)\n/*\n *  The following static local tables must be filled-in before the first use of\n *  the GCM or AES ciphers. They are used for the AES key expansion/scheduling\n *  and once built are read-only and thread safe. The \"gcm_initialize\" function\n *  must be called once during system initialization to populate these arrays\n *  for subsequent use by the AES key scheduler. If they have not been built\n *  before attempted use, an error will be returned to the caller.\n *\n *  NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since\n *  GCM uses AES in counter-mode, where the AES cipher output is XORed with\n *  the GCM input, we ONLY NEED AES encryption.  Thus, to save space AES\n *  decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h.\n */\n // We always need our forward tables\nstatic uchar FSb[256];      // Forward substitution box (FSb)\nstatic uint32_t FT0[256];   // Forward key schedule assembly tables\nstatic uint32_t FT1[256];\nstatic uint32_t FT2[256];\nstatic uint32_t FT3[256];\n\n#if AES_DECRYPTION          // We ONLY need reverse for decryption\nstatic uchar RSb[256];      // Reverse substitution box (RSb)\nstatic uint32_t RT0[256];   // Reverse key schedule assembly tables\nstatic uint32_t RT1[256];\nstatic uint32_t RT2[256];\nstatic uint32_t RT3[256];\n#endif                      /* AES_DECRYPTION */\n\nstatic uint32_t RCON[10];   // AES round constants\n\n/*\n * Platform Endianness Neutralizing Load and Store Macro definitions\n * AES wants platform-neutral Little Endian (LE) byte ordering\n */\n#define GET_UINT32_LE(n,b,i) {                  \\\n    (n) = ( (uint32_t) (b)[(i)    ]       )     \\\n        | ( (uint32_t) (b)[(i) + 1] <<  8 )     \\\n        | ( (uint32_t) (b)[(i) + 2] << 16 )     \\\n        | ( (uint32_t) (b)[(i) + 3] << 24 ); }\n\n#define PUT_UINT32_LE(n,b,i) {                  \\\n    (b)[(i)    ] = (uchar) ( (n)       );       \\\n    (b)[(i) + 1] = (uchar) ( (n) >>  8 );       \\\n    (b)[(i) + 2] = (uchar) ( (n) >> 16 );       \\\n    (b)[(i) + 3] = (uchar) ( (n) >> 24 ); }\n\n /*\n  *  AES forward and reverse encryption round processing macros\n  */\n#define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3)     \\\n{                                               \\\n    X0 = *RK++ ^ FT0[ ( Y0       ) & 0xFF ] ^   \\\n                 FT1[ ( Y1 >>  8 ) & 0xFF ] ^   \\\n                 FT2[ ( Y2 >> 16 ) & 0xFF ] ^   \\\n                 FT3[ ( Y3 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X1 = *RK++ ^ FT0[ ( Y1       ) & 0xFF ] ^   \\\n                 FT1[ ( Y2 >>  8 ) & 0xFF ] ^   \\\n                 FT2[ ( Y3 >> 16 ) & 0xFF ] ^   \\\n                 FT3[ ( Y0 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X2 = *RK++ ^ FT0[ ( Y2       ) & 0xFF ] ^   \\\n                 FT1[ ( Y3 >>  8 ) & 0xFF ] ^   \\\n                 FT2[ ( Y0 >> 16 ) & 0xFF ] ^   \\\n                 FT3[ ( Y1 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X3 = *RK++ ^ FT0[ ( Y3       ) & 0xFF ] ^   \\\n                 FT1[ ( Y0 >>  8 ) & 0xFF ] ^   \\\n                 FT2[ ( Y1 >> 16 ) & 0xFF ] ^   \\\n                 FT3[ ( Y2 >> 24 ) & 0xFF ];    \\\n}\n\n#define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3)     \\\n{                                               \\\n    X0 = *RK++ ^ RT0[ ( Y0       ) & 0xFF ] ^   \\\n                 RT1[ ( Y3 >>  8 ) & 0xFF ] ^   \\\n                 RT2[ ( Y2 >> 16 ) & 0xFF ] ^   \\\n                 RT3[ ( Y1 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X1 = *RK++ ^ RT0[ ( Y1       ) & 0xFF ] ^   \\\n                 RT1[ ( Y0 >>  8 ) & 0xFF ] ^   \\\n                 RT2[ ( Y3 >> 16 ) & 0xFF ] ^   \\\n                 RT3[ ( Y2 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X2 = *RK++ ^ RT0[ ( Y2       ) & 0xFF ] ^   \\\n                 RT1[ ( Y1 >>  8 ) & 0xFF ] ^   \\\n                 RT2[ ( Y0 >> 16 ) & 0xFF ] ^   \\\n                 RT3[ ( Y3 >> 24 ) & 0xFF ];    \\\n                                                \\\n    X3 = *RK++ ^ RT0[ ( Y3       ) & 0xFF ] ^   \\\n                 RT1[ ( Y2 >>  8 ) & 0xFF ] ^   \\\n                 RT2[ ( Y1 >> 16 ) & 0xFF ] ^   \\\n                 RT3[ ( Y0 >> 24 ) & 0xFF ];    \\\n}\n\n  /*\n   *  These macros improve the readability of the key\n   *  generation initialization code by collapsing\n   *  repetitive common operations into logical pieces.\n   */\n#define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 )\n#define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) )\n#define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 )\n#define MIX(x,y) { y = ( (y << 1) | (y >> 7) ) & 0xFF; x ^= y; }\n#define CPY128   { *RK++ = *SK++; *RK++ = *SK++; \\\n                   *RK++ = *SK++; *RK++ = *SK++; }\n\n   /******************************************************************************\n\t*\n\t*  AES_INIT_KEYGEN_TABLES\n\t*\n\t*  Fills the AES key expansion tables allocated above with their static\n\t*  data. This is not \"per key\" data, but static system-wide read-only\n\t*  table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once\n\t*  at system initialization to setup the tables for all subsequent use.\n\t*\n\t******************************************************************************/\nvoid aes_init_keygen_tables(void)\n{\n\tint i, x, y, z;     // general purpose iteration and computation locals\n\tint pow[256];\n\tint log[256];\n\n\tif (aes_tables_inited) return;\n\n\t// fill the 'pow' and 'log' tables over GF(2^8)\n\tfor (i = 0, x = 1; i < 256; i++) {\n\t\tpow[i] = x;\n\t\tlog[x] = i;\n\t\tx = (x ^ XTIME(x)) & 0xFF;\n\t}\n\t// compute the round constants\n\tfor (i = 0, x = 1; i < 10; i++) {\n\t\tRCON[i] = (uint32_t)x;\n\t\tx = XTIME(x) & 0xFF;\n\t}\n\t// fill the forward and reverse substitution boxes\n\tFSb[0x00] = 0x63;\n#if AES_DECRYPTION  // whether AES decryption is supported\n\tRSb[0x63] = 0x00;\n#endif /* AES_DECRYPTION */\n\n\tfor (i = 1; i < 256; i++) {\n\t\tx = y = pow[255 - log[i]];\n\t\tMIX(x, y);\n\t\tMIX(x, y);\n\t\tMIX(x, y);\n\t\tMIX(x, y);\n\t\tFSb[i] = (uchar)(x ^= 0x63);\n#if AES_DECRYPTION  // whether AES decryption is supported\n\t\tRSb[x] = (uchar)i;\n#endif /* AES_DECRYPTION */\n\n\t}\n\t// generate the forward and reverse key expansion tables\n\tfor (i = 0; i < 256; i++) {\n\t\tx = FSb[i];\n\t\ty = XTIME(x) & 0xFF;\n\t\tz = (y ^ x) & 0xFF;\n\n\t\tFT0[i] = ((uint32_t)y) ^ ((uint32_t)x << 8) ^\n\t\t\t((uint32_t)x << 16) ^ ((uint32_t)z << 24);\n\n\t\tFT1[i] = ROTL8(FT0[i]);\n\t\tFT2[i] = ROTL8(FT1[i]);\n\t\tFT3[i] = ROTL8(FT2[i]);\n\n#if AES_DECRYPTION  // whether AES decryption is supported\n\t\tx = RSb[i];\n\n\t\tRT0[i] = ((uint32_t)MUL(0x0E, x)) ^\n\t\t\t((uint32_t)MUL(0x09, x) << 8) ^\n\t\t\t((uint32_t)MUL(0x0D, x) << 16) ^\n\t\t\t((uint32_t)MUL(0x0B, x) << 24);\n\n\t\tRT1[i] = ROTL8(RT0[i]);\n\t\tRT2[i] = ROTL8(RT1[i]);\n\t\tRT3[i] = ROTL8(RT2[i]);\n#endif /* AES_DECRYPTION */\n\t}\n\taes_tables_inited = 1;  // flag that the tables have been generated\n}                           // to permit subsequent use of the AES cipher\n\n/******************************************************************************\n *\n *  AES_SET_ENCRYPTION_KEY\n *\n *  This is called by 'aes_setkey' when we're establishing a key for\n *  subsequent encryption.  We give it a pointer to the encryption\n *  context, a pointer to the key, and the key's length in bytes.\n *  Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).\n *\n ******************************************************************************/\nint aes_set_encryption_key(aes_context *ctx,\n\tconst uchar *key,\n\tuint keysize)\n{\n\tuint i;                 // general purpose iteration local\n\tuint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer\n\n\tfor (i = 0; i < (keysize >> 2); i++) {\n\t\tGET_UINT32_LE(RK[i], key, i << 2);\n\t}\n\n\tswitch (ctx->rounds)\n\t{\n\tcase 10:\n\t\tfor (i = 0; i < 10; i++, RK += 4) {\n\t\t\tRK[4] = RK[0] ^ RCON[i] ^\n\t\t\t\t((uint32_t)FSb[(RK[3] >> 8) & 0xFF]) ^\n\t\t\t\t((uint32_t)FSb[(RK[3] >> 16) & 0xFF] << 8) ^\n\t\t\t\t((uint32_t)FSb[(RK[3] >> 24) & 0xFF] << 16) ^\n\t\t\t\t((uint32_t)FSb[(RK[3]) & 0xFF] << 24);\n\n\t\t\tRK[5] = RK[1] ^ RK[4];\n\t\t\tRK[6] = RK[2] ^ RK[5];\n\t\t\tRK[7] = RK[3] ^ RK[6];\n\t\t}\n\t\tbreak;\n\n\tcase 12:\n\t\tfor (i = 0; i < 8; i++, RK += 6) {\n\t\t\tRK[6] = RK[0] ^ RCON[i] ^\n\t\t\t\t((uint32_t)FSb[(RK[5] >> 8) & 0xFF]) ^\n\t\t\t\t((uint32_t)FSb[(RK[5] >> 16) & 0xFF] << 8) ^\n\t\t\t\t((uint32_t)FSb[(RK[5] >> 24) & 0xFF] << 16) ^\n\t\t\t\t((uint32_t)FSb[(RK[5]) & 0xFF] << 24);\n\n\t\t\tRK[7] = RK[1] ^ RK[6];\n\t\t\tRK[8] = RK[2] ^ RK[7];\n\t\t\tRK[9] = RK[3] ^ RK[8];\n\t\t\tRK[10] = RK[4] ^ RK[9];\n\t\t\tRK[11] = RK[5] ^ RK[10];\n\t\t}\n\t\tbreak;\n\n\tcase 14:\n\t\tfor (i = 0; i < 7; i++, RK += 8) {\n\t\t\tRK[8] = RK[0] ^ RCON[i] ^\n\t\t\t\t((uint32_t)FSb[(RK[7] >> 8) & 0xFF]) ^\n\t\t\t\t((uint32_t)FSb[(RK[7] >> 16) & 0xFF] << 8) ^\n\t\t\t\t((uint32_t)FSb[(RK[7] >> 24) & 0xFF] << 16) ^\n\t\t\t\t((uint32_t)FSb[(RK[7]) & 0xFF] << 24);\n\n\t\t\tRK[9] = RK[1] ^ RK[8];\n\t\t\tRK[10] = RK[2] ^ RK[9];\n\t\t\tRK[11] = RK[3] ^ RK[10];\n\n\t\t\tRK[12] = RK[4] ^\n\t\t\t\t((uint32_t)FSb[(RK[11]) & 0xFF]) ^\n\t\t\t\t((uint32_t)FSb[(RK[11] >> 8) & 0xFF] << 8) ^\n\t\t\t\t((uint32_t)FSb[(RK[11] >> 16) & 0xFF] << 16) ^\n\t\t\t\t((uint32_t)FSb[(RK[11] >> 24) & 0xFF] << 24);\n\n\t\t\tRK[13] = RK[5] ^ RK[12];\n\t\t\tRK[14] = RK[6] ^ RK[13];\n\t\t\tRK[15] = RK[7] ^ RK[14];\n\t\t}\n\t\tbreak;\n\n\tdefault:\n\t\treturn -1;\n\t}\n\treturn(0);\n}\n\n#if AES_DECRYPTION  // whether AES decryption is supported\n\n/******************************************************************************\n *\n *  AES_SET_DECRYPTION_KEY\n *\n *  This is called by 'aes_setkey' when we're establishing a\n *  key for subsequent decryption.  We give it a pointer to\n *  the encryption context, a pointer to the key, and the key's\n *  length in bits. Valid lengths are: 128, 192, or 256 bits.\n *\n ******************************************************************************/\nint aes_set_decryption_key(aes_context *ctx,\n\tconst uchar *key,\n\tuint keysize)\n{\n\tint i, j;\n\taes_context cty;            // a calling aes context for set_encryption_key\n\tuint32_t *RK = ctx->rk;     // initialize our RoundKey buffer pointer\n\tuint32_t *SK;\n\tint ret;\n\n\tcty.rounds = ctx->rounds;   // initialize our local aes context\n\tcty.rk = cty.buf;           // round count and key buf pointer\n\n\tif ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0)\n\t\treturn(ret);\n\n\tSK = cty.rk + cty.rounds * 4;\n\n\tCPY128  // copy a 128-bit block from *SK to *RK\n\n\t\tfor (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {\n\t\t\tfor (j = 0; j < 4; j++, SK++) {\n\t\t\t\t*RK++ = RT0[FSb[(*SK) & 0xFF]] ^\n\t\t\t\t\tRT1[FSb[(*SK >> 8) & 0xFF]] ^\n\t\t\t\t\tRT2[FSb[(*SK >> 16) & 0xFF]] ^\n\t\t\t\t\tRT3[FSb[(*SK >> 24) & 0xFF]];\n\t\t\t}\n\t\t}\n\tCPY128  // copy a 128-bit block from *SK to *RK\n\t\tmemset(&cty, 0, sizeof(aes_context));   // clear local aes context\n\treturn(0);\n}\n\n#endif /* AES_DECRYPTION */\n\n/******************************************************************************\n *\n *  AES_SETKEY\n *\n *  Invoked to establish the key schedule for subsequent encryption/decryption\n *\n ******************************************************************************/\nint aes_setkey(aes_context *ctx,   // AES context provided by our caller\n\tint mode,           // ENCRYPT or DECRYPT flag\n\tconst uchar *key,   // pointer to the key\n\tuint keysize)      // key length in bytes\n{\n\t// since table initialization is not thread safe, we could either add\n\t// system-specific mutexes and init the AES key generation tables on\n\t// demand, or ask the developer to simply call \"gcm_initialize\" once during\n\t// application startup before threading begins. That's what we choose.\n\tif (!aes_tables_inited) return (-1);  // fail the call when not inited.\n\n\tctx->mode = mode;       // capture the key type we're creating\n\tctx->rk = ctx->buf;     // initialize our round key pointer\n\n\tswitch (keysize)       // set the rounds count based upon the keysize\n\t{\n\tcase 16: ctx->rounds = 10; break;   // 16-byte, 128-bit key\n\tcase 24: ctx->rounds = 12; break;   // 24-byte, 192-bit key\n\tcase 32: ctx->rounds = 14; break;   // 32-byte, 256-bit key\n\tdefault: return(-1);\n\t}\n\n#if AES_DECRYPTION\n\tif (mode == AES_DECRYPT)   // expand our key for encryption or decryption\n\t\treturn(aes_set_decryption_key(ctx, key, keysize));\n\telse     /* ENCRYPT */\n#endif /* AES_DECRYPTION */\n\t\treturn(aes_set_encryption_key(ctx, key, keysize));\n}\n\n/******************************************************************************\n *\n *  AES_CIPHER\n *\n *  Perform AES encryption and decryption.\n *  The AES context will have been setup with the encryption mode\n *  and all keying information appropriate for the task.\n *\n ******************************************************************************/\nint aes_cipher(aes_context *ctx,\n\tconst uchar input[16],\n\tuchar output[16])\n{\n\tint i;\n\tuint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;   // general purpose locals\n\n\tRK = ctx->rk;\n\n\tGET_UINT32_LE(X0, input, 0); X0 ^= *RK++;    // load our 128-bit\n\tGET_UINT32_LE(X1, input, 4); X1 ^= *RK++;    // input buffer in a storage\n\tGET_UINT32_LE(X2, input, 8); X2 ^= *RK++;    // memory endian-neutral way\n\tGET_UINT32_LE(X3, input, 12); X3 ^= *RK++;\n\n#if AES_DECRYPTION  // whether AES decryption is supported\n\n\tif (ctx->mode == AES_DECRYPT)\n\t{\n\t\tfor (i = (ctx->rounds >> 1) - 1; i > 0; i--)\n\t\t{\n\t\t\tAES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);\n\t\t\tAES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);\n\t\t}\n\n\t\tAES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);\n\n\t\tX0 = *RK++ ^ \\\n\t\t\t((uint32_t)RSb[(Y0) & 0xFF]) ^\n\t\t\t((uint32_t)RSb[(Y3 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)RSb[(Y2 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)RSb[(Y1 >> 24) & 0xFF] << 24);\n\n\t\tX1 = *RK++ ^ \\\n\t\t\t((uint32_t)RSb[(Y1) & 0xFF]) ^\n\t\t\t((uint32_t)RSb[(Y0 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)RSb[(Y3 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)RSb[(Y2 >> 24) & 0xFF] << 24);\n\n\t\tX2 = *RK++ ^ \\\n\t\t\t((uint32_t)RSb[(Y2) & 0xFF]) ^\n\t\t\t((uint32_t)RSb[(Y1 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)RSb[(Y0 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)RSb[(Y3 >> 24) & 0xFF] << 24);\n\n\t\tX3 = *RK++ ^ \\\n\t\t\t((uint32_t)RSb[(Y3) & 0xFF]) ^\n\t\t\t((uint32_t)RSb[(Y2 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)RSb[(Y1 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)RSb[(Y0 >> 24) & 0xFF] << 24);\n\t}\n\telse /* ENCRYPT */\n\t{\n#endif /* AES_DECRYPTION */\n\n\t\tfor (i = (ctx->rounds >> 1) - 1; i > 0; i--)\n\t\t{\n\t\t\tAES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);\n\t\t\tAES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);\n\t\t}\n\n\t\tAES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);\n\n\t\tX0 = *RK++ ^ \\\n\t\t\t((uint32_t)FSb[(Y0) & 0xFF]) ^\n\t\t\t((uint32_t)FSb[(Y1 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)FSb[(Y2 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)FSb[(Y3 >> 24) & 0xFF] << 24);\n\n\t\tX1 = *RK++ ^ \\\n\t\t\t((uint32_t)FSb[(Y1) & 0xFF]) ^\n\t\t\t((uint32_t)FSb[(Y2 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)FSb[(Y3 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)FSb[(Y0 >> 24) & 0xFF] << 24);\n\n\t\tX2 = *RK++ ^ \\\n\t\t\t((uint32_t)FSb[(Y2) & 0xFF]) ^\n\t\t\t((uint32_t)FSb[(Y3 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)FSb[(Y0 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)FSb[(Y1 >> 24) & 0xFF] << 24);\n\n\t\tX3 = *RK++ ^ \\\n\t\t\t((uint32_t)FSb[(Y3) & 0xFF]) ^\n\t\t\t((uint32_t)FSb[(Y0 >> 8) & 0xFF] << 8) ^\n\t\t\t((uint32_t)FSb[(Y1 >> 16) & 0xFF] << 16) ^\n\t\t\t((uint32_t)FSb[(Y2 >> 24) & 0xFF] << 24);\n\n#if AES_DECRYPTION  // whether AES decryption is supported\n\t}\n#endif /* AES_DECRYPTION */\n\n\tPUT_UINT32_LE(X0, output, 0);\n\tPUT_UINT32_LE(X1, output, 4);\n\tPUT_UINT32_LE(X2, output, 8);\n\tPUT_UINT32_LE(X3, output, 12);\n\n\treturn(0);\n}\n/* end of aes.c */\n"
  },
  {
    "path": "nfq/crypto/aes.h",
    "content": "/******************************************************************************\n*\n* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL\n*\n* This is a simple and straightforward implementation of the AES Rijndael\n* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus\n* of this work was correctness & accuracy.  It is written in 'C' without any\n* particular focus upon optimization or speed. It should be endian (memory\n* byte order) neutral since the few places that care are handled explicitly.\n*\n* This implementation of Rijndael was created by Steven M. Gibson of GRC.com.\n*\n* It is intended for general purpose use, but was written in support of GRC's\n* reference implementation of the SQRL (Secure Quick Reliable Login) client.\n*\n* See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html\n*\n* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE\n* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.\n*\n*******************************************************************************/\n\n#pragma once\n\n/******************************************************************************/\n#define AES_DECRYPTION  0       // whether AES decryption is supported\n/******************************************************************************/\n\n#include <string.h>\n\n#define AES_ENCRYPT         1       // specify whether we're encrypting\n#define AES_DECRYPT         0       // or decrypting\n\n#if defined(_MSC_VER)\n#include <basetsd.h>\ntypedef UINT32 uint32_t;\n#else\n#include <inttypes.h>\n#endif\n\ntypedef unsigned char uchar;    // add some convienent shorter types\ntypedef unsigned int uint;\n\n\n/******************************************************************************\n *  AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use\n ******************************************************************************/\nvoid aes_init_keygen_tables(void);\n\n\n/******************************************************************************\n *  AES_CONTEXT : cipher context / holds inter-call data\n ******************************************************************************/\ntypedef struct {\n\tint mode;           // 1 for Encryption, 0 for Decryption\n\tint rounds;         // keysize-based rounds count\n\tuint32_t *rk;       // pointer to current round key\n\tuint32_t buf[68];   // key expansion buffer\n} aes_context;\n\n\n/******************************************************************************\n *  AES_SETKEY : called to expand the key for encryption or decryption\n ******************************************************************************/\nint aes_setkey(aes_context *ctx,       // pointer to context\n\tint mode,               // 1 or 0 for Encrypt/Decrypt\n\tconst uchar *key,       // AES input key\n\tuint keysize);         // size in bytes (must be 16, 24, 32 for\n\t\t\t\t\t// 128, 192 or 256-bit keys respectively)\n\t\t\t\t\t\t\t// returns 0 for success\n\n/******************************************************************************\n *  AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data\n ******************************************************************************/\nint aes_cipher(aes_context *ctx,       // pointer to context\n\tconst uchar input[16],  // 128-bit block to en/decipher\n\tuchar output[16]);     // 128-bit output result block\n\t\t\t\t\t\t\t// returns 0 for success\n"
  },
  {
    "path": "nfq/crypto/gcm.c",
    "content": "/******************************************************************************\n*\n* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL\n*\n* This is a simple and straightforward implementation of AES-GCM authenticated\n* encryption. The focus of this work was correctness & accuracy. It is written\n* in straight 'C' without any particular focus upon optimization or speed. It\n* should be endian (memory byte order) neutral since the few places that care\n* are handled explicitly.\n*\n* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.\n*\n* It is intended for general purpose use, but was written in support of GRC's\n* reference implementation of the SQRL (Secure Quick Reliable Login) client.\n*\n* See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf\n*         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/\n*         gcm/gcm-revised-spec.pdf\n*\n* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE\n* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.\n*\n*******************************************************************************/\n\n#include \"gcm.h\"\n#include \"aes.h\"\n\n/******************************************************************************\n *                      ==== IMPLEMENTATION WARNING ====\n *\n *  This code was developed for use within SQRL's fixed environmnent. Thus, it\n *  is somewhat less \"general purpose\" than it would be if it were designed as\n *  a general purpose AES-GCM library. Specifically, it bothers with almost NO\n *  error checking on parameter limits, buffer bounds, etc. It assumes that it\n *  is being invoked by its author or by someone who understands the values it\n *  expects to receive. Its behavior will be undefined otherwise.\n *\n *  All functions that might fail are defined to return 'ints' to indicate a\n *  problem. Most do not do so now. But this allows for error propagation out\n *  of internal functions if robust error checking should ever be desired.\n *\n ******************************************************************************/\n\n /* Calculating the \"GHASH\"\n  *\n  * There are many ways of calculating the so-called GHASH in software, each with\n  * a traditional size vs performance tradeoff.  The GHASH (Galois field hash) is\n  * an intriguing construction which takes two 128-bit strings (also the cipher's\n  * block size and the fundamental operation size for the system) and hashes them\n  * into a third 128-bit result.\n  *\n  * Many implementation solutions have been worked out that use large precomputed\n  * table lookups in place of more time consuming bit fiddling, and this approach\n  * can be scaled easily upward or downward as needed to change the time/space\n  * tradeoff. It's been studied extensively and there's a solid body of theory and\n  * practice.  For example, without using any lookup tables an implementation\n  * might obtain 119 cycles per byte throughput, whereas using a simple, though\n  * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13\n  * cycles per byte.\n  *\n  * And Intel's processors have, since 2010, included an instruction which does\n  * the entire 128x128->128 bit job in just several 64x64->128 bit pieces.\n  *\n  * Since SQRL is interactive, and only processing a few 128-bit blocks, I've\n  * settled upon a relatively slower but appealing small-table compromise which\n  * folds a bunch of not only time consuming but also bit twiddling into a simple\n  * 16-entry table which is attributed to Victor Shoup's 1996 work while at\n  * Bellcore: \"On Fast and Provably Secure MessageAuthentication Based on\n  * Universal Hashing.\"  See: http://www.shoup.net/papers/macs.pdf\n  * See, also section 4.1 of the \"gcm-revised-spec\" cited above.\n  */\n\n  /*\n   *  This 16-entry table of pre-computed constants is used by the\n   *  GHASH multiplier to improve over a strictly table-free but\n   *  significantly slower 128x128 bit multiple within GF(2^128).\n   */\nstatic const uint64_t last4[16] = {\n\t0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,\n\t0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 };\n\n/*\n * Platform Endianness Neutralizing Load and Store Macro definitions\n * GCM wants platform-neutral Big Endian (BE) byte ordering\n */\n#define GET_UINT32_BE(n,b,i) {                      \\\n    (n) = ( (uint32_t) (b)[(i)    ] << 24 )         \\\n        | ( (uint32_t) (b)[(i) + 1] << 16 )         \\\n        | ( (uint32_t) (b)[(i) + 2] <<  8 )         \\\n        | ( (uint32_t) (b)[(i) + 3]       ); }\n\n#define PUT_UINT32_BE(n,b,i) {                      \\\n    (b)[(i)    ] = (uchar) ( (n) >> 24 );   \\\n    (b)[(i) + 1] = (uchar) ( (n) >> 16 );   \\\n    (b)[(i) + 2] = (uchar) ( (n) >>  8 );   \\\n    (b)[(i) + 3] = (uchar) ( (n)       ); }\n\n\n /******************************************************************************\n  *\n  *  GCM_INITIALIZE\n  *\n  *  Must be called once to initialize the GCM library.\n  *\n  *  At present, this only calls the AES keygen table generator, which expands\n  *  the AES keying tables for use. This is NOT A THREAD-SAFE function, so it\n  *  MUST be called during system initialization before a multi-threading\n  *  environment is running.\n  *\n  ******************************************************************************/\nint gcm_initialize(void)\n{\n\taes_init_keygen_tables();\n\treturn(0);\n}\n\n\n/******************************************************************************\n *\n *  GCM_MULT\n *\n *  Performs a GHASH operation on the 128-bit input vector 'x', setting\n *  the 128-bit output vector to 'x' times H using our precomputed tables.\n *  'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field.\n *\n ******************************************************************************/\nstatic void gcm_mult(gcm_context *ctx,     // pointer to established context\n\tconst uchar x[16],    // pointer to 128-bit input vector\n\tuchar output[16])    // pointer to 128-bit output vector\n{\n\tint i;\n\tuchar lo, hi, rem;\n\tuint64_t zh, zl;\n\n\tlo = (uchar)(x[15] & 0x0f);\n\thi = (uchar)(x[15] >> 4);\n\tzh = ctx->HH[lo];\n\tzl = ctx->HL[lo];\n\n\tfor (i = 15; i >= 0; i--) {\n\t\tlo = (uchar)(x[i] & 0x0f);\n\t\thi = (uchar)(x[i] >> 4);\n\n\t\tif (i != 15) {\n\t\t\trem = (uchar)(zl & 0x0f);\n\t\t\tzl = (zh << 60) | (zl >> 4);\n\t\t\tzh = (zh >> 4);\n\t\t\tzh ^= (uint64_t)last4[rem] << 48;\n\t\t\tzh ^= ctx->HH[lo];\n\t\t\tzl ^= ctx->HL[lo];\n\t\t}\n\t\trem = (uchar)(zl & 0x0f);\n\t\tzl = (zh << 60) | (zl >> 4);\n\t\tzh = (zh >> 4);\n\t\tzh ^= (uint64_t)last4[rem] << 48;\n\t\tzh ^= ctx->HH[hi];\n\t\tzl ^= ctx->HL[hi];\n\t}\n\tPUT_UINT32_BE(zh >> 32, output, 0);\n\tPUT_UINT32_BE(zh, output, 4);\n\tPUT_UINT32_BE(zl >> 32, output, 8);\n\tPUT_UINT32_BE(zl, output, 12);\n}\n\n\n/******************************************************************************\n *\n *  GCM_SETKEY\n *\n *  This is called to set the AES-GCM key. It initializes the AES key\n *  and populates the gcm context's pre-calculated HTables.\n *\n ******************************************************************************/\nint gcm_setkey(gcm_context *ctx,   // pointer to caller-provided gcm context\n\tconst uchar *key,   // pointer to the AES encryption key\n\tconst uint keysize) // size in bytes (must be 16, 24, 32 for\n\t\t\t\t// 128, 192 or 256-bit keys respectively)\n{\n\tint ret, i, j;\n\tuint64_t hi, lo;\n\tuint64_t vl, vh;\n\tunsigned char h[16];\n\n\tmemset(ctx, 0, sizeof(gcm_context));  // zero caller-provided GCM context\n\tmemset(h, 0, 16);                     // initialize the block to encrypt\n\n\t// encrypt the null 128-bit block to generate a key-based value\n\t// which is then used to initialize our GHASH lookup tables\n\tif ((ret = aes_setkey(&ctx->aes_ctx, AES_ENCRYPT, key, keysize)) != 0)\n\t\treturn(ret);\n\tif ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0)\n\t\treturn(ret);\n\n\tGET_UINT32_BE(hi, h, 0);    // pack h as two 64-bit ints, big-endian\n\tGET_UINT32_BE(lo, h, 4);\n\tvh = (uint64_t)hi << 32 | lo;\n\n\tGET_UINT32_BE(hi, h, 8);\n\tGET_UINT32_BE(lo, h, 12);\n\tvl = (uint64_t)hi << 32 | lo;\n\n\tctx->HL[8] = vl;                // 8 = 1000 corresponds to 1 in GF(2^128)\n\tctx->HH[8] = vh;\n\tctx->HH[0] = 0;                 // 0 corresponds to 0 in GF(2^128)\n\tctx->HL[0] = 0;\n\n\tfor (i = 4; i > 0; i >>= 1) {\n\t\tuint32_t T = (uint32_t)(vl & 1) * 0xe1000000U;\n\t\tvl = (vh << 63) | (vl >> 1);\n\t\tvh = (vh >> 1) ^ ((uint64_t)T << 32);\n\t\tctx->HL[i] = vl;\n\t\tctx->HH[i] = vh;\n\t}\n\tfor (i = 2; i < 16; i <<= 1) {\n\t\tuint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;\n\t\tvh = *HiH;\n\t\tvl = *HiL;\n\t\tfor (j = 1; j < i; j++) {\n\t\t\tHiH[j] = vh ^ ctx->HH[j];\n\t\t\tHiL[j] = vl ^ ctx->HL[j];\n\t\t}\n\t}\n\treturn(0);\n}\n\n\n/******************************************************************************\n *\n *    GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH.\n *\n *  SETKEY:\n *\n *   START: Sets the Encryption/Decryption mode.\n *          Accepts the initialization vector and additional data.\n *\n *  UPDATE: Encrypts or decrypts the plaintext or ciphertext.\n *\n *  FINISH: Performs a final GHASH to generate the authentication tag.\n *\n ******************************************************************************\n *\n *  GCM_START\n *\n *  Given a user-provided GCM context, this initializes it, sets the encryption\n *  mode, and preprocesses the initialization vector and additional AEAD data.\n *\n ******************************************************************************/\nint gcm_start(gcm_context *ctx,    // pointer to user-provided GCM context\n\tint mode,            // AES_ENCRYPT or AES_DECRYPT\n\tconst uchar *iv,     // pointer to initialization vector\n\tsize_t iv_len,       // IV length in bytes (should == 12)\n\tconst uchar *add,    // ptr to additional AEAD data (NULL if none)\n\tsize_t add_len)     // length of additional AEAD data (bytes)\n{\n\tint ret;            // our error return if the AES encrypt fails\n\tuchar work_buf[16]; // XOR source built from provided IV if len != 16\n\tconst uchar *p;     // general purpose array pointer\n\tsize_t use_len;     // byte count to process, up to 16 bytes\n\tsize_t i;           // local loop iterator\n\n\t// since the context might be reused under the same key\n\t// we zero the working buffers for this next new process\n\tmemset(ctx->y, 0x00, sizeof(ctx->y));\n\tmemset(ctx->buf, 0x00, sizeof(ctx->buf));\n\tctx->len = 0;\n\tctx->add_len = 0;\n\n\tctx->mode = mode;               // set the GCM encryption/decryption mode\n\tctx->aes_ctx.mode = AES_ENCRYPT;    // GCM *always* runs AES in ENCRYPTION mode\n\n\tif (iv_len == 12) {                // GCM natively uses a 12-byte, 96-bit IV\n\t\tmemcpy(ctx->y, iv, iv_len);   // copy the IV to the top of the 'y' buff\n\t\tctx->y[15] = 1;                 // start \"counting\" from 1 (not 0)\n\t}\n\telse    // if we don't have a 12-byte IV, we GHASH whatever we've been given\n\t{\n\t\tmemset(work_buf, 0x00, 16);               // clear the working buffer\n\t\tPUT_UINT32_BE(iv_len * 8, work_buf, 12);  // place the IV into buffer\n\n\t\tp = iv;\n\t\twhile (iv_len > 0) {\n\t\t\tuse_len = (iv_len < 16) ? iv_len : 16;\n\t\t\tfor (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];\n\t\t\tgcm_mult(ctx, ctx->y, ctx->y);\n\t\t\tiv_len -= use_len;\n\t\t\tp += use_len;\n\t\t}\n\t\tfor (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];\n\t\tgcm_mult(ctx, ctx->y, ctx->y);\n\t}\n\n\tif ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0)\n\t\treturn(ret);\n\n\tctx->add_len = add_len;\n\tp = add;\n\twhile (add_len > 0) {\n\t\tuse_len = (add_len < 16) ? add_len : 16;\n\t\tfor (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];\n\t\tgcm_mult(ctx, ctx->buf, ctx->buf);\n\t\tadd_len -= use_len;\n\t\tp += use_len;\n\t}\n\treturn(0);\n}\n\n/******************************************************************************\n *\n *  GCM_UPDATE\n *\n *  This is called once or more to process bulk plaintext or ciphertext data.\n *  We give this some number of bytes of input and it returns the same number\n *  of output bytes. If called multiple times (which is fine) all but the final\n *  invocation MUST be called with length mod 16 == 0. (Only the final call can\n *  have a partial block length of < 128 bits.)\n *\n ******************************************************************************/\nint gcm_update(gcm_context *ctx,       // pointer to user-provided GCM context\n\tsize_t length,          // length, in bytes, of data to process\n\tconst uchar *input,     // pointer to source data\n\tuchar *output)         // pointer to destination data\n{\n\tint ret;            // our error return if the AES encrypt fails\n\tuchar ectr[16];     // counter-mode cipher output for XORing\n\tsize_t use_len;     // byte count to process, up to 16 bytes\n\tsize_t i;           // local loop iterator\n\n\tctx->len += length; // bump the GCM context's running length count\n\n\twhile (length > 0) {\n\t\t// clamp the length to process at 16 bytes\n\t\tuse_len = (length < 16) ? length : 16;\n\n\t\t// increment the context's 128-bit IV||Counter 'y' vector\n\t\tfor (i = 16; i > 12; i--) if (++ctx->y[i - 1] != 0) break;\n\n\t\t// encrypt the context's 'y' vector under the established key\n\t\tif ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0)\n\t\t\treturn(ret);\n\n\t\t// encrypt or decrypt the input to the output\n\t\tif (ctx->mode == AES_ENCRYPT)\n\t\t{\n\t\t\tfor (i = 0; i < use_len; i++) {\n\t\t\t\t// XOR the cipher's ouptut vector (ectr) with our input\n\t\t\t\toutput[i] = (uchar)(ectr[i] ^ input[i]);\n\t\t\t\t// now we mix in our data into the authentication hash.\n\t\t\t\t// if we're ENcrypting we XOR in the post-XOR (output) \n\t\t\t\t// results, but if we're DEcrypting we XOR in the input \n\t\t\t\t// data\n\t\t\t\tctx->buf[i] ^= output[i];\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor (i = 0; i < use_len; i++) {\n\t\t\t\t// but if we're DEcrypting we XOR in the input data first, \n\t\t\t\t// i.e. before saving to ouput data, otherwise if the input \n\t\t\t\t// and output buffer are the same (inplace decryption) we \n\t\t\t\t// would not get the correct auth tag\n\n\t\t\t\tctx->buf[i] ^= input[i];\n\n\t\t\t\t// XOR the cipher's ouptut vector (ectr) with our input\n\t\t\t\toutput[i] = (uchar)(ectr[i] ^ input[i]);\n\t\t\t}\n\t\t}\n\t\tgcm_mult(ctx, ctx->buf, ctx->buf);    // perform a GHASH operation\n\n\t\tlength -= use_len;  // drop the remaining byte count to process\n\t\tinput += use_len;  // bump our input pointer forward\n\t\toutput += use_len;  // bump our output pointer forward\n\t}\n\treturn(0);\n}\n\n/******************************************************************************\n *\n *  GCM_FINISH\n *\n *  This is called once after all calls to GCM_UPDATE to finalize the GCM.\n *  It performs the final GHASH to produce the resulting authentication TAG.\n *\n ******************************************************************************/\nint gcm_finish(gcm_context *ctx,   // pointer to user-provided GCM context\n\tuchar *tag,         // pointer to buffer which receives the tag\n\tsize_t tag_len)    // length, in bytes, of the tag-receiving buf\n{\n\tuchar work_buf[16];\n\tuint64_t orig_len = ctx->len * 8;\n\tuint64_t orig_add_len = ctx->add_len * 8;\n\tsize_t i;\n\n\tif (tag_len>16) return -1;\n\n\tif (tag_len) memcpy(tag, ctx->base_ectr, tag_len);\n\n\tif (orig_len || orig_add_len) {\n\t\tmemset(work_buf, 0x00, 16);\n\n\t\tPUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);\n\t\tPUT_UINT32_BE((orig_add_len), work_buf, 4);\n\t\tPUT_UINT32_BE((orig_len >> 32), work_buf, 8);\n\t\tPUT_UINT32_BE((orig_len), work_buf, 12);\n\n\t\tfor (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];\n\t\tgcm_mult(ctx, ctx->buf, ctx->buf);\n\t\tfor (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];\n\t}\n\treturn(0);\n}\n\n\n/******************************************************************************\n *\n *  GCM_CRYPT_AND_TAG\n *\n *  This either encrypts or decrypts the user-provided data and, either\n *  way, generates an authentication tag of the requested length. It must be\n *  called with a GCM context whose key has already been set with GCM_SETKEY.\n *\n *  The user would typically call this explicitly to ENCRYPT a buffer of data\n *  and optional associated data, and produce its an authentication tag.\n *\n *  To reverse the process the user would typically call the companion\n *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided\n *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function\n *  to perform its decryption and tag generation, which it then compares.\n *\n ******************************************************************************/\nint gcm_crypt_and_tag(\n\tgcm_context *ctx,       // gcm context with key already setup\n\tint mode,               // cipher direction: AES_ENCRYPT or AES_DECRYPT\n\tconst uchar *iv,        // pointer to the 12-byte initialization vector\n\tsize_t iv_len,          // byte length if the IV. should always be 12\n\tconst uchar *add,       // pointer to the non-ciphered additional data\n\tsize_t add_len,         // byte length of the additional AEAD data\n\tconst uchar *input,     // pointer to the cipher data source\n\tuchar *output,          // pointer to the cipher data destination\n\tsize_t length,          // byte length of the cipher data\n\tuchar *tag,             // pointer to the tag to be generated\n\tsize_t tag_len)        // byte length of the tag to be generated\n{   /*\n\t   assuming that the caller has already invoked gcm_setkey to\n\t   prepare the gcm context with the keying material, we simply\n\t   invoke each of the three GCM sub-functions in turn...\n\t*/\n\tif (tag_len>16) return -1;\n\n\tint ret;\n\tif ((ret=gcm_start(ctx, mode, iv, iv_len, add, add_len))) return ret;\n\tif ((ret=gcm_update(ctx, length, input, output))) return ret;\n\treturn gcm_finish(ctx, tag, tag_len);\n}\n\n\n/******************************************************************************\n *\n *  GCM_AUTH_DECRYPT\n *\n *  This DECRYPTS a user-provided data buffer with optional associated data.\n *  It then verifies a user-supplied authentication tag against the tag just\n *  re-created during decryption to verify that the data has not been altered.\n *\n *  This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption\n *  and authentication tag generation.\n *\n ******************************************************************************/\nint gcm_auth_decrypt(\n\tgcm_context *ctx,       // gcm context with key already setup\n\tconst uchar *iv,        // pointer to the 12-byte initialization vector\n\tsize_t iv_len,          // byte length if the IV. should always be 12\n\tconst uchar *add,       // pointer to the non-ciphered additional data\n\tsize_t add_len,         // byte length of the additional AEAD data\n\tconst uchar *input,     // pointer to the cipher data source\n\tuchar *output,          // pointer to the cipher data destination\n\tsize_t length,          // byte length of the cipher data\n\tconst uchar *tag,       // pointer to the tag to be authenticated\n\tsize_t tag_len)        // byte length of the tag <= 16\n{\n\tuchar check_tag[16];        // the tag generated and returned by decryption\n\tint diff;                   // an ORed flag to detect authentication errors\n\tsize_t i;                   // our local iterator\n\tint ret;\n\n\tif (tag_len>16) return -1;\n\n\t/*\n\t   we use GCM_DECRYPT_AND_TAG (above) to perform our decryption\n\t   (which is an identical XORing to reverse the previous one)\n\t   and also to re-generate the matching authentication tag\n\t*/\n\tif ((ret = gcm_crypt_and_tag(ctx, AES_DECRYPT, iv, iv_len, add, add_len, input, output, length, check_tag, tag_len))) return ret;\n\n\t// now we verify the authentication tag in 'constant time'\n\tfor (diff = 0, i = 0; i < tag_len; i++)\n\t\tdiff |= tag[i] ^ check_tag[i];\n\n\tif (diff)\n\t{\n\t\t// see whether any bits differed?\n\t\tmemset(output, 0, length);    // if so... wipe the output data\n\t\treturn(GCM_AUTH_FAILURE);     // return GCM_AUTH_FAILURE\n\t}\n\treturn 0;\n}\n\n/******************************************************************************\n *\n *  GCM_ZERO_CTX\n *\n *  The GCM context contains both the GCM context and the AES context.\n *  This includes keying and key-related material which is security-\n *  sensitive, so it MUST be zeroed after use. This function does that.\n *\n ******************************************************************************/\nvoid gcm_zero_ctx(gcm_context *ctx)\n{\n\t// zero the context originally provided to us\n\tmemset(ctx, 0, sizeof(gcm_context));\n}\n"
  },
  {
    "path": "nfq/crypto/gcm.h",
    "content": "/******************************************************************************\n*\n* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL\n*\n* This is a simple and straightforward implementation of AES-GCM authenticated\n* encryption. The focus of this work was correctness & accuracy. It is written\n* in straight 'C' without any particular focus upon optimization or speed. It\n* should be endian (memory byte order) neutral since the few places that care\n* are handled explicitly.\n*\n* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.\n*\n* It is intended for general purpose use, but was written in support of GRC's\n* reference implementation of the SQRL (Secure Quick Reliable Login) client.\n*\n* See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf\n*         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \\\n*         gcm/gcm-revised-spec.pdf\n*\n* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE\n* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.\n*\n*******************************************************************************/\n#pragma once\n\n#define GCM_AUTH_FAILURE    0x55555555  // authentication failure\n\n#include \"aes.h\"                        // gcm_context includes aes_context\n\n#if defined(_MSC_VER)\n#include <basetsd.h>\ntypedef UINT32 uint32_t;\ntypedef UINT64 uint64_t;\n#else\n#include <stdint.h>\n#endif\n\n\n/******************************************************************************\n *  GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx\n ******************************************************************************/\ntypedef struct {\n\tint mode;               // cipher direction: encrypt/decrypt\n\tuint64_t len;           // cipher data length processed so far\n\tuint64_t add_len;       // total add data length\n\tuint64_t HL[16];        // precalculated lo-half HTable\n\tuint64_t HH[16];        // precalculated hi-half HTable\n\tuchar base_ectr[16];    // first counter-mode cipher output for tag\n\tuchar y[16];            // the current cipher-input IV|Counter value\n\tuchar buf[16];          // buf working value\n\taes_context aes_ctx;    // cipher context used\n} gcm_context;\n\n\n/******************************************************************************\n *  GCM_CONTEXT : MUST be called once before ANY use of this library\n ******************************************************************************/\nint gcm_initialize(void);\n\n\n/******************************************************************************\n *  GCM_SETKEY : sets the GCM (and AES) keying material for use\n ******************************************************************************/\nint gcm_setkey(gcm_context *ctx,   // caller-provided context ptr\n\tconst uchar *key,   // pointer to cipher key\n\tconst uint keysize  // size in bytes (must be 16, 24, 32 for\n\t\t\t\t// 128, 192 or 256-bit keys respectively)\n); // returns 0 for success\n\n\n/******************************************************************************\n *\n *  GCM_CRYPT_AND_TAG\n *\n *  This either encrypts or decrypts the user-provided data and, either\n *  way, generates an authentication tag of the requested length. It must be\n *  called with a GCM context whose key has already been set with GCM_SETKEY.\n *\n *  The user would typically call this explicitly to ENCRYPT a buffer of data\n *  and optional associated data, and produce its an authentication tag.\n *\n *  To reverse the process the user would typically call the companion\n *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided\n *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function\n *  to perform its decryption and tag generation, which it then compares.\n *\n ******************************************************************************/\nint gcm_crypt_and_tag(\n\tgcm_context *ctx,       // gcm context with key already setup\n\tint mode,               // cipher direction: ENCRYPT (1) or DECRYPT (0)\n\tconst uchar *iv,        // pointer to the 12-byte initialization vector\n\tsize_t iv_len,          // byte length if the IV. should always be 12\n\tconst uchar *add,       // pointer to the non-ciphered additional data\n\tsize_t add_len,         // byte length of the additional AEAD data\n\tconst uchar *input,     // pointer to the cipher data source\n\tuchar *output,          // pointer to the cipher data destination\n\tsize_t length,          // byte length of the cipher data\n\tuchar *tag,             // pointer to the tag to be generated\n\tsize_t tag_len);       // byte length of the tag to be generated\n\n\n/******************************************************************************\n *\n *  GCM_AUTH_DECRYPT\n *\n *  This DECRYPTS a user-provided data buffer with optional associated data.\n *  It then verifies a user-supplied authentication tag against the tag just\n *  re-created during decryption to verify that the data has not been altered.\n *\n *  This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption\n *  and authentication tag generation.\n *\n ******************************************************************************/\nint gcm_auth_decrypt(\n\tgcm_context *ctx,       // gcm context with key already setup\n\tconst uchar *iv,        // pointer to the 12-byte initialization vector\n\tsize_t iv_len,          // byte length if the IV. should always be 12\n\tconst uchar *add,       // pointer to the non-ciphered additional data\n\tsize_t add_len,         // byte length of the additional AEAD data\n\tconst uchar *input,     // pointer to the cipher data source\n\tuchar *output,          // pointer to the cipher data destination\n\tsize_t length,          // byte length of the cipher data\n\tconst uchar *tag,       // pointer to the tag to be authenticated\n\tsize_t tag_len);       // byte length of the tag <= 16\n\n\n/******************************************************************************\n *\n *  GCM_START\n *\n *  Given a user-provided GCM context, this initializes it, sets the encryption\n *  mode, and preprocesses the initialization vector and additional AEAD data.\n *\n ******************************************************************************/\nint gcm_start(gcm_context *ctx,    // pointer to user-provided GCM context\n\tint mode,            // ENCRYPT (1) or DECRYPT (0)\n\tconst uchar *iv,     // pointer to initialization vector\n\tsize_t iv_len,       // IV length in bytes (should == 12)\n\tconst uchar *add,    // pointer to additional AEAD data (NULL if none)\n\tsize_t add_len);    // length of additional AEAD data (bytes)\n\n\n/******************************************************************************\n *\n *  GCM_UPDATE\n *\n *  This is called once or more to process bulk plaintext or ciphertext data.\n *  We give this some number of bytes of input and it returns the same number\n *  of output bytes. If called multiple times (which is fine) all but the final\n *  invocation MUST be called with length mod 16 == 0. (Only the final call can\n *  have a partial block length of < 128 bits.)\n *\n ******************************************************************************/\nint gcm_update(gcm_context *ctx,       // pointer to user-provided GCM context\n\tsize_t length,          // length, in bytes, of data to process\n\tconst uchar *input,     // pointer to source data\n\tuchar *output);        // pointer to destination data\n\n\n/******************************************************************************\n *\n *  GCM_FINISH\n *\n *  This is called once after all calls to GCM_UPDATE to finalize the GCM.\n *  It performs the final GHASH to produce the resulting authentication TAG.\n *\n ******************************************************************************/\nint gcm_finish(gcm_context *ctx,   // pointer to user-provided GCM context\n\tuchar *tag,         // ptr to tag buffer - NULL if tag_len = 0\n\tsize_t tag_len);   // length, in bytes, of the tag-receiving buf\n\n\n/******************************************************************************\n *\n *  GCM_ZERO_CTX\n *\n *  The GCM context contains both the GCM context and the AES context.\n *  This includes keying and key-related material which is security-\n *  sensitive, so it MUST be zeroed after use. This function does that.\n *\n ******************************************************************************/\nvoid gcm_zero_ctx(gcm_context *ctx);\n"
  },
  {
    "path": "nfq/crypto/hkdf.c",
    "content": "/**************************** hkdf.c ***************************/\n/***************** See RFC 6234 for details. *******************/\n/* Copyright (c) 2011 IETF Trust and the persons identified as */\n/* authors of the code.  All rights reserved.                  */\n/* See sha.h for terms of use and redistribution.              */\n\n/*\n *  Description:\n *      This file implements the HKDF algorithm (HMAC-based\n *      Extract-and-Expand Key Derivation Function, RFC 5869),\n *      expressed in terms of the various SHA algorithms.\n */\n\n#include \"sha.h\"\n#include <string.h>\n#include <stdlib.h>\n\n /*\n  *  hkdf\n  *\n  *  Description:\n  *      This function will generate keying material using HKDF.\n  *\n  *  Parameters:\n  *      whichSha: [in]\n  *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n  *      salt[ ]: [in]\n  *          The optional salt value (a non-secret random value);\n  *          if not provided (salt == NULL), it is set internally\n  *          to a string of HashLen(whichSha) zeros.\n  *      salt_len: [in]\n  *          The length of the salt value.  (Ignored if salt == NULL.)\n  *      ikm[ ]: [in]\n  *          Input keying material.\n  *      ikm_len: [in]\n  *          The length of the input keying material.\n  *      info[ ]: [in]\n  *          The optional context and application specific information.\n  *          If info == NULL or a zero-length string, it is ignored.\n  *      info_len: [in]\n  *          The length of the optional context and application specific\n  *          information.  (Ignored if info == NULL.)\n  *      okm[ ]: [out]\n  *          Where the HKDF is to be stored.\n  *      okm_len: [in]\n  *          The length of the buffer to hold okm.\n  *          okm_len must be <= 255 * USHABlockSize(whichSha)\n  *\n  *  Notes:\n  *      Calls hkdfExtract() and hkdfExpand().\n  *\n  *  Returns:\n  *      sha Error Code.\n  *\n  */\nint hkdf(SHAversion whichSha,\n\tconst unsigned char *salt, size_t salt_len,\n\tconst unsigned char *ikm, size_t ikm_len,\n\tconst unsigned char *info, size_t info_len,\n\tuint8_t okm[], size_t okm_len)\n{\n\tuint8_t prk[USHAMaxHashSize];\n\tint ret;\n\tif ((ret=hkdfExtract(whichSha, salt, salt_len, ikm, ikm_len, prk))) return ret;\n\treturn hkdfExpand(whichSha, prk, USHAHashSize(whichSha), info, info_len, okm, okm_len);\n}\n\n/*\n *  hkdfExtract\n *\n *  Description:\n *      This function will perform HKDF extraction.\n *\n *  Parameters:\n *      whichSha: [in]\n *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n *      salt[ ]: [in]\n *          The optional salt value (a non-secret random value);\n *          if not provided (salt == NULL), it is set internally\n *          to a string of HashLen(whichSha) zeros.\n *      salt_len: [in]\n *          The length of the salt value.  (Ignored if salt == NULL.)\n *      ikm[ ]: [in]\n *          Input keying material.\n *      ikm_len: [in]\n *          The length of the input keying material.\n *      prk[ ]: [out]\n *          Array where the HKDF extraction is to be stored.\n *          Must be larger than USHAHashSize(whichSha);\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hkdfExtract(SHAversion whichSha,\n\tconst unsigned char *salt, size_t salt_len,\n\tconst unsigned char *ikm, size_t ikm_len,\n\tuint8_t prk[USHAMaxHashSize])\n{\n\tunsigned char nullSalt[USHAMaxHashSize];\n\tif (salt == 0) {\n\t\tsalt = nullSalt;\n\t\tsalt_len = USHAHashSize(whichSha);\n\t\tmemset(nullSalt, '\\0', salt_len);\n\t}\n\treturn hmac(whichSha, ikm, ikm_len, salt, salt_len, prk);\n}\n\n/*\n *  hkdfExpand\n *\n *  Description:\n *      This function will perform HKDF expansion.\n *\n *  Parameters:\n *      whichSha: [in]\n *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n *      prk[ ]: [in]\n *          The pseudo-random key to be expanded; either obtained\n *          directly from a cryptographically strong, uniformly\n *          distributed pseudo-random number generator, or as the\n *          output from hkdfExtract().\n *      prk_len: [in]\n *          The length of the pseudo-random key in prk;\n *          should at least be equal to USHAHashSize(whichSHA).\n *      info[ ]: [in]\n *          The optional context and application specific information.\n *          If info == NULL or a zero-length string, it is ignored.\n *      info_len: [in]\n *          The length of the optional context and application specific\n *          information.  (Ignored if info == NULL.)\n *      okm[ ]: [out]\n *          Where the HKDF is to be stored.\n *      okm_len: [in]\n *          The length of the buffer to hold okm.\n *          okm_len must be <= 255 * USHABlockSize(whichSha)\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hkdfExpand(SHAversion whichSha, const uint8_t prk[], size_t prk_len,\n\tconst unsigned char *info, size_t info_len,\n\tuint8_t okm[], size_t okm_len)\n{\n\tsize_t hash_len, N;\n\tunsigned char T[USHAMaxHashSize];\n\tsize_t Tlen, where, i;\n\tint ret;\n\n\tif (info == 0) {\n\t\tinfo = (const unsigned char *)\"\";\n\t\tinfo_len = 0;\n\t}\n\tif (!okm || !okm_len) return shaBadParam;\n\n\thash_len = USHAHashSize(whichSha);\n\tif (prk_len < hash_len) return shaBadParam;\n\tN = okm_len / hash_len;\n\tif ((okm_len % hash_len) != 0) N++;\n\tif (N > 255) return shaBadParam;\n\n\tTlen = 0;\n\twhere = 0;\n\tfor (i = 1; i <= N; i++) {\n\t\tHMACContext context;\n\t\tunsigned char c = i;\n\t\tif ((ret=hmacReset(&context, whichSha, prk, prk_len))) return ret;\n\t\tif ((ret=hmacInput(&context, T, Tlen))) return ret;\n\t\tif ((ret=hmacInput(&context, info, info_len))) return ret;\n\t\tif ((ret=hmacInput(&context, &c, 1))) return ret;\n\t\tif ((ret=hmacResult(&context, T))) return ret;\n\t\tmemcpy(okm + where, T,\n\t\t\t(i != N) ? hash_len : (okm_len - where));\n\t\twhere += hash_len;\n\t\tTlen = hash_len;\n\t}\n\treturn shaSuccess;\n}\n\n/*\n *  hkdfReset\n *\n *  Description:\n *      This function will initialize the hkdfContext in preparation\n *      for key derivation using the modular HKDF interface for\n *      arbitrary length inputs.\n *\n *  Parameters:\n *      context: [in/out]\n *          The context to reset.\n *      whichSha: [in]\n *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n *      salt[ ]: [in]\n *          The optional salt value (a non-secret random value);\n *          if not provided (salt == NULL), it is set internally\n *          to a string of HashLen(whichSha) zeros.\n *      salt_len: [in]\n *          The length of the salt value.  (Ignored if salt == NULL.)\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hkdfReset(HKDFContext *context, enum SHAversion whichSha,\n\tconst unsigned char *salt, size_t salt_len)\n{\n\tunsigned char nullSalt[USHAMaxHashSize];\n\tif (!context) return shaNull;\n\n\tcontext->whichSha = whichSha;\n\tcontext->hashSize = USHAHashSize(whichSha);\n\tif (salt == 0) {\n\t\tsalt = nullSalt;\n\t\tsalt_len = context->hashSize;\n\t\tmemset(nullSalt, '\\0', salt_len);\n\t}\n\n\treturn hmacReset(&context->hmacContext, whichSha, salt, salt_len);\n}\n\n/*\n *  hkdfInput\n *\n *  Description:\n *      This function accepts an array of octets as the next portion\n *      of the input keying material.  It may be called multiple times.\n *\n *  Parameters:\n *      context: [in/out]\n *          The HKDF context to update.\n *      ikm[ ]: [in]\n *          An array of octets representing the next portion of\n *          the input keying material.\n *      ikm_len: [in]\n *          The length of ikm.\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hkdfInput(HKDFContext *context, const unsigned char *ikm,\n\tsize_t ikm_len)\n{\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\treturn hmacInput(&context->hmacContext, ikm, ikm_len);\n}\n\n/*\n * hkdfFinalBits\n *\n * Description:\n *   This function will add in any final bits of the\n *   input keying material.\n *\n * Parameters:\n *   context: [in/out]\n *     The HKDF context to update\n *   ikm_bits: [in]\n *     The final bits of the input keying material, in the upper\n *     portion of the byte.  (Use 0b###00000 instead of 0b00000###\n *     to input the three bits ###.)\n *   ikm_bit_count: [in]\n *     The number of bits in message_bits, between 1 and 7.\n *\n * Returns:\n *   sha Error Code.\n */\nint hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits,\n\tunsigned int ikm_bit_count)\n{\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\treturn hmacFinalBits(&context->hmacContext, ikm_bits, ikm_bit_count);\n}\n\n/*\n * hkdfResult\n *\n * Description:\n *   This function will finish the HKDF extraction and perform the\n *   final HKDF expansion.\n *\n * Parameters:\n *   context: [in/out]\n *     The HKDF context to use to calculate the HKDF hash.\n *   prk[ ]: [out]\n *     An optional location to store the HKDF extraction.\n *     Either NULL, or pointer to a buffer that must be\n *     larger than USHAHashSize(whichSha);\n *   info[ ]: [in]\n *     The optional context and application specific information.\n *     If info == NULL or a zero-length string, it is ignored.\n *   info_len: [in]\n *     The length of the optional context and application specific\n *     information.  (Ignored if info == NULL.)\n *   okm[ ]: [out]\n *     Where the HKDF is to be stored.\n *   okm_len: [in]\n *     The length of the buffer to hold okm.\n *     okm_len must be <= 255 * USHABlockSize(whichSha)\n *\n * Returns:\n *   sha Error Code.\n *\n */\nint hkdfResult(HKDFContext *context,\n\tuint8_t prk[USHAMaxHashSize],\n\tconst unsigned char *info, size_t info_len,\n\tuint8_t okm[], size_t okm_len)\n{\n\tuint8_t prkbuf[USHAMaxHashSize];\n\tint ret;\n\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\tif (!okm) return context->Corrupted = shaBadParam;\n\tif (!prk) prk = prkbuf;\n\n\tif (!(ret = hmacResult(&context->hmacContext, prk)))\n\t\tret = hkdfExpand(context->whichSha, prk, context->hashSize, info, info_len, okm, okm_len);\n\tcontext->Computed = 1;\n\treturn context->Corrupted = ret;\n}\n\n"
  },
  {
    "path": "nfq/crypto/hmac.c",
    "content": "/**************************** hmac.c ***************************/\n/***************** See RFC 6234 for details. *******************/\n/* Copyright (c) 2011 IETF Trust and the persons identified as */\n/* authors of the code.  All rights reserved.                  */\n/* See sha.h for terms of use and redistribution.              */\n\n/*\n *  Description:\n *      This file implements the HMAC algorithm (Keyed-Hashing for\n *      Message Authentication, [RFC 2104]), expressed in terms of\n *      the various SHA algorithms.\n */\n\n#include \"sha.h\"\n#include <stddef.h>\n\n /*\n  *  hmac\n  *\n  *  Description:\n  *      This function will compute an HMAC message digest.\n  *\n  *  Parameters:\n  *      whichSha: [in]\n  *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n  *      message_array[ ]: [in]\n  *          An array of octets representing the message.\n  *          Note: in RFC 2104, this parameter is known\n  *          as 'text'.\n  *      length: [in]\n  *          The length of the message in message_array.\n  *      key[ ]: [in]\n  *          The secret shared key.\n  *      key_len: [in]\n  *          The length of the secret shared key.\n  *      digest[ ]: [out]\n  *          Where the digest is to be returned.\n  *          NOTE: The length of the digest is determined by\n  *              the value of whichSha.\n  *\n  *  Returns:\n  *      sha Error Code.\n  *\n  */\n\nint hmac(SHAversion whichSha,\n\tconst unsigned char *message_array, size_t length,\n\tconst unsigned char *key, size_t key_len,\n\tuint8_t digest[USHAMaxHashSize])\n{\n\tHMACContext context;\n\tint ret;\n\tif ((ret=hmacReset(&context, whichSha, key, key_len))) return ret;\n\tif ((ret=hmacInput(&context, message_array, length))) return ret;\n\treturn hmacResult(&context, digest);\n}\n\n/*\n *  hmacReset\n *\n *  Description:\n *      This function will initialize the hmacContext in preparation\n *      for computing a new HMAC message digest.\n *\n *  Parameters:\n *      context: [in/out]\n *          The context to reset.\n *      whichSha: [in]\n *          One of SHA1, SHA224, SHA256, SHA384, SHA512\n *      key[ ]: [in]\n *          The secret shared key.\n *      key_len: [in]\n *          The length of the secret shared key.\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hmacReset(HMACContext *context, enum SHAversion whichSha,\n\tconst unsigned char *key, size_t key_len)\n{\n\tsize_t i, blocksize, hashsize;\n\tint ret;\n\n\t/* inner padding - key XORd with ipad */\n\tunsigned char k_ipad[USHA_Max_Message_Block_Size];\n\n\t/* temporary buffer when keylen > blocksize */\n\tunsigned char tempkey[USHAMaxHashSize];\n\n\tif (!context) return shaNull;\n\tcontext->Computed = 0;\n\tcontext->Corrupted = shaSuccess;\n\n\tblocksize = context->blockSize = USHABlockSize(whichSha);\n\thashsize = context->hashSize = USHAHashSize(whichSha);\n\tcontext->whichSha = whichSha;\n\n\t/*\n\t * If key is longer than the hash blocksize,\n\t * reset it to key = HASH(key).\n\t */\n\tif (key_len > blocksize) {\n\t\tUSHAContext tcontext;\n\t\tif ((ret=USHAReset(&tcontext, whichSha)) || (ret=USHAInput(&tcontext, key, key_len)) || (ret=USHAResult(&tcontext, tempkey)))\n\t\t\treturn ret;\n\n\t\tkey = tempkey;\n\t\tkey_len = hashsize;\n\t}\n\n\t/*\n\t * The HMAC transform looks like:\n\t *\n\t * SHA(K XOR opad, SHA(K XOR ipad, text))\n\t *\n\t * where K is an n byte key, 0-padded to a total of blocksize bytes,\n\t * ipad is the byte 0x36 repeated blocksize times,\n\t * opad is the byte 0x5c repeated blocksize times,\n\t * and text is the data being protected.\n\t */\n\n\t /* store key into the pads, XOR'd with ipad and opad values */\n\tfor (i = 0; i < key_len; i++) {\n\t\tk_ipad[i] = key[i] ^ 0x36;\n\t\tcontext->k_opad[i] = key[i] ^ 0x5c;\n\t}\n\t/* remaining pad bytes are '\\0' XOR'd with ipad and opad values */\n\tfor (; i < blocksize; i++) {\n\t\tk_ipad[i] = 0x36;\n\t\tcontext->k_opad[i] = 0x5c;\n\t}\n\n\t/* perform inner hash */\n\t/* init context for 1st pass */\n\tif (!(ret = USHAReset(&context->shaContext, whichSha)))\n\t\t/* and start with inner pad */\n\t\tret = USHAInput(&context->shaContext, k_ipad, blocksize);\n\treturn context->Corrupted = ret;\n}\n\n/*\n *  hmacInput\n *\n *  Description:\n *      This function accepts an array of octets as the next portion\n *      of the message.  It may be called multiple times.\n *\n *  Parameters:\n *      context: [in/out]\n *          The HMAC context to update.\n *      text[ ]: [in]\n *          An array of octets representing the next portion of\n *          the message.\n *      text_len: [in]\n *          The length of the message in text.\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint hmacInput(HMACContext *context, const unsigned char *text,\n\tsize_t text_len)\n{\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\t/* then text of datagram */\n\treturn context->Corrupted =\n\t\tUSHAInput(&context->shaContext, text, text_len);\n}\n\n/*\n * hmacFinalBits\n *\n * Description:\n *   This function will add in any final bits of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The HMAC context to update.\n *   message_bits: [in]\n *     The final bits of the message, in the upper portion of the\n *     byte.  (Use 0b###00000 instead of 0b00000### to input the\n *     three bits ###.)\n *   length: [in]\n *     The number of bits in message_bits, between 1 and 7.\n *\n * Returns:\n *   sha Error Code.\n */\nint hmacFinalBits(HMACContext *context,\n\tuint8_t bits, unsigned int bit_count)\n{\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\t/* then final bits of datagram */\n\treturn context->Corrupted = USHAFinalBits(&context->shaContext, bits, bit_count);\n}\n\n/*\n * hmacResult\n *\n * Description:\n *   This function will return the N-byte message digest into the\n *   Message_Digest array provided by the caller.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to use to calculate the HMAC hash.\n *   digest[ ]: [out]\n *     Where the digest is returned.\n *     NOTE 2: The length of the hash is determined by the value of\n *      whichSha that was passed to hmacReset().\n *\n * Returns:\n *   sha Error Code.\n *\n */\nint hmacResult(HMACContext *context, uint8_t *digest)\n{\n\tint ret;\n\tif (!context) return shaNull;\n\tif (context->Corrupted) return context->Corrupted;\n\tif (context->Computed) return context->Corrupted = shaStateError;\n\n\t/* finish up 1st pass */\n\t/* (Use digest here as a temporary buffer.) */\n\tif (!(ret=USHAResult(&context->shaContext, digest)) &&\n\t\t/* perform outer SHA */\n\t\t/* init context for 2nd pass */\n\t\t!(ret=USHAReset(&context->shaContext, context->whichSha)) &&\n\t\t/* start with outer pad */\n\t\t!(ret=USHAInput(&context->shaContext, context->k_opad, context->blockSize)) &&\n\t\t/* then results of 1st hash */\n\t\t!(ret=USHAInput(&context->shaContext, digest, context->hashSize)))\n\t\t/* finish up 2nd pass */\n\t\tret=USHAResult(&context->shaContext, digest);\n\n\tcontext->Computed = 1;\n\treturn context->Corrupted = ret;\n}\n"
  },
  {
    "path": "nfq/crypto/sha-private.h",
    "content": "/************************ sha-private.h ************************/\n/***************** See RFC 6234 for details. *******************/\n#pragma once\n/*\n * These definitions are defined in FIPS 180-3, section 4.1.\n * Ch() and Maj() are defined identically in sections 4.1.1,\n * 4.1.2, and 4.1.3.\n *\n * The definitions used in FIPS 180-3 are as follows:\n */\n\n#ifndef USE_MODIFIED_MACROS\n#define SHA_Ch(x,y,z)        (((x) & (y)) ^ ((~(x)) & (z)))\n#define SHA_Maj(x,y,z)       (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))\n#else /* USE_MODIFIED_MACROS */\n/*\n * The following definitions are equivalent and potentially faster.\n */\n\n#define SHA_Ch(x, y, z)      (((x) & ((y) ^ (z))) ^ (z))\n#define SHA_Maj(x, y, z)     (((x) & ((y) | (z))) | ((y) & (z)))\n\n#endif /* USE_MODIFIED_MACROS */\n\n#define SHA_Parity(x, y, z)  ((x) ^ (y) ^ (z))\n"
  },
  {
    "path": "nfq/crypto/sha.h",
    "content": "/**************************** sha.h ****************************/\n/***************** See RFC 6234 for details. *******************/\n/*\n   Copyright (c) 2011 IETF Trust and the persons identified as\n   authors of the code.  All rights reserved.\n   Redistribution and use in source and binary forms, with or\n   without modification, are permitted provided that the following\n   conditions are met:\n   - Redistributions of source code must retain the above\n     copyright notice, this list of conditions and\n     the following disclaimer.\n   - Redistributions in binary form must reproduce the above\n     copyright notice, this list of conditions and the following\n     disclaimer in the documentation and/or other materials provided\n     with the distribution.\n   - Neither the name of Internet Society, IETF or IETF Trust, nor\n     the names of specific contributors, may be used to endorse or\n     promote products derived from this software without specific\n     prior written permission.\n   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n   CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n   DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\n   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#pragma once\n\n/*\n *  Description:\n *      This file implements the Secure Hash Algorithms\n *      as defined in the U.S. National Institute of Standards\n *      and Technology Federal Information Processing Standards\n *      Publication (FIPS PUB) 180-3 published in October 2008\n *      and formerly defined in its predecessors, FIPS PUB 180-1\n *      and FIP PUB 180-2.\n *\n *      A combined document showing all algorithms is available at\n *              http://csrc.nist.gov/publications/fips/\n *                     fips180-3/fips180-3_final.pdf\n *\n *      The five hashes are defined in these sizes:\n *              SHA-1           20 byte / 160 bit\n *              SHA-224         28 byte / 224 bit\n *              SHA-256         32 byte / 256 bit\n *              SHA-384         48 byte / 384 bit\n *              SHA-512         64 byte / 512 bit\n *\n *  Compilation Note:\n *    These files may be compiled with two options:\n *        USE_32BIT_ONLY - use 32-bit arithmetic only, for systems\n *                         without 64-bit integers\n *\n *        USE_MODIFIED_MACROS - use alternate form of the SHA_Ch()\n *                         and SHA_Maj() macros that are equivalent\n *                         and potentially faster on many systems\n *\n */\n\n#include <stdint.h>\n#include <stddef.h>\n\n/*\n * If you do not have the ISO standard stdint.h header file, then you\n * must typedef the following:\n *    name              meaning\n *  uint64_t         unsigned 64-bit integer\n *  uint32_t         unsigned 32-bit integer\n *  uint8_t          unsigned 8-bit integer (i.e., unsigned char)\n *  int_least16_t    integer of >= 16 bits\n *\n * See stdint-example.h\n */\n\n#ifndef _SHA_enum_\n#define _SHA_enum_\n/*\n *  All SHA functions return one of these values.\n */\nenum {\n    shaSuccess = 0,\n    shaNull,            /* Null pointer parameter */\n    shaInputTooLong,    /* input data too long */\n    shaStateError,      /* called Input after FinalBits or Result */\n    shaBadParam         /* passed a bad parameter */\n};\n#endif /* _SHA_enum_ */\n\n/*\n *  These constants hold size information for each of the SHA\n *  hashing operations\n */\nenum {\n    SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64,\n    SHA256_Message_Block_Size = 64,\n    USHA_Max_Message_Block_Size = SHA256_Message_Block_Size,\n\n    SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32,\n    USHAMaxHashSize = SHA256HashSize,\n\n    SHA1HashSizeBits = 160, SHA224HashSizeBits = 224,\n    SHA256HashSizeBits = 256, USHAMaxHashSizeBits = SHA256HashSizeBits\n};\n\n/*\n *  These constants are used in the USHA (Unified SHA) functions.\n */\ntypedef enum SHAversion {\n    SHA224, SHA256\n} SHAversion;\n\n/*\n *  This structure will hold context information for the SHA-256\n *  hashing operation.\n */\ntypedef struct SHA256Context {\n    uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */\n\n    uint32_t Length_High;               /* Message length in bits */\n    uint32_t Length_Low;                /* Message length in bits */\n\n    int_least16_t Message_Block_Index;  /* Message_Block array index */\n                                        /* 512-bit message blocks */\n    uint8_t Message_Block[SHA256_Message_Block_Size];\n\n    int Computed;                   /* Is the hash computed? */\n    int Corrupted;                  /* Cumulative corruption code */\n} SHA256Context;\n\n/*\n *  This structure will hold context information for the SHA-224\n *  hashing operation.  It uses the SHA-256 structure for computation.\n */\ntypedef struct SHA256Context SHA224Context;\n\n/*\n *  This structure holds context information for all SHA\n *  hashing operations.\n */\ntypedef struct USHAContext {\n    int whichSha;               /* which SHA is being used */\n    union {\n      SHA224Context sha224Context; SHA256Context sha256Context;\n    } ctx;\n\n} USHAContext;\n\n/*\n *  This structure will hold context information for the HMAC\n *  keyed-hashing operation.\n */\ntypedef struct HMACContext {\n    int whichSha;               /* which SHA is being used */\n    int hashSize;               /* hash size of SHA being used */\n    int blockSize;              /* block size of SHA being used */\n    USHAContext shaContext;     /* SHA context */\n    unsigned char k_opad[USHA_Max_Message_Block_Size];\n                        /* outer padding - key XORd with opad */\n    int Computed;               /* Is the MAC computed? */\n    int Corrupted;              /* Cumulative corruption code */\n\n} HMACContext;\n\n/*\n *  This structure will hold context information for the HKDF\n *  extract-and-expand Key Derivation Functions.\n */\ntypedef struct HKDFContext {\n    int whichSha;               /* which SHA is being used */\n    HMACContext hmacContext;\n    int hashSize;               /* hash size of SHA being used */\n    unsigned char prk[USHAMaxHashSize];\n                        /* pseudo-random key - output of hkdfInput */\n    int Computed;               /* Is the key material computed? */\n    int Corrupted;              /* Cumulative corruption code */\n} HKDFContext;\n\n/*\n *  Function Prototypes\n */\n\n\n/* SHA-224 */\nint SHA224Reset(SHA224Context *);\nint SHA224Input(SHA224Context *, const uint8_t *bytes,\n                       unsigned int bytecount);\nint SHA224FinalBits(SHA224Context *, uint8_t bits,\n                           unsigned int bit_count);\nint SHA224Result(SHA224Context *,\n                        uint8_t Message_Digest[SHA224HashSize]);\n\n/* SHA-256 */\nint SHA256Reset(SHA256Context *);\nint SHA256Input(SHA256Context *, const uint8_t *bytes,\n                       unsigned int bytecount);\nint SHA256FinalBits(SHA256Context *, uint8_t bits,\n                           unsigned int bit_count);\nint SHA256Result(SHA256Context *,\n                        uint8_t Message_Digest[SHA256HashSize]);\n\n/* Unified SHA functions, chosen by whichSha */\nint USHAReset(USHAContext *context, SHAversion whichSha);\nint USHAInput(USHAContext *context,\n                     const uint8_t *bytes, unsigned int bytecount);\nint USHAFinalBits(USHAContext *context,\n                         uint8_t bits, unsigned int bit_count);\nint USHAResult(USHAContext *context,\n                      uint8_t Message_Digest[USHAMaxHashSize]);\nint USHABlockSize(enum SHAversion whichSha);\nint USHAHashSize(enum SHAversion whichSha);\n\n/*\n * HMAC Keyed-Hashing for Message Authentication, RFC 2104,\n * for all SHAs.\n * This interface allows a fixed-length text input to be used.\n */\nint hmac(SHAversion whichSha, /* which SHA algorithm to use */\n    const unsigned char *text,     /* pointer to data stream */\n    size_t text_len,                  /* length of data stream */\n    const unsigned char *key,      /* pointer to authentication key */\n    size_t key_len,                   /* length of authentication key */\n    uint8_t digest[USHAMaxHashSize]); /* caller digest to fill in */\n\n/*\n * HMAC Keyed-Hashing for Message Authentication, RFC 2104,\n * for all SHAs.\n * This interface allows any length of text input to be used.\n */\nint hmacReset(HMACContext *context, enum SHAversion whichSha,\n                     const unsigned char *key, size_t key_len);\nint hmacInput(HMACContext *context, const unsigned char *text,\n                     size_t text_len);\nint hmacFinalBits(HMACContext *context, uint8_t bits,\n                         unsigned int bit_count);\nint hmacResult(HMACContext *context,\n                      uint8_t digest[USHAMaxHashSize]);\n\n\n/*\n * HKDF HMAC-based Extract-and-Expand Key Derivation Function,\n * RFC 5869, for all SHAs.\n */\nint hkdf(SHAversion whichSha,\n    const unsigned char *salt, size_t salt_len,\n    const unsigned char *ikm, size_t ikm_len,\n    const unsigned char *info, size_t info_len,\n    uint8_t okm[ ], size_t okm_len);\n\nint hkdfExtract(SHAversion whichSha, const unsigned char *salt,\n                       size_t salt_len, const unsigned char *ikm,\n                       size_t ikm_len, uint8_t prk[USHAMaxHashSize]);\nint hkdfExpand(SHAversion whichSha, const uint8_t prk[ ],\n                      size_t prk_len, const unsigned char *info,\n                      size_t info_len, uint8_t okm[ ], size_t okm_len);\n\n/*\n * HKDF HMAC-based Extract-and-Expand Key Derivation Function,\n * RFC 5869, for all SHAs.\n * This interface allows any length of text input to be used.\n */\nint hkdfReset(HKDFContext *context, enum SHAversion whichSha,\n                     const unsigned char *salt, size_t salt_len);\nint hkdfInput(HKDFContext *context, const unsigned char *ikm,\n                     size_t ikm_len);\nint hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits,\n                         unsigned int ikm_bit_count);\nint hkdfResult(HKDFContext *context,\n                      uint8_t prk[USHAMaxHashSize],\n                      const unsigned char *info, size_t info_len,\n                      uint8_t okm[USHAMaxHashSize], size_t okm_len);\n"
  },
  {
    "path": "nfq/crypto/sha224-256.c",
    "content": "/************************* sha224-256.c ************************/\n/***************** See RFC 6234 for details. *******************/\n/* Copyright (c) 2011 IETF Trust and the persons identified as */\n/* authors of the code.  All rights reserved.                  */\n/* See sha.h for terms of use and redistribution.              */\n\n/*\n * Description:\n *   This file implements the Secure Hash Algorithms SHA-224 and\n *   SHA-256 as defined in the U.S. National Institute of Standards\n *   and Technology Federal Information Processing Standards\n *   Publication (FIPS PUB) 180-3 published in October 2008\n *   and formerly defined in its predecessors, FIPS PUB 180-1\n *   and FIP PUB 180-2.\n *\n *   A combined document showing all algorithms is available at\n *       http://csrc.nist.gov/publications/fips/\n *              fips180-3/fips180-3_final.pdf\n *\n *   The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit\n *   message digests for a given data stream.  It should take about\n *   2**n steps to find a message with the same digest as a given\n *   message and 2**(n/2) to find any two messages with the same\n *   digest, when n is the digest size in bits.  Therefore, this\n *   algorithm can serve as a means of providing a\n *   \"fingerprint\" for a message.\n *\n * Portability Issues:\n *   SHA-224 and SHA-256 are defined in terms of 32-bit \"words\".\n *   This code uses <stdint.h> (included via \"sha.h\") to define 32-\n *   and 8-bit unsigned integer types.  If your C compiler does not\n *   support 32-bit unsigned integers, this code is not\n *   appropriate.\n *\n * Caveats:\n *   SHA-224 and SHA-256 are designed to work with messages less\n *   than 2^64 bits long.  This implementation uses SHA224/256Input()\n *   to hash the bits that are a multiple of the size of an 8-bit\n *   octet, and then optionally uses SHA224/256FinalBits()\n *   to hash the final few bits of the input.\n */\n\n#include \"sha.h\"\n#include \"sha-private.h\"\n\n/* Define the SHA shift, rotate left, and rotate right macros */\n#define SHA256_SHR(bits,word)      ((word) >> (bits))\n#define SHA256_ROTL(bits,word)                         \\\n  (((word) << (bits)) | ((word) >> (32-(bits))))\n#define SHA256_ROTR(bits,word)                         \\\n  (((word) >> (bits)) | ((word) << (32-(bits))))\n\n/* Define the SHA SIGMA and sigma macros */\n#define SHA256_SIGMA0(word)   \\\n  (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word))\n#define SHA256_SIGMA1(word)   \\\n  (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word))\n#define SHA256_sigma0(word)   \\\n  (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word))\n#define SHA256_sigma1(word)   \\\n  (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word))\n\n/*\n * Add \"length\" to the length.\n * Set Corrupted when overflow has occurred.\n */\nstatic int SHA224_256AddLength(SHA256Context *context, uint32_t length)\n{\n\tuint32_t addTemp = context->Length_Low;\n\tif (((context->Length_Low += length) < addTemp) && (++(context)->Length_High == 0)) context->Corrupted = shaInputTooLong;\n\treturn context->Corrupted;\n}\n\n/* Local Function Prototypes */\nstatic int SHA224_256Reset(SHA256Context *context, uint32_t *H0);\nstatic void SHA224_256ProcessMessageBlock(SHA256Context *context);\nstatic void SHA224_256Finalize(SHA256Context *context,\n  uint8_t Pad_Byte);\nstatic void SHA224_256PadMessage(SHA256Context *context,\n  uint8_t Pad_Byte);\nstatic int SHA224_256ResultN(SHA256Context *context,\n  uint8_t Message_Digest[ ], int HashSize);\n\n/* Initial Hash Values: FIPS 180-3 section 5.3.2 */\nstatic uint32_t SHA224_H0[SHA256HashSize/4] = {\n    0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939,\n    0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4\n};\n\n/* Initial Hash Values: FIPS 180-3 section 5.3.3 */\nstatic uint32_t SHA256_H0[SHA256HashSize/4] = {\n  0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,\n  0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19\n};\n\n/*\n * SHA224Reset\n *\n * Description:\n *   This function will initialize the SHA224Context in preparation\n *   for computing a new SHA224 message digest.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to reset.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA224Reset(SHA224Context *context)\n{\n  return SHA224_256Reset(context, SHA224_H0);\n}\n\n/*\n * SHA224Input\n *\n * Description:\n *   This function accepts an array of octets as the next portion\n *   of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   message_array[ ]: [in]\n *     An array of octets representing the next portion of\n *     the message.\n *   length: [in]\n *     The length of the message in message_array.\n *\n * Returns:\n *   sha Error Code.\n *\n */\nint SHA224Input(SHA224Context *context, const uint8_t *message_array,\n    unsigned int length)\n{\n  return SHA256Input(context, message_array, length);\n}\n\n/*\n * SHA224FinalBits\n *\n * Description:\n *   This function will add in any final bits of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   message_bits: [in]\n *     The final bits of the message, in the upper portion of the\n *     byte.  (Use 0b###00000 instead of 0b00000### to input the\n *     three bits ###.)\n *   length: [in]\n *     The number of bits in message_bits, between 1 and 7.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA224FinalBits(SHA224Context *context,\n                    uint8_t message_bits, unsigned int length)\n{\n  return SHA256FinalBits(context, message_bits, length);\n}\n\n/*\n * SHA224Result\n *\n * Description:\n *   This function will return the 224-bit message digest\n *   into the Message_Digest array provided by the caller.\n *   NOTE:\n *    The first octet of hash is stored in the element with index 0,\n *    the last octet of hash in the element with index 27.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to use to calculate the SHA hash.\n *   Message_Digest[ ]: [out]\n *     Where the digest is returned.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA224Result(SHA224Context *context,\n    uint8_t Message_Digest[SHA224HashSize])\n{\n  return SHA224_256ResultN(context, Message_Digest, SHA224HashSize);\n}\n\n/*\n * SHA256Reset\n *\n * Description:\n *   This function will initialize the SHA256Context in preparation\n *   for computing a new SHA256 message digest.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to reset.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA256Reset(SHA256Context *context)\n{\n  return SHA224_256Reset(context, SHA256_H0);\n}\n\n/*\n * SHA256Input\n *\n * Description:\n *   This function accepts an array of octets as the next portion\n *   of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   message_array[ ]: [in]\n *     An array of octets representing the next portion of\n *     the message.\n *   length: [in]\n *     The length of the message in message_array.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA256Input(SHA256Context *context, const uint8_t *message_array,\n    unsigned int length)\n{\n  if (!context) return shaNull;\n  if (!length) return shaSuccess;\n  if (!message_array) return shaNull;\n  if (context->Computed) return context->Corrupted = shaStateError;\n  if (context->Corrupted) return context->Corrupted;\n\n  while (length--) {\n    context->Message_Block[context->Message_Block_Index++] =\n            *message_array;\n\n    if ((SHA224_256AddLength(context, 8) == shaSuccess) &&\n      (context->Message_Block_Index == SHA256_Message_Block_Size))\n      SHA224_256ProcessMessageBlock(context);\n\n    message_array++;\n  }\n\n  return context->Corrupted;\n\n}\n\n/*\n * SHA256FinalBits\n *\n * Description:\n *   This function will add in any final bits of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   message_bits: [in]\n *     The final bits of the message, in the upper portion of the\n *     byte.  (Use 0b###00000 instead of 0b00000### to input the\n *     three bits ###.)\n *   length: [in]\n *     The number of bits in message_bits, between 1 and 7.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA256FinalBits(SHA256Context *context,\n                    uint8_t message_bits, unsigned int length)\n{\n  static uint8_t masks[8] = {\n      /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80,\n      /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0,\n      /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8,\n      /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE\n  };\n  static uint8_t markbit[8] = {\n      /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40,\n      /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10,\n      /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04,\n      /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01\n  };\n\n  if (!context) return shaNull;\n  if (!length) return shaSuccess;\n  if (context->Corrupted) return context->Corrupted;\n  if (context->Computed) return context->Corrupted = shaStateError;\n  if (length >= 8) return context->Corrupted = shaBadParam;\n\n  SHA224_256AddLength(context, length);\n  SHA224_256Finalize(context, (uint8_t)\n    ((message_bits & masks[length]) | markbit[length]));\n\n  return context->Corrupted;\n}\n\n/*\n * SHA256Result\n *\n * Description:\n *   This function will return the 256-bit message digest\n *   into the Message_Digest array provided by the caller.\n *   NOTE:\n *    The first octet of hash is stored in the element with index 0,\n *    the last octet of hash in the element with index 31.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to use to calculate the SHA hash.\n *   Message_Digest[ ]: [out]\n *     Where the digest is returned.\n *\n * Returns:\n *   sha Error Code.\n */\nint SHA256Result(SHA256Context *context,\n                 uint8_t Message_Digest[SHA256HashSize])\n{\n  return SHA224_256ResultN(context, Message_Digest, SHA256HashSize);\n}\n\n/*\n * SHA224_256Reset\n *\n * Description:\n *   This helper function will initialize the SHA256Context in\n *   preparation for computing a new SHA-224 or SHA-256 message digest.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to reset.\n *   H0[ ]: [in]\n *     The initial hash value array to use.\n *\n * Returns:\n *   sha Error Code.\n */\nstatic int SHA224_256Reset(SHA256Context *context, uint32_t *H0)\n{\n  if (!context) return shaNull;\n\n  context->Length_High = context->Length_Low = 0;\n  context->Message_Block_Index  = 0;\n\n  context->Intermediate_Hash[0] = H0[0];\n  context->Intermediate_Hash[1] = H0[1];\n  context->Intermediate_Hash[2] = H0[2];\n  context->Intermediate_Hash[3] = H0[3];\n  context->Intermediate_Hash[4] = H0[4];\n  context->Intermediate_Hash[5] = H0[5];\n  context->Intermediate_Hash[6] = H0[6];\n  context->Intermediate_Hash[7] = H0[7];\n\n  context->Computed  = 0;\n  context->Corrupted = shaSuccess;\n\n  return shaSuccess;\n}\n\n/*\n * SHA224_256ProcessMessageBlock\n *\n * Description:\n *   This helper function will process the next 512 bits of the\n *   message stored in the Message_Block array.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *\n * Returns:\n *   Nothing.\n *\n * Comments:\n *   Many of the variable names in this code, especially the\n *   single character names, were used because those were the\n *   names used in the Secure Hash Standard.\n */\nstatic void SHA224_256ProcessMessageBlock(SHA256Context *context)\n{\n  /* Constants defined in FIPS 180-3, section 4.2.2 */\n  static const uint32_t K[64] = {\n      0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,\n      0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,\n      0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,\n      0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,\n      0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,\n      0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,\n      0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,\n      0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n      0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,\n      0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,\n      0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,\n      0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n      0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n  };\n  int        t, t4;                   /* Loop counter */\n  uint32_t   temp1, temp2;            /* Temporary word value */\n  uint32_t   W[64];                   /* Word sequence */\n  uint32_t   A, B, C, D, E, F, G, H;  /* Word buffers */\n\n  /*\n   * Initialize the first 16 words in the array W\n   */\n  for (t = t4 = 0; t < 16; t++, t4 += 4)\n    W[t] = (((uint32_t)context->Message_Block[t4]) << 24) |\n           (((uint32_t)context->Message_Block[t4 + 1]) << 16) |\n           (((uint32_t)context->Message_Block[t4 + 2]) << 8) |\n           (((uint32_t)context->Message_Block[t4 + 3]));\n  for (t = 16; t < 64; t++)\n    W[t] = SHA256_sigma1(W[t-2]) + W[t-7] +\n        SHA256_sigma0(W[t-15]) + W[t-16];\n\n  A = context->Intermediate_Hash[0];\n  B = context->Intermediate_Hash[1];\n  C = context->Intermediate_Hash[2];\n  D = context->Intermediate_Hash[3];\n  E = context->Intermediate_Hash[4];\n  F = context->Intermediate_Hash[5];\n  G = context->Intermediate_Hash[6];\n  H = context->Intermediate_Hash[7];\n\n  for (t = 0; t < 64; t++) {\n    temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];\n    temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C);\n    H = G;\n    G = F;\n    F = E;\n    E = D + temp1;\n    D = C;\n    C = B;\n    B = A;\n    A = temp1 + temp2;\n  }\n\n  context->Intermediate_Hash[0] += A;\n  context->Intermediate_Hash[1] += B;\n  context->Intermediate_Hash[2] += C;\n  context->Intermediate_Hash[3] += D;\n  context->Intermediate_Hash[4] += E;\n  context->Intermediate_Hash[5] += F;\n  context->Intermediate_Hash[6] += G;\n  context->Intermediate_Hash[7] += H;\n\n  context->Message_Block_Index = 0;\n}\n\n/*\n * SHA224_256Finalize\n *\n * Description:\n *   This helper function finishes off the digest calculations.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   Pad_Byte: [in]\n *     The last byte to add to the message block before the 0-padding\n *     and length.  This will contain the last bits of the message\n *     followed by another single bit.  If the message was an\n *     exact multiple of 8-bits long, Pad_Byte will be 0x80.\n *\n * Returns:\n *   sha Error Code.\n */\nstatic void SHA224_256Finalize(SHA256Context *context,\n    uint8_t Pad_Byte)\n{\n  int i;\n  SHA224_256PadMessage(context, Pad_Byte);\n  /* message may be sensitive, so clear it out */\n  for (i = 0; i < SHA256_Message_Block_Size; ++i)\n    context->Message_Block[i] = 0;\n  context->Length_High = 0;     /* and clear length */\n  context->Length_Low = 0;\n  context->Computed = 1;\n}\n\n/*\n * SHA224_256PadMessage\n *\n * Description:\n *   According to the standard, the message must be padded to the next\n *   even multiple of 512 bits.  The first padding bit must be a '1'.\n *   The last 64 bits represent the length of the original message.\n *   All bits in between should be 0.  This helper function will pad\n *   the message according to those rules by filling the\n *   Message_Block array accordingly.  When it returns, it can be\n *   assumed that the message digest has been computed.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to pad.\n *   Pad_Byte: [in]\n *     The last byte to add to the message block before the 0-padding\n *     and length.  This will contain the last bits of the message\n *     followed by another single bit.  If the message was an\n *     exact multiple of 8-bits long, Pad_Byte will be 0x80.\n *\n * Returns:\n *   Nothing.\n */\nstatic void SHA224_256PadMessage(SHA256Context *context,\n    uint8_t Pad_Byte)\n{\n  /*\n   * Check to see if the current message block is too small to hold\n   * the initial padding bits and length.  If so, we will pad the\n   * block, process it, and then continue padding into a second\n   * block.\n   */\n  if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) {\n    context->Message_Block[context->Message_Block_Index++] = Pad_Byte;\n    while (context->Message_Block_Index < SHA256_Message_Block_Size)\n      context->Message_Block[context->Message_Block_Index++] = 0;\n    SHA224_256ProcessMessageBlock(context);\n  } else\n    context->Message_Block[context->Message_Block_Index++] = Pad_Byte;\n\n  while (context->Message_Block_Index < (SHA256_Message_Block_Size-8))\n    context->Message_Block[context->Message_Block_Index++] = 0;\n\n  /*\n   * Store the message length as the last 8 octets\n   */\n  context->Message_Block[56] = (uint8_t)(context->Length_High >> 24);\n  context->Message_Block[57] = (uint8_t)(context->Length_High >> 16);\n  context->Message_Block[58] = (uint8_t)(context->Length_High >> 8);\n  context->Message_Block[59] = (uint8_t)(context->Length_High);\n  context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24);\n  context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16);\n  context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8);\n  context->Message_Block[63] = (uint8_t)(context->Length_Low);\n\n  SHA224_256ProcessMessageBlock(context);\n}\n\n/*\n * SHA224_256ResultN\n *\n * Description:\n *   This helper function will return the 224-bit or 256-bit message\n *   digest into the Message_Digest array provided by the caller.\n *   NOTE:\n *    The first octet of hash is stored in the element with index 0,\n *    the last octet of hash in the element with index 27/31.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to use to calculate the SHA hash.\n *   Message_Digest[ ]: [out]\n *     Where the digest is returned.\n *   HashSize: [in]\n *     The size of the hash, either 28 or 32.\n *\n * Returns:\n *   sha Error Code.\n */\nstatic int SHA224_256ResultN(SHA256Context *context,\n    uint8_t Message_Digest[ ], int HashSize)\n{\n  int i;\n\n  if (!context) return shaNull;\n  if (!Message_Digest) return shaNull;\n  if (context->Corrupted) return context->Corrupted;\n\n  if (!context->Computed)\n    SHA224_256Finalize(context, 0x80);\n\n  for (i = 0; i < HashSize; ++i)\n    Message_Digest[i] = (uint8_t)\n      (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ));\n\n  return shaSuccess;\n}\n\n"
  },
  {
    "path": "nfq/crypto/usha.c",
    "content": "/**************************** usha.c ***************************/\n/***************** See RFC 6234 for details. *******************/\n/* Copyright (c) 2011 IETF Trust and the persons identified as */\n/* authors of the code.  All rights reserved.                  */\n/* See sha.h for terms of use and redistribution.              */\n\n/*\n *  Description:\n *     This file implements a unified interface to the SHA algorithms.\n */\n\n#include \"sha.h\"\n\n/*\n *  USHAReset\n *\n *  Description:\n *      This function will initialize the SHA Context in preparation\n *      for computing a new SHA message digest.\n *\n *  Parameters:\n *      context: [in/out]\n *          The context to reset.\n *      whichSha: [in]\n *          Selects which SHA reset to call\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint USHAReset(USHAContext *context, enum SHAversion whichSha)\n{\n  if (!context) return shaNull;\n  context->whichSha = whichSha;\n  switch (whichSha) {\n    case SHA224: return SHA224Reset((SHA224Context*)&context->ctx);\n    case SHA256: return SHA256Reset((SHA256Context*)&context->ctx);\n    default: return shaBadParam;\n  }\n}\n\n/*\n *  USHAInput\n *\n *  Description:\n *      This function accepts an array of octets as the next portion\n *      of the message.\n *\n *  Parameters:\n *      context: [in/out]\n *          The SHA context to update.\n *      message_array: [in]\n *          An array of octets representing the next portion of\n *          the message.\n *      length: [in]\n *          The length of the message in message_array.\n *\n *  Returns:\n *      sha Error Code.\n *\n */\nint USHAInput(USHAContext *context,\n              const uint8_t *bytes, unsigned int bytecount)\n{\n  if (!context) return shaNull;\n  switch (context->whichSha) {\n    case SHA224:\n      return SHA224Input((SHA224Context*)&context->ctx, bytes,\n          bytecount);\n    case SHA256:\n      return SHA256Input((SHA256Context*)&context->ctx, bytes,\n          bytecount);\n    default: return shaBadParam;\n  }\n}\n\n/*\n * USHAFinalBits\n *\n * Description:\n *   This function will add in any final bits of the message.\n *\n * Parameters:\n *   context: [in/out]\n *     The SHA context to update.\n *   message_bits: [in]\n *     The final bits of the message, in the upper portion of the\n *     byte.  (Use 0b###00000 instead of 0b00000### to input the\n *     three bits ###.)\n *   length: [in]\n *     The number of bits in message_bits, between 1 and 7.\n *\n * Returns:\n *   sha Error Code.\n */\nint USHAFinalBits(USHAContext *context,\n                  uint8_t bits, unsigned int bit_count)\n{\n  if (!context) return shaNull;\n  switch (context->whichSha) {\n    case SHA224:\n      return SHA224FinalBits((SHA224Context*)&context->ctx, bits,\n          bit_count);\n    case SHA256:\n      return SHA256FinalBits((SHA256Context*)&context->ctx, bits,\n          bit_count);\n    default: return shaBadParam;\n  }\n}\n\n/*\n * USHAResult\n *\n * Description:\n *   This function will return the message digest of the appropriate\n *   bit size, as returned by USHAHashSizeBits(whichSHA) for the\n *   'whichSHA' value used in the preceeding call to USHAReset,\n *   into the Message_Digest array provided by the caller.\n *\n * Parameters:\n *   context: [in/out]\n *     The context to use to calculate the SHA-1 hash.\n *   Message_Digest: [out]\n *     Where the digest is returned.\n *\n * Returns:\n *   sha Error Code.\n *\n */\nint USHAResult(USHAContext *context,\n               uint8_t Message_Digest[USHAMaxHashSize])\n{\n  if (!context) return shaNull;\n  switch (context->whichSha) {\n    case SHA224:\n      return SHA224Result((SHA224Context*)&context->ctx,\n                          Message_Digest);\n    case SHA256:\n      return SHA256Result((SHA256Context*)&context->ctx,\n                          Message_Digest);\n    default: return shaBadParam;\n  }\n}\n\n/*\n * USHABlockSize\n *\n * Description:\n *   This function will return the blocksize for the given SHA\n *   algorithm.\n *\n * Parameters:\n *   whichSha:\n *     which SHA algorithm to query\n *\n * Returns:\n *   block size\n *\n */\nint USHABlockSize(enum SHAversion whichSha)\n{\n  switch (whichSha) {\n    case SHA224: return SHA224_Message_Block_Size;\n    default:\n    case SHA256: return SHA256_Message_Block_Size;\n  }\n}\n\n/*\n * USHAHashSize\n *\n * Description:\n *   This function will return the hashsize for the given SHA\n *   algorithm.\n *\n * Parameters:\n *   whichSha:\n *     which SHA algorithm to query\n *\n * Returns:\n *   hash size\n *\n */\nint USHAHashSize(enum SHAversion whichSha)\n{\n  switch (whichSha) {\n    case SHA224: return SHA224HashSize;\n    default:\n    case SHA256: return SHA256HashSize;\n  }\n}\n"
  },
  {
    "path": "nfq/darkmagic.c",
    "content": "#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <arpa/inet.h>\n#include <sys/param.h>\n#include <errno.h>\n#include <fcntl.h>\n\n#ifndef IP_NODEFRAG\n// for very old toolchains\n#define IP_NODEFRAG     22\n#endif\n\n#include \"darkmagic.h\"\n#include \"helpers.h\"\n#include \"params.h\"\n#include \"nfqws.h\"\n\n#ifdef __CYGWIN__\n#include <wlanapi.h>\n#include <netlistmgr.h>\n\n#ifndef ERROR_INVALID_IMAGE_HASH\n#define ERROR_INVALID_IMAGE_HASH __MSABI_LONG(577)\n#endif\n\n#endif\n\n#ifdef __linux__\n#include <linux/nl80211.h>\n#include <linux/genetlink.h>\n#include <libmnl/libmnl.h>\n#include <net/if.h>\n#define _LINUX_IF_H // prevent conflict between linux/if.h and net/if.h in old gcc 4.x\n#include <linux/wireless.h>\n#include <sys/ioctl.h>\n#endif\n\nuint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment)\n{\n\treturn htonl(ntohl(netorder_value)+cpuorder_increment);\n}\nuint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment)\n{\n\treturn htons(ntohs(netorder_value)+cpuorder_increment);\n}\n\nbool ip_has_df(const struct ip *ip)\n{\n\treturn ip && !!(ntohs(ip->ip_off) & IP_DF);\n}\n\nuint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind)\n{\n\tuint8_t *t = (uint8_t*)(tcp+1);\n\tuint8_t *end = (uint8_t*)tcp + (tcp->th_off<<2);\n\twhile(t<end)\n\t{\n\t\tswitch(*t)\n\t\t{\n\t\t\tcase 0: // end\n\t\t\t\treturn NULL;\n\t\t\tcase 1: // noop\n\t\t\t\tt++;\n\t\t\t\tbreak;\n\t\t\tdefault: // kind,len,data\n\t\t\t\tif ((t+1)>=end || t[1]<2 || (t+t[1])>end)\n\t\t\t\t\treturn NULL;\n\t\t\t\tif (*t==kind)\n\t\t\t\t\treturn t;\n\t\t\t\tt+=t[1];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn NULL;\n}\nuint32_t *tcp_find_timestamps(struct tcphdr *tcp)\n{\n\tuint8_t *t = tcp_find_option(tcp,8);\n\treturn (t && t[1]==10) ? (uint32_t*)(t+2) : NULL;\n}\nuint8_t tcp_find_scale_factor(const struct tcphdr *tcp)\n{\n\tuint8_t *scale = tcp_find_option((struct tcphdr*)tcp,3); // tcp option 3 - scale factor\n\tif (scale && scale[1]==3) return scale[2];\n\treturn SCALE_NONE;\n}\nbool tcp_has_fastopen(const struct tcphdr *tcp)\n{\n\tuint8_t *opt;\n\t// new style RFC7413\n\topt = tcp_find_option((struct tcphdr*)tcp, 34);\n\tif (opt) return true;\n\t// old style RFC6994\n\topt = tcp_find_option((struct tcphdr*)tcp, 254);\n\treturn opt && opt[1]>=4 && opt[2]==0xF9 && opt[3]==0x89;\n}\nuint16_t tcp_find_mss(struct tcphdr *tcp)\n{\n\tuint8_t *t = tcp_find_option(tcp,2);\n\treturn (t && t[1]==4) ? *(uint16_t*)(t+2) : 0;\n}\nbool tcp_has_sack(struct tcphdr *tcp)\n{\n\tuint8_t *t = tcp_find_option(tcp,4);\n\treturn !!t;\n}\n\n// n prefix (nsport, nwsize) means network byte order\nstatic void fill_tcphdr(\n\tstruct tcphdr *tcp, uint32_t fooling, uint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nsport, uint16_t ndport,\n\tuint16_t nwsize, uint8_t scale_factor,\n\tuint32_t *timestamps,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tuint16_t data_len)\n{\n\tchar *tcpopt = (char*)(tcp+1);\n\tuint8_t t=0;\n\n\tmemset(tcp,0,sizeof(*tcp));\n\ttcp->th_sport = nsport;\n\ttcp->th_dport = ndport;\n\tif (fooling & FOOL_BADSEQ)\n\t{\n\t\ttcp->th_seq = net32_add(nseq,badseq_increment);\n\t\ttcp->th_ack = net32_add(nack_seq,badseq_ack_increment);\n\t}\n\telse\n\t{\n\t\ttcp->th_seq = nseq;\n\t\ttcp->th_ack = nack_seq;\n\t}\n\ttcp->th_off = 5;\n\tif ((fooling & FOOL_DATANOACK) && !(tcp_flags & (TH_SYN|TH_RST)) && data_len)\n\t\ttcp_flags &= ~TH_ACK;\n\ttcp->th_flags = (uint8_t)tcp_flags;\n\ttcp->th_x2 = (tcp_flags>>8) & 0xF;\n\ttcp->th_win     = nwsize;\n\tif (nmss)\n\t{\n\t\ttcpopt[t++] = 2; // kind\n\t\ttcpopt[t++] = 4; // len\n\t\t*(uint16_t*)(tcpopt+t) = nmss;\n\t\tt+=2;\n\t}\n\tif (sack)\n\t{\n\t\ttcpopt[t++] = 4; // kind\n\t\ttcpopt[t++] = 2; // len\n\t}\n\tif (fooling & FOOL_MD5SIG)\n\t{\n\t\ttcpopt[t] = 19; // kind\n\t\ttcpopt[t+1] = 18; // len\n\t\t*(uint32_t*)(tcpopt+t+2)=random();\n\t\t*(uint32_t*)(tcpopt+t+6)=random();\n\t\t*(uint32_t*)(tcpopt+t+10)=random();\n\t\t*(uint32_t*)(tcpopt+t+14)=random();\n\t\tt+=18;\n\t}\n\tif (timestamps)\n\t{\n\t\ttcpopt[t] = 8; // kind\n\t\ttcpopt[t+1] = 10; // len\n\t\tmemcpy(tcpopt+t+2,timestamps,8);\n\t\t// forge TSval, keep TSecr\n\t\tif (fooling & FOOL_TS) *(uint32_t*)(tcpopt+t+2) = net32_add(*(uint32_t*)(tcpopt+t+2),ts_increment);\n\t\tt+=10;\n\t}\n\tif (scale_factor!=SCALE_NONE)\n\t{\n\t\ttcpopt[t++]=3;\n\t\ttcpopt[t++]=3;\n\t\ttcpopt[t++]=scale_factor;\n\t}\n\twhile (t&3) tcpopt[t++]=1; // noop\n\ttcp->th_off += t>>2;\n\ttcp->th_sum = 0;\n}\nstatic uint16_t tcpopt_len(bool sack, bool mss, uint32_t fooling, const uint32_t *timestamps, uint8_t scale_factor)\n{\n\tuint16_t t=0;\n\tif (sack) t+=2;\n\tif (mss) t+=4;\n\tif (fooling & FOOL_MD5SIG) t+=18;\n\tif (timestamps) t+=10;\n\tif (scale_factor!=SCALE_NONE) t+=3;\n\treturn (t+3)&~3;\n}\n\n// n prefix (nsport, nwsize) means network byte order\nstatic void fill_udphdr(struct udphdr *udp, uint16_t nsport, uint16_t ndport, uint16_t len_payload)\n{\n\tudp->uh_sport = nsport;\n\tudp->uh_dport = ndport;\n\tudp->uh_ulen = htons(len_payload+sizeof(struct udphdr));\n\tudp->uh_sum = 0;\n}\n\nstatic void fill_iphdr(struct ip *ip, const struct in_addr *src, const struct in_addr *dst, uint16_t pktlen, uint8_t proto, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id)\n{\n\tip->ip_tos = tos;\n\tip->ip_sum = 0;\n\tip->ip_off = DF ? htons(IP_DF) : 0;\n\tip->ip_v = 4;\n\tip->ip_hl = 5;\n\tip->ip_len = htons(pktlen);\n\tip->ip_id = ip_id;\n\tip->ip_ttl = ttl;\n\tip->ip_p = proto;\n\tip->ip_src = *src;\n\tip->ip_dst = *dst;\n}\nstatic void fill_ip6hdr(struct ip6_hdr *ip6, const struct in6_addr *src, const struct in6_addr *dst, uint16_t payloadlen, uint8_t proto, uint8_t ttl, uint32_t flow_label)\n{\n\tip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(ntohl(flow_label) & 0x0FFFFFFF | 0x60000000);\n\tip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen);\n\tip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = proto;\n\tip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl;\n\tip6->ip6_src = *src;\n\tip6->ip6_dst = *dst;\n}\n\nbool prepare_tcp_segment4(\n\tconst struct sockaddr_in *src, const struct sockaddr_in *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\tuint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor);\n\tuint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len;\n\tuint16_t pktlen = sizeof(struct ip) + ip_payload_len;\n\tif (pktlen>*buflen) return false;\n\n\tstruct ip *ip = (struct ip*)buf;\n\tstruct tcphdr *tcp = (struct tcphdr*)(ip+1);\n\tuint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen;\n\n\tfill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_TCP, DF, ttl, tos, ip_id);\n\tfill_tcphdr(tcp,fooling,tcp_flags,sack,nmss,nseq,nack_seq,src->sin_port,dst->sin_port,nwsize,scale_factor,timestamps,ts_increment,badseq_increment,badseq_ack_increment,len);\n\n\tmemcpy(payload,data,len);\n\ttcp4_fix_checksum(tcp,ip_payload_len,&ip->ip_src,&ip->ip_dst);\n\tif (fooling & FOOL_BADSUM) tcp->th_sum^=(uint16_t)(1+random()%0xFFFF);\n\n\t*buflen = pktlen;\n\treturn true;\n}\n\nbool prepare_tcp_segment6(\n\tconst struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tuint8_t ttl,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\tuint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor);\n\tuint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len;\n\tuint16_t ip_payload_len = transport_payload_len +\n\t\t8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) +\n\t\t16*!!(fooling & FOOL_HOPBYHOP2) +\n\t\t8*!!(fooling & FOOL_DESTOPT) +\n\t\t8*!!(fooling & FOOL_IPFRAG1);\n\tuint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len;\n\tif (pktlen>*buflen) return false;\n\n\tstruct ip6_hdr *ip6 = (struct ip6_hdr*)buf;\n\tstruct tcphdr *tcp = (struct tcphdr*)(ip6+1);\n\tuint8_t proto = IPPROTO_TCP, *nexttype = NULL;\n\n\tif (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))\n\t{\n\t\tstruct ip6_hbh *hbh = (struct ip6_hbh*)tcp;\n\t\ttcp = (struct tcphdr*)((uint8_t*)tcp+8);\n\t\tmemset(hbh,0,8);\n\t\t// extra HOPBYHOP header. standard violation\n\t\tif (fooling & FOOL_HOPBYHOP2)\n\t\t{\n\t\t\thbh = (struct ip6_hbh*)tcp;\n\t\t\ttcp = (struct tcphdr*)((uint8_t*)tcp+8);\n\t\t\tmemset(hbh,0,8);\n\t\t}\n\t\thbh->ip6h_nxt = IPPROTO_TCP;\n\t\tnexttype = &hbh->ip6h_nxt;\n\t\tproto = IPPROTO_HOPOPTS;\n\t}\n\tif (fooling & FOOL_DESTOPT)\n\t{\n\t\tstruct ip6_dest *dest = (struct ip6_dest*)tcp;\n\t\ttcp = (struct tcphdr*)((uint8_t*)tcp+8);\n\t\tmemset(dest,0,8);\n\t\tdest->ip6d_nxt = IPPROTO_TCP;\n\t\tif (nexttype)\n\t\t\t*nexttype = IPPROTO_DSTOPTS;\n\t\telse\n\t\t\tproto = IPPROTO_DSTOPTS;\n\t\tnexttype = &dest->ip6d_nxt;\n\t}\n\tif (fooling & FOOL_IPFRAG1)\n\t{\n\t\tstruct ip6_frag *frag = (struct ip6_frag*)tcp;\n\t\ttcp = (struct tcphdr*)((uint8_t*)tcp+sizeof(struct ip6_frag));\n\t\tfrag->ip6f_nxt = IPPROTO_TCP;\n\t\tfrag->ip6f_ident = htonl(1+random()%0xFFFFFFFF);\n\t\tfrag->ip6f_reserved = 0;\n\t\tfrag->ip6f_offlg = 0;\n\t\tif (nexttype)\n\t\t\t*nexttype = IPPROTO_FRAGMENT;\n\t\telse\n\t\t\tproto = IPPROTO_FRAGMENT;\n\t}\n\n\tuint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen;\n\n\tfill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label);\n\tfill_tcphdr(tcp,fooling,tcp_flags,sack,nmss,nseq,nack_seq,src->sin6_port,dst->sin6_port,nwsize,scale_factor,timestamps,ts_increment,badseq_increment,badseq_ack_increment,len);\n\n\tmemcpy(payload,data,len);\n\ttcp6_fix_checksum(tcp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst);\n\tif (fooling & FOOL_BADSUM) tcp->th_sum^=(1+random()%0xFFFF);\n\n\t*buflen = pktlen;\n\treturn true;\n}\n\nbool prepare_tcp_segment(\n\tconst struct sockaddr *src, const struct sockaddr *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\treturn (src->sa_family==AF_INET && dst->sa_family==AF_INET) ?\n\t\tprepare_tcp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,tcp_flags,sack,nmss,nseq,nack_seq,nwsize,scale_factor,timestamps,DF,ttl,tos,ip_id,fooling,ts_increment,badseq_increment,badseq_ack_increment,data,len,buf,buflen) :\n\t\t(src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ?\n\t\tprepare_tcp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,tcp_flags,sack,nmss,nseq,nack_seq,nwsize,scale_factor,timestamps,ttl,flow_label,fooling,ts_increment,badseq_increment,badseq_ack_increment,data,len,buf,buflen) :\n\t\tfalse;\n}\n\n\n// padlen<0 means payload shrinking\nbool prepare_udp_segment4(\n\tconst struct sockaddr_in *src, const struct sockaddr_in *dst,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\tif ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte\n\tif ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range\n\tif (padlen<0)\n\t{\n\t\tlen+=padlen;\n\t\tpadlen=0;\n\t}\n\tuint16_t datalen = (uint16_t)(len + padlen);\n\tuint16_t ip_payload_len = sizeof(struct udphdr) + datalen;\n\tuint16_t pktlen = sizeof(struct ip) + ip_payload_len;\n\tif (pktlen>*buflen) return false;\n\n\tstruct ip *ip = (struct ip*)buf;\n\tstruct udphdr *udp = (struct udphdr*)(ip+1);\n\tuint8_t *payload = (uint8_t*)(udp+1);\n\n\n\tfill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_UDP, DF, ttl, tos, ip_id);\n\tfill_udphdr(udp, src->sin_port, dst->sin_port, datalen);\n\n\tmemcpy(payload,data,len);\n\tif (padding)\n\t\tfill_pattern(payload+len,padlen,padding,padding_size,0);\n\telse\n\t\tmemset(payload+len,0,padlen);\n\tudp4_fix_checksum(udp,ip_payload_len,&ip->ip_src,&ip->ip_dst);\n\tif (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF);\n\n\t*buflen = pktlen;\n\treturn true;\n}\nbool prepare_udp_segment6(\n\tconst struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,\n\tuint8_t ttl,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\tif ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte\n\tif ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range\n\tif (padlen<0)\n\t{\n\t\tlen+=padlen;\n\t\tpadlen=0;\n\t}\n\tuint16_t datalen = (uint16_t)(len + padlen);\n\tuint16_t transport_payload_len = sizeof(struct udphdr) + datalen;\n\tuint16_t ip_payload_len = transport_payload_len +\n\t\t8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) +\n\t\t16*!!(fooling & FOOL_HOPBYHOP2) +\n\t\t8*!!(fooling & FOOL_DESTOPT) +\n\t\t8*!!(fooling & FOOL_IPFRAG1);\n\tuint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len;\n\tif (pktlen>*buflen) return false;\n\n\tstruct ip6_hdr *ip6 = (struct ip6_hdr*)buf;\n\tstruct udphdr *udp = (struct udphdr*)(ip6+1);\n\tuint8_t proto = IPPROTO_UDP, *nexttype = NULL;\n\n\tif (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))\n\t{\n\t\tstruct ip6_hbh *hbh = (struct ip6_hbh*)udp;\n\t\tudp = (struct udphdr*)((uint8_t*)udp+8);\n\t\tmemset(hbh,0,8);\n\t\t// extra HOPBYHOP header. standard violation\n\t\tif (fooling & FOOL_HOPBYHOP2)\n\t\t{\n\t\t\thbh = (struct ip6_hbh*)udp;\n\t\t\tudp = (struct udphdr*)((uint8_t*)udp+8);\n\t\t\tmemset(hbh,0,8);\n\t\t}\n\t\thbh->ip6h_nxt = IPPROTO_UDP;\n\t\tnexttype = &hbh->ip6h_nxt;\n\t\tproto = IPPROTO_HOPOPTS;\n\t}\n\tif (fooling & FOOL_DESTOPT)\n\t{\n\t\tstruct ip6_dest *dest = (struct ip6_dest*)udp;\n\t\tudp = (struct udphdr*)((uint8_t*)udp+8);\n\t\tmemset(dest,0,8);\n\t\tdest->ip6d_nxt = IPPROTO_UDP;\n\t\tif (nexttype)\n\t\t\t*nexttype = IPPROTO_DSTOPTS;\n\t\telse\n\t\t\tproto = IPPROTO_DSTOPTS;\n\t\tnexttype = &dest->ip6d_nxt;\n\t}\n\tif (fooling & FOOL_IPFRAG1)\n\t{\n\t\tstruct ip6_frag *frag = (struct ip6_frag*)udp;\n\t\tudp = (struct udphdr*)((uint8_t*)udp+sizeof(struct ip6_frag));\n\t\tfrag->ip6f_nxt = IPPROTO_UDP;\n\t\tfrag->ip6f_ident = htonl(1+random()%0xFFFFFFFF);\n\t\tfrag->ip6f_reserved = 0;\n\t\tfrag->ip6f_offlg = 0;\n\t\tif (nexttype)\n\t\t\t*nexttype = IPPROTO_FRAGMENT;\n\t\telse\n\t\t\tproto = IPPROTO_FRAGMENT;\n\t}\n\n\tuint8_t *payload = (uint8_t*)(udp+1);\n\n\tfill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label);\n\tfill_udphdr(udp, src->sin6_port, dst->sin6_port, datalen);\n\n\tmemcpy(payload,data,len);\n\tif (padding)\n\t\tfill_pattern(payload+len,padlen,padding,padding_size,0);\n\telse\n\t\tmemset(payload+len,0,padlen);\n\tudp6_fix_checksum(udp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst);\n\tif (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF);\n\n\t*buflen = pktlen;\n\treturn true;\n}\nbool prepare_udp_segment(\n\tconst struct sockaddr *src, const struct sockaddr *dst,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen)\n{\n\treturn (src->sa_family==AF_INET && dst->sa_family==AF_INET) ?\n\t\tprepare_udp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,DF,ttl,tos,ip_id,fooling,padding,padding_size,padlen,data,len,buf,buflen) :\n\t\t(src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ?\n\t\tprepare_udp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,ttl,flow_label,fooling,padding,padding_size,padlen,data,len,buf,buflen) :\n\t\tfalse;\n}\n\nbool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen)\n{\n\tif ((len_pkt+8)<=*buflen && len_pkt>=sizeof(struct ip6_hdr))\n\t{\n\t\tstruct ip6_hdr *ip6 = (struct ip6_hdr *)buf;\n\t\tstruct ip6_ext *hdr = (struct ip6_ext*)(ip6+1);\n\t\t*ip6 = *(struct ip6_hdr*)data_pkt;\n\t\tmemset(hdr,0,8);\n\t\tmemcpy((uint8_t*)hdr+8, data_pkt+sizeof(struct ip6_hdr), len_pkt-sizeof(struct ip6_hdr));\n\t\thdr->ip6e_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;\n\t\tip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = type;\n\t\tip6->ip6_ctlun.ip6_un1.ip6_un1_plen = net16_add(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen, 8);\n\t\t*buflen = len_pkt + 8;\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n// split ipv4 packet into 2 fragments at data payload position frag_pos\nbool ip_frag4(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size)\n{\n\tuint16_t hdrlen, payload_len;\n\t// frag_pos must be 8-byte aligned\n\tif (frag_pos & 7 || pkt_size < sizeof(struct ip)) return false;\n\tpayload_len = htons(((struct ip *)pkt)->ip_len);\n\thdrlen = ((struct ip *)pkt)->ip_hl<<2;\n\tif (payload_len>pkt_size || hdrlen>pkt_size || hdrlen>payload_len) return false;\n\tpayload_len -= hdrlen;\n\tif (frag_pos>=payload_len || *pkt1_size<(hdrlen+frag_pos) || *pkt2_size<(hdrlen+payload_len-frag_pos)) return false;\n\n\tmemcpy(pkt1, pkt, hdrlen+frag_pos);\n\t((struct ip*)pkt1)->ip_off = htons(IP_MF);\n\t((struct ip*)pkt1)->ip_len = htons(hdrlen+frag_pos);\n\tif (ident!=(uint32_t)-1) ((struct ip*)pkt1)->ip_id = (uint16_t)ident;\n\t*pkt1_size=hdrlen+frag_pos;\n\tip4_fix_checksum((struct ip *)pkt1);\n\n\tmemcpy(pkt2, pkt, hdrlen);\n\tmemcpy(pkt2+hdrlen, pkt+hdrlen+frag_pos, payload_len-frag_pos);\n\t((struct ip*)pkt2)->ip_off = htons((uint16_t)frag_pos>>3 & IP_OFFMASK);\n\t((struct ip*)pkt2)->ip_len = htons(hdrlen+payload_len-frag_pos);\n\tif (ident!=(uint32_t)-1) ((struct ip*)pkt2)->ip_id = (uint16_t)ident;\n\t*pkt2_size=hdrlen+payload_len-frag_pos;\n\tip4_fix_checksum((struct ip *)pkt2);\n\n\treturn true;\n}\nbool ip_frag6(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size)\n{\n\tsize_t payload_len, unfragmentable;\n\tuint8_t *last_header_type;\n\tuint8_t proto;\n\tstruct ip6_frag *frag;\n\tconst uint8_t *payload;\n\n\tif (frag_pos & 7 || pkt_size < sizeof(struct ip6_hdr)) return false;\n\tpayload_len = sizeof(struct ip6_hdr) + htons(((struct ip6_hdr*)pkt)->ip6_ctlun.ip6_un1.ip6_un1_plen);\n\tif (pkt_size < payload_len) return false;\n\n\tpayload = pkt;\n\tproto_skip_ipv6((uint8_t**)&payload, &payload_len, &proto, &last_header_type);\n\tunfragmentable = payload - pkt;\n\n\t//printf(\"pkt_size=%zu FRAG_POS=%zu payload_len=%zu unfragmentable=%zu dh=%zu\\n\",pkt_size,frag_pos,payload_len,unfragmentable,last_header_type - pkt);\n\n\tif (frag_pos>=payload_len ||\n\t\t*pkt1_size<(unfragmentable + sizeof(struct ip6_frag) + frag_pos) ||\n\t\t*pkt2_size<(unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos))\n\t{\n\t\treturn false;\n\t}\n\n\tmemcpy(pkt1, pkt, unfragmentable);\n\t((struct ip6_hdr*)pkt1)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + frag_pos);\n\tpkt1[last_header_type - pkt] = IPPROTO_FRAGMENT;\n\tfrag = (struct ip6_frag*)(pkt1 + unfragmentable);\n\tfrag->ip6f_nxt = proto;\n\tfrag->ip6f_reserved = 0;\n\tfrag->ip6f_offlg = IP6F_MORE_FRAG;\n\tfrag->ip6f_ident = ident;\n\tmemcpy(frag+1, pkt + unfragmentable, frag_pos);\n\t*pkt1_size = unfragmentable + sizeof(struct ip6_frag) + frag_pos;\n\n\tmemcpy(pkt2, pkt, sizeof(struct ip6_hdr));\n\t((struct ip6_hdr*)pkt2)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + payload_len - frag_pos);\n\tpkt2[last_header_type - pkt] = IPPROTO_FRAGMENT;\n\tfrag = (struct ip6_frag*)(pkt2 + unfragmentable);\n\tfrag->ip6f_nxt = proto;\n\tfrag->ip6f_reserved = 0;\n\tfrag->ip6f_offlg = htons(frag_pos);\n\tfrag->ip6f_ident = ident;\n\tmemcpy(frag+1, pkt + unfragmentable + frag_pos, payload_len - frag_pos);\n\t*pkt2_size = unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos;\n\n\treturn true;\n}\nbool ip_frag(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size)\n{\n\tif (proto_check_ipv4(pkt,pkt_size))\n\t\treturn ip_frag4(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size);\n\telse if (proto_check_ipv6(pkt,pkt_size))\n\t\treturn ip_frag6(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size);\n\telse\n\t\treturn false;\n}\n\nbool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl)\n{\n\tif (ttl)\n\t{\n\t\tif (ip)\n\t\t{\n\t\t\tif (ip->ip_ttl!=ttl)\n\t\t\t{\n\t\t\t\tip->ip_ttl = ttl;\n\t\t\t\tip4_fix_checksum(ip);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse if (ip6)\n\t\t{\n\t\t\tif (ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim!=ttl)\n\t\t\t{\n\t\t\t\tip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nvoid apply_tcp_flags(struct tcphdr *tcp, uint16_t fl)\n{\n\tif (tcp)\n\t{\n\t\ttcp->th_flags = (uint8_t)fl;\n\t\ttcp->th_x2 = (fl>>8) & 0xF;\n\t}\n}\nuint16_t get_tcp_flags(const struct tcphdr *tcp)\n{\n\treturn tcp->th_flags | (tcp->th_x2<<8);\n}\n\n\n\nvoid extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport)\n{\n\tif (sport) *sport  = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0);\n\tif (dport) *dport  = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0);\n\tif (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : -1;\n}\n\nvoid extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst)\n{\n\tif (ip)\n\t{\n\t\tstruct sockaddr_in *si;\n\n\t\tif (dst)\n\t\t{\n\t\t\tsi = (struct sockaddr_in*)dst;\n\t\t\tsi->sin_family = AF_INET;\n\t\t\tsi->sin_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0;\n\t\t\tsi->sin_addr = ip->ip_dst;\n\t\t}\n\n\t\tif (src)\n\t\t{\n\t\t\tsi = (struct sockaddr_in*)src;\n\t\t\tsi->sin_family = AF_INET;\n\t\t\tsi->sin_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0;\n\t\t\tsi->sin_addr = ip->ip_src;\n\t\t}\n\t}\n\telse if (ip6hdr)\n\t{\n\t\tstruct sockaddr_in6 *si;\n\n\t\tif (dst)\n\t\t{\n\t\t\tsi = (struct sockaddr_in6*)dst;\n\t\t\tsi->sin6_family = AF_INET6;\n\t\t\tsi->sin6_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0;\n\t\t\tsi->sin6_addr = ip6hdr->ip6_dst;\n\t\t\tsi->sin6_flowinfo = 0;\n\t\t\tsi->sin6_scope_id = 0;\n\t\t}\n\n\t\tif (src)\n\t\t{\n\t\t\tsi = (struct sockaddr_in6*)src;\n\t\t\tsi->sin6_family = AF_INET6;\n\t\t\tsi->sin6_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0;\n\t\t\tsi->sin6_addr = ip6hdr->ip6_src;\n\t\t\tsi->sin6_flowinfo = 0;\n\t\t\tsi->sin6_scope_id = 0;\n\t\t}\n\t}\n}\n\nconst char *proto_name(uint8_t proto)\n{\n\tswitch(proto)\n\t{\n\t\tcase IPPROTO_TCP:\n\t\t\treturn \"tcp\";\n\t\tcase IPPROTO_UDP:\n\t\t\treturn \"udp\";\n\t\tcase IPPROTO_ICMP:\n\t\t\treturn \"icmp\";\n\t\tcase IPPROTO_ICMPV6:\n\t\t\treturn \"icmp6\";\n\t\tcase IPPROTO_IGMP:\n\t\t\treturn \"igmp\";\n\t\tcase IPPROTO_ESP:\n\t\t\treturn \"esp\";\n\t\tcase IPPROTO_AH:\n\t\t\treturn \"ah\";\n\t\tcase IPPROTO_IPV6:\n\t\t\treturn \"6in4\";\n\t\tcase IPPROTO_IPIP:\n\t\t\treturn \"4in4\";\n#ifdef IPPROTO_GRE\n\t\tcase IPPROTO_GRE:\n\t\t\treturn \"gre\";\n#endif\n#ifdef IPPROTO_SCTP\n\t\tcase IPPROTO_SCTP:\n\t\t\treturn \"sctp\";\n#endif\n\t\tdefault:\n\t\t\treturn NULL;\n\t}\n}\nstatic void str_proto_name(char *s, size_t s_len, uint8_t proto)\n{\n\tconst char *name = proto_name(proto);\n\tif (name)\n\t\tsnprintf(s,s_len,\"%s\",name);\n\telse\n\t\tsnprintf(s,s_len,\"%u\",proto);\n}\nuint16_t family_from_proto(uint8_t l3proto)\n{\n\tswitch(l3proto)\n\t{\n\t\tcase IPPROTO_IP: return AF_INET;\n\t\tcase IPPROTO_IPV6: return AF_INET6;\n\t\tdefault: return -1;\n\t}\n}\n\nstatic void str_srcdst_ip(char *s, size_t s_len, const void *saddr,const void *daddr)\n{\n\tchar s_ip[16],d_ip[16];\n\t*s_ip=*d_ip=0;\n\tinet_ntop(AF_INET, saddr, s_ip, sizeof(s_ip));\n\tinet_ntop(AF_INET, daddr, d_ip, sizeof(d_ip));\n\tsnprintf(s,s_len,\"%s => %s\",s_ip,d_ip);\n}\nvoid str_ip(char *s, size_t s_len, const struct ip *ip)\n{\n\tchar ss[35],s_proto[16];\n\tstr_srcdst_ip(ss,sizeof(ss),&ip->ip_src,&ip->ip_dst);\n\tstr_proto_name(s_proto,sizeof(s_proto),ip->ip_p);\n\tsnprintf(s,s_len,\"%s proto=%s ttl=%u\",ss,s_proto,ip->ip_ttl);\n}\nvoid print_ip(const struct ip *ip)\n{\n\tchar s[66];\n\tstr_ip(s,sizeof(s),ip);\n\tprintf(\"%s\",s);\n}\nvoid str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr)\n{\n\tchar s_ip[40],d_ip[40];\n\t*s_ip=*d_ip=0;\n\tinet_ntop(AF_INET6, saddr, s_ip, sizeof(s_ip));\n\tinet_ntop(AF_INET6, daddr, d_ip, sizeof(d_ip));\n\tsnprintf(s,s_len,\"%s => %s\",s_ip,d_ip);\n}\nvoid str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto)\n{\n\tchar ss[83],s_proto[16];\n\tstr_srcdst_ip6(ss,sizeof(ss),&ip6hdr->ip6_src,&ip6hdr->ip6_dst);\n\tstr_proto_name(s_proto,sizeof(s_proto),proto);\n\tsnprintf(s,s_len,\"%s proto=%s ttl=%u\",ss,s_proto,ip6hdr->ip6_hlim);\n}\nvoid print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto)\n{\n\tchar s[128];\n\tstr_ip6hdr(s,sizeof(s),ip6hdr,proto);\n\tprintf(\"%s\",s);\n}\nvoid str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr)\n{\n\tchar flags[7],*f=flags;\n\tif (tcphdr->th_flags & TH_SYN) *f++='S';\n\tif (tcphdr->th_flags & TH_ACK) *f++='A';\n\tif (tcphdr->th_flags & TH_RST) *f++='R';\n\tif (tcphdr->th_flags & TH_FIN) *f++='F';\n\tif (tcphdr->th_flags & TH_PUSH) *f++='P';\n\tif (tcphdr->th_flags & TH_URG) *f++='U';\n\t*f=0;\n\tsnprintf(s,s_len,\"sport=%u dport=%u flags=%s seq=%u ack_seq=%u\",htons(tcphdr->th_sport),htons(tcphdr->th_dport),flags,htonl(tcphdr->th_seq),htonl(tcphdr->th_ack));\n}\nvoid print_tcphdr(const struct tcphdr *tcphdr)\n{\n\tchar s[80];\n\tstr_tcphdr(s,sizeof(s),tcphdr);\n\tprintf(\"%s\",s);\n}\nvoid str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr)\n{\n\tsnprintf(s,s_len,\"sport=%u dport=%u\",htons(udphdr->uh_sport),htons(udphdr->uh_dport));\n}\nvoid print_udphdr(const struct udphdr *udphdr)\n{\n\tchar s[30];\n\tstr_udphdr(s,sizeof(s),udphdr);\n\tprintf(\"%s\",s);\n}\n\n\n\n\nbool proto_check_ipv4(const uint8_t *data, size_t len)\n{\n\treturn \tlen >= 20 && (data[0] & 0xF0) == 0x40 &&\n\t\tlen >= ((data[0] & 0x0F) << 2);\n}\n// move to transport protocol\nvoid proto_skip_ipv4(uint8_t **data, size_t *len)\n{\n\tsize_t l;\n\n\tl = (**data & 0x0F) << 2;\n\t*data += l;\n\t*len -= l;\n}\nbool proto_check_tcp(const uint8_t *data, size_t len)\n{\n\treturn len >= 20 && len >= ((data[12] & 0xF0) >> 2);\n}\nvoid proto_skip_tcp(uint8_t **data, size_t *len)\n{\n\tsize_t l;\n\tl = ((*data)[12] & 0xF0) >> 2;\n\t*data += l;\n\t*len -= l;\n}\nbool proto_check_udp(const uint8_t *data, size_t len)\n{\n\treturn len >= 8 && len>=(data[4]<<8 | data[5]);\n}\nvoid proto_skip_udp(uint8_t **data, size_t *len)\n{\n\t*data += 8;\n\t*len -= 8;\n}\n\nbool proto_check_ipv6(const uint8_t *data, size_t len)\n{\n\treturn \tlen >= 40 && (data[0] & 0xF0) == 0x60 &&\n\t\t(len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length\n}\n// move to transport protocol\n// proto_type = 0 => error\nvoid proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type)\n{\n\tsize_t hdrlen;\n\tuint8_t HeaderType;\n\n\tif (proto_type) *proto_type = 0; // put error in advance\n\n\tHeaderType = (*data)[6]; // NextHeader field\n\tif (proto_type) *proto_type = HeaderType;\n\tif (last_header_type) *last_header_type = (*data)+6;\n\t*data += 40; *len -= 40; // skip ipv6 base header\n\twhile (*len > 0) // need at least one byte for NextHeader field\n\t{\n\t\tswitch (HeaderType)\n\t\t{\n\t\tcase 0: // Hop-by-Hop Options\n\t\tcase 43: // routing\n\t\tcase 60: // Destination Options\n\t\tcase 135: // mobility\n\t\tcase 139: // Host Identity Protocol Version v2\n\t\tcase 140: // Shim6\n\t\t\tif (*len < 2) return; // error\n\t\t\thdrlen = 8 + ((*data)[1] << 3);\n\t\t\tbreak;\n\t\tcase 44: // fragment. length fixed to 8, hdrlen field defined as reserved\n\t\t\thdrlen = 8;\n\t\t\tbreak;\n\t\tcase 51: // authentication\n\t\t\t// special case. length in ah header is in 32-bit words minus 2\n\t\t\tif (*len < 2) return; // error\n\t\t\thdrlen = 8 + ((*data)[1] << 2);\n\t\t\tbreak;\n\t\tcase 59: // no next header\n\t\t\treturn; // error\n\t\tdefault:\n\t\t\t// we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit\n\t\t\tif (proto_type) *proto_type = HeaderType;\n\t\t\treturn;\n\t\t}\n\t\tif (*len < hdrlen) return; // error\n\t\tHeaderType = **data;\n\t\tif (last_header_type) *last_header_type = *data;\n\t\t// advance to the next header location\n\t\t*len -= hdrlen;\n\t\t*data += hdrlen;\n\t}\n\t// we have garbage\n}\n\nvoid proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis)\n{\n\tmemset(dis,0,sizeof(*dis));\n\n\tdis->data_pkt = data;\n\tdis->len_pkt = len;\n\n\tif (proto_check_ipv4(data, len))\n\t{\n\t\tdis->ip = (struct ip *) data;\n\t\tdis->proto = dis->ip->ip_p;\n\t\tproto_skip_ipv4(&data, &len);\n\t}\n\telse if (proto_check_ipv6(data, len))\n\t{\n\t\tdis->ip6 = (struct ip6_hdr *) data;\n\t\tproto_skip_ipv6(&data, &len, &dis->proto, NULL);\n\t}\n\telse\n\t{\n\t\treturn;\n\t}\n\n\tif (dis->proto==IPPROTO_TCP && proto_check_tcp(data, len))\n\t{\n\t\tdis->tcp = (struct tcphdr *) data;\n\t\tdis->transport_len = len;\n\n\t\tproto_skip_tcp(&data, &len);\n\n\t\tdis->data_payload = data;\n\t\tdis->len_payload = len;\n\n\t}\n\telse if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len))\n\t{\n\t\tdis->udp = (struct udphdr *) data;\n\t\tdis->transport_len = len;\n\n\t\tproto_skip_udp(&data, &len);\n\n\t\tdis->data_payload = data;\n\t\tdis->len_payload = len;\n\t}\n}\n\n\nbool tcp_synack_segment(const struct tcphdr *tcphdr)\n{\n\t/* check for set bits in TCP hdr */\n\treturn ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == (TH_ACK|TH_SYN));\n}\nbool tcp_syn_segment(const struct tcphdr *tcphdr)\n{\n\t/* check for set bits in TCP hdr */\n\treturn ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_SYN);\n}\nbool tcp_ack_segment(const struct tcphdr *tcphdr)\n{\n\t/* check for set bits in TCP hdr */\n\treturn ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_ACK);\n}\n\nvoid tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor)\n{\n\tuint8_t *scale,scale_factor_old;\n\n\tif (scale_factor!=SCALE_NONE)\n\t{\n\t\tscale = tcp_find_option(tcp,3); // tcp option 3 - scale factor\n\t\tif (scale && scale[1]==3) // length should be 3\n\t\t{\n\t\t\tscale_factor_old=scale[2];\n\t\t\t// do not allow increasing scale factor\n\t\t\tif (scale_factor>=scale_factor_old)\n\t\t\t\tDLOG(\"Scale factor %u unchanged\\n\", scale_factor_old);\n\t\t\telse\n\t\t\t{\n\t\t\t\tscale[2]=scale_factor;\n\t\t\t\tDLOG(\"Scale factor change %u => %u\\n\", scale_factor_old, scale_factor);\n\t\t\t}\n\t\t}\n\t}\n}\n// scale_factor=SCALE_NONE - do not change\nvoid tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor)\n{\n\tuint16_t winsize_old;\n\n\twinsize_old = htons(tcp->th_win); // << scale_factor;\n\ttcp->th_win = htons(winsize);\n\tDLOG(\"Window size change %u => %u\\n\", winsize_old, winsize);\n\n\ttcp_rewrite_wscale(tcp, scale_factor);\n}\n\n\n#ifdef __CYGWIN__\n\nstatic HANDLE w_filter = NULL;\nstatic OVERLAPPED ovl = { .hEvent = NULL };\nstatic const struct str_list_head *wlan_filter_ssid = NULL, *nlm_filter_net = NULL;\nstatic DWORD logical_net_filter_tick=0;\nuint32_t w_win32_error=0;\nINetworkListManager* pNetworkListManager=NULL;\n\nstatic void guid2str(const GUID *guid, char *str)\n{\n\tsnprintf(str,37, \"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);\n}\nstatic bool str2guid(const char* str, GUID *guid)\n{\n\tunsigned int u[11],k;\n\n\tif (36 != strlen(str) || 11 != sscanf(str, \"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\", u+0, u+1, u+2, u+3, u+4, u+5, u+6, u+7, u+8, u+9, u+10))\n\t\treturn false;\n\tguid->Data1 = u[0];\n\tif ((u[1] & 0xFFFF0000) || (u[2] & 0xFFFF0000)) return false;\n\tguid->Data2 = (USHORT)u[1];\n\tguid->Data3 = (USHORT)u[2];\n\tfor (k = 0; k < 8; k++)\n\t{\n\t\tif (u[k+3] & 0xFFFFFF00) return false;\n\t\tguid->Data4[k] = (UCHAR)u[k+3];\n\t}\n\treturn true;\n}\n\nstatic const char *sNetworkCards=\"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\NetworkCards\";\n// get adapter name from guid string\nstatic bool AdapterID2Name(const GUID *guid,char *name,DWORD name_len)\n{\n\tchar sguid[39],sidx[32],val[256];\n\tHKEY hkNetworkCards,hkCard;\n\tDWORD dwIndex,dwLen;\n\tbool bRet = false;\n\tWCHAR namew[128];\n\tDWORD namew_len;\n\n\tif (name_len<2) return false;\n\n\tif ((w_win32_error = RegOpenKeyExA(HKEY_LOCAL_MACHINE,sNetworkCards,0,KEY_ENUMERATE_SUB_KEYS,&hkNetworkCards)) == ERROR_SUCCESS)\n\t{\n\t\tguid2str(guid, sguid+1);\n\t\tsguid[0]='{';\n\t\tsguid[37]='}';\n\t\tsguid[38]='\\0';\n\n\t\tfor (dwIndex=0;;dwIndex++)\n\t\t{\n\t\t\tdwLen=sizeof(sidx)-1;\n\t\t\tw_win32_error = RegEnumKeyExA(hkNetworkCards,dwIndex,sidx,&dwLen,NULL,NULL,NULL,NULL);\n\t\t\tif (w_win32_error == ERROR_SUCCESS)\n\t\t\t{\n\t\t\t\tsidx[dwLen]='\\0';\n\n\t\t\t\tif ((w_win32_error = RegOpenKeyExA(hkNetworkCards,sidx,0,KEY_QUERY_VALUE,&hkCard)) == ERROR_SUCCESS)\n\t\t\t\t{\n\t\t\t\t\tdwLen=sizeof(val)-1;\n\t\t\t\t\tif ((w_win32_error = RegQueryValueExA(hkCard,\"ServiceName\",NULL,NULL,val,&dwLen)) == ERROR_SUCCESS)\n\t\t\t\t\t{\n\t\t\t\t\t\tval[dwLen]='\\0';\n\t\t\t\t\t\tif (!strcmp(val,sguid))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnamew_len = sizeof(namew)-sizeof(WCHAR);\n\t\t\t\t\t\t\tif ((w_win32_error = RegQueryValueExW(hkCard,L\"Description\",NULL,NULL,(LPBYTE)namew,&namew_len)) == ERROR_SUCCESS)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnamew[namew_len/sizeof(WCHAR)]=L'\\0';\n\t\t\t\t\t\t\t\tif (WideCharToMultiByte(CP_UTF8, 0, namew, -1, name, name_len, NULL, NULL))\n\t\t\t\t\t\t\t\t\tbRet = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tRegCloseKey(hkCard);\n\t\t\t\t}\n\t\t\t\tif (bRet) break;\n\t\t\t}\n\t\t\telse\n\t\t\t\tbreak;\n\t\t}\n\t\tRegCloseKey(hkNetworkCards);\n\t}\n\n\treturn bRet;\n}\n\nbool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter)\n{\n\twin_dark_deinit();\n\tif (LIST_EMPTY(ssid_filter)) ssid_filter=NULL;\n\tif (LIST_EMPTY(nlm_filter)) nlm_filter=NULL;\n\tif (nlm_filter)\n\t{\n\t\tif (SUCCEEDED(w_win32_error = CoInitialize(NULL)))\n\t\t{\n\t\t\tif (FAILED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager)))\n\t\t\t{\n\t\t\t\tCoUninitialize();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\treturn false;\n\t}\n\tnlm_filter_net = nlm_filter;\n\twlan_filter_ssid = ssid_filter;\n\treturn true;\n}\nbool win_dark_deinit(void)\n{\n\tif (pNetworkListManager)\n\t{\n\t\tpNetworkListManager->lpVtbl->Release(pNetworkListManager);\n\t\tpNetworkListManager = NULL;\n\t}\n\tif (nlm_filter_net) CoUninitialize();\n\twlan_filter_ssid = nlm_filter_net = NULL;\n}\n\n\nbool nlm_list(bool bAll)\n{\n\tbool bRet = true;\n\n\tif (SUCCEEDED(w_win32_error = CoInitialize(NULL)))\n\t{\n\t\tINetworkListManager* pNetworkListManager;\n\t\tif (SUCCEEDED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager)))\n\t\t{\n\t\t\tIEnumNetworks* pEnumNetworks;\n\t\t\tif (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_ALL, &pEnumNetworks)))\n\t\t\t{\n\t\t\t\tINetwork *pNet;\n\t\t\t\tINetworkConnection *pConn;\n\t\t\t\tIEnumNetworkConnections *pEnumConnections;\n\t\t\t\tVARIANT_BOOL bIsConnected, bIsConnectedInet;\n\t\t\t\tNLM_NETWORK_CATEGORY category;\n\t\t\t\tGUID idNet, idAdapter;\n\t\t\t\tBSTR bstrName;\n\t\t\t\tchar Name[128],Name2[128];\n\t\t\t\tint connected;\n\t\t\t\tfor (connected = 1; connected >= !bAll; connected--)\n\t\t\t\t{\n\t\t\t\t\tfor (;;)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (FAILED(w_win32_error = pEnumNetworks->lpVtbl->Next(pEnumNetworks, 1, &pNet, NULL)))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbRet = false;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (w_win32_error != S_OK) break;\n\t\t\t\t\t\tif (SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnected(pNet, &bIsConnected)) &&\n\t\t\t\t\t\t\tSUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnectedToInternet(pNet, &bIsConnectedInet)) &&\n\t\t\t\t\t\t\tSUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) &&\n\t\t\t\t\t\t\tSUCCEEDED(w_win32_error = pNet->lpVtbl->GetCategory(pNet, &category)) &&\n\t\t\t\t\t\t\tSUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName)))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!!bIsConnected == connected)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tprintf(\"Name    : %s\", Name);\n\t\t\t\t\t\t\t\t\tif (bIsConnected) printf(\" (connected)\");\n\t\t\t\t\t\t\t\t\tif (bIsConnectedInet) printf(\" (inet)\");\n\t\t\t\t\t\t\t\t\tprintf(\" (%s)\\n\",\n\t\t\t\t\t\t\t\t\t\tcategory==NLM_NETWORK_CATEGORY_PUBLIC ? \"public\" :\n\t\t\t\t\t\t\t\t\t\tcategory==NLM_NETWORK_CATEGORY_PRIVATE ? \"private\" :\n\t\t\t\t\t\t\t\t\t\tcategory==NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED ? \"domain\" :\n\t\t\t\t\t\t\t\t\t\t\"unknown\");\n\t\t\t\t\t\t\t\t\tguid2str(&idNet, Name);\n\t\t\t\t\t\t\t\t\tprintf(\"NetID   : %s\\n\", Name);\t\n\t\t\t\t\t\t\t\t\tif (connected && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkConnections(pNet, &pEnumConnections)))\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twhile ((w_win32_error = pEnumConnections->lpVtbl->Next(pEnumConnections, 1, &pConn, NULL))==S_OK)\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tif (SUCCEEDED(w_win32_error = pConn->lpVtbl->GetAdapterId(pConn,&idAdapter)))\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tguid2str(&idAdapter, Name);\n\t\t\t\t\t\t\t\t\t\t\t\tif (AdapterID2Name(&idAdapter,Name2,sizeof(Name2)))\n\t\t\t\t\t\t\t\t\t\t\t\t\tprintf(\"Adapter : %s (%s)\\n\", Name2, Name);\n\t\t\t\t\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t\t\t\t\tprintf(\"Adapter : %s\\n\", Name);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tpConn->lpVtbl->Release(pConn);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tpEnumConnections->lpVtbl->Release(pEnumConnections);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tw_win32_error = HRESULT_FROM_WIN32(GetLastError());\n\t\t\t\t\t\t\t\t\tbRet = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tSysFreeString(bstrName);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tbRet = false;\n\t\t\t\t\t\tpNet->lpVtbl->Release(pNet);\n\t\t\t\t\t\tif (!bRet) break;\n\t\t\t\t\t}\n\t\t\t\t\tif (!bRet) break;\n\t\t\t\t\tpEnumNetworks->lpVtbl->Reset(pEnumNetworks);\n\t\t\t\t}\n\t\t\t\tpEnumNetworks->lpVtbl->Release(pEnumNetworks);\n\t\t\t}\n\t\t\telse\n\t\t\t\tbRet = false;\n\t\t\tpNetworkListManager->lpVtbl->Release(pNetworkListManager);\n\t\t}\n\t\telse\n\t\t\tbRet = false;\n\t}\n\telse\n\t\tbRet = false;\n\n\tCoUninitialize();\n\treturn bRet;\n}\n\nstatic bool nlm_filter_match(const struct str_list_head *nlm_list)\n{\n\t// no filter given. always matches.\n\tif (!nlm_list || LIST_EMPTY(nlm_list))\n\t{\n\t\tw_win32_error = 0;\n\t\treturn true;\n\t}\n\n\tbool bRet = true, bMatch = false;\n\tIEnumNetworks* pEnum;\n\n\tif (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_CONNECTED, &pEnum)))\n\t{\n\t\tINetwork* pNet;\n\t\tGUID idNet,g;\n\t\tBSTR bstrName;\n\t\tchar Name[128];\n\t\tstruct str_list *nlm;\n\t\tfor (;;)\n\t\t{\n\t\t\tif (FAILED(w_win32_error = pEnum->lpVtbl->Next(pEnum, 1, &pNet, NULL)))\n\t\t\t{\n\t\t\t\tbRet = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (w_win32_error != S_OK) break;\n\t\t\tif (SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) &&\n\t\t\t\tSUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName)))\n\t\t\t{\n\t\t\t\tif (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL))\n\t\t\t\t{\n\t\t\t\t\tLIST_FOREACH(nlm, nlm_list, next)\n\t\t\t\t\t{\n\t\t\t\t\t\tbMatch = !strcmp(Name,nlm->str) || str2guid(nlm->str,&g) && !memcmp(&idNet,&g,sizeof(GUID));\n\t\t\t\t\t\tif (bMatch) break;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tw_win32_error = HRESULT_FROM_WIN32(GetLastError());\n\t\t\t\t\tbRet = false;\n\t\t\t\t}\n\t\t\t\tSysFreeString(bstrName);\n\t\t\t}\n\t\t\telse\n\t\t\t\tbRet = false;\n\t\t\tpNet->lpVtbl->Release(pNet);\n\t\t\tif (!bRet || bMatch) break;\n\t\t}\n\t\tpEnum->lpVtbl->Release(pEnum);\n\t}\n\telse\n\t\tbRet = false;\n\treturn bRet && bMatch;\n}\n\nstatic bool wlan_filter_match(const struct str_list_head *ssid_list)\n{\n\tDWORD dwCurVersion;\n\tHANDLE hClient = NULL;\n\tPWLAN_INTERFACE_INFO_LIST pIfList = NULL;\n\tPWLAN_INTERFACE_INFO pIfInfo;\n\tPWLAN_CONNECTION_ATTRIBUTES pConnectInfo;\n\tDWORD connectInfoSize, k;\n\tbool bRes;\n\tstruct str_list *ssid;\n\tsize_t len;\n\n\t// no filter given. always matches.\n\tif (!ssid_list || LIST_EMPTY(ssid_list))\n\t{\n\t\tw_win32_error = 0;\n\t\treturn true;\n\t}\n\n\tw_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient);\n\tif (w_win32_error != ERROR_SUCCESS) goto fail;\n\tw_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList);\n\tif (w_win32_error != ERROR_SUCCESS) goto fail;\n\tfor (k = 0; k < pIfList->dwNumberOfItems; k++)\n\t{\n\t\tpIfInfo = pIfList->InterfaceInfo + k;\n\t\tif (pIfInfo->isState == wlan_interface_state_connected)\n\t\t{\n\t\t\tw_win32_error = WlanQueryInterface(hClient,\n\t\t\t\t&pIfInfo->InterfaceGuid,\n\t\t\t\twlan_intf_opcode_current_connection,\n\t\t\t\tNULL,\n\t\t\t\t&connectInfoSize,\n\t\t\t\t(PVOID *)&pConnectInfo,\n\t\t\t\tNULL);\n\t\t\tif (w_win32_error != ERROR_SUCCESS) goto fail;\n\n//\t\t\tprintf(\"%s\\n\", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID);\n\n\t\t\tLIST_FOREACH(ssid, ssid_list, next)\n\t\t\t{\n\t\t\t\tlen = strlen(ssid->str);\n\t\t\t\tif (len==pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str,pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID,len))\n\t\t\t\t{\t\n\t\t\t\t\tWlanFreeMemory(pConnectInfo);\n\t\t\t\t\tgoto found;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tWlanFreeMemory(pConnectInfo);\n\t\t}\n\t}\n\tw_win32_error = 0;\nfail:\n\tbRes = false;\nex:\n\tif (pIfList) WlanFreeMemory(pIfList);\n\tif (hClient) WlanCloseHandle(hClient, 0);\n\treturn bRes;\nfound:\n\tw_win32_error = 0;\n\tbRes = true;\n\tgoto ex;\n}\n\nbool logical_net_filter_match(void)\n{\n\treturn wlan_filter_match(wlan_filter_ssid) && nlm_filter_match(nlm_filter_net);\n}\n\nstatic bool logical_net_filter_match_rate_limited(void)\n{\n\tDWORD dwTick = GetTickCount() / 1000;\n\tif (logical_net_filter_tick == dwTick) return true;\n\tlogical_net_filter_tick = dwTick;\n\treturn logical_net_filter_match();\n}\n\nstatic HANDLE windivert_init_filter(const char *filter, UINT64 flags)\n{\n\tLPSTR errormessage = NULL;\n\tHANDLE h, hMutex;\n\tconst char *mutex_name = \"Global\\\\winws_windivert_mutex\";\n\n\t// windivert driver start in windivert.dll has race conditions\n\thMutex = CreateMutexA(NULL,TRUE,mutex_name);\n\tif (hMutex && GetLastError()==ERROR_ALREADY_EXISTS)\n\t\tWaitForSingleObject(hMutex,INFINITE);\n\th = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags);\n\tw_win32_error = GetLastError();\n\n\tif (hMutex)\n\t{\n\t\tReleaseMutex(hMutex);\n\t\tCloseHandle(hMutex);\n\t\tSetLastError(w_win32_error);\n\t}\n\n\tif (h != INVALID_HANDLE_VALUE) return h;\n\n\tFormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n\t\tNULL, w_win32_error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPSTR)&errormessage, 0, NULL);\n\tDLOG_ERR(\"windivert: error opening filter: %s\", errormessage);\n\tLocalFree(errormessage);\n\tif (w_win32_error == ERROR_INVALID_IMAGE_HASH)\n\t\tDLOG_ERR(\"windivert: try to disable secure boot and install OS patches\\n\");\n\n\treturn NULL;\n}\nvoid rawsend_cleanup(void)\n{\n\tif (w_filter)\n\t{\n\t\tCancelIoEx(w_filter,&ovl);\n\t\tWinDivertClose(w_filter);\n\t\tw_filter=NULL;\n\t}\n\tif (ovl.hEvent)\n\t{\n\t\tCloseHandle(ovl.hEvent);\n\t\tovl.hEvent=NULL;\n\t}\n}\nbool windivert_init(const char *filter)\n{\n\trawsend_cleanup();\n\tw_filter = windivert_init_filter(filter, 0);\n\tif (w_filter)\n\t{\n\t\tovl.hEvent = CreateEventW(NULL,FALSE,FALSE,NULL);\n\t\tif (!ovl.hEvent)\n\t\t{\n\t\t\tw_win32_error = GetLastError();\n\t\t\trawsend_cleanup();\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa)\n{\n\tUINT recv_len;\n\tDWORD err;\n\tDWORD rd;\n\tchar c;\n\n\tif (bQuit)\n\t{\n\t\terrno=EINTR;\n\t\treturn false;\n\t}\n\tif (!logical_net_filter_match_rate_limited())\n\t{\n\t\terrno=ENODEV;\n\t\treturn false;\n\t}\n\tusleep(0);\n\tif (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl))\n\t{\n\t\t*len = recv_len;\n\t\treturn true;\n\t}\n\tfor(;;)\n\t{\n\t\tw_win32_error = GetLastError();\n\t\tswitch(w_win32_error)\n\t\t{\n\t\t\tcase ERROR_IO_PENDING:\n\t\t\t\t// make signals working\n\t\t\t\twhile (WaitForSingleObject(ovl.hEvent,50)==WAIT_TIMEOUT)\n\t\t\t\t{\n\t\t\t\t\tif (bQuit)\n\t\t\t\t\t{\n\t\t\t\t\t\terrno=EINTR;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (!logical_net_filter_match_rate_limited())\n\t\t\t\t\t{\n\t\t\t\t\t\terrno=ENODEV;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tusleep(0);\n\t\t\t\t}\n\t\t\t\tif (!GetOverlappedResult(hFilter,&ovl,&rd,TRUE))\n\t\t\t\t\tcontinue;\n\t\t\t\t*len = rd;\n\t\t\t\treturn true;\n\t\t\tcase ERROR_INSUFFICIENT_BUFFER:\n\t\t\t\terrno = ENOBUFS;\n\t\t\t\tbreak;\n\t\t\tcase ERROR_NO_DATA:\n\t\t\t\terrno = ESHUTDOWN;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\terrno = EIO;\n\t\t}\n\t\tbreak;\n\t}\n\treturn false;\n}\nbool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa)\n{\n\treturn windivert_recv_filter(w_filter,packet,len,wa);\n}\n\nstatic bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa)\n{\n\tbool b = WinDivertSend(hFilter,packet,(UINT)len,NULL,wa);\n\tw_win32_error = GetLastError();\n\treturn b;\n}\nbool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa)\n{\n\treturn windivert_send_filter(w_filter,packet,len,wa);\n}\n\nbool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len)\n{\n\tWINDIVERT_ADDRESS wa;\n\n\tmemset(&wa,0,sizeof(wa));\n\t// pseudo interface id IfIdx.SubIfIdx\n\tif (sscanf(ifout,\"%u.%u\",&wa.Network.IfIdx,&wa.Network.SubIfIdx)!=2)\n\t{\n\t\terrno = EINVAL;\n\t\treturn false;\n\t}\n\twa.Outbound=1;\n\twa.IPChecksum=1;\n\twa.TCPChecksum=1;\n\twa.UDPChecksum=1;\n\twa.IPv6 = (dst->sa_family==AF_INET6);\n\n\treturn windivert_send(data,len,&wa);\n}\n\n#else // *nix\n\nstatic int rawsend_sock4=-1, rawsend_sock6=-1;\nstatic bool b_bind_fix4=false, b_bind_fix6=false;\nstatic void rawsend_clean_sock(int *sock)\n{\n\tif (sock && *sock!=-1)\n\t{\n\t\tclose(*sock);\n\t\t*sock=-1;\n\t}\n}\nvoid rawsend_cleanup(void)\n{\n\trawsend_clean_sock(&rawsend_sock4);\n\trawsend_clean_sock(&rawsend_sock6);\n}\nstatic int *rawsend_family_sock(sa_family_t family)\n{\n\tswitch(family)\n\t{\n\t\tcase AF_INET: return &rawsend_sock4;\n\t\tcase AF_INET6: return &rawsend_sock6;\n\t\tdefault: return NULL;\n\t}\n}\n\n#ifdef BSD\nint socket_divert(sa_family_t family)\n{\n\tint fd;\n\t\n#ifdef __FreeBSD__\n\t// freebsd14+ way\n\t// don't want to use ifdefs with os version to make binaries compatible with all versions\n\tfd = socket(PF_DIVERT, SOCK_RAW, 0);\n\tif (fd==-1 && (errno==EPROTONOSUPPORT || errno==EAFNOSUPPORT || errno==EPFNOSUPPORT))\n#endif\n\t\t// freebsd13- or openbsd way\n\t\tfd = socket(family, SOCK_RAW, IPPROTO_DIVERT);\n\treturn fd;\n}\nstatic int rawsend_socket_divert(sa_family_t family)\n{\n\t// HACK HACK HACK HACK HACK HACK HACK HACK\n\t// FreeBSD doesnt allow IP_HDRINCL for IPV6\n\t// OpenBSD doesnt allow rawsending tcp frames\n\t// we either have to go to the link layer (its hard, possible problems arise, compat testing, ...) or use some HACKING\n\t// from my point of view disabling direct ability to send ip frames is not security. its SHIT\n\n\tint fd = socket_divert(family);\n\tif (fd!=-1 && !set_socket_buffers(fd,4096,RAW_SNDBUF))\n\t{\n\t\tclose(fd);\n\t\treturn -1;\n\t}\n\treturn fd;\n}\nstatic int rawsend_sendto_divert(sa_family_t family, int sock, const void *buf, size_t len)\n{\n\tstruct sockaddr_storage sa;\n\tsocklen_t slen;\n\n#ifdef __FreeBSD__\n\t// since FreeBSD 14 it requires hardcoded ipv4 values, although can also send ipv6 frames\n\tfamily = AF_INET;\n\tslen = sizeof(struct sockaddr_in);\n#else\n\t// OpenBSD requires correct family and size\n\tswitch(family)\n\t{\n\t\tcase AF_INET:\n\t\t\tslen = sizeof(struct sockaddr_in);\n\t\t\tbreak;\n\t\tcase AF_INET6:\n\t\t\tslen = sizeof(struct sockaddr_in6);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n#endif\n\tmemset(&sa,0,slen);\n\tsa.ss_family = family;\n\treturn sendto(sock, buf, len, 0, (struct sockaddr*)&sa, slen);\n}\n#endif\n\nstatic int rawsend_socket_raw(int domain, int proto)\n{\n\tint fd = socket(domain, SOCK_RAW, proto);\n\tif (fd!=-1)\n\t{\n\t\t#ifdef __linux__\n\t\tint s=RAW_SNDBUF/2;\n\t\tint r=2048;\n\t\t#else\n\t\tint s=RAW_SNDBUF;\n\t\tint r=4096;\n\t\t#endif\n\t\tif (!set_socket_buffers(fd,r,s))\n\t\t{\n\t\t\tclose(fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n\treturn fd;\n}\n\nstatic bool set_socket_fwmark(int sock, uint32_t fwmark)\n{\n#ifdef BSD\n#ifdef SO_USER_COOKIE\n\tif (setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &fwmark, sizeof(fwmark)) == -1)\n\t{\n\t\tDLOG_PERROR(\"rawsend: setsockopt(SO_USER_COOKIE)\");\n\t\treturn false;\n\t}\n#endif\n#elif defined(__linux__)\n\tif (setsockopt(sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1)\n\t{\n\t\tDLOG_PERROR(\"rawsend: setsockopt(SO_MARK)\");\n\t\treturn false;\n\t}\n\n#endif\n\treturn true;\n}\n\nstatic int rawsend_socket(sa_family_t family)\n{\n\tint *sock = rawsend_family_sock(family);\n\tif (!sock) return -1;\n\t\n\tif (*sock==-1)\n\t{\n\t\tint yes=1,pri=6;\n\t\t//printf(\"rawsend_socket: family %d\",family);\n\n#ifdef __FreeBSD__\n\t\t// IPPROTO_RAW with ipv6 in FreeBSD always returns EACCES on sendto.\n\t\t// must use IPPROTO_TCP for ipv6. IPPROTO_RAW works for ipv4\n\t\t// divert sockets are always v4 but accept both v4 and v6\n\t\t*sock = rawsend_socket_divert(AF_INET);\n#elif defined(__OpenBSD__) || defined (__APPLE__)\n\t\t// OpenBSD does not allow sending TCP frames through raw sockets\n\t\t// I dont know about macos. They have dropped ipfw in recent versions and their PF does not support divert-packet\n\t\t*sock = rawsend_socket_divert(family);\n#else\n\t\t*sock = rawsend_socket_raw(family, IPPROTO_RAW);\n#endif\n\t\tif (*sock==-1)\n\t\t{\n\t\t\tDLOG_PERROR(\"rawsend: socket()\");\n\t\t\treturn -1;\n\t\t}\n#ifdef __linux__\n\t\tif (setsockopt(*sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"rawsend: setsockopt(SO_PRIORITY)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tif (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_NODEFRAG, &yes, sizeof(yes)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"rawsend: setsockopt(IP_NODEFRAG)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tif (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_FREEBIND, &yes, sizeof(yes)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"rawsend: setsockopt(IP_FREEBIND)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tif (family==AF_INET6 && setsockopt(*sock, SOL_IPV6, IPV6_FREEBIND, &yes, sizeof(yes)) == -1)\n\t\t{\n\t\t\t//DLOG_PERROR(\"rawsend: setsockopt(IPV6_FREEBIND)\");\n\t\t\t// dont error because it's supported only from kernel 4.15\n\t\t}\n#endif\n\t}\n\treturn *sock;\nexiterr:\n\trawsend_clean_sock(sock);\n\treturn -1;\n}\nbool rawsend_preinit(bool bind_fix4, bool bind_fix6)\n{\n\tb_bind_fix4 = bind_fix4;\n\tb_bind_fix6 = bind_fix6;\n\t// allow ipv6 disabled systems\n\treturn rawsend_socket(AF_INET)!=-1 && (rawsend_socket(AF_INET6)!=-1 || errno==EAFNOSUPPORT);\n}\nbool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len)\n{\n\tssize_t bytes;\n\tint sock=rawsend_socket(dst->sa_family);\n\tif (sock==-1) return false;\n\tif (!set_socket_fwmark(sock,fwmark)) return false;\n\n\tint salen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);\n\tstruct sockaddr_storage dst2;\n\tmemcpy(&dst2,dst,salen);\n\tif (dst->sa_family==AF_INET6)\n\t\t((struct sockaddr_in6 *)&dst2)->sin6_port = 0; // or will be EINVAL in linux\n\n#if defined(BSD)\n\tbytes = rawsend_sendto_divert(dst->sa_family,sock,data,len);\n\tif (bytes==-1)\n\t{\n\t\tDLOG_PERROR(\"rawsend: sendto_divert\");\n\t\treturn false;\n\t}\n\treturn true;\n\n#else\n\n#ifdef __linux__\n\tstruct sockaddr_storage sa_src;\n\tswitch(dst->sa_family)\n\t{\n\t\tcase AF_INET:\n\t\t\tif (!b_bind_fix4) goto nofix;\n\t\t\textract_endpoints(data,NULL,NULL,NULL, &sa_src, NULL);\n\t\t\tbreak;\n\t\tcase AF_INET6:\n\t\t\tif (!b_bind_fix6) goto nofix;\n\t\t\textract_endpoints(NULL,data,NULL,NULL, &sa_src, NULL);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn false; // should not happen\n\t}\n\t//printf(\"family %u dev %s bind : \",  dst->sa_family, ifout); print_sockaddr((struct sockaddr *)&sa_src); printf(\"\\n\");\n\tif (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifout, ifout ? strlen(ifout)+1 : 0) == -1)\n\t{\n\t\tDLOG_PERROR(\"rawsend: setsockopt(SO_BINDTODEVICE)\");\n\t\treturn false;\n\t}\n\tif (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)))\n\t{\n\t\tDLOG_PERROR(\"rawsend: bind (ignoring)\");\n\t\t// do not fail. this can happen regardless of IP_FREEBIND\n\t\t// rebind to any address\n\t\tmemset(&sa_src,0,sizeof(sa_src));\n\t\tsa_src.ss_family = dst->sa_family;\n\t\tif (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)))\n\t\t{\n\t\t\tDLOG_PERROR(\"rawsend: bind to any\");\n\t\t\treturn false;\n\t\t}\n\t}\nnofix:\n#endif\n\n\t// normal raw socket sendto\n\tbytes = sendto(sock, data, len, 0, (struct sockaddr*)&dst2, salen);\n\tif (bytes==-1)\n\t{\n\t\tchar s[40];\n\t\tsnprintf(s,sizeof(s),\"rawsend: sendto (%zu)\",len);\n\t\tDLOG_PERROR(s);\n\t\treturn false;\n\t}\n\treturn true;\n#endif\n}\n\n#endif // not CYGWIN\n\nbool rawsend_rp(const struct rawpacket *rp)\n{\n\treturn rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,rp->packet,rp->len);\n}\nbool rawsend_queue(struct rawpacket_tailhead *q)\n{\n\tstruct rawpacket *rp;\n\tbool b;\n\tfor (b=true; (rp=rawpacket_dequeue(q)) ; rawpacket_free(rp))\n\t\tb &= rawsend_rp(rp);\n\treturn b;\n}\n\n\n\n#if defined(HAS_FILTER_SSID) && defined(__linux__)\n\n// linux-specific wlan retrieval implementation\n\ntypedef void netlink_prepare_nlh_cb_t(struct nlmsghdr *nlh, void *param);\n\nstatic bool netlink_genl_simple_transact(struct mnl_socket* nl, uint16_t type, uint16_t flags, uint8_t cmd, uint8_t version, netlink_prepare_nlh_cb_t cb_prepare_nlh, void *prepare_data, mnl_cb_t cb_data, void *data)\n{\n\tchar buf[MNL_SOCKET_BUFFER_SIZE];\n\tstruct nlmsghdr *nlh;\n\tstruct genlmsghdr *genl;\n\tssize_t rd;\n\n\tnlh = mnl_nlmsg_put_header(buf);\n\tnlh->nlmsg_type\t= type;\n\tnlh->nlmsg_flags = flags;\n\n\tgenl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));\n\tgenl->cmd = cmd;\n\tgenl->version = version;\n\n\tif (cb_prepare_nlh) cb_prepare_nlh(nlh, prepare_data);\n\n\tif (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)\n\t{\n\t\tDLOG_PERROR(\"mnl_socket_sendto\");\n\t\treturn false;\n\t}\n\n\twhile ((rd=mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0)\n\t{\n\t\tswitch(mnl_cb_run(buf, rd, 0, 0, cb_data, data))\n\t\t{\n\t\t\tcase MNL_CB_STOP:\n\t\t\t\treturn true;\n\t\t\tcase MNL_CB_OK:\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic void wlan_id_prepare(struct nlmsghdr *nlh, void *param)\n{\n\tmnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, \"nl80211\");\n}\nstatic int wlan_id_attr_cb(const struct nlattr *attr, void *data)\n{\n\tif (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)\n\t{\n\t\tDLOG_PERROR(\"mnl_attr_type_valid\");\n\t\treturn MNL_CB_ERROR;\n\t}\n\n\tswitch(mnl_attr_get_type(attr))\n\t{\n\t\tcase CTRL_ATTR_FAMILY_ID:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_U16) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate(family_id)\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\t*((uint16_t*)data) = mnl_attr_get_u16(attr);\n\t\tbreak;\n\t}\n\treturn MNL_CB_OK;\n}\nstatic int wlan_id_cb(const struct nlmsghdr *nlh, void *data)\n{\n\treturn mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_id_attr_cb, data);\n}\nstatic uint16_t wlan_get_family_id(struct mnl_socket* nl)\n{\n\tuint16_t id;\n\treturn netlink_genl_simple_transact(nl, GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, 1, wlan_id_prepare, NULL, wlan_id_cb, &id) ? id : 0;\n}\n\nstatic int wlan_info_attr_cb(const struct nlattr *attr, void *data)\n{\n\tstruct wlan_interface *wlan = (struct wlan_interface *)data;\n\tswitch(mnl_attr_get_type(attr))\n\t{\n\t\tcase NL80211_ATTR_IFINDEX:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate(ifindex)\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\twlan->ifindex = mnl_attr_get_u32(attr);\n\t\t\tbreak;\n\t\tcase NL80211_ATTR_SSID:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate(ssid)\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\tsnprintf(wlan->ssid,sizeof(wlan->ssid),\"%s\",mnl_attr_get_str(attr));\n\t\t\tbreak;\n\t\tcase NL80211_ATTR_IFNAME:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate(ifname)\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\tsnprintf(wlan->ifname,sizeof(wlan->ifname),\"%s\",mnl_attr_get_str(attr));\n\t\t\tbreak;\n\t}\n\treturn MNL_CB_OK;\n}\nstruct wlan_info_req\n{\n\tstruct wlan_interface_collection *wc;\n\tbool bReqSSID;\n};\nstatic int wlan_info_cb(const struct nlmsghdr *nlh, void *data)\n{\n\tint ret;\n\tstruct wlan_info_req *wr = (struct wlan_info_req*)data;\n\tif (wr->wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;\n\tmemset(wr->wc->wlan + wr->wc->count,0,sizeof(struct wlan_interface));\n\tret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_info_attr_cb, wr->wc->wlan + wr->wc->count);\n\tif (ret>=0 && (!wr->bReqSSID || *wr->wc->wlan[wr->wc->count].ssid) && *wr->wc->wlan[wr->wc->count].ifname && wr->wc->wlan[wr->wc->count].ifindex)\n\t\twr->wc->count++;\n\treturn ret;\n}\nstatic bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w, bool bReqSSID)\n{\n\tstruct wlan_info_req req = { .bReqSSID = bReqSSID, .wc = w };\n\treturn netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0, NULL, NULL, wlan_info_cb, &req);\n}\n\n\nstatic void scan_prepare(struct nlmsghdr *nlh, void *param)\n{\n\tmnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, *(int*)param);\n}\nstatic uint8_t *find_ie(uint8_t *buf, size_t len, uint8_t ie)\n{\n\twhile (len>=2)\n\t{\n\t\tif (len<(2+buf[1])) break;\n\t\tif (buf[0]==ie) return buf;\n\t\tbuf+=buf[1]+2;\n\t\tlen-=buf[1]+2;\n\t}\n\treturn NULL;\n}\nstatic int scan_info_attr_cb(const struct nlattr *attr, void *data)\n{\n\tstruct wlan_interface *wlan = (struct wlan_interface *)data;\n\tconst struct nlattr *nested;\n\tuint8_t *payload, *ie;\n\tuint16_t payload_len;\n\tbool ok;\n\n\tswitch(mnl_attr_get_type(attr))\n\t{\n\t\tcase NL80211_ATTR_IFINDEX:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\twlan->ifindex = mnl_attr_get_u32(attr);\n\t\t\tif (!if_indextoname(wlan->ifindex, wlan->ifname))\n\t\t\t\tDLOG_PERROR(\"if_indextoname\");\n\t\t\tbreak;\n\t\tcase NL80211_ATTR_BSS:\n\t\t\tif (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"mnl_attr_validate\");\n\t\t\t\treturn MNL_CB_ERROR;\n\t\t\t}\n\t\t\tok = false;\n\t\t\tmnl_attr_for_each_nested(nested, attr)\n\t\t\t{\n\t\t\t\tif (mnl_attr_get_type(nested)==NL80211_BSS_STATUS)\n\t\t\t\t{\n\t\t\t\t\tuint32_t status = mnl_attr_get_u32(nested);\n\t\t\t\t\tif (status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_AUTHENTICATED || status==NL80211_BSS_STATUS_IBSS_JOINED)\n\t\t\t\t\t{\n\t\t\t\t\t\tok=1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!ok) break;\n\t\t\tmnl_attr_for_each_nested(nested, attr)\n\t\t\t{\n\t\t\t\tswitch(mnl_attr_get_type(nested))\n\t\t\t\t{\n\t\t\t\t\tcase NL80211_BSS_INFORMATION_ELEMENTS:\n\t\t\t\t\t\tpayload_len = mnl_attr_get_payload_len(nested);\n\t\t\t\t\t\tpayload = mnl_attr_get_payload(nested);\n\t\t\t\t\t\tie = find_ie(payload,payload_len,0);\n\t\t\t\t\t\tif (ie)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tuint8_t l = ie[1];\n\t\t\t\t\t\t\tif (l>=(sizeof(wlan->ssid))) l=sizeof(wlan->ssid)-1;\n\t\t\t\t\t\t\tmemcpy(wlan->ssid,ie+2,l);\n\t\t\t\t\t\t\twlan->ssid[l]=0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn MNL_CB_OK;\n}\nstatic int scan_info_cb(const struct nlmsghdr *nlh, void *data)\n{\n\tint ret;\n\tstruct wlan_interface_collection *wc = (struct wlan_interface_collection*)data;\n\tif (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK;\n\tmemset(wc->wlan+wc->count,0,sizeof(wc->wlan[0]));\n\tret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), scan_info_attr_cb, wc->wlan+wc->count);\n\tif (ret>=0 && *wc->wlan[wc->count].ssid && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex)\n\t\twc->count++;\n\treturn ret;\n}\nstatic bool scan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w)\n{\n\tstruct wlan_interface_collection wc_all = { .count = 0 };\n\t// wlan_info does not return ssid since kernel 5.19\n\t// it's used to enumerate all wifi interfaces then call scan_info on each\n\tif (!wlan_info(nl, wlan_family_id, &wc_all, false)) return false;\n\tw->count=0;\n\tfor(int i=0;i<wc_all.count;i++)\n\t\tif (!netlink_genl_simple_transact(nl, wlan_family_id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0, scan_prepare, (void*)&wc_all.wlan[i].ifindex, scan_info_cb, w))\n\t\t\treturn false;\n\treturn true;\n}\n\n\nstatic bool wlan_init80211(struct mnl_socket** nl)\n{\n\tif (!(*nl = mnl_socket_open(NETLINK_GENERIC)))\n\t{\n\t\tDLOG_PERROR(\"mnl_socket_open\");\n\t\treturn false;\n\t}\n\tif (mnl_socket_bind(*nl, 0, MNL_SOCKET_AUTOPID))\n\t{\n\t\tDLOG_PERROR(\"mnl_socket_bind\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic void wlan_deinit80211(struct mnl_socket** nl)\n{\n\tif (*nl)\n\t{\n\t\tmnl_socket_close(*nl);\n\t\t*nl = NULL;\n\t}\n}\n\nstatic time_t wlan_info_last = 0;\nstatic bool wlan_info_rate_limited(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w)\n{\n\tbool bres = true;\n\ttime_t now = time(NULL);\n\n\t// do not purge too often to save resources\n\tif (wlan_info_last != now)\n\t{\n\t\tbres = scan_info(nl,wlan_family_id,w);\n\t\twlan_info_last = now;\n\t}\n\treturn bres;\n}\n\nstatic struct mnl_socket* nl_wifi = NULL;\nstatic uint16_t id_nl80211;\nstruct wlan_interface_collection wlans = { .count = 0 };\n\nvoid wlan_info_deinit(void)\n{\n\twlan_deinit80211(&nl_wifi);\n}\nbool wlan_info_init(void)\n{\n\twlan_info_deinit();\n\n\tif (!wlan_init80211(&nl_wifi)) return false;\n\tif (!(id_nl80211 = wlan_get_family_id(nl_wifi)))\n\t{\n\t\twlan_info_deinit();\n\t\treturn false;\n\t}\n\treturn true;\n}\nbool wlan_info_get_rate_limited(void)\n{\n\treturn wlan_info_rate_limited(nl_wifi, id_nl80211, &wlans);\n}\n\n#endif\n\n\n#ifdef HAS_FILTER_SSID\nconst char *wlan_ifname2ssid(const struct wlan_interface_collection *w, const char *ifname)\n{\n\tint i;\n\tif (ifname)\n\t{\n\t\tfor (i=0;i<w->count;i++)\n\t\t\tif (!strcmp(w->wlan[i].ifname,ifname))\n\t\t\t\treturn w->wlan[i].ssid;\n\t}\n\treturn NULL;\n}\nconst char *wlan_ifidx2ssid(const struct wlan_interface_collection *w,int ifidx)\n{\n\tint i;\n\tfor (i=0;i<w->count;i++)\n\t\tif (w->wlan[i].ifindex == ifidx)\n\t\t\treturn w->wlan[i].ssid;\n\treturn NULL;\n}\nconst char *wlan_ssid_search_ifname(const char *ifname)\n{\n\treturn wlan_ifname2ssid(&wlans,ifname);\n}\nconst char *wlan_ssid_search_ifidx(int ifidx)\n{\n\treturn wlan_ifidx2ssid(&wlans,ifidx);\n}\n\n#endif\n\n\n\nuint8_t hop_count_guess(uint8_t ttl)\n{\n\t// 18.65.168.125 ( cloudfront ) \t255\n\t// 157.254.246.178 \t\t\t128\n\t// 1.1.1.1\t\t\t\t 64\n\t// guess original ttl. consider path lengths less than 32 hops\n\n\tuint8_t orig;\n\n\tif (ttl>223)\n\t\torig=255;\n\telse if (ttl<128 && ttl>96)\n\t\torig=128;\n\telse if (ttl<64 && ttl>32)\n\t\torig=64;\n\telse\n\t\treturn 0;\n\n\treturn orig - ttl;\n}\n// return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling\nuint8_t autottl_eval(uint8_t hop_count, const autottl *attl)\n{\n\tuint8_t fake;\n\tint d;\n\n\td = (int)hop_count + attl->delta;\n\tif (d<attl->min) fake=attl->min;\n\telse if (d>attl->max) fake=attl->max;\n\telse fake=(uint8_t)d;\n\n\tif (attl->delta<0 && fake>=hop_count || attl->delta>=0 && fake<hop_count)\n\t\treturn 0;\n\n\treturn fake;\n}\n\nvoid do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6)\n{\n\tuint16_t nport;\n\n\tif (ip && target4)\n\t{\n\t\tnport = target4->sin_port;\n\t\tif (bOutbound)\n\t\t\tip->ip_dst = target4->sin_addr;\n\t\telse\n\t\t\tip->ip_src = target4->sin_addr;\n\t\tip4_fix_checksum(ip);\n\t}\n\telse if (ip6 && target6)\n\t{\n\t\tnport = target6->sin6_port;\n\t\tif (bOutbound)\n\t\t\tip6->ip6_dst = target6->sin6_addr;\n\t\telse\n\t\t\tip6->ip6_src = target6->sin6_addr;\n\t}\n\telse\n\t\treturn;\n\tif (nport)\n\t{\n\t\tif (tcphdr)\n\t\t{\n\t\t\tif (bOutbound)\n\t\t\t\ttcphdr->th_dport = nport;\n\t\t\telse\n\t\t\t\ttcphdr->th_sport = nport;\n\t\t}\n\t\tif (udphdr)\n\t\t{\n\t\t\tif (bOutbound)\n\t\t\t\tudphdr->uh_dport = nport;\n\t\t\telse\n\t\t\t\tudphdr->uh_sport = nport;\n\t\t}\n\t}\n}\n\n\nvoid verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr)\n{\n\tif (!(verdict & VERDICT_NOCSUM))\n\t{\n\t\t#ifdef __CYGWIN__\n\t\t// always fix csum for windivert. original can be partial or bad\n\t\tif ((verdict & VERDICT_MASK)!=VERDICT_DROP)\n\t\t#elif defined(__FreeBSD__)\n\t\t// FreeBSD tend to pass ipv6 frames with wrong checksum\n\t\tif ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr)\n\t\t#else\n\t\t// if original packet was tampered earlier it needs checksum fixed\n\t\tif ((verdict & VERDICT_MASK)==VERDICT_MODIFY)\n\t\t#endif\n\t\t\ttcp_fix_checksum(tcphdr,transport_len,ip,ip6hdr);\n\t}\n}\nvoid verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr)\n{\n\tif (!(verdict & VERDICT_NOCSUM))\n\t{\n\t\t#ifdef __CYGWIN__\n\t\t// always fix csum for windivert. original can be partial or bad\n\t\tif ((verdict & VERDICT_MASK)!=VERDICT_DROP)\n\t\t#elif defined(__FreeBSD__)\n\t\t// FreeBSD tend to pass ipv6 frames with wrong checksum\n\t\tif ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr)\n\t\t#else\n\t\t// if original packet was tampered earlier it needs checksum fixed\n\t\tif ((verdict & VERDICT_MASK)==VERDICT_MODIFY)\n\t\t#endif\n\t\t\tudp_fix_checksum(udphdr,transport_len,ip,ip6hdr);\n\t}\n}\n\nvoid dbgprint_socket_buffers(int fd)\n{\n\tif (params.debug)\n\t{\n\t\tint v;\n\t\tsocklen_t sz;\n\t\tsz = sizeof(int);\n\t\tif (!getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &v, &sz))\n\t\t\tDLOG(\"fd=%d SO_RCVBUF=%d\\n\", fd, v);\n\t\t\tsz = sizeof(int);\n\t\tif (!getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &v, &sz))\n\t\t\tDLOG(\"fd=%d SO_SNDBUF=%d\\n\", fd, v);\n\t}\n}\nbool set_socket_buffers(int fd, int rcvbuf, int sndbuf)\n{\n\tDLOG(\"set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\\n\", fd, rcvbuf, sndbuf);\n\tif (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (SO_RCVBUF)\");\n\t\treturn false;\n\t}\n\tif (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (SO_SNDBUF)\");\n\t\treturn false;\n\t}\n\tdbgprint_socket_buffers(fd);\n\treturn true;\n}\n"
  },
  {
    "path": "nfq/darkmagic.h",
    "content": "#pragma once\n\n#include \"nfqws.h\"\n#include \"checksum.h\"\n#include \"packet_queue.h\"\n#include \"pools.h\"\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/param.h>\n#include <netinet/in.h>\n\n#define __FAVOR_BSD\n#include <netinet/ip.h>\n#include <netinet/ip6.h>\n#include <netinet/tcp.h>\n#include <netinet/udp.h>\n\n#ifndef IPV6_FREEBIND\n#define IPV6_FREEBIND           78\n#endif\n\n#ifdef __CYGWIN__\n#define INITGUID\n#include \"windivert/windivert.h\"\n#endif\n\n#ifndef IPPROTO_DIVERT\n#define IPPROTO_DIVERT 258\n#endif\n\n#ifndef AF_DIVERT\n#define\tAF_DIVERT\t44\t/* divert(4) */\n#endif\n#ifndef PF_DIVERT\n#define PF_DIVERT AF_DIVERT\n#endif\n\n// returns netorder value\nuint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment);\nuint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment);\n\n#define FOOL_NONE\t0x00\n#define FOOL_MD5SIG\t0x01\n#define FOOL_BADSUM\t0x02\n#define FOOL_TS\t\t0x04\n#define FOOL_BADSEQ\t0x08\n#define FOOL_HOPBYHOP\t0x10\n#define FOOL_HOPBYHOP2\t0x20\n#define FOOL_DESTOPT\t0x40\n#define FOOL_IPFRAG1\t0x80\n#define FOOL_DATANOACK\t0x100\n\n#define SCALE_NONE ((uint8_t)-1)\n\n#define VERDICT_PASS\t0\n#define VERDICT_MODIFY\t1\n#define VERDICT_DROP\t2\n#define VERDICT_MASK\t3\n#define VERDICT_NOCSUM\t4\n#define VERDICT_GARBAGE\t8\n\n#define IP4_TOS(ip_header) (ip_header ? ip_header->ip_tos : 0)\n#define IP4_IP_ID(ip_header) (ip_header ? ip_header->ip_id : 0)\n#define IP6_FLOW(ip6_header) (ip6_header ? ip6_header->ip6_ctlun.ip6_un1.ip6_un1_flow : 0)\n\n// seq and wsize have network byte order\nbool prepare_tcp_segment4(\n\tconst struct sockaddr_in *src, const struct sockaddr_in *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\nbool prepare_tcp_segment6(\n\tconst struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tuint8_t ttl,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\nbool prepare_tcp_segment(\n\tconst struct sockaddr *src, const struct sockaddr *dst,\n\tuint16_t tcp_flags,\n\tbool sack,\n\tuint16_t nmss,\n\tuint32_t nseq, uint32_t nack_seq,\n\tuint16_t nwsize,\n\tuint8_t scale_factor,\n\tuint32_t *timestamps,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tuint32_t ts_increment,\n\tuint32_t badseq_increment,\n\tuint32_t badseq_ack_increment,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\n\n\nbool prepare_udp_segment4(\n\tconst struct sockaddr_in *src, const struct sockaddr_in *dst,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\nbool prepare_udp_segment6(\n\tconst struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,\n\tuint8_t ttl,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\nbool prepare_udp_segment(\n\tconst struct sockaddr *src, const struct sockaddr *dst,\n\tbool DF,\n\tuint8_t ttl,\n\tuint8_t tos,\n\tuint16_t ip_id,\n\tuint32_t flow_label,\n\tuint32_t fooling,\n\tconst uint8_t *padding, size_t padding_size,\n\tint padlen,\n\tconst void *data, uint16_t len,\n\tuint8_t *buf, size_t *buflen);\n\nbool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen);\n\n// ipv4: ident==-1 - copy ip_id from original ipv4 packet\nbool ip_frag4(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size);\nbool ip_frag6(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size);\nbool ip_frag(\n\tconst uint8_t *pkt, size_t pkt_size,\n\tsize_t frag_pos, uint32_t ident,\n\tuint8_t *pkt1, size_t *pkt1_size,\n\tuint8_t *pkt2, size_t *pkt2_size);\n\t\nbool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl);\nuint16_t get_tcp_flags(const struct tcphdr *tcp);\nvoid apply_tcp_flags(struct tcphdr *tcp, uint16_t fl);\n\nvoid extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport);\nvoid extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst);\nuint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind);\nuint32_t *tcp_find_timestamps(struct tcphdr *tcp);\nuint8_t tcp_find_scale_factor(const struct tcphdr *tcp);\nuint16_t tcp_find_mss(struct tcphdr *tcp);\nbool tcp_has_sack(struct tcphdr *tcp);\n\nbool tcp_has_fastopen(const struct tcphdr *tcp);\n\nbool ip_has_df(const struct ip *ip);\n\n#ifdef __CYGWIN__\nextern uint32_t w_win32_error;\n\nbool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter);\nbool win_dark_deinit(void);\nbool logical_net_filter_match(void);\nbool nlm_list(bool bAll);\nbool windivert_init(const char *filter);\nbool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa);\nbool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa);\n#else\n// should pre-do it if dropping privileges. otherwise its not necessary\nbool rawsend_preinit(bool bind_fix4, bool bind_fix6);\n#endif\n\n// auto creates internal socket and uses it for subsequent calls\nbool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len);\nbool rawsend_rp(const struct rawpacket *rp);\n// return trues if all packets were send successfully\nbool rawsend_queue(struct rawpacket_tailhead *q);\n// cleans up socket autocreated by rawsend\nvoid rawsend_cleanup(void);\n\n#ifdef BSD\nint socket_divert(sa_family_t family);\n#endif\n\nconst char *proto_name(uint8_t proto);\nuint16_t family_from_proto(uint8_t l3proto);\nvoid print_ip(const struct ip *ip);\nvoid print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto);\nvoid print_tcphdr(const struct tcphdr *tcphdr);\nvoid print_udphdr(const struct udphdr *udphdr);\nvoid str_ip(char *s, size_t s_len, const struct ip *ip);\nvoid str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto);\nvoid str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr);\nvoid str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr);\nvoid str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr);\n\nbool proto_check_ipv4(const uint8_t *data, size_t len);\nvoid proto_skip_ipv4(uint8_t **data, size_t *len);\nbool proto_check_ipv6(const uint8_t *data, size_t len);\nvoid proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type);\nbool proto_check_tcp(const uint8_t *data, size_t len);\nvoid proto_skip_tcp(uint8_t **data, size_t *len);\nbool proto_check_udp(const uint8_t *data, size_t len);\nvoid proto_skip_udp(uint8_t **data, size_t *len);\nstruct dissect\n{\n\tuint8_t *data_pkt;\n\tsize_t len_pkt;\n\tstruct ip *ip;\n\tstruct ip6_hdr *ip6;\n\tuint8_t proto;\n\tstruct tcphdr *tcp;\n\tstruct udphdr *udp;\n\tsize_t transport_len;\n\tuint8_t *data_payload;\n\tsize_t len_payload;\n};\nvoid proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis);\n\nbool tcp_synack_segment(const struct tcphdr *tcphdr);\nbool tcp_syn_segment(const struct tcphdr *tcphdr);\nbool tcp_ack_segment(const struct tcphdr *tcphdr);\n// scale_factor=SCALE_NONE - do not change\nvoid tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor);\nvoid tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor);\n\ntypedef struct\n{\n\tint8_t delta;\n\tuint8_t min, max;\n} autottl;\n#define AUTOTTL_ENABLED(a) ((a).delta || (a).min || (a).max)\n\nuint8_t hop_count_guess(uint8_t ttl);\nuint8_t autottl_eval(uint8_t hop_count, const autottl *attl);\nvoid do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6);\n\nvoid verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr);\nvoid verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr);\n\nvoid dbgprint_socket_buffers(int fd);\nbool set_socket_buffers(int fd, int rcvbuf, int sndbuf);\n\n\n#ifdef HAS_FILTER_SSID\n\nstruct wlan_interface\n{\n\tint ifindex;\n\tchar ifname[IFNAMSIZ], ssid[33];\n};\n#define WLAN_INTERFACE_MAX 16\nstruct wlan_interface_collection\n{\n\tint count;\n\tstruct wlan_interface wlan[WLAN_INTERFACE_MAX];\n};\n\nextern struct wlan_interface_collection wlans;\n\nvoid wlan_info_deinit(void);\nbool wlan_info_init(void);\nbool wlan_info_get_rate_limited(void);\nconst char *wlan_ssid_search_ifname(const char *ifname);\nconst char *wlan_ssid_search_ifidx(int ifidx);\n\n#endif\n"
  },
  {
    "path": "nfq/desync.c",
    "content": "#define _GNU_SOURCE\n\n#include <string.h>\n#include <errno.h>\n\n#include \"desync.h\"\n#include \"protocol.h\"\n#include \"params.h\"\n#include \"helpers.h\"\n#include \"hostlist.h\"\n#include \"ipset.h\"\n#include \"conntrack.h\"\n\nconst char *fake_http_request_default = \"GET / HTTP/1.1\\r\\nHost: www.iana.org\\r\\n\"\n\"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0\\r\\n\"\n\"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\\r\\n\"\n\"Accept-Encoding: gzip, deflate, br\\r\\n\\r\\n\";\n\n// SNI - www.microsoft.com\nconst uint8_t fake_tls_clienthello_default[680] = {\n  0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41,\n  0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f,\n  0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b,\n  0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2,\n  0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba,\n  0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65,\n  0x80, 0x5d, 0x40, 0x38, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02,\n  0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30,\n  0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,\n  0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16,\n  0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,\n  0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17,\n  0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00,\n  0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01,\n  0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,\n  0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74,\n  0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00,\n  0x00, 0x00, 0x00, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03,\n  0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b,\n  0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad,\n  0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4,\n  0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4,\n  0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8,\n  0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca,\n  0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60,\n  0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07,\n  0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f,\n  0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00,\n  0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x0d, 0x00, 0x18,\n  0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05,\n  0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01,\n  0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01,\n  0x00, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe,\n  0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62,\n  0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce,\n  0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11,\n  0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27,\n  0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51,\n  0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b,\n  0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11,\n  0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8,\n  0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0,\n  0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe,\n  0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45,\n  0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc,\n  0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50,\n  0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5,\n  0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5,\n  0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf,\n  0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b,\n  0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16,\n  0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2,\n  0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51,\n  0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0,\n  0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4,\n  0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb,\n  0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b\n};\n\n#define PKTDATA_MAXDUMP 32\n#define IP_MAXDUMP 80\n\n#define TCP_MAX_REASM 16384\n#define UDP_MAX_REASM 16384\n\nstatic void TLSDebugHandshake(const uint8_t *tls, size_t sz)\n{\n\tif (!params.debug) return;\n\n\tif (sz < 6) return;\n\n\tconst uint8_t *ext;\n\tsize_t len, len2;\n\n\tuint16_t v_handshake = pntoh16(tls + 4), v, v2;\n\tDLOG(\"TLS handshake version : %s\\n\", TLSVersionStr(v_handshake));\n\n\tif (TLSFindExtInHandshake(tls, sz, 43, &ext, &len, false))\n\t{\n\t\tif (len)\n\t\t{\n\t\t\tlen2 = ext[0];\n\t\t\tif (len2 < len)\n\t\t\t{\n\t\t\t\tfor (ext++, len2 &= ~1; len2; len2 -= 2, ext += 2)\n\t\t\t\t{\n\t\t\t\t\tv = pntoh16(ext);\n\t\t\t\t\tDLOG(\"TLS supported versions ext : %s\\n\", TLSVersionStr(v));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tDLOG(\"TLS supported versions ext : not present\\n\");\n\n\tif (TLSFindExtInHandshake(tls, sz, 16, &ext, &len, false))\n\t{\n\t\tif (len >= 2)\n\t\t{\n\t\t\tlen2 = pntoh16(ext);\n\t\t\tif (len2 <= (len - 2))\n\t\t\t{\n\t\t\t\tchar s[32];\n\t\t\t\tfor (ext += 2; len2;)\n\t\t\t\t{\n\t\t\t\t\tv = *ext; ext++; len2--;\n\t\t\t\t\tif (v <= len2)\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = v < sizeof(s) ? v : sizeof(s) - 1;\n\t\t\t\t\t\tmemcpy(s, ext, v2);\n\t\t\t\t\t\ts[v2] = 0;\n\t\t\t\t\t\tDLOG(\"TLS ALPN ext : %s\\n\", s);\n\t\t\t\t\t\tlen2 -= v;\n\t\t\t\t\t\text += v;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tDLOG(\"TLS ALPN ext : not present\\n\");\n\n\tDLOG(\"TLS ECH ext : %s\\n\", TLSFindExtInHandshake(tls, sz, 65037, NULL, NULL, false) ? \"present\" : \"not present\");\n}\nstatic void TLSDebug(const uint8_t *tls, size_t sz)\n{\n\tif (!params.debug) return;\n\n\tif (sz < 11) return;\n\n\tDLOG(\"TLS record layer version : %s\\n\", TLSVersionStr(pntoh16(tls + 1)));\n\n\tsize_t reclen = TLSRecordLen(tls);\n\tif (reclen < sz) sz = reclen; // correct len if it has more data than the first tls record has\n\n\tTLSDebugHandshake(tls + 5, sz - 5);\n}\n\nbool desync_valid_zero_stage(enum dpi_desync_mode mode)\n{\n\treturn mode == DESYNC_SYNACK || mode == DESYNC_SYNDATA;\n}\nbool desync_valid_first_stage(enum dpi_desync_mode mode)\n{\n\treturn mode == DESYNC_FAKE || mode == DESYNC_FAKE_KNOWN || mode == DESYNC_RST || mode == DESYNC_RSTACK || mode == DESYNC_HOPBYHOP || mode == DESYNC_DESTOPT || mode == DESYNC_IPFRAG1;\n}\nbool desync_only_first_stage(enum dpi_desync_mode mode)\n{\n\treturn false;\n}\nbool desync_valid_second_stage_tcp(enum dpi_desync_mode mode)\n{\n\treturn\n\t\tmode == DESYNC_NONE || mode == DESYNC_FAKEDDISORDER || mode == DESYNC_FAKEDSPLIT || mode == DESYNC_MULTISPLIT || mode == DESYNC_MULTIDISORDER || mode == DESYNC_HOSTFAKESPLIT ||\n\t\tmode == DESYNC_IPFRAG2;\n}\nbool desync_valid_second_stage_udp(enum dpi_desync_mode mode)\n{\n\treturn mode == DESYNC_NONE || mode == DESYNC_UDPLEN || mode == DESYNC_TAMPER || mode == DESYNC_IPFRAG2;\n}\nbool desync_valid_second_stage(enum dpi_desync_mode mode)\n{\n\treturn desync_valid_second_stage_tcp(mode) || desync_valid_second_stage_udp(mode);\n}\nenum dpi_desync_mode desync_mode_from_string(const char *s)\n{\n\tif (!s)\n\t\treturn DESYNC_NONE;\n\telse if (!strcmp(s, \"fake\"))\n\t\treturn DESYNC_FAKE;\n\telse if (!strcmp(s, \"fakeknown\"))\n\t\treturn DESYNC_FAKE_KNOWN;\n\telse if (!strcmp(s, \"rst\"))\n\t\treturn DESYNC_RST;\n\telse if (!strcmp(s, \"rstack\"))\n\t\treturn DESYNC_RSTACK;\n\telse if (!strcmp(s, \"synack\"))\n\t\treturn DESYNC_SYNACK;\n\telse if (!strcmp(s, \"syndata\"))\n\t\treturn DESYNC_SYNDATA;\n\telse if (!strcmp(s, \"fakeddisorder\") || !strcmp(s, \"disorder\"))\n\t\treturn DESYNC_FAKEDDISORDER;\n\telse if (!strcmp(s, \"fakedsplit\") || !strcmp(s, \"split\"))\n\t\treturn DESYNC_FAKEDSPLIT;\n\telse if (!strcmp(s, \"multisplit\") || !strcmp(s, \"split2\"))\n\t\treturn DESYNC_MULTISPLIT;\n\telse if (!strcmp(s, \"multidisorder\") || !strcmp(s, \"disorder2\"))\n\t\treturn DESYNC_MULTIDISORDER;\n\telse if (!strcmp(s, \"hostfakesplit\"))\n\t\treturn DESYNC_HOSTFAKESPLIT;\n\telse if (!strcmp(s, \"ipfrag2\"))\n\t\treturn DESYNC_IPFRAG2;\n\telse if (!strcmp(s, \"hopbyhop\"))\n\t\treturn DESYNC_HOPBYHOP;\n\telse if (!strcmp(s, \"destopt\"))\n\t\treturn DESYNC_DESTOPT;\n\telse if (!strcmp(s, \"ipfrag1\"))\n\t\treturn DESYNC_IPFRAG1;\n\telse if (!strcmp(s, \"udplen\"))\n\t\treturn DESYNC_UDPLEN;\n\telse if (!strcmp(s, \"tamper\"))\n\t\treturn DESYNC_TAMPER;\n\treturn DESYNC_INVALID;\n}\n\nstatic bool dp_match(\n\tstruct desync_profile *dp,\n\tuint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid,\n\tbool *bCheckDone, bool *bCheckResult, bool *bExcluded)\n{\n\tbool bHostlistsEmpty;\n\n\tif (bCheckDone) *bCheckDone = false;\n\n\tif (!HostlistsReloadCheckForProfile(dp)) return false;\n\n\tif ((dest->sa_family == AF_INET && !dp->filter_ipv4) || (dest->sa_family == AF_INET6 && !dp->filter_ipv6))\n\t\t// L3 filter does not match\n\t\treturn false;\n\tif ((l3proto == IPPROTO_TCP && !port_filters_in_range(&dp->pf_tcp, saport(dest))) || (l3proto == IPPROTO_UDP && !port_filters_in_range(&dp->pf_udp, saport(dest))))\n\t\t// L4 filter does not match\n\t\treturn false;\n\tif (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7))\n\t\t// L7 filter does not match\n\t\treturn false;\n#ifdef HAS_FILTER_SSID\n\tif (!LIST_EMPTY(&dp->filter_ssid) && !strlist_search(&dp->filter_ssid, ssid))\n\t\treturn false;\n#endif\n\n\tbHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp);\n\tif (!dp->hostlist_auto && !hostname && !bHostlistsEmpty)\n\t\t// avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown.\n\t\treturn false;\n\tif (!IpsetCheck(dp, dest->sa_family == AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family == AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL))\n\t\t// target ip does not match\n\t\treturn false;\n\n\t// autohostlist profile matching l3/l4/l7 filter always win\n\tif (dp->hostlist_auto) return true;\n\n\tif (bHostlistsEmpty)\n\t\t// profile without hostlist filter wins\n\t\treturn true;\n\telse\n\t{\n\t\t// if hostlists are present profile matches only if hostname is known and satisfy profile hostlists\n\t\tif (hostname)\n\t\t{\n\t\t\tif (bCheckDone) *bCheckDone = true;\n\t\t\tbool b;\n\t\t\tb = HostlistCheck(dp, hostname, bNoSubdom, bExcluded, true);\n\t\t\tif (bCheckResult) *bCheckResult = b;\n\t\t\treturn b;\n\t\t}\n\t}\n\treturn false;\n}\nstatic struct desync_profile *dp_find(\n\tstruct desync_profile_list_head *head,\n\tuint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid,\n\tbool *bCheckDone, bool *bCheckResult, bool *bExcluded)\n{\n\tstruct desync_profile_list *dpl;\n\tif (params.debug)\n\t{\n\t\tchar ip_port[48];\n\t\tntop46_port(dest, ip_port, sizeof(ip_port));\n\t\tDLOG(\"desync profile search for %s target=%s l7proto=%s ssid='%s' hostname='%s'\\n\", proto_name(l3proto), ip_port, l7proto_str(l7proto), ssid ? ssid : \"\", hostname ? hostname : \"\");\n\t}\n\tif (bCheckDone) *bCheckDone = false;\n\tLIST_FOREACH(dpl, head, next)\n\t{\n\t\tif (dp_match(&dpl->dp, l3proto, dest, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded))\n\t\t{\n\t\t\tDLOG(\"desync profile %d matches\\n\", dpl->dp.n);\n\t\t\treturn &dpl->dp;\n\t\t}\n\t}\n\tDLOG(\"desync profile not found\\n\");\n\treturn NULL;\n}\n\n// auto creates internal socket and uses it for subsequent calls\nstatic bool rawsend_rep(int repeats, const struct sockaddr* dst, uint32_t fwmark, const char *ifout, const void *data, size_t len)\n{\n\tfor (int i = 0; i < repeats; i++)\n\t\tif (!rawsend(dst, fwmark, ifout, data, len))\n\t\t\treturn false;\n\treturn true;\n}\n\n\nstatic uint64_t cutoff_get_limit(const t_ctrack *ctrack, char mode)\n{\n\tswitch (mode)\n\t{\n\tcase 'n': return ctrack->pcounter_orig;\n\tcase 'd': return ctrack->pdcounter_orig;\n\tcase 's': return ctrack->seq_last - ctrack->seq0;\n\tdefault: return 0;\n\t}\n}\nstatic bool cutoff_test(const t_ctrack *ctrack, uint64_t cutoff, char mode)\n{\n\treturn cutoff && cutoff_get_limit(ctrack, mode) >= cutoff;\n}\nstatic void maybe_cutoff(t_ctrack *ctrack, uint8_t proto)\n{\n\tif (ctrack && ctrack->dp)\n\t{\n\t\tif (proto == IPPROTO_TCP)\n\t\t\tctrack->b_wssize_cutoff |= cutoff_test(ctrack, ctrack->dp->wssize_cutoff, ctrack->dp->wssize_cutoff_mode);\n\t\tctrack->b_desync_cutoff |= cutoff_test(ctrack, ctrack->dp->desync_cutoff, ctrack->dp->desync_cutoff_mode);\n\t\tctrack->b_dup_cutoff |= cutoff_test(ctrack, ctrack->dp->dup_cutoff, ctrack->dp->dup_cutoff_mode);\n\t\tctrack->b_orig_mod_cutoff |= cutoff_test(ctrack, ctrack->dp->orig_mod_cutoff, ctrack->dp->orig_mod_cutoff_mode);\n\n\t\t// in MULTI STRATEGY concept conntrack entry holds desync profile\n\t\t// we do not want to remove conntrack entries ASAP anymore\n\n\t\t/*\n\t\t// we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached\n\t\t// do not drop udp entry because it will be recreated when next packet arrives\n\t\tif (proto==IPPROTO_TCP)\n\t\t\tctrack->b_cutoff |= \\\n\t\t\t\t(!ctrack->dp->wssize || ctrack->b_wssize_cutoff) &&\n\t\t\t\t(!ctrack->dp->desync_cutoff || ctrack->b_desync_cutoff) &&\n\t\t\t\t(!ctrack->hostname_ah_check || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP) &&\n\t\t\t\tReasmIsEmpty(&ctrack->reasm_orig);\n\t\t*/\n\t}\n}\nstatic void wssize_cutoff(t_ctrack *ctrack)\n{\n\tif (ctrack)\n\t{\n\t\tctrack->b_wssize_cutoff = true;\n\t\tmaybe_cutoff(ctrack, IPPROTO_TCP);\n\t}\n}\nstatic void forced_wssize_cutoff(t_ctrack *ctrack)\n{\n\tif (ctrack && ctrack->dp && !ctrack->b_wssize_cutoff)\n\t{\n\t\tDLOG(\"forced wssize-cutoff\\n\");\n\t\twssize_cutoff(ctrack);\n\t}\n}\n\nstatic void ctrack_stop_retrans_counter(t_ctrack *ctrack)\n{\n\tif (ctrack && ctrack->hostname_ah_check)\n\t{\n\t\tctrack->req_retrans_counter = RETRANS_COUNTER_STOP;\n\t\tmaybe_cutoff(ctrack, IPPROTO_TCP);\n\t}\n}\n\nstatic void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto)\n{\n\tif (hostname)\n\t{\n\t\thostfail_pool *fail_counter;\n\n\t\tfail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);\n\t\tif (fail_counter)\n\t\t{\n\t\t\tHostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);\n\t\t\tDLOG(\"auto hostlist (profile %d) : %s : fail counter reset. website is working.\\n\", dp->n, hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : fail counter reset. website is working.\", hostname, dp->n, client_ip_port, l7proto_str(l7proto));\n\t\t}\n\t}\n}\n\n// return true if retrans trigger fires\nstatic bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold, const char *client_ip_port, t_l7proto l7proto)\n{\n\tif (ctrack && ctrack->dp && ctrack->hostname_ah_check && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP)\n\t{\n\t\tif (l4proto == IPPROTO_TCP)\n\t\t{\n\t\t\tif (!ctrack->req_seq_finalized || ctrack->req_seq_abandoned)\n\t\t\t\treturn false;\n\t\t\tif (!seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))\n\t\t\t{\n\t\t\t\tDLOG(\"req retrans : tcp seq %u not within the req range %u-%u. stop tracking.\\n\", ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end);\n\t\t\t\tctrack_stop_retrans_counter(ctrack);\n\t\t\t\tauto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, l7proto);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tctrack->req_retrans_counter++;\n\t\tif (ctrack->req_retrans_counter >= threshold)\n\t\t{\n\t\t\tDLOG(\"req retrans threshold reached : %u/%u\\n\", ctrack->req_retrans_counter, threshold);\n\t\t\tctrack_stop_retrans_counter(ctrack);\n\t\t\treturn true;\n\t\t}\n\t\tDLOG(\"req retrans counter : %u/%u\\n\", ctrack->req_retrans_counter, threshold);\n\t}\n\treturn false;\n}\nstatic void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto)\n{\n\thostfail_pool *fail_counter;\n\n\tfail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);\n\tif (!fail_counter)\n\t{\n\t\tfail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time);\n\t\tif (!fail_counter)\n\t\t{\n\t\t\tDLOG_ERR(\"HostFailPoolAdd: out of memory\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\tfail_counter->counter++;\n\tDLOG(\"auto hostlist (profile %d) : %s : fail counter %d/%d\\n\", dp->n, hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold);\n\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : fail counter %d/%d\", hostname, dp->n, client_ip_port, l7proto_str(l7proto), fail_counter->counter, dp->hostlist_auto_fail_threshold);\n\tif (fail_counter->counter >= dp->hostlist_auto_fail_threshold)\n\t{\n\t\tDLOG(\"auto hostlist (profile %d) : fail threshold reached. about to add %s to auto hostlist\\n\", dp->n, hostname);\n\t\tHostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);\n\n\t\tDLOG(\"auto hostlist (profile %d) : rechecking %s to avoid duplicates\\n\", dp->n, hostname);\n\t\tbool bExcluded = false;\n\t\tif (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded)\n\t\t{\n\t\t\tDLOG(\"auto hostlist (profile %d) : adding %s to %s\\n\", dp->n, hostname, dp->hostlist_auto->filename);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : adding to %s\", hostname, dp->n, client_ip_port, l7proto_str(l7proto), dp->hostlist_auto->filename);\n\t\t\tif (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"StrPoolAddStr out of memory\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!append_to_list_file(dp->hostlist_auto->filename, hostname))\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"write to auto hostlist\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig))\n\t\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG(\"auto hostlist (profile %d) : NOT adding %s\\n\", dp->n, hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : NOT adding, duplicate detected\", hostname, dp->n, client_ip_port, l7proto_str(l7proto));\n\t\t}\n\t}\n}\n\nstatic void process_retrans_fail(t_ctrack *ctrack, uint8_t proto, const struct sockaddr *client)\n{\n\tchar client_ip_port[48];\n\tif (*params.hostlist_auto_debuglog)\n\t\tntop46_port((struct sockaddr*)client, client_ip_port, sizeof(client_ip_port));\n\telse\n\t\t*client_ip_port = 0;\n\tif (ctrack && ctrack->dp && ctrack->hostname && auto_hostlist_retrans(ctrack, proto, ctrack->dp->hostlist_auto_retrans_threshold, client_ip_port, ctrack->l7proto))\n\t{\n\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : retrans threshold reached\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\tauto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);\n\t}\n}\n\nstatic bool send_delayed(t_ctrack *ctrack)\n{\n\tif (!rawpacket_queue_empty(&ctrack->delayed))\n\t{\n\t\tDLOG(\"SENDING %u delayed packets\\n\", rawpacket_queue_count(&ctrack->delayed));\n\t\treturn rawsend_queue(&ctrack->delayed);\n\t}\n\treturn true;\n}\n\n\nstatic bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)\n{\n\tReasmClear(reasm);\n\tif (sz <= szMax)\n\t{\n\t\tuint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : 0;\n\t\tif (ReasmInit(reasm, sz, seq))\n\t\t{\n\t\t\tReasmFeed(reasm, seq, data_payload, len_payload);\n\t\t\tDLOG(\"starting reassemble. now we have %zu/%zu\\n\", reasm->size_present, reasm->size);\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t\tDLOG(\"reassemble init failed. out of memory\\n\");\n\t}\n\telse\n\t\tDLOG(\"unexpected large payload for reassemble: size=%zu\\n\", sz);\n\treturn false;\n}\nstatic bool reasm_orig_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)\n{\n\treturn reasm_start(ctrack, &ctrack->reasm_orig, proto, sz, szMax, data_payload, len_payload);\n}\nstatic bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, const uint8_t *data_payload, size_t len_payload)\n{\n\tif (ctrack && !ReasmIsEmpty(reasm))\n\t{\n\t\tuint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : (uint32_t)reasm->size_present;\n\t\tif (ReasmFeed(reasm, seq, data_payload, len_payload))\n\t\t{\n\t\t\tDLOG(\"reassemble : feeding data payload size=%zu. now we have %zu/%zu\\n\", len_payload, reasm->size_present, reasm->size);\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tReasmClear(reasm);\n\t\t\tDLOG(\"reassemble session failed\\n\");\n\t\t\tsend_delayed(ctrack);\n\t\t}\n\t}\n\treturn false;\n}\nstatic bool reasm_orig_feed(t_ctrack *ctrack, uint8_t proto, const uint8_t *data_payload, size_t len_payload)\n{\n\treturn reasm_feed(ctrack, &ctrack->reasm_orig, proto, data_payload, len_payload);\n}\nstatic void reasm_orig_stop(t_ctrack *ctrack, const char *dlog_msg)\n{\n\tif (ctrack)\n\t{\n\t\tif (!ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t{\n\t\t\tDLOG(\"%s\", dlog_msg);\n\t\t\tReasmClear(&ctrack->reasm_orig);\n\t\t}\n\t\tsend_delayed(ctrack);\n\t}\n}\nstatic void reasm_orig_cancel(t_ctrack *ctrack)\n{\n\treasm_orig_stop(ctrack, \"reassemble session cancelled\\n\");\n}\nstatic void reasm_orig_fin(t_ctrack *ctrack)\n{\n\treasm_orig_stop(ctrack, \"reassemble session finished\\n\");\n}\n\n\nstatic uint8_t ct_new_postnat_fix(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, const struct tcphdr *tcp)\n{\n#ifdef __linux__\n\t// if used in postnat chain, dropping initial packet will cause conntrack connection teardown\n\t// so we need to workaround this.\n\t// SYN and SYN,ACK checks are for conntrack-less mode\n\tif (ctrack && ctrack->pcounter_orig == 1 || tcp && (tcp_syn_segment(tcp) || tcp_synack_segment(tcp)))\n\t{\n\t\tDLOG(\"applying linux postnat conntrack workaround\\n\");\n\t\t// make ip protocol invalid and low TTL\n\t\tif (ip6)\n\t\t{\n\t\t\tip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = 255;\n\t\t\tip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = 1;\n\t\t}\n\t\tif (ip)\n\t\t{\n\t\t\t// this likely also makes ipv4 header checksum invalid\n\t\t\tip->ip_p = 255;\n\t\t\tip->ip_ttl = 1;\n\t\t}\n\t\treturn VERDICT_MODIFY | VERDICT_NOCSUM;\n\t}\n#endif\n\treturn VERDICT_DROP;\n}\n\n\nstatic bool check_desync_interval(const struct desync_profile *dp, const t_ctrack *ctrack)\n{\n\tif (dp)\n\t{\n\t\tif (dp->desync_start)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (!cutoff_test(ctrack, dp->desync_start, dp->desync_start_mode))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"desync-start not reached (mode %c): %llu/%u . not desyncing\\n\", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_start_mode), dp->desync_start);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"desync-start reached (mode %c): %llu/%u\\n\", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_start_mode), dp->desync_start);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not desyncing. desync-start is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tif (dp->desync_cutoff)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (ctrack->b_desync_cutoff)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"desync-cutoff reached (mode %c): %llu/%u . not desyncing\\n\", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_cutoff_mode), dp->desync_cutoff);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"desync-cutoff not reached (mode %c): %llu/%u\\n\", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->desync_cutoff_mode), dp->desync_cutoff);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not desyncing. desync-cutoff is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\nstatic bool process_desync_interval(const struct desync_profile *dp, t_ctrack *ctrack)\n{\n\tif (check_desync_interval(dp, ctrack))\n\t\treturn true;\n\telse\n\t{\n\t\treasm_orig_cancel(ctrack);\n\t\treturn false;\n\t}\n}\nstatic bool check_dup_interval(const struct desync_profile *dp, const t_ctrack *ctrack)\n{\n\tif (dp)\n\t{\n\t\tif (dp->dup_start)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (!cutoff_test(ctrack, dp->dup_start, dp->dup_start_mode))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"dup-start not reached (mode %c): %llu/%u . not duping\\n\", dp->dup_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_start_mode), dp->dup_start);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"dup-start reached (mode %c): %llu/%u\\n\", dp->dup_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_start_mode), dp->dup_start);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not duping. dup-start is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tif (dp->dup_cutoff)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (ctrack->b_dup_cutoff)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"dup-cutoff reached (mode %c): %llu/%u . not duping\\n\", dp->dup_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_cutoff_mode), dp->dup_cutoff);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"dup-cutoff not reached (mode %c): %llu/%u\\n\", dp->dup_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->dup_cutoff_mode), dp->dup_cutoff);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not duping. dup-cutoff is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\nstatic bool check_orig_mod_interval(const struct desync_profile *dp, const t_ctrack *ctrack)\n{\n\tif (dp)\n\t{\n\t\tif (dp->orig_mod_start)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (!cutoff_test(ctrack, dp->orig_mod_start, dp->orig_mod_start_mode))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"orig-mod-start not reached (mode %c): %llu/%u . not modding original\\n\", dp->orig_mod_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_start_mode), dp->orig_mod_start);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"orig-mod-start reached (mode %c): %llu/%u\\n\", dp->orig_mod_start_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_start_mode), dp->orig_mod_start);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not modding original. orig-mod-start is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tif (dp->orig_mod_cutoff)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (ctrack->b_orig_mod_cutoff)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"orig-mod-cutoff reached (mode %c): %llu/%u . not modding original\\n\", dp->orig_mod_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_cutoff_mode), dp->orig_mod_cutoff);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"orig-mod-cutoff not reached (mode %c): %llu/%u\\n\", dp->orig_mod_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->orig_mod_cutoff_mode), dp->orig_mod_cutoff);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not modding original. orig-mod-cutoff is set but conntrack entry is missing\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic bool replay_queue(struct rawpacket_tailhead *q);\n\nstatic size_t pos_normalize(size_t split_pos, size_t reasm_offset, size_t len_payload)\n{\n\treturn (split_pos > reasm_offset && (split_pos - reasm_offset) < len_payload) ? split_pos - reasm_offset : 0;\n}\n\nstatic uint8_t autottl_guess(autottl *attl, uint8_t hop_count, const char *attl_kind)\n{\n\tif (AUTOTTL_ENABLED(*attl))\n\t{\n\t\tuint8_t autottl = autottl_eval(hop_count, attl);\n\t\tif (autottl)\n\t\t\tDLOG(\"%s autottl: guessed %u\\n\", attl_kind, autottl);\n\t\telse\n\t\t\tDLOG(\"%s autottl: could not guess\\n\", attl_kind);\n\t\treturn autottl;\n\t}\n\telse\n\t\treturn 0;\n}\nstatic void autottl_discover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface)\n{\n\tif (ctrack && params.autottl_present && !ctrack->b_autottl_discovered)\n\t{\n\t\tip_cache_item *ipc = ipcacheTouch(&params.ipcache, a4, a6, iface);\n\t\tif (!ipc)\n\t\t{\n\t\t\tDLOG_ERR(\"ipcache: out of memory\\n\");\n\t\t\treturn;\n\t\t}\n\t\tif (ctrack->incoming_ttl)\n\t\t{\n\t\t\tuint8_t old_hops = ipc->hops;\n\t\t\tipc->hops = hop_count_guess(ctrack->incoming_ttl);\n\t\t\tDLOG(\"incoming hops guessed %u\\n\", ipc->hops);\n\t\t\tif (old_hops != ipc->hops)\n\t\t\t\tDLOG(\"updated autottl cache\\n\");\n\t\t}\n\t\telse if (ipc->hops)\n\t\t\tDLOG(\"using cached hops %u\\n\", ipc->hops);\n\t\telse\n\t\t\tDLOG(\"hop count unknown\\n\");\n\t\tif (ipc->hops)\n\t\t{\n\t\t\tctrack->desync_autottl = autottl_guess(a6 ? &ctrack->dp->desync_autottl6 : &ctrack->dp->desync_autottl, ipc->hops, \"desync\");\n\t\t\tctrack->orig_autottl = autottl_guess(a6 ? &ctrack->dp->orig_autottl6 : &ctrack->dp->orig_autottl, ipc->hops, \"orig\");\n\t\t\tctrack->dup_autottl = autottl_guess(a6 ? &ctrack->dp->dup_autottl6 : &ctrack->dp->dup_autottl, ipc->hops, \"dup\");\n\t\t}\n\t\tctrack->b_autottl_discovered = true;\n\t}\n}\nstatic void autottl_rediscover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface)\n{\n\tif (ctrack)\n\t{\n\t\tctrack->b_autottl_discovered = false;\n\t\tautottl_discover(ctrack, a4, a6, iface);\n\t}\n}\n\nstatic bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip)\n{\n\tif (!params.cache_hostname) return true;\n\n\tip_cache_item *ipc = ipcacheTouch(&params.ipcache, a4, a6, NULL);\n\tif (!ipc)\n\t{\n\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\\n\");\n\t\treturn false;\n\t}\n\tif (!ipc->hostname || strcmp(ipc->hostname, hostname))\n\t{\n\t\tfree(ipc->hostname);\n\t\tif (!(ipc->hostname = strdup(hostname)))\n\t\t{\n\t\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\\n\");\n\t\t\treturn false;\n\t\t}\n\t\tipc->hostname_is_ip = hostname_is_ip;\n\t\tDLOG(\"hostname cached (is_ip=%u): %s\\n\", hostname_is_ip, hostname);\n\t}\n\treturn true;\n}\nstatic bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len, bool *hostname_is_ip)\n{\n\tif (!params.cache_hostname)\n\t{\n\t\t*hostname = 0;\n\t\treturn true;\n\t}\n\tip_cache_item *ipc = ipcacheTouch(&params.ipcache, a4, a6, NULL);\n\tif (!ipc)\n\t{\n\t\tDLOG_ERR(\"ipcache_get_hostname: out of memory\\n\");\n\t\treturn false;\n\t}\n\tif (ipc->hostname)\n\t{\n\t\tDLOG(\"got cached hostname (is_ip=%u): %s\\n\", ipc->hostname_is_ip, ipc->hostname);\n\t\tsnprintf(hostname, hostname_buf_len, \"%s\", ipc->hostname);\n\t\tif (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip;\n\t}\n\telse\n\t\t*hostname = 0;\n\treturn true;\n}\n\n\nstatic uint16_t IP4_IP_ID_FIX(const struct ip *ip, t_ip_id_mode mode)\n{\n\tif (ip)\n\t{\n\t\tswitch(mode)\n\t\t{\n\t\t\tcase IPID_SEQ:\n\t\t\tcase IPID_SEQ_GROUP:\n\t\t\t\treturn ip->ip_id ? ip->ip_id : (uint16_t)random();\n\t\t\tcase IPID_SAME:\n\t\t\t\treturn ip->ip_id;\n\t\t\tcase IPID_RND:\n\t\t\t\treturn (uint16_t)(random()%0xFFFF + 1);\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn 0;\n}\nstatic uint16_t IP4_IP_ID_ADD(uint16_t ip_id, uint16_t inc, t_ip_id_mode mode)\n{\n\tswitch(mode)\n\t{\n\t\tcase IPID_SEQ_GROUP:\n\t\tcase IPID_SEQ:\n\t\t\tif (ip_id)\n\t\t\t{\n\t\t\t\tip_id = net16_add(ip_id, inc);\n\t\t\t\tif (!ip_id) ip_id = net16_add(ip_id, ((int16_t)inc) < 0 ? -1 : 1); // do not allow zero\n\t\t\t}\n\t\tcase IPID_SAME:\n\t\t\treturn ip_id;\n\t\tcase IPID_RND:\n\t\t\treturn (uint16_t)(random()%0xFFFF + 1);;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n#define IP4_IP_ID_NEXT(ip_id,mode) IP4_IP_ID_ADD(ip_id,+1,mode)\n#define IP4_IP_ID_PREV(ip_id,mode) IP4_IP_ID_ADD(ip_id,-1,mode)\n\n\n// fake_mod buffer must at least sizeof(desync_profile->fake_tls)\n// size does not change\n// return : true - altered, false - not altered\nstatic bool runtime_tls_mod(int fake_n, const struct fake_tls_mod_cache *modcache, const struct fake_tls_mod *tls_mod, const uint8_t *fake_data, size_t fake_data_size, const uint8_t *payload, size_t payload_len, uint8_t *fake_mod)\n{\n\tbool b = false;\n\tif (modcache) // it's filled only if it's TLS\n\t{\n\t\tif (tls_mod->mod & FAKE_TLS_MOD_PADENCAP)\n\t\t{\n\t\t\tsize_t sz_rec = pntoh16(fake_data + 3) + payload_len;\n\t\t\tsize_t sz_handshake = pntoh24(fake_data + 6) + payload_len;\n\t\t\tsize_t sz_ext = pntoh16(fake_data + modcache->extlen_offset) + payload_len;\n\t\t\tsize_t sz_pad = pntoh16(fake_data + modcache->padlen_offset) + payload_len;\n\t\t\tif ((sz_rec & ~0xFFFF) || (sz_handshake & ~0xFFFFFF) || (sz_ext & ~0xFFFF) || (sz_pad & ~0xFFFF))\n\t\t\t\tDLOG(\"fake[%d] cannot apply padencap tls mod. length overflow.\\n\", fake_n);\n\t\t\telse\n\t\t\t{\n\t\t\t\tmemcpy(fake_mod, fake_data, fake_data_size);\n\t\t\t\tphton16(fake_mod + 3, (uint16_t)sz_rec);\n\t\t\t\tphton24(fake_mod + 6, (uint32_t)sz_handshake);\n\t\t\t\tphton16(fake_mod + modcache->extlen_offset, (uint16_t)sz_ext);\n\t\t\t\tphton16(fake_mod + modcache->padlen_offset, (uint16_t)sz_pad);\n\t\t\t\tb = true;\n\t\t\t\tDLOG(\"fake[%d] applied padencap tls mod. sizes increased by %zu bytes.\\n\", fake_n, payload_len);\n\t\t\t}\n\t\t}\n\t\tif (tls_mod->mod & FAKE_TLS_MOD_RND)\n\t\t{\n\t\t\tif (!b)\tmemcpy(fake_mod, fake_data, fake_data_size);\n\t\t\tfill_random_bytes(fake_mod + 11, 32); // random\n\t\t\tfill_random_bytes(fake_mod + 44, fake_mod[43]); // session id\n\t\t\tb = true;\n\t\t\tDLOG(\"fake[%d] applied rnd tls mod\\n\", fake_n);\n\t\t}\n\t\tif (tls_mod->mod & FAKE_TLS_MOD_DUP_SID)\n\t\t{\n\t\t\tif (payload_len < 44)\n\t\t\t\tDLOG(\"fake[%d] cannot apply dupsid tls mod. data payload is too short.\\n\", fake_n);\n\t\t\telse if (fake_data[43] != payload[43])\n\t\t\t\tDLOG(\"fake[%d] cannot apply dupsid tls mod. fake and orig session id length mismatch : %u!=%u.\\n\", fake_n, fake_data[43], payload[43]);\n\t\t\telse if (payload_len < (44 + payload[43]))\n\t\t\t\tDLOG(\"fake[%d] cannot apply dupsid tls mod. data payload is not valid.\\n\", fake_n);\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!b)\tmemcpy(fake_mod, fake_data, fake_data_size);\n\t\t\t\tmemcpy(fake_mod + 44, payload + 44, fake_mod[43]); // session id\n\t\t\t\tb = true;\n\t\t\t\tDLOG(\"fake[%d] applied dupsid tls mod\\n\", fake_n);\n\t\t\t}\n\t\t}\n\t}\n\treturn b;\n}\n\nstatic bool rewrite_tcp_flags(uint16_t *flags, uint16_t unset, uint16_t set, const char *what)\n{\n\tif (set || unset)\n\t{\n\t\tuint16_t fl_new = *flags & ~unset | set;\n\t\tif (fl_new!=*flags)\n\t\t{\n\t\t\tDLOG(\"rewrite %s tcp flags 0x%03X => 0x%03X\\n\", what, *flags, fl_new);\n\t\t\t*flags = fl_new;\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic uint8_t orig_mod(const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis)\n{\n\tuint8_t ttl, ttl_orig;\n\tbool bModded = false;\n\n\tif (check_orig_mod_interval(dp, ctrack))\n\t{\n\t\tttl = (ctrack && ctrack->orig_autottl) ? ctrack->orig_autottl : dis->ip6 ? dp->orig_mod_ttl6 : dp->orig_mod_ttl;\n\t\tif (ttl)\n\t\t{\n\t\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\t\t\tif (ttl_orig != ttl)\n\t\t\t{\n\t\t\t\tDLOG(\"rewrite original packet ttl %u => %u\\n\", ttl_orig, ttl);\n\t\t\t\trewrite_ttl(dis->ip, dis->ip6, ttl);\n\t\t\t\tbModded = true;\n\t\t\t}\n\t\t}\n\t\tif (dis->tcp)\n\t\t{\n\t\t\tuint16_t flags = get_tcp_flags(dis->tcp);\n\t\t\tif (rewrite_tcp_flags(&flags, dp->orig_tcp_flags_unset, dp->orig_tcp_flags_set, \"original\"))\n\t\t\t{\n\t\t\t\tapply_tcp_flags(dis->tcp,flags);\n\t\t\t\tbModded = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn bModded;\n}\n\nstatic bool orig_send_rewrite(\n\tuint32_t fwmark, const char *ifout, const struct sockaddr *dst,\n\tuint8_t ttl_orig, uint8_t ttl_fake, const struct desync_profile *dp, const struct dissect *dis)\n{\n\tunsigned int k;\n\n\t// here we avoid heavy ops and preserve exact tcp options structure\n\n\tif (ttl_fake == ttl_orig)\n\t\tDLOG(\"sending %u dups\\n\", dp->dup_repeats);\n\telse\n\t\tDLOG(\"sending %u dups with ttl rewrite %u => %u\\n\", dp->dup_repeats, ttl_orig, ttl_fake);\n\trewrite_ttl(dis->ip, dis->ip6, ttl_fake);\n\n\t// send dups\n\tfor (k = 0; k < dp->dup_repeats; k++)\n\t{\n\t\tif (!rawsend(dst, fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t{\n\t\t\trewrite_ttl(dis->ip, dis->ip6, ttl_orig);\n\t\t\treturn false;\n\t\t}\n\t}\n\trewrite_ttl(dis->ip, dis->ip6, ttl_orig);\n\treturn true;\n}\n\n// return : true - orig was sent completely, false - should send orig another way\nstatic bool tcp_orig_send(uint8_t verdict, uint32_t fwmark, const char *ifout, const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis, bool bForceSend)\n{\n\tif (dp->dup_repeats || bForceSend)\n\t{\n\t\tunsigned int k;\n\t\tuint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100];\n\t\tsize_t len;\n\t\tuint16_t ip_id, nmss;\n\t\tstruct sockaddr_storage src, dst;\n\t\tuint8_t ttl_orig, ttl_dup, scale_factor;\n\t\tuint16_t flags_dup;\n\t\tuint32_t *timestamps;\n\t\tbool sack, DF, bTF;\n\n\t\textract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst);\n\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\n\t\tverdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6);\n\n\t\tif (dp->dup_repeats && check_dup_interval(dp, ctrack))\n\t\t{\n\t\t\tttl_dup = (ctrack && ctrack->dup_autottl) ? ctrack->dup_autottl : (dis->ip6 ? (dp->dup_ttl6 ? dp->dup_ttl6 : ttl_orig) : (dp->dup_ttl ? dp->dup_ttl : ttl_orig));\n\n\t\t\tflags_dup = dis->tcp->th_flags;\n\t\t\tbTF = rewrite_tcp_flags(&flags_dup, dp->dup_tcp_flags_unset, dp->dup_tcp_flags_set, \"dup\");\n\t\t\tif (bTF || dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME))\n\t\t\t{\n\t\t\t\tscale_factor = tcp_find_scale_factor(dis->tcp);\n\t\t\t\ttimestamps = tcp_find_timestamps(dis->tcp);\n\t\t\t\tsack = tcp_has_sack(dis->tcp);\n\t\t\t\tnmss = tcp_find_mss(dis->tcp);\n\t\t\t\tip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\n\t\t\t\tlen = sizeof(pkt);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst,\n\t\t\t\t\tflags_dup, sack, nmss,\n\t\t\t\t\tdis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tip_has_df(dis->ip), ttl_dup, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->dup_fooling_mode, dp->dup_ts_increment, dp->dup_badseq_increment, dp->dup_badseq_ack_increment,\n\t\t\t\t\tdis->data_payload, dis->len_payload, pkt, &len))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"dup: packet reconstruct failed\\n\");\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tDLOG(\"sending %u dups with packet reconstruct. ttl %u => %u\\n\", dp->dup_repeats, ttl_orig, ttl_dup);\n\n\t\t\t\t// send dups\n\t\t\t\tfor (k = 0; k < dp->dup_repeats; k++)\n\t\t\t\t{\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len))\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode);\n\t\t\t\t\tif (dis->ip) ((struct ip*)pkt)->ip_id = ip_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_dup, dp, dis))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (dp->dup_replace)\n\t\t\t\tDLOG(\"NOT sending original because of dup_replace\\n\");\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"sending original ttl %u\\n\", ttl_orig);\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (bForceSend)\n\t\t{\n\t\t\tDLOG(\"sending original ttl %u\\n\", ttl_orig);\n\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n// return : true - orig was sent completely, false - should send orig another way\nstatic bool udp_orig_send(uint8_t verdict, uint32_t fwmark, const char *ifout, const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis, bool bForceSend)\n{\n\tif (dp->dup_repeats || bForceSend)\n\t{\n\t\tunsigned int k;\n\t\tuint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100];\n\t\tsize_t len;\n\t\tuint16_t ip_id;\n\t\tstruct sockaddr_storage src, dst;\n\t\tuint8_t ttl_orig, ttl_fake;\n\n\t\textract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst);\n\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\n\t\tverdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);\n\n\t\tif (dp->dup_repeats && check_dup_interval(dp, ctrack))\n\t\t{\n\t\t\tttl_fake = (ctrack && ctrack->dup_autottl) ? ctrack->dup_autottl : (dis->ip6 ? (dp->dup_ttl6 ? dp->dup_ttl6 : ttl_orig) : (dp->dup_ttl ? dp->dup_ttl : ttl_orig));\n\n\t\t\tif (dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME))\n\t\t\t{\n\t\t\t\tip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\n\t\t\t\tlen = sizeof(pkt);\n\t\t\t\tif (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst,\n\t\t\t\t\tip_has_df(dis->ip), ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->dup_fooling_mode, NULL, 0, 0,\n\t\t\t\t\tdis->data_payload, dis->len_payload, pkt, &len))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"dup: packet reconstruct failed\\n\");\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tDLOG(\"sending %u dups with packet reconstruct. ttl %u => %u\\n\", dp->dup_repeats, ttl_orig, ttl_fake);\n\n\t\t\t\t// send dups\n\t\t\t\tfor (k = 0; k < dp->dup_repeats; k++)\n\t\t\t\t{\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len))\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode);\n\t\t\t\t\tif (dis->ip) ((struct ip*)pkt)->ip_id = ip_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_fake, dp, dis))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (dp->dup_replace)\n\t\t\t\tDLOG(\"NOT sending original because of dup_replace\\n\");\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"sending original ttl %u\\n\", ttl_orig);\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (bForceSend)\n\t\t{\n\t\t\tDLOG(\"sending original ttl %u\\n\", ttl_orig);\n\t\t\tif (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, struct dissect *dis)\n{\n\tuint8_t verdict = VERDICT_PASS;\n\n\t// additional safety check\n\tif (!!dis->ip == !!dis->ip6) return verdict;\n\n\tstruct desync_profile *dp = NULL;\n\n\tt_ctrack *ctrack = NULL, *ctrack_replay = NULL;\n\tbool bReverse = false;\n\tbool bFake = false;\n\n\tstruct sockaddr_storage src, dst;\n\tuint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100];\n\tsize_t pkt1_len, pkt2_len, pkt3_len;\n\tuint8_t ttl_orig, ttl_fake, scale_factor;\n\tuint32_t *timestamps;\n\tbool bSack, DF;\n\tuint16_t nmss;\n\tchar host[256];\n\tconst char *ifname = NULL, *ssid = NULL;\n\n\tuint32_t desync_fwmark = fwmark | params.desync_fwmark;\n\textract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst);\n\ttimestamps = tcp_find_timestamps(dis->tcp);\n\tDF = ip_has_df(dis->ip);\n\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\n\tif (replay)\n\t{\n\t\t// in replay mode conntrack_replay is not NULL and ctrack is NULL\n\n\t\t//ConntrackPoolDump(&params.conntrack);\n\t\tif (!ConntrackPoolDoubleSearch(&params.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, &ctrack_replay, &bReverse) || bReverse)\n\t\t\treturn verdict;\n\n\t\tifname = bReverse ? ifin : ifout;\n#ifdef HAS_FILTER_SSID\n\t\tssid = wlan_ssid_search_ifname(ifname);\n\t\tif (ssid) DLOG(\"found ssid for %s : %s\\n\", ifname, ssid);\n#endif\n\t\tdp = ctrack_replay->dp;\n\t\tif (dp)\n\t\t\tDLOG(\"using cached desync profile %d\\n\", dp->n);\n\t\telse if (!ctrack_replay->dp_search_complete)\n\t\t{\n\t\t\tdp = ctrack_replay->dp = dp_find(&params.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL);\n\t\t\tctrack_replay->dp_search_complete = true;\n\t\t}\n\t\tif (!dp)\n\t\t{\n\t\t\tDLOG(\"matching desync profile not found\\n\");\n\t\t\treturn verdict;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack\n\n\t\tif (!params.ctrack_disable)\n\t\t{\n\t\t\tConntrackPoolPurge(&params.conntrack);\n\t\t\tif (ConntrackPoolFeed(&params.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, dis->len_payload, &ctrack, &bReverse))\n\t\t\t{\n\t\t\t\tdp = ctrack->dp;\n\t\t\t\tctrack_replay = ctrack;\n\t\t\t}\n\t\t}\n\t\tifname = bReverse ? ifin : ifout;\n#ifdef HAS_FILTER_SSID\n\t\tssid = wlan_ssid_search_ifname(ifname);\n\t\tif (ssid) DLOG(\"found ssid for %s : %s\\n\", ifname, ssid);\n#endif\n\t\tif (dp)\n\t\t\tDLOG(\"using cached desync profile %d\\n\", dp->n);\n\t\telse if (!ctrack || !ctrack->dp_search_complete)\n\t\t{\n\t\t\tconst char *hostname = NULL;\n\t\t\tbool hostname_is_ip = false;\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\thostname = ctrack->hostname;\n\t\t\t\thostname_is_ip = ctrack->hostname_is_ip;\n\t\t\t\tif (!hostname && !bReverse)\n\t\t\t\t{\n\t\t\t\t\tif (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host)\n\t\t\t\t\t\tif (!(hostname = ctrack_replay->hostname = strdup(host)))\n\t\t\t\t\t\t\tDLOG_ERR(\"strdup(host): out of memory\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tdp = dp_find(&params.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL);\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tctrack->dp = dp;\n\t\t\t\tctrack->dp_search_complete = true;\n\t\t\t}\n\t\t}\n\t\tif (!dp)\n\t\t{\n\t\t\tDLOG(\"matching desync profile not found\\n\");\n\t\t\treturn verdict;\n\t\t}\n\t\tmaybe_cutoff(ctrack, IPPROTO_TCP);\n\n\t\tHostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters);\n\n\t\t//ConntrackPoolDump(&params.conntrack);\n\n\t\tif (tcp_synack_segment(dis->tcp))\n\t\t{\n\t\t\tif (dp->wsize)\n\t\t\t{\n\t\t\t\ttcp_rewrite_winsize(dis->tcp, dp->wsize, dp->wscale);\n\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t}\n\t\t\tif (dp->synack_split == SS_SYN)\n\t\t\t{\n\t\t\t\tDLOG(\"split SYNACK : clearing ACK bit\\n\");\n\t\t\t\tdis->tcp->th_flags &= ~TH_ACK;\n\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t}\n\t\t}\n\n\t\tif (bReverse)\n\t\t{\n\t\t\tif (ctrack && !ctrack->incoming_ttl)\n\t\t\t{\n\t\t\t\tctrack->incoming_ttl = ttl_orig;\n\t\t\t\tDLOG(\"incoming TTL %u\\n\", ttl_orig);\n\t\t\t\tautottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin);\n\t\t\t}\n\n\t\t\t// process reply packets for auto hostlist mode\n\t\t\t// by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked\n\t\t\t// we only process first-sequence replies. do not react to subsequent redirects or RSTs\n\t\t\tif (ctrack && ctrack->hostname && ctrack->hostname_ah_check && (ctrack->ack_last - ctrack->ack0) == 1)\n\t\t\t{\n\t\t\t\tbool bFail = false;\n\n\t\t\t\tchar client_ip_port[48];\n\t\t\t\tif (*params.hostlist_auto_debuglog)\n\t\t\t\t\tntop46_port((struct sockaddr*)&dst, client_ip_port, sizeof(client_ip_port));\n\t\t\t\telse\n\t\t\t\t\t*client_ip_port = 0;\n\n\t\t\t\tif (dis->tcp->th_flags & TH_RST)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"incoming RST detected for hostname %s\\n\", ctrack->hostname);\n\t\t\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : incoming RST\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\t\t\t\tbFail = true;\n\t\t\t\t}\n\t\t\t\telse if (dis->len_payload && ctrack->l7proto == HTTP)\n\t\t\t\t{\n\t\t\t\t\tif (IsHttpReply(dis->data_payload, dis->len_payload))\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"incoming HTTP reply detected for hostname %s\\n\", ctrack->hostname);\n\t\t\t\t\t\tbFail = HttpReplyLooksLikeDPIRedirect(dis->data_payload, dis->len_payload, ctrack->hostname);\n\t\t\t\t\t\tif (bFail)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDLOG(\"redirect to another domain detected. possibly DPI redirect.\\n\");\n\t\t\t\t\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : redirect to another domain\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tDLOG(\"local or in-domain redirect detected. it's not a DPI redirect.\\n\");\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// received not http reply. do not monitor this connection anymore\n\t\t\t\t\t\tDLOG(\"incoming unknown HTTP data detected for hostname %s\\n\", ctrack->hostname);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (bFail)\n\t\t\t\t\tauto_hostlist_failed(dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);\n\t\t\t\telse\n\t\t\t\t\tif (dis->len_payload)\n\t\t\t\t\t\tauto_hostlist_reset_fail_counter(dp, ctrack->hostname, client_ip_port, ctrack->l7proto);\n\t\t\t\tif (dis->tcp->th_flags & TH_RST)\n\t\t\t\t\tConntrackClearHostname(ctrack); // do not react to further dup RSTs\n\t\t\t}\n\n\t\t\treturn verdict; // nothing to do. do not waste cpu\n\t\t}\n\n\t\tautottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout);\n\n\t\tif (orig_mod(dp, ctrack, dis)) // ttl can change !\n\t\t\tverdict = VERDICT_MODIFY;\n\n\t\tif (dp->wssize)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (ctrack->b_wssize_cutoff)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"wssize-cutoff reached (mode %c): %llu/%u . not changing wssize.\\n\", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->wssize_cutoff_mode), dp->wssize_cutoff);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (dp->wssize_cutoff) DLOG(\"wssize-cutoff not reached (mode %c): %llu/%u\\n\", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack, dp->wssize_cutoff_mode), dp->wssize_cutoff);\n\t\t\t\t\ttcp_rewrite_winsize(dis->tcp, dp->wssize, dp->wsscale);\n\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"not changing wssize. wssize is set but conntrack entry is missing\\n\");\n\t\t\t}\n\t\t}\n\n\t\tif ((dp->synack_split == SS_SYNACK || dp->synack_split == SS_ACKSYN) && tcp_synack_segment(dis->tcp))\n\t\t{\n\t\t\t// reconstruct required\n\n\t\t\tdis->tcp->th_flags &= ~TH_ACK;\n\t\t\ttcp_fix_checksum(dis->tcp, dis->transport_len, dis->ip, dis->ip6);\n\n\t\t\tchar ss[2], i;\n\t\t\tif (dp->synack_split == SS_SYNACK)\n\t\t\t{\n\t\t\t\tss[0] = 'S';\n\t\t\t\tss[1] = 'A';\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tss[0] = 'A';\n\t\t\t\tss[1] = 'S';\n\t\t\t}\n\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_ACK, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, SCALE_NONE, timestamps,\n\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode), IP6_FLOW(dis->ip6),\n\t\t\t\tFOOL_NONE, 0, 0, 0, NULL, 0, pkt1, &pkt1_len))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"cannot prepare split SYNACK ACK part\\n\");\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tfor (int i = 0; i < 2; i++)\n\t\t\t{\n\t\t\t\tswitch (ss[i])\n\t\t\t\t{\n\t\t\t\tcase 'S':\n\t\t\t\t\tDLOG(\"split SYNACK : SYN\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'A':\n\t\t\t\t\tDLOG(\"split SYNACK : ACK\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn VERDICT_DROP;\n\t\t}\n\n\t\t// start and cutoff limiters\n\t\tif (!process_desync_interval(dp, ctrack)) goto send_orig;\n\t} // !replay\n\n\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\tttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig));\n\tuint16_t flags_orig = get_tcp_flags(dis->tcp);\n\tscale_factor = tcp_find_scale_factor(dis->tcp);\n\tbSack = tcp_has_sack(dis->tcp);\n\tnmss = tcp_find_mss(dis->tcp);\n\n\tuint16_t ip_id=0;\n\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\tif (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\n\tif (!replay)\n\t{\n\t\tif (tcp_syn_segment(dis->tcp))\n\t\t{\n\t\t\tswitch (dp->desync_mode0)\n\t\t\t{\n\t\t\tcase DESYNC_SYNACK:\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_SYN | TH_ACK, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tNULL, 0, pkt1, &pkt1_len))\n\t\t\t\t{\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tDLOG(\"sending fake SYNACK\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tbreak;\n\t\t\tcase DESYNC_SYNDATA:\n\t\t\t\t// make sure we are not breaking TCP fast open\n\t\t\t\tif (tcp_has_fastopen(dis->tcp))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"received SYN with TCP fast open option. syndata desync is not applied.\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (dis->len_payload)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"received SYN with data payload. syndata desync is not applied.\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, bSack, nmss, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t0, 0, 0, 0, dp->fake_syndata, dp->fake_syndata_size, pkt1, &pkt1_len))\n\t\t\t\t{\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending SYN with fake data : \");\n\t\t\t\thexdump_limited_dlog(dp->fake_syndata, dp->fake_syndata_size, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tverdict = VERDICT_DROP;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// can do nothing else with SYN packet\n\t\t\tgoto send_orig;\n\t\t}\n\n\t} // !replay\n\n\tif (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload)\n\t{\n\t\tstruct blob_collection_head *fake;\n\n\t\tuint8_t *p, *phost = NULL;\n\t\tconst uint8_t *rdata_payload = dis->data_payload;\n\t\tsize_t rlen_payload = dis->len_payload;\n\t\tsize_t split_pos, seqovl_pos;\n\t\tsize_t multisplit_pos[MAX_SPLITS];\n\t\tint multisplit_count;\n\t\tint i;\n\n\t\tbool bHaveHost = false, bHostIsIp = false;\n\t\tt_l7proto l7proto = UNKNOWN;\n\n\t\tif (replay)\n\t\t{\n\t\t\trdata_payload = ctrack_replay->reasm_orig.packet;\n\t\t\trlen_payload = ctrack_replay->reasm_orig.size_present;\n\t\t}\n\t\telse if (reasm_orig_feed(ctrack, IPPROTO_TCP, dis->data_payload, dis->len_payload))\n\t\t{\n\t\t\trdata_payload = ctrack->reasm_orig.packet;\n\t\t\trlen_payload = ctrack->reasm_orig.size_present;\n\t\t}\n\n\t\tprocess_retrans_fail(ctrack, IPPROTO_TCP, (struct sockaddr*)&src);\n\n\t\tif (IsHttp(rdata_payload, rlen_payload))\n\t\t{\n\t\t\tDLOG(\"packet contains HTTP request\\n\");\n\t\t\tl7proto = HTTP;\n\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\n\t\t\t// we do not reassemble http\n\t\t\treasm_orig_cancel(ctrack);\n\t\t\tif (!dp->wssize_no_forced_cutoff) forced_wssize_cutoff(ctrack);\n\n\t\t\tbHaveHost = HttpExtractHost(rdata_payload, rlen_payload, host, sizeof(host));\n\t\t\tif (!bHaveHost)\n\t\t\t{\n\t\t\t\tDLOG(\"not applying tampering to HTTP without Host:\\n\");\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\t// we do not reassemble http\n\t\t\t\tif (!ctrack->req_seq_present)\n\t\t\t\t{\n\t\t\t\t\tctrack->req_seq_start = ctrack->seq_last;\n\t\t\t\t\tctrack->req_seq_end = ctrack->pos_orig - 1;\n\t\t\t\t\tctrack->req_seq_present = ctrack->req_seq_finalized = true;\n\t\t\t\t\tDLOG(\"req retrans : tcp seq interval %u-%u\\n\", ctrack->req_seq_start, ctrack->req_seq_end);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (IsTLSClientHello(rdata_payload, rlen_payload, TLS_PARTIALS_ENABLE))\n\t\t{\n\t\t\tbool bReqFull = IsTLSRecordFull(rdata_payload, rlen_payload);\n\t\t\tDLOG(bReqFull ? \"packet contains full TLS ClientHello\\n\" : \"packet contains partial TLS ClientHello\\n\");\n\t\t\tl7proto = TLS;\n\n\t\t\tif (bReqFull) TLSDebug(rdata_payload, rlen_payload);\n\n\t\t\tbHaveHost = TLSHelloExtractHost(rdata_payload, rlen_payload, host, sizeof(host), TLS_PARTIALS_ENABLE);\n\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tif (!ctrack->l7proto) ctrack->l7proto = l7proto;\n\t\t\t\t// do not reasm retransmissions\n\t\t\t\tif (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !ctrack->req_seq_abandoned &&\n\t\t\t\t\t!(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end)))\n\t\t\t\t{\n\t\t\t\t\t// do not reconstruct unexpected large payload (they are feeding garbage ?)\n\t\t\t\t\tif (!reasm_orig_start(ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload))\n\t\t\t\t\t{\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!ctrack->req_seq_finalized)\n\t\t\t\t{\n\t\t\t\t\tif (!ctrack->req_seq_present)\n\t\t\t\t\t{\n\t\t\t\t\t\t// lower bound of request seq interval\n\t\t\t\t\t\tctrack->req_seq_start = ctrack->seq_last;\n\t\t\t\t\t\tctrack->req_seq_present = true;\n\t\t\t\t\t}\n\t\t\t\t\t// upper bound of request seq interval\n\t\t\t\t\t// it can grow on every packet until request is complete. then interval is finalized and never touched again.\n\t\t\t\t\tctrack->req_seq_end = ctrack->pos_orig - 1;\n\t\t\t\t\tDLOG(\"req retrans : seq interval %u-%u\\n\", ctrack->req_seq_start, ctrack->req_seq_end);\n\t\t\t\t\tctrack->req_seq_finalized |= bReqFull;\n\t\t\t\t}\n\t\t\t\tif (!dp->wssize_no_forced_cutoff && (bReqFull || ReasmIsEmpty(&ctrack->reasm_orig))) forced_wssize_cutoff(ctrack);\n\n\t\t\t\tif (!ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t\t\t{\n\t\t\t\t\tverdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6);\n\t\t\t\t\tif (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload))\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"DELAY desync until reasm is complete (#%u)\\n\", rawpacket_queue_count(&ctrack->delayed));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG_ERR(\"rawpacket_queue failed !\\n\");\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tif (ReasmIsFull(&ctrack->reasm_orig))\n\t\t\t\t\t{\n\t\t\t\t\t\treplay_queue(&ctrack->delayed);\n\t\t\t\t\t\treasm_orig_fin(ctrack);\n\t\t\t\t\t}\n\t\t\t\t\treturn VERDICT_DROP;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (dp->desync_skip_nosni && !bHaveHost)\n\t\t\t{\n\t\t\t\tDLOG(\"not applying tampering to TLS ClientHello without hostname in the SNI\\n\");\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t}\n\n\t\tif (ctrack && ctrack->req_seq_finalized)\n\t\t{\n\t\t\tuint32_t dseq = ctrack->seq_last - ctrack->req_seq_end;\n\t\t\t// do not react to 32-bit overflowed sequence numbers. allow 16 Mb grace window then cutoff.\n\t\t\tif (dseq >= 0x1000000 && !(dseq & 0x80000000)) ctrack->req_seq_abandoned = true;\n\t\t}\n\n\t\tif (bHaveHost)\n\t\t{\n\t\t\tbHostIsIp = strip_host_to_ip(host);\n\t\t\tDLOG(\"hostname: %s\\n\", host);\n\t\t}\n\n\t\tbool bDiscoveredL7;\n\t\tif (ctrack_replay)\n\t\t{\n\t\t\tif ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN))\n\t\t\t\tctrack_replay->l7proto_discovered = true;\n\t\t}\n\t\telse\n\t\t\tbDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN;\n\t\tif (bDiscoveredL7) DLOG(\"discovered l7 protocol\\n\");\n\n\t\tbool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered);\n\t\tif (bDiscoveredHostname)\n\t\t{\n\t\t\tDLOG(\"discovered hostname\\n\");\n\t\t\tif (ctrack_replay)\n\t\t\t{\n\t\t\t\tfree(ctrack_replay->hostname);\n\t\t\t\tctrack_replay->hostname = strdup(host);\n\t\t\t\tctrack_replay->hostname_is_ip = bHostIsIp;\n\t\t\t\tif (!ctrack_replay->hostname)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"hostname dup : out of memory\");\n\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tctrack_replay->hostname_discovered = true;\n\t\t\t\tif (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp))\n\t\t\t\t{\n\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tbool bCheckDone = false, bCheckResult = false, bCheckExcluded = false;\n\t\tif (bDiscoveredL7 || bDiscoveredHostname)\n\t\t{\n\t\t\tstruct desync_profile *dp_prev = dp;\n\n\t\t\tdp = dp_find(&params.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst,\n\t\t\t\tctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL,\n\t\t\t\tctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp,\n\t\t\t\tctrack_replay ? ctrack_replay->l7proto : l7proto, ssid,\n\t\t\t\t&bCheckDone, &bCheckResult, &bCheckExcluded);\n\t\t\tif (ctrack_replay)\n\t\t\t{\n\t\t\t\tctrack_replay->dp = dp;\n\t\t\t\tctrack_replay->dp_search_complete = true;\n\t\t\t\tctrack_replay->bCheckDone = bCheckDone;\n\t\t\t\tctrack_replay->bCheckResult = bCheckResult;\n\t\t\t\tctrack_replay->bCheckExcluded = bCheckExcluded;\n\t\t\t}\n\t\t\tif (!dp)\n\t\t\t{\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tif (dp != dp_prev)\n\t\t\t{\n\t\t\t\tDLOG(\"desync profile changed by revealed l7 protocol or hostname !\\n\");\n\t\t\t\tautottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout);\n\t\t\t\tip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\t\t\t\t// re-evaluate start/cutoff limiters\n\t\t\t\tif (replay)\n\t\t\t\t{\n\t\t\t\t\tif (orig_mod(dp, ctrack_replay, dis)) // ttl can change !\n\t\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmaybe_cutoff(ctrack, IPPROTO_TCP);\n\t\t\t\t\tif (orig_mod(dp, ctrack, dis)) // ttl can change !\n\t\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t\tif (!process_desync_interval(dp, ctrack))\n\t\t\t\t\t{\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\t\t\t\tttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig));\n\t\t\t}\n\t\t}\n\t\telse if (ctrack_replay)\n\t\t{\n\t\t\tbCheckDone = ctrack_replay->bCheckDone;\n\t\t\tbCheckResult = ctrack_replay->bCheckResult;\n\t\t\tbCheckExcluded = ctrack_replay->bCheckExcluded;\n\t\t}\n\n\t\tif (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp))\n\t\t{\n\t\t\tif (!bCheckDone)\n\t\t\t\tbCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false);\n\t\t\tif (bCheckResult)\n\t\t\t\tctrack_stop_retrans_counter(ctrack_replay);\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (ctrack_replay)\n\t\t\t\t{\n\t\t\t\t\tctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded;\n\t\t\t\t\tif (!ctrack_replay->hostname_ah_check)\n\t\t\t\t\t\tctrack_stop_retrans_counter(ctrack_replay);\n\t\t\t\t}\n\t\t\t\tDLOG(\"not applying tampering to this request\\n\");\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t}\n\n\t\tif (l7proto == UNKNOWN)\n\t\t{\n\t\t\tif (!dp->desync_any_proto)\n\t\t\t{\n\t\t\t\tDLOG(\"not applying tampering to unknown protocol\\n\");\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tDLOG(\"applying tampering to unknown protocol\\n\");\n\t\t}\n\n\t\tif ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase || dp->methodeol) && HttpFindHost(&phost, dis->data_payload, dis->len_payload))\n\t\t{\n\t\t\tif (dp->hostcase)\n\t\t\t{\n\t\t\t\tDLOG(\"modifying Host: => %c%c%c%c:\\n\", dp->hostspell[0], dp->hostspell[1], dp->hostspell[2], dp->hostspell[3]);\n\t\t\t\tmemcpy(phost, dp->hostspell, 4);\n\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t}\n\t\t\tif (dp->domcase)\n\t\t\t{\n\t\t\t\tDLOG(\"mixing domain case\\n\");\n\t\t\t\tfor (p = phost + 5; p < (dis->data_payload + dis->len_payload) && *p != '\\r' && *p != '\\n'; p++)\n\t\t\t\t\t*p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p);\n\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t}\n\t\t\tuint8_t *pua;\n\t\t\tif (dp->hostnospace)\n\t\t\t{\n\t\t\t\tif ((pua = (uint8_t*)memmem(dis->data_payload, dis->len_payload, \"\\r\\nUser-Agent: \", 14)) &&\n\t\t\t\t\t(pua = (uint8_t*)memmem(pua + 1, dis->len_payload - (pua - dis->data_payload) - 1, \"\\r\\n\", 2)))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"removing space after Host: and adding it to User-Agent:\\n\");\n\t\t\t\t\tif (pua > phost)\n\t\t\t\t\t{\n\t\t\t\t\t\tmemmove(phost + 5, phost + 6, pua - phost - 6);\n\t\t\t\t\t\tpua[-1] = ' ';\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tmemmove(pua + 1, pua, phost - pua + 5);\n\t\t\t\t\t\t*pua = ' ';\n\t\t\t\t\t}\n\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tDLOG(\"cannot do hostnospace because valid User-Agent: not found\\n\");\n\t\t\t}\n\t\t\telse if (dp->methodeol)\n\t\t\t{\n\t\t\t\tif (phost[5] == ' ' || phost[5] == '\\t')\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"removing space after Host: and adding '\\\\n' before method\\n\");\n\t\t\t\t\tmemmove(dis->data_payload + 1, dis->data_payload, phost - dis->data_payload + 5);\n\t\t\t\t\tdis->data_payload[0] = '\\n';\n\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tDLOG(\"cannot do methodeol because there's no space or tab after Host:\\n\");\n\t\t\t}\n\n\t\t}\n\n\t\tif (dp->desync_mode == DESYNC_NONE)\n\t\t{\n\t\t\treasm_orig_cancel(ctrack);\n\t\t\tgoto send_orig;\n\t\t}\n\n\t\tif (params.debug)\n\t\t{\n\t\t\tchar s1[48], s2[48];\n\t\t\tntop46_port((struct sockaddr *)&src, s1, sizeof(s1));\n\t\t\tntop46_port((struct sockaddr *)&dst, s2, sizeof(s2));\n\t\t\tDLOG(\"dpi desync src=%s dst=%s\\n\", s1, s2);\n\t\t}\n\n\t\tswitch (l7proto)\n\t\t{\n\t\tcase HTTP:\n\t\t\tfake = &dp->fake_http;\n\t\t\tbreak;\n\t\tcase TLS:\n\t\t\tfake = &dp->fake_tls;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfake = &dp->fake_unknown;\n\t\t\tbreak;\n\t\t}\n\t\tif (dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER)\n\t\t{\n\t\t\tsplit_pos = 0;\n\t\t\tResolveMultiPos(rdata_payload, rlen_payload, l7proto, dp->splits, dp->split_count, multisplit_pos, &multisplit_count);\n\t\t\tif (params.debug)\n\t\t\t{\n\t\t\t\tif (multisplit_count)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"multisplit pos: \");\n\t\t\t\t\tfor (i = 0; i < multisplit_count; i++) DLOG(\"%zu \", multisplit_pos[i]);\n\t\t\t\t\tDLOG(\"\\n\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tDLOG(\"all multisplit pos are outside of this packet\\n\");\n\t\t\t}\n\t\t\tif (multisplit_count)\n\t\t\t{\n\t\t\t\tint j;\n\t\t\t\tfor (i = j = 0; i < multisplit_count; i++)\n\t\t\t\t{\n\t\t\t\t\tmultisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload);\n\t\t\t\t\tif (multisplit_pos[j]) j++;\n\t\t\t\t}\n\t\t\t\tmultisplit_count = j;\n\t\t\t\tif (params.debug)\n\t\t\t\t{\n\t\t\t\t\tif (multisplit_count)\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"normalized multisplit pos: \");\n\t\t\t\t\t\tfor (i = 0; i < multisplit_count; i++) DLOG(\"%zu \", multisplit_pos[i]);\n\t\t\t\t\t\tDLOG(\"\\n\");\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tDLOG(\"all multisplit pos are outside of this packet\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER)\n\t\t{\n\t\t\tmultisplit_count = 0;\n\t\t\t// first look for non-abs split\n\t\t\tfor (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++)\n\t\t\t\tif (dp->splits[i].marker != PM_ABS)\n\t\t\t\t\tsplit_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i);\n\t\t\t// second look for abs split\n\t\t\tif (!split_pos)\n\t\t\t\tfor (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++)\n\t\t\t\t\tif (dp->splits[i].marker == PM_ABS)\n\t\t\t\t\t\tsplit_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i);\n\t\t\tif (!split_pos) split_pos = 1;\n\t\t\tDLOG(\"regular split pos: %zu\\n\", split_pos);\n\t\t\tif (!split_pos || split_pos > rlen_payload) split_pos = 1;\n\t\t\tsplit_pos = pos_normalize(split_pos, reasm_offset, dis->len_payload);\n\t\t\tif (split_pos)\n\t\t\t\tDLOG(\"normalized regular split pos : %zu\\n\", split_pos);\n\t\t\telse\n\t\t\t\tDLOG(\"regular split pos is outside of this packet\\n\");\n\t\t}\n\t\telse if (dp->desync_mode == DESYNC_HOSTFAKESPLIT || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT)\n\t\t{\n\t\t\tstruct proto_pos splits[2] = {\n\t\t\t\t{.marker = PM_HOST,.pos = 0},\n\t\t\t\t{.marker = PM_HOST_END,.pos = 0}\n\t\t\t};\n\t\t\tsplit_pos = 0;\n\t\t\tResolveMultiPos(rdata_payload, rlen_payload, l7proto, splits, 2, multisplit_pos, &multisplit_count);\n\t\t\tif (multisplit_count != 2)\n\t\t\t{\n\t\t\t\tDLOG(\"hostfakesplit: host and endhost positions not found\\n\");\n\t\t\t\tmultisplit_count = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint j;\n\t\t\t\tfor (i = j = 0; i < multisplit_count; i++)\n\t\t\t\t{\n\t\t\t\t\tmultisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload);\n\t\t\t\t\tif (multisplit_pos[j]) j++;\n\t\t\t\t}\n\t\t\t\tmultisplit_count = j;\n\t\t\t\tif (multisplit_count != 2)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"hostfakesplit: host or endhost are outside of this packet\\n\");\n\t\t\t\t\tmultisplit_count = 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"normalized hostfakesplit pos: \");\n\t\t\t\t\tfor (i = 0; i < multisplit_count; i++) DLOG(\"%zu \", multisplit_pos[i]);\n\t\t\t\t\tDLOG(\"\\n\");\n\t\t\t\t\tmultisplit_pos[2] = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->hostfakesplit_midhost);\n\t\t\t\t\tif (multisplit_pos[2])\n\t\t\t\t\t{\n\t\t\t\t\t\tmultisplit_pos[2] = pos_normalize(multisplit_pos[2], reasm_offset, dis->len_payload);\n\t\t\t\t\t\tif (multisplit_pos[2] > multisplit_pos[0] && multisplit_pos[2] < multisplit_pos[1])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDLOG(\"normalized hostfakesplit midhost pos: %zu\\n\", multisplit_pos[2]);\n\t\t\t\t\t\t\tmultisplit_count++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmultisplit_count = 0;\n\t\t\tsplit_pos = 0;\n\t\t}\n\t\tif (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_MULTISPLIT)\n\t\t{\n\t\t\t// split seqovl only uses absolute positive values\n\t\t\tseqovl_pos = (dp->seqovl.marker == PM_ABS && dp->seqovl.pos > 0) ? dp->seqovl.pos : 0;\n\t\t\tif (seqovl_pos)\tDLOG(\"seqovl : %zu\\n\", seqovl_pos);\n\t\t}\n\t\telse if (dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_MULTIDISORDER)\n\t\t{\n\t\t\tseqovl_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->seqovl);\n\t\t\tseqovl_pos = pos_normalize(seqovl_pos, reasm_offset, dis->len_payload);\n\t\t\tif (seqovl_pos)\tDLOG(\"normalized seqovl : %zu\\n\", seqovl_pos);\n\t\t}\n\t\telse\n\t\t\tseqovl_pos = 0;\n\n\t\tuint32_t fooling_orig = FOOL_NONE;\n\n\t\tuint16_t flags_fake = flags_orig;\n\t\trewrite_tcp_flags(&flags_fake, dp->desync_tcp_flags_unset, dp->desync_tcp_flags_set, \"desync\");\n\n\t\tswitch (dp->desync_mode)\n\t\t{\n\t\tcase DESYNC_FAKE_KNOWN:\n\t\t\tif (reasm_offset) break;\n\t\t\tif (l7proto == UNKNOWN)\n\t\t\t{\n\t\t\t\tDLOG(\"not applying fake because of unknown protocol\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase DESYNC_FAKE:\n\t\t\tif (reasm_offset) break;\n\n\t\t\t{\n\t\t\t\tstruct blob_item *fake_item;\n\t\t\t\tsize_t fake_size;\n\t\t\t\tuint8_t *fake_data;\n\t\t\t\tuint8_t fake_data_buf[FAKE_MAX_TCP];\n\t\t\t\tint n = 0;\n\t\t\t\tuint32_t sequence, sequence0;\n\n\t\t\t\tsequence = sequence0 = ntohl(dis->tcp->th_seq);\n\t\t\t\tLIST_FOREACH(fake_item, fake, next)\n\t\t\t\t{\n\t\t\t\t\tn++;\n\t\t\t\t\tswitch (l7proto)\n\t\t\t\t\t{\n\t\t\t\t\tcase TLS:\n\t\t\t\t\t\tif ((fake_item->size <= sizeof(fake_data_buf)) &&\n\t\t\t\t\t\t\truntime_tls_mod(n, (struct fake_tls_mod_cache *)fake_item->extra, (struct fake_tls_mod *)fake_item->extra2, fake_item->data, fake_item->size, rdata_payload, rlen_payload, fake_data_buf))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfake_data = fake_data_buf;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tfake_data = fake_item->data;\n\t\t\t\t\t}\n\t\t\t\t\tfake_data += fake_item->offset;\n\t\t\t\t\tfake_size = fake_item->size - fake_item->offset;\n\n\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, htonl(sequence), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\t\tfake_data, fake_size, pkt1, &pkt1_len))\n\t\t\t\t\t{\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending fake[%d] seq=+%u : \", n, sequence - sequence0);\n\t\t\t\t\thexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t{\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\tif (dp->tcp_mod.seq) sequence += fake_size;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbFake = true;\n\t\t\tbreak;\n\t\tcase DESYNC_RST:\n\t\tcase DESYNC_RSTACK:\n\t\t\tif (reasm_offset) break;\n\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_RST | (dp->desync_mode == DESYNC_RSTACK ? TH_ACK : 0), false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\tNULL, 0, pkt1, &pkt1_len))\n\t\t\t{\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tDLOG(\"sending fake RST/RSTACK\\n\");\n\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t{\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\tbFake = true;\n\t\t\tbreak;\n\t\tcase DESYNC_HOPBYHOP:\n\t\tcase DESYNC_DESTOPT:\n\t\tcase DESYNC_IPFRAG1:\n\t\t\tfooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1;\n\t\t\tif (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_tcp(dp->desync_mode2) ||\n\t\t\t\t(!split_pos && (dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER)) ||\n\t\t\t\t(multisplit_count < 2 && dp->desync_mode2 == DESYNC_HOSTFAKESPLIT) ||\n\t\t\t\t(!multisplit_count && (dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER))))\n\t\t\t{\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\trdata_payload = NULL;\n\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\tdis->data_payload, dis->len_payload, pkt1, &pkt1_len))\n\t\t\t\t{\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tDLOG(\"resending original packet with extension header\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t// this mode is final, no other options available\n\t\t\t\treturn VERDICT_DROP;\n\t\t\t}\n\t\tdefault:\n\t\t\tpkt1_len = 0;\n\t\t\tbreak;\n\t\t}\n\n\t\t// we do not need reasm buffer anymore\n\t\treasm_orig_cancel(ctrack);\n\t\trdata_payload = NULL;\n\n\t\tenum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2;\n\t\tswitch (desync_mode)\n\t\t{\n\t\tcase DESYNC_HOSTFAKESPLIT:\n\t\t\t// can be 2 or 3 split pos\n\t\t\t// if 2 split pos : host, endhost\n\t\t\t// if 3 split pos : host, endhost, midhost\n\t\t\tif (multisplit_count >= 2)\n\t\t\t{\n\t\t\t\tuint8_t *seg;\n\t\t\t\tsize_t seg_len, host_size, pos_host, pos_endhost, pos_split_host, sz;\n\t\t\t\tuint8_t *fakehost;\n\t\t\t\tuint16_t ip_id_after_host;\n\n\t\t\t\tseg = dis->data_payload;\n\t\t\t\tseg_len = dis->len_payload;\n\t\t\t\tpos_host = multisplit_pos[0];\n\t\t\t\tpos_endhost = multisplit_pos[1];\n\t\t\t\tpos_split_host = multisplit_count >= 3 ? multisplit_pos[2] : 0;\n\t\t\t\thost_size = pos_endhost - pos_host;\n\n\t\t\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\n\t\t\t\t// before_host segment\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\tdis->tcp->th_seq, dis->tcp->th_ack,\n\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\tseg, pos_host, pkt1, &pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending hostfakesplit before_host part 0-%zu len=%zu : \", pos_host - 1, pos_host);\n\t\t\t\thexdump_limited_dlog(seg, pos_host, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tif (!(fakehost = malloc(host_size + 1)))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"fakehost out of memory\\n\");\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tif (*dp->hfs_mod.host)\n\t\t\t\t{\n\t\t\t\t\tif (host_size <= dp->hfs_mod.host_size)\n\t\t\t\t\t{\n\t\t\t\t\t\t// \"google.com\" => \"gle.com\"\n\t\t\t\t\t\t// \"google.com\" => \"google.com\"\n\t\t\t\t\t\tmemcpy(fakehost, dp->hfs_mod.host + dp->hfs_mod.host_size - host_size, host_size);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// \"google.com\" => \"nb4auv9.google.com\"\n\t\t\t\t\t\t// \"google.com\" => \".google.com\"\n\t\t\t\t\t\tsz = host_size - dp->hfs_mod.host_size;\n\t\t\t\t\t\tmemcpy(fakehost + sz, dp->hfs_mod.host, dp->hfs_mod.host_size);\n\t\t\t\t\t\tfakehost[--sz] = '.';\n\t\t\t\t\t\tif (sz)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfill_random_az(fakehost, 1);\n\t\t\t\t\t\t\tsz--;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfill_random_az09(fakehost + 1, sz);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfill_random_az(fakehost, 1);\n\t\t\t\t\tfill_random_az09(fakehost + 1, host_size - 1);\n\t\t\t\t\tif (host_size >= 7)\n\t\t\t\t\t{\n\t\t\t\t\t\tfakehost[host_size - 4] = '.';\n\t\t\t\t\t\tmemcpy(fakehost + host_size - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfakehost[host_size] = 0;\n\t\t\t\tDLOG(\"generated fake host: %s\\n\", fakehost);\n\n\t\t\t\t// pkt2: fake_host segment\n\t\t\t\tpkt2_len = sizeof(pkt2);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0,\n\t\t\t\t\tnet32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tfakehost, host_size, pkt2, &pkt2_len))\n\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending hostfakesplit fake host %zu-%zu len=%zu : \", pos_host, pos_endhost - 1, host_size);\n\t\t\t\thexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len))\n\t\t\t\t\tgoto send_orig_clean;\n\n\t\t\t\tip_id_after_host = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tif (pos_split_host) ip_id_after_host = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode);\n\n\t\t\t\t// pkt3: after_host segment\n\t\t\t\tpkt3_len = sizeof(pkt3);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\tnet32_add(dis->tcp->th_seq, pos_endhost), dis->tcp->th_ack,\n\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id_after_host, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\tseg + pos_endhost, seg_len - pos_endhost, pkt3, &pkt3_len))\n\t\t\t\t\tgoto send_orig_clean;\n\n\t\t\t\tif (dp->hfs_mod.ordering == 1)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"sending hostfakesplit after_host part %zu-%zu len=%zu : \", pos_endhost, seg_len - 1, seg_len - pos_endhost);\n\t\t\t\t\thexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len))\n\t\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\t}\n\n\t\t\t\tsz = pos_split_host ? pos_split_host - pos_host : host_size;\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\tnet32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack,\n\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\tseg + pos_host, sz, pkt1, &pkt1_len))\n\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\n\t\t\t\tDLOG(\"sending hostfakesplit real host %s%zu-%zu len=%zu : \", pos_split_host ? \"part 1 \" : \"\", pos_host, pos_host + sz - 1, sz);\n\t\t\t\thexdump_limited_dlog(seg + pos_host, sz, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig_clean;\n\n\t\t\t\tif (pos_split_host)\n\t\t\t\t{\n\t\t\t\t\tsz = pos_endhost - pos_split_host;\n\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\t\tnet32_add(dis->tcp->th_seq, pos_split_host), dis->tcp->th_ack,\n\t\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\t\tseg + pos_split_host, sz, pkt1, &pkt1_len))\n\t\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\tDLOG(\"sending hostfakesplit real host part 2 %zu-%zu len=%zu : \", pos_split_host, pos_endhost - 1, sz);\n\t\t\t\t\thexdump_limited_dlog(seg + pos_split_host, sz, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\t}\n\n\t\t\t\tif (dp->hfs_mod.ordering == 0)\n\t\t\t\t{\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (dis->ip) ((struct ip*)pkt2)->ip_id = ip_id;\n\t\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending hostfakesplit fake(2) host %zu-%zu len=%zu : \", pos_host, pos_endhost - 1, host_size);\n\t\t\t\t\thexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len))\n\t\t\t\t\t\tgoto send_orig_clean;\n\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t\t{\n\t\t\t\t\t\tip_id_after_host = ip_id;\n\t\t\t\t\t\tif (dis->ip) ((struct ip*)pkt3)->ip_id = ip_id;\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending hostfakesplit after_host part %zu-%zu len=%zu : \", pos_endhost, seg_len - 1, seg_len - pos_endhost);\n\t\t\t\t\thexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len))\n\t\t\t\t\t\tgoto send_orig_clean;\n\t\t\t\t}\n\n\t\t\t\tfree(fakehost);\n\n\t\t\t\tif (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode);\n\n\t\t\t\treturn VERDICT_DROP;\n\t\t\tsend_orig_clean:\n\t\t\t\tfree(fakehost);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\telse\n\t\t\t\tgoto unsplitted_part;\n\n\t\tcase DESYNC_MULTISPLIT:\n\t\t\tif (multisplit_count)\n\t\t\t{\n\t\t\t\tuint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg;\n\t\t\t\tsize_t seg_len, from, to;\n\t\t\t\tunsigned int seqovl;\n\n\t\t\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\n\t\t\t\tfor (i = 0, from = 0; i <= multisplit_count; i++)\n\t\t\t\t{\n\t\t\t\t\tto = i == multisplit_count ? dis->len_payload : multisplit_pos[i];\n\n\t\t\t\t\t// do seqovl only to the first packet\n\t\t\t\t\t// otherwise it's prone to race condition on server side\n\t\t\t\t\t// what happens first : server pushes socket buffer to process or another packet with seqovl arrives\n\t\t\t\t\tseqovl = (i == 0 && reasm_offset == 0) ? seqovl_pos : 0;\n#ifdef __linux__\n\t\t\t\t\t// only linux return error if MTU is exceeded\n\t\t\t\t\tfor (;; seqovl = 0)\n\t\t\t\t\t{\n#endif\n\t\t\t\t\t\tif (seqovl)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tseg_len = to - from + seqovl;\n\t\t\t\t\t\t\tif (seg_len > sizeof(ovlseg))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDLOG(\"seqovl is too large\");\n\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0);\n\t\t\t\t\t\t\tmemcpy(ovlseg + seqovl, dis->data_payload + from, to - from);\n\t\t\t\t\t\t\tseg = ovlseg;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tseqovl = 0;\n\t\t\t\t\t\t\tseg = dis->data_payload + from;\n\t\t\t\t\t\t\tseg_len = to - from;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\t\t\tnet32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack,\n\t\t\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\t\t\tseg, seg_len, pkt1, &pkt1_len))\n\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\t\tDLOG(\"sending multisplit part %d %zu-%zu len=%zu seqovl=%u : \", i + 1, from, to - 1, to - from, seqovl);\n\t\t\t\t\t\thexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\t{\n#ifdef __linux__\n\t\t\t\t\t\t\tif (errno == EMSGSIZE && seqovl)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDLOG(\"MTU exceeded. cancelling seqovl.\\n\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n#endif\n\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t}\n#ifdef __linux__\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n#endif\n\n\t\t\t\t\tfrom = to;\n\t\t\t\t}\n\n\t\t\t\tif (replay) ctrack_replay->ip_id = ip_id;\n\n\t\t\t\treturn VERDICT_DROP;\n\t\t\t}\n\t\t\telse\n\t\t\t\tgoto unsplitted_part;\n\n\t\tcase DESYNC_MULTIDISORDER:\n\t\t\tif (multisplit_count)\n\t\t\t{\n\t\t\t\tuint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg;\n\t\t\t\tsize_t seg_len, from, to;\n\t\t\t\tunsigned int seqovl;\n\t\t\t\tuint16_t ip_id_end;\n\n\t\t\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\n\t\t\t\tip_id_end = ip_id = IP4_IP_ID_ADD(ip_id, (uint16_t)multisplit_count, dp->ip_id_mode);\n\n\t\t\t\tfor (i = multisplit_count - 1, to = dis->len_payload; i >= -1; i--)\n\t\t\t\t{\n\t\t\t\t\tfrom = i >= 0 ? multisplit_pos[i] : 0;\n\n\t\t\t\t\tseg = dis->data_payload + from;\n\t\t\t\t\tseg_len = to - from;\n\t\t\t\t\tseqovl = 0;\n\t\t\t\t\t// do seqovl only to the second packet\n\t\t\t\t\t// otherwise sequence overlap becomes problematic. overlap algorithm is not too obvious.\n\t\t\t\t\t// real observations revealed that server can receive overlap junk instead of real data\n\t\t\t\t\tif (i == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (seqovl_pos >= from)\n\t\t\t\t\t\t\tDLOG(\"seqovl>=split_pos (%zu>=%zu). cancelling seqovl for part %d.\\n\", seqovl_pos, from, i + 2);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tseqovl = seqovl_pos;\n\t\t\t\t\t\t\tseg_len = to - from + seqovl;\n\t\t\t\t\t\t\tif (seg_len > sizeof(ovlseg))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDLOG(\"seqovl is too large\");\n\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0);\n\t\t\t\t\t\t\tmemcpy(ovlseg + seqovl, dis->data_payload + from, to - from);\n\t\t\t\t\t\t\tseg = ovlseg;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0,\n\t\t\t\t\t\tnet32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack,\n\t\t\t\t\t\tdis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tfooling_orig, 0, 0, 0,\n\t\t\t\t\t\tseg, seg_len, pkt1, &pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tip_id = IP4_IP_ID_PREV(ip_id,dp->ip_id_mode);\n\t\t\t\t\tDLOG(\"sending multisplit part %d %zu-%zu len=%zu seqovl=%u : \", i + 2, from, to - 1, to - from, seqovl);\n\t\t\t\t\thexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\n\t\t\t\t\tto = from;\n\t\t\t\t}\n\n\t\t\t\tif (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_end,dp->ip_id_mode);\n\n\t\t\t\treturn VERDICT_DROP;\n\t\t\t}\n\t\t\telse\n\t\t\t\tgoto unsplitted_part;\n\n\t\tcase DESYNC_FAKEDDISORDER:\n\t\t\t{\n\t\t\t\tuint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], fakeseg2[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg;\n\t\t\t\tsize_t seg_len, fakeseg2_len;\n\t\t\t\tunsigned int seqovl;\n\t\t\t\tint order;\n\n\t\t\t\tif (dis->len_payload > sizeof(pat))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"packet is too large\\n\");\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\n\t\t\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\n\t\t\t\tseqovl = 0;\n\t\t\t\tif (split_pos)\n\t\t\t\t{\n\t\t\t\t\torder = dp->fs_mod.ordering & 3;\n\t\t\t\t\tif (seqovl_pos >= split_pos)\n\t\t\t\t\t\tDLOG(\"seqovl>=split_pos (%zu>=%zu). cancelling seqovl.\\n\", seqovl_pos, split_pos);\n\t\t\t\t\telse\n\t\t\t\t\t\tseqovl = seqovl_pos;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\torder = (dp->fs_mod.ordering >> 3) & 3;\n\t\t\t\t}\n\n\t\t\t\tfill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset);\n\n\t\t\t\tif (seqovl)\n\t\t\t\t{\n\t\t\t\t\tseg_len = dis->len_payload - split_pos + seqovl;\n\t\t\t\t\tif (seg_len > sizeof(fakeseg))\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"seqovl is too large\\n\");\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tfill_pattern(fakeseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0);\n\t\t\t\t\tmemcpy(fakeseg + seqovl, dis->data_payload + split_pos, dis->len_payload - split_pos);\n\t\t\t\t\tseg = fakeseg;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tseg = dis->data_payload + split_pos;\n\t\t\t\t\tseg_len = dis->len_payload - split_pos;\n\t\t\t\t}\n\n\t\t\t\tfakeseg2_len = sizeof(fakeseg2);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tpat + split_pos, dis->len_payload - split_pos, fakeseg2, &fakeseg2_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tif (order == 0)\n\t\t\t\t{\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\tDLOG(\"sending fake(1) 2nd out-of-order tcp segment %zu-%zu len=%zu : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos);\n\t\t\t\t\thexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, split_pos - seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tseg, seg_len, pkt1, &pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending 2nd out-of-order tcp segment %zu-%zu len=%zu seqovl=%u : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos, seqovl);\n\t\t\t\thexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tif (order <= 1)\n\t\t\t\t{\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (dis->ip) ((struct ip*)fakeseg2)->ip_id = ip_id;\n\t\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending fake(2) 2nd out-of-order tcp segment %zu-%zu len=%zu : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos);\n\t\t\t\t\thexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\n\t\t\t\tif (split_pos)\n\t\t\t\t{\n\t\t\t\t\tseg_len = sizeof(fakeseg);\n\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\t\tpat, split_pos, fakeseg, &seg_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\tDLOG(\"sending fake(1) 1st out-of-order tcp segment 0-%zu len=%zu : \", split_pos - 1, split_pos);\n\t\t\t\t\thexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len))\n\t\t\t\t\t\tgoto send_orig;\n\n\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tfooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\t\tdis->data_payload, split_pos, pkt1, &pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\tDLOG(\"sending 1st out-of-order tcp segment 0-%zu len=%zu : \", split_pos - 1, split_pos);\n\t\t\t\t\thexdump_limited_dlog(dis->data_payload, split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\n\t\t\t\t\tif (order <= 2)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id;\n\t\t\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDLOG(\"sending fake(2) 1st out-of-order tcp segment 0-%zu len=%zu : \", split_pos - 1, split_pos);\n\t\t\t\t\t\thexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len))\n\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (replay) ctrack_replay->ip_id = ip_id;\n\n\t\t\t\treturn VERDICT_DROP;\n\t\t\t}\n\n\t\tcase DESYNC_FAKEDSPLIT:\n\t\t{\n\t\t\tuint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg;\n\t\t\tsize_t fakeseg_len, seg_len;\n\t\t\tint order;\n\n\t\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\n\t\t\tif (dis->len_payload > sizeof(pat))\n\t\t\t{\n\t\t\t\tDLOG(\"packet is too large\\n\");\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\n\t\t\tfill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset);\n\n\t\t\tif (split_pos)\n\t\t\t{\n\t\t\t\torder = dp->fs_mod.ordering & 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\torder = (dp->fs_mod.ordering >> 3) & 3;\n\t\t\t\tsplit_pos = dis->len_payload;\n\t\t\t}\n\n\t\t\tfakeseg_len = sizeof(fakeseg);\n\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\tpat, split_pos, fakeseg, &fakeseg_len))\n\t\t\t\tgoto send_orig;\n\n\t\t\tif (order == 0)\n\t\t\t{\n\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending fake(1) 1st tcp segment 0-%zu len=%zu : \", split_pos - 1, split_pos);\n\t\t\t\thexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t}\n\n\t\t\tunsigned int seqovl = (reasm_offset || split_pos>=dis->len_payload) ? 0 : seqovl_pos;\n#ifdef __linux__\n\t\t\t// only linux return error if MTU is exceeded\n\t\t\tfor (;; seqovl = 0)\n\t\t\t{\n#endif\n\t\t\t\tif (seqovl)\n\t\t\t\t{\n\t\t\t\t\tseg_len = split_pos + seqovl;\n\t\t\t\t\tif (seg_len > sizeof(ovlseg))\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"seqovl is too large\\n\");\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tfill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0);\n\t\t\t\t\tmemcpy(ovlseg + seqovl, dis->data_payload, split_pos);\n\t\t\t\t\tseg = ovlseg;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tseg = dis->data_payload;\n\t\t\t\t\tseg_len = split_pos;\n\t\t\t\t}\n\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, -seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tseg, seg_len, pkt1, &pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending 1st tcp segment 0-%zu len=%zu seqovl=%u : \", split_pos - 1, split_pos, seqovl);\n\t\t\t\thexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t{\n#ifdef __linux__\n\t\t\t\t\tif (errno == EMSGSIZE && seqovl)\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"MTU exceeded. cancelling seqovl.\\n\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n#endif\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n#ifdef __linux__\n\t\t\t\tbreak;\n\t\t\t}\n#endif\n\t\t\tif (order <= 1)\n\t\t\t{\n\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t{\n\t\t\t\t\tif (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t}\n\t\t\t\tDLOG(\"sending fake(2) 1st tcp segment 0-%zu len=%zu : \", split_pos - 1, split_pos);\n\t\t\t\thexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t}\n\n\t\t\tif (split_pos < dis->len_payload)\n\t\t\t{\n\t\t\t\tfakeseg_len = sizeof(fakeseg);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tdp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tpat + split_pos, dis->len_payload - split_pos, fakeseg, &fakeseg_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending fake(1) 2nd tcp segment %zu-%zu len=%zu : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos);\n\t\t\t\thexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, split_pos), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps,\n\t\t\t\t\tDF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\tfooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment,\n\t\t\t\t\tdis->data_payload + split_pos, dis->len_payload - split_pos, pkt1, &pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\tDLOG(\"sending 2nd tcp segment %zu-%zu len=%zu : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos);\n\t\t\t\thexdump_limited_dlog(dis->data_payload + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tif (order <= 2)\n\t\t\t\t{\n\t\t\t\t\tif (dp->ip_id_mode!=IPID_SEQ_GROUP)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id;\n\t\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending fake(2) 2nd tcp segment %zu-%zu len=%zu : \", split_pos, dis->len_payload - 1, dis->len_payload - split_pos);\n\t\t\t\t\thexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (replay) ctrack_replay->ip_id = ip_id;\n\n\t\t\treturn VERDICT_DROP;\n\t\t}\n\t\tcase DESYNC_IPFRAG2:\n\t\t\tif (reasm_offset)\n\t\t\t\tgoto unsplitted_part;\n\t\t\telse\n\t\t\t{\n\t\t\t\tverdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6);\n\n\t\t\t\tuint8_t *pkt_orig;\n\t\t\t\tsize_t pkt_orig_len;\n\n\t\t\t\tuint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF);\n\t\t\t\tsize_t ipfrag_pos = (dp->desync_ipfrag_pos_tcp && dp->desync_ipfrag_pos_tcp < dis->transport_len) ? dp->desync_ipfrag_pos_tcp : 24;\n\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tpkt2_len = sizeof(pkt2);\n\n\t\t\t\tif (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT))\n\t\t\t\t{\n\t\t\t\t\tpkt_orig_len = sizeof(pkt3);\n\t\t\t\t\tif (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tpkt_orig = pkt3;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tpkt_orig = dis->data_pkt;\n\t\t\t\t\tpkt_orig_len = dis->len_pkt;\n\t\t\t\t}\n\n\t\t\t\tif (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tDLOG(\"sending 1st ip fragment 0-%zu ip_payload_len=%zu : \", ipfrag_pos - 1, ipfrag_pos);\n\t\t\t\thexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tDLOG(\"sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : \", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos);\n\t\t\t\thexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len))\n\t\t\t\t\tgoto send_orig;\n\n\t\t\t\tif (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\n\t\t\t\treturn VERDICT_DROP;\n\t\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\nsend_orig:\n\n\tif ((verdict & VERDICT_MASK) == VERDICT_DROP)\n\t\tverdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp);\n\telse\n\t\tif (tcp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake))\n\t\t\tverdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp);\n\treturn verdict;\n\nunsplitted_part:\n\tif (replay && dis->ip && ctrack_replay->ip_id)\n\t{\n\t\tDLOG(\"changing ip_id of unsplitted part\\n\");\n\t\tdis->ip->ip_id = ctrack_replay->ip_id;\n\t\tctrack_replay->ip_id = IP4_IP_ID_NEXT(ctrack_replay->ip_id,dp->ip_id_mode);\n\t\treturn VERDICT_MODIFY;\n\t}\n\tgoto send_orig;\n\n}\n\n// return : true - should continue, false - should stop with verdict\nstatic bool quic_reasm_cancel(t_ctrack *ctrack, const char *reason)\n{\n\treasm_orig_cancel(ctrack);\n\tif (ctrack && ctrack->dp && ctrack->dp->desync_any_proto)\n\t{\n\t\tDLOG(\"%s. applying tampering because desync_any_proto is set\\n\", reason);\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\tDLOG(\"%s. not applying tampering because desync_any_proto is not set\\n\", reason);\n\t\treturn false;\n\t}\n}\n\nstatic uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, struct dissect *dis)\n{\n\tuint8_t verdict = VERDICT_PASS;\n\n\t// additional safety check\n\tif (!!dis->ip == !!dis->ip6) return verdict;\n\n\tstruct desync_profile *dp = NULL;\n\n\tt_ctrack *ctrack = NULL, *ctrack_replay = NULL;\n\tbool bReverse = false;\n\tbool bFake = false;\n\n\tstruct sockaddr_storage src, dst;\n\tuint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100];\n\tsize_t pkt1_len, pkt2_len;\n\tuint8_t ttl_orig, ttl_fake;\n\tbool DF;\n\tchar host[256];\n\tt_l7proto l7proto = UNKNOWN;\n\tconst char *ifname = NULL, *ssid = NULL;\n\n\textract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst);\n\n\tif (replay)\n\t{\n\t\t// in replay mode conntrack_replay is not NULL and ctrack is NULL\n\n\t\t//ConntrackPoolDump(&params.conntrack);\n\t\tif (!ConntrackPoolDoubleSearch(&params.conntrack, dis->ip, dis->ip6, NULL, dis->udp, &ctrack_replay, &bReverse) || bReverse)\n\t\t\treturn verdict;\n\n\t\tifname = bReverse ? ifin : ifout;\n#ifdef HAS_FILTER_SSID\n\t\tssid = wlan_ssid_search_ifname(ifname);\n\t\tif (ssid) DLOG(\"found ssid for %s : %s\\n\", ifname, ssid);\n#endif\n\n\t\tdp = ctrack_replay->dp;\n\t\tif (dp)\n\t\t\tDLOG(\"using cached desync profile %d\\n\", dp->n);\n\t\telse if (!ctrack_replay->dp_search_complete)\n\t\t{\n\t\t\tdp = ctrack_replay->dp = dp_find(&params.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL);\n\t\t\tctrack_replay->dp_search_complete = true;\n\t\t}\n\t\tif (!dp)\n\t\t{\n\t\t\tDLOG(\"matching desync profile not found\\n\");\n\t\t\treturn verdict;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack\n\n\t\tif (!params.ctrack_disable)\n\t\t{\n\t\t\tConntrackPoolPurge(&params.conntrack);\n\t\t\tif (ConntrackPoolFeed(&params.conntrack, dis->ip, dis->ip6, NULL, dis->udp, dis->len_payload, &ctrack, &bReverse))\n\t\t\t{\n\t\t\t\tdp = ctrack->dp;\n\t\t\t\tctrack_replay = ctrack;\n\t\t\t}\n\t\t}\n\t\tifname = bReverse ? ifin : ifout;\n#ifdef HAS_FILTER_SSID\n\t\tssid = wlan_ssid_search_ifname(ifname);\n\t\tif (ssid) DLOG(\"found ssid for %s : %s\\n\", ifname, ssid);\n#endif\n\t\tif (dp)\n\t\t\tDLOG(\"using cached desync profile %d\\n\", dp->n);\n\t\telse if (!ctrack || !ctrack->dp_search_complete)\n\t\t{\n\t\t\tconst char *hostname = NULL;\n\t\t\tbool hostname_is_ip = false;\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\thostname = ctrack->hostname;\n\t\t\t\thostname_is_ip = ctrack->hostname_is_ip;\n\t\t\t\tif (!hostname && !bReverse)\n\t\t\t\t{\n\t\t\t\t\tif (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host)\n\t\t\t\t\t\tif (!(hostname = ctrack_replay->hostname = strdup(host)))\n\t\t\t\t\t\t\tDLOG_ERR(\"strdup(host): out of memory\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tdp = dp_find(&params.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL);\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tctrack->dp = dp;\n\t\t\t\tctrack->dp_search_complete = true;\n\t\t\t}\n\t\t}\n\t\tif (!dp)\n\t\t{\n\t\t\tDLOG(\"matching desync profile not found\\n\");\n\t\t\treturn verdict;\n\t\t}\n\t\tmaybe_cutoff(ctrack, IPPROTO_UDP);\n\n\t\tHostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters);\n\t\t//ConntrackPoolDump(&params.conntrack);\n\n\t\tif (bReverse)\n\t\t{\n\t\t\tif (ctrack)\n\t\t\t{\n\t\t\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\t\t\t\tif (!ctrack->incoming_ttl)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"incoming TTL %u\\n\", ttl_orig);\n\t\t\t\t\tctrack->incoming_ttl = ttl_orig;\n\t\t\t\t\tautottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn verdict; // nothing to do. do not waste cpu\n\t\t}\n\n\t\tautottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout);\n\n\t\tif (orig_mod(dp, ctrack, dis)) // ttl can change !\n\t\t\tverdict = VERDICT_MODIFY;\n\n\t\t// start and cutoff limiters\n\t\tif (!process_desync_interval(dp, ctrack)) goto send_orig;\n\t}\n\n\tuint32_t desync_fwmark = fwmark | params.desync_fwmark;\n\tDF = ip_has_df(dis->ip);\n\n\tif (dis->len_payload)\n\t{\n\t\tstruct blob_collection_head *fake;\n\t\tbool bHaveHost = false, bHostIsIp = false;\n\t\tuint16_t ip_id=0;\n\n\t\tif (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id;\n\t\tif (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\n\t\tif (IsQUICInitial(dis->data_payload, dis->len_payload))\n\t\t{\n\t\t\tDLOG(\"packet contains QUIC initial\\n\");\n\t\t\tl7proto = QUIC;\n\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\n\t\t\tuint8_t clean[16384], *pclean;\n\t\t\tsize_t clean_len;\n\n\t\t\tif (replay)\n\t\t\t{\n\t\t\t\tclean_len = ctrack_replay->reasm_orig.size_present;\n\t\t\t\tpclean = ctrack_replay->reasm_orig.packet;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tclean_len = sizeof(clean);\n\t\t\t\tpclean = QUICDecryptInitial(dis->data_payload, dis->len_payload, clean, &clean_len) ? clean : NULL;\n\t\t\t}\n\t\t\tif (pclean)\n\t\t\t{\n\t\t\t\tif (ctrack && !ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t\t\t{\n\t\t\t\t\tif (ReasmHasSpace(&ctrack->reasm_orig, clean_len))\n\t\t\t\t\t{\n\t\t\t\t\t\treasm_orig_feed(ctrack, IPPROTO_UDP, clean, clean_len);\n\t\t\t\t\t\tpclean = ctrack->reasm_orig.packet;\n\t\t\t\t\t\tclean_len = ctrack->reasm_orig.size_present;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"QUIC reasm is too long. cancelling.\\n\");\n\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\tgoto send_orig; // cannot be first packet\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tuint8_t defrag[UDP_MAX_REASM];\n\t\t\t\tsize_t hello_offset, hello_len, defrag_len = sizeof(defrag);\n\t\t\t\tbool bFull;\n\t\t\t\tif (QUICDefragCrypto(pclean, clean_len, defrag, &defrag_len, &bFull))\n\t\t\t\t{\n\t\t\t\t\tif (bFull)\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"QUIC initial contains CRYPTO with full fragment coverage\\n\");\n\n\t\t\t\t\t\tbool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len);\n\t\t\t\t\t\tbool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag + hello_offset, hello_len) : false;\n\n\t\t\t\t\t\tDLOG(bIsHello ? bReqFull ? \"packet contains full TLS ClientHello\\n\" : \"packet contains partial TLS ClientHello\\n\" : \"packet does not contain TLS ClientHello\\n\");\n\n\t\t\t\t\t\tif (bReqFull) TLSDebugHandshake(defrag + hello_offset, hello_len);\n\n\t\t\t\t\t\tif (ctrack)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// preallocate max buffer to avoid reallocs that cause memory copy\n\t\t\t\t\t\t\t\tif (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tverdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);\n\t\t\t\t\t\t\t\tif (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDLOG(\"DELAY desync until reasm is complete (#%u)\\n\", rawpacket_queue_count(&ctrack->delayed));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDLOG_ERR(\"rawpacket_queue failed !\\n\");\n\t\t\t\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (bReqFull)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treplay_queue(&ctrack->delayed);\n\t\t\t\t\t\t\t\t\treasm_orig_fin(ctrack);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (bIsHello)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE);\n\t\t\t\t\t\t\tif (!bHaveHost && dp->desync_skip_nosni)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\t\t\tDLOG(\"not applying tampering to QUIC ClientHello without hostname in the SNI\\n\");\n\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!quic_reasm_cancel(ctrack, \"QUIC initial without ClientHello\")) goto send_orig;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG(\"QUIC initial contains CRYPTO with partial fragment coverage\\n\");\n\t\t\t\t\t\tif (ctrack)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (ReasmIsEmpty(&ctrack->reasm_orig))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// preallocate max buffer to avoid reallocs that cause memory copy\n\t\t\t\t\t\t\t\tif (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tverdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);\n\t\t\t\t\t\t\tif (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDLOG(\"DELAY desync until reasm is complete (#%u)\\n\", rawpacket_queue_count(&ctrack->delayed));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDLOG_ERR(\"rawpacket_queue failed !\\n\");\n\t\t\t\t\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!quic_reasm_cancel(ctrack, \"QUIC initial fragmented CRYPTO\")) goto send_orig;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// defrag failed\n\t\t\t\t\tif (!quic_reasm_cancel(ctrack, \"QUIC initial defrag CRYPTO failed\")) goto send_orig;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// decrypt failed\n\t\t\t\tif (!quic_reasm_cancel(ctrack, \"QUIC initial decryption failed\")) goto send_orig;\n\t\t\t}\n\t\t}\n\t\telse // not QUIC initial\n\t\t{\n\t\t\t// received payload without host. it means we are out of the request retransmission phase. stop counter\n\t\t\tctrack_stop_retrans_counter(ctrack);\n\n\t\t\treasm_orig_cancel(ctrack);\n\n\t\t\tif (IsWireguardHandshakeInitiation(dis->data_payload, dis->len_payload))\n\t\t\t{\n\t\t\t\tDLOG(\"packet contains wireguard handshake initiation\\n\");\n\t\t\t\tl7proto = WIREGUARD;\n\t\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\t\t\t}\n\t\t\telse if (IsDhtD1(dis->data_payload, dis->len_payload))\n\t\t\t{\n\t\t\t\tDLOG(\"packet contains DHT d1...e\\n\");\n\t\t\t\tl7proto = DHT;\n\t\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\t\t\t}\n\t\t\telse if (IsDiscordIpDiscoveryRequest(dis->data_payload, dis->len_payload))\n\t\t\t{\n\t\t\t\tDLOG(\"packet contains discord voice IP discovery\\n\");\n\t\t\t\tl7proto = DISCORD;\n\t\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\t\t\t}\n\t\t\telse if (IsStunMessage(dis->data_payload, dis->len_payload))\n\t\t\t{\n\t\t\t\tDLOG(\"packet contains STUN message\\n\");\n\t\t\t\tl7proto = STUN;\n\t\t\t\tif (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!dp->desync_any_proto)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"not applying tampering to unknown protocol\\n\");\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tDLOG(\"applying tampering to unknown protocol\\n\");\n\t\t\t}\n\t\t}\n\n\t\tif (bHaveHost)\n\t\t{\n\t\t\tbHostIsIp = strip_host_to_ip(host);\n\t\t\tDLOG(\"hostname: %s\\n\", host);\n\t\t}\n\n\t\tbool bDiscoveredL7;\n\t\tif (ctrack_replay)\n\t\t{\n\t\t\tif ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN))\n\t\t\t\tctrack_replay->l7proto_discovered = true;\n\t\t}\n\t\telse\n\t\t\tbDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN;\n\t\tif (bDiscoveredL7) DLOG(\"discovered l7 protocol\\n\");\n\n\t\tbool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered);\n\t\tif (bDiscoveredHostname)\n\t\t{\n\t\t\tDLOG(\"discovered hostname\\n\");\n\t\t\tif (ctrack_replay)\n\t\t\t{\n\t\t\t\tctrack_replay->hostname_discovered = true;\n\t\t\t\tfree(ctrack_replay->hostname);\n\t\t\t\tctrack_replay->hostname = strdup(host);\n\t\t\t\tctrack_replay->hostname_is_ip = bHostIsIp;\n\t\t\t\tif (!ctrack_replay->hostname)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"hostname dup : out of memory\");\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tif (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp))\n\t\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t}\n\n\t\tbool bCheckDone = false, bCheckResult = false, bCheckExcluded = false;\n\t\tif (bDiscoveredL7 || bDiscoveredHostname)\n\t\t{\n\t\t\tstruct desync_profile *dp_prev = dp;\n\n\t\t\tdp = dp_find(&params.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst,\n\t\t\t\tctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL,\n\t\t\t\tctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp,\n\t\t\t\tctrack_replay ? ctrack_replay->l7proto : l7proto, ssid,\n\t\t\t\t&bCheckDone, &bCheckResult, &bCheckExcluded);\n\t\t\tif (ctrack_replay)\n\t\t\t{\n\t\t\t\tctrack_replay->dp = dp;\n\t\t\t\tctrack_replay->dp_search_complete = true;\n\t\t\t\tctrack_replay->bCheckDone = bCheckDone;\n\t\t\t\tctrack_replay->bCheckResult = bCheckResult;\n\t\t\t\tctrack_replay->bCheckExcluded = bCheckExcluded;\n\t\t\t}\n\t\t\tif (!dp)\n\t\t\t{\n\t\t\t\treasm_orig_cancel(ctrack);\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t\tif (dp != dp_prev)\n\t\t\t{\n\t\t\t\tDLOG(\"desync profile changed by revealed l7 protocol or hostname !\\n\");\n\t\t\t\tautottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout);\n\t\t\t\tip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode);\n\t\t\t\t// re-evaluate start/cutoff limiters\n\t\t\t\tif (replay)\n\t\t\t\t{\n\t\t\t\t\tif (orig_mod(dp, ctrack_replay, dis)) // ttl can change !\n\t\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmaybe_cutoff(ctrack, IPPROTO_UDP);\n\t\t\t\t\tif (orig_mod(dp, ctrack, dis)) // ttl can change !\n\t\t\t\t\t\tverdict = VERDICT_MODIFY;\n\t\t\t\t\tif (!process_desync_interval(dp, ctrack)) goto send_orig;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (ctrack_replay)\n\t\t{\n\t\t\tbCheckDone = ctrack_replay->bCheckDone;\n\t\t\tbCheckResult = ctrack_replay->bCheckResult;\n\t\t\tbCheckExcluded = ctrack_replay->bCheckExcluded;\n\t\t}\n\n\t\tif (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp))\n\t\t{\n\t\t\tif (!bCheckDone)\n\t\t\t\tbCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false);\n\t\t\tif (bCheckResult)\n\t\t\t\tctrack_stop_retrans_counter(ctrack_replay);\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (ctrack_replay)\n\t\t\t\t{\n\t\t\t\t\tctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded;\n\t\t\t\t\tif (ctrack_replay->hostname_ah_check)\n\t\t\t\t\t{\n\t\t\t\t\t\t// first request is not retrans\n\t\t\t\t\t\tif (!bDiscoveredHostname && !reasm_offset)\n\t\t\t\t\t\t\tprocess_retrans_fail(ctrack_replay, IPPROTO_UDP, (struct sockaddr*)&src);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tDLOG(\"not applying tampering to this request\\n\");\n\t\t\t\tgoto send_orig;\n\t\t\t}\n\t\t}\n\n\t\t// desync profile may have changed after hostname was revealed\n\t\tswitch (l7proto)\n\t\t{\n\t\tcase QUIC:\n\t\t\tfake = &dp->fake_quic;\n\t\t\tbreak;\n\t\tcase WIREGUARD:\n\t\t\tfake = &dp->fake_wg;\n\t\t\tbreak;\n\t\tcase DHT:\n\t\t\tfake = &dp->fake_dht;\n\t\t\tbreak;\n\t\tcase DISCORD:\n\t\t\tfake = &dp->fake_discord;\n\t\t\tbreak;\n\t\tcase STUN:\n\t\t\tfake = &dp->fake_stun;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfake = &dp->fake_unknown_udp;\n\t\t\tbreak;\n\t\t}\n\n\t\tttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim;\n\t\tttl_fake = (ctrack_replay && ctrack_replay->desync_autottl) ? ctrack_replay->desync_autottl : (dis->ip6 ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig));\n\n\t\tuint32_t fooling_orig = FOOL_NONE;\n\n\t\tif (params.debug)\n\t\t{\n\t\t\tchar s1[48], s2[48];\n\t\t\tntop46_port((struct sockaddr *)&src, s1, sizeof(s1));\n\t\t\tntop46_port((struct sockaddr *)&dst, s2, sizeof(s2));\n\t\t\tDLOG(\"dpi desync src=%s dst=%s\\n\", s1, s2);\n\t\t}\n\n\t\tswitch (dp->desync_mode)\n\t\t{\n\t\tcase DESYNC_FAKE_KNOWN:\n\t\t\tif (l7proto == UNKNOWN)\n\t\t\t{\n\t\t\t\tDLOG(\"not applying fake because of unknown protocol\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase DESYNC_FAKE:\n\t\t\tif (!reasm_offset)\n\t\t\t{\n\t\t\t\tsize_t fake_size;\n\t\t\t\tuint8_t *fake_data;\n\t\t\t\tstruct blob_item *fake_item;\n\t\t\t\tint n = 0;\n\n\t\t\t\tLIST_FOREACH(fake_item, fake, next)\n\t\t\t\t{\n\t\t\t\t\tn++;\n\n\t\t\t\t\tfake_data = fake_item->data + fake_item->offset;\n\t\t\t\t\tfake_size = fake_item->size - fake_item->offset;\n\n\t\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\t\tif (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst,\n\t\t\t\t\t\tDF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6),\n\t\t\t\t\t\tdp->desync_fooling_mode, NULL, 0, 0,\n\t\t\t\t\t\tfake_data, fake_size, pkt1, &pkt1_len))\n\t\t\t\t\t{\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\t}\n\t\t\t\t\tDLOG(\"sending fake[%d] : \", n);\n\t\t\t\t\thexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG(\"\\n\");\n\t\t\t\t\tif (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\t\tgoto send_orig;\n\t\t\t\t\tip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\t\t\t\t}\n\t\t\t\tbFake = true;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase DESYNC_HOPBYHOP:\n\t\tcase DESYNC_DESTOPT:\n\t\tcase DESYNC_IPFRAG1:\n\t\t\tfooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1;\n\t\t\tif (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_udp(dp->desync_mode2)))\n\t\t\t{\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst,\n\t\t\t\t\tDF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6), fooling_orig, NULL, 0, 0,\n\t\t\t\t\tdis->data_payload, dis->len_payload, pkt1, &pkt1_len))\n\t\t\t\t{\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t}\n\t\t\t\tDLOG(\"resending original packet with extension header\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\t// this mode is final, no other options available\n\t\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpkt1_len = 0;\n\t\t\tbreak;\n\t\t}\n\n\t\tenum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2;\n\t\tswitch (desync_mode)\n\t\t{\n\t\tcase DESYNC_UDPLEN:\n\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\tif (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->udplen_pattern, sizeof(dp->udplen_pattern), dp->udplen_increment, dis->data_payload, dis->len_payload, pkt1, &pkt1_len))\n\t\t\t{\n\t\t\t\tDLOG(\"could not construct packet with modified length. too large ?\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tDLOG(\"resending original packet with increased by %d length\\n\", dp->udplen_increment);\n\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\tgoto send_orig;\n\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\tcase DESYNC_TAMPER:\n\t\t\tif (IsDhtD1(dis->data_payload, dis->len_payload))\n\t\t\t{\n\t\t\t\tsize_t szbuf, szcopy;\n\t\t\t\tmemcpy(pkt2, \"d2:001:x\", 8);\n\t\t\t\tpkt2_len = 8;\n\t\t\t\tszbuf = sizeof(pkt2) - pkt2_len;\n\t\t\t\tszcopy = dis->len_payload - 1;\n\t\t\t\tif (szcopy > szbuf)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"packet is too long to tamper\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmemcpy(pkt2 + pkt2_len, dis->data_payload + 1, szcopy);\n\t\t\t\tpkt2_len += szcopy;\n\t\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\t\tif (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, NULL, 0, 0, pkt2, pkt2_len, pkt1, &pkt1_len))\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"could not construct packet with modified length. too large ?\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tDLOG(\"resending tampered DHT\\n\");\n\t\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG(\"payload is not tamperable\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase DESYNC_IPFRAG2:\n\t\t{\n\t\t\tverdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6);\n\n\t\t\tuint8_t *pkt_orig;\n\t\t\tsize_t pkt_orig_len;\n\n\t\t\tuint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF);\n\t\t\tsize_t ipfrag_pos = (dp->desync_ipfrag_pos_udp && dp->desync_ipfrag_pos_udp < dis->transport_len) ? dp->desync_ipfrag_pos_udp : sizeof(struct udphdr);\n\n\t\t\tpkt1_len = sizeof(pkt1);\n\t\t\tpkt2_len = sizeof(pkt2);\n\n\t\t\tif (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT))\n\t\t\t{\n\t\t\t\tpkt_orig_len = sizeof(pkt3);\n\t\t\t\tif (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len))\n\t\t\t\t\tgoto send_orig;\n\t\t\t\tpkt_orig = pkt3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpkt_orig = dis->data_pkt;\n\t\t\t\tpkt_orig_len = dis->len_pkt;\n\t\t\t}\n\n\t\t\tif (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len))\n\t\t\t\tgoto send_orig;\n\n\t\t\tDLOG(\"sending 1st ip fragment 0-%zu ip_payload_len=%zu : \", ipfrag_pos - 1, ipfrag_pos);\n\t\t\thexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG(\"\\n\");\n\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len))\n\t\t\t\tgoto send_orig;\n\n\t\t\tDLOG(\"sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : \", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos);\n\t\t\thexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG(\"\\n\");\n\t\t\tif (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len))\n\t\t\t\tgoto send_orig;\n\n\t\t\tif (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode);\n\n\t\t\treturn ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\nsend_orig:\n\tif ((verdict & VERDICT_MASK) == VERDICT_DROP)\n\t\tverdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\telse\n\t\tif (udp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake))\n\t\t\tverdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL);\n\treturn verdict;\n}\n\n\nstatic void packet_debug(bool replay, const struct dissect *dis)\n{\n\tif (params.debug)\n\t{\n\t\tif (replay) DLOG(\"REPLAY \");\n\t\tif (dis->ip)\n\t\t{\n\t\t\tchar s[66];\n\t\t\tstr_ip(s, sizeof(s), dis->ip);\n\t\t\tDLOG(\"IP4: %s\", s);\n\t\t}\n\t\telse if (dis->ip6)\n\t\t{\n\t\t\tchar s[128];\n\t\t\tstr_ip6hdr(s, sizeof(s), dis->ip6, dis->proto);\n\t\t\tDLOG(\"IP6: %s\", s);\n\t\t}\n\t\tif (dis->tcp)\n\t\t{\n\t\t\tchar s[80];\n\t\t\tstr_tcphdr(s, sizeof(s), dis->tcp);\n\t\t\tDLOG(\" %s\\n\", s);\n\t\t\tif (dis->len_payload) { DLOG(\"TCP: len=%zu : \", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG(\"\\n\"); }\n\n\t\t}\n\t\telse if (dis->udp)\n\t\t{\n\t\t\tchar s[30];\n\t\t\tstr_udphdr(s, sizeof(s), dis->udp);\n\t\t\tDLOG(\" %s\\n\", s);\n\t\t\tif (dis->len_payload) { DLOG(\"UDP: len=%zu : \", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG(\"\\n\"); }\n\t\t}\n\t\telse\n\t\t\tDLOG(\"\\n\");\n\t}\n}\n\n\nstatic uint8_t dpi_desync_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt)\n{\n\tstruct dissect dis;\n\tuint8_t verdict = VERDICT_PASS;\n\n\tproto_dissect_l3l4(data_pkt, *len_pkt, &dis);\n\tif (!!dis.ip != !!dis.ip6)\n\t{\n\t\tpacket_debug(replay, &dis);\n\t\tswitch (dis.proto)\n\t\t{\n\t\tcase IPPROTO_TCP:\n\t\t\tif (dis.tcp)\n\t\t\t{\n\t\t\t\tverdict = dpi_desync_tcp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis);\n\t\t\t\tverdict_tcp_csum_fix(verdict, dis.tcp, dis.transport_len, dis.ip, dis.ip6);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IPPROTO_UDP:\n\t\t\tif (dis.udp)\n\t\t\t{\n\t\t\t\tverdict = dpi_desync_udp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis);\n\t\t\t\tverdict_udp_csum_fix(verdict, dis.udp, dis.transport_len, dis.ip, dis.ip6);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t*len_pkt = dis.len_pkt;\n\t}\n\treturn verdict;\n}\nuint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt)\n{\n\tipcachePurgeRateLimited(&params.ipcache, params.ipcache_lifetime);\n\treturn dpi_desync_packet_play(false, 0, fwmark, ifin, ifout, data_pkt, len_pkt);\n}\n\n\n\nstatic bool replay_queue(struct rawpacket_tailhead *q)\n{\n\tstruct rawpacket *rp;\n\tsize_t offset;\n\tunsigned int i;\n\tbool b = true;\n\tfor (i = 1, offset = 0; (rp = rawpacket_dequeue(q)); offset += rp->len_payload, rawpacket_free(rp), i++)\n\t{\n\t\tDLOG(\"REPLAYING delayed packet #%u offset %zu\\n\", i, offset);\n\t\tuint8_t verdict = dpi_desync_packet_play(true, offset, rp->fwmark, rp->ifin, rp->ifout, rp->packet, &rp->len);\n\t\tswitch (verdict & VERDICT_MASK)\n\t\t{\n\t\tcase VERDICT_MODIFY:\n\t\t\tDLOG(\"SENDING delayed packet #%u modified\\n\", i);\n\t\t\tb &= rawsend_rp(rp);\n\t\t\tbreak;\n\t\tcase VERDICT_PASS:\n\t\t\tDLOG(\"SENDING delayed packet #%u unmodified\\n\", i);\n\t\t\tb &= rawsend_rp(rp);\n\t\t\tbreak;\n\t\tcase VERDICT_DROP:\n\t\t\tDLOG(\"DROPPING delayed packet #%u\\n\", i);\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn b;\n}\n"
  },
  {
    "path": "nfq/desync.h",
    "content": "#pragma once\n\n#include \"darkmagic.h\"\n\n#include <stdint.h>\n#include <stdbool.h>\n\n#define __FAVOR_BSD\n#include <netinet/ip.h>\n#include <netinet/ip6.h>\n#include <netinet/tcp.h>\n#include <netinet/in.h>\n\n#ifdef __linux__\n#define DPI_DESYNC_FWMARK_DEFAULT 0x40000000\n#else\n#define DPI_DESYNC_FWMARK_DEFAULT 512\n#endif\n\n#define DPI_DESYNC_MAX_FAKE_LEN 9216\n\nenum dpi_desync_mode {\n\tDESYNC_NONE=0,\n\tDESYNC_INVALID,\n\tDESYNC_FAKE,\n\tDESYNC_FAKE_KNOWN,\n\tDESYNC_RST,\n\tDESYNC_RSTACK,\n\tDESYNC_SYNACK,\n\tDESYNC_SYNDATA,\n\tDESYNC_FAKEDSPLIT,\n\tDESYNC_FAKEDDISORDER,\n\tDESYNC_MULTISPLIT,\n\tDESYNC_MULTIDISORDER,\n\tDESYNC_HOSTFAKESPLIT,\n\tDESYNC_IPFRAG2,\n\tDESYNC_HOPBYHOP,\n\tDESYNC_DESTOPT,\n\tDESYNC_IPFRAG1,\n\tDESYNC_UDPLEN,\n\tDESYNC_TAMPER\n};\n\nextern const char *fake_http_request_default;\nextern const uint8_t fake_tls_clienthello_default[680];\nvoid randomize_default_tls_payload(uint8_t *p);\n\nenum dpi_desync_mode desync_mode_from_string(const char *s);\nbool desync_valid_zero_stage(enum dpi_desync_mode mode);\nbool desync_valid_first_stage(enum dpi_desync_mode mode);\nbool desync_only_first_stage(enum dpi_desync_mode mode);\nbool desync_valid_second_stage(enum dpi_desync_mode mode);\nbool desync_valid_second_stage_tcp(enum dpi_desync_mode mode);\nbool desync_valid_second_stage_udp(enum dpi_desync_mode mode);\n\nuint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt);\n"
  },
  {
    "path": "nfq/gzip.c",
    "content": "#include \"gzip.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define ZCHUNK 16384\n#define BUFMIN 128\n#define BUFCHUNK (1024*128)\n\nint z_readfile(FILE *F, char **buf, size_t *size)\n{\n\tz_stream zs;\n\tint r;\n\tunsigned char in[ZCHUNK];\n\tsize_t bufsize;\n\tvoid *newbuf;\n\n\tmemset(&zs, 0, sizeof(zs));\n\n\t*buf = NULL;\n\tbufsize = *size = 0;\n\n\tr = inflateInit2(&zs, 47);\n\tif (r != Z_OK)  return r;\n\n\tdo\n\t{\n\t\tzs.avail_in = fread(in, 1, sizeof(in), F);\n\t\tif (ferror(F))\n\t\t{\n\t\t\tr = Z_ERRNO;\n\t\t\tgoto zerr;\n\t\t}\n\t\tif (!zs.avail_in) break;\n\t\tzs.next_in = in;\n\t\tdo\n\t\t{\n\t\t\tif ((bufsize - *size) < BUFMIN)\n\t\t\t{\n\t\t\t\tbufsize += BUFCHUNK;\n\t\t\t\tnewbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize);\n\t\t\t\tif (!newbuf)\n\t\t\t\t{\n\t\t\t\t\tr = Z_MEM_ERROR;\n\t\t\t\t\tgoto zerr;\n\t\t\t\t}\n\t\t\t\t*buf = newbuf;\n\t\t\t}\n\t\t\tzs.avail_out = bufsize - *size;\n\t\t\tzs.next_out = (unsigned char*)(*buf + *size);\n\t\t\tr = inflate(&zs, Z_NO_FLUSH);\n\t\t\tif (r != Z_OK && r != Z_STREAM_END) goto zerr;\n\t\t\t*size = bufsize - zs.avail_out;\n\t\t} while (r == Z_OK && zs.avail_in);\n\t} while (r == Z_OK);\n\n\tif (*size < bufsize)\n\t{\n\t\t// free extra space\n\t\tif ((newbuf = realloc(*buf, *size))) *buf = newbuf;\n\t}\n\n\tinflateEnd(&zs);\n\treturn Z_OK;\n\nzerr:\n\tinflateEnd(&zs);\n\tfree(*buf);\n\t*buf = NULL;\n\treturn r;\n}\n\nbool is_gzip(FILE* F)\n{\n\tunsigned char magic[2];\n\tbool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B;\n\tfseek(F, 0, SEEK_SET);\n\treturn b;\n}\n"
  },
  {
    "path": "nfq/gzip.h",
    "content": "#pragma once\n\n#include <stdio.h>\n#include <zlib.h>\n#include <stdbool.h>\n\nint z_readfile(FILE *F,char **buf,size_t *size);\nbool is_gzip(FILE* F);\n"
  },
  {
    "path": "nfq/helpers.c",
    "content": "#define _GNU_SOURCE\n\n#include \"helpers.h\"\n\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include <sys/stat.h>\n#include <libgen.h>\n#include <fcntl.h>\n\nint unique_size_t(size_t *pu, int ct)\n{\n\tsize_t i, j, u;\n\tfor (i = j = 0; j < ct; i++)\n\t{\n\t\tu = pu[j++];\n\t\tfor (; j < ct && pu[j] == u; j++);\n\t\tpu[i] = u;\n\t}\n\treturn i;\n}\nstatic int cmp_size_t(const void * a, const void * b)\n{\n\treturn *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b;\n}\nvoid qsort_size_t(size_t *array,size_t ct)\n{\n\tqsort(array,ct,sizeof(*array),cmp_size_t);\n}\n\n\nvoid rtrim(char *s)\n{\n\tif (s)\n\t\tfor (char *p = s + strlen(s) - 1; p >= s && (*p == '\\n' || *p == '\\r'); p--) *p = '\\0';\n}\n\nvoid replace_char(char *s, char from, char to)\n{\n\tfor(;*s;s++) if (*s==from) *s=to;\n}\n\nchar *strncasestr(const char *s, const char *find, size_t slen)\n{\n\tchar c, sc;\n\tsize_t len;\n\n\tif ((c = *find++) != '\\0')\n\t{\n\t\tlen = strlen(find);\n\t\tdo\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif (slen-- < 1 || (sc = *s++) == '\\0') return NULL;\n\t\t\t} while (toupper(c) != toupper(sc));\n\t\t\tif (len > slen)\treturn NULL;\n\t\t} while (strncasecmp(s, find, len) != 0);\n\t\ts--;\n\t}\n\treturn (char *)s;\n}\n\n\nbool load_file(const char *filename, void *buffer, size_t *buffer_size)\n{\n\tFILE *F;\n\n\tF = fopen(filename, \"rb\");\n\tif (!F) return false;\n\n\t*buffer_size = fread(buffer, 1, *buffer_size, F);\n\tif (ferror(F))\n\t{\n\t\tfclose(F);\n\t\treturn false;\n\t}\n\n\tfclose(F);\n\treturn true;\n}\nbool load_file_nonempty(const char *filename, void *buffer, size_t *buffer_size)\n{\n\tbool b = load_file(filename, buffer, buffer_size);\n\treturn b && *buffer_size;\n}\nbool save_file(const char *filename, const void *buffer, size_t buffer_size)\n{\n\tFILE *F;\n\n\tF = fopen(filename, \"wb\");\n\tif (!F) return false;\n\n\tfwrite(buffer, 1, buffer_size, F);\n\tif (ferror(F))\n\t{\n\t\tfclose(F);\n\t\treturn false;\n\t}\n\n\tfclose(F);\n\treturn true;\n}\nbool append_to_list_file(const char *filename, const char *s)\n{\n\tFILE *F = fopen(filename,\"at\");\n\tif (!F) return false;\n\tbool bOK = fprintf(F,\"%s\\n\",s)>0;\n\tfclose(F);\n\treturn bOK;\n}\n\nvoid expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen)\n{\n\tunsigned int target_bitlen = target_bytelen<<3;\n\tunsigned int bitlen = target_bitlen<source_bitlen ? target_bitlen : source_bitlen;\n\tunsigned int bytelen = bitlen>>3;\n\n\tif ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen);\n\tmemcpy(target,source,bytelen);\n\tif ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1));\n}\n\n// \"       [fd00::1]\" => \"fd00::1\"\n// \"[fd00::1]:8000\" => \"fd00::1\"\n// \"127.0.0.1\" => \"127.0.0.1\"\n// \" 127.0.0.1:8000\" => \"127.0.0.1\"\n// \" vk.com:8000\" => \"vk.com\"\n// return value:  true - host is ip addr\nbool strip_host_to_ip(char *host)\n{\n\tsize_t l;\n\tchar *h,*p;\n\tuint8_t addr[16];\n\n\tfor (h = host ; *h==' ' || *h=='\\t' ; h++);\n\tl = strlen(h);\n\tif (l>=2)\n\t{\n\t\tif (*h=='[')\n\t\t{\n\t\t\t// ipv6 ?\n\t\t\tfor (p=++h ; *p && *p!=']' ;  p++);\n\t\t\tif (*p==']')\n\t\t\t{\n\t\t\t\tl = p-h;\n\t\t\t\tmemmove(host,h,l);\n\t\t\t\thost[l]=0;\n\t\t\t\treturn inet_pton(AF_INET6, host, addr)>0;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (inet_pton(AF_INET6, h, addr)>0)\n\t\t\t{\n\t\t\t\t// ipv6 ?\n\t\t\t\tif (host!=h)\n\t\t\t\t{\n\t\t\t\t\tl = strlen(h);\n\t\t\t\t\tmemmove(host,h,l);\n\t\t\t\t\thost[l]=0;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// ipv4 ?\n\t\t\t\tfor (p=h ; *p && *p!=':' ;  p++);\n\t\t\t\tl = p-h;\n\t\t\t\tif (host!=h) memmove(host,h,l);\n\t\t\t\thost[l]=0;\n\t\t\t\treturn inet_pton(AF_INET, host, addr)>0;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nvoid ntop46(const struct sockaddr *sa, char *str, size_t len)\n{\n\tif (!len) return;\n\t*str = 0;\n\tswitch (sa->sa_family)\n\t{\n\tcase AF_INET:\n\t\tinet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len);\n\t\tbreak;\n\tcase AF_INET6:\n\t\tinet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len);\n\t\tbreak;\n\tdefault:\n\t\tsnprintf(str, len, \"UNKNOWN_FAMILY_%d\", sa->sa_family);\n\t}\n}\nvoid ntop46_port(const struct sockaddr *sa, char *str, size_t len)\n{\n\tchar ip[40];\n\tntop46(sa, ip, sizeof(ip));\n\tswitch (sa->sa_family)\n\t{\n\tcase AF_INET:\n\t\tsnprintf(str, len, \"%s:%u\", ip, ntohs(((struct sockaddr_in*)sa)->sin_port));\n\t\tbreak;\n\tcase AF_INET6:\n\t\tsnprintf(str, len, \"[%s]:%u\", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port));\n\t\tbreak;\n\tdefault:\n\t\tsnprintf(str, len, \"%s\", ip);\n\t}\n}\nvoid print_sockaddr(const struct sockaddr *sa)\n{\n\tchar ip_port[48];\n\n\tntop46_port(sa, ip_port, sizeof(ip_port));\n\tprintf(\"%s\", ip_port);\n}\n\nbool pton4_port(const char *s, struct sockaddr_in *sa)\n{\n\tchar ip[16],*p;\n\tsize_t l;\n\tunsigned int u;\n\n\tp = strchr(s,':');\n\tif (!p) return false;\n\tl = p-s;\n\tif (l<7 || l>15) return false;\n\tmemcpy(ip,s,l);\n\tip[l]=0;\n\tp++;\n\n\tsa->sin_family = AF_INET;\n\tif (inet_pton(AF_INET,ip,&sa->sin_addr)!=1 || sscanf(p,\"%u\",&u)!=1 || !u || u>0xFFFF) return false;\n\tsa->sin_port = htons((uint16_t)u);\n\t\n\treturn true;\n}\nbool pton6_port(const char *s, struct sockaddr_in6 *sa)\n{\n\tchar ip[40],*p;\n\tsize_t l;\n\tunsigned int u;\n\n\tif (*s++!='[') return false;\n\tp = strchr(s,']');\n\tif (!p || p[1]!=':') return false;\n\tl = p-s;\n\tif (l<2 || l>39) return false;\n\tp+=2;\n\tmemcpy(ip,s,l);\n\tip[l]=0;\n\n\tsa->sin6_family = AF_INET6;\n\tif (inet_pton(AF_INET6,ip,&sa->sin6_addr)!=1 || sscanf(p,\"%u\",&u)!=1 || !u || u>0xFFFF) return false;\n\tsa->sin6_port = htons((uint16_t)u);\n\tsa->sin6_flowinfo = 0;\n\tsa->sin6_scope_id = 0;\n\t\n\treturn true;\n}\n\nuint16_t saport(const struct sockaddr *sa)\n{\n\treturn htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port :\n\t\t     sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0);\n}\n\n\nuint64_t pntoh64(const void *p)\n{\n\treturn (uint64_t)*((const uint8_t *)(p)+0) << 56 |\n\t\t(uint64_t)*((const uint8_t *)(p)+1) << 48 |\n\t\t(uint64_t)*((const uint8_t *)(p)+2) << 40 |\n\t\t(uint64_t)*((const uint8_t *)(p)+3) << 32 |\n\t\t(uint64_t)*((const uint8_t *)(p)+4) << 24 |\n\t\t(uint64_t)*((const uint8_t *)(p)+5) << 16 |\n\t\t(uint64_t)*((const uint8_t *)(p)+6) << 8 |\n\t\t(uint64_t)*((const uint8_t *)(p)+7) << 0;\n}\nvoid phton64(uint8_t *p, uint64_t v)\n{\n\tp[0] = (uint8_t)(v >> 56);\n\tp[1] = (uint8_t)(v >> 48);\n\tp[2] = (uint8_t)(v >> 40);\n\tp[3] = (uint8_t)(v >> 32);\n\tp[4] = (uint8_t)(v >> 24);\n\tp[5] = (uint8_t)(v >> 16);\n\tp[6] = (uint8_t)(v >> 8);\n\tp[7] = (uint8_t)(v >> 0);\n}\n\nbool seq_within(uint32_t s, uint32_t s1, uint32_t s2)\n{\n\treturn (s2>=s1 && s>=s1 && s<=s2) || (s2<s1 && (s<=s2 || s>=s1));\n}\n\nbool ipv6_addr_is_zero(const struct in6_addr *a)\n{\n    return !memcmp(a,\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",16);\n}\n\n\n#define INVALID_HEX_DIGIT ((uint8_t)-1)\nstatic inline uint8_t parse_hex_digit(char c)\n{\n\treturn (c>='0' && c<='9') ? c-'0' : (c>='a' && c<='f') ? c-'a'+0xA : (c>='A' && c<='F') ? c-'A'+0xA : INVALID_HEX_DIGIT;\n}\nstatic inline bool parse_hex_byte(const char *s, uint8_t *pbyte)\n{\n\tuint8_t u,l;\n\tu = parse_hex_digit(s[0]);\n\tl = parse_hex_digit(s[1]);\n\tif (u==INVALID_HEX_DIGIT || l==INVALID_HEX_DIGIT)\n\t{\n\t\t*pbyte=0;\n\t\treturn false;\n\t}\n\telse\n\t{\n\t\t*pbyte=(u<<4) | l;\n\t\treturn true;\n\t}\n}\nbool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size)\n{\n\tuint8_t *pe = pbuf+*size;\n\t*size=0;\n\twhile(pbuf<pe && *s)\n\t{\n\t\tif (!parse_hex_byte(s,pbuf))\n\t\t\treturn false;\n\t\tpbuf++; s+=2; (*size)++;\n\t}\n\treturn true;\n}\n\nvoid fill_pattern(uint8_t *buf,size_t bufsize,const void *pattern,size_t patsize,size_t offset)\n{\n\tsize_t size;\n\n\tif (offset%=patsize)\n\t{\n\t\tsize = patsize-offset;\n\t\tsize = bufsize>size ? size : bufsize;\n\t\tmemcpy(buf,pattern+offset,size);\n\t\tbuf += size;\n\t\tbufsize -= size;\n\t}\n\twhile (bufsize)\n\t{\n\t\tsize = bufsize>patsize ? patsize : bufsize;\n\t\tmemcpy(buf,pattern,size);\n\t\tbuf += size;\n\t\tbufsize -= size;\n\t}\n}\n\nint fprint_localtime(FILE *F)\n{\n\tstruct tm t;\n\ttime_t now;\n\n\ttime(&now);\n\tlocaltime_r(&now,&t);\n\treturn fprintf(F, \"%02d.%02d.%04d %02d:%02d:%02d\", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec);\n}\n\ntime_t file_mod_time(const char *filename)\n{\n\tstruct stat st;\n\treturn stat(filename,&st)==-1 ? 0 : st.st_mtime;\n}\nbool file_mod_signature(const char *filename, file_mod_sig *ms)\n{\n\tstruct stat st;\n\tif (stat(filename,&st)==-1)\n\t{\n\t\tFILE_MOD_RESET(ms);\n\t\treturn false;\n\t}\n\tms->mod_time=st.st_mtime;\n\tms->size=st.st_size;\n\treturn true;\n}\n\nbool file_open_test(const char *filename, int flags)\n{\n\tint fd = open(filename,flags);\n\tif (fd>=0)\n\t{\n\t\tclose(fd);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nbool pf_in_range(uint16_t port, const port_filter *pf)\n{\n\treturn port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg);\n}\nbool pf_parse(const char *s, port_filter *pf)\n{\n\tunsigned int v1,v2;\n\tchar c;\n\n\tif (!s) return false;\n\tif (*s=='*' && s[1]==0)\n\t{\n\t\tpf->from=1; pf->to=0xFFFF;\n\t\treturn true;\n\t}\n\tif (*s=='~')\n\t{\n\t\tpf->neg=true;\n\t\ts++;\n\t}\n\telse\n\t\tpf->neg=false;\n\tif (sscanf(s,\"%u-%u%c\",&v1,&v2,&c)==2)\n\t{\n\t\tif (v1>65535 || v2>65535 || v1>v2) return false;\n\t\tpf->from=(uint16_t)v1;\n\t\tpf->to=(uint16_t)v2;\n\t}\n\telse if (sscanf(s,\"%u%c\",&v1,&c)==1)\n\t{\n\t\tif (v1>65535) return false;\n\t\tpf->to=pf->from=(uint16_t)v1;\n\t}\n\telse\n\t\treturn false;\n\t// deny all case\n\tif (!pf->from && !pf->to) pf->neg=true;\n\treturn true;\n}\nbool pf_is_empty(const port_filter *pf)\n{\n\treturn !pf->neg && !pf->from && !pf->to;\n}\n\nvoid fill_random_bytes(uint8_t *p,size_t sz)\n{\n\tsize_t k,sz16 = sz>>1;\n\tfor(k=0;k<sz16;k++) ((uint16_t*)p)[k]=(uint16_t)random();\n\tif (sz & 1) p[sz-1]=(uint8_t)random();\n}\nvoid fill_random_az(uint8_t *p,size_t sz)\n{\n\tsize_t k;\n\tfor(k=0;k<sz;k++) p[k] = 'a'+(random() % ('z'-'a'));\n}\nvoid fill_random_az09(uint8_t *p,size_t sz)\n{\n\tsize_t k;\n\tuint8_t rnd;\n\tfor(k=0;k<sz;k++)\n\t{\n\t\trnd = random() % (10 + 'z'-'a'+1);\n\t\tp[k] = rnd<10 ? rnd+'0' : 'a'+rnd-10;\n\t}\n}\n\nvoid set_console_io_buffering(void)\n{\n\tsetvbuf(stdout, NULL, _IOLBF, 0);\n\tsetvbuf(stderr, NULL, _IOLBF, 0);\n}\n\nbool set_env_exedir(const char *argv0)\n{\n\tchar *s,*d;\n\tbool bOK=false;\n\tif ((s = strdup(argv0)))\n\t{\n\t\tif ((d = dirname(s)))\n\t\t\tbOK = !setenv(\"EXEDIR\",d,1);\n\t\tfree(s);\n\t}\n\treturn bOK;\n}\n\n\nvoid str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr)\n{\n\tchar s_ip[16];\n\t*s_ip=0;\n\tinet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip));\n\tsnprintf(s,s_len,cidr->preflen<32 ? \"%s/%u\" : \"%s\", s_ip, cidr->preflen);\n}\nvoid print_cidr4(const struct cidr4 *cidr)\n{\n\tchar s[19];\n\tstr_cidr4(s,sizeof(s),cidr);\n\tprintf(\"%s\",s);\n}\nvoid str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr)\n{\n\tchar s_ip[40];\n\t*s_ip=0;\n\tinet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip));\n\tsnprintf(s,s_len,cidr->preflen<128 ? \"%s/%u\" : \"%s\", s_ip, cidr->preflen);\n}\nvoid print_cidr6(const struct cidr6 *cidr)\n{\n\tchar s[44];\n\tstr_cidr6(s,sizeof(s),cidr);\n\tprintf(\"%s\",s);\n}\nbool parse_cidr4(char *s, struct cidr4 *cidr)\n{\n\tchar *p,d;\n\tbool b;\n\tunsigned int plen;\n\n\tif ((p = strchr(s, '/')))\n\t{\n\t\tif (sscanf(p + 1, \"%u\", &plen)!=1 || plen>32)\n\t\t\treturn false;\n\t\tcidr->preflen = (uint8_t)plen;\n\t\td=*p; *p=0; // backup char\n\t}\n\telse\n\t\tcidr->preflen = 32;\n\tb = (inet_pton(AF_INET, s, &cidr->addr)==1);\n\tif (p) *p=d; // restore char\n\treturn b;\n}\nbool parse_cidr6(char *s, struct cidr6 *cidr)\n{\n\tchar *p,d;\n\tbool b;\n\tunsigned int plen;\n\n\tif ((p = strchr(s, '/')))\n\t{\n\t\tif (sscanf(p + 1, \"%u\", &plen)!=1 || plen>128)\n\t\t\treturn false;\n\t\tcidr->preflen = (uint8_t)plen;\n\t\td=*p; *p=0; // backup char\n\t}\n\telse\n\t\tcidr->preflen = 128;\n\tb = (inet_pton(AF_INET6, s, &cidr->addr)==1);\n\tif (p) *p=d; // restore char\n\treturn b;\n}\n"
  },
  {
    "path": "nfq/helpers.h",
    "content": "#pragma once\n\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <stddef.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <time.h>\n\n#define UNARY_PLUS(v) (v>0 ? \"+\" : \"\")\n\n// this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes.\ntypedef union\n{\n\tstruct sockaddr_in sa4;\t\t// size 16\n\tstruct sockaddr_in6 sa6;\t// size 28\n\tchar _align[32];\t\t// force 16-byte alignment for ip6_and int128 ops\n} sockaddr_in46;\n\nint unique_size_t(size_t *pu, int ct);\nvoid qsort_size_t(size_t *array,size_t ct);\n\nvoid rtrim(char *s);\nvoid replace_char(char *s, char from, char to);\nchar *strncasestr(const char *s,const char *find, size_t slen);\n\nbool load_file(const char *filename,void *buffer,size_t *buffer_size);\nbool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size);\nbool save_file(const char *filename, const void *buffer, size_t buffer_size);\nbool append_to_list_file(const char *filename, const char *s);\n\nvoid expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen);\n\nbool strip_host_to_ip(char *host);\n\nvoid print_sockaddr(const struct sockaddr *sa);\nvoid ntop46(const struct sockaddr *sa, char *str, size_t len);\nvoid ntop46_port(const struct sockaddr *sa, char *str, size_t len);\nbool pton4_port(const char *s, struct sockaddr_in *sa);\nbool pton6_port(const char *s, struct sockaddr_in6 *sa);\n\nuint16_t saport(const struct sockaddr *sa);\n\nbool seq_within(uint32_t s, uint32_t s1, uint32_t s2);\n\nuint64_t pntoh64(const void *p);\nvoid phton64(uint8_t *p, uint64_t v);\n\nbool ipv6_addr_is_zero(const struct in6_addr *a);\n\nstatic inline uint16_t pntoh16(const uint8_t *p) {\n\treturn ((uint16_t)p[0] << 8) | (uint16_t)p[1];\n}\nstatic inline void phton16(uint8_t *p, uint16_t v) {\n\tp[0] = (uint8_t)(v >> 8);\n\tp[1] = v & 0xFF;\n}\nstatic inline uint32_t pntoh24(const uint8_t *p) {\n\treturn ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2];\n}\nstatic inline void phton24(uint8_t *p, uint32_t v) {\n\tp[0] = (uint8_t)(v>>16);\n\tp[1] = (uint8_t)(v>>8);\n\tp[2] = (uint8_t)v;\n}\nstatic inline uint32_t pntoh32(const uint8_t *p) {\n\treturn ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];\n}\n\nbool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size);\nvoid fill_pattern(uint8_t *buf,size_t bufsize,const void *pattern,size_t patsize,size_t offset);\n\nint fprint_localtime(FILE *F);\n\ntypedef struct\n{\n\ttime_t mod_time;\n\toff_t size;\n} file_mod_sig;\n#define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size))\n#define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig))\nbool file_mod_signature(const char *filename, file_mod_sig *ms);\ntime_t file_mod_time(const char *filename);\nbool file_open_test(const char *filename, int flags);\n\ntypedef struct\n{\n\tuint16_t from,to;\n\tbool neg;\n} port_filter;\nbool pf_in_range(uint16_t port, const port_filter *pf);\nbool pf_parse(const char *s, port_filter *pf);\nbool pf_is_empty(const port_filter *pf);\n\nvoid fill_random_bytes(uint8_t *p,size_t sz);\nvoid fill_random_az(uint8_t *p,size_t sz);\nvoid fill_random_az09(uint8_t *p,size_t sz);\n\nvoid set_console_io_buffering(void);\nbool set_env_exedir(const char *argv0);\n\n\nstruct cidr4\n{\n\tstruct in_addr addr;\n\tuint8_t\tpreflen;\n};\nstruct cidr6\n{\n\tstruct in6_addr addr;\n\tuint8_t\tpreflen;\n};\nvoid str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr);\nvoid print_cidr4(const struct cidr4 *cidr);\nvoid str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr);\nvoid print_cidr6(const struct cidr6 *cidr);\nbool parse_cidr4(char *s, struct cidr4 *cidr);\nbool parse_cidr6(char *s, struct cidr6 *cidr);\n"
  },
  {
    "path": "nfq/hostlist.c",
    "content": "#include <stdio.h>\n#include \"hostlist.h\"\n#include \"gzip.h\"\n#include \"helpers.h\"\n\n// inplace tolower() and add to pool\nstatic bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct)\n{\n\tchar *p=*s;\n\n\t// comment line\n\tif ( *p == '#' || *p == ';' || *p == '/' || *p == '\\r' || *p == '\\n')\n\t{\n\t\t// advance until eol\n\t\tfor (; p<end && *p && *p!='\\r' && *p != '\\n'; p++);\n\t}\n\telse\n\t{\n\t\t// advance until eol lowering all chars\n\t\tuint32_t flags = 0;\n\t\tif (*p=='^')\n\t\t{\n\t\t\tp = ++(*s);\n\t\t\tflags |= HOSTLIST_POOL_FLAG_STRICT_MATCH;\n\t\t}\n\t\tfor (; p<end && *p && *p!='\\r' && *p != '\\n'; p++) *p=tolower(*p);\n\t\tif (!HostlistPoolAddStrLen(hostlist, *s, p-*s, flags))\n\t\t{\n\t\t\tHostlistPoolDestroy(hostlist);\n\t\t\t*hostlist = NULL;\n\t\t\treturn false;\n\t\t}\n\t\tif (ct) (*ct)++;\n\t}\n\t// advance to the next line\n\tfor (; p<end && (!*p || *p=='\\r' || *p=='\\n') ; p++);\n\t*s = p;\n\treturn true;\n}\n\nbool AppendHostlistItem(hostlist_pool **hostlist, char *s)\n{\n\treturn addpool(hostlist,&s,s+strlen(s),NULL);\n}\n\nbool AppendHostList(hostlist_pool **hostlist, const char *filename)\n{\n\tchar *p, *e, s[256], *zbuf;\n\tsize_t zsize;\n\tint ct = 0;\n\tFILE *F;\n\tint r;\n\n\tDLOG_CONDUP(\"Loading hostlist %s\\n\",filename);\n\n\tif (!(F = fopen(filename, \"rb\")))\n\t{\n\t\tDLOG_ERR(\"Could not open %s\\n\", filename);\n\t\treturn false;\n\t}\n\n\tif (is_gzip(F))\n\t{\n\t\tr = z_readfile(F,&zbuf,&zsize);\n\t\tfclose(F);\n\t\tif (r==Z_OK)\n\t\t{\n\t\t\tDLOG_CONDUP(\"zlib compression detected. uncompressed size : %zu\\n\", zsize);\n\n\t\t\tp = zbuf;\n\t\t\te = zbuf + zsize;\n\t\t\twhile(p<e)\n\t\t\t{\n\t\t\t\tif (!addpool(hostlist,&p,e,&ct))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Not enough memory to store host list : %s\\n\", filename);\n\t\t\t\t\tfree(zbuf);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(zbuf);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG_ERR(\"zlib decompression failed : result %d\\n\",r);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDLOG_CONDUP(\"loading plain text list\\n\");\n\n\t\twhile (fgets(s, sizeof(s), F))\n\t\t{\n\t\t\tp = s;\n\t\t\tif (!addpool(hostlist,&p,p+strlen(p),&ct))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Not enough memory to store host list : %s\\n\", filename);\n\t\t\t\tfclose(F);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tfclose(F);\n\t}\n\n\tDLOG_CONDUP(\"Loaded %d hosts from %s\\n\", ct, filename);\n\treturn true;\n}\n\nstatic bool LoadHostList(struct hostlist_file *hfile)\n{\n\tif (hfile->filename)\n\t{\n\t\tfile_mod_sig fsig;\n\t\tif (!file_mod_signature(hfile->filename, &fsig))\n\t\t{\n\t\t\t// stat() error\n\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t\tDLOG_ERR(\"cannot access hostlist file '%s'. in-memory content remains unchanged.\\n\",hfile->filename);\n\t\t\treturn true;\n\t\t}\n\t\tif (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date\n\t\tHostlistPoolDestroy(&hfile->hostlist);\n\t\tif (!AppendHostList(&hfile->hostlist, hfile->filename))\n\t\t{\n\t\t\tHostlistPoolDestroy(&hfile->hostlist);\n\t\t\treturn false;\n\t\t}\n\t\thfile->mod_sig=fsig;\n\t}\n\treturn true;\n}\nstatic bool LoadHostLists(struct hostlist_files_head *list)\n{\n\tbool bres=true;\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t{\n\t\tif (!LoadHostList(hfile))\n\t\t\t// at least one failed\n\t\t\tbres=false;\n\t}\n\treturn bres;\n}\n\nbool NonEmptyHostlist(hostlist_pool **hostlist)\n{\n\t// add impossible hostname if the list is empty\n\treturn *hostlist ? true : HostlistPoolAddStrLen(hostlist, \"@&()\", 4, 0);\n}\n\nstatic void MakeAutolistsNonEmpty()\n{\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tif (dpl->dp.hostlist_auto)\n\t\t\tNonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist);\n\t}\n}\n\nbool LoadAllHostLists()\n{\n\tif (!LoadHostLists(&params.hostlists))\n\t\treturn false;\n\tMakeAutolistsNonEmpty();\n\treturn true;\n}\n\n\n\nstatic bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains)\n{\n\tif (hostlist)\n\t{\n\t\tconst char *p = host;\n\t\tconst struct hostlist_pool *hp;\n\t\tbool bHostFull=true;\n\t\twhile (p)\n\t\t{\n\t\t\tDLOG(\"hostlist check for %s : \", p);\n\t\t\thp = HostlistPoolGetStr(hostlist, p);\n\t\t\tif (hp)\n\t\t\t{\n\t\t\t\tif ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"negative : strict_mismatch : %s != %s\\n\", p, host);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"positive\\n\");\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tDLOG(\"negative\\n\");\n\t\t\tif (no_match_subdomains) break;\n\t\t\tp = strchr(p, '.');\n\t\t\tif (p) p++;\n\t\t\tbHostFull = false;\n\t\t}\n\t}\n\treturn false;\n}\n\n\nstatic bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists)\n{\n\tstruct hostlist_item *item;\n\tLIST_FOREACH(item, hostlists, next)\n\t{\n\t\tif (!LoadHostList(item->hfile))\n\t\t\treturn false;\n\t}\n\tMakeAutolistsNonEmpty();\n\treturn true;\n}\nbool HostlistsReloadCheckForProfile(const struct desync_profile *dp)\n{\n\treturn HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude);\n}\n// return : true = apply fooling, false = do not apply\nstatic bool HostlistCheck_(const struct hostlist_collection_head *hostlists, const struct hostlist_collection_head *hostlists_exclude, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck)\n{\n\tstruct hostlist_item *item;\n\n\tif (excluded) *excluded = false;\n\n\tif (!bSkipReloadCheck)\n\t\tif (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude))\n\t\t\treturn false;\n\n\tLIST_FOREACH(item, hostlists_exclude, next)\n\t{\n\t\tDLOG(\"[%s] exclude \", item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\tif (SearchHostList(item->hfile->hostlist, host, no_match_subdomains))\n\t\t{\n\t\t\tif (excluded) *excluded = true;\n\t\t\treturn false;\n\t\t}\n\t}\n\t// old behavior compat: all include lists are empty means check passes\n\tif (!hostlist_collection_is_empty(hostlists))\n\t{\n\t\tLIST_FOREACH(item, hostlists, next)\n\t\t{\n\t\t\tDLOG(\"[%s] include \", item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\t\tif (SearchHostList(item->hfile->hostlist, host, no_match_subdomains))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n// return : true = apply fooling, false = do not apply\nbool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck)\n{\n\tDLOG(\"* hostlist check for profile %d\\n\",dp->n);\n\treturn HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck);\n}\n\n\nstatic struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename)\n{\n\tstruct hostlist_file *hfile;\n\n\tif (filename)\n\t{\n\t\tif (!(hfile=hostlist_files_search(hostlists, filename)))\n\t\t\tif (!(hfile=hostlist_files_add(hostlists, filename)))\n\t\t\t\treturn NULL;\n\t\tif (!hostlist_collection_search(hl_collection, filename))\n\t\t\tif (!hostlist_collection_add(hl_collection, hfile))\n\t\t\t\treturn NULL;\n\t}\n\telse\n\t{\n\t\tif (!(hfile=hostlist_files_add(hostlists, NULL)))\n\t\t\treturn NULL;\n\t\tif (!hostlist_collection_add(hl_collection, hfile))\n\t\t\treturn NULL;\n\t}\n\n\treturn hfile;\n}\nstruct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename)\n{\n/*\n\tif (filename && !file_mod_time(filename))\n\t{\n\t\tDLOG_ERR(\"cannot access hostlist file '%s'\\n\",filename);\n\t\treturn NULL;\n\t}\n*/\n\treturn RegisterHostlist_(\n\t\t&params.hostlists,\n\t\tbExclude ? &dp->hl_collection_exclude : &dp->hl_collection,\n\t\tfilename);\n}\n\nvoid HostlistsDebug()\n{\n\tif (!params.debug) return;\n\n\tstruct hostlist_file *hfile;\n\tstruct desync_profile_list *dpl;\n\tstruct hostlist_item *hl_item;\n\n\tLIST_FOREACH(hfile, &params.hostlists, next)\n\t{\n\t\tif (hfile->filename)\n\t\t\tDLOG(\"hostlist file %s%s\\n\",hfile->filename,hfile->hostlist ? \"\" : \" (empty)\");\n\t\telse\n\t\t\tDLOG(\"hostlist fixed%s\\n\",hfile->hostlist ? \"\" : \" (empty)\");\n\t}\n\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tLIST_FOREACH(hl_item, &dpl->dp.hl_collection, next)\n\t\t\tif (hl_item->hfile!=dpl->dp.hostlist_auto)\n\t\t\t{\n\t\t\t\tif (hl_item->hfile->filename)\n\t\t\t\t\tDLOG(\"profile %d include hostlist %s%s\\n\",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\t\telse\n\t\t\t\t\tDLOG(\"profile %d include fixed hostlist%s\\n\",dpl->dp.n, hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\t}\n\t\tLIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next)\n\t\t{\n\t\t\tif (hl_item->hfile->filename)\n\t\t\t\tDLOG(\"profile %d exclude hostlist %s%s\\n\",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\telse\n\t\t\t\tDLOG(\"profile %d exclude fixed hostlist%s\\n\",dpl->dp.n,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t}\n\t\tif (dpl->dp.hostlist_auto)\n\t\t\tDLOG(\"profile %d auto hostlist %s%s\\n\",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? \"\" : \" (empty)\");\n\t}\n}\n"
  },
  {
    "path": "nfq/hostlist.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include \"pools.h\"\n#include \"params.h\"\n\nbool AppendHostlistItem(hostlist_pool **hostlist, char *s);\nbool AppendHostList(hostlist_pool **hostlist, const char *filename);\nbool LoadAllHostLists();\nbool NonEmptyHostlist(hostlist_pool **hostlist);\n// return : true = apply fooling, false = do not apply\nbool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck);\nstruct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename);\nbool HostlistsReloadCheckForProfile(const struct desync_profile *dp);\nvoid HostlistsDebug();\n\n#define ResetAllHostlistsModTime() hostlist_files_reset_modtime(&params.hostlists)\n"
  },
  {
    "path": "nfq/ipset.c",
    "content": "#include <stdio.h>\n#include \"ipset.h\"\n#include \"gzip.h\"\n#include \"helpers.h\"\n\n\n// inplace tolower() and add to pool\nstatic bool addpool(ipset *ips, char **s, const char *end, int *ct)\n{\n\tchar *p, cidr[128];\n\tsize_t l;\n\tstruct cidr4 c4;\n\tstruct cidr6 c6;\n\n\t// advance until eol\n\tfor (p=*s; p<end && *p && *p!='\\r' && *p != '\\n'; p++);\n\n\t// comment line\n\tif (!(**s == '#' || **s == ';' || **s == '/' || **s == '\\r' || **s == '\\n' ))\n\t{\n\t\tl = p-*s;\n\t\tif (l>=sizeof(cidr)) l=sizeof(cidr)-1;\n\t\tmemcpy(cidr,*s,l);\n\t\tcidr[l]=0;\n\t\trtrim(cidr);\n\n\t\tif (parse_cidr4(cidr,&c4))\n\t\t{\n\t\t\tif (!ipset4AddCidr(&ips->ips4, &c4))\n\t\t\t{\n\t\t\t\tipsetDestroy(ips);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (ct) (*ct)++;\n\t\t}\n\t\telse if (parse_cidr6(cidr,&c6))\n\t\t{\n\t\t\tif (!ipset6AddCidr(&ips->ips6, &c6))\n\t\t\t{\n\t\t\t\tipsetDestroy(ips);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (ct) (*ct)++;\n\t\t}\n\t\telse\n\t\t\tDLOG_ERR(\"bad ip or subnet : %s\\n\",cidr);\n\t}\n\n\t// advance to the next line\n\tfor (; p<end && (!*p || *p=='\\r' || *p=='\\n') ; p++);\n\t*s = p;\n\treturn true;\n\n}\n\nbool AppendIpsetItem(ipset *ips, char *ip)\n{\n\treturn addpool(ips,&ip,ip+strlen(ip),NULL);\n}\n\nstatic bool AppendIpset(ipset *ips, const char *filename)\n{\n\tchar *p, *e, s[256], *zbuf;\n\tsize_t zsize;\n\tint ct = 0;\n\tFILE *F;\n\tint r;\n\n\tDLOG_CONDUP(\"Loading ipset %s\\n\",filename);\n\n\tif (!(F = fopen(filename, \"rb\")))\n\t{\n\t\tDLOG_ERR(\"Could not open %s\\n\", filename);\n\t\treturn false;\n\t}\n\n\tif (is_gzip(F))\n\t{\n\t\tr = z_readfile(F,&zbuf,&zsize);\n\t\tfclose(F);\n\t\tif (r==Z_OK)\n\t\t{\n\t\t\tDLOG_CONDUP(\"zlib compression detected. uncompressed size : %zu\\n\", zsize);\n\n\t\t\tp = zbuf;\n\t\t\te = zbuf + zsize;\n\t\t\twhile(p<e)\n\t\t\t{\n\t\t\t\tif (!addpool(ips,&p,e,&ct))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Not enough memory to store ipset : %s\\n\", filename);\n\t\t\t\t\tfree(zbuf);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(zbuf);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG_ERR(\"zlib decompression failed : result %d\\n\",r);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDLOG_CONDUP(\"loading plain text list\\n\");\n\n\t\twhile (fgets(s, sizeof(s)-1, F))\n\t\t{\n\t\t\tp = s;\n\t\t\tif (!addpool(ips,&p,p+strlen(p),&ct))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Not enough memory to store ipset : %s\\n\", filename);\n\t\t\t\tfclose(F);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tfclose(F);\n\t}\n\n\tDLOG_CONDUP(\"Loaded %d ip/subnets from %s\\n\", ct, filename);\n\treturn true;\n}\n\nstatic bool LoadIpset(struct ipset_file *hfile)\n{\n\tif (hfile->filename)\n\t{\n\t\tfile_mod_sig fsig;\n\t\tif (!file_mod_signature(hfile->filename, &fsig))\n\t\t{\n\t\t\t// stat() error\n\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t\tDLOG_ERR(\"cannot access ipset file '%s'. in-memory content remains unchanged.\\n\",hfile->filename);\n\t\t\treturn true;\n\t\t}\n\t\tif (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date\n\t\tipsetDestroy(&hfile->ipset);\n\t\tif (!AppendIpset(&hfile->ipset, hfile->filename))\n\t\t{\n\t\t\tipsetDestroy(&hfile->ipset);\n\t\t\treturn false;\n\t\t}\n\t\thfile->mod_sig=fsig;\n\t}\n\treturn true;\n}\nstatic bool LoadIpsets(struct ipset_files_head *list)\n{\n\tbool bres=true;\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t{\n\t\tif (!LoadIpset(hfile))\n\t\t\t// at least one failed\n\t\t\tbres=false;\n\t}\n\treturn bres;\n}\n\nbool LoadAllIpsets()\n{\n\treturn LoadIpsets(&params.ipsets);\n}\n\nstatic bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tchar s_ip[40];\n\tbool bInSet=false;\n\n\tif (!!ipv4 != !!ipv6)\n\t{\n\t\t*s_ip=0;\n\t\tif (ipv4)\n\t\t{\n\t\t\tif (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip));\n\t\t\tif (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32);\n\t\t}\n\t\tif (ipv6)\n\t\t{\n\t\t\tif (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip));\n\t\t\tif (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128);\n\t\t}\n\t\tDLOG(\"ipset check for %s : %s\\n\", s_ip, bInSet ? \"positive\" : \"negative\");\n\t}\n\telse\n\t\t// ipv4 and ipv6 are both empty or non-empty\n\t\tDLOG(\"ipset check error !!!!!!!! ipv4=%p ipv6=%p\\n\",ipv4,ipv6);\n\treturn bInSet;\n}\n\nstatic bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets)\n{\n\tstruct ipset_item *item;\n\tLIST_FOREACH(item, ipsets, next)\n\t{\n\t\tif (!LoadIpset(item->hfile))\n\t\t\treturn false;\n\t}\n\treturn true;\n}\nbool IpsetsReloadCheckForProfile(const struct desync_profile *dp)\n{\n\treturn IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude);\n}\n\nstatic bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tstruct ipset_item *item;\n\n\tif (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude))\n\t\treturn false;\n\n\tLIST_FOREACH(item, ips_exclude, next)\n\t{\n\t\tDLOG(\"[%s] exclude \",item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\tif (SearchIpset(&item->hfile->ipset, ipv4, ipv6))\n\t\t\treturn false;\n\t}\n\t// old behavior compat: all include lists are empty means check passes\n\tif (!ipset_collection_is_empty(ips))\n\t{\n\t\tLIST_FOREACH(item, ips, next)\n\t\t{\n\t\t\tDLOG(\"[%s] include \",item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\t\tif (SearchIpset(&item->hfile->ipset, ipv4, ipv6))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nbool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tif (PROFILE_IPSETS_ABSENT(dp)) return true;\n\tDLOG(\"* ipset check for profile %d\\n\",dp->n);\n\treturn IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6);\n}\n\n\nstatic struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename)\n{\n\tstruct ipset_file *hfile;\n\tif (filename)\n\t{\n\t\tif (!(hfile=ipset_files_search(ipsets, filename)))\n\t\t\tif (!(hfile=ipset_files_add(ipsets, filename)))\n\t\t\t\treturn NULL;\n\t\tif (!ipset_collection_search(ips_collection, filename))\n\t\t\tif (!ipset_collection_add(ips_collection, hfile))\n\t\t\t\treturn NULL;\n\t}\n\telse\n\t{\n\t\tif (!(hfile=ipset_files_add(ipsets, NULL)))\n\t\t\treturn NULL;\n\t\tif (!ipset_collection_add(ips_collection, hfile))\n\t\t\treturn NULL;\n\t}\n\treturn hfile;\n}\nstruct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename)\n{\n\tif (filename && !file_mod_time(filename))\n\t{\n\t\tDLOG_ERR(\"cannot access ipset file '%s'\\n\",filename);\n\t\treturn NULL;\n\t}\n\treturn RegisterIpset_(\n\t\t&params.ipsets,\n\t\tbExclude ? &dp->ips_collection_exclude : &dp->ips_collection,\n\t\tfilename);\n}\n\nstatic const char *dbg_ipset_fill(const ipset *ips)\n{\n\tif (ips->ips4)\n\t\tif (ips->ips6)\n\t\t\treturn \"ipv4+ipv6\";\n\t\telse\n\t\t\treturn \"ipv4\";\n\telse\n\t\tif (ips->ips6)\n\t\t\treturn \"ipv6\";\n\t\telse\n\t\t\treturn \"empty\";\n}\nvoid IpsetsDebug()\n{\n\tif (!params.debug) return;\n\n\tstruct ipset_file *hfile;\n\tstruct desync_profile_list *dpl;\n\tstruct ipset_item *ips_item;\n\n\tLIST_FOREACH(hfile, &params.ipsets, next)\n\t{\n\t\tif (hfile->filename)\n\t\t\tDLOG(\"ipset file %s (%s)\\n\",hfile->filename,dbg_ipset_fill(&hfile->ipset));\n\t\telse\n\t\t\tDLOG(\"ipset fixed (%s)\\n\",dbg_ipset_fill(&hfile->ipset));\n\t}\n\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tLIST_FOREACH(ips_item, &dpl->dp.ips_collection, next)\n\t\t\tif (ips_item->hfile->filename)\n\t\t\t\tDLOG(\"profile %d include ipset %s (%s)\\n\",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\t\telse\n\t\t\t\tDLOG(\"profile %d include fixed ipset (%s)\\n\",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\tLIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next)\n\t\t\tif (ips_item->hfile->filename)\n\t\t\t\tDLOG(\"profile %d exclude ipset %s (%s)\\n\",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\t\telse\n\t\t\t\tDLOG(\"profile %d exclude fixed ipset (%s)\\n\",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t}\n}\n"
  },
  {
    "path": "nfq/ipset.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <arpa/inet.h>\n#include \"params.h\"\n#include \"pools.h\"\n\nbool LoadAllIpsets();\nbool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6);\nstruct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename);\nvoid IpsetsDebug();\nbool AppendIpsetItem(ipset *ips, char *ip);\n\n#define ResetAllIpsetModTime() ipset_files_reset_modtime(&params.ipsets)\n"
  },
  {
    "path": "nfq/kavl.h",
    "content": "/* The MIT License\n\n   Copyright (c) 2018 by Attractive Chaos <attractor@live.co.uk>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n/* An example:\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"kavl.h\"\n\nstruct my_node {\n  char key;\n  KAVL_HEAD(struct my_node) head;\n};\n#define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key))\nKAVL_INIT(my, struct my_node, head, my_cmp)\n\nint main(void) {\n  const char *str = \"MNOLKQOPHIA\"; // from wiki, except a duplicate\n  struct my_node *root = 0;\n  int i, l = strlen(str);\n  for (i = 0; i < l; ++i) {        // insert in the input order\n    struct my_node *q, *p = malloc(sizeof(*p));\n    p->key = str[i];\n    q = kavl_insert(my, &root, p, 0);\n    if (p != q) free(p);           // if already present, free\n  }\n  kavl_itr_t(my) itr;\n  kavl_itr_first(my, root, &itr);  // place at first\n  do {                             // traverse\n    const struct my_node *p = kavl_at(&itr);\n    putchar(p->key);\n    free((void*)p);                // free node\n  } while (kavl_itr_next(my, &itr));\n  putchar('\\n');\n  return 0;\n}\n*/\n\n#ifndef KAVL_H\n#define KAVL_H\n\n#ifdef __STRICT_ANSI__\n#define inline __inline__\n#endif\n\n#define KAVL_MAX_DEPTH 64\n\n#define kavl_size(head, p) ((p)? (p)->head.size : 0)\n#define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0)\n\n#define KAVL_HEAD(__type) \\\n\tstruct { \\\n\t\t__type *p[2]; \\\n\t\tsigned char balance; /* balance factor */ \\\n\t\tunsigned size; /* #elements in subtree */ \\\n\t}\n\n#define __KAVL_FIND(suf, __scope, __type, __head,  __cmp) \\\n\t__scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \\\n\t\tconst __type *p = root; \\\n\t\tunsigned cnt = 0; \\\n\t\twhile (p != 0) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\tif (cmp < 0) p = p->__head.p[0]; \\\n\t\t\telse if (cmp > 0) p = p->__head.p[1]; \\\n\t\t\telse break; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\treturn (__type*)p; \\\n\t}\n\n#define __KAVL_ROTATE(suf, __type, __head) \\\n\t/* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \\\n\tstatic inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \\\n\t\tint opp = 1 - dir; /* opposite direction */ \\\n\t\t__type *q = p->__head.p[opp]; \\\n\t\tunsigned size_p = p->__head.size; \\\n\t\tp->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \\\n\t\tq->__head.size = size_p; \\\n\t\tp->__head.p[opp] = q->__head.p[dir]; \\\n\t\tq->__head.p[dir] = p; \\\n\t\treturn q; \\\n\t} \\\n\t/* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \\\n\tstatic inline __type *kavl_rotate2_##suf(__type *p, int dir) { \\\n\t\tint b1, opp = 1 - dir; \\\n\t\t__type *q = p->__head.p[opp], *r = q->__head.p[dir]; \\\n\t\tunsigned size_x_dir = kavl_size_child(__head, r, dir); \\\n\t\tr->__head.size = p->__head.size; \\\n\t\tp->__head.size -= q->__head.size - size_x_dir; \\\n\t\tq->__head.size -= size_x_dir + 1; \\\n\t\tp->__head.p[opp] = r->__head.p[dir]; \\\n\t\tr->__head.p[dir] = p; \\\n\t\tq->__head.p[dir] = r->__head.p[opp]; \\\n\t\tr->__head.p[opp] = q; \\\n\t\tb1 = dir == 0? +1 : -1; \\\n\t\tif (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \\\n\t\telse if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \\\n\t\telse q->__head.balance = b1, p->__head.balance = 0; \\\n\t\tr->__head.balance = 0; \\\n\t\treturn r; \\\n\t}\n\n#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \\\n\t__scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \\\n\t\tunsigned char stack[KAVL_MAX_DEPTH]; \\\n\t\t__type *path[KAVL_MAX_DEPTH]; \\\n\t\t__type *bp, *bq; \\\n\t\t__type *p, *q, *r = 0; /* _r_ is potentially the new root */ \\\n\t\tint i, which = 0, top, b1, path_len; \\\n\t\tunsigned cnt = 0; \\\n\t\tbp = *root_, bq = 0; \\\n\t\t/* find the insertion location */ \\\n\t\tfor (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\tif (cmp == 0) { \\\n\t\t\t\tif (cnt_) *cnt_ = cnt; \\\n\t\t\t\treturn p; \\\n\t\t\t} \\\n\t\t\tif (p->__head.balance != 0) \\\n\t\t\t\tbq = q, bp = p, top = 0; \\\n\t\t\tstack[top++] = which = (cmp > 0); \\\n\t\t\tpath[path_len++] = p; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\tx->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \\\n\t\tif (q == 0) *root_ = x; \\\n\t\telse q->__head.p[which] = x; \\\n\t\tif (bp == 0) return x; \\\n\t\tfor (i = 0; i < path_len; ++i) ++path[i]->__head.size; \\\n\t\tfor (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \\\n\t\t\tif (stack[top] == 0) --p->__head.balance; \\\n\t\t\telse ++p->__head.balance; \\\n\t\tif (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \\\n\t\t/* re-balance */ \\\n\t\twhich = (bp->__head.balance < 0); \\\n\t\tb1 = which == 0? +1 : -1; \\\n\t\tq = bp->__head.p[1 - which]; \\\n\t\tif (q->__head.balance == b1) { \\\n\t\t\tr = kavl_rotate1_##suf(bp, which); \\\n\t\t\tq->__head.balance = bp->__head.balance = 0; \\\n\t\t} else r = kavl_rotate2_##suf(bp, which); \\\n\t\tif (bq == 0) *root_ = r; \\\n\t\telse bq->__head.p[bp != bq->__head.p[0]] = r; \\\n\t\treturn x; \\\n\t}\n\n#define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \\\n\t__scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \\\n\t\t__type *p, *path[KAVL_MAX_DEPTH], fake; \\\n\t\tunsigned char dir[KAVL_MAX_DEPTH]; \\\n\t\tint i, d = 0, cmp; \\\n\t\tunsigned cnt = 0; \\\n\t\tfake.__head.p[0] = *root_, fake.__head.p[1] = 0; \\\n\t\tif (cnt_) *cnt_ = 0; \\\n\t\tif (x) { \\\n\t\t\tfor (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \\\n\t\t\t\tint which = (cmp > 0); \\\n\t\t\t\tif (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\t\tdir[d] = which; \\\n\t\t\t\tpath[d++] = p; \\\n\t\t\t\tp = p->__head.p[which]; \\\n\t\t\t\tif (p == 0) { \\\n\t\t\t\t\tif (cnt_) *cnt_ = 0; \\\n\t\t\t\t\treturn 0; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t\tcnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \\\n\t\t} else { \\\n\t\t\tfor (p = &fake, cnt = 1; p; p = p->__head.p[0]) \\\n\t\t\t\tdir[d] = 0, path[d++] = p; \\\n\t\t\tp = path[--d]; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\tfor (i = 1; i < d; ++i) --path[i]->__head.size; \\\n\t\tif (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \\\n\t\t\tpath[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \\\n\t\t} else { \\\n\t\t\t__type *q = p->__head.p[1]; \\\n\t\t\tif (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \\\n\t\t\t\tq->__head.p[0] = p->__head.p[0]; \\\n\t\t\t\tq->__head.balance = p->__head.balance; \\\n\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = q; \\\n\t\t\t\tpath[d] = q, dir[d++] = 1; \\\n\t\t\t\tq->__head.size = p->__head.size - 1; \\\n\t\t\t} else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \\\n\t\t\t\t__type *r; \\\n\t\t\t\tint e = d++; /* backup _d_ */\\\n\t\t\t\tfor (;;) { \\\n\t\t\t\t\tdir[d] = 0; \\\n\t\t\t\t\tpath[d++] = q; \\\n\t\t\t\t\tr = q->__head.p[0]; \\\n\t\t\t\t\tif (r->__head.p[0] == 0) break; \\\n\t\t\t\t\tq = r; \\\n\t\t\t\t} \\\n\t\t\t\tr->__head.p[0] = p->__head.p[0]; \\\n\t\t\t\tq->__head.p[0] = r->__head.p[1]; \\\n\t\t\t\tr->__head.p[1] = p->__head.p[1]; \\\n\t\t\t\tr->__head.balance = p->__head.balance; \\\n\t\t\t\tpath[e-1]->__head.p[dir[e-1]] = r; \\\n\t\t\t\tpath[e] = r, dir[e] = 1; \\\n\t\t\t\tfor (i = e + 1; i < d; ++i) --path[i]->__head.size; \\\n\t\t\t\tr->__head.size = p->__head.size - 1; \\\n\t\t\t} \\\n\t\t} \\\n\t\twhile (--d > 0) { \\\n\t\t\t__type *q = path[d]; \\\n\t\t\tint which, other, b1 = 1, b2 = 2; \\\n\t\t\twhich = dir[d], other = 1 - which; \\\n\t\t\tif (which) b1 = -b1, b2 = -b2; \\\n\t\t\tq->__head.balance += b1; \\\n\t\t\tif (q->__head.balance == b1) break; \\\n\t\t\telse if (q->__head.balance == b2) { \\\n\t\t\t\t__type *r = q->__head.p[other]; \\\n\t\t\t\tif (r->__head.balance == -b1) { \\\n\t\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \\\n\t\t\t\t} else { \\\n\t\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \\\n\t\t\t\t\tif (r->__head.balance == 0) { \\\n\t\t\t\t\t\tr->__head.balance = -b1; \\\n\t\t\t\t\t\tq->__head.balance = b1; \\\n\t\t\t\t\t\tbreak; \\\n\t\t\t\t\t} else r->__head.balance = q->__head.balance = 0; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\t*root_ = fake.__head.p[0]; \\\n\t\treturn p; \\\n\t}\n\n#define kavl_free(__type, __head, __root, __free) do { \\\n\t\t__type *_p, *_q; \\\n\t\tfor (_p = __root; _p; _p = _q) { \\\n\t\t\tif (_p->__head.p[0] == 0) { \\\n\t\t\t\t_q = _p->__head.p[1]; \\\n\t\t\t\t__free(_p); \\\n\t\t\t} else { \\\n\t\t\t\t_q = _p->__head.p[0]; \\\n\t\t\t\t_p->__head.p[0] = _q->__head.p[1]; \\\n\t\t\t\t_q->__head.p[1] = _p; \\\n\t\t\t} \\\n\t\t} \\\n\t} while (0)\n\n#define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \\\n\tstruct kavl_itr_##suf { \\\n\t\tconst __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \\\n\t}; \\\n\t__scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \\\n\t\tconst __type *p; \\\n\t\tfor (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \\\n\t\t\t*++itr->top = p; \\\n\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t} \\\n\t__scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \\\n\t\tconst __type *p = root; \\\n\t\titr->top = itr->stack - 1; \\\n\t\twhile (p != 0) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \\\n\t\t\telse if (cmp > 0) p = p->__head.p[1]; \\\n\t\t\telse break; \\\n\t\t} \\\n\t\tif (p) { \\\n\t\t\t*++itr->top = p; \\\n\t\t\titr->right = p->__head.p[1]; \\\n\t\t\treturn 1; \\\n\t\t} else if (itr->top >= itr->stack) { \\\n\t\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t\t\treturn 0; \\\n\t\t} else return 0; \\\n\t} \\\n\t__scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \\\n\t\tfor (;;) { \\\n\t\t\tconst __type *p; \\\n\t\t\tfor (p = itr->right, --itr->top; p; p = p->__head.p[0]) \\\n\t\t\t\t*++itr->top = p; \\\n\t\t\tif (itr->top < itr->stack) return 0; \\\n\t\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t\t\treturn 1; \\\n\t\t} \\\n\t}\n\n/**\n * Insert a node to the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param proot   pointer to the root of the tree (in/out: root may change)\n * @param x       node to insert (in)\n * @param cnt     number of nodes smaller than or equal to _x_; can be NULL (out)\n *\n * @return _x_ if not present in the tree, or the node equal to x.\n */\n#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt)\n\n/**\n * Find a node in the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param x       node value to find (in)\n * @param cnt     number of nodes smaller than or equal to _x_; can be NULL (out)\n *\n * @return node equal to _x_ if present, or NULL if absent\n */\n#define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt)\n\n/**\n * Delete a node from the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param proot   pointer to the root of the tree (in/out: root may change)\n * @param x       node value to delete; if NULL, delete the first node (in)\n *\n * @return node removed from the tree if present, or NULL if absent\n */\n#define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt)\n#define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0)\n\n#define kavl_itr_t(suf) struct kavl_itr_##suf\n\n/**\n * Place the iterator at the smallest object\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param itr     iterator\n */\n#define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr)\n\n/**\n * Place the iterator at the object equal to or greater than the query\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param x       query (in)\n * @param itr     iterator (out)\n *\n * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is\n *         larger than all objects in the tree\n */\n#define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr)\n\n/**\n * Move to the next object in order\n *\n * @param itr     iterator (modified)\n *\n * @return 1 if there is a next object; 0 otherwise\n */\n#define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr)\n\n/**\n * Return the pointer at the iterator\n *\n * @param itr     iterator\n *\n * @return pointer if present; NULL otherwise\n */\n#define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top)\n\n#define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_FIND(suf, __scope, __type, __head,  __cmp) \\\n\t__KAVL_ROTATE(suf, __type, __head) \\\n\t__KAVL_INSERT(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_ERASE(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_ITR(suf, __scope, __type, __head, __cmp)\n\n#define KAVL_INIT(suf, __type, __head, __cmp) \\\n\tKAVL_INIT2(suf,, __type, __head, __cmp)\n\n#endif\n"
  },
  {
    "path": "nfq/nfqws.c",
    "content": "#define _GNU_SOURCE\n\n#include \"nfqws.h\"\n#include \"sec.h\"\n#include \"desync.h\"\n#include \"helpers.h\"\n#include \"checksum.h\"\n#include \"params.h\"\n#include \"protocol.h\"\n#include \"hostlist.h\"\n#include \"ipset.h\"\n#include \"gzip.h\"\n#include \"pools.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <arpa/inet.h>\n#include <getopt.h>\n#include <fcntl.h>\n#include <pwd.h>\n#include <signal.h>\n#include <errno.h>\n#include <time.h>\n#include <sys/param.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <netinet/in.h>\n#include <syslog.h>\n#include <grp.h>\n\n#ifdef __CYGWIN__\n#include \"win.h\"\n#endif\n\n#ifdef USE_SYSTEMD\n#include <systemd/sd-daemon.h>\n#endif\n\n#ifdef __linux__\n#include <libnetfilter_queue/libnetfilter_queue.h>\n#define NF_DROP 0\n#define NF_ACCEPT 1\n#endif\n\n#define CTRACK_T_SYN\t60\n#define CTRACK_T_FIN\t60\n#define CTRACK_T_EST\t300\n#define CTRACK_T_UDP\t60\n\n#define MAX_CONFIG_FILE_SIZE 16384\n\nstruct params_s params;\nstatic bool bReload = false;\n#ifdef __CYGWIN__\nbool bQuit = false;\n#endif\n\nstatic void onhup(int sig)\n{\n\tprintf(\"HUP received ! Lists will be reloaded.\\n\");\n\tbReload = true;\n}\nstatic void ReloadCheck()\n{\n\tif (bReload)\n\t{\n\t\tResetAllHostlistsModTime();\n\t\tif (!LoadAllHostLists())\n\t\t{\n\t\t\tDLOG_ERR(\"hostlists load failed. this is fatal.\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tResetAllIpsetModTime();\n\t\tif (!LoadAllIpsets())\n\t\t{\n\t\t\tDLOG_ERR(\"ipset load failed. this is fatal.\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tbReload = false;\n\t}\n}\n\nstatic void onusr1(int sig)\n{\n\tprintf(\"\\nCONNTRACK DUMP\\n\");\n\tConntrackPoolDump(&params.conntrack);\n\tprintf(\"\\n\");\n}\nstatic void onusr2(int sig)\n{\n\tprintf(\"\\nHOSTFAIL POOL DUMP\\n\");\n\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tprintf(\"\\nDESYNC PROFILE %d\\n\", dpl->dp.n);\n\t\tHostFailPoolDump(dpl->dp.hostlist_auto_fail_counters);\n\t}\n\tif (params.autottl_present || params.cache_hostname)\n\t{\n\t\tprintf(\"\\nIPCACHE\\n\");\n\t\tipcachePrint(&params.ipcache);\n\t}\n\tprintf(\"\\n\");\n}\n\nstatic void pre_desync(void)\n{\n\tsignal(SIGHUP, onhup);\n\tsignal(SIGUSR1, onusr1);\n\tsignal(SIGUSR2, onusr2);\n}\n\n\nstatic uint8_t processPacketData(uint32_t *mark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt)\n{\n#ifdef __linux__\n\tif (*mark & params.desync_fwmark)\n\t{\n\t\tDLOG(\"ignoring generated packet\\n\");\n\t\treturn VERDICT_PASS;\n\t}\n#endif\n\treturn dpi_desync_packet(*mark, ifin, ifout, data_pkt, len_pkt);\n}\n\n\nstatic bool test_list_files()\n{\n\tstruct hostlist_file *hfile;\n\tstruct ipset_file *ifile;\n\n\tLIST_FOREACH(hfile, &params.hostlists, next)\n\t\tif (hfile->filename && !file_open_test(hfile->filename, O_RDONLY))\n\t\t{\n\t\t\tDLOG_PERROR(\"file_open_test\");\n\t\t\tDLOG_ERR(\"cannot access hostlist file '%s'\\n\", hfile->filename);\n\t\t\treturn false;\n\t\t}\n\tLIST_FOREACH(ifile, &params.ipsets, next)\n\t\tif (ifile->filename && !file_open_test(ifile->filename, O_RDONLY))\n\t\t{\n\t\t\tDLOG_PERROR(\"file_open_test\");\n\t\t\tDLOG_ERR(\"cannot access ipset file '%s'\\n\", ifile->filename);\n\t\t\treturn false;\n\t\t}\n\treturn true;\n}\n\n\n#ifdef __linux__\nstatic int nfq_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie)\n{\n\tint id, ilen;\n\tsize_t len;\n\tstruct nfqnl_msg_packet_hdr *ph;\n\tuint8_t *data;\n\tuint32_t ifidx_out, ifidx_in;\n\tchar ifout[IFNAMSIZ], ifin[IFNAMSIZ];\n\n\tph = nfq_get_msg_packet_hdr(nfa);\n\tid = ph ? ntohl(ph->packet_id) : 0;\n\n\tuint32_t mark = nfq_get_nfmark(nfa);\n\tilen = nfq_get_payload(nfa, &data);\n\n\tifidx_out = nfq_get_outdev(nfa);\n\t*ifout = 0;\n\tif (ifidx_out) if_indextoname(ifidx_out, ifout);\n\n\tifidx_in = nfq_get_indev(nfa);\n\t*ifin = 0;\n\tif (ifidx_in) if_indextoname(ifidx_in, ifin);\n\n\tDLOG(\"\\npacket: id=%d len=%d mark=%08X ifin=%s(%u) ifout=%s(%u)\\n\", id, ilen, mark, ifin, ifidx_in, ifout, ifidx_out);\n\n\tif (ilen >= 0)\n\t{\n\t\tlen = ilen;\n\t\tuint8_t verdict = processPacketData(&mark, ifin, ifout, data, &len);\n\t\tswitch (verdict & VERDICT_MASK)\n\t\t{\n\t\tcase VERDICT_MODIFY:\n\t\t\tDLOG(\"packet: id=%d pass modified. len=%zu\\n\", id, len);\n\t\t\treturn nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)len, data);\n\t\tcase VERDICT_DROP:\n\t\t\tDLOG(\"packet: id=%d drop\\n\", id);\n\t\t\treturn nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL);\n\t\t}\n\t}\n\tDLOG(\"packet: id=%d pass unmodified\\n\", id);\n\treturn nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL);\n}\nstatic void nfq_deinit(struct nfq_handle **h, struct nfq_q_handle **qh)\n{\n\tif (*qh)\n\t{\n\t\tDLOG_CONDUP(\"unbinding from queue %u\\n\", params.qnum);\n\t\tnfq_destroy_queue(*qh);\n\t\t*qh = NULL;\n\t}\n\tif (*h)\n\t{\n\t\tDLOG_CONDUP(\"closing library handle\\n\");\n\t\tnfq_close(*h);\n\t\t*h = NULL;\n\t}\n}\nstatic bool nfq_init(struct nfq_handle **h, struct nfq_q_handle **qh)\n{\n\tnfq_deinit(h, qh);\n\n\tDLOG_CONDUP(\"opening library handle\\n\");\n\t*h = nfq_open();\n\tif (!*h) {\n\t\tDLOG_PERROR(\"nfq_open()\");\n\t\tgoto exiterr;\n\t}\n\n\tDLOG_CONDUP(\"unbinding existing nf_queue handler for AF_INET (if any)\\n\");\n\tif (nfq_unbind_pf(*h, AF_INET) < 0) {\n\t\tDLOG_PERROR(\"nfq_unbind_pf()\");\n\t\tgoto exiterr;\n\t}\n\n\tDLOG_CONDUP(\"binding nfnetlink_queue as nf_queue handler for AF_INET\\n\");\n\tif (nfq_bind_pf(*h, AF_INET) < 0) {\n\t\tDLOG_PERROR(\"nfq_bind_pf()\");\n\t\tgoto exiterr;\n\t}\n\n\tDLOG_CONDUP(\"binding this socket to queue '%u'\\n\", params.qnum);\n\t*qh = nfq_create_queue(*h, params.qnum, &nfq_cb, &params);\n\tif (!*qh) {\n\t\tDLOG_PERROR(\"nfq_create_queue()\");\n\t\tgoto exiterr;\n\t}\n\n\tDLOG_CONDUP(\"setting copy_packet mode\\n\");\n\tif (nfq_set_mode(*qh, NFQNL_COPY_PACKET, 0xffff) < 0) {\n\t\tDLOG_PERROR(\"can't set packet_copy mode\");\n\t\tgoto exiterr;\n\t}\n\tif (nfq_set_queue_maxlen(*qh, Q_MAXLEN) < 0) {\n\t\tDLOG_PERROR(\"can't set queue maxlen\");\n\t\tgoto exiterr;\n\t}\n\t// accept packets if they cant be handled\n\tif (nfq_set_queue_flags(*qh, NFQA_CFG_F_FAIL_OPEN, NFQA_CFG_F_FAIL_OPEN))\n\t{\n\t\tDLOG_ERR(\"can't set queue flags. its OK on linux <3.6\\n\");\n\t\t// dot not fail. not supported on old linuxes <3.6 \n\t}\n\n\tnfnl_rcvbufsiz(nfq_nfnlh(*h), Q_RCVBUF);\n\n\tDLOG_CONDUP(\"initializing raw sockets bind-fix4=%u bind-fix6=%u\\n\", params.bind_fix4, params.bind_fix6);\n\tif (!rawsend_preinit(params.bind_fix4, params.bind_fix6))\n\t\tgoto exiterr;\n\n\tint yes = 1, fd = nfq_fd(*h);\n\n#if defined SOL_NETLINK && defined NETLINK_NO_ENOBUFS\n\tif (setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &yes, sizeof(yes)) == -1)\n\t\tDLOG_PERROR(\"setsockopt(NETLINK_NO_ENOBUFS)\");\n#endif\n\n\treturn true;\nexiterr:\n\tnfq_deinit(h, qh);\n\treturn false;\n}\n\nstatic void notify_ready(void)\n{\n#ifdef USE_SYSTEMD\n\tint r = sd_notify(0, \"READY=1\");\n\tif (r < 0)\n\t\tDLOG_ERR(\"sd_notify: %s\\n\", strerror(-r));\n#endif\n}\n\nstatic int nfq_main(void)\n{\n\tuint8_t buf[16384] __attribute__((aligned));\n\tstruct nfq_handle *h = NULL;\n\tstruct nfq_q_handle *qh = NULL;\n\tint fd, e;\n\tssize_t rd;\n\tFILE *Fpid = NULL;\n\n\tif (*params.pidfile && !(Fpid = fopen(params.pidfile, \"w\")))\n\t{\n\t\tDLOG_PERROR(\"create pidfile\");\n\t\treturn 1;\n\t}\n\n\tif (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count) || !dropcaps())\n\t\tgoto err;\n\tprint_id();\n\tif (params.droproot && !test_list_files())\n\t\tgoto err;\n\n\tif (!nfq_init(&h, &qh))\n\t\tgoto err;\n\n#ifdef HAS_FILTER_SSID\n\tif (params.filter_ssid_present)\n\t{\n\t\tif (!wlan_info_init())\n\t\t{\n\t\t\tDLOG_ERR(\"cannot initialize wlan info capture\\n\");\n\t\t\tgoto err;\n\t\t}\n\t\tDLOG(\"wlan info capture initialized\\n\");\n\t}\n#endif\n\n\tif (params.daemon) daemonize();\n\n\tsec_harden();\n\n\tif (Fpid)\n\t{\n\t\tif (fprintf(Fpid, \"%d\", getpid()) <= 0)\n\t\t{\n\t\t\tDLOG_PERROR(\"write pidfile\");\n\t\t\tgoto err;\n\t\t}\n\t\tfclose(Fpid);\n\t\tFpid = NULL;\n\t}\n\n\tpre_desync();\n\tnotify_ready();\n\n\tfd = nfq_fd(h);\n\tdo\n\t{\n\t\twhile ((rd = recv(fd, buf, sizeof(buf), 0)) >= 0)\n\t\t{\n\t\t\tReloadCheck();\n#ifdef HAS_FILTER_SSID\n\t\t\tif (params.filter_ssid_present)\n\t\t\t\tif (!wlan_info_get_rate_limited())\n\t\t\t\t\tDLOG_ERR(\"cannot get wlan info\\n\");\n#endif\n\t\t\tif (rd)\n\t\t\t{\n\t\t\t\tint r = nfq_handle_packet(h, (char *)buf, (int)rd);\n\t\t\t\tif (r<0) DLOG_ERR(\"nfq_handle_packet result %d, errno %d : %s\\n\", r, errno, strerror(errno));\n\t\t\t}\n\t\t\telse\n\t\t\t\tDLOG(\"recv from nfq returned 0 !\\n\");\n\t\t}\n\t\te = errno;\n\t\tDLOG_ERR(\"recv: recv=%zd errno %d\\n\", rd, e);\n\t\terrno = e;\n\t\tDLOG_PERROR(\"recv\");\n\t\t// do not fail on ENOBUFS\n\t} while (e == ENOBUFS);\n\n\tnfq_deinit(&h, &qh);\n#ifdef HAS_FILTER_SSID\n\twlan_info_deinit();\n#endif\n\treturn 0;\nerr:\n\tif (Fpid) fclose(Fpid);\n\tnfq_deinit(&h, &qh);\n#ifdef HAS_FILTER_SSID\n\twlan_info_deinit();\n#endif\n\treturn 1;\n}\n\n#elif defined(BSD)\n\nstatic int dvt_main(void)\n{\n\tuint8_t buf[16384] __attribute__((aligned));\n\tstruct sockaddr_storage sa_from;\n\tint fd[2] = { -1,-1 }; // 4,6\n\tint i, r, res = 1, fdct = 1, fdmax;\n\tunsigned int id = 0;\n\tsocklen_t socklen;\n\tssize_t rd, wr;\n\tfd_set fdset;\n\tFILE *Fpid = NULL;\n\n\tif (*params.pidfile && !(Fpid = fopen(params.pidfile, \"w\")))\n\t{\n\t\tDLOG_PERROR(\"create pidfile\");\n\t\treturn 1;\n\t}\n\n\t{\n\t\tstruct sockaddr_in bp4;\n\t\tbp4.sin_family = AF_INET;\n\t\tbp4.sin_port = htons(params.port);\n\t\tbp4.sin_addr.s_addr = INADDR_ANY;\n\n\t\tDLOG_CONDUP(\"creating divert4 socket\\n\");\n\t\tfd[0] = socket_divert(AF_INET);\n\t\tif (fd[0] == -1) {\n\t\t\tDLOG_PERROR(\"socket (DIVERT4)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tDLOG_CONDUP(\"binding divert4 socket\\n\");\n\t\tif (bind(fd[0], (struct sockaddr*)&bp4, sizeof(bp4)) < 0)\n\t\t{\n\t\t\tDLOG_PERROR(\"bind (DIVERT4)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t}\n\n\n#ifdef __OpenBSD__\n\t{\n\t\t// in OpenBSD must use separate divert sockets for ipv4 and ipv6\n\t\tstruct sockaddr_in6 bp6;\n\t\tmemset(&bp6, 0, sizeof(bp6));\n\t\tbp6.sin6_family = AF_INET6;\n\t\tbp6.sin6_port = htons(params.port);\n\n\t\tDLOG_CONDUP(\"creating divert6 socket\\n\");\n\t\tfd[1] = socket_divert(AF_INET6);\n\t\tif (fd[1] == -1) {\n\t\t\tDLOG_PERROR(\"socket (DIVERT6)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tDLOG_CONDUP(\"binding divert6 socket\\n\");\n\t\tif (bind(fd[1], (struct sockaddr*)&bp6, sizeof(bp6)) < 0)\n\t\t{\n\t\t\tDLOG_PERROR(\"bind (DIVERT6)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tfdct++;\n\t}\n#endif\n\tfdmax = (fd[0] > fd[1] ? fd[0] : fd[1]) + 1;\n\n\tDLOG_CONDUP(\"initializing raw sockets\\n\");\n\tif (!rawsend_preinit(false, false))\n\t\tgoto exiterr;\n\n\n\tif (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count))\n\t\tgoto exiterr;\n\tprint_id();\n\tif (params.droproot && !test_list_files())\n\t\tgoto exiterr;\n\n\tif (params.daemon) daemonize();\n\n\tif (Fpid)\n\t{\n\t\tif (fprintf(Fpid, \"%d\", getpid()) <= 0)\n\t\t{\n\t\t\tDLOG_PERROR(\"write pidfile\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tfclose(Fpid);\n\t\tFpid = NULL;\n\t}\n\n\tpre_desync();\n\n\tfor (;;)\n\t{\n\t\tFD_ZERO(&fdset);\n\t\tfor (i = 0; i < fdct; i++) FD_SET(fd[i], &fdset);\n\t\tr = select(fdmax, &fdset, NULL, NULL, NULL);\n\t\tif (r == -1)\n\t\t{\n\t\t\tif (errno == EINTR)\n\t\t\t{\n\t\t\t\t// a signal received\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tDLOG_PERROR(\"select\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tfor (i = 0; i < fdct; i++)\n\t\t{\n\t\t\tif (FD_ISSET(fd[i], &fdset))\n\t\t\t{\n\t\t\t\tsocklen = sizeof(sa_from);\n\t\t\t\trd = recvfrom(fd[i], buf, sizeof(buf), 0, (struct sockaddr*)&sa_from, &socklen);\n\t\t\t\tif (rd < 0)\n\t\t\t\t{\n\t\t\t\t\tDLOG_PERROR(\"recvfrom\");\n\t\t\t\t\tgoto exiterr;\n\t\t\t\t}\n\t\t\t\telse if (rd > 0)\n\t\t\t\t{\n\t\t\t\t\tuint32_t mark = 0;\n\t\t\t\t\tuint8_t verdict;\n\t\t\t\t\tsize_t len = rd;\n\n\t\t\t\t\tReloadCheck();\n\n\t\t\t\t\tDLOG(\"\\npacket: id=%u len=%zu\\n\", id, len);\n\t\t\t\t\tverdict = processPacketData(&mark, NULL, NULL, buf, &len);\n\t\t\t\t\tswitch (verdict & VERDICT_MASK)\n\t\t\t\t\t{\n\t\t\t\t\tcase VERDICT_PASS:\n\t\t\t\t\tcase VERDICT_MODIFY:\n\t\t\t\t\t\tif ((verdict & VERDICT_MASK) == VERDICT_PASS)\n\t\t\t\t\t\t\tDLOG(\"packet: id=%u reinject unmodified\\n\", id);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tDLOG(\"packet: id=%u reinject modified len=%zu\\n\", id, len);\n\t\t\t\t\t\twr = sendto(fd[i], buf, len, 0, (struct sockaddr*)&sa_from, socklen);\n\t\t\t\t\t\tif (wr < 0)\n\t\t\t\t\t\t\tDLOG_PERROR(\"reinject sendto\");\n\t\t\t\t\t\telse if (wr != len)\n\t\t\t\t\t\t\tDLOG_ERR(\"reinject sendto: not all data was reinjected. received %zu, sent %zd\\n\", len, wr);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tDLOG(\"packet: id=%u drop\\n\", id);\n\t\t\t\t\t}\n\t\t\t\t\tid++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"unexpected zero size recvfrom\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tres = 0;\nexiterr:\n\tif (Fpid) fclose(Fpid);\n\tif (fd[0] != -1) close(fd[0]);\n\tif (fd[1] != -1) close(fd[1]);\n\treturn res;\n}\n\n\n#elif defined (__CYGWIN__)\n\nstatic int win_main(const char *windivert_filter)\n{\n\tsize_t len;\n\tunsigned int id;\n\tuint8_t verdict;\n\tbool bOutbound;\n\tuint8_t packet[16384];\n\tuint32_t mark;\n\tWINDIVERT_ADDRESS wa;\n\tchar ifname[IFNAMSIZ];\n\n\tif (params.daemon) daemonize();\n\n\tif (*params.pidfile && !writepid(params.pidfile))\n\t{\n\t\tDLOG_ERR(\"could not write pidfile\");\n\t\treturn ERROR_TOO_MANY_OPEN_FILES; // code 4 = The system cannot open the file\n\t}\n\n\tif (!win_dark_init(&params.ssid_filter, &params.nlm_filter))\n\t{\n\t\tDLOG_ERR(\"win_dark_init failed. win32 error %u (0x%08X)\\n\", w_win32_error, w_win32_error);\n\t\treturn w_win32_error;\n\t}\n\n\tpre_desync();\n\n\tfor (;;)\n\t{\n\t\tif (!logical_net_filter_match())\n\t\t{\n\t\t\tDLOG_CONDUP(\"logical network is not present. waiting it to appear.\\n\");\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif (bQuit)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"QUIT requested\\n\");\n\t\t\t\t\twin_dark_deinit();\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tusleep(500000);\n\t\t\t} while (!logical_net_filter_match());\n\t\t\tDLOG_CONDUP(\"logical network now present\\n\");\n\t\t}\n\n\t\tif (!windivert_init(windivert_filter))\n\t\t{\n\t\t\twin_dark_deinit();\n\t\t\treturn w_win32_error;\n\t\t}\n\n\t\tDLOG_CONDUP(\"windivert initialized. capture is started.\\n\");\n\n\t\tfor (id = 0;; id++)\n\t\t{\n\t\t\tlen = sizeof(packet);\n\t\t\tif (!windivert_recv(packet, &len, &wa))\n\t\t\t{\n\t\t\t\tif (errno == ENOBUFS)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"windivert: ignoring too large packet\\n\");\n\t\t\t\t\tcontinue; // too large packet\n\t\t\t\t}\n\t\t\t\telse if (errno == ENODEV)\n\t\t\t\t{\n\t\t\t\t\tDLOG_CONDUP(\"logical network disappeared. deinitializing windivert.\\n\");\n\t\t\t\t\trawsend_cleanup();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\telse if (errno == EINTR)\n\t\t\t\t{\n\t\t\t\t\tDLOG(\"QUIT requested\\n\");\n\t\t\t\t\twin_dark_deinit();\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tDLOG_ERR(\"windivert: recv failed. errno %d\\n\", errno);\n\t\t\t\twin_dark_deinit();\n\t\t\t\treturn w_win32_error;\n\t\t\t}\n\n\t\t\tReloadCheck();\n\n\t\t\t*ifname = 0;\n\t\t\tsnprintf(ifname, sizeof(ifname), \"%u.%u\", wa.Network.IfIdx, wa.Network.SubIfIdx);\n\t\t\tDLOG(\"\\npacket: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\\n\", id, len, wa.Outbound ? \"outbound\" : \"inbound\", wa.IPv6, wa.IPChecksum, wa.TCPChecksum, wa.UDPChecksum, wa.Network.IfIdx, wa.Network.SubIfIdx);\n\t\t\tif (wa.Impostor)\n\t\t\t{\n\t\t\t\tDLOG(\"windivert: passing impostor packet\\n\");\n\t\t\t\tverdict = VERDICT_PASS;\n\t\t\t}\n\t\t\telse if (wa.Loopback)\n\t\t\t{\n\t\t\t\tDLOG(\"windivert: passing loopback packet\\n\");\n\t\t\t\tverdict = VERDICT_PASS;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tmark = 0;\n\t\t\t\t// pseudo interface id IfIdx.SubIfIdx\n\t\t\t\tverdict = processPacketData(&mark, ifname, ifname, packet, &len);\n\t\t\t}\n\t\t\tswitch (verdict & VERDICT_MASK)\n\t\t\t{\n\t\t\tcase VERDICT_PASS:\n\t\t\tcase VERDICT_MODIFY:\n\t\t\t\tif ((verdict & VERDICT_MASK) == VERDICT_PASS)\n\t\t\t\t\tDLOG(\"packet: id=%u reinject unmodified\\n\", id);\n\t\t\t\telse\n\t\t\t\t\tDLOG(\"packet: id=%u reinject modified len=%zu\\n\", id, len);\n\t\t\t\tif (!windivert_send(packet, len, &wa))\n\t\t\t\t\tDLOG_ERR(\"windivert: reinject of packet id=%u failed\\n\", id);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tDLOG(\"packet: id=%u drop\\n\", id);\n\t\t\t}\n\t\t}\n\t}\n\twin_dark_deinit();\n\treturn 0;\n}\n\n#endif // multiple OS divert handlers\n\n\n\n\nstatic void exit_clean(int code)\n{\n\tcleanup_params(&params);\n\texit(code);\n}\n\n\nstatic bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids)\n{\n\tunsigned int u;\n\tchar c, *p, *e;\n\n\t*gid_count = 0;\n\tif ((e = strchr(optarg, ':'))) *e++ = 0;\n\tif (sscanf(opt, \"%u\", &u) != 1) return false;\n\t*uid = (uid_t)u;\n\tfor (p = e; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\t\tif (p)\n\t\t{\n\t\t\tif (sscanf(p, \"%u\", &u) != 1) return false;\n\t\t\tif (*gid_count >= max_gids) return false;\n\t\t\tgid[(*gid_count)++] = (gid_t)u;\n\t\t}\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_ws_scale_factor(char *s, uint16_t *wsize, uint8_t *wscale)\n{\n\tint v;\n\tchar *p;\n\n\tif ((p = strchr(s, ':'))) *p++ = 0;\n\tv = atoi(s);\n\tif (v < 0 || v>65535)\n\t{\n\t\tDLOG_ERR(\"bad wsize\\n\");\n\t\treturn false;\n\t}\n\t*wsize = (uint16_t)v;\n\tif (p && *p)\n\t{\n\t\tv = atoi(p);\n\t\tif (v < 0 || v>255)\n\t\t{\n\t\t\tDLOG_ERR(\"bad wscale\\n\");\n\t\t\treturn false;\n\t\t}\n\t\t*wscale = (uint8_t)v;\n\t}\n\treturn true;\n}\n\nstatic bool parse_cutoff(const char *opt, unsigned int *value, char *mode)\n{\n\t*mode = (*opt == 'n' || *opt == 'd' || *opt == 's') ? *opt++ : 'n';\n\treturn sscanf(opt, \"%u\", value) > 0;\n}\nstatic bool parse_net32_signed(const char *opt, uint32_t *value)\n{\n\tif (((opt[0] == '0' && opt[1] == 'x') || (opt[0] == '-' && opt[1] == '0' && opt[2] == 'x')) && sscanf(opt + 2 + (opt[0] == '-'), \"%X\", (int32_t*)value) > 0)\n\t{\n\t\tif (opt[0] == '-') *value = -*value;\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\treturn sscanf(opt, \"%d\", (int32_t*)value) > 0;\n\t}\n}\nstatic void load_file_or_exit(const char *filename, void *buf, size_t *size, size_t *offset)\n{\n\tsize_t ofs;\n\n\t// 0xaabbcc\n\t// filename\n\t// @filename\n\t// +123@filename\n\n\tif (offset) *offset = 0;\n\tif (filename[0] == '0' && filename[1] == 'x')\n\t{\n\t\tif (!parse_hex_str(filename + 2, buf, size) || !*size)\n\t\t{\n\t\t\tDLOG_ERR(\"invalid hex string: %s\\n\", filename + 2);\n\t\t\texit_clean(1);\n\t\t}\n\t\tDLOG(\"read %zu bytes from hex string\\n\", *size);\n\t}\n\telse\n\t{\n\t\tofs = 0;\n\t\tif (filename[0] == '+')\n\t\t{\n\t\t\tfilename++;\n\t\t\tif (sscanf(filename, \"%zu\", &ofs) != 1)\n\t\t\t{\n\t\t\t\tDLOG(\"offset read error: %s\\n\", filename);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\twhile (*filename && *filename != '@') filename++;\n\t\t\tif (*filename == '@') filename++;\n\t\t}\n\t\telse if (filename[0] == '@')\n\t\t\tfilename++;\n\t\tif (!load_file_nonempty(filename, buf, size))\n\t\t{\n\t\t\tDLOG_ERR(\"could not read %s\\n\", filename);\n\t\t\texit_clean(1);\n\t\t}\n\t\tDLOG(\"read %zu bytes from '%s'. offset=%zu\\n\", *size, filename, ofs);\n\t\tif (ofs >= *size)\n\t\t{\n\t\t\tDLOG(\"'%s' : offset %zu is out of data range %zu\\n\", filename, ofs, *size);\n\t\t\texit_clean(1);\n\t\t}\n\t\tif (offset)\n\t\t\t*offset = ofs;\n\t\telse\n\t\t\tmemmove(buf, (uint8_t*)buf + ofs, *size -= ofs);\n\t}\n}\n\nstatic bool parse_autottl(const char *s, autottl *t, int8_t def_delta, uint8_t def_min, uint8_t def_max)\n{\n\tbool neg = true;\n\tunsigned int delta, min, max;\n\n\tt->delta = def_delta;\n\tt->min = def_min;\n\tt->max = def_max;\n\tif (s)\n\t{\n\t\t// \"-\" means disable\n\t\tif (s[0] == '-' && s[1] == 0)\n\t\t\tmemset(t, 0, sizeof(*t));\n\t\telse\n\t\t{\n\t\t\tmax = t->max;\n\t\t\tif (*s == '+')\n\t\t\t{\n\t\t\t\tneg = false;\n\t\t\t\ts++;\n\t\t\t}\n\t\t\telse if (*s == '-')\n\t\t\t\ts++;\n\t\t\tswitch (sscanf(s, \"%u:%u-%u\", &delta, &min, &max))\n\t\t\t{\n\t\t\tcase 3:\n\t\t\t\tif ((delta && !max) || max > 255) return false;\n\t\t\t\tt->max = (uint8_t)max;\n\t\t\tcase 2:\n\t\t\t\tif ((delta && !min) || min > 255 || min > max) return false;\n\t\t\t\tt->min = (uint8_t)min;\n\t\t\tcase 1:\n\t\t\t\tif (delta > 127) return false;\n\t\t\t\tt->delta = (int8_t)(neg ? -delta : delta);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic bool parse_l7_list(char *opt, uint32_t *l7)\n{\n\tchar *e, *p, c;\n\n\tfor (p = opt, *l7 = 0; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (!strcmp(p, \"http\"))\n\t\t\t*l7 |= L7_PROTO_HTTP;\n\t\telse if (!strcmp(p, \"tls\"))\n\t\t\t*l7 |= L7_PROTO_TLS;\n\t\telse if (!strcmp(p, \"quic\"))\n\t\t\t*l7 |= L7_PROTO_QUIC;\n\t\telse if (!strcmp(p, \"wireguard\"))\n\t\t\t*l7 |= L7_PROTO_WIREGUARD;\n\t\telse if (!strcmp(p, \"dht\"))\n\t\t\t*l7 |= L7_PROTO_DHT;\n\t\telse if (!strcmp(p, \"discord\"))\n\t\t\t*l7 |= L7_PROTO_DISCORD;\n\t\telse if (!strcmp(p, \"stun\"))\n\t\t\t*l7 |= L7_PROTO_STUN;\n\t\telse if (!strcmp(p, \"unknown\"))\n\t\t\t*l7 |= L7_PROTO_UNKNOWN;\n\t\telse return false;\n\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_pf_list(char *opt, struct port_filters_head *pfl)\n{\n\tchar *e, *p, c;\n\tport_filter pf;\n\tbool b;\n\n\tfor (p = opt; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tb = pf_parse(p, &pf) && port_filter_add(pfl, &pf);\n\t\tif (e) *e++ = c;\n\t\tif (!b) return false;\n\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6)\n{\n\tchar *e, *p, c;\n\n\tfor (p = opt, *ipv4 = *ipv6 = false; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (!strcmp(p, \"ipv4\"))\n\t\t\t*ipv4 = true;\n\t\telse if (!strcmp(p, \"ipv6\"))\n\t\t\t*ipv6 = true;\n\t\telse return false;\n\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_httpreqpos(const char *s, struct proto_pos *sp)\n{\n\tif (!strcmp(s, \"method\"))\n\t{\n\t\tsp->marker = PM_HTTP_METHOD;\n\t\tsp->pos = 2;\n\t}\n\telse if (!strcmp(s, \"host\"))\n\t{\n\t\tsp->marker = PM_HOST;\n\t\tsp->pos = 1;\n\t}\n\telse\n\t\treturn false;\n\treturn true;\n}\nstatic bool parse_tlspos(const char *s, struct proto_pos *sp)\n{\n\tif (!strcmp(s, \"sni\"))\n\t{\n\t\tsp->marker = PM_HOST;\n\t\tsp->pos = 1;\n\t}\n\telse if (!strcmp(s, \"sniext\"))\n\t{\n\t\tsp->marker = PM_SNI_EXT;\n\t\tsp->pos = 1;\n\t}\n\telse if (!strcmp(s, \"snisld\"))\n\t{\n\t\tsp->marker = PM_HOST_MIDSLD;\n\t\tsp->pos = 0;\n\t}\n\telse\n\t\treturn false;\n\treturn true;\n}\n\nstatic bool parse_int16(const char *p, int16_t *v)\n{\n\tif (*p == '+' || *p == '-' || *p >= '0' && *p <= '9')\n\t{\n\t\tint i = atoi(p);\n\t\t*v = (int16_t)i;\n\t\treturn *v == i; // check overflow\n\t}\n\treturn false;\n}\nstatic bool parse_posmarker(const char *opt, uint8_t *posmarker)\n{\n\tif (!strcmp(opt, \"host\"))\n\t\t*posmarker = PM_HOST;\n\telse if (!strcmp(opt, \"endhost\"))\n\t\t*posmarker = PM_HOST_END;\n\telse if (!strcmp(opt, \"sld\"))\n\t\t*posmarker = PM_HOST_SLD;\n\telse if (!strcmp(opt, \"midsld\"))\n\t\t*posmarker = PM_HOST_MIDSLD;\n\telse if (!strcmp(opt, \"endsld\"))\n\t\t*posmarker = PM_HOST_ENDSLD;\n\telse if (!strcmp(opt, \"method\"))\n\t\t*posmarker = PM_HTTP_METHOD;\n\telse if (!strcmp(opt, \"sniext\"))\n\t\t*posmarker = PM_SNI_EXT;\n\telse\n\t\treturn false;\n\treturn true;\n}\nstatic bool parse_split_pos(char *opt, struct proto_pos *split)\n{\n\tif (parse_int16(opt, &split->pos))\n\t{\n\t\tsplit->marker = PM_ABS;\n\t\treturn !!split->pos;\n\t}\n\telse\n\t{\n\t\tchar c, *p = opt;\n\t\tbool b;\n\n\t\tfor (; *opt && *opt != '+' && *opt != '-'; opt++);\n\t\tc = *opt; *opt = 0;\n\t\tb = parse_posmarker(p, &split->marker);\n\t\t*opt = c;\n\t\tif (!b) return false;\n\t\tif (*opt)\n\t\t\treturn parse_int16(opt, &split->pos);\n\t\telse\n\t\t\tsplit->pos = 0;\n\t}\n\treturn true;\n}\nstatic bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count)\n{\n\tchar c, *e, *p;\n\n\tfor (p = opt, *split_count = 0; p && *split_count < splits_size; (*split_count)++)\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\t\tif (!parse_split_pos(p, splits + *split_count)) return false;\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\tif (p) return false; // too much splits\n\treturn true;\n}\n\nstatic bool parse_domain_list(char *opt, hostlist_pool **pp)\n{\n\tchar *e, *p, c;\n\n\tfor (p = opt; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*p && !AppendHostlistItem(pp, p)) return false;\n\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_ip_list(char *opt, ipset *pp)\n{\n\tchar *e, *p, c;\n\n\tfor (p = opt; p; )\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*p && !AppendIpsetItem(pp, p)) return false;\n\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_tlsmod_list(char *opt, struct fake_tls_mod *tls_mod)\n{\n\tchar *e, *e2, *p, c, c2;\n\n\ttls_mod->mod &= FAKE_TLS_MOD_SAVE_MASK;\n\ttls_mod->mod |= FAKE_TLS_MOD_SET;\n\tfor (p = opt; p; )\n\t{\n\t\tfor (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++);\n\n\t\tif ((e = strchr(e2, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*e2 == '=')\n\t\t{\n\t\t\tc2 = *e2;\n\t\t\t*e2 = 0;\n\t\t}\n\t\telse\n\t\t\te2 = NULL;\n\n\t\tif (!strcmp(p, \"rnd\"))\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_RND;\n\t\telse if (!strcmp(p, \"rndsni\"))\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_RND_SNI;\n\t\telse if (!strcmp(p, \"sni\"))\n\t\t{\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_SNI;\n\t\t\tif (!e2 || !e2[1] || e2[1] == ',') goto err;\n\t\t\tstrncpy(tls_mod->sni, e2 + 1, sizeof(tls_mod->sni) - 1);\n\t\t\ttls_mod->sni[sizeof(tls_mod->sni) - 1 - 1] = 0;\n\t\t}\n\t\telse if (!strcmp(p, \"padencap\"))\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_PADENCAP;\n\t\telse if (!strcmp(p, \"dupsid\"))\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_DUP_SID;\n\t\telse if (strcmp(p, \"none\"))\n\t\t\tgoto err;\n\n\t\tif (e2) *e2 = c2;\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\nerr:\n\tif (e2) *e2 = c2;\n\tif (e) *e++ = c;\n\treturn false;\n}\n\nstatic bool parse_hostfakesplit_mod(char *opt, struct hostfakesplit_mod *hfs_mod)\n{\n\tchar *e, *e2, *p, c, c2;\n\n\tfor (p = opt; p; )\n\t{\n\t\tfor (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++);\n\n\t\tif ((e = strchr(e2, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*e2 == '=')\n\t\t{\n\t\t\tc2 = *e2;\n\t\t\t*e2 = 0;\n\t\t}\n\t\telse\n\t\t\te2 = NULL;\n\n\t\tif (!strcmp(p, \"host\"))\n\t\t{\n\t\t\tif (!e2 || !e2[1] || e2[1] == ',') goto err;\n\t\t\tstrncpy(hfs_mod->host, e2 + 1, sizeof(hfs_mod->host) - 1);\n\t\t\thfs_mod->host[sizeof(hfs_mod->host) - 1 - 1] = 0;\n\t\t\thfs_mod->host_size = strlen(hfs_mod->host); // cache value\n\t\t}\n\t\telse if (!strcmp(p, \"altorder\"))\n\t\t{\n\t\t\tif (!e2 || !e2[1] || e2[1] == ',') goto err;\n\t\t\thfs_mod->ordering = atoi(e2 + 1);\n\t\t\tif (hfs_mod->ordering < 0 || hfs_mod->ordering>1) goto err;\n\t\t}\n\t\telse if (strcmp(p, \"none\"))\n\t\t\tgoto err;\n\n\t\tif (e2) *e2 = c2;\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\nerr:\n\tif (e2) *e2 = c2;\n\tif (e) *e++ = c;\n\treturn false;\n}\n\nstatic bool parse_fakedsplit_mod(char *opt, struct fakedsplit_mod *fs_mod)\n{\n\tchar *e, *e2, *p, c, c2;\n\n\tfor (p = opt; p; )\n\t{\n\t\tfor (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++);\n\n\t\tif ((e = strchr(e2, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*e2 == '=')\n\t\t{\n\t\t\tc2 = *e2;\n\t\t\t*e2 = 0;\n\t\t}\n\t\telse\n\t\t\te2 = NULL;\n\n\t\tif (!strcmp(p, \"altorder\"))\n\t\t{\n\t\t\tif (!e2 || !e2[1] || e2[1] == ',') goto err;\n\t\t\tfs_mod->ordering = atoi(e2 + 1);\n\t\t\t// unsplitted altorder in 0x03 mask. 0x04 reserved. splitted altorder in 0x18 mask\n\t\t\tif ((fs_mod->ordering & 0xFFFFFFE4) || (((fs_mod->ordering>>3) & 3)>2)) goto err;\n\t\t}\n\t\telse if (strcmp(p, \"none\"))\n\t\t\tgoto err;\n\n\t\tif (e2) *e2 = c2;\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\nerr:\n\tif (e2) *e2 = c2;\n\tif (e) *e++ = c;\n\treturn false;\n}\n\nstatic bool parse_tcpmod(char *opt, struct tcp_mod *tcp_mod)\n{\n\tchar *e, *e2, *p, c, c2;\n\n\tfor (p = opt; p; )\n\t{\n\t\tfor (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++);\n\n\t\tif ((e = strchr(e2, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\n\t\tif (*e2 == '=')\n\t\t{\n\t\t\tc2 = *e2;\n\t\t\t*e2 = 0;\n\t\t}\n\t\telse\n\t\t\te2 = NULL;\n\n\t\tif (!strcmp(p, \"seq\"))\n\t\t{\n\t\t\ttcp_mod->seq = true;\n\t\t}\n\t\telse if (strcmp(p, \"none\"))\n\t\t\tgoto err;\n\n\t\tif (e2) *e2 = c2;\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\treturn true;\nerr:\n\tif (e2) *e2 = c2;\n\tif (e) *e++ = c;\n\treturn false;\n}\n\nstatic bool parse_fooling(char *opt, unsigned int *fooling_mode)\n{\n\tchar *e, *p = opt;\n\twhile (p)\n\t{\n\t\te = strchr(p, ',');\n\t\tif (e) *e++ = 0;\n\t\tif (!strcmp(p, \"md5sig\"))\n\t\t\t*fooling_mode |= FOOL_MD5SIG;\n\t\telse if (!strcmp(p, \"ts\"))\n\t\t\t*fooling_mode |= FOOL_TS;\n\t\telse if (!strcmp(p, \"badsum\"))\n\t\t\t*fooling_mode |= FOOL_BADSUM;\n\t\telse if (!strcmp(p, \"badseq\"))\n\t\t\t*fooling_mode |= FOOL_BADSEQ;\n\t\telse if (!strcmp(p, \"datanoack\"))\n\t\t\t*fooling_mode |= FOOL_DATANOACK;\n\t\telse if (!strcmp(p, \"hopbyhop\"))\n\t\t\t*fooling_mode |= FOOL_HOPBYHOP;\n\t\telse if (!strcmp(p, \"hopbyhop2\"))\n\t\t\t*fooling_mode |= FOOL_HOPBYHOP2;\n\t\telse if (strcmp(p, \"none\"))\n\t\t\treturn false;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_strlist(char *opt, struct str_list_head *list)\n{\n\tchar *e, *p = optarg;\n\twhile (p)\n\t{\n\t\te = strchr(p, ',');\n\t\tif (e) *e++ = 0;\n\t\tif (*p && !strlist_add(list, p))\n\t\t\treturn false;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_tcpflags(char *opt, uint16_t *fl)\n{\n\tunsigned int u;\n\tchar *e, *p, c;\n\n\tif (sscanf(optarg, \"0x%X\", &u)<=0 && sscanf(optarg, \"%u\", &u)<=0)\n\t{\n\t\t*fl=0;\n\t\tfor (p = opt; p; )\n\t\t{\n\t\t\tif ((e = strchr(p, ',')))\n\t\t\t{\n\t\t\t\tc = *e;\n\t\t\t\t*e = 0;\n\t\t\t}\n\n\t\t\tif (!strcasecmp(p, \"FIN\"))\n\t\t\t\t*fl |= TH_FIN;\n\t\t\telse if (!strcasecmp(p, \"SYN\"))\n\t\t\t\t*fl |= TH_SYN;\n\t\t\telse if (!strcasecmp(p, \"RST\"))\n\t\t\t\t*fl |= TH_RST;\n\t\t\telse if (!strcasecmp(p, \"PSH\") || !strcasecmp(p, \"PUSH\"))\n\t\t\t\t*fl |= TH_PUSH;\n\t\t\telse if (!strcasecmp(p, \"ACK\"))\n\t\t\t\t*fl |= TH_ACK;\n\t\t\telse if (!strcasecmp(p, \"URG\"))\n\t\t\t\t*fl |= TH_URG;\n\t\t\telse if (!strcasecmp(p, \"ECE\"))\n\t\t\t\t*fl |= 0x40;\n\t\t\telse if (!strcasecmp(p, \"CWR\"))\n\t\t\t\t*fl |= 0x80;\n\t\t\telse if (!strcasecmp(p, \"AE\") || !strcasecmp(p, \"AECN\") || !strcasecmp(p, \"ACCECN\"))\n\t\t\t\t*fl |= 0x100;\n\t\t\telse if (!strcasecmp(p, \"R1\"))\n\t\t\t\t*fl |= 0x200;\n\t\t\telse if (!strcasecmp(p, \"R2\"))\n\t\t\t\t*fl |= 0x400;\n\t\t\telse if (!strcasecmp(p, \"R3\"))\n\t\t\t\t*fl |= 0x800;\n\t\t\telse\n\t\t\t\treturn false;\n\t\t\tif (e) *e++ = c;\n\t\t\tp = e;\n\t\t}\n\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\t*fl = u & 0xFFF;\n\t\treturn *fl==u;\n\t}\n}\n\n\n\nstatic void split_compat(struct desync_profile *dp)\n{\n\tif (!dp->split_count)\n\t{\n\t\tdp->splits[dp->split_count].marker = PM_ABS;\n\t\tdp->splits[dp->split_count].pos = 2;\n\t\tdp->split_count++;\n\t}\n\tif ((dp->seqovl.marker != PM_ABS || dp->seqovl.pos < 0) && (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_MULTISPLIT))\n\t{\n\t\tDLOG_ERR(\"split seqovl supports only absolute positive positions\\n\");\n\t\texit_clean(1);\n\t}\n}\n\nstatic void SplitDebug(void)\n{\n\tstruct desync_profile_list *dpl;\n\tconst struct desync_profile *dp;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tdp = &dpl->dp;\n\t\tfor (int x = 0; x < dp->split_count; x++)\n\t\t\tDLOG(\"profile %d multisplit %s %d\\n\", dp->n, posmarker_name(dp->splits[x].marker), dp->splits[x].pos);\n\t\tif (!PROTO_POS_EMPTY(&dp->seqovl)) DLOG(\"profile %d seqovl %s %d\\n\", dp->n, posmarker_name(dp->seqovl.marker), dp->seqovl.pos);\n\t\tif (!PROTO_POS_EMPTY(&dp->hostfakesplit_midhost)) DLOG(\"profile %d hostfakesplit midhost %s %d\\n\", dp->n, posmarker_name(dp->hostfakesplit_midhost.marker), dp->hostfakesplit_midhost.pos);\n\t}\n}\n\nstatic bool onetime_tls_mod_blob(int profile_n, int fake_n, const struct fake_tls_mod *tls_mod, uint8_t *fake_tls, size_t *fake_tls_size, size_t fake_tls_buf_size, struct fake_tls_mod_cache *modcache)\n{\n\tconst uint8_t *ext;\n\tsize_t extlen;\n\n\tmodcache->extlen_offset = modcache->padlen_offset = 0;\n\tif (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_PADENCAP))\n\t{\n\t\tif (!TLSFindExtLen(fake_tls, *fake_tls_size, &modcache->extlen_offset))\n\t\t{\n\t\t\tDLOG_ERR(\"profile %d fake[%d] padencap set but tls fake structure invalid\\n\", profile_n, fake_n);\n\t\t\treturn false;\n\t\t}\n\t\tDLOG(\"profile %d fake[%d] tls extensions length offset : %zu\\n\", profile_n, fake_n, modcache->extlen_offset);\n\t\tif (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI))\n\t\t{\n\t\t\tsize_t slen;\n\t\t\tif (!TLSFindExt(fake_tls, *fake_tls_size, 0, &ext, &extlen, false))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"profile %d fake[%d] sni mod is set but tls fake does not have SNI\\n\", profile_n, fake_n);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tuint8_t *sniext = fake_tls + (ext - fake_tls);\n\t\t\tif (!TLSAdvanceToHostInSNI(&ext, &extlen, &slen))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"profile %d fake[%d] sni set but tls fake has invalid SNI structure\\n\", profile_n, fake_n);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tuint8_t *sni = fake_tls + (ext - fake_tls);\n\t\t\tif (tls_mod->mod & FAKE_TLS_MOD_SNI)\n\t\t\t{\n\t\t\t\tsize_t slen_new = strlen(tls_mod->sni);\n\t\t\t\tssize_t slen_delta = slen_new - slen;\n\t\t\t\tchar *s1 = NULL;\n\t\t\t\tif (params.debug)\n\t\t\t\t{\n\t\t\t\t\tif ((s1 = malloc(slen + 1)))\n\t\t\t\t\t{\n\t\t\t\t\t\tmemcpy(s1, sni, slen); s1[slen] = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (slen_delta)\n\t\t\t\t{\n\t\t\t\t\tif ((*fake_tls_size + slen_delta) > fake_tls_buf_size)\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG_ERR(\"profile %d fake[%d] not enough space for new SNI\\n\", profile_n, fake_n);\n\t\t\t\t\t\tfree(s1);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tmemmove(sni + slen_new, sni + slen, fake_tls + *fake_tls_size - (sni + slen));\n\t\t\t\t\tphton16(fake_tls + 3, (uint16_t)(pntoh16(fake_tls + 3) + slen_delta));\n\t\t\t\t\tphton24(fake_tls + 6, (uint32_t)(pntoh24(fake_tls + 6) + slen_delta));\n\t\t\t\t\tphton16(fake_tls + modcache->extlen_offset, (uint16_t)(pntoh16(fake_tls + modcache->extlen_offset) + slen_delta));\n\t\t\t\t\tphton16(sniext - 2, (uint16_t)(pntoh16(sniext - 2) + slen_delta));\n\t\t\t\t\tphton16(sniext, (uint16_t)(pntoh16(sniext) + slen_delta));\n\t\t\t\t\tphton16(sni - 2, (uint16_t)(pntoh16(sni - 2) + slen_delta));\n\t\t\t\t\t*fake_tls_size += slen_delta;\n\t\t\t\t\tslen = slen_new;\n\t\t\t\t}\n\t\t\t\tDLOG(\"profile %d fake[%d] change SNI : %s => %s size_delta=%zd\\n\", profile_n, fake_n, s1, tls_mod->sni, slen_delta);\n\t\t\t\tfree(s1);\n\n\t\t\t\tmemcpy(sni, tls_mod->sni, slen_new);\n\t\t\t}\n\t\t\tif (tls_mod->mod & FAKE_TLS_MOD_RND_SNI)\n\t\t\t{\n\t\t\t\tif (!slen)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"profile %d fake[%d] rndsni set but tls fake has zero sized SNI\\n\", profile_n, fake_n);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tchar *s1 = NULL, *s2 = NULL;\n\t\t\t\tif (params.debug)\n\t\t\t\t{\n\t\t\t\t\tif ((s1 = malloc(slen + 1)))\n\t\t\t\t\t{\n\t\t\t\t\t\tmemcpy(s1, sni, slen); s1[slen] = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfill_random_az(sni, 1);\n\t\t\t\tif (slen >= 7) // domain name in SNI must be at least 3 chars long to enable xxx.tls randomization\n\t\t\t\t{\n\t\t\t\t\tfill_random_az09(sni + 1, slen - 5);\n\t\t\t\t\tsni[slen - 4] = '.';\n\t\t\t\t\tmemcpy(sni + slen - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tfill_random_az09(sni + 1, slen - 1);\n\n\t\t\t\tif (params.debug)\n\t\t\t\t{\n\t\t\t\t\tif (s1 && (s2 = malloc(slen + 1)))\n\t\t\t\t\t{\n\t\t\t\t\t\tmemcpy(s2, sni, slen); s2[slen] = 0;\n\t\t\t\t\t\tDLOG(\"profile %d fake[%d] generated random SNI : %s -> %s\\n\", profile_n, fake_n, s1, s2);\n\t\t\t\t\t}\n\t\t\t\t\tfree(s1); free(s2);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (tls_mod->mod & FAKE_TLS_MOD_PADENCAP)\n\t\t{\n\t\t\tif (TLSFindExt(fake_tls, *fake_tls_size, 21, &ext, &extlen, false))\n\t\t\t{\n\t\t\t\tif ((ext - fake_tls + extlen) != *fake_tls_size)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"profile %d fake[%d] tls padding ext is present but it's not at the end. padding ext offset %zu, padding ext size %zu, fake size %zu\\n\", profile_n, fake_n, ext - fake_tls, extlen, *fake_tls_size);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tmodcache->padlen_offset = ext - fake_tls - 2;\n\t\t\t\tDLOG(\"profile %d fake[%d] tls padding ext is present, padding length offset %zu\\n\", profile_n, fake_n, modcache->padlen_offset);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif ((*fake_tls_size + 4) > fake_tls_buf_size)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"profile %d fake[%d] tls padding is absent and there's no space to add it\\n\", profile_n, fake_n);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tphton16(fake_tls + *fake_tls_size, 21);\n\t\t\t\t*fake_tls_size += 2;\n\t\t\t\tmodcache->padlen_offset = *fake_tls_size;\n\t\t\t\tphton16(fake_tls + *fake_tls_size, 0);\n\t\t\t\t*fake_tls_size += 2;\n\t\t\t\tphton16(fake_tls + modcache->extlen_offset, pntoh16(fake_tls + modcache->extlen_offset) + 4);\n\t\t\t\tphton16(fake_tls + 3, pntoh16(fake_tls + 3) + 4); // increase tls record len\n\t\t\t\tphton24(fake_tls + 6, pntoh24(fake_tls + 6) + 4); // increase tls handshake len\n\t\t\t\tDLOG(\"profile %d fake[%d] tls padding is absent. added. padding length offset %zu\\n\", profile_n, fake_n, modcache->padlen_offset);\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\nstatic bool onetime_tls_mod(struct desync_profile *dp)\n{\n\tstruct blob_item *fake_tls;\n\tstruct fake_tls_mod *tls_mod;\n\tint n = 0;\n\n\tLIST_FOREACH(fake_tls, &dp->fake_tls, next)\n\t{\n\t\t++n;\n\t\ttls_mod = (struct fake_tls_mod *)fake_tls->extra2;\n\t\tif (!tls_mod) continue;\n\t\tif (dp->n && !(tls_mod->mod & (FAKE_TLS_MOD_SET | FAKE_TLS_MOD_CUSTOM_FAKE)))\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_RND | FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_DUP_SID; // old behavior compat + dup_sid\n\t\tif (!(tls_mod->mod & ~FAKE_TLS_MOD_SAVE_MASK))\n\t\t\tcontinue;\n\n\t\tif (!IsTLSClientHello(fake_tls->data, fake_tls->size, false) || (fake_tls->size < (44 + fake_tls->data[43]))) // has session id ?\n\t\t{\n\t\t\tDLOG(\"profile %d fake[%d] tls mod set but tls fake structure invalid.\\n\", dp->n, n);\n\t\t\treturn false;\n\t\t}\n\t\tif (!fake_tls->extra)\n\t\t{\n\t\t\tfake_tls->extra = malloc(sizeof(struct fake_tls_mod_cache));\n\t\t\tif (!fake_tls->extra) return false;\n\t\t}\n\t\tif (!onetime_tls_mod_blob(dp->n, n, tls_mod, fake_tls->data, &fake_tls->size, fake_tls->size_buf, (struct fake_tls_mod_cache*)fake_tls->extra))\n\t\t\treturn false;\n\t\tif (fake_tls->offset >= fake_tls->size)\n\t\t{\n\t\t\tDLOG(\"profile %d fake[%d] tls mod shrinked data to %zu and offset %zu is now out of data range\\n\", dp->n, n, fake_tls->size, fake_tls->offset);\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic struct blob_item *load_blob_to_collection(const char *filename, struct blob_collection_head *blobs, size_t max_size, size_t size_reserve)\n{\n\tstruct blob_item *blob = blob_collection_add(blobs);\n\tuint8_t *p;\n\n\tif (!blob || (!(blob->data = malloc(max_size + size_reserve))))\n\t{\n\t\tDLOG_ERR(\"out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\tblob->size = max_size;\n\tload_file_or_exit(filename, blob->data, &blob->size, &blob->offset);\n\tp = realloc(blob->data, blob->size + size_reserve);\n\tif (!p)\n\t{\n\t\tDLOG_ERR(\"out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\tblob->data = p;\n\tblob->size_buf = blob->size + size_reserve;\n\treturn blob;\n}\nstatic struct blob_item *load_const_blob_to_collection(const void *data, size_t sz, struct blob_collection_head *blobs, size_t size_reserve, size_t offset)\n{\n\tif (offset >= sz)\n\t{\n\t\tDLOG_ERR(\"offset %zu is out of data range %zu\\n\", offset, sz);\n\t\texit_clean(1);\n\t}\n\tstruct blob_item *blob = blob_collection_add(blobs);\n\tif (!blob || (!(blob->data = malloc(sz + size_reserve))))\n\t{\n\t\tDLOG_ERR(\"out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\tblob->size = sz;\n\tblob->size_buf = sz + size_reserve;\n\tblob->offset = offset;\n\tmemcpy(blob->data, data, sz);\n\treturn blob;\n}\n\n\n#ifdef __CYGWIN__\nstatic bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len)\n{\n\tchar *e, *p, c, s1[64];\n\tport_filter pf;\n\tint n;\n\n\tif (len < 3) return false;\n\n\tfor (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++)\n\t{\n\t\tif ((e = strchr(p, ',')))\n\t\t{\n\t\t\tc = *e;\n\t\t\t*e = 0;\n\t\t}\n\t\tif (!pf_parse(p, &pf)) return false;\n\n\t\tif (pf.from == pf.to)\n\t\t\tsnprintf(s1, sizeof(s1), \"(%s.%s %s %u)\", l4, portname, pf.neg ? \"!=\" : \"==\", pf.from);\n\t\telse\n\t\t\tsnprintf(s1, sizeof(s1), \"(%s.%s %s %u %s %s.%s %s %u)\", l4, portname, pf.neg ? \"<\" : \">=\", pf.from, pf.neg ? \"or\" : \"and\", l4, portname, pf.neg ? \">\" : \"<=\", pf.to);\n\t\tif (n) strncat(buf, \" or \", len - strlen(buf) - 1);\n\t\tstrncat(buf, s1, len - strlen(buf) - 1);\n\n\t\tif (e) *e++ = c;\n\t\tp = e;\n\t}\n\tstrncat(buf, \")\", len - strlen(buf) - 1);\n\treturn true;\n}\n\n#define DIVERT_NO_LOCALNETSv4_DST \"(\" \\\n                   \"(ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and \" \\\n                   \"(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and \" \\\n                   \"(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and \" \\\n                   \"(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and \" \\\n                   \"(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255))\"\n#define DIVERT_NO_LOCALNETSv4_SRC \"(\" \\\n                   \"(ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and \" \\\n                   \"(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and \" \\\n                   \"(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and \" \\\n                   \"(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and \" \\\n                   \"(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255))\"\n\n#define DIVERT_NO_LOCALNETSv6_DST \"(\" \\\n                   \"(ipv6.DstAddr > ::1) and \" \\\n                   \"(ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and \" \\\n                   \"(ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and \" \\\n                   \"(ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and \" \\\n                   \"(ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))\"\n#define DIVERT_NO_LOCALNETSv6_SRC \"(\" \\\n                   \"(ipv6.SrcAddr > ::1) and \" \\\n                   \"(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and \" \\\n                   \"(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and \" \\\n                   \"(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and \" \\\n                   \"(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0))\"\n\n#define DIVERT_NO_LOCALNETS_SRC \"(\" DIVERT_NO_LOCALNETSv4_SRC \" or \" DIVERT_NO_LOCALNETSv6_SRC \")\"\n#define DIVERT_NO_LOCALNETS_DST \"(\" DIVERT_NO_LOCALNETSv4_DST \" or \" DIVERT_NO_LOCALNETSv6_DST \")\"\n\n#define DIVERT_TCP_NOT_EMPTY \"(!tcp or tcp.Syn or tcp.Rst or tcp.Fin or tcp.PayloadLength>0)\"\n#define DIVERT_TCP_INBOUNDS \"(tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin)\"\n\n// HTTP/1.? 30(2|7)\n#define DIVERT_HTTP_REDIRECT \"tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)\"\n\n#define DIVERT_PROLOG \"!impostor and !loopback\"\n\nstatic bool wf_make_filter(\n\tchar *wf, size_t len,\n\tunsigned int IfIdx, unsigned int SubIfIdx,\n\tbool ipv4, bool ipv6,\n\tconst char *pf_tcp_src, const char *pf_tcp_dst,\n\tconst char *pf_udp_src, const char *pf_udp_dst,\n\tconst struct str_list_head *wf_raw_part,\n\tbool bFilterOutLAN)\n{\n\tchar pf_dst_buf[8192], iface[64];\n\tstruct str_list *wfpart;\n\tint n;\n\tconst char *pf_dst;\n\tconst char *f_tcpin = *pf_tcp_src ? dp_list_have_autohostlist(&params.desync_profiles) ? \"(\" DIVERT_TCP_INBOUNDS \" or (\" DIVERT_HTTP_REDIRECT \"))\" : DIVERT_TCP_INBOUNDS : \"\";\n\tconst char *f_tcp_not_empty = (*pf_tcp_src && !dp_list_need_all_out(&params.desync_profiles)) ? DIVERT_TCP_NOT_EMPTY \" and \" : \"\";\n\tsnprintf(iface, sizeof(iface), \" ifIdx=%u and subIfIdx=%u and\", IfIdx, SubIfIdx);\n\n\tsnprintf(wf, len, \"%s and%s%s\\n(\",\n\t\tDIVERT_PROLOG,\n\t\tIfIdx ? iface : \"\",\n\t\tipv4 ? ipv6 ? \"\" : \" ip and\" : \" ipv6 and\"\n\t);\n\n\tn = 0;\n\tif (!LIST_EMPTY(wf_raw_part))\n\t{\n\t\tLIST_FOREACH(wfpart, wf_raw_part, next)\n\t\t{\n\t\t\tsnprintf(wf + strlen(wf), len - strlen(wf), \"%s(\\n%s\\n )\", n ? \"\\n or\\n \" : \"\\n \", wfpart->str);\n\t\t\tn++;\n\t\t}\n\t}\n\n\tif (*pf_tcp_src || *pf_udp_src)\n\t{\n\t\tif (*pf_tcp_src && *pf_udp_src)\n\t\t{\n\t\t\tsnprintf(pf_dst_buf, sizeof(pf_dst_buf), \"(%s or %s)\", pf_tcp_dst, pf_udp_dst);\n\t\t\tpf_dst = pf_dst_buf;\n\t\t}\n\t\telse\n\t\t\tpf_dst = *pf_tcp_dst ? pf_tcp_dst : pf_udp_dst;\n\n\t\tsnprintf(wf + strlen(wf), len - strlen(wf), n++ ? \"\\n or\\n \" : \"\\n \");\n\n\t\tsnprintf(wf + strlen(wf), len - strlen(wf),\n\t\t\t\"(\\n  (outbound and %s%s)\\n  or\\n  (inbound and tcp%s%s%s%s%s)\\n )\",\n\t\t\tf_tcp_not_empty,\n\t\t\tpf_dst,\n\t\t\t*pf_tcp_src ? \"\" : \" and false\",\n\t\t\t*f_tcpin ? \" and \" : \"\",\n\t\t\t*f_tcpin ? f_tcpin : \"\",\n\t\t\t*pf_tcp_src ? \" and \" : \"\",\n\t\t\t*pf_tcp_src ? pf_tcp_src : \"\");\n\n\t}\n\tstrncat(wf, \"\\n)\", len - strlen(wf) - 1);\n\n\tif (bFilterOutLAN)\n\t\tsnprintf(wf + strlen(wf), len - strlen(wf), \"\\nand\\n(\\n outbound and %s\\n or\\n inbound and %s\\n)\\n\",\n\t\t\tipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_DST : DIVERT_NO_LOCALNETSv4_DST : DIVERT_NO_LOCALNETSv6_DST,\n\t\t\tipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_SRC : DIVERT_NO_LOCALNETSv4_SRC : DIVERT_NO_LOCALNETSv6_SRC);\n\n\treturn true;\n}\n\nstatic unsigned int hash_jen(const void *data, unsigned int len)\n{\n\tunsigned int hash;\n\tHASH_JEN(data, len, hash);\n\treturn hash;\n}\n\n#endif\n\n\nstatic void exithelp(void)\n{\n\tprintf(\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\t\t\" @<config_file>|$<config_file>\\t\\t\\t\\t; read file for options. must be the only argument. other options are ignored.\\n\\n\"\n#endif\n#ifdef __ANDROID__\n\t\t\" --debug=0|1|syslog|android|@<filename>\\n\"\n#else\n\t\t\" --debug=0|1|syslog|@<filename>\\n\"\n#endif\n\t\t\" --version\\t\\t\\t\\t\\t\\t; print version and exit\\n\"\n\t\t\" --dry-run\\t\\t\\t\\t\\t\\t; verify parameters and exit with code 0 if successful\\n\"\n\t\t\" --comment=any_text\\n\"\n#ifdef __linux__\n\t\t\" --qnum=<nfqueue_number>\\n\"\n#elif defined(BSD)\n\t\t\" --port=<port>\\t\\t\\t\\t\\t\\t; divert port\\n\"\n#endif\n\t\t\" --daemon\\t\\t\\t\\t\\t\\t; daemonize\\n\"\n\t\t\" --pidfile=<filename>\\t\\t\\t\\t\\t; write pid to file\\n\"\n#ifndef __CYGWIN__\n\t\t\" --user=<username>\\t\\t\\t\\t\\t; drop root privs\\n\"\n\t\t\" --uid=uid[:gid1,gid2,...]\\t\\t\\t\\t; drop root privs\\n\"\n#endif\n#ifdef __linux__\n\t\t\" --bind-fix4\\t\\t\\t\\t\\t\\t; apply outgoing interface selection fix for generated ipv4 packets\\n\"\n\t\t\" --bind-fix6\\t\\t\\t\\t\\t\\t; apply outgoing interface selection fix for generated ipv6 packets\\n\"\n#endif\n\t\t\" --ctrack-timeouts=S:E:F[:U]\\t\\t\\t\\t; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default %u:%u:%u:%u\\n\"\n\t\t\" --ctrack-disable=[0|1]\\t\\t\\t\\t\\t; 1 or no argument disables conntrack\\n\"\n\t\t\" --ipcache-lifetime=<int>\\t\\t\\t\\t; time in seconds to keep cached hop count and domain name (default %u). 0 = no expiration\\n\"\n\t\t\" --ipcache-hostname=[0|1]\\t\\t\\t\\t; 1 or no argument enables ip->hostname caching\\n\"\n#ifdef __CYGWIN__\n\t\t\"\\nWINDIVERT FILTER:\\n\"\n\t\t\" --wf-iface=<int>[.<int>]\\t\\t\\t\\t; numeric network interface and subinterface indexes\\n\"\n\t\t\" --wf-l3=ipv4|ipv6\\t\\t\\t\\t\\t; L3 protocol filter. multiple comma separated values allowed.\\n\"\n\t\t\" --wf-tcp=[~]port1[-port2]\\t\\t\\t\\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\\n\"\n\t\t\" --wf-udp=[~]port1[-port2]\\t\\t\\t\\t; UDP port filter. ~ means negation. multiple comma separated values allowed.\\n\"\n\t\t\" --wf-raw-part=<filter>|@<filename>\\t\\t\\t; partial raw windivert filter string or filename\\n\"\n\t\t\" --wf-filter-lan=0|1\\t\\t\\t\\t\\t; add excluding filter for non-global IP (default : 1)\\n\"\n\t\t\" --wf-raw=<filter>|@<filename>\\t\\t\\t\\t; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part\\n\"\n\t\t\" --wf-save=<filename>\\t\\t\\t\\t\\t; save windivert filter string to a file and exit\\n\"\n\t\t\"\\nLOGICAL NETWORK FILTER:\\n\"\n\t\t\" --ssid-filter=ssid1[,ssid2,ssid3,...]\\t\\t\\t; enable winws only if any of specified wifi SSIDs connected\\n\"\n\t\t\" --nlm-filter=net1[,net2,net3,...]\\t\\t\\t; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted.\\n\"\n\t\t\" --nlm-list[=all]\\t\\t\\t\\t\\t; list Network List Manager (NLM) networks. connected only or all.\\n\"\n#endif\n\t\t\"\\nMULTI-STRATEGY:\\n\"\n\t\t\" --new\\t\\t\\t\\t\\t\\t\\t; begin new strategy\\n\"\n\t\t\" --skip\\t\\t\\t\\t\\t\\t\\t; do not use this strategy\\n\"\n\t\t\" --filter-l3=ipv4|ipv6\\t\\t\\t\\t\\t; L3 protocol filter. multiple comma separated values allowed.\\n\"\n\t\t\" --filter-tcp=[~]port1[-port2]|*\\t\\t\\t; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. comma separated list allowed.\\n\"\n\t\t\" --filter-udp=[~]port1[-port2]|*\\t\\t\\t; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list allowed.\\n\"\n\t\t\" --filter-l7=[http|tls|quic|wireguard|dht|discord|stun|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed.\\n\"\n#ifdef HAS_FILTER_SSID\n\t\t\" --filter-ssid=ssid1[,ssid2,ssid3,...]\\t\\t\\t; per profile wifi SSID filter\\n\"\n#endif\n\t\t\" --ipset=<filename>\\t\\t\\t\\t\\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\\n\"\n\t\t\" --ipset-ip=<ip_list>\\t\\t\\t\\t\\t; comma separated fixed subnet list\\n\"\n\t\t\" --ipset-exclude=<filename>\\t\\t\\t\\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\\n\"\n\t\t\" --ipset-exclude-ip=<ip_list>\\t\\t\\t\\t; comma separated fixed subnet list\\n\"\n\t\t\"\\nHOSTLIST FILTER:\\n\"\n\t\t\" --hostlist=<filename>\\t\\t\\t\\t\\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\\n\"\n\t\t\" --hostlist-domains=<domain_list>\\t\\t\\t; comma separated fixed domain list\\n\"\n\t\t\" --hostlist-exclude=<filename>\\t\\t\\t\\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\\n\"\n\t\t\" --hostlist-exclude-domains=<domain_list>\\t\\t; comma separated fixed domain list\\n\"\n\t\t\" --hostlist-auto=<filename>\\t\\t\\t\\t; detect DPI blocks and build hostlist automatically\\n\"\n\t\t\" --hostlist-auto-fail-threshold=<int>\\t\\t\\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\\n\"\n\t\t\" --hostlist-auto-fail-time=<int>\\t\\t\\t; all failed attemps must be within these seconds (default : %d)\\n\"\n\t\t\" --hostlist-auto-retrans-threshold=<int>\\t\\t; how many request retransmissions cause attempt to fail (default : %d)\\n\"\n\t\t\" --hostlist-auto-debug=<logfile>\\t\\t\\t; debug auto hostlist positives\\n\"\n\t\t\"\\nTAMPER:\\n\"\n\t\t\" --wsize=<window_size>[:<scale_factor>]\\t\\t\\t; set window size. 0 = do not modify. OBSOLETE !\\n\"\n\t\t\" --wssize=<window_size>[:<scale_factor>]\\t\\t; set window size for server. 0 = do not modify. default scale_factor = 0.\\n\"\n\t\t\" --wssize-cutoff=[n|d|s]N\\t\\t\\t\\t; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\\n\"\n\t\t\" --wssize-forced-cutoff=0|1\\t\\t\\t\\t; 1(default)=auto cutoff wssize on known protocol\\n\"\n\t\t\" --synack-split=[syn|synack|acksyn]\\t\\t\\t; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN\\n\"\n\t\t\" --orig-ttl=<int>\\t\\t\\t\\t\\t; set TTL for original packets\\n\"\n\t\t\" --orig-ttl6=<int>\\t\\t\\t\\t\\t; set ipv6 hop limit for original packets. by default ttl value is used\\n\"\n\t\t\" --orig-autottl=[<delta>[:<min>[-<max>]]|-]\\t\\t; auto ttl mode for both ipv4 and ipv6. default: +%d:%u-%u\\n\"\n\t\t\" --orig-autottl6=[<delta>[:<min>[-<max>]]|-]\\t\\t; overrides --orig-autottl for ipv6 only\\n\"\n\t\t\" --orig-tcp-flags-set=<int|0xHEX|flaglist>\\t\\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\\n\"\n\t\t\" --orig-tcp-flags-unset=<int|0xHEX|flaglist>\\t\\t; unset these tcp flags (flags &= ~value)\\n\"\n\t\t\" --orig-mod-start=[n|d|s]N\\t\\t\\t\\t; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\\n\"\n\t\t\" --orig-mod-cutoff=[n|d|s]N\\t\\t\\t\\t; apply orig TTL mod to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\\n\"\n\t\t\" --dup=<int>\\t\\t\\t\\t\\t\\t; duplicate original packets. send N dups before original.\\n\"\n\t\t\" --dup-replace=[0|1]\\t\\t\\t\\t\\t; 1 or no argument means do not send original, only dups\\n\"\n\t\t\" --dup-ttl=<int>\\t\\t\\t\\t\\t; set TTL for dups\\n\"\n\t\t\" --dup-ttl6=<int>\\t\\t\\t\\t\\t; set ipv6 hop limit for dups. by default ttl value is used\\n\"\n\t\t\" --dup-autottl=[<delta>[:<min>[-<max>]]|-]\\t\\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\\n\"\n\t\t\" --dup-autottl6=[<delta>[:<min>[-<max>]]|-]\\t\\t; overrides --dup-autottl for ipv6 only\\n\"\n\t\t\" --dup-tcp-flags-set=<int|0xHEX|flaglist>\\t\\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\\n\"\n\t\t\" --dup-tcp-flags-unset=<int|0xHEX|flaglist>\\t\\t; unset these tcp flags (flags &= ~value)\\n\"\n\t\t\" --dup-fooling=<mode>[,<mode>]\\t\\t\\t\\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\\n\"\n\t\t\" --dup-ts-increment=<int|0xHEX>\\t\\t\\t\\t; ts fooling TSval signed increment for dup. default %d\\n\"\n\t\t\" --dup-badseq-increment=<int|0xHEX>\\t\\t\\t; badseq fooling seq signed increment for dup. default %d\\n\"\n\t\t\" --dup-badack-increment=<int|0xHEX>\\t\\t\\t; badseq fooling ackseq signed increment for dup. default %d\\n\"\n\t\t\" --dup-ip-id=same|zero|seq|rnd\\t\\t\\t\\t; ipv4 ip_id mode for dupped packets\\n\"\n\t\t\" --dup-start=[n|d|s]N\\t\\t\\t\\t\\t; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\\n\"\n\t\t\" --dup-cutoff=[n|d|s]N\\t\\t\\t\\t\\t; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\\n\"\n\t\t\" --hostcase\\t\\t\\t\\t\\t\\t; change Host: => host:\\n\"\n\t\t\" --hostspell\\t\\t\\t\\t\\t\\t; exact spelling of \\\"Host\\\" header. must be 4 chars. default is \\\"host\\\"\\n\"\n\t\t\" --hostnospace\\t\\t\\t\\t\\t\\t; remove space after Host: and add it to User-Agent: to preserve packet size\\n\"\n\t\t\" --domcase\\t\\t\\t\\t\\t\\t; mix domain case : Host: TeSt.cOm\\n\"\n\t\t\" --methodeol\\t\\t\\t\\t\\t\\t; add '\\\\n' before method and remove space from Host:\\n\"\n\t\t\" --ip-id=zero|seq|seqgroup|rnd\\t\\t\\t\\t; ipv4 ip_id assignment scheme\\n\"\n\t\t\" --dpi-desync=[<mode0>,]<mode>[,<mode2>]\\t\\t; try to desync dpi state. modes :\\n\"\n\t\t\"\\t\\t\\t\\t\\t\\t\\t; synack syndata fake fakeknown rst rstack hopbyhop destopt ipfrag1\\n\"\n\t\t\"\\t\\t\\t\\t\\t\\t\\t; multisplit multidisorder fakedsplit fakeddisorder hostfakesplit ipfrag2 udplen tamper\\n\"\n#ifdef __linux__\n\t\t\" --dpi-desync-fwmark=<int|0xHEX>\\t\\t\\t; override fwmark for desync packet. default = 0x%08X (%u)\\n\"\n#elif defined(SO_USER_COOKIE)\n\t\t\" --dpi-desync-sockarg=<int|0xHEX>\\t\\t\\t; override sockarg (SO_USER_COOKIE) for desync packet. default = 0x%08X (%u)\\n\"\n#endif\n\t\t\" --dpi-desync-ttl=<int>\\t\\t\\t\\t\\t; set ttl for fakes packets\\n\"\n\t\t\" --dpi-desync-ttl6=<int>\\t\\t\\t\\t; set ipv6 hop limit for fake packet. by default --dpi-desync-ttl value is used.\\n\"\n\t\t\" --dpi-desync-autottl=[<delta>[:<min>[-<max>]]|-]\\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\\n\"\n\t\t\" --dpi-desync-autottl6=[<delta>[:<min>[-<max>]]|-]\\t; overrides --dpi-desync-autottl for ipv6 only\\n\"\n\t\t\" --dpi-desync-tcp-flags-set=<int|0xHEX|flaglist>\\t; set these tcp flags (flags |= value). value can be int, hex or comma separated list : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3\\n\"\n\t\t\" --dpi-desync-tcp-flags-unset=<int|0xHEX|flaglist>\\t; unset these tcp flags (flags &= ~value)\\n\"\n\t\t\" --dpi-desync-fooling=<mode>[,<mode>]\\t\\t\\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\\n\"\n\t\t\" --dpi-desync-repeats=<N>\\t\\t\\t\\t; send every desync packet N times\\n\"\n\t\t\" --dpi-desync-skip-nosni=0|1\\t\\t\\t\\t; 1(default)=do not act on ClientHello without SNI\\n\"\n\t\t\" --dpi-desync-split-pos=N|-N|marker+N|marker-N\\t\\t; comma separated list of split positions\\n\"\n\t\t\"\\t\\t\\t\\t\\t\\t\\t; markers: method,host,endhost,sld,endsld,midsld,sniext\\n\"\n\t\t\"\\t\\t\\t\\t\\t\\t\\t; full list is only used by multisplit and multidisorder\\n\"\n\t\t\"\\t\\t\\t\\t\\t\\t\\t; fakedsplit/fakeddisorder use first l7-protocol-compatible parameter if present, first abs value otherwise\\n\"\n\t\t\" --dpi-desync-split-seqovl=N|-N|marker+N|marker-N\\t; use sequence overlap before first sent original split segment\\n\"\n\t\t\" --dpi-desync-split-seqovl-pattern=[+ofs]@<filename>|0xHEX ; pattern for the fake part of overlap\\n\"\n\t\t\" --dpi-desync-fakedsplit-pattern=[+ofs]@<filename>|0xHEX ; fake pattern for fakedsplit/fakeddisorder\\n\"\n\t\t\" --dpi-desync-fakedsplit-mod=mod[,mod]\\t\\t\\t; mods can be none,altorder=0|1|2|3 + 0|8|16\\n\"\n\t\t\" --dpi-desync-hostfakesplit-midhost=marker+N|marker-N\\t; additionally split real hostname at specified marker. must be within host..endhost or won't be splitted.\\n\"\n\t\t\" --dpi-desync-hostfakesplit-mod=mod[,mod]\\t\\t; mods can be none,host=<hostname>,altorder=0|1\\n\"\n\t\t\" --dpi-desync-ipfrag-pos-udp=<8..%u>\\t\\t\\t; ip frag position starting from the transport header. multiple of 8, default %u.\\n\"\n\t\t\" --dpi-desync-ipfrag-pos-tcp=<8..%u>\\t\\t\\t; ip frag position starting from the transport header. multiple of 8, default %u.\\n\"\n\t\t\" --dpi-desync-ts-increment=<int|0xHEX>\\t\\t\\t; ts fooling TSval signed increment. default %d\\n\"\n\t\t\" --dpi-desync-badseq-increment=<int|0xHEX>\\t\\t; badseq fooling seq signed increment. default %d\\n\"\n\t\t\" --dpi-desync-badack-increment=<int|0xHEX>\\t\\t; badseq fooling ackseq signed increment. default %d\\n\"\n\t\t\" --dpi-desync-any-protocol=0|1\\t\\t\\t\\t; 0(default)=desync only http and tls  1=desync any nonempty data packet\\n\"\n\t\t\" --dpi-desync-fake-tcp-mod=mod[,mod]\\t\\t\\t; comma separated list of tcp fake mods. available mods : none,seq\\n\"\n\t\t\" --dpi-desync-fake-http=[+ofs]@<filename>|0xHEX\\t\\t; fake http request\\n\"\n\t\t\" --dpi-desync-fake-tls=[+ofs]@<filename>|0xHEX|![+offset] ; fake TLS ClientHello (for https)\\n\"\n\t\t\" --dpi-desync-fake-tls-mod=mod[,mod]\\t\\t\\t; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=<sni>,dupsid,padencap\\n\"\n\t\t\" --dpi-desync-fake-unknown=[+ofs]@<filename>|0xHEX\\t; unknown protocol fake payload\\n\"\n\t\t\" --dpi-desync-fake-syndata=[+ofs]@<filename>|0xHEX\\t; SYN data payload\\n\"\n\t\t\" --dpi-desync-fake-quic=[+ofs]@<filename>|0xHEX\\t\\t; fake QUIC Initial\\n\"\n\t\t\" --dpi-desync-fake-wireguard=[+ofs]@<filename>|0xHEX\\t; fake wireguard handshake initiation\\n\"\n\t\t\" --dpi-desync-fake-dht=[+ofs]@<filename>|0xHEX\\t\\t; DHT protocol fake payload (d1...e)\\n\"\n\t\t\" --dpi-desync-fake-discord=[+ofs]@<filename>|0xHEX\\t; discord protocol fake payload (Voice IP Discovery)\\n\"\n\t\t\" --dpi-desync-fake-stun=[+ofs]@<filename>|0xHEX\\t\\t; STUN protocol fake payload\\n\"\n\t\t\" --dpi-desync-fake-unknown-udp=[+ofs]@<filename>|0xHEX\\t; unknown udp protocol fake payload\\n\"\n\t\t\" --dpi-desync-udplen-increment=<int>\\t\\t\\t; increase or decrease udp packet length by N bytes (default %u). negative values decrease length.\\n\"\n\t\t\" --dpi-desync-udplen-pattern=[+ofs]@<filename>|0xHEX\\t; udp tail fill pattern\\n\"\n\t\t\" --dpi-desync-start=[n|d|s]N\\t\\t\\t\\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\\n\"\n\t\t\" --dpi-desync-cutoff=[n|d|s]N\\t\\t\\t\\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\\n\",\n\t\tCTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP,\n\t\tIPCACHE_LIFETIME,\n\t\tHOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT,\n\t\tAUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX,\n\t\tAUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX,\n\t\tTS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT,\n#if defined(__linux__) || defined(SO_USER_COOKIE)\n\t\tDPI_DESYNC_FWMARK_DEFAULT, DPI_DESYNC_FWMARK_DEFAULT,\n#endif\n\t\tAUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX,\n\t\tDPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT,\n\t\tDPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT,\n\t\tTS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT,\n\t\tUDPLEN_INCREMENT_DEFAULT\n\t);\n\texit(1);\n}\nstatic void exithelp_clean(void)\n{\n\tcleanup_params(&params);\n\texithelp();\n}\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n// no static to not allow optimizer to inline this func (save stack)\nvoid config_from_file(const char *filename)\n{\n\t// config from a file\n\tchar buf[MAX_CONFIG_FILE_SIZE];\n\tbuf[0] = 'x';\t// fake argv[0]\n\tbuf[1] = ' ';\n\tsize_t bufsize = sizeof(buf) - 3;\n\tif (!load_file(filename, buf + 2, &bufsize))\n\t{\n\t\tDLOG_ERR(\"could not load config file '%s'\\n\", filename);\n\t\texit_clean(1);\n\t}\n\tbuf[bufsize + 2] = 0;\n\t// wordexp fails if it sees \\t \\n \\r between args\n\treplace_char(buf, '\\n', ' ');\n\treplace_char(buf, '\\r', ' ');\n\treplace_char(buf, '\\t', ' ');\n\tif (wordexp(buf, &params.wexp, WRDE_NOCMD))\n\t{\n\t\tDLOG_ERR(\"failed to split command line options from file '%s'\\n\", filename);\n\t\texit_clean(1);\n\t}\n}\n#endif\n\nvoid check_dp(const struct desync_profile *dp)\n{\n\t// only linux has connbytes limiter\n\tif ((dp->desync_any_proto && !dp->desync_cutoff &&\n\t\t(dp->desync_mode == DESYNC_FAKE || dp->desync_mode == DESYNC_RST || dp->desync_mode == DESYNC_RSTACK ||\n\t\t\tdp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_HOSTFAKESPLIT ||\n\t\t\tdp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT))\n\t\t||\n\t\tdp->dup_repeats && !dp->dup_cutoff)\n\t{\n#ifdef __linux__\n\t\tDLOG_CONDUP(\"WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\\n\", dp->n);\n\t\tDLOG_CONDUP(\"WARNING !!! it's completely ok if connbytes or payload based ip/nf tables limiter is applied. Make sure it exists.\\n\");\n#else\n\t\tDLOG_CONDUP(\"WARNING !!! possible TRASH FLOOD configuration detected in profile %d\\n\", dp->n);\n\t\tDLOG_CONDUP(\"WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\\n\", dp->n);\n\t\tDLOG_CONDUP(\"WARNING !!! fakes or dups will be sent on every processed packet\\n\");\n\t\tDLOG_CONDUP(\"WARNING !!! make sure it's really what you want\\n\");\n#ifdef __CYGWIN__\n\t\tDLOG_CONDUP(\"WARNING !!! in most cases this is acceptable only with custom payload based windivert filter (--wf-raw, --wf-raw-part)\\n\");\n#endif\n#endif\n\t}\n}\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH)\n#ifdef __ANDROID__\n#define PRINT_VER printf(\"github android version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#else\n#define PRINT_VER printf(\"github version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#endif\n#else\n#ifdef __ANDROID__\n#define PRINT_VER printf(\"self-built android version %s %s\\n\\n\", __DATE__, __TIME__)\n#else\n#define PRINT_VER printf(\"self-built version %s %s\\n\\n\", __DATE__, __TIME__)\n#endif\n#endif\n\nenum opt_indices {\n\tIDX_DEBUG,\n\tIDX_DRY_RUN,\n\tIDX_VERSION,\n\tIDX_COMMENT,\n#ifdef __linux__\n\tIDX_QNUM,\n#elif defined(BSD)\n\tIDX_PORT,\n#endif\n\tIDX_DAEMON,\n\tIDX_PIDFILE,\n#ifndef __CYGWIN__\n\tIDX_USER,\n\tIDX_UID,\n#endif\n\tIDX_WSIZE,\n\tIDX_WSSIZE,\n\tIDX_WSSIZE_CUTOFF,\n\tIDX_WSSIZE_FORCED_CUTOFF,\n\tIDX_SYNACK_SPLIT,\n\tIDX_CTRACK_TIMEOUTS,\n\tIDX_CTRACK_DISABLE,\n\tIDX_IPCACHE_LIFETIME,\n\tIDX_IPCACHE_HOSTNAME,\n\tIDX_HOSTCASE,\n\tIDX_HOSTSPELL,\n\tIDX_HOSTNOSPACE,\n\tIDX_DOMCASE,\n\tIDX_METHODEOL,\n\tIDX_IP_ID,\n\tIDX_DPI_DESYNC,\n#ifdef __linux__\n\tIDX_DPI_DESYNC_FWMARK,\n#elif defined(SO_USER_COOKIE)\n\tIDX_DPI_DESYNC_SOCKARG,\n#endif\n\tIDX_DUP,\n\tIDX_DUP_TTL,\n\tIDX_DUP_TTL6,\n\tIDX_DUP_AUTOTTL,\n\tIDX_DUP_AUTOTTL6,\n\tIDX_DUP_TCP_FLAGS_SET,\n\tIDX_DUP_TCP_FLAGS_UNSET,\n\tIDX_DUP_FOOLING,\n\tIDX_DUP_TS_INCREMENT,\n\tIDX_DUP_BADSEQ_INCREMENT,\n\tIDX_DUP_BADACK_INCREMENT,\n\tIDX_DUP_REPLACE,\n\tIDX_DUP_IP_ID,\n\tIDX_DUP_START,\n\tIDX_DUP_CUTOFF,\n\tIDX_ORIG_TTL,\n\tIDX_ORIG_TTL6,\n\tIDX_ORIG_AUTOTTL,\n\tIDX_ORIG_AUTOTTL6,\n\tIDX_ORIG_TCP_FLAGS_SET,\n\tIDX_ORIG_TCP_FLAGS_UNSET,\n\tIDX_ORIG_MOD_START,\n\tIDX_ORIG_MOD_CUTOFF,\n\tIDX_DPI_DESYNC_TTL,\n\tIDX_DPI_DESYNC_TTL6,\n\tIDX_DPI_DESYNC_AUTOTTL,\n\tIDX_DPI_DESYNC_AUTOTTL6,\n\tIDX_DPI_DESYNC_TCP_FLAGS_SET,\n\tIDX_DPI_DESYNC_TCP_FLAGS_UNSET,\n\tIDX_DPI_DESYNC_FOOLING,\n\tIDX_DPI_DESYNC_REPEATS,\n\tIDX_DPI_DESYNC_SKIP_NOSNI,\n\tIDX_DPI_DESYNC_SPLIT_POS,\n\tIDX_DPI_DESYNC_SPLIT_HTTP_REQ,\n\tIDX_DPI_DESYNC_SPLIT_TLS,\n\tIDX_DPI_DESYNC_SPLIT_SEQOVL,\n\tIDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN,\n\tIDX_DPI_DESYNC_FAKEDSPLIT_PATTERN,\n\tIDX_DPI_DESYNC_FAKEDSPLIT_MOD,\n\tIDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST,\n\tIDX_DPI_DESYNC_HOSTFAKESPLIT_MOD,\n\tIDX_DPI_DESYNC_IPFRAG_POS_TCP,\n\tIDX_DPI_DESYNC_IPFRAG_POS_UDP,\n\tIDX_DPI_DESYNC_TS_INCREMENT,\n\tIDX_DPI_DESYNC_BADSEQ_INCREMENT,\n\tIDX_DPI_DESYNC_BADACK_INCREMENT,\n\tIDX_DPI_DESYNC_ANY_PROTOCOL,\n\tIDX_DPI_DESYNC_FAKE_TCP_MOD,\n\tIDX_DPI_DESYNC_FAKE_HTTP,\n\tIDX_DPI_DESYNC_FAKE_TLS,\n\tIDX_DPI_DESYNC_FAKE_TLS_MOD,\n\tIDX_DPI_DESYNC_FAKE_UNKNOWN,\n\tIDX_DPI_DESYNC_FAKE_SYNDATA,\n\tIDX_DPI_DESYNC_FAKE_QUIC,\n\tIDX_DPI_DESYNC_FAKE_WIREGUARD,\n\tIDX_DPI_DESYNC_FAKE_DHT,\n\tIDX_DPI_DESYNC_FAKE_DISCORD,\n\tIDX_DPI_DESYNC_FAKE_STUN,\n\tIDX_DPI_DESYNC_FAKE_UNKNOWN_UDP,\n\tIDX_DPI_DESYNC_UDPLEN_INCREMENT,\n\tIDX_DPI_DESYNC_UDPLEN_PATTERN,\n\tIDX_DPI_DESYNC_CUTOFF,\n\tIDX_DPI_DESYNC_START,\n\tIDX_HOSTLIST,\n\tIDX_HOSTLIST_DOMAINS,\n\tIDX_HOSTLIST_EXCLUDE,\n\tIDX_HOSTLIST_EXCLUDE_DOMAINS,\n\tIDX_HOSTLIST_AUTO,\n\tIDX_HOSTLIST_AUTO_FAIL_THRESHOLD,\n\tIDX_HOSTLIST_AUTO_FAIL_TIME,\n\tIDX_HOSTLIST_AUTO_RETRANS_THRESHOLD,\n\tIDX_HOSTLIST_AUTO_DEBUG,\n\tIDX_NEW,\n\tIDX_SKIP,\n\tIDX_FILTER_L3,\n\tIDX_FILTER_TCP,\n\tIDX_FILTER_UDP,\n\tIDX_FILTER_L7,\n#ifdef HAS_FILTER_SSID\n\tIDX_FILTER_SSID,\n#endif\n\tIDX_IPSET,\n\tIDX_IPSET_IP,\n\tIDX_IPSET_EXCLUDE,\n\tIDX_IPSET_EXCLUDE_IP,\n#ifdef __linux__\n\tIDX_BIND_FIX4,\n\tIDX_BIND_FIX6,\n#elif defined(__CYGWIN__)\n\tIDX_WF_IFACE,\n\tIDX_WF_L3,\n\tIDX_WF_TCP,\n\tIDX_WF_UDP,\n\tIDX_WF_RAW,\n\tIDX_WF_RAW_PART,\n\tIDX_WF_FILTER_LAN,\n\tIDX_WF_SAVE,\n\tIDX_SSID_FILTER,\n\tIDX_NLM_FILTER,\n\tIDX_NLM_LIST,\n#endif\n\tIDX_LAST,\n};\n\nstatic const struct option long_options[] = {\n\t[IDX_DEBUG] = {\"debug\", optional_argument, 0, 0},\n\t[IDX_DRY_RUN] = {\"dry-run\", no_argument, 0, 0},\n\t[IDX_VERSION] = {\"version\", no_argument, 0, 0},\n\t[IDX_COMMENT] = {\"comment\", optional_argument, 0, 0},\n#ifdef __linux__\n\t[IDX_QNUM] = {\"qnum\", required_argument, 0, 0},\n#elif defined(BSD)\n\t[IDX_PORT] = {\"port\", required_argument, 0, 0},\n#endif\n\t[IDX_DAEMON] = {\"daemon\", no_argument, 0, 0},\n\t[IDX_PIDFILE] = {\"pidfile\", required_argument, 0, 0},\n#ifndef __CYGWIN__\n\t[IDX_USER] = {\"user\", required_argument, 0, 0},\n\t[IDX_UID] = {\"uid\", required_argument, 0, 0},\n#endif\n\t[IDX_WSIZE] = {\"wsize\", required_argument, 0, 0},\n\t[IDX_WSSIZE] = {\"wssize\", required_argument, 0, 0},\n\t[IDX_WSSIZE_CUTOFF] = {\"wssize-cutoff\", required_argument, 0, 0},\n\t[IDX_WSSIZE_FORCED_CUTOFF] = {\"wssize-forced-cutoff\", required_argument, 0, 0},\n\t[IDX_SYNACK_SPLIT] = {\"synack-split\", optional_argument, 0, 0},\n\t[IDX_CTRACK_TIMEOUTS] = {\"ctrack-timeouts\", required_argument, 0, 0},\n\t[IDX_CTRACK_DISABLE] = {\"ctrack-disable\", optional_argument, 0, 0},\n\t[IDX_IPCACHE_LIFETIME] = {\"ipcache-lifetime\", required_argument, 0, 0},\n\t[IDX_IPCACHE_HOSTNAME] = {\"ipcache-hostname\", optional_argument, 0, 0},\n\t[IDX_HOSTCASE] = {\"hostcase\", no_argument, 0, 0},\n\t[IDX_HOSTSPELL] = {\"hostspell\", required_argument, 0, 0},\n\t[IDX_HOSTNOSPACE] = {\"hostnospace\", no_argument, 0, 0},\n\t[IDX_DOMCASE] = {\"domcase\", no_argument, 0, 0},\n\t[IDX_METHODEOL] = {\"methodeol\", no_argument, 0, 0},\n\t[IDX_IP_ID] = {\"ip-id\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC] = {\"dpi-desync\", required_argument, 0, 0},\n#ifdef __linux__\n\t[IDX_DPI_DESYNC_FWMARK] = {\"dpi-desync-fwmark\", required_argument, 0, 0},\n#elif defined(SO_USER_COOKIE)\n\t[IDX_DPI_DESYNC_SOCKARG] = {\"dpi-desync-sockarg\", required_argument, 0, 0},\n#endif\n\t[IDX_DUP] = {\"dup\", required_argument, 0, 0},\n\t[IDX_DUP_TTL] = {\"dup-ttl\", required_argument, 0, 0},\n\t[IDX_DUP_TTL6] = {\"dup-ttl6\", required_argument, 0, 0},\n\t[IDX_DUP_AUTOTTL] = {\"dup-autottl\", optional_argument, 0, 0},\n\t[IDX_DUP_AUTOTTL6] = {\"dup-autottl6\", optional_argument, 0, 0},\n\t[IDX_DUP_TCP_FLAGS_SET] = {\"dup-tcp-flags-set\", optional_argument, 0, 0},\n\t[IDX_DUP_TCP_FLAGS_UNSET] = {\"dup-tcp-flags-unset\", optional_argument, 0, 0},\n\t[IDX_DUP_FOOLING] = {\"dup-fooling\", required_argument, 0, 0},\n\t[IDX_DUP_TS_INCREMENT] = {\"dup-ts-increment\", required_argument, 0, 0},\n\t[IDX_DUP_BADSEQ_INCREMENT] = {\"dup-badseq-increment\", required_argument, 0, 0},\n\t[IDX_DUP_BADACK_INCREMENT] = {\"dup-badack-increment\", required_argument, 0, 0},\n\t[IDX_DUP_REPLACE] = {\"dup-replace\", optional_argument, 0, 0},\n\t[IDX_DUP_IP_ID] = {\"dup-ip-id\", required_argument, 0, 0},\n\t[IDX_DUP_START] = {\"dup-start\", required_argument, 0, 0},\n\t[IDX_DUP_CUTOFF] = {\"dup-cutoff\", required_argument, 0, 0},\n\t[IDX_ORIG_TTL] = {\"orig-ttl\", required_argument, 0, 0},\n\t[IDX_ORIG_TTL6] = {\"orig-ttl6\", required_argument, 0, 0},\n\t[IDX_ORIG_AUTOTTL] = {\"orig-autottl\", optional_argument, 0, 0},\n\t[IDX_ORIG_AUTOTTL6] = {\"orig-autottl6\", optional_argument, 0, 0},\n\t[IDX_ORIG_TCP_FLAGS_SET] = {\"orig-tcp-flags-set\", optional_argument, 0, 0},\n\t[IDX_ORIG_TCP_FLAGS_UNSET] = {\"orig-tcp-flags-unset\", optional_argument, 0, 0},\n\t[IDX_ORIG_MOD_START] = {\"orig-mod-start\", required_argument, 0, 0},\n\t[IDX_ORIG_MOD_CUTOFF] = {\"orig-mod-cutoff\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_TTL] = {\"dpi-desync-ttl\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_TTL6] = {\"dpi-desync-ttl6\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_AUTOTTL] = {\"dpi-desync-autottl\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_AUTOTTL6] = {\"dpi-desync-autottl6\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_TCP_FLAGS_SET] = {\"dpi-desync-tcp-flags-set\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_TCP_FLAGS_UNSET] = {\"dpi-desync-tcp-flags-unset\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FOOLING] = {\"dpi-desync-fooling\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_REPEATS] = {\"dpi-desync-repeats\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SKIP_NOSNI] = {\"dpi-desync-skip-nosni\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SPLIT_POS] = {\"dpi-desync-split-pos\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SPLIT_HTTP_REQ] = {\"dpi-desync-split-http-req\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SPLIT_TLS] = {\"dpi-desync-split-tls\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SPLIT_SEQOVL] = {\"dpi-desync-split-seqovl\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN] = {\"dpi-desync-split-seqovl-pattern\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN] = {\"dpi-desync-fakedsplit-pattern\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKEDSPLIT_MOD] = {\"dpi-desync-fakedsplit-mod\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST] = {\"dpi-desync-hostfakesplit-midhost\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD] = {\"dpi-desync-hostfakesplit-mod\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_IPFRAG_POS_TCP] = {\"dpi-desync-ipfrag-pos-tcp\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_IPFRAG_POS_UDP] = {\"dpi-desync-ipfrag-pos-udp\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_TS_INCREMENT] = {\"dpi-desync-ts-increment\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_BADSEQ_INCREMENT] = {\"dpi-desync-badseq-increment\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_BADACK_INCREMENT] = {\"dpi-desync-badack-increment\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_ANY_PROTOCOL] = {\"dpi-desync-any-protocol\", optional_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_TCP_MOD] = {\"dpi-desync-fake-tcp-mod\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_HTTP] = {\"dpi-desync-fake-http\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_TLS] = {\"dpi-desync-fake-tls\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_TLS_MOD] = {\"dpi-desync-fake-tls-mod\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_UNKNOWN] = {\"dpi-desync-fake-unknown\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_SYNDATA] = {\"dpi-desync-fake-syndata\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_QUIC] = {\"dpi-desync-fake-quic\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_WIREGUARD] = {\"dpi-desync-fake-wireguard\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_DHT] = {\"dpi-desync-fake-dht\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_DISCORD] = {\"dpi-desync-fake-discord\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_STUN] = {\"dpi-desync-fake-stun\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP] = {\"dpi-desync-fake-unknown-udp\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_UDPLEN_INCREMENT] = {\"dpi-desync-udplen-increment\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_UDPLEN_PATTERN] = {\"dpi-desync-udplen-pattern\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_CUTOFF] = {\"dpi-desync-cutoff\", required_argument, 0, 0},\n\t[IDX_DPI_DESYNC_START] = {\"dpi-desync-start\", required_argument, 0, 0},\n\t[IDX_HOSTLIST] = {\"hostlist\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_DOMAINS] = {\"hostlist-domains\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_EXCLUDE] = {\"hostlist-exclude\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_EXCLUDE_DOMAINS] = {\"hostlist-exclude-domains\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO] = {\"hostlist-auto\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {\"hostlist-auto-fail-threshold\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_FAIL_TIME] = {\"hostlist-auto-fail-time\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {\"hostlist-auto-retrans-threshold\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_DEBUG] = {\"hostlist-auto-debug\", required_argument, 0, 0},\n\t[IDX_NEW] = {\"new\", no_argument, 0, 0},\n\t[IDX_SKIP] = {\"skip\", no_argument, 0, 0},\n\t[IDX_FILTER_L3] = {\"filter-l3\", required_argument, 0, 0},\n\t[IDX_FILTER_TCP] = {\"filter-tcp\", required_argument, 0, 0},\n\t[IDX_FILTER_UDP] = {\"filter-udp\", required_argument, 0, 0},\n\t[IDX_FILTER_L7] = {\"filter-l7\", required_argument, 0, 0},\n#ifdef HAS_FILTER_SSID\n\t[IDX_FILTER_SSID] = {\"filter-ssid\", required_argument, 0, 0},\n#endif\n\t[IDX_IPSET] = {\"ipset\", required_argument, 0, 0},\n\t[IDX_IPSET_IP] = {\"ipset-ip\", required_argument, 0, 0},\n\t[IDX_IPSET_EXCLUDE] = {\"ipset-exclude\", required_argument, 0, 0},\n\t[IDX_IPSET_EXCLUDE_IP] = {\"ipset-exclude-ip\", required_argument, 0, 0},\n#ifdef __linux__\n\t[IDX_BIND_FIX4] = {\"bind-fix4\", no_argument, 0, 0},\n\t[IDX_BIND_FIX6] = {\"bind-fix6\", no_argument, 0, 0},\n#elif defined(__CYGWIN__)\n\t[IDX_WF_IFACE] = {\"wf-iface\", required_argument, 0, 0},\n\t[IDX_WF_L3] = {\"wf-l3\", required_argument, 0, 0},\n\t[IDX_WF_TCP] = {\"wf-tcp\", required_argument, 0, 0},\n\t[IDX_WF_UDP] = {\"wf-udp\", required_argument, 0, 0},\n\t[IDX_WF_RAW] = {\"wf-raw\", required_argument, 0, 0},\n\t[IDX_WF_RAW_PART] = {\"wf-raw-part\", required_argument, 0, 0},\n\t[IDX_WF_FILTER_LAN] = {\"wf-filter-lan\", required_argument, 0, 0},\n\t[IDX_WF_SAVE] = {\"wf-save\", required_argument, 0, 0},\n\t[IDX_SSID_FILTER] = {\"ssid-filter\", required_argument, 0, 0},\n\t[IDX_NLM_FILTER] = {\"nlm-filter\", required_argument, 0, 0},\n\t[IDX_NLM_LIST] = {\"nlm-list\", optional_argument, 0, 0},\n#endif\n\t[IDX_LAST] = {NULL, 0, NULL, 0},\n};\n\nint main(int argc, char **argv)\n{\n\tset_console_io_buffering();\n\tset_env_exedir(argv[0]);\n\n#ifdef __CYGWIN__\n\tif (service_run(argc, argv))\n\t{\n\t\t// we were running as service. now exit.\n\t\treturn 0;\n\t}\n#endif\n\tint result, v;\n\tint option_index = 0;\n\tbool bSkip = false, bDry = false;\n\tstruct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL;\n\tstruct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL;\n#ifdef __CYGWIN__\n\tchar windivert_filter[16384], wf_pf_tcp_src[4096], wf_pf_tcp_dst[4096], wf_pf_udp_src[4096], wf_pf_udp_dst[4096], wf_save_file[256];\n\tbool wf_ipv4 = true, wf_ipv6 = true, wf_filter_lan = true;\n\tunsigned int IfIdx = 0, SubIfIdx = 0;\n\tunsigned int hash_wf_tcp = 0, hash_wf_udp = 0, hash_wf_raw = 0, hash_wf_raw_part = 0, hash_ssid_filter = 0, hash_nlm_filter = 0;\n\t*windivert_filter = *wf_pf_tcp_src = *wf_pf_tcp_dst = *wf_pf_udp_src = *wf_pf_udp_dst = *wf_save_file = 0;\n#endif\n\n\tsrandom(time(NULL));\n\taes_init_keygen_tables(); // required for aes\n\n\tPRINT_VER;\n\n\tmemset(&params, 0, sizeof(params));\n\n\tstruct desync_profile_list *dpl;\n\tstruct desync_profile *dp;\n\tint desync_profile_count = 0;\n\n\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t{\n\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\tdp = &dpl->dp;\n\tdp->n = ++desync_profile_count;\n\n#ifdef __linux__\n\tparams.qnum = -1;\n#elif defined(BSD)\n\tparams.port = 0;\n#endif\n\tparams.desync_fwmark = DPI_DESYNC_FWMARK_DEFAULT;\n\tparams.ctrack_t_syn = CTRACK_T_SYN;\n\tparams.ctrack_t_est = CTRACK_T_EST;\n\tparams.ctrack_t_fin = CTRACK_T_FIN;\n\tparams.ctrack_t_udp = CTRACK_T_UDP;\n\tparams.ipcache_lifetime = IPCACHE_LIFETIME;\n\n\tLIST_INIT(&params.hostlists);\n\tLIST_INIT(&params.ipsets);\n\n#ifdef __CYGWIN__\n\tLIST_INIT(&params.ssid_filter);\n\tLIST_INIT(&params.nlm_filter);\n\tLIST_INIT(&params.wf_raw_part);\n#else\n\tif (can_drop_root())\n\t{\n\t\tparams.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid\n\t\tparams.gid_count = 1;\n\t\tparams.droproot = true;\n\t}\n#endif\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\tif (argc >= 2 && (argv[1][0] == '@' || argv[1][0] == '$'))\n\t{\n\t\tconfig_from_file(argv[1] + 1);\n\t\targv = params.wexp.we_wordv;\n\t\targc = params.wexp.we_wordc;\n\t}\n#endif\n\n\tif (argc < 2) exithelp_clean();\n\twhile ((v = getopt_long_only(argc, argv, \"\", long_options, &option_index)) != -1)\n\t{\n\t\tif (v)\n\t\t{\n\t\t\tif (bDry)\n\t\t\t\texit_clean(1);\n\t\t\telse\n\t\t\t\texithelp_clean();\n\t\t}\n\t\tswitch (option_index)\n\t\t{\n\t\tcase IDX_DEBUG:\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\tif (*optarg == '@')\n\t\t\t\t{\n\t\t\t\t\tstrncpy(params.debug_logfile, optarg + 1, sizeof(params.debug_logfile));\n\t\t\t\t\tparams.debug_logfile[sizeof(params.debug_logfile) - 1] = 0;\n\t\t\t\t\tFILE *F = fopen(params.debug_logfile, \"wt\");\n\t\t\t\t\tif (!F)\n\t\t\t\t\t{\n\t\t\t\t\t\tfprintf(stderr, \"cannot create %s\\n\", params.debug_logfile);\n\t\t\t\t\t\texit_clean(1);\n\t\t\t\t\t}\n\t\t\t\t\tfclose(F);\n\t\t\t\t\tparams.debug = true;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_FILE;\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(optarg, \"syslog\"))\n\t\t\t\t{\n\t\t\t\t\tparams.debug = true;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_SYSLOG;\n\t\t\t\t\topenlog(progname, LOG_PID, LOG_USER);\n\t\t\t\t}\n#ifdef __ANDROID__\n\t\t\t\telse if (!strcmp(optarg, \"android\"))\n\t\t\t\t{\n\t\t\t\t\tif (!params.debug) params.debug = 1;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_ANDROID;\n\t\t\t\t}\n#endif\n\t\t\t\telse if (optarg[0] >= '0' && optarg[0] <= '1')\n\t\t\t\t{\n\t\t\t\t\tparams.debug = atoi(optarg);\n\t\t\t\t\tparams.debug_target = LOG_TARGET_CONSOLE;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfprintf(stderr, \"invalid debug mode : %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tparams.debug = true;\n\t\t\t\tparams.debug_target = LOG_TARGET_CONSOLE;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DRY_RUN:\n\t\t\tbDry = true;\n\t\t\tbreak;\n\t\tcase IDX_VERSION:\n\t\t\texit_clean(0);\n\t\t\tbreak;\n\t\tcase IDX_COMMENT:\n\t\t\tbreak;\n#ifdef __linux__\n\t\tcase IDX_QNUM:\n\t\t\tparams.qnum = atoi(optarg);\n\t\t\tif (params.qnum < 0 || params.qnum>65535)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad qnum\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n#elif defined(BSD)\n\t\tcase IDX_PORT:\n\t\t{\n\t\t\tint i = atoi(optarg);\n\t\t\tif (i <= 0 || i > 65535)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad port number\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.port = (uint16_t)i;\n\t\t}\n\t\tbreak;\n#endif\n\t\tcase IDX_DAEMON:\n\t\t\tparams.daemon = true;\n\t\t\tbreak;\n\t\tcase IDX_PIDFILE:\n\t\t\tsnprintf(params.pidfile, sizeof(params.pidfile), \"%s\", optarg);\n\t\t\tbreak;\n#ifndef __CYGWIN__\n\t\tcase IDX_USER:\n\t\t{\n\t\t\tfree(params.user); params.user = NULL;\n\t\t\tstruct passwd *pwd = getpwnam(optarg);\n\t\t\tif (!pwd)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"non-existent username supplied\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.uid = pwd->pw_uid;\n\t\t\tparams.gid[0] = pwd->pw_gid;\n\t\t\tparams.gid_count = 1;\n\t\t\tif (!(params.user = strdup(optarg)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"strdup: out of memory\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.droproot = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase IDX_UID:\n\t\t\tfree(params.user); params.user = NULL;\n\t\t\tif (!parse_uid(optarg, &params.uid, params.gid, &params.gid_count, MAX_GIDS))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--uid should be : uid[:gid,gid,...]\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!params.gid_count)\n\t\t\t{\n\t\t\t\tparams.gid[0] = 0x7FFFFFFF;\n\t\t\t\tparams.gid_count = 1;\n\t\t\t}\n\t\t\tparams.droproot = true;\n\t\t\tbreak;\n#endif\n\t\tcase IDX_WSIZE:\n\t\t\tif (!parse_ws_scale_factor(optarg, &dp->wsize, &dp->wscale))\n\t\t\t\texit_clean(1);\n\t\t\tbreak;\n\t\tcase IDX_WSSIZE:\n\t\t\tif (!parse_ws_scale_factor(optarg, &dp->wssize, &dp->wsscale))\n\t\t\t\texit_clean(1);\n\t\t\tbreak;\n\t\tcase IDX_WSSIZE_CUTOFF:\n\t\t\tif (!parse_cutoff(optarg, &dp->wssize_cutoff, &dp->wssize_cutoff_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid wssize-cutoff value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WSSIZE_FORCED_CUTOFF:\n\t\t\tdp->wssize_no_forced_cutoff = !atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_SYNACK_SPLIT:\n\t\t\tdp->synack_split = SS_SYN;\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\tif (!strcmp(optarg, \"synack\"))\n\t\t\t\t\tdp->synack_split = SS_SYNACK;\n\t\t\t\telse if (!strcmp(optarg, \"acksyn\"))\n\t\t\t\t\tdp->synack_split = SS_ACKSYN;\n\t\t\t\telse if (strcmp(optarg, \"syn\"))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"invalid synack-split value\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_CTRACK_TIMEOUTS:\n\t\t\tif (sscanf(optarg, \"%u:%u:%u:%u\", &params.ctrack_t_syn, &params.ctrack_t_est, &params.ctrack_t_fin, &params.ctrack_t_udp) < 3)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid ctrack-timeouts value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_CTRACK_DISABLE:\n\t\t\tparams.ctrack_disable = !optarg || atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_IPCACHE_LIFETIME:\n\t\t\tif (sscanf(optarg, \"%u\", &params.ipcache_lifetime) != 1)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid ipcache-lifetime value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPCACHE_HOSTNAME:\n\t\t\tparams.cache_hostname = !optarg || atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_HOSTCASE:\n\t\t\tdp->hostcase = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTSPELL:\n\t\t\tif (strlen(optarg) != 4)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"hostspell must be exactly 4 chars long\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->hostcase = true;\n\t\t\tmemcpy(dp->hostspell, optarg, 4);\n\t\t\tbreak;\n\t\tcase IDX_HOSTNOSPACE:\n\t\t\tif (dp->methodeol)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--hostnospace and --methodeol are incompatible\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->hostnospace = true;\n\t\t\tbreak;\n\t\tcase IDX_DOMCASE:\n\t\t\tdp->domcase = true;\n\t\t\tbreak;\n\t\tcase IDX_METHODEOL:\n\t\t\tif (dp->hostnospace)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--hostnospace and --methodeol are incompatible\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->methodeol = true;\n\t\t\tbreak;\n\t\tcase IDX_IP_ID:\n\t\t\tif (!strcmp(optarg,\"zero\"))\n\t\t\t\tdp->ip_id_mode = IPID_ZERO;\n\t\t\telse if (!strcmp(optarg,\"seq\"))\n\t\t\t\tdp->ip_id_mode = IPID_SEQ;\n\t\t\telse if (!strcmp(optarg,\"seqgroup\"))\n\t\t\t\tdp->ip_id_mode = IPID_SEQ_GROUP;\n\t\t\telse if (!strcmp(optarg,\"rnd\"))\n\t\t\t\tdp->ip_id_mode = IPID_RND;\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid ip_id mode : %s\\n\",optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC:\n\t\t{\n\t\t\tchar *mode = optarg, *mode2, *mode3;\n\t\t\tmode2 = mode ? strchr(mode, ',') : NULL;\n\t\t\tif (mode2) *mode2++ = 0;\n\t\t\tmode3 = mode2 ? strchr(mode2, ',') : NULL;\n\t\t\tif (mode3) *mode3++ = 0;\n\n\t\t\tdp->desync_mode0 = desync_mode_from_string(mode);\n\t\t\tif (desync_valid_zero_stage(dp->desync_mode0))\n\t\t\t{\n\t\t\t\tmode = mode2;\n\t\t\t\tmode2 = mode3;\n\t\t\t\tmode3 = NULL;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdp->desync_mode0 = DESYNC_NONE;\n\t\t\t}\n\t\t\tdp->desync_mode = desync_mode_from_string(mode);\n\t\t\tdp->desync_mode2 = desync_mode_from_string(mode2);\n\t\t\tif (dp->desync_mode0 == DESYNC_INVALID || dp->desync_mode == DESYNC_INVALID || dp->desync_mode2 == DESYNC_INVALID)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid dpi-desync mode\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (mode3)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid desync combo : %s+%s+%s\\n\", mode, mode2, mode3);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (dp->desync_mode2 && (desync_only_first_stage(dp->desync_mode) || !(desync_valid_first_stage(dp->desync_mode) && desync_valid_second_stage(dp->desync_mode2))))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid desync combo : %s+%s\\n\", mode, mode2);\n\t\t\t\texit_clean(1);\n\t\t\t}\n#if defined(__OpenBSD__)\n\t\t\tif (dp->desync_mode == DESYNC_IPFRAG2 || dp->desync_mode2 == DESYNC_IPFRAG2)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"OpenBSD has checksum issues with fragmented packets. ipfrag disabled.\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n#endif\n\t\t}\n\t\tbreak;\n#if defined(__linux__)\n\t\tcase IDX_DPI_DESYNC_FWMARK:\n#elif defined(SO_USER_COOKIE)\n\t\tcase IDX_DPI_DESYNC_SOCKARG:\n#endif\n#if defined(__linux__) || defined(SO_USER_COOKIE)\n\t\t\tparams.desync_fwmark = 0;\n\t\t\tif (sscanf(optarg, \"0x%X\", &params.desync_fwmark) <= 0) sscanf(optarg, \"%u\", &params.desync_fwmark);\n\t\t\tif (!params.desync_fwmark)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"fwmark/sockarg should be decimal or 0xHEX and should not be zero\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n#endif\n\n\t\tcase IDX_DUP:\n\t\t\tif (sscanf(optarg, \"%u\", &dp->dup_repeats) < 1 || dp->dup_repeats > 1024)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-repeats must be within 0..1024\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_TTL:\n\t\t\tdp->dup_ttl = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DUP_TTL6:\n\t\t\tdp->dup_ttl6 = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DUP_AUTOTTL:\n\t\t\tif (!parse_autottl(optarg, &dp->dup_autottl, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-autottl value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_DUP_AUTOTTL6:\n\t\t\tif (!parse_autottl(optarg, &dp->dup_autottl6, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-autottl6 value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_DUP_TCP_FLAGS_SET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->dup_tcp_flags_set))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_TCP_FLAGS_UNSET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->dup_tcp_flags_unset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_REPLACE:\n\t\t\tdp->dup_replace = !optarg || atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DUP_FOOLING:\n\t\t\tif (!parse_fooling(optarg, &dp->dup_fooling_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_TS_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->dup_ts_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-ts-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_BADSEQ_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->dup_badseq_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-badseq-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_BADACK_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->dup_badseq_ack_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dup-badack-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_CUTOFF:\n\t\t\tif (!parse_cutoff(optarg, &dp->dup_cutoff, &dp->dup_cutoff_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid dup-cutoff value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_START:\n\t\t\tif (!parse_cutoff(optarg, &dp->dup_start, &dp->dup_start_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid dup-start value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DUP_IP_ID:\n\t\t\tif (!strcmp(optarg,\"zero\"))\n\t\t\t\tdp->dup_ip_id_mode = IPID_ZERO;\n\t\t\telse if (!strcmp(optarg,\"same\"))\n\t\t\t\tdp->dup_ip_id_mode = IPID_SAME;\n\t\t\telse if (!strcmp(optarg,\"seq\"))\n\t\t\t\tdp->dup_ip_id_mode = IPID_SEQ;\n\t\t\telse if (!strcmp(optarg,\"rnd\"))\n\t\t\t\tdp->dup_ip_id_mode = IPID_RND;\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid dup ip_id mode : %s\\n\",optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase IDX_ORIG_TTL:\n\t\t\tdp->orig_mod_ttl = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_ORIG_TTL6:\n\t\t\tdp->orig_mod_ttl6 = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_ORIG_AUTOTTL:\n\t\t\tif (!parse_autottl(optarg, &dp->orig_autottl, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"orig-autottl value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_ORIG_AUTOTTL6:\n\t\t\tif (!parse_autottl(optarg, &dp->orig_autottl6, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"orig-autottl6 value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_ORIG_TCP_FLAGS_SET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->orig_tcp_flags_set))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_ORIG_TCP_FLAGS_UNSET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->orig_tcp_flags_unset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_ORIG_MOD_CUTOFF:\n\t\t\tif (!parse_cutoff(optarg, &dp->orig_mod_cutoff, &dp->orig_mod_cutoff_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid orig-mod-cutoff value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_ORIG_MOD_START:\n\t\t\tif (!parse_cutoff(optarg, &dp->orig_mod_start, &dp->orig_mod_start_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid orig-mod-start value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase IDX_DPI_DESYNC_TTL:\n\t\t\tdp->desync_ttl = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_TTL6:\n\t\t\tdp->desync_ttl6 = (uint8_t)atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_AUTOTTL:\n\t\t\tif (!parse_autottl(optarg, &dp->desync_autottl, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-autottl value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_AUTOTTL6:\n\t\t\tif (!parse_autottl(optarg, &dp->desync_autottl6, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-autottl6 value error\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.autottl_present = true;\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_TCP_FLAGS_SET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->desync_tcp_flags_set))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_TCP_FLAGS_UNSET:\n\t\t\tif (!parse_tcpflags(optarg, &dp->desync_tcp_flags_unset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid tcp flags\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FOOLING:\n\t\t\tif (!parse_fooling(optarg, &dp->desync_fooling_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_REPEATS:\n\t\t\tif (sscanf(optarg, \"%u\", &dp->desync_repeats) < 1 || !dp->desync_repeats || dp->desync_repeats > 1024)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-repeats must be within 1..1024\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_SKIP_NOSNI:\n\t\t\tdp->desync_skip_nosni = !optarg || atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_SPLIT_POS:\n\t\t{\n\t\t\tint ct;\n\t\t\tif (!parse_split_pos_list(optarg, dp->splits + dp->split_count, MAX_SPLITS - dp->split_count, &ct))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\\n\", dp->split_count, MAX_SPLITS, optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->split_count += ct;\n\t\t\tbreak;\n\t\t}\n\t\tcase IDX_DPI_DESYNC_SPLIT_HTTP_REQ:\n\t\t\t// obsolete arg\n\t\t\tDLOG_CONDUP(\"WARNING ! --dpi-desync-split-http-req is deprecated. use --dpi-desync-split-pos with markers.\\n\", MAX_SPLITS);\n\t\t\tif (dp->split_count >= MAX_SPLITS)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Too much splits. max splits: %u\\n\", MAX_SPLITS);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_httpreqpos(optarg, dp->splits + dp->split_count))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for dpi-desync-split-http-req\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->split_count++;\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_SPLIT_TLS:\n\t\t\t// obsolete arg\n\t\t\tDLOG_CONDUP(\"WARNING ! --dpi-desync-split-tls is deprecated. use --dpi-desync-split-pos with markers.\\n\", MAX_SPLITS);\n\t\t\tif (dp->split_count >= MAX_SPLITS)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Too much splits. max splits: %u\\n\", MAX_SPLITS);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_tlspos(optarg, dp->splits + dp->split_count))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for dpi-desync-split-tls\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->split_count++;\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_SPLIT_SEQOVL:\n\t\t\tif (!strcmp(optarg, \"0\"))\n\t\t\t{\n\t\t\t\t// allow zero = disable seqovl\n\t\t\t\tdp->seqovl.marker = PM_ABS;\n\t\t\t\tdp->seqovl.pos = 0;\n\t\t\t}\n\t\t\telse if (!parse_split_pos(optarg, &dp->seqovl))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for dpi-desync-split-seqovl\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN:\n\t\t{\n\t\t\tchar buf[sizeof(dp->seqovl_pattern)];\n\t\t\tsize_t sz = sizeof(buf);\n\t\t\tload_file_or_exit(optarg, buf, &sz, NULL);\n\t\t\tfill_pattern(dp->seqovl_pattern, sizeof(dp->seqovl_pattern), buf, sz, 0);\n\t\t\tbreak;\n\t\t}\n\t\tcase IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN:\n\t\t{\n\t\t\tfree(dp->fsplit_pattern);\n\t\t\tif (!(dp->fsplit_pattern = malloc(dp->fsplit_pattern_size=32768)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"out of memory\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tload_file_or_exit(optarg, dp->fsplit_pattern, &dp->fsplit_pattern_size, NULL);\n\t\t\tdp->fsplit_pattern = realloc(dp->fsplit_pattern, dp->fsplit_pattern_size);\n\t\t\tbreak;\n\t\t}\n\t\tcase IDX_DPI_DESYNC_FAKEDSPLIT_MOD:\n\t\t\tif (!parse_fakedsplit_mod(optarg, &dp->fs_mod))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid fakedsplit mod : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST:\n\t\t\tif (!strcmp(optarg, \"0\"))\n\t\t\t{\n\t\t\t\t// allow zero = disable midhost split\n\t\t\t\tdp->hostfakesplit_midhost.marker = PM_ABS;\n\t\t\t\tdp->hostfakesplit_midhost.pos = 0;\n\t\t\t}\n\t\t\telse if (!parse_split_pos(optarg, &dp->hostfakesplit_midhost))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for dpi-desync-hostfakesplit-midhost\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD:\n\t\t\tif (!parse_hostfakesplit_mod(optarg, &dp->hfs_mod))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid hostfakesplit mod : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_IPFRAG_POS_TCP:\n\t\t\tif (sscanf(optarg, \"%u\", &dp->desync_ipfrag_pos_tcp) < 1 || dp->desync_ipfrag_pos_tcp<1 || dp->desync_ipfrag_pos_tcp>DPI_DESYNC_MAX_FAKE_LEN)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-ipfrag-pos-tcp must be within 1..%u range\\n\", DPI_DESYNC_MAX_FAKE_LEN);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (dp->desync_ipfrag_pos_tcp & 7)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-ipfrag-pos-tcp must be multiple of 8\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_IPFRAG_POS_UDP:\n\t\t\tif (sscanf(optarg, \"%u\", &dp->desync_ipfrag_pos_udp) < 1 || dp->desync_ipfrag_pos_udp<1 || dp->desync_ipfrag_pos_udp>DPI_DESYNC_MAX_FAKE_LEN)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-ipfrag-pos-udp must be within 1..%u range\\n\", DPI_DESYNC_MAX_FAKE_LEN);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (dp->desync_ipfrag_pos_udp & 7)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-ipfrag-pos-udp must be multiple of 8\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_TS_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->desync_ts_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-ts-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_BADSEQ_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->desync_badseq_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-badseq-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_BADACK_INCREMENT:\n\t\t\tif (!parse_net32_signed(optarg, &dp->desync_badseq_ack_increment))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-badack-increment should be signed decimal or signed 0xHEX\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_ANY_PROTOCOL:\n\t\t\tdp->desync_any_proto = !optarg || atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_TCP_MOD:\n\t\t\tif (!parse_tcpmod(optarg, &dp->tcp_mod))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid tcp mod : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_HTTP:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_http, FAKE_MAX_TCP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_TLS:\n\t\t{\n\t\t\tif (optarg[0] == '!' && (optarg[1] == 0 || optarg[1] == '+'))\n\t\t\t\tdp->tls_fake_last = load_const_blob_to_collection(fake_tls_clienthello_default, sizeof(fake_tls_clienthello_default), &dp->fake_tls, 4 + sizeof(dp->tls_mod_last.sni), optarg[1] == '+' ? (size_t)atoi(optarg + 1) : 0);\n\t\t\telse\n\t\t\t\tdp->tls_fake_last = load_blob_to_collection(optarg, &dp->fake_tls, FAKE_MAX_TCP, 4 + sizeof(dp->tls_mod_last.sni));\n\t\t\tif (!(dp->tls_fake_last->extra2 = malloc(sizeof(struct fake_tls_mod))))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"out of memory\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tstruct fake_tls_mod *tls_mod = (struct fake_tls_mod*)dp->tls_fake_last->extra2;\n\t\t\t*tls_mod = dp->tls_mod_last;\n\t\t\ttls_mod->mod |= FAKE_TLS_MOD_CUSTOM_FAKE;\n\t\t}\n\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_TLS_MOD:\n\t\t\tif (!parse_tlsmod_list(optarg, &dp->tls_mod_last))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid tls mod : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (dp->tls_fake_last)\n\t\t\t\t*(struct fake_tls_mod*)dp->tls_fake_last->extra2 = dp->tls_mod_last;\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_UNKNOWN:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_unknown, FAKE_MAX_TCP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_SYNDATA:\n\t\t\tdp->fake_syndata_size = sizeof(dp->fake_syndata);\n\t\t\tload_file_or_exit(optarg, dp->fake_syndata, &dp->fake_syndata_size, NULL);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_QUIC:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_quic, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_WIREGUARD:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_wg, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_DHT:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_dht, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_DISCORD:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_discord, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_STUN:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_stun, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP:\n\t\t\tload_blob_to_collection(optarg, &dp->fake_unknown_udp, FAKE_MAX_UDP, 0);\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_UDPLEN_INCREMENT:\n\t\t\tif (sscanf(optarg, \"%d\", &dp->udplen_increment) < 1 || dp->udplen_increment > 0x7FFF || dp->udplen_increment < -0x8000)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"dpi-desync-udplen-increment must be integer within -32768..32767 range\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_UDPLEN_PATTERN:\n\t\t{\n\t\t\tchar buf[sizeof(dp->udplen_pattern)];\n\t\t\tsize_t sz = sizeof(buf);\n\t\t\tload_file_or_exit(optarg, buf, &sz, NULL);\n\t\t\tfill_pattern(dp->udplen_pattern, sizeof(dp->udplen_pattern), buf, sz, 0);\n\t\t}\n\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_CUTOFF:\n\t\t\tif (!parse_cutoff(optarg, &dp->desync_cutoff, &dp->desync_cutoff_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid desync-cutoff value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DPI_DESYNC_START:\n\t\t\tif (!parse_cutoff(optarg, &dp->desync_start, &dp->desync_start_mode))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid desync-start value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterHostlist(dp, false, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_DOMAINS:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_hl && !(anon_hl = RegisterHostlist(dp, false, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_domain_list(optarg, &anon_hl->hostlist))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add domains to anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_EXCLUDE:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterHostlist(dp, true, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_EXCLUDE_DOMAINS:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_hl_exclude && !(anon_hl_exclude = RegisterHostlist(dp, true, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_domain_list(optarg, &anon_hl_exclude->hostlist))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add domains to anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO:\n\t\t\tif (bSkip) break;\n\t\t\tif (dp->hostlist_auto)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"only one auto hostlist per profile is supported\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\t{\n\t\t\t\tFILE *F = fopen(optarg, \"a+b\");\n\t\t\t\tif (!F)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"cannot create %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tbool bGzip = is_gzip(F);\n\t\t\t\tfclose(F);\n\t\t\t\tif (bGzip)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"gzipped auto hostlists are not supported\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!(dp->hostlist_auto = RegisterHostlist(dp, false, optarg)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_FAIL_THRESHOLD:\n\t\t\tdp->hostlist_auto_fail_threshold = atoi(optarg);\n\t\t\tif (dp->hostlist_auto_fail_threshold < 1 || dp->hostlist_auto_fail_threshold>20)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"auto hostlist fail threshold must be within 1..20\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_FAIL_TIME:\n\t\t\tdp->hostlist_auto_fail_time = atoi(optarg);\n\t\t\tif (dp->hostlist_auto_fail_time < 1)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"auto hostlist fail time is not valid\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD:\n\t\t\tdp->hostlist_auto_retrans_threshold = atoi(optarg);\n\t\t\tif (dp->hostlist_auto_retrans_threshold < 2 || dp->hostlist_auto_retrans_threshold>10)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"auto hostlist fail threshold must be within 2..10\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_DEBUG:\n\t\t{\n\t\t\tFILE *F = fopen(optarg, \"a+t\");\n\t\t\tif (!F)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"cannot create %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tfclose(F);\n\t\t\tstrncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog));\n\t\t\tparams.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\\0';\n\t\t}\n\t\tbreak;\n\n\t\tcase IDX_NEW:\n\t\t\tif (bSkip)\n\t\t\t{\n\t\t\t\tdp_clear(dp);\n\t\t\t\tdp_init(dp);\n\t\t\t\tdp->n = desync_profile_count;\n\t\t\t\tbSkip = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcheck_dp(dp);\n\t\t\t\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tdp = &dpl->dp;\n\t\t\t\tdp->n = ++desync_profile_count;\n\t\t\t}\n\t\t\tanon_hl = anon_hl_exclude = NULL;\n\t\t\tanon_ips = anon_ips_exclude = NULL;\n\t\t\tbreak;\n\t\tcase IDX_SKIP:\n\t\t\tbSkip = true;\n\t\t\tbreak;\n\n\t\tcase IDX_FILTER_L3:\n\t\t\tif (!wf_make_l3(optarg, &dp->filter_ipv4, &dp->filter_ipv6))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --filter-l3\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_FILTER_TCP:\n\t\t\tif (!parse_pf_list(optarg, &dp->pf_tcp))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid port filter : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\t// deny tcp if not set\n\t\t\tif (!port_filters_deny_if_empty(&dp->pf_udp))\n\t\t\t\texit_clean(1);\n\t\t\tbreak;\n\t\tcase IDX_FILTER_UDP:\n\t\t\tif (!parse_pf_list(optarg, &dp->pf_udp))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid port filter : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\t// deny tcp if not set\n\t\t\tif (!port_filters_deny_if_empty(&dp->pf_tcp))\n\t\t\t\texit_clean(1);\n\t\t\tbreak;\n\t\tcase IDX_FILTER_L7:\n\t\t\tif (!parse_l7_list(optarg, &dp->filter_l7))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid l7 filter : %s\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n#ifdef HAS_FILTER_SSID\n\t\tcase IDX_FILTER_SSID:\n\t\t\tif (!parse_strlist(optarg, &dp->filter_ssid))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"strlist_add failed\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.filter_ssid_present = true;\n\t\t\tbreak;\n#endif\n\t\tcase IDX_IPSET:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterIpset(dp, false, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register ipset '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPSET_IP:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_ips && !(anon_ips = RegisterIpset(dp, false, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_ip_list(optarg, &anon_ips->ipset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add subnets to anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPSET_EXCLUDE:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterIpset(dp, true, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register ipset '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPSET_EXCLUDE_IP:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_ips_exclude && !(anon_ips_exclude = RegisterIpset(dp, true, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_ip_list(optarg, &anon_ips_exclude->ipset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add subnets to anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\n\n#ifdef __linux__\n\t\tcase IDX_BIND_FIX4:\n\t\t\tparams.bind_fix4 = true;\n\t\t\tbreak;\n\t\tcase IDX_BIND_FIX6:\n\t\t\tparams.bind_fix6 = true;\n\t\t\tbreak;\n#elif defined(__CYGWIN__)\n\t\tcase IDX_WF_IFACE:\n\t\t\tif (!sscanf(optarg, \"%u.%u\", &IfIdx, &SubIfIdx))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --wf-iface\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_L3:\n\t\t\tif (!wf_make_l3(optarg, &wf_ipv4, &wf_ipv6))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --wf-l3\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_TCP:\n\t\t\thash_wf_tcp = hash_jen(optarg, strlen(optarg));\n\t\t\tif (!wf_make_pf(optarg, \"tcp\", \"SrcPort\", wf_pf_tcp_src, sizeof(wf_pf_tcp_src)) ||\n\t\t\t\t!wf_make_pf(optarg, \"tcp\", \"DstPort\", wf_pf_tcp_dst, sizeof(wf_pf_tcp_dst)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --wf-tcp\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_UDP:\n\t\t\thash_wf_udp = hash_jen(optarg, strlen(optarg));\n\t\t\tif (!wf_make_pf(optarg, \"udp\", \"SrcPort\", wf_pf_udp_src, sizeof(wf_pf_udp_src)) ||\n\t\t\t\t!wf_make_pf(optarg, \"udp\", \"DstPort\", wf_pf_udp_dst, sizeof(wf_pf_udp_dst)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --wf-udp\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_RAW:\n\t\t\thash_wf_raw = hash_jen(optarg, strlen(optarg));\n\t\t\tif (optarg[0] == '@')\n\t\t\t{\n\t\t\t\tsize_t sz = sizeof(windivert_filter) - 1;\n\t\t\t\tload_file_or_exit(optarg, windivert_filter, &sz, NULL);\n\t\t\t\twindivert_filter[sz] = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstrncpy(windivert_filter, optarg, sizeof(windivert_filter));\n\t\t\t\twindivert_filter[sizeof(windivert_filter) - 1] = '\\0';\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_RAW_PART:\n\t\t\thash_wf_raw_part ^= hash_jen(optarg, strlen(optarg));\n\t\t\t{\n\t\t\t\tchar wfpart[sizeof(windivert_filter)];\n\t\t\t\tif (optarg[0] == '@')\n\t\t\t\t{\n\t\t\t\t\tsize_t sz = sizeof(wfpart) - 1;\n\t\t\t\t\tload_file_or_exit(optarg, wfpart, &sz, NULL);\n\t\t\t\t\twfpart[sz] = 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstrncpy(wfpart, optarg, sizeof(wfpart));\n\t\t\t\t\twfpart[sizeof(wfpart) - 1] = '\\0';\n\t\t\t\t}\n\t\t\t\tif (!strlist_add(&params.wf_raw_part, wfpart))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"out of memory\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_WF_FILTER_LAN:\n\t\t\twf_filter_lan = !!atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_WF_SAVE:\n\t\t\tstrncpy(wf_save_file, optarg, sizeof(wf_save_file));\n\t\t\twf_save_file[sizeof(wf_save_file) - 1] = '\\0';\n\t\t\tbreak;\n\t\tcase IDX_SSID_FILTER:\n\t\t\thash_ssid_filter = hash_jen(optarg, strlen(optarg));\n\t\t\tif (!parse_strlist(optarg, &params.ssid_filter))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"strlist_add failed\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_NLM_FILTER:\n\t\t\thash_nlm_filter = hash_jen(optarg, strlen(optarg));\n\t\t\tif (!parse_strlist(optarg, &params.nlm_filter))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"strlist_add failed\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_NLM_LIST:\n\t\t\tif (!nlm_list(optarg && !strcmp(optarg, \"all\")))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"could not get list of NLM networks\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\texit_clean(0);\n\n#endif\n\t\t}\n\t}\n\tif (bSkip)\n\t{\n\t\tLIST_REMOVE(dpl, next);\n\t\tdp_entry_destroy(dpl);\n\t\tdesync_profile_count--;\n\t}\n\telse\n\t\tcheck_dp(dp);\n\n\t// do not need args from file anymore\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\tcleanup_args(&params);\n#endif\n\targv = NULL; argc = 0;\n\n#ifdef __linux__\n\tif (params.qnum < 0)\n\t{\n\t\tDLOG_ERR(\"Need queue number (--qnum)\\n\");\n\t\texit_clean(1);\n\t}\n#elif defined(BSD)\n\tif (!params.port)\n\t{\n\t\tDLOG_ERR(\"Need divert port (--port)\\n\");\n\t\texit_clean(1);\n\t}\n#endif\n\n\tDLOG(\"adding low-priority default empty desync profile\\n\");\n\t// add default empty profile\n\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t{\n\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\n\tDLOG_CONDUP(\"we have %d user defined desync profile(s) and default low priority profile 0\\n\", desync_profile_count);\n\n#ifndef __CYGWIN__\n\tif (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1))\n\t\tfprintf(stderr, \"could not chown %s. log file may not be writable after privilege drop\\n\", params.debug_logfile);\n\tif (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1))\n\t\tDLOG_ERR(\"could not chown %s. auto hostlist debug log may not be writable after privilege drop\\n\", params.hostlist_auto_debuglog);\n#endif\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tdp = &dpl->dp;\n\n\t\t// not specified - use desync_ttl value instead\n\t\tif (dp->desync_ttl6 == 0xFF) dp->desync_ttl6 = dp->desync_ttl;\n\t\tif (dp->dup_ttl6 == 0xFF) dp->dup_ttl6 = dp->dup_ttl;\n\t\tif (dp->orig_mod_ttl6 == 0xFF) dp->orig_mod_ttl6 = dp->orig_mod_ttl;\n\t\tif (!dp->fsplit_pattern)\n\t\t{\n\t\t\tif ((dp->fsplit_pattern=calloc(64,1)))\n\t\t\t\tdp->fsplit_pattern_size=64;\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"out of memory\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t}\n\t\tif (!AUTOTTL_ENABLED(dp->desync_autottl6)) dp->desync_autottl6 = dp->desync_autottl;\n\t\tif (!AUTOTTL_ENABLED(dp->orig_autottl6)) dp->orig_autottl6 = dp->orig_autottl;\n\t\tif (!AUTOTTL_ENABLED(dp->dup_autottl6)) dp->dup_autottl6 = dp->dup_autottl;\n\t\tif (AUTOTTL_ENABLED(dp->desync_autottl))\n\t\t\tDLOG(\"profile %d desync autottl ipv4 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->desync_autottl.delta), dp->desync_autottl.delta, dp->desync_autottl.min, dp->desync_autottl.max);\n\t\tif (AUTOTTL_ENABLED(dp->desync_autottl6))\n\t\t\tDLOG(\"profile %d desync autottl ipv6 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->desync_autottl6.delta), dp->desync_autottl6.delta, dp->desync_autottl6.min, dp->desync_autottl6.max);\n\t\tif (AUTOTTL_ENABLED(dp->orig_autottl))\n\t\t\tDLOG(\"profile %d orig autottl ipv4 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->orig_autottl.delta), dp->orig_autottl.delta, dp->orig_autottl.min, dp->orig_autottl.max);\n\t\tif (AUTOTTL_ENABLED(dp->orig_autottl6))\n\t\t\tDLOG(\"profile %d orig autottl ipv6 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->orig_autottl6.delta), dp->orig_autottl6.delta, dp->orig_autottl6.min, dp->orig_autottl6.max);\n\t\tif (AUTOTTL_ENABLED(dp->dup_autottl))\n\t\t\tDLOG(\"profile %d dup autottl ipv4 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->dup_autottl.delta), dp->dup_autottl.delta, dp->dup_autottl.min, dp->dup_autottl.max);\n\t\tif (AUTOTTL_ENABLED(dp->dup_autottl6))\n\t\t\tDLOG(\"profile %d dup autottl ipv6 %s%d:%u-%u\\n\", dp->n, UNARY_PLUS(dp->dup_autottl6.delta), dp->dup_autottl6.delta, dp->dup_autottl6.min, dp->dup_autottl6.max);\n\t\tsplit_compat(dp);\n\t\tif (!dp_fake_defaults(dp))\n\t\t{\n\t\t\tDLOG_ERR(\"could not fill fake defaults\\n\");\n\t\t\texit_clean(1);\n\t\t}\n\t\tif (!onetime_tls_mod(dp))\n\t\t{\n\t\t\tDLOG_ERR(\"could not mod tls\\n\");\n\t\t\texit_clean(1);\n\t\t}\n#ifndef __CYGWIN__\n\t\tif (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1))\n\t\t\tDLOG_ERR(\"could not chown %s. auto hostlist file may not be writable after privilege drop\\n\", dp->hostlist_auto->filename);\n#endif\n\t}\n\n\tif (!test_list_files())\n\t\texit_clean(1);\n\n\tif (!LoadAllHostLists())\n\t{\n\t\tDLOG_ERR(\"hostlists load failed\\n\");\n\t\texit_clean(1);\n\t}\n\tif (!LoadAllIpsets())\n\t{\n\t\tDLOG_ERR(\"ipset load failed\\n\");\n\t\texit_clean(1);\n\t}\n\n\tDLOG(\"\\nlists summary:\\n\");\n\tHostlistsDebug();\n\tIpsetsDebug();\n\n\tDLOG(\"\\nsplits summary:\\n\");\n\tSplitDebug();\n\tDLOG(\"\\n\");\n\n#ifdef __CYGWIN__\n\tif (!*windivert_filter)\n\t{\n\t\tif (!*wf_pf_tcp_src && !*wf_pf_udp_src && LIST_EMPTY(&params.wf_raw_part))\n\t\t{\n\t\t\tDLOG_ERR(\"windivert filter : must specify port or/and partial raw filter\\n\");\n\t\t\texit_clean(1);\n\t\t}\n\t\tif (!wf_make_filter(windivert_filter, sizeof(windivert_filter), IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, wf_pf_tcp_src, wf_pf_tcp_dst, wf_pf_udp_src, wf_pf_udp_dst, &params.wf_raw_part, wf_filter_lan))\n\t\t{\n\t\t\tDLOG_ERR(\"windivert filter : could not make filter\\n\");\n\t\t\texit_clean(1);\n\t\t}\n\t}\n\tDLOG(\"windivert filter size: %zu\\nwindivert filter:\\n%s\\n\", strlen(windivert_filter), windivert_filter);\n\tif (*wf_save_file)\n\t{\n\t\tif (save_file(wf_save_file, windivert_filter, strlen(windivert_filter)))\n\t\t{\n\t\t\tDLOG_ERR(\"windivert filter: raw filter saved to %s\\n\", wf_save_file);\n\t\t\texit_clean(0);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG_ERR(\"windivert filter: could not save raw filter to %s\\n\", wf_save_file);\n\t\t\texit_clean(1);\n\t\t}\n\t}\n\tHANDLE hMutexArg;\n\t{\n\t\tchar mutex_name[128];\n\t\tsnprintf(mutex_name, sizeof(mutex_name), \"Global\\\\winws_arg_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u\", hash_wf_tcp, hash_wf_udp, hash_wf_raw, hash_wf_raw_part, hash_ssid_filter, hash_nlm_filter, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6);\n\n\t\thMutexArg = CreateMutexA(NULL, TRUE, mutex_name);\n\t\tif (hMutexArg && GetLastError() == ERROR_ALREADY_EXISTS)\n\t\t{\n\t\t\tCloseHandle(hMutexArg);\thMutexArg = NULL;\n\t\t\tDLOG_ERR(\"A copy of winws is already running with the same filter\\n\");\n\t\t\tgoto exiterr;\n\t\t}\n\t}\n#endif\n\n\tif (bDry)\n\t{\n#ifndef __CYGWIN__\n\t\tif (params.droproot)\n\t\t{\n\t\t\tif (!droproot(params.uid, params.user, params.gid, params.gid_count))\n\t\t\t\texit_clean(1);\n#ifdef __linux__\n\t\t\tif (!dropcaps())\n\t\t\t\texit_clean(1);\n#endif\n\t\t\tprint_id();\n\t\t\tif (!test_list_files())\n\t\t\t\texit_clean(1);\n\t\t}\n#endif\n\t\tDLOG_CONDUP(\"command line parameters verified\\n\");\n\t\texit_clean(0);\n\t}\n\n\tif (params.ctrack_disable)\n\t\tDLOG_CONDUP(\"conntrack disabled ! some functions will not work. make sure it's what you want.\\n\");\n\telse\n\t{\n\t\tDLOG(\"initializing conntrack with timeouts tcp=%u:%u:%u udp=%u\\n\", params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp);\n\t\tConntrackPoolInit(&params.conntrack, 10, params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp);\n\t}\n\tif (params.autottl_present || params.cache_hostname) DLOG(\"ipcache lifetime %us\\n\", params.ipcache_lifetime);\n\n#ifdef __linux__\n\tresult = nfq_main();\n#elif defined(BSD)\n\tresult = dvt_main();\n#elif defined(__CYGWIN__)\n\tresult = win_main(windivert_filter);\n#else\n#error unsupported OS\n#endif\nex:\n\trawsend_cleanup();\n\tcleanup_params(&params);\n#ifdef __CYGWIN__\n\tif (hMutexArg)\n\t{\n\t\tReleaseMutex(hMutexArg);\n\t\tCloseHandle(hMutexArg);\n\t}\n#endif\n\treturn result;\nexiterr:\n\tresult = 1;\n\tgoto ex;\n}\n"
  },
  {
    "path": "nfq/nfqws.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n\n#ifdef __linux__\n#define HAS_FILTER_SSID 1\n#endif\n\n#ifdef __CYGWIN__\nextern bool bQuit;\n#endif\nint main(int argc, char *argv[]);\n"
  },
  {
    "path": "nfq/packet_queue.c",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"packet_queue.h\"\n\nvoid rawpacket_queue_init(struct rawpacket_tailhead *q)\n{\n\tTAILQ_INIT(q);\n}\nvoid rawpacket_free(struct rawpacket *rp)\n{\n\tif (rp) free(rp->packet);\n\tfree(rp);\n}\nstruct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q)\n{\n\tstruct rawpacket *rp;\n\trp = TAILQ_FIRST(q);\n\tif (rp)\tTAILQ_REMOVE(q, rp, next);\n\treturn rp;\n}\nvoid rawpacket_queue_destroy(struct rawpacket_tailhead *q)\n{\n\tstruct rawpacket *rp;\n\twhile((rp = rawpacket_dequeue(q))) rawpacket_free(rp);\n}\n\nstruct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *ifin,const char *ifout,const void *data,size_t len,size_t len_payload)\n{\n\tstruct rawpacket *rp = malloc(sizeof(struct rawpacket));\n\tif (!rp) return NULL;\n\n\trp->packet = malloc(len);\n\tif (!rp->packet)\n\t{\n\t\tfree(rp);\n\t\treturn NULL;\n\t}\n\t\n\trp->dst = *dst;\n\trp->fwmark = fwmark;\n\tif (ifin)\n\t\tsnprintf(rp->ifin,sizeof(rp->ifin),\"%s\",ifin);\n\telse\n\t\t*rp->ifin = 0;\n\tif (ifout)\n\t\tsnprintf(rp->ifout,sizeof(rp->ifout),\"%s\",ifout);\n\telse\n\t\t*rp->ifout = 0;\n\tmemcpy(rp->packet,data,len);\n\trp->len=len;\n\trp->len_payload=len_payload;\n\t\n\tTAILQ_INSERT_TAIL(q, rp, next);\n\t\n\treturn rp;\n}\n\nunsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q)\n{\n\tconst struct rawpacket *rp;\n\tunsigned int ct=0;\n\tTAILQ_FOREACH(rp, q, next) ct++;\n\treturn ct;\n}\nbool rawpacket_queue_empty(const struct rawpacket_tailhead *q)\n{\n\treturn !TAILQ_FIRST(q);\n}\n"
  },
  {
    "path": "nfq/packet_queue.h",
    "content": "#pragma once\n\n#include <inttypes.h>\n#include <stdbool.h>\n#include <sys/queue.h>\n#include <net/if.h>\n#include <sys/socket.h>\n\nstruct rawpacket\n{\n\tstruct sockaddr_storage dst;\n\tchar ifin[IFNAMSIZ], ifout[IFNAMSIZ];\n\tuint32_t fwmark;\n\tsize_t len, len_payload;\n\tuint8_t *packet;\n\tTAILQ_ENTRY(rawpacket) next;\n};\nTAILQ_HEAD(rawpacket_tailhead, rawpacket);\n\nvoid rawpacket_queue_init(struct rawpacket_tailhead *q);\nvoid rawpacket_queue_destroy(struct rawpacket_tailhead *q);\nbool rawpacket_queue_empty(const struct rawpacket_tailhead *q);\nunsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q);\nstruct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *ifin,const char *ifout,const void *data,size_t len,size_t len_payload);\nstruct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q);\nvoid rawpacket_free(struct rawpacket *rp);\n"
  },
  {
    "path": "nfq/params.c",
    "content": "#include \"params.h\"\n\n#include <stdarg.h>\n#include <syslog.h>\n#include <errno.h>\n#ifdef __ANDROID__\n#include <android/log.h>\n#endif\n\n#include \"pools.h\"\n#include \"desync.h\"\n\n#ifdef BSD\nconst char *progname = \"dvtws\";\n#elif defined(__CYGWIN__)\nconst char *progname = \"winws\";\n#elif defined(__linux__)\nconst char *progname = \"nfqws\";\n#else\n#error UNKNOWN_SYSTEM_TIME\n#endif\n\nconst char * tld[6] = { \"com\",\"org\",\"net\",\"edu\",\"gov\",\"biz\" };\n\nint DLOG_FILE(FILE *F, const char *format, va_list args)\n{\n\treturn vfprintf(F, format, args);\n}\nint DLOG_CON(const char *format, int syslog_priority, va_list args)\n{\n\treturn DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args);\n}\nint DLOG_FILENAME(const char *filename, const char *format, va_list args)\n{\n\tint r;\n\tFILE *F = fopen(filename,\"at\");\n\tif (F)\n\t{\n\t\tr = DLOG_FILE(F, format, args);\n\t\tfclose(F);\n\t}\n\telse\n\t\tr=-1;\n\treturn r;\n}\n\ntypedef void (*f_log_function)(int priority, const char *line);\n\nstatic char log_buf[1024];\nstatic size_t log_buf_sz=0;\nstatic void syslog_log_function(int priority, const char *line)\n{\n\tsyslog(priority,\"%s\",line);\n}\n#ifdef __ANDROID__\nstatic enum android_LogPriority syslog_priority_to_android(int priority)\n{\n\tenum android_LogPriority ap;\n\tswitch(priority)\n\t{\n\t\tcase LOG_INFO:\n\t\tcase LOG_NOTICE: ap=ANDROID_LOG_INFO; break;\n\t\tcase LOG_ERR: ap=ANDROID_LOG_ERROR; break;\n\t\tcase LOG_WARNING: ap=ANDROID_LOG_WARN; break;\n\t\tcase LOG_EMERG:\n\t\tcase LOG_ALERT:\n\t\tcase LOG_CRIT: ap=ANDROID_LOG_FATAL; break;\n\t\tcase LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break;\n\t\tdefault: ap=ANDROID_LOG_UNKNOWN;\n\t}\n\treturn ap;\n}\nstatic void android_log_function(int priority, const char *line)\n{\n\t__android_log_print(syslog_priority_to_android(priority), progname, \"%s\", line);\n}\n#endif\nstatic void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args)\n{\n\tif (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0)\n\t{\n\t\tlog_buf_sz=strlen(log_buf);\n\t\t// log when buffer is full or buffer ends with \\n\n\t\tif (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\\n'))\n\t\t{\n\t\t\tlog_function(syslog_priority,log_buf);\n\t\t\tlog_buf_sz = 0;\n\t\t}\n\t}\n}\n\nstatic int DLOG_VA(const char *format, int syslog_priority, bool condup, va_list args)\n{\n\tint r=0;\n\tva_list args2;\n\n\tif (condup && !(params.debug && params.debug_target==LOG_TARGET_CONSOLE))\n\t{\n\t\tva_copy(args2,args);\n\t\tDLOG_CON(format,syslog_priority,args2);\n\t\tva_end(args2);\n\t}\n\tif (params.debug)\n\t{\n\t\tswitch(params.debug_target)\n\t\t{\n\t\t\tcase LOG_TARGET_CONSOLE:\n\t\t\t\tr = DLOG_CON(format,syslog_priority,args);\n\t\t\t\tbreak;\n\t\t\tcase LOG_TARGET_FILE:\n\t\t\t\tr = DLOG_FILENAME(params.debug_logfile,format,args);\n\t\t\t\tbreak;\n\t\t\tcase LOG_TARGET_SYSLOG:\n\t\t\t\t// skip newlines\n\t\t\t\tlog_buffered(syslog_log_function,syslog_priority,format,args);\n\t\t\t\tr = 1;\n\t\t\t\tbreak;\n#ifdef __ANDROID__\n\t\t\tcase LOG_TARGET_ANDROID:\n\t\t\t\t// skip newlines\n\t\t\t\tlog_buffered(android_log_function,syslog_priority,format,args);\n\t\t\t\tr = 1;\n\t\t\t\tbreak;\n#endif\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn r;\n}\n\nint DLOG(const char *format, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, format);\n\tr = DLOG_VA(format, LOG_DEBUG, false, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_CONDUP(const char *format, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, format);\n\tr = DLOG_VA(format, LOG_DEBUG, true, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_ERR(const char *format, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, format);\n\tr = DLOG_VA(format, LOG_ERR, true, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_PERROR(const char *s)\n{\n\treturn DLOG_ERR(\"%s: %s\\n\", s, strerror(errno));\n}\n\n\nint LOG_APPEND(const char *filename, const char *format, va_list args)\n{\n\tint r;\n\tFILE *F = fopen(filename,\"at\");\n\tif (F)\n\t{\n\t\tfprint_localtime(F);\n\t\tfprintf(F, \" : \");\n\t\tr = vfprintf(F, format, args);\n\t\tfprintf(F, \"\\n\");\n\t\tfclose(F);\n\t}\n\telse\n\t\tr=-1;\n\treturn r;\n}\n\nint HOSTLIST_DEBUGLOG_APPEND(const char *format, ...)\n{\n\tif (*params.hostlist_auto_debuglog)\n\t{\n\t\tint r;\n\t\tva_list args;\n\n\t\tva_start(args, format);\n\t\tr = LOG_APPEND(params.hostlist_auto_debuglog, format, args);\n\t\tva_end(args);\n\t\treturn r;\n\t}\n\telse\n\t\treturn 0;\n}\n\nvoid hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit)\n{\n\tsize_t k;\n\tbool bcut = false;\n\tif (size > limit)\n\t{\n\t\tsize = limit;\n\t\tbcut = true;\n\t}\n\tif (!size) return;\n\tfor (k = 0; k < size; k++) DLOG(\"%02X \", data[k]);\n\tDLOG(bcut ? \"... : \" : \": \");\n\tfor (k = 0; k < size; k++) DLOG(\"%c\", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.');\n\tif (bcut) DLOG(\" ...\");\n}\n\nvoid dp_init(struct desync_profile *dp)\n{\n\tLIST_INIT(&dp->hl_collection);\n\tLIST_INIT(&dp->hl_collection_exclude);\n\tLIST_INIT(&dp->ips_collection);\n\tLIST_INIT(&dp->ips_collection_exclude);\n\tLIST_INIT(&dp->pf_tcp);\n\tLIST_INIT(&dp->pf_udp);\n#ifdef HAS_FILTER_SSID\n\tLIST_INIT(&dp->filter_ssid);\n#endif\n\n\tmemcpy(dp->hostspell, \"host\", 4); // default hostspell\n\tdp->desync_skip_nosni = true;\n\tdp->desync_ipfrag_pos_udp = IPFRAG_UDP_DEFAULT;\n\tdp->desync_ipfrag_pos_tcp = IPFRAG_TCP_DEFAULT;\n\tdp->desync_repeats = 1;\n\tdp->fake_syndata_size = 16;\n\tdp->wscale=-1; // default - dont change scale factor (client)\n\tdp->desync_ttl6 = dp->dup_ttl6 = dp->orig_mod_ttl6 = 0xFF; // unused\n\tdp->desync_ts_increment = dp->dup_ts_increment = TS_INCREMENT_DEFAULT;\n\tdp->desync_badseq_increment = dp->dup_badseq_increment = BADSEQ_INCREMENT_DEFAULT;\n\tdp->desync_badseq_ack_increment = dp->dup_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT;\n\tdp->wssize_cutoff_mode = dp->desync_start_mode = dp->desync_cutoff_mode = dp->dup_start_mode = dp->dup_cutoff_mode = dp->orig_mod_start_mode = dp->orig_mod_cutoff_mode = 'n'; // packet number by default\n\tdp->udplen_increment = UDPLEN_INCREMENT_DEFAULT;\n\tdp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;\n\tdp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;\n\tdp->hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT;\n\tdp->filter_ipv4 = dp->filter_ipv6 = true;\n\tdp->dup_ip_id_mode = IPID_SAME;\n}\nbool dp_fake_defaults(struct desync_profile *dp)\n{\n\tstruct blob_item *item;\n\tif (blob_collection_empty(&dp->fake_http))\n\t\tif (!blob_collection_add_blob(&dp->fake_http,fake_http_request_default,strlen(fake_http_request_default),0,0))\n\t\t\treturn false;\n\tif (blob_collection_empty(&dp->fake_tls))\n\t{\n\t\tif (!(item=blob_collection_add_blob(&dp->fake_tls,fake_tls_clienthello_default,sizeof(fake_tls_clienthello_default),4+sizeof(((struct fake_tls_mod*)0)->sni),0)))\n\t\t\treturn false;\n\t\tif (!(item->extra2 = malloc(sizeof(struct fake_tls_mod))))\n\t\t\treturn false;\n\t\t*(struct fake_tls_mod*)item->extra2 = dp->tls_mod_last;\n\t}\n\tif (blob_collection_empty(&dp->fake_unknown))\n\t{\n\t\tif (!(item=blob_collection_add_blob(&dp->fake_unknown,NULL,256,0,0)))\n\t\t\treturn false;\n\t\tmemset(item->data,0,item->size);\n\t}\n\tif (blob_collection_empty(&dp->fake_quic))\n\t{\n\t\tif (!(item=blob_collection_add_blob(&dp->fake_quic,NULL,620,0,0)))\n\t\t\treturn false;\n\t\tmemset(item->data,0,item->size);\n\t\titem->data[0] = 0x40;\n\t}\n\tstruct blob_collection_head **fake,*fakes_z64[] = {&dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, &dp->fake_unknown_udp,NULL};\n\tfor(fake=fakes_z64;*fake;fake++)\n\t{\n\t\tif (blob_collection_empty(*fake))\n\t\t{\n\t\t\tif (!(item=blob_collection_add_blob(*fake,NULL,64,0,0)))\n\t\t\t\treturn false;\n\t\t\tmemset(item->data,0,item->size);\n\t\t}\n\t}\n\treturn true;\n}\nstruct desync_profile_list *dp_list_add(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list));\n\tif (!entry) return NULL;\n\n\tdp_init(&entry->dp);\n\n\t// add to the tail\n\tstruct desync_profile_list *dpn,*dpl=LIST_FIRST(head);\n\tif (dpl)\n\t{\n\t\twhile ((dpn=LIST_NEXT(dpl,next))) dpl = dpn;\n\t\tLIST_INSERT_AFTER(dpl, entry, next);\n\t}\n\telse\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\n\treturn entry;\n}\nstatic void dp_clear_dynamic(struct desync_profile *dp)\n{\n\tfree(dp->fsplit_pattern);\n\n\thostlist_collection_destroy(&dp->hl_collection);\n\thostlist_collection_destroy(&dp->hl_collection_exclude);\n\tipset_collection_destroy(&dp->ips_collection);\n\tipset_collection_destroy(&dp->ips_collection_exclude);\n\tport_filters_destroy(&dp->pf_tcp);\n\tport_filters_destroy(&dp->pf_udp);\n#ifdef HAS_FILTER_SSID\n\tstrlist_destroy(&dp->filter_ssid);\n#endif\n\tHostFailPoolDestroy(&dp->hostlist_auto_fail_counters);\n\tstruct blob_collection_head **fake,*fakes[] = {&dp->fake_http, &dp->fake_tls, &dp->fake_unknown, &dp->fake_unknown_udp, &dp->fake_quic, &dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, NULL};\n\tfor(fake=fakes;*fake;fake++) blob_collection_destroy(*fake);\n}\nvoid dp_clear(struct desync_profile *dp)\n{\n\tdp_clear_dynamic(dp);\n\tmemset(dp,0,sizeof(*dp));\n}\nvoid dp_entry_destroy(struct desync_profile_list *entry)\n{\n\tdp_clear_dynamic(&entry->dp);\n\tfree(entry);\n}\nvoid dp_list_destroy(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tdp_entry_destroy(entry);\n\t}\n}\nbool dp_list_have_autohostlist(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, head, next)\n\t\tif (dpl->dp.hostlist_auto)\n\t\t\treturn true;\n\treturn false;\n}\n// check if we need empty outgoing ACK\nbool dp_list_need_all_out(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, head, next)\n\t\tif (dpl->dp.dup_repeats || PROFILE_HAS_ORIG_MOD(&dpl->dp))\n\t\t\treturn true;\n\treturn false;\n}\n\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\nvoid cleanup_args(struct params_s *params)\n{\n\twordfree(&params->wexp);\n}\n#endif\n\nvoid cleanup_params(struct params_s *params)\n{\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\tcleanup_args(params);\n#endif\n\n\tConntrackPoolDestroy(&params->conntrack);\n\n\tdp_list_destroy(&params->desync_profiles);\n\n\thostlist_files_destroy(&params->hostlists);\n\tipset_files_destroy(&params->ipsets);\n\tipcacheDestroy(&params->ipcache);\n#ifdef __CYGWIN__\n\tstrlist_destroy(&params->ssid_filter);\n\tstrlist_destroy(&params->nlm_filter);\n\tstrlist_destroy(&params->wf_raw_part);\n#else\n\tfree(params->user); params->user=NULL;\n#endif\n}\n"
  },
  {
    "path": "nfq/params.h",
    "content": "#pragma once\n\n#include \"nfqws.h\"\n#include \"pools.h\"\n#include \"conntrack.h\"\n#include \"desync.h\"\n#include \"protocol.h\"\n#include \"helpers.h\"\n\n#include <sys/param.h>\n#include <sys/types.h>\n#include <net/if.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <time.h>\n#include <sys/queue.h>\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n#include <wordexp.h>\n#endif\n\n#define TLS_PARTIALS_ENABLE\ttrue\n\n#define RAW_SNDBUF\t(64*1024)\t// in bytes\n\n#define Q_MAXLEN\t4986\t\t// in packets\n#define Q_RCVBUF\t(1024*1024)\t// in bytes\n\n#define BADSEQ_INCREMENT_DEFAULT \t-10000\n#define BADSEQ_ACK_INCREMENT_DEFAULT \t-66000\n\n#define TS_INCREMENT_DEFAULT \t\t-600000\n\n#define IPFRAG_UDP_DEFAULT 8\n#define IPFRAG_TCP_DEFAULT 32\n\n#define UDPLEN_INCREMENT_DEFAULT \t2\n\n#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT\t3\n#define\tHOSTLIST_AUTO_FAIL_TIME_DEFAULT \t60\n#define\tHOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT\t3\n\n#define IPCACHE_LIFETIME\t\t7200\n\n#define AUTOTTL_DEFAULT_DESYNC_DELTA\t-1\n#define AUTOTTL_DEFAULT_DESYNC_MIN\t3\n#define AUTOTTL_DEFAULT_DESYNC_MAX\t20\n#define AUTOTTL_DEFAULT_ORIG_DELTA\t+5\n#define AUTOTTL_DEFAULT_ORIG_MIN\t3\n#define AUTOTTL_DEFAULT_ORIG_MAX\t64\n#define AUTOTTL_DEFAULT_DUP_DELTA\t-1\n#define AUTOTTL_DEFAULT_DUP_MIN\t\t3\n#define AUTOTTL_DEFAULT_DUP_MAX\t\t64\n\n\n#define MAX_SPLITS\t64\n\n#define FAKE_TLS_MOD_SAVE_MASK\t\t0x0F\n#define FAKE_TLS_MOD_SET\t\t0x01\n#define FAKE_TLS_MOD_CUSTOM_FAKE\t0x02\n#define FAKE_TLS_MOD_RND\t\t0x10\n#define FAKE_TLS_MOD_DUP_SID\t\t0x20\n#define FAKE_TLS_MOD_RND_SNI\t\t0x40\n#define FAKE_TLS_MOD_SNI\t\t0x80\n#define FAKE_TLS_MOD_PADENCAP\t\t0x100\n\n#define FAKE_MAX_TCP\t1460\n#define FAKE_MAX_UDP\t1472\n\n#define MAX_GIDS 64\n\nextern const char * tld[6];\n\nenum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID };\n\nstruct fake_tls_mod_cache\n{\n\tsize_t extlen_offset, padlen_offset;\n};\nstruct fake_tls_mod\n{\n\tchar sni[128];\n\tuint32_t mod;\n};\nstruct hostfakesplit_mod\n{\n\tchar host[128];\n\tsize_t host_size;\n\tint ordering;\n};\nstruct fakedsplit_mod\n{\n\tint ordering;\n};\nstruct tcp_mod\n{\n\tbool seq;\n};\n\ntypedef enum {SS_NONE=0,SS_SYN,SS_SYNACK,SS_ACKSYN} t_synack_split;\ntypedef enum {IPID_SEQ=0,IPID_SEQ_GROUP,IPID_RND,IPID_ZERO,IPID_SAME} t_ip_id_mode;\n\nstruct desync_profile\n{\n\tint n;\t// number of the profile\n\n\tuint16_t wsize,wssize;\n\tuint8_t wscale,wsscale;\n\tchar wssize_cutoff_mode; // n - packets, d - data packets, s - relative sequence\n\tbool wssize_no_forced_cutoff;\n\tunsigned int wssize_cutoff;\n\n\tt_synack_split synack_split;\n\n\tt_ip_id_mode ip_id_mode;\n\n\tbool hostcase, hostnospace, domcase, methodeol;\n\tchar hostspell[4];\n\tenum dpi_desync_mode desync_mode0,desync_mode,desync_mode2;\n\tbool desync_retrans,desync_skip_nosni,desync_any_proto;\n\tunsigned int desync_repeats,desync_ipfrag_pos_tcp,desync_ipfrag_pos_udp;\n\n\t// multisplit\n\tstruct proto_pos splits[MAX_SPLITS];\n\tint split_count;\n\tstruct proto_pos seqovl,hostfakesplit_midhost;\n\n\tchar dup_start_mode, dup_cutoff_mode; // n - packets, d - data packets, s - relative sequence\n\tbool dup_replace;\n\tunsigned int dup_start, dup_cutoff;\n\tunsigned int dup_repeats;\n\tuint8_t dup_ttl, dup_ttl6;\n\tuint32_t dup_fooling_mode;\n\tuint32_t dup_ts_increment, dup_badseq_increment, dup_badseq_ack_increment;\n\tautottl dup_autottl, dup_autottl6;\n\tuint16_t dup_tcp_flags_set, dup_tcp_flags_unset;\n\tt_ip_id_mode dup_ip_id_mode;\n\n\tchar orig_mod_start_mode, orig_mod_cutoff_mode; // n - packets, d - data packets, s - relative sequence\n\tunsigned int orig_mod_start, orig_mod_cutoff;\n\tuint8_t orig_mod_ttl, orig_mod_ttl6;\n\tautottl orig_autottl, orig_autottl6;\n\tuint16_t orig_tcp_flags_set, orig_tcp_flags_unset;\n\n\tchar desync_start_mode, desync_cutoff_mode; // n - packets, d - data packets, s - relative sequence\n\tunsigned int desync_start, desync_cutoff;\n\tuint8_t desync_ttl, desync_ttl6;\n\tautottl desync_autottl, desync_autottl6;\n\tuint32_t desync_fooling_mode;\n\tuint32_t desync_ts_increment, desync_badseq_increment, desync_badseq_ack_increment;\n\tuint16_t desync_tcp_flags_set, desync_tcp_flags_unset;\n\n\tstruct blob_collection_head fake_http,fake_tls,fake_unknown,fake_unknown_udp,fake_quic,fake_wg,fake_dht,fake_discord,fake_stun;\n\tuint8_t fake_syndata[FAKE_MAX_TCP],seqovl_pattern[FAKE_MAX_TCP],udplen_pattern[FAKE_MAX_UDP];\n\tuint8_t *fsplit_pattern;\n\tsize_t fake_syndata_size, fsplit_pattern_size;\n\n\tstruct fake_tls_mod tls_mod_last;\n\tstruct blob_item *tls_fake_last;\n\n\tstruct hostfakesplit_mod hfs_mod;\n\tstruct fakedsplit_mod fs_mod;\n\tstruct tcp_mod tcp_mod;\n\n\tint udplen_increment;\n\n\tbool filter_ipv4,filter_ipv6;\n\tstruct port_filters_head pf_tcp,pf_udp;\n\tuint32_t filter_l7;\t// L7_PROTO_* bits\n\n#ifdef HAS_FILTER_SSID\n\t// per profile ssid filter\n\t// annot use global filter because it's not possible to bind multiple instances to a single queue\n\t// it's possible to run multiple winws instances on the same windivert filter, but it's not the case for linux\n\tstruct str_list_head filter_ssid;\n#endif\n\n\t// list of pointers to ipsets\n\tstruct ipset_collection_head ips_collection, ips_collection_exclude;\n\n\t// list of pointers to hostlist files\n\tstruct hostlist_collection_head hl_collection, hl_collection_exclude;\n\t// pointer to autohostlist. NULL if no autohostlist for the profile.\n\tstruct hostlist_file *hostlist_auto;\n\tint hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold;\n\n\thostfail_pool *hostlist_auto_fail_counters;\n};\n\n#define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&(dp)->ips_collection) && !LIST_FIRST(&(dp)->ips_collection_exclude))\n#define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&(dp)->ips_collection) && ipset_collection_is_empty(&(dp)->ips_collection_exclude))\n#define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&(dp)->hl_collection) && hostlist_collection_is_empty(&(dp)->hl_collection_exclude))\n#define PROFILE_HAS_ORIG_MOD(dp) ((dp)->orig_mod_ttl || (dp)->orig_mod_ttl6 || (dp)->orig_tcp_flags_set || (dp)->orig_tcp_flags_unset)\n\nstruct desync_profile_list {\n\tstruct desync_profile dp;\n\tLIST_ENTRY(desync_profile_list) next;\n};\nLIST_HEAD(desync_profile_list_head, desync_profile_list);\nstruct desync_profile_list *dp_list_add(struct desync_profile_list_head *head);\nvoid dp_entry_destroy(struct desync_profile_list *entry);\nvoid dp_list_destroy(struct desync_profile_list_head *head);\nbool dp_list_have_autohostlist(struct desync_profile_list_head *head);\nbool dp_list_need_all_out(struct desync_profile_list_head *head);\nvoid dp_init(struct desync_profile *dp);\nbool dp_fake_defaults(struct desync_profile *dp);\nvoid dp_clear(struct desync_profile *dp);\n\nstruct params_s\n{\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\twordexp_t wexp; // for file based config\n#endif\n\n\tenum log_target debug_target;\n\tchar debug_logfile[PATH_MAX];\n\tbool debug;\n\n\tbool daemon;\n\n#ifdef __linux__\n\tint qnum;\n#elif defined(BSD)\n\tuint16_t port; // divert port\n#endif\n\tchar bind_fix4,bind_fix6;\n\tuint32_t desync_fwmark; // unused in BSD\n\t\n\tstruct desync_profile_list_head desync_profiles;\n\t\n#ifdef __CYGWIN__\n\tstruct str_list_head ssid_filter,nlm_filter;\n\tstruct str_list_head wf_raw_part;\n#else\n\tbool droproot;\n\tchar *user;\n\tuid_t uid;\n\tgid_t gid[MAX_GIDS];\n\tint gid_count;\n#endif\n\tchar pidfile[PATH_MAX];\n\n\tchar hostlist_auto_debuglog[PATH_MAX];\n\n\t// hostlist files with data for all profiles\n\tstruct hostlist_files_head hostlists;\n\t// ipset files with data for all profiles\n\tstruct ipset_files_head ipsets;\n\n\tunsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp;\n\tt_conntrack conntrack;\n\tbool ctrack_disable;\n\n\tbool autottl_present;\n#ifdef HAS_FILTER_SSID\n\tbool filter_ssid_present;\n#endif\n\n\tbool cache_hostname;\n\tunsigned int ipcache_lifetime;\n\tip_cache ipcache;\n};\n\nextern struct params_s params;\nextern const char *progname;\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\nvoid cleanup_args(struct params_s *params);\n#endif\nvoid cleanup_params(struct params_s *params);\n\nint DLOG(const char *format, ...);\nint DLOG_ERR(const char *format, ...);\nint DLOG_PERROR(const char *s);\nint DLOG_CONDUP(const char *format, ...);\nint HOSTLIST_DEBUGLOG_APPEND(const char *format, ...);\nvoid hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit);\n"
  },
  {
    "path": "nfq/pools.c",
    "content": "#define _GNU_SOURCE\n#include \"pools.h\"\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <arpa/inet.h>\n\n#define DESTROY_STR_POOL(etype, ppool) \\\n\tetype *elem, *tmp; \\\n\tHASH_ITER(hh, *ppool, elem, tmp) { \\\n\t\tfree(elem->str); \\\n\t\tHASH_DEL(*ppool, elem); \\\n\t\tfree(elem); \\\n\t}\n\n#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \\\n\tetype *elem; \\\n\tif (!(elem = (etype*)malloc(sizeof(etype)))) \\\n\t\treturn false; \\\n\tif (!(elem->str = malloc(keystr_len + 1))) \\\n\t{ \\\n\t\tfree(elem); \\\n\t\treturn false; \\\n\t} \\\n\tmemcpy(elem->str, keystr, keystr_len); \\\n\telem->str[keystr_len] = 0; \\\n\toom = false; \\\n\tHASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \\\n\tif (oom) \\\n\t{ \\\n\t\tfree(elem->str); \\\n\t\tfree(elem); \\\n\t\treturn false; \\\n\t}\n#define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \\\n\tetype *elem_find; \\\n\tHASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \\\n\tif (!elem_find) { \\\n\t\tADD_STR_POOL(etype,ppool,keystr,keystr_len); \\\n\t\telem->flags = flg; \\\n\t}\n\n#undef uthash_nonfatal_oom\n#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)\nstatic bool oom = false;\nstatic void ut_oom_recover(void *elem)\n{\n\toom = true;\n}\n\n// for not zero terminated strings\nbool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags)\n{\n\tADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags)\n\treturn true;\n}\n// for zero terminated strings\nbool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags)\n{\n\treturn HostlistPoolAddStrLen(pp, s, strlen(s), flags);\n}\n\nhostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s)\n{\n\thostlist_pool *elem;\n\tHASH_FIND_STR(p, s, elem);\n\treturn elem;\n}\nbool HostlistPoolCheckStr(hostlist_pool *p, const char *s)\n{\n\treturn !!HostlistPoolGetStr(p,s);\n}\n\nvoid HostlistPoolDestroy(hostlist_pool **pp)\n{\n\tDESTROY_STR_POOL(hostlist_pool, pp)\n}\n\n\n\nvoid HostFailPoolDestroy(hostfail_pool **pp)\n{\n\tDESTROY_STR_POOL(hostfail_pool, pp)\n}\nhostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)\n{\n\tsize_t slen = strlen(s);\n\tADD_STR_POOL(hostfail_pool, pp, s, slen)\n\telem->expire = time(NULL) + fail_time;\n\telem->counter = 0;\n\treturn elem;\n}\nhostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)\n{\n\thostfail_pool *elem;\n\tHASH_FIND_STR(p, s, elem);\n\treturn elem;\n}\nvoid HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)\n{\n\tfree(elem->str);\n\tHASH_DEL(*p, elem);\n\tfree(elem);\n}\nvoid HostFailPoolPurge(hostfail_pool **pp)\n{\n\thostfail_pool *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *pp, elem, tmp)\n\t{\n\t\tif (now >= elem->expire)\n\t\t\tHostFailPoolDel(pp, elem);\n\t}\n}\nstatic time_t host_fail_purge_prev=0;\nvoid HostFailPoolPurgeRateLimited(hostfail_pool **pp)\n{\n\ttime_t now = time(NULL);\n\t// do not purge too often to save resources\n\tif (host_fail_purge_prev != now)\n\t{\n\t\tHostFailPoolPurge(pp);\n\t\thost_fail_purge_prev = now;\n\t}\n}\nvoid HostFailPoolDump(hostfail_pool *p)\n{\n\thostfail_pool *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, p, elem, tmp)\n\t\tprintf(\"host=%s counter=%d time_left=%lld\\n\",elem->str,elem->counter,(long long int)elem->expire-now);\n}\n\n\nbool strlist_add(struct str_list_head *head, const char *filename)\n{\n\tstruct str_list *entry = malloc(sizeof(struct str_list));\n\tif (!entry) return false;\n\tentry->str = strdup(filename);\n\tif (!entry->str)\n\t{\n\t\tfree(entry);\n\t\treturn false;\n\t}\n\tLIST_INSERT_HEAD(head, entry, next);\n\treturn true;\n}\nstatic void strlist_entry_destroy(struct str_list *entry)\n{\n\tfree(entry->str);\n\tfree(entry);\n}\nvoid strlist_destroy(struct str_list_head *head)\n{\n\tstruct str_list *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tstrlist_entry_destroy(entry);\n\t}\n}\nbool strlist_search(const struct str_list_head *head, const char *str)\n{\n\tstruct str_list *entry;\n\tif (str)\n\t{\n\t\tLIST_FOREACH(entry, head, next)\n\t\t{\n\t\t\tif (!strcmp(entry->str, str))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n\n\nstruct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename)\n{\n\tstruct hostlist_file *entry = malloc(sizeof(struct hostlist_file));\n\tif (entry)\n\t{\n\t\tif (filename)\n\t\t{\n\t\t\tif (!(entry->filename = strdup(filename)))\n\t\t\t{\n\t\t\t\tfree(entry);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tentry->filename = NULL;\n\t\tFILE_MOD_RESET(&entry->mod_sig);\n\t\tentry->hostlist = NULL;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstatic void hostlist_files_entry_destroy(struct hostlist_file *entry)\n{\n\tfree(entry->filename);\n\tHostlistPoolDestroy(&entry->hostlist);\n\tfree(entry);\n}\nvoid hostlist_files_destroy(struct hostlist_files_head *head)\n{\n\tstruct hostlist_file *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\thostlist_files_entry_destroy(entry);\n\t}\n}\nstruct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename)\n{\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, head, next)\n\t{\n\t\tif (hfile->filename && !strcmp(hfile->filename,filename))\n\t\t\treturn hfile;\n\t}\n\treturn NULL;\n}\nvoid hostlist_files_reset_modtime(struct hostlist_files_head *list)\n{\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t\tFILE_MOD_RESET(&hfile->mod_sig);\n}\n\nstruct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile)\n{\n\tstruct hostlist_item *entry = malloc(sizeof(struct hostlist_item));\n\tif (entry)\n\t{\n\t\tentry->hfile = hfile;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid hostlist_collection_destroy(struct hostlist_collection_head *head)\n{\n\tstruct hostlist_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nstruct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename)\n{\n\tstruct hostlist_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->filename && !strcmp(item->hfile->filename,filename))\n\t\t\treturn item;\n\t}\n\treturn NULL;\n}\nbool hostlist_collection_is_empty(const struct hostlist_collection_head *head)\n{\n\tconst struct hostlist_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->hostlist)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nstatic int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q)\n{\n\tunsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen;\n\tunsigned int df = bitlen & 7, bytes = bitlen >> 3;\n\tint cmp = memcmp(p->data, q->data, bytes);\n\n\tif (cmp || !df) return cmp;\n\n\tuint8_t c1 = p->data[bytes] >> (8 - df);\n\tuint8_t c2 = q->data[bytes] >> (8 - df);\n\treturn c1<c2 ? -1 : c1==c2 ? 0 : 1;\n}\nKAVL_INIT(kavl_bit, struct kavl_bit_elem, head, kavl_bit_cmp)\nstatic void kavl_bit_destroy_elem(struct kavl_bit_elem *e)\n{\n\tif (e)\n\t{\n\t\tfree(e->data);\n\t\tfree(e);\n\t}\n}\nvoid kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen)\n{\n\tstruct kavl_bit_elem temp = {\n\t\t.bitlen = bitlen, .data = (uint8_t*)data\n\t};\n\tkavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0));\n}\nvoid kavl_bit_destroy(struct kavl_bit_elem **hdr)\n{\n\twhile (*hdr)\n\t{\n\t\tstruct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr);\n\t\tif (!e)\tbreak;\n\t\tkavl_bit_destroy_elem(e);\n\t}\n\tfree(*hdr);\n}\nstruct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size)\n{\n\tif (!struct_size) struct_size=sizeof(struct kavl_bit_elem);\n\n\tstruct kavl_bit_elem *v, *e = calloc(1, struct_size);\n\tif (!e) return 0;\n\n\te->bitlen = bitlen;\n\te->data = data;\n\n\tv = kavl_insert(kavl_bit, hdr, e, 0);\n\twhile (e != v && e->bitlen < v->bitlen)\n\t{\n\t\tkavl_bit_delete(hdr, v->data, v->bitlen);\n\t\tv = kavl_insert(kavl_bit, hdr, e, 0);\n\t}\n\tif (e != v) kavl_bit_destroy_elem(e);\n\treturn v;\n}\nstruct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen)\n{\n\tstruct kavl_bit_elem temp = {\n\t\t.bitlen = bitlen, .data = (uint8_t*)data\n\t};\n\treturn kavl_find(kavl_bit, hdr, &temp, 0);\n}\n\nstatic bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen)\n{\n\tuint8_t bytelen = (preflen+7)>>3;\n\tuint8_t *abuf = malloc(bytelen);\n\tif (!abuf) return false;\n\tmemcpy(abuf,a,bytelen);\n\tif (!kavl_bit_add(ipset,abuf,preflen,0))\n\t{\n\t\tfree(abuf);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nbool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen)\n{\n\treturn !!kavl_bit_get(ipset,a,preflen);\n}\nbool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen)\n{\n\tif (preflen>32) return false;\n\treturn ipset_kavl_add(ipset,a,preflen);\n}\nvoid ipset4Print(struct kavl_bit_elem *ipset)\n{\n\tif (!ipset) return;\n\n\tstruct cidr4 c;\n\tconst struct kavl_bit_elem *elem;\n\tkavl_itr_t(kavl_bit) itr;\n\tkavl_itr_first(kavl_bit, ipset, &itr);\n\tdo\n\t{\n\t\telem = kavl_at(&itr);\n\t\tc.preflen = elem->bitlen;\n\t\texpand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr));\n\t\tprint_cidr4(&c);\n\t\tprintf(\"\\n\");\n\t}\n\twhile (kavl_itr_next(kavl_bit, &itr));\n}\n\nbool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen)\n{\n\treturn !!kavl_bit_get(ipset,a,preflen);\n}\nbool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen)\n{\n\tif (preflen>128) return false;\n\treturn ipset_kavl_add(ipset,a,preflen);\n}\nvoid ipset6Print(struct kavl_bit_elem *ipset)\n{\n\tif (!ipset) return;\n\n\tstruct cidr6 c;\n\tconst struct kavl_bit_elem *elem;\n\tkavl_itr_t(kavl_bit) itr;\n\tkavl_itr_first(kavl_bit, ipset, &itr);\n\tdo\n\t{\n\t\telem = kavl_at(&itr);\n\t\tc.preflen = elem->bitlen;\n\t\texpand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr));\n\t\tprint_cidr6(&c);\n\t\tprintf(\"\\n\");\n\t}\n\twhile (kavl_itr_next(kavl_bit, &itr));\n}\n\nvoid ipsetDestroy(ipset *ipset)\n{\n\tkavl_bit_destroy(&ipset->ips4);\n\tkavl_bit_destroy(&ipset->ips6);\n}\nvoid ipsetPrint(ipset *ipset)\n{\n\tipset4Print(ipset->ips4);\n\tipset6Print(ipset->ips6);\n}\n\n\nstruct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename)\n{\n\tstruct ipset_file *entry = malloc(sizeof(struct ipset_file));\n\tif (entry)\n\t{\n\t\tif (filename)\n\t\t{\n\t\t\tif (!(entry->filename = strdup(filename)))\n\t\t\t{\n\t\t\t\tfree(entry);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tentry->filename = NULL;\n\t\tFILE_MOD_RESET(&entry->mod_sig);\n\t\tmemset(&entry->ipset,0,sizeof(entry->ipset));\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstatic void ipset_files_entry_destroy(struct ipset_file *entry)\n{\n\tfree(entry->filename);\n\tipsetDestroy(&entry->ipset);\n\tfree(entry);\n}\nvoid ipset_files_destroy(struct ipset_files_head *head)\n{\n\tstruct ipset_file *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tipset_files_entry_destroy(entry);\n\t}\n}\nstruct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename)\n{\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, head, next)\n\t{\n\t\tif (hfile->filename && !strcmp(hfile->filename,filename))\n\t\t\treturn hfile;\n\t}\n\treturn NULL;\n}\nvoid ipset_files_reset_modtime(struct ipset_files_head *list)\n{\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t\tFILE_MOD_RESET(&hfile->mod_sig);\n}\n\nstruct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile)\n{\n\tstruct ipset_item *entry = malloc(sizeof(struct ipset_item));\n\tif (entry)\n\t{\n\t\tentry->hfile = hfile;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid ipset_collection_destroy(struct ipset_collection_head *head)\n{\n\tstruct ipset_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nstruct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename)\n{\n\tstruct ipset_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->filename && !strcmp(item->hfile->filename,filename))\n\t\t\treturn item;\n\t}\n\treturn NULL;\n}\nbool ipset_collection_is_empty(const struct ipset_collection_head *head)\n{\n\tconst struct ipset_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (!IPSET_EMPTY(&item->hfile->ipset))\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nbool port_filter_add(struct port_filters_head *head, const port_filter *pf)\n{\n\tstruct port_filter_item *entry = malloc(sizeof(struct port_filter_item));\n\tif (entry)\n\t{\n\t\tentry->pf = *pf;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid port_filters_destroy(struct port_filters_head *head)\n{\n\tstruct port_filter_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nbool port_filters_in_range(const struct port_filters_head *head, uint16_t port)\n{\n\tconst struct port_filter_item *item;\n\n\tif (!LIST_FIRST(head)) return true;\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (pf_in_range(port, &item->pf))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\nbool port_filters_deny_if_empty(struct port_filters_head *head)\n{\n\tport_filter pf;\n\tif (LIST_FIRST(head)) return true;\n\treturn pf_parse(\"0\",&pf) && port_filter_add(head,&pf);\n}\n\n\n\t\t\nstruct blob_item *blob_collection_add(struct blob_collection_head *head)\n{\n\tstruct blob_item *entry = calloc(1,sizeof(struct blob_item));\n\tif (entry)\n\t{\n\t\t// insert to the end\n\t\tstruct blob_item *itemc,*iteml=LIST_FIRST(head);\n\t\tif (iteml)\n\t\t{\n\t\t\twhile ((itemc=LIST_NEXT(iteml,next))) iteml = itemc;\n\t\t\tLIST_INSERT_AFTER(iteml, entry, next);\n\t\t}\n\t\telse\n\t\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstruct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset)\n{\n\tif (offset>=size) return NULL;\n\tstruct blob_item *entry = calloc(1,sizeof(struct blob_item));\n\tif (!entry) return NULL;\n\tif (!(entry->data = malloc(size+size_reserve))) \n\t{\n\t\tfree(entry);\n\t\treturn NULL;\n\t}\n\tif (data) memcpy(entry->data,data,size);\n\tentry->size = size;\n\tentry->size_buf = size+size_reserve;\n\tentry->offset = offset;\n\n\t// insert to the end\n\tstruct blob_item *itemc,*iteml=LIST_FIRST(head);\n\tif (iteml)\n\t{\n\t\twhile ((itemc=LIST_NEXT(iteml,next))) iteml = itemc;\n\t\tLIST_INSERT_AFTER(iteml, entry, next);\n\t}\n\telse\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\n\treturn entry;\n}\n\nvoid blob_collection_destroy(struct blob_collection_head *head)\n{\n\tstruct blob_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry->extra);\n\t\tfree(entry->extra2);\n\t\tfree(entry->data);\n\t\tfree(entry);\n\t}\n}\nbool blob_collection_empty(const struct blob_collection_head *head)\n{\n\treturn !LIST_FIRST(head);\n}\n\n\n\nstatic void ipcache_item_touch(ip_cache_item *item)\n{\n\ttime(&item->last);\n}\nstatic void ipcache_item_init(ip_cache_item *item)\n{\n\tipcache_item_touch(item);\n\titem->hostname = NULL;\n\titem->hostname_is_ip = false;\n\titem->hops = 0;\n}\nstatic void ipcache_item_destroy(ip_cache_item *item)\n{\n\tfree(item->hostname);\n}\n\nstatic void ipcache4Destroy(ip_cache4 **ipcache)\n{\n\tip_cache4 *elem, *tmp;\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tHASH_DEL(*ipcache, elem);\n\t\tipcache_item_destroy(&elem->data);\n\t\tfree(elem);\n\t}\n}\nstatic void ipcache4Key(ip4if *key, const struct in_addr *a, const char *iface)\n{\n\tmemset(key,0,sizeof(*key)); // make sure everything is zero\n\tkey->addr = *a;\n\tif (iface) snprintf(key->iface,sizeof(key->iface),\"%s\",iface);\n}\nstatic ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a, const char *iface)\n{\n\tip_cache4 *entry;\n\tstruct ip4if key;\n\n\tipcache4Key(&key,a,iface);\n\tHASH_FIND(hh, ipcache, &key, sizeof(key), entry);\n\treturn entry;\n}\nstatic ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a, const char *iface)\n{\n\t// avoid dups\n\tip_cache4 *entry = ipcache4Find(*ipcache,a,iface);\n\tif (entry) return entry; // already included\n\n\tentry = malloc(sizeof(ip_cache4));\n\tif (!entry) return NULL;\n\tipcache4Key(&entry->key,a,iface);\n\n\toom = false;\n\tHASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry);\n\tif (oom) { free(entry); return NULL; }\n\n\tipcache_item_init(&entry->data);\n\n\treturn entry;\n}\nstatic void ipcache4Print(ip_cache4 *ipcache)\n{\n\tchar s_ip[16];\n\ttime_t now;\n\tip_cache4 *ipc, *tmp;\n\n\ttime(&now);\n\tHASH_ITER(hh, ipcache , ipc, tmp)\n\t{\n\t\t*s_ip=0;\n\t\tinet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip));\n\t\tprintf(\"%s iface=%s : hops %u hostname=%s hostname_is_ip=%u now=last+%llu\\n\", s_ip, ipc->key.iface, ipc->data.hops, ipc->data.hostname ? ipc->data.hostname : \"\", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last));\n\t}\n}\n\nstatic void ipcache6Destroy(ip_cache6 **ipcache)\n{\n\tip_cache6 *elem, *tmp;\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tHASH_DEL(*ipcache, elem);\n\t\tipcache_item_destroy(&elem->data);\n\t\tfree(elem);\n\t}\n}\nstatic void ipcache6Key(ip6if *key, const struct in6_addr *a, const char *iface)\n{\n\tmemset(key,0,sizeof(*key)); // make sure everything is zero\n\tkey->addr = *a;\n\tif (iface) snprintf(key->iface,sizeof(key->iface),\"%s\",iface);\n}\nstatic ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a, const char *iface)\n{\n\tip_cache6 *entry;\n\tip6if key;\n\n\tipcache6Key(&key,a,iface);\n\tHASH_FIND(hh, ipcache, &key, sizeof(key), entry);\n\treturn entry;\n}\nstatic ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a, const char *iface)\n{\n\t// avoid dups\n\tip_cache6 *entry = ipcache6Find(*ipcache,a,iface);\n\tif (entry) return entry; // already included\n\n\tentry = malloc(sizeof(ip_cache6));\n\tif (!entry) return NULL;\n\tipcache6Key(&entry->key,a,iface);\n\n\toom = false;\n\tHASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry);\n\tif (oom) { free(entry); return NULL; }\n\n\tipcache_item_init(&entry->data);\n\n\treturn entry;\n}\nstatic void ipcache6Print(ip_cache6 *ipcache)\n{\n\tchar s_ip[40];\n\ttime_t now;\n\tip_cache6 *ipc, *tmp;\n\n\ttime(&now);\n\tHASH_ITER(hh, ipcache , ipc, tmp)\n\t{\n\t\t*s_ip=0;\n\t\tinet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip));\n\t\tprintf(\"%s iface=%s : hops %u hostname=%s hostname_is_ip=%u now=last+%llu\\n\", s_ip, ipc->key.iface, ipc->data.hops, ipc->data.hostname ? ipc->data.hostname : \"\", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last));\n\t}\n}\n\nvoid ipcacheDestroy(ip_cache *ipcache)\n{\n\tipcache4Destroy(&ipcache->ipcache4);\n\tipcache6Destroy(&ipcache->ipcache6);\n}\nvoid ipcachePrint(ip_cache *ipcache)\n{\n\tipcache4Print(ipcache->ipcache4);\n\tipcache6Print(ipcache->ipcache6);\n}\n\nip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface)\n{\n\tip_cache4 *ipcache4;\n\tip_cache6 *ipcache6;\n\tif (a4)\n\t{\n\t\tif ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4,iface)))\n\t\t{\n\t\t\tipcache_item_touch(&ipcache4->data);\n\t\t\treturn &ipcache4->data;\n\t\t}\n\t}\n\telse if (a6)\n\t{\n\t\tif ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6,iface)))\n\t\t{\n\t\t\tipcache_item_touch(&ipcache6->data);\n\t\t\treturn &ipcache6->data;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime)\n{\n\tip_cache4 *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tif (now >= (elem->data.last + lifetime))\n\t\t{\n\t\t\tHASH_DEL(*ipcache, elem);\n\t\t\tipcache_item_destroy(&elem->data);\n\t\t\tfree(elem);\n\t\t}\n\t}\n}\nstatic void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime)\n{\n\tip_cache6 *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tif (now >= (elem->data.last + lifetime))\n\t\t{\n\t\t\tHASH_DEL(*ipcache, elem);\n\t\t\tipcache_item_destroy(&elem->data);\n\t\t\tfree(elem);\n\t\t}\n\t}\n}\nstatic void ipcache_purge(ip_cache *ipcache, time_t lifetime)\n{\n\tif (lifetime) // 0 = no expire\n\t{\n\t\tipcache4_purge(&ipcache->ipcache4, lifetime);\n\t\tipcache6_purge(&ipcache->ipcache6, lifetime);\n\t}\n}\nstatic time_t ipcache_purge_prev=0;\nvoid ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime)\n{\n\ttime_t now = time(NULL);\n\t// do not purge too often to save resources\n\tif (ipcache_purge_prev != now)\n\t{\n\t\tipcache_purge(ipcache, lifetime);\n\t\tipcache_purge_prev = now;\n\t}\n}\n\n"
  },
  {
    "path": "nfq/pools.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <ctype.h>\n#include <sys/queue.h>\n#include <net/if.h>\n#include <time.h>\n\n#include \"helpers.h\"\n\n//#define HASH_BLOOM 20\n#define HASH_NONFATAL_OOM 1\n#define HASH_FUNCTION HASH_BER\n#include \"uthash.h\"\n\n#include \"kavl.h\"\n\n#define HOSTLIST_POOL_FLAG_STRICT_MATCH\t\t1\n\ntypedef struct hostlist_pool {\n\tchar *str;\t\t/* key */\n\tuint32_t flags;\t\t/* custom data */\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} hostlist_pool;\n\nvoid HostlistPoolDestroy(hostlist_pool **pp);\nbool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags);\nbool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags);\nhostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s);\n\nstruct str_list {\n\tchar *str;\n\tLIST_ENTRY(str_list) next;\n};\nLIST_HEAD(str_list_head, str_list);\n\nbool strlist_add(struct str_list_head *head, const char *filename);\nvoid strlist_destroy(struct str_list_head *head);\nbool strlist_search(const struct str_list_head *head, const char *str);\n\n\ntypedef struct hostfail_pool {\n\tchar *str;\t\t/* key */\n\tint counter;\t/* value */\n\ttime_t expire;\t/* when to expire record (unixtime) */\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} hostfail_pool;\n\nvoid HostFailPoolDestroy(hostfail_pool **pp);\nhostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);\nhostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);\nvoid HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);\nvoid HostFailPoolPurge(hostfail_pool **pp);\nvoid HostFailPoolPurgeRateLimited(hostfail_pool **pp);\nvoid HostFailPoolDump(hostfail_pool *p);\n\n\nstruct hostlist_file {\n\tchar *filename;\n\tfile_mod_sig mod_sig;\n\thostlist_pool *hostlist;\n\tLIST_ENTRY(hostlist_file) next;\n};\nLIST_HEAD(hostlist_files_head, hostlist_file);\n\nstruct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename);\nvoid hostlist_files_destroy(struct hostlist_files_head *head);\nstruct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename);\nvoid hostlist_files_reset_modtime(struct hostlist_files_head *list);\n\nstruct hostlist_item {\n\tstruct hostlist_file *hfile;\n\tLIST_ENTRY(hostlist_item) next;\n};\nLIST_HEAD(hostlist_collection_head, hostlist_item);\nstruct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile);\nvoid hostlist_collection_destroy(struct hostlist_collection_head *head);\nstruct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename);\nbool hostlist_collection_is_empty(const struct hostlist_collection_head *head);\n\n\nstruct kavl_bit_elem\n{\n\tunsigned int bitlen;\n\tuint8_t *data;\n\tKAVL_HEAD(struct kavl_bit_elem) head;\n};\n\nstruct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen);\nstruct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size);\nvoid kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen);\nvoid kavl_bit_destroy(struct kavl_bit_elem **hdr);\n\n// combined ipset ipv4 and ipv6\ntypedef struct ipset {\n\tstruct kavl_bit_elem *ips4,*ips6;\n} ipset;\n\n#define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6)\n\nbool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen);\nstatic inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr)\n{\n\treturn ipset4Add(ipset,&cidr->addr,cidr->preflen);\n}\nbool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen);\nvoid ipset4Print(struct kavl_bit_elem *ipset);\n\nbool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen);\nstatic inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr)\n{\n\treturn ipset6Add(ipset,&cidr->addr,cidr->preflen);\n}\nbool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen);\nvoid ipset6Print(struct kavl_bit_elem *ipset);\n\nvoid ipsetDestroy(ipset *ipset);\nvoid ipsetPrint(ipset *ipset);\n\n\nstruct ipset_file {\n\tchar *filename;\n\tfile_mod_sig mod_sig;\n\tipset ipset;\n\tLIST_ENTRY(ipset_file) next;\n};\nLIST_HEAD(ipset_files_head, ipset_file);\n\nstruct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename);\nvoid ipset_files_destroy(struct ipset_files_head *head);\nstruct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename);\nvoid ipset_files_reset_modtime(struct ipset_files_head *list);\n\nstruct ipset_item {\n\tstruct ipset_file *hfile;\n\tLIST_ENTRY(ipset_item) next;\n};\nLIST_HEAD(ipset_collection_head, ipset_item);\nstruct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile);\nvoid ipset_collection_destroy(struct ipset_collection_head *head);\nstruct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename);\nbool ipset_collection_is_empty(const struct ipset_collection_head *head);\n\n\nstruct port_filter_item {\n\tport_filter pf;\n\tLIST_ENTRY(port_filter_item) next;\n};\nLIST_HEAD(port_filters_head, port_filter_item);\nbool port_filter_add(struct port_filters_head *head, const port_filter *pf);\nvoid port_filters_destroy(struct port_filters_head *head);\nbool port_filters_in_range(const struct port_filters_head *head, uint16_t port);\nbool port_filters_deny_if_empty(struct port_filters_head *head);\n\n\nstruct blob_item {\n\tuint8_t *data;\t// main data blob\n\tsize_t size;\t// main data blob size\n\tsize_t size_buf;// main data blob allocated size\n\tsize_t offset;  // optional offset to useful data\n\tvoid *extra;\t// any data without size\n\tvoid *extra2;\t// any data without size\n\tLIST_ENTRY(blob_item) next;\n};\nLIST_HEAD(blob_collection_head, blob_item);\nstruct blob_item *blob_collection_add(struct blob_collection_head *head);\nstruct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset);\nvoid blob_collection_destroy(struct blob_collection_head *head);\nbool blob_collection_empty(const struct blob_collection_head *head);\n\n\ntypedef struct ip4if\n{\n\tchar iface[IFNAMSIZ];\n\tstruct in_addr addr;\n} ip4if;\ntypedef struct ip6if\n{\n\tchar iface[IFNAMSIZ];\n\tstruct in6_addr addr;\n} ip6if;\ntypedef struct ip_cache_item\n{\n\ttime_t last;\n\tchar *hostname;\n\tbool hostname_is_ip;\n\tuint8_t hops;\n} ip_cache_item;\ntypedef struct ip_cache4\n{\n\tip4if key;\n\tip_cache_item data;\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} ip_cache4;\ntypedef struct ip_cache6\n{\n\tip6if key;\n\tip_cache_item data;\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} ip_cache6;\ntypedef struct ip_cache\n{\n\tip_cache4 *ipcache4;\n\tip_cache6 *ipcache6;\n} ip_cache;\n\nip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface);\nvoid ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime);\nvoid ipcacheDestroy(ip_cache *ipcache);\nvoid ipcachePrint(ip_cache *ipcache);\n"
  },
  {
    "path": "nfq/protocol.c",
    "content": "#define _GNU_SOURCE\n\n#include \"protocol.h\"\n#include \"helpers.h\"\n#include \"params.h\"\n\n#include <string.h>\n#include <ctype.h>\n#include <arpa/inet.h>\n#include <string.h>\n\n// find N level domain\nstatic bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len)\n{\n\tint i;\n\tconst uint8_t *p1,*p2;\n\tfor (i=1,p2=dom+dlen;i<level;i++)\n\t{\n\t\tfor (p2--; p2>dom && *p2!='.'; p2--);\n\t\tif (p2<=dom) return false;\n\t}\n\tfor (p1=p2-1 ; p1>dom && *p1!='.'; p1--);\n\tif (*p1=='.') p1++;\n\tif (p) *p = p1;\n\tif (len) *len = p2-p1;\n\treturn true;\n}\n\nconst char *l7proto_str(t_l7proto l7)\n{\n\tswitch(l7)\n\t{\n\t\tcase HTTP: return \"http\";\n\t\tcase TLS: return \"tls\";\n\t\tcase QUIC: return \"quic\";\n\t\tcase WIREGUARD: return \"wireguard\";\n\t\tcase DHT: return \"dht\";\n\t\tcase DISCORD: return \"discord\";\n\t\tcase STUN: return \"stun\";\n\t\tdefault: return \"unknown\";\n\t}\n}\nbool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7)\n{\n\treturn  (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) ||\n\t\t(l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) ||\n\t\t(l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) ||\n\t\t(l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) ||\n\t\t(l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) ||\n\t\t(l7proto==DHT && (filter_l7 & L7_PROTO_DHT)) ||\n\t\t(l7proto==DISCORD && (filter_l7 & L7_PROTO_DISCORD)) ||\n\t\t(l7proto==STUN && (filter_l7 & L7_PROTO_STUN));\n}\n\nbool IsHostMarker(uint8_t posmarker)\n{\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\nconst char *posmarker_name(uint8_t posmarker)\n{\n\tswitch(posmarker)\n\t{\n\t\tcase PM_ABS: return \"abs\";\n\t\tcase PM_HOST: return \"host\";\n\t\tcase PM_HOST_END: return \"endhost\";\n\t\tcase PM_HOST_SLD: return \"sld\";\n\t\tcase PM_HOST_MIDSLD: return \"midsld\";\n\t\tcase PM_HOST_ENDSLD: return \"endsld\";\n\t\tcase PM_HTTP_METHOD: return \"method\";\n\t\tcase PM_SNI_EXT: return \"sniext\";\n\t\tdefault: return \"?\";\n\t}\n}\n\nstatic size_t CheckPos(size_t sz, ssize_t offset)\n{\n\treturn (offset>=0 && offset<sz) ? offset : 0;\n}\nsize_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tssize_t offset;\n\tswitch(posmarker)\n\t{\n\t\tcase PM_ABS:\n\t\t\toffset = (pos<0) ? sz+pos : pos;\n\t\t\treturn CheckPos(sz,offset);\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\nstatic size_t HostPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz, size_t offset_host, size_t len_host)\n{\n\tssize_t offset;\n\tconst uint8_t *p;\n\tsize_t slen;\n\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\t\toffset = offset_host+pos;\n\t\t\tbreak;\n\t\tcase PM_HOST_END:\n\t\t\toffset = offset_host+len_host+pos;\n\t\t\tbreak;\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\tif (((offset_host+len_host)<=sz) && FindNLD(data+offset_host,len_host,2,&p,&slen))\n\t\t\t\toffset = (posmarker==PM_HOST_SLD ? p-data : posmarker==PM_HOST_ENDSLD ? p-data+slen : slen==1 ? p+1-data : p+slen/2-data) + pos;\n\t\t\telse\n\t\t\t\toffset = 0;\n\t\t\tbreak;\n\t}\n\treturn CheckPos(sz,offset);\n}\nsize_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp)\n{\n\tswitch(l7proto)\n\t{\n\t\tcase HTTP:\n\t\t\treturn HttpPos(sp->marker, sp->pos, data, sz);\n\t\tcase TLS:\n\t\t\treturn TLSPos(sp->marker, sp->pos, data, sz);\n\t\tdefault:\n\t\t\treturn AnyProtoPos(sp->marker, sp->pos, data, sz);\n\t}\n}\nvoid ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count)\n{\n\tint i,j;\n\tfor(i=j=0;i<split_count;i++)\n\t{\n\t\tpos[j] = ResolvePos(data,sz,l7proto,splits+i);\n\t\tif (pos[j]) j++;\n\t}\n\tqsort_size_t(pos, j);\n\tj=unique_size_t(pos, j);\n\t*pos_count=j;\n}\n\n\nconst char *http_methods[] = { \"GET /\",\"POST /\",\"HEAD /\",\"OPTIONS \",\"PUT /\",\"DELETE /\",\"CONNECT \",\"TRACE /\",NULL };\nconst char *HttpMethod(const uint8_t *data, size_t len)\n{\n\tconst char **method;\n\tsize_t method_len;\n\tfor (method = http_methods; *method; method++)\n\t{\n\t\tmethod_len = strlen(*method);\n\t\tif (method_len <= len && !memcmp(data, *method, method_len))\n\t\t\treturn *method;\n\t}\n\treturn NULL;\n}\nbool IsHttp(const uint8_t *data, size_t len)\n{\n\treturn !!HttpMethod(data,len);\n}\n\nstatic bool IsHostAt(const uint8_t *p)\n{\n\treturn \\\n\t\tp[0]=='\\n' &&\n\t\t(p[1]=='H' || p[1]=='h') &&\n\t\t(p[2]=='o' || p[2]=='O') &&\n\t\t(p[3]=='s' || p[3]=='S') &&\n\t\t(p[4]=='t' || p[4]=='T') &&\n\t\tp[5]==':';\n}\nstatic uint8_t *FindHostIn(uint8_t *buf, size_t bs)\n{\n\tsize_t pos;\n\tif (bs<6) return NULL;\n\tbs-=6;\n\tfor(pos=0;pos<=bs;pos++)\n\t\tif (IsHostAt(buf+pos))\n\t\t\treturn buf+pos;\n\n\treturn NULL;\n}\nstatic const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs)\n{\n\tsize_t pos;\n\tif (bs<6) return NULL;\n\tbs-=6;\n\tfor(pos=0;pos<=bs;pos++)\n\t\tif (IsHostAt(buf+pos))\n\t\t\treturn buf+pos;\n\n\treturn NULL;\n}\n// pHost points to \"Host: ...\"\nbool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs)\n{\n\tif (!*pHost)\n\t{\n\t\t*pHost = FindHostIn(buf, bs);\n\t\tif (*pHost) (*pHost)++;\n\t}\n\treturn !!*pHost;\n}\nbool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs)\n{\n\tif (!*pHost)\n\t{\n\t\t*pHost = FindHostInConst(buf, bs);\n\t\tif (*pHost) (*pHost)++;\n\t}\n\treturn !!*pHost;\n}\n\nbool IsHttpReply(const uint8_t *data, size_t len)\n{\n\t// HTTP/1.x 200\\r\\n\n\treturn len>14 && !memcmp(data,\"HTTP/1.\",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&\n\t\tdata[9]>='0' && data[9]<='9' &&\n\t\tdata[10]>='0' && data[10]<='9' &&\n\t\tdata[11]>='0' && data[11]<='9';\n}\nint HttpReplyCode(const uint8_t *data, size_t len)\n{\n\treturn (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');\n}\nbool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)\n{\n\tconst uint8_t *p, *s, *e = data + len;\n\n\tp = (uint8_t*)strncasestr((char*)data, header, len);\n\tif (!p) return false;\n\tp += strlen(header);\n\twhile (p < e && (*p == ' ' || *p == '\\t')) p++;\n\ts = p;\n\twhile (s < e && (*s != '\\r' && *s != '\\n' && *s != ' ' && *s != '\\t')) s++;\n\tif (s > p)\n\t{\n\t\tsize_t slen = s - p;\n\t\tif (buf && len_buf)\n\t\t{\n\t\t\tif (slen >= len_buf) slen = len_buf - 1;\n\t\t\tfor (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);\n\t\t\tbuf[slen] = 0;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\nbool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)\n{\n\treturn HttpExtractHeader(data, len, \"\\nHost:\", host, len_host);\n}\n// DPI redirects are global redirects to another domain\nbool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)\n{\n\tchar loc[256],*redirect_host, *p;\n\tint code;\n\t\n\tif (!host || !*host) return false;\n\t\n\tcode = HttpReplyCode(data,len);\n\t\n\tif ((code!=302 && code!=307) || !HttpExtractHeader(data,len,\"\\nLocation:\",loc,sizeof(loc))) return false;\n\n\t// something like : https://censor.net/badpage.php?reason=denied&source=RKN\n\t\t\n\tif (!strncmp(loc,\"http://\",7))\n\t\tredirect_host=loc+7;\n\telse if (!strncmp(loc,\"https://\",8))\n\t\tredirect_host=loc+8;\n\telse\n\t\treturn false;\n\t\t\n\t// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN\n\t\n\tfor(p=redirect_host; *p && *p!='/' ; p++);\n\t*p=0;\n\tif (!*redirect_host) return false;\n\n\t// somethinkg like : censor.net\n\t\n\t// extract 2nd level domains\n\tconst char *dhost, *drhost;\n\tif (!FindNLD((uint8_t*)host,strlen(host),2,(const uint8_t**)&dhost,NULL) || !FindNLD((uint8_t*)redirect_host,strlen(redirect_host),2,(const uint8_t**)&drhost,NULL))\n\t\treturn false;\n\n\t// compare 2nd level domains\t\t\n\treturn strcasecmp(dhost, drhost)!=0;\n}\nsize_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tconst uint8_t *method, *host=NULL, *p;\n\tsize_t offset_host,len_host;\n\tssize_t offset;\n\tint i;\n\t\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HTTP_METHOD:\n\t\t\t// recognize some tpws pre-applied hacks\n\t\t\tmethod=data;\n\t\t\tif (sz<10) break;\n\t\t\tif (*method=='\\n' || *method=='\\r') method++;\n\t\t\tif (*method=='\\n' || *method=='\\r') method++;\n\t\t\tfor (p=method,i=0; i<9 && *p>='A' && *p<='Z'; i++,p++);\n\t\t\tif (i<3 || *p!=' ') break;\n\t\t\treturn CheckPos(sz,method-data+pos);\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\tif (HttpFindHostConst(&host,data,sz) && (host-data+7)<sz)\n\t\t\t{\n\t\t\t\thost+=5;\n\t\t\t\tif (*host==' ' || *host=='\\t') host++;\n\t\t\t\toffset_host = host-data;\n\t\t\t\tif (posmarker!=PM_HOST)\n\t\t\t\t\tfor (len_host=0; (offset_host+len_host)<sz && data[offset_host+len_host]!='\\r' && data[offset_host+len_host]!='\\n'; len_host++);\n\t\t\t\telse\n\t\t\t\t\tlen_host = 0;\n\t\t\t\treturn HostPos(posmarker,pos,data,sz,offset_host,len_host);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn AnyProtoPos(posmarker,pos,data,sz);\n\t}\n\treturn 0;\n}\n\n\nconst char *TLSVersionStr(uint16_t tlsver)\n{\n\tswitch(tlsver)\n\t{\n\t\tcase 0x0301: return \"TLS 1.0\";\n\t\tcase 0x0302: return \"TLS 1.1\";\n\t\tcase 0x0303: return \"TLS 1.2\";\n\t\tcase 0x0304: return \"TLS 1.3\";\n\t\tdefault:\n\t\t\t// 0x0a0a, 0x1a1a, ..., 0xfafa\n\t\t\treturn (((tlsver & 0x0F0F) == 0x0A0A) && ((tlsver>>12)==((tlsver>>4)&0xF))) ? \"GREASE\" : \"UNKNOWN\";\n\t}\n}\n\nuint16_t TLSRecordDataLen(const uint8_t *data)\n{\n\treturn pntoh16(data + 3);\n}\nsize_t TLSRecordLen(const uint8_t *data)\n{\n\treturn TLSRecordDataLen(data) + 5;\n}\nbool IsTLSRecordFull(const uint8_t *data, size_t len)\n{\n\treturn TLSRecordLen(data)<=len;\n}\nbool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK)\n{\n\treturn len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len);\n}\n\nsize_t TLSHandshakeLen(const uint8_t *data)\n{\n\treturn data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length\n}\nbool IsTLSHandshakeClientHello(const uint8_t *data, size_t len)\n{\n\treturn len>=4 && data[0]==0x01 && TLSHandshakeLen(data)>0;\n}\nbool IsTLSHandshakeFull(const uint8_t *data, size_t len)\n{\n\treturn (4+TLSHandshakeLen(data))<=len;\n}\n\n\nbool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off)\n{\n\t// +0\n\t// u8\tHandshakeType: ClientHello\n\t// u24\tLength\n\t// u16\tVersion\n\t// c[32] random\n\t// u8\tSessionIDLength\n\t//\t<SessionID>\n\t// u16\tCipherSuitesLength\n\t//\t<CipherSuites>\n\t// u8\tCompressionMethodsLength\n\t//\t<CompressionMethods>\n\t// u16\tExtensionsLength\n\n\tsize_t l;\n\n\tl = 1 + 3 + 2 + 32;\n\t// SessionIDLength\n\tif (len < (l + 1)) return false;\n\tl += data[l] + 1;\n\t// CipherSuitesLength\n\tif (len < (l + 2)) return false;\n\tl += pntoh16(data + l) + 2;\n\t// CompressionMethodsLength\n\tif (len < (l + 1)) return false;\n\tl += data[l] + 1;\n\t// ExtensionsLength\n\tif (len < (l + 2)) return false;\n\t*off = l;\n\treturn true;\n}\nbool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off)\n{\n\tif (!TLSFindExtLenOffsetInHandshake(data+5,len-5,off))\n\t\treturn false;\n\t*off+=5;\n\treturn true;\n}\n\n// bPartialIsOK=true - accept partial packets not containing the whole TLS message\nbool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)\n{\n\t// +0\n\t// u8\tHandshakeType: ClientHello\n\t// u24\tLength\n\t// u16\tVersion\n\t// c[32] random\n\t// u8\tSessionIDLength\n\t//\t<SessionID>\n\t// u16\tCipherSuitesLength\n\t//\t<CipherSuites>\n\t// u8\tCompressionMethodsLength\n\t//\t<CompressionMethods>\n\t// u16\tExtensionsLength\n\n\tsize_t l;\n\n\tif (!bPartialIsOK && !IsTLSHandshakeFull(data,len)) return false;\n\n\tif (!TLSFindExtLenOffsetInHandshake(data,len,&l)) return false;\n\n\tdata += l; len -= l;\n\tl = pntoh16(data);\n\tdata += 2; len -= 2;\n\t\n\tif (bPartialIsOK)\n\t{\n\t\tif (len < l) l = len;\n\t}\n\telse\n\t{\n\t\tif (len < l) return false;\n\t}\n\n\twhile (l >= 4)\n\t{\n\t\tuint16_t etype = pntoh16(data);\n\t\tsize_t elen = pntoh16(data + 2);\n\t\tdata += 4; l -= 4;\n\t\tif (l < elen) break;\n\t\tif (etype == type)\n\t\t{\n\t\t\tif (ext && len_ext)\n\t\t\t{\n\t\t\t\t*ext = data;\n\t\t\t\t*len_ext = elen;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tdata += elen; l -= elen;\n\t}\n\n\treturn false;\n}\nbool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)\n{\n\t// +0\n\t// u8\tContentType: Handshake\n\t// u16\tVersion: TLS1.0\n\t// u16\tLength\n\tsize_t reclen;\n\tif (!IsTLSClientHello(data, len, bPartialIsOK)) return false;\n\treclen=TLSRecordLen(data);\n\tif (reclen<len) len=reclen; // correct len if it has more data than the first tls record has\n\treturn TLSFindExtInHandshake(data + 5, len - 5, type, ext, len_ext, bPartialIsOK);\n}\nbool TLSAdvanceToHostInSNI(const uint8_t **ext, size_t *elen, size_t *slen)\n{\n\t// u16\tdata+0 - name list length\n\t// u8\tdata+2 - server name type. 0=host_name\n\t// u16\tdata+3 - server name length\n\tif (*elen < 5 || (*ext)[2] != 0) return false;\n\t*slen = pntoh16(*ext + 3);\n\t*ext += 5; *elen -= 5;\n\treturn *slen <= *elen;\n}\nstatic bool TLSExtractHostFromExt(const uint8_t *ext, size_t elen, char *host, size_t len_host)\n{\n\t// u16\tdata+0 - name list length\n\t// u8\tdata+2 - server name type. 0=host_name\n\t// u16\tdata+3 - server name length\n\tsize_t slen;\n\tif (!TLSAdvanceToHostInSNI(&ext,&elen,&slen))\n\t\treturn false;\n\tif (host && len_host)\n\t{\n\t\tif (slen >= len_host) slen = len_host - 1;\n\t\tfor (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]);\n\t\thost[slen] = 0;\n\t}\n\treturn true;\n}\nbool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)\n{\n\tconst uint8_t *ext;\n\tsize_t elen;\n\n\tif (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false;\n\treturn TLSExtractHostFromExt(ext, elen, host, len_host);\n}\nbool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)\n{\n\tconst uint8_t *ext;\n\tsize_t elen;\n\n\tif (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false;\n\treturn TLSExtractHostFromExt(ext, elen, host, len_host);\n}\n\nsize_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tsize_t elen;\n\tconst uint8_t *ext, *p;\n\tsize_t offset_host,len_host;\n\tssize_t offset;\n\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\tcase PM_SNI_EXT:\n\t\t\tif (TLSFindExt(data,sz,0,&ext,&elen,TLS_PARTIALS_ENABLE))\n\t\t\t{\n\t\t\t\tif (posmarker==PM_SNI_EXT)\n\t\t\t\t{\n\t\t\t\t\treturn CheckPos(sz,ext-data+pos);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host))\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\toffset_host = ext-data;\n\t\t\t\t\treturn HostPos(posmarker,pos,data,sz,offset_host,len_host);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\treturn AnyProtoPos(posmarker,pos,data,sz);\n\t}\n}\n\n\n\nstatic uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)\n{\n\tswitch (*tvb >> 6)\n\t{\n\tcase 0: /* 0b00 => 1 byte length (6 bits Usable) */\n\t\tif (value) *value = *tvb & 0x3F;\n\t\treturn 1;\n\tcase 1: /* 0b01 => 2 bytes length (14 bits Usable) */\n\t\tif (value) *value = pntoh16(tvb) & 0x3FFF;\n\t\treturn 2;\n\tcase 2: /* 0b10 => 4 bytes length (30 bits Usable) */\n\t\tif (value) *value = pntoh32(tvb) & 0x3FFFFFFF;\n\t\treturn 4;\n\tcase 3: /* 0b11 => 8 bytes length (62 bits Usable) */\n\t\tif (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF;\n\t\treturn 8;\n\t}\n\t// impossible case\n\tif (*value) *value = 0;\n\treturn 0;\n}\nstatic uint8_t tvb_get_size(uint8_t tvb)\n{\n\treturn 1 << (tvb >> 6);\n}\n\nbool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len)\n{\n\tsize_t offset = 1;\n\tuint64_t coff, clen;\n\tif (len < 3 || *data != 6) return false;\n\tif ((offset+tvb_get_size(data[offset])) >= len) return false;\n\toffset += tvb_get_varint(data + offset, &coff);\n\t// offset must be 0 if it's a full segment, not just a chunk\n\tif (coff || (offset+tvb_get_size(data[offset])) >= len) return false;\n\toffset += tvb_get_varint(data + offset, &clen);\n\tif ((offset + clen) > len || !IsTLSHandshakeClientHello(data+offset,clen)) return false;\n\tif (hello_offset) *hello_offset = offset;\n\tif (hello_len) *hello_len = (size_t)clen;\n\treturn true;\n}\n\n/* Returns the QUIC draft version or 0 if not applicable. */\nuint8_t QUICDraftVersion(uint32_t version)\n{\n\t/* IETF Draft versions */\n\tif ((version >> 8) == 0xff0000)\n\t\treturn (uint8_t)version;\n\t/* Facebook mvfst, based on draft -22. */\n\tif (version == 0xfaceb001)\n\t\treturn 22;\n\t/* Facebook mvfst, based on draft -27. */\n\tif (version == 0xfaceb002 || version == 0xfaceb00e)\n\t\treturn 27;\n\t/* GQUIC Q050, T050 and T051: they are not really based on any drafts,\n\t * but we must return a sensible value */\n\tif (version == 0x51303530 || version == 0x54303530 || version == 0x54303531)\n\t\treturn 27;\n\t/* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15\n\t   \"Versions that follow the pattern 0x?a?a?a?a are reserved for use in\n\t   forcing version negotiation to be exercised\"\n\t   It is tricky to return a correct draft version: such number is primarily\n\t   used to select a proper salt (which depends on the version itself), but\n\t   we don't have a real version here! Let's hope that we need to handle\n\t   only latest drafts... */\n\tif ((version & 0x0F0F0F0F) == 0x0a0a0a0a)\n\t\treturn 29;\n\t/* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the\n\t   final draft version */\n\tif (version == 0x00000001)\n\t\treturn 34;\n\t/* QUIC Version 2 */\n\t/* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */\n\tif ((version == 0x709A50C4) || (version == 0x6b3343cf))\n\t\treturn 100;\n\n\treturn 0;\n}\n\nstatic bool is_quic_draft_max(uint32_t draft_version, uint8_t max_version)\n{\n\treturn draft_version && draft_version <= max_version;\n}\nstatic bool is_quic_v2(uint32_t version)\n{\n    return (version == 0x709A50C4) || (version == 0x6b3343cf);\n}\n\nstatic bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, const char *label, uint8_t *out, size_t out_len)\n{\n\tuint8_t hkdflabel[64];\n\n\tsize_t label_size = strlen(label);\n\tif (label_size > 255) return false;\n\tsize_t hkdflabel_size = 2 + 1 + label_size + 1;\n\tif (hkdflabel_size > sizeof(hkdflabel)) return false;\n\n\tphton16(hkdflabel, out_len);\n\thkdflabel[2] = (uint8_t)label_size;\n\tmemcpy(hkdflabel + 3, label, label_size);\n\thkdflabel[3 + label_size] = 0;\n\treturn !hkdfExpand(SHA256, secret, secret_len, hkdflabel, hkdflabel_size, out, out_len);\n}\n\nstatic bool quic_derive_initial_secret(const quic_cid_t *cid, uint8_t *client_initial_secret, uint32_t version)\n{\n\t/*\n\t * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2\n\t *\n\t * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899\n\t * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id)\n\t *\n\t * client_initial_secret = HKDF-Expand-Label(initial_secret,\n\t *                                           \"client in\", \"\", Hash.length)\n\t * server_initial_secret = HKDF-Expand-Label(initial_secret,\n\t *                                           \"server in\", \"\", Hash.length)\n\t *\n\t * Hash for handshake packets is SHA-256 (output size 32).\n\t */\n\tstatic const uint8_t handshake_salt_draft_22[20] = {\n\t\t0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a,\n\t\t0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a\n\t};\n\tstatic const uint8_t handshake_salt_draft_23[20] = {\n\t\t0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7,\n\t\t0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,\n\t};\n\tstatic const uint8_t handshake_salt_draft_29[20] = {\n\t\t0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97,\n\t\t0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99\n\t};\n\tstatic const uint8_t handshake_salt_v1[20] = {\n\t\t0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,\n\t\t0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a\n\t};\n\tstatic const uint8_t hanshake_salt_draft_q50[20] = {\n\t\t0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94,\n\t\t0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45\n\t};\n\tstatic const uint8_t hanshake_salt_draft_t50[20] = {\n\t\t0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80,\n\t\t0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10\n\t};\n\tstatic const uint8_t hanshake_salt_draft_t51[20] = {\n\t\t0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50,\n\t\t0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d\n\t};\n\tstatic const uint8_t handshake_salt_v2[20] = {\n\t\t0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93,\n\t\t0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9\n\t};\n\n\tint err;\n\tconst uint8_t *salt;\n\tuint8_t secret[USHAMaxHashSize];\n\tuint8_t draft_version = QUICDraftVersion(version);\n\n\tif (version == 0x51303530) {\n\t\tsalt = hanshake_salt_draft_q50;\n\t}\n\telse if (version == 0x54303530) {\n\t\tsalt = hanshake_salt_draft_t50;\n\t}\n\telse if (version == 0x54303531) {\n\t\tsalt = hanshake_salt_draft_t51;\n\t}\n\telse if (is_quic_draft_max(draft_version, 22)) {\n\t\tsalt = handshake_salt_draft_22;\n\t}\n\telse if (is_quic_draft_max(draft_version, 28)) {\n\t\tsalt = handshake_salt_draft_23;\n\t}\n\telse if (is_quic_draft_max(draft_version, 32)) {\n\t\tsalt = handshake_salt_draft_29;\n\t}\n\telse if (is_quic_draft_max(draft_version, 34)) {\n\t\tsalt = handshake_salt_v1;\n\t}\n\telse {\n\t\tsalt = handshake_salt_v2;\n\t}\n\n\terr = hkdfExtract(SHA256, salt, 20, cid->cid, cid->len, secret);\n\tif (err) return false;\n\n\tif (client_initial_secret && !quic_hkdf_expand_label(secret, SHA256HashSize, \"tls13 client in\", client_initial_secret, SHA256HashSize))\n\t\treturn false;\n\n\treturn true;\n}\nbool QUICIsLongHeader(const uint8_t *data, size_t len)\n{\n\treturn len>=9 && !!(*data & 0x80);\n}\nuint32_t QUICExtractVersion(const uint8_t *data, size_t len)\n{\n\t// long header, fixed bit, type=initial\n\treturn QUICIsLongHeader(data, len) ? ntohl(*(uint32_t*)(data + 1)) : 0;\n}\nbool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid)\n{\n\tif (!QUICIsLongHeader(data,len) || !data[5] || data[5] > QUIC_MAX_CID_LENGTH || (6+data[5])>len) return false;\n\tcid->len = data[5];\n\tmemcpy(&cid->cid, data + 6, data[5]);\n\treturn true;\n}\nbool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len)\n{\n\tuint32_t ver = QUICExtractVersion(data, data_len);\n\tif (!ver) return false;\n\n\tquic_cid_t dcid;\n\tif (!QUICExtractDCID(data, data_len, &dcid)) return false;\n\n\tuint8_t client_initial_secret[SHA256HashSize];\n\tif (!quic_derive_initial_secret(&dcid, client_initial_secret, ver)) return false;\n\n\tuint8_t aeskey[16], aesiv[12], aeshp[16];\n\tbool v1_label = !is_quic_v2(ver);\n\tif (!quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? \"tls13 quic key\" : \"tls13 quicv2 key\", aeskey, sizeof(aeskey)) ||\n\t\t!quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? \"tls13 quic iv\" : \"tls13 quicv2 iv\", aesiv, sizeof(aesiv)) ||\n\t\t!quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? \"tls13 quic hp\" : \"tls13 quicv2 hp\", aeshp, sizeof(aeshp)))\n\t{\n\t\treturn false;\n\t}\n\n\tuint64_t payload_len,token_len,pn_offset;\n\tpn_offset = 1 + 4 + 1 + data[5];\n\tif (pn_offset >= data_len) return false;\n\t// SCID length\n\tpn_offset += 1 + data[pn_offset];\n\tif (pn_offset >= data_len || (pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false;\n\t// token length\n\tpn_offset += tvb_get_varint(data + pn_offset, &token_len);\n\tpn_offset += token_len;\n\tif (pn_offset >= data_len || (pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false;\n\tpn_offset += tvb_get_varint(data + pn_offset, &payload_len);\n\tif (payload_len<20 || (pn_offset + payload_len)>data_len) return false;\n\n\tuint8_t sample_enc[16];\n\taes_context ctx;\n\tif (aes_setkey(&ctx, 1, aeshp, sizeof(aeshp)) || aes_cipher(&ctx, data + pn_offset + 4, sample_enc)) return false;\n\n\tuint8_t mask[5];\n\tmemcpy(mask, sample_enc, sizeof(mask));\n\n\tuint8_t packet0 = data[0] ^ (mask[0] & 0x0f);\n\tuint8_t pkn_len = (packet0 & 0x03) + 1;\n\n\tuint8_t pkn_bytes[4];\n\tmemcpy(pkn_bytes, data + pn_offset, pkn_len);\n\tuint32_t pkn = 0;\n\tfor (uint8_t i = 0; i < pkn_len; i++) pkn |= (uint32_t)(pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i));\n\n \tphton64(aesiv + sizeof(aesiv) - 8, pntoh64(aesiv + sizeof(aesiv) - 8) ^ pkn);\n\n\tuint64_t cryptlen = payload_len - pkn_len - 16;\n\tif (cryptlen > *clean_len) return false;\n\t*clean_len = (size_t)cryptlen;\n\tconst uint8_t *decrypt_begin = data + pn_offset + pkn_len;\n\n\tuint8_t atag[16],header[2048];\n\tuint64_t header_len = pn_offset + pkn_len;\n\tif (header_len > sizeof(header)) return false; // not likely header will be so large\n\tmemcpy(header, data, header_len);\n\theader[0] = packet0;\n\tfor(uint8_t i = 0; i < pkn_len; i++) header[header_len - 1 - i] = (uint8_t)(pkn >> (8 * i));\n\n\tif (aes_gcm_crypt(AES_DECRYPT, clean, decrypt_begin, cryptlen, aeskey, sizeof(aeskey), aesiv, sizeof(aesiv), header, header_len, atag, sizeof(atag)))\n\t\treturn false;\n\n\t// check if message was decrypted correctly : good keys , no data corruption\n\treturn !memcmp(data + pn_offset + pkn_len + cryptlen, atag, 16);\n}\n\nstruct range64\n{\n\tuint64_t offset,len;\n};\n#define MAX_DEFRAG_PIECES\t128\nstatic int cmp_range64(const void * a, const void * b)\n{\n\treturn (((struct range64*)a)->offset < ((struct range64*)b)->offset) ? -1 : (((struct range64*)a)->offset > ((struct range64*)b)->offset) ? 1 : 0;\n}\nbool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull)\n{\n\t// Crypto frame can be split into multiple chunks\n\t// chromium randomly splits it and pads with zero/one bytes to force support the standard\n\t// mozilla does not split\n\n\tif (*defrag_len<10) return false;\n\tuint8_t *defrag_data = defrag+10;\n\tsize_t defrag_data_len = *defrag_len-10;\n\tuint8_t ft;\n\tuint64_t offset,sz,szmax=0,zeropos=0,pos=0;\n\tbool found=false;\n\tstruct range64 ranges[MAX_DEFRAG_PIECES];\n\tint i,j,range=0;\n\n\twhile(pos<clean_len)\n\t{\n\t\t// frame type\n\t\tft = clean[pos];\n\t\tpos++;\n\t\tif (ft>1) // 00 - padding, 01 - ping\n\t\t{\n\t\t\tif (ft!=6) return false; // dont want to know all possible frame type formats\n\n\t\t\tif (pos>=clean_len) return false;\n\t\t\tif (range>=MAX_DEFRAG_PIECES) return false;\n\n\t\t\tif ((pos+tvb_get_size(clean[pos])>=clean_len)) return false;\n\t\t\tpos += tvb_get_varint(clean+pos, &offset);\n\n\t\t\tif ((pos+tvb_get_size(clean[pos])>clean_len)) return false;\n\t\t\tpos += tvb_get_varint(clean+pos, &sz);\n\t\t\tif ((pos+sz)>clean_len) return false;\n\n\t\t\tif ((offset+sz)>defrag_data_len) return false; // defrag buf overflow\n\n\t\t\t// remove exact duplicates early to save cpu\n\t\t\tfor(i=0;i<range;i++)\n\t\t\t\tif (ranges[i].offset==offset && ranges[i].len==sz)\n\t\t\t\t\tgoto skip_range;\n\n\t\t\tif (zeropos < offset)\n\t\t\t\t// make sure no uninitialized gaps exist in case of not full fragment coverage\n\t\t\t\tmemset(defrag_data+zeropos,0,offset-zeropos);\n\t\t\tif ((offset+sz) > zeropos)\n\t\t\t\tzeropos=offset+sz;\n\n\t\t\tfound=true;\n\t\t\tif ((offset+sz) > szmax) szmax = offset+sz;\n\t\t\tmemcpy(defrag_data+offset,clean+pos,sz);\n\t\t\tranges[range].offset = offset;\n\t\t\tranges[range].len = sz;\n\t\t\trange++;\nskip_range:\n\t\t\tpos+=sz;\n\t\t}\n\t}\n\tif (found)\n\t{\n\t\tqsort(ranges, range, sizeof(*ranges), cmp_range64);\n\n//\t\tfor(i=0 ; i<range ; i++)\n//\t\t\tprintf(\"range1 %llu-%llu\\n\",ranges[i].offset,ranges[i].offset+ranges[i].len);\n\n\t\tif (range>0)\n\t\t{\n\t\t\tfor (j=0,i=1; i < range; i++)\n\t\t\t{\n\t\t\t\tuint64_t current_end = ranges[j].offset + ranges[j].len;\n\t\t\t\tuint64_t next_start = ranges[i].offset;\n\t\t\t\tuint64_t next_end = ranges[i].offset + ranges[i].len;\n\n\t\t\t\tif (next_start <= current_end)\n\t\t\t\t\tranges[j].len = MAX(next_end,current_end) - ranges[j].offset;\n\t\t\t\telse\n\t\t\t\t\tranges[++j] = ranges[i];\n\t\t\t}\n\t\t\trange = j+1;\n\t\t}\n\n//\t\tfor(i=0 ; i<range ; i++)\n//\t\t\tprintf(\"range2 %llu-%llu\\n\",ranges[i].offset,ranges[i].offset+ranges[i].len);\n\n\t\tdefrag[0] = 6;\n\t\tdefrag[1] = 0; // offset\n\t\t// 2..9 - length 64 bit\n\t\t// +10 - data start\n\t\tphton64(defrag+2,szmax);\n\t\tdefrag[2] |= 0xC0; // 64 bit value\n\t\t*defrag_len = (size_t)(szmax+10);\n\n\t\t*bFull = range==1 && !ranges[0].offset;\n\t\t//printf(\"bFull=%u\\n\",*bFull);\n\t}\n\treturn found;\n}\n\n/*\nbool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello)\n{\n\tif (bIsCryptoHello) *bIsCryptoHello=false;\n\tif (bDecryptOK) *bDecryptOK=false;\n\n\tuint8_t clean[1500];\n\tsize_t clean_len = sizeof(clean);\n\tif (!QUICDecryptInitial(data,data_len,clean,&clean_len)) return false;\n\n\tif (bDecryptOK) *bDecryptOK=true;\n\n\tuint8_t defrag[1500];\n\tsize_t defrag_len = sizeof(defrag);\n\tif (!QUICDefragCrypto(clean,clean_len,defrag,&defrag_len)) return false;\n\n\tsize_t hello_offset, hello_len;\n\tif (!IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len)) return false;\n\tif (bIsCryptoHello) *bIsCryptoHello=true;\n\n\treturn TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, len_host, NULL, true);\n}\n*/\n\nbool IsQUICInitial(const uint8_t *data, size_t len)\n{\n\t// too small packets are not likely to be initials\n\t// long header, fixed bit\n\tif (len < 128) return false;\n\n\tuint32_t ver = QUICExtractVersion(data,len);\n\tif (QUICDraftVersion(ver) < 11) return false;\n\n\tif ((data[0] & 0xF0) != (is_quic_v2(ver) ? 0xD0 : 0xC0)) return false;\n\n\tuint64_t offset=5, sz, sz2;\n\n\t// DCID\n\tif (data[offset] > QUIC_MAX_CID_LENGTH) return false;\n\toffset += 1 + data[offset];\n\n\tif (offset>=len) return false;\n\n\t// SCID\n\tif (data[offset] > QUIC_MAX_CID_LENGTH) return false;\n\toffset += 1 + data[offset];\n\n\t// token length\n\tif (offset>=len || (offset + tvb_get_size(data[offset])) > len) return false;\n\toffset += tvb_get_varint(data + offset, &sz);\n\toffset += sz;\n\tif (offset >= len) return false;\n\n\t// payload length\n\tsz2 = tvb_get_size(data[offset]);\n\tif ((offset + sz2) > len) return false;\n\ttvb_get_varint(data + offset, &sz);\n\toffset += sz2 + sz;\n\tif (offset > len) return false;\n\n\treturn true;\n}\n\n\n\nbool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len)\n{\n    return len==148 && data[0]==1;\n}\nbool IsDhtD1(const uint8_t *data, size_t len)\n{\n\treturn len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e';\n}\nbool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len)\n{\n\treturn len==74 &&\n\t\tdata[0]==0 && data[1]==1 &&\n\t\tdata[2]==0 && data[3]==70 &&\n\t\t!memcmp(data+8,\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",64);\n\t\t// address is not set in request\n}\nbool IsStunMessage(const uint8_t *data, size_t len)\n{\n\treturn len>=20 && // header size\n\t\t(data[0]&0xC0)==0 && // 2 most significant bits must be zeroes\n\t\t(data[3]&3)==0 && // length must be a multiple of 4\n\t\tpntoh32(data+4)==0x2112A442 && // magic cookie\n\t\tpntoh16(data+2)<=(len-20);\n}\n"
  },
  {
    "path": "nfq/protocol.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include \"crypto/sha.h\"\n#include \"crypto/aes-gcm.h\"\n#include \"helpers.h\"\n\ntypedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT, DISCORD, STUN} t_l7proto;\n#define L7_PROTO_HTTP\t\t0x00000001\n#define L7_PROTO_TLS\t\t0x00000002\n#define L7_PROTO_QUIC\t\t0x00000004\n#define L7_PROTO_WIREGUARD\t0x00000008\n#define L7_PROTO_DHT\t\t0x00000010\n#define L7_PROTO_DISCORD\t0x00000020\n#define L7_PROTO_STUN\t\t0x00000040\n#define L7_PROTO_UNKNOWN\t0x80000000\nconst char *l7proto_str(t_l7proto l7);\nbool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7);\n\n// pos markers\n#define PM_ABS\t\t0\n#define PM_HOST\t\t1\n#define PM_HOST_END\t2\n#define PM_HOST_SLD\t3\n#define PM_HOST_MIDSLD\t4\n#define PM_HOST_ENDSLD\t5\n#define PM_HTTP_METHOD\t6\n#define PM_SNI_EXT\t7\nstruct proto_pos\n{\n\tint16_t pos;\n\tuint8_t marker;\n};\n#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0)\nbool IsHostMarker(uint8_t posmarker);\nconst char *posmarker_name(uint8_t posmarker);\nsize_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp);\nvoid ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count);\n\nextern const char *http_methods[9];\nconst char *HttpMethod(const uint8_t *data, size_t len);\nbool IsHttp(const uint8_t *data, size_t len);\nbool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs);\nbool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs);\n// header must be passed like this : \"\\nHost:\"\nbool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);\nbool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);\nbool IsHttpReply(const uint8_t *data, size_t len);\nconst char *HttpFind2ndLevelDomain(const char *host);\n// must be pre-checked by IsHttpReply\nint HttpReplyCode(const uint8_t *data, size_t len);\n// must be pre-checked by IsHttpReply\nbool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);\n\nconst char *TLSVersionStr(uint16_t tlsver);\nuint16_t TLSRecordDataLen(const uint8_t *data);\nsize_t TLSRecordLen(const uint8_t *data);\nbool IsTLSRecordFull(const uint8_t *data, size_t len);\nbool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK);\nsize_t TLSHandshakeLen(const uint8_t *data);\nbool IsTLSHandshakeClientHello(const uint8_t *data, size_t len);\nbool IsTLSHandshakeFull(const uint8_t *data, size_t len);\nbool TLSAdvanceToHostInSNI(const uint8_t **ext, size_t *elen, size_t *slen);\nbool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off);\nbool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off);\nbool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);\nbool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);\nbool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);\nbool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);\n\nbool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len);\nbool IsDhtD1(const uint8_t *data, size_t len);\nbool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len);\nbool IsStunMessage(const uint8_t *data, size_t len);\n\n#define QUIC_MAX_CID_LENGTH  20\ntypedef struct quic_cid {\n\tuint8_t      len;\n\tuint8_t      cid[QUIC_MAX_CID_LENGTH];\n} quic_cid_t;\n\nbool IsQUICInitial(const uint8_t *data, size_t len);\nbool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len);\nbool QUICIsLongHeader(const uint8_t *data, size_t len);\nuint32_t QUICExtractVersion(const uint8_t *data, size_t len);\nuint8_t QUICDraftVersion(uint32_t version);\nbool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid);\n\nbool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len);\n// returns true if crypto frames were found . bFull = true if crypto frame fragments have full coverage\nbool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull);\n//bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello);\n"
  },
  {
    "path": "nfq/sec.c",
    "content": "#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"sec.h\"\n#include <unistd.h>\n#include <fcntl.h>\n#include <grp.h>\n\n#include \"params.h\"\n\n#ifdef __linux__\n\n#include <sys/prctl.h>\n#include <sys/syscall.h>\n#include <linux/seccomp.h>\n#include <linux/filter.h>\n// __X32_SYSCALL_BIT defined in linux/unistd.h\n#include <linux/unistd.h>\n#include <syscall.h>\n#include <errno.h>\n\n/************ SECCOMP ************/\n\n// block most of the undesired syscalls to harden against code execution\nstatic long blocked_syscalls[] = {\n#ifdef SYS_execv\nSYS_execv,\n#endif\nSYS_execve,\n#ifdef SYS_execveat\nSYS_execveat,\n#endif\n#ifdef SYS_exec_with_loader\nSYS_exec_with_loader,\n#endif\n#ifdef SYS_clone\nSYS_clone,\n#endif\n#ifdef SYS_clone2\nSYS_clone2,\n#endif\n#ifdef SYS_clone3\nSYS_clone3,\n#endif\n#ifdef SYS_osf_execve\nSYS_osf_execve,\n#endif\n#ifdef SYS_fork\nSYS_fork,\n#endif\n#ifdef SYS_vfork\nSYS_vfork,\n#endif\n#ifdef SYS_uselib\nSYS_uselib,\n#endif\n#ifdef SYS_unlink\nSYS_unlink,\n#endif\nSYS_unlinkat,\n#ifdef SYS_chmod\nSYS_chmod,\n#endif\nSYS_fchmod,SYS_fchmodat,\n#ifdef SYS_chown\nSYS_chown,\n#endif\n#ifdef SYS_chown32\nSYS_chown32,\n#endif\nSYS_fchown,\n#ifdef SYS_fchown32\nSYS_fchown32,\n#endif\n#ifdef SYS_lchown\nSYS_lchown,\n#endif\n#ifdef SYS_lchown32\nSYS_lchown32,\n#endif\nSYS_fchownat,\n#ifdef SYS_symlink\nSYS_symlink,\n#endif\nSYS_symlinkat,\n#ifdef SYS_link\nSYS_link,\n#endif\nSYS_linkat,\nSYS_truncate,\n#ifdef SYS_truncate64\nSYS_truncate64,\n#endif\nSYS_ftruncate,\n#ifdef SYS_ftruncate64\nSYS_ftruncate64,\n#endif\n#ifdef SYS_mknod\nSYS_mknod,\n#endif\nSYS_mknodat,\n#ifdef SYS_mkdir\nSYS_mkdir,\n#endif\nSYS_mkdirat,\n#ifdef SYS_rmdir\nSYS_rmdir,\n#endif\n#ifdef SYS_rename\nSYS_rename,\n#endif\n#ifdef SYS_renameat2\nSYS_renameat2,\n#endif\n#ifdef SYS_renameat\nSYS_renameat,\n#endif\n#ifdef SYS_readdir\nSYS_readdir,\n#endif\n#ifdef SYS_getdents\nSYS_getdents,\n#endif\n#ifdef SYS_getdents64\nSYS_getdents64,\n#endif\n#ifdef SYS_process_vm_readv\nSYS_process_vm_readv,\n#endif\n#ifdef SYS_process_vm_writev\nSYS_process_vm_writev,\n#endif\n#ifdef SYS_process_madvise\nSYS_process_madvise,\n#endif\n#ifdef SYS_tkill\nSYS_tkill,\n#endif\n#ifdef SYS_tgkill\nSYS_tgkill,\n#endif\nSYS_kill, SYS_ptrace\n};\n#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls))\n\nstatic void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k)\n{\n\tfilter->code = code;\n\tfilter->jt = jt;\n\tfilter->jf = jf;\n\tfilter->k = k;\n}\n// deny all blocked syscalls\nstatic bool set_seccomp(void)\n{\n#ifdef __X32_SYSCALL_BIT\n #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT)\n#else\n #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT)\n#endif\n\tstruct sock_filter sockf[SECCOMP_PROG_SIZE];\n\tstruct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf };\n\tint i,idx=0;\n\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr);\n#ifdef __X32_SYSCALL_BIT\n\t// x86 only\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail\n#else\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);\n#endif\n\n/*\n\t// ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr\n*/\n\tfor(i=0 ; i<BLOCKED_SYSCALL_COUNT ; i++)\n\t{\n\t\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, BLOCKED_SYSCALL_COUNT-i, 0, blocked_syscalls[i]);\n\t}\n\tset_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); // success case\n\tset_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); // fail case\n\treturn prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) >= 0;\n}\n\nbool sec_harden(void)\n{\n\tbool bRes = true;\n\tif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))\n\t{\n\t\tDLOG_PERROR(\"PR_SET_NO_NEW_PRIVS(prctl)\");\n\t\tbRes = false;\n\t}\n#if ARCH_NR!=0\n\tif (!set_seccomp())\n\t{\n\t\tDLOG_PERROR(\"seccomp\");\n\t\tif (errno==EINVAL) DLOG_ERR(\"seccomp: this can be safely ignored if kernel does not support seccomp\\n\");\n\t\tbRes = false;\n\t}\n#endif\n\treturn bRes;\n}\n\n\n\n\nbool checkpcap(uint64_t caps)\n{\n\tif (!caps) return true; // no special caps reqd\n\n\tstruct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};\n\tstruct __user_cap_data_struct cd[2];\n\tuint32_t c0 = (uint32_t)caps;\n\tuint32_t c1 = (uint32_t)(caps>>32);\n\n\treturn !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1;\n}\nbool setpcap(uint64_t caps)\n{\n\tstruct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};\n\tstruct __user_cap_data_struct cd[2];\n\t\n\tcd[0].effective = cd[0].permitted = (uint32_t)caps;\n\tcd[0].inheritable = 0;\n\tcd[1].effective = cd[1].permitted = (uint32_t)(caps>>32);\n\tcd[1].inheritable = 0;\n\n\treturn !capset(&ch,cd);\n}\nint getmaxcap(void)\n{\n\tint maxcap = CAP_LAST_CAP;\n\tFILE *F = fopen(\"/proc/sys/kernel/cap_last_cap\", \"r\");\n\tif (F)\n\t{\n\t\tint n = fscanf(F, \"%d\", &maxcap);\n\t\tfclose(F);\n\t}\n\treturn maxcap;\n\n}\nbool dropcaps(void)\n{\n\tuint64_t caps = (1<<CAP_NET_ADMIN)|(1<<CAP_NET_RAW);\n\tint maxcap = getmaxcap();\n\n\tif (setpcap(caps|(1<<CAP_SETPCAP)))\n\t{\n\t\tfor (int cap = 0; cap <= maxcap; cap++)\n\t\t{\n\t\t\tif (prctl(PR_CAPBSET_DROP, cap)<0)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"could not drop bound cap %d\\n\", cap);\n\t\t\t\tDLOG_PERROR(\"cap_drop_bound\");\n\t\t\t}\n\t\t}\n\t}\n\t// now without CAP_SETPCAP\n\tif (!setpcap(caps))\n\t{\n\t\tDLOG_PERROR(\"setpcap\");\n\t\treturn checkpcap(caps);\n\t}\n\treturn true;\n}\n\n#endif // __linux__\n\n#ifndef __CYGWIN__\n\n#ifndef __linux__\nbool sec_harden(void)\n{\n\t// noop\n\treturn true;\n}\n#endif\n\nbool can_drop_root(void)\n{\n#ifdef __linux__\n\t// has some caps\n\treturn checkpcap((1<<CAP_SETUID)|(1<<CAP_SETGID));\n#else\n\t// effective root\n\treturn !geteuid();\n#endif\n}\n\nbool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count)\n{\n\tif (gid_count<1)\n\t{\n\t\tDLOG_ERR(\"droproot: no groups specified\");\n\t\treturn false;\n\t}\n#ifdef __linux__\n\tif (prctl(PR_SET_KEEPCAPS, 1L))\n\t{\n\t\tDLOG_PERROR(\"prctl(PR_SET_KEEPCAPS)\");\n\t\treturn false;\n\t}\n#endif\n\tif (user)\n\t{\n\t\t// macos has strange supp gid handling. they cache only 16 groups and fail setgroups if more than 16 gids specified.\n\t\t// better to leave it to the os\n\t\tif (initgroups(user,gid[0]))\n\t\t{\n\t\t\tDLOG_PERROR(\"initgroups\");\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (setgroups(gid_count,gid))\n\t\t{\n\t\t\tDLOG_PERROR(\"setgroups\");\n\t\t\treturn false;\n\t\t}\n\t}\n\tif (setgid(gid[0]))\n\t{\n\t\tDLOG_PERROR(\"setgid\");\n\t\treturn false;\n\t}\n\tif (setuid(uid))\n\t{\n\t\tDLOG_PERROR(\"setuid\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nvoid print_id(void)\n{\n int i,N;\n gid_t g[128];\n\n DLOG_CONDUP(\"Running as UID=%u GID=\",getuid());\n N=getgroups(sizeof(g)/sizeof(*g),g);\n if (N>0)\n {\n\tfor(i=0;i<N;i++)\n\t\tDLOG_CONDUP(i==(N-1) ? \"%u\" : \"%u,\", g[i]);\n\tDLOG_CONDUP(\"\\n\");\n }\n else\n\tDLOG_CONDUP(\"%u\\n\",getgid());\n}\n\n#endif\n\n\n\nvoid daemonize(void)\n{\n\tint pid;\n#ifdef __CYGWIN__\n\tchar *cwd = get_current_dir_name();\n#endif\n\n\tpid = fork();\n\tif (pid == -1)\n\t{\n\t\tDLOG_PERROR(\"fork\");\n\t\texit(2);\n\t}\n\telse if (pid != 0)\n\t\texit(0);\n\n#ifdef __CYGWIN__\n\tchdir(get_current_dir_name());\n#endif\n\n\tif (setsid() == -1)\n\t\texit(2);\n\tif (chdir(\"/\") == -1)\n\t\texit(2);\n\tclose(STDIN_FILENO);\n\tclose(STDOUT_FILENO);\n\tclose(STDERR_FILENO);\n\t/* redirect fd's 0,1,2 to /dev/null */\n\topen(\"/dev/null\", O_RDWR);\n\tint fd;\n\t/* stdin */\n\tfd = dup(0);\n\t/* stdout */\n\tfd = dup(0);\n\t/* stderror */\n}\n\nbool writepid(const char *filename)\n{\n\tFILE *F;\n\tif (!(F = fopen(filename, \"w\")))\n\t\treturn false;\n\tfprintf(F, \"%d\", getpid());\n\tfclose(F);\n\treturn true;\n}\n"
  },
  {
    "path": "nfq/sec.h",
    "content": "#pragma once\n\n#include <sys/types.h>\n#include <stdbool.h>\n\n#ifdef __linux__\n\n#include <stddef.h>\n#include <sys/capability.h>\n#include <linux/audit.h>\n\nbool checkpcap(uint64_t caps);\nbool setpcap(uint64_t caps);\nint getmaxcap(void);\nbool dropcaps(void);\n\n#define syscall_nr (offsetof(struct seccomp_data, nr))\n#define arch_nr (offsetof(struct seccomp_data, arch))\n#define syscall_arg(x) (offsetof(struct seccomp_data, args[x]))\n\n#ifndef __AUDIT_ARCH_64BIT\n#define __AUDIT_ARCH_64BIT 0x80000000\n#endif\n#ifndef __AUDIT_ARCH_LE\n#define __AUDIT_ARCH_LE    0x40000000\n#endif\n#ifndef EM_RISCV\n#define EM_RISCV 243\n#endif\n#ifndef AUDIT_ARCH_RISCV64\n#define AUDIT_ARCH_RISCV64 (EM_RISCV | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE)\n#endif\n#ifndef EM_LOONGARCH\n#define EM_LOONGARCH 258\n#endif\n#ifndef AUDIT_ARCH_LOONGARCH64\n#define AUDIT_ARCH_LOONGARCH64 (EM_LOONGARCH | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE)\n#endif\n\n#if defined(__aarch64__)\n\n# define ARCH_NR\tAUDIT_ARCH_AARCH64\n\n#elif defined(__amd64__)\n\n# define ARCH_NR\tAUDIT_ARCH_X86_64\n\n#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__))\n\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_ARM\n# else\n#  define ARCH_NR\tAUDIT_ARCH_ARMEB\n# endif\n\n#elif defined(__i386__)\n\n# define ARCH_NR\tAUDIT_ARCH_I386\n\n#elif defined(__mips__)\n\n#if _MIPS_SIM == _MIPS_SIM_ABI32\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_MIPSEL\n# else\n#  define ARCH_NR\tAUDIT_ARCH_MIPS\n# endif\n#elif _MIPS_SIM == _MIPS_SIM_ABI64\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_MIPSEL64\n# else\n#  define ARCH_NR\tAUDIT_ARCH_MIPS64\n# endif\n#else\n# error \"Unsupported mips abi\"\n#endif\n\n#elif defined(__PPC64__)\n\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_PPC64LE\n# else\n#  define ARCH_NR\tAUDIT_ARCH_PPC64\n# endif\n\n#elif defined(__PPC__)\n\n# define ARCH_NR\tAUDIT_ARCH_PPC\n\n#elif __riscv && __riscv_xlen == 64\n\n# define ARCH_NR\tAUDIT_ARCH_RISCV64\n\n#elif defined(__loongarch__) && __loongarch_grlen == 64\n\n# define ARCH_NR AUDIT_ARCH_LOONGARCH64\n\n#elif defined(__e2k__)\n\n# define ARCH_NR\tAUDIT_ARCH_E2K\n\n#else\n\n# error \"Platform does not support seccomp filter yet\"\n\n#endif\n\n#endif\n\n\n#ifndef __CYGWIN__\nbool sec_harden(void);\nbool can_drop_root(void);\nbool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count);\nvoid print_id(void);\n#endif\n\nvoid daemonize(void);\nbool writepid(const char *filename);\n"
  },
  {
    "path": "nfq/uthash.h",
    "content": "/*\nCopyright (c) 2003-2021, Troy D. Hanson     http://troydhanson.github.io/uthash/\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER\nOR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#ifndef UTHASH_H\n#define UTHASH_H\n\n#define UTHASH_VERSION 2.3.0\n\n#include <string.h>   /* memcmp, memset, strlen */\n#include <stddef.h>   /* ptrdiff_t */\n#include <stdlib.h>   /* exit */\n\n#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT\n/* This codepath is provided for backward compatibility, but I plan to remove it. */\n#warning \"HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead\"\ntypedef unsigned int uint32_t;\ntypedef unsigned char uint8_t;\n#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT\n#else\n#include <stdint.h>   /* uint8_t, uint32_t */\n#endif\n\n/* These macros use decltype or the earlier __typeof GNU extension.\n   As decltype is only available in newer compilers (VS2010 or gcc 4.3+\n   when compiling c++ source) this code uses whatever method is needed\n   or, for VS2008 where neither is available, uses casting workarounds. */\n#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)\n#if defined(_MSC_VER)   /* MS compiler */\n#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */\n#define DECLTYPE(x) (decltype(x))\n#else                   /* VS2008 or older (or VS2010 in C mode) */\n#define NO_DECLTYPE\n#endif\n#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)\n#define NO_DECLTYPE\n#else                   /* GNU, Sun and other compilers */\n#define DECLTYPE(x) (__typeof(x))\n#endif\n#endif\n\n#ifdef NO_DECLTYPE\n#define DECLTYPE(x)\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  char **_da_dst = (char**)(&(dst));                                             \\\n  *_da_dst = (char*)(src);                                                       \\\n} while (0)\n#else\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  (dst) = DECLTYPE(dst)(src);                                                    \\\n} while (0)\n#endif\n\n#ifndef uthash_malloc\n#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */\n#endif\n#ifndef uthash_free\n#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */\n#endif\n#ifndef uthash_bzero\n#define uthash_bzero(a,n) memset(a,'\\0',n)\n#endif\n#ifndef uthash_strlen\n#define uthash_strlen(s) strlen(s)\n#endif\n\n#ifndef HASH_FUNCTION\n#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)\n#endif\n\n#ifndef HASH_KEYCMP\n#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)\n#endif\n\n#ifndef uthash_noexpand_fyi\n#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */\n#endif\n#ifndef uthash_expand_fyi\n#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */\n#endif\n\n#ifndef HASH_NONFATAL_OOM\n#define HASH_NONFATAL_OOM 0\n#endif\n\n#if HASH_NONFATAL_OOM\n/* malloc failures can be recovered from */\n\n#ifndef uthash_nonfatal_oom\n#define uthash_nonfatal_oom(obj) do {} while (0)    /* non-fatal OOM error */\n#endif\n\n#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)\n#define IF_HASH_NONFATAL_OOM(x) x\n\n#else\n/* malloc failures result in lost memory, hash tables are unusable */\n\n#ifndef uthash_fatal\n#define uthash_fatal(msg) exit(-1)        /* fatal OOM error */\n#endif\n\n#define HASH_RECORD_OOM(oomed) uthash_fatal(\"out of memory\")\n#define IF_HASH_NONFATAL_OOM(x)\n\n#endif\n\n/* initial number of buckets */\n#define HASH_INITIAL_NUM_BUCKETS 32U     /* initial number of buckets        */\n#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */\n#define HASH_BKT_CAPACITY_THRESH 10U     /* expand when bucket count reaches */\n\n/* calculate the element whose hash handle address is hhp */\n#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))\n/* calculate the hash handle from element address elp */\n#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))\n\n#define HASH_ROLLBACK_BKT(hh, head, itemptrhh)                                   \\\ndo {                                                                             \\\n  struct UT_hash_handle *_hd_hh_item = (itemptrhh);                              \\\n  unsigned _hd_bkt;                                                              \\\n  HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);         \\\n  (head)->hh.tbl->buckets[_hd_bkt].count++;                                      \\\n  _hd_hh_item->hh_next = NULL;                                                   \\\n  _hd_hh_item->hh_prev = NULL;                                                   \\\n} while (0)\n\n#define HASH_VALUE(keyptr,keylen,hashv)                                          \\\ndo {                                                                             \\\n  HASH_FUNCTION(keyptr, keylen, hashv);                                          \\\n} while (0)\n\n#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \\\ndo {                                                                             \\\n  (out) = NULL;                                                                  \\\n  if (head) {                                                                    \\\n    unsigned _hf_bkt;                                                            \\\n    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \\\n    if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \\\n      HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \\\ndo {                                                                             \\\n  (out) = NULL;                                                                  \\\n  if (head) {                                                                    \\\n    unsigned _hf_hashv;                                                          \\\n    HASH_VALUE(keyptr, keylen, _hf_hashv);                                       \\\n    HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);             \\\n  }                                                                              \\\n} while (0)\n\n#ifdef HASH_BLOOM\n#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)\n#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)\n#define HASH_BLOOM_MAKE(tbl,oomed)                                               \\\ndo {                                                                             \\\n  (tbl)->bloom_nbits = HASH_BLOOM;                                               \\\n  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \\\n  if (!(tbl)->bloom_bv) {                                                        \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                           \\\n    (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                     \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_BLOOM_FREE(tbl)                                                     \\\ndo {                                                                             \\\n  uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                              \\\n} while (0)\n\n#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))\n#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))\n\n#define HASH_BLOOM_ADD(tbl,hashv)                                                \\\n  HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#define HASH_BLOOM_TEST(tbl,hashv)                                               \\\n  HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#else\n#define HASH_BLOOM_MAKE(tbl,oomed)\n#define HASH_BLOOM_FREE(tbl)\n#define HASH_BLOOM_ADD(tbl,hashv)\n#define HASH_BLOOM_TEST(tbl,hashv) (1)\n#define HASH_BLOOM_BYTELEN 0U\n#endif\n\n#define HASH_MAKE_TABLE(hh,head,oomed)                                           \\\ndo {                                                                             \\\n  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table));         \\\n  if (!(head)->hh.tbl) {                                                         \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table));                         \\\n    (head)->hh.tbl->tail = &((head)->hh);                                        \\\n    (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                      \\\n    (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;            \\\n    (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                  \\\n    (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                    \\\n        HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));               \\\n    (head)->hh.tbl->signature = HASH_SIGNATURE;                                  \\\n    if (!(head)->hh.tbl->buckets) {                                              \\\n      HASH_RECORD_OOM(oomed);                                                    \\\n      uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                        \\\n    } else {                                                                     \\\n      uthash_bzero((head)->hh.tbl->buckets,                                      \\\n          HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));             \\\n      HASH_BLOOM_MAKE((head)->hh.tbl, oomed);                                    \\\n      IF_HASH_NONFATAL_OOM(                                                      \\\n        if (oomed) {                                                             \\\n          uthash_free((head)->hh.tbl->buckets,                                   \\\n              HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));           \\\n          uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                    \\\n        }                                                                        \\\n      )                                                                          \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \\\n} while (0)\n\n#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \\\n} while (0)\n\n#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \\\n} while (0)\n\n#define HASH_APPEND_LIST(hh, head, add)                                          \\\ndo {                                                                             \\\n  (add)->hh.next = NULL;                                                         \\\n  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \\\n  (head)->hh.tbl->tail->next = (add);                                            \\\n  (head)->hh.tbl->tail = &((add)->hh);                                           \\\n} while (0)\n\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  do {                                                                           \\\n    if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) {                             \\\n      break;                                                                     \\\n    }                                                                            \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n\n#ifdef NO_DECLTYPE\n#undef HASH_AKBI_INNER_LOOP\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  char *_hs_saved_head = (char*)(head);                                          \\\n  do {                                                                           \\\n    DECLTYPE_ASSIGN(head, _hs_iter);                                             \\\n    if (cmpfcn(head, add) > 0) {                                                 \\\n      DECLTYPE_ASSIGN(head, _hs_saved_head);                                     \\\n      break;                                                                     \\\n    }                                                                            \\\n    DECLTYPE_ASSIGN(head, _hs_saved_head);                                       \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n#endif\n\n#if HASH_NONFATAL_OOM\n\n#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \\\ndo {                                                                             \\\n  if (!(oomed)) {                                                                \\\n    unsigned _ha_bkt;                                                            \\\n    (head)->hh.tbl->num_items++;                                                 \\\n    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                  \\\n    HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);    \\\n    if (oomed) {                                                                 \\\n      HASH_ROLLBACK_BKT(hh, head, &(add)->hh);                                   \\\n      HASH_DELETE_HH(hh, head, &(add)->hh);                                      \\\n      (add)->hh.tbl = NULL;                                                      \\\n      uthash_nonfatal_oom(add);                                                  \\\n    } else {                                                                     \\\n      HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                   \\\n      HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                \\\n    }                                                                            \\\n  } else {                                                                       \\\n    (add)->hh.tbl = NULL;                                                        \\\n    uthash_nonfatal_oom(add);                                                    \\\n  }                                                                              \\\n} while (0)\n\n#else\n\n#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \\\ndo {                                                                             \\\n  unsigned _ha_bkt;                                                              \\\n  (head)->hh.tbl->num_items++;                                                   \\\n  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \\\n  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);      \\\n  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \\\n  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \\\n} while (0)\n\n#endif\n\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \\\ndo {                                                                             \\\n  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (char*) (keyptr);                                              \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \\\n    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \\\n      (head) = (add);                                                            \\\n    IF_HASH_NONFATAL_OOM( } )                                                    \\\n  } else {                                                                       \\\n    void *_hs_iter = (head);                                                     \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn);                                 \\\n    if (_hs_iter) {                                                              \\\n      (add)->hh.next = _hs_iter;                                                 \\\n      if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) {     \\\n        HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add);              \\\n      } else {                                                                   \\\n        (head) = (add);                                                          \\\n      }                                                                          \\\n      HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add);                      \\\n    } else {                                                                     \\\n      HASH_APPEND_LIST(hh, head, add);                                           \\\n    }                                                                            \\\n  }                                                                              \\\n  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE_INORDER\");                    \\\n} while (0)\n\n#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \\\ndo {                                                                             \\\n  unsigned _hs_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)\n\n#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \\\n  HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \\\ndo {                                                                             \\\n  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (const void*) (keyptr);                                        \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \\\n    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \\\n      (head) = (add);                                                            \\\n    IF_HASH_NONFATAL_OOM( } )                                                    \\\n  } else {                                                                       \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_APPEND_LIST(hh, head, add);                                             \\\n  }                                                                              \\\n  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE\");                            \\\n} while (0)\n\n#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \\\ndo {                                                                             \\\n  unsigned _ha_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)\n\n#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \\\n  HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)\n\n#define HASH_TO_BKT(hashv,num_bkts,bkt)                                          \\\ndo {                                                                             \\\n  bkt = ((hashv) & ((num_bkts) - 1U));                                           \\\n} while (0)\n\n/* delete \"delptr\" from the hash table.\n * \"the usual\" patch-up process for the app-order doubly-linked-list.\n * The use of _hd_hh_del below deserves special explanation.\n * These used to be expressed using (delptr) but that led to a bug\n * if someone used the same symbol for the head and deletee, like\n *  HASH_DELETE(hh,users,users);\n * We want that to work, but by changing the head (users) below\n * we were forfeiting our ability to further refer to the deletee (users)\n * in the patch-up process. Solution: use scratch space to\n * copy the deletee pointer, then the latter references are via that\n * scratch pointer rather than through the repointed (users) symbol.\n */\n#define HASH_DELETE(hh,head,delptr)                                              \\\n    HASH_DELETE_HH(hh, head, &(delptr)->hh)\n\n#define HASH_DELETE_HH(hh,head,delptrhh)                                         \\\ndo {                                                                             \\\n  struct UT_hash_handle *_hd_hh_del = (delptrhh);                                \\\n  if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) {                \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket));    \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  } else {                                                                       \\\n    unsigned _hd_bkt;                                                            \\\n    if (_hd_hh_del == (head)->hh.tbl->tail) {                                    \\\n      (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev);     \\\n    }                                                                            \\\n    if (_hd_hh_del->prev != NULL) {                                              \\\n      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next;   \\\n    } else {                                                                     \\\n      DECLTYPE_ASSIGN(head, _hd_hh_del->next);                                   \\\n    }                                                                            \\\n    if (_hd_hh_del->next != NULL) {                                              \\\n      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev;   \\\n    }                                                                            \\\n    HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);        \\\n    HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);               \\\n    (head)->hh.tbl->num_items--;                                                 \\\n  }                                                                              \\\n  HASH_FSCK(hh, head, \"HASH_DELETE_HH\");                                         \\\n} while (0)\n\n/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */\n#define HASH_FIND_STR(head,findstr,out)                                          \\\ndo {                                                                             \\\n    unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr);            \\\n    HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out);                     \\\n} while (0)\n#define HASH_ADD_STR(head,strfield,add)                                          \\\ndo {                                                                             \\\n    unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield);    \\\n    HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add);                  \\\n} while (0)\n#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \\\ndo {                                                                             \\\n    unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield);    \\\n    HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced);    \\\n} while (0)\n#define HASH_FIND_INT(head,findint,out)                                          \\\n    HASH_FIND(hh,head,findint,sizeof(int),out)\n#define HASH_ADD_INT(head,intfield,add)                                          \\\n    HASH_ADD(hh,head,intfield,sizeof(int),add)\n#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)\n#define HASH_FIND_PTR(head,findptr,out)                                          \\\n    HASH_FIND(hh,head,findptr,sizeof(void *),out)\n#define HASH_ADD_PTR(head,ptrfield,add)                                          \\\n    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)\n#define HASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)\n#define HASH_DEL(head,delptr)                                                    \\\n    HASH_DELETE(hh,head,delptr)\n\n/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.\n * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.\n */\n#ifdef HASH_DEBUG\n#include <stdio.h>   /* fprintf, stderr */\n#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)\n#define HASH_FSCK(hh,head,where)                                                 \\\ndo {                                                                             \\\n  struct UT_hash_handle *_thh;                                                   \\\n  if (head) {                                                                    \\\n    unsigned _bkt_i;                                                             \\\n    unsigned _count = 0;                                                         \\\n    char *_prev;                                                                 \\\n    for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) {           \\\n      unsigned _bkt_count = 0;                                                   \\\n      _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                            \\\n      _prev = NULL;                                                              \\\n      while (_thh) {                                                             \\\n        if (_prev != (char*)(_thh->hh_prev)) {                                   \\\n          HASH_OOPS(\"%s: invalid hh_prev %p, actual %p\\n\",                       \\\n              (where), (void*)_thh->hh_prev, (void*)_prev);                      \\\n        }                                                                        \\\n        _bkt_count++;                                                            \\\n        _prev = (char*)(_thh);                                                   \\\n        _thh = _thh->hh_next;                                                    \\\n      }                                                                          \\\n      _count += _bkt_count;                                                      \\\n      if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {                \\\n        HASH_OOPS(\"%s: invalid bucket count %u, actual %u\\n\",                    \\\n            (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);         \\\n      }                                                                          \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid hh item count %u, actual %u\\n\",                     \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n    _count = 0;                                                                  \\\n    _prev = NULL;                                                                \\\n    _thh =  &(head)->hh;                                                         \\\n    while (_thh) {                                                               \\\n      _count++;                                                                  \\\n      if (_prev != (char*)_thh->prev) {                                          \\\n        HASH_OOPS(\"%s: invalid prev %p, actual %p\\n\",                            \\\n            (where), (void*)_thh->prev, (void*)_prev);                           \\\n      }                                                                          \\\n      _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                         \\\n      _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL);     \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid app item count %u, actual %u\\n\",                    \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n#else\n#define HASH_FSCK(hh,head,where)\n#endif\n\n/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to\n * the descriptor to which this macro is defined for tuning the hash function.\n * The app can #include <unistd.h> to get the prototype for write(2). */\n#ifdef HASH_EMIT_KEYS\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \\\ndo {                                                                             \\\n  unsigned _klen = fieldlen;                                                     \\\n  write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                  \\\n  write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                        \\\n} while (0)\n#else\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)\n#endif\n\n/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */\n#define HASH_BER(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hb_keylen = (unsigned)keylen;                                        \\\n  const unsigned char *_hb_key = (const unsigned char*)(key);                    \\\n  (hashv) = 0;                                                                   \\\n  while (_hb_keylen-- != 0U) {                                                   \\\n    (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                           \\\n  }                                                                              \\\n} while (0)\n\n\n/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at\n * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */\n#define HASH_SAX(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _sx_i;                                                                \\\n  const unsigned char *_hs_key = (const unsigned char*)(key);                    \\\n  hashv = 0;                                                                     \\\n  for (_sx_i=0; _sx_i < keylen; _sx_i++) {                                       \\\n    hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                       \\\n  }                                                                              \\\n} while (0)\n/* FNV-1a variation */\n#define HASH_FNV(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _fn_i;                                                                \\\n  const unsigned char *_hf_key = (const unsigned char*)(key);                    \\\n  (hashv) = 2166136261U;                                                         \\\n  for (_fn_i=0; _fn_i < keylen; _fn_i++) {                                       \\\n    hashv = hashv ^ _hf_key[_fn_i];                                              \\\n    hashv = hashv * 16777619U;                                                   \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OAT(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _ho_i;                                                                \\\n  const unsigned char *_ho_key=(const unsigned char*)(key);                      \\\n  hashv = 0;                                                                     \\\n  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \\\n      hashv += _ho_key[_ho_i];                                                   \\\n      hashv += (hashv << 10);                                                    \\\n      hashv ^= (hashv >> 6);                                                     \\\n  }                                                                              \\\n  hashv += (hashv << 3);                                                         \\\n  hashv ^= (hashv >> 11);                                                        \\\n  hashv += (hashv << 15);                                                        \\\n} while (0)\n\n#define HASH_JEN_MIX(a,b,c)                                                      \\\ndo {                                                                             \\\n  a -= b; a -= c; a ^= ( c >> 13 );                                              \\\n  b -= c; b -= a; b ^= ( a << 8 );                                               \\\n  c -= a; c -= b; c ^= ( b >> 13 );                                              \\\n  a -= b; a -= c; a ^= ( c >> 12 );                                              \\\n  b -= c; b -= a; b ^= ( a << 16 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 5 );                                               \\\n  a -= b; a -= c; a ^= ( c >> 3 );                                               \\\n  b -= c; b -= a; b ^= ( a << 10 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 15 );                                              \\\n} while (0)\n\n#define HASH_JEN(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hj_i,_hj_j,_hj_k;                                                    \\\n  unsigned const char *_hj_key=(unsigned const char*)(key);                      \\\n  hashv = 0xfeedbeefu;                                                           \\\n  _hj_i = _hj_j = 0x9e3779b9u;                                                   \\\n  _hj_k = (unsigned)(keylen);                                                    \\\n  while (_hj_k >= 12U) {                                                         \\\n    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \\\n        + ( (unsigned)_hj_key[2] << 16 )                                         \\\n        + ( (unsigned)_hj_key[3] << 24 ) );                                      \\\n    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \\\n        + ( (unsigned)_hj_key[6] << 16 )                                         \\\n        + ( (unsigned)_hj_key[7] << 24 ) );                                      \\\n    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \\\n        + ( (unsigned)_hj_key[10] << 16 )                                        \\\n        + ( (unsigned)_hj_key[11] << 24 ) );                                     \\\n                                                                                 \\\n     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \\\n                                                                                 \\\n     _hj_key += 12;                                                              \\\n     _hj_k -= 12U;                                                               \\\n  }                                                                              \\\n  hashv += (unsigned)(keylen);                                                   \\\n  switch ( _hj_k ) {                                                             \\\n    case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */         \\\n    case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */         \\\n    case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */         \\\n    case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */         \\\n    case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */         \\\n    case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */         \\\n    case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */         \\\n    case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */         \\\n    case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */         \\\n    case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */         \\\n    case 1:  _hj_i += _hj_key[0];                      /* FALLTHROUGH */         \\\n    default: ;                                                                   \\\n  }                                                                              \\\n  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \\\n} while (0)\n\n/* The Paul Hsieh hash function */\n#undef get16bits\n#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \\\n  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)\n#define get16bits(d) (*((const uint16_t *) (d)))\n#endif\n\n#if !defined (get16bits)\n#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \\\n                       +(uint32_t)(((const uint8_t *)(d))[0]) )\n#endif\n#define HASH_SFH(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \\\n  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \\\n                                                                                 \\\n  unsigned _sfh_rem = _sfh_len & 3U;                                             \\\n  _sfh_len >>= 2;                                                                \\\n  hashv = 0xcafebabeu;                                                           \\\n                                                                                 \\\n  /* Main loop */                                                                \\\n  for (;_sfh_len > 0U; _sfh_len--) {                                             \\\n    hashv    += get16bits (_sfh_key);                                            \\\n    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \\\n    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \\\n    _sfh_key += 2U*sizeof (uint16_t);                                            \\\n    hashv    += hashv >> 11;                                                     \\\n  }                                                                              \\\n                                                                                 \\\n  /* Handle end cases */                                                         \\\n  switch (_sfh_rem) {                                                            \\\n    case 3: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 16;                                                \\\n            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \\\n            hashv += hashv >> 11;                                                \\\n            break;                                                               \\\n    case 2: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 11;                                                \\\n            hashv += hashv >> 17;                                                \\\n            break;                                                               \\\n    case 1: hashv += *_sfh_key;                                                  \\\n            hashv ^= hashv << 10;                                                \\\n            hashv += hashv >> 1;                                                 \\\n            break;                                                               \\\n    default: ;                                                                   \\\n  }                                                                              \\\n                                                                                 \\\n  /* Force \"avalanching\" of final 127 bits */                                    \\\n  hashv ^= hashv << 3;                                                           \\\n  hashv += hashv >> 5;                                                           \\\n  hashv ^= hashv << 4;                                                           \\\n  hashv += hashv >> 17;                                                          \\\n  hashv ^= hashv << 25;                                                          \\\n  hashv += hashv >> 6;                                                           \\\n} while (0)\n\n/* iterate over items in a known bucket to find desired item */\n#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \\\ndo {                                                                             \\\n  if ((head).hh_head != NULL) {                                                  \\\n    DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head));                     \\\n  } else {                                                                       \\\n    (out) = NULL;                                                                \\\n  }                                                                              \\\n  while ((out) != NULL) {                                                        \\\n    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \\\n      if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) {                  \\\n        break;                                                                   \\\n      }                                                                          \\\n    }                                                                            \\\n    if ((out)->hh.hh_next != NULL) {                                             \\\n      DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next));                \\\n    } else {                                                                     \\\n      (out) = NULL;                                                              \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n/* add an item to a bucket  */\n#define HASH_ADD_TO_BKT(head,hh,addhh,oomed)                                     \\\ndo {                                                                             \\\n  UT_hash_bucket *_ha_head = &(head);                                            \\\n  _ha_head->count++;                                                             \\\n  (addhh)->hh_next = _ha_head->hh_head;                                          \\\n  (addhh)->hh_prev = NULL;                                                       \\\n  if (_ha_head->hh_head != NULL) {                                               \\\n    _ha_head->hh_head->hh_prev = (addhh);                                        \\\n  }                                                                              \\\n  _ha_head->hh_head = (addhh);                                                   \\\n  if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \\\n      && !(addhh)->tbl->noexpand) {                                              \\\n    HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed);                              \\\n    IF_HASH_NONFATAL_OOM(                                                        \\\n      if (oomed) {                                                               \\\n        HASH_DEL_IN_BKT(head,addhh);                                             \\\n      }                                                                          \\\n    )                                                                            \\\n  }                                                                              \\\n} while (0)\n\n/* remove an item from a given bucket */\n#define HASH_DEL_IN_BKT(head,delhh)                                              \\\ndo {                                                                             \\\n  UT_hash_bucket *_hd_head = &(head);                                            \\\n  _hd_head->count--;                                                             \\\n  if (_hd_head->hh_head == (delhh)) {                                            \\\n    _hd_head->hh_head = (delhh)->hh_next;                                        \\\n  }                                                                              \\\n  if ((delhh)->hh_prev) {                                                        \\\n    (delhh)->hh_prev->hh_next = (delhh)->hh_next;                                \\\n  }                                                                              \\\n  if ((delhh)->hh_next) {                                                        \\\n    (delhh)->hh_next->hh_prev = (delhh)->hh_prev;                                \\\n  }                                                                              \\\n} while (0)\n\n/* Bucket expansion has the effect of doubling the number of buckets\n * and redistributing the items into the new buckets. Ideally the\n * items will distribute more or less evenly into the new buckets\n * (the extent to which this is true is a measure of the quality of\n * the hash function as it applies to the key domain).\n *\n * With the items distributed into more buckets, the chain length\n * (item count) in each bucket is reduced. Thus by expanding buckets\n * the hash keeps a bound on the chain length. This bounded chain\n * length is the essence of how a hash provides constant time lookup.\n *\n * The calculation of tbl->ideal_chain_maxlen below deserves some\n * explanation. First, keep in mind that we're calculating the ideal\n * maximum chain length based on the *new* (doubled) bucket count.\n * In fractions this is just n/b (n=number of items,b=new num buckets).\n * Since the ideal chain length is an integer, we want to calculate\n * ceil(n/b). We don't depend on floating point arithmetic in this\n * hash, so to calculate ceil(n/b) with integers we could write\n *\n *      ceil(n/b) = (n/b) + ((n%b)?1:0)\n *\n * and in fact a previous version of this hash did just that.\n * But now we have improved things a bit by recognizing that b is\n * always a power of two. We keep its base 2 log handy (call it lb),\n * so now we can write this with a bit shift and logical AND:\n *\n *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)\n *\n */\n#define HASH_EXPAND_BUCKETS(hh,tbl,oomed)                                        \\\ndo {                                                                             \\\n  unsigned _he_bkt;                                                              \\\n  unsigned _he_bkt_i;                                                            \\\n  struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                   \\\n  UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                  \\\n  _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                              \\\n           sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);             \\\n  if (!_he_new_buckets) {                                                        \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero(_he_new_buckets,                                                \\\n        sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);                \\\n    (tbl)->ideal_chain_maxlen =                                                  \\\n       ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) +                      \\\n       ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);    \\\n    (tbl)->nonideal_items = 0;                                                   \\\n    for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) {           \\\n      _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head;                             \\\n      while (_he_thh != NULL) {                                                  \\\n        _he_hh_nxt = _he_thh->hh_next;                                           \\\n        HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt);           \\\n        _he_newbkt = &(_he_new_buckets[_he_bkt]);                                \\\n        if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) {                 \\\n          (tbl)->nonideal_items++;                                               \\\n          if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \\\n            _he_newbkt->expand_mult++;                                           \\\n          }                                                                      \\\n        }                                                                        \\\n        _he_thh->hh_prev = NULL;                                                 \\\n        _he_thh->hh_next = _he_newbkt->hh_head;                                  \\\n        if (_he_newbkt->hh_head != NULL) {                                       \\\n          _he_newbkt->hh_head->hh_prev = _he_thh;                                \\\n        }                                                                        \\\n        _he_newbkt->hh_head = _he_thh;                                           \\\n        _he_thh = _he_hh_nxt;                                                    \\\n      }                                                                          \\\n    }                                                                            \\\n    uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \\\n    (tbl)->num_buckets *= 2U;                                                    \\\n    (tbl)->log2_num_buckets++;                                                   \\\n    (tbl)->buckets = _he_new_buckets;                                            \\\n    (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ?   \\\n        ((tbl)->ineff_expands+1U) : 0U;                                          \\\n    if ((tbl)->ineff_expands > 1U) {                                             \\\n      (tbl)->noexpand = 1;                                                       \\\n      uthash_noexpand_fyi(tbl);                                                  \\\n    }                                                                            \\\n    uthash_expand_fyi(tbl);                                                      \\\n  }                                                                              \\\n} while (0)\n\n\n/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */\n/* Note that HASH_SORT assumes the hash handle name to be hh.\n * HASH_SRT was added to allow the hash handle name to be passed in. */\n#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)\n#define HASH_SRT(hh,head,cmpfcn)                                                 \\\ndo {                                                                             \\\n  unsigned _hs_i;                                                                \\\n  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \\\n  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \\\n  if (head != NULL) {                                                            \\\n    _hs_insize = 1;                                                              \\\n    _hs_looping = 1;                                                             \\\n    _hs_list = &((head)->hh);                                                    \\\n    while (_hs_looping != 0U) {                                                  \\\n      _hs_p = _hs_list;                                                          \\\n      _hs_list = NULL;                                                           \\\n      _hs_tail = NULL;                                                           \\\n      _hs_nmerges = 0;                                                           \\\n      while (_hs_p != NULL) {                                                    \\\n        _hs_nmerges++;                                                           \\\n        _hs_q = _hs_p;                                                           \\\n        _hs_psize = 0;                                                           \\\n        for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) {                           \\\n          _hs_psize++;                                                           \\\n          _hs_q = ((_hs_q->next != NULL) ?                                       \\\n            HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                   \\\n          if (_hs_q == NULL) {                                                   \\\n            break;                                                               \\\n          }                                                                      \\\n        }                                                                        \\\n        _hs_qsize = _hs_insize;                                                  \\\n        while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) {    \\\n          if (_hs_psize == 0U) {                                                 \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) {                     \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else if ((cmpfcn(                                                    \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)),             \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q))              \\\n                )) <= 0) {                                                       \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else {                                                               \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          }                                                                      \\\n          if ( _hs_tail != NULL ) {                                              \\\n            _hs_tail->next = ((_hs_e != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL);                       \\\n          } else {                                                               \\\n            _hs_list = _hs_e;                                                    \\\n          }                                                                      \\\n          if (_hs_e != NULL) {                                                   \\\n            _hs_e->prev = ((_hs_tail != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL);                    \\\n          }                                                                      \\\n          _hs_tail = _hs_e;                                                      \\\n        }                                                                        \\\n        _hs_p = _hs_q;                                                           \\\n      }                                                                          \\\n      if (_hs_tail != NULL) {                                                    \\\n        _hs_tail->next = NULL;                                                   \\\n      }                                                                          \\\n      if (_hs_nmerges <= 1U) {                                                   \\\n        _hs_looping = 0;                                                         \\\n        (head)->hh.tbl->tail = _hs_tail;                                         \\\n        DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list));           \\\n      }                                                                          \\\n      _hs_insize *= 2U;                                                          \\\n    }                                                                            \\\n    HASH_FSCK(hh, head, \"HASH_SRT\");                                             \\\n  }                                                                              \\\n} while (0)\n\n/* This function selects items from one hash into another hash.\n * The end result is that the selected items have dual presence\n * in both hashes. There is no copy of the items made; rather\n * they are added into the new hash through a secondary hash\n * hash handle that must be present in the structure. */\n#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \\\ndo {                                                                             \\\n  unsigned _src_bkt, _dst_bkt;                                                   \\\n  void *_last_elt = NULL, *_elt;                                                 \\\n  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \\\n  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \\\n  if ((src) != NULL) {                                                           \\\n    for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {    \\\n      for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;               \\\n        _src_hh != NULL;                                                         \\\n        _src_hh = _src_hh->hh_next) {                                            \\\n        _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                         \\\n        if (cond(_elt)) {                                                        \\\n          IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; )                             \\\n          _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho);          \\\n          _dst_hh->key = _src_hh->key;                                           \\\n          _dst_hh->keylen = _src_hh->keylen;                                     \\\n          _dst_hh->hashv = _src_hh->hashv;                                       \\\n          _dst_hh->prev = _last_elt;                                             \\\n          _dst_hh->next = NULL;                                                  \\\n          if (_last_elt_hh != NULL) {                                            \\\n            _last_elt_hh->next = _elt;                                           \\\n          }                                                                      \\\n          if ((dst) == NULL) {                                                   \\\n            DECLTYPE_ASSIGN(dst, _elt);                                          \\\n            HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed);                             \\\n            IF_HASH_NONFATAL_OOM(                                                \\\n              if (_hs_oomed) {                                                   \\\n                uthash_nonfatal_oom(_elt);                                       \\\n                (dst) = NULL;                                                    \\\n                continue;                                                        \\\n              }                                                                  \\\n            )                                                                    \\\n          } else {                                                               \\\n            _dst_hh->tbl = (dst)->hh_dst.tbl;                                    \\\n          }                                                                      \\\n          HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);      \\\n          HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \\\n          (dst)->hh_dst.tbl->num_items++;                                        \\\n          IF_HASH_NONFATAL_OOM(                                                  \\\n            if (_hs_oomed) {                                                     \\\n              HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh);                           \\\n              HASH_DELETE_HH(hh_dst, dst, _dst_hh);                              \\\n              _dst_hh->tbl = NULL;                                               \\\n              uthash_nonfatal_oom(_elt);                                         \\\n              continue;                                                          \\\n            }                                                                    \\\n          )                                                                      \\\n          HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv);                          \\\n          _last_elt = _elt;                                                      \\\n          _last_elt_hh = _dst_hh;                                                \\\n        }                                                                        \\\n      }                                                                          \\\n    }                                                                            \\\n  }                                                                              \\\n  HASH_FSCK(hh_dst, dst, \"HASH_SELECT\");                                         \\\n} while (0)\n\n#define HASH_CLEAR(hh,head)                                                      \\\ndo {                                                                             \\\n  if ((head) != NULL) {                                                          \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OVERHEAD(hh,head)                                                   \\\n (((head) != NULL) ? (                                                           \\\n (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \\\n          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \\\n           sizeof(UT_hash_table)                                   +             \\\n           (HASH_BLOOM_BYTELEN))) : 0U)\n\n#ifdef NO_DECLTYPE\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \\\n  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#else\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \\\n  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#endif\n\n/* obtain a count of items in the hash */\n#define HASH_COUNT(head) HASH_CNT(hh,head)\n#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)\n\ntypedef struct UT_hash_bucket {\n   struct UT_hash_handle *hh_head;\n   unsigned count;\n\n   /* expand_mult is normally set to 0. In this situation, the max chain length\n    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If\n    * the bucket's chain exceeds this length, bucket expansion is triggered).\n    * However, setting expand_mult to a non-zero value delays bucket expansion\n    * (that would be triggered by additions to this particular bucket)\n    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.\n    * (The multiplier is simply expand_mult+1). The whole idea of this\n    * multiplier is to reduce bucket expansions, since they are expensive, in\n    * situations where we know that a particular bucket tends to be overused.\n    * It is better to let its chain length grow to a longer yet-still-bounded\n    * value, than to do an O(n) bucket expansion too often.\n    */\n   unsigned expand_mult;\n\n} UT_hash_bucket;\n\n/* random signature used only to find hash tables in external analysis */\n#define HASH_SIGNATURE 0xa0111fe1u\n#define HASH_BLOOM_SIGNATURE 0xb12220f2u\n\ntypedef struct UT_hash_table {\n   UT_hash_bucket *buckets;\n   unsigned num_buckets, log2_num_buckets;\n   unsigned num_items;\n   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */\n   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */\n\n   /* in an ideal situation (all buckets used equally), no bucket would have\n    * more than ceil(#items/#buckets) items. that's the ideal chain length. */\n   unsigned ideal_chain_maxlen;\n\n   /* nonideal_items is the number of items in the hash whose chain position\n    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven\n    * hash distribution; reaching them in a chain traversal takes >ideal steps */\n   unsigned nonideal_items;\n\n   /* ineffective expands occur when a bucket doubling was performed, but\n    * afterward, more than half the items in the hash had nonideal chain\n    * positions. If this happens on two consecutive expansions we inhibit any\n    * further expansion, as it's not helping; this happens when the hash\n    * function isn't a good fit for the key domain. When expansion is inhibited\n    * the hash will still work, albeit no longer in constant time. */\n   unsigned ineff_expands, noexpand;\n\n   uint32_t signature; /* used only to find hash tables in external analysis */\n#ifdef HASH_BLOOM\n   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */\n   uint8_t *bloom_bv;\n   uint8_t bloom_nbits;\n#endif\n\n} UT_hash_table;\n\ntypedef struct UT_hash_handle {\n   struct UT_hash_table *tbl;\n   void *prev;                       /* prev element in app order      */\n   void *next;                       /* next element in app order      */\n   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */\n   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */\n   const void *key;                  /* ptr to enclosing struct's key  */\n   unsigned keylen;                  /* enclosing struct's key len     */\n   unsigned hashv;                   /* result of hash-fcn(key)        */\n} UT_hash_handle;\n\n#endif /* UTHASH_H */\n"
  },
  {
    "path": "nfq/win.c",
    "content": "#ifdef __CYGWIN__\n\n#include <windows.h>\n\n#include \"win.h\"\n#include \"nfqws.h\"\n\n#define SERVICE_NAME \"winws\"\n\nstatic SERVICE_STATUS ServiceStatus;\nstatic SERVICE_STATUS_HANDLE hStatus = NULL;\nstatic int service_argc = 0;\nstatic char **service_argv = NULL;\n\nvoid service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)));\n\nbool service_run(int argc, char *argv[])\n{\n\tSERVICE_TABLE_ENTRY ServiceTable[] = {\n\t\t{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},\n\t\t{NULL, NULL}\n\t};\n\n\tservice_argc = argc;\n\tservice_argv = argv;\n\n\treturn StartServiceCtrlDispatcherA(ServiceTable);\n}\n\nstatic void service_set_status(DWORD state)\n{\n\tServiceStatus.dwCurrentState = state;\n\tSetServiceStatus(hStatus, &ServiceStatus);\n}\n\n// Control handler function\nvoid service_controlhandler(DWORD request)\n{\n\tswitch (request)\n\t{\n\tcase SERVICE_CONTROL_STOP:\n\tcase SERVICE_CONTROL_SHUTDOWN:\n\t\tbQuit = true;\n\t\tServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;\n\t\tbreak;\n\t}\n\tSetServiceStatus(hStatus, &ServiceStatus);\n}\n\nvoid service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)))\n{\n\tServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;\n\tServiceStatus.dwCurrentState = SERVICE_RUNNING;\n\tServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;\n\tServiceStatus.dwWin32ExitCode = 0;\n\tServiceStatus.dwServiceSpecificExitCode = 0;\n\tServiceStatus.dwCheckPoint = 1;\n\tServiceStatus.dwWaitHint = 0;\n\n\thStatus = RegisterServiceCtrlHandlerA(\n\t\tSERVICE_NAME,\n\t\t(LPHANDLER_FUNCTION)service_controlhandler);\n\tif (hStatus == (SERVICE_STATUS_HANDLE)0)\n\t{\n\t\t// Registering Control Handler failed\n\t\treturn;\n\t}\n\n\tSetServiceStatus(hStatus, &ServiceStatus);\n\n\t// Calling main with saved argc & argv\n\tServiceStatus.dwWin32ExitCode = (DWORD)main(service_argc, service_argv);\n\n\tServiceStatus.dwCurrentState = SERVICE_STOPPED;\n\tSetServiceStatus(hStatus, &ServiceStatus);\n\treturn;\n}\n\n\n#endif\n"
  },
  {
    "path": "nfq/win.h",
    "content": "#pragma once\n\n#ifdef __CYGWIN__\n\n#include <stdbool.h>\n\nbool service_run(int argc, char *argv[]);\n\n#endif\n\n"
  },
  {
    "path": "nfq/windows/windivert/windivert.h",
    "content": "/*\n * windivert.h\n * (C) 2019, all rights reserved,\n *\n * This file is part of WinDivert.\n *\n * WinDivert is free software: you can redistribute it and/or modify it under\n * the terms of the GNU Lesser General Public License as published by the\n * Free Software Foundation, either version 3 of the License, or (at your\n * option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public\n * License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n * WinDivert is free software; you can redistribute it and/or modify it under\n * the terms of the GNU General Public License as published by the Free\n * Software Foundation; either version 2 of the License, or (at your option)\n * any later version.\n * \n * This program is distributed in the hope that it will be useful, but\n * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * for more details.\n * \n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc., 51\n * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n */\n\n#ifndef __WINDIVERT_H\n#define __WINDIVERT_H\n\n#ifndef WINDIVERT_KERNEL\n#include <windows.h>\n#endif      /* WINDIVERT_KERNEL */\n\n#ifndef WINDIVERTEXPORT\n#define WINDIVERTEXPORT     extern __declspec(dllimport)\n#endif      /* WINDIVERTEXPORT */\n\n#ifdef __MINGW32__\n#define __in\n#define __in_opt\n#define __out\n#define __out_opt\n#define __inout\n#define __inout_opt\n#include <stdint.h>\n#define INT8    int8_t\n#define UINT8   uint8_t\n#define INT16   int16_t\n#define UINT16  uint16_t\n#define INT32   int32_t\n#define UINT32  uint32_t\n#define INT64   int64_t\n#define UINT64  uint64_t\n#endif      /* __MINGW32__ */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/****************************************************************************/\n/* WINDIVERT API                                                            */\n/****************************************************************************/\n\n/*\n * WinDivert layers.\n */\ntypedef enum\n{\n    WINDIVERT_LAYER_NETWORK = 0,        /* Network layer. */\n    WINDIVERT_LAYER_NETWORK_FORWARD = 1,/* Network layer (forwarded packets) */\n    WINDIVERT_LAYER_FLOW = 2,           /* Flow layer. */\n    WINDIVERT_LAYER_SOCKET = 3,         /* Socket layer. */\n    WINDIVERT_LAYER_REFLECT = 4,        /* Reflect layer. */\n} WINDIVERT_LAYER, *PWINDIVERT_LAYER;\n\n/*\n * WinDivert NETWORK and NETWORK_FORWARD layer data.\n */\ntypedef struct\n{\n    UINT32 IfIdx;                       /* Packet's interface index. */\n    UINT32 SubIfIdx;                    /* Packet's sub-interface index. */\n} WINDIVERT_DATA_NETWORK, *PWINDIVERT_DATA_NETWORK;\n\n/*\n * WinDivert FLOW layer data.\n */\ntypedef struct\n{\n    UINT64 EndpointId;                  /* Endpoint ID. */\n    UINT64 ParentEndpointId;            /* Parent endpoint ID. */\n    UINT32 ProcessId;                   /* Process ID. */\n    UINT32 LocalAddr[4];                /* Local address. */\n    UINT32 RemoteAddr[4];               /* Remote address. */\n    UINT16 LocalPort;                   /* Local port. */\n    UINT16 RemotePort;                  /* Remote port. */\n    UINT8  Protocol;                    /* Protocol. */\n} WINDIVERT_DATA_FLOW, *PWINDIVERT_DATA_FLOW;\n\n/*\n * WinDivert SOCKET layer data.\n */\ntypedef struct\n{\n    UINT64 EndpointId;                  /* Endpoint ID. */\n    UINT64 ParentEndpointId;            /* Parent Endpoint ID. */\n    UINT32 ProcessId;                   /* Process ID. */\n    UINT32 LocalAddr[4];                /* Local address. */\n    UINT32 RemoteAddr[4];               /* Remote address. */\n    UINT16 LocalPort;                   /* Local port. */\n    UINT16 RemotePort;                  /* Remote port. */\n    UINT8  Protocol;                    /* Protocol. */\n} WINDIVERT_DATA_SOCKET, *PWINDIVERT_DATA_SOCKET;\n\n/*\n * WinDivert REFLECTION layer data.\n */\ntypedef struct\n{\n    INT64  Timestamp;                   /* Handle open time. */\n    UINT32 ProcessId;                   /* Handle process ID. */\n    WINDIVERT_LAYER Layer;              /* Handle layer. */\n    UINT64 Flags;                       /* Handle flags. */\n    INT16  Priority;                    /* Handle priority. */\n} WINDIVERT_DATA_REFLECT, *PWINDIVERT_DATA_REFLECT;\n\n/*\n * WinDivert address.\n */\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable: 4201)\n#endif\ntypedef struct\n{\n    INT64  Timestamp;                   /* Packet's timestamp. */\n    UINT32 Layer:8;                     /* Packet's layer. */\n    UINT32 Event:8;                     /* Packet event. */\n    UINT32 Sniffed:1;                   /* Packet was sniffed? */\n    UINT32 Outbound:1;                  /* Packet is outound? */\n    UINT32 Loopback:1;                  /* Packet is loopback? */\n    UINT32 Impostor:1;                  /* Packet is impostor? */\n    UINT32 IPv6:1;                      /* Packet is IPv6? */\n    UINT32 IPChecksum:1;                /* Packet has valid IPv4 checksum? */\n    UINT32 TCPChecksum:1;               /* Packet has valid TCP checksum? */\n    UINT32 UDPChecksum:1;               /* Packet has valid UDP checksum? */\n    UINT32 Reserved1:8;\n    UINT32 Reserved2;\n    union\n    {\n        WINDIVERT_DATA_NETWORK Network; /* Network layer data. */\n        WINDIVERT_DATA_FLOW Flow;       /* Flow layer data. */\n        WINDIVERT_DATA_SOCKET Socket;   /* Socket layer data. */\n        WINDIVERT_DATA_REFLECT Reflect; /* Reflect layer data. */\n        UINT8 Reserved3[64];\n    };\n} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n/*\n * WinDivert events.\n */\ntypedef enum\n{\n    WINDIVERT_EVENT_NETWORK_PACKET = 0, /* Network packet. */\n    WINDIVERT_EVENT_FLOW_ESTABLISHED = 1,\n                                        /* Flow established. */\n    WINDIVERT_EVENT_FLOW_DELETED = 2,   /* Flow deleted. */\n    WINDIVERT_EVENT_SOCKET_BIND = 3,    /* Socket bind. */\n    WINDIVERT_EVENT_SOCKET_CONNECT = 4, /* Socket connect. */\n    WINDIVERT_EVENT_SOCKET_LISTEN = 5,  /* Socket listen. */\n    WINDIVERT_EVENT_SOCKET_ACCEPT = 6,  /* Socket accept. */\n    WINDIVERT_EVENT_SOCKET_CLOSE = 7,   /* Socket close. */\n    WINDIVERT_EVENT_REFLECT_OPEN = 8,   /* WinDivert handle opened. */\n    WINDIVERT_EVENT_REFLECT_CLOSE = 9,  /* WinDivert handle closed. */\n} WINDIVERT_EVENT, *PWINDIVERT_EVENT;\n\n/*\n * WinDivert flags.\n */\n#define WINDIVERT_FLAG_SNIFF            0x0001\n#define WINDIVERT_FLAG_DROP             0x0002\n#define WINDIVERT_FLAG_RECV_ONLY        0x0004\n#define WINDIVERT_FLAG_READ_ONLY        WINDIVERT_FLAG_RECV_ONLY\n#define WINDIVERT_FLAG_SEND_ONLY        0x0008\n#define WINDIVERT_FLAG_WRITE_ONLY       WINDIVERT_FLAG_SEND_ONLY\n#define WINDIVERT_FLAG_NO_INSTALL       0x0010\n#define WINDIVERT_FLAG_FRAGMENTS        0x0020\n\n/*\n * WinDivert parameters.\n */\ntypedef enum\n{\n    WINDIVERT_PARAM_QUEUE_LENGTH = 0,   /* Packet queue length. */\n    WINDIVERT_PARAM_QUEUE_TIME = 1,     /* Packet queue time. */\n    WINDIVERT_PARAM_QUEUE_SIZE = 2,     /* Packet queue size. */\n    WINDIVERT_PARAM_VERSION_MAJOR = 3,  /* Driver version (major). */\n    WINDIVERT_PARAM_VERSION_MINOR = 4,  /* Driver version (minor). */\n} WINDIVERT_PARAM, *PWINDIVERT_PARAM;\n#define WINDIVERT_PARAM_MAX             WINDIVERT_PARAM_VERSION_MINOR\n\n/*\n * WinDivert shutdown parameter.\n */\ntypedef enum\n{\n    WINDIVERT_SHUTDOWN_RECV = 0x1,      /* Shutdown recv. */\n    WINDIVERT_SHUTDOWN_SEND = 0x2,      /* Shutdown send. */\n    WINDIVERT_SHUTDOWN_BOTH = 0x3,      /* Shutdown recv and send. */\n} WINDIVERT_SHUTDOWN, *PWINDIVERT_SHUTDOWN;\n#define WINDIVERT_SHUTDOWN_MAX          WINDIVERT_SHUTDOWN_BOTH\n\n#ifndef WINDIVERT_KERNEL\n\n/*\n * Open a WinDivert handle.\n */\nWINDIVERTEXPORT HANDLE WinDivertOpen(\n    __in        const char *filter,\n    __in        WINDIVERT_LAYER layer,\n    __in        INT16 priority,\n    __in        UINT64 flags);\n\n/*\n * Receive (read) a packet from a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertRecv(\n    __in        HANDLE handle,\n    __out_opt   VOID *pPacket,\n    __in        UINT packetLen,\n    __out_opt   UINT *pRecvLen,\n    __out_opt   WINDIVERT_ADDRESS *pAddr);\n\n/*\n * Receive (read) a packet from a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertRecvEx(\n    __in        HANDLE handle,\n    __out_opt   VOID *pPacket,\n    __in        UINT packetLen,\n    __out_opt   UINT *pRecvLen,\n    __in        UINT64 flags,\n    __out       WINDIVERT_ADDRESS *pAddr,\n    __inout_opt UINT *pAddrLen,\n    __inout_opt LPOVERLAPPED lpOverlapped);\n\n/*\n * Send (write/inject) a packet to a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertSend(\n    __in        HANDLE handle,\n    __in        const VOID *pPacket,\n    __in        UINT packetLen,\n    __out_opt   UINT *pSendLen,\n    __in        const WINDIVERT_ADDRESS *pAddr);\n\n/*\n * Send (write/inject) a packet to a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertSendEx(\n    __in        HANDLE handle,\n    __in        const VOID *pPacket,\n    __in        UINT packetLen,\n    __out_opt   UINT *pSendLen,\n    __in        UINT64 flags,\n    __in        const WINDIVERT_ADDRESS *pAddr,\n    __in        UINT addrLen,\n    __inout_opt LPOVERLAPPED lpOverlapped);\n\n/*\n * Shutdown a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertShutdown(\n    __in        HANDLE handle,\n    __in        WINDIVERT_SHUTDOWN how);\n\n/*\n * Close a WinDivert handle.\n */\nWINDIVERTEXPORT BOOL WinDivertClose(\n    __in        HANDLE handle);\n\n/*\n * Set a WinDivert handle parameter.\n */\nWINDIVERTEXPORT BOOL WinDivertSetParam(\n    __in        HANDLE handle,\n    __in        WINDIVERT_PARAM param,\n    __in        UINT64 value);\n\n/*\n * Get a WinDivert handle parameter.\n */\nWINDIVERTEXPORT BOOL WinDivertGetParam(\n    __in        HANDLE handle,\n    __in        WINDIVERT_PARAM param,\n    __out       UINT64 *pValue);\n\n#endif      /* WINDIVERT_KERNEL */\n\n/*\n * WinDivert constants.\n */\n#define WINDIVERT_PRIORITY_HIGHEST              30000\n#define WINDIVERT_PRIORITY_LOWEST               (-WINDIVERT_PRIORITY_HIGHEST)\n#define WINDIVERT_PARAM_QUEUE_LENGTH_DEFAULT    4096\n#define WINDIVERT_PARAM_QUEUE_LENGTH_MIN        32\n#define WINDIVERT_PARAM_QUEUE_LENGTH_MAX        16384\n#define WINDIVERT_PARAM_QUEUE_TIME_DEFAULT      2000        /* 2s */\n#define WINDIVERT_PARAM_QUEUE_TIME_MIN          100         /* 100ms */\n#define WINDIVERT_PARAM_QUEUE_TIME_MAX          16000       /* 16s */\n#define WINDIVERT_PARAM_QUEUE_SIZE_DEFAULT      4194304     /* 4MB */\n#define WINDIVERT_PARAM_QUEUE_SIZE_MIN          65535       /* 64KB */\n#define WINDIVERT_PARAM_QUEUE_SIZE_MAX          33554432    /* 32MB */\n#define WINDIVERT_BATCH_MAX                     0xFF        /* 255 */\n#define WINDIVERT_MTU_MAX                       (40 + 0xFFFF)\n\n/****************************************************************************/\n/* WINDIVERT HELPER API                                                     */\n/****************************************************************************/\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable: 4214)\n#endif\n\n/*\n * IPv4/IPv6/ICMP/ICMPv6/TCP/UDP header definitions.\n */\ntypedef struct\n{\n    UINT8  HdrLength:4;\n    UINT8  Version:4;\n    UINT8  TOS;\n    UINT16 Length;\n    UINT16 Id;\n    UINT16 FragOff0;\n    UINT8  TTL;\n    UINT8  Protocol;\n    UINT16 Checksum;\n    UINT32 SrcAddr;\n    UINT32 DstAddr;\n} WINDIVERT_IPHDR, *PWINDIVERT_IPHDR;\n\n#define WINDIVERT_IPHDR_GET_FRAGOFF(hdr)                    \\\n    (((hdr)->FragOff0) & 0xFF1F)\n#define WINDIVERT_IPHDR_GET_MF(hdr)                         \\\n    ((((hdr)->FragOff0) & 0x0020) != 0)\n#define WINDIVERT_IPHDR_GET_DF(hdr)                         \\\n    ((((hdr)->FragOff0) & 0x0040) != 0)\n#define WINDIVERT_IPHDR_GET_RESERVED(hdr)                   \\\n    ((((hdr)->FragOff0) & 0x0080) != 0)\n\n#define WINDIVERT_IPHDR_SET_FRAGOFF(hdr, val)               \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->FragOff0 = (((hdr)->FragOff0) & 0x00E0) |    \\\n            ((val) & 0xFF1F);                               \\\n    }                                                       \\\n    while (FALSE)\n#define WINDIVERT_IPHDR_SET_MF(hdr, val)                    \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFDF) |    \\\n            (((val) & 0x0001) << 5);                        \\\n    }                                                       \\\n    while (FALSE)\n#define WINDIVERT_IPHDR_SET_DF(hdr, val)                    \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFBF) |    \\\n            (((val) & 0x0001) << 6);                        \\\n    }                                                       \\\n    while (FALSE)\n#define WINDIVERT_IPHDR_SET_RESERVED(hdr, val)              \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFF7F) |    \\\n            (((val) & 0x0001) << 7);                        \\\n    }                                                       \\\n    while (FALSE)\n\ntypedef struct\n{\n    UINT8  TrafficClass0:4;\n    UINT8  Version:4;\n    UINT8  FlowLabel0:4;\n    UINT8  TrafficClass1:4;\n    UINT16 FlowLabel1;\n    UINT16 Length;\n    UINT8  NextHdr;\n    UINT8  HopLimit;\n    UINT32 SrcAddr[4];\n    UINT32 DstAddr[4];\n} WINDIVERT_IPV6HDR, *PWINDIVERT_IPV6HDR;\n\n#define WINDIVERT_IPV6HDR_GET_TRAFFICCLASS(hdr)             \\\n    ((((hdr)->TrafficClass0) << 4) | ((hdr)->TrafficClass1))\n#define WINDIVERT_IPV6HDR_GET_FLOWLABEL(hdr)                \\\n    ((((UINT32)(hdr)->FlowLabel0) << 16) | ((UINT32)(hdr)->FlowLabel1))\n\n#define WINDIVERT_IPV6HDR_SET_TRAFFICCLASS(hdr, val)        \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->TrafficClass0 = ((UINT8)(val) >> 4);         \\\n        (hdr)->TrafficClass1 = (UINT8)(val);                \\\n    }                                                       \\\n    while (FALSE)\n#define WINDIVERT_IPV6HDR_SET_FLOWLABEL(hdr, val)           \\\n    do                                                      \\\n    {                                                       \\\n        (hdr)->FlowLabel0 = (UINT8)((val) >> 16);           \\\n        (hdr)->FlowLabel1 = (UINT16)(val);                  \\\n    }                                                       \\\n    while (FALSE)\n\ntypedef struct\n{\n    UINT8  Type;\n    UINT8  Code;\n    UINT16 Checksum;\n    UINT32 Body;\n} WINDIVERT_ICMPHDR, *PWINDIVERT_ICMPHDR;\n\ntypedef struct\n{\n    UINT8  Type;\n    UINT8  Code;\n    UINT16 Checksum;\n    UINT32 Body;\n} WINDIVERT_ICMPV6HDR, *PWINDIVERT_ICMPV6HDR;\n\ntypedef struct\n{\n    UINT16 SrcPort;\n    UINT16 DstPort;\n    UINT32 SeqNum;\n    UINT32 AckNum;\n    UINT16 Reserved1:4;\n    UINT16 HdrLength:4;\n    UINT16 Fin:1;\n    UINT16 Syn:1;\n    UINT16 Rst:1;\n    UINT16 Psh:1;\n    UINT16 Ack:1;\n    UINT16 Urg:1;\n    UINT16 Reserved2:2;\n    UINT16 Window;\n    UINT16 Checksum;\n    UINT16 UrgPtr;\n} WINDIVERT_TCPHDR, *PWINDIVERT_TCPHDR;\n\ntypedef struct\n{\n    UINT16 SrcPort;\n    UINT16 DstPort;\n    UINT16 Length;\n    UINT16 Checksum;\n} WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR;\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n/*\n * Flags for WinDivertHelperCalcChecksums()\n */\n#define WINDIVERT_HELPER_NO_IP_CHECKSUM                     1\n#define WINDIVERT_HELPER_NO_ICMP_CHECKSUM                   2\n#define WINDIVERT_HELPER_NO_ICMPV6_CHECKSUM                 4\n#define WINDIVERT_HELPER_NO_TCP_CHECKSUM                    8\n#define WINDIVERT_HELPER_NO_UDP_CHECKSUM                    16\n\n#ifndef WINDIVERT_KERNEL\n\n/*\n * Hash a packet.\n */\nWINDIVERTEXPORT UINT64 WinDivertHelperHashPacket(\n    __in        const VOID *pPacket,\n    __in        UINT packetLen,\n    __in        UINT64 seed\n#ifdef __cplusplus\n                = 0\n#endif\n);\n\n/*\n * Parse IPv4/IPv6/ICMP/ICMPv6/TCP/UDP headers from a raw packet.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperParsePacket(\n    __in        const VOID *pPacket,\n    __in        UINT packetLen,\n    __out_opt   PWINDIVERT_IPHDR *ppIpHdr,\n    __out_opt   PWINDIVERT_IPV6HDR *ppIpv6Hdr,\n    __out_opt   UINT8 *pProtocol,\n    __out_opt   PWINDIVERT_ICMPHDR *ppIcmpHdr,\n    __out_opt   PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr,\n    __out_opt   PWINDIVERT_TCPHDR *ppTcpHdr,\n    __out_opt   PWINDIVERT_UDPHDR *ppUdpHdr,\n    __out_opt   PVOID *ppData,\n    __out_opt   UINT *pDataLen,\n    __out_opt   PVOID *ppNext,\n    __out_opt   UINT *pNextLen);\n\n/*\n * Parse an IPv4 address.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperParseIPv4Address(\n    __in        const char *addrStr,\n    __out_opt   UINT32 *pAddr);\n\n/*\n * Parse an IPv6 address.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperParseIPv6Address(\n    __in        const char *addrStr,\n    __out_opt   UINT32 *pAddr);\n\n/*\n * Format an IPv4 address.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperFormatIPv4Address(\n    __in        UINT32 addr,\n    __out       char *buffer,\n    __in        UINT bufLen);\n\n/*\n * Format an IPv6 address.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperFormatIPv6Address(\n    __in        const UINT32 *pAddr,\n    __out       char *buffer,\n    __in        UINT bufLen);\n\n/*\n * Calculate IPv4/IPv6/ICMP/ICMPv6/TCP/UDP checksums.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperCalcChecksums(\n    __inout     VOID *pPacket, \n    __in        UINT packetLen,\n    __out_opt   WINDIVERT_ADDRESS *pAddr,\n    __in        UINT64 flags);\n\n/*\n * Decrement the TTL/HopLimit.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperDecrementTTL(\n    __inout     VOID *pPacket,\n    __in        UINT packetLen);\n\n/*\n * Compile the given filter string.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperCompileFilter(\n    __in        const char *filter,\n    __in        WINDIVERT_LAYER layer,\n    __out_opt   char *object,\n    __in        UINT objLen,\n    __out_opt   const char **errorStr,\n    __out_opt   UINT *errorPos);\n\n/*\n * Evaluate the given filter string.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperEvalFilter(\n    __in        const char *filter,\n    __in        const VOID *pPacket,\n    __in        UINT packetLen,\n    __in        const WINDIVERT_ADDRESS *pAddr);\n\n/*\n * Format the given filter string.\n */\nWINDIVERTEXPORT BOOL WinDivertHelperFormatFilter(\n    __in        const char *filter,\n    __in        WINDIVERT_LAYER layer,\n    __out       char *buffer,\n    __in        UINT bufLen);\n\n/*\n * Byte ordering.\n */\nWINDIVERTEXPORT UINT16 WinDivertHelperNtohs(\n    __in        UINT16 x);\nWINDIVERTEXPORT UINT16 WinDivertHelperHtons(\n    __in        UINT16 x);\nWINDIVERTEXPORT UINT32 WinDivertHelperNtohl(\n    __in        UINT32 x);\nWINDIVERTEXPORT UINT32 WinDivertHelperHtonl(\n    __in        UINT32 x);\nWINDIVERTEXPORT UINT64 WinDivertHelperNtohll(\n    __in        UINT64 x);\nWINDIVERTEXPORT UINT64 WinDivertHelperHtonll(\n    __in        UINT64 x);\nWINDIVERTEXPORT void WinDivertHelperNtohIPv6Address(\n    __in        const UINT *inAddr,\n    __out       UINT *outAddr);\nWINDIVERTEXPORT void WinDivertHelperHtonIPv6Address(\n    __in        const UINT *inAddr,\n    __out       UINT *outAddr);\n\n/*\n * Old names to be removed in the next version.\n */\nWINDIVERTEXPORT void WinDivertHelperNtohIpv6Address(\n    __in        const UINT *inAddr,\n    __out       UINT *outAddr);\nWINDIVERTEXPORT void WinDivertHelperHtonIpv6Address(\n    __in        const UINT *inAddr,\n    __out       UINT *outAddr);\n\n#endif      /* WINDIVERT_KERNEL */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif      /* __WINDIVERT_H */\n"
  },
  {
    "path": "tmp/.keep",
    "content": ""
  },
  {
    "path": "tpws/BSDmakefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto\nLIBS = -lz -lpthread\nSRC_FILES = *.c\n\nall: tpws\n\ntpws: $(SRC_FILES)\n\t$(CC) $(CFLAGS) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS)\n\nclean:\n\trm -f tpws *.o\n"
  },
  {
    "path": "tpws/Makefile",
    "content": "CC ?= cc\nOPTIMIZE ?= -Os\nCFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -ffunction-sections -fdata-sections\nCFLAGS_SYSTEMD = -DUSE_SYSTEMD\nCFLAGS_BSD = -Wno-address-of-packed-member\nLDFLAGS += -flto=auto\nLDFLAGS_ANDROID = -Wl,--gc-sections -llog\nLDFLAGS_BSD = -Wl,--gc-sections\nLDFLAGS_LINUX = -Wl,--gc-sections\nLDFLAGS_MAC = -Wl,-dead_strip\nLIBS = -lz -lpthread\nLIBS_SYSTEMD = -lsystemd\nLIBS_ANDROID = -lz\nSRC_FILES = *.c\nSRC_FILES_ANDROID = $(SRC_FILES) andr/*.c\n\nall: tpws\n\ntpws: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o tpws $(SRC_FILES) $(LIBS) $(LDFLAGS) $(LDFLAGS_LINUX)\n\nsystemd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o tpws $(SRC_FILES) $(LIBS) $(LIBS_SYSTEMD) $(LDFLAGS) $(LDFLAGS_LINUX)\n\nandroid: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) -o tpws $(SRC_FILES_ANDROID) $(LIBS_ANDROID) $(LDFLAGS) $(LDFLAGS_ANDROID)\n\nbsd: $(SRC_FILES)\n\t$(CC) -s $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) $(LDFLAGS_BSD)\n\nmac: $(SRC_FILES)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsa -target arm64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) $(LDFLAGS_MAC)\n\t$(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsx -target x86_64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) $(LDFLAGS_MAC)\n\tstrip tpwsa tpwsx\n\tlipo -create -output tpws tpwsx tpwsa\n\trm -f tpwsx tpwsa\n\nclean:\n\trm -f tpws *.o\n"
  },
  {
    "path": "tpws/andr/_musl_license.txt",
    "content": "Code in this dir is taken from musl libc to support old android versions <7.0\n\nmusl as a whole is licensed under the following standard MIT license:\n\n----------------------------------------------------------------------\nCopyright  2005-2020 Rich Felker, et al.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n----------------------------------------------------------------------\n"
  },
  {
    "path": "tpws/andr/getifaddrs.c",
    "content": "#define _GNU_SOURCE\n#include <errno.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <ifaddrs.h>\n#include <syscall.h>\n#include <net/if.h>\n#include <netinet/in.h>\n#include \"netlink.h\"\n\n#define IFADDRS_HASH_SIZE 64\n\n/* getifaddrs() reports hardware addresses with PF_PACKET that implies\n * struct sockaddr_ll.  But e.g. Infiniband socket address length is\n * longer than sockaddr_ll.ssl_addr[8] can hold. Use this hack struct\n * to extend ssl_addr - callers should be able to still use it. */\nstruct sockaddr_ll_hack {\n\tunsigned short sll_family, sll_protocol;\n\tint sll_ifindex;\n\tunsigned short sll_hatype;\n\tunsigned char sll_pkttype, sll_halen;\n\tunsigned char sll_addr[24];\n};\n\nunion sockany {\n\tstruct sockaddr sa;\n\tstruct sockaddr_ll_hack ll;\n\tstruct sockaddr_in v4;\n\tstruct sockaddr_in6 v6;\n};\n\nstruct ifaddrs_storage {\n\tstruct ifaddrs ifa;\n\tstruct ifaddrs_storage *hash_next;\n\tunion sockany addr, netmask, ifu;\n\tunsigned int index;\n\tchar name[IFNAMSIZ+1];\n};\n\nstruct ifaddrs_ctx {\n\tstruct ifaddrs *first;\n\tstruct ifaddrs *last;\n\tstruct ifaddrs_storage *hash[IFADDRS_HASH_SIZE];\n};\n\nvoid freeifaddrs(struct ifaddrs *ifp)\n{\n\tstruct ifaddrs *n;\n\twhile (ifp) {\n\t\tn = ifp->ifa_next;\n\t\tfree(ifp);\n\t\tifp = n;\n\t}\n}\n\nstatic void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex)\n{\n\tuint8_t *dst;\n\tint len;\n\n\tswitch (af) {\n\tcase AF_INET:\n\t\tdst = (uint8_t*) &sa->v4.sin_addr;\n\t\tlen = 4;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tdst = (uint8_t*) &sa->v6.sin6_addr;\n\t\tlen = 16;\n\t\tif (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr))\n\t\t\tsa->v6.sin6_scope_id = ifindex;\n\t\tbreak;\n\tdefault:\n\t\treturn;\n\t}\n\tif (addrlen < len) return;\n\tsa->sa.sa_family = af;\n\tmemcpy(dst, addr, len);\n\t*r = &sa->sa;\n}\n\nstatic void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen)\n{\n\tuint8_t addr[16] = {0};\n\tint i;\n\n\tif (prefixlen > 8*sizeof(addr)) prefixlen = 8*sizeof(addr);\n\ti = prefixlen / 8;\n\tmemset(addr, 0xff, i);\n\tif (i < sizeof(addr)) addr[i++] = 0xff << (8 - (prefixlen % 8));\n\tcopy_addr(r, af, sa, addr, sizeof(addr), 0);\n}\n\nstatic void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype)\n{\n\tif (addrlen > sizeof(sa->ll.sll_addr)) return;\n\tsa->ll.sll_family = AF_PACKET;\n\tsa->ll.sll_ifindex = ifindex;\n\tsa->ll.sll_hatype = hatype;\n\tsa->ll.sll_halen = addrlen;\n\tmemcpy(sa->ll.sll_addr, addr, addrlen);\n\t*r = &sa->sa;\n}\n\nstatic int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h)\n{\n\tstruct ifaddrs_ctx *ctx = pctx;\n\tstruct ifaddrs_storage *ifs, *ifs0;\n\tstruct ifinfomsg *ifi = NLMSG_DATA(h);\n\tstruct ifaddrmsg *ifa = NLMSG_DATA(h);\n\tstruct rtattr *rta;\n\tint stats_len = 0;\n\n\tif (h->nlmsg_type == RTM_NEWLINK) {\n\t\tfor (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {\n\t\t\tif (rta->rta_type != IFLA_STATS) continue;\n\t\t\tstats_len = RTA_DATALEN(rta);\n\t\t\tbreak;\n\t\t}\n\t} else {\n\t\tfor (ifs0 = ctx->hash[ifa->ifa_index % IFADDRS_HASH_SIZE]; ifs0; ifs0 = ifs0->hash_next)\n\t\t\tif (ifs0->index == ifa->ifa_index)\n\t\t\t\tbreak;\n\t\tif (!ifs0) return 0;\n\t}\n\n\tifs = calloc(1, sizeof(struct ifaddrs_storage) + stats_len);\n\tif (ifs == 0) return -1;\n\n\tif (h->nlmsg_type == RTM_NEWLINK) {\n\t\tifs->index = ifi->ifi_index;\n\t\tifs->ifa.ifa_flags = ifi->ifi_flags;\n\n\t\tfor (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {\n\t\t\tswitch (rta->rta_type) {\n\t\t\tcase IFLA_IFNAME:\n\t\t\t\tif (RTA_DATALEN(rta) < sizeof(ifs->name)) {\n\t\t\t\t\tmemcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta));\n\t\t\t\t\tifs->ifa.ifa_name = ifs->name;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase IFLA_ADDRESS:\n\t\t\t\tcopy_lladdr(&ifs->ifa.ifa_addr, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type);\n\t\t\t\tbreak;\n\t\t\tcase IFLA_BROADCAST:\n\t\t\t\tcopy_lladdr(&ifs->ifa.ifa_broadaddr, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type);\n\t\t\t\tbreak;\n\t\t\tcase IFLA_STATS:\n\t\t\t\tifs->ifa.ifa_data = (void*)(ifs+1);\n\t\t\t\tmemcpy(ifs->ifa.ifa_data, RTA_DATA(rta), RTA_DATALEN(rta));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (ifs->ifa.ifa_name) {\n\t\t\tunsigned int bucket = ifs->index % IFADDRS_HASH_SIZE;\n\t\t\tifs->hash_next = ctx->hash[bucket];\n\t\t\tctx->hash[bucket] = ifs;\n\t\t}\n\t} else {\n\t\tifs->ifa.ifa_name = ifs0->ifa.ifa_name;\n\t\tifs->ifa.ifa_flags = ifs0->ifa.ifa_flags;\n\t\tfor (rta = NLMSG_RTA(h, sizeof(*ifa)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {\n\t\t\tswitch (rta->rta_type) {\n\t\t\tcase IFA_ADDRESS:\n\t\t\t\t/* If ifa_addr is already set we, received an IFA_LOCAL before\n\t\t\t\t * so treat this as destination address */\n\t\t\t\tif (ifs->ifa.ifa_addr)\n\t\t\t\t\tcopy_addr(&ifs->ifa.ifa_dstaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);\n\t\t\t\telse\n\t\t\t\t\tcopy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);\n\t\t\t\tbreak;\n\t\t\tcase IFA_BROADCAST:\n\t\t\t\tcopy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);\n\t\t\t\tbreak;\n\t\t\tcase IFA_LOCAL:\n\t\t\t\t/* If ifa_addr is set and we get IFA_LOCAL, assume we have\n\t\t\t\t * a point-to-point network. Move address to correct field. */\n\t\t\t\tif (ifs->ifa.ifa_addr) {\n\t\t\t\t\tifs->ifu = ifs->addr;\n\t\t\t\t\tifs->ifa.ifa_dstaddr = &ifs->ifu.sa;\n\t\t\t\t\tmemset(&ifs->addr, 0, sizeof(ifs->addr));\n\t\t\t\t}\n\t\t\t\tcopy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);\n\t\t\t\tbreak;\n\t\t\tcase IFA_LABEL:\n\t\t\t\tif (RTA_DATALEN(rta) < sizeof(ifs->name)) {\n\t\t\t\t\tmemcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta));\n\t\t\t\t\tifs->ifa.ifa_name = ifs->name;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (ifs->ifa.ifa_addr)\n\t\t\tgen_netmask(&ifs->ifa.ifa_netmask, ifa->ifa_family, &ifs->netmask, ifa->ifa_prefixlen);\n\t}\n\n\tif (ifs->ifa.ifa_name) {\n\t\tif (!ctx->first) ctx->first = &ifs->ifa;\n\t\tif (ctx->last) ctx->last->ifa_next = &ifs->ifa;\n\t\tctx->last = &ifs->ifa;\n\t} else {\n\t\tfree(ifs);\n\t}\n\treturn 0;\n}\n\nint getifaddrs(struct ifaddrs **ifap)\n{\n\tstruct ifaddrs_ctx _ctx, *ctx = &_ctx;\n\tint r;\n\tmemset(ctx, 0, sizeof *ctx);\n\tr = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx);\n\tif (r == 0) *ifap = ctx->first;\n\telse freeifaddrs(ctx->first);\n\treturn r;\n}\n"
  },
  {
    "path": "tpws/andr/ifaddrs.h",
    "content": "#pragma once\n\n#include <ifaddrs.h>\n\n#if __ANDROID_API__ < 24\nvoid freeifaddrs(struct ifaddrs *);\nint getifaddrs(struct ifaddrs **);\n#endif\n"
  },
  {
    "path": "tpws/andr/netlink.c",
    "content": "#include <errno.h>\n#include <string.h>\n#include <syscall.h>\n#include <sys/socket.h>\n#include <unistd.h>\n\n#include \"netlink.h\"\n\nstatic int __netlink_enumerate(int fd, unsigned int seq, int type, int af,\n\tint (*cb)(void *ctx, struct nlmsghdr *h), void *ctx)\n{\n\tstruct nlmsghdr *h;\n\tunion {\n\t\tuint8_t buf[8192];\n\t\tstruct {\n\t\t\tstruct nlmsghdr nlh;\n\t\t\tstruct rtgenmsg g;\n\t\t} req;\n\t\tstruct nlmsghdr reply;\n\t} u;\n\tint r, ret;\n\n\tmemset(&u.req, 0, sizeof(u.req));\n\tu.req.nlh.nlmsg_len = sizeof(u.req);\n\tu.req.nlh.nlmsg_type = type;\n\tu.req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;\n\tu.req.nlh.nlmsg_seq = seq;\n\tu.req.g.rtgen_family = af;\n\tr = send(fd, &u.req, sizeof(u.req), 0);\n\tif (r < 0) return r;\n\n\twhile (1) {\n\t\tr = recv(fd, u.buf, sizeof(u.buf), MSG_DONTWAIT);\n\t\tif (r <= 0) return -1;\n\t\tfor (h = &u.reply; NLMSG_OK(h, (void*)&u.buf[r]); h = NLMSG_NEXT(h)) {\n\t\t\tif (h->nlmsg_type == NLMSG_DONE) return 0;\n\t\t\tif (h->nlmsg_type == NLMSG_ERROR) return -1;\n\t\t\tret = cb(ctx, h);\n\t\t\tif (ret) return ret;\n\t\t}\n\t}\n}\n\nint __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx)\n{\n\tint fd, r;\n\n\tfd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);\n\tif (fd < 0) return -1;\n\tr = __netlink_enumerate(fd, 1, RTM_GETLINK, link_af, cb, ctx);\n\tif (!r) r = __netlink_enumerate(fd, 2, RTM_GETADDR, addr_af, cb, ctx);\n\tclose(fd);\n\treturn r;\n}\n"
  },
  {
    "path": "tpws/andr/netlink.h",
    "content": "#include <stdint.h>\n\n/* linux/netlink.h */\n\n#define NETLINK_ROUTE 0\n\nstruct nlmsghdr {\n\tuint32_t\tnlmsg_len;\n\tuint16_t\tnlmsg_type;\n\tuint16_t\tnlmsg_flags;\n\tuint32_t\tnlmsg_seq;\n\tuint32_t\tnlmsg_pid;\n};\n\n#define NLM_F_REQUEST\t1\n#define NLM_F_MULTI\t2\n#define NLM_F_ACK\t4\n\n#define NLM_F_ROOT\t0x100\n#define NLM_F_MATCH\t0x200\n#define NLM_F_ATOMIC\t0x400\n#define NLM_F_DUMP\t(NLM_F_ROOT|NLM_F_MATCH)\n\n#define NLMSG_NOOP\t0x1\n#define NLMSG_ERROR\t0x2\n#define NLMSG_DONE\t0x3\n#define NLMSG_OVERRUN\t0x4\n\n/* linux/rtnetlink.h */\n\n#define RTM_NEWLINK\t16\n#define RTM_GETLINK\t18\n#define RTM_NEWADDR\t20\n#define RTM_GETADDR\t22\n\nstruct rtattr {\n\tunsigned short\trta_len;\n\tunsigned short\trta_type;\n};\n\nstruct rtgenmsg {\n\tunsigned char\trtgen_family;\n};\n\nstruct ifinfomsg {\n\tunsigned char\tifi_family;\n\tunsigned char\t__ifi_pad;\n\tunsigned short\tifi_type;\n\tint\t\tifi_index;\n\tunsigned\tifi_flags;\n\tunsigned\tifi_change;\n};\n\n/* linux/if_link.h */\n\n#define IFLA_ADDRESS\t1\n#define IFLA_BROADCAST\t2\n#define IFLA_IFNAME\t3\n#define IFLA_STATS\t7\n\n/* linux/if_addr.h */\n\nstruct ifaddrmsg {\n\tuint8_t\t\tifa_family;\n\tuint8_t\t\tifa_prefixlen;\n\tuint8_t\t\tifa_flags;\n\tuint8_t\t\tifa_scope;\n\tuint32_t\tifa_index;\n};\n\n#define IFA_ADDRESS\t1\n#define IFA_LOCAL\t2\n#define IFA_LABEL\t3\n#define IFA_BROADCAST\t4\n\n/* musl */\n\n#define NETLINK_ALIGN(len)\t(((len)+3) & ~3)\n#define NLMSG_DATA(nlh)\t\t((void*)((char*)(nlh)+sizeof(struct nlmsghdr)))\n#define NLMSG_DATALEN(nlh)\t((nlh)->nlmsg_len-sizeof(struct nlmsghdr))\n#define NLMSG_DATAEND(nlh)\t((char*)(nlh)+(nlh)->nlmsg_len)\n#define NLMSG_NEXT(nlh)\t\t(struct nlmsghdr*)((char*)(nlh)+NETLINK_ALIGN((nlh)->nlmsg_len))\n#define NLMSG_OK(nlh,end)\t((char*)(end)-(char*)(nlh) >= sizeof(struct nlmsghdr))\n\n#define RTA_DATA(rta)\t\t((void*)((char*)(rta)+sizeof(struct rtattr)))\n#define RTA_DATALEN(rta)\t((rta)->rta_len-sizeof(struct rtattr))\n#define RTA_DATAEND(rta)\t((char*)(rta)+(rta)->rta_len)\n#define RTA_NEXT(rta)\t\t(struct rtattr*)((char*)(rta)+NETLINK_ALIGN((rta)->rta_len))\n#define RTA_OK(rta,end)\t\t((char*)(end)-(char*)(rta) >= sizeof(struct rtattr))\n\n#define NLMSG_RTA(nlh,len)\t((void*)((char*)(nlh)+sizeof(struct nlmsghdr)+NETLINK_ALIGN(len)))\n#define NLMSG_RTAOK(rta,nlh)\tRTA_OK(rta,NLMSG_DATAEND(nlh))\n\nint __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx);\n"
  },
  {
    "path": "tpws/epoll-shim/include/sys/epoll.h",
    "content": "#ifndef\tSHIM_SYS_EPOLL_H\n#define\tSHIM_SYS_EPOLL_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdint.h>\n#include <sys/types.h>\n#include <fcntl.h>\n\n#if defined(__NetBSD__)\n#include <sys/sigtypes.h>\n#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__APPLE__)\n#include <sys/signal.h>\n#endif\n\n#define EPOLL_CLOEXEC O_CLOEXEC\n#define EPOLL_NONBLOCK O_NONBLOCK\n\nenum EPOLL_EVENTS { __EPOLL_DUMMY };\n#define EPOLLIN 0x001\n#define EPOLLPRI 0x002\n#define EPOLLOUT 0x004\n#define EPOLLRDNORM 0x040\n#define EPOLLNVAL 0x020\n#define EPOLLRDBAND 0x080\n#define EPOLLWRNORM 0x100\n#define EPOLLWRBAND 0x200\n#define EPOLLMSG 0x400\n#define EPOLLERR 0x008\n#define EPOLLHUP 0x010\n#define EPOLLRDHUP 0x2000\n#define EPOLLEXCLUSIVE (1U<<28)\n#define EPOLLWAKEUP (1U<<29)\n#define EPOLLONESHOT (1U<<30)\n#define EPOLLET (1U<<31)\n\n#define EPOLL_CTL_ADD 1\n#define EPOLL_CTL_DEL 2\n#define EPOLL_CTL_MOD 3\n\ntypedef union epoll_data {\n\tvoid *ptr;\n\tint fd;\n\tuint32_t u32;\n\tuint64_t u64;\n} epoll_data_t;\n\nstruct epoll_event {\n\tuint32_t events;\n\tepoll_data_t data;\n}\n#ifdef __x86_64__\n__attribute__ ((__packed__))\n#endif\n;\n\n\nint epoll_create(int);\nint epoll_create1(int);\nint epoll_ctl(int, int, int, struct epoll_event *);\nint epoll_wait(int, struct epoll_event *, int, int);\nint epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);\n\n\n#ifndef SHIM_SYS_SHIM_HELPERS\n#define SHIM_SYS_SHIM_HELPERS\n#include <unistd.h> /* IWYU pragma: keep */\n\nextern int epoll_shim_close(int);\n#define close epoll_shim_close\n#endif\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* sys/epoll.h */\n"
  },
  {
    "path": "tpws/epoll-shim/src/epoll.c",
    "content": "#include <sys/epoll.h>\n\n#include <sys/event.h>\n#include <sys/param.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#include <assert.h>\n#include <errno.h>\n#include <poll.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"epoll_shim_ctx.h\"\n\n#ifdef __NetBSD__\n#define ppoll pollts\n#endif\n\n// TODO(jan): Remove this once the definition is exposed in <sys/time.h> in\n// all supported FreeBSD versions.\n#ifndef timespecsub\n#define timespecsub(tsp, usp, vsp)                                            \\\n\tdo {                                                                  \\\n\t\t(vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec;                \\\n\t\t(vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec;             \\\n\t\tif ((vsp)->tv_nsec < 0) {                                     \\\n\t\t\t(vsp)->tv_sec--;                                      \\\n\t\t\t(vsp)->tv_nsec += 1000000000L;                        \\\n\t\t}                                                             \\\n\t} while (0)\n#endif\n\nstatic errno_t\nepollfd_close(FDContextMapNode *node)\n{\n\treturn epollfd_ctx_terminate(&node->ctx.epollfd);\n}\n\nstatic FDContextVTable const epollfd_vtable = {\n    .read_fun = fd_context_default_read,\n    .write_fun = fd_context_default_write,\n    .close_fun = epollfd_close,\n};\n\nstatic FDContextMapNode *\nepoll_create_impl(errno_t *ec)\n{\n\tFDContextMapNode *node;\n\n\tnode = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec);\n\tif (!node) {\n\t\treturn NULL;\n\t}\n\n\tnode->flags = 0;\n\n\tif ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/\n\t\t node->fd)) != 0) {\n\t\tgoto fail;\n\t}\n\n\tnode->vtable = &epollfd_vtable;\n\treturn node;\n\nfail:\n\tepoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node);\n\t(void)fd_context_map_node_destroy(node);\n\treturn NULL;\n}\n\nstatic int\nepoll_create_common(void)\n{\n\tFDContextMapNode *node;\n\terrno_t ec;\n\n\tnode = epoll_create_impl(&ec);\n\tif (!node) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn node->fd;\n}\n\nint\nepoll_create(int size)\n{\n\tif (size <= 0) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\treturn epoll_create_common();\n}\n\nint\nepoll_create1(int flags)\n{\n\tif (flags & ~EPOLL_CLOEXEC) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\treturn epoll_create_common();\n}\n\nstatic errno_t\nepoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev)\n{\n\tif (!ev && op != EPOLL_CTL_DEL) {\n\t\treturn EFAULT;\n\t}\n\n\tFDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);\n\tif (!node || node->vtable != &epollfd_vtable) {\n\t\tstruct stat sb;\n\t\treturn (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL;\n\t}\n\n\treturn epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev);\n}\n\nint\nepoll_ctl(int fd, int op, int fd2, struct epoll_event *ev)\n{\n\terrno_t ec = epoll_ctl_impl(fd, op, fd2, ev);\n\tif (ec != 0) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic bool\nis_no_wait_deadline(struct timespec const *deadline)\n{\n\treturn (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0);\n}\n\nstatic errno_t\nepollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,\n    int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs)\n{\n\terrno_t ec;\n\n\tfor (;;) {\n\t\tif ((ec = epollfd_ctx_wait(epollfd, /**/\n\t\t\t ev, cnt, actual_cnt)) != 0) {\n\t\t\treturn ec;\n\t\t}\n\n\t\tif (*actual_cnt || is_no_wait_deadline(deadline)) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tstruct timespec timeout;\n\n\t\tif (deadline) {\n\t\t\tstruct timespec current_time;\n\n\t\t\tif (clock_gettime(CLOCK_MONOTONIC, /**/\n\t\t\t\t&current_time) < 0) {\n\t\t\t\treturn errno;\n\t\t\t}\n\n\t\t\ttimespecsub(deadline, &current_time, &timeout);\n\t\t\tif (timeout.tv_sec < 0 ||\n\t\t\t    is_no_wait_deadline(&timeout)) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\t(void)pthread_mutex_lock(&epollfd->mutex);\n\n\t\tnfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size);\n\n\t\tsize_t size;\n\t\tif (__builtin_mul_overflow(nfds, sizeof(struct pollfd),\n\t\t\t&size)) {\n\t\t\tec = ENOMEM;\n\t\t\t(void)pthread_mutex_unlock(&epollfd->mutex);\n\t\t\treturn ec;\n\t\t}\n\n\t\tstruct pollfd *pfds = malloc(size);\n\t\tif (!pfds) {\n\t\t\tec = errno;\n\t\t\t(void)pthread_mutex_unlock(&epollfd->mutex);\n\t\t\treturn ec;\n\t\t}\n\n\t\tepollfd_ctx_fill_pollfds(epollfd, pfds);\n\n\t\t(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);\n\t\t++epollfd->nr_polling_threads;\n\t\t(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);\n\n\t\t(void)pthread_mutex_unlock(&epollfd->mutex);\n\n\t\t/*\n\t\t * This surfaced a race condition when\n\t\t * registering/unregistering poll-only fds. The tests should\n\t\t * still succeed if this is enabled.\n\t\t */\n#if 0\n\t\tusleep(500000);\n#endif\n\n\t\tint n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs);\n\t\tif (n < 0) {\n\t\t\tec = errno;\n\t\t}\n\n\t\tfree(pfds);\n\n\t\t(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);\n\t\t--epollfd->nr_polling_threads;\n\t\tif (epollfd->nr_polling_threads == 0) {\n\t\t\t(void)pthread_cond_signal(\n\t\t\t    &epollfd->nr_polling_threads_cond);\n\t\t}\n\t\t(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);\n\n\t\tif (n < 0) {\n\t\t\treturn ec;\n\t\t}\n\t}\n}\n\nstatic errno_t\ntimeout_to_deadline(struct timespec *deadline, int to)\n{\n\tassert(to >= 0);\n\n\tif (to == 0) {\n\t\t*deadline = (struct timespec){0, 0};\n\t} else if (to > 0) {\n\t\tif (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) {\n\t\t\treturn errno;\n\t\t}\n\n\t\tif (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1,\n\t\t\t&deadline->tv_sec)) {\n\t\t\treturn EINVAL;\n\t\t}\n\t\tdeadline->tv_sec -= 1;\n\n\t\tdeadline->tv_nsec += (to % 1000) * 1000000L;\n\t\tif (deadline->tv_nsec >= 1000000000) {\n\t\t\tdeadline->tv_nsec -= 1000000000;\n\t\t\tdeadline->tv_sec += 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic errno_t\nepoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to,\n    sigset_t const *sigs, int *actual_cnt)\n{\n\tif (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) {\n\t\treturn EINVAL;\n\t}\n\n\tFDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);\n\tif (!node || node->vtable != &epollfd_vtable) {\n\t\tstruct stat sb;\n\t\treturn (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL;\n\t}\n\n\tstruct timespec deadline;\n\terrno_t ec;\n\tif (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) {\n\t\treturn ec;\n\t}\n\n\treturn epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt,\n\t    actual_cnt, (to >= 0) ? &deadline : NULL, sigs);\n}\n\nint\nepoll_pwait(int fd, struct epoll_event *ev, int cnt, int to,\n    sigset_t const *sigs)\n{\n\tint actual_cnt;\n\n\terrno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt);\n\tif (ec != 0) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn actual_cnt;\n}\n\nint\nepoll_wait(int fd, struct epoll_event *ev, int cnt, int to)\n{\n\treturn epoll_pwait(fd, ev, cnt, to, NULL);\n}\n"
  },
  {
    "path": "tpws/epoll-shim/src/epoll_shim_ctx.c",
    "content": "#include \"epoll_shim_ctx.h\"\n\n#include <sys/event.h>\n\n#include <assert.h>\n#include <errno.h>\n#include <limits.h>\n#include <stdlib.h>\n\nstatic void\nfd_context_map_node_init(FDContextMapNode *node, int kq)\n{\n\tnode->fd = kq;\n\tnode->vtable = NULL;\n}\n\nstatic FDContextMapNode *\nfd_context_map_node_create(int kq, errno_t *ec)\n{\n\tFDContextMapNode *node;\n\n\tnode = malloc(sizeof(FDContextMapNode));\n\tif (!node) {\n\t\t*ec = errno;\n\t\treturn NULL;\n\t}\n\n\tfd_context_map_node_init(node, kq);\n\treturn node;\n}\n\nstatic errno_t\nfd_context_map_node_terminate(FDContextMapNode *node, bool close_fd)\n{\n\terrno_t ec = node->vtable ? node->vtable->close_fun(node) : 0;\n\n\tif (close_fd && close(node->fd) < 0) {\n\t\tec = ec ? ec : errno;\n\t}\n\n\treturn ec;\n}\n\nerrno_t\nfd_context_map_node_destroy(FDContextMapNode *node)\n{\n\terrno_t ec = fd_context_map_node_terminate(node, true);\n\tfree(node);\n\treturn ec;\n}\n\n/**/\n\nerrno_t\nfd_context_default_read(FDContextMapNode *node, /**/\n    void *buf, size_t nbytes, size_t *bytes_transferred)\n{\n\t(void)node;\n\t(void)buf;\n\t(void)nbytes;\n\t(void)bytes_transferred;\n\n\treturn EINVAL;\n}\n\nerrno_t\nfd_context_default_write(FDContextMapNode *node, /**/\n    void const *buf, size_t nbytes, size_t *bytes_transferred)\n{\n\t(void)node;\n\t(void)buf;\n\t(void)nbytes;\n\t(void)bytes_transferred;\n\n\treturn EINVAL;\n}\n\n/**/\n\nstatic int\nfd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2)\n{\n\treturn (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd);\n}\n\nRB_PROTOTYPE_STATIC(fd_context_map_, fd_context_map_node_, entry,\n    fd_context_map_node_cmp);\nRB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry,\n    fd_context_map_node_cmp);\n\nEpollShimCtx epoll_shim_ctx = {\n    .fd_context_map = RB_INITIALIZER(&fd_context_map),\n    .mutex = PTHREAD_MUTEX_INITIALIZER,\n};\n\nstatic FDContextMapNode *\nepoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq,\n    errno_t *ec)\n{\n\tFDContextMapNode *node;\n\n\t{\n\t\tFDContextMapNode find;\n\t\tfind.fd = kq;\n\n\t\tnode = RB_FIND(fd_context_map_, /**/\n\t\t    &epoll_shim_ctx->fd_context_map, &find);\n\t}\n\n\tif (node) {\n\t\t/*\n\t\t * If we get here, someone must have already closed the old fd\n\t\t * with a normal 'close()' call, i.e. not with our\n\t\t * 'epoll_shim_close()' wrapper. The fd inside the node\n\t\t * refers now to the new kq we are currently creating. We\n\t\t * must not close it, but we must clean up the old context\n\t\t * object!\n\t\t */\n\t\t(void)fd_context_map_node_terminate(node, false);\n\t\tfd_context_map_node_init(node, kq);\n\t} else {\n\t\tnode = fd_context_map_node_create(kq, ec);\n\t\tif (!node) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\tvoid *colliding_node = RB_INSERT(fd_context_map_,\n\t\t    &epoll_shim_ctx->fd_context_map, node);\n\t\t(void)colliding_node;\n\t\tassert(colliding_node == NULL);\n\t}\n\n\treturn node;\n}\n\nFDContextMapNode *\nepoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec)\n{\n\tFDContextMapNode *node;\n\n\tint kq = kqueue();\n\tif (kq < 0) {\n\t\t*ec = errno;\n\t\treturn NULL;\n\t}\n\n\t(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);\n\tnode = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec);\n\t(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);\n\n\tif (!node) {\n\t\tclose(kq);\n\t}\n\n\treturn node;\n}\n\nstatic FDContextMapNode *\nepoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd)\n{\n\tFDContextMapNode *node;\n\n\tFDContextMapNode find;\n\tfind.fd = fd;\n\n\tnode = RB_FIND(fd_context_map_, /**/\n\t    &epoll_shim_ctx->fd_context_map, &find);\n\n\treturn node;\n}\n\nFDContextMapNode *\nepoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd)\n{\n\tFDContextMapNode *node;\n\n\t(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);\n\tnode = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd);\n\t(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);\n\n\treturn node;\n}\n\nFDContextMapNode *\nepoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd)\n{\n\tFDContextMapNode *node;\n\n\t(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);\n\tnode = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd);\n\tif (node) {\n\t\tRB_REMOVE(fd_context_map_, /**/\n\t\t    &epoll_shim_ctx->fd_context_map, node);\n\t}\n\t(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);\n\n\treturn node;\n}\n\nvoid\nepoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx,\n    FDContextMapNode *node)\n{\n\t(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);\n\tRB_REMOVE(fd_context_map_, /**/\n\t    &epoll_shim_ctx->fd_context_map, node);\n\t(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);\n}\n\n/**/\n\nint\nepoll_shim_close(int fd)\n{\n\tFDContextMapNode *node;\n\n\tnode = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd);\n\tif (!node) {\n\t\treturn close(fd);\n\t}\n\n\terrno_t ec = fd_context_map_node_destroy(node);\n\tif (ec != 0) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nssize_t\nepoll_shim_read(int fd, void *buf, size_t nbytes)\n{\n\tFDContextMapNode *node;\n\n\tnode = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);\n\tif (!node) {\n\t\treturn read(fd, buf, nbytes);\n\t}\n\n\tif (nbytes > SSIZE_MAX) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tsize_t bytes_transferred;\n\terrno_t ec = node->vtable->read_fun(node, /**/\n\t    buf, nbytes, &bytes_transferred);\n\tif (ec != 0) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn (ssize_t)bytes_transferred;\n}\n\nssize_t\nepoll_shim_write(int fd, void const *buf, size_t nbytes)\n{\n\tFDContextMapNode *node;\n\n\tnode = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);\n\tif (!node) {\n\t\treturn write(fd, buf, nbytes);\n\t}\n\n\tif (nbytes > SSIZE_MAX) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tsize_t bytes_transferred;\n\terrno_t ec = node->vtable->write_fun(node, /**/\n\t    buf, nbytes, &bytes_transferred);\n\tif (ec != 0) {\n\t\terrno = ec;\n\t\treturn -1;\n\t}\n\n\treturn (ssize_t)bytes_transferred;\n}\n"
  },
  {
    "path": "tpws/epoll-shim/src/epoll_shim_ctx.h",
    "content": "#ifndef EPOLL_SHIM_CTX_H_\n#define EPOLL_SHIM_CTX_H_\n\n#include \"fix.h\"\n\n#include <sys/tree.h>\n\n#include <unistd.h>\n\n#include \"epollfd_ctx.h\"\n#include \"eventfd_ctx.h\"\n#include \"signalfd_ctx.h\"\n#include \"timerfd_ctx.h\"\n\nstruct fd_context_map_node_;\ntypedef struct fd_context_map_node_ FDContextMapNode;\n\ntypedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/\n    void *buf, size_t nbytes, size_t *bytes_transferred);\ntypedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/\n    const void *buf, size_t nbytes, size_t *bytes_transferred);\ntypedef errno_t (*fd_context_close_fun)(FDContextMapNode *node);\n\ntypedef struct {\n\tfd_context_read_fun read_fun;\n\tfd_context_write_fun write_fun;\n\tfd_context_close_fun close_fun;\n} FDContextVTable;\n\nerrno_t fd_context_default_read(FDContextMapNode *node, /**/\n    void *buf, size_t nbytes, size_t *bytes_transferred);\nerrno_t fd_context_default_write(FDContextMapNode *node, /**/\n    void const *buf, size_t nbytes, size_t *bytes_transferred);\n\nstruct fd_context_map_node_ {\n\tRB_ENTRY(fd_context_map_node_) entry;\n\tint fd;\n\tint flags;\n\tunion {\n\t\tEpollFDCtx epollfd;\n\t\tEventFDCtx eventfd;\n\t\tTimerFDCtx timerfd;\n\t\tSignalFDCtx signalfd;\n\t} ctx;\n\tFDContextVTable const *vtable;\n};\n\nerrno_t fd_context_map_node_destroy(FDContextMapNode *node);\n\n/**/\n\ntypedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap;\n\ntypedef struct {\n\tFDContextMap fd_context_map;\n\tpthread_mutex_t mutex;\n} EpollShimCtx;\n\nextern EpollShimCtx epoll_shim_ctx;\n\nFDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx,\n    errno_t *ec);\nFDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx,\n    int fd);\nFDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx,\n    int fd);\nvoid epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx,\n    FDContextMapNode *node);\n\n/**/\n\nint epoll_shim_close(int fd);\nssize_t epoll_shim_read(int fd, void *buf, size_t nbytes);\nssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes);\n\n#endif\n"
  },
  {
    "path": "tpws/epoll-shim/src/epollfd_ctx.c",
    "content": "#include \"epollfd_ctx.h\"\n\n#include <sys/types.h>\n\n#if defined(__FreeBSD__)\n#include <sys/capsicum.h>\n#endif\n#include <sys/event.h>\n#include <sys/ioctl.h>\n#include <sys/queue.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n\n#if defined(__DragonFly__)\n/* For TAILQ_FOREACH_SAFE. */\n#include <netproto/802_11/ieee80211_dragonfly.h>\n#endif\n\n#include <assert.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <limits.h>\n#include <poll.h>\n#include <unistd.h>\n\nstatic RegisteredFDsNode *\nregistered_fds_node_create(int fd)\n{\n\tRegisteredFDsNode *node;\n\n\tnode = malloc(sizeof(*node));\n\tif (!node) {\n\t\treturn NULL;\n\t}\n\n\t*node = (RegisteredFDsNode){.fd = fd, .self_pipe = {-1, -1}};\n\n\treturn node;\n}\n\nstatic void\nregistered_fds_node_destroy(RegisteredFDsNode *node)\n{\n\tif (node->self_pipe[0] >= 0 && node->self_pipe[1] >= 0) {\n\t\t(void)close(node->self_pipe[0]);\n\t\t(void)close(node->self_pipe[1]);\n\t}\n\n\tfree(node);\n}\n\ntypedef struct {\n\tint evfilt_read;\n\tint evfilt_write;\n\tint evfilt_except;\n} NeededFilters;\n\nstatic NeededFilters\nget_needed_filters(RegisteredFDsNode *fd2_node)\n{\n\tNeededFilters needed_filters;\n\n\tneeded_filters.evfilt_except = 0;\n\n\tif (fd2_node->node_type == NODE_TYPE_FIFO) {\n\t\tif (fd2_node->node_data.fifo.readable &&\n\t\t    fd2_node->node_data.fifo.writable) {\n\t\t\tneeded_filters.evfilt_read = !!(\n\t\t\t    fd2_node->events & EPOLLIN);\n\t\t\tneeded_filters.evfilt_write = !!(\n\t\t\t    fd2_node->events & EPOLLOUT);\n\n\t\t\tif (fd2_node->events == 0) {\n\t\t\t\tneeded_filters.evfilt_read =\n\t\t\t\t    fd2_node->eof_state ? 1 : EV_CLEAR;\n\t\t\t}\n\n\t\t} else if (fd2_node->node_data.fifo.readable) {\n\t\t\tneeded_filters.evfilt_read = !!(\n\t\t\t    fd2_node->events & EPOLLIN);\n\t\t\tneeded_filters.evfilt_write = 0;\n\n\t\t\tif (needed_filters.evfilt_read == 0) {\n\t\t\t\tneeded_filters.evfilt_read =\n\t\t\t\t    fd2_node->eof_state ? 1 : EV_CLEAR;\n\t\t\t}\n\t\t} else if (fd2_node->node_data.fifo.writable) {\n\t\t\tneeded_filters.evfilt_read = 0;\n\t\t\tneeded_filters.evfilt_write = !!(\n\t\t\t    fd2_node->events & EPOLLOUT);\n\n\t\t\tif (needed_filters.evfilt_write == 0) {\n\t\t\t\tneeded_filters.evfilt_write =\n\t\t\t\t    fd2_node->eof_state ? 1 : EV_CLEAR;\n\t\t\t}\n\t\t} else {\n\t\t\t__builtin_unreachable();\n\t\t}\n\n\t\tgoto out;\n\t}\n\n\tif (fd2_node->node_type == NODE_TYPE_KQUEUE) {\n\t\tneeded_filters.evfilt_read = !!(fd2_node->events & EPOLLIN);\n\t\tneeded_filters.evfilt_write = 0;\n\n\t\tassert(fd2_node->eof_state == 0);\n\n\t\tif (needed_filters.evfilt_read == 0) {\n\t\t\tneeded_filters.evfilt_read = EV_CLEAR;\n\t\t}\n\n\t\tgoto out;\n\t}\n\n\tif (fd2_node->node_type == NODE_TYPE_SOCKET) {\n\t\tneeded_filters.evfilt_read = !!(fd2_node->events & EPOLLIN);\n\n\t\tif (needed_filters.evfilt_read == 0 &&\n\t\t    (fd2_node->events & EPOLLRDHUP)) {\n\t\t\tneeded_filters.evfilt_read = (fd2_node->eof_state &\n\t\t\t\t\t\t\t EOF_STATE_READ_EOF)\n\t\t\t    ? 1\n\t\t\t    : EV_CLEAR;\n\t\t}\n\n#ifdef EVFILT_EXCEPT\n\t\tneeded_filters.evfilt_except = !!(fd2_node->events & EPOLLPRI);\n#else\n\t\tif (needed_filters.evfilt_read == 0 &&\n\t\t    (fd2_node->events & EPOLLPRI)) {\n\t\t\tneeded_filters.evfilt_read = fd2_node->pollpri_active\n\t\t\t    ? 1\n\t\t\t    : EV_CLEAR;\n\t\t}\n#endif\n\n\t\tneeded_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT);\n\n\t\t/* Let's use EVFILT_READ to drive the POLLHUP. */\n\t\tif (fd2_node->eof_state ==\n\t\t    (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) {\n\t\t\tif (needed_filters.evfilt_read != 1 &&\n\t\t\t    needed_filters.evfilt_write != 1) {\n\t\t\t\tneeded_filters.evfilt_read = 1;\n\t\t\t}\n\n\t\t\tif (needed_filters.evfilt_read) {\n\t\t\t\tneeded_filters.evfilt_write = 0;\n\t\t\t} else {\n\t\t\t\tneeded_filters.evfilt_read = 0;\n\t\t\t}\n\t\t}\n\n\t\t/* We need something to detect POLLHUP. */\n\t\tif (fd2_node->eof_state == 0 &&\n\t\t    needed_filters.evfilt_read == 0 &&\n\t\t    needed_filters.evfilt_write == 0) {\n\t\t\tneeded_filters.evfilt_read = EV_CLEAR;\n\t\t}\n\n\t\tif (fd2_node->eof_state == EOF_STATE_READ_EOF) {\n\t\t\tif (needed_filters.evfilt_write == 0) {\n\t\t\t\tneeded_filters.evfilt_write = EV_CLEAR;\n\t\t\t}\n\t\t}\n\n\t\tif (fd2_node->eof_state == EOF_STATE_WRITE_EOF) {\n\t\t\tif (needed_filters.evfilt_read == 0) {\n\t\t\t\tneeded_filters.evfilt_read = EV_CLEAR;\n\t\t\t}\n\t\t}\n\n\t\tgoto out;\n\t}\n\n\tneeded_filters.evfilt_read = !!(fd2_node->events & EPOLLIN);\n\tneeded_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT);\n\n\tif (fd2_node->events == 0) {\n\t\tneeded_filters.evfilt_read = fd2_node->eof_state ? 1\n\t\t\t\t\t\t\t\t : EV_CLEAR;\n\t}\n\nout:\n\tif (fd2_node->is_edge_triggered) {\n\t\tif (needed_filters.evfilt_read) {\n\t\t\tneeded_filters.evfilt_read = EV_CLEAR;\n\t\t}\n\t\tif (needed_filters.evfilt_write) {\n\t\t\tneeded_filters.evfilt_write = EV_CLEAR;\n\t\t}\n\t\tif (needed_filters.evfilt_except) {\n\t\t\tneeded_filters.evfilt_except = EV_CLEAR;\n\t\t}\n\t}\n\n\tassert(needed_filters.evfilt_read || needed_filters.evfilt_write);\n\tassert(needed_filters.evfilt_read == 0 ||\n\t    needed_filters.evfilt_read == 1 ||\n\t    needed_filters.evfilt_read == EV_CLEAR);\n\tassert(needed_filters.evfilt_write == 0 ||\n\t    needed_filters.evfilt_write == 1 ||\n\t    needed_filters.evfilt_write == EV_CLEAR);\n\tassert(needed_filters.evfilt_except == 0 ||\n\t    needed_filters.evfilt_except == 1 ||\n\t    needed_filters.evfilt_except == EV_CLEAR);\n\n\treturn needed_filters;\n}\n\nstatic void\nregistered_fds_node_update_flags_from_epoll_event(RegisteredFDsNode *fd2_node,\n    struct epoll_event *ev)\n{\n\tfd2_node->events = ev->events &\n\t    (EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLOUT);\n\tfd2_node->data = ev->data;\n\tfd2_node->is_edge_triggered = ev->events & EPOLLET;\n\tfd2_node->is_oneshot = ev->events & EPOLLONESHOT;\n\n\tif (fd2_node->is_oneshot) {\n\t\tfd2_node->is_edge_triggered = true;\n\t}\n}\n\nstatic errno_t\nregistered_fds_node_add_self_trigger(RegisteredFDsNode *fd2_node,\n    EpollFDCtx *epollfd)\n{\n\tstruct kevent kevs[1];\n\n#ifdef EVFILT_USER\n\tEV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/\n\t    EV_ADD | EV_CLEAR, 0, 0, fd2_node);\n#else\n\tif (fd2_node->self_pipe[0] < 0 && fd2_node->self_pipe[1] < 0) {\n\t\tif (pipe2(fd2_node->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) {\n\t\t\terrno_t ec = errno;\n\t\t\tfd2_node->self_pipe[0] = fd2_node->self_pipe[1] = -1;\n\t\t\treturn ec;\n\t\t}\n\n\t\tassert(fd2_node->self_pipe[0] >= 0);\n\t\tassert(fd2_node->self_pipe[1] >= 0);\n\t}\n\n\tEV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/\n\t    EV_ADD | EV_CLEAR, 0, 0, fd2_node);\n#endif\n\n\tif (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) {\n\t\treturn errno;\n\t}\n\n\treturn 0;\n}\n\nstatic void\nregistered_fds_node_trigger_self(RegisteredFDsNode *fd2_node,\n    EpollFDCtx *epollfd)\n{\n#ifdef EVFILT_USER\n\tstruct kevent kevs[1];\n\tEV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/\n\t    0, NOTE_TRIGGER, 0, fd2_node);\n\t(void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL);\n#else\n\t(void)epollfd;\n\tassert(fd2_node->self_pipe[1] >= 0);\n\n\tchar c = 0;\n\t(void)write(fd2_node->self_pipe[1], &c, 1);\n#endif\n}\n\nstatic void\nregistered_fds_node_feed_event(RegisteredFDsNode *fd2_node,\n    EpollFDCtx *epollfd, struct kevent const *kev)\n{\n\tint revents = 0;\n\n\tif (fd2_node->node_type == NODE_TYPE_POLL) {\n\t\tassert(fd2_node->revents == 0);\n\n#ifdef EVFILT_USER\n\t\tassert(kev->filter == EVFILT_USER);\n#else\n\t\tchar c[32];\n\t\twhile (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) {\n\t\t}\n#endif\n\n\t\tstruct pollfd pfd = {\n\t\t    .fd = fd2_node->fd,\n\t\t    .events = (short)fd2_node->events,\n\t\t};\n\n\t\trevents = poll(&pfd, 1, 0) < 0 ? EPOLLERR : pfd.revents;\n\n\t\tfd2_node->revents = revents & POLLNVAL ? 0 : (uint32_t)revents;\n\t\tassert(!(fd2_node->revents &\n\t\t    ~(uint32_t)(POLLIN | POLLOUT | POLLERR | POLLHUP)));\n\t\treturn;\n\t}\n\n\tif (fd2_node->node_type == NODE_TYPE_FIFO &&\n#ifdef EVFILT_USER\n\t    kev->filter == EVFILT_USER\n#else\n\t    (fd2_node->self_pipe[0] >= 0 &&\n\t\tkev->ident == (uintptr_t)fd2_node->self_pipe[0])\n#endif\n\t) {\n\t\tassert(fd2_node->revents == 0);\n\n\t\tassert(!fd2_node->has_evfilt_read);\n\t\tassert(!fd2_node->has_evfilt_write);\n\t\tassert(!fd2_node->has_evfilt_except);\n\n\t\tNeededFilters needed_filters = get_needed_filters(fd2_node);\n\t\tassert(needed_filters.evfilt_write);\n\n\t\tstruct kevent nkev[1];\n\t\tEV_SET(&nkev[0], fd2_node->fd, EVFILT_WRITE,\n\t\t    EV_ADD | (needed_filters.evfilt_write & EV_CLEAR) |\n\t\t\tEV_RECEIPT,\n\t\t    0, 0, fd2_node);\n\n\t\tif (kevent(epollfd->kq, nkev, 1, nkev, 1, NULL) != 1 ||\n\t\t    nkev[0].data != 0) {\n\t\t\trevents = EPOLLERR | EPOLLOUT;\n\n\t\t\tif (!fd2_node->is_edge_triggered) {\n\t\t\t\tregistered_fds_node_trigger_self(fd2_node,\n\t\t\t\t    epollfd);\n\t\t\t}\n\n\t\t\tgoto out;\n\t\t} else {\n\t\t\tfd2_node->has_evfilt_write = true;\n\t\t\treturn;\n\t\t}\n\t}\n\n#ifdef EVFILT_EXCEPT\n\tassert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE ||\n\t    kev->filter == EVFILT_EXCEPT);\n#else\n\tassert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE);\n#endif\n\tassert((int)kev->ident == fd2_node->fd);\n\n\tif (kev->filter == EVFILT_READ) {\n\t\trevents |= EPOLLIN;\n#ifndef EVFILT_EXCEPT\n\t\tif (fd2_node->events & EPOLLPRI) {\n\t\t\tstruct pollfd pfd = {\n\t\t\t    .fd = fd2_node->fd,\n\t\t\t    .events = POLLPRI,\n\t\t\t};\n\n\t\t\tif ((poll(&pfd, 1, 0) == 1) &&\n\t\t\t    (pfd.revents & POLLPRI)) {\n\t\t\t\trevents |= EPOLLPRI;\n\t\t\t\tfd2_node->pollpri_active = true;\n\t\t\t} else {\n\t\t\t\tfd2_node->pollpri_active = false;\n\t\t\t}\n\t\t}\n#endif\n\t} else if (kev->filter == EVFILT_WRITE) {\n\t\trevents |= EPOLLOUT;\n\t}\n#ifdef EVFILT_EXCEPT\n\telse if (kev->filter == EVFILT_EXCEPT) {\n\t\tassert((kev->fflags & NOTE_OOB) != 0);\n\n\t\trevents |= EPOLLPRI;\n\t\tgoto out;\n\t}\n#endif\n\n\tif (fd2_node->node_type == NODE_TYPE_SOCKET) {\n\t\tif (kev->filter == EVFILT_READ) {\n\t\t\tif (kev->flags & EV_EOF) {\n\t\t\t\tfd2_node->eof_state |= EOF_STATE_READ_EOF;\n\t\t\t} else {\n\t\t\t\tfd2_node->eof_state &= ~EOF_STATE_READ_EOF;\n\t\t\t}\n\t\t} else if (kev->filter == EVFILT_WRITE) {\n\t\t\tif (kev->flags & EV_EOF) {\n\t\t\t\tfd2_node->eof_state |= EOF_STATE_WRITE_EOF;\n\t\t\t} else {\n\t\t\t\tfd2_node->eof_state &= ~EOF_STATE_WRITE_EOF;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif (kev->filter == EVFILT_READ) {\n\t\t\tif (kev->flags & EV_EOF) {\n\t\t\t\tfd2_node->eof_state = EOF_STATE_READ_EOF |\n\t\t\t\t    EOF_STATE_WRITE_EOF;\n\t\t\t} else {\n\t\t\t\tfd2_node->eof_state = 0;\n\t\t\t}\n\t\t} else if (kev->filter == EVFILT_WRITE) {\n\t\t\tif (kev->flags & EV_EOF) {\n\t\t\t\tfd2_node->eof_state = EOF_STATE_READ_EOF |\n\t\t\t\t    EOF_STATE_WRITE_EOF;\n\t\t\t} else {\n\t\t\t\tfd2_node->eof_state = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (kev->flags & EV_ERROR) {\n\t\trevents |= EPOLLERR;\n\t}\n\n\tif (kev->flags & EV_EOF) {\n\t\tif (kev->fflags) {\n\t\t\trevents |= EPOLLERR;\n\t\t}\n\t}\n\n\tif (fd2_node->eof_state) {\n\t\tint epoll_event;\n\n\t\tif (fd2_node->node_type == NODE_TYPE_FIFO) {\n\t\t\tif (kev->filter == EVFILT_READ) {\n\t\t\t\tepoll_event = EPOLLHUP;\n\t\t\t\tif (kev->data == 0) {\n\t\t\t\t\trevents &= ~EPOLLIN;\n\t\t\t\t}\n\t\t\t} else if (kev->filter == EVFILT_WRITE) {\n\t\t\t\tif (fd2_node->has_evfilt_read) {\n\t\t\t\t\tassert(\n\t\t\t\t\t    fd2_node->node_data.fifo.readable);\n\t\t\t\t\tassert(\n\t\t\t\t\t    fd2_node->node_data.fifo.writable);\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Any non-zero revents must have come\n\t\t\t\t\t * from the EVFILT_READ filter. It\n\t\t\t\t\t * could either be \"POLLIN\",\n\t\t\t\t\t * \"POLLIN | POLLHUP\" or \"POLLHUP\", so\n\t\t\t\t\t * we know if there is data to read.\n\t\t\t\t\t * But we also know that the FIFO is\n\t\t\t\t\t * done, so set POLLHUP because it\n\t\t\t\t\t * would be set anyway.\n\t\t\t\t\t *\n\t\t\t\t\t * If revents is zero, not setting it\n\t\t\t\t\t * will simply ignore this EVFILT_WRITE\n\t\t\t\t\t * and wait for the next EVFILT_READ\n\t\t\t\t\t * (which will be EOF).\n\t\t\t\t\t */\n\n\t\t\t\t\tif (fd2_node->revents != 0) {\n\t\t\t\t\t\tfd2_node->revents |= POLLHUP;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tepoll_event = EPOLLERR;\n\t\t\t\tif (kev->data < PIPE_BUF) {\n\t\t\t\t\trevents &= ~EPOLLOUT;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t__builtin_unreachable();\n\t\t\t}\n\t\t} else if (fd2_node->node_type == NODE_TYPE_SOCKET) {\n\t\t\tepoll_event = 0;\n\n\t\t\tif (fd2_node->eof_state & EOF_STATE_READ_EOF) {\n\t\t\t\tepoll_event |= EPOLLIN | EPOLLRDHUP;\n\t\t\t}\n\n\t\t\tif (fd2_node->eof_state & EOF_STATE_WRITE_EOF) {\n\t\t\t\tepoll_event |= EPOLLOUT;\n\t\t\t}\n\n\t\t\tif (fd2_node->eof_state ==\n\t\t\t    (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) {\n\t\t\t\tepoll_event |= EPOLLHUP;\n\t\t\t}\n\t\t} else {\n\t\t\tepoll_event = EPOLLHUP;\n\t\t}\n\n\t\trevents |= epoll_event;\n\t}\n\nout:\n\tfd2_node->revents |= (uint32_t)revents;\n\tfd2_node->revents &= (fd2_node->events | EPOLLHUP | EPOLLERR);\n\n\tif (fd2_node->revents && (uintptr_t)fd2_node->fd == kev->ident) {\n\t\tif (kev->filter == EVFILT_READ) {\n\t\t\tfd2_node->got_evfilt_read = true;\n\t\t} else if (kev->filter == EVFILT_WRITE) {\n\t\t\tfd2_node->got_evfilt_write = true;\n\t\t}\n#ifdef EVFILT_EXCEPT\n\t\telse if (kev->filter == EVFILT_EXCEPT) {\n\t\t\tfd2_node->got_evfilt_except = true;\n\t\t}\n#endif\n\t}\n}\n\nstatic void\nregistered_fds_node_register_for_completion(int *kq,\n    RegisteredFDsNode *fd2_node)\n{\n\tstruct kevent kev[3];\n\tint n = 0;\n\n\tif (fd2_node->has_evfilt_read && !fd2_node->got_evfilt_read) {\n\t\tEV_SET(&kev[n++], fd2_node->fd, EVFILT_READ,\n\t\t    EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node);\n\t}\n\tif (fd2_node->has_evfilt_write && !fd2_node->got_evfilt_write) {\n\t\tEV_SET(&kev[n++], fd2_node->fd, EVFILT_WRITE,\n\t\t    EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node);\n\t}\n\tif (fd2_node->has_evfilt_except && !fd2_node->got_evfilt_except) {\n#ifdef EVFILT_EXCEPT\n\t\tEV_SET(&kev[n++], fd2_node->fd, EVFILT_EXCEPT,\n\t\t    EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_OOB, 0, fd2_node);\n#else\n\t\tassert(0);\n#endif\n\t}\n\n\tif (n == 0) {\n\t\treturn;\n\t}\n\n\tif (*kq < 0) {\n\t\t*kq = kqueue();\n\t}\n\n\tif (*kq >= 0) {\n\t\t(void)kevent(*kq, kev, n, kev, n, NULL);\n\t}\n}\n\nstatic void\nregistered_fds_node_complete(int kq)\n{\n\tif (kq < 0) {\n\t\treturn;\n\t}\n\n\tstruct kevent kevs[32];\n\tint n;\n\n\twhile ((n = kevent(kq, /**/\n\t\t    NULL, 0, kevs, 32, &(struct timespec){0, 0})) > 0) {\n\t\tfor (int i = 0; i < n; ++i) {\n\t\t\tRegisteredFDsNode *fd2_node =\n\t\t\t    (RegisteredFDsNode *)kevs[i].udata;\n\n\t\t\tregistered_fds_node_feed_event(fd2_node, NULL,\n\t\t\t    &kevs[i]);\n\t\t}\n\t}\n\n\t(void)close(kq);\n}\n\nstatic int\nfd_cmp(RegisteredFDsNode *e1, RegisteredFDsNode *e2)\n{\n\treturn (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd);\n}\n\nRB_PROTOTYPE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp);\nRB_GENERATE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp);\n\nerrno_t\nepollfd_ctx_init(EpollFDCtx *epollfd, int kq)\n{\n\terrno_t ec;\n\n\t*epollfd = (EpollFDCtx){\n\t    .kq = kq,\n\t    .registered_fds = RB_INITIALIZER(&registered_fds),\n\t    .self_pipe = {-1, -1},\n\t};\n\n\tTAILQ_INIT(&epollfd->poll_fds);\n\n\tif ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) {\n\t\treturn ec;\n\t}\n\n\tif ((ec = pthread_mutex_init(&epollfd->nr_polling_threads_mutex,\n\t\t NULL)) != 0) {\n\t\tpthread_mutex_destroy(&epollfd->mutex);\n\t\treturn ec;\n\t}\n\n\tif ((ec = pthread_cond_init(&epollfd->nr_polling_threads_cond,\n\t\t NULL)) != 0) {\n\t\tpthread_mutex_destroy(&epollfd->nr_polling_threads_mutex);\n\t\tpthread_mutex_destroy(&epollfd->mutex);\n\t\treturn ec;\n\t}\n\n\treturn 0;\n}\n\nerrno_t\nepollfd_ctx_terminate(EpollFDCtx *epollfd)\n{\n\terrno_t ec = 0;\n\terrno_t ec_local;\n\n\tec_local = pthread_cond_destroy(&epollfd->nr_polling_threads_cond);\n\tec = ec ? ec : ec_local;\n\tec_local = pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex);\n\tec = ec ? ec : ec_local;\n\tec_local = pthread_mutex_destroy(&epollfd->mutex);\n\tec = ec ? ec : ec_local;\n\n\tRegisteredFDsNode *np;\n\tRegisteredFDsNode *np_temp;\n\tRB_FOREACH_SAFE(np, registered_fds_set_, &epollfd->registered_fds,\n\t    np_temp)\n\t{\n\t\tRB_REMOVE(registered_fds_set_, &epollfd->registered_fds, np);\n\t\tregistered_fds_node_destroy(np);\n\t}\n\n\tfree(epollfd->kevs);\n\tfree(epollfd->pfds);\n\tif (epollfd->self_pipe[0] >= 0 && epollfd->self_pipe[1] >= 0) {\n\t\t(void)close(epollfd->self_pipe[0]);\n\t\t(void)close(epollfd->self_pipe[1]);\n\t}\n\n\treturn ec;\n}\n\nstatic errno_t\nepollfd_ctx_make_kevs_space(EpollFDCtx *epollfd, size_t cnt)\n{\n\tassert(cnt > 0);\n\n\tif (cnt <= epollfd->kevs_length) {\n\t\treturn 0;\n\t}\n\n\tsize_t size;\n\tif (__builtin_mul_overflow(cnt, sizeof(struct kevent), &size)) {\n\t\treturn ENOMEM;\n\t}\n\n\tstruct kevent *new_kevs = realloc(epollfd->kevs, size);\n\tif (!new_kevs) {\n\t\treturn errno;\n\t}\n\n\tepollfd->kevs = new_kevs;\n\tepollfd->kevs_length = cnt;\n\n\treturn 0;\n}\n\nstatic errno_t\nepollfd_ctx_make_pfds_space(EpollFDCtx *epollfd)\n{\n\tsize_t cnt = 1 + epollfd->poll_fds_size;\n\n\tif (cnt <= epollfd->pfds_length) {\n\t\treturn 0;\n\t}\n\n\tsize_t size;\n\tif (__builtin_mul_overflow(cnt, sizeof(struct pollfd), &size)) {\n\t\treturn ENOMEM;\n\t}\n\n\tstruct pollfd *new_pfds = realloc(epollfd->pfds, size);\n\tif (!new_pfds) {\n\t\treturn errno;\n\t}\n\n\tepollfd->pfds = new_pfds;\n\tepollfd->pfds_length = cnt;\n\n\treturn 0;\n}\n\nstatic errno_t\nepollfd_ctx__add_self_trigger(EpollFDCtx *epollfd)\n{\n\tstruct kevent kevs[1];\n\n#ifdef EVFILT_USER\n\tEV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0);\n#else\n\tif (epollfd->self_pipe[0] < 0 && epollfd->self_pipe[1] < 0) {\n\t\tif (pipe2(epollfd->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) {\n\t\t\terrno_t ec = errno;\n\t\t\tepollfd->self_pipe[0] = epollfd->self_pipe[1] = -1;\n\t\t\treturn ec;\n\t\t}\n\n\t\tassert(epollfd->self_pipe[0] >= 0);\n\t\tassert(epollfd->self_pipe[1] >= 0);\n\t}\n\n\tEV_SET(&kevs[0], epollfd->self_pipe[0], EVFILT_READ, /**/\n\t    EV_ADD | EV_CLEAR, 0, 0, 0);\n#endif\n\n\tif (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) {\n\t\treturn errno;\n\t}\n\n\treturn 0;\n}\n\nstatic void\nepollfd_ctx__trigger_self(EpollFDCtx *epollfd)\n{\n#ifdef EVFILT_USER\n\tstruct kevent kevs[1];\n\tEV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0);\n\t(void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL);\n#else\n\tassert(epollfd->self_pipe[0] >= 0);\n\tassert(epollfd->self_pipe[1] >= 0);\n\n\tchar c = 0;\n\t(void)write(epollfd->self_pipe[1], &c, 1);\n#endif\n}\n\nstatic void\nepollfd_ctx__trigger_repoll(EpollFDCtx *epollfd)\n{\n\t(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);\n\tunsigned long nr_polling_threads = epollfd->nr_polling_threads;\n\t(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);\n\n\tif (nr_polling_threads == 0) {\n\t\treturn;\n\t}\n\n\tepollfd_ctx__trigger_self(epollfd);\n\n\t(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);\n\twhile (epollfd->nr_polling_threads != 0) {\n\t\tpthread_cond_wait(&epollfd->nr_polling_threads_cond,\n\t\t    &epollfd->nr_polling_threads_mutex);\n\t}\n\t(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);\n\n#ifndef EVFILT_USER\n\tchar c[32];\n\twhile (read(epollfd->self_pipe[0], c, sizeof(c)) >= 0) {\n\t}\n#endif\n}\n\nstatic void\nepollfd_ctx__remove_node_from_kq(EpollFDCtx *epollfd,\n    RegisteredFDsNode *fd2_node)\n{\n\tif (fd2_node->is_on_pollfd_list) {\n\t\tTAILQ_REMOVE(&epollfd->poll_fds, fd2_node, pollfd_list_entry);\n\t\tfd2_node->is_on_pollfd_list = false;\n\t\tassert(epollfd->poll_fds_size != 0);\n\t\t--epollfd->poll_fds_size;\n\n\t\tepollfd_ctx__trigger_repoll(epollfd);\n\t}\n\n\tif (fd2_node->self_pipe[0] >= 0) {\n\t\tstruct kevent kevs[1];\n\t\tEV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/\n\t\t    EV_DELETE, 0, 0, 0);\n\t\t(void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL);\n\n\t\tchar c[32];\n\t\twhile (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) {\n\t\t}\n\t}\n\n\tif (fd2_node->node_type == NODE_TYPE_POLL) {\n#ifdef EVFILT_USER\n\t\tstruct kevent kevs[1];\n\t\tEV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/\n\t\t    EV_DELETE, 0, 0, 0);\n\t\t(void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL);\n#endif\n\t} else {\n\t\tstruct kevent kevs[3];\n\t\tint fd2 = fd2_node->fd;\n\n\t\tEV_SET(&kevs[0], fd2, EVFILT_READ, /**/\n\t\t    EV_DELETE | EV_RECEIPT, 0, 0, 0);\n\t\tEV_SET(&kevs[1], fd2, EVFILT_WRITE, /**/\n\t\t    EV_DELETE | EV_RECEIPT, 0, 0, 0);\n#ifdef EVFILT_USER\n\t\tEV_SET(&kevs[2], (uintptr_t)fd2_node, EVFILT_USER, /**/\n\t\t    EV_DELETE | EV_RECEIPT, 0, 0, 0);\n#endif\n\t\t(void)kevent(epollfd->kq, kevs, 3, kevs, 3, NULL);\n\n\t\tfd2_node->has_evfilt_read = false;\n\t\tfd2_node->has_evfilt_write = false;\n\t\tfd2_node->has_evfilt_except = false;\n\t}\n}\n\nstatic errno_t\nepollfd_ctx__register_events(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node)\n{\n\terrno_t ec = 0;\n\n\t/* Only sockets support EPOLLRDHUP and EPOLLPRI. */\n\tif (fd2_node->node_type != NODE_TYPE_SOCKET) {\n\t\tfd2_node->events &= ~(uint32_t)EPOLLRDHUP;\n\t\tfd2_node->events &= ~(uint32_t)EPOLLPRI;\n\t}\n\n\tint const fd2 = fd2_node->fd;\n\tstruct kevent kev[4] = {\n\t    {.data = 0},\n\t    {.data = 0},\n\t    {.data = 0},\n\t    {.data = 0},\n\t};\n\n\tassert(fd2 >= 0);\n\n\tint evfilt_read_index = -1;\n\tint evfilt_write_index = -1;\n\n\tif (fd2_node->node_type != NODE_TYPE_POLL) {\n\t\tif (fd2_node->is_registered) {\n\t\t\tepollfd_ctx__remove_node_from_kq(epollfd, fd2_node);\n\t\t}\n\n\t\tint n = 0;\n\n\t\tassert(!fd2_node->has_evfilt_read);\n\t\tassert(!fd2_node->has_evfilt_write);\n\t\tassert(!fd2_node->has_evfilt_except);\n\n\t\tNeededFilters needed_filters = get_needed_filters(fd2_node);\n\n\t\tif (needed_filters.evfilt_read) {\n\t\t\tfd2_node->has_evfilt_read = true;\n\t\t\tevfilt_read_index = n;\n\t\t\tEV_SET(&kev[n++], fd2, EVFILT_READ,\n\t\t\t    EV_ADD | (needed_filters.evfilt_read & EV_CLEAR),\n\t\t\t    0, 0, fd2_node);\n\t\t}\n\t\tif (needed_filters.evfilt_write) {\n\t\t\tfd2_node->has_evfilt_write = true;\n\t\t\tevfilt_write_index = n;\n\t\t\tEV_SET(&kev[n++], fd2, EVFILT_WRITE,\n\t\t\t    EV_ADD | (needed_filters.evfilt_write & EV_CLEAR),\n\t\t\t    0, 0, fd2_node);\n\t\t}\n\n\t\tassert(n != 0);\n\n\t\tif (needed_filters.evfilt_except) {\n#ifdef EVFILT_EXCEPT\n\t\t\tfd2_node->has_evfilt_except = true;\n\t\t\tEV_SET(&kev[n++], fd2, EVFILT_EXCEPT,\n\t\t\t    EV_ADD | (needed_filters.evfilt_except & EV_CLEAR),\n\t\t\t    NOTE_OOB, 0, fd2_node);\n#else\n\t\t\tassert(0);\n#endif\n\t\t}\n\n\t\tfor (int i = 0; i < n; ++i) {\n\t\t\tkev[i].flags |= EV_RECEIPT;\n\t\t}\n\n\t\tint ret = kevent(epollfd->kq, kev, n, kev, n, NULL);\n\t\tif (ret < 0) {\n\t\t\tec = errno;\n\t\t\tgoto out;\n\t\t}\n\n\t\tassert(ret == n);\n\n\t\tfor (int i = 0; i < n; ++i) {\n\t\t\tassert((kev[i].flags & EV_ERROR) != 0);\n\t\t}\n\t}\n\n\t/* Check for fds that only support poll. */\n\tif (((fd2_node->node_type == NODE_TYPE_OTHER &&\n\t\t kev[0].data == ENODEV) ||\n\t\tfd2_node->node_type == NODE_TYPE_POLL)) {\n\n\t\tassert((fd2_node->events & /**/\n\t\t\t   ~(uint32_t)(EPOLLIN | EPOLLOUT)) == 0);\n\t\tassert(fd2_node->is_registered ||\n\t\t    fd2_node->node_type == NODE_TYPE_OTHER);\n\n\t\tfd2_node->has_evfilt_read = false;\n\t\tfd2_node->has_evfilt_write = false;\n\t\tfd2_node->has_evfilt_except = false;\n\n\t\tfd2_node->node_type = NODE_TYPE_POLL;\n\n\t\tif ((ec = registered_fds_node_add_self_trigger(fd2_node,\n\t\t\t epollfd)) != 0) {\n\t\t\tgoto out;\n\t\t}\n\n\t\tif (!fd2_node->is_on_pollfd_list) {\n\t\t\tif ((ec = /**/\n\t\t\t\tepollfd_ctx__add_self_trigger(epollfd)) != 0) {\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tTAILQ_INSERT_TAIL(&epollfd->poll_fds, fd2_node,\n\t\t\t    pollfd_list_entry);\n\t\t\tfd2_node->is_on_pollfd_list = true;\n\t\t\t++epollfd->poll_fds_size;\n\t\t}\n\n\t\t/* This is outside the above if because poll \".events\" might\n\t\t * have changed which needs a retriggering. */\n\t\tepollfd_ctx__trigger_repoll(epollfd);\n\n\t\tgoto out;\n\t}\n\n\tfor (int i = 0; i < 4; ++i) {\n\t\tif (kev[i].data != 0) {\n\t\t\tif ((kev[i].data == EPIPE\n#ifdef __NetBSD__\n\t\t\t\t|| kev[i].data == EBADF\n#endif\n\t\t\t\t) &&\n\t\t\t    i == evfilt_write_index &&\n\t\t\t    fd2_node->node_type == NODE_TYPE_FIFO) {\n\n\t\t\t\tfd2_node->eof_state = EOF_STATE_READ_EOF |\n\t\t\t\t    EOF_STATE_WRITE_EOF;\n\t\t\t\tfd2_node->has_evfilt_write = false;\n\n\t\t\t\tif (evfilt_read_index < 0) {\n\t\t\t\t\tif ((ec = registered_fds_node_add_self_trigger(\n\t\t\t\t\t\t fd2_node, epollfd)) != 0) {\n\t\t\t\t\t\tgoto out;\n\t\t\t\t\t}\n\n\t\t\t\t\tregistered_fds_node_trigger_self(\n\t\t\t\t\t    fd2_node, epollfd);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tec = (int)kev[i].data;\n\t\t\t\tgoto out;\n\t\t\t}\n\t\t}\n\t}\n\n\tec = 0;\n\nout:\n\treturn ec;\n}\n\nstatic void\nepollfd_ctx_remove_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node)\n{\n\tepollfd_ctx__remove_node_from_kq(epollfd, fd2_node);\n\n\tRB_REMOVE(registered_fds_set_, &epollfd->registered_fds, fd2_node);\n\tassert(epollfd->registered_fds_size > 0);\n\t--epollfd->registered_fds_size;\n\n\tregistered_fds_node_destroy(fd2_node);\n}\n\n#if defined(__FreeBSD__)\nstatic void\nmodify_fifo_rights_from_capabilities(RegisteredFDsNode *fd2_node)\n{\n\tassert(fd2_node->node_data.fifo.readable);\n\tassert(fd2_node->node_data.fifo.writable);\n\n\tcap_rights_t rights;\n\tmemset(&rights, 0, sizeof(rights));\n\n\tif (cap_rights_get(fd2_node->fd, &rights) == 0) {\n\t\tcap_rights_t test_rights;\n\n\t\tcap_rights_init(&test_rights, CAP_READ);\n\t\tbool has_read_rights = cap_rights_contains(&rights,\n\t\t    &test_rights);\n\n\t\tcap_rights_init(&test_rights, CAP_WRITE);\n\t\tbool has_write_rights = cap_rights_contains(&rights,\n\t\t    &test_rights);\n\n\t\tif (has_read_rights != has_write_rights) {\n\t\t\tfd2_node->node_data.fifo.readable = has_read_rights;\n\t\t\tfd2_node->node_data.fifo.writable = has_write_rights;\n\t\t}\n\t}\n}\n#endif\n\nstatic errno_t\nepollfd_ctx_add_node(EpollFDCtx *epollfd, int fd2, struct epoll_event *ev,\n    struct stat const *statbuf)\n{\n\tRegisteredFDsNode *fd2_node = registered_fds_node_create(fd2);\n\tif (!fd2_node) {\n\t\treturn ENOMEM;\n\t}\n\n\tif (S_ISFIFO(statbuf->st_mode)) {\n\t\tint tmp;\n\n\t\tif (ioctl(fd2_node->fd, FIONREAD, &tmp) < 0 &&\n\t\t    errno == ENOTTY) {\n#ifdef __FreeBSD__\n\t\t\t/*\n\t\t\t * On FreeBSD we need to distinguish between kqueues\n\t\t\t * and native eventfds.\n\t\t\t */\n\t\t\tif (ioctl(fd2_node->fd, FIONBIO, &tmp) < 0 &&\n\t\t\t    errno == ENOTTY) {\n\t\t\t\tfd2_node->node_type = NODE_TYPE_KQUEUE;\n\t\t\t} else {\n\t\t\t\tfd2_node->node_type = NODE_TYPE_OTHER;\n\t\t\t}\n#else\n\t\t\tfd2_node->node_type = NODE_TYPE_KQUEUE;\n#endif\n\t\t} else {\n\t\t\tfd2_node->node_type = NODE_TYPE_FIFO;\n\n\t\t\tint fl = fcntl(fd2, F_GETFL, 0);\n\t\t\tif (fl < 0) {\n\t\t\t\terrno_t ec = errno;\n\t\t\t\tregistered_fds_node_destroy(fd2_node);\n\t\t\t\treturn ec;\n\t\t\t}\n\n\t\t\tfl &= O_ACCMODE;\n\n\t\t\tif (fl == O_RDWR) {\n\t\t\t\tfd2_node->node_data.fifo.readable = true;\n\t\t\t\tfd2_node->node_data.fifo.writable = true;\n#if defined(__FreeBSD__)\n\t\t\t\tmodify_fifo_rights_from_capabilities(fd2_node);\n#endif\n\t\t\t} else if (fl == O_WRONLY) {\n\t\t\t\tfd2_node->node_data.fifo.writable = true;\n\t\t\t} else if (fl == O_RDONLY) {\n\t\t\t\tfd2_node->node_data.fifo.readable = true;\n\t\t\t} else {\n\t\t\t\tregistered_fds_node_destroy(fd2_node);\n\t\t\t\treturn EINVAL;\n\t\t\t}\n\t\t}\n\t} else if (S_ISSOCK(statbuf->st_mode)) {\n\t\tfd2_node->node_type = NODE_TYPE_SOCKET;\n\t} else {\n\t\t/* May also be NODE_TYPE_POLL,\n\t\t   will be checked when registering. */\n\t\tfd2_node->node_type = NODE_TYPE_OTHER;\n\t}\n\n\tregistered_fds_node_update_flags_from_epoll_event(fd2_node, ev);\n\n\tvoid *colliding_node = RB_INSERT(registered_fds_set_,\n\t    &epollfd->registered_fds, fd2_node);\n\t(void)colliding_node;\n\tassert(colliding_node == NULL);\n\t++epollfd->registered_fds_size;\n\n\terrno_t ec = epollfd_ctx__register_events(epollfd, fd2_node);\n\tif (ec != 0) {\n\t\tepollfd_ctx_remove_node(epollfd, fd2_node);\n\t\treturn ec;\n\t}\n\n\tfd2_node->is_registered = true;\n\n\treturn 0;\n}\n\nstatic errno_t\nepollfd_ctx_modify_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node,\n    struct epoll_event *ev)\n{\n\tregistered_fds_node_update_flags_from_epoll_event(fd2_node, ev);\n\n\tassert(fd2_node->is_registered);\n\n\terrno_t ec = epollfd_ctx__register_events(epollfd, fd2_node);\n\tif (ec != 0) {\n\t\tepollfd_ctx_remove_node(epollfd, fd2_node);\n\t\treturn ec;\n\t}\n\n\treturn 0;\n}\n\nstatic errno_t\nepollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2,\n    struct epoll_event *ev)\n{\n\tassert(op == EPOLL_CTL_DEL || ev != NULL);\n\n\tif (epollfd->kq == fd2) {\n\t\treturn EINVAL;\n\t}\n\n\tif (op != EPOLL_CTL_DEL &&\n\t    ((ev->events &\n\t\t~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLRDHUP | /**/\n\t\t    EPOLLPRI | /* unsupported by FreeBSD's kqueue! */\n\t\t    EPOLLHUP | EPOLLERR | /**/\n\t\t    EPOLLET | EPOLLONESHOT)))) {\n\t\treturn EINVAL;\n\t}\n\n\tRegisteredFDsNode *fd2_node;\n\t{\n\t\tRegisteredFDsNode find;\n\t\tfind.fd = fd2;\n\n\t\tfd2_node = RB_FIND(registered_fds_set_, /**/\n\t\t    &epollfd->registered_fds, &find);\n\t}\n\n\tstruct stat statbuf;\n\tif (fstat(fd2, &statbuf) < 0) {\n\t\terrno_t ec = errno;\n\n\t\t/* If the fstat fails for any reason we must clear\n\t\t * internal state to avoid EEXIST errors in future\n\t\t * calls to epoll_ctl. */\n\t\tif (fd2_node) {\n\t\t\tepollfd_ctx_remove_node(epollfd, fd2_node);\n\t\t}\n\n\t\treturn ec;\n\t}\n\n\terrno_t ec;\n\n\tif (op == EPOLL_CTL_ADD) {\n\t\tec = fd2_node\n\t\t    ? EEXIST\n\t\t    : epollfd_ctx_add_node(epollfd, fd2, ev, &statbuf);\n\t} else if (op == EPOLL_CTL_DEL) {\n\t\tec = !fd2_node\n\t\t    ? ENOENT\n\t\t    : (epollfd_ctx_remove_node(epollfd, fd2_node), 0);\n\t} else if (op == EPOLL_CTL_MOD) {\n\t\tec = !fd2_node\n\t\t    ? ENOENT\n\t\t    : epollfd_ctx_modify_node(epollfd, fd2_node, ev);\n\t} else {\n\t\tec = EINVAL;\n\t}\n\n\treturn ec;\n}\n\nvoid\nepollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds)\n{\n\tpfds[0] = (struct pollfd){.fd = epollfd->kq, .events = POLLIN};\n\n\tRegisteredFDsNode *poll_node;\n\tsize_t i = 1;\n\tTAILQ_FOREACH(poll_node, &epollfd->poll_fds, pollfd_list_entry)\n\t{\n\t\tpfds[i++] = (struct pollfd){\n\t\t    .fd = poll_node->fd,\n\t\t    .events = poll_node->node_type == NODE_TYPE_POLL\n\t\t\t? (short)poll_node->events\n\t\t\t: POLLPRI,\n\t\t};\n\t}\n}\n\nerrno_t\nepollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev)\n{\n\terrno_t ec;\n\n\t(void)pthread_mutex_lock(&epollfd->mutex);\n\tec = epollfd_ctx_ctl_impl(epollfd, op, fd2, ev);\n\t(void)pthread_mutex_unlock(&epollfd->mutex);\n\n\treturn ec;\n}\n\nstatic errno_t\nepollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,\n    int *actual_cnt)\n{\n\terrno_t ec;\n\n\tassert(cnt >= 1);\n\n\tec = epollfd_ctx_make_pfds_space(epollfd);\n\tif (ec != 0) {\n\t\treturn ec;\n\t}\n\n\tepollfd_ctx_fill_pollfds(epollfd, epollfd->pfds);\n\n\tint n = poll(epollfd->pfds, (nfds_t)(1 + epollfd->poll_fds_size), 0);\n\tif (n < 0) {\n\t\treturn errno;\n\t}\n\tif (n == 0) {\n\t\t*actual_cnt = 0;\n\t\treturn 0;\n\t}\n\n\t{\n\t\tRegisteredFDsNode *poll_node, *tmp_poll_node;\n\t\tsize_t i = 1;\n\t\tTAILQ_FOREACH_SAFE(poll_node, &epollfd->poll_fds,\n\t\t    pollfd_list_entry, tmp_poll_node)\n\t\t{\n\t\t\tstruct pollfd *pfd = &epollfd->pfds[i++];\n\n\t\t\tif (pfd->revents & POLLNVAL) {\n\t\t\t\tepollfd_ctx_remove_node(epollfd, poll_node);\n\t\t\t} else if (pfd->revents) {\n\t\t\t\tregistered_fds_node_trigger_self(poll_node,\n\t\t\t\t    epollfd);\n\t\t\t}\n\t\t}\n\t}\n\nagain:;\n\n\t/*\n\t * Each registered fd can produce a maximum of 3 kevents. If\n\t * the provided space in 'ev' is large enough to hold results\n\t * for all registered fds, provide enough space for the kevent\n\t * call as well. Add some wiggle room for the 'poll only fd'\n\t * notification mechanism.\n\t */\n\tif ((size_t)cnt >= epollfd->registered_fds_size) {\n\t\tif (__builtin_add_overflow(cnt, 1, &cnt)) {\n\t\t\treturn ENOMEM;\n\t\t}\n\t\tif (__builtin_mul_overflow(cnt, 3, &cnt)) {\n\t\t\treturn ENOMEM;\n\t\t}\n\t}\n\n\tec = epollfd_ctx_make_kevs_space(epollfd, (size_t)cnt);\n\tif (ec != 0) {\n\t\treturn ec;\n\t}\n\n\tstruct kevent *kevs = epollfd->kevs;\n\tassert(kevs != NULL);\n\n\tn = kevent(epollfd->kq, NULL, 0, kevs, cnt, &(struct timespec){0, 0});\n\tif (n < 0) {\n\t\treturn errno;\n\t}\n\n\tint j = 0;\n\n\tfor (int i = 0; i < n; ++i) {\n\t\tRegisteredFDsNode *fd2_node =\n\t\t    (RegisteredFDsNode *)kevs[i].udata;\n\n\t\tif (!fd2_node) {\n#ifdef EVFILT_USER\n\t\t\tassert(kevs[i].filter == EVFILT_USER);\n#else\n\t\t\tassert(kevs[i].filter == EVFILT_READ);\n#endif\n\t\t\tassert(kevs[i].udata == 0);\n\t\t\tcontinue;\n\t\t}\n\n\t\tuint32_t old_revents = fd2_node->revents;\n\t\tNeededFilters old_needed_filters = get_needed_filters(\n\t\t    fd2_node);\n\n\t\tregistered_fds_node_feed_event(fd2_node, epollfd, &kevs[i]);\n\n\t\tif (fd2_node->node_type != NODE_TYPE_POLL &&\n\t\t    !(fd2_node->is_edge_triggered &&\n\t\t\tfd2_node->eof_state ==\n\t\t\t    (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF) &&\n\t\t\tfd2_node->node_type != NODE_TYPE_FIFO)) {\n\n\t\t\tNeededFilters needed_filters = get_needed_filters(\n\t\t\t    fd2_node);\n\n\t\t\tif (old_needed_filters.evfilt_read !=\n\t\t\t\tneeded_filters.evfilt_read ||\n\t\t\t    old_needed_filters.evfilt_write !=\n\t\t\t\tneeded_filters.evfilt_write) {\n\n\t\t\t\tif (epollfd_ctx__register_events(epollfd,\n\t\t\t\t\tfd2_node) != 0) {\n\t\t\t\t\tepollfd_ctx__remove_node_from_kq(\n\t\t\t\t\t    epollfd, fd2_node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (fd2_node->revents && !old_revents) {\n\t\t\tev[j++].data.ptr = fd2_node;\n\t\t}\n\t}\n\n\t{\n\t\tint completion_kq = -1;\n\n\t\tfor (int i = 0; i < j; ++i) {\n\t\t\tRegisteredFDsNode *fd2_node =\n\t\t\t    (RegisteredFDsNode *)ev[i].data.ptr;\n\n\t\t\tif (n == cnt || fd2_node->is_edge_triggered) {\n\t\t\t\tregistered_fds_node_register_for_completion(\n\t\t\t\t    &completion_kq, fd2_node);\n\t\t\t}\n\t\t}\n\n\t\tregistered_fds_node_complete(completion_kq);\n\t}\n\n\tfor (int i = 0; i < j; ++i) {\n\t\tRegisteredFDsNode *fd2_node =\n\t\t    (RegisteredFDsNode *)ev[i].data.ptr;\n\n\t\tev[i].events = fd2_node->revents;\n\t\tev[i].data = fd2_node->data;\n\n\t\tfd2_node->revents = 0;\n\t\tfd2_node->got_evfilt_read = false;\n\t\tfd2_node->got_evfilt_write = false;\n\t\tfd2_node->got_evfilt_except = false;\n\n\t\tif (fd2_node->is_oneshot) {\n\t\t\tepollfd_ctx__remove_node_from_kq(epollfd, fd2_node);\n\t\t}\n\t}\n\n\tif (n && j == 0) {\n\t\tgoto again;\n\t}\n\n\t*actual_cnt = j;\n\treturn 0;\n}\n\nerrno_t\nepollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,\n    int *actual_cnt)\n{\n\terrno_t ec;\n\n\t(void)pthread_mutex_lock(&epollfd->mutex);\n\tec = epollfd_ctx_wait_impl(epollfd, ev, cnt, actual_cnt);\n\t(void)pthread_mutex_unlock(&epollfd->mutex);\n\n\treturn ec;\n}\n"
  },
  {
    "path": "tpws/epoll-shim/src/epollfd_ctx.h",
    "content": "#ifndef EPOLLFD_CTX_H_\n#define EPOLLFD_CTX_H_\n\n#include \"fix.h\"\n\n#define SHIM_SYS_SHIM_HELPERS\n#include <sys/epoll.h>\n\n#include <sys/queue.h>\n#include <sys/tree.h>\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#include <poll.h>\n#include <pthread.h>\n\nstruct registered_fds_node_;\ntypedef struct registered_fds_node_ RegisteredFDsNode;\n\ntypedef enum {\n\tEOF_STATE_READ_EOF = 0x01,\n\tEOF_STATE_WRITE_EOF = 0x02,\n} EOFState;\n\ntypedef enum {\n\tNODE_TYPE_FIFO = 1,\n\tNODE_TYPE_SOCKET = 2,\n\tNODE_TYPE_KQUEUE = 3,\n\tNODE_TYPE_OTHER = 4,\n\tNODE_TYPE_POLL = 5,\n} NodeType;\n\nstruct registered_fds_node_ {\n\tRB_ENTRY(registered_fds_node_) entry;\n\tTAILQ_ENTRY(registered_fds_node_) pollfd_list_entry;\n\n\tint fd;\n\tepoll_data_t data;\n\n\tbool is_registered;\n\n\tbool has_evfilt_read;\n\tbool has_evfilt_write;\n\tbool has_evfilt_except;\n\n\tbool got_evfilt_read;\n\tbool got_evfilt_write;\n\tbool got_evfilt_except;\n\n\tNodeType node_type;\n\tunion {\n\t\tstruct {\n\t\t\tbool readable;\n\t\t\tbool writable;\n\t\t} fifo;\n\t} node_data;\n\tint eof_state;\n\tbool pollpri_active;\n\n\tuint16_t events;\n\tuint32_t revents;\n\n\tbool is_edge_triggered;\n\tbool is_oneshot;\n\n\tbool is_on_pollfd_list;\n\tint self_pipe[2];\n};\n\ntypedef TAILQ_HEAD(pollfds_list_, registered_fds_node_) PollFDList;\ntypedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet;\n\ntypedef struct {\n\tint kq; // non owning\n\tpthread_mutex_t mutex;\n\n\tPollFDList poll_fds;\n\tsize_t poll_fds_size;\n\n\tRegisteredFDsSet registered_fds;\n\tsize_t registered_fds_size;\n\n\tstruct kevent *kevs;\n\tsize_t kevs_length;\n\n\tstruct pollfd *pfds;\n\tsize_t pfds_length;\n\n\tpthread_mutex_t nr_polling_threads_mutex;\n\tpthread_cond_t nr_polling_threads_cond;\n\tunsigned long nr_polling_threads;\n\n\tint self_pipe[2];\n} EpollFDCtx;\n\nerrno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq);\nerrno_t epollfd_ctx_terminate(EpollFDCtx *epollfd);\n\nvoid epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds);\n\nerrno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2,\n    struct epoll_event *ev);\nerrno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,\n    int *actual_cnt);\n\n#endif\n"
  },
  {
    "path": "tpws/epoll-shim/src/eventfd_ctx.h",
    "content": "#ifndef EVENTFD_CTX_H_\n#define EVENTFD_CTX_H_\n\n#include \"fix.h\"\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#include <pthread.h>\n\n#define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0)\n\ntypedef struct {\n\tint kq_; // non owning\n\tint flags_;\n\tpthread_mutex_t mutex_;\n\n\tbool is_signalled_;\n\tint self_pipe_[2]; // only used if EVFILT_USER is not available\n\tuint_least64_t counter_;\n} EventFDCtx;\n\nerrno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter,\n    int flags);\nerrno_t eventfd_ctx_terminate(EventFDCtx *eventfd);\n\nerrno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value);\nerrno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value);\n\n#endif\n"
  },
  {
    "path": "tpws/epoll-shim/src/fix.c",
    "content": "#include \"fix.h\"\r\n\r\n#ifdef __APPLE__\r\n\r\n#include <errno.h>\r\n\r\nint ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask)\r\n{\r\n\t// macos does not implement ppoll\r\n\t// this is a hacky ppoll shim. only for tpws which does not require sigmask\r\n\tif (sigmask)\r\n\t{\r\n\t\terrno = EINVAL;\r\n\t\treturn -1;\r\n\t}\r\n\treturn poll(fds,nfds,tmo_p ? tmo_p->tv_sec*1000 + tmo_p->tv_nsec/1000000 : -1);\r\n}\r\n\r\n#endif\r\n"
  },
  {
    "path": "tpws/epoll-shim/src/fix.h",
    "content": "#pragma once\n\n#ifndef _ERRNO_T_DEFINED\n#define _ERRNO_T_DEFINED\ntypedef int errno_t;\n#endif\n\n#ifdef __APPLE__\n\n#include <time.h>\n#include <signal.h>\n#include <poll.h>\n\nstruct itimerspec {\n        struct timespec  it_interval;\n        struct timespec  it_value;\n};\nint ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);\n\n#endif\n"
  },
  {
    "path": "tpws/epoll-shim/src/signalfd_ctx.h",
    "content": "#ifndef SIGNALFD_CTX_H_\n#define SIGNALFD_CTX_H_\n\n#include \"fix.h\"\n\n#include <signal.h>\n#include <stdint.h>\n#include <stdlib.h>\n\ntypedef struct {\n\tint kq; // non owning\n} SignalFDCtx;\n\nerrno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs);\nerrno_t signalfd_ctx_terminate(SignalFDCtx *signalfd);\n\nerrno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident);\n\n#endif\n"
  },
  {
    "path": "tpws/epoll-shim/src/timerfd_ctx.h",
    "content": "#ifndef TIMERFD_CTX_H_\n#define TIMERFD_CTX_H_\n\n#include \"fix.h\"\n\n#include <stdatomic.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#include <pthread.h>\n#include <time.h>\n\ntypedef struct {\n\tint kq; // non owning\n\tint flags;\n\tpthread_mutex_t mutex;\n\n\tint clockid;\n\t/*\n\t * Next expiration time, absolute (clock given by clockid).\n\t * If it_interval is != 0, it is a periodic timer.\n\t * If it_value is == 0, the timer is disarmed.\n\t */\n\tstruct itimerspec current_itimerspec;\n\tuint64_t nr_expirations;\n} TimerFDCtx;\n\nerrno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid);\nerrno_t timerfd_ctx_terminate(TimerFDCtx *timerfd);\n\nerrno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags,\n    struct itimerspec const *new, struct itimerspec *old);\nerrno_t timerfd_ctx_gettime(TimerFDCtx *timerfd, struct itimerspec *cur);\n\nerrno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value);\n\n#endif\n"
  },
  {
    "path": "tpws/gzip.c",
    "content": "#include \"gzip.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define ZCHUNK 16384\n#define BUFMIN 128\n#define BUFCHUNK (1024*128)\n\nint z_readfile(FILE *F, char **buf, size_t *size)\n{\n\tz_stream zs;\n\tint r;\n\tunsigned char in[ZCHUNK];\n\tsize_t bufsize;\n\tvoid *newbuf;\n\n\tmemset(&zs, 0, sizeof(zs));\n\n\t*buf = NULL;\n\tbufsize = *size = 0;\n\n\tr = inflateInit2(&zs, 47);\n\tif (r != Z_OK)  return r;\n\n\tdo\n\t{\n\t\tzs.avail_in = fread(in, 1, sizeof(in), F);\n\t\tif (ferror(F))\n\t\t{\n\t\t\tr = Z_ERRNO;\n\t\t\tgoto zerr;\n\t\t}\n\t\tif (!zs.avail_in) break;\n\t\tzs.next_in = in;\n\t\tdo\n\t\t{\n\t\t\tif ((bufsize - *size) < BUFMIN)\n\t\t\t{\n\t\t\t\tbufsize += BUFCHUNK;\n\t\t\t\tnewbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize);\n\t\t\t\tif (!newbuf)\n\t\t\t\t{\n\t\t\t\t\tr = Z_MEM_ERROR;\n\t\t\t\t\tgoto zerr;\n\t\t\t\t}\n\t\t\t\t*buf = newbuf;\n\t\t\t}\n\t\t\tzs.avail_out = bufsize - *size;\n\t\t\tzs.next_out = (unsigned char*)(*buf + *size);\n\t\t\tr = inflate(&zs, Z_NO_FLUSH);\n\t\t\tif (r != Z_OK && r != Z_STREAM_END) goto zerr;\n\t\t\t*size = bufsize - zs.avail_out;\n\t\t} while (r == Z_OK && zs.avail_in);\n\t} while (r == Z_OK);\n\n\tif (*size < bufsize)\n\t{\n\t\t// free extra space\n\t\tif ((newbuf = realloc(*buf, *size))) *buf = newbuf;\n\t}\n\n\tinflateEnd(&zs);\n\treturn Z_OK;\n\nzerr:\n\tinflateEnd(&zs);\n\tfree(*buf);\n\t*buf = NULL;\n\treturn r;\n}\n\nbool is_gzip(FILE* F)\n{\n\tunsigned char magic[2];\n\tbool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B;\n\tfseek(F, 0, SEEK_SET);\n\treturn b;\n}\n"
  },
  {
    "path": "tpws/gzip.h",
    "content": "#pragma once\n\n#include <stdio.h>\n#include <zlib.h>\n#include <stdbool.h>\n\nint z_readfile(FILE *F,char **buf,size_t *size);\nbool is_gzip(FILE* F);\n"
  },
  {
    "path": "tpws/helpers.c",
    "content": "#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include <errno.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <time.h>\n#include <sys/stat.h>\n#include <libgen.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#ifdef __ANDROID__\n#include \"andr/ifaddrs.h\"\n#else\n#include <ifaddrs.h>\n#endif\n\n#include \"helpers.h\"\n#ifdef __linux__\n#include <linux/tcp.h>\n#endif\n#include \"linux_compat.h\"\n\nint unique_size_t(size_t *pu, int ct)\n{\n\tsize_t i, j, u;\n\tfor (i = j = 0; j < ct; i++)\n\t{\n\t\tu = pu[j++];\n\t\tfor (; j < ct && pu[j] == u; j++);\n\t\tpu[i] = u;\n\t}\n\treturn i;\n}\nstatic int cmp_size_t(const void * a, const void * b)\n{\n\treturn *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b;\n}\nvoid qsort_size_t(size_t *array, size_t ct)\n{\n\tqsort(array, ct, sizeof(*array), cmp_size_t);\n}\n\n\nvoid rtrim(char *s)\n{\n\tif (s)\n\t\tfor (char *p = s + strlen(s) - 1; p >= s && (*p == '\\n' || *p == '\\r'); p--) *p = '\\0';\n}\n\nvoid replace_char(char *s, char from, char to)\n{\n\tfor (; *s; s++) if (*s == from) *s = to;\n}\n\nchar *strncasestr(const char *s, const char *find, size_t slen)\n{\n\tchar c, sc;\n\tsize_t len;\n\n\tif ((c = *find++) != '\\0')\n\t{\n\t\tlen = strlen(find);\n\t\tdo\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif (slen-- < 1 || (sc = *s++) == '\\0') return NULL;\n\t\t\t} while (toupper(c) != toupper(sc));\n\t\t\tif (len > slen)\treturn NULL;\n\t\t} while (strncasecmp(s, find, len) != 0);\n\t\ts--;\n\t}\n\treturn (char *)s;\n}\n\nbool str_ends_with(const char *s, const char *suffix)\n{\n    size_t slen = strlen(s);\n    size_t suffix_len = strlen(suffix);\n    return suffix_len <= slen && !strcmp(s + slen - suffix_len, suffix);\n}\n\nbool load_file(const char *filename, void *buffer, size_t *buffer_size)\n{\n\tFILE *F;\n\n\tF = fopen(filename, \"rb\");\n\tif (!F) return false;\n\n\t*buffer_size = fread(buffer, 1, *buffer_size, F);\n\tif (ferror(F))\n\t{\n\t\tfclose(F);\n\t\treturn false;\n\t}\n\n\tfclose(F);\n\treturn true;\n}\n\nbool append_to_list_file(const char *filename, const char *s)\n{\n\tFILE *F = fopen(filename, \"at\");\n\tif (!F) return false;\n\tbool bOK = fprintf(F, \"%s\\n\", s) > 0;\n\tfclose(F);\n\treturn bOK;\n}\n\nvoid expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen)\n{\n\tunsigned int target_bitlen = target_bytelen<<3;\n\tunsigned int bitlen = target_bitlen<source_bitlen ? target_bitlen : source_bitlen;\n\tunsigned int bytelen = bitlen>>3;\n\n\tif ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen);\n\tmemcpy(target,source,bytelen);\n\tif ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1));\n}\n\n// \"       [fd00::1]\" => \"fd00::1\"\n// \"[fd00::1]:8000\" => \"fd00::1\"\n// \"127.0.0.1\" => \"127.0.0.1\"\n// \" 127.0.0.1:8000\" => \"127.0.0.1\"\n// \" vk.com:8000\" => \"vk.com\"\n// return value:  true - host is ip addr\nbool strip_host_to_ip(char *host)\n{\n\tsize_t l;\n\tchar *h,*p;\n\tuint8_t addr[16];\n\n\tfor (h = host ; *h==' ' || *h=='\\t' ; h++);\n\tl = strlen(h);\n\tif (l>=2)\n\t{\n\t\tif (*h=='[')\n\t\t{\n\t\t\t// ipv6 ?\n\t\t\tfor (p=++h ; *p && *p!=']' ;  p++);\n\t\t\tif (*p==']')\n\t\t\t{\n\t\t\t\tl = p-h;\n\t\t\t\tmemmove(host,h,l);\n\t\t\t\thost[l]=0;\n\t\t\t\treturn inet_pton(AF_INET6, host, addr)>0;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (inet_pton(AF_INET6, h, addr)>0)\n\t\t\t{\n\t\t\t\t// ipv6 ?\n\t\t\t\tif (host!=h)\n\t\t\t\t{\n\t\t\t\t\tl = strlen(h);\n\t\t\t\t\tmemmove(host,h,l);\n\t\t\t\t\thost[l]=0;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// ipv4 ?\n\t\t\t\tfor (p=h ; *p && *p!=':' ;  p++);\n\t\t\t\tl = p-h;\n\t\t\t\tif (host!=h) memmove(host,h,l);\n\t\t\t\thost[l]=0;\n\t\t\t\treturn inet_pton(AF_INET, host, addr)>0;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nvoid ntop46(const struct sockaddr *sa, char *str, size_t len)\n{\n\tif (!len) return;\n\t*str = 0;\n\tswitch (sa->sa_family)\n\t{\n\tcase AF_INET:\n\t\tinet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len);\n\t\tbreak;\n\tcase AF_INET6:\n\t\tinet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len);\n\t\tbreak;\n\tdefault:\n\t\tsnprintf(str, len, \"UNKNOWN_FAMILY_%d\", sa->sa_family);\n\t}\n}\nvoid ntop46_port(const struct sockaddr *sa, char *str, size_t len)\n{\n\tchar ip[40];\n\tntop46(sa, ip, sizeof(ip));\n\tswitch (sa->sa_family)\n\t{\n\tcase AF_INET:\n\t\tsnprintf(str, len, \"%s:%u\", ip, ntohs(((struct sockaddr_in*)sa)->sin_port));\n\t\tbreak;\n\tcase AF_INET6:\n\t\tsnprintf(str, len, \"[%s]:%u\", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port));\n\t\tbreak;\n\tdefault:\n\t\tsnprintf(str, len, \"%s\", ip);\n\t}\n}\nvoid print_sockaddr(const struct sockaddr *sa)\n{\n\tchar ip_port[48];\n\n\tntop46_port(sa, ip_port, sizeof(ip_port));\n\tprintf(\"%s\", ip_port);\n}\n\n// -1 = error,  0 = not local, 1 = local\nbool check_local_ip(const struct sockaddr *saddr)\n{\n\tstruct ifaddrs *addrs, *a;\n\n\tif (is_localnet(saddr))\n\t\treturn true;\n\n\tif (getifaddrs(&addrs) < 0) return false;\n\ta = addrs;\n\n\tbool bres = false;\n\twhile (a)\n\t{\n\t\tif (a->ifa_addr && sacmp(a->ifa_addr, saddr))\n\t\t{\n\t\t\tbres = true;\n\t\t\tbreak;\n\t\t}\n\t\ta = a->ifa_next;\n\t}\n\n\tfreeifaddrs(addrs);\n\treturn bres;\n}\nvoid print_addrinfo(const struct addrinfo *ai)\n{\n\tchar str[64];\n\twhile (ai)\n\t{\n\t\tswitch (ai->ai_family)\n\t\t{\n\t\tcase AF_INET:\n\t\t\tif (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str)))\n\t\t\t\tprintf(\"%s\\n\", str);\n\t\t\tbreak;\n\t\tcase AF_INET6:\n\t\t\tif (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str)))\n\t\t\t\tprintf(\"%s\\n\", str);\n\t\t\tbreak;\n\t\t}\n\t\tai = ai->ai_next;\n\t}\n}\n\n\n\nbool saismapped(const struct sockaddr_in6 *sa)\n{\n\t// ::ffff:1.2.3.4\n\treturn !memcmp(sa->sin6_addr.s6_addr, \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xff\\xff\", 12);\n}\nbool samappedcmp(const struct sockaddr_in *sa1, const struct sockaddr_in6 *sa2)\n{\n\treturn saismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr + 12, &sa1->sin_addr.s_addr, 4);\n}\nbool sacmp(const struct sockaddr *sa1, const struct sockaddr *sa2)\n{\n\treturn (sa1->sa_family == AF_INET && sa2->sa_family == AF_INET && !memcmp(&((struct sockaddr_in*)sa1)->sin_addr, &((struct sockaddr_in*)sa2)->sin_addr, sizeof(struct in_addr))) ||\n\t\t(sa1->sa_family == AF_INET6 && sa2->sa_family == AF_INET6 && !memcmp(&((struct sockaddr_in6*)sa1)->sin6_addr, &((struct sockaddr_in6*)sa2)->sin6_addr, sizeof(struct in6_addr))) ||\n\t\t(sa1->sa_family == AF_INET && sa2->sa_family == AF_INET6 && samappedcmp((struct sockaddr_in*)sa1, (struct sockaddr_in6*)sa2)) ||\n\t\t(sa1->sa_family == AF_INET6 && sa2->sa_family == AF_INET && samappedcmp((struct sockaddr_in*)sa2, (struct sockaddr_in6*)sa1));\n}\nuint16_t saport(const struct sockaddr *sa)\n{\n\treturn htons(sa->sa_family == AF_INET ? ((struct sockaddr_in*)sa)->sin_port :\n\t\tsa->sa_family == AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0);\n}\nbool saconvmapped(struct sockaddr_storage *a)\n{\n\tif ((a->ss_family == AF_INET6) && saismapped((struct sockaddr_in6*)a))\n\t{\n\t\tuint32_t ip4 = IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr);\n\t\tuint16_t port = ((struct sockaddr_in6*)a)->sin6_port;\n\t\ta->ss_family = AF_INET;\n\t\t((struct sockaddr_in*)a)->sin_addr.s_addr = ip4;\n\t\t((struct sockaddr_in*)a)->sin_port = port;\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nvoid sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa)\n{\n\tswitch (sa->sa_family)\n\t{\n\tcase AF_INET:\n\t\tmemcpy(sa_dest, sa, sizeof(struct sockaddr_in));\n\t\tbreak;\n\tcase AF_INET6:\n\t\tmemcpy(sa_dest, sa, sizeof(struct sockaddr_in6));\n\t\tbreak;\n\tdefault:\n\t\tsa_dest->ss_family = 0;\n\t}\n}\nvoid sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa)\n{\n\tsacopy((struct sockaddr_storage*)sa_dest, sa);\n}\n\nbool is_localnet(const struct sockaddr *a)\n{\n\t// match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0\n\treturn (a->sa_family == AF_INET && (IN_LOOPBACK(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)) ||\n\t\tINADDR_ANY == ntohl((((struct sockaddr_in *)a)->sin_addr.s_addr)))) ||\n\t\t(a->sa_family == AF_INET6 && (IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)a)->sin6_addr) ||\n\t\t\tIN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)a)->sin6_addr) ||\n\t\t\t(IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)a)->sin6_addr) && (IN_LOOPBACK(ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))) ||\n\t\t\t\tINADDR_ANY == ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))))));\n}\nbool is_linklocal(const struct sockaddr_in6 *a)\n{\n\t// fe80::/10\n\treturn a->sin6_addr.s6_addr[0] == 0xFE && (a->sin6_addr.s6_addr[1] & 0xC0) == 0x80;\n}\nbool is_private6(const struct sockaddr_in6* a)\n{\n\t// fc00::/7\n\treturn (a->sin6_addr.s6_addr[0] & 0xFE) == 0xFC;\n}\n\n\n\nbool set_keepalive(int fd)\n{\n\tint yes = 1;\n\treturn setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1;\n}\nbool set_ttl(int fd, int ttl)\n{\n\treturn setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != -1;\n}\nbool set_hl(int fd, int hl)\n{\n\treturn setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl)) != -1;\n}\nbool set_ttl_hl(int fd, int ttl)\n{\n\tbool b1, b2;\n\t// try to set both but one may fail if family is wrong\n\tb1 = set_ttl(fd, ttl);\n\tb2 = set_hl(fd, ttl);\n\treturn b1 || b2;\n}\nint get_so_error(int fd)\n{\n\t// getsockopt(SO_ERROR) clears error\n\tint errn;\n\tsocklen_t optlen = sizeof(errn);\n\tif (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1)\n\t\terrn = errno;\n\treturn errn;\n}\n\nint fprint_localtime(FILE *F)\n{\n\tstruct tm t;\n\ttime_t now;\n\n\ttime(&now);\n\tlocaltime_r(&now, &t);\n\treturn fprintf(F, \"%02d.%02d.%04d %02d:%02d:%02d\", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec);\n}\n\ntime_t file_mod_time(const char *filename)\n{\n\tstruct stat st;\n\treturn stat(filename, &st) == -1 ? 0 : st.st_mtime;\n}\nbool file_mod_signature(const char *filename, file_mod_sig *ms)\n{\n\tstruct stat st;\n\tif (stat(filename,&st)==-1)\n\t{\n\t\tFILE_MOD_RESET(ms);\n\t\treturn false;\n\t}\n\tms->mod_time=st.st_mtime;\n\tms->size=st.st_size;\n\treturn true;\n}\n\nbool file_open_test(const char *filename, int flags)\n{\n\tint fd = open(filename,flags);\n\tif (fd>=0)\n\t{\n\t\tclose(fd);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nbool pf_in_range(uint16_t port, const port_filter *pf)\n{\n\treturn port && (((!pf->from && !pf->to) || (port >= pf->from && port <= pf->to)) ^ pf->neg);\n}\nbool pf_parse(const char *s, port_filter *pf)\n{\n\tunsigned int v1, v2;\n\tchar c;\n\n\tif (!s) return false;\n\tif (*s == '*' && s[1] == 0)\n\t{\n\t\tpf->from = 1; pf->to = 0xFFFF;\n\t\treturn true;\n\t}\n\tif (*s == '~')\n\t{\n\t\tpf->neg = true;\n\t\ts++;\n\t}\n\telse\n\t\tpf->neg = false;\n\tif (sscanf(s, \"%u-%u%c\", &v1, &v2, &c) == 2)\n\t{\n\t\tif (v1 > 65535 || v2 > 65535 || v1 > v2) return false;\n\t\tpf->from = (uint16_t)v1;\n\t\tpf->to = (uint16_t)v2;\n\t}\n\telse if (sscanf(s, \"%u%c\", &v1, &c) == 1)\n\t{\n\t\tif (v1 > 65535) return false;\n\t\tpf->to = pf->from = (uint16_t)v1;\n\t}\n\telse\n\t\treturn false;\n\t// deny all case\n\tif (!pf->from && !pf->to) pf->neg = true;\n\treturn true;\n}\nbool pf_is_empty(const port_filter *pf)\n{\n\treturn !pf->neg && !pf->from && !pf->to;\n}\n\nvoid set_console_io_buffering(void)\n{\n\tsetvbuf(stdout, NULL, _IOLBF, 0);\n\tsetvbuf(stderr, NULL, _IOLBF, 0);\n}\n\nbool set_env_exedir(const char *argv0)\n{\n\tchar *s, *d;\n\tbool bOK = false;\n\tif ((s = strdup(argv0)))\n\t{\n\t\tif ((d = dirname(s)))\n\t\t\tsetenv(\"EXEDIR\", s, 1);\n\t\tfree(s);\n\t}\n\treturn bOK;\n}\n\n\nvoid str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr)\n{\n\tchar s_ip[16];\n\t*s_ip = 0;\n\tinet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip));\n\tsnprintf(s, s_len, cidr->preflen < 32 ? \"%s/%u\" : \"%s\", s_ip, cidr->preflen);\n}\nvoid print_cidr4(const struct cidr4 *cidr)\n{\n\tchar s[19];\n\tstr_cidr4(s, sizeof(s), cidr);\n\tprintf(\"%s\", s);\n}\nvoid str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr)\n{\n\tchar s_ip[40];\n\t*s_ip = 0;\n\tinet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip));\n\tsnprintf(s, s_len, cidr->preflen < 128 ? \"%s/%u\" : \"%s\", s_ip, cidr->preflen);\n}\nvoid print_cidr6(const struct cidr6 *cidr)\n{\n\tchar s[44];\n\tstr_cidr6(s, sizeof(s), cidr);\n\tprintf(\"%s\", s);\n}\nbool parse_cidr4(char *s, struct cidr4 *cidr)\n{\n\tchar *p, d;\n\tbool b;\n\tunsigned int plen;\n\n\tif ((p = strchr(s, '/')))\n\t{\n\t\tif (sscanf(p + 1, \"%u\", &plen) != 1 || plen > 32)\n\t\t\treturn false;\n\t\tcidr->preflen = (uint8_t)plen;\n\t\td = *p; *p = 0; // backup char\n\t}\n\telse\n\t\tcidr->preflen = 32;\n\tb = (inet_pton(AF_INET, s, &cidr->addr) == 1);\n\tif (p) *p = d; // restore char\n\treturn b;\n}\nbool parse_cidr6(char *s, struct cidr6 *cidr)\n{\n\tchar *p, d;\n\tbool b;\n\tunsigned int plen;\n\n\tif ((p = strchr(s, '/')))\n\t{\n\t\tif (sscanf(p + 1, \"%u\", &plen) != 1 || plen > 128)\n\t\t\treturn false;\n\t\tcidr->preflen = (uint8_t)plen;\n\t\td = *p; *p = 0; // backup char\n\t}\n\telse\n\t\tcidr->preflen = 128;\n\tb = (inet_pton(AF_INET6, s, &cidr->addr) == 1);\n\tif (p) *p = d; // restore char\n\treturn b;\n}\n\n\nvoid msleep(unsigned int ms)\n{\n\tstruct timespec time = {\n\t\t .tv_nsec = (ms % 1000) * 1000000,\n\t\t .tv_sec = ms / 1000\n\t};\n\tnanosleep(&time, 0);\n}\n\n#ifdef __linux__\nbool socket_supports_notsent()\n{\n\tint sfd;\n\tstruct tcp_info_new tcpi;\n\n\tsfd = socket(AF_INET,SOCK_STREAM,0);\n\tif (sfd<0) return false;\n\n\tsocklen_t ts = sizeof(tcpi);\n\tif (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0)\n\t{\n\t\tclose(sfd);\n\t\treturn false;\n\t}\n\tclose(sfd);\n\n\treturn ts>=((char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes));\n}\nbool socket_has_notsent(int sfd)\n{\n\tstruct tcp_info_new tcpi;\n\tsocklen_t ts = sizeof(tcpi);\n\n\tif (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0)\n\t\treturn false;\n\tif (tcpi.tcpi_state != 1) // TCP_ESTABLISHED\n\t\treturn false;\n\tsize_t s = (char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes);\n\tif (ts < s)\n\t\t// old structure version\n\t\treturn false;\n\treturn !!tcpi.tcpi_notsent_bytes;\n}\nbool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms)\n{\n\tstruct timespec tres;\n\tunsigned int mtick;\n\n\tif (wasted_ms) *wasted_ms=0;\n\tif (!socket_has_notsent(sfd)) return true;\n\n\tif (clock_getres(CLOCK_MONOTONIC,&tres))\n\t{\n\t\ttres.tv_nsec = 10000000;\n\t\ttres.tv_sec = 0;\n\t}\n\tmtick = (unsigned int)(tres.tv_sec*1000) + (unsigned int)(tres.tv_nsec/1000000);\n\tif (mtick<1) mtick=1;\n\tfor(;;)\n\t{\n\t\tmsleep(mtick);\n\t\tif (wasted_ms) *wasted_ms+=mtick;\n\t\tif (!socket_has_notsent(sfd)) return true;\n\t\tif (delay_ms<=mtick) break;\n\t\tdelay_ms-=mtick;\n\t}\n\treturn false;\n}\n\nint is_wsl(void)\n{\n  struct utsname buf;\n  if (uname(&buf) != 0)\n    return -1;\n\n  if (strcmp(buf.sysname, \"Linux\") != 0)\n    return 0;\n  if (str_ends_with(buf.release, \"microsoft-standard-WSL2\"))\n    return 2;\n  if (str_ends_with(buf.release, \"-Microsoft\"))\n    return 1;\n\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "tpws/helpers.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdbool.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <netdb.h>\n#include <stdio.h>\n#include <time.h>\n#include <sys/utsname.h>\n\n// this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes.\ntypedef union\n{\n\tsa_family_t sa_family;\n\tstruct sockaddr_in sa4;\t\t// size 16\n\tstruct sockaddr_in6 sa6;\t// size 28\n} sockaddr_in46;\n\nint unique_size_t(size_t *pu, int ct);\nvoid qsort_size_t(size_t *array,size_t ct);\n\nvoid rtrim(char *s);\nvoid replace_char(char *s, char from, char to);\nchar *strncasestr(const char *s,const char *find, size_t slen);\n\nbool str_ends_with(const char *s, const char *suffix);\n\nbool load_file(const char *filename,void *buffer,size_t *buffer_size);\nbool append_to_list_file(const char *filename, const char *s);\n\nvoid expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen);\n\nbool strip_host_to_ip(char *host);\n\nvoid ntop46(const struct sockaddr *sa, char *str, size_t len);\nvoid ntop46_port(const struct sockaddr *sa, char *str, size_t len);\nvoid print_sockaddr(const struct sockaddr *sa);\nvoid print_addrinfo(const struct addrinfo *ai);\nbool check_local_ip(const struct sockaddr *saddr);\n\nbool saismapped(const struct sockaddr_in6 *sa);\nbool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2);\nbool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2);\nuint16_t saport(const struct sockaddr *sa);\n// true = was converted\nbool saconvmapped(struct sockaddr_storage *a);\n\nvoid sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa);\nvoid sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa);\n\nbool is_localnet(const struct sockaddr *a);\nbool is_linklocal(const struct sockaddr_in6* a);\nbool is_private6(const struct sockaddr_in6* a);\n\nbool set_keepalive(int fd);\nbool set_ttl(int fd, int ttl);\nbool set_hl(int fd, int hl);\nbool set_ttl_hl(int fd, int ttl);\nint get_so_error(int fd);\n\n// alignment-safe functions\nstatic inline uint16_t pntoh16(const uint8_t *p) {\n\treturn ((uint16_t)p[0] << 8) | (uint16_t)p[1];\n}\nstatic inline void phton16(uint8_t *p, uint16_t v) {\n\tp[0] = (uint8_t)(v>>8);\n\tp[1] = (uint8_t)v;\n}\n\nint fprint_localtime(FILE *F);\n\ntypedef struct\n{\n\ttime_t mod_time;\n\toff_t size;\n} file_mod_sig;\n#define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size))\n#define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig))\nbool file_mod_signature(const char *filename, file_mod_sig *ms);\ntime_t file_mod_time(const char *filename);\nbool file_open_test(const char *filename, int flags);\n\ntypedef struct\n{\n\tuint16_t from,to;\n\tbool neg;\n} port_filter;\nbool pf_in_range(uint16_t port, const port_filter *pf);\nbool pf_parse(const char *s, port_filter *pf);\nbool pf_is_empty(const port_filter *pf);\n\nvoid set_console_io_buffering(void);\nbool set_env_exedir(const char *argv0);\n\n#ifndef IN_LOOPBACK\n#define IN_LOOPBACK(a)          ((((uint32_t) (a)) & 0xff000000) == 0x7f000000)\n#endif\n\n#ifdef __GNUC__\n#define IN6_EXTRACT_MAP4(a) \\\n  (__extension__                                                              \\\n   ({ const struct in6_addr *__a = (const struct in6_addr *) (a);             \\\n      (((const uint32_t *) (__a))[3]); }))\n#else\n#define IN6_EXTRACT_MAP4(a)\t(((const uint32_t *) (a))[3])\n#endif\n\n\nstruct cidr4\n{\n\tstruct in_addr addr;\n\tuint8_t\tpreflen;\n};\nstruct cidr6\n{\n\tstruct in6_addr addr;\n\tuint8_t\tpreflen;\n};\nvoid str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr);\nvoid print_cidr4(const struct cidr4 *cidr);\nvoid str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr);\nvoid print_cidr6(const struct cidr6 *cidr);\nbool parse_cidr4(char *s, struct cidr4 *cidr);\nbool parse_cidr6(char *s, struct cidr6 *cidr);\n\nvoid msleep(unsigned int ms);\n#ifdef __linux__\nbool socket_supports_notsent();\nbool socket_has_notsent(int sfd);\nbool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms);\n\nint is_wsl();\n#endif\n"
  },
  {
    "path": "tpws/hostlist.c",
    "content": "#include <stdio.h>\n#include \"hostlist.h\"\n#include \"gzip.h\"\n#include \"helpers.h\"\n\n// inplace tolower() and add to pool\nstatic bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct)\n{\n\tchar *p=*s;\n\n\t// comment line\n\tif ( *p == '#' || *p == ';' || *p == '/' || *p == '\\r' || *p == '\\n')\n\t{\n\t\t// advance until eol\n\t\tfor (; p<end && *p && *p!='\\r' && *p != '\\n'; p++);\n\t}\n\telse\n\t{\n\t\t// advance until eol lowering all chars\n\t\tuint32_t flags = 0;\n\t\tif (*p=='^')\n\t\t{\n\t\t\tp = ++(*s);\n\t\t\tflags |= HOSTLIST_POOL_FLAG_STRICT_MATCH;\n\t\t}\n\t\tfor (; p<end && *p && *p!='\\r' && *p != '\\n'; p++) *p=tolower(*p);\n\t\tif (!HostlistPoolAddStrLen(hostlist, *s, p-*s, flags))\n\t\t{\n\t\t\tHostlistPoolDestroy(hostlist);\n\t\t\t*hostlist = NULL;\n\t\t\treturn false;\n\t\t}\n\t\tif (ct) (*ct)++;\n\t}\n\t// advance to the next line\n\tfor (; p<end && (!*p || *p=='\\r' || *p=='\\n') ; p++);\n\t*s = p;\n\treturn true;\n}\n\nbool AppendHostlistItem(hostlist_pool **hostlist, char *s)\n{\n\treturn addpool(hostlist,&s,s+strlen(s),NULL);\n}\n\nbool AppendHostList(hostlist_pool **hostlist, const char *filename)\n{\n\tchar *p, *e, s[256], *zbuf;\n\tsize_t zsize;\n\tint ct = 0;\n\tFILE *F;\n\tint r;\n\n\tDLOG_CONDUP(\"Loading hostlist %s\\n\",filename);\n\n\tif (!(F = fopen(filename, \"rb\")))\n\t{\n\t\tDLOG_ERR(\"Could not open %s\\n\", filename);\n\t\treturn false;\n\t}\n\n\tif (is_gzip(F))\n\t{\n\t\tr = z_readfile(F,&zbuf,&zsize);\n\t\tfclose(F);\n\t\tif (r==Z_OK)\n\t\t{\n\t\t\tDLOG_CONDUP(\"zlib compression detected. uncompressed size : %zu\\n\", zsize);\n\n\t\t\tp = zbuf;\n\t\t\te = zbuf + zsize;\n\t\t\twhile(p<e)\n\t\t\t{\n\t\t\t\tif (!addpool(hostlist,&p,e,&ct))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Not enough memory to store host list : %s\\n\", filename);\n\t\t\t\t\tfree(zbuf);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(zbuf);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG_ERR(\"zlib decompression failed : result %d\\n\",r);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDLOG_CONDUP(\"loading plain text list\\n\");\n\n\t\twhile (fgets(s, sizeof(s), F))\n\t\t{\n\t\t\tp = s;\n\t\t\tif (!addpool(hostlist,&p,p+strlen(p),&ct))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Not enough memory to store host list : %s\\n\", filename);\n\t\t\t\tfclose(F);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tfclose(F);\n\t}\n\n\tDLOG_CONDUP(\"Loaded %d hosts from %s\\n\", ct, filename);\n\treturn true;\n}\n\nstatic bool LoadHostList(struct hostlist_file *hfile)\n{\n\tif (hfile->filename)\n\t{\n\t\tfile_mod_sig fsig;\n\t\tif (!file_mod_signature(hfile->filename, &fsig))\n\t\t{\n\t\t\t// stat() error\n\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t\tDLOG_ERR(\"cannot access hostlist file '%s'. in-memory content remains unchanged.\\n\",hfile->filename);\n\t\t\treturn true;\n\t\t}\n\t\tif (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date\n\t\tHostlistPoolDestroy(&hfile->hostlist);\n\t\tif (!AppendHostList(&hfile->hostlist, hfile->filename))\n\t\t{\n\t\t\tHostlistPoolDestroy(&hfile->hostlist);\n\t\t\treturn false;\n\t\t}\n\t\thfile->mod_sig=fsig;\n\t}\n\treturn true;\n}\nstatic bool LoadHostLists(struct hostlist_files_head *list)\n{\n\tbool bres=true;\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t{\n\t\tif (!LoadHostList(hfile))\n\t\t\t// at least one failed\n\t\t\tbres=false;\n\t}\n\treturn bres;\n}\n\nbool NonEmptyHostlist(hostlist_pool **hostlist)\n{\n\t// add impossible hostname if the list is empty\n\treturn *hostlist ? true : HostlistPoolAddStrLen(hostlist, \"@&()\", 4, 0);\n}\n\nstatic void MakeAutolistsNonEmpty()\n{\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tif (dpl->dp.hostlist_auto)\n\t\t\tNonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist);\n\t}\n}\n\nbool LoadAllHostLists()\n{\n\tif (!LoadHostLists(&params.hostlists))\n\t\treturn false;\n\tMakeAutolistsNonEmpty();\n\treturn true;\n}\n\n\n\nstatic bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains)\n{\n\tif (hostlist)\n\t{\n\t\tconst char *p = host;\n\t\tconst struct hostlist_pool *hp;\n\t\tbool bHostFull=true;\n\t\twhile (p)\n\t\t{\n\t\t\tVPRINT(\"hostlist check for %s : \", p);\n\t\t\thp = HostlistPoolGetStr(hostlist, p);\n\t\t\tif (hp)\n\t\t\t{\n\t\t\t\tif ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull)\n\t\t\t\t{\n\t\t\t\t\tVPRINT(\"negative : strict_mismatch : %s != %s\\n\", p, host);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tVPRINT(\"positive\\n\");\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tVPRINT(\"negative\\n\");\n\t\t\tif (no_match_subdomains) break;\n\t\t\tp = strchr(p, '.');\n\t\t\tif (p) p++;\n\t\t\tbHostFull = false;\n\t\t}\n\t}\n\treturn false;\n}\n\n\nstatic bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists)\n{\n\tstruct hostlist_item *item;\n\tLIST_FOREACH(item, hostlists, next)\n\t{\n\t\tif (!LoadHostList(item->hfile))\n\t\t\treturn false;\n\t}\n\tMakeAutolistsNonEmpty();\n\treturn true;\n}\nbool HostlistsReloadCheckForProfile(const struct desync_profile *dp)\n{\n\treturn HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude);\n}\n// return : true = apply fooling, false = do not apply\nstatic bool HostlistCheck_(const struct hostlist_collection_head *hostlists, const struct hostlist_collection_head *hostlists_exclude, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck)\n{\n\tstruct hostlist_item *item;\n\n\tif (excluded) *excluded = false;\n\n\tif (!bSkipReloadCheck)\n\t\tif (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude))\n\t\t\treturn false;\n\n\tLIST_FOREACH(item, hostlists_exclude, next)\n\t{\n\t\tVPRINT(\"[%s] exclude \", item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\tif (SearchHostList(item->hfile->hostlist, host, no_match_subdomains))\n\t\t{\n\t\t\tif (excluded) *excluded = true;\n\t\t\treturn false;\n\t\t}\n\t}\n\t// old behavior compat: all include lists are empty means check passes\n\tif (!hostlist_collection_is_empty(hostlists))\n\t{\n\t\tLIST_FOREACH(item, hostlists, next)\n\t\t{\n\t\t\tVPRINT(\"[%s] include \", item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\t\tif (SearchHostList(item->hfile->hostlist, host, no_match_subdomains))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n// return : true = apply fooling, false = do not apply\nbool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck)\n{\n\tVPRINT(\"* hostlist check for profile %d\\n\",dp->n);\n\treturn HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck);\n}\n\n\nstatic struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename)\n{\n\tstruct hostlist_file *hfile;\n\n\tif (filename)\n\t{\n\t\tif (!(hfile=hostlist_files_search(hostlists, filename)))\n\t\t\tif (!(hfile=hostlist_files_add(hostlists, filename)))\n\t\t\t\treturn NULL;\n\t\tif (!hostlist_collection_search(hl_collection, filename))\n\t\t\tif (!hostlist_collection_add(hl_collection, hfile))\n\t\t\t\treturn NULL;\n\t}\n\telse\n\t{\n\t\tif (!(hfile=hostlist_files_add(hostlists, NULL)))\n\t\t\treturn NULL;\n\t\tif (!hostlist_collection_add(hl_collection, hfile))\n\t\t\treturn NULL;\n\t}\n\n\treturn hfile;\n}\nstruct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename)\n{\n/*\n\tif (filename && !file_mod_time(filename))\n\t{\n\t\tDLOG_ERR(\"cannot access hostlist file '%s'\\n\",filename);\n\t\treturn NULL;\n\t}\n*/\n\treturn RegisterHostlist_(\n\t\t&params.hostlists,\n\t\tbExclude ? &dp->hl_collection_exclude : &dp->hl_collection,\n\t\tfilename);\n}\n\nvoid HostlistsDebug()\n{\n\tif (!params.debug) return;\n\n\tstruct hostlist_file *hfile;\n\tstruct desync_profile_list *dpl;\n\tstruct hostlist_item *hl_item;\n\n\tLIST_FOREACH(hfile, &params.hostlists, next)\n\t{\n\t\tif (hfile->filename)\n\t\t\tVPRINT(\"hostlist file %s%s\\n\",hfile->filename,hfile->hostlist ? \"\" : \" (empty)\");\n\t\telse\n\t\t\tVPRINT(\"hostlist fixed%s\\n\",hfile->hostlist ? \"\" : \" (empty)\");\n\t}\n\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tLIST_FOREACH(hl_item, &dpl->dp.hl_collection, next)\n\t\t\tif (hl_item->hfile!=dpl->dp.hostlist_auto)\n\t\t\t{\n\t\t\t\tif (hl_item->hfile->filename)\n\t\t\t\t\tVPRINT(\"profile %d include hostlist %s%s\\n\",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\t\telse\n\t\t\t\t\tVPRINT(\"profile %d include fixed hostlist%s\\n\",dpl->dp.n, hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\t}\n\t\tLIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next)\n\t\t{\n\t\t\tif (hl_item->hfile->filename)\n\t\t\t\tVPRINT(\"profile %d exclude hostlist %s%s\\n\",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t\telse\n\t\t\t\tVPRINT(\"profile %d exclude fixed hostlist%s\\n\",dpl->dp.n,hl_item->hfile->hostlist ? \"\" : \" (empty)\");\n\t\t}\n\t\tif (dpl->dp.hostlist_auto)\n\t\t\tVPRINT(\"profile %d auto hostlist %s%s\\n\",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? \"\" : \" (empty)\");\n\t}\n}\n"
  },
  {
    "path": "tpws/hostlist.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include \"pools.h\"\n#include \"params.h\"\n\nbool AppendHostlistItem(hostlist_pool **hostlist, char *s);\nbool AppendHostList(hostlist_pool **hostlist, const char *filename);\nbool LoadAllHostLists();\nbool NonEmptyHostlist(hostlist_pool **hostlist);\n// return : true = apply fooling, false = do not apply\nbool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck);\nstruct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename);\nbool HostlistsReloadCheckForProfile(const struct desync_profile *dp);\nvoid HostlistsDebug();\n\n#define ResetAllHostlistsModTime() hostlist_files_reset_modtime(&params.hostlists)\n"
  },
  {
    "path": "tpws/ipset.c",
    "content": "#include <stdio.h>\n#include \"ipset.h\"\n#include \"gzip.h\"\n#include \"helpers.h\"\n\n\n// inplace tolower() and add to pool\nstatic bool addpool(ipset *ips, char **s, const char *end, int *ct)\n{\n\tchar *p, cidr[128];\n\tsize_t l;\n\tstruct cidr4 c4;\n\tstruct cidr6 c6;\n\n\t// advance until eol\n\tfor (p=*s; p<end && *p && *p!='\\r' && *p != '\\n'; p++);\n\n\t// comment line\n\tif (!(**s == '#' || **s == ';' || **s == '/' || **s == '\\r' || **s == '\\n' ))\n\t{\n\t\tl = p-*s;\n\t\tif (l>=sizeof(cidr)) l=sizeof(cidr)-1;\n\t\tmemcpy(cidr,*s,l);\n\t\tcidr[l]=0;\n\t\trtrim(cidr);\n\n\t\tif (parse_cidr4(cidr,&c4))\n\t\t{\n\t\t\tif (!ipset4AddCidr(&ips->ips4, &c4))\n\t\t\t{\n\t\t\t\tipsetDestroy(ips);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (ct) (*ct)++;\n\t\t}\n\t\telse if (parse_cidr6(cidr,&c6))\n\t\t{\n\t\t\tif (!ipset6AddCidr(&ips->ips6, &c6))\n\t\t\t{\n\t\t\t\tipsetDestroy(ips);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (ct) (*ct)++;\n\t\t}\n\t\telse\n\t\t\tDLOG_ERR(\"bad ip or subnet : %s\\n\",cidr);\n\t}\n\n\t// advance to the next line\n\tfor (; p<end && (!*p || *p=='\\r' || *p=='\\n') ; p++);\n\t*s = p;\n\treturn true;\n\n}\n\nbool AppendIpsetItem(ipset *ips, char *ip)\n{\n\treturn addpool(ips,&ip,ip+strlen(ip),NULL);\n}\n\nstatic bool AppendIpset(ipset *ips, const char *filename)\n{\n\tchar *p, *e, s[256], *zbuf;\n\tsize_t zsize;\n\tint ct = 0;\n\tFILE *F;\n\tint r;\n\n\tDLOG_CONDUP(\"Loading ipset %s\\n\",filename);\n\n\tif (!(F = fopen(filename, \"rb\")))\n\t{\n\t\tDLOG_ERR(\"Could not open %s\\n\", filename);\n\t\treturn false;\n\t}\n\n\tif (is_gzip(F))\n\t{\n\t\tr = z_readfile(F,&zbuf,&zsize);\n\t\tfclose(F);\n\t\tif (r==Z_OK)\n\t\t{\n\t\t\tDLOG_CONDUP(\"zlib compression detected. uncompressed size : %zu\\n\", zsize);\n\n\t\t\tp = zbuf;\n\t\t\te = zbuf + zsize;\n\t\t\twhile(p<e)\n\t\t\t{\n\t\t\t\tif (!addpool(ips,&p,e,&ct))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Not enough memory to store ipset : %s\\n\", filename);\n\t\t\t\t\tfree(zbuf);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(zbuf);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDLOG_ERR(\"zlib decompression failed : result %d\\n\",r);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDLOG_CONDUP(\"loading plain text list\\n\");\n\n\t\twhile (fgets(s, sizeof(s)-1, F))\n\t\t{\n\t\t\tp = s;\n\t\t\tif (!addpool(ips,&p,p+strlen(p),&ct))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Not enough memory to store ipset : %s\\n\", filename);\n\t\t\t\tfclose(F);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tfclose(F);\n\t}\n\n\tDLOG_CONDUP(\"Loaded %d ip/subnets from %s\\n\", ct, filename);\n\treturn true;\n}\n\nstatic bool LoadIpset(struct ipset_file *hfile)\n{\n\tif (hfile->filename)\n\t{\n\t\tfile_mod_sig fsig;\n\t\tif (!file_mod_signature(hfile->filename, &fsig))\n\t\t{\n\t\t\t// stat() error\n\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t\tDLOG_ERR(\"cannot access ipset file '%s'. in-memory content remains unchanged.\\n\",hfile->filename);\n\t\t\treturn true;\n\t\t}\n\t\tif (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date\n\t\tipsetDestroy(&hfile->ipset);\n\t\tif (!AppendIpset(&hfile->ipset, hfile->filename))\n\t\t{\n\t\t\tipsetDestroy(&hfile->ipset);\n\t\t\treturn false;\n\t\t}\n\t\thfile->mod_sig=fsig;\n\t}\n\treturn true;\n}\nstatic bool LoadIpsets(struct ipset_files_head *list)\n{\n\tbool bres=true;\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t{\n\t\tif (!LoadIpset(hfile))\n\t\t\t// at least one failed\n\t\t\tbres=false;\n\t}\n\treturn bres;\n}\n\nbool LoadAllIpsets()\n{\n\treturn LoadIpsets(&params.ipsets);\n}\n\nstatic bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tchar s_ip[40];\n\tbool bInSet=false;\n\n\tif (!!ipv4 != !!ipv6)\n\t{\n\t\t*s_ip=0;\n\t\tif (ipv4)\n\t\t{\n\t\t\tif (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip));\n\t\t\tif (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32);\n\t\t}\n\t\tif (ipv6)\n\t\t{\n\t\t\tif (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip));\n\t\t\tif (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128);\n\t\t}\n\t\tVPRINT(\"ipset check for %s : %s\\n\", s_ip, bInSet ? \"positive\" : \"negative\");\n\t}\n\telse\n\t\t// ipv4 and ipv6 are both empty or non-empty\n\t\tVPRINT(\"ipset check error !!!!!!!! ipv4=%p ipv6=%p\\n\",ipv4,ipv6);\n\treturn bInSet;\n}\n\nstatic bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets)\n{\n\tstruct ipset_item *item;\n\tLIST_FOREACH(item, ipsets, next)\n\t{\n\t\tif (!LoadIpset(item->hfile))\n\t\t\treturn false;\n\t}\n\treturn true;\n}\nbool IpsetsReloadCheckForProfile(const struct desync_profile *dp)\n{\n\treturn IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude);\n}\n\nstatic bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tstruct ipset_item *item;\n\n\tif (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude))\n\t\treturn false;\n\n\tLIST_FOREACH(item, ips_exclude, next)\n\t{\n\t\tVPRINT(\"[%s] exclude \",item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\tif (SearchIpset(&item->hfile->ipset, ipv4, ipv6))\n\t\t\treturn false;\n\t}\n\t// old behavior compat: all include lists are empty means check passes\n\tif (!ipset_collection_is_empty(ips))\n\t{\n\t\tLIST_FOREACH(item, ips, next)\n\t\t{\n\t\t\tVPRINT(\"[%s] include \",item->hfile->filename ? item->hfile->filename : \"fixed\");\n\t\t\tif (SearchIpset(&item->hfile->ipset, ipv4, ipv6))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nbool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6)\n{\n\tif (PROFILE_IPSETS_ABSENT(dp)) return true;\n\tVPRINT(\"* ipset check for profile %d\\n\",dp->n);\n\treturn IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6);\n}\n\n\nstatic struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename)\n{\n\tstruct ipset_file *hfile;\n\tif (filename)\n\t{\n\t\tif (!(hfile=ipset_files_search(ipsets, filename)))\n\t\t\tif (!(hfile=ipset_files_add(ipsets, filename)))\n\t\t\t\treturn NULL;\n\t\tif (!ipset_collection_search(ips_collection, filename))\n\t\t\tif (!ipset_collection_add(ips_collection, hfile))\n\t\t\t\treturn NULL;\n\t}\n\telse\n\t{\n\t\tif (!(hfile=ipset_files_add(ipsets, NULL)))\n\t\t\treturn NULL;\n\t\tif (!ipset_collection_add(ips_collection, hfile))\n\t\t\treturn NULL;\n\t}\n\treturn hfile;\n}\nstruct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename)\n{\n\tif (filename && !file_mod_time(filename))\n\t{\n\t\tDLOG_ERR(\"cannot access ipset file '%s'\\n\",filename);\n\t\treturn NULL;\n\t}\n\treturn RegisterIpset_(\n\t\t&params.ipsets,\n\t\tbExclude ? &dp->ips_collection_exclude : &dp->ips_collection,\n\t\tfilename);\n}\n\nstatic const char *dbg_ipset_fill(const ipset *ips)\n{\n\tif (ips->ips4)\n\t\tif (ips->ips6)\n\t\t\treturn \"ipv4+ipv6\";\n\t\telse\n\t\t\treturn \"ipv4\";\n\telse\n\t\tif (ips->ips6)\n\t\t\treturn \"ipv6\";\n\t\telse\n\t\t\treturn \"empty\";\n}\nvoid IpsetsDebug()\n{\n\tif (!params.debug) return;\n\n\tstruct ipset_file *hfile;\n\tstruct desync_profile_list *dpl;\n\tstruct ipset_item *ips_item;\n\n\tLIST_FOREACH(hfile, &params.ipsets, next)\n\t{\n\t\tif (hfile->filename)\n\t\t\tVPRINT(\"ipset file %s (%s)\\n\",hfile->filename,dbg_ipset_fill(&hfile->ipset));\n\t\telse\n\t\t\tVPRINT(\"ipset fixed (%s)\\n\",dbg_ipset_fill(&hfile->ipset));\n\t}\n\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tLIST_FOREACH(ips_item, &dpl->dp.ips_collection, next)\n\t\t\tif (ips_item->hfile->filename)\n\t\t\t\tVPRINT(\"profile %d include ipset %s (%s)\\n\",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\t\telse\n\t\t\t\tVPRINT(\"profile %d include fixed ipset (%s)\\n\",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\tLIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next)\n\t\t\tif (ips_item->hfile->filename)\n\t\t\t\tVPRINT(\"profile %d exclude ipset %s (%s)\\n\",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t\t\telse\n\t\t\t\tVPRINT(\"profile %d exclude fixed ipset (%s)\\n\",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset));\n\t}\n}\n"
  },
  {
    "path": "tpws/ipset.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <arpa/inet.h>\n#include \"params.h\"\n#include \"pools.h\"\n\nbool LoadAllIpsets();\nbool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6);\nstruct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename);\nvoid IpsetsDebug();\nbool AppendIpsetItem(ipset *ips, char *ip);\n\n#define ResetAllIpsetModTime() ipset_files_reset_modtime(&params.ipsets)\n"
  },
  {
    "path": "tpws/kavl.h",
    "content": "/* The MIT License\n\n   Copyright (c) 2018 by Attractive Chaos <attractor@live.co.uk>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n/* An example:\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"kavl.h\"\n\nstruct my_node {\n  char key;\n  KAVL_HEAD(struct my_node) head;\n};\n#define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key))\nKAVL_INIT(my, struct my_node, head, my_cmp)\n\nint main(void) {\n  const char *str = \"MNOLKQOPHIA\"; // from wiki, except a duplicate\n  struct my_node *root = 0;\n  int i, l = strlen(str);\n  for (i = 0; i < l; ++i) {        // insert in the input order\n    struct my_node *q, *p = malloc(sizeof(*p));\n    p->key = str[i];\n    q = kavl_insert(my, &root, p, 0);\n    if (p != q) free(p);           // if already present, free\n  }\n  kavl_itr_t(my) itr;\n  kavl_itr_first(my, root, &itr);  // place at first\n  do {                             // traverse\n    const struct my_node *p = kavl_at(&itr);\n    putchar(p->key);\n    free((void*)p);                // free node\n  } while (kavl_itr_next(my, &itr));\n  putchar('\\n');\n  return 0;\n}\n*/\n\n#ifndef KAVL_H\n#define KAVL_H\n\n#ifdef __STRICT_ANSI__\n#define inline __inline__\n#endif\n\n#define KAVL_MAX_DEPTH 64\n\n#define kavl_size(head, p) ((p)? (p)->head.size : 0)\n#define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0)\n\n#define KAVL_HEAD(__type) \\\n\tstruct { \\\n\t\t__type *p[2]; \\\n\t\tsigned char balance; /* balance factor */ \\\n\t\tunsigned size; /* #elements in subtree */ \\\n\t}\n\n#define __KAVL_FIND(suf, __scope, __type, __head,  __cmp) \\\n\t__scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \\\n\t\tconst __type *p = root; \\\n\t\tunsigned cnt = 0; \\\n\t\twhile (p != 0) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\tif (cmp < 0) p = p->__head.p[0]; \\\n\t\t\telse if (cmp > 0) p = p->__head.p[1]; \\\n\t\t\telse break; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\treturn (__type*)p; \\\n\t}\n\n#define __KAVL_ROTATE(suf, __type, __head) \\\n\t/* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \\\n\tstatic inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \\\n\t\tint opp = 1 - dir; /* opposite direction */ \\\n\t\t__type *q = p->__head.p[opp]; \\\n\t\tunsigned size_p = p->__head.size; \\\n\t\tp->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \\\n\t\tq->__head.size = size_p; \\\n\t\tp->__head.p[opp] = q->__head.p[dir]; \\\n\t\tq->__head.p[dir] = p; \\\n\t\treturn q; \\\n\t} \\\n\t/* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \\\n\tstatic inline __type *kavl_rotate2_##suf(__type *p, int dir) { \\\n\t\tint b1, opp = 1 - dir; \\\n\t\t__type *q = p->__head.p[opp], *r = q->__head.p[dir]; \\\n\t\tunsigned size_x_dir = kavl_size_child(__head, r, dir); \\\n\t\tr->__head.size = p->__head.size; \\\n\t\tp->__head.size -= q->__head.size - size_x_dir; \\\n\t\tq->__head.size -= size_x_dir + 1; \\\n\t\tp->__head.p[opp] = r->__head.p[dir]; \\\n\t\tr->__head.p[dir] = p; \\\n\t\tq->__head.p[dir] = r->__head.p[opp]; \\\n\t\tr->__head.p[opp] = q; \\\n\t\tb1 = dir == 0? +1 : -1; \\\n\t\tif (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \\\n\t\telse if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \\\n\t\telse q->__head.balance = b1, p->__head.balance = 0; \\\n\t\tr->__head.balance = 0; \\\n\t\treturn r; \\\n\t}\n\n#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \\\n\t__scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \\\n\t\tunsigned char stack[KAVL_MAX_DEPTH]; \\\n\t\t__type *path[KAVL_MAX_DEPTH]; \\\n\t\t__type *bp, *bq; \\\n\t\t__type *p, *q, *r = 0; /* _r_ is potentially the new root */ \\\n\t\tint i, which = 0, top, b1, path_len; \\\n\t\tunsigned cnt = 0; \\\n\t\tbp = *root_, bq = 0; \\\n\t\t/* find the insertion location */ \\\n\t\tfor (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\tif (cmp == 0) { \\\n\t\t\t\tif (cnt_) *cnt_ = cnt; \\\n\t\t\t\treturn p; \\\n\t\t\t} \\\n\t\t\tif (p->__head.balance != 0) \\\n\t\t\t\tbq = q, bp = p, top = 0; \\\n\t\t\tstack[top++] = which = (cmp > 0); \\\n\t\t\tpath[path_len++] = p; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\tx->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \\\n\t\tif (q == 0) *root_ = x; \\\n\t\telse q->__head.p[which] = x; \\\n\t\tif (bp == 0) return x; \\\n\t\tfor (i = 0; i < path_len; ++i) ++path[i]->__head.size; \\\n\t\tfor (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \\\n\t\t\tif (stack[top] == 0) --p->__head.balance; \\\n\t\t\telse ++p->__head.balance; \\\n\t\tif (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \\\n\t\t/* re-balance */ \\\n\t\twhich = (bp->__head.balance < 0); \\\n\t\tb1 = which == 0? +1 : -1; \\\n\t\tq = bp->__head.p[1 - which]; \\\n\t\tif (q->__head.balance == b1) { \\\n\t\t\tr = kavl_rotate1_##suf(bp, which); \\\n\t\t\tq->__head.balance = bp->__head.balance = 0; \\\n\t\t} else r = kavl_rotate2_##suf(bp, which); \\\n\t\tif (bq == 0) *root_ = r; \\\n\t\telse bq->__head.p[bp != bq->__head.p[0]] = r; \\\n\t\treturn x; \\\n\t}\n\n#define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \\\n\t__scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \\\n\t\t__type *p, *path[KAVL_MAX_DEPTH], fake; \\\n\t\tunsigned char dir[KAVL_MAX_DEPTH]; \\\n\t\tint i, d = 0, cmp; \\\n\t\tunsigned cnt = 0; \\\n\t\tfake.__head.p[0] = *root_, fake.__head.p[1] = 0; \\\n\t\tif (cnt_) *cnt_ = 0; \\\n\t\tif (x) { \\\n\t\t\tfor (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \\\n\t\t\t\tint which = (cmp > 0); \\\n\t\t\t\tif (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \\\n\t\t\t\tdir[d] = which; \\\n\t\t\t\tpath[d++] = p; \\\n\t\t\t\tp = p->__head.p[which]; \\\n\t\t\t\tif (p == 0) { \\\n\t\t\t\t\tif (cnt_) *cnt_ = 0; \\\n\t\t\t\t\treturn 0; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t\tcnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \\\n\t\t} else { \\\n\t\t\tfor (p = &fake, cnt = 1; p; p = p->__head.p[0]) \\\n\t\t\t\tdir[d] = 0, path[d++] = p; \\\n\t\t\tp = path[--d]; \\\n\t\t} \\\n\t\tif (cnt_) *cnt_ = cnt; \\\n\t\tfor (i = 1; i < d; ++i) --path[i]->__head.size; \\\n\t\tif (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \\\n\t\t\tpath[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \\\n\t\t} else { \\\n\t\t\t__type *q = p->__head.p[1]; \\\n\t\t\tif (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \\\n\t\t\t\tq->__head.p[0] = p->__head.p[0]; \\\n\t\t\t\tq->__head.balance = p->__head.balance; \\\n\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = q; \\\n\t\t\t\tpath[d] = q, dir[d++] = 1; \\\n\t\t\t\tq->__head.size = p->__head.size - 1; \\\n\t\t\t} else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \\\n\t\t\t\t__type *r; \\\n\t\t\t\tint e = d++; /* backup _d_ */\\\n\t\t\t\tfor (;;) { \\\n\t\t\t\t\tdir[d] = 0; \\\n\t\t\t\t\tpath[d++] = q; \\\n\t\t\t\t\tr = q->__head.p[0]; \\\n\t\t\t\t\tif (r->__head.p[0] == 0) break; \\\n\t\t\t\t\tq = r; \\\n\t\t\t\t} \\\n\t\t\t\tr->__head.p[0] = p->__head.p[0]; \\\n\t\t\t\tq->__head.p[0] = r->__head.p[1]; \\\n\t\t\t\tr->__head.p[1] = p->__head.p[1]; \\\n\t\t\t\tr->__head.balance = p->__head.balance; \\\n\t\t\t\tpath[e-1]->__head.p[dir[e-1]] = r; \\\n\t\t\t\tpath[e] = r, dir[e] = 1; \\\n\t\t\t\tfor (i = e + 1; i < d; ++i) --path[i]->__head.size; \\\n\t\t\t\tr->__head.size = p->__head.size - 1; \\\n\t\t\t} \\\n\t\t} \\\n\t\twhile (--d > 0) { \\\n\t\t\t__type *q = path[d]; \\\n\t\t\tint which, other, b1 = 1, b2 = 2; \\\n\t\t\twhich = dir[d], other = 1 - which; \\\n\t\t\tif (which) b1 = -b1, b2 = -b2; \\\n\t\t\tq->__head.balance += b1; \\\n\t\t\tif (q->__head.balance == b1) break; \\\n\t\t\telse if (q->__head.balance == b2) { \\\n\t\t\t\t__type *r = q->__head.p[other]; \\\n\t\t\t\tif (r->__head.balance == -b1) { \\\n\t\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \\\n\t\t\t\t} else { \\\n\t\t\t\t\tpath[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \\\n\t\t\t\t\tif (r->__head.balance == 0) { \\\n\t\t\t\t\t\tr->__head.balance = -b1; \\\n\t\t\t\t\t\tq->__head.balance = b1; \\\n\t\t\t\t\t\tbreak; \\\n\t\t\t\t\t} else r->__head.balance = q->__head.balance = 0; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\t*root_ = fake.__head.p[0]; \\\n\t\treturn p; \\\n\t}\n\n#define kavl_free(__type, __head, __root, __free) do { \\\n\t\t__type *_p, *_q; \\\n\t\tfor (_p = __root; _p; _p = _q) { \\\n\t\t\tif (_p->__head.p[0] == 0) { \\\n\t\t\t\t_q = _p->__head.p[1]; \\\n\t\t\t\t__free(_p); \\\n\t\t\t} else { \\\n\t\t\t\t_q = _p->__head.p[0]; \\\n\t\t\t\t_p->__head.p[0] = _q->__head.p[1]; \\\n\t\t\t\t_q->__head.p[1] = _p; \\\n\t\t\t} \\\n\t\t} \\\n\t} while (0)\n\n#define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \\\n\tstruct kavl_itr_##suf { \\\n\t\tconst __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \\\n\t}; \\\n\t__scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \\\n\t\tconst __type *p; \\\n\t\tfor (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \\\n\t\t\t*++itr->top = p; \\\n\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t} \\\n\t__scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \\\n\t\tconst __type *p = root; \\\n\t\titr->top = itr->stack - 1; \\\n\t\twhile (p != 0) { \\\n\t\t\tint cmp; \\\n\t\t\tcmp = __cmp(x, p); \\\n\t\t\tif (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \\\n\t\t\telse if (cmp > 0) p = p->__head.p[1]; \\\n\t\t\telse break; \\\n\t\t} \\\n\t\tif (p) { \\\n\t\t\t*++itr->top = p; \\\n\t\t\titr->right = p->__head.p[1]; \\\n\t\t\treturn 1; \\\n\t\t} else if (itr->top >= itr->stack) { \\\n\t\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t\t\treturn 0; \\\n\t\t} else return 0; \\\n\t} \\\n\t__scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \\\n\t\tfor (;;) { \\\n\t\t\tconst __type *p; \\\n\t\t\tfor (p = itr->right, --itr->top; p; p = p->__head.p[0]) \\\n\t\t\t\t*++itr->top = p; \\\n\t\t\tif (itr->top < itr->stack) return 0; \\\n\t\t\titr->right = (*itr->top)->__head.p[1]; \\\n\t\t\treturn 1; \\\n\t\t} \\\n\t}\n\n/**\n * Insert a node to the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param proot   pointer to the root of the tree (in/out: root may change)\n * @param x       node to insert (in)\n * @param cnt     number of nodes smaller than or equal to _x_; can be NULL (out)\n *\n * @return _x_ if not present in the tree, or the node equal to x.\n */\n#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt)\n\n/**\n * Find a node in the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param x       node value to find (in)\n * @param cnt     number of nodes smaller than or equal to _x_; can be NULL (out)\n *\n * @return node equal to _x_ if present, or NULL if absent\n */\n#define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt)\n\n/**\n * Delete a node from the tree\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param proot   pointer to the root of the tree (in/out: root may change)\n * @param x       node value to delete; if NULL, delete the first node (in)\n *\n * @return node removed from the tree if present, or NULL if absent\n */\n#define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt)\n#define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0)\n\n#define kavl_itr_t(suf) struct kavl_itr_##suf\n\n/**\n * Place the iterator at the smallest object\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param itr     iterator\n */\n#define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr)\n\n/**\n * Place the iterator at the object equal to or greater than the query\n *\n * @param suf     name suffix used in KAVL_INIT()\n * @param root    root of the tree\n * @param x       query (in)\n * @param itr     iterator (out)\n *\n * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is\n *         larger than all objects in the tree\n */\n#define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr)\n\n/**\n * Move to the next object in order\n *\n * @param itr     iterator (modified)\n *\n * @return 1 if there is a next object; 0 otherwise\n */\n#define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr)\n\n/**\n * Return the pointer at the iterator\n *\n * @param itr     iterator\n *\n * @return pointer if present; NULL otherwise\n */\n#define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top)\n\n#define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_FIND(suf, __scope, __type, __head,  __cmp) \\\n\t__KAVL_ROTATE(suf, __type, __head) \\\n\t__KAVL_INSERT(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_ERASE(suf, __scope, __type, __head, __cmp) \\\n\t__KAVL_ITR(suf, __scope, __type, __head, __cmp)\n\n#define KAVL_INIT(suf, __type, __head, __cmp) \\\n\tKAVL_INIT2(suf,, __type, __head, __cmp)\n\n#endif\n"
  },
  {
    "path": "tpws/linux_compat.h",
    "content": "#ifdef __linux__\n\n#include <linux/types.h>\n\n#ifndef TCP_USER_TIMEOUT\n#define TCP_USER_TIMEOUT 18\n#endif\n\n#ifndef IP6T_SO_ORIGINAL_DST\n #define IP6T_SO_ORIGINAL_DST 80\n#endif\n\n#ifndef PR_SET_NO_NEW_PRIVS\n #define PR_SET_NO_NEW_PRIVS\t38\n#endif\n\n// workaround for old headers\n\nstruct tcp_info_new {\n\t__u8    tcpi_state;\n\t__u8    tcpi_ca_state;\n\t__u8    tcpi_retransmits;\n\t__u8    tcpi_probes;\n\t__u8    tcpi_backoff;\n\t__u8    tcpi_options;\n\t__u8    tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;\n\t__u8    tcpi_delivery_rate_app_limited : 1, tcpi_fastopen_client_fail : 2;\n\n\t__u32   tcpi_rto;\n\t__u32   tcpi_ato;\n\t__u32   tcpi_snd_mss;\n\t__u32   tcpi_rcv_mss;\n\n\t__u32   tcpi_unacked;\n\t__u32   tcpi_sacked;\n\t__u32   tcpi_lost;\n\t__u32   tcpi_retrans;\n\t__u32   tcpi_fackets;\n\n\t/* Times. */\n\t__u32   tcpi_last_data_sent;\n\t__u32   tcpi_last_ack_sent;     /* Not remembered, sorry. */\n\t__u32   tcpi_last_data_recv;\n\t__u32   tcpi_last_ack_recv;\n\n\t/* Metrics. */\n\t__u32   tcpi_pmtu;\n\t__u32   tcpi_rcv_ssthresh;\n\t__u32   tcpi_rtt;\n\t__u32   tcpi_rttvar;\n\t__u32   tcpi_snd_ssthresh;\n\t__u32   tcpi_snd_cwnd;\n\t__u32   tcpi_advmss;\n\t__u32   tcpi_reordering;\n\n\t__u32   tcpi_rcv_rtt;\n\t__u32   tcpi_rcv_space;\n\n\t__u32   tcpi_total_retrans;\n\n\t__u64   tcpi_pacing_rate;\n\t__u64   tcpi_max_pacing_rate;\n\t__u64   tcpi_bytes_acked;    /* RFC4898 tcpEStatsAppHCThruOctetsAcked */\n\t__u64   tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */\n\t__u32   tcpi_segs_out;       /* RFC4898 tcpEStatsPerfSegsOut */\n\t__u32   tcpi_segs_in;        /* RFC4898 tcpEStatsPerfSegsIn */\n\n\t__u32   tcpi_notsent_bytes;\n\t__u32   tcpi_min_rtt;\n\t__u32   tcpi_data_segs_in;      /* RFC4898 tcpEStatsDataSegsIn */\n\t__u32   tcpi_data_segs_out;     /* RFC4898 tcpEStatsDataSegsOut */\n\n\t__u64   tcpi_delivery_rate;\n\n\t__u64   tcpi_busy_time;      /* Time (usec) busy sending data */\n\t__u64   tcpi_rwnd_limited;   /* Time (usec) limited by receive window */\n\t__u64   tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */\n\n\t__u32   tcpi_delivered;\n\t__u32   tcpi_delivered_ce;\n\n\t__u64   tcpi_bytes_sent;     /* RFC4898 tcpEStatsPerfHCDataOctetsOut */\n\t__u64   tcpi_bytes_retrans;  /* RFC4898 tcpEStatsPerfOctetsRetrans */\n\t__u32   tcpi_dsack_dups;     /* RFC4898 tcpEStatsStackDSACKDups */\n\t__u32   tcpi_reord_seen;     /* reordering events seen */\n\n\t__u32   tcpi_rcv_ooopack;    /* Out-of-order packets received */\n\n\t__u32   tcpi_snd_wnd;        /* peer's advertised receive window after\n\t\t\t\t\t\t\t\t  * scaling (bytes)\n\t\t\t\t\t\t\t\t  */\n\t__u32   tcpi_rcv_wnd;        /* local advertised receive window after\n\t\t\t\t\t\t\t\t  * scaling (bytes)\n\t\t\t\t\t\t\t\t  */\n\n\t__u32   tcpi_rehash;         /* PLB or timeout triggered rehash attempts */\n\n\t__u16   tcpi_total_rto; /* Total number of RTO timeouts, including\n\t\t\t\t\t\t\t * SYN/SYN-ACK and recurring timeouts.\n\t\t\t\t\t\t\t */\n\t__u16   tcpi_total_rto_recoveries;      /* Total number of RTO\n\t\t\t\t\t\t\t\t\t\t\t * recoveries, including any\n\t\t\t\t\t\t\t\t\t\t\t * unfinished recovery.\n\t\t\t\t\t\t\t\t\t\t\t */\n\t__u32   tcpi_total_rto_time;    /* Total time spent in RTO recoveries\n\t\t\t\t\t\t\t\t\t * in milliseconds, including any\n\t\t\t\t\t\t\t\t\t * unfinished recovery.\n\t\t\t\t\t\t\t\t\t */\n};\n\n#endif\n"
  },
  {
    "path": "tpws/macos/net/pfvar.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <netinet/in.h>\n\n// taken from an older apple SDK\n// some fields are different from BSDs\n\n#define DIOCNATLOOK\t_IOWR('D', 23, struct pfioc_natlook)\n\nenum    { PF_INOUT, PF_IN, PF_OUT, PF_FWD };\n\nstruct pf_addr {\n\tunion {\n\t\tstruct in_addr\t\tv4;\n\t\tstruct in6_addr\t\tv6;\n\t\tu_int8_t\t\taddr8[16];\n\t\tu_int16_t\t\taddr16[8];\n\t\tu_int32_t\t\taddr32[4];\n\t} pfa;\t\t    /* 128-bit address */\n#define v4\tpfa.v4\n#define v6\tpfa.v6\n#define addr8\tpfa.addr8\n#define addr16\tpfa.addr16\n#define addr32\tpfa.addr32\n};\n\nunion pf_state_xport {\n\tu_int16_t\tport;\n\tu_int16_t\tcall_id;\n\tu_int32_t\tspi;\n};\n\nstruct pfioc_natlook {\n\tstruct pf_addr\t saddr;\n\tstruct pf_addr\t daddr;\n\tstruct pf_addr\t rsaddr;\n\tstruct pf_addr\t rdaddr;\n\tunion pf_state_xport\tsxport;\n\tunion pf_state_xport\tdxport;\n\tunion pf_state_xport\trsxport;\n\tunion pf_state_xport\trdxport;\n\tsa_family_t\t af;\n\tu_int8_t\t proto;\n\tu_int8_t\t proto_variant;\n\tu_int8_t\t direction;\n};\n"
  },
  {
    "path": "tpws/macos/sys/tree.h",
    "content": "/*\t$NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $\t*/\n/*\t$OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $\t*/\n/* $FreeBSD: releng/12.2/sys/sys/tree.h 326256 2017-11-27 15:01:59Z pfg $ */\n\n/*-\n * SPDX-License-Identifier: BSD-2-Clause-FreeBSD\n *\n * Copyright 2002 Niels Provos <provos@citi.umich.edu>\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef\t_SYS_TREE_H_\n#define\t_SYS_TREE_H_\n\n#include <sys/cdefs.h>\n\n/*\n * This file defines data structures for different types of trees:\n * splay trees and red-black trees.\n *\n * A splay tree is a self-organizing data structure.  Every operation\n * on the tree causes a splay to happen.  The splay moves the requested\n * node to the root of the tree and partly rebalances it.\n *\n * This has the benefit that request locality causes faster lookups as\n * the requested nodes move to the top of the tree.  On the other hand,\n * every lookup causes memory writes.\n *\n * The Balance Theorem bounds the total access time for m operations\n * and n inserts on an initially empty tree as O((m + n)lg n).  The\n * amortized cost for a sequence of m accesses to a splay tree is O(lg n);\n *\n * A red-black tree is a binary search tree with the node color as an\n * extra attribute.  It fulfills a set of conditions:\n *\t- every search path from the root to a leaf consists of the\n *\t  same number of black nodes,\n *\t- each red node (except for the root) has a black parent,\n *\t- each leaf node is black.\n *\n * Every operation on a red-black tree is bounded as O(lg n).\n * The maximum height of a red-black tree is 2lg (n+1).\n */\n\n#define SPLAY_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *sph_root; /* root of the tree */\t\t\t\\\n}\n\n#define SPLAY_INITIALIZER(root)\t\t\t\t\t\t\\\n\t{ NULL }\n\n#define SPLAY_INIT(root) do {\t\t\t\t\t\t\\\n\t(root)->sph_root = NULL;\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define SPLAY_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *spe_left; /* left element */\t\t\t\\\n\tstruct type *spe_right; /* right element */\t\t\t\\\n}\n\n#define SPLAY_LEFT(elm, field)\t\t(elm)->field.spe_left\n#define SPLAY_RIGHT(elm, field)\t\t(elm)->field.spe_right\n#define SPLAY_ROOT(head)\t\t(head)->sph_root\n#define SPLAY_EMPTY(head)\t\t(SPLAY_ROOT(head) == NULL)\n\n/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */\n#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {\t\t\t\\\n\tSPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);\t\\\n\tSPLAY_RIGHT(tmp, field) = (head)->sph_root;\t\t\t\\\n\t(head)->sph_root = tmp;\t\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\t\n#define SPLAY_ROTATE_LEFT(head, tmp, field) do {\t\t\t\\\n\tSPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);\t\\\n\tSPLAY_LEFT(tmp, field) = (head)->sph_root;\t\t\t\\\n\t(head)->sph_root = tmp;\t\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define SPLAY_LINKLEFT(head, tmp, field) do {\t\t\t\t\\\n\tSPLAY_LEFT(tmp, field) = (head)->sph_root;\t\t\t\\\n\ttmp = (head)->sph_root;\t\t\t\t\t\t\\\n\t(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define SPLAY_LINKRIGHT(head, tmp, field) do {\t\t\t\t\\\n\tSPLAY_RIGHT(tmp, field) = (head)->sph_root;\t\t\t\\\n\ttmp = (head)->sph_root;\t\t\t\t\t\t\\\n\t(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define SPLAY_ASSEMBLE(head, node, left, right, field) do {\t\t\\\n\tSPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field);\t\\\n\tSPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\\\n\tSPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field);\t\\\n\tSPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field);\t\\\n} while (/*CONSTCOND*/ 0)\n\n/* Generates prototypes and inline functions */\n\n#define SPLAY_PROTOTYPE(name, type, field, cmp)\t\t\t\t\\\nvoid name##_SPLAY(struct name *, struct type *);\t\t\t\\\nvoid name##_SPLAY_MINMAX(struct name *, int);\t\t\t\t\\\nstruct type *name##_SPLAY_INSERT(struct name *, struct type *);\t\t\\\nstruct type *name##_SPLAY_REMOVE(struct name *, struct type *);\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n/* Finds the node with the same key as elm */\t\t\t\t\\\nstatic __inline struct type *\t\t\t\t\t\t\\\nname##_SPLAY_FIND(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tif (SPLAY_EMPTY(head))\t\t\t\t\t\t\\\n\t\treturn(NULL);\t\t\t\t\t\t\\\n\tname##_SPLAY(head, elm);\t\t\t\t\t\\\n\tif ((cmp)(elm, (head)->sph_root) == 0)\t\t\t\t\\\n\t\treturn (head->sph_root);\t\t\t\t\\\n\treturn (NULL);\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\nstatic __inline struct type *\t\t\t\t\t\t\\\nname##_SPLAY_NEXT(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tname##_SPLAY(head, elm);\t\t\t\t\t\\\n\tif (SPLAY_RIGHT(elm, field) != NULL) {\t\t\t\t\\\n\t\telm = SPLAY_RIGHT(elm, field);\t\t\t\t\\\n\t\twhile (SPLAY_LEFT(elm, field) != NULL) {\t\t\\\n\t\t\telm = SPLAY_LEFT(elm, field);\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t} else\t\t\t\t\t\t\t\t\\\n\t\telm = NULL;\t\t\t\t\t\t\\\n\treturn (elm);\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\nstatic __inline struct type *\t\t\t\t\t\t\\\nname##_SPLAY_MIN_MAX(struct name *head, int val)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tname##_SPLAY_MINMAX(head, val);\t\t\t\t\t\\\n        return (SPLAY_ROOT(head));\t\t\t\t\t\\\n}\n\n/* Main splay operation.\n * Moves node close to the key of elm to top\n */\n#define SPLAY_GENERATE(name, type, field, cmp)\t\t\t\t\\\nstruct type *\t\t\t\t\t\t\t\t\\\nname##_SPLAY_INSERT(struct name *head, struct type *elm)\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n    if (SPLAY_EMPTY(head)) {\t\t\t\t\t\t\\\n\t    SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;\t\\\n    } else {\t\t\t\t\t\t\t\t\\\n\t    int __comp;\t\t\t\t\t\t\t\\\n\t    name##_SPLAY(head, elm);\t\t\t\t\t\\\n\t    __comp = (cmp)(elm, (head)->sph_root);\t\t\t\\\n\t    if(__comp < 0) {\t\t\t\t\t\t\\\n\t\t    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\\\n\t\t    SPLAY_RIGHT(elm, field) = (head)->sph_root;\t\t\\\n\t\t    SPLAY_LEFT((head)->sph_root, field) = NULL;\t\t\\\n\t    } else if (__comp > 0) {\t\t\t\t\t\\\n\t\t    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\\\n\t\t    SPLAY_LEFT(elm, field) = (head)->sph_root;\t\t\\\n\t\t    SPLAY_RIGHT((head)->sph_root, field) = NULL;\t\\\n\t    } else\t\t\t\t\t\t\t\\\n\t\t    return ((head)->sph_root);\t\t\t\t\\\n    }\t\t\t\t\t\t\t\t\t\\\n    (head)->sph_root = (elm);\t\t\t\t\t\t\\\n    return (NULL);\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\nstruct type *\t\t\t\t\t\t\t\t\\\nname##_SPLAY_REMOVE(struct name *head, struct type *elm)\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *__tmp;\t\t\t\t\t\t\\\n\tif (SPLAY_EMPTY(head))\t\t\t\t\t\t\\\n\t\treturn (NULL);\t\t\t\t\t\t\\\n\tname##_SPLAY(head, elm);\t\t\t\t\t\\\n\tif ((cmp)(elm, (head)->sph_root) == 0) {\t\t\t\\\n\t\tif (SPLAY_LEFT((head)->sph_root, field) == NULL) {\t\\\n\t\t\t(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\\\n\t\t} else {\t\t\t\t\t\t\\\n\t\t\t__tmp = SPLAY_RIGHT((head)->sph_root, field);\t\\\n\t\t\t(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\\\n\t\t\tname##_SPLAY(head, elm);\t\t\t\\\n\t\t\tSPLAY_RIGHT((head)->sph_root, field) = __tmp;\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\treturn (elm);\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (NULL);\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\nvoid\t\t\t\t\t\t\t\t\t\\\nname##_SPLAY(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type __node, *__left, *__right, *__tmp;\t\t\t\\\n\tint __comp;\t\t\t\t\t\t\t\\\n\\\n\tSPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\\\n\t__left = __right = &__node;\t\t\t\t\t\\\n\\\n\twhile ((__comp = (cmp)(elm, (head)->sph_root)) != 0) {\t\t\\\n\t\tif (__comp < 0) {\t\t\t\t\t\\\n\t\t\t__tmp = SPLAY_LEFT((head)->sph_root, field);\t\\\n\t\t\tif (__tmp == NULL)\t\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\tif ((cmp)(elm, __tmp) < 0){\t\t\t\\\n\t\t\t\tSPLAY_ROTATE_RIGHT(head, __tmp, field);\t\\\n\t\t\t\tif (SPLAY_LEFT((head)->sph_root, field) == NULL)\\\n\t\t\t\t\tbreak;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tSPLAY_LINKLEFT(head, __right, field);\t\t\\\n\t\t} else if (__comp > 0) {\t\t\t\t\\\n\t\t\t__tmp = SPLAY_RIGHT((head)->sph_root, field);\t\\\n\t\t\tif (__tmp == NULL)\t\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\tif ((cmp)(elm, __tmp) > 0){\t\t\t\\\n\t\t\t\tSPLAY_ROTATE_LEFT(head, __tmp, field);\t\\\n\t\t\t\tif (SPLAY_RIGHT((head)->sph_root, field) == NULL)\\\n\t\t\t\t\tbreak;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tSPLAY_LINKRIGHT(head, __left, field);\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tSPLAY_ASSEMBLE(head, &__node, __left, __right, field);\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n/* Splay with either the minimum or the maximum element\t\t\t\\\n * Used to find minimum or maximum element in tree.\t\t\t\\\n */\t\t\t\t\t\t\t\t\t\\\nvoid name##_SPLAY_MINMAX(struct name *head, int __comp) \\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type __node, *__left, *__right, *__tmp;\t\t\t\\\n\\\n\tSPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\\\n\t__left = __right = &__node;\t\t\t\t\t\\\n\\\n\twhile (1) {\t\t\t\t\t\t\t\\\n\t\tif (__comp < 0) {\t\t\t\t\t\\\n\t\t\t__tmp = SPLAY_LEFT((head)->sph_root, field);\t\\\n\t\t\tif (__tmp == NULL)\t\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\tif (__comp < 0){\t\t\t\t\\\n\t\t\t\tSPLAY_ROTATE_RIGHT(head, __tmp, field);\t\\\n\t\t\t\tif (SPLAY_LEFT((head)->sph_root, field) == NULL)\\\n\t\t\t\t\tbreak;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tSPLAY_LINKLEFT(head, __right, field);\t\t\\\n\t\t} else if (__comp > 0) {\t\t\t\t\\\n\t\t\t__tmp = SPLAY_RIGHT((head)->sph_root, field);\t\\\n\t\t\tif (__tmp == NULL)\t\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\tif (__comp > 0) {\t\t\t\t\\\n\t\t\t\tSPLAY_ROTATE_LEFT(head, __tmp, field);\t\\\n\t\t\t\tif (SPLAY_RIGHT((head)->sph_root, field) == NULL)\\\n\t\t\t\t\tbreak;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tSPLAY_LINKRIGHT(head, __left, field);\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tSPLAY_ASSEMBLE(head, &__node, __left, __right, field);\t\t\\\n}\n\n#define SPLAY_NEGINF\t-1\n#define SPLAY_INF\t1\n\n#define SPLAY_INSERT(name, x, y)\tname##_SPLAY_INSERT(x, y)\n#define SPLAY_REMOVE(name, x, y)\tname##_SPLAY_REMOVE(x, y)\n#define SPLAY_FIND(name, x, y)\t\tname##_SPLAY_FIND(x, y)\n#define SPLAY_NEXT(name, x, y)\t\tname##_SPLAY_NEXT(x, y)\n#define SPLAY_MIN(name, x)\t\t(SPLAY_EMPTY(x) ? NULL\t\\\n\t\t\t\t\t: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))\n#define SPLAY_MAX(name, x)\t\t(SPLAY_EMPTY(x) ? NULL\t\\\n\t\t\t\t\t: name##_SPLAY_MIN_MAX(x, SPLAY_INF))\n\n#define SPLAY_FOREACH(x, name, head)\t\t\t\t\t\\\n\tfor ((x) = SPLAY_MIN(name, head);\t\t\t\t\\\n\t     (x) != NULL;\t\t\t\t\t\t\\\n\t     (x) = SPLAY_NEXT(name, head, x))\n\n/* Macros that define a red-black tree */\n#define RB_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *rbh_root; /* root of the tree */\t\t\t\\\n}\n\n#define RB_INITIALIZER(root)\t\t\t\t\t\t\\\n\t{ NULL }\n\n#define RB_INIT(root) do {\t\t\t\t\t\t\\\n\t(root)->rbh_root = NULL;\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define RB_BLACK\t0\n#define RB_RED\t\t1\n#define RB_ENTRY(type)\t\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *rbe_left;\t\t/* left element */\t\t\\\n\tstruct type *rbe_right;\t\t/* right element */\t\t\\\n\tstruct type *rbe_parent;\t/* parent element */\t\t\\\n\tint rbe_color;\t\t\t/* node color */\t\t\\\n}\n\n#define RB_LEFT(elm, field)\t\t(elm)->field.rbe_left\n#define RB_RIGHT(elm, field)\t\t(elm)->field.rbe_right\n#define RB_PARENT(elm, field)\t\t(elm)->field.rbe_parent\n#define RB_COLOR(elm, field)\t\t(elm)->field.rbe_color\n#define RB_ROOT(head)\t\t\t(head)->rbh_root\n#define RB_EMPTY(head)\t\t\t(RB_ROOT(head) == NULL)\n\n#define RB_SET(elm, parent, field) do {\t\t\t\t\t\\\n\tRB_PARENT(elm, field) = parent;\t\t\t\t\t\\\n\tRB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;\t\t\\\n\tRB_COLOR(elm, field) = RB_RED;\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define RB_SET_BLACKRED(black, red, field) do {\t\t\t\t\\\n\tRB_COLOR(black, field) = RB_BLACK;\t\t\t\t\\\n\tRB_COLOR(red, field) = RB_RED;\t\t\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#ifndef RB_AUGMENT\n#define RB_AUGMENT(x)\tdo {} while (0)\n#endif\n\n#define RB_ROTATE_LEFT(head, elm, tmp, field) do {\t\t\t\\\n\t(tmp) = RB_RIGHT(elm, field);\t\t\t\t\t\\\n\tif ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) {\t\\\n\t\tRB_PARENT(RB_LEFT(tmp, field), field) = (elm);\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tRB_AUGMENT(elm);\t\t\t\t\t\t\\\n\tif ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {\t\\\n\t\tif ((elm) == RB_LEFT(RB_PARENT(elm, field), field))\t\\\n\t\t\tRB_LEFT(RB_PARENT(elm, field), field) = (tmp);\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\tRB_RIGHT(RB_PARENT(elm, field), field) = (tmp);\t\\\n\t} else\t\t\t\t\t\t\t\t\\\n\t\t(head)->rbh_root = (tmp);\t\t\t\t\\\n\tRB_LEFT(tmp, field) = (elm);\t\t\t\t\t\\\n\tRB_PARENT(elm, field) = (tmp);\t\t\t\t\t\\\n\tRB_AUGMENT(tmp);\t\t\t\t\t\t\\\n\tif ((RB_PARENT(tmp, field)))\t\t\t\t\t\\\n\t\tRB_AUGMENT(RB_PARENT(tmp, field));\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {\t\t\t\\\n\t(tmp) = RB_LEFT(elm, field);\t\t\t\t\t\\\n\tif ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) {\t\\\n\t\tRB_PARENT(RB_RIGHT(tmp, field), field) = (elm);\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tRB_AUGMENT(elm);\t\t\t\t\t\t\\\n\tif ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {\t\\\n\t\tif ((elm) == RB_LEFT(RB_PARENT(elm, field), field))\t\\\n\t\t\tRB_LEFT(RB_PARENT(elm, field), field) = (tmp);\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\tRB_RIGHT(RB_PARENT(elm, field), field) = (tmp);\t\\\n\t} else\t\t\t\t\t\t\t\t\\\n\t\t(head)->rbh_root = (tmp);\t\t\t\t\\\n\tRB_RIGHT(tmp, field) = (elm);\t\t\t\t\t\\\n\tRB_PARENT(elm, field) = (tmp);\t\t\t\t\t\\\n\tRB_AUGMENT(tmp);\t\t\t\t\t\t\\\n\tif ((RB_PARENT(tmp, field)))\t\t\t\t\t\\\n\t\tRB_AUGMENT(RB_PARENT(tmp, field));\t\t\t\\\n} while (/*CONSTCOND*/ 0)\n\n/* Generates prototypes and inline functions */\n#define\tRB_PROTOTYPE(name, type, field, cmp)\t\t\t\t\\\n\tRB_PROTOTYPE_INTERNAL(name, type, field, cmp,)\n#define\tRB_PROTOTYPE_STATIC(name, type, field, cmp)\t\t\t\\\n\tRB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)\n#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)\t\t\\\n\tRB_PROTOTYPE_INSERT_COLOR(name, type, attr);\t\t\t\\\n\tRB_PROTOTYPE_REMOVE_COLOR(name, type, attr);\t\t\t\\\n\tRB_PROTOTYPE_INSERT(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_REMOVE(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_FIND(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_NFIND(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_NEXT(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_PREV(name, type, attr);\t\t\t\t\\\n\tRB_PROTOTYPE_MINMAX(name, type, attr);\n#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr)\t\t\t\\\n\tattr void name##_RB_INSERT_COLOR(struct name *, struct type *)\n#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr)\t\t\t\\\n\tattr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *)\n#define RB_PROTOTYPE_REMOVE(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_REMOVE(struct name *, struct type *)\n#define RB_PROTOTYPE_INSERT(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_INSERT(struct name *, struct type *)\n#define RB_PROTOTYPE_FIND(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_FIND(struct name *, struct type *)\n#define RB_PROTOTYPE_NFIND(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_NFIND(struct name *, struct type *)\n#define RB_PROTOTYPE_NEXT(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_NEXT(struct type *)\n#define RB_PROTOTYPE_PREV(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_PREV(struct type *)\n#define RB_PROTOTYPE_MINMAX(name, type, attr)\t\t\t\t\\\n\tattr struct type *name##_RB_MINMAX(struct name *, int)\n\n/* Main rb operation.\n * Moves node close to the key of elm to top\n */\n#define\tRB_GENERATE(name, type, field, cmp)\t\t\t\t\\\n\tRB_GENERATE_INTERNAL(name, type, field, cmp,)\n#define\tRB_GENERATE_STATIC(name, type, field, cmp)\t\t\t\\\n\tRB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)\n#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)\t\t\\\n\tRB_GENERATE_INSERT_COLOR(name, type, field, attr)\t\t\\\n\tRB_GENERATE_REMOVE_COLOR(name, type, field, attr)\t\t\\\n\tRB_GENERATE_INSERT(name, type, field, cmp, attr)\t\t\\\n\tRB_GENERATE_REMOVE(name, type, field, attr)\t\t\t\\\n\tRB_GENERATE_FIND(name, type, field, cmp, attr)\t\t\t\\\n\tRB_GENERATE_NFIND(name, type, field, cmp, attr)\t\t\t\\\n\tRB_GENERATE_NEXT(name, type, field, attr)\t\t\t\\\n\tRB_GENERATE_PREV(name, type, field, attr)\t\t\t\\\n\tRB_GENERATE_MINMAX(name, type, field, attr)\n\n#define RB_GENERATE_INSERT_COLOR(name, type, field, attr)\t\t\\\nattr void\t\t\t\t\t\t\t\t\\\nname##_RB_INSERT_COLOR(struct name *head, struct type *elm)\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *parent, *gparent, *tmp;\t\t\t\t\\\n\twhile ((parent = RB_PARENT(elm, field)) != NULL &&\t\t\\\n\t    RB_COLOR(parent, field) == RB_RED) {\t\t\t\\\n\t\tgparent = RB_PARENT(parent, field);\t\t\t\\\n\t\tif (parent == RB_LEFT(gparent, field)) {\t\t\\\n\t\t\ttmp = RB_RIGHT(gparent, field);\t\t\t\\\n\t\t\tif (tmp && RB_COLOR(tmp, field) == RB_RED) {\t\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_BLACK;\t\\\n\t\t\t\tRB_SET_BLACKRED(parent, gparent, field);\\\n\t\t\t\telm = gparent;\t\t\t\t\\\n\t\t\t\tcontinue;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tif (RB_RIGHT(parent, field) == elm) {\t\t\\\n\t\t\t\tRB_ROTATE_LEFT(head, parent, tmp, field);\\\n\t\t\t\ttmp = parent;\t\t\t\t\\\n\t\t\t\tparent = elm;\t\t\t\t\\\n\t\t\t\telm = tmp;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tRB_SET_BLACKRED(parent, gparent, field);\t\\\n\t\t\tRB_ROTATE_RIGHT(head, gparent, tmp, field);\t\\\n\t\t} else {\t\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(gparent, field);\t\t\t\\\n\t\t\tif (tmp && RB_COLOR(tmp, field) == RB_RED) {\t\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_BLACK;\t\\\n\t\t\t\tRB_SET_BLACKRED(parent, gparent, field);\\\n\t\t\t\telm = gparent;\t\t\t\t\\\n\t\t\t\tcontinue;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tif (RB_LEFT(parent, field) == elm) {\t\t\\\n\t\t\t\tRB_ROTATE_RIGHT(head, parent, tmp, field);\\\n\t\t\t\ttmp = parent;\t\t\t\t\\\n\t\t\t\tparent = elm;\t\t\t\t\\\n\t\t\t\telm = tmp;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tRB_SET_BLACKRED(parent, gparent, field);\t\\\n\t\t\tRB_ROTATE_LEFT(head, gparent, tmp, field);\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tRB_COLOR(head->rbh_root, field) = RB_BLACK;\t\t\t\\\n}\n\n#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr)\t\t\\\nattr void\t\t\t\t\t\t\t\t\\\nname##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *tmp;\t\t\t\t\t\t\\\n\twhile ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&\t\\\n\t    elm != RB_ROOT(head)) {\t\t\t\t\t\\\n\t\tif (RB_LEFT(parent, field) == elm) {\t\t\t\\\n\t\t\ttmp = RB_RIGHT(parent, field);\t\t\t\\\n\t\t\tif (RB_COLOR(tmp, field) == RB_RED) {\t\t\\\n\t\t\t\tRB_SET_BLACKRED(tmp, parent, field);\t\\\n\t\t\t\tRB_ROTATE_LEFT(head, parent, tmp, field);\\\n\t\t\t\ttmp = RB_RIGHT(parent, field);\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tif ((RB_LEFT(tmp, field) == NULL ||\t\t\\\n\t\t\t    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\\\n\t\t\t    (RB_RIGHT(tmp, field) == NULL ||\t\t\\\n\t\t\t    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_RED;\t\t\\\n\t\t\t\telm = parent;\t\t\t\t\\\n\t\t\t\tparent = RB_PARENT(elm, field);\t\t\\\n\t\t\t} else {\t\t\t\t\t\\\n\t\t\t\tif (RB_RIGHT(tmp, field) == NULL ||\t\\\n\t\t\t\t    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\\\n\t\t\t\t\tstruct type *oleft;\t\t\\\n\t\t\t\t\tif ((oleft = RB_LEFT(tmp, field)) \\\n\t\t\t\t\t    != NULL)\t\t\t\\\n\t\t\t\t\t\tRB_COLOR(oleft, field) = RB_BLACK;\\\n\t\t\t\t\tRB_COLOR(tmp, field) = RB_RED;\t\\\n\t\t\t\t\tRB_ROTATE_RIGHT(head, tmp, oleft, field);\\\n\t\t\t\t\ttmp = RB_RIGHT(parent, field);\t\\\n\t\t\t\t}\t\t\t\t\t\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_COLOR(parent, field);\\\n\t\t\t\tRB_COLOR(parent, field) = RB_BLACK;\t\\\n\t\t\t\tif (RB_RIGHT(tmp, field))\t\t\\\n\t\t\t\t\tRB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\\\n\t\t\t\tRB_ROTATE_LEFT(head, parent, tmp, field);\\\n\t\t\t\telm = RB_ROOT(head);\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t} else {\t\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(parent, field);\t\t\t\\\n\t\t\tif (RB_COLOR(tmp, field) == RB_RED) {\t\t\\\n\t\t\t\tRB_SET_BLACKRED(tmp, parent, field);\t\\\n\t\t\t\tRB_ROTATE_RIGHT(head, parent, tmp, field);\\\n\t\t\t\ttmp = RB_LEFT(parent, field);\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tif ((RB_LEFT(tmp, field) == NULL ||\t\t\\\n\t\t\t    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\\\n\t\t\t    (RB_RIGHT(tmp, field) == NULL ||\t\t\\\n\t\t\t    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_RED;\t\t\\\n\t\t\t\telm = parent;\t\t\t\t\\\n\t\t\t\tparent = RB_PARENT(elm, field);\t\t\\\n\t\t\t} else {\t\t\t\t\t\\\n\t\t\t\tif (RB_LEFT(tmp, field) == NULL ||\t\\\n\t\t\t\t    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\\\n\t\t\t\t\tstruct type *oright;\t\t\\\n\t\t\t\t\tif ((oright = RB_RIGHT(tmp, field)) \\\n\t\t\t\t\t    != NULL)\t\t\t\\\n\t\t\t\t\t\tRB_COLOR(oright, field) = RB_BLACK;\\\n\t\t\t\t\tRB_COLOR(tmp, field) = RB_RED;\t\\\n\t\t\t\t\tRB_ROTATE_LEFT(head, tmp, oright, field);\\\n\t\t\t\t\ttmp = RB_LEFT(parent, field);\t\\\n\t\t\t\t}\t\t\t\t\t\\\n\t\t\t\tRB_COLOR(tmp, field) = RB_COLOR(parent, field);\\\n\t\t\t\tRB_COLOR(parent, field) = RB_BLACK;\t\\\n\t\t\t\tif (RB_LEFT(tmp, field))\t\t\\\n\t\t\t\t\tRB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\\\n\t\t\t\tRB_ROTATE_RIGHT(head, parent, tmp, field);\\\n\t\t\t\telm = RB_ROOT(head);\t\t\t\\\n\t\t\t\tbreak;\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tif (elm)\t\t\t\t\t\t\t\\\n\t\tRB_COLOR(elm, field) = RB_BLACK;\t\t\t\\\n}\n\n#define RB_GENERATE_REMOVE(name, type, field, attr)\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_REMOVE(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *child, *parent, *old = elm;\t\t\t\\\n\tint color;\t\t\t\t\t\t\t\\\n\tif (RB_LEFT(elm, field) == NULL)\t\t\t\t\\\n\t\tchild = RB_RIGHT(elm, field);\t\t\t\t\\\n\telse if (RB_RIGHT(elm, field) == NULL)\t\t\t\t\\\n\t\tchild = RB_LEFT(elm, field);\t\t\t\t\\\n\telse {\t\t\t\t\t\t\t\t\\\n\t\tstruct type *left;\t\t\t\t\t\\\n\t\telm = RB_RIGHT(elm, field);\t\t\t\t\\\n\t\twhile ((left = RB_LEFT(elm, field)) != NULL)\t\t\\\n\t\t\telm = left;\t\t\t\t\t\\\n\t\tchild = RB_RIGHT(elm, field);\t\t\t\t\\\n\t\tparent = RB_PARENT(elm, field);\t\t\t\t\\\n\t\tcolor = RB_COLOR(elm, field);\t\t\t\t\\\n\t\tif (child)\t\t\t\t\t\t\\\n\t\t\tRB_PARENT(child, field) = parent;\t\t\\\n\t\tif (parent) {\t\t\t\t\t\t\\\n\t\t\tif (RB_LEFT(parent, field) == elm)\t\t\\\n\t\t\t\tRB_LEFT(parent, field) = child;\t\t\\\n\t\t\telse\t\t\t\t\t\t\\\n\t\t\t\tRB_RIGHT(parent, field) = child;\t\\\n\t\t\tRB_AUGMENT(parent);\t\t\t\t\\\n\t\t} else\t\t\t\t\t\t\t\\\n\t\t\tRB_ROOT(head) = child;\t\t\t\t\\\n\t\tif (RB_PARENT(elm, field) == old)\t\t\t\\\n\t\t\tparent = elm;\t\t\t\t\t\\\n\t\t(elm)->field = (old)->field;\t\t\t\t\\\n\t\tif (RB_PARENT(old, field)) {\t\t\t\t\\\n\t\t\tif (RB_LEFT(RB_PARENT(old, field), field) == old)\\\n\t\t\t\tRB_LEFT(RB_PARENT(old, field), field) = elm;\\\n\t\t\telse\t\t\t\t\t\t\\\n\t\t\t\tRB_RIGHT(RB_PARENT(old, field), field) = elm;\\\n\t\t\tRB_AUGMENT(RB_PARENT(old, field));\t\t\\\n\t\t} else\t\t\t\t\t\t\t\\\n\t\t\tRB_ROOT(head) = elm;\t\t\t\t\\\n\t\tRB_PARENT(RB_LEFT(old, field), field) = elm;\t\t\\\n\t\tif (RB_RIGHT(old, field))\t\t\t\t\\\n\t\t\tRB_PARENT(RB_RIGHT(old, field), field) = elm;\t\\\n\t\tif (parent) {\t\t\t\t\t\t\\\n\t\t\tleft = parent;\t\t\t\t\t\\\n\t\t\tdo {\t\t\t\t\t\t\\\n\t\t\t\tRB_AUGMENT(left);\t\t\t\\\n\t\t\t} while ((left = RB_PARENT(left, field)) != NULL); \\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tgoto color;\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tparent = RB_PARENT(elm, field);\t\t\t\t\t\\\n\tcolor = RB_COLOR(elm, field);\t\t\t\t\t\\\n\tif (child)\t\t\t\t\t\t\t\\\n\t\tRB_PARENT(child, field) = parent;\t\t\t\\\n\tif (parent) {\t\t\t\t\t\t\t\\\n\t\tif (RB_LEFT(parent, field) == elm)\t\t\t\\\n\t\t\tRB_LEFT(parent, field) = child;\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\tRB_RIGHT(parent, field) = child;\t\t\\\n\t\tRB_AUGMENT(parent);\t\t\t\t\t\\\n\t} else\t\t\t\t\t\t\t\t\\\n\t\tRB_ROOT(head) = child;\t\t\t\t\t\\\ncolor:\t\t\t\t\t\t\t\t\t\\\n\tif (color == RB_BLACK)\t\t\t\t\t\t\\\n\t\tname##_RB_REMOVE_COLOR(head, parent, child);\t\t\\\n\treturn (old);\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\\\n\n#define RB_GENERATE_INSERT(name, type, field, cmp, attr)\t\t\\\n/* Inserts a node into the RB tree */\t\t\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_INSERT(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *tmp;\t\t\t\t\t\t\\\n\tstruct type *parent = NULL;\t\t\t\t\t\\\n\tint comp = 0;\t\t\t\t\t\t\t\\\n\ttmp = RB_ROOT(head);\t\t\t\t\t\t\\\n\twhile (tmp) {\t\t\t\t\t\t\t\\\n\t\tparent = tmp;\t\t\t\t\t\t\\\n\t\tcomp = (cmp)(elm, parent);\t\t\t\t\\\n\t\tif (comp < 0)\t\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(tmp, field);\t\t\t\\\n\t\telse if (comp > 0)\t\t\t\t\t\\\n\t\t\ttmp = RB_RIGHT(tmp, field);\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\treturn (tmp);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tRB_SET(elm, parent, field);\t\t\t\t\t\\\n\tif (parent != NULL) {\t\t\t\t\t\t\\\n\t\tif (comp < 0)\t\t\t\t\t\t\\\n\t\t\tRB_LEFT(parent, field) = elm;\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\tRB_RIGHT(parent, field) = elm;\t\t\t\\\n\t\tRB_AUGMENT(parent);\t\t\t\t\t\\\n\t} else\t\t\t\t\t\t\t\t\\\n\t\tRB_ROOT(head) = elm;\t\t\t\t\t\\\n\tname##_RB_INSERT_COLOR(head, elm);\t\t\t\t\\\n\treturn (NULL);\t\t\t\t\t\t\t\\\n}\n\n#define RB_GENERATE_FIND(name, type, field, cmp, attr)\t\t\t\\\n/* Finds the node with the same key as elm */\t\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_FIND(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *tmp = RB_ROOT(head);\t\t\t\t\\\n\tint comp;\t\t\t\t\t\t\t\\\n\twhile (tmp) {\t\t\t\t\t\t\t\\\n\t\tcomp = cmp(elm, tmp);\t\t\t\t\t\\\n\t\tif (comp < 0)\t\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(tmp, field);\t\t\t\\\n\t\telse if (comp > 0)\t\t\t\t\t\\\n\t\t\ttmp = RB_RIGHT(tmp, field);\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\treturn (tmp);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (NULL);\t\t\t\t\t\t\t\\\n}\n\n#define RB_GENERATE_NFIND(name, type, field, cmp, attr)\t\t\t\\\n/* Finds the first node greater than or equal to the search key */\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_NFIND(struct name *head, struct type *elm)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *tmp = RB_ROOT(head);\t\t\t\t\\\n\tstruct type *res = NULL;\t\t\t\t\t\\\n\tint comp;\t\t\t\t\t\t\t\\\n\twhile (tmp) {\t\t\t\t\t\t\t\\\n\t\tcomp = cmp(elm, tmp);\t\t\t\t\t\\\n\t\tif (comp < 0) {\t\t\t\t\t\t\\\n\t\t\tres = tmp;\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(tmp, field);\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\telse if (comp > 0)\t\t\t\t\t\\\n\t\t\ttmp = RB_RIGHT(tmp, field);\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\treturn (tmp);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (res);\t\t\t\t\t\t\t\\\n}\n\n#define RB_GENERATE_NEXT(name, type, field, attr)\t\t\t\\\n/* ARGSUSED */\t\t\t\t\t\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_NEXT(struct type *elm)\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tif (RB_RIGHT(elm, field)) {\t\t\t\t\t\\\n\t\telm = RB_RIGHT(elm, field);\t\t\t\t\\\n\t\twhile (RB_LEFT(elm, field))\t\t\t\t\\\n\t\t\telm = RB_LEFT(elm, field);\t\t\t\\\n\t} else {\t\t\t\t\t\t\t\\\n\t\tif (RB_PARENT(elm, field) &&\t\t\t\t\\\n\t\t    (elm == RB_LEFT(RB_PARENT(elm, field), field)))\t\\\n\t\t\telm = RB_PARENT(elm, field);\t\t\t\\\n\t\telse {\t\t\t\t\t\t\t\\\n\t\t\twhile (RB_PARENT(elm, field) &&\t\t\t\\\n\t\t\t    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\\\n\t\t\t\telm = RB_PARENT(elm, field);\t\t\\\n\t\t\telm = RB_PARENT(elm, field);\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (elm);\t\t\t\t\t\t\t\\\n}\n\n#define RB_GENERATE_PREV(name, type, field, attr)\t\t\t\\\n/* ARGSUSED */\t\t\t\t\t\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_PREV(struct type *elm)\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tif (RB_LEFT(elm, field)) {\t\t\t\t\t\\\n\t\telm = RB_LEFT(elm, field);\t\t\t\t\\\n\t\twhile (RB_RIGHT(elm, field))\t\t\t\t\\\n\t\t\telm = RB_RIGHT(elm, field);\t\t\t\\\n\t} else {\t\t\t\t\t\t\t\\\n\t\tif (RB_PARENT(elm, field) &&\t\t\t\t\\\n\t\t    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\t\\\n\t\t\telm = RB_PARENT(elm, field);\t\t\t\\\n\t\telse {\t\t\t\t\t\t\t\\\n\t\t\twhile (RB_PARENT(elm, field) &&\t\t\t\\\n\t\t\t    (elm == RB_LEFT(RB_PARENT(elm, field), field)))\\\n\t\t\t\telm = RB_PARENT(elm, field);\t\t\\\n\t\t\telm = RB_PARENT(elm, field);\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (elm);\t\t\t\t\t\t\t\\\n}\n\n#define RB_GENERATE_MINMAX(name, type, field, attr)\t\t\t\\\nattr struct type *\t\t\t\t\t\t\t\\\nname##_RB_MINMAX(struct name *head, int val)\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\\\n\tstruct type *tmp = RB_ROOT(head);\t\t\t\t\\\n\tstruct type *parent = NULL;\t\t\t\t\t\\\n\twhile (tmp) {\t\t\t\t\t\t\t\\\n\t\tparent = tmp;\t\t\t\t\t\t\\\n\t\tif (val < 0)\t\t\t\t\t\t\\\n\t\t\ttmp = RB_LEFT(tmp, field);\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\ttmp = RB_RIGHT(tmp, field);\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\treturn (parent);\t\t\t\t\t\t\\\n}\n\n#define RB_NEGINF\t-1\n#define RB_INF\t1\n\n#define RB_INSERT(name, x, y)\tname##_RB_INSERT(x, y)\n#define RB_REMOVE(name, x, y)\tname##_RB_REMOVE(x, y)\n#define RB_FIND(name, x, y)\tname##_RB_FIND(x, y)\n#define RB_NFIND(name, x, y)\tname##_RB_NFIND(x, y)\n#define RB_NEXT(name, x, y)\tname##_RB_NEXT(y)\n#define RB_PREV(name, x, y)\tname##_RB_PREV(y)\n#define RB_MIN(name, x)\t\tname##_RB_MINMAX(x, RB_NEGINF)\n#define RB_MAX(name, x)\t\tname##_RB_MINMAX(x, RB_INF)\n\n#define RB_FOREACH(x, name, head)\t\t\t\t\t\\\n\tfor ((x) = RB_MIN(name, head);\t\t\t\t\t\\\n\t     (x) != NULL;\t\t\t\t\t\t\\\n\t     (x) = name##_RB_NEXT(x))\n\n#define RB_FOREACH_FROM(x, name, y)\t\t\t\t\t\\\n\tfor ((x) = (y);\t\t\t\t\t\t\t\\\n\t    ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);\t\\\n\t     (x) = (y))\n\n#define RB_FOREACH_SAFE(x, name, head, y)\t\t\t\t\\\n\tfor ((x) = RB_MIN(name, head);\t\t\t\t\t\\\n\t    ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);\t\\\n\t     (x) = (y))\n\n#define RB_FOREACH_REVERSE(x, name, head)\t\t\t\t\\\n\tfor ((x) = RB_MAX(name, head);\t\t\t\t\t\\\n\t     (x) != NULL;\t\t\t\t\t\t\\\n\t     (x) = name##_RB_PREV(x))\n\n#define RB_FOREACH_REVERSE_FROM(x, name, y)\t\t\t\t\\\n\tfor ((x) = (y);\t\t\t\t\t\t\t\\\n\t    ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);\t\\\n\t     (x) = (y))\n\n#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)\t\t\t\\\n\tfor ((x) = RB_MAX(name, head);\t\t\t\t\t\\\n\t    ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);\t\\\n\t     (x) = (y))\n\n#endif\t/* _SYS_TREE_H_ */\n"
  },
  {
    "path": "tpws/params.c",
    "content": "#include \"params.h\"\n#include <stdarg.h>\n#include <syslog.h>\n#include <errno.h>\n#ifdef __ANDROID__\n#include <android/log.h>\n#endif\n\nconst char *progname = \"tpws\";\n\nint DLOG_FILE(FILE *F, const char *format, va_list args)\n{\n\treturn vfprintf(F, format, args);\n}\nint DLOG_CON(const char *format, int syslog_priority, va_list args)\n{\n\treturn DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args);\n}\nint DLOG_FILENAME(const char *filename, const char *format, va_list args)\n{\n\tint r;\n\tFILE *F = fopen(filename,\"at\");\n\tif (F)\n\t{\n\t\tr = DLOG_FILE(F, format, args);\n\t\tfclose(F);\n\t}\n\telse\n\t\tr=-1;\n\treturn r;\n}\n\ntypedef void (*f_log_function)(int priority, const char *line);\n\nstatic char log_buf[1024];\nstatic size_t log_buf_sz=0;\nstatic void syslog_log_function(int priority, const char *line)\n{\n\tsyslog(priority,\"%s\",log_buf);\n}\n#ifdef __ANDROID__\nstatic enum android_LogPriority syslog_priority_to_android(int priority)\n{\n\tenum android_LogPriority ap;\n\tswitch(priority)\n\t{\n\t\tcase LOG_INFO:\n\t\tcase LOG_NOTICE: ap=ANDROID_LOG_INFO; break;\n\t\tcase LOG_ERR: ap=ANDROID_LOG_ERROR; break;\n\t\tcase LOG_WARNING: ap=ANDROID_LOG_WARN; break;\n\t\tcase LOG_EMERG:\n\t\tcase LOG_ALERT:\n\t\tcase LOG_CRIT: ap=ANDROID_LOG_FATAL; break;\n\t\tcase LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break;\n\t\tdefault: ap=ANDROID_LOG_UNKNOWN;\n\t}\n\treturn ap;\n}\nstatic void android_log_function(int priority, const char *line)\n{\n\t__android_log_print(syslog_priority_to_android(priority), progname, \"%s\", line);\n}\n#endif\nstatic void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args)\n{\n\tif (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0)\n\t{\n\t\tlog_buf_sz=strlen(log_buf);\n\t\t// log when buffer is full or buffer ends with \\n\n\t\tif (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\\n'))\n\t\t{\n\t\t\tlog_function(syslog_priority,log_buf);\n\t\t\tlog_buf_sz = 0;\n\t\t}\n\t}\n}\n\nstatic int DLOG_VA(const char *format, int syslog_priority, bool condup, int level, va_list args)\n{\n\tint r=0;\n\tva_list args2;\n\n\tif (condup && !(params.debug>=level && params.debug_target==LOG_TARGET_CONSOLE))\n\t{\n\t\tva_copy(args2,args);\n\t\tDLOG_CON(format,syslog_priority,args2);\n\t\tva_end(args2);\n\t}\n\tif (params.debug>=level)\n\t{\n\t\tswitch(params.debug_target)\n\t\t{\n\t\t\tcase LOG_TARGET_CONSOLE:\n\t\t\t\tr = DLOG_CON(format,syslog_priority,args);\n\t\t\t\tbreak;\n\t\t\tcase LOG_TARGET_FILE:\n\t\t\t\tr = DLOG_FILENAME(params.debug_logfile,format,args);\n\t\t\t\tbreak;\n\t\t\tcase LOG_TARGET_SYSLOG:\n\t\t\t\t// skip newlines\n\t\t\t\tlog_buffered(syslog_log_function,syslog_priority,format,args);\n\t\t\t\tr = 1;\n\t\t\t\tbreak;\n#ifdef __ANDROID__\n\t\t\tcase LOG_TARGET_ANDROID:\n\t\t\t\t// skip newlines\n\t\t\t\tlog_buffered(android_log_function,syslog_priority,format,args);\n\t\t\t\tr = 1;\n\t\t\t\tbreak;\n#endif\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn r;\n}\n\nint DLOG(const char *format, int level, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, level);\n\tr = DLOG_VA(format, LOG_DEBUG, false, level, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_CONDUP(const char *format, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, format);\n\tr = DLOG_VA(format, LOG_DEBUG, true, 1, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_ERR(const char *format, ...)\n{\n\tint r;\n\tva_list args;\n\tva_start(args, format);\n\tr = DLOG_VA(format, LOG_ERR, true, 1, args);\n\tva_end(args);\n\treturn r;\n}\nint DLOG_PERROR(const char *s)\n{\n\treturn DLOG_ERR(\"%s: %s\\n\", s, strerror(errno));\n}\n\n\nint LOG_APPEND(const char *filename, const char *format, va_list args)\n{\n\tint r;\n\tFILE *F = fopen(filename,\"at\");\n\tif (F)\n\t{\n\t\tfprint_localtime(F);\n\t\tfprintf(F, \" : \");\n\t\tr = vfprintf(F, format, args);\n\t\tfprintf(F, \"\\n\");\n\t\tfclose(F);\n\t}\n\telse\n\t\tr=-1;\n\treturn r;\n}\n\nint HOSTLIST_DEBUGLOG_APPEND(const char *format, ...)\n{\n\tif (*params.hostlist_auto_debuglog)\n\t{\n\t\tint r;\n\t\tva_list args;\n\n\t\tva_start(args, format);\n\t\tr = LOG_APPEND(params.hostlist_auto_debuglog, format, args);\n\t\tva_end(args);\n\t\treturn r;\n\t}\n\telse\n\t\treturn 0;\n}\n\nvoid hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit)\n{\n\tsize_t k;\n\tbool bcut = false;\n\tif (size > limit)\n\t{\n\t\tsize = limit;\n\t\tbcut = true;\n\t}\n\tif (!size) return;\n\tfor (k = 0; k < size; k++) VPRINT(\"%02X \", data[k]);\n\tVPRINT(bcut ? \"... : \" : \": \");\n\tfor (k = 0; k < size; k++) VPRINT(\"%c\", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.');\n\tif (bcut) VPRINT(\" ...\");\n}\n\nvoid dp_init(struct desync_profile *dp)\n{\n\tLIST_INIT(&dp->hl_collection);\n\tLIST_INIT(&dp->hl_collection_exclude);\n\tLIST_INIT(&dp->ips_collection);\n\tLIST_INIT(&dp->ips_collection_exclude);\n\tLIST_INIT(&dp->pf_tcp);\n\n\tdp->filter_ipv4 = dp->filter_ipv6 = true;\n\tmemcpy(dp->hostspell, \"host\", 4); // default hostspell\n\tdp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;\n\tdp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;\n}\n\nstruct desync_profile_list *dp_list_add(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list));\n\tif (!entry) return NULL;\n\n\tdp_init(&entry->dp);\n\n\t// add to the tail\n\tstruct desync_profile_list *dpn,*dpl=LIST_FIRST(head);\n\tif (dpl)\n\t{\n\t\twhile ((dpn=LIST_NEXT(dpl,next))) dpl = dpn;\n\t\tLIST_INSERT_AFTER(dpl, entry, next);\n\t}\n\telse\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\n\treturn entry;\n}\nstatic void dp_clear_dynamic(struct desync_profile *dp)\n{\n\thostlist_collection_destroy(&dp->hl_collection);\n\thostlist_collection_destroy(&dp->hl_collection_exclude);\n\tipset_collection_destroy(&dp->ips_collection);\n\tipset_collection_destroy(&dp->ips_collection_exclude);\n\tport_filters_destroy(&dp->pf_tcp);\n\tHostFailPoolDestroy(&dp->hostlist_auto_fail_counters);\n}\nvoid dp_clear(struct desync_profile *dp)\n{\n\tdp_clear_dynamic(dp);\n\tmemset(dp,0,sizeof(*dp));\n}\nvoid dp_entry_destroy(struct desync_profile_list *entry)\n{\n\tdp_clear_dynamic(&entry->dp);\n\tfree(entry);\n}\nvoid dp_list_destroy(struct desync_profile_list_head *head)\n{\n\tstruct desync_profile_list *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tdp_entry_destroy(entry);\n\t}\n}\n\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\nvoid cleanup_args(struct params_s *params)\n{\n\twordfree(&params->wexp);\n}\n#endif\nvoid cleanup_params(struct params_s *params)\n{\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\tcleanup_args(params);\n#endif\n\n\tdp_list_destroy(&params->desync_profiles);\n\n\thostlist_files_destroy(&params->hostlists);\n\tipset_files_destroy(&params->ipsets);\n\tipcacheDestroy(&params->ipcache);\n\tfree(params->user); params->user=NULL;\n}\n"
  },
  {
    "path": "tpws/params.h",
    "content": "#pragma once\n\n#include <net/if.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/param.h>\n#include <sys/types.h>\n#include <sys/queue.h>\n#include <time.h>\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n#include <wordexp.h>\n#endif\n\n\n#include \"tpws.h\"\n#include \"pools.h\"\n#include \"helpers.h\"\n#include \"protocol.h\"\n\n#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT\t3\n#define\tHOSTLIST_AUTO_FAIL_TIME_DEFAULT \t60\n\n#define FIX_SEG_DEFAULT_MAX_WAIT 50\n\n#define IPCACHE_LIFETIME 7200\n\n#define MAX_GIDS 64\n\nenum bindll { unwanted=0, no, prefer, force };\n\n#define MAX_BINDS 32\nstruct bind_s\n{\n\tchar bindaddr[64],bindiface[IF_NAMESIZE];\n\tbool bind_if6;\n\tenum bindll bindll;\n\tint bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll;\n};\n\n#define MAX_SPLITS 16\n\nenum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID };\n\nstruct desync_profile\n{\n\tint n;\t// number of the profile\n\n\tbool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase;\n\tint hostpad;\n\tchar hostspell[4];\n\tbool split_any_protocol;\n\tbool disorder, disorder_http, disorder_tls;\n\tbool oob, oob_http, oob_tls;\n\tuint8_t oob_byte;\n\n\t// multisplit\n\tstruct proto_pos splits[MAX_SPLITS];\n\tint split_count;\n\tstruct proto_pos tlsrec;\n\n\tint mss;\n\n\tbool tamper_start_n,tamper_cutoff_n;\n\tunsigned int tamper_start,tamper_cutoff;\n\n\tbool filter_ipv4,filter_ipv6;\n\tstruct port_filters_head pf_tcp;\n\tuint32_t filter_l7;\t// L7_PROTO_* bits\n\n\t// list of pointers to ipsets\n\tstruct ipset_collection_head ips_collection, ips_collection_exclude;\n\n\t// list of pointers to hostlist files\n\tstruct hostlist_collection_head hl_collection, hl_collection_exclude;\n\t// pointer to autohostlist. NULL if no autohostlist for the profile.\n\tstruct hostlist_file *hostlist_auto;\n\tint hostlist_auto_fail_threshold, hostlist_auto_fail_time;\n\n\thostfail_pool *hostlist_auto_fail_counters;\n};\n\n#define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&dp->ips_collection) && !LIST_FIRST(&dp->ips_collection_exclude))\n#define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&dp->ips_collection) && ipset_collection_is_empty(&dp->ips_collection_exclude))\n#define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&dp->hl_collection) && hostlist_collection_is_empty(&dp->hl_collection_exclude))\n\nstruct desync_profile_list {\n\tstruct desync_profile dp;\n\tLIST_ENTRY(desync_profile_list) next;\n};\nLIST_HEAD(desync_profile_list_head, desync_profile_list);\nstruct desync_profile_list *dp_list_add(struct desync_profile_list_head *head);\nvoid dp_entry_destroy(struct desync_profile_list *entry);\nvoid dp_list_destroy(struct desync_profile_list_head *head);\nvoid dp_init(struct desync_profile *dp);\nvoid dp_clear(struct desync_profile *dp);\n\nstruct params_s\n{\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\twordexp_t wexp; // for file based config\n#endif\n\n\tint debug;\n\tenum log_target debug_target;\n\tchar debug_logfile[PATH_MAX];\n\n\tstruct bind_s binds[MAX_BINDS];\n\tint binds_last;\n\tbool bind_wait_only;\n\tuint16_t port;\n\tstruct sockaddr_in connect_bind4;\n\tstruct sockaddr_in6 connect_bind6;\n\tchar connect_bind6_ifname[IF_NAMESIZE];\n\n\tuint8_t proxy_type;\n\tunsigned int fix_seg;\n\tbool fix_seg_avail;\n\tbool no_resolve;\n\tbool skip_nodelay;\n\tbool daemon;\n\tbool droproot;\n\tchar *user;\n\tuid_t uid;\n\tgid_t gid[MAX_GIDS];\n\tint gid_count;\n\tchar pidfile[PATH_MAX];\n\tint maxconn,resolver_threads,maxfiles,max_orphan_time;\n\tint local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf;\n#if defined(__linux__) || defined(__APPLE__)\n\tint tcp_user_timeout_local,tcp_user_timeout_remote;\n#endif\n\n#if defined(BSD)\n\tbool pf_enable;\n#endif\n#ifdef SPLICE_PRESENT\n\tbool nosplice;\n#endif\n\n\tint ttl_default;\n\tchar hostlist_auto_debuglog[PATH_MAX];\n\n\t// hostlist files with data for all profiles\n\tstruct hostlist_files_head hostlists;\n\t// ipset files with data for all profiles\n\tstruct ipset_files_head ipsets;\n\n\tbool tamper; // any tamper option is set\n\tbool tamper_lim; // tamper-start or tamper-cutoff set in any profile\n\tstruct desync_profile_list_head desync_profiles;\n\n\tunsigned int ipcache_lifetime;\n\tbool cache_hostname;\n\tip_cache ipcache;\n};\n\nextern struct params_s params;\nextern const char *progname;\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\nvoid cleanup_args(struct params_s *params);\n#endif\nvoid cleanup_params(struct params_s *params);\n\nint DLOG(const char *format, int level, ...);\nint DLOG_CONDUP(const char *format, ...);\nint DLOG_ERR(const char *format, ...);\nint DLOG_PERROR(const char *s);\nint HOSTLIST_DEBUGLOG_APPEND(const char *format, ...);\nvoid hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit);\n\n#define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__)\n#define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__)\n"
  },
  {
    "path": "tpws/pools.c",
    "content": "#define _GNU_SOURCE\n#include \"pools.h\"\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <arpa/inet.h>\n\n#define DESTROY_STR_POOL(etype, ppool) \\\n\tetype *elem, *tmp; \\\n\tHASH_ITER(hh, *ppool, elem, tmp) { \\\n\t\tfree(elem->str); \\\n\t\tHASH_DEL(*ppool, elem); \\\n\t\tfree(elem); \\\n\t}\n\t\n#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \\\n\tetype *elem; \\\n\tif (!(elem = (etype*)malloc(sizeof(etype)))) \\\n\t\treturn false; \\\n\tif (!(elem->str = malloc(keystr_len + 1))) \\\n\t{ \\\n\t\tfree(elem); \\\n\t\treturn false; \\\n\t} \\\n\tmemcpy(elem->str, keystr, keystr_len); \\\n\telem->str[keystr_len] = 0; \\\n\toom = false; \\\n\tHASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \\\n\tif (oom) \\\n\t{ \\\n\t\tfree(elem->str); \\\n\t\tfree(elem); \\\n\t\treturn false; \\\n\t}\n#define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \\\n\tetype *elem_find; \\\n\tHASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \\\n\tif (!elem_find) { \\\n\t\tADD_STR_POOL(etype,ppool,keystr,keystr_len); \\\n\t\telem->flags = flg; \\\n\t}\n\n#undef uthash_nonfatal_oom\n#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)\nstatic bool oom = false;\nstatic void ut_oom_recover(void *elem)\n{\n\toom = true;\n}\n\n// for not zero terminated strings\nbool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags)\n{\n\tADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags)\n\treturn true;\n}\n// for zero terminated strings\nbool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags)\n{\n\treturn HostlistPoolAddStrLen(pp, s, strlen(s), flags);\n}\n\nhostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s)\n{\n\thostlist_pool *elem;\n\tHASH_FIND_STR(p, s, elem);\n\treturn elem;\n}\nbool HostlistPoolCheckStr(hostlist_pool *p, const char *s)\n{\n\treturn !!HostlistPoolGetStr(p,s);\n}\n\nvoid HostlistPoolDestroy(hostlist_pool **pp)\n{\n\tDESTROY_STR_POOL(hostlist_pool, pp)\n}\n\n\n\nvoid HostFailPoolDestroy(hostfail_pool **pp)\n{\n\tDESTROY_STR_POOL(hostfail_pool, pp)\n}\nhostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)\n{\n\tsize_t slen = strlen(s);\n\tADD_STR_POOL(hostfail_pool, pp, s, slen)\n\telem->expire = time(NULL) + fail_time;\n\telem->counter = 0;\n\treturn elem;\n}\nhostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)\n{\n\thostfail_pool *elem;\n\tHASH_FIND_STR(p, s, elem);\n\treturn elem;\n}\nvoid HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)\n{\n\tfree(elem->str);\n\tHASH_DEL(*p, elem);\n\tfree(elem);\n}\nvoid HostFailPoolPurge(hostfail_pool **pp)\n{\n\thostfail_pool *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *pp, elem, tmp)\n\t{\n\t\tif (now >= elem->expire)\n\t\t\tHostFailPoolDel(pp, elem);\n\t}\n}\nstatic time_t host_fail_purge_prev=0;\nvoid HostFailPoolPurgeRateLimited(hostfail_pool **pp)\n{\n\ttime_t now = time(NULL);\n\t// do not purge too often to save resources\n\tif (host_fail_purge_prev != now)\n\t{\n\t\tHostFailPoolPurge(pp);\n\t\thost_fail_purge_prev = now;\n\t}\n}\nvoid HostFailPoolDump(hostfail_pool *p)\n{\n\thostfail_pool *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, p, elem, tmp)\n\t\tprintf(\"host=%s counter=%d time_left=%lld\\n\",elem->str,elem->counter,(long long int)elem->expire-now);\n}\n\n\nbool strlist_add(struct str_list_head *head, const char *filename)\n{\n\tstruct str_list *entry = malloc(sizeof(struct str_list));\n\tif (!entry) return false;\n\tentry->str = strdup(filename);\n\tif (!entry->str)\n\t{\n\t\tfree(entry);\n\t\treturn false;\n\t}\n\tLIST_INSERT_HEAD(head, entry, next);\n\treturn true;\n}\nstatic void strlist_entry_destroy(struct str_list *entry)\n{\n\tfree(entry->str);\n\tfree(entry);\n}\nvoid strlist_destroy(struct str_list_head *head)\n{\n\tstruct str_list *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tstrlist_entry_destroy(entry);\n\t}\n}\n\n\n\nstruct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename)\n{\n\tstruct hostlist_file *entry = malloc(sizeof(struct hostlist_file));\n\tif (entry)\n\t{\n\t\tif (filename)\n\t\t{\n\t\t\tif (!(entry->filename = strdup(filename)))\n\t\t\t{\n\t\t\t\tfree(entry);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tentry->filename = NULL;\n\t\tFILE_MOD_RESET(&entry->mod_sig);\n\t\tentry->hostlist = NULL;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstatic void hostlist_files_entry_destroy(struct hostlist_file *entry)\n{\n\tfree(entry->filename);\n\tHostlistPoolDestroy(&entry->hostlist);\n\tfree(entry);\n}\nvoid hostlist_files_destroy(struct hostlist_files_head *head)\n{\n\tstruct hostlist_file *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\thostlist_files_entry_destroy(entry);\n\t}\n}\nstruct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename)\n{\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, head, next)\n\t{\n\t\tif (hfile->filename && !strcmp(hfile->filename,filename))\n\t\t\treturn hfile;\n\t}\n\treturn NULL;\n}\nvoid hostlist_files_reset_modtime(struct hostlist_files_head *list)\n{\n\tstruct hostlist_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t\tFILE_MOD_RESET(&hfile->mod_sig);\n}\n\nstruct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile)\n{\n\tstruct hostlist_item *entry = malloc(sizeof(struct hostlist_item));\n\tif (entry)\n\t{\n\t\tentry->hfile = hfile;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid hostlist_collection_destroy(struct hostlist_collection_head *head)\n{\n\tstruct hostlist_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nstruct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename)\n{\n\tstruct hostlist_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->filename && !strcmp(item->hfile->filename,filename))\n\t\t\treturn item;\n\t}\n\treturn NULL;\n}\nbool hostlist_collection_is_empty(const struct hostlist_collection_head *head)\n{\n\tconst struct hostlist_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->hostlist)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nstatic int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q)\n{\n\tunsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen;\n\tunsigned int df = bitlen & 7, bytes = bitlen >> 3;\n\tint cmp = memcmp(p->data, q->data, bytes);\n\n\tif (cmp || !df) return cmp;\n\n\tuint8_t c1 = p->data[bytes] >> (8 - df);\n\tuint8_t c2 = q->data[bytes] >> (8 - df);\n\treturn c1<c2 ? -1 : c1==c2 ? 0 : 1;\n}\nKAVL_INIT(kavl_bit, struct kavl_bit_elem, head, kavl_bit_cmp)\nstatic void kavl_bit_destroy_elem(struct kavl_bit_elem *e)\n{\n\tif (e)\n\t{\n\t\tfree(e->data);\n\t\tfree(e);\n\t}\n}\nvoid kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen)\n{\n\tstruct kavl_bit_elem temp = {\n\t\t.bitlen = bitlen, .data = (uint8_t*)data\n\t};\n\tkavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0));\n}\nvoid kavl_bit_destroy(struct kavl_bit_elem **hdr)\n{\n\twhile (*hdr)\n\t{\n\t\tstruct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr);\n\t\tif (!e)\tbreak;\n\t\tkavl_bit_destroy_elem(e);\n\t}\n\tfree(*hdr);\n}\nstruct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size)\n{\n\tif (!struct_size) struct_size=sizeof(struct kavl_bit_elem);\n\n\tstruct kavl_bit_elem *v, *e = calloc(1, struct_size);\n\tif (!e) return 0;\n\n\te->bitlen = bitlen;\n\te->data = data;\n\n\tv = kavl_insert(kavl_bit, hdr, e, 0);\n\twhile (e != v && e->bitlen < v->bitlen)\n\t{\n\t\tkavl_bit_delete(hdr, v->data, v->bitlen);\n\t\tv = kavl_insert(kavl_bit, hdr, e, 0);\n\t}\n\tif (e != v) kavl_bit_destroy_elem(e);\n\treturn v;\n}\nstruct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen)\n{\n\tstruct kavl_bit_elem temp = {\n\t\t.bitlen = bitlen, .data = (uint8_t*)data\n\t};\n\treturn kavl_find(kavl_bit, hdr, &temp, 0);\n}\n\nstatic bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen)\n{\n\tuint8_t bytelen = (preflen+7)>>3;\n\tuint8_t *abuf = malloc(bytelen);\n\tif (!abuf) return false;\n\tmemcpy(abuf,a,bytelen);\n\tif (!kavl_bit_add(ipset,abuf,preflen,0))\n\t{\n\t\tfree(abuf);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nbool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen)\n{\n\treturn !!kavl_bit_get(ipset,a,preflen);\n}\nbool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen)\n{\n\tif (preflen>32) return false;\n\treturn ipset_kavl_add(ipset,a,preflen);\n}\nvoid ipset4Print(struct kavl_bit_elem *ipset)\n{\n\tif (!ipset) return;\n\n\tstruct cidr4 c;\n\tconst struct kavl_bit_elem *elem;\n\tkavl_itr_t(kavl_bit) itr;\n\tkavl_itr_first(kavl_bit, ipset, &itr);\n\tdo\n\t{\n\t\telem = kavl_at(&itr);\n\t\tc.preflen = elem->bitlen;\n\t\texpand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr));\n\t\tprint_cidr4(&c);\n\t\tprintf(\"\\n\");\n\t}\n\twhile (kavl_itr_next(kavl_bit, &itr));\n}\n\nbool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen)\n{\n\treturn !!kavl_bit_get(ipset,a,preflen);\n}\nbool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen)\n{\n\tif (preflen>128) return false;\n\treturn ipset_kavl_add(ipset,a,preflen);\n}\nvoid ipset6Print(struct kavl_bit_elem *ipset)\n{\n\tif (!ipset) return;\n\n\tstruct cidr6 c;\n\tconst struct kavl_bit_elem *elem;\n\tkavl_itr_t(kavl_bit) itr;\n\tkavl_itr_first(kavl_bit, ipset, &itr);\n\tdo\n\t{\n\t\telem = kavl_at(&itr);\n\t\tc.preflen = elem->bitlen;\n\t\texpand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr));\n\t\tprint_cidr6(&c);\n\t\tprintf(\"\\n\");\n\t}\n\twhile (kavl_itr_next(kavl_bit, &itr));\n}\n\nvoid ipsetDestroy(ipset *ipset)\n{\n\tkavl_bit_destroy(&ipset->ips4);\n\tkavl_bit_destroy(&ipset->ips6);\n}\nvoid ipsetPrint(ipset *ipset)\n{\n\tipset4Print(ipset->ips4);\n\tipset6Print(ipset->ips6);\n}\n\n\nstruct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename)\n{\n\tstruct ipset_file *entry = malloc(sizeof(struct ipset_file));\n\tif (entry)\n\t{\n\t\tif (filename)\n\t\t{\n\t\t\tif (!(entry->filename = strdup(filename)))\n\t\t\t{\n\t\t\t\tfree(entry);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tentry->filename = NULL;\n\t\tFILE_MOD_RESET(&entry->mod_sig);\n\t\tmemset(&entry->ipset,0,sizeof(entry->ipset));\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstatic void ipset_files_entry_destroy(struct ipset_file *entry)\n{\n\tfree(entry->filename);\n\tipsetDestroy(&entry->ipset);\n\tfree(entry);\n}\nvoid ipset_files_destroy(struct ipset_files_head *head)\n{\n\tstruct ipset_file *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tipset_files_entry_destroy(entry);\n\t}\n}\nstruct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename)\n{\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, head, next)\n\t{\n\t\tif (hfile->filename && !strcmp(hfile->filename,filename))\n\t\t\treturn hfile;\n\t}\n\treturn NULL;\n}\nvoid ipset_files_reset_modtime(struct ipset_files_head *list)\n{\n\tstruct ipset_file *hfile;\n\n\tLIST_FOREACH(hfile, list, next)\n\t\tFILE_MOD_RESET(&hfile->mod_sig);\n}\n\nstruct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile)\n{\n\tstruct ipset_item *entry = malloc(sizeof(struct ipset_item));\n\tif (entry)\n\t{\n\t\tentry->hfile = hfile;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid ipset_collection_destroy(struct ipset_collection_head *head)\n{\n\tstruct ipset_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nstruct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename)\n{\n\tstruct ipset_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (item->hfile->filename && !strcmp(item->hfile->filename,filename))\n\t\t\treturn item;\n\t}\n\treturn NULL;\n}\nbool ipset_collection_is_empty(const struct ipset_collection_head *head)\n{\n\tconst struct ipset_item *item;\n\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (!IPSET_EMPTY(&item->hfile->ipset))\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nbool port_filter_add(struct port_filters_head *head, const port_filter *pf)\n{\n\tstruct port_filter_item *entry = malloc(sizeof(struct port_filter_item));\n\tif (entry)\n\t{\n\t\tentry->pf = *pf;\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nvoid port_filters_destroy(struct port_filters_head *head)\n{\n\tstruct port_filter_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry);\n\t}\n}\nbool port_filters_in_range(const struct port_filters_head *head, uint16_t port)\n{\n\tconst struct port_filter_item *item;\n\n\tif (!LIST_FIRST(head)) return true;\n\tLIST_FOREACH(item, head, next)\n\t{\n\t\tif (pf_in_range(port, &item->pf))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\nbool port_filters_deny_if_empty(struct port_filters_head *head)\n{\n\tport_filter pf;\n\tif (LIST_FIRST(head)) return true;\n\treturn pf_parse(\"0\",&pf) && port_filter_add(head,&pf);\n}\n\n\n\t\t\nstruct blob_item *blob_collection_add(struct blob_collection_head *head)\n{\n\tstruct blob_item *entry = calloc(1,sizeof(struct blob_item));\n\tif (entry)\n\t{\n\t\t// insert to the end\n\t\tstruct blob_item *itemc,*iteml=LIST_FIRST(head);\n\t\tif (iteml)\n\t\t{\n\t\t\twhile ((itemc=LIST_NEXT(iteml,next))) iteml = itemc;\n\t\t\tLIST_INSERT_AFTER(iteml, entry, next);\n\t\t}\n\t\telse\n\t\t\tLIST_INSERT_HEAD(head, entry, next);\n\t}\n\treturn entry;\n}\nstruct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve)\n{\n\tstruct blob_item *entry = calloc(1,sizeof(struct blob_item));\n\tif (!entry) return NULL;\n\tif (!(entry->data = malloc(size+size_reserve))) \n\t{\n\t\tfree(entry);\n\t\treturn NULL;\n\t}\n\tif (data) memcpy(entry->data,data,size);\n\tentry->size = size;\n\tentry->size_buf = size+size_reserve;\n\n\t// insert to the end\n\tstruct blob_item *itemc,*iteml=LIST_FIRST(head);\n\tif (iteml)\n\t{\n\t\twhile ((itemc=LIST_NEXT(iteml,next))) iteml = itemc;\n\t\tLIST_INSERT_AFTER(iteml, entry, next);\n\t}\n\telse\n\t\tLIST_INSERT_HEAD(head, entry, next);\n\n\treturn entry;\n}\n\nvoid blob_collection_destroy(struct blob_collection_head *head)\n{\n\tstruct blob_item *entry;\n\twhile ((entry = LIST_FIRST(head)))\n\t{\n\t\tLIST_REMOVE(entry, next);\n\t\tfree(entry->extra);\n\t\tfree(entry->extra2);\n\t\tfree(entry->data);\n\t\tfree(entry);\n\t}\n}\nbool blob_collection_empty(const struct blob_collection_head *head)\n{\n\treturn !LIST_FIRST(head);\n}\n\n\n\nstatic void ipcache_item_touch(ip_cache_item *item)\n{\n\ttime(&item->last);\n}\nstatic void ipcache_item_init(ip_cache_item *item)\n{\n\tipcache_item_touch(item);\n\titem->hostname = NULL;\n\titem->hostname_is_ip = false;\n}\nstatic void ipcache_item_destroy(ip_cache_item *item)\n{\n\tfree(item->hostname);\n}\n\nstatic void ipcache4Destroy(ip_cache4 **ipcache)\n{\n\tip_cache4 *elem, *tmp;\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tHASH_DEL(*ipcache, elem);\n\t\tipcache_item_destroy(&elem->data);\n\t\tfree(elem);\n\t}\n}\nstatic void ipcache4Key(ip4if *key, const struct in_addr *a)\n{\n\tmemset(key,0,sizeof(*key)); // make sure everything is zero\n\tkey->addr = *a;\n}\nstatic ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a)\n{\n\tip_cache4 *entry;\n\tstruct ip4if key;\n\n\tipcache4Key(&key,a);\n\tHASH_FIND(hh, ipcache, &key, sizeof(key), entry);\n\treturn entry;\n}\nstatic ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a)\n{\n\t// avoid dups\n\tip_cache4 *entry = ipcache4Find(*ipcache,a);\n\tif (entry) return entry; // already included\n\n\tentry = malloc(sizeof(ip_cache4));\n\tif (!entry) return NULL;\n\tipcache4Key(&entry->key,a);\n\n\toom = false;\n\tHASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry);\n\tif (oom) { free(entry); return NULL; }\n\n\tipcache_item_init(&entry->data);\n\n\treturn entry;\n}\nstatic void ipcache4Print(ip_cache4 *ipcache)\n{\n\tchar s_ip[16];\n\ttime_t now;\n\tip_cache4 *ipc, *tmp;\n\n\ttime(&now);\n\tHASH_ITER(hh, ipcache , ipc, tmp)\n\t{\n\t\t*s_ip=0;\n\t\tinet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip));\n\t\tprintf(\"%s : hostname=%s hostname_is_ip=%u now=last+%llu\\n\", s_ip, ipc->data.hostname ? ipc->data.hostname : \"\", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last));\n\t}\n}\n\nstatic void ipcache6Destroy(ip_cache6 **ipcache)\n{\n\tip_cache6 *elem, *tmp;\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tHASH_DEL(*ipcache, elem);\n\t\tipcache_item_destroy(&elem->data);\n\t\tfree(elem);\n\t}\n}\nstatic void ipcache6Key(ip6if *key, const struct in6_addr *a)\n{\n\tmemset(key,0,sizeof(*key)); // make sure everything is zero\n\tkey->addr = *a;\n}\nstatic ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a)\n{\n\tip_cache6 *entry;\n\tip6if key;\n\n\tipcache6Key(&key,a);\n\tHASH_FIND(hh, ipcache, &key, sizeof(key), entry);\n\treturn entry;\n}\nstatic ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a)\n{\n\t// avoid dups\n\tip_cache6 *entry = ipcache6Find(*ipcache,a);\n\tif (entry) return entry; // already included\n\n\tentry = malloc(sizeof(ip_cache6));\n\tif (!entry) return NULL;\n\tipcache6Key(&entry->key,a);\n\n\toom = false;\n\tHASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry);\n\tif (oom) { free(entry); return NULL; }\n\n\tipcache_item_init(&entry->data);\n\n\treturn entry;\n}\nstatic void ipcache6Print(ip_cache6 *ipcache)\n{\n\tchar s_ip[40];\n\ttime_t now;\n\tip_cache6 *ipc, *tmp;\n\n\ttime(&now);\n\tHASH_ITER(hh, ipcache , ipc, tmp)\n\t{\n\t\t*s_ip=0;\n\t\tinet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip));\n\t\tprintf(\"%s : hostname=%s hostname_is_ip=%u now=last+%llu\\n\", s_ip, ipc->data.hostname ? ipc->data.hostname : \"\", ipc->data.hostname_is_ip, (unsigned long long)(now-ipc->data.last));\n\t}\n}\n\nvoid ipcacheDestroy(ip_cache *ipcache)\n{\n\tipcache4Destroy(&ipcache->ipcache4);\n\tipcache6Destroy(&ipcache->ipcache6);\n}\nvoid ipcachePrint(ip_cache *ipcache)\n{\n\tipcache4Print(ipcache->ipcache4);\n\tipcache6Print(ipcache->ipcache6);\n}\n\nip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6)\n{\n\tip_cache4 *ipcache4;\n\tip_cache6 *ipcache6;\n\tif (a4)\n\t{\n\t\tif ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4)))\n\t\t{\n\t\t\tipcache_item_touch(&ipcache4->data);\n\t\t\treturn &ipcache4->data;\n\t\t}\n\t}\n\telse if (a6)\n\t{\n\t\tif ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6)))\n\t\t{\n\t\t\tipcache_item_touch(&ipcache6->data);\n\t\t\treturn &ipcache6->data;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime)\n{\n\tip_cache4 *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tif (now >= (elem->data.last + lifetime))\n\t\t{\n\t\t\tHASH_DEL(*ipcache, elem);\n\t\t\tipcache_item_destroy(&elem->data);\n\t\t\tfree(elem);\n\t\t}\n\t}\n}\nstatic void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime)\n{\n\tip_cache6 *elem, *tmp;\n\ttime_t now = time(NULL);\n\tHASH_ITER(hh, *ipcache, elem, tmp)\n\t{\n\t\tif (now >= (elem->data.last + lifetime))\n\t\t{\n\t\t\tHASH_DEL(*ipcache, elem);\n\t\t\tipcache_item_destroy(&elem->data);\n\t\t\tfree(elem);\n\t\t}\n\t}\n}\nstatic void ipcache_purge(ip_cache *ipcache, time_t lifetime)\n{\n\tif (lifetime) // 0 = no expire\n\t{\n\t\tipcache4_purge(&ipcache->ipcache4, lifetime);\n\t\tipcache6_purge(&ipcache->ipcache6, lifetime);\n\t}\n}\nstatic time_t ipcache_purge_prev=0;\nvoid ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime)\n{\n\ttime_t now = time(NULL);\n\t// do not purge too often to save resources\n\tif (ipcache_purge_prev != now)\n\t{\n\t\tipcache_purge(ipcache, lifetime);\n\t\tipcache_purge_prev = now;\n\t}\n}\n"
  },
  {
    "path": "tpws/pools.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <ctype.h>\n#include <sys/queue.h>\n#include <net/if.h>\n#include <time.h>\n\n#include \"helpers.h\"\n\n//#define HASH_BLOOM 20\n#define HASH_NONFATAL_OOM 1\n#define HASH_FUNCTION HASH_BER\n#include \"uthash.h\"\n\n#include \"kavl.h\"\n\n#define HOSTLIST_POOL_FLAG_STRICT_MATCH\t\t1\n\ntypedef struct hostlist_pool {\n\tchar *str;\t\t/* key */\n\tuint32_t flags;\t\t/* custom data */\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} hostlist_pool;\n\nvoid HostlistPoolDestroy(hostlist_pool **pp);\nbool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags);\nbool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags);\nhostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s);\n\nstruct str_list {\n\tchar *str;\n\tLIST_ENTRY(str_list) next;\n};\nLIST_HEAD(str_list_head, str_list);\n\ntypedef struct hostfail_pool {\n\tchar *str;\t\t/* key */\n\tint counter;\t/* value */\n\ttime_t expire;\t/* when to expire record (unixtime) */\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} hostfail_pool;\n\nvoid HostFailPoolDestroy(hostfail_pool **pp);\nhostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);\nhostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);\nvoid HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);\nvoid HostFailPoolPurge(hostfail_pool **pp);\nvoid HostFailPoolPurgeRateLimited(hostfail_pool **pp);\nvoid HostFailPoolDump(hostfail_pool *p);\n\nbool strlist_add(struct str_list_head *head, const char *filename);\nvoid strlist_destroy(struct str_list_head *head);\n\n\n\nstruct hostlist_file {\n\tchar *filename;\n\tfile_mod_sig mod_sig;\n\thostlist_pool *hostlist;\n\tLIST_ENTRY(hostlist_file) next;\n};\nLIST_HEAD(hostlist_files_head, hostlist_file);\n\nstruct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename);\nvoid hostlist_files_destroy(struct hostlist_files_head *head);\nstruct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename);\nvoid hostlist_files_reset_modtime(struct hostlist_files_head *list);\n\nstruct hostlist_item {\n\tstruct hostlist_file *hfile;\n\tLIST_ENTRY(hostlist_item) next;\n};\nLIST_HEAD(hostlist_collection_head, hostlist_item);\nstruct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile);\nvoid hostlist_collection_destroy(struct hostlist_collection_head *head);\nstruct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename);\nbool hostlist_collection_is_empty(const struct hostlist_collection_head *head);\n\n\nstruct kavl_bit_elem\n{\n\tunsigned int bitlen;\n\tuint8_t *data;\n\tKAVL_HEAD(struct kavl_bit_elem) head;\n};\n\nstruct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen);\nstruct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size);\nvoid kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen);\nvoid kavl_bit_destroy(struct kavl_bit_elem **hdr);\n\n// combined ipset ipv4 and ipv6\ntypedef struct ipset {\n\tstruct kavl_bit_elem *ips4,*ips6;\n} ipset;\n\n#define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6)\n\nbool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen);\nstatic inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr)\n{\n\treturn ipset4Add(ipset,&cidr->addr,cidr->preflen);\n}\nbool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen);\nvoid ipset4Print(struct kavl_bit_elem *ipset);\n\nbool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen);\nstatic inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr)\n{\n\treturn ipset6Add(ipset,&cidr->addr,cidr->preflen);\n}\nbool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen);\nvoid ipset6Print(struct kavl_bit_elem *ipset);\n\nvoid ipsetDestroy(ipset *ipset);\nvoid ipsetPrint(ipset *ipset);\n\n\nstruct ipset_file {\n\tchar *filename;\n\tfile_mod_sig mod_sig;\n\tipset ipset;\n\tLIST_ENTRY(ipset_file) next;\n};\nLIST_HEAD(ipset_files_head, ipset_file);\n\nstruct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename);\nvoid ipset_files_destroy(struct ipset_files_head *head);\nstruct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename);\nvoid ipset_files_reset_modtime(struct ipset_files_head *list);\n\nstruct ipset_item {\n\tstruct ipset_file *hfile;\n\tLIST_ENTRY(ipset_item) next;\n};\nLIST_HEAD(ipset_collection_head, ipset_item);\nstruct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile);\nvoid ipset_collection_destroy(struct ipset_collection_head *head);\nstruct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename);\nbool ipset_collection_is_empty(const struct ipset_collection_head *head);\n\n\nstruct port_filter_item {\n\tport_filter pf;\n\tLIST_ENTRY(port_filter_item) next;\n};\nLIST_HEAD(port_filters_head, port_filter_item);\nbool port_filter_add(struct port_filters_head *head, const port_filter *pf);\nvoid port_filters_destroy(struct port_filters_head *head);\nbool port_filters_in_range(const struct port_filters_head *head, uint16_t port);\nbool port_filters_deny_if_empty(struct port_filters_head *head);\n\n\nstruct blob_item {\n\tuint8_t *data;\t// main data blob\n\tsize_t size;\t// main data blob size\n\tsize_t size_buf;// main data blob allocated size\n\tvoid *extra;\t// any data without size\n\tvoid *extra2;\t// any data without size\n\tLIST_ENTRY(blob_item) next;\n};\nLIST_HEAD(blob_collection_head, blob_item);\nstruct blob_item *blob_collection_add(struct blob_collection_head *head);\nstruct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve);\nvoid blob_collection_destroy(struct blob_collection_head *head);\nbool blob_collection_empty(const struct blob_collection_head *head);\n\n\ntypedef struct ip4if\n{\n\tstruct in_addr addr;\n} ip4if;\ntypedef struct ip6if\n{\n\tstruct in6_addr addr;\n} ip6if;\ntypedef struct ip_cache_item\n{\n\ttime_t last;\n\tchar *hostname;\n\tbool hostname_is_ip;\n} ip_cache_item;\ntypedef struct ip_cache4\n{\n\tip4if key;\n\tip_cache_item data;\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} ip_cache4;\ntypedef struct ip_cache6\n{\n\tip6if key;\n\tip_cache_item data;\n\tUT_hash_handle hh;\t/* makes this structure hashable */\n} ip_cache6;\ntypedef struct ip_cache\n{\n\tip_cache4 *ipcache4;\n\tip_cache6 *ipcache6;\n} ip_cache;\n\nip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6);\nvoid ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime);\nvoid ipcacheDestroy(ip_cache *ipcache);\nvoid ipcachePrint(ip_cache *ipcache);\n"
  },
  {
    "path": "tpws/protocol.c",
    "content": "#define _GNU_SOURCE\n\n#include \"protocol.h\"\n#include \"helpers.h\"\n#include <string.h>\n#include <ctype.h>\n#include <arpa/inet.h>\n#include <string.h>\n\n// find N level domain\nstatic bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len)\n{\n\tint i;\n\tconst uint8_t *p1,*p2;\n\tfor (i=1,p2=dom+dlen;i<level;i++)\n\t{\n\t\tfor (p2--; p2>dom && *p2!='.'; p2--);\n\t\tif (p2<=dom) return false;\n\t}\n\tfor (p1=p2-1 ; p1>dom && *p1!='.'; p1--);\n\tif (*p1=='.') p1++;\n\tif (p) *p = p1;\n\tif (len) *len = p2-p1;\n\treturn true;\n}\n\nconst char *l7proto_str(t_l7proto l7)\n{\n\tswitch(l7)\n\t{\n\t\tcase HTTP: return \"http\";\n\t\tcase TLS: return \"tls\";\n\t\tcase QUIC: return \"quic\";\n\t\tcase WIREGUARD: return \"wireguard\";\n\t\tcase DHT: return \"dht\";\n\t\tdefault: return \"unknown\";\n\t}\n}\nbool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7)\n{\n\treturn  (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) ||\n\t\t(l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) ||\n\t\t(l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) ||\n\t\t(l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) ||\n\t\t(l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) ||\n\t\t(l7proto==DHT && (filter_l7 & L7_PROTO_DHT));\n}\n\n#define PM_ABS\t\t0\n#define PM_HOST\t\t1\n#define PM_HOST_END\t2\n#define PM_HOST_SLD\t3\n#define PM_HOST_MIDSLD\t4\n#define PM_HOST_ENDSLD\t5\n#define PM_HTTP_METHOD\t6\n#define PM_SNI_EXT\t7\nbool IsHostMarker(uint8_t posmarker)\n{\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\nconst char *posmarker_name(uint8_t posmarker)\n{\n\tswitch(posmarker)\n\t{\n\t\tcase PM_ABS: return \"abs\";\n\t\tcase PM_HOST: return \"host\";\n\t\tcase PM_HOST_END: return \"endhost\";\n\t\tcase PM_HOST_SLD: return \"sld\";\n\t\tcase PM_HOST_MIDSLD: return \"midsld\";\n\t\tcase PM_HOST_ENDSLD: return \"endsld\";\n\t\tcase PM_HTTP_METHOD: return \"method\";\n\t\tcase PM_SNI_EXT: return \"sniext\";\n\t\tdefault: return \"?\";\n\t}\n}\n\nstatic size_t CheckPos(size_t sz, ssize_t offset)\n{\n\treturn (offset>=0 && offset<sz) ? offset : 0;\n}\nsize_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tssize_t offset;\n\tswitch(posmarker)\n\t{\n\t\tcase PM_ABS:\n\t\t\toffset = (pos<0) ? sz+pos : pos;\n\t\t\treturn CheckPos(sz,offset);\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\nstatic size_t HostPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz, size_t offset_host, size_t len_host)\n{\n\tssize_t offset;\n\tconst uint8_t *p;\n\tsize_t slen;\n\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\t\toffset = offset_host+pos;\n\t\t\tbreak;\n\t\tcase PM_HOST_END:\n\t\t\toffset = offset_host+len_host+pos;\n\t\t\tbreak;\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\tif (((offset_host+len_host)<=sz) && FindNLD(data+offset_host,len_host,2,&p,&slen))\n\t\t\t\toffset = (posmarker==PM_HOST_SLD ? p-data : posmarker==PM_HOST_ENDSLD ? p-data+slen : slen==1 ? p+1-data : p+slen/2-data) + pos;\n\t\t\telse\n\t\t\t\toffset = 0;\n\t\t\tbreak;\n\t}\n\treturn CheckPos(sz,offset);\n}\nsize_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp)\n{\n\tswitch(l7proto)\n\t{\n\t\tcase HTTP:\n\t\t\treturn HttpPos(sp->marker, sp->pos, data, sz);\n\t\tcase TLS:\n\t\t\treturn TLSPos(sp->marker, sp->pos, data, sz);\n\t\tdefault:\n\t\t\treturn AnyProtoPos(sp->marker, sp->pos, data, sz);\n\t}\n}\nvoid ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count)\n{\n\tint i,j;\n\tfor(i=j=0;i<split_count;i++)\n\t{\n\t\tpos[j] = ResolvePos(data,sz,l7proto,splits+i);\n\t\tif (pos[j]) j++;\n\t}\n\tqsort_size_t(pos, j);\n\tj=unique_size_t(pos, j);\n\t*pos_count=j;\n}\n\n\nconst char *http_methods[] = { \"GET /\",\"POST /\",\"HEAD /\",\"OPTIONS \",\"PUT /\",\"DELETE /\",\"CONNECT \",\"TRACE /\",NULL };\nconst char *HttpMethod(const uint8_t *data, size_t len)\n{\n\tconst char **method;\n\tsize_t method_len;\n\tfor (method = http_methods; *method; method++)\n\t{\n\t\tmethod_len = strlen(*method);\n\t\tif (method_len <= len && !memcmp(data, *method, method_len))\n\t\t\treturn *method;\n\t}\n\treturn NULL;\n}\nbool IsHttp(const uint8_t *data, size_t len)\n{\n\treturn !!HttpMethod(data,len);\n}\n\nstatic bool IsHostAt(const uint8_t *p)\n{\n\treturn \\\n\t\tp[0]=='\\n' &&\n\t\t(p[1]=='H' || p[1]=='h') &&\n\t\t(p[2]=='o' || p[2]=='O') &&\n\t\t(p[3]=='s' || p[3]=='S') &&\n\t\t(p[4]=='t' || p[4]=='T') &&\n\t\tp[5]==':';\n}\nstatic uint8_t *FindHostIn(uint8_t *buf, size_t bs)\n{\n\tsize_t pos;\n\tif (bs<6) return NULL;\n\tbs-=6;\n\tfor(pos=0;pos<=bs;pos++)\n\t\tif (IsHostAt(buf+pos))\n\t\t\treturn buf+pos;\n\n\treturn NULL;\n}\nstatic const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs)\n{\n\tsize_t pos;\n\tif (bs<6) return NULL;\n\tbs-=6;\n\tfor(pos=0;pos<=bs;pos++)\n\t\tif (IsHostAt(buf+pos))\n\t\t\treturn buf+pos;\n\n\treturn NULL;\n}\n// pHost points to \"Host: ...\"\nbool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs)\n{\n\tif (!*pHost)\n\t{\n\t\t*pHost = FindHostIn(buf, bs);\n\t\tif (*pHost) (*pHost)++;\n\t}\n\treturn !!*pHost;\n}\nbool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs)\n{\n\tif (!*pHost)\n\t{\n\t\t*pHost = FindHostInConst(buf, bs);\n\t\tif (*pHost) (*pHost)++;\n\t}\n\treturn !!*pHost;\n}\nbool IsHttpReply(const uint8_t *data, size_t len)\n{\n\t// HTTP/1.x 200\\r\\n\n\treturn len>14 && !memcmp(data,\"HTTP/1.\",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&\n\t\tdata[9]>='0' && data[9]<='9' &&\n\t\tdata[10]>='0' && data[10]<='9' &&\n\t\tdata[11]>='0' && data[11]<='9';\n}\nint HttpReplyCode(const uint8_t *data, size_t len)\n{\n\treturn (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');\n}\nbool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)\n{\n\tconst uint8_t *p, *s, *e = data + len;\n\n\tp = (uint8_t*)strncasestr((char*)data, header, len);\n\tif (!p) return false;\n\tp += strlen(header);\n\twhile (p < e && (*p == ' ' || *p == '\\t')) p++;\n\ts = p;\n\twhile (s < e && (*s != '\\r' && *s != '\\n' && *s != ' ' && *s != '\\t')) s++;\n\tif (s > p)\n\t{\n\t\tsize_t slen = s - p;\n\t\tif (buf && len_buf)\n\t\t{\n\t\t\tif (slen >= len_buf) slen = len_buf - 1;\n\t\t\tfor (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);\n\t\t\tbuf[slen] = 0;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\nbool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)\n{\n\treturn HttpExtractHeader(data, len, \"\\nHost:\", host, len_host);\n}\n// DPI redirects are global redirects to another domain\nbool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)\n{\n\tchar loc[256],*redirect_host, *p;\n\tint code;\n\t\n\tif (!host || !*host) return false;\n\t\n\tcode = HttpReplyCode(data,len);\n\t\n\tif ((code!=302 && code!=307) || !HttpExtractHeader(data,len,\"\\nLocation:\",loc,sizeof(loc))) return false;\n\n\t// something like : https://censor.net/badpage.php?reason=denied&source=RKN\n\t\t\n\tif (!strncmp(loc,\"http://\",7))\n\t\tredirect_host=loc+7;\n\telse if (!strncmp(loc,\"https://\",8))\n\t\tredirect_host=loc+8;\n\telse\n\t\treturn false;\n\t\t\n\t// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN\n\t\n\tfor(p=redirect_host; *p && *p!='/' ; p++);\n\t*p=0;\n\tif (!*redirect_host) return false;\n\n\t// somethinkg like : censor.net\n\t\n\t// extract 2nd level domains\n\tconst char *dhost, *drhost;\n\tif (!FindNLD((uint8_t*)host,strlen(host),2,(const uint8_t**)&dhost,NULL) || !FindNLD((uint8_t*)redirect_host,strlen(redirect_host),2,(const uint8_t**)&drhost,NULL))\n\t\treturn false;\n\n\t// compare 2nd level domains\t\t\n\treturn strcasecmp(dhost, drhost)!=0;\n}\nsize_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tconst uint8_t *method, *host=NULL, *p;\n\tsize_t offset_host,len_host;\n\tssize_t offset;\n\tint i;\n\t\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HTTP_METHOD:\n\t\t\t// recognize some tpws pre-applied hacks\n\t\t\tmethod=data;\n\t\t\tif (sz<10) break;\n\t\t\tif (*method=='\\n' || *method=='\\r') method++;\n\t\t\tif (*method=='\\n' || *method=='\\r') method++;\n\t\t\tfor (p=method,i=0;i<7;i++) if (*p>='A' && *p<='Z') p++;\n\t\t\tif (i<3 || *p!=' ') break;\n\t\t\treturn CheckPos(sz,method-data+pos);\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\t\tif (HttpFindHostConst(&host,data,sz) && (host-data+7)<sz)\n\t\t\t{\n\t\t\t\thost+=5;\n\t\t\t\tif (*host==' ' || *host=='\\t') host++;\n\t\t\t\toffset_host = host-data;\n\t\t\t\tif (posmarker!=PM_HOST)\n\t\t\t\t\tfor (len_host=0; (offset_host+len_host)<sz && data[offset_host+len_host]!='\\r' && data[offset_host+len_host]!='\\n'; len_host++);\n\t\t\t\telse\n\t\t\t\t\tlen_host = 0;\n\t\t\t\treturn HostPos(posmarker,pos,data,sz,offset_host,len_host);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn AnyProtoPos(posmarker,pos,data,sz);\n\t}\n\treturn 0;\n}\n\n\n\nconst char *TLSVersionStr(uint16_t tlsver)\n{\n\tswitch(tlsver)\n\t{\n\t\tcase 0x0301: return \"TLS 1.0\";\n\t\tcase 0x0302: return \"TLS 1.1\";\n\t\tcase 0x0303: return \"TLS 1.2\";\n\t\tcase 0x0304: return \"TLS 1.3\";\n\t\tdefault:\n\t\t\t// 0x0a0a, 0x1a1a, ..., 0xfafa\n\t\t\treturn (((tlsver & 0x0F0F) == 0x0A0A) && ((tlsver>>12)==((tlsver>>4)&0xF))) ? \"GREASE\" : \"UNKNOWN\";\n\t}\n}\n\nuint16_t TLSRecordDataLen(const uint8_t *data)\n{\n\treturn pntoh16(data + 3);\n}\nsize_t TLSRecordLen(const uint8_t *data)\n{\n\treturn TLSRecordDataLen(data) + 5;\n}\nbool IsTLSRecordFull(const uint8_t *data, size_t len)\n{\n\treturn TLSRecordLen(data)<=len;\n}\nbool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK)\n{\n\treturn len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len);\n}\n\n// bPartialIsOK=true - accept partial packets not containing the whole TLS message\nbool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)\n{\n\t// +0\n\t// u8\tHandshakeType: ClientHello\n\t// u24\tLength\n\t// u16\tVersion\n\t// c[32] random\n\t// u8\tSessionIDLength\n\t//\t<SessionID>\n\t// u16\tCipherSuitesLength\n\t//\t<CipherSuites>\n\t// u8\tCompressionMethodsLength\n\t//\t<CompressionMethods>\n\t// u16\tExtensionsLength\n\n\tsize_t l, ll;\n\n\tl = 1 + 3 + 2 + 32;\n\t// SessionIDLength\n\tif (len < (l + 1)) return false;\n\tif (!bPartialIsOK)\n\t{\n\t    ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length\n\t    if (len < (ll + 4)) return false;\n\t}\n\tl += data[l] + 1;\n\t// CipherSuitesLength\n\tif (len < (l + 2)) return false;\n\tl += pntoh16(data + l) + 2;\n\t// CompressionMethodsLength\n\tif (len < (l + 1)) return false;\n\tl += data[l] + 1;\n\t// ExtensionsLength\n\tif (len < (l + 2)) return false;\n\n\tdata += l; len -= l;\n\tl = pntoh16(data);\n\tdata += 2; len -= 2;\n\t\n\tif (bPartialIsOK)\n\t{\n\t\tif (len < l) l = len;\n\t}\n\telse\n\t{\n\t\tif (len < l) return false;\n\t}\n\n\twhile (l >= 4)\n\t{\n\t\tuint16_t etype = pntoh16(data);\n\t\tsize_t elen = pntoh16(data + 2);\n\t\tdata += 4; l -= 4;\n\t\tif (l < elen) break;\n\t\tif (etype == type)\n\t\t{\n\t\t\tif (ext && len_ext)\n\t\t\t{\n\t\t\t\t*ext = data;\n\t\t\t\t*len_ext = elen;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tdata += elen; l -= elen;\n\t}\n\n\treturn false;\n}\nbool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)\n{\n\t// +0\n\t// u8\tContentType: Handshake\n\t// u16\tVersion: TLS1.0\n\t// u16\tLength\n\tsize_t reclen;\n\tif (!IsTLSClientHello(data, len, bPartialIsOK)) return false;\n\treclen=TLSRecordLen(data);\n\tif (reclen<len) len=reclen; // correct len if it has more data than the first tls record has\n\treturn TLSFindExtInHandshake(data + 5, len - 5, type, ext, len_ext, bPartialIsOK);\n}\nstatic bool TLSAdvanceToHostInSNI(const uint8_t **ext, size_t *elen, size_t *slen)\n{\n\t// u16\tdata+0 - name list length\n\t// u8\tdata+2 - server name type. 0=host_name\n\t// u16\tdata+3 - server name length\n\tif (*elen < 5 || (*ext)[2] != 0) return false;\n\t*slen = pntoh16(*ext + 3);\n\t*ext += 5; *elen -= 5;\n\treturn *slen <= *elen;\n}\nstatic bool TLSExtractHostFromExt(const uint8_t *ext, size_t elen, char *host, size_t len_host)\n{\n\t// u16\tdata+0 - name list length\n\t// u8\tdata+2 - server name type. 0=host_name\n\t// u16\tdata+3 - server name length\n\tsize_t slen;\n\tif (!TLSAdvanceToHostInSNI(&ext,&elen,&slen))\n\t\treturn false;\n\tif (host && len_host)\n\t{\n\t\tif (slen >= len_host) slen = len_host - 1;\n\t\tfor (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]);\n\t\thost[slen] = 0;\n\t}\n\treturn true;\n}\nbool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)\n{\n\tconst uint8_t *ext;\n\tsize_t elen;\n\n\tif (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false;\n\treturn TLSExtractHostFromExt(ext, elen, host, len_host);\n}\nbool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)\n{\n\tconst uint8_t *ext;\n\tsize_t elen;\n\n\tif (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false;\n\treturn TLSExtractHostFromExt(ext, elen, host, len_host);\n}\n\n// find N level domain in SNI\nstatic bool TLSHelloFindNLDInSNI(const uint8_t *ext, size_t elen, int level, const uint8_t **p, size_t *len)\n{\n\tsize_t slen;\n\treturn TLSAdvanceToHostInSNI(&ext,&elen,&slen) && FindNLD(ext,slen,level,p,len);\n}\n// find the middle of second level domain (SLD) in SNI ext : www.sobaka.ru => aka.ru\n// return false if SNI ext is bad or SLD is not found\nstatic bool TLSHelloFindMiddleOfSLDInSNI(const uint8_t *ext, size_t elen, const uint8_t **p)\n{\n\tsize_t len;\n\tif (!TLSHelloFindNLDInSNI(ext,elen,2,p,&len))\n\t\treturn false;\n\t// in case of one letter SLD (x.com) we split at '.' to prevent appearance of the whole SLD\n\t*p = (len==1) ? *p+1 : *p+len/2;\n\treturn true;\n}\nsize_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)\n{\n\tsize_t elen;\n\tconst uint8_t *ext, *p;\n\tsize_t offset_host,len_host;\n\tssize_t offset;\n\n\tswitch(posmarker)\n\t{\n\t\tcase PM_HOST:\n\t\tcase PM_HOST_END:\n\t\tcase PM_HOST_SLD:\n\t\tcase PM_HOST_MIDSLD:\n\t\tcase PM_HOST_ENDSLD:\n\t\tcase PM_SNI_EXT:\n\t\t\tif (TLSFindExt(data,sz,0,&ext,&elen,false))\n\t\t\t{\n\t\t\t\tif (posmarker==PM_SNI_EXT)\n\t\t\t\t{\n\t\t\t\t\treturn CheckPos(sz,ext-data+pos);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host))\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\toffset_host = ext-data;\n\t\t\t\t\treturn HostPos(posmarker,pos,data,sz,offset_host,len_host);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\treturn AnyProtoPos(posmarker,pos,data,sz);\n\t}\n}\n"
  },
  {
    "path": "tpws/protocol.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n\ntypedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto;\n#define L7_PROTO_HTTP\t\t0x00000001\n#define L7_PROTO_TLS\t\t0x00000002\n#define L7_PROTO_QUIC\t\t0x00000004\n#define L7_PROTO_WIREGUARD\t0x00000008\n#define L7_PROTO_DHT\t\t0x00000010\n#define L7_PROTO_UNKNOWN\t0x80000000\nconst char *l7proto_str(t_l7proto l7);\nbool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7);\n\n// pos markers\n#define PM_ABS\t\t0\n#define PM_HOST\t\t1\n#define PM_HOST_END\t2\n#define PM_HOST_SLD\t3\n#define PM_HOST_MIDSLD\t4\n#define PM_HOST_ENDSLD\t5\n#define PM_HTTP_METHOD\t6\n#define PM_SNI_EXT\t7\nstruct proto_pos\n{\n\tint16_t pos;\n\tuint8_t marker;\n};\n#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0)\nbool IsHostMarker(uint8_t posmarker);\nconst char *posmarker_name(uint8_t posmarker);\nsize_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);\nsize_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp);\nvoid ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count);\n\n\nextern const char *http_methods[9];\nconst char *HttpMethod(const uint8_t *data, size_t len);\nbool IsHttp(const uint8_t *data, size_t len);\nbool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs);\nbool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs);\n// header must be passed like this : \"\\nHost:\"\nbool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);\nbool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);\nbool IsHttpReply(const uint8_t *data, size_t len);\nconst char *HttpFind2ndLevelDomain(const char *host);\n// must be pre-checked by IsHttpReply\nint HttpReplyCode(const uint8_t *data, size_t len);\n// must be pre-checked by IsHttpReply\nbool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);\n\nconst char *TLSVersionStr(uint16_t tlsver);\nuint16_t TLSRecordDataLen(const uint8_t *data);\nsize_t TLSRecordLen(const uint8_t *data);\nbool IsTLSRecordFull(const uint8_t *data, size_t len);\nbool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK);\nbool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);\nbool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);\nbool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);\nbool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);\n"
  },
  {
    "path": "tpws/redirect.c",
    "content": "#include \"redirect.h\"\n#include <fcntl.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <arpa/inet.h>\n#include <sys/ioctl.h>\n\n#include \"params.h\"\n#include \"helpers.h\"\n#include \"linux_compat.h\"\n\n#ifdef __linux__\n #include <linux/netfilter_ipv4.h>\n#endif\n\n\n#if defined(BSD)\n\n#include <net/if.h>\n#include <net/pfvar.h>\n\nstatic int redirector_fd=-1;\n\nvoid redir_close(void)\n{\n\tif (redirector_fd!=-1)\n\t{\n\t\tclose(redirector_fd);\n\t\tredirector_fd = -1;\n\t\tDBGPRINT(\"closed redirector\\n\");\n\t}\n}\nstatic bool redir_open_private(const char *fname, int flags)\n{\n\tredir_close();\n\tredirector_fd = open(fname, flags);\n\tif (redirector_fd < 0)\n\t{\n\t\tDLOG_PERROR(\"redir_openv_private\");\n\t\treturn false;\n\t}\n\tDBGPRINT(\"opened redirector %s\\n\",fname);\n\treturn true;\n}\nbool redir_init(void)\n{\n\treturn params.pf_enable ? redir_open_private(\"/dev/pf\", O_RDONLY) : true;\n}\n\nstatic bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst)\n{\n\tstruct pfioc_natlook nl;\n\tstruct sockaddr_storage asa2;\n\n\tif (redirector_fd==-1) return false;\n\n\tif (params.debug>=2)\n\t{\n\t\tchar s[48],s2[48];\n\t\t*s=0; ntop46_port(accept_sa, s, sizeof(s));\n\t\t*s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2));\n\t\tDBGPRINT(\"destination_from_pf %s %s\\n\",s,s2);\n\t}\n\n\tsaconvmapped(orig_dst);\n\tif (accept_sa->sa_family==AF_INET6 && orig_dst->ss_family==AF_INET)\n\t{\n\t\tmemcpy(&asa2,accept_sa,sizeof(struct sockaddr_in6));\n\t\tsaconvmapped(&asa2);\n\t\taccept_sa = (struct sockaddr*)&asa2;\n\t}\n\n\tif (params.debug>=2)\n\t{\n\t\tchar s[48],s2[48];\n\t\t*s=0; ntop46_port(accept_sa, s, sizeof(s));\n\t\t*s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2));\n\t\tDBGPRINT(\"destination_from_pf (saconvmapped) %s %s\\n\",s,s2);\n\t}\n\n\tif (accept_sa->sa_family!=orig_dst->ss_family)\n\t{\n\t\tDBGPRINT(\"accept_sa and orig_dst sa_family mismatch : %d %d\\n\", accept_sa->sa_family, orig_dst->ss_family);\n\t\treturn false;\n\t}\n\n\tmemset(&nl, 0, sizeof(nl));\n\tnl.proto           = IPPROTO_TCP;\n\tnl.direction       = PF_OUT;\n\tnl.af = orig_dst->ss_family;\n\tswitch(orig_dst->ss_family)\n\t{\n\tcase AF_INET:\n\t\t{\n\t\tstruct sockaddr_in *sin = (struct sockaddr_in *)orig_dst;\n\t\tnl.daddr.v4.s_addr = sin->sin_addr.s_addr;\n\t\tnl.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr;\n#ifdef __APPLE__\n\t\tnl.sxport.port     = ((struct sockaddr_in*)accept_sa)->sin_port;\n\t\tnl.dxport.port     = sin->sin_port;\n#else\n\t\tnl.sport           = ((struct sockaddr_in*)accept_sa)->sin_port;\n\t\tnl.dport           = sin->sin_port;\n#endif\n\t\t}\n\t\tbreak;\n\tcase AF_INET6:\n\t\t{\n\t\tstruct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)orig_dst;\n\t\tnl.daddr.v6 = sin6->sin6_addr;\n\t\tnl.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr;\n#ifdef __APPLE__\n\t\tnl.sxport.port = ((struct sockaddr_in6*)accept_sa)->sin6_port;\n\t\tnl.dxport.port = sin6->sin6_port;\n#else\n\t\tnl.sport = ((struct sockaddr_in6*)accept_sa)->sin6_port;\n\t\tnl.dport = sin6->sin6_port;\n#endif\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tDBGPRINT(\"destination_from_pf : unexpected address family %d\\n\",orig_dst->ss_family);\n\t\treturn false;\n\t}\n\n\tif (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0)\n\t{\n\t\tDLOG_PERROR(\"ioctl(DIOCNATLOOK) failed\");\n\t\treturn false;\n\t}\n\tDBGPRINT(\"destination_from_pf : got orig dest addr from pf\\n\");\n\n\tswitch(nl.af)\n\t{\n\tcase AF_INET:\n\t\torig_dst->ss_family = nl.af;\n#ifdef __APPLE__\n\t\t((struct sockaddr_in*)orig_dst)->sin_port = nl.rdxport.port;\n#else\n\t\t((struct sockaddr_in*)orig_dst)->sin_port = nl.rdport;\n#endif\n\t\t((struct sockaddr_in*)orig_dst)->sin_addr = nl.rdaddr.v4;\n\t\tbreak;\n\tcase AF_INET6:\n\t\torig_dst->ss_family = nl.af;\n#ifdef __APPLE__\n\t\t((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdxport.port;\n#else\n\t\t((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdport;\n#endif\n\t\t((struct sockaddr_in6*)orig_dst)->sin6_addr = nl.rdaddr.v6;\n\t\tbreak;\n\tdefault:\n\t\tDBGPRINT(\"destination_from_pf : DIOCNATLOOK returned unexpected address family %d\\n\",nl.af);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n\n#else\n\nbool redir_init(void) {return true;}\nvoid redir_close(void) {};\n\n#endif\n\n\n\n//Store the original destination address in orig_dst\nbool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst)\n{\n\tchar orig_dst_str[INET6_ADDRSTRLEN];\n\tsocklen_t addrlen = sizeof(*orig_dst);\n\tint r;\n\n\tmemset(orig_dst, 0, addrlen);\n\n\t//For UDP transparent proxying:\n\t//Set IP_RECVORIGDSTADDR socket option for getting the original \n\t//destination of a datagram\n\n#ifdef __linux__\n\t// DNAT\n\tr=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);\n\tif (r<0)\n\t\tr = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);\n\tif (r<0)\n\t{\n\t\tDBGPRINT(\"both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\\n\");\n#endif\n\t\t// TPROXY : socket is bound to original destination\n\t\tr=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen);\n\t\tif (r<0)\n\t\t{\n\t\t\tDLOG_PERROR(\"getsockname\");\n\t\t\treturn false;\n\t\t}\n\t\tif (orig_dst->ss_family==AF_INET6)\n\t\t\t((struct sockaddr_in6*)orig_dst)->sin6_scope_id=0; // or MacOS will not connect()\n#ifdef BSD\n\t\tif (params.pf_enable && !destination_from_pf(accept_sa, orig_dst))\n\t\t\tDBGPRINT(\"pf filter destination_from_pf failed\\n\");\n#endif\n#ifdef __linux__\n\t}\n#endif\n\tif (saconvmapped(orig_dst))\n\t\tDBGPRINT(\"Original destination : converted ipv6 mapped address to ipv4\\n\");\n\n\tif (params.debug)\n\t{\n\t\tif (orig_dst->ss_family == AF_INET)\n\t\t{\n\t\t\tinet_ntop(AF_INET, &(((struct sockaddr_in*) orig_dst)->sin_addr), orig_dst_str, INET_ADDRSTRLEN);\n\t\t\tVPRINT(\"Original destination for socket fd=%d : %s:%d\\n\", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port));\n\t\t}\n\t\telse if (orig_dst->ss_family == AF_INET6)\n\t\t{\n\t\t\tinet_ntop(AF_INET6,&(((struct sockaddr_in6*) orig_dst)->sin6_addr), orig_dst_str, INET6_ADDRSTRLEN);\n\t\t\tVPRINT(\"Original destination for socket fd=%d : [%s]:%d\\n\", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port));\n\t\t}\n\t}\n\treturn true;\n}\n"
  },
  {
    "path": "tpws/redirect.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n\nbool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst);\nbool redir_init(void);\nvoid redir_close(void);\n"
  },
  {
    "path": "tpws/resolver.c",
    "content": "#define _GNU_SOURCE\n\n#include \"resolver.h\"\n#include \"params.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <semaphore.h>\n#include <fcntl.h> \n#include <pthread.h>\n#include <signal.h>\n#include <sys/syscall.h>\n#include <errno.h>\n#include <unistd.h>\n\n#define SIG_BREAK SIGUSR1\n\n#ifdef __APPLE__\n\tstatic const char *sem_name=\"/tpws_resolver\";\n#endif\n\nTAILQ_HEAD(resolve_tailhead, resolve_item);\n\ntypedef struct\n{\n\tint fd_signal_pipe;\n\tsem_t *sem;\n#ifndef __APPLE__\n\tsem_t _sem;\n#endif\n\tstruct resolve_tailhead resolve_list;\n\tpthread_mutex_t resolve_list_lock;\n\tint threads;\n\tpthread_t *thread;\n\tbool bInit, bStop;\n} t_resolver;\nstatic t_resolver resolver = { .bInit = false };\n\n#define rlist_lock pthread_mutex_lock(&resolver.resolve_list_lock)\n#define rlist_unlock pthread_mutex_unlock(&resolver.resolve_list_lock)\n\nstatic void resolver_clear_list(void)\n{\n\tstruct resolve_item *ri;\n\n\tfor (;;)\n\t{\n\t\tri = TAILQ_FIRST(&resolver.resolve_list);\n\t\tif (!ri) break;\n\t\tTAILQ_REMOVE(&resolver.resolve_list, ri, next);\n\t\tfree(ri);\n\t}\n}\n\nint resolver_thread_count(void)\n{\n\treturn resolver.bInit ? resolver.threads : 0;\n}\n \nstatic void *resolver_thread(void *arg)\n{\n\tint r;\n\tsigset_t signal_mask;\n\n\tsigemptyset(&signal_mask);\n\tsigaddset(&signal_mask, SIG_BREAK);\n\n\t//printf(\"resolver_thread %d start\\n\",syscall(SYS_gettid));\n\tfor(;;)\n\t{\n\t\tif (resolver.bStop) break;\n\t\tr = sem_wait(resolver.sem);\n\t\tif (resolver.bStop) break;\n\t\tif (r)\n\t\t{\n\t\t\tif (errno!=EINTR)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"sem_wait (resolver_thread)\");\n\t\t\t\tbreak; // fatal err\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstruct resolve_item *ri;\n\t\t\tssize_t wr;\n\n\t\t\trlist_lock;\n\t\t\tri = TAILQ_FIRST(&resolver.resolve_list);\n\t\t\tif (ri) TAILQ_REMOVE(&resolver.resolve_list, ri, next);\n\t\t\trlist_unlock;\n\n\t\t\tif (ri)\n\t\t\t{\n\t\t\t\tstruct addrinfo *ai,hints;\n\t\t\t\tchar sport[6];\n\n\t\t\t\t//printf(\"THREAD %d GOT JOB %s\\n\", syscall(SYS_gettid), ri->dom);\n\t\t\t\tsnprintf(sport,sizeof(sport),\"%u\",ri->port);\n\t\t\t\tmemset(&hints, 0, sizeof(struct addrinfo));\n\t\t\t\thints.ai_socktype = SOCK_STREAM;\n\t\t\t\t// unfortunately getaddrinfo cannot be interrupted with a signal. we cannot cancel a query\n\t\t\t\tri->ga_res = getaddrinfo(ri->dom,sport,&hints,&ai);\n\t\t\t\tif (!ri->ga_res)\n\t\t\t\t{\n\t\t\t\t\tif (ai->ai_addrlen>sizeof(ri->ss))\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG_ERR(\"getaddrinfo returned too large address\\n\");\n\t\t\t\t\t\tri->ga_res = EAI_FAIL;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tmemcpy(&ri->ss, ai->ai_addr, ai->ai_addrlen);\n\t\t\t\t\tfreeaddrinfo(ai);\n\t\t\t\t}\n\t\t\t\t//printf(\"THREAD %d END JOB %s  FIRST=%p\\n\", syscall(SYS_gettid), ri->dom, TAILQ_FIRST(&resolver.resolve_list));\n\n\t\t\t\t// never interrupt this\n\t\t\t\tpthread_sigmask(SIG_BLOCK, &signal_mask, NULL);\n\t\t\t\twr = write(resolver.fd_signal_pipe,&ri,sizeof(void*));\n\t\t\t\tif (wr<0)\n\t\t\t\t{\n\t\t\t\t\tfree(ri);\n\t\t\t\t\tDLOG_PERROR(\"write resolve_pipe\");\n\t\t\t\t}\n\t\t\t\telse if (wr!=sizeof(void*))\n\t\t\t\t{\n\t\t\t\t\t// partial pointer write is FATAL. in any case it will cause pointer corruption and coredump\n\t\t\t\t\tfree(ri);\n\t\t\t\t\tDLOG_ERR(\"write resolve_pipe : not full write\\n\");\n\t\t\t\t\texit(1000);\n\t\t\t\t}\n\t\t\t\tpthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);\n\t\t\t}\n\t\t}\n\t}\n\t//printf(\"resolver_thread %d exit\\n\",syscall(SYS_gettid));\n\treturn NULL;\n}\n\nstatic void sigbreak(int sig)\n{\n}\n\nvoid resolver_deinit(void)\n{\n\tif (resolver.bInit)\n\t{\n\t\tresolver.bStop = true;\n\n\t\t// wait all threads to terminate\n\t\tfor (int t = 0; t < resolver.threads; t++)\n\t\t\tpthread_kill(resolver.thread[t], SIGUSR1);\n\t\tfor (int t = 0; t < resolver.threads; t++)\n\t\t{\n\t\t\tpthread_kill(resolver.thread[t], SIGUSR1);\n\t\t\tpthread_join(resolver.thread[t], NULL);\n\t\t}\n\t\n\t\tpthread_mutex_destroy(&resolver.resolve_list_lock);\n\t\tfree(resolver.thread);\n\n\t\t#ifdef __APPLE__\n\t\t\tsem_close(resolver.sem);\n\t\t#else\n\t\t\tsem_destroy(resolver.sem);\n\t\t#endif\n\n\t\tresolver_clear_list();\n\n\t\tmemset(&resolver,0,sizeof(resolver));\n\t}\n}\n\nbool resolver_init(int threads, int fd_signal_pipe)\n{\n\tint t;\n\tstruct sigaction action;\n\n\tif (threads<1 || resolver.bInit) return false;\n\n\tmemset(&resolver,0,sizeof(resolver));\n\tresolver.bInit = true;\n\n#ifdef __APPLE__\n\t// MacOS does not support unnamed semaphores\n\n\tchar sn[64];\n\tsnprintf(sn,sizeof(sn),\"%s_%d\",sem_name,getpid());\n\tresolver.sem = sem_open(sn,O_CREAT,0600,0);\n\tif (resolver.sem==SEM_FAILED)\n\t{\n\t\tDLOG_PERROR(\"sem_open\");\n\t\tgoto ex;\n\t}\n\t// unlink immediately to remove tails\n\tsem_unlink(sn);\n#else\n\tif (sem_init(&resolver._sem,0,0)==-1)\n\t{\t\n\t\tDLOG_PERROR(\"sem_init\");\n\t\tgoto ex;\n\t}\n\tresolver.sem = &resolver._sem;\n#endif\n\n\tif (pthread_mutex_init(&resolver.resolve_list_lock, NULL)) goto ex;\n\n\tresolver.fd_signal_pipe = fd_signal_pipe;\n\tTAILQ_INIT(&resolver.resolve_list);\n\n\t// start as many threads as we can up to specified number\n\tresolver.thread = malloc(sizeof(pthread_t)*threads);\n\tif (!resolver.thread) goto ex;\n\n\tmemset(&action,0,sizeof(action));\n\taction.sa_handler = sigbreak;\n\tsigaction(SIG_BREAK, &action, NULL);\n\n\n\tpthread_attr_t attr;\n\tif (pthread_attr_init(&attr)) goto ex;\n\t// set minimum thread stack size\n\n\tif (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>32768 ? PTHREAD_STACK_MIN : 32768))\n\t{\n\t\tpthread_attr_destroy(&attr);\n\t\tgoto ex;\n\t}\n\n\tfor(t=0, resolver.threads=threads ; t<threads ; t++)\n\t{\n\t\tif (pthread_create(resolver.thread + t, &attr, resolver_thread, NULL))\n\t\t{\n\t\t\tresolver.threads=t;\n\t\t\tbreak;\n\t\t}\n\t}\n\tpthread_attr_destroy(&attr);\n\tif (!resolver.threads) goto ex;\n\n\treturn true;\n\nex:\n\tresolver_deinit();\n\treturn false;\n}\n\n\n\nstruct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr)\n{\n\tstruct resolve_item *ri = calloc(1,sizeof(struct resolve_item));\n\tif (!ri) return NULL;\n\n\tstrncpy(ri->dom,dom,sizeof(ri->dom));\n\tri->dom[sizeof(ri->dom)-1] = 0;\n\tri->port = port;\n\tri->ptr = ptr;\n\n\trlist_lock;\n\tTAILQ_INSERT_TAIL(&resolver.resolve_list, ri, next);\n\trlist_unlock;\n\tif (sem_post(resolver.sem)<0)\n\t{\n\t\tDLOG_PERROR(\"resolver_queue sem_post\");\n\t\tfree(ri);\n\t\treturn NULL;\n\t}\n\treturn ri;\n}\n"
  },
  {
    "path": "tpws/resolver.h",
    "content": "#pragma once\n\n#include <inttypes.h>\n#include <stdbool.h>\n#include <sys/queue.h>\n#include <sys/socket.h>\n#include <netdb.h>\n\n#include \"helpers.h\"\n\nstruct resolve_item\n{\n\tchar dom[256];\t// request dom\n\tsockaddr_in46 ss; // resolve result\n\tint ga_res;\t// getaddrinfo result code\n\tuint16_t port;\t// request port\n\tvoid *ptr;\n\tTAILQ_ENTRY(resolve_item) next;\n};\n\nstruct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr);\nvoid resolver_deinit(void);\nbool resolver_init(int threads, int fd_signal_pipe);\nint resolver_thread_count(void);\n"
  },
  {
    "path": "tpws/sec.c",
    "content": "#define _GNU_SOURCE\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"sec.h\"\n#include <unistd.h>\n#include <fcntl.h>\n#include <grp.h>\n\n#ifdef __linux__\n\n#include <sys/prctl.h>\n#include <sys/syscall.h>\n#include <linux/seccomp.h>\n#include <linux/filter.h>\n// __X32_SYSCALL_BIT defined in linux/unistd.h\n#include <linux/unistd.h>\n#include <syscall.h>\n#include <errno.h>\n\n/************ SECCOMP ************/\n\n// block most of the undesired syscalls to harden against code execution\nstatic long blocked_syscalls[] = {\n#ifdef SYS_execv\nSYS_execv,\n#endif\nSYS_execve,\n#ifdef SYS_execveat\nSYS_execveat,\n#endif\n#ifdef SYS_exec_with_loader\nSYS_exec_with_loader,\n#endif\n#ifdef SYS_osf_execve\nSYS_osf_execve,\n#endif\n#ifdef SYS_uselib\nSYS_uselib,\n#endif\n#ifdef SYS_unlink\nSYS_unlink,\n#endif\nSYS_unlinkat,\n#ifdef SYS_chmod\nSYS_chmod,\n#endif\nSYS_fchmod,SYS_fchmodat,\n#ifdef SYS_chown\nSYS_chown,\n#endif\n#ifdef SYS_chown32\nSYS_chown32,\n#endif\nSYS_fchown,\n#ifdef SYS_fchown32\nSYS_fchown32,\n#endif\n#ifdef SYS_lchown\nSYS_lchown,\n#endif\n#ifdef SYS_lchown32\nSYS_lchown32,\n#endif\nSYS_fchownat,\n#ifdef SYS_symlink\nSYS_symlink,\n#endif\nSYS_symlinkat,\n#ifdef SYS_link\nSYS_link,\n#endif\nSYS_linkat,\nSYS_truncate,\n#ifdef SYS_truncate64\nSYS_truncate64,\n#endif\nSYS_ftruncate,\n#ifdef SYS_ftruncate64\nSYS_ftruncate64,\n#endif\n#ifdef SYS_mknod\nSYS_mknod,\n#endif\nSYS_mknodat,\n#ifdef SYS_mkdir\nSYS_mkdir,\n#endif\nSYS_mkdirat,\n#ifdef SYS_rmdir\nSYS_rmdir,\n#endif\n#ifdef SYS_rename\nSYS_rename,\n#endif\n#ifdef SYS_renameat2\nSYS_renameat2,\n#endif\n#ifdef SYS_renameat\nSYS_renameat,\n#endif\n#ifdef SYS_readdir\nSYS_readdir,\n#endif\n#ifdef SYS_getdents\nSYS_getdents,\n#endif\n#ifdef SYS_getdents64\nSYS_getdents64,\n#endif\n#ifdef SYS_process_vm_readv\nSYS_process_vm_readv,\n#endif\n#ifdef SYS_process_vm_writev\nSYS_process_vm_writev,\n#endif\n#ifdef SYS_process_madvise\nSYS_process_madvise,\n#endif\nSYS_kill, SYS_ptrace\n};\n#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls))\n\nstatic void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k)\n{\n\tfilter->code = code;\n\tfilter->jt = jt;\n\tfilter->jf = jf;\n\tfilter->k = k;\n}\n// deny all blocked syscalls\nstatic bool set_seccomp(void)\n{\n#ifdef __X32_SYSCALL_BIT\n #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT)\n#else\n #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT)\n#endif\n\tstruct sock_filter sockf[SECCOMP_PROG_SIZE];\n\tstruct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf };\n\tint i,idx=0;\n\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr);\n#ifdef __X32_SYSCALL_BIT\n\t// x86 only\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail\n#else\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);\n#endif\n\n/*\n\t// ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd\n\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad\n\tset_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr\n*/\n\tfor(i=0 ; i<BLOCKED_SYSCALL_COUNT ; i++)\n\t{\n\t\tset_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, BLOCKED_SYSCALL_COUNT-i, 0, blocked_syscalls[i]);\n\t}\n\tset_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); // success case\n\tset_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); // fail case\n\treturn prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) >= 0;\n}\n\nbool sec_harden(void)\n{\n\tbool bRes = true;\n\tif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))\n\t{\n\t\tDLOG_PERROR(\"PR_SET_NO_NEW_PRIVS(prctl)\");\n\t\tbRes = false;\n\t}\n#if ARCH_NR!=0\n\tif (!set_seccomp())\n\t{\n\t\tDLOG_PERROR(\"seccomp\");\n\t\tif (errno==EINVAL) DLOG_ERR(\"seccomp: this can be safely ignored if kernel does not support seccomp\\n\");\n\t\tbRes = false;\n\t}\n#endif\n\treturn bRes;\n}\n\n\nbool checkpcap(uint64_t caps)\n{\n\tif (!caps) return true; // no special caps reqd\n\n\tstruct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};\n\tstruct __user_cap_data_struct cd[2];\n\tuint32_t c0 = (uint32_t)caps;\n\tuint32_t c1 = (uint32_t)(caps>>32);\n\n\treturn !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1;\n}\nbool setpcap(uint64_t caps)\n{\n\tstruct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};\n\tstruct __user_cap_data_struct cd[2];\n\t\n\tcd[0].effective = cd[0].permitted = (uint32_t)caps;\n\tcd[0].inheritable = 0;\n\tcd[1].effective = cd[1].permitted = (uint32_t)(caps>>32);\n\tcd[1].inheritable = 0;\n\n\treturn !capset(&ch,cd);\n}\nint getmaxcap(void)\n{\n\tint maxcap = CAP_LAST_CAP;\n\tFILE *F = fopen(\"/proc/sys/kernel/cap_last_cap\", \"r\");\n\tif (F)\n\t{\n\t\tint n = fscanf(F, \"%d\", &maxcap);\n\t\tfclose(F);\n\t}\n\treturn maxcap;\n\n}\nbool dropcaps(void)\n{\n\tuint64_t caps = 0;\n\tint maxcap = getmaxcap();\n\n\tif (setpcap(caps|(1<<CAP_SETPCAP)))\n\t{\n\t\tfor (int cap = 0; cap <= maxcap; cap++)\n\t\t{\n\t\t\tif (prctl(PR_CAPBSET_DROP, cap)<0)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"could not drop bound cap %d\\n\", cap);\n\t\t\t\tDLOG_PERROR(\"cap_drop_bound\");\n\t\t\t}\n\t\t}\n\t}\n\t// now without CAP_SETPCAP\n\tif (!setpcap(caps))\n\t{\n\t\tDLOG_PERROR(\"setpcap\");\n\t\treturn checkpcap(caps);\n\t}\n\treturn true;\n}\n#else // __linux__\n\nbool sec_harden(void)\n{\n\t// noop\n\treturn true;\n}\n\n#endif // __linux__\n\n\n\nbool can_drop_root(void)\n{\n#ifdef __linux__\n\t// has some caps\n\treturn checkpcap((1<<CAP_SETUID)|(1<<CAP_SETGID));\n#else\n\t// effective root\n\treturn !geteuid();\n#endif\n}\n\nbool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count)\n{\n\tif (gid_count<1)\n\t{\n\t\tDLOG_ERR(\"droproot: no groups specified\");\n\t\treturn false;\n\t}\n#ifdef __linux__\n\tif (prctl(PR_SET_KEEPCAPS, 1L))\n\t{\n\t\tDLOG_PERROR(\"prctl(PR_SET_KEEPCAPS)\");\n\t\treturn false;\n\t}\n#endif\n\tif (user)\n\t{\n\t\t// macos has strange supp gid handling. they cache only 16 groups and fail setgroups if more than 16 gids specified.\n\t\t// better to leave it to the os\n\t\tif (initgroups(user,gid[0]))\n\t\t{\n\t\t\tDLOG_PERROR(\"initgroups\");\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (setgroups(gid_count,gid))\n\t\t{\n\t\t\tDLOG_PERROR(\"setgroups\");\n\t\t\treturn false;\n\t\t}\n\t}\n\tif (setgid(gid[0]))\n\t{\n\t\tDLOG_PERROR(\"setgid\");\n\t\treturn false;\n\t}\n\tif (setuid(uid))\n\t{\n\t\tDLOG_PERROR(\"setuid\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nvoid print_id(void)\n{\n int i,N;\n gid_t g[128];\n\n DLOG_CONDUP(\"Running as UID=%u GID=\",getuid());\n N=getgroups(sizeof(g)/sizeof(*g),g);\n if (N>0)\n {\n\tfor(i=0;i<N;i++)\n\t\tDLOG_CONDUP(i==(N-1) ? \"%u\" : \"%u,\", g[i]);\n\tDLOG_CONDUP(\"\\n\");\n }\n else\n\tDLOG_CONDUP(\"%u\\n\",getgid());\n}\n\nvoid daemonize(void)\n{\n\tint pid;\n\n\tpid = fork();\n\tif (pid == -1)\n\t{\n\t\tDLOG_PERROR(\"fork\");\n\t\texit(2);\n\t}\n\telse if (pid != 0)\n\t\texit(0);\n\n\tif (setsid() == -1)\n\t\texit(2);\n\tif (chdir(\"/\") == -1)\n\t\texit(2);\n\tclose(STDIN_FILENO);\n\tclose(STDOUT_FILENO);\n\tclose(STDERR_FILENO);\n\t/* redirect fd's 0,1,2 to /dev/null */\n\topen(\"/dev/null\", O_RDWR);\n\tint fd;\n\t/* stdin */\n\tfd = dup(0);\n\t/* stdout */\n\tfd = dup(0);\n\t/* stderror */\n}\n\nbool writepid(const char *filename)\n{\n\tFILE *F;\n\tif (!(F = fopen(filename, \"w\")))\n\t\treturn false;\n\tfprintf(F, \"%d\", getpid());\n\tfclose(F);\n\treturn true;\n}\n"
  },
  {
    "path": "tpws/sec.h",
    "content": "#pragma once\n\n#include \"params.h\"\n\n#include <sys/types.h>\n#include <stdbool.h>\n\n#ifdef __linux__\n\n#include <stddef.h>\n#include <sys/capability.h>\n#include <linux/audit.h>\n\nbool checkpcap(uint64_t caps);\nbool setpcap(uint64_t caps);\nint getmaxcap(void);\nbool dropcaps(void);\n\n#define syscall_nr (offsetof(struct seccomp_data, nr))\n#define arch_nr (offsetof(struct seccomp_data, arch))\n#define syscall_arg(x) (offsetof(struct seccomp_data, args[x]))\n\n#ifndef __AUDIT_ARCH_64BIT\n#define __AUDIT_ARCH_64BIT 0x80000000\n#endif\n#ifndef __AUDIT_ARCH_LE\n#define __AUDIT_ARCH_LE    0x40000000\n#endif\n#ifndef EM_RISCV\n#define EM_RISCV 243\n#endif\n#ifndef AUDIT_ARCH_RISCV64\n#define AUDIT_ARCH_RISCV64 (EM_RISCV | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE)\n#endif\n#ifndef EM_LOONGARCH\n#define EM_LOONGARCH 258\n#endif\n#ifndef AUDIT_ARCH_LOONGARCH64\n#define AUDIT_ARCH_LOONGARCH64 (EM_LOONGARCH | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE)\n#endif\n\n#if defined(__aarch64__)\n\n# define ARCH_NR\tAUDIT_ARCH_AARCH64\n\n#elif defined(__amd64__)\n\n# define ARCH_NR\tAUDIT_ARCH_X86_64\n\n#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__))\n\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_ARM\n# else\n#  define ARCH_NR\tAUDIT_ARCH_ARMEB\n# endif\n\n#elif defined(__i386__)\n\n# define ARCH_NR\tAUDIT_ARCH_I386\n\n#elif defined(__mips__)\n\n#if _MIPS_SIM == _MIPS_SIM_ABI32\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_MIPSEL\n# else\n#  define ARCH_NR\tAUDIT_ARCH_MIPS\n# endif\n#elif _MIPS_SIM == _MIPS_SIM_ABI64\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_MIPSEL64\n# else\n#  define ARCH_NR\tAUDIT_ARCH_MIPS64\n# endif\n#else\n# error \"Unsupported mips abi\"\n#endif\n\n#elif defined(__PPC64__)\n\n# if __BYTE_ORDER == __LITTLE_ENDIAN\n#  define ARCH_NR\tAUDIT_ARCH_PPC64LE\n# else\n#  define ARCH_NR\tAUDIT_ARCH_PPC64\n# endif\n\n#elif defined(__PPC__)\n\n# define ARCH_NR\tAUDIT_ARCH_PPC\n\n#elif __riscv && __riscv_xlen == 64\n\n# define ARCH_NR\tAUDIT_ARCH_RISCV64\n\n#elif defined(__loongarch__) && __loongarch_grlen == 64\n\n# define ARCH_NR AUDIT_ARCH_LOONGARCH64\n\n#elif defined(__e2k__)\n\n# define ARCH_NR\tAUDIT_ARCH_E2K\n\n#else\n\n# error \"Platform does not support seccomp filter yet\"\n\n#endif\n\n#endif\n\nbool sec_harden(void);\nbool can_drop_root();\nbool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count);\nvoid print_id(void);\nvoid daemonize(void);\nbool writepid(const char *filename);\n"
  },
  {
    "path": "tpws/socks.h",
    "content": "#pragma once\r\n\r\n#include <stdint.h>\r\n#include <arpa/inet.h>\r\n\r\n#pragma pack(push,1)\r\n\r\n#define S4_CMD_CONNECT\t\t1\r\n#define S4_CMD_BIND\t\t2\r\ntypedef struct\r\n{\r\n\tuint8_t ver,cmd;\r\n\tuint16_t port;\r\n\tuint32_t ip;\r\n} s4_req;\r\n#define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4)\r\n#define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT)\r\n\r\n#define S4_REP_OK\t\t90\r\n#define S4_REP_FAILED\t\t91\r\ntypedef struct\r\n{\r\n\tuint8_t zero,rep;\r\n\tuint16_t port;\r\n\tuint32_t ip;\r\n} s4_rep;\r\n\r\n\r\n\r\n#define S5_AUTH_NONE\t\t0\r\n#define S5_AUTH_GSSAPI\t\t1\r\n#define S5_AUTH_USERPASS\t2\r\n#define S5_AUTH_UNACCEPTABLE\t0xFF\r\ntypedef struct\r\n{\r\n\tuint8_t ver,nmethods,methods[255];\r\n} s5_handshake;\r\n#define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods))\r\ntypedef struct\r\n{\r\n\tuint8_t ver,method;\r\n} s5_handshake_ack;\r\n\r\n#define S5_CMD_CONNECT\t\t1\r\n#define S5_CMD_BIND\t\t2\r\n#define S5_CMD_UDP_ASSOC\t3\r\n#define S5_ATYP_IP4\t\t1\r\n#define S5_ATYP_DOM\t\t3\r\n#define S5_ATYP_IP6\t\t4\r\ntypedef struct\r\n{\r\n\tuint8_t ver,cmd,rsv,atyp;\r\n\tunion {\r\n\t\tstruct {\r\n\t\t\tstruct in_addr addr;\r\n\t\t\tuint16_t port;\r\n\t\t} d4;\r\n\t\tstruct {\r\n\t\t\tstruct in6_addr addr;\r\n\t\t\tuint16_t port;\r\n\t\t} d6;\r\n\t\tstruct {\r\n\t\t\tuint8_t len;\r\n\t\t\tchar domport[255+2]; // max hostname + binary port\r\n\t\t} dd;\r\n\t};\r\n} s5_req;\r\n#define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5)\r\n#define S5_IP46_VALID(r,l) ((r->atyp==S5_ATYP_IP4 && l>=(4+sizeof(r->d4))) || (r->atyp==S5_ATYP_IP6 && l>=(4+sizeof(r->d6))))\r\n#define S5_REQ_CONNECT_VALID(r,l) (S5_REQ_HEADER_VALID(r,l) && r->cmd==S5_CMD_CONNECT && (S5_IP46_VALID(r,l) || (r->atyp==S5_ATYP_DOM && l>=5 && l>=(5+r->dd.len))))\r\n#define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0)\r\n\r\n#define S5_REP_OK\t\t\t0\r\n#define S5_REP_GENERAL_FAILURE\t\t1\r\n#define S5_REP_NOT_ALLOWED_BY_RULESET\t2\r\n#define S5_REP_NETWORK_UNREACHABLE\t3\r\n#define S5_REP_HOST_UNREACHABLE\t\t4\r\n#define S5_REP_CONN_REFUSED\t\t5\r\n#define S5_REP_TTL_EXPIRED\t\t6\r\n#define S5_REP_COMMAND_NOT_SUPPORTED\t7\r\n#define S5_REP_ADDR_TYPE_NOT_SUPPORTED\t8\r\ntypedef struct\r\n{\r\n\tuint8_t ver,rep,rsv,atyp;\r\n\tunion {\r\n\t\tstruct {\r\n\t\t\tstruct in_addr addr;\r\n\t\t\tuint16_t port;\r\n\t\t} d4;\r\n\t};\r\n} s5_rep;\r\n\r\n#pragma pack(pop)\r\n"
  },
  {
    "path": "tpws/tamper.c",
    "content": "#define _GNU_SOURCE\n\n#include <string.h>\n#include <stdio.h>\n#include \"tamper.h\"\n#include \"hostlist.h\"\n#include \"ipset.h\"\n#include \"protocol.h\"\n#include \"helpers.h\"\n#include \"pools.h\"\n\n#define PKTDATA_MAXDUMP 32\n\nvoid packet_debug(const uint8_t *data, size_t sz)\n{\n\thexdump_limited_dlog(data, sz, PKTDATA_MAXDUMP); VPRINT(\"\\n\");\n}\n\nstatic void TLSDebugHandshake(const uint8_t *tls,size_t sz)\n{\n\tif (!params.debug) return;\n\n\tif (sz<6) return;\n\n\tconst uint8_t *ext;\n\tsize_t len,len2;\n\n\tuint16_t v_handshake=pntoh16(tls+4), v, v2;\n\tVPRINT(\"TLS handshake version : %s\\n\",TLSVersionStr(v_handshake));\n\n\tif (TLSFindExtInHandshake(tls,sz,43,&ext,&len,false))\n\t{\n\t\tif (len)\n\t\t{\n\t\t\tlen2 = ext[0];\n\t\t\tif (len2<len)\n\t\t\t{\n\t\t\t\tfor(ext++,len2&=~1 ; len2 ; len2-=2,ext+=2)\n\t\t\t\t{\n\t\t\t\t\tv = pntoh16(ext);\n\t\t\t\t\tVPRINT(\"TLS supported versions ext : %s\\n\",TLSVersionStr(v));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tVPRINT(\"TLS supported versions ext : not present\\n\");\n\n\tif (TLSFindExtInHandshake(tls,sz,16,&ext,&len,false))\n\t{\n\t\tif (len>=2)\n\t\t{\n\t\t\tlen2 = pntoh16(ext);\n\t\t\tif (len2<=(len-2))\n\t\t\t{\n\t\t\t\tchar s[32];\n\t\t\t\tfor(ext+=2; len2 ;)\n\t\t\t\t{\n\t\t\t\t\tv = *ext; ext++; len2--;\n\t\t\t\t\tif (v<=len2)\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = v<sizeof(s) ? v : sizeof(s)-1;\n\t\t\t\t\t\tmemcpy(s,ext,v2);\n\t\t\t\t\t\ts[v2]=0;\n\t\t\t\t\t\tVPRINT(\"TLS ALPN ext : %s\\n\",s);\n\t\t\t\t\t\tlen2-=v;\n\t\t\t\t\t\text+=v;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tVPRINT(\"TLS ALPN ext : not present\\n\");\n\n\tVPRINT(\"TLS ECH ext : %s\\n\",TLSFindExtInHandshake(tls,sz,65037,NULL,NULL,false) ? \"present\" : \"not present\");\n}\nstatic void TLSDebug(const uint8_t *tls,size_t sz)\n{\n\tif (!params.debug) return;\n\n\tif (sz<11) return;\n\n\tVPRINT(\"TLS record layer version : %s\\n\",TLSVersionStr(pntoh16(tls+1)));\n\n\tsize_t reclen=TLSRecordLen(tls);\n\tif (reclen<sz) sz=reclen; // correct len if it has more data than the first tls record has\n\n\tTLSDebugHandshake(tls+5,sz-5);\n}\n\nbool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip)\n{\n\tif (!params.cache_hostname) return true;\n\n\tip_cache_item *ipc = ipcacheTouch(&params.ipcache,a4,a6);\n\tif (!ipc)\n\t{\n\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\\n\");\n\t\treturn false;\n\t}\n\tif (!ipc->hostname || strcmp(ipc->hostname,hostname))\n\t{\n\t\tfree(ipc->hostname);\n\t\tif (!(ipc->hostname = strdup(hostname)))\n\t\t{\n\t\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\\n\");\n\t\t\treturn false;\n\t\t}\n\t\tipc->hostname_is_ip = hostname_is_ip;\n\t\tVPRINT(\"hostname cached (is_ip=%u): %s\\n\", hostname_is_ip, hostname);\n\t}\n\treturn true;\n}\nstatic bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len, bool *hostname_is_ip)\n{\n\tif (!params.cache_hostname)\n\t{\n\t\t*hostname = 0;\n\t\treturn true;\n\t}\n\tip_cache_item *ipc = ipcacheTouch(&params.ipcache,a4,a6);\n\tif (!ipc)\n\t{\n\t\tDLOG_ERR(\"ipcache_get_hostname: out of memory\\n\");\n\t\treturn false;\n\t}\n\tif (ipc->hostname)\n\t{\n\t\tVPRINT(\"got cached hostname (is_ip=%u): %s\\n\", ipc->hostname_is_ip, ipc->hostname);\n\t\tsnprintf(hostname,hostname_buf_len,\"%s\",ipc->hostname);\n\t\tif (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip;\n\t}\n\telse\n\t\t*hostname = 0;\n\treturn true;\n}\n\nstatic bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto)\n{\n\tbool bHostlistsEmpty;\n\n\tif (!HostlistsReloadCheckForProfile(dp)) return false;\n\n\tif ((dest->sa_family==AF_INET && !dp->filter_ipv4) || (dest->sa_family==AF_INET6 && !dp->filter_ipv6))\n\t\t// L3 filter does not match\n\t\treturn false;\n\tif (!port_filters_in_range(&dp->pf_tcp,saport(dest)))\n\t\t// L4 filter does not match\n\t\treturn false;\n\tif (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7))\n\t\t// L7 filter does not match\n\t\treturn false;\n\tbHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp);\n\tif (!dp->hostlist_auto && !hostname && !bHostlistsEmpty)\n\t\t// avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown.\n\t\treturn false;\n\tif (!IpsetCheck(dp, dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL))\n\t\t// target ip does not match\n\t\treturn false;\n\n\t// autohostlist profile matching l3/l4/l7 filter always win\n\tif (dp->hostlist_auto) return true;\n\n\tif (bHostlistsEmpty)\n\t\t// profile without hostlist filter wins\n\t\treturn true;\n\telse if (hostname)\n\t\t// if hostlists are present profile matches only if hostname is known and satisfy profile hostlists\n\t\treturn HostlistCheck(dp, hostname, bNoSubdom, NULL, true);\n\n\treturn false;\n}\nstatic struct desync_profile *dp_find(struct desync_profile_list_head *head, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto)\n{\n\tstruct desync_profile_list *dpl;\n\tif (params.debug)\n\t{\n\t\tchar ip_port[48];\n\t\tntop46_port(dest, ip_port,sizeof(ip_port));\n\t\tVPRINT(\"desync profile search for tcp target=%s l7proto=%s hostname='%s'\\n\", ip_port, l7proto_str(l7proto), hostname ? hostname : \"\");\n\t}\n\tLIST_FOREACH(dpl, head, next)\n\t{\n\t\tif (dp_match(&dpl->dp,dest,hostname,bNoSubdom,l7proto))\n\t\t{\n\t\t\tVPRINT(\"desync profile %d matches\\n\",dpl->dp.n);\n\t\t\treturn &dpl->dp;\n\t\t}\n\t}\n\tVPRINT(\"desync profile not found\\n\");\n\treturn NULL;\n}\n\nvoid apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest)\n{\n\tipcachePurgeRateLimited(&params.ipcache, params.ipcache_lifetime);\n\tif (!ctrack->hostname)\n\t{\n\t\tchar host[256];\n\t\tif (ipcache_get_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , host, sizeof(host), &ctrack->hostname_is_ip) && *host)\n\t\t\tif (!(ctrack->hostname=strdup(host)))\n\t\t\t\tDLOG_ERR(\"hostname dup : out of memory\");\n\t}\n\tctrack->dp = dp_find(&params.desync_profiles, dest, ctrack->hostname, ctrack->hostname_is_ip, ctrack->l7proto);\n}\n\n\n\n// segment buffer has at least 5 extra bytes to extend data block\nvoid tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags)\n{\n\tuint8_t *p, *pp, *pHost = NULL;\n\tsize_t method_len = 0, pos, tpos, orig_size=*size;\n\tconst char *method;\n\tbool bHaveHost = false, bHostIsIp = false;\n\tchar *pc, Host[256];\n\tt_l7proto l7proto;\n\n\tDBGPRINT(\"tamper_out\\n\");\n\n\tif (!ctrack->dp) return;\n\n\tif (params.debug)\n\t{\n\t\tchar ip_port[48];\n\t\tntop46_port(dest,ip_port,sizeof(ip_port));\n\t\tVPRINT(\"tampering tcp segment with size %zu to %s\\n\", *size, ip_port);\n\t\tif (ctrack->dp) VPRINT(\"using cached desync profile %d\\n\",ctrack->dp->n);\n\t\tif (ctrack->hostname) VPRINT(\"connection hostname: %s\\n\", ctrack->hostname);\n\t}\n\t\t\n\tif (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6)\n\t{\n\t\tDLOG_ERR(\"tamper_out dest family unknown\\n\");\n\t\treturn;\n\t}\n\n\tif (multisplit_count) *multisplit_count=0;\n\tif (split_flags) *split_flags=0;\n\n\tif ((method = HttpMethod(segment,*size)))\n\t{\n\t\tmethod_len = strlen(method)-2;\n\t\tVPRINT(\"Data block looks like http request start : %s\\n\", method);\n\t\tl7proto=HTTP;\n\t\tif (HttpFindHost(&pHost,segment,*size))\n\t\t{\n\t\t\tp = pHost + 5;\n\t\t\twhile (p < (segment + *size) && (*p == ' ' || *p == '\\t')) p++;\n\t\t\tpp = p;\n\t\t\twhile (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\\r' && *pp != '\\n') pp++;\n\t\t\tmemcpy(Host, p, pp - p);\n\t\t\tHost[pp - p] = '\\0';\n\t\t\tbHaveHost = true;\n\t\t\tfor(pc = Host; *pc; pc++) *pc=tolower(*pc);\n\t\t}\n\t}\n\telse if (IsTLSClientHello(segment,*size,false))\n\t{\n\t\tVPRINT(\"Data block contains TLS ClientHello\\n\");\n\t\tl7proto=TLS;\n\t\tTLSDebug(segment,*size);\n\t\tbHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false);\n\t}\n\telse\n\t{\n\t\tVPRINT(\"Data block contains unknown payload\\n\");\n\t\tl7proto = UNKNOWN;\n\t}\n\n\tif (bHaveHost)\n\t{\n\t\tbHostIsIp = strip_host_to_ip(Host);\n\t\tVPRINT(\"request hostname: %s\\n\", Host);\n\t\tif (!ipcache_put_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , Host, bHostIsIp))\n\t\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\");\n\t}\n\n\tbool bDiscoveredL7 = ctrack->l7proto==UNKNOWN && l7proto!=UNKNOWN;\n\tif (bDiscoveredL7)\n\t{\n\t\tVPRINT(\"discovered l7 protocol\\n\");\n\t\tctrack->l7proto=l7proto;\n\t}\n\n\tbool bDiscoveredHostname = bHaveHost && !ctrack->hostname_discovered;\n\tif (bDiscoveredHostname)\n\t{\n\t\tVPRINT(\"discovered hostname\\n\");\n\t\tfree(ctrack->hostname);\n\t\tif (!(ctrack->hostname=strdup(Host)))\n\t\t{\n\t\t\tDLOG_ERR(\"strdup hostname : out of memory\\n\");\n\t\t\treturn;\n\t\t}\n\t\tctrack->hostname_is_ip = bHostIsIp;\n\t\tctrack->hostname_discovered = true;\n\t}\n\n\tif (bDiscoveredL7 || bDiscoveredHostname)\n\t{\n\t\tstruct desync_profile *dp_prev = ctrack->dp;\n\t\tapply_desync_profile(ctrack, dest);\n\t\tif (ctrack->dp!=dp_prev)\n\t\t{\n\t\t\tVPRINT(\"desync profile changed by revealed l7 protocol or hostname !\\n\");\n\t\t\tctrack->b_host_checked = ctrack->b_ah_check = false;\n\t\t}\n\t}\n\n\tif (l7proto!=UNKNOWN && ctrack->dp->hostlist_auto)\n\t{\n\t\tif (bHaveHost && !ctrack->b_host_checked)\n\t\t{\n\t\t\tbool bHostExcluded;\n\t\t\tctrack->b_host_matches = HostlistCheck(ctrack->dp, Host, bHostIsIp, &bHostExcluded, false);\n\t\t\tctrack->b_host_checked = true;\n\t\t\tif (!ctrack->b_host_matches)\n\t\t\t\tctrack->b_ah_check = !bHostExcluded;\n\t\t}\n\t\tif (!ctrack->b_host_matches)\n\t\t{\n\t\t\tVPRINT(\"Not acting on this request\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\tswitch(l7proto)\n\t{\n\t\tcase HTTP:\n\t\t\tif (ctrack->dp->unixeol)\n\t\t\t{\n\t\t\t\tp = pp = segment;\n\t\t\t\twhile ((p = memmem(p, segment + *size - p, \"\\r\\n\", 2)))\n\t\t\t\t{\n\t\t\t\t\t*p = '\\n'; p++;\n\t\t\t\t\tmemmove(p, p + 1, segment + *size - p - 1);\n\t\t\t\t\t(*size)--;\n\t\t\t\t\tif (pp == (p - 1))\n\t\t\t\t\t{\n\t\t\t\t\t\t// probably end of http headers\n\t\t\t\t\t\tVPRINT(\"Found double EOL at pos %td. Stop replacing.\\n\", pp - segment);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tpp = p;\n\t\t\t\t}\n\t\t\t\tpHost = NULL; // invalidate\n\t\t\t}\n\t\t\tif (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size)\n\t\t\t{\n\t\t\t\tVPRINT(\"Adding EOL before method\\n\");\n\t\t\t\tif (ctrack->dp->unixeol)\n\t\t\t\t{\n\t\t\t\t\tmemmove(segment + 1, segment, *size);\n\t\t\t\t\t(*size)++;;\n\t\t\t\t\tsegment[0] = '\\n';\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmemmove(segment + 2, segment, *size);\n\t\t\t\t\t*size += 2;\n\t\t\t\t\tsegment[0] = '\\r';\n\t\t\t\t\tsegment[1] = '\\n';\n\t\t\t\t}\n\t\t\t\tpHost = NULL; // invalidate\n\t\t\t}\n\t\t\tif (ctrack->dp->methodspace && *size<segment_buffer_size)\n\t\t\t{\n\t\t\t\t// we only work with data blocks looking as HTTP query, so method is at the beginning\n\t\t\t\tVPRINT(\"Adding extra space after method\\n\");\n\t\t\t\tp = segment + method_len + 1;\n\t\t\t\tpos = method_len + 1;\n\t\t\t\tmemmove(p + 1, p, *size - pos);\n\t\t\t\t*p = ' '; // insert extra space\n\t\t\t\t(*size)++; // block will grow by 1 byte\n\t\t\t\tif (pHost) pHost++; // Host: position will move by 1 byte\n\t\t\t}\n\t\t\tif ((ctrack->dp->hostdot || ctrack->dp->hosttab) && *size<segment_buffer_size && HttpFindHost(&pHost,segment,*size))\n\t\t\t{\n\t\t\t\tp = pHost + 5;\n\t\t\t\twhile (p < (segment + *size) && *p != '\\r' && *p != '\\n') p++;\n\t\t\t\tif (p < (segment + *size))\n\t\t\t\t{\n\t\t\t\t\tpos = p - segment;\n\t\t\t\t\tVPRINT(\"Adding %s to host name at pos %zu\\n\", ctrack->dp->hostdot ? \"dot\" : \"tab\", pos);\n\t\t\t\t\tmemmove(p + 1, p, *size - pos);\n\t\t\t\t\t*p = ctrack->dp->hostdot ? '.' : '\\t'; // insert dot or tab\n\t\t\t\t\t(*size)++; // block will grow by 1 byte\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size))\n\t\t\t{\n\t\t\t\tp = pHost + 5;\n\t\t\t\tpos = p - segment;\n\t\t\t\tVPRINT(\"Mixing domain case at pos %zu\\n\",pos);\n\t\t\t\tfor (; p < (segment + *size) && *p != '\\r' && *p != '\\n'; p++)\n\t\t\t\t\t*p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p);\n\t\t\t}\n\t\t\tif (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ')\n\t\t\t{\n\t\t\t\tp = pHost + 6;\n\t\t\t\tpos = p - segment;\n\t\t\t\tVPRINT(\"Removing space before host name at pos %zu\\n\", pos);\n\t\t\t\tmemmove(p - 1, p, *size - pos);\n\t\t\t\t(*size)--; // block will shrink by 1 byte\n\t\t\t}\n\t\t\tif (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size))\n\t\t\t{\n\t\t\t\tVPRINT(\"Changing 'Host:' => '%c%c%c%c:' at pos %td\\n\", ctrack->dp->hostspell[0], ctrack->dp->hostspell[1], ctrack->dp->hostspell[2], ctrack->dp->hostspell[3], pHost - segment);\n\t\t\t\tmemcpy(pHost, ctrack->dp->hostspell, 4);\n\t\t\t}\n\t\t\tif (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size))\n\t\t\t{\n\t\t\t\t//  add :  XXXXX: <padding?[\\r\\n|\\n]\n\t\t\t\tchar s[8];\n\t\t\t\tsize_t hsize = ctrack->dp->unixeol ? 8 : 9;\n\t\t\t\tsize_t hostpad = ctrack->dp->hostpad<hsize ? hsize : ctrack->dp->hostpad;\n\n\t\t\t\tif ((hsize+*size)>segment_buffer_size)\n\t\t\t\t\tVPRINT(\"could not add host padding : buffer too small\\n\");\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif ((hostpad+*size)>segment_buffer_size)\n\t\t\t\t\t{\n\t\t\t\t\t\thostpad=segment_buffer_size-*size;\n\t\t\t\t\t\tVPRINT(\"host padding reduced to %zu bytes : buffer too small\\n\", hostpad);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tVPRINT(\"host padding with %zu bytes\\n\", hostpad);\n\t\t\t\t\t\n\t\t\t\t\tp = pHost;\n\t\t\t\t\tpos = p - segment;\n\t\t\t\t\tmemmove(p + hostpad, p, *size - pos);\n\t\t\t\t\t(*size) += hostpad;\n\t\t\t\t\twhile(hostpad)\n\t\t\t\t\t{\n\t\t\t\t\t\t#define MAX_HDR_SIZE\t2048\n\t\t\t\t\t\tsize_t padsize = hostpad > hsize ? hostpad-hsize : 0;\n\t\t\t\t\t\tif (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE;\n\t\t\t\t\t\t// if next header would be too small then add extra padding to the current one\n\t\t\t\t\t\tif ((hostpad-padsize-hsize)<hsize) padsize+=hostpad-padsize-hsize;\n\t\t\t\t\t\tsnprintf(s,sizeof(s),\"%c%04x: \", 'a'+rand()%('z'-'a'+1), rand() & 0xFFFF);\n\t\t\t\t\t\tmemcpy(p,s,7);\n\t\t\t\t\t\tp+=7;\n\t\t\t\t\t\tmemset(p,'a'+rand()%('z'-'a'+1),padsize);\n\t\t\t\t\t\tp+=padsize;\n\t\t\t\t\t\tif (ctrack->dp->unixeol)\n\t\t\t\t\t\t\t*p++='\\n';\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*p++='\\r';\n\t\t\t\t\t\t\t*p++='\\n';\n\t\t\t\t\t\t}\n\t\t\t\t\t\thostpad-=hsize+padsize;\n\t\t\t\t\t}\n\t\t\t\t\tpHost = NULL; // invalidate\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);\n\t\t\tif (split_flags)\n\t\t\t{\n\t\t\t\tif (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER;\n\t\t\t\tif (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase TLS:\n\t\t\tif (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);\n\t\t\tif ((5+*size)<=segment_buffer_size)\n\t\t\t{\n\t\t\t\ttpos = ResolvePos(segment, *size, l7proto, &ctrack->dp->tlsrec);\n\t\t\t\tif (tpos>5)\n\t\t\t\t{\n\t\t\t\t\t// construct 2 TLS records from one\n\t\t\t\t\tuint16_t l = pntoh16(segment+3); // length\n\t\t\t\t\tif (l>=2)\n\t\t\t\t\t{\n\t\t\t\t\t\tint i;\n\t\t\t\t\t\tsize_t dlen;\n\t\t\t\t\t\t// length is checked in IsTLSClientHello and cannot exceed buffer size\n\t\t\t\t\t\tif ((tpos-5)>=l) tpos=5+1;\n\t\t\t\t\t\tVPRINT(\"making 2 TLS records at pos %zu\\n\",tpos);\n\t\t\t\t\t\tmemmove(segment+tpos+5,segment+tpos,*size-tpos);\n\t\t\t\t\t\tsegment[tpos] = segment[0];\n\t\t\t\t\t\tsegment[tpos+1] = segment[1];\n\t\t\t\t\t\tsegment[tpos+2] = segment[2];\n\t\t\t\t\t\tphton16(segment+tpos+3,l-(tpos-5));\n\t\t\t\t\t\tphton16(segment+3,tpos-5);\n\t\t\t\t\t\t*size += 5;\n\t\t\t\t\t\tVPRINT(\"-2nd TLS record: \");\n\t\t\t\t\t\tdlen = tpos<16 ? tpos : 16;\n\t\t\t\t\t\tpacket_debug(segment+tpos-dlen,dlen);\n\t\t\t\t\t\tVPRINT(\"+2nd TLS record: \");\n\t\t\t\t\t\tpacket_debug(segment+tpos,*size-tpos);\n\t\t\t\t\t\t// fix split positions after tlsrec. increase split pos by tlsrec header size (5 bytes)\n\t\t\t\t\t\tif (multisplit_pos)\n\t\t\t\t\t\t\tfor(i=0;i<*multisplit_count;i++)\n\t\t\t\t\t\t\t\tif (multisplit_pos[i]>tpos) multisplit_pos[i]+=5;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (split_flags)\n\t\t\t{\n\t\t\t\tif (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER;\n\t\t\t\tif (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tif (multisplit_pos && ctrack->dp->split_any_protocol)\n\t\t\t\tResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);\n\t}\n\n\tif (split_flags)\n\t{\n\t\tif (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER;\n\t\tif (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB;\n\t}\n\tif (orig_size!=*size)\n\t{\n\t\tVPRINT(\"segment size changed: %zu -> %zu\\n\", orig_size, *size);\n\t}\n\tif (params.debug && multisplit_count && *multisplit_count)\n\t{\n\t\tVPRINT(\"multisplit pos: \");\n\t\tfor (int i=0;i<*multisplit_count;i++) VPRINT(\"%zu \",multisplit_pos[i]);\n\t\tVPRINT(\"\\n\");\n\t}\n}\n\nstatic void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto)\n{\n\tif (hostname)\n\t{\n\t\thostfail_pool *fail_counter;\n\t\n\t\tfail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);\n\t\tif (fail_counter)\n\t\t{\n\t\t\tHostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);\n\t\t\tVPRINT(\"auto hostlist (profile %d) : %s : fail counter reset. website is working.\\n\", dp->n, hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : fail counter reset. website is working.\", hostname, dp->n, client_ip_port, l7proto_str(l7proto));\n\t\t}\n\t}\n}\n\nstatic void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto)\n{\n\thostfail_pool *fail_counter;\n\n\tfail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);\n\tif (!fail_counter)\n\t{\n\t\tfail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time);\n\t\tif (!fail_counter)\n\t\t{\n\t\t\tDLOG_ERR(\"HostFailPoolAdd: out of memory\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\tfail_counter->counter++;\n\tVPRINT(\"auto hostlist (profile %d) : %s : fail counter %d/%d\\n\", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold);\n\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : fail counter %d/%d\", hostname, dp->n, client_ip_port, l7proto_str(l7proto), fail_counter->counter, dp->hostlist_auto_fail_threshold);\n\tif (fail_counter->counter >= dp->hostlist_auto_fail_threshold)\n\t{\n\t\tVPRINT(\"auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\\n\", dp->n , hostname);\n\t\tHostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);\n\t\t\n\t\tVPRINT(\"auto hostlist (profile %d) : rechecking %s to avoid duplicates\\n\", dp->n, hostname);\n\t\tbool bExcluded=false;\n\t\tif (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded)\n\t\t{\n\t\t\tVPRINT(\"auto hostlist (profile %d) : adding %s to %s\\n\", dp->n, hostname, dp->hostlist_auto->filename);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : adding to %s\", hostname, dp->n, client_ip_port, l7proto_str(l7proto), dp->hostlist_auto->filename);\n\t\t\tif (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"StrPoolAddStr out of memory\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!append_to_list_file(dp->hostlist_auto->filename, hostname))\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"write to auto hostlist:\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig))\n\t\t\t\tDLOG_PERROR(\"file_mod_signature\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVPRINT(\"auto hostlist (profile %d) : NOT adding %s\\n\", dp->n, hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : NOT adding, duplicate detected\", hostname, dp->n, client_ip_port, l7proto_str(l7proto));\n\t\t}\n\t}\n}\n\nvoid tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size)\n{\n\tDBGPRINT(\"tamper_in hostname=%s\\n\", ctrack->hostname);\n\n\tbool bFail=false;\n\n\tchar client_ip_port[48];\n\tif (*params.hostlist_auto_debuglog)\n\t\tntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port));\n\telse\n\t\t*client_ip_port=0;\n\n\tif (ctrack->dp && ctrack->b_ah_check)\n\t{\n\t\tHostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);\n\n\t\tif (ctrack->l7proto==HTTP && ctrack->hostname)\n\t\t{\n\t\t\tif (IsHttpReply(segment,*size))\n\t\t\t{\n\t\t\t\tVPRINT(\"incoming HTTP reply detected for hostname %s\\n\", ctrack->hostname);\n\t\t\t\tbFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname);\n\t\t\t\tif (bFail)\n\t\t\t\t{\n\t\t\t\t\tVPRINT(\"redirect to another domain detected. possibly DPI redirect.\\n\");\n\t\t\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : redirect to another domain\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tVPRINT(\"local or in-domain redirect detected. it's not a DPI redirect.\\n\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// received not http reply. do not monitor this connection anymore\n\t\t\t\tVPRINT(\"incoming unknown HTTP data detected for hostname %s\\n\", ctrack->hostname);\n\t\t\t}\n\t\t\tif (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);\n\t\t}\n\t\tif (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, ctrack->l7proto);\n\t}\n\tctrack->bTamperInCutoff = true;\n}\n\nvoid rst_in(t_ctrack *ctrack, const struct sockaddr *client)\n{\n\tDBGPRINT(\"rst_in hostname=%s\\n\", ctrack->hostname);\n\n\tchar client_ip_port[48];\n\tif (*params.hostlist_auto_debuglog)\n\t\tntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port));\n\telse\n\t\t*client_ip_port=0;\n\n\tif (ctrack->dp && ctrack->b_ah_check)\n\t{\n\t\tHostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);\n\n\t\tif (!ctrack->bTamperInCutoff && ctrack->hostname)\n\t\t{\n\t\t\tVPRINT(\"incoming RST detected for hostname %s\\n\", ctrack->hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : incoming RST\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\t\tauto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);\n\t\t}\n\t}\n}\nvoid hup_out(t_ctrack *ctrack, const struct sockaddr *client)\n{\n\tDBGPRINT(\"hup_out hostname=%s\\n\", ctrack->hostname);\n\t\n\tchar client_ip_port[48];\n\tif (*params.hostlist_auto_debuglog)\n\t\tntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port));\n\telse\n\t\t*client_ip_port=0;\n\n\tif (ctrack->dp && ctrack->b_ah_check)\n\t{\n\t\tHostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);\n\n\t\tif (!ctrack->bTamperInCutoff && ctrack->hostname)\n\t\t{\n\t\t\t// local leg dropped connection after first request. probably due to timeout.\n\t\t\tVPRINT(\"local leg closed connection after first request (timeout ?). hostname: %s\\n\", ctrack->hostname);\n\t\t\tHOSTLIST_DEBUGLOG_APPEND(\"%s : profile %d : client %s : proto %s : client closed connection without server reply\", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto));\n\t\t\tauto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tpws/tamper.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n#include \"params.h\"\n\n#define SPLIT_FLAG_DISORDER\t0x01\n#define SPLIT_FLAG_OOB\t\t0x02\n\ntypedef struct\n{\n\t// common state\n\tt_l7proto l7proto;\n\tbool bTamperInCutoff;\n\tbool b_host_checked,b_host_matches,b_ah_check;\n\tbool hostname_discovered;\n\tbool hostname_is_ip;\n\tchar *hostname;\n\tstruct desync_profile *dp;\t\t// desync profile cache\n} t_ctrack;\n\nvoid apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest);\nbool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip);\n\nvoid tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags);\nvoid tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size);\n// connection reset by remote leg\nvoid rst_in(t_ctrack *ctrack, const struct sockaddr *client);\n// local leg closed connection (timeout waiting response ?)\nvoid hup_out(t_ctrack *ctrack, const struct sockaddr *client);\n\nvoid packet_debug(const uint8_t *data, size_t sz);\n"
  },
  {
    "path": "tpws/tpws.c",
    "content": "#define _GNU_SOURCE\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netinet/ip.h>\n#include <net/if.h>\n#include <netdb.h>\n#include <unistd.h>\n#include <arpa/inet.h>\n#include <sys/select.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <sys/ioctl.h>\n#include <signal.h>\n#include <errno.h>\n#include <stdbool.h>\n#include <netinet/tcp.h>\n#include <getopt.h>\n#include <pwd.h>\n#include <sys/resource.h>\n#include <time.h>\n#include <syslog.h>\n#include <grp.h>\n\n#ifdef __ANDROID__\n#include \"andr/ifaddrs.h\"\n#else\n#include <ifaddrs.h>\n#endif\n\n#include \"tpws.h\"\n\n#ifdef BSD\n #include <sys/sysctl.h>\n#endif\n\n#include \"tpws_conn.h\"\n#include \"hostlist.h\"\n#include \"ipset.h\"\n#include \"params.h\"\n#include \"sec.h\"\n#include \"redirect.h\"\n#include \"helpers.h\"\n#include \"gzip.h\"\n#include \"pools.h\"\n\n\n#define MAX_CONFIG_FILE_SIZE 16384\n\nstruct params_s params;\nstatic bool bReload=false;\n\nstatic void onhup(int sig)\n{\n\tprintf(\"HUP received ! Lists will be reloaded.\\n\");\n\tbReload=true;\n}\nvoid ReloadCheck()\n{\n\tif (bReload)\n\t{\n\t\tResetAllHostlistsModTime();\n\t\tif (!LoadAllHostLists())\n\t\t{\n\t\t\tDLOG_ERR(\"hostlists load failed. this is fatal.\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tResetAllIpsetModTime();\n\t\tif (!LoadAllIpsets())\n\t\t{\n\t\t\tDLOG_ERR(\"ipset load failed. this is fatal.\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tbReload=false;\n\t}\n}\n\nstatic void onusr2(int sig)\n{\n\tprintf(\"\\nHOSTFAIL POOL DUMP\\n\");\n\n\tstruct desync_profile_list *dpl;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tprintf(\"\\nDESYNC PROFILE %d\\n\",dpl->dp.n);\n\t\tHostFailPoolDump(dpl->dp.hostlist_auto_fail_counters);\n\t}\n\n\tif (params.cache_hostname)\n\t{\n\t\tprintf(\"\\nIPCACHE\\n\");\n\t\tipcachePrint(&params.ipcache);\n\t}\n\n\tprintf(\"\\n\");\n}\n\n\nstatic int8_t block_sigpipe(void)\n{\n\tsigset_t sigset;\n\tmemset(&sigset, 0, sizeof(sigset));\n\n\t//Get the old sigset, add SIGPIPE and update sigset\n\tif (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) {\n\t\tDLOG_PERROR(\"sigprocmask (get)\");\n\t\treturn -1;\n\t}\n\n\tif (sigaddset(&sigset, SIGPIPE) == -1) {\n\t\tDLOG_PERROR(\"sigaddset\");\n\t\treturn -1;\n\t}\n\n\tif (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {\n\t\tDLOG_PERROR(\"sigprocmask (set)\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic bool test_list_files()\n{\n\tstruct hostlist_file *hfile;\n\tstruct ipset_file *ifile;\n\n\tLIST_FOREACH(hfile, &params.hostlists, next)\n\t\tif (hfile->filename && !file_open_test(hfile->filename, O_RDONLY))\n\t\t{\n\t\t\tDLOG_PERROR(\"file_open_test\");\n\t\t\tDLOG_ERR(\"cannot access hostlist file '%s'\\n\",hfile->filename);\n\t\t\treturn false;\n\t\t}\n\tLIST_FOREACH(ifile, &params.ipsets, next)\n\t\tif (ifile->filename && !file_open_test(ifile->filename, O_RDONLY))\n\t\t{\n\t\t\tDLOG_PERROR(\"file_open_test\");\n\t\t\tDLOG_ERR(\"cannot access ipset file '%s'\\n\",ifile->filename);\n\t\t\treturn false;\n\t\t}\n\treturn true;\n}\n\nstatic bool is_interface_online(const char *ifname)\n{\n\tstruct ifreq ifr;\n\tint sock;\n\t\n\tif ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1)\n\t\treturn false;\n\tmemset(&ifr, 0, sizeof(ifr));\n\tstrncpy(ifr.ifr_name, ifname, IFNAMSIZ);\n\tifr.ifr_name[IFNAMSIZ-1] = 0;\n\tioctl(sock, SIOCGIFFLAGS, &ifr);\n\tclose(sock);\n\treturn !!(ifr.ifr_flags & IFF_UP);\n}\nstatic int get_default_ttl(void)\n{\n\tint sock,ttl=0;\n\tsocklen_t optlen=sizeof(ttl);\n\t\n\tif ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))!=-1)\n\t{\n\t    getsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, &optlen);\n\t    close(sock);\n\t}\n\treturn ttl;\n}\n\n\nstatic void exithelp(void)\n{\n\tprintf(\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\t\t\" @<config_file>|$<config_file>\\t\\t; read file for options. must be the only argument. other options are ignored.\\n\\n\"\n#endif\n\t\t\" --bind-addr=<v4_addr>|<v6_addr>\\t; for v6 link locals append %%interface_name\\n\"\n\t\t\" --bind-iface4=<interface_name>\\t\\t; bind to the first ipv4 addr of interface\\n\"\n\t\t\" --bind-iface6=<interface_name>\\t\\t; bind to the first ipv6 addr of interface\\n\"\n\t\t\" --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\\n\"\n\t\t\" --bind-wait-ifup=<sec>\\t\\t\\t; wait for interface to appear and up\\n\"\n\t\t\" --bind-wait-ip=<sec>\\t\\t\\t; after ifup wait for ip address to appear up to N seconds\\n\"\n\t\t\" --bind-wait-ip-linklocal=<sec>\\t\\t; (prefer) accept only LL first N seconds then any  (unwanted) accept only globals first N seconds then LL\\n\"\n\t\t\" --bind-wait-only\\t\\t\\t; wait for bind conditions satisfaction then exit. return code 0 if success.\\n\"\n\t\t\" * multiple binds are supported. each bind-addr, bind-iface* start new bind\\n\"\n\t\t\" --connect-bind-addr=<v4_addr>|<v6_addr> ; address for outbound connections. for v6 link locals append %%interface_name\\n\"\n\t\t\" --port=<port>\\t\\t\\t\\t; only one port number for all binds is supported\\n\"\n\t\t\" --socks\\t\\t\\t\\t; implement socks4/5 proxy instead of transparent proxy\\n\"\n\t\t\" --no-resolve\\t\\t\\t\\t; disable socks5 remote dns ability\\n\"\n\t\t\" --resolver-threads=<int>\\t\\t; number of resolver worker threads\\n\"\n\t\t\" --local-rcvbuf=<bytes>\\n\"\n\t\t\" --local-sndbuf=<bytes>\\n\"\n\t\t\" --remote-rcvbuf=<bytes>\\n\"\n\t\t\" --remote-sndbuf=<bytes>\\n\"\n#ifdef SPLICE_PRESENT\n\t\t\" --nosplice\\t\\t\\t\\t; do not use splice to transfer data between sockets\\n\"\n#endif\n\t\t\" --skip-nodelay\\t\\t\\t\\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\\n\"\n#if defined(__linux__) || defined(__APPLE__)\n\t\t\" --local-tcp-user-timeout=<seconds>\\t; set tcp user timeout for local leg (default : %d, 0 = system default)\\n\"\n\t\t\" --remote-tcp-user-timeout=<seconds>\\t; set tcp user timeout for remote leg (default : %d, 0 = system default)\\n\"\n#endif\n\t\t\" --maxconn=<max_connections>\\n\"\n#ifdef SPLICE_PRESENT\n\t\t\" --maxfiles=<max_open_files>\\t\\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\\n\"\n#else\n\t\t\" --maxfiles=<max_open_files>\\t\\t; should be at least (connections*2+16)\\n\"\n#endif\n\t\t\" --max-orphan-time=<sec>\\t\\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\\n\"\n\t\t\" --daemon\\t\\t\\t\\t; daemonize\\n\"\n\t\t\" --pidfile=<filename>\\t\\t\\t; write pid to file\\n\"\n\t\t\" --user=<username>\\t\\t\\t; drop root privs\\n\"\n\t\t\" --uid=uid[:gid1,gid2,...]\\t\\t; drop root privs\\n\"\n#if defined(__FreeBSD__) || defined(__OpenBSD__)\n\t\t\" --enable-pf\\t\\t\\t\\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\\n\"\n#endif\n#if defined(__linux__)\n\t\t\" --fix-seg=<int>\\t\\t\\t; fix segmentation failures at the cost of possible slowdown. wait up to N msec (default %u)\\n\"\n#endif\n\t\t\" --ipcache-lifetime=<int>\\t\\t; time in seconds to keep cached domain name (default %u). 0 = no expiration\\n\"\n\t\t\" --ipcache-hostname=[0|1]\\t\\t; 1 or no argument enables ip->hostname caching\\n\"\n#ifdef __ANDROID__\n\t\t\" --debug=0|1|2|syslog|android|@<filename> ; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\\n\"\n#else\n\t\t\" --debug=0|1|2|syslog|@<filename>\\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\\n\"\n#endif\n\t\t\" --debug-level=0|1|2\\t\\t\\t; specify debug level\\n\"\n\t\t\" --dry-run\\t\\t\\t\\t; verify parameters and exit with code 0 if successful\\n\"\n\t\t\" --version\\t\\t\\t\\t; print version and exit\\n\"\n\t\t\" --comment=any_text\\n\"\n\t\t\"\\nMULTI-STRATEGY:\\n\"\n\t\t\" --new\\t\\t\\t\\t\\t; begin new strategy\\n\"\n\t\t\" --skip\\t\\t\\t\\t\\t; do not use this strategy\\n\"\n\t\t\" --filter-l3=ipv4|ipv6\\t\\t\\t; L3 protocol filter. multiple comma separated values allowed.\\n\"\n\t\t\" --filter-tcp=[~]port1[-port2]|*\\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\\n\"\n\t\t\" --filter-l7=[http|tls|unknown]\\t\\t; L6-L7 protocol filter. multiple comma separated values allowed.\\n\"\n\t\t\" --ipset=<filename>\\t\\t\\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\\n\"\n\t\t\" --ipset-ip=<ip_list>\\t\\t\\t; comma separated fixed subnet list\\n\"\n\t\t\" --ipset-exclude=<filename>\\t\\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\\n\"\n\t\t\" --ipset-exclude-ip=<ip_list>\\t\\t; comma separated fixed subnet list\\n\"\n\t\t\"\\nHOSTLIST FILTER:\\n\"\n\t\t\" --hostlist=<filename>\\t\\t\\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\\n\"\n\t\t\" --hostlist-domains=<domain_list>\\t; comma separated fixed domain list\\n\"\n\t\t\" --hostlist-exclude=<filename>\\t\\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\\n\"\n\t\t\" --hostlist-exclude-domains=<domain_list> ; comma separated fixed domain list\\n\"\n\t\t\" --hostlist-auto=<filename>\\t\\t; detect DPI blocks and build hostlist automatically\\n\"\n\t\t\" --hostlist-auto-fail-threshold=<int>\\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\\n\"\n\t\t\" --hostlist-auto-fail-time=<int>\\t; all failed attemps must be within these seconds (default : %d)\\n\"\n\t\t\" --hostlist-auto-debug=<logfile>\\t; debug auto hostlist positives\\n\"\n\t\t\"\\nTAMPER:\\n\"\n\t\t\" --split-pos=N|-N|marker+N|marker-N\\t; comma separated list of split positions\\n\"\n\t\t\"\\t\\t\\t\\t\\t; markers: method,host,endhost,sld,endsld,midsld,sniext\\n\"\n\t\t\" --split-any-protocol\\t\\t\\t; split not only http and TLS\\n\"\n#if defined(BSD) && !defined(__APPLE__)\n\t\t\" --disorder[=http|tls]\\t\\t\\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\\n\"\n#else\n\t\t\" --disorder[=http|tls]\\t\\t\\t; when splitting simulate sending second fragment first\\n\"\n#endif\n\t\t\" --oob[=http|tls]\\t\\t\\t; when splitting send out of band byte. default is HEX 0x00.\\n\"\n\t\t\" --oob-data=<char>|0xHEX\\t\\t; override default 0x00 OOB byte.\\n\"\n\t\t\" --hostcase\\t\\t\\t\\t; change Host: => host:\\n\"\n\t\t\" --hostspell\\t\\t\\t\\t; exact spelling of \\\"Host\\\" header. must be 4 chars. default is \\\"host\\\"\\n\"\n\t\t\" --hostdot\\t\\t\\t\\t; add \\\".\\\" after Host: name\\n\"\n\t\t\" --hosttab\\t\\t\\t\\t; add tab after Host: name\\n\"\n\t\t\" --hostnospace\\t\\t\\t\\t; remove space after Host:\\n\"\n\t\t\" --hostpad=<bytes>\\t\\t\\t; add dummy padding headers before Host:\\n\"\n\t\t\" --domcase\\t\\t\\t\\t; mix domain case : Host: TeSt.cOm\\n\"\n\t\t\" --methodspace\\t\\t\\t\\t; add extra space after method\\n\"\n\t\t\" --methodeol\\t\\t\\t\\t; add end-of-line before method\\n\"\n\t\t\" --unixeol\\t\\t\\t\\t; replace 0D0A to 0A\\n\"\n\t\t\" --tlsrec=N|-N|marker+N|marker-N\\t; make 2 TLS records. split records at specified position.\\n\"\n#ifdef __linux__\n\t\t\" --mss=<int>\\t\\t\\t\\t; set client MSS. forces server to split messages but significantly decreases speed !\\n\"\n#endif\n\t\t\" --tamper-start=[n]<pos>\\t\\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\\n\"\n\t\t\" --tamper-cutoff=[n]<pos>\\t\\t; do not tamper anymore after specified outbound stream position. default is unlimited.\\n\",\n#if defined(__linux__) || defined(__APPLE__)\n\t\tDEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE,\n#endif\n#ifdef __linux__\n\t\tFIX_SEG_DEFAULT_MAX_WAIT,\n#endif\n\t\tIPCACHE_LIFETIME,\n\t\tHOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT\n\t);\n\texit(1);\n}\nstatic void exithelp_clean(void)\n{\n\tcleanup_params(&params);\n\texithelp();\n}\nstatic void exit_clean(int code)\n{\n\tcleanup_params(&params);\n\texit(code);\n}\nstatic void nextbind_clean(void)\n{\n\tparams.binds_last++;\n\tif (params.binds_last>=MAX_BINDS)\n\t{\n\t\tDLOG_ERR(\"maximum of %d binds are supported\\n\",MAX_BINDS);\n\t\texit_clean(1);\n\t}\n}\nstatic void checkbind_clean(void)\n{\n\tif (params.binds_last<0)\n\t{\n\t\tDLOG_ERR(\"start new bind with --bind-addr,--bind-iface*\\n\");\n\t\texit_clean(1);\n\t}\n}\n\n\nvoid save_default_ttl(void)\n{\n\tif (!params.ttl_default)\n\t{\n    \t    params.ttl_default = get_default_ttl();\n\t    if (!params.ttl_default)\n\t    {\n\t\t    DLOG_ERR(\"could not get default ttl\\n\");\n\t\t    exit_clean(1);\n\t    }\n\t}\n}\n\nstatic bool parse_httpreqpos(const char *s, struct proto_pos *sp)\n{\n\tif (!strcmp(s, \"method\"))\n\t{\n\t\tsp->marker = PM_HTTP_METHOD;\n\t\tsp->pos=2;\n\t}\n\telse if (!strcmp(s, \"host\"))\n\t{\n\t\tsp->marker = PM_HOST;\n\t\tsp->pos=1;\n\t}\n\telse\n\t\treturn false;\n\treturn true;\n}\nstatic bool parse_tlspos(const char *s, struct proto_pos *sp)\n{\n\tif (!strcmp(s, \"sni\"))\n\t{\n\t\tsp->marker = PM_HOST;\n\t\tsp->pos=1;\n\t}\n\telse if (!strcmp(s, \"sniext\"))\n\t{\n\t\tsp->marker = PM_SNI_EXT;\n\t\tsp->pos=1;\n\t}\n\telse if (!strcmp(s, \"snisld\"))\n\t{\n\t\tsp->marker = PM_HOST_MIDSLD;\n\t\tsp->pos=0;\n\t}\n\telse\n\t\treturn false;\n\treturn true;\n}\n\nstatic bool parse_int16(const char *p, int16_t *v)\n{\n\tif (*p=='+' || *p=='-' || *p>='0' && *p<='9')\n\t{\n\t\tint i = atoi(p);\n\t\t*v = (int16_t)i;\n\t\treturn *v==i; // check overflow\n\t}\n\treturn false;\n}\nstatic bool parse_posmarker(const char *opt, uint8_t *posmarker)\n{\n\tif (!strcmp(opt,\"host\"))\n\t\t*posmarker = PM_HOST;\n\telse if (!strcmp(opt,\"endhost\"))\n\t\t*posmarker = PM_HOST_END;\n\telse if (!strcmp(opt,\"sld\"))\n\t\t*posmarker = PM_HOST_SLD;\n\telse if (!strcmp(opt,\"midsld\"))\n\t\t*posmarker = PM_HOST_MIDSLD;\n\telse if (!strcmp(opt,\"endsld\"))\n\t\t*posmarker = PM_HOST_ENDSLD;\n\telse if (!strcmp(opt,\"method\"))\n\t\t*posmarker = PM_HTTP_METHOD;\n\telse if (!strcmp(opt,\"sniext\"))\n\t\t*posmarker = PM_SNI_EXT;\n\telse\n\t\treturn false;\n\treturn true;\n}\nstatic bool parse_split_pos(char *opt, struct proto_pos *split)\n{\n\tif (parse_int16(opt,&split->pos))\n\t{\n\t\tsplit->marker = PM_ABS;\n\t\treturn !!split->pos;\n\t}\n\telse\n\t{\n\t\tchar c,*p=opt;\n\t\tbool b;\n\n\t\tfor (; *opt && *opt!='+' && *opt!='-'; opt++);\n\t\tc=*opt; *opt=0;\n\t\tb=parse_posmarker(p,&split->marker);\n\t\t*opt=c;\n\t\tif (!b) return false;\n\t\tif (*opt)\n\t\t\treturn parse_int16(opt,&split->pos);\n\t\telse\n\t\t\tsplit->pos = 0;\n\t}\n\treturn true;\n}\nstatic bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count)\n{\n\tchar c,*e,*p;\n\n\tfor (p=opt, *split_count=0 ; p && *split_count<splits_size ; (*split_count)++)\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\t\tif (!parse_split_pos(p,splits+*split_count)) return false;\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\tif (p) return false; // too much splits\n\treturn true;\n}\nstatic void SplitDebug(void)\n{\n\tstruct desync_profile_list *dpl;\n\tconst struct desync_profile *dp;\n\tint x;\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tdp = &dpl->dp;\n\t\tfor(x=0;x<dp->split_count;x++)\n\t\t\tVPRINT(\"profile %d multisplit %s %d\\n\",dp->n,posmarker_name(dp->splits[x].marker),dp->splits[x].pos);\n\t\tif (!PROTO_POS_EMPTY(&dp->tlsrec))\n\t\t\tVPRINT(\"profile %d tlsrec %s %d\\n\",dp->n,posmarker_name(dp->tlsrec.marker),dp->tlsrec.pos);\n\t}\n}\n\nstatic bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6)\n{\n\tchar *e,*p,c;\n\n\tfor (p=opt,*ipv4=*ipv6=false ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\n\t\tif (!strcmp(p,\"ipv4\"))\n\t\t\t*ipv4 = true;\n\t\telse if (!strcmp(p,\"ipv6\"))\n\t\t\t*ipv6 = true;\n\t\telse return false;\n\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_l7_list(char *opt, uint32_t *l7)\n{\n\tchar *e,*p,c;\n\n\tfor (p=opt,*l7=0 ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\n\t\tif (!strcmp(p,\"http\"))\n\t\t\t*l7 |= L7_PROTO_HTTP;\n\t\telse if (!strcmp(p,\"tls\"))\n\t\t\t*l7 |= L7_PROTO_TLS;\n\t\telse if (!strcmp(p,\"unknown\"))\n\t\t\t*l7 |= L7_PROTO_UNKNOWN;\n\t\telse return false;\n\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_pf_list(char *opt, struct port_filters_head *pfl)\n{\n\tchar *e,*p,c;\n\tport_filter pf;\n\tbool b;\n\n\tfor (p=opt ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\n\t\tb = pf_parse(p,&pf) && port_filter_add(pfl,&pf);\n\t\tif (e) *e++=c;\n\t\tif (!b) return false;\n\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_domain_list(char *opt, hostlist_pool **pp)\n{\n\tchar *e,*p,c;\n\n\tfor (p=opt ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\n\t\tif (*p && !AppendHostlistItem(pp,p)) return false;\n\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_ip_list(char *opt, ipset *pp)\n{\n\tchar *e,*p,c;\n\n\tfor (p=opt ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\n\t\tif (*p && !AppendIpsetItem(pp,p)) return false;\n\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\nstatic bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids)\n{\n\tunsigned int u;\n\tchar c, *p, *e;\n\n\t*gid_count=0;\n\tif ((e = strchr(optarg,':'))) *e++=0;\n\tif (sscanf(opt,\"%u\",&u)!=1) return false;\n\t*uid = (uid_t)u;\n\tfor (p=e ; p ; )\n\t{\n\t\tif ((e = strchr(p,',')))\n\t\t{\n\t\t\tc=*e;\n\t\t\t*e=0;\n\t\t}\n\t\tif (p)\n\t\t{\n\t\t\tif (sscanf(p,\"%u\",&u)!=1) return false;\n\t\t\tif (*gid_count>=max_gids) return false;\n\t\t\tgid[(*gid_count)++] = (gid_t)u;\n\t\t}\n\t\tif (e) *e++=c;\n\t\tp = e;\n\t}\n\treturn true;\n}\n\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n// no static to not allow optimizer to inline this func (save stack)\nvoid config_from_file(const char *filename)\n{\n\t// config from a file\n\tchar buf[MAX_CONFIG_FILE_SIZE];\n\tbuf[0]='x';\t// fake argv[0]\n\tbuf[1]=' ';\n\tsize_t bufsize=sizeof(buf)-3;\n\tif (!load_file(filename,buf+2,&bufsize))\n\t{\n\t\tDLOG_ERR(\"could not load config file '%s'\\n\",filename);\n\t\texit_clean(1);\n\t}\n\tbuf[bufsize+2]=0;\n\t// wordexp fails if it sees \\t \\n \\r between args\n\treplace_char(buf,'\\n',' ');\n\treplace_char(buf,'\\r',' ');\n\treplace_char(buf,'\\t',' ');\n\tif (wordexp(buf, &params.wexp, WRDE_NOCMD))\n\t{\n\t\tDLOG_ERR(\"failed to split command line options from file '%s'\\n\",filename);\n\t\texit_clean(1);\n\t}\n}\n#endif\n\n#ifndef __linux__\nstatic bool check_oob_disorder(const struct desync_profile *dp)\n{\n\treturn !(\n\t\tdp->oob && (dp->disorder || dp->disorder_http || dp->disorder_tls) ||\n\t\tdp->oob_http && (dp->disorder || dp->disorder_http) ||\n\t\tdp->oob_tls && (dp->disorder || dp->disorder_tls));\n}\n#endif\n\nenum opt_indices {\n\tIDX_HELP,\n\tIDX_H,\n\tIDX_BIND_ADDR,\n\tIDX_BIND_IFACE4,\n\tIDX_BIND_IFACE6,\n\tIDX_BIND_LINKLOCAL,\n\tIDX_BIND_WAIT_IFUP,\n\tIDX_BIND_WAIT_IP,\n\tIDX_BIND_WAIT_IP_LINKLOCAL,\n\tIDX_BIND_WAIT_ONLY,\n\tIDX_PORT,\n\tIDX_DAEMON,\n\tIDX_USER,\n\tIDX_UID,\n\tIDX_MAXCONN,\n\tIDX_MAXFILES,\n\tIDX_MAX_ORPHAN_TIME,\n\tIDX_IPCACHE_LIFETIME,\n\tIDX_IPCACHE_HOSTNAME,\n\tIDX_HOSTCASE,\n\tIDX_HOSTSPELL,\n\tIDX_HOSTDOT,\n\tIDX_HOSTNOSPACE,\n\tIDX_HOSTPAD,\n\tIDX_DOMCASE,\n\tIDX_SPLIT_HTTP_REQ,\n\tIDX_SPLIT_TLS,\n\tIDX_SPLIT_POS,\n\tIDX_SPLIT_ANY_PROTOCOL,\n\tIDX_DISORDER,\n\tIDX_OOB,\n\tIDX_OOB_DATA,\n\tIDX_METHODSPACE,\n\tIDX_METHODEOL,\n\tIDX_HOSTTAB,\n\tIDX_UNIXEOL,\n\tIDX_TLSREC,\n\tIDX_TLSREC_POS,\n\tIDX_HOSTLIST,\n\tIDX_HOSTLIST_DOMAINS,\n\tIDX_HOSTLIST_EXCLUDE,\n\tIDX_HOSTLIST_EXCLUDE_DOMAINS,\n\tIDX_HOSTLIST_AUTO,\n\tIDX_HOSTLIST_AUTO_FAIL_THRESHOLD,\n\tIDX_HOSTLIST_AUTO_FAIL_TIME,\n\tIDX_HOSTLIST_AUTO_DEBUG,\n\tIDX_PIDFILE,\n\tIDX_DEBUG,\n\tIDX_DEBUG_LEVEL,\n\tIDX_DRY_RUN,\n\tIDX_VERSION,\n\tIDX_COMMENT,\n\tIDX_LOCAL_RCVBUF,\n\tIDX_LOCAL_SNDBUF,\n\tIDX_REMOTE_RCVBUF,\n\tIDX_REMOTE_SNDBUF,\n\tIDX_SOCKS,\n\tIDX_NO_RESOLVE,\n\tIDX_RESOLVER_THREADS,\n\tIDX_SKIP_NODELAY,\n\tIDX_TAMPER_START,\n\tIDX_TAMPER_CUTOFF,\n\tIDX_CONNECT_BIND_ADDR,\n\n\tIDX_NEW,\n\tIDX_SKIP,\n\tIDX_FILTER_L3,\n\tIDX_FILTER_TCP,\n\tIDX_FILTER_L7,\n\tIDX_IPSET,\n\tIDX_IPSET_IP,\n\tIDX_IPSET_EXCLUDE,\n\tIDX_IPSET_EXCLUDE_IP,\n\n#if defined(__FreeBSD__) || defined(__OpenBSD__)\n\tIDX_ENABLE_PF,\n#elif defined(__APPLE__)\n\tIDX_LOCAL_TCP_USER_TIMEOUT,\n\tIDX_REMOTE_TCP_USER_TIMEOUT,\n#elif defined(__linux__)\n\tIDX_LOCAL_TCP_USER_TIMEOUT,\n\tIDX_REMOTE_TCP_USER_TIMEOUT,\n\tIDX_MSS,\n\tIDX_FIX_SEG,\n#ifdef SPLICE_PRESENT\n\tIDX_NOSPLICE,\n#endif\n#endif\n\tIDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, // ignored. for nfqws command line compatibility\n\tIDX_LAST,\n};\n\nstatic const struct option long_options[] = {\n\t[IDX_HELP] = {\"help\", no_argument, 0, 0},\n\t[IDX_H] = {\"h\", no_argument, 0, 0},\n\t[IDX_BIND_ADDR] = {\"bind-addr\", required_argument, 0, 0},\n\t[IDX_BIND_IFACE4] = {\"bind-iface4\", required_argument, 0, 0},\n\t[IDX_BIND_IFACE6] = {\"bind-iface6\", required_argument, 0, 0},\n\t[IDX_BIND_LINKLOCAL] = {\"bind-linklocal\", required_argument, 0, 0},\n\t[IDX_BIND_WAIT_IFUP] = {\"bind-wait-ifup\", required_argument, 0, 0},\n\t[IDX_BIND_WAIT_IP] = {\"bind-wait-ip\", required_argument, 0, 0},\n\t[IDX_BIND_WAIT_IP_LINKLOCAL] = {\"bind-wait-ip-linklocal\", required_argument, 0, 0},\n\t[IDX_BIND_WAIT_ONLY] = {\"bind-wait-only\", no_argument, 0, 0},\n\t[IDX_PORT] = {\"port\", required_argument, 0, 0},\n\t[IDX_DAEMON] = {\"daemon\", no_argument, 0, 0},\n\t[IDX_USER] = {\"user\", required_argument, 0, 0},\n\t[IDX_UID] = {\"uid\", required_argument, 0, 0},\n\t[IDX_MAXCONN] = {\"maxconn\", required_argument, 0, 0},\n\t[IDX_MAXFILES] = {\"maxfiles\", required_argument, 0, 0},\n\t[IDX_IPCACHE_LIFETIME] = {\"ipcache-lifetime\", required_argument, 0, 0},\n\t[IDX_IPCACHE_HOSTNAME] = {\"ipcache-hostname\", optional_argument, 0, 0},\n\t[IDX_MAX_ORPHAN_TIME] = {\"max-orphan-time\", required_argument, 0, 0},\n\t[IDX_HOSTCASE] = {\"hostcase\", no_argument, 0, 0},\n\t[IDX_HOSTSPELL] = {\"hostspell\", required_argument, 0, 0},\n\t[IDX_HOSTDOT] = {\"hostdot\", no_argument, 0, 0},\n\t[IDX_HOSTNOSPACE] = {\"hostnospace\", no_argument, 0, 0},\n\t[IDX_HOSTPAD] = {\"hostpad\", required_argument, 0, 0},\n\t[IDX_DOMCASE] = {\"domcase\", no_argument, 0, 0},\n\t[IDX_SPLIT_HTTP_REQ] = {\"split-http-req\", required_argument, 0, 0},\n\t[IDX_SPLIT_TLS] = {\"split-tls\", required_argument, 0, 0},\n\t[IDX_SPLIT_POS] = {\"split-pos\", required_argument, 0, 0},\n\t[IDX_SPLIT_ANY_PROTOCOL] = {\"split-any-protocol\", optional_argument, 0, 0},\n\t[IDX_DISORDER] = {\"disorder\", optional_argument, 0, 0},\n\t[IDX_OOB] = {\"oob\", optional_argument, 0, 0},\n\t[IDX_OOB_DATA] = {\"oob-data\", required_argument, 0, 0},\n\t[IDX_METHODSPACE] = {\"methodspace\", no_argument, 0, 0},\n\t[IDX_METHODEOL] = {\"methodeol\", no_argument, 0, 0},\n\t[IDX_HOSTTAB] = {\"hosttab\", no_argument, 0, 0},\n\t[IDX_UNIXEOL] = {\"unixeol\", no_argument, 0, 0},\n\t[IDX_TLSREC] = {\"tlsrec\", required_argument, 0, 0},\n\t[IDX_TLSREC_POS] = {\"tlsrec-pos\", required_argument, 0, 0},\n\t[IDX_HOSTLIST] = {\"hostlist\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_DOMAINS] = {\"hostlist-domains\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_EXCLUDE] = {\"hostlist-exclude\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_EXCLUDE_DOMAINS] = {\"hostlist-exclude-domains\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO] = {\"hostlist-auto\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {\"hostlist-auto-fail-threshold\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_FAIL_TIME] = {\"hostlist-auto-fail-time\", required_argument, 0, 0},\n\t[IDX_HOSTLIST_AUTO_DEBUG] = {\"hostlist-auto-debug\", required_argument, 0, 0},\n\t[IDX_PIDFILE] = {\"pidfile\", required_argument, 0, 0},\n\t[IDX_DEBUG] = {\"debug\", optional_argument, 0, 0},\n\t[IDX_DEBUG_LEVEL] = {\"debug-level\", required_argument, 0, 0},\n\t[IDX_DRY_RUN] = {\"dry-run\", no_argument, 0, 0},\n\t[IDX_VERSION] = {\"version\", no_argument, 0, 0},\n\t[IDX_COMMENT] = {\"comment\", optional_argument, 0, 0},\n\t[IDX_LOCAL_RCVBUF] = {\"local-rcvbuf\", required_argument, 0, 0},\n\t[IDX_LOCAL_SNDBUF] = {\"local-sndbuf\", required_argument, 0, 0},\n\t[IDX_REMOTE_RCVBUF] = {\"remote-rcvbuf\", required_argument, 0, 0},\n\t[IDX_REMOTE_SNDBUF] = {\"remote-sndbuf\", required_argument, 0, 0},\n\t[IDX_SOCKS] = {\"socks\", no_argument, 0, 0},\n\t[IDX_NO_RESOLVE] = {\"no-resolve\", no_argument, 0, 0},\n\t[IDX_RESOLVER_THREADS] = {\"resolver-threads\", required_argument, 0, 0},\n\t[IDX_SKIP_NODELAY] = {\"skip-nodelay\", no_argument, 0, 0},\n\t[IDX_TAMPER_START] = {\"tamper-start\", required_argument, 0, 0},\n\t[IDX_TAMPER_CUTOFF] = {\"tamper-cutoff\", required_argument, 0, 0},\n\t[IDX_CONNECT_BIND_ADDR] = {\"connect-bind-addr\", required_argument, 0, 0},\n\n\t[IDX_NEW] = {\"new\", no_argument, 0, 0},\n\t[IDX_SKIP] = {\"skip\", no_argument, 0, 0},\n\t[IDX_FILTER_L3] = {\"filter-l3\", required_argument, 0, 0},\n\t[IDX_FILTER_TCP] = {\"filter-tcp\", required_argument, 0, 0},\n\t[IDX_FILTER_L7] = {\"filter-l7\", required_argument, 0, 0},\n\t[IDX_IPSET] = {\"ipset\", required_argument, 0, 0},\n\t[IDX_IPSET_IP] = {\"ipset-ip\", required_argument, 0, 0},\n\t[IDX_IPSET_EXCLUDE] = {\"ipset-exclude\", required_argument, 0, 0},\n\t[IDX_IPSET_EXCLUDE_IP] = {\"ipset-exclude-ip\", required_argument, 0, 0},\n\n#if defined(__FreeBSD__) || defined(__OpenBSD__)\n\t[IDX_ENABLE_PF] = {\"enable-pf\", no_argument, 0, 0},\n#elif defined(__APPLE__)\n\t[IDX_LOCAL_TCP_USER_TIMEOUT] = {\"local-tcp-user-timeout\", required_argument, 0, 0},\n\t[IDX_REMOTE_TCP_USER_TIMEOUT] = {\"remote-tcp-user-timeout\", required_argument, 0, 0},\n#elif defined(__linux__)\n\t[IDX_LOCAL_TCP_USER_TIMEOUT] = {\"local-tcp-user-timeout\", required_argument, 0, 0},\n\t[IDX_REMOTE_TCP_USER_TIMEOUT] = {\"remote-tcp-user-timeout\", required_argument, 0, 0},\n\t[IDX_MSS] = {\"mss\", required_argument, 0, 0},\n\t[IDX_FIX_SEG] = {\"fix-seg\", optional_argument, 0, 0},\n#ifdef SPLICE_PRESENT\n\t[IDX_NOSPLICE] = {\"nosplice\", no_argument, 0, 0},\n#endif\n#endif\n\t[IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {\"hostlist-auto-retrans-threshold\", optional_argument, 0, 0},\n\t[IDX_LAST] = {NULL, 0, NULL, 0},\n};\n\nvoid parse_params(int argc, char *argv[])\n{\n\tint option_index = 0;\n\tint v, i;\n\tbool bSkip=false, bDry=false;\n\tstruct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL;\n\tstruct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL;\n\n\tmemset(&params, 0, sizeof(params));\n\tparams.maxconn = DEFAULT_MAX_CONN;\n\tparams.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME;\n\tparams.binds_last = -1;\n\tparams.ipcache_lifetime = IPCACHE_LIFETIME;\n#if defined(__linux__) || defined(__APPLE__)\n\tparams.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL;\n\tparams.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE;\n#endif\n\n#if defined(__APPLE__)\n\tparams.pf_enable = true; // OpenBSD and MacOS have no other choice\n#endif\n\n#ifdef __linux__\n\tparams.fix_seg_avail = socket_supports_notsent();\n#endif\n\n\tLIST_INIT(&params.hostlists);\n\tLIST_INIT(&params.ipsets);\n\n\tif (can_drop_root())\n\t{\n\t\tparams.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid\n\t\tparams.gid_count = 1;\n\t\tparams.droproot = true;\n\t}\n\n\tstruct desync_profile_list *dpl;\n\tstruct desync_profile *dp;\n\tint desync_profile_count=0;\n\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t{\n\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\tdp = &dpl->dp;\n\tdp->n = ++desync_profile_count;\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\tif (argc>=2 && (argv[1][0]=='@' || argv[1][0]=='$'))\n\t{\n\t\tconfig_from_file(argv[1]+1);\n\t\targv=params.wexp.we_wordv;\n\t\targc=params.wexp.we_wordc;\n\t}\n#endif\n\t\n\twhile ((v = getopt_long_only(argc, argv, \"\", long_options, &option_index)) != -1)\n\t{\n\t\tif (v)\n\t\t{\n\t\t\tif (bDry)\n\t\t\t\texit_clean(1);\n\t\t\telse\n\t\t\t\texithelp_clean();\n\t\t}\n\t\tswitch (option_index)\n\t\t{\n\t\tcase IDX_HELP:\n\t\tcase IDX_H:\n\t\t\texithelp_clean();\n\t\t\tbreak;\n\t\tcase IDX_BIND_ADDR:\n\t\t\tnextbind_clean();\n\t\t\t{\n\t\t\t\tchar *p = strchr(optarg,'%');\n\t\t\t\tif (p)\n\t\t\t\t{\n\t\t\t\t\t*p=0;\n\t\t\t\t\tstrncpy(params.binds[params.binds_last].bindiface, p+1, sizeof(params.binds[params.binds_last].bindiface));\n\t\t\t\t}\n\t\t\t\tstrncpy(params.binds[params.binds_last].bindaddr, optarg, sizeof(params.binds[params.binds_last].bindaddr));\n\t\t\t}\n\t\t\tparams.binds[params.binds_last].bindaddr[sizeof(params.binds[params.binds_last].bindaddr) - 1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_BIND_IFACE4:\n\t\t\tnextbind_clean();\n\t\t\tparams.binds[params.binds_last].bind_if6=false;\n\t\t\tstrncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface));\n\t\t\tparams.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_BIND_IFACE6:\n\t\t\tnextbind_clean();\n\t\t\tparams.binds[params.binds_last].bind_if6=true;\n\t\t\tstrncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface));\n\t\t\tparams.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0;\n\t\t\tbreak;\n\t\tcase IDX_BIND_LINKLOCAL:\n\t\t\tcheckbind_clean();\n\t\t\tparams.binds[params.binds_last].bindll = true;\n\t\t\tif (!strcmp(optarg, \"no\"))\n\t\t\t\tparams.binds[params.binds_last].bindll=no;\n\t\t\telse if (!strcmp(optarg, \"prefer\"))\n\t\t\t\tparams.binds[params.binds_last].bindll=prefer;\n\t\t\telse if (!strcmp(optarg, \"force\"))\n\t\t\t\tparams.binds[params.binds_last].bindll=force;\n\t\t\telse if (!strcmp(optarg, \"unwanted\"))\n\t\t\t\tparams.binds[params.binds_last].bindll=unwanted;\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid parameter in bind-linklocal : %s\\n\",optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_BIND_WAIT_IFUP:\n\t\t\tcheckbind_clean();\n\t\t\tparams.binds[params.binds_last].bind_wait_ifup = atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_BIND_WAIT_IP:\n\t\t\tcheckbind_clean();\n\t\t\tparams.binds[params.binds_last].bind_wait_ip = atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_BIND_WAIT_IP_LINKLOCAL:\n\t\t\tcheckbind_clean();\n\t\t\tparams.binds[params.binds_last].bind_wait_ip_ll = atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_BIND_WAIT_ONLY:\n\t\t\tparams.bind_wait_only = true;\n\t\t\tbreak;\n\t\tcase IDX_PORT:\n\t\t\ti = atoi(optarg);\n\t\t\tif (i <= 0 || i > 65535)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad port number\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.port = (uint16_t)i;\n\t\t\tbreak;\n\t\tcase IDX_DAEMON:\n\t\t\tparams.daemon = true;\n\t\t\tbreak;\n\t\tcase IDX_USER:\n\t\t{\n\t\t\tfree(params.user); params.user=NULL;\n\t\t\tstruct passwd *pwd = getpwnam(optarg);\n\t\t\tif (!pwd)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"non-existent username supplied\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.uid = pwd->pw_uid;\n\t\t\tparams.gid[0]=pwd->pw_gid;\n\t\t\tparams.gid_count=1;\n\t\t\tif (!(params.user=strdup(optarg)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"strdup: out of memory\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.droproot = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase IDX_UID:\n\t\t\tfree(params.user); params.user=NULL;\n\t\t\tif (!parse_uid(optarg,&params.uid,params.gid,&params.gid_count,MAX_GIDS))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--uid should be : uid[:gid,gid,...]\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!params.gid_count)\n\t\t\t{\n\t\t\t\tparams.gid[0] = 0x7FFFFFFF;\n\t\t\t\tparams.gid_count = 1;\n\t\t\t}\n\t\t\tparams.droproot = true;\n\t\t\tbreak;\n\t\tcase IDX_MAXCONN:\n\t\t\tparams.maxconn = atoi(optarg);\n\t\t\tif (params.maxconn <= 0 || params.maxconn > 10000)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad maxconn\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_MAXFILES:\n\t\t\tparams.maxfiles = atoi(optarg);\n\t\t\tif (params.maxfiles < 0)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad maxfiles\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_MAX_ORPHAN_TIME:\n\t\t\tparams.max_orphan_time = atoi(optarg);\n\t\t\tif (params.max_orphan_time < 0)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad max_orphan_time\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPCACHE_LIFETIME:\n\t\t\tif (sscanf(optarg, \"%u\", &params.ipcache_lifetime)!=1)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"invalid ipcache-lifetime value\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPCACHE_HOSTNAME:\n\t\t\tparams.cache_hostname = !optarg || !!atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_HOSTCASE:\n\t\t\tdp->hostcase = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTSPELL:\n\t\t\tif (strlen(optarg) != 4)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"hostspell must be exactly 4 chars long\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->hostcase = true;\n\t\t\tmemcpy(dp->hostspell, optarg, 4);\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTDOT:\n\t\t\tdp->hostdot = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTNOSPACE:\n\t\t\tdp->hostnospace = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTPAD:\n\t\t\tdp->hostpad = atoi(optarg);\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_DOMCASE:\n\t\t\tdp->domcase = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_SPLIT_HTTP_REQ:\n\t\t\tDLOG_CONDUP(\"WARNING ! --split-http-req is deprecated. use --split-pos with markers.\\n\",MAX_SPLITS);\n\t\t\tif (dp->split_count>=MAX_SPLITS)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Too much splits. max splits: %u\\n\",MAX_SPLITS);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_httpreqpos(optarg, dp->splits + dp->split_count))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for split-http-req\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->split_count++;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_SPLIT_TLS:\n\t\t\t// obsolete arg\n\t\t\tDLOG_CONDUP(\"WARNING ! --split-tls is deprecated. use --split-pos with markers.\\n\",MAX_SPLITS);\n\t\t\tif (dp->split_count>=MAX_SPLITS)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Too much splits. max splits: %u\\n\",MAX_SPLITS);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_tlspos(optarg, dp->splits + dp->split_count))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for split-tls\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tdp->split_count++;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_SPLIT_POS:\n\t\t\t{\n\t\t\t\tint ct;\n\t\t\t\tif (!parse_split_pos_list(optarg,dp->splits+dp->split_count,MAX_SPLITS-dp->split_count,&ct))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\\n\",dp->split_count,MAX_SPLITS,optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tdp->split_count += ct;\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_SPLIT_ANY_PROTOCOL:\n\t\t\tdp->split_any_protocol = true;\n\t\t\tbreak;\n\t\tcase IDX_DISORDER:\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\tif (!strcmp(optarg,\"http\")) dp->disorder_http=true;\n\t\t\t\telse if (!strcmp(optarg,\"tls\")) dp->disorder_tls=true;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Invalid argument for disorder\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tdp->disorder = true;\n#ifndef __linux__\n\t\t\tif (!check_oob_disorder(dp))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n#endif\n\t\t\tbreak;\n\t\tcase IDX_OOB:\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\tif (!strcmp(optarg,\"http\")) dp->oob_http=true;\n\t\t\t\telse if (!strcmp(optarg,\"tls\")) dp->oob_tls=true;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Invalid argument for oob\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tdp->oob = true;\n#ifndef __linux__\n\t\t\tif (!check_oob_disorder(dp))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n#endif\n\t\t\tbreak;\n\t\tcase IDX_OOB_DATA:\n\t\t\t{\n\t\t\t\tsize_t l = strlen(optarg);\n\t\t\t\tunsigned int bt;\n\t\t\t\tif (l==1) dp->oob_byte = (uint8_t)*optarg;\n\t\t\t\telse if (l!=4 || sscanf(optarg,\"0x%02X\",&bt)!=1)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"Invalid argument for oob-data\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\telse dp->oob_byte = (uint8_t)bt;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_METHODSPACE:\n\t\t\tdp->methodspace = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_METHODEOL:\n\t\t\tdp->methodeol = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTTAB:\n\t\t\tdp->hosttab = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_UNIXEOL:\n\t\t\tdp->unixeol = true;\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_TLSREC:\n\t\t\tif (!parse_split_pos(optarg, &dp->tlsrec) && !parse_tlspos(optarg, &dp->tlsrec))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for tlsrec\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_TLSREC_POS:\n\t\t\t// obsolete arg\n\t\t\ti = atoi(optarg);\n\t\t\tdp->tlsrec.marker = PM_ABS;\n\t\t\tdp->tlsrec.pos = (int16_t)i;\n\t\t\tif (!dp->tlsrec.pos || i!=dp->tlsrec.pos)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for tlsrec-pos\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterHostlist(dp, false, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_DOMAINS:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_hl && !(anon_hl=RegisterHostlist(dp, false, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_domain_list(optarg, &anon_hl->hostlist))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add domains to anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_EXCLUDE:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterHostlist(dp, true, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_EXCLUDE_DOMAINS:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_hl_exclude && !(anon_hl_exclude=RegisterHostlist(dp, true, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_domain_list(optarg, &anon_hl_exclude->hostlist))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add domains to anonymous hostlist\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO:\n\t\t\tif (bSkip) break;\n\t\t\tif (dp->hostlist_auto)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"only one auto hostlist per profile is supported\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\t{\n\t\t\t\tFILE *F = fopen(optarg,\"a+b\");\n\t\t\t\tif (!F)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"cannot create %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tbool bGzip = is_gzip(F);\n\t\t\t\tfclose(F);\n\t\t\t\tif (bGzip)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"gzipped auto hostlists are not supported\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!(dp->hostlist_auto=RegisterHostlist(dp, false, optarg)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register hostlist '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true; // need to detect blocks and update autohostlist. cannot just slice.\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_FAIL_THRESHOLD:\n\t\t\tdp->hostlist_auto_fail_threshold = atoi(optarg);\n\t\t\tif (dp->hostlist_auto_fail_threshold<1 || dp->hostlist_auto_fail_threshold>20)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"auto hostlist fail threshold must be within 1..20\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_FAIL_TIME:\n\t\t\tdp->hostlist_auto_fail_time = atoi(optarg);\n\t\t\tif (dp->hostlist_auto_fail_time<1)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"auto hostlist fail time is not valid\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_HOSTLIST_AUTO_DEBUG:\n\t\t\t{\n\t\t\t\tFILE *F = fopen(optarg,\"a+t\");\n\t\t\t\tif (!F)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"cannot create %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tfclose(F);\n\t\t\t\tstrncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog));\n\t\t\t\tparams.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\\0';\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_PIDFILE:\n\t\t\tsnprintf(params.pidfile,sizeof(params.pidfile),\"%s\",optarg);\n\t\t\tbreak;\n\t\tcase IDX_DEBUG:\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\tif (*optarg=='@')\n\t\t\t\t{\n\t\t\t\t\tstrncpy(params.debug_logfile,optarg+1,sizeof(params.debug_logfile));\n\t\t\t\t\tparams.debug_logfile[sizeof(params.debug_logfile)-1] = 0;\n\t\t\t\t\tFILE *F = fopen(params.debug_logfile,\"wt\");\n\t\t\t\t\tif (!F)\n\t\t\t\t\t{\n\t\t\t\t\t\tfprintf(stderr, \"cannot create %s\\n\", params.debug_logfile);\n\t\t\t\t\t\texit_clean(1);\n\t\t\t\t\t}\n\t\t\t\t\tif (!params.debug) params.debug = 1;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_FILE;\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(optarg,\"syslog\"))\n\t\t\t\t{\n\t\t\t\t\tif (!params.debug) params.debug = 1;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_SYSLOG;\n\t\t\t\t\topenlog(progname,LOG_PID,LOG_USER);\n\t\t\t\t}\n#ifdef __ANDROID__\n\t\t\t\telse if (!strcmp(optarg,\"android\"))\n\t\t\t\t{\n\t\t\t\t\tif (!params.debug) params.debug = 1;\n\t\t\t\t\tparams.debug_target = LOG_TARGET_ANDROID;\n\t\t\t\t}\n#endif\n\t\t\t\telse if (optarg[0]>='0' && optarg[0]<='2')\n\t\t\t\t{\n\t\t\t\t\tparams.debug = atoi(optarg);\n\t\t\t\t\tparams.debug_target = LOG_TARGET_CONSOLE;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfprintf(stderr, \"invalid debug mode : %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tparams.debug = 1;\n\t\t\t\tparams.debug_target = LOG_TARGET_CONSOLE;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_DEBUG_LEVEL:\n\t\t\tparams.debug = atoi(optarg);\n\t\t\tbreak;\n\t\tcase IDX_DRY_RUN:\n\t\t\tbDry = true;\n\t\t\tbreak;\n\t\tcase IDX_VERSION:\n\t\t\texit_clean(0);\n\t\t\tbreak;\n\t\tcase IDX_COMMENT:\n\t\t\tbreak;\n\t\tcase IDX_LOCAL_RCVBUF:\n#ifdef __linux__\n\t\t\tparams.local_rcvbuf = atoi(optarg)/2;\n#else\n\t\t\tparams.local_rcvbuf = atoi(optarg);\n#endif\n\t\t\tbreak;\n\t\tcase IDX_LOCAL_SNDBUF:\n#ifdef __linux__\n\t\t\tparams.local_sndbuf = atoi(optarg)/2;\n#else\n\t\t\tparams.local_sndbuf = atoi(optarg);\n#endif\n\t\t\tbreak;\n\t\tcase IDX_REMOTE_RCVBUF:\n#ifdef __linux__\n\t\t\tparams.remote_rcvbuf = atoi(optarg)/2;\n#else\n\t\t\tparams.remote_rcvbuf = atoi(optarg);\n#endif\n\t\t\tbreak;\n\t\tcase IDX_REMOTE_SNDBUF:\n#ifdef __linux__\n\t\t\tparams.remote_sndbuf = atoi(optarg)/2;\n#else\n\t\t\tparams.remote_sndbuf = atoi(optarg);\n#endif\n\t\t\tbreak;\n\t\tcase IDX_SOCKS:\n\t\t\tparams.proxy_type = CONN_TYPE_SOCKS;\n\t\t\tbreak;\n\t\tcase IDX_NO_RESOLVE:\n\t\t\tparams.no_resolve = true;\n\t\t\tbreak;\n\t\tcase IDX_RESOLVER_THREADS:\n\t\t\tparams.resolver_threads = atoi(optarg);\n\t\t\tif (params.resolver_threads<1 || params.resolver_threads>300)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"resolver-threads must be within 1..300\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_SKIP_NODELAY:\n\t\t\tparams.skip_nodelay = true;\n\t\t\tbreak;\n\t\tcase IDX_TAMPER_START:\n\t\t\t{\n\t\t\t\tconst char *p=optarg;\n\t\t\t\tif (*p=='n')\n\t\t\t\t{\n\t\t\t\t\tdp->tamper_start_n=true;\n\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tdp->tamper_start_n=false;\n\t\t\t\tdp->tamper_start = atoi(p);\n\t\t\t}\n\t\t\tparams.tamper_lim = true;\n\t\t\tbreak;\n\t\tcase IDX_TAMPER_CUTOFF:\n\t\t\t{\n\t\t\t\tconst char *p=optarg;\n\t\t\t\tif (*p=='n')\n\t\t\t\t{\n\t\t\t\t\tdp->tamper_cutoff_n=true;\n\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tdp->tamper_cutoff_n=false;\n\t\t\t\tdp->tamper_cutoff = atoi(p);\n\t\t\t}\n\t\t\tparams.tamper_lim = true;\n\t\t\tbreak;\n\t\tcase IDX_CONNECT_BIND_ADDR:\n\t\t\t{\n\t\t\t\tchar *p = strchr(optarg,'%');\n\t\t\t\tif (p) *p++=0;\n\t\t\t\tif (inet_pton(AF_INET, optarg, &params.connect_bind4.sin_addr))\n\t\t\t\t{\n\t\t\t\t\tparams.connect_bind4.sin_family = AF_INET;\n\t\t\t\t}\n\t\t\t\telse if (inet_pton(AF_INET6, optarg, &params.connect_bind6.sin6_addr))\n\t\t\t\t{\n\t\t\t\t\tparams.connect_bind6.sin6_family = AF_INET6;\n\t\t\t\t\tif (p && *p)\n\t\t\t\t\t{\n\t\t\t\t\t\t// copy interface name for delayed resolution\n\t\t\t\t\t\tstrncpy(params.connect_bind6_ifname,p,sizeof(params.connect_bind6_ifname));\n\t\t\t\t\t\tparams.connect_bind6_ifname[sizeof(params.connect_bind6_ifname)-1]=0;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"bad bind addr : %s\\n\", optarg);\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\n\t\tcase IDX_NEW:\n\t\t\tif (bSkip)\n\t\t\t{\n\t\t\t\tdp_clear(dp);\n\t\t\t\tdp_init(dp);\n\t\t\t\tdp->n = desync_profile_count;\n\t\t\t\tbSkip = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tdp = &dpl->dp;\n\t\t\t\tdp->n = ++desync_profile_count;\n\t\t\t}\n\t\t\tanon_hl = anon_hl_exclude = NULL;\n\t\t\tanon_ips = anon_ips_exclude = NULL;\n\t\t\tbreak;\n\t\tcase IDX_SKIP:\n\t\t\tbSkip = true;\n\t\t\tbreak;\n\t\tcase IDX_FILTER_L3:\n\t\t\tif (!wf_make_l3(optarg,&dp->filter_ipv4,&dp->filter_ipv6))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"bad value for --filter-l3\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_FILTER_TCP:\n\t\t\tif (!parse_pf_list(optarg,&dp->pf_tcp))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid port filter : %s\\n\",optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_FILTER_L7:\n\t\t\tif (!parse_l7_list(optarg,&dp->filter_l7))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid l7 filter : %s\\n\",optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_IPSET:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterIpset(dp, false, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register ipset '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_IPSET_IP:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_ips && !(anon_ips=RegisterIpset(dp, false, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_ip_list(optarg, &anon_ips->ipset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add subnets to anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_IPSET_EXCLUDE:\n\t\t\tif (bSkip) break;\n\t\t\tif (!RegisterIpset(dp, true, optarg))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register ipset '%s'\\n\", optarg);\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\t\tcase IDX_IPSET_EXCLUDE_IP:\n\t\t\tif (bSkip) break;\n\t\t\tif (!anon_ips_exclude && !(anon_ips_exclude=RegisterIpset(dp, true, NULL)))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to register anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (!parse_ip_list(optarg, &anon_ips_exclude->ipset))\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"failed to add subnets to anonymous ipset\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tparams.tamper = true;\n\t\t\tbreak;\n\n#if defined(__FreeBSD__) || defined(__OpenBSD__)\n\t\tcase IDX_ENABLE_PF:\n\t\t\tparams.pf_enable = true;\n\t\t\tbreak;\n#elif defined(__linux__) || defined(__APPLE__)\n\t\tcase IDX_LOCAL_TCP_USER_TIMEOUT:\n\t\t\tparams.tcp_user_timeout_local = atoi(optarg);\n\t\t\tif (params.tcp_user_timeout_local<0 || params.tcp_user_timeout_local>86400)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for tcp user timeout. must be 0..86400\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_REMOTE_TCP_USER_TIMEOUT:\n\t\t\tparams.tcp_user_timeout_remote = atoi(optarg);\n\t\t\tif (params.tcp_user_timeout_remote<0 || params.tcp_user_timeout_remote>86400)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid argument for tcp user timeout. must be 0..86400\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n#endif\n\n#if defined(__linux__)\n\t\tcase IDX_MSS:\n\t\t\t// this option does not work in any BSD and MacOS. OS may accept but it changes nothing\n\t\t\tdp->mss = atoi(optarg);\n\t\t\tif (dp->mss<88 || dp->mss>32767)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"Invalid value for MSS. Linux accepts MSS 88-32767.\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase IDX_FIX_SEG:\n\t\t\tif (!params.fix_seg_avail)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"--fix-seg is supported since kernel 4.6\\n\");\n\t\t\t\texit_clean(1);\n\t\t\t}\n\t\t\tif (optarg)\n\t\t\t{\n\t\t\t\ti = atoi(optarg);\n\t\t\t\tif (i < 0 || i > 1000)\n\t\t\t\t{\n\t\t\t\t\tDLOG_ERR(\"fix_seg value must be within 0..1000\\n\");\n\t\t\t\t\texit_clean(1);\n\t\t\t\t}\n\t\t\t\tparams.fix_seg = i;\n\t\t\t}\n\t\t\telse\n\t\t\t\tparams.fix_seg = FIX_SEG_DEFAULT_MAX_WAIT;\n\t\t\tbreak;\n#ifdef SPLICE_PRESENT\n\t\tcase IDX_NOSPLICE:\n\t\t\tparams.nosplice = true;\n\t\t\tbreak;\n#endif\n#endif\n\t\t}\n\t}\n\tif (bSkip)\n\t{\n\t\tLIST_REMOVE(dpl,next);\n\t\tdp_entry_destroy(dpl);\n\t\tdesync_profile_count--;\n\t}\n\n\tif (!params.bind_wait_only && !params.port)\n\t{\n\t\tDLOG_ERR(\"Need port number\\n\");\n\t\texit_clean(1);\n\t}\n\tif (params.binds_last<=0)\n\t{\n\t\tparams.binds_last=0; // default bind to all\n\t}\n\tif (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50;\n\n\tVPRINT(\"adding low-priority default empty desync profile\\n\");\n\t// add default empty profile\n\tif (!(dpl = dp_list_add(&params.desync_profiles)))\n\t{\n\t\tDLOG_ERR(\"desync_profile_add: out of memory\\n\");\n\t\texit_clean(1);\n\t}\n\n\tDLOG_CONDUP(\"we have %d user defined desync profile(s) and default low priority profile 0\\n\",desync_profile_count);\n\n\tsave_default_ttl();\n\tif (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1))\n\t\tfprintf(stderr, \"could not chown %s. log file may not be writable after privilege drop\\n\", params.debug_logfile);\n\tif (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1))\n\t\tDLOG_ERR(\"could not chown %s. auto hostlist debug log may not be writable after privilege drop\\n\", params.hostlist_auto_debuglog);\n\n#ifdef __linux__\n\tbool bHasMSS=false, bHasOOB=false, bHasDisorder=false;\n#endif\n\tLIST_FOREACH(dpl, &params.desync_profiles, next)\n\t{\n\t\tdp = &dpl->dp;\n\t\tif (params.skip_nodelay && dp->split_count)\n\t\t{\n\t\t\tDLOG_ERR(\"Cannot split with --skip-nodelay\\n\");\n\t\t\texit_clean(1);\n\t\t}\n\t\tif (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1))\n\t\t\tDLOG_ERR(\"could not chown %s. auto hostlist file may not be writable after privilege drop\\n\", dp->hostlist_auto->filename);\n#ifdef __linux__\n\t\tif (dp->mss) bHasMSS=true;\n\t\tif (dp->oob || dp->oob_http || dp->oob_tls) bHasOOB=true;\n\t\tif (dp->disorder || dp->disorder_http || dp->disorder_tls) bHasDisorder=true;\n#endif\n\t}\n#ifdef __linux__\n\tif (is_wsl()==1)\n\t{\n\t\tif (!params.nosplice) DLOG_CONDUP(\"WARNING ! WSL1 may have problems with splice. Consider using `--nosplice`.\\n\");\n\t\tif (bHasMSS) DLOG_CONDUP(\"WARNING ! WSL1 does not support MSS socket option. MSS will likely fail.\\n\");\n\t\tif (bHasOOB) DLOG_CONDUP(\"WARNING ! WSL1 does not support OOB. OOB will likely fail.\\n\");\n\t\tif (bHasDisorder) DLOG_CONDUP(\"WARNING ! Windows retransmits whole TCP segment. Disorder will not function properly.\\n\");\n\t\tfflush(stdout);\n\t}\n#endif\n\n\tif (!test_list_files())\n\t\texit_clean(1);\n\n\tif (!LoadAllHostLists())\n\t{\n\t\tDLOG_ERR(\"hostlists load failed\\n\");\n\t\texit_clean(1);\n\t}\n\tif (!LoadAllIpsets())\n\t{\n\t\tDLOG_ERR(\"ipset load failed\\n\");\n\t\texit_clean(1);\n\t}\n\n\tVPRINT(\"\\nlists summary:\\n\");\n\tHostlistsDebug();\n\tIpsetsDebug();\n\n\tVPRINT(\"\\nsplits summary:\\n\");\n\tSplitDebug();\n\tVPRINT(\"\\n\");\n\n#if !defined( __OpenBSD__) && !defined(__ANDROID__)\n\t// do not need args from file anymore\n\tcleanup_args(&params);\n#endif\n\tif (bDry)\n\t{\n\t\tif (params.droproot)\n\t\t{\n\t\t\tif (!droproot(params.uid,params.user,params.gid,params.gid_count))\n\t\t\t\texit_clean(1);\n#ifdef __linux__\n\t\t\tif (!dropcaps())\n\t\t\t\texit_clean(1);\n#endif\n\t\t\tprint_id();\n\t\t\tif (!test_list_files())\n\t\t\t\texit_clean(1);\n\t\t}\n\t\tDLOG_CONDUP(\"command line parameters verified\\n\");\n\t\texit_clean(0);\n\t}\n}\n\n\nstatic bool find_listen_addr(struct sockaddr_storage *salisten, const char *bindiface, bool bind_if6, enum bindll bindll, int *if_index)\n{\n\tstruct ifaddrs *addrs,*a;\n\tbool found=false;\n    \n\tif (getifaddrs(&addrs)<0)\n\t\treturn false;\n\n\t// for ipv6 preference order\n\t// bind-linklocal-1 : link-local,any\n\t// bind-linklocal=0 : private,global,link-local\n\tfor(int pass=0;pass<3;pass++)\n\t{\n\t\ta  = addrs;\n\t\twhile (a)\n\t\t{\n\t\t\tif (a->ifa_addr)\n\t\t\t{\n\t\t\t\tif (a->ifa_addr->sa_family==AF_INET &&\n\t\t\t\t    *bindiface && !bind_if6 && !strcmp(a->ifa_name, bindiface))\n\t\t\t\t{\n\t\t\t\t\tsalisten->ss_family = AF_INET;\n\t\t\t\t\tmemcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr));\n\t\t\t\t\tfound=true;\n\t\t\t\t\tgoto ex;\n\t\t\t\t}\n\t\t\t\t// ipv6 links locals are fe80::/10\n\t\t\t\telse if (a->ifa_addr->sa_family==AF_INET6\n\t\t\t\t          &&\n\t\t\t\t         ((!*bindiface && (bindll==prefer || bindll==force)) ||\n\t\t\t\t          (*bindiface && bind_if6 && !strcmp(a->ifa_name, bindiface)))\n\t\t\t\t          &&\n\t\t\t\t\t ((bindll==force && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) ||\n\t\t\t\t\t  (bindll==prefer && ((pass==0 && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || pass==2)) ||\n\t\t\t\t\t  (bindll==no && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)))) ||\n\t\t\t\t\t  (bindll==unwanted && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || pass==2)))\n\t\t\t\t\t)\n\t\t\t\t{\n\t\t\t\t\tsalisten->ss_family = AF_INET6;\n\t\t\t\t\tmemcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr));\n\t\t\t\t\tif (if_index) *if_index = if_nametoindex(a->ifa_name);\n\t\t\t\t\tfound=true;\n\t\t\t\t\tgoto ex;\n\t\t\t\t}\n\t\t\t}\n\t\t\ta = a->ifa_next;\n\t\t}\n\t}\nex:\n\tfreeifaddrs(addrs);\n\treturn found;\n}\n\nstatic bool read_system_maxfiles(rlim_t *maxfile)\n{\n#ifdef __linux__\n\tFILE *F;\n\tint n;\n\tuintmax_t um;\n\tif (!(F=fopen(\"/proc/sys/fs/file-max\",\"r\")))\n\t\treturn false;\n\tn=fscanf(F,\"%ju\",&um);\n\tfclose(F);\n\tif (n != 1) return false;\n\t*maxfile = (rlim_t)um;\n\treturn true;\n#elif defined(BSD)\n\tint maxfiles,mib[2]={CTL_KERN, KERN_MAXFILES};\n\tsize_t len = sizeof(maxfiles);\n\tif (sysctl(mib,2,&maxfiles,&len,NULL,0)==-1)\n\t\treturn false;\n\t*maxfile = (rlim_t)maxfiles;\n\treturn true;\n#else\n\treturn false;\n#endif\n}\nstatic bool write_system_maxfiles(rlim_t maxfile)\n{\n#ifdef __linux__\n\tFILE *F;\n\tint n;\n\tif (!(F=fopen(\"/proc/sys/fs/file-max\",\"w\")))\n\t\treturn false;\n\tn=fprintf(F,\"%ju\",(uintmax_t)maxfile);\n\tfclose(F);\n\treturn !!n;\n#elif defined(BSD)\n\tint maxfiles=(int)maxfile,mib[2]={CTL_KERN, KERN_MAXFILES};\n\tif (sysctl(mib,2,NULL,0,&maxfiles,sizeof(maxfiles))==-1)\n\t\treturn false;\n\treturn true;\n#else\n\treturn false;\n#endif\n}\n\nstatic bool set_ulimit(void)\n{\n\trlim_t fdmax,fdmin_system,cur_lim=0;\n\tint n;\n\n\tif (!params.maxfiles)\n\t{\n\t\t// 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket), 2 fds (2 socket) for nosplice\n\t\t// additional 1/2 for unpaired remote legs sending buffers\n\t\t// 16 for listen_fd, epoll, hostlist, ...\n#ifdef SPLICE_PRESENT\n\t\tfdmax = (rlim_t)(params.nosplice ? 2 : (params.tamper && !params.tamper_lim ? 4 : 6)) * (rlim_t)params.maxconn;\n#else\n\t\tfdmax = 2 * params.maxconn;\n#endif\n\t\tfdmax += fdmax/2 + 16;\n\t}\n\telse\n\t\tfdmax = params.maxfiles;\n\tfdmin_system = fdmax + 4096;\n\tDBGPRINT(\"set_ulimit : fdmax=%ju fdmin_system=%ju\\n\",(uintmax_t)fdmax,(uintmax_t)fdmin_system);\n\n\tif (!read_system_maxfiles(&cur_lim))\n\t\treturn false;\n\tDBGPRINT(\"set_ulimit : current system file-max=%ju\\n\",(uintmax_t)cur_lim);\n\tif (cur_lim<fdmin_system)\n\t{\n\t\tDBGPRINT(\"set_ulimit : system fd limit is too low. trying to increase to %ju\\n\",(uintmax_t)fdmin_system);\n\t\tif (!write_system_maxfiles(fdmin_system))\n\t\t{\n\t\t\tDLOG_ERR(\"could not set system-wide max file descriptors\\n\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstruct rlimit rlim = {fdmax,fdmax};\n\tn=setrlimit(RLIMIT_NOFILE, &rlim);\n\tif (n==-1) DLOG_PERROR(\"setrlimit\");\n\treturn n!=-1;\n}\n\nstruct salisten_s\n{\n\tstruct sockaddr_storage salisten;\n\tsocklen_t salisten_len;\n\tint ipv6_only;\n\tint bind_wait_ip_left; // how much seconds left from bind_wait_ip\n};\nstatic const char *bindll_s[] = { \"unwanted\",\"no\",\"prefer\",\"force\" };\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH)\n#ifdef __ANDROID__\n#define PRINT_VER printf(\"github android version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#else\n#define PRINT_VER printf(\"github version %s (%s)\\n\\n\", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH))\n#endif\n#else\n#ifdef __ANDROID__\n#define PRINT_VER printf(\"self-built android version %s %s\\n\\n\", __DATE__, __TIME__)\n#else\n#define PRINT_VER printf(\"self-built version %s %s\\n\\n\", __DATE__, __TIME__)\n#endif\n#endif\n\nint main(int argc, char *argv[])\n{\n\tint i, listen_fd[MAX_BINDS], yes = 1, retval = 0, if_index, exit_v=EXIT_FAILURE;\n\tstruct salisten_s list[MAX_BINDS];\n\tchar ip_port[48];\n\tFILE *Fpid = NULL;\n\n\tset_console_io_buffering();\n\tset_env_exedir(argv[0]);\n\tsrand(time(NULL));\n\n\tPRINT_VER;\n\n\tparse_params(argc, argv);\n\targv=NULL; argc=0;\n\n\tmemset(&list, 0, sizeof(list));\n\tfor(i=0;i<=params.binds_last;i++) listen_fd[i]=-1;\n\n\tfor(i=0;i<=params.binds_last;i++)\n\t{\n\t\tVPRINT(\"Prepare bind %d : addr=%s iface=%s v6=%u link_local=%s wait_ifup=%d wait_ip=%d wait_ip_ll=%d\\n\",i,\n\t\t\tparams.binds[i].bindaddr,params.binds[i].bindiface,params.binds[i].bind_if6,bindll_s[params.binds[i].bindll],\n\t\t\tparams.binds[i].bind_wait_ifup,params.binds[i].bind_wait_ip,params.binds[i].bind_wait_ip_ll);\n\t\tif_index=0;\n\t\tif (*params.binds[i].bindiface)\n\t\t{\n\t\t\tif (params.binds[i].bind_wait_ifup > 0)\n\t\t\t{\n\t\t\t\tint sec=0;\n\t\t\t\tif (!is_interface_online(params.binds[i].bindiface))\n\t\t\t\t{\n\t\t\t\t\tDLOG_CONDUP(\"waiting for ifup of %s for up to %d second(s)...\\n\",params.binds[i].bindiface,params.binds[i].bind_wait_ifup);\n\t\t\t\t\tdo\n\t\t\t\t\t{\n\t\t\t\t\t\tsleep(1);\n\t\t\t\t\t\tsec++;\n\t\t\t\t\t}\n\t\t\t\t\twhile (!is_interface_online(params.binds[i].bindiface) && sec<params.binds[i].bind_wait_ifup);\n\t\t\t\t\tif (sec>=params.binds[i].bind_wait_ifup)\n\t\t\t\t\t{\n\t\t\t\t\t\tDLOG_CONDUP(\"wait timed out\\n\");\n\t\t\t\t\t\tgoto exiterr;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!(if_index = if_nametoindex(params.binds[i].bindiface)) && params.binds[i].bind_wait_ip<=0)\n\t\t\t{\n\t\t\t\tDLOG_CONDUP(\"bad iface %s\\n\",params.binds[i].bindiface);\n\t\t\t\tgoto exiterr;\n\t\t\t}\n\t\t}\n\t\tlist[i].bind_wait_ip_left = params.binds[i].bind_wait_ip;\n\t\tif (*params.binds[i].bindaddr)\n\t\t{\n\t\t\tif (inet_pton(AF_INET, params.binds[i].bindaddr, &((struct sockaddr_in*)(&list[i].salisten))->sin_addr))\n\t\t\t{\n\t\t\t\tlist[i].salisten.ss_family = AF_INET;\n\t\t\t}\n\t\t\telse if (inet_pton(AF_INET6, params.binds[i].bindaddr, &((struct sockaddr_in6*)(&list[i].salisten))->sin6_addr))\n\t\t\t{\n\t\t\t\tlist[i].salisten.ss_family = AF_INET6;\n\t\t\t\tlist[i].ipv6_only = 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDLOG_CONDUP(\"bad bind addr : %s\\n\", params.binds[i].bindaddr);\n\t\t\t\tgoto exiterr;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (*params.binds[i].bindiface || params.binds[i].bindll)\n\t\t\t{\n\t\t\t\tbool found;\n\t\t\t\tenum bindll bindll_1;\n\t\t\t\tint sec=0;\n\n\t\t\t\tif (params.binds[i].bind_wait_ip > 0)\n\t\t\t\t{\n\t\t\t\t\tDLOG_CONDUP(\"waiting for ip on %s for up to %d second(s)...\\n\", *params.binds[i].bindiface ? params.binds[i].bindiface : \"<any>\", params.binds[i].bind_wait_ip);\n\t\t\t\t\tif (params.binds[i].bind_wait_ip_ll>0)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (params.binds[i].bindll==prefer)\n\t\t\t\t\t\t\tDLOG_CONDUP(\"during the first %d second(s) accepting only link locals...\\n\", params.binds[i].bind_wait_ip_ll);\n\t\t\t\t\t\telse if (params.binds[i].bindll==unwanted)\n\t\t\t\t\t\t\tDLOG_CONDUP(\"during the first %d second(s) accepting only ipv6 globals...\\n\", params.binds[i].bind_wait_ip_ll);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor(;;)\n\t\t\t\t{\n\t\t\t\t\t// allow, no, prefer, force\n\t\t\t\t\tbindll_1 =\t(params.binds[i].bindll==prefer && sec<params.binds[i].bind_wait_ip_ll) ? force : \n\t\t\t\t\t\t\t(params.binds[i].bindll==unwanted && sec<params.binds[i].bind_wait_ip_ll) ? no : \n\t\t\t\t\t\t\tparams.binds[i].bindll;\n\t\t\t\t\tif (sec && sec==params.binds[i].bind_wait_ip_ll)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (params.binds[i].bindll==prefer)\n\t\t\t\t\t\t\tDLOG_CONDUP(\"link local address wait timeout. now accepting globals\\n\");\n\t\t\t\t\t\telse if (params.binds[i].bindll==unwanted)\n\t\t\t\t\t\t\tDLOG_CONDUP(\"global ipv6 address wait timeout. now accepting link locals\\n\");\n\t\t\t\t\t}\n\t\t\t\t\tfound = find_listen_addr(&list[i].salisten,params.binds[i].bindiface,params.binds[i].bind_if6,bindll_1,&if_index);\n\t\t\t\t\tif (found) break;\n\n\t\t\t\t\tif (sec>=params.binds[i].bind_wait_ip)\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tsleep(1);\n\t\t\t\t\tsec++;\n\t\t\t\t} \n\n\t\t\t\tif (!found)\n\t\t\t\t{\n\t\t\t\t\tDLOG_CONDUP(\"suitable ip address not found\\n\");\n\t\t\t\t\tgoto exiterr;\n\t\t\t\t}\n\t\t\t\tlist[i].bind_wait_ip_left = params.binds[i].bind_wait_ip - sec;\n\t\t\t\tlist[i].ipv6_only=1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlist[i].salisten.ss_family = AF_INET6;\n\t\t\t\t// leave sin6_addr zero\n\t\t\t}\n\t\t}\n\t\tif (list[i].salisten.ss_family == AF_INET6)\n\t\t{\n\t\t\tlist[i].salisten_len = sizeof(struct sockaddr_in6);\n\t\t\t((struct sockaddr_in6*)(&list[i].salisten))->sin6_port = htons(params.port);\n\t\t\tif (is_linklocal((struct sockaddr_in6*)(&list[i].salisten)))\n\t\t\t\t((struct sockaddr_in6*)(&list[i].salisten))->sin6_scope_id = if_index;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlist[i].salisten_len = sizeof(struct sockaddr_in);\n\t\t\t((struct sockaddr_in*)(&list[i].salisten))->sin_port = htons(params.port);\n\t\t}\n\t}\n\n\tif (params.bind_wait_only)\n\t{\n\t\tDLOG_CONDUP(\"bind wait condition satisfied\\n\");\n\t\texit_v = 0;\n\t\tgoto exiterr;\n\t}\n\n\tif (params.proxy_type==CONN_TYPE_TRANSPARENT && !redir_init())\n\t{\n\t\tDLOG_ERR(\"could not initialize redirector !!!\\n\");\n\t\tgoto exiterr;\n\t}\n\n\tfor(i=0;i<=params.binds_last;i++)\n\t{\n\t\tif (params.debug)\n\t\t{\n\t\t\tntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port));\n\t\t\tVPRINT(\"Binding %d to %s\\n\",i,ip_port);\n\t\t}\n\n\t\tif ((listen_fd[i] = socket(list[i].salisten.ss_family, SOCK_STREAM, 0)) == -1) {\n\t\t\tDLOG_PERROR(\"socket\");\n\t\t\tgoto exiterr;\n\t\t}\n\n#ifndef __OpenBSD__\n// in OpenBSD always IPV6_ONLY for wildcard sockets\n\t\tif ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"setsockopt (IPV6_ONLY)\");\n\t\t\tgoto exiterr;\n\t\t}\n#endif\n\n\t\tif (setsockopt(listen_fd[i], SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"setsockopt (SO_REUSEADDR)\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\n\t\t//Mark that this socket can be used for transparent proxying\n\t\t//This allows the socket to accept connections for non-local IPs\n\t\tif (params.proxy_type==CONN_TYPE_TRANSPARENT)\n\t\t{\n\t\t#ifdef __linux__\n\t\t\tif (setsockopt(listen_fd[i], SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"setsockopt (IP_TRANSPARENT)\");\n\t\t\t\tgoto exiterr;\n\t\t\t}\n\t\t#elif defined(BSD) && defined(SO_BINDANY)\n\t\t\tif (setsockopt(listen_fd[i], SOL_SOCKET, SO_BINDANY, &yes, sizeof(yes)) == -1)\n\t\t\t{\n\t\t\t\tDLOG_PERROR(\"setsockopt (SO_BINDANY)\");\n\t\t\t\tgoto exiterr;\n\t\t\t}\n\t\t#endif\n\t\t}\n\n\t\tif (!set_socket_buffers(listen_fd[i], params.local_rcvbuf, params.local_sndbuf))\n\t\t\tgoto exiterr;\n\t\tif (!params.local_rcvbuf)\n\t\t{\n\t\t\t// HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ?\n\t\t\tint v;\n\t\t\tsocklen_t sz=sizeof(int);\n\t\t\tif (!getsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,&sz))\n\t\t\t{\n\t\t\t\tv/=2;\n\t\t\t\tsetsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,sizeof(int));\n\t\t\t}\n\t\t}\n\t\tbool bBindBug=false;\n\t\tfor(;;)\n\t\t{\n\t\t\tif (bind(listen_fd[i], (struct sockaddr *)&list[i].salisten, list[i].salisten_len) == -1)\n\t\t\t{\n\t\t\t\t// in linux strange behaviour was observed\n\t\t\t\t// just after ifup and address assignment there's short window when bind() can't bind to addresses got from getifaddrs()\n\t\t\t\t// it does not happen to transparent sockets because they can bind to any non-existend ip\n\t\t\t\t// also only ipv6 seem to be buggy this way\n\t\t\t\tif (errno==EADDRNOTAVAIL && params.proxy_type!=CONN_TYPE_TRANSPARENT && list[i].bind_wait_ip_left)\n\t\t\t\t{\n\t\t\t\t\tif (!bBindBug)\n\t\t\t\t\t{\n\t\t\t\t\t\tntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port));\n\t\t\t\t\t\tDLOG_CONDUP(\"address %s is not available. will retry for %d sec\\n\",ip_port,list[i].bind_wait_ip_left);\n\t\t\t\t\t\tbBindBug=true;\n\t\t\t\t\t}\n\t\t\t\t\tsleep(1);\n\t\t\t\t\tlist[i].bind_wait_ip_left--;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tDLOG_PERROR(\"bind\");\n\t\t\t\tgoto exiterr;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (listen(listen_fd[i], BACKLOG) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"listen\");\n\t\t\tgoto exiterr;\n\t\t}\n\t}\n\n\tif (params.cache_hostname) VPRINT(\"ipcache lifetime %us\\n\", params.ipcache_lifetime);\n\tDLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? \"socks mode\\n\" : \"transparent proxy mode\\n\");\n\tif (!params.tamper) DLOG_CONDUP(\"TCP proxy mode (no tampering)\\n\");\n\n\tif (*params.pidfile && !(Fpid=fopen(params.pidfile,\"w\")))\n\t{\n\t\tDLOG_PERROR(\"create pidfile\");\n\t\tgoto exiterr;\n\t}\n\n\tset_ulimit();\n\tif (params.droproot && !droproot(params.uid,params.user,params.gid,params.gid_count))\n\t\tgoto exiterr;\n#ifdef __linux__\n\tif (!dropcaps())\n\t\tgoto exiterr;\n#endif\n\tprint_id();\n\tif (params.droproot && !test_list_files())\n\t\tgoto exiterr;\n\n\tif (params.daemon) daemonize();\n\n\tsec_harden();\n\n\tif (Fpid)\n\t{\n\t\tif (fprintf(Fpid, \"%d\", getpid())<=0)\n\t\t{\n\t\t\tDLOG_PERROR(\"write pidfile\");\n\t\t\tgoto exiterr;\n\t\t}\n\t\tfclose(Fpid);\n\t\tFpid=NULL;\n\t}\n\n\t//splice() causes the process to receive the SIGPIPE-signal if one part (for\n\t//example a socket) is closed during splice(). I would rather have splice()\n\t//fail and return -1, so blocking SIGPIPE.\n\tif (block_sigpipe() == -1) {\n\t\tDLOG_ERR(\"Could not block SIGPIPE signal\\n\");\n\t\tgoto exiterr;\n\t}\n\n\tsignal(SIGHUP, onhup); \n\tsignal(SIGUSR2, onusr2);\n\n\tretval = event_loop(listen_fd,params.binds_last+1);\n\texit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS;\n\tDLOG_CONDUP(\"Exiting\\n\");\n\t\nexiterr:\n\tif (Fpid) fclose(Fpid);\n\tredir_close();\n\tfor(i=0;i<=params.binds_last;i++) if (listen_fd[i]!=-1) close(listen_fd[i]);\n\tcleanup_params(&params);\n\treturn exit_v;\n}\n"
  },
  {
    "path": "tpws/tpws.h",
    "content": "#pragma once\n\n#ifdef __linux__\n #define SPLICE_PRESENT\n#endif\n\n#include <sys/param.h>\n\nvoid ReloadCheck();\n"
  },
  {
    "path": "tpws/tpws_conn.c",
    "content": "#define _GNU_SOURCE\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n#include <fcntl.h>\n#include <netdb.h>\n\n#ifdef USE_SYSTEMD\n#include <systemd/sd-daemon.h>\n#endif\n\n#include \"tpws.h\"\n#include \"tpws_conn.h\"\n#include \"redirect.h\"\n#include \"tamper.h\"\n#include \"socks.h\"\n#include \"helpers.h\"\n#include \"hostlist.h\"\n#include \"linux_compat.h\"\n\nstatic void notify_ready(void)\n{\n#ifdef USE_SYSTEMD\n\tint r = sd_notify(0, \"READY=1\");\n\tif (r < 0)\n\t\tDLOG_ERR(\"sd_notify: %s\\n\", strerror(-r));\n#endif\n}\n\n// keep separate legs counter. counting every time thousands of legs can consume cpu\nstatic int legs_local, legs_remote;\n/*\nstatic void count_legs(struct tailhead *conn_list)\n{\n\ttproxy_conn_t *conn = NULL;\n\n\tlegs_local = legs_remote = 0;\n\tTAILQ_FOREACH(conn, conn_list, conn_ptrs)\n\t\tconn->remote ? legs_remote++ : legs_local++;\n\t\n}\n*/\nstatic void print_legs(void)\n{\n\tVPRINT(\"Legs : local:%d remote:%d\\n\", legs_local, legs_remote);\n}\n\n\nstatic bool socks5_send_rep(int fd,uint8_t rep)\n{\n\ts5_rep s5rep;\n\tmemset(&s5rep,0,sizeof(s5rep));\n\ts5rep.ver = 5;\n\ts5rep.rep = rep;\n\ts5rep.atyp = S5_ATYP_IP4;\n\treturn send(fd,&s5rep,sizeof(s5rep),MSG_DONTWAIT)==sizeof(s5rep);\n}\nstatic bool socks5_send_rep_errno(int fd,int errn)\n{\n\tuint8_t rep;\n\tswitch(errn)\n\t{\n\t\tcase 0:\n\t\t\trep=S5_REP_OK; break;\n\t\tcase ECONNREFUSED:\n\t\t\trep=S5_REP_CONN_REFUSED; break;\n\t\tcase ENETUNREACH:\n\t\t\trep=S5_REP_NETWORK_UNREACHABLE; break;\n\t\tcase ETIMEDOUT:\n\t\tcase EHOSTUNREACH:\n\t\t\trep=S5_REP_HOST_UNREACHABLE; break;\n\t\tdefault:\n\t\t\trep=S5_REP_GENERAL_FAILURE;\n\t}\n\treturn socks5_send_rep(fd,rep);\n}\nstatic bool socks4_send_rep(int fd, uint8_t rep)\n{\n\ts4_rep s4rep;\n\tmemset(&s4rep, 0, sizeof(s4rep));\n\ts4rep.rep = rep;\n\treturn send(fd, &s4rep, sizeof(s4rep), MSG_DONTWAIT) == sizeof(s4rep);\n}\nstatic bool socks4_send_rep_errno(int fd, int errn)\n{\n\treturn socks4_send_rep(fd, errn ? S4_REP_FAILED : S4_REP_OK);\n}\nstatic bool socks_send_rep(uint8_t ver, int fd, uint8_t rep5)\n{\n\treturn ver==5 ? socks5_send_rep(fd, rep5) : socks4_send_rep(fd, rep5 ? S4_REP_FAILED : S4_REP_OK);\n}\nstatic bool socks_send_rep_errno(uint8_t ver, int fd, int errn)\n{\n\treturn ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn);\n}\n\n\nstatic bool cork(int fd, int enable)\n{\n#ifdef __linux__\n\tint e = errno;\n\tif (setsockopt(fd, SOL_TCP, TCP_CORK, &enable, sizeof(enable))<0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (TCP_CORK)\");\n\t\terrno = e;\n\t\treturn false;\n\t}\n\terrno = e;\n#endif\n\treturn true;\n}\n\nssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl)\n{\n\tssize_t wr;\n\n\tif (!params.skip_nodelay)\n\t{\n\t\tint ttl_apply = ttl ? ttl : params.ttl_default;\n\t\tDBGPRINT(\"send_with_ttl %d fd=%d\\n\",ttl,fd);\n\t\tif (!set_ttl_hl(fd, ttl_apply))\n\t\t\t//DLOG_ERR(\"could not set ttl %d to fd=%d\\n\",ttl,fd);\n\t\t\tDLOG_ERR(\"could not set ttl %d to fd=%d\\n\",ttl_apply,fd);\n\t\tcork(fd,true);\n\t}\n\twr = send(fd, buf, len, flags);\n\tif (!params.skip_nodelay)\n\t\tcork(fd,false);\n\treturn wr;\n}\n\n\nstatic bool send_buffer_create(send_buffer_t *sb, const void *data, size_t len, size_t extra_bytes, int flags, int ttl)\n{\n\tif (sb->data)\n\t{\n\t\tDLOG_ERR(\"FATAL : send_buffer_create but buffer is not empty\\n\");\n\t\texit(1);\n\t}\n\tsb->data = malloc(len + extra_bytes);\n\tif (!sb->data)\n\t{\n\t\tDBGPRINT(\"send_buffer_create failed\\n\");\n\t\treturn false;\n\t}\n\tif (data) memcpy(sb->data,data,len);\n\tsb->len = len;\n\tsb->pos = 0;\n\tsb->ttl = ttl;\n\tsb->flags = flags;\n\treturn true;\n}\nstatic bool send_buffer_realloc(send_buffer_t *sb, size_t extra_bytes)\n{\n\tif (sb->data)\n\t{\n\t\tuint8_t *p = (uint8_t*)realloc(sb->data, sb->len + extra_bytes);\n\t\tif (p)\n\t\t{\n\t\t\tsb->data = p;\n\t\t\tDBGPRINT(\"reallocated send_buffer from %zd to %zd\\n\", sb->len, sb->len + extra_bytes);\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDBGPRINT(\"failed to realloc send_buffer from %zd to %zd\\n\", sb->len, sb->len + extra_bytes);\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic void send_buffer_free(send_buffer_t *sb)\n{\n\tfree(sb->data);\n\tsb->data = NULL;\n}\nstatic void send_buffers_free(send_buffer_t *sb_array, int count)\n{\n\tfor (int i=0;i<count;i++)\n\t\tsend_buffer_free(sb_array+i);\n}\nstatic void conn_free_buffers(tproxy_conn_t *conn)\n{\n\tsend_buffers_free(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]));\n}\nstatic bool send_buffer_present(send_buffer_t *sb)\n{\n\treturn !!sb->data;\n}\nstatic bool send_buffers_present(send_buffer_t *sb_array, int count)\n{\n\tfor(int i=0;i<count;i++)\n\t\tif (send_buffer_present(sb_array+i))\n\t\t\treturn true;\n\treturn false;\n}\nstatic ssize_t send_buffer_send(send_buffer_t *sb, int fd)\n{\n\tssize_t wr;\n\n\twr = send_with_ttl(fd, sb->data + sb->pos, sb->len - sb->pos, sb->flags, sb->ttl);\n\tDBGPRINT(\"send_buffer_send len=%zu pos=%zu wr=%zd err=%d\\n\",sb->len,sb->pos,wr,errno);\n\tif (wr>0)\n\t{\n\t\tsb->pos += wr;\n\t\tif (sb->pos >= sb->len)\n\t\t{\n\t\t\tsend_buffer_free(sb);\n\t\t}\n\t}\n\telse if (wr<0 && errno==EAGAIN) wr=0;\n\t\n\treturn wr;\n}\nstatic ssize_t send_buffers_send(send_buffer_t *sb_array, int count, int fd, size_t *real_wr)\n{\n\tssize_t wr,twr=0;\n\n\tfor (int i=0;i<count;i++)\n\t{\n\t\tif (send_buffer_present(sb_array+i))\n\t\t{\n\t\t\twr = send_buffer_send(sb_array+i, fd);\n\t\t\tDBGPRINT(\"send_buffers_send(%d) wr=%zd err=%d\\n\",i,wr,errno);\n\t\t\tif (wr<0)\n\t\t\t{\n\t\t\t\tif (real_wr) *real_wr = twr;\n\t\t\t\treturn wr; // send error\n\t\t\t}\n\t\t\ttwr+=wr;\n\t\t\tif (send_buffer_present(sb_array+i)) // send next buffer only when current is fully sent\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tif (real_wr) *real_wr = twr;\n\treturn twr;\n}\n\nstatic bool conn_in_tcp_mode(tproxy_conn_t *conn)\n{\n\treturn !(conn->conn_type==CONN_TYPE_SOCKS && conn->socks_state!=S_TCP);\n}\n\nstatic bool conn_partner_alive(tproxy_conn_t *conn)\n{\n\treturn conn->partner && conn->partner->state!=CONN_CLOSED;\n}\nstatic bool conn_buffers_present(tproxy_conn_t *conn)\n{\n\treturn send_buffers_present(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]));\n}\nstatic ssize_t conn_buffers_send(tproxy_conn_t *conn)\n{\n\tsize_t wr,real_twr;\n\twr = send_buffers_send(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]), conn->fd, &real_twr);\n\tconn->twr += real_twr;\n\treturn wr;\n}\nstatic bool conn_has_unsent(tproxy_conn_t *conn)\n{\n\treturn conn->wr_unsent || conn_buffers_present(conn);\n}\nstatic int conn_bytes_unread(tproxy_conn_t *conn)\n{\n\tint numbytes=-1;\n\tioctl(conn->fd, FIONREAD, &numbytes);\n\treturn numbytes;\n}\nstatic bool conn_has_unsent_pair(tproxy_conn_t *conn)\n{\n\treturn conn_has_unsent(conn) || (conn_partner_alive(conn) && conn_has_unsent(conn->partner));\n}\n\nstatic bool conn_shutdown(tproxy_conn_t *conn)\n{\n\tconn->bShutdown = true;\n\tif (shutdown(conn->fd,SHUT_WR)<0)\n\t{\n\t\tDLOG_PERROR(\"shutdown\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic ssize_t send_or_buffer(send_buffer_t *sb, int fd, const void *buf, size_t len, int flags, int ttl)\n{\n\tssize_t wr=0;\n\tif (len)\n\t{\n\t\twr = send_with_ttl(fd, buf, len, flags, ttl);\n\t\tif (wr<0 && errno==EAGAIN) wr=0;\n\t\tif (wr>=0 && wr<len)\n\t\t{\n\t\t\tif (!send_buffer_create(sb, buf+wr, len-wr, 0, flags, ttl))\n\t\t\t\twr=-1;\n\t\t}\n\t}\n\treturn wr;\n}\n\nstatic void dbgprint_socket_buffers(int fd)\n{\n\tif (params.debug>=2)\n\t{\n\t\tint v;\n\t\tsocklen_t sz;\n\t\tsz=sizeof(int);\n\t\tif (!getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&v,&sz))\n\t\t\tDBGPRINT(\"fd=%d SO_RCVBUF=%d\\n\",fd,v);\n\t\tsz=sizeof(int);\n\t\tif (!getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&v,&sz))\n\t\t\tDBGPRINT(\"fd=%d SO_SNDBUF=%d\\n\",fd,v);\n\t}\n}\n\nbool set_socket_buffers(int fd, int rcvbuf, int sndbuf)\n{\n\tDBGPRINT(\"set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\\n\",fd,rcvbuf,sndbuf);\n\tif (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (SO_RCVBUF)\");\n\t\treturn false;\n\t}\n\tif (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (SO_SNDBUF)\");\n\t\treturn false;\n\t}\n\tdbgprint_socket_buffers(fd);\n\treturn true;\n}\n\n\nstatic bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err)\n{\n\t// if proxy mode acknowledge connection request\n\t// conn = remote. conn->partner = local\n\tif (!conn->remote || !conn_partner_alive(conn)) return false;\n\tbool bres = true;\n\tswitch(conn->partner->conn_type)\n\t{\n\t\tcase CONN_TYPE_SOCKS:\n\t\t\tif (conn->partner->socks_state==S_WAIT_CONNECTION)\n\t\t\t{\n\t\t\t\tconn->partner->socks_state=S_TCP;\n\t\t\t\tbres = socks_send_rep_errno(conn->partner->socks_ver,conn->partner->fd,sock_err);\n\t\t\t\tDBGPRINT(\"socks connection acknowledgement. bres=%d remote_errn=%d remote_fd=%d local_fd=%d\\n\",bres,sock_err,conn->fd,conn->partner->fd);\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn bres;\n}\n\n#if defined(__linux__) || defined(__APPLE__)\n\nstatic void set_user_timeout(int fd, int timeout)\n{\n#ifdef __linux__\n\tif (timeout>0)\n\t{\n\t\tint msec = 1000*timeout;\n\t\tif (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &msec, sizeof(int)) <0)\n\t\t\tDLOG_PERROR(\"setsockopt (TCP_USER_TIMEOUT)\");\n\t}\n#elif defined(__APPLE__)\n\tif (timeout>0 && setsockopt(fd, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &timeout, sizeof(int)) <0)\n\t\tDLOG_PERROR(\"setsockopt (TCP_RXT_CONNDROPTIME)\");\n#endif\n}\n\n#else\n\n#define set_user_timeout(fd,timeout)\n\n#endif\n\n\n//Createas a socket and initiates the connection to the host specified by \n//remote_addr.\n//Returns -1 if something fails, >0 on success (socket fd).\nstatic int connect_remote(const struct sockaddr *remote_addr, int mss)\n{\n\tint remote_fd = 0, yes = 1, no = 0;\n    \n\t\n \tif((remote_fd = socket(remote_addr->sa_family, SOCK_STREAM, 0)) < 0)\n \t{\n\t\tDLOG_PERROR(\"socket (connect_remote)\");\n\t\treturn -1;\n\t}\n\t// Use NONBLOCK to avoid slow connects affecting the performance of other connections\n\t// separate fcntl call to comply with macos\n\tif (fcntl(remote_fd, F_SETFL, O_NONBLOCK)<0)\n\t{\n\t\tDLOG_PERROR(\"socket set O_NONBLOCK (connect_remote)\");\n\t\tclose(remote_fd);\n\t\treturn -1;\n\t}\n\tif (setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (SO_REUSEADDR, connect_remote)\");\n\t\tclose(remote_fd);\n\t\treturn -1;\n\t}\n\tif (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf))\n\t{\n\t\tclose(remote_fd);\n\t\treturn -1;\n\t}\n\tif (!set_keepalive(remote_fd))\n\t{\n\t\tDLOG_PERROR(\"set_keepalive\");\n\t\tclose(remote_fd);\n\t\treturn -1;\n\t}\n\tif (setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, params.skip_nodelay ? &no : &yes, sizeof(int)) <0)\n\t{\n\t\tDLOG_PERROR(\"setsockopt (TCP_NODELAY, connect_remote)\");\n\t\tclose(remote_fd);\n\t\treturn -1;\n\t}\n#ifdef __linux__\n\tif (mss)\n\t{\n\t\tVPRINT(\"Setting MSS %d\\n\", mss);\n\t\tif (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(int)) <0)\n\t\t{\n\t\t\tDLOG_PERROR(\"setsockopt (TCP_MAXSEG, connect_remote)\");\n\t\t\tclose(remote_fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n#endif\n\n\t// if no bind address specified - address family will be 0 in params_connect_bindX\n\tif(remote_addr->sa_family == params.connect_bind4.sin_family)\n\t{\n\t\tif (bind(remote_fd, (struct sockaddr *)&params.connect_bind4, sizeof(struct sockaddr_in)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"bind on connect\");\n\t\t\tclose(remote_fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n\telse if(remote_addr->sa_family == params.connect_bind6.sin6_family)\n\t{\n\t\tif (*params.connect_bind6_ifname && !params.connect_bind6.sin6_scope_id)\n\t\t{\n\t\t\tparams.connect_bind6.sin6_scope_id=if_nametoindex(params.connect_bind6_ifname);\n\t\t\tif (!params.connect_bind6.sin6_scope_id)\n\t\t\t{\n\t\t\t\tDLOG_ERR(\"interface name not found : %s\\n\", params.connect_bind6_ifname);\n\t\t\t\tclose(remote_fd);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\tif (bind(remote_fd, (struct sockaddr *)&params.connect_bind6, sizeof(struct sockaddr_in6)) == -1)\n\t\t{\n\t\t\tDLOG_PERROR(\"bind on connect\");\n\t\t\tclose(remote_fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tset_user_timeout(remote_fd, params.tcp_user_timeout_remote);\n\n\tif (connect(remote_fd, remote_addr, remote_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0)\n\t{\n\t\tif(errno != EINPROGRESS)\n\t\t{\n\t\t\tDLOG_PERROR(\"connect (connect_remote)\");\n\t\t\tclose(remote_fd);\n\t\t\treturn -1;\n\t\t}\n\t}\n\tDBGPRINT(\"Connecting remote fd=%d\\n\",remote_fd);\n\n\treturn remote_fd;\n}\n\nstatic bool connect_remote_conn(tproxy_conn_t *conn)\n{\n\tint mss=0;\n\n\tif (conn->track.hostname)\n\t\tif (!ipcache_put_hostname(conn->dest.sa_family==AF_INET ? &((struct sockaddr_in*)&conn->dest)->sin_addr : NULL, conn->dest.sa_family==AF_INET6 ? &((struct sockaddr_in6*)&conn->dest)->sin6_addr : NULL , conn->track.hostname, conn->track.hostname_is_ip))\n\t\t\tDLOG_ERR(\"ipcache_put_hostname: out of memory\");\n\tapply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest);\n\n\tif (conn->track.dp && conn->track.dp->mss)\n\t{\n\t\tmss = conn->track.dp->mss;\n\t\tif (conn->track.dp->hostlist_auto)\n\t\t{\n\t\t\tif (conn->track.hostname)\n\t\t\t{\n\t\t\t\tbool bHostExcluded;\n\t\t\t\tconn->track.b_host_matches = HostlistCheck(conn->track.dp, conn->track.hostname, conn->track.hostname_is_ip, &bHostExcluded, false);\n\t\t\t\tconn->track.b_host_checked = true;\n\t\t\t\tif (!conn->track.b_host_matches)\n\t\t\t\t{\n\t\t\t\t\tconn->track.b_ah_check = !bHostExcluded;\n\t\t\t\t\tmss = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn (conn->partner->fd = connect_remote((struct sockaddr *)&conn->dest, mss))>=0;\n}\n\n//Free resources occupied by this connection\nstatic void free_conn(tproxy_conn_t *conn)\n{\n\tif (!conn) return;\n\tif (conn->fd) close(conn->fd);\n\tif (conn->splice_pipe[0])\n\t{\n\t\tclose(conn->splice_pipe[0]);\n\t\tclose(conn->splice_pipe[1]);\n\t}\n\tconn_free_buffers(conn);\n\tif (conn->partner) conn->partner->partner=NULL;\n\tfree(conn->track.hostname);\n\tif (conn->socks_ri) conn->socks_ri->ptr = NULL; // detach conn\n\tfree(conn);\n}\nstatic tproxy_conn_t *new_conn(int fd, bool remote)\n{\n\ttproxy_conn_t *conn;\n\n\t//Create connection object and fill in information\n\tif((conn = (tproxy_conn_t*) calloc(1, sizeof(tproxy_conn_t))) == NULL)\n\t{\n\t\tDLOG_ERR(\"Could not allocate memory for connection\\n\");\n\t\treturn NULL;\n\t}\n\n\tconn->state = CONN_UNAVAILABLE;\n\tconn->fd = fd;\n\tconn->remote = remote;\n\n#ifdef SPLICE_PRESENT\n\t// if dont tamper - both legs are spliced, create 2 pipes\n\t// otherwise create pipe only in local leg\n\tif (!params.nosplice && ( !remote || !params.tamper || params.tamper_lim ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0)\n\t{\n\t\tDLOG_ERR(\"Could not create the splice pipe\\n\");\n\t\tfree_conn(conn);\n\t\treturn NULL;\n\t}\n#endif\n\t\n\treturn conn;\n}\n\nstatic bool epoll_set(tproxy_conn_t *conn, uint32_t events)\n{\n\tstruct epoll_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.events = events;\n\tev.data.ptr = (void*) conn;\n\tDBGPRINT(\"epoll_set fd=%d events=%08X\\n\",conn->fd,events);\n\tif(epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, &ev)==-1 &&\n\t   epoll_ctl(conn->efd, EPOLL_CTL_ADD, conn->fd, &ev)==-1)\n\t{\n\t\tDLOG_PERROR(\"epoll_ctl (add/mod)\");\n\t\treturn false;\n\t}\n\treturn true;\n}\nstatic bool epoll_del(tproxy_conn_t *conn)\n{\n\tstruct epoll_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\n\tDBGPRINT(\"epoll_del fd=%d\\n\",conn->fd);\n\tif(epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, &ev)==-1)\n\t{\n\t\tDLOG_PERROR(\"epoll_ctl (del)\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic bool epoll_update_flow(tproxy_conn_t *conn)\n{\n\tif (conn->bFlowInPrev==conn->bFlowIn && conn->bFlowOutPrev==conn->bFlowOut && conn->bPrevRdhup==(conn->state==CONN_RDHUP))\n\t\treturn true; // unchanged, no need to syscall\n\tDBGPRINT(\"SET FLOW fd=%d to in=%d out=%d state_rdhup=%d\\n\",conn->fd,conn->bFlowIn,conn->bFlowOut,conn->state==CONN_RDHUP);\n\tuint32_t evtmask = (conn->state==CONN_RDHUP ? 0 : EPOLLRDHUP)|(conn->bFlowIn?EPOLLIN:0)|(conn->bFlowOut?EPOLLOUT:0);\n\tif (!epoll_set(conn, evtmask))\n\t\treturn false;\n\tconn->bFlowInPrev = conn->bFlowIn;\n\tconn->bFlowOutPrev = conn->bFlowOut;\n\tconn->bPrevRdhup = (conn->state==CONN_RDHUP);\n\treturn true;\n}\nstatic bool epoll_set_flow(tproxy_conn_t *conn, bool bFlowIn, bool bFlowOut)\n{\n\tconn->bFlowIn = bFlowIn;\n\tconn->bFlowOut = bFlowOut;\n\treturn epoll_update_flow(conn);\n}\n\n//Acquires information, initiates a connect and initialises a new connection\n//object. Return NULL if anything fails, pointer to object otherwise\nstatic tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,int local_fd, const struct sockaddr *accept_sa, uint16_t listen_port, conn_type_t proxy_type)\n{\n\tstruct sockaddr_storage orig_dst;\n\ttproxy_conn_t *conn;\n\n\tif (proxy_type==CONN_TYPE_TRANSPARENT)\n\t{\n\t\tif(!get_dest_addr(local_fd, accept_sa, &orig_dst))\n\t\t{\n\t\t\tDLOG_ERR(\"Could not get destination address\\n\");\n\t\t\tclose(local_fd);\n\t\t\treturn NULL;\n\t\t}\n\t\tif (check_local_ip((struct sockaddr*)&orig_dst) && saport((struct sockaddr*)&orig_dst)==listen_port)\n\t\t{\n\t\t\tVPRINT(\"Dropping connection to local address to the same port to avoid loop\\n\");\n\t\t\tclose(local_fd);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\t// socket buffers inherited from listen_fd\n\tdbgprint_socket_buffers(local_fd);\n\n\tif(!set_keepalive(local_fd))\n\t{\n\t\tDLOG_PERROR(\"set_keepalive\");\n\t\tclose(local_fd);\n\t\treturn 0;\n\t}\n\n\tif(!(conn = new_conn(local_fd, false)))\n\t{\n\t\tclose(local_fd);\n\t\treturn NULL;\n\t}\n\tconn->conn_type = proxy_type; // only local connection has proxy_type. remote is always in tcp mode\n\tconn->state = CONN_AVAILABLE; // accepted connection is immediately available\n\tconn->efd = efd;\n\n\tsocklen_t salen=sizeof(conn->client);\n\tgetpeername(conn->fd,(struct sockaddr *)&conn->client,&salen);\n\n\tif (proxy_type==CONN_TYPE_TRANSPARENT)\n\t{\n\t\tsa46copy(&conn->dest, (struct sockaddr *)&orig_dst);\n\n\t\tif(!(conn->partner = new_conn(0, true)))\n\t\t{\n\t\t\tfree_conn(conn);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tconn->partner->partner = conn;\n\t\tconn->partner->efd = efd;\n\t\tconn->partner->client = conn->client;\n\t\tconn->partner->dest = conn->dest;\n\n\t\tif (!connect_remote_conn(conn))\n\t\t{\n\t\t\tDLOG_ERR(\"Failed to connect\\n\");\n\t\t\tfree_conn(conn->partner);\n\t\t\tfree_conn(conn);\n\t\t\treturn NULL;\n\t\t}\n\n\t\t//remote_fd is connecting. Non-blocking connects are signaled as done by\n\t\t//socket being marked as ready for writing\n\t\tif (!epoll_set(conn->partner, EPOLLOUT))\n\t\t{\n\t\t\tfree_conn(conn->partner);\n\t\t\tfree_conn(conn);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\t//Transparent proxy mode :\n\t// Local socket can be closed while waiting for connection attempt. I need\n\t// to detect this when waiting for connect() to complete. However, I dont\n\t// want to get EPOLLIN-events, as I dont want to receive any data before\n\t// remote connection is established\n\t//Proxy mode : I need to service proxy protocol\n\t// remote connection not started until proxy handshake is complete\n\n\tif (!epoll_set(conn, proxy_type==CONN_TYPE_TRANSPARENT ? EPOLLRDHUP : (EPOLLIN|EPOLLRDHUP)))\n\t{\n\t\tfree_conn(conn->partner);\n\t\tfree_conn(conn);\n\t\treturn NULL;\n\t}\n\n\tTAILQ_INSERT_HEAD(conn_list, conn, conn_ptrs);\n\tlegs_local++;\n\tif (conn->partner)\n\t{\n\t\tTAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs);\n\t\tlegs_remote++;\n\t}\n\n\treturn conn;\n} \n\n//Checks if a connection attempt was successful or not\n//Returns true if successfull, false if not\nstatic bool check_connection_attempt(tproxy_conn_t *conn, int efd)\n{\n\tint errn = 0;\n\tsocklen_t optlen = sizeof(errn);\n\n\tif (conn->state!=CONN_UNAVAILABLE || !conn->remote)\n\t{\n\t\t// locals are connected since accept\n\t\t// remote need to be checked only once\n\t\treturn true;\n\t}\n\n\t// check the connection was sucessfull. it means its not in in SO_ERROR state\n\tif(getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1)\n\t{\n\t\tDLOG_PERROR(\"getsockopt (SO_ERROR)\");\n\t\treturn false;\n\t}\n\tif (!errn)\n\t{\n\t\tif (params.debug>=1)\n\t\t{\n\t\t\tsockaddr_in46 sa;\n\t\t\tsocklen_t salen=sizeof(sa);\n\t\t\tchar ip_port[48];\n\n\t\t\tif (getsockname(conn->fd,(struct sockaddr *)&sa,&salen))\n\t\t\t\t*ip_port=0;\n\t\t\telse\n\t\t\t\tntop46_port((struct sockaddr*)&sa,ip_port,sizeof(ip_port));\n\t\t\tVPRINT(\"Socket fd=%d (remote) connected from : %s\\n\", conn->fd, ip_port);\n\t\t}\n\t\tif (!epoll_set_flow(conn, true, false) || (conn_partner_alive(conn) && !epoll_set_flow(conn->partner, true, false)))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t\tconn->state = CONN_AVAILABLE;\n\t}\n\tproxy_remote_conn_ack(conn,get_so_error(conn->fd));\n\treturn !errn;\n}\n\n\n\n\nstatic bool epoll_set_flow_pair(tproxy_conn_t *conn)\n{\n\tbool bHasUnsent = conn_has_unsent(conn);\n\tbool bHasUnsentPartner = conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false;\n\n\tDBGPRINT(\"epoll_set_flow_pair fd=%d remote=%d partner_fd=%d bHasUnsent=%d bHasUnsentPartner=%d state_rdhup=%d\\n\", \n\t\t\tconn->fd , conn->remote, conn_partner_alive(conn) ? conn->partner->fd : 0, bHasUnsent, bHasUnsentPartner, conn->state==CONN_RDHUP);\n\tif (!epoll_set_flow(conn, !bHasUnsentPartner && (conn->state != CONN_RDHUP), bHasUnsent))\n\t\treturn false;\n\tif (conn_partner_alive(conn))\n\t{\n\t\tif (!epoll_set_flow(conn->partner, !bHasUnsent && (conn->partner->state != CONN_RDHUP), bHasUnsentPartner))\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic bool handle_unsent(tproxy_conn_t *conn)\n{\n\tssize_t wr;\n\n\tDBGPRINT(\"+handle_unsent, fd=%d has_unsent=%d has_unsent_partner=%d\\n\",conn->fd,conn_has_unsent(conn),conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false);\n\t\n#ifdef SPLICE_PRESENT\n\tif (!params.nosplice && conn->wr_unsent)\n\t{\n\t\twr = splice(conn->splice_pipe[0], NULL, conn->fd, NULL, conn->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);\n\t\tDBGPRINT(\"splice unsent=%zd wr=%zd err=%d\\n\",conn->wr_unsent,wr,errno);\n\t\tif (wr<0)\n\t\t{\n\t\t\tif (errno==EAGAIN) wr=0;\n\t\t\telse return false;\n\t\t}\n\t\tconn->twr += wr;\n\t\tconn->wr_unsent -= wr;\n\t}\n#endif\n\tif (!conn->wr_unsent && conn_buffers_present(conn))\n\t{\n\t\twr=conn_buffers_send(conn);\n\t\tDBGPRINT(\"conn_buffers_send wr=%zd\\n\",wr);\n\t\tif (wr<0) return false;\n\t}\n\tif (!conn_has_unsent(conn) && conn_partner_alive(conn) && conn->partner->state==CONN_RDHUP)\n\t{\n\t\tif (!conn->bShutdown)\n\t\t{\n\t\t\tDBGPRINT(\"fd=%d no more has unsent. partner in RDHUP state. executing delayed shutdown.\\n\", conn->fd);\n\t\t\tif (!conn_shutdown(conn))\n\t\t\t{\n\t\t\t\tDBGPRINT(\"emergency connection close due to failed shutdown\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tif (conn->state==CONN_RDHUP && !conn_has_unsent(conn->partner))\n\t\t{\n\t\t\tDBGPRINT(\"both partners are in RDHUP state and have no unsent. closing.\\n\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn epoll_set_flow_pair(conn);\n}\n\n\nstatic bool proxy_mode_connect_remote(tproxy_conn_t *conn, struct tailhead *conn_list)\n{\n\tint remote_fd;\n\n\tif (params.debug>=1)\n\t{\n\t\tchar ip_port[48];\n\t\tntop46_port((struct sockaddr *)&conn->dest,ip_port,sizeof(ip_port));\n\t\tVPRINT(\"socks target for fd=%d is : %s\\n\", conn->fd, ip_port);\n\t}\n\tif (check_local_ip((struct sockaddr *)&conn->dest))\n\t{\n\t\tVPRINT(\"Dropping connection to local address for security reasons\\n\");\n\t\tsocks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET);\n\t\treturn false;\n\t}\n\n\tif (!(conn->partner = new_conn(remote_fd, true)))\n\t{\n\t\tclose(remote_fd);\n\t\tDLOG_ERR(\"socks out-of-memory (1)\\n\");\n\t\tsocks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE);\n\t\treturn false;\n\t}\n\tconn->partner->partner = conn;\n\tconn->partner->efd = conn->efd;\n\tconn->partner->client = conn->client;\n\tconn->partner->dest = conn->dest;\n\n\tif (!connect_remote_conn(conn))\n\t{\n\t\tfree_conn(conn->partner); conn->partner = NULL;\n\t\tDLOG_ERR(\"socks failed to connect (1) errno=%d\\n\", errno);\n\t\tsocks_send_rep_errno(conn->socks_ver, conn->fd, errno);\n\t\treturn false;\n\t}\n\n\tif (!epoll_set(conn->partner, EPOLLOUT))\n\t{\n\t\tDLOG_ERR(\"socks epoll_set error %d\\n\", errno);\n\t\tfree_conn(conn->partner);\n\t\tconn->partner = NULL;\n\t\tsocks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE);\n\t\treturn false;\n\t}\n\tTAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs);\n\tlegs_remote++;\n\tprint_legs();\n\tDBGPRINT(\"S_WAIT_CONNECTION\\n\");\n\tconn->socks_state = S_WAIT_CONNECTION;\n\treturn true;\n}\n\nstatic bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list)\n{\n\t// To simplify things I dont care about buffering. If message splits, I just hang up\n\t// in proxy mode messages are short. they can be split only intentionally. all normal programs send them in one packet\n\n\tssize_t rd,wr;\n\tchar buf[sizeof(s5_req)]; // s5_req - the largest possible req\n\n\t// receive proxy control message\n\trd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT);\n\tDBGPRINT(\"handle_proxy_mode rd=%zd\\n\",rd);\n\tif (rd<1) return false; // hangup\n\tswitch(conn->conn_type)\n\t{\n\t\tcase CONN_TYPE_SOCKS:\n\t\t\tswitch(conn->socks_state)\n\t\t\t{\n\t\t\t\tcase S_WAIT_HANDSHAKE:\n\t\t\t\t\tDBGPRINT(\"S_WAIT_HANDSHAKE\\n\");\n\t\t\t\t\tif (buf[0] != 5 && buf[0] != 4) return false; // unknown socks version\n\t\t\t\t\tconn->socks_ver = buf[0];\n\t\t\t\t\tDBGPRINT(\"socks version %u\\n\", conn->socks_ver);\n\t\t\t\t\tif (conn->socks_ver==5)\n\t\t\t\t\t{\n\t\t\t\t\t\ts5_handshake *m = (s5_handshake*)buf;\n\t\t\t\t\t\ts5_handshake_ack ack;\n\t\t\t\t\t\tuint8_t k;\n\n\t\t\t\t\t\tack.ver=5;\n\t\t\t\t\t\tif (!S5_REQ_HANDHSHAKE_VALID(m,rd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 proxy handshake invalid\\n\");\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (k=0;k<m->nmethods;k++) if (m->methods[k]==S5_AUTH_NONE) break;\n\t\t\t\t\t\tif (k>=m->nmethods)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 client wants authentication but we dont support\\n\");\n\t\t\t\t\t\t\tack.method=S5_AUTH_UNACCEPTABLE;\n\t\t\t\t\t\t\twr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDBGPRINT(\"socks5 recv valid handshake\\n\");\n\t\t\t\t\t\tack.method=S5_AUTH_NONE;\n\t\t\t\t\t\twr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT);\n\t\t\t\t\t\tif (wr!=sizeof(ack))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 handshake ack send error. wr=%zd errno=%d\\n\",wr,errno);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDBGPRINT(\"socks5 send handshake ack OK\\n\");\n\t\t\t\t\t\tconn->socks_state=S_WAIT_REQUEST;\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// socks4 does not have separate handshake phase. it starts with connect request\n\t\t\t\t\t\t// ipv6 and domain resolving are not supported\n\t\t\t\t\t\ts4_req *m = (s4_req*)buf;\n\t\t\t\t\t\tif (!S4_REQ_HEADER_VALID(m, rd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks4 request invalid\\n\");\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (m->cmd!=S4_CMD_CONNECT)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// BIND is not supported\n\t\t\t\t\t\t\tDBGPRINT(\"socks4 unsupported command %02X\\n\", m->cmd);\n\t\t\t\t\t\t\tsocks4_send_rep(conn->fd, S4_REP_FAILED);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!S4_REQ_CONNECT_VALID(m, rd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks4 connect request invalid\\n\");\n\t\t\t\t\t\t\tsocks4_send_rep(conn->fd, S4_REP_FAILED);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!m->port)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks4 zero port\\n\");\n\t\t\t\t\t\t\tsocks4_send_rep(conn->fd, S4_REP_FAILED);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (m->ip==htonl(1)) // special ip 0.0.0.1\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVPRINT(\"socks4a protocol not supported\\n\");\n\t\t\t\t\t\t\tsocks4_send_rep(conn->fd, S4_REP_FAILED);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET;\n\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_port = m->port;\n\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_addr.s_addr = m->ip;\n\t\t\t\t\t\treturn proxy_mode_connect_remote(conn, conn_list);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase S_WAIT_REQUEST:\n\t\t\t\t\tDBGPRINT(\"S_WAIT_REQUEST\\n\");\n\t\t\t\t\t{\n\t\t\t\t\t\ts5_req *m = (s5_req*)buf;\n\n\t\t\t\t\t\tif (!S5_REQ_HEADER_VALID(m,rd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 request invalid\\n\");\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (m->cmd!=S5_CMD_CONNECT)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// BIND and UDP are not supported\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 unsupported command %02X\\n\", m->cmd);\n\t\t\t\t\t\t\tsocks5_send_rep(conn->fd,S5_REP_COMMAND_NOT_SUPPORTED);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!S5_REQ_CONNECT_VALID(m,rd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"socks5 connect request invalid\\n\");\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDBGPRINT(\"socks5 recv valid connect request\\n\");\n\t\t\t\t\t\tswitch(m->atyp)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase S5_ATYP_IP4:\n\t\t\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET;\n\t\t\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_port = m->d4.port;\n\t\t\t\t\t\t\t\t((struct sockaddr_in*)&conn->dest)->sin_addr = m->d4.addr;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase S5_ATYP_IP6:\n\t\t\t\t\t\t\t\t((struct sockaddr_in6*)&conn->dest)->sin6_family = AF_INET6;\n\t\t\t\t\t\t\t\t((struct sockaddr_in6*)&conn->dest)->sin6_port = m->d6.port;\n\t\t\t\t\t\t\t\t((struct sockaddr_in6*)&conn->dest)->sin6_addr = m->d6.addr;\n\t\t\t\t\t\t\t\t((struct sockaddr_in6*)&conn->dest)->sin6_flowinfo = 0;\n\t\t\t\t\t\t\t\t((struct sockaddr_in6*)&conn->dest)->sin6_scope_id = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase S5_ATYP_DOM:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tuint16_t port;\n\n\t\t\t\t\t\t\t\t\tif (params.no_resolve)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tVPRINT(\"socks5 hostname resolving disabled\\n\");\n\t\t\t\t\t\t\t\t\t\tsocks5_send_rep(conn->fd,S5_REP_NOT_ALLOWED_BY_RULESET);\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tport=S5_PORT_FROM_DD(m,rd);\n\t\t\t\t\t\t\t\t\tif (!port)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tVPRINT(\"socks5 no port is given\\n\");\n\t\t\t\t\t\t\t\t\t\tsocks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE);\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tm->dd.domport[m->dd.len] = 0;\n\t\t\t\t\t\t\t\t\tDBGPRINT(\"socks5 queue resolve hostname '%s' port '%u'\\n\",m->dd.domport,port);\n\t\t\t\t\t\t\t\t\tconn->socks_ri = resolver_queue(m->dd.domport,port,conn);\n\t\t\t\t\t\t\t\t\tif (!conn->socks_ri)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tVPRINT(\"socks5 could not queue resolve item\\n\");\n\t\t\t\t\t\t\t\t\t\tsocks5_send_rep(conn->fd,S5_REP_GENERAL_FAILURE);\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconn->socks_state=S_WAIT_RESOLVE;\n\t\t\t\t\t\t\t\t\tDBGPRINT(\"S_WAIT_RESOLVE\\n\");\n\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\treturn false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp\n\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn proxy_mode_connect_remote(conn,conn_list);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase S_WAIT_RESOLVE:\n\t\t\t\t\tDBGPRINT(\"socks received message while in S_WAIT_RESOLVE. hanging up\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase S_WAIT_CONNECTION:\n\t\t\t\t\tDBGPRINT(\"socks received message while in S_WAIT_CONNECTION. hanging up\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tDBGPRINT(\"socks received message while in an unexpected connection state\\n\");\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn false;\n}\n\nstatic bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list)\n{\n\ttproxy_conn_t *conn = (tproxy_conn_t *)ri->ptr;\n\n\tif (conn && (conn->state != CONN_CLOSED))\n\t{\n\t\tif (conn->socks_state==S_WAIT_RESOLVE)\n\t\t{\n\t\t\tDBGPRINT(\"resolve_complete %s. getaddrinfo result %d\\n\", ri->dom, ri->ga_res);\n\t\t\tif (ri->ga_res)\n\t\t\t{\n\t\t\t\tsocks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE);\n\t\t\t\treturn false;;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!conn->track.hostname)\n\t\t\t\t{\n\t\t\t\t\tDBGPRINT(\"resolve_complete put hostname : %s\\n\", ri->dom);\n\t\t\t\t\tif (!(conn->track.hostname = strdup(ri->dom)))\n\t\t\t\t\t\tDLOG_ERR(\"dup hostname: out of memory\\n\");\n\t\t\t\t}\n\t\t\t\tsa46copy(&conn->dest, (struct sockaddr *)&ri->ss);\n\t\t\t\treturn proxy_mode_connect_remote(conn,conn_list);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tDLOG_ERR(\"resolve_complete: conn in wrong socks_state !!! (%s)\\n\", ri->dom);\n\t}\n\telse\n\t\tDBGPRINT(\"resolve_complete: orphaned resolve for %s\\n\", ri->dom);\n\n\treturn true;\n}\n\n\nstatic bool in_tamper_out_range(tproxy_conn_t *conn)\n{\n\tif (!conn->track.dp) return true;\n\tbool in_range = \\\n\t\t((conn->track.dp->tamper_start_n ? (conn->tnrd+1) : conn->trd) >= conn->track.dp->tamper_start &&\n\t\t (!conn->track.dp->tamper_cutoff || (conn->track.dp->tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < conn->track.dp->tamper_cutoff));\n\tDBGPRINT(\"tamper_out range check. stream pos %\" PRIu64 \"(n%\" PRIu64 \"). tamper range %s%u-%s%u (%s)\\n\",\n\t\tconn->trd, conn->tnrd+1,\n\t\tconn->track.dp ? conn->track.dp->tamper_start_n ? \"n\" : \"\" : \"?\" , conn->track.dp ? conn->track.dp->tamper_start : 0,\n\t\tconn->track.dp ? conn->track.dp->tamper_cutoff_n ? \"n\" : \"\" : \"?\" , conn->track.dp ? conn->track.dp->tamper_cutoff : 0,\n\t\tin_range ? \"IN RANGE\" : \"OUT OF RANGE\");\n\treturn in_range;\n\t\t \n}\n\nstatic void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags)\n{\n\tif (multisplit_count) *multisplit_count=0;\n\tif (params.tamper)\n\t{\n\t\tif (conn->remote)\n\t\t{\n\t\t\tif (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff)\n\t\t\t\ttamper_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client,segment,segment_buffer_size,segment_size);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (in_tamper_out_range(conn))\n\t\t\t\ttamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,multisplit_pos,multisplit_count,split_flags);\n\t\t}\n\t}\n}\n\n// buffer must have at least one extra byte for OOB\nstatic ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte)\n{\n\tssize_t wr;\n\tif (oob)\n\t{\n\t\tuint8_t oob_save;\n\t\toob_save = buf[len];\n\t\tbuf[len] = oob_byte;\n\t\twr = send_with_ttl(fd, buf, len+1, MSG_OOB, ttl);\n\t\tbuf[len] = oob_save;\n\t\tif (wr<0 && errno==EAGAIN) wr=0;\n\t}\n\telse\n\t\twr = send_with_ttl(fd, buf, len, 0, ttl);\n\treturn wr;\n}\n\n\nstatic unsigned int segfail_count=0;\nstatic time_t segfail_report_time=0;\nstatic void report_segfail(void)\n{\n\ttime_t now = time(NULL);\n\tsegfail_count++;\n\tif (now==segfail_report_time)\n\t\tVPRINT(\"WARNING ! segmentation failed. total fails : %u\\n\", segfail_count);\n\telse\n\t{\n\t\tDLOG_ERR(\"WARNING ! segmentation failed. total fails : %u\\n\", segfail_count);\n\t\tsegfail_report_time = now;\n\t}\n}\n\n#define RD_BLOCK_SIZE 65536\n#define MAX_WASTE (1024*1024)\n\nstatic bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt)\n{\n\tint numbytes;\n\tssize_t rd = 0, wr = 0;\n\tsize_t bs;\n\n\n\tDBGPRINT(\"+handle_epoll\\n\");\n\n\tif (!conn_in_tcp_mode(conn))\n\t{\n\t\tif (!(evt & EPOLLIN))\n\t\t\treturn true; // nothing to read\n\t\treturn handle_proxy_mode(conn,conn_list);\n\t}\n\n\tif (!handle_unsent(conn))\n\t\treturn false; // error\n\tif (!conn_partner_alive(conn) && !conn_has_unsent(conn))\n\t\treturn false; // when no partner, we only waste read and send unsent\n\n\tif (!(evt & EPOLLIN))\n\t\treturn true; // nothing to read\n\n\tif (!conn_partner_alive(conn))\n\t{\n\t\t// throw it to a black hole\n\t\tuint8_t waste[65070];\n\t\tuint64_t trd=0;\n\n\t\twhile((rd=recv(conn->fd, waste, sizeof(waste), MSG_DONTWAIT))>0 && trd<MAX_WASTE)\n\t\t{\n\t\t\ttrd+=rd;\n\t\t\tconn->trd+=rd;\n\t\t}\n\t\tDBGPRINT(\"wasted recv=%zd all_rd=%\" PRIu64 \" err=%d\\n\",rd,trd,errno);\n\t\treturn true;\n\t}\n\n\t// do not receive new until old is sent\n\tif (conn_has_unsent(conn->partner))\n\t\treturn true;\n\t\t\n\tbool oom=false;\n\n\tnumbytes=conn_bytes_unread(conn);\n\tDBGPRINT(\"numbytes=%d\\n\",numbytes);\n\tif (numbytes>0)\n\t{\n\t\tDBGPRINT(\"%s leg fd=%d stream pos : %\" PRIu64 \"(n%\" PRIu64 \")/%\" PRIu64 \"\\n\", conn->remote ? \"remote\" : \"local\", conn->fd, conn->trd,conn->tnrd+1,conn->twr);\n#ifdef SPLICE_PRESENT\n\t\tif (!params.nosplice && (!params.tamper || (conn->remote && conn->partner->track.bTamperInCutoff) || (!conn->remote && !in_tamper_out_range(conn))))\n\t\t{\n\t\t\t// incoming data from remote leg we splice without touching\n\t\t\t// pipe is in the local leg, so its in conn->partner->splice_pipe\n\t\t\t// if we dont tamper - splice both legs\n\n\t\t\trd = splice(conn->fd, NULL, conn->partner->splice_pipe[1], NULL, SPLICE_LEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);\n\t\t\tDBGPRINT(\"splice fd=%d remote=%d len=%d rd=%zd err=%d\\n\",conn->fd,conn->remote,SPLICE_LEN,rd,errno);\n\t\t\tif (rd<0 && errno==EAGAIN) rd=0;\n\t\t\tif (rd>0)\n\t\t\t{\n\t\t\t\tconn->tnrd++;\n\t\t\t\tconn->trd += rd;\n\t\t\t\tconn->partner->wr_unsent += rd;\n\t\t\t\twr = splice(conn->partner->splice_pipe[0], NULL, conn->partner->fd, NULL, conn->partner->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);\n\t\t\t\tDBGPRINT(\"splice fd=%d remote=%d wr=%zd err=%d\\n\",conn->partner->fd,conn->partner->remote,wr,errno);\n\t\t\t\tif (wr<0 && errno==EAGAIN) wr=0;\n\t\t\t\tif (wr>0)\n\t\t\t\t{\n\t\t\t\t\tconn->partner->wr_unsent -= wr;\n\t\t\t\t\tconn->partner->twr += wr;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n#endif\n\t\t{\n\t\t\t// incoming data from local leg\n\t\t\tuint8_t buf[RD_BLOCK_SIZE + 6];\n\n\t\t\trd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT);\n\t\t\tDBGPRINT(\"recv fd=%d rd=%zd err=%d\\n\",conn->fd, rd,errno);\n\t\t\tif (rd<0 && errno==EAGAIN) rd=0;\n\t\t\tif (rd>0)\n\t\t\t{\n\t\t\t\tsize_t multisplit_pos[MAX_SPLITS];\n\t\t\t\tint multisplit_count;\n\n\t\t\t\tuint8_t split_flags;\n\n\t\t\t\tbs = rd;\n\n\t\t\t\t// tamper needs to know stream position of the block start\n\t\t\t\ttamper(conn, buf, sizeof(buf), &bs, multisplit_pos, &multisplit_count, &split_flags);\n\t\t\t\t// increase after tamper\n\t\t\t\tconn->tnrd++;\n\t\t\t\tconn->trd+=rd;\n\n\t\t\t\tif (multisplit_count)\n\t\t\t\t{\n\t\t\t\t\tssize_t from,to,len;\n\t\t\t\t\tint i;\n\t\t\t\t\tbool bApplyDisorder, bApplyOOB;\n\t\t\t\t\tfor (i=0,from=0;i<=multisplit_count;i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tto = i==multisplit_count ? bs : multisplit_pos[i];\n\n\t\t\t\t\t\tbApplyDisorder = !(i & 1) && i<multisplit_count && (split_flags & SPLIT_FLAG_DISORDER);\n\t\t\t\t\t\tbApplyOOB = i==0 && (split_flags & SPLIT_FLAG_OOB);\n\t\t\t\t\t\tlen = to-from;\n#ifdef __linux__\n\t\t\t\t\t\tif (params.fix_seg_avail)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (params.fix_seg)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tunsigned int wasted;\n\t\t\t\t\t\t\t\tbool bWaitOK = socket_wait_notsent(conn->partner->fd, params.fix_seg, &wasted);\n\t\t\t\t\t\t\t\tif (wasted)\n\t\t\t\t\t\t\t\t\tVPRINT(\"WARNING ! wasted %u ms to fix segmenation\\n\", wasted);\n\t\t\t\t\t\t\t\tif (!bWaitOK)\n\t\t\t\t\t\t\t\t\treport_segfail();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (socket_has_notsent(conn->partner->fd))\n\t\t\t\t\t\t\t\t\treport_segfail();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n#endif\n\t\t\t\t\t\tVPRINT(\"Sending multisplit part %d %zd-%zd (len %zd)%s%s : \", i+1, from, to, len, bApplyDisorder ? \" with disorder\" : \"\", bApplyOOB ? \" with OOB\" : \"\");\n\t\t\t\t\t\tpacket_debug(buf+from,len);\n\t\t\t\t\t\twr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0);\n\t\t\t\t\t\tif (wr<0) break;\n\t\t\t\t\t\tconn->partner->twr += wr;\n\t\t\t\t\t\tif (wr<len)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfrom+=wr;\n\t\t\t\t\t\t\tVPRINT(\"Cannot send part %d immediately. only %zd bytes were sent (%zd left in segment). cancelling split.\\n\", i+1, wr, bs-from);\n\t\t\t\t\t\t\twr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf+from, bs-from, 0, 0);\n\t\t\t\t\t\t\tif (wr>0) conn->partner->twr += wr;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfrom = to;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\twr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs, 0, 0);\n\t\t\t\t\tDBGPRINT(\"send_or_buffer(3) fd=%d wr=%zd err=%d\\n\",conn->partner->fd,wr,errno);\n\t\t\t\t\tif (wr>0) conn->partner->twr += wr;\n\t\t\t\t}\n\t\t\t\tif (wr<0 && errno==ENOMEM) oom=true;\n\t\t\t}\n\t\t}\n\n\t\tif (!epoll_set_flow_pair(conn))\n\t\t\treturn false;\n\t}\n\t\n\tDBGPRINT(\"-handle_epoll rd=%zd wr=%zd\\n\",rd,wr);\n\tif (oom) DBGPRINT(\"handle_epoll: OUT_OF_MEMORY\\n\");\n\n\t// do not fail if partner fails.\n\t// if partner fails there will be another epoll event with EPOLLHUP or EPOLLERR\n\treturn rd>=0 && !oom;\n}\n\nstatic bool remove_closed_connections(int efd, struct tailhead *close_list)\n{\n\ttproxy_conn_t *conn = NULL;\n\tbool bRemoved = false;\n\n\twhile ((conn = TAILQ_FIRST(close_list)))\n\t{\n\t\tTAILQ_REMOVE(close_list, conn, conn_ptrs);\n\n\t\tepoll_del(conn);\n\t\tVPRINT(\"Socket fd=%d (partner_fd=%d, remote=%d) closed, connection removed. total_read=%\" PRIu64 \" total_write=%\" PRIu64 \" event_count=%u\\n\",\n\t\t\tconn->fd, conn->partner ? conn->partner->fd : 0, conn->remote, conn->trd, conn->twr, conn->event_count);\n\t\tif (conn->remote) legs_remote--; else legs_local--;\n\t\tfree_conn(conn);\n\t\tbRemoved = true;\n\t}\n\treturn bRemoved;\n}\n\n// move to close list connection and its partner\nstatic void close_tcp_conn(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn)\n{\n\tif (conn->state != CONN_CLOSED)\n\t{\n\t\tconn->state = CONN_CLOSED;\n\t\tTAILQ_REMOVE(conn_list, conn, conn_ptrs);\n\t\tTAILQ_INSERT_TAIL(close_list, conn, conn_ptrs);\n\t}\n}\n\n\nstatic bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number)\n{\n\tif (conn_partner_alive(conn))\n\t{\n\t\tint numbytes=conn_bytes_unread(conn);\n\t\tDBGPRINT(\"read_all_and_buffer(%d) numbytes=%d\\n\",buffer_number,numbytes);\n\t\tif (numbytes>0)\n\t\t{\n\t\t\tif (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 6, 0, 0))\n\t\t\t{\n\t\t\t\tssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT);\n\t\t\t\tif (rd>0)\n\t\t\t\t{\n\t\t\t\t\tconn->trd+=rd;\n\t\t\t\t\tconn->partner->wr_buf[buffer_number].len = rd;\n\n\t\t\t\t\tconn->partner->bFlowOut = true;\n\n\t\t\t\t\ttamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+6, &conn->partner->wr_buf[buffer_number].len, NULL, NULL, NULL);\n\n\t\t\t\t\tif (epoll_update_flow(conn->partner))\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\tsend_buffer_free(conn->partner->wr_buf+buffer_number);\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\n\nstatic bool conn_timed_out(tproxy_conn_t *conn)\n{\n\tif (conn->orphan_since && conn->state==CONN_UNAVAILABLE)\n\t{\n\t\ttime_t timediff = time(NULL) - conn->orphan_since;\n\t\treturn timediff>=params.max_orphan_time;\n\t}\n\telse\n\t\treturn false;\n}\nstatic void conn_close_timed_out(struct tailhead *conn_list, struct tailhead *close_list)\n{\n\ttproxy_conn_t *c,*cnext = NULL;\n\n\tDBGPRINT(\"conn_close_timed_out\\n\");\n\n\tc = TAILQ_FIRST(conn_list);\n\twhile(c)\n\t{\n\t\tcnext = TAILQ_NEXT(c,conn_ptrs);\n\t\tif (conn_timed_out(c))\n\t\t{\n\t\t\tDBGPRINT(\"closing timed out connection: fd=%d remote=%d\\n\",c->fd,c->remote);\n\t\t\tclose_tcp_conn(conn_list,close_list,c);\n\t\t}\n\t\tc = cnext;\n\t}\n}\n\nstatic void conn_close_both(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn)\n{\n\tif (conn_partner_alive(conn)) close_tcp_conn(conn_list,close_list,conn->partner);\n\tclose_tcp_conn(conn_list,close_list,conn);\n}\nstatic void conn_close_with_partner_check(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn)\n{\n\tclose_tcp_conn(conn_list,close_list,conn);\n\tif (conn_partner_alive(conn))\n\t{ \n\t\tif (!conn_has_unsent(conn->partner))\n\t\t\tclose_tcp_conn(conn_list,close_list,conn->partner);\n\t\telse if (conn->partner->remote && conn->partner->state==CONN_UNAVAILABLE && params.max_orphan_time)\n\t\t\t// time out only remote legs that are not connected yet\n\t\t\tconn->partner->orphan_since = time(NULL);\n\t}\n}\n\n\nstatic bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list, int fd)\n{\n\tssize_t rd;\n\tstruct resolve_item *ri;\n\tbool b;\n\n\trd = read(fd,&ri,sizeof(void*));\n\tif (rd<0)\n\t{\n\t\tDLOG_PERROR(\"resolve_pipe read\");\n\t\treturn false;\n\t}\n\telse if (rd!=sizeof(void*))\n\t{\n\t\t// partial pointer read is FATAL. in any case it will cause pointer corruption and coredump\n\t\tDLOG_ERR(\"resolve_pipe not full read %zd\\n\",rd);\n\t\texit(1000);\n\t}\n\tb = resolve_complete(ri, conn_list);\n\t*conn = (tproxy_conn_t *)ri->ptr;\n\tif (*conn) (*conn)->socks_ri = NULL;\n\tfree(ri);\n\treturn b;\n}\n\nint event_loop(const int *listen_fd, size_t listen_fd_ct)\n{\n\tint retval = 0, num_events = 0;\n\tint tmp_fd = 0; //Used to temporarily hold the accepted file descriptor\n\ttproxy_conn_t *conn = NULL;\n\tint efd=0, i;\n\tstruct epoll_event ev, events[MAX_EPOLL_EVENTS];\n\tstruct tailhead conn_list, close_list;\n\ttime_t tm,last_timeout_check=0;\n\ttproxy_conn_t *listen_conn = NULL;\n\tsize_t sct;\n\tstruct sockaddr_storage accept_sa;\n\tsocklen_t accept_salen;\n\tint resolve_pipe[2];\n\n\tif (!listen_fd_ct) return -1;\n\t                                         \t\n\tresolve_pipe[0]=resolve_pipe[1]=0;\n\n\tlegs_local = legs_remote = 0;\n\t//Initialize queue (remember that TAILQ_HEAD just defines the struct)\n\tTAILQ_INIT(&conn_list);\n\tTAILQ_INIT(&close_list);\n\n\tif ((efd = epoll_create(1)) == -1) {\n\t\tDLOG_PERROR(\"epoll_create\");\n\t\treturn -1;\n\t}\n\n\tif (!(listen_conn=calloc(listen_fd_ct,sizeof(*listen_conn))))\n\t{\n\t\tDLOG_PERROR(\"calloc listen_conn\");\n\t\treturn -1;\n\t}\n\t\n\t//Start monitoring listen sockets\n\tmemset(&ev, 0, sizeof(ev));\n\tev.events = EPOLLIN;\n\tfor(sct=0;sct<listen_fd_ct;sct++)\n\t{\n\t\tlisten_conn[sct].listener = true;\n\t\tlisten_conn[sct].fd = listen_fd[sct];\n\t\tev.data.ptr = listen_conn + sct;\n\t\tif (epoll_ctl(efd, EPOLL_CTL_ADD, listen_conn[sct].fd, &ev) == -1) {\n\t\t\tDLOG_PERROR(\"epoll_ctl (listen socket)\");\n\t\t\tretval = -1;\n\t\t\tgoto ex;\n\t\t}\n\t}\n\tif ((params.proxy_type==CONN_TYPE_SOCKS) && !params.no_resolve)\n\t{\n\t\tif (pipe(resolve_pipe)==-1)\n\t\t{\n\t\t\tDLOG_PERROR(\"pipe (resolve_pipe)\");\n\t\t\tretval = -1;\n\t\t\tgoto ex;\n\t\t}\n\t\tif (fcntl(resolve_pipe[0], F_SETFL, O_NONBLOCK) < 0)\n\t\t{\n\t\t\tDLOG_PERROR(\"resolve_pipe set O_NONBLOCK\");\n\t\t\tretval = -1;\n\t\t\tgoto ex;\n\t\t}\n\t\tev.data.ptr = NULL;\n\t\tif (epoll_ctl(efd, EPOLL_CTL_ADD, resolve_pipe[0], &ev) == -1) {\n\t\t\tDLOG_PERROR(\"epoll_ctl (listen socket)\");\n\t\t\tretval = -1;\n\t\t\tgoto ex;\n\t\t}\n\t\tif (!resolver_init(params.resolver_threads,resolve_pipe[1]))\n\t\t{\n\t\t\tDLOG_ERR(\"could not initialize multithreaded resolver\\n\");\n\t\t\tretval = -1;\n\t\t\tgoto ex;\n\t\t}\n\t\tVPRINT(\"initialized multi threaded resolver with %d threads\\n\",resolver_thread_count());\n\t}\n\n\tnotify_ready();\n\n\tfor(;;)\n\t{\n\t\tReloadCheck();\n\n\t\tDBGPRINT(\"epoll_wait\\n\");\n\n\t\tif ((num_events = epoll_wait(efd, events, MAX_EPOLL_EVENTS, -1)) == -1)\n\t\t{\n\t\t\tif (errno == EINTR) continue; // system call was interrupted\n\t\t\tDLOG_PERROR(\"epoll_wait\");\n\t\t\tretval = -1;\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (i = 0; i < num_events; i++)\n\t\t{\n\t\t\tconn = (tproxy_conn_t*)events[i].data.ptr;\n\t\t\tif (!conn)\n\t\t\t{\n\t\t\t\tDBGPRINT(\"\\nEVENT mask %08X resolve_pipe\\n\",events[i].events);\n\t\t\t\tif (events[i].events & EPOLLIN)\n\t\t\t\t{\n\t\t\t\t\tDBGPRINT(\"EPOLLIN\\n\");\n\t\t\t\t\tif (!handle_resolve_pipe(&conn, &conn_list, resolve_pipe[0]))\n\t\t\t\t\t{\n\t\t\t\t\t\tDBGPRINT(\"handle_resolve_pipe false\\n\");\n\t\t\t\t\t\tif (conn) close_tcp_conn(&conn_list,&close_list,conn);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconn->event_count++;\n\t\t\tif (conn->listener)\n\t\t\t{\n\t\t\t\tDBGPRINT(\"\\nEVENT mask %08X fd=%d accept\\n\",events[i].events,conn->fd);\n\n\t\t\t\taccept_salen = sizeof(accept_sa);\n\t\t\t\t//Accept new connection\n#if defined (__APPLE__)\n\t\t\t\t// macos does not have accept4()\n\t\t\t\ttmp_fd = accept(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen);\n#else\n\t\t\t\ttmp_fd = accept4(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen, SOCK_NONBLOCK);\n#endif\n\t\t\t\tif (tmp_fd < 0)\n\t\t\t\t{\n\t\t\t\t\tDLOG_PERROR(\"Failed to accept connection\");\n\t\t\t\t}\n\t\t\t\telse if (legs_local >= params.maxconn) // each connection has 2 legs - local and remote\n\t\t\t\t{\n\t\t\t\t\tclose(tmp_fd);\n\t\t\t\t\tVPRINT(\"Too many local legs : %d\\n\", legs_local);\n\t\t\t\t}\n#if defined (__APPLE__)\n\t\t\t\t// separate fcntl call to comply with macos\n\t\t\t\telse if (fcntl(tmp_fd, F_SETFL, O_NONBLOCK) < 0)\n\t\t\t\t{\n\t\t\t\t\tDLOG_PERROR(\"socket set O_NONBLOCK (accept)\");\n\t\t\t\t\tclose(tmp_fd);\n\t\t\t\t}\n#endif\n\t\t\t\telse if (!(conn=add_tcp_connection(efd, &conn_list, tmp_fd, (struct sockaddr*)&accept_sa, params.port, params.proxy_type)))\n\t\t\t\t{\n\t\t\t\t\t// add_tcp_connection closes fd in case of failure\n\t\t\t\t\tVPRINT(\"Failed to add connection\\n\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\t\n\t\t\t\t\tprint_legs();\n\n\t\t\t\t\tif (params.debug>=1)\n\t\t\t\t\t{\n\t\t\t\t\t\tchar ip_port[48];\n\t\t\t\t\t\tntop46_port((struct sockaddr*)&conn->client,ip_port,sizeof(ip_port));\n\t\t\t\t\t\tVPRINT(\"Socket fd=%d (local) connected from %s\\n\", conn->fd, ip_port);\n\t\t\t\t\t}\n\t\t\t\t\tset_user_timeout(conn->fd, params.tcp_user_timeout_local);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDBGPRINT(\"\\nEVENT mask %08X fd=%d remote=%d fd_partner=%d\\n\",events[i].events,conn->fd,conn->remote,conn_partner_alive(conn) ? conn->partner->fd : 0);\n\n\t\t\t\tif (conn->state != CONN_CLOSED)\n\t\t\t\t{\n\t\t\t\t\tif (events[i].events & (EPOLLHUP|EPOLLERR))\n\t\t\t\t\t{\n\t\t\t\t\t\tint errn = get_so_error(conn->fd);\n\t\t\t\t\t\tconst char *se;\n\t\t\t\t\t\tswitch (events[i].events & (EPOLLHUP|EPOLLERR))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase EPOLLERR: se=\"EPOLLERR\"; break;\n\t\t\t\t\t\t\tcase EPOLLHUP: se=\"EPOLLHUP\"; break;\n\t\t\t\t\t\t\tcase EPOLLHUP|EPOLLERR: se=\"EPOLLERR EPOLLHUP\"; break;\n\t\t\t\t\t\t\tdefault: se=NULL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tVPRINT(\"Socket fd=%d (partner_fd=%d, remote=%d) %s so_error=%d (%s)\\n\",conn->fd,conn->partner ? conn->partner->fd : 0,conn->remote,se,errn,strerror(errn));\n\t\t\t\t\t\tproxy_remote_conn_ack(conn,errn);\n\t\t\t\t\t\tread_all_and_buffer(conn,3);\n\t\t\t\t\t\tif (errn==ECONNRESET && conn_partner_alive(conn))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (conn->remote && params.tamper) rst_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client);\n\n\t\t\t\t\t\t\tstruct linger lin;\n\t\t\t\t\t\t\tlin.l_onoff=1;\n\t\t\t\t\t\t\tlin.l_linger=0;\n\t\t\t\t\t\t\tDBGPRINT(\"setting LINGER=0 to partner to force mirrored RST close\\n\");\n\t\t\t\t\t\t\tif (setsockopt(conn->partner->fd,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))<0)\n\t\t\t\t\t\t\t\tDLOG_PERROR(\"setsockopt (SO_LINGER)\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconn_close_with_partner_check(&conn_list,&close_list,conn);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (events[i].events & EPOLLOUT)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!check_connection_attempt(conn, efd))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVPRINT(\"Connection attempt failed for fd=%d\\n\", conn->fd);\n\t\t\t\t\t\t\tconn_close_both(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (events[i].events & EPOLLRDHUP)\n\t\t\t\t\t{\n\t\t\t\t\t\tDBGPRINT(\"EPOLLRDHUP\\n\");\n\t\t\t\t\t\tread_all_and_buffer(conn,2);\n\t\t\t\t\t\tif (!conn->remote && params.tamper) hup_out(&conn->track,(struct sockaddr*)&conn->client);\n\n\t\t\t\t\t\tconn->state = CONN_RDHUP; // only writes. do not receive RDHUP anymore\n\t\t\t\t\t\tif (conn_has_unsent(conn))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"conn fd=%d has unsent\\n\", conn->fd);\n\t\t\t\t\t\t\tepoll_set_flow(conn,false,true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"conn fd=%d has no unsent\\n\", conn->fd);\n\t\t\t\t\t\t\tconn->bFlowIn = false;\n\t\t\t\t\t\t\tepoll_update_flow(conn);\n\t\t\t\t\t\t\tif (conn_partner_alive(conn))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (conn_has_unsent(conn->partner))\n\t\t\t\t\t\t\t\t\tDBGPRINT(\"partner has unset. partner shutdown delayed.\\n\");\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDBGPRINT(\"partner has no unsent. shutting down partner.\\n\");\n\t\t\t\t\t\t\t\t\tif (!conn_shutdown(conn->partner))\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tDBGPRINT(\"emergency connection close due to failed shutdown\\n\");\n\t\t\t\t\t\t\t\t\t\tconn_close_with_partner_check(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (conn->partner->state==CONN_RDHUP)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tDBGPRINT(\"both partners are in RDHUP state and have no unsent. closing.\\n\");\n\t\t\t\t\t\t\t\t\t\tconn_close_with_partner_check(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDBGPRINT(\"partner is absent or not alive. closing.\\n\");\n\t\t\t\t\t\t\t\tclose_tcp_conn(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (events[i].events & (EPOLLIN|EPOLLOUT))\n\t\t\t\t\t{\n\t\t\t\t\t\tconst char *se;\n\t\t\t\t\t\tswitch (events[i].events & (EPOLLIN|EPOLLOUT))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase EPOLLIN: se=\"EPOLLIN\"; break;\n\t\t\t\t\t\t\tcase EPOLLOUT: se=\"EPOLLOUT\"; break;\n\t\t\t\t\t\t\tcase EPOLLIN|EPOLLOUT: se=\"EPOLLIN EPOLLOUT\"; break;\n\t\t\t\t\t\t\tdefault: se=NULL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (se) DBGPRINT(\"%s\\n\",se);\n\t\t\t\t\t\t// will not receive this until successful check_connection_attempt()\n\t\t\t\t\t\tif (!handle_epoll(conn, &conn_list, events[i].events))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"handle_epoll false\\n\");\n\t\t\t\t\t\t\tconn_close_with_partner_check(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((conn->state == CONN_RDHUP) && conn_partner_alive(conn) && !conn->partner->bShutdown && !conn_has_unsent(conn))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDBGPRINT(\"conn fd=%d has no unsent. shutting down partner.\\n\", conn->fd);\n\t\t\t\t\t\t\tif (!conn_shutdown(conn->partner))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDBGPRINT(\"emergency connection close due to failed shutdown\\n\");\n\t\t\t\t\t\t\t\tconn_close_with_partner_check(&conn_list,&close_list,conn);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t\ttm = time(NULL);\n\t\tif (last_timeout_check!=tm)\n\t\t{\n\t\t\t// limit whole list lookups to once per second\n\t\t\tlast_timeout_check=tm;\n\t\t\tconn_close_timed_out(&conn_list,&close_list);\n\t\t}\n\t\tif (remove_closed_connections(efd, &close_list))\n\t\t{\n\t\t\t// at least one leg was removed. recount legs\n\t\t\tprint_legs();\n\t\t}\n\t}\n\nex:\n\tif (efd) close(efd);\n\tfree(listen_conn);\n\tresolver_deinit();\n\tif (resolve_pipe[0]) close(resolve_pipe[0]);\n\tif (resolve_pipe[1]) close(resolve_pipe[1]);\n\treturn retval;\n}\n"
  },
  {
    "path": "tpws/tpws_conn.h",
    "content": "#pragma once\n\n#include <stdbool.h>\n#include <inttypes.h>\n#include <sys/queue.h>\n#include <time.h>\n#include \"tamper.h\"\n#include \"params.h\"\n#include \"resolver.h\"\n\n#define BACKLOG 10\n#define MAX_EPOLL_EVENTS 64\n#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT\n#define SPLICE_LEN 65536\n#define DEFAULT_MAX_CONN\t512\n#define DEFAULT_MAX_ORPHAN_TIME\t5\n#define DEFAULT_TCP_USER_TIMEOUT_LOCAL\t10\n#define DEFAULT_TCP_USER_TIMEOUT_REMOTE\t20\n\nint event_loop(const int *listen_fd, size_t listen_fd_ct);\n\n//Three different states of a connection\nenum{\n\tCONN_UNAVAILABLE=0, // connecting\n\tCONN_AVAILABLE, // operational\n\tCONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked\n\tCONN_CLOSED // will be deleted soon\n};\ntypedef uint8_t conn_state_t;\n\n// data in a send_buffer can be sent in several stages\n// pos indicates size of already sent data\n// when pos==len its time to free buffer\nstruct send_buffer\n{\n\tuint8_t *data;\n\tsize_t len,pos;\n\tint ttl, flags;\n};\ntypedef struct send_buffer send_buffer_t;\n\nenum{\n\tCONN_TYPE_TRANSPARENT=0,\n\tCONN_TYPE_SOCKS\n};\ntypedef uint8_t conn_type_t;\n\nstruct tproxy_conn\n{\n\tbool listener; // true - listening socket. false = connecion socket\n\tbool remote; // false - accepted, true - connected\n\tint efd; // epoll fd\n\tint fd;\n\tint splice_pipe[2];\n\tconn_state_t state;\n\tconn_type_t conn_type;\n\tsockaddr_in46 client, dest; // ip:port of client, ip:port of target\n\n\tstruct tproxy_conn *partner; // other leg\n\ttime_t orphan_since;\n\n\t// socks5 state machine\n\tenum {\n\t\tS_WAIT_HANDSHAKE=0,\n\t\tS_WAIT_REQUEST,\n\t\tS_WAIT_RESOLVE,\n\t\tS_WAIT_CONNECTION,\n\t\tS_TCP\n\t} socks_state;\n\tuint8_t socks_ver;\n\tstruct resolve_item *socks_ri;\n\n\t// these value are used in flow control. we do not use ET (edge triggered) polling\n\t// if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time\n\tbool bFlowIn,bFlowOut, bShutdown, bFlowInPrev,bFlowOutPrev, bPrevRdhup;\n\n\t// total read,write\n\tuint64_t trd,twr, tnrd;\n\t// number of epoll_wait events\n\tunsigned int event_count;\n\n\t// connection is either spliced or send/recv\n\t// spliced connection have pipe buffering but also can have send_buffer's\n\t// pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1\n\t// send/recv connection do not have pipe and wr_unsent is meaningless, always 0\n\tssize_t wr_unsent; // unsent bytes in the pipe\n\t// buffer 0 : send before split_pos\n\t// buffer 1 : send after split_pos\n\t// buffer 2 : after RDHUP read all and buffer to the partner\n\t// buffer 3 : after HUP read all and buffer to the partner\n\t// (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL)\n\t// all buffers are sent strictly from 0 to countof(wr_buf)-1\n\t// buffer cannot be sent if there is unsent data in a lower buffer\n\tstruct send_buffer wr_buf[4];\n\n\tt_ctrack track;\n\n\t//Create the struct which contains ptrs to next/prev element\n\tTAILQ_ENTRY(tproxy_conn) conn_ptrs;\n};\ntypedef struct tproxy_conn tproxy_conn_t;\n\n//Define the struct tailhead (code in sys/queue.h is quite intuitive)\n//Use tail queue for efficient delete\nTAILQ_HEAD(tailhead, tproxy_conn);\n\n\nbool set_socket_buffers(int fd, int rcvbuf, int sndbuf);\n"
  },
  {
    "path": "tpws/uthash.h",
    "content": "/*\nCopyright (c) 2003-2021, Troy D. Hanson     http://troydhanson.github.io/uthash/\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER\nOR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#ifndef UTHASH_H\n#define UTHASH_H\n\n#define UTHASH_VERSION 2.3.0\n\n#include <string.h>   /* memcmp, memset, strlen */\n#include <stddef.h>   /* ptrdiff_t */\n#include <stdlib.h>   /* exit */\n\n#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT\n/* This codepath is provided for backward compatibility, but I plan to remove it. */\n#warning \"HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead\"\ntypedef unsigned int uint32_t;\ntypedef unsigned char uint8_t;\n#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT\n#else\n#include <stdint.h>   /* uint8_t, uint32_t */\n#endif\n\n/* These macros use decltype or the earlier __typeof GNU extension.\n   As decltype is only available in newer compilers (VS2010 or gcc 4.3+\n   when compiling c++ source) this code uses whatever method is needed\n   or, for VS2008 where neither is available, uses casting workarounds. */\n#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)\n#if defined(_MSC_VER)   /* MS compiler */\n#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */\n#define DECLTYPE(x) (decltype(x))\n#else                   /* VS2008 or older (or VS2010 in C mode) */\n#define NO_DECLTYPE\n#endif\n#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)\n#define NO_DECLTYPE\n#else                   /* GNU, Sun and other compilers */\n#define DECLTYPE(x) (__typeof(x))\n#endif\n#endif\n\n#ifdef NO_DECLTYPE\n#define DECLTYPE(x)\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  char **_da_dst = (char**)(&(dst));                                             \\\n  *_da_dst = (char*)(src);                                                       \\\n} while (0)\n#else\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  (dst) = DECLTYPE(dst)(src);                                                    \\\n} while (0)\n#endif\n\n#ifndef uthash_malloc\n#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */\n#endif\n#ifndef uthash_free\n#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */\n#endif\n#ifndef uthash_bzero\n#define uthash_bzero(a,n) memset(a,'\\0',n)\n#endif\n#ifndef uthash_strlen\n#define uthash_strlen(s) strlen(s)\n#endif\n\n#ifndef HASH_FUNCTION\n#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)\n#endif\n\n#ifndef HASH_KEYCMP\n#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)\n#endif\n\n#ifndef uthash_noexpand_fyi\n#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */\n#endif\n#ifndef uthash_expand_fyi\n#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */\n#endif\n\n#ifndef HASH_NONFATAL_OOM\n#define HASH_NONFATAL_OOM 0\n#endif\n\n#if HASH_NONFATAL_OOM\n/* malloc failures can be recovered from */\n\n#ifndef uthash_nonfatal_oom\n#define uthash_nonfatal_oom(obj) do {} while (0)    /* non-fatal OOM error */\n#endif\n\n#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)\n#define IF_HASH_NONFATAL_OOM(x) x\n\n#else\n/* malloc failures result in lost memory, hash tables are unusable */\n\n#ifndef uthash_fatal\n#define uthash_fatal(msg) exit(-1)        /* fatal OOM error */\n#endif\n\n#define HASH_RECORD_OOM(oomed) uthash_fatal(\"out of memory\")\n#define IF_HASH_NONFATAL_OOM(x)\n\n#endif\n\n/* initial number of buckets */\n#define HASH_INITIAL_NUM_BUCKETS 32U     /* initial number of buckets        */\n#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */\n#define HASH_BKT_CAPACITY_THRESH 10U     /* expand when bucket count reaches */\n\n/* calculate the element whose hash handle address is hhp */\n#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))\n/* calculate the hash handle from element address elp */\n#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))\n\n#define HASH_ROLLBACK_BKT(hh, head, itemptrhh)                                   \\\ndo {                                                                             \\\n  struct UT_hash_handle *_hd_hh_item = (itemptrhh);                              \\\n  unsigned _hd_bkt;                                                              \\\n  HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);         \\\n  (head)->hh.tbl->buckets[_hd_bkt].count++;                                      \\\n  _hd_hh_item->hh_next = NULL;                                                   \\\n  _hd_hh_item->hh_prev = NULL;                                                   \\\n} while (0)\n\n#define HASH_VALUE(keyptr,keylen,hashv)                                          \\\ndo {                                                                             \\\n  HASH_FUNCTION(keyptr, keylen, hashv);                                          \\\n} while (0)\n\n#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \\\ndo {                                                                             \\\n  (out) = NULL;                                                                  \\\n  if (head) {                                                                    \\\n    unsigned _hf_bkt;                                                            \\\n    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \\\n    if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \\\n      HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \\\ndo {                                                                             \\\n  (out) = NULL;                                                                  \\\n  if (head) {                                                                    \\\n    unsigned _hf_hashv;                                                          \\\n    HASH_VALUE(keyptr, keylen, _hf_hashv);                                       \\\n    HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);             \\\n  }                                                                              \\\n} while (0)\n\n#ifdef HASH_BLOOM\n#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)\n#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)\n#define HASH_BLOOM_MAKE(tbl,oomed)                                               \\\ndo {                                                                             \\\n  (tbl)->bloom_nbits = HASH_BLOOM;                                               \\\n  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \\\n  if (!(tbl)->bloom_bv) {                                                        \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                           \\\n    (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                     \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_BLOOM_FREE(tbl)                                                     \\\ndo {                                                                             \\\n  uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                              \\\n} while (0)\n\n#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))\n#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))\n\n#define HASH_BLOOM_ADD(tbl,hashv)                                                \\\n  HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#define HASH_BLOOM_TEST(tbl,hashv)                                               \\\n  HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#else\n#define HASH_BLOOM_MAKE(tbl,oomed)\n#define HASH_BLOOM_FREE(tbl)\n#define HASH_BLOOM_ADD(tbl,hashv)\n#define HASH_BLOOM_TEST(tbl,hashv) (1)\n#define HASH_BLOOM_BYTELEN 0U\n#endif\n\n#define HASH_MAKE_TABLE(hh,head,oomed)                                           \\\ndo {                                                                             \\\n  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table));         \\\n  if (!(head)->hh.tbl) {                                                         \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table));                         \\\n    (head)->hh.tbl->tail = &((head)->hh);                                        \\\n    (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                      \\\n    (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;            \\\n    (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                  \\\n    (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                    \\\n        HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));               \\\n    (head)->hh.tbl->signature = HASH_SIGNATURE;                                  \\\n    if (!(head)->hh.tbl->buckets) {                                              \\\n      HASH_RECORD_OOM(oomed);                                                    \\\n      uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                        \\\n    } else {                                                                     \\\n      uthash_bzero((head)->hh.tbl->buckets,                                      \\\n          HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));             \\\n      HASH_BLOOM_MAKE((head)->hh.tbl, oomed);                                    \\\n      IF_HASH_NONFATAL_OOM(                                                      \\\n        if (oomed) {                                                             \\\n          uthash_free((head)->hh.tbl->buckets,                                   \\\n              HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));           \\\n          uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                    \\\n        }                                                                        \\\n      )                                                                          \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \\\n} while (0)\n\n#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \\\n} while (0)\n\n#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \\\n} while (0)\n\n#define HASH_APPEND_LIST(hh, head, add)                                          \\\ndo {                                                                             \\\n  (add)->hh.next = NULL;                                                         \\\n  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \\\n  (head)->hh.tbl->tail->next = (add);                                            \\\n  (head)->hh.tbl->tail = &((add)->hh);                                           \\\n} while (0)\n\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  do {                                                                           \\\n    if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) {                             \\\n      break;                                                                     \\\n    }                                                                            \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n\n#ifdef NO_DECLTYPE\n#undef HASH_AKBI_INNER_LOOP\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  char *_hs_saved_head = (char*)(head);                                          \\\n  do {                                                                           \\\n    DECLTYPE_ASSIGN(head, _hs_iter);                                             \\\n    if (cmpfcn(head, add) > 0) {                                                 \\\n      DECLTYPE_ASSIGN(head, _hs_saved_head);                                     \\\n      break;                                                                     \\\n    }                                                                            \\\n    DECLTYPE_ASSIGN(head, _hs_saved_head);                                       \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n#endif\n\n#if HASH_NONFATAL_OOM\n\n#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \\\ndo {                                                                             \\\n  if (!(oomed)) {                                                                \\\n    unsigned _ha_bkt;                                                            \\\n    (head)->hh.tbl->num_items++;                                                 \\\n    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                  \\\n    HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);    \\\n    if (oomed) {                                                                 \\\n      HASH_ROLLBACK_BKT(hh, head, &(add)->hh);                                   \\\n      HASH_DELETE_HH(hh, head, &(add)->hh);                                      \\\n      (add)->hh.tbl = NULL;                                                      \\\n      uthash_nonfatal_oom(add);                                                  \\\n    } else {                                                                     \\\n      HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                   \\\n      HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                \\\n    }                                                                            \\\n  } else {                                                                       \\\n    (add)->hh.tbl = NULL;                                                        \\\n    uthash_nonfatal_oom(add);                                                    \\\n  }                                                                              \\\n} while (0)\n\n#else\n\n#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \\\ndo {                                                                             \\\n  unsigned _ha_bkt;                                                              \\\n  (head)->hh.tbl->num_items++;                                                   \\\n  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \\\n  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);      \\\n  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \\\n  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \\\n} while (0)\n\n#endif\n\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \\\ndo {                                                                             \\\n  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (char*) (keyptr);                                              \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \\\n    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \\\n      (head) = (add);                                                            \\\n    IF_HASH_NONFATAL_OOM( } )                                                    \\\n  } else {                                                                       \\\n    void *_hs_iter = (head);                                                     \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn);                                 \\\n    if (_hs_iter) {                                                              \\\n      (add)->hh.next = _hs_iter;                                                 \\\n      if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) {     \\\n        HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add);              \\\n      } else {                                                                   \\\n        (head) = (add);                                                          \\\n      }                                                                          \\\n      HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add);                      \\\n    } else {                                                                     \\\n      HASH_APPEND_LIST(hh, head, add);                                           \\\n    }                                                                            \\\n  }                                                                              \\\n  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE_INORDER\");                    \\\n} while (0)\n\n#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \\\ndo {                                                                             \\\n  unsigned _hs_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)\n\n#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \\\n  HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \\\ndo {                                                                             \\\n  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (const void*) (keyptr);                                        \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \\\n    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \\\n      (head) = (add);                                                            \\\n    IF_HASH_NONFATAL_OOM( } )                                                    \\\n  } else {                                                                       \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_APPEND_LIST(hh, head, add);                                             \\\n  }                                                                              \\\n  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE\");                            \\\n} while (0)\n\n#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \\\ndo {                                                                             \\\n  unsigned _ha_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)\n\n#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \\\n  HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)\n\n#define HASH_TO_BKT(hashv,num_bkts,bkt)                                          \\\ndo {                                                                             \\\n  bkt = ((hashv) & ((num_bkts) - 1U));                                           \\\n} while (0)\n\n/* delete \"delptr\" from the hash table.\n * \"the usual\" patch-up process for the app-order doubly-linked-list.\n * The use of _hd_hh_del below deserves special explanation.\n * These used to be expressed using (delptr) but that led to a bug\n * if someone used the same symbol for the head and deletee, like\n *  HASH_DELETE(hh,users,users);\n * We want that to work, but by changing the head (users) below\n * we were forfeiting our ability to further refer to the deletee (users)\n * in the patch-up process. Solution: use scratch space to\n * copy the deletee pointer, then the latter references are via that\n * scratch pointer rather than through the repointed (users) symbol.\n */\n#define HASH_DELETE(hh,head,delptr)                                              \\\n    HASH_DELETE_HH(hh, head, &(delptr)->hh)\n\n#define HASH_DELETE_HH(hh,head,delptrhh)                                         \\\ndo {                                                                             \\\n  struct UT_hash_handle *_hd_hh_del = (delptrhh);                                \\\n  if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) {                \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket));    \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  } else {                                                                       \\\n    unsigned _hd_bkt;                                                            \\\n    if (_hd_hh_del == (head)->hh.tbl->tail) {                                    \\\n      (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev);     \\\n    }                                                                            \\\n    if (_hd_hh_del->prev != NULL) {                                              \\\n      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next;   \\\n    } else {                                                                     \\\n      DECLTYPE_ASSIGN(head, _hd_hh_del->next);                                   \\\n    }                                                                            \\\n    if (_hd_hh_del->next != NULL) {                                              \\\n      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev;   \\\n    }                                                                            \\\n    HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);        \\\n    HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);               \\\n    (head)->hh.tbl->num_items--;                                                 \\\n  }                                                                              \\\n  HASH_FSCK(hh, head, \"HASH_DELETE_HH\");                                         \\\n} while (0)\n\n/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */\n#define HASH_FIND_STR(head,findstr,out)                                          \\\ndo {                                                                             \\\n    unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr);            \\\n    HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out);                     \\\n} while (0)\n#define HASH_ADD_STR(head,strfield,add)                                          \\\ndo {                                                                             \\\n    unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield);    \\\n    HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add);                  \\\n} while (0)\n#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \\\ndo {                                                                             \\\n    unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield);    \\\n    HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced);    \\\n} while (0)\n#define HASH_FIND_INT(head,findint,out)                                          \\\n    HASH_FIND(hh,head,findint,sizeof(int),out)\n#define HASH_ADD_INT(head,intfield,add)                                          \\\n    HASH_ADD(hh,head,intfield,sizeof(int),add)\n#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)\n#define HASH_FIND_PTR(head,findptr,out)                                          \\\n    HASH_FIND(hh,head,findptr,sizeof(void *),out)\n#define HASH_ADD_PTR(head,ptrfield,add)                                          \\\n    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)\n#define HASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)\n#define HASH_DEL(head,delptr)                                                    \\\n    HASH_DELETE(hh,head,delptr)\n\n/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.\n * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.\n */\n#ifdef HASH_DEBUG\n#include <stdio.h>   /* fprintf, stderr */\n#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)\n#define HASH_FSCK(hh,head,where)                                                 \\\ndo {                                                                             \\\n  struct UT_hash_handle *_thh;                                                   \\\n  if (head) {                                                                    \\\n    unsigned _bkt_i;                                                             \\\n    unsigned _count = 0;                                                         \\\n    char *_prev;                                                                 \\\n    for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) {           \\\n      unsigned _bkt_count = 0;                                                   \\\n      _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                            \\\n      _prev = NULL;                                                              \\\n      while (_thh) {                                                             \\\n        if (_prev != (char*)(_thh->hh_prev)) {                                   \\\n          HASH_OOPS(\"%s: invalid hh_prev %p, actual %p\\n\",                       \\\n              (where), (void*)_thh->hh_prev, (void*)_prev);                      \\\n        }                                                                        \\\n        _bkt_count++;                                                            \\\n        _prev = (char*)(_thh);                                                   \\\n        _thh = _thh->hh_next;                                                    \\\n      }                                                                          \\\n      _count += _bkt_count;                                                      \\\n      if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {                \\\n        HASH_OOPS(\"%s: invalid bucket count %u, actual %u\\n\",                    \\\n            (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);         \\\n      }                                                                          \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid hh item count %u, actual %u\\n\",                     \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n    _count = 0;                                                                  \\\n    _prev = NULL;                                                                \\\n    _thh =  &(head)->hh;                                                         \\\n    while (_thh) {                                                               \\\n      _count++;                                                                  \\\n      if (_prev != (char*)_thh->prev) {                                          \\\n        HASH_OOPS(\"%s: invalid prev %p, actual %p\\n\",                            \\\n            (where), (void*)_thh->prev, (void*)_prev);                           \\\n      }                                                                          \\\n      _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                         \\\n      _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL);     \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid app item count %u, actual %u\\n\",                    \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n#else\n#define HASH_FSCK(hh,head,where)\n#endif\n\n/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to\n * the descriptor to which this macro is defined for tuning the hash function.\n * The app can #include <unistd.h> to get the prototype for write(2). */\n#ifdef HASH_EMIT_KEYS\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \\\ndo {                                                                             \\\n  unsigned _klen = fieldlen;                                                     \\\n  write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                  \\\n  write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                        \\\n} while (0)\n#else\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)\n#endif\n\n/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */\n#define HASH_BER(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hb_keylen = (unsigned)keylen;                                        \\\n  const unsigned char *_hb_key = (const unsigned char*)(key);                    \\\n  (hashv) = 0;                                                                   \\\n  while (_hb_keylen-- != 0U) {                                                   \\\n    (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                           \\\n  }                                                                              \\\n} while (0)\n\n\n/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at\n * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */\n#define HASH_SAX(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _sx_i;                                                                \\\n  const unsigned char *_hs_key = (const unsigned char*)(key);                    \\\n  hashv = 0;                                                                     \\\n  for (_sx_i=0; _sx_i < keylen; _sx_i++) {                                       \\\n    hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                       \\\n  }                                                                              \\\n} while (0)\n/* FNV-1a variation */\n#define HASH_FNV(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _fn_i;                                                                \\\n  const unsigned char *_hf_key = (const unsigned char*)(key);                    \\\n  (hashv) = 2166136261U;                                                         \\\n  for (_fn_i=0; _fn_i < keylen; _fn_i++) {                                       \\\n    hashv = hashv ^ _hf_key[_fn_i];                                              \\\n    hashv = hashv * 16777619U;                                                   \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OAT(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _ho_i;                                                                \\\n  const unsigned char *_ho_key=(const unsigned char*)(key);                      \\\n  hashv = 0;                                                                     \\\n  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \\\n      hashv += _ho_key[_ho_i];                                                   \\\n      hashv += (hashv << 10);                                                    \\\n      hashv ^= (hashv >> 6);                                                     \\\n  }                                                                              \\\n  hashv += (hashv << 3);                                                         \\\n  hashv ^= (hashv >> 11);                                                        \\\n  hashv += (hashv << 15);                                                        \\\n} while (0)\n\n#define HASH_JEN_MIX(a,b,c)                                                      \\\ndo {                                                                             \\\n  a -= b; a -= c; a ^= ( c >> 13 );                                              \\\n  b -= c; b -= a; b ^= ( a << 8 );                                               \\\n  c -= a; c -= b; c ^= ( b >> 13 );                                              \\\n  a -= b; a -= c; a ^= ( c >> 12 );                                              \\\n  b -= c; b -= a; b ^= ( a << 16 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 5 );                                               \\\n  a -= b; a -= c; a ^= ( c >> 3 );                                               \\\n  b -= c; b -= a; b ^= ( a << 10 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 15 );                                              \\\n} while (0)\n\n#define HASH_JEN(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hj_i,_hj_j,_hj_k;                                                    \\\n  unsigned const char *_hj_key=(unsigned const char*)(key);                      \\\n  hashv = 0xfeedbeefu;                                                           \\\n  _hj_i = _hj_j = 0x9e3779b9u;                                                   \\\n  _hj_k = (unsigned)(keylen);                                                    \\\n  while (_hj_k >= 12U) {                                                         \\\n    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \\\n        + ( (unsigned)_hj_key[2] << 16 )                                         \\\n        + ( (unsigned)_hj_key[3] << 24 ) );                                      \\\n    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \\\n        + ( (unsigned)_hj_key[6] << 16 )                                         \\\n        + ( (unsigned)_hj_key[7] << 24 ) );                                      \\\n    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \\\n        + ( (unsigned)_hj_key[10] << 16 )                                        \\\n        + ( (unsigned)_hj_key[11] << 24 ) );                                     \\\n                                                                                 \\\n     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \\\n                                                                                 \\\n     _hj_key += 12;                                                              \\\n     _hj_k -= 12U;                                                               \\\n  }                                                                              \\\n  hashv += (unsigned)(keylen);                                                   \\\n  switch ( _hj_k ) {                                                             \\\n    case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */         \\\n    case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */         \\\n    case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */         \\\n    case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */         \\\n    case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */         \\\n    case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */         \\\n    case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */         \\\n    case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */         \\\n    case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */         \\\n    case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */         \\\n    case 1:  _hj_i += _hj_key[0];                      /* FALLTHROUGH */         \\\n    default: ;                                                                   \\\n  }                                                                              \\\n  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \\\n} while (0)\n\n/* The Paul Hsieh hash function */\n#undef get16bits\n#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \\\n  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)\n#define get16bits(d) (*((const uint16_t *) (d)))\n#endif\n\n#if !defined (get16bits)\n#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \\\n                       +(uint32_t)(((const uint8_t *)(d))[0]) )\n#endif\n#define HASH_SFH(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \\\n  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \\\n                                                                                 \\\n  unsigned _sfh_rem = _sfh_len & 3U;                                             \\\n  _sfh_len >>= 2;                                                                \\\n  hashv = 0xcafebabeu;                                                           \\\n                                                                                 \\\n  /* Main loop */                                                                \\\n  for (;_sfh_len > 0U; _sfh_len--) {                                             \\\n    hashv    += get16bits (_sfh_key);                                            \\\n    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \\\n    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \\\n    _sfh_key += 2U*sizeof (uint16_t);                                            \\\n    hashv    += hashv >> 11;                                                     \\\n  }                                                                              \\\n                                                                                 \\\n  /* Handle end cases */                                                         \\\n  switch (_sfh_rem) {                                                            \\\n    case 3: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 16;                                                \\\n            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \\\n            hashv += hashv >> 11;                                                \\\n            break;                                                               \\\n    case 2: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 11;                                                \\\n            hashv += hashv >> 17;                                                \\\n            break;                                                               \\\n    case 1: hashv += *_sfh_key;                                                  \\\n            hashv ^= hashv << 10;                                                \\\n            hashv += hashv >> 1;                                                 \\\n            break;                                                               \\\n    default: ;                                                                   \\\n  }                                                                              \\\n                                                                                 \\\n  /* Force \"avalanching\" of final 127 bits */                                    \\\n  hashv ^= hashv << 3;                                                           \\\n  hashv += hashv >> 5;                                                           \\\n  hashv ^= hashv << 4;                                                           \\\n  hashv += hashv >> 17;                                                          \\\n  hashv ^= hashv << 25;                                                          \\\n  hashv += hashv >> 6;                                                           \\\n} while (0)\n\n/* iterate over items in a known bucket to find desired item */\n#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \\\ndo {                                                                             \\\n  if ((head).hh_head != NULL) {                                                  \\\n    DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head));                     \\\n  } else {                                                                       \\\n    (out) = NULL;                                                                \\\n  }                                                                              \\\n  while ((out) != NULL) {                                                        \\\n    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \\\n      if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) {                  \\\n        break;                                                                   \\\n      }                                                                          \\\n    }                                                                            \\\n    if ((out)->hh.hh_next != NULL) {                                             \\\n      DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next));                \\\n    } else {                                                                     \\\n      (out) = NULL;                                                              \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n/* add an item to a bucket  */\n#define HASH_ADD_TO_BKT(head,hh,addhh,oomed)                                     \\\ndo {                                                                             \\\n  UT_hash_bucket *_ha_head = &(head);                                            \\\n  _ha_head->count++;                                                             \\\n  (addhh)->hh_next = _ha_head->hh_head;                                          \\\n  (addhh)->hh_prev = NULL;                                                       \\\n  if (_ha_head->hh_head != NULL) {                                               \\\n    _ha_head->hh_head->hh_prev = (addhh);                                        \\\n  }                                                                              \\\n  _ha_head->hh_head = (addhh);                                                   \\\n  if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \\\n      && !(addhh)->tbl->noexpand) {                                              \\\n    HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed);                              \\\n    IF_HASH_NONFATAL_OOM(                                                        \\\n      if (oomed) {                                                               \\\n        HASH_DEL_IN_BKT(head,addhh);                                             \\\n      }                                                                          \\\n    )                                                                            \\\n  }                                                                              \\\n} while (0)\n\n/* remove an item from a given bucket */\n#define HASH_DEL_IN_BKT(head,delhh)                                              \\\ndo {                                                                             \\\n  UT_hash_bucket *_hd_head = &(head);                                            \\\n  _hd_head->count--;                                                             \\\n  if (_hd_head->hh_head == (delhh)) {                                            \\\n    _hd_head->hh_head = (delhh)->hh_next;                                        \\\n  }                                                                              \\\n  if ((delhh)->hh_prev) {                                                        \\\n    (delhh)->hh_prev->hh_next = (delhh)->hh_next;                                \\\n  }                                                                              \\\n  if ((delhh)->hh_next) {                                                        \\\n    (delhh)->hh_next->hh_prev = (delhh)->hh_prev;                                \\\n  }                                                                              \\\n} while (0)\n\n/* Bucket expansion has the effect of doubling the number of buckets\n * and redistributing the items into the new buckets. Ideally the\n * items will distribute more or less evenly into the new buckets\n * (the extent to which this is true is a measure of the quality of\n * the hash function as it applies to the key domain).\n *\n * With the items distributed into more buckets, the chain length\n * (item count) in each bucket is reduced. Thus by expanding buckets\n * the hash keeps a bound on the chain length. This bounded chain\n * length is the essence of how a hash provides constant time lookup.\n *\n * The calculation of tbl->ideal_chain_maxlen below deserves some\n * explanation. First, keep in mind that we're calculating the ideal\n * maximum chain length based on the *new* (doubled) bucket count.\n * In fractions this is just n/b (n=number of items,b=new num buckets).\n * Since the ideal chain length is an integer, we want to calculate\n * ceil(n/b). We don't depend on floating point arithmetic in this\n * hash, so to calculate ceil(n/b) with integers we could write\n *\n *      ceil(n/b) = (n/b) + ((n%b)?1:0)\n *\n * and in fact a previous version of this hash did just that.\n * But now we have improved things a bit by recognizing that b is\n * always a power of two. We keep its base 2 log handy (call it lb),\n * so now we can write this with a bit shift and logical AND:\n *\n *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)\n *\n */\n#define HASH_EXPAND_BUCKETS(hh,tbl,oomed)                                        \\\ndo {                                                                             \\\n  unsigned _he_bkt;                                                              \\\n  unsigned _he_bkt_i;                                                            \\\n  struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                   \\\n  UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                  \\\n  _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                              \\\n           sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);             \\\n  if (!_he_new_buckets) {                                                        \\\n    HASH_RECORD_OOM(oomed);                                                      \\\n  } else {                                                                       \\\n    uthash_bzero(_he_new_buckets,                                                \\\n        sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);                \\\n    (tbl)->ideal_chain_maxlen =                                                  \\\n       ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) +                      \\\n       ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);    \\\n    (tbl)->nonideal_items = 0;                                                   \\\n    for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) {           \\\n      _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head;                             \\\n      while (_he_thh != NULL) {                                                  \\\n        _he_hh_nxt = _he_thh->hh_next;                                           \\\n        HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt);           \\\n        _he_newbkt = &(_he_new_buckets[_he_bkt]);                                \\\n        if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) {                 \\\n          (tbl)->nonideal_items++;                                               \\\n          if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \\\n            _he_newbkt->expand_mult++;                                           \\\n          }                                                                      \\\n        }                                                                        \\\n        _he_thh->hh_prev = NULL;                                                 \\\n        _he_thh->hh_next = _he_newbkt->hh_head;                                  \\\n        if (_he_newbkt->hh_head != NULL) {                                       \\\n          _he_newbkt->hh_head->hh_prev = _he_thh;                                \\\n        }                                                                        \\\n        _he_newbkt->hh_head = _he_thh;                                           \\\n        _he_thh = _he_hh_nxt;                                                    \\\n      }                                                                          \\\n    }                                                                            \\\n    uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \\\n    (tbl)->num_buckets *= 2U;                                                    \\\n    (tbl)->log2_num_buckets++;                                                   \\\n    (tbl)->buckets = _he_new_buckets;                                            \\\n    (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ?   \\\n        ((tbl)->ineff_expands+1U) : 0U;                                          \\\n    if ((tbl)->ineff_expands > 1U) {                                             \\\n      (tbl)->noexpand = 1;                                                       \\\n      uthash_noexpand_fyi(tbl);                                                  \\\n    }                                                                            \\\n    uthash_expand_fyi(tbl);                                                      \\\n  }                                                                              \\\n} while (0)\n\n\n/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */\n/* Note that HASH_SORT assumes the hash handle name to be hh.\n * HASH_SRT was added to allow the hash handle name to be passed in. */\n#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)\n#define HASH_SRT(hh,head,cmpfcn)                                                 \\\ndo {                                                                             \\\n  unsigned _hs_i;                                                                \\\n  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \\\n  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \\\n  if (head != NULL) {                                                            \\\n    _hs_insize = 1;                                                              \\\n    _hs_looping = 1;                                                             \\\n    _hs_list = &((head)->hh);                                                    \\\n    while (_hs_looping != 0U) {                                                  \\\n      _hs_p = _hs_list;                                                          \\\n      _hs_list = NULL;                                                           \\\n      _hs_tail = NULL;                                                           \\\n      _hs_nmerges = 0;                                                           \\\n      while (_hs_p != NULL) {                                                    \\\n        _hs_nmerges++;                                                           \\\n        _hs_q = _hs_p;                                                           \\\n        _hs_psize = 0;                                                           \\\n        for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) {                           \\\n          _hs_psize++;                                                           \\\n          _hs_q = ((_hs_q->next != NULL) ?                                       \\\n            HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                   \\\n          if (_hs_q == NULL) {                                                   \\\n            break;                                                               \\\n          }                                                                      \\\n        }                                                                        \\\n        _hs_qsize = _hs_insize;                                                  \\\n        while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) {    \\\n          if (_hs_psize == 0U) {                                                 \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) {                     \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else if ((cmpfcn(                                                    \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)),             \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q))              \\\n                )) <= 0) {                                                       \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else {                                                               \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          }                                                                      \\\n          if ( _hs_tail != NULL ) {                                              \\\n            _hs_tail->next = ((_hs_e != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL);                       \\\n          } else {                                                               \\\n            _hs_list = _hs_e;                                                    \\\n          }                                                                      \\\n          if (_hs_e != NULL) {                                                   \\\n            _hs_e->prev = ((_hs_tail != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL);                    \\\n          }                                                                      \\\n          _hs_tail = _hs_e;                                                      \\\n        }                                                                        \\\n        _hs_p = _hs_q;                                                           \\\n      }                                                                          \\\n      if (_hs_tail != NULL) {                                                    \\\n        _hs_tail->next = NULL;                                                   \\\n      }                                                                          \\\n      if (_hs_nmerges <= 1U) {                                                   \\\n        _hs_looping = 0;                                                         \\\n        (head)->hh.tbl->tail = _hs_tail;                                         \\\n        DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list));           \\\n      }                                                                          \\\n      _hs_insize *= 2U;                                                          \\\n    }                                                                            \\\n    HASH_FSCK(hh, head, \"HASH_SRT\");                                             \\\n  }                                                                              \\\n} while (0)\n\n/* This function selects items from one hash into another hash.\n * The end result is that the selected items have dual presence\n * in both hashes. There is no copy of the items made; rather\n * they are added into the new hash through a secondary hash\n * hash handle that must be present in the structure. */\n#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \\\ndo {                                                                             \\\n  unsigned _src_bkt, _dst_bkt;                                                   \\\n  void *_last_elt = NULL, *_elt;                                                 \\\n  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \\\n  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \\\n  if ((src) != NULL) {                                                           \\\n    for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {    \\\n      for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;               \\\n        _src_hh != NULL;                                                         \\\n        _src_hh = _src_hh->hh_next) {                                            \\\n        _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                         \\\n        if (cond(_elt)) {                                                        \\\n          IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; )                             \\\n          _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho);          \\\n          _dst_hh->key = _src_hh->key;                                           \\\n          _dst_hh->keylen = _src_hh->keylen;                                     \\\n          _dst_hh->hashv = _src_hh->hashv;                                       \\\n          _dst_hh->prev = _last_elt;                                             \\\n          _dst_hh->next = NULL;                                                  \\\n          if (_last_elt_hh != NULL) {                                            \\\n            _last_elt_hh->next = _elt;                                           \\\n          }                                                                      \\\n          if ((dst) == NULL) {                                                   \\\n            DECLTYPE_ASSIGN(dst, _elt);                                          \\\n            HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed);                             \\\n            IF_HASH_NONFATAL_OOM(                                                \\\n              if (_hs_oomed) {                                                   \\\n                uthash_nonfatal_oom(_elt);                                       \\\n                (dst) = NULL;                                                    \\\n                continue;                                                        \\\n              }                                                                  \\\n            )                                                                    \\\n          } else {                                                               \\\n            _dst_hh->tbl = (dst)->hh_dst.tbl;                                    \\\n          }                                                                      \\\n          HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);      \\\n          HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \\\n          (dst)->hh_dst.tbl->num_items++;                                        \\\n          IF_HASH_NONFATAL_OOM(                                                  \\\n            if (_hs_oomed) {                                                     \\\n              HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh);                           \\\n              HASH_DELETE_HH(hh_dst, dst, _dst_hh);                              \\\n              _dst_hh->tbl = NULL;                                               \\\n              uthash_nonfatal_oom(_elt);                                         \\\n              continue;                                                          \\\n            }                                                                    \\\n          )                                                                      \\\n          HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv);                          \\\n          _last_elt = _elt;                                                      \\\n          _last_elt_hh = _dst_hh;                                                \\\n        }                                                                        \\\n      }                                                                          \\\n    }                                                                            \\\n  }                                                                              \\\n  HASH_FSCK(hh_dst, dst, \"HASH_SELECT\");                                         \\\n} while (0)\n\n#define HASH_CLEAR(hh,head)                                                      \\\ndo {                                                                             \\\n  if ((head) != NULL) {                                                          \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OVERHEAD(hh,head)                                                   \\\n (((head) != NULL) ? (                                                           \\\n (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \\\n          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \\\n           sizeof(UT_hash_table)                                   +             \\\n           (HASH_BLOOM_BYTELEN))) : 0U)\n\n#ifdef NO_DECLTYPE\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \\\n  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#else\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \\\n  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#endif\n\n/* obtain a count of items in the hash */\n#define HASH_COUNT(head) HASH_CNT(hh,head)\n#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)\n\ntypedef struct UT_hash_bucket {\n   struct UT_hash_handle *hh_head;\n   unsigned count;\n\n   /* expand_mult is normally set to 0. In this situation, the max chain length\n    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If\n    * the bucket's chain exceeds this length, bucket expansion is triggered).\n    * However, setting expand_mult to a non-zero value delays bucket expansion\n    * (that would be triggered by additions to this particular bucket)\n    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.\n    * (The multiplier is simply expand_mult+1). The whole idea of this\n    * multiplier is to reduce bucket expansions, since they are expensive, in\n    * situations where we know that a particular bucket tends to be overused.\n    * It is better to let its chain length grow to a longer yet-still-bounded\n    * value, than to do an O(n) bucket expansion too often.\n    */\n   unsigned expand_mult;\n\n} UT_hash_bucket;\n\n/* random signature used only to find hash tables in external analysis */\n#define HASH_SIGNATURE 0xa0111fe1u\n#define HASH_BLOOM_SIGNATURE 0xb12220f2u\n\ntypedef struct UT_hash_table {\n   UT_hash_bucket *buckets;\n   unsigned num_buckets, log2_num_buckets;\n   unsigned num_items;\n   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */\n   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */\n\n   /* in an ideal situation (all buckets used equally), no bucket would have\n    * more than ceil(#items/#buckets) items. that's the ideal chain length. */\n   unsigned ideal_chain_maxlen;\n\n   /* nonideal_items is the number of items in the hash whose chain position\n    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven\n    * hash distribution; reaching them in a chain traversal takes >ideal steps */\n   unsigned nonideal_items;\n\n   /* ineffective expands occur when a bucket doubling was performed, but\n    * afterward, more than half the items in the hash had nonideal chain\n    * positions. If this happens on two consecutive expansions we inhibit any\n    * further expansion, as it's not helping; this happens when the hash\n    * function isn't a good fit for the key domain. When expansion is inhibited\n    * the hash will still work, albeit no longer in constant time. */\n   unsigned ineff_expands, noexpand;\n\n   uint32_t signature; /* used only to find hash tables in external analysis */\n#ifdef HASH_BLOOM\n   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */\n   uint8_t *bloom_bv;\n   uint8_t bloom_nbits;\n#endif\n\n} UT_hash_table;\n\ntypedef struct UT_hash_handle {\n   struct UT_hash_table *tbl;\n   void *prev;                       /* prev element in app order      */\n   void *next;                       /* next element in app order      */\n   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */\n   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */\n   const void *key;                  /* ptr to enclosing struct's key  */\n   unsigned keylen;                  /* enclosing struct's key len     */\n   unsigned hashv;                   /* result of hash-fcn(key)        */\n} UT_hash_handle;\n\n#endif /* UTHASH_H */\n"
  },
  {
    "path": "uninstall_easy.sh",
    "content": "#!/bin/sh\n\n# automated script for easy uninstalling zapret\n\nEXEDIR=\"$(dirname \"$0\")\"\nEXEDIR=\"$(cd \"$EXEDIR\"; pwd)\"\nZAPRET_BASE=${ZAPRET_BASE:-\"$EXEDIR\"}\nZAPRET_RW=${ZAPRET_RW:-\"$ZAPRET_BASE\"}\nZAPRET_CONFIG=${ZAPRET_CONFIG:-\"$ZAPRET_RW/config\"}\nZAPRET_CONFIG_DEFAULT=\"$ZAPRET_BASE/config.default\"\nIPSET_DIR=\"$ZAPRET_BASE/ipset\"\n\n[ -f \"$ZAPRET_CONFIG\" ] || {\n\tZAPRET_CONFIG_DIR=\"$(dirname \"$ZAPRET_CONFIG\")\"\n\t[ -d \"$ZAPRET_CONFIG_DIR\" ] || mkdir -p \"$ZAPRET_CONFIG_DIR\"\n\tcp \"$ZAPRET_CONFIG_DEFAULT\" \"$ZAPRET_CONFIG\"\n}\n\n. \"$ZAPRET_CONFIG\"\n. \"$ZAPRET_BASE/common/base.sh\"\n. \"$ZAPRET_BASE/common/elevate.sh\"\n. \"$ZAPRET_BASE/common/fwtype.sh\"\n. \"$ZAPRET_BASE/common/dialog.sh\"\n. \"$ZAPRET_BASE/common/ipt.sh\"\n. \"$ZAPRET_BASE/common/nft.sh\"\n. \"$ZAPRET_BASE/common/pf.sh\"\n. \"$ZAPRET_BASE/common/installer.sh\"\n\nremove_systemd()\n{\n\tclear_ipset\n\tservice_stop_systemd\n\tservice_remove_systemd\n\ttimer_remove_systemd\n\tnft_del_table\n\tcrontab_del\n}\n\nremove_openrc()\n{\n\tclear_ipset\n\tservice_remove_openrc\n\tnft_del_table\n\tcrontab_del\n}\n\nremove_linux()\n{\n\tINIT_SCRIPT_SRC=\"$EXEDIR/init.d/sysv/zapret\"\n\n\tclear_ipset\n\n\techo \\* executing sysv init stop\n\t\"$INIT_SCRIPT_SRC\" stop\n\t\n\tnft_del_table\n\tcrontab_del\n\n\techo\n\techo '!!! WARNING. YOUR UNINSTALL IS INCOMPLETE !!!'\n\techo 'you must manually remove zapret auto start from your system'\n}\n\nremove_openwrt()\n{\n\tOPENWRT_FW_INCLUDE=/etc/firewall.zapret\n\n\tclear_ipset\n\tservice_remove_sysv\n\tremove_openwrt_firewall\n\tremove_openwrt_iface_hook\n\tnft_del_table\n\trestart_openwrt_firewall\n\tcrontab_del\n\tremove_extra_pkgs_openwrt\n\techo\n\techo to fully remove zapret : rm -r \\\"$ZAPRET_BASE\\\"\n}\n\nremove_macos()\n{\n\tremove_macos_firewall\n\tservice_remove_macos\n\tcrontab_del\n}\n\n\nfix_sbin_path\ncheck_system\nrequire_root\n\n[ \"$SYSTEM\" = \"macos\" ] && . \"$EXEDIR/init.d/macos/functions\"\n\ncase $SYSTEM in\n\tsystemd)\n\t\tremove_systemd\n\t\t;;\n\topenrc)\n\t\tremove_openrc\n\t\t;;\n\tlinux)\n\t\tremove_linux\n\t\t;;\n\topenwrt)\n\t\tremove_openwrt\n\t\t;;\n\tmacos)\n\t\tremove_macos\n\t\t;;\nesac\n\n\nexitp 0\n"
  }
]