Repository: bol-van/zapret Branch: master Commit: 363fbe6943b1 Files: 235 Total size: 1.8 MB Directory structure: gitextract_d8cviuz8/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── config.yml │ │ └── issue-warning.md │ └── workflows/ │ ├── build.yml │ └── libnetfilter_queue-android.patch ├── .gitignore ├── Makefile ├── blockcheck.sh ├── common/ │ ├── base.sh │ ├── custom.sh │ ├── dialog.sh │ ├── elevate.sh │ ├── fwtype.sh │ ├── installer.sh │ ├── ipt.sh │ ├── linux_daemons.sh │ ├── linux_fw.sh │ ├── linux_iphelper.sh │ ├── list.sh │ ├── nft.sh │ ├── pf.sh │ └── virt.sh ├── config.default ├── docs/ │ ├── LICENSE.txt │ ├── bsd.en.md │ ├── bsd.md │ ├── bsdfw.txt │ ├── changes.txt │ ├── compile/ │ │ ├── build_howto_openwrt.txt │ │ ├── build_howto_unix.txt │ │ ├── build_howto_windows.txt │ │ └── openwrt/ │ │ └── package/ │ │ └── zapret/ │ │ ├── ip2net/ │ │ │ ├── Makefile │ │ │ └── readme.txt │ │ ├── mdig/ │ │ │ ├── Makefile │ │ │ └── readme.txt │ │ ├── nfqws/ │ │ │ ├── Makefile │ │ │ └── readme.txt │ │ └── tpws/ │ │ ├── Makefile │ │ └── readme.txt │ ├── iptables.txt │ ├── nftables.txt │ ├── nftables_notes.txt │ ├── quick_start.md │ ├── quick_start_windows.md │ ├── readme.en.md │ ├── readme.md │ ├── redsocks.txt │ ├── windows.en.md │ ├── windows.md │ └── wireguard_iproute_openwrt.txt ├── files/ │ └── huawei/ │ └── E8372/ │ ├── run-zapret-hostlist │ ├── run-zapret-ip │ ├── unfuck_nfqueue.ko │ ├── unzapret │ ├── unzapret-ip │ ├── zapret │ └── zapret-ip ├── init.d/ │ ├── custom.d.examples.linux/ │ │ ├── 10-keenetic-udp-fix │ │ ├── 20-fw-extra │ │ ├── 50-dht4all │ │ ├── 50-discord-media │ │ ├── 50-nfqws-ipset │ │ ├── 50-quic4all │ │ ├── 50-stun4all │ │ ├── 50-tpws-ipset │ │ └── 50-wg4all │ ├── macos/ │ │ ├── custom.d/ │ │ │ └── .keep │ │ ├── custom.d.examples/ │ │ │ └── 50-extra-tpws │ │ ├── functions │ │ ├── zapret │ │ └── zapret.plist │ ├── openrc/ │ │ └── zapret │ ├── openwrt/ │ │ ├── 90-zapret │ │ ├── custom.d/ │ │ │ └── .keep │ │ ├── firewall.zapret │ │ ├── functions │ │ └── zapret │ ├── openwrt-minimal/ │ │ ├── readme.txt │ │ └── tpws/ │ │ └── etc/ │ │ ├── config/ │ │ │ └── tpws │ │ ├── firewall.user │ │ ├── init.d/ │ │ │ └── tpws │ │ └── nftables.d/ │ │ └── 90-tpws.nft │ ├── pfsense/ │ │ └── zapret.sh │ ├── runit/ │ │ └── zapret/ │ │ ├── finish │ │ └── run │ ├── s6/ │ │ └── zapret/ │ │ ├── down │ │ ├── type │ │ └── up │ ├── systemd/ │ │ ├── nfqws@.service │ │ ├── tpws@.service │ │ ├── zapret-list-update.service │ │ ├── zapret-list-update.timer │ │ └── zapret.service │ ├── sysv/ │ │ ├── custom.d/ │ │ │ └── .keep │ │ ├── functions │ │ └── zapret │ └── windivert.filter.examples/ │ ├── README.txt │ ├── windivert_part.discord_media.txt │ ├── windivert_part.quic_initial_ietf.txt │ ├── windivert_part.stun.txt │ └── windivert_part.wireguard.txt ├── install_bin.sh ├── install_easy.sh ├── install_prereq.sh ├── ip2net/ │ ├── Makefile │ ├── ip2net.c │ ├── qsort.c │ └── qsort.h ├── ipset/ │ ├── antifilter.helper │ ├── clear_lists.sh │ ├── create_ipset.sh │ ├── def.sh │ ├── get_antifilter_allyouneed.sh │ ├── get_antifilter_ip.sh │ ├── get_antifilter_ipresolve.sh │ ├── get_antifilter_ipsmart.sh │ ├── get_antifilter_ipsum.sh │ ├── get_antizapret_domains.sh │ ├── get_config.sh │ ├── get_exclude.sh │ ├── get_ipban.sh │ ├── get_reestr_preresolved.sh │ ├── get_reestr_preresolved_smart.sh │ ├── get_reestr_resolvable_domains.sh │ ├── get_refilter_domains.sh │ ├── get_refilter_ipsum.sh │ ├── get_user.sh │ └── zapret-hosts-user-exclude.txt.default ├── mdig/ │ ├── Makefile │ └── mdig.c ├── nfq/ │ ├── BSDmakefile │ ├── Makefile │ ├── checksum.c │ ├── checksum.h │ ├── conntrack.c │ ├── conntrack.h │ ├── crypto/ │ │ ├── aes-gcm.c │ │ ├── aes-gcm.h │ │ ├── aes.c │ │ ├── aes.h │ │ ├── gcm.c │ │ ├── gcm.h │ │ ├── hkdf.c │ │ ├── hmac.c │ │ ├── sha-private.h │ │ ├── sha.h │ │ ├── sha224-256.c │ │ └── usha.c │ ├── darkmagic.c │ ├── darkmagic.h │ ├── desync.c │ ├── desync.h │ ├── gzip.c │ ├── gzip.h │ ├── helpers.c │ ├── helpers.h │ ├── hostlist.c │ ├── hostlist.h │ ├── ipset.c │ ├── ipset.h │ ├── kavl.h │ ├── nfqws.c │ ├── nfqws.h │ ├── packet_queue.c │ ├── packet_queue.h │ ├── params.c │ ├── params.h │ ├── pools.c │ ├── pools.h │ ├── protocol.c │ ├── protocol.h │ ├── sec.c │ ├── sec.h │ ├── uthash.h │ ├── win.c │ ├── win.h │ └── windows/ │ ├── res/ │ │ ├── 32/ │ │ │ ├── winicon.o │ │ │ └── winmanifest.o │ │ └── 64/ │ │ ├── winicon.o │ │ └── winmanifest.o │ └── windivert/ │ ├── libwindivert32.a │ ├── libwindivert64.a │ └── windivert.h ├── tmp/ │ └── .keep ├── tpws/ │ ├── BSDmakefile │ ├── Makefile │ ├── andr/ │ │ ├── _musl_license.txt │ │ ├── getifaddrs.c │ │ ├── ifaddrs.h │ │ ├── netlink.c │ │ └── netlink.h │ ├── epoll-shim/ │ │ ├── include/ │ │ │ └── sys/ │ │ │ └── epoll.h │ │ └── src/ │ │ ├── epoll.c │ │ ├── epoll_shim_ctx.c │ │ ├── epoll_shim_ctx.h │ │ ├── epollfd_ctx.c │ │ ├── epollfd_ctx.h │ │ ├── eventfd_ctx.h │ │ ├── fix.c │ │ ├── fix.h │ │ ├── signalfd_ctx.h │ │ └── timerfd_ctx.h │ ├── gzip.c │ ├── gzip.h │ ├── helpers.c │ ├── helpers.h │ ├── hostlist.c │ ├── hostlist.h │ ├── ipset.c │ ├── ipset.h │ ├── kavl.h │ ├── linux_compat.h │ ├── macos/ │ │ ├── net/ │ │ │ └── pfvar.h │ │ └── sys/ │ │ └── tree.h │ ├── params.c │ ├── params.h │ ├── pools.c │ ├── pools.h │ ├── protocol.c │ ├── protocol.h │ ├── redirect.c │ ├── redirect.h │ ├── resolver.c │ ├── resolver.h │ ├── sec.c │ ├── sec.h │ ├── socks.h │ ├── tamper.c │ ├── tamper.h │ ├── tpws.c │ ├── tpws.h │ ├── tpws_conn.c │ ├── tpws_conn.h │ └── uthash.h └── uninstall_easy.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.cmd eol=crlf *.bat eol=crlf init.d/windivert.filter.examples/** eol=crlf files/** binary ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/issue-warning.md ================================================ --- name: bugs about: do not write lame questions title: '' labels: '' assignees: '' --- Issues - это место для обращений к разработчику. Discussions - место для обсуждения вопросов между пользователями. Не тратьте время разработчика на ерунду. Вам не будут здесь объяснять как скопировать, что "писать в", почему сразу же закрывается окно, почему не открывается сайт или госуслуги. Здесь не место обсуждению любых шаманств, т.е. манипуляций без понимания, в стиле 4pda. Пишите только конкретные проблемы на техническом уровне в оригинальном zapret (не в сборках), которые вы заметили, и которые являются или могут являться багами в софте. Или если вы считаете, что ваше обращение обосновано, технически грамотно и по адресу. Все, что будет нарушать эти критерии, может быть молча удалено, закрыто или перенесено в дискуссии на усмотрение разработчика. Если сомневаетесь - пишите лучше сразу в дискуссии. Прочитайте для начала docs/quick_start.md или docs/quick_start_windows.md. Там объясняется для кого этот софт, какие требуются знания, почему это не простая волшебная таблетка. Вирусов здесь нет. У вас либо чья-то сборка, либо ваш антивирус давно пора отправить на покой. Антивирусы в основном жалуются на upx и windivert, которые убраны НЕ будут. upx - это паковщик для сокращения требуемого места на openwrt, windivert - замена iptables для windows, потенциальный инструмент хакера или компонент зловредной программы, но сам по себе вирусом не является. Не согласны - удаляйте софт. За агрессивные наезды "почему автор распространяет вирусы" молча схватите бан. Here is the place for bugs only. All questions, especially user-like questions (non-technical) go to Discussions. There're also no viruses here. All virus claims and everyting non-technical and non-bugs will be instantly deleted, closed or moved to Discussions. ================================================ FILE: .github/workflows/build.yml ================================================ name: build run-name: ${{ startsWith(github.ref, 'refs/tags/v') && format('Release {0}', github.ref_name) || null }} on: workflow_dispatch: push: tags: - v[0-9]+* # branches: # - master # paths: # - 'ip2net/**' # - 'mdig/**' # - 'nfq/**' # - 'tpws/**' jobs: build-linux: name: Linux ${{ matrix.arch }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - arch: arm64 tool: aarch64-unknown-linux-musl - arch: arm tool: armv6-unknown-linux-musleabi - arch: mips64 tool: mips64-unknown-linux-musl - arch: mipselsf tool: mipsel-unknown-linux-muslsf - arch: mipssf tool: mips-unknown-linux-muslsf - arch: ppc tool: powerpc-unknown-linux-musl - arch: x86 tool: i586-unknown-linux-musl - arch: x86_64 tool: x86_64-unknown-linux-musl - arch: lexra tool: mips-linux dir: rsdk-4.6.4-5281-EB-3.10-0.9.33-m32ub-20141001 env: CFLAGS: '-march=5281' LDFLAGS: '-lgcc_eh' repo: 'bol-van/build' steps: - name: Checkout uses: actions/checkout@v4 with: path: zapret - name: Set up build tools env: ARCH: ${{ matrix.arch }} TOOL: ${{ matrix.tool }} REPO: ${{ matrix.arch == 'lexra' && matrix.repo || 'bol-van/musl-cross' }} DIR: ${{ matrix.arch == 'lexra' && matrix.dir || matrix.tool }} run: | if [[ "$ARCH" == lexra ]]; then sudo dpkg --add-architecture i386 sudo apt update -qq sudo apt install -y libcap-dev libc6:i386 zlib1g:i386 URL=https://github.com/$REPO/raw/refs/heads/master/$DIR.txz else sudo apt update -qq sudo apt install -y libcap-dev URL=https://github.com/$REPO/releases/download/latest/$TOOL.tar.xz fi mkdir -p $HOME/tools wget -qO- $URL | tar -C $HOME/tools -xJ || exit 1 [[ -d "$HOME/tools/$DIR/bin" ]] && echo "$HOME/tools/$DIR/bin" >> $GITHUB_PATH - name: Build env: ARCH: ${{ matrix.arch }} TARGET: ${{ matrix.tool }} CFLAGS: ${{ matrix.env.CFLAGS != '' && matrix.env.CFLAGS || null }} LDFLAGS: ${{ matrix.env.LDFLAGS != '' && matrix.env.LDFLAGS || null }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | DEPS_DIR=$GITHUB_WORKSPACE/deps export CC="$TARGET-gcc" export LD=$TARGET-ld export AR=$TARGET-ar export NM=$TARGET-nm export STRIP=$TARGET-strip export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig export STAGING_DIR=$RUNNER_TEMP OPTIMIZE=-Oz case "$ARCH" in lexra) OPTIMIZE=-Os ;; arm) CPU="-mcpu=arm1176jzf-s -mthumb" ;; esac # netfilter libs wget -qO- https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj wget -qO- https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj wget -qO- https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj for i in libmnl libnfnetlink libnetfilter_queue ; do ( cd $i-* CFLAGS="$OPTIMIZE $CPU -flto=auto $CFLAGS" \ ./configure --prefix= --host=$TARGET --enable-static --disable-shared --disable-dependency-tracking make install -j$(nproc) DESTDIR=$DEPS_DIR ) sed -i "s|^prefix=.*|prefix=$DEPS_DIR|g" $DEPS_DIR/lib/pkgconfig/$i.pc done # zlib gh api repos/madler/zlib/releases/latest --jq '.tag_name' |\ xargs -I{} wget -qO- https://github.com/madler/zlib/archive/refs/tags/{}.tar.gz | tar -xz ( cd zlib-* CFLAGS="$OPTIMIZE $CPU -flto=auto $CFLAGS" \ ./configure --prefix= --static make install -j$(nproc) DESTDIR=$DEPS_DIR ) # headers # wget https://git.alpinelinux.org/aports/plain/main/bsd-compat-headers/queue.h && \ # wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/plain/libcap/include/sys/capability.h && \ install -Dm644 -t $DEPS_DIR/include/sys /usr/include/x86_64-linux-gnu/sys/queue.h /usr/include/sys/capability.h # zapret OPTIMIZE=$OPTIMIZE \ CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -static-libgcc -static $CPU -I$DEPS_DIR/include $CFLAGS" \ LDFLAGS="-L$DEPS_DIR/lib $LDFLAGS" \ make -C zapret -j$(nproc) tar -C zapret/binaries/my -cJf zapret-linux-$ARCH.tar.xz . - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: zapret-linux-${{ matrix.arch }} path: zapret-*.tar.xz if-no-files-found: error build-macos: name: macOS runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Build zapret run: | export CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}" make mac -j$(sysctl -n hw.logicalcpu) tar -C binaries/my -cJf zapret-mac-x64.tar.xz . - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: zapret-mac-x64 path: zapret-*.tar.xz if-no-files-found: error build-freebsd: name: FreeBSD ${{ matrix.arch }} runs-on: ubuntu-latest strategy: matrix: include: - target: x86_64 arch: x86_64 # - target: i386 # arch: x86 container: image: empterdose/freebsd-cross-build:11.4 steps: - name: Checkout uses: actions/checkout@v4 - name: Install packages run: apk add tar xz - name: Build zapret env: TARGET: ${{ matrix.target }} ARCH: ${{ matrix.arch }} run: | export CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}" settarget $TARGET-freebsd11 make bsd -j$(nproc) tar -C binaries/my -cJf zapret-freebsd-$ARCH.tar.xz . - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: zapret-freebsd-${{ matrix.arch }} path: zapret-*.tar.xz if-no-files-found: error build-windows: name: Windows ${{ matrix.arch }} runs-on: windows-latest strategy: fail-fast: false matrix: arch: [ x86_64, x86 ] steps: - name: Checkout uses: actions/checkout@v4 with: path: zapret - name: Set up MinGW uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.arch == 'x86_64' && 'MINGW64' || 'MINGW32' }} install: >- ${{ matrix.arch == 'x86_64' && 'mingw-w64-x86_64-toolchain' || 'mingw-w64-i686-toolchain' }} - name: Build ip2net, mdig shell: msys2 {0} run: | export CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}" mkdir -p output cd zapret mingw32-make -C ip2net win mingw32-make -C mdig win cp -a {ip2net/ip2net,mdig/mdig}.exe ../output - name: Restore psmisc from cache id: cache-restore-psmisc uses: actions/cache/restore@v4 with: path: ${{ github.workspace }}/psmisc key: psmisc-${{ matrix.arch }} - name: Set up Cygwin env: PACKAGES: ${{ steps.cache-restore-psmisc.outputs.cache-hit != 'true' && 'cygport gettext-devel libiconv-devel libncurses-devel' || null }} uses: cygwin/cygwin-install-action@v4 with: platform: ${{ matrix.arch }} site: ${{ matrix.arch == 'x86_64' && 'http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215' || null }} check-sig: ${{ matrix.arch == 'x86_64' && 'false' || null }} packages: >- gcc-core make zlib-devel zip unzip wget ${{ env.PACKAGES }} - name: Build psmisc if: steps.cache-restore-psmisc.outputs.cache-hit != 'true' env: URL: https://mirrors.kernel.org/sourceware/cygwin/x86_64/release/psmisc shell: C:\cygwin\bin\bash.exe -eo pipefail '{0}' run: >- export MAKEFLAGS=-j$(nproc) && mkdir -p psmisc && cd psmisc && wget -qO- ${URL} | grep -Po 'href=\"\Kpsmisc-(\d+\.)+\d+.+src\.tar\.xz(?=\")' | xargs -I{} wget -O- ${URL}/{} | tar -xJ && cd psmisc-*.src && echo CYGCONF_ARGS+=\" --disable-dependency-tracking --disable-nls\" >> psmisc.cygport && cygport psmisc.cygport prep compile install - name: Save psmisc to cache if: steps.cache-restore-psmisc.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: ${{ github.workspace }}/psmisc key: psmisc-${{ matrix.arch }} - name: Build winws env: TARGET: ${{ matrix.arch == 'x86_64' && 'cygwin' || 'cygwin32' }} shell: C:\cygwin\bin\bash.exe -eo pipefail '{0}' run: >- export MAKEFLAGS=-j$(nproc) && export CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}" && cd zapret && make -C nfq ${TARGET} && cp -a nfq/winws.exe ../output - name: Create zip env: BITS: ${{ matrix.arch == 'x86_64' && '64' || '32' }} DIR: ${{ matrix.arch == 'x86_64' && 'x64' || 'x86' }} shell: C:\cygwin\bin\bash.exe -e '{0}' run: >- cp -a -t output psmisc/psmisc-*.src/psmisc-*/inst/usr/bin/killall.exe /usr/bin/cygwin1.dll && wget -O WinDivert.zip https://github.com/basil00/WinDivert/releases/download/v2.2.2/WinDivert-2.2.2-A.zip && unzip -j WinDivert.zip "*/${DIR}/WinDivert.dll" "*/${DIR}/WinDivert${BITS}.sys" -d output && zip zapret-win-${{ matrix.arch }}.zip -j output/* - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: zapret-win-${{ matrix.arch }} path: zapret-*.zip if-no-files-found: error build-android: name: Android ${{ matrix.abi }} runs-on: ubuntu-latest strategy: matrix: include: - abi: armeabi-v7a target: armv7a-linux-androideabi - abi: arm64-v8a target: aarch64-linux-android - abi: x86 target: i686-linux-android - abi: x86_64 target: x86_64-linux-android steps: - name: Checkout uses: actions/checkout@v4 with: path: zapret - name: Build env: ABI: ${{ matrix.abi }} API: 21 TARGET: ${{ matrix.target }} GH_TOKEN: ${{ github.token }} run: | DEPS_DIR=$GITHUB_WORKSPACE/deps export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 export CC="$TOOLCHAIN/bin/clang --target=$TARGET$API" export AR=$TOOLCHAIN/bin/llvm-ar export AS=$CC export LD=$TOOLCHAIN/bin/ld export RANLIB=$TOOLCHAIN/bin/llvm-ranlib export STRIP=$TOOLCHAIN/bin/llvm-strip export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig case "$ABI" in armeabi-v7a) CPU="-mthumb" ;; arm64-v8a) PAGESIZE="-Wl,-z,max-page-size=16384" ;; esac # netfilter libs wget -qO- https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj wget -qO- https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj wget -qO- https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj patch -p1 -d libnetfilter_queue-* -i ../zapret/.github/workflows/libnetfilter_queue-android.patch for i in libmnl libnfnetlink libnetfilter_queue ; do ( cd $i-* CFLAGS="$CPU -Os -flto=auto -Wno-implicit-function-declaration" \ ./configure --prefix= --host=$TARGET --enable-static --disable-shared --disable-dependency-tracking make install -j$(nproc) DESTDIR=$DEPS_DIR ) sed -i "s|^prefix=.*|prefix=$DEPS_DIR|g" $DEPS_DIR/lib/pkgconfig/$i.pc done # zapret CFLAGS="$CPU -DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -I$DEPS_DIR/include" \ LDFLAGS="-L$DEPS_DIR/lib $PAGESIZE" \ make -C zapret android -j$(nproc) # strip unwanted ELF sections to prevent warnings on old Android versions gh api repos/termux/termux-elf-cleaner/releases/latest --jq '.tag_name' |\ xargs -I{} wget -O elf-cleaner https://github.com/termux/termux-elf-cleaner/releases/download/{}/termux-elf-cleaner chmod +x elf-cleaner ./elf-cleaner --api-level $API zapret/binaries/my/* zip zapret-android-$ABI.zip -j zapret/binaries/my/* - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: zapret-android-${{ matrix.abi }} path: zapret-*.zip if-no-files-found: error release: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: [ build-linux, build-windows, build-macos, build-freebsd, build-android ] permissions: contents: write runs-on: ubuntu-latest env: repo_dir: zapret-${{ github.ref_name }} steps: - name: Checkout uses: actions/checkout@v4 with: path: ${{ env.repo_dir }} - name: Download artifacts uses: actions/download-artifact@v4 id: bins with: path: ${{ env.repo_dir }}/binaries pattern: zapret-* - name: Install upx uses: crazy-max/ghaction-upx@v3 with: install-only: true version: v4.2.4 - name: Prepare binaries shell: bash run: | cd ${{ steps.bins.outputs.download-path }} run_upx() { upx --best --lzma $@ || true } run_dir() { for f in $dir/* ; do # extract binaries case $f in *.tar.xz ) tar -C $dir -xvf $f && rm $f if [[ $dir == *-linux-x86_64 ]]; then tar -C $dir -czvf $dir/tpws_wsl.tgz tpws run_upx $dir/* elif [[ $dir =~ linux ]] && [[ $dir != *-linux-mips64 ]] && [[ $dir != *-linux-lexra ]]; then run_upx $dir/* fi ;; *.zip ) unzip $f -d $dir && rm $f if [[ $dir =~ win ]]; then chmod -x $dir/* fi ;; esac done mv $dir $1 } for dir in * ; do if [ -d $dir ]; then echo "Processing $dir" case $dir in *-android-arm64-v8a ) run_dir android-arm64 ;; *-android-armeabi-v7a ) run_dir android-arm ;; *-android-x86 ) run_dir android-x86 ;; *-android-x86_64 ) run_dir android-x86_64 ;; *-freebsd-x86_64 ) run_dir freebsd-x86_64 ;; *-linux-arm ) run_dir linux-arm ;; *-linux-arm64 ) run_dir linux-arm64 ;; *-linux-mips64 ) run_dir linux-mips64 ;; *-linux-mipselsf ) run_dir linux-mipsel ;; *-linux-mipssf ) run_dir linux-mips ;; *-linux-ppc ) run_dir linux-ppc ;; *-linux-x86 ) run_dir linux-x86 ;; *-linux-x86_64 ) run_dir linux-x86_64 ;; *-linux-lexra ) run_dir linux-lexra ;; *-mac-x64 ) run_dir mac64 ;; *-win-x86 ) run_dir windows-x86 ;; *-win-x86_64 ) run_dir windows-x86_64 ;; esac fi done ls -lhR - name: Create release bundles run: | rm -rf ${{ env.repo_dir }}/.git* find ${{ env.repo_dir }}/binaries -type f -exec sha256sum {} \; >sha256sum.txt tar --owner=0 --group=0 -czf ${{ env.repo_dir }}.tar.gz ${{ env.repo_dir }} zip -qr ${{ env.repo_dir }}.zip ${{ env.repo_dir }} ( cd ${{ env.repo_dir }} rm -rf binaries/{android*,freebsd*,mac*,win*,x86_64/tpws_wsl.tgz} \ init.d/{openrc,macos,pfsense,runit,s6,systemd,windivert.filter.examples} \ tpws nfq ip2net mdig docs files/huawei Makefile ) tar --owner=0 --group=0 -czf ${{ env.repo_dir }}-openwrt-embedded.tar.gz ${{ env.repo_dir }} - name: Upload release assets uses: softprops/action-gh-release@v2 with: fail_on_unmatched_files: true prerelease: false draft: false body: | ### zapret ${{ github.ref_name }} files: | zapret*.tar.gz zapret*.zip sha256sum.txt ================================================ FILE: .github/workflows/libnetfilter_queue-android.patch ================================================ --- a/src/extra/pktbuff.c +++ b/src/extra/pktbuff.c @@ -14,7 +14,7 @@ #include /* for memcpy */ #include -#include +#include #include #include --- a/src/nlmsg.c +++ b/src/nlmsg.c @@ -21,7 +21,7 @@ #include -#include +// #include #include "internal.h" --- a/src/extra/tcp.c +++ b/src/extra/tcp.c @@ -139,12 +139,16 @@ void nfq_tcp_compute_checksum_ipv6(struc * (union is compatible to any of its members) * This means this part of the code is -fstrict-aliasing safe now. */ +#ifndef __ANDROID__ union tcp_word_hdr { struct tcphdr hdr; uint32_t words[5]; }; +#endif +#ifndef tcp_flag_word #define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words[3]) +#endif /** * nfq_pkt_snprintf_tcp_hdr - print tcp header into one buffer in a humnan ================================================ FILE: .gitignore ================================================ /config ip2net/ip2net mdig/mdig nfq/dvtws nfq/nfqws nfq/winws.exe nfq/WinDivert* tpws/tpws binaries/my/ ipset/zapret-ip*.txt ipset/zapret-ip*.gz ipset/zapret-hosts*.txt ipset/zapret-hosts*.gz ================================================ FILE: Makefile ================================================ DIRS := nfq tpws ip2net mdig DIRS_MAC := tpws ip2net mdig TGT := binaries/my all: clean @mkdir -p "$(TGT)"; \ for dir in $(DIRS); do \ find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ $(MAKE) -C "$$dir" || exit; \ for exe in "$$dir/"*; do \ if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ mv -f "$$exe" "${TGT}" ; \ ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ fi \ done \ done systemd: clean @mkdir -p "$(TGT)"; \ for dir in $(DIRS); do \ find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ $(MAKE) -C "$$dir" systemd || exit; \ for exe in "$$dir/"*; do \ if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ mv -f "$$exe" "${TGT}" ; \ ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ fi \ done \ done android: clean @mkdir -p "$(TGT)"; \ for dir in $(DIRS); do \ find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ $(MAKE) -C "$$dir" android || exit; \ for exe in "$$dir/"*; do \ if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ mv -f "$$exe" "${TGT}" ; \ ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ fi \ done \ done bsd: clean @mkdir -p "$(TGT)"; \ for dir in $(DIRS); do \ find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ $(MAKE) -C "$$dir" bsd || exit; \ for exe in "$$dir/"*; do \ if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ mv -f "$$exe" "${TGT}" ; \ ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ fi \ done \ done mac: clean @mkdir -p "$(TGT)"; \ for dir in $(DIRS_MAC); do \ find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ $(MAKE) -C "$$dir" mac || exit; \ for exe in "$$dir/"*; do \ if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ mv -f "$$exe" "${TGT}" ; \ ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ fi \ done \ done clean: @[ -d "$(TGT)" ] && rm -rf "$(TGT)" ; \ for dir in $(DIRS); do \ $(MAKE) -C "$$dir" clean; \ done ================================================ FILE: blockcheck.sh ================================================ #!/bin/sh EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" CURL=${CURL:-curl} [ -f "$ZAPRET_CONFIG" ] || { [ -f "$ZAPRET_CONFIG_DEFAULT" ] && { ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" } } [ -f "$ZAPRET_CONFIG" ] && . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/dialog.sh" . "$ZAPRET_BASE/common/elevate.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/virt.sh" DOMAINS_DEFAULT=${DOMAINS_DEFAULT:-rutracker.org} QNUM=${QNUM:-59780} SOCKS_PORT=${SOCKS_PORT:-1993} TPWS_UID=${TPWS_UID:-1} TPWS_GID=${TPWS_GID:-3003} NFQWS=${NFQWS:-${ZAPRET_BASE}/nfq/nfqws} DVTWS=${DVTWS:-${ZAPRET_BASE}/nfq/dvtws} WINWS=${WINWS:-${ZAPRET_BASE}/nfq/winws} TPWS=${TPWS:-${ZAPRET_BASE}/tpws/tpws} MDIG=${MDIG:-${ZAPRET_BASE}/mdig/mdig} DESYNC_MARK=0x10000000 IPFW_RULE_NUM=${IPFW_RULE_NUM:-1} IPFW_DIVERT_PORT=${IPFW_DIVERT_PORT:-59780} CURL_MAX_TIME=${CURL_MAX_TIME:-2} CURL_MAX_TIME_QUIC=${CURL_MAX_TIME_QUIC:-$CURL_MAX_TIME} CURL_MAX_TIME_DOH=${CURL_MAX_TIME_DOH:-2} MIN_TTL=${MIN_TTL:-1} MAX_TTL=${MAX_TTL:-12} MIN_AUTOTTL_DELTA=${MIN_AUTOTTL_DELTA:-1} MAX_AUTOTTL_DELTA=${MAX_AUTOTTL_DELTA:-5} USER_AGENT=${USER_AGENT:-Mozilla} HTTP_PORT=${HTTP_PORT:-80} HTTPS_PORT=${HTTPS_PORT:-443} QUIC_PORT=${QUIC_PORT:-443} UNBLOCKED_DOM=${UNBLOCKED_DOM:-iana.org} PARALLEL_OUT=/tmp/zapret_parallel SIM_SUCCESS_RATE=${SIM_SUCCESS_RATE:-10} HDRTEMP=/tmp/zapret-hdr NFT_TABLE=blockcheck DNSCHECK_DNS=${DNSCHECK_DNS:-8.8.8.8 1.1.1.1 77.88.8.1} DNSCHECK_DOM=${DNSCHECK_DOM:-pornhub.com ej.ru rutracker.org www.torproject.org bbc.com} DOH_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"} DNSCHECK_DIG1=/tmp/dig1.txt DNSCHECK_DIG2=/tmp/dig2.txt DNSCHECK_DIGS=/tmp/digs.txt IPSET_FILE=/tmp/blockcheck_ipset.txt unset PF_STATUS PF_RULES_SAVE=/tmp/pf-zapret-save.conf unset ALL_PROXY killwait() { # $1 - signal (-9, -2, ...) # $2 - pid kill $1 $2 # suppress job kill message wait $2 2>/dev/null } exitp() { local A [ "$BATCH" = 1 ] || { echo echo press enter to continue read A } exit $1 } pf_is_avail() { [ -c /dev/pf ] } pf_status() { pfctl -qsi | sed -nre "s/^Status: ([^ ]+).*$/\1/p" } pf_is_enabled() { [ "$(pf_status)" = Enabled ] } pf_save() { PF_STATUS=0 pf_is_enabled && PF_STATUS=1 [ "$UNAME" = "OpenBSD" ] && pfctl -sr >"$PF_RULES_SAVE" } pf_restore() { [ -n "$PF_STATUS" ] || return case "$UNAME" in OpenBSD) if [ -f "$PF_RULES_SAVE" ]; then pfctl -qf "$PF_RULES_SAVE" else echo | pfctl -qf - fi ;; Darwin) # it's not possible to save all rules in the right order. hard to reorder. if not ordered pf will refuse to load conf. pfctl -qf /etc/pf.conf ;; esac if [ "$PF_STATUS" = 1 ]; then pfctl -qe else pfctl -qd fi } pf_clean() { rm -f "$PF_RULES_SAVE" } opf_dvtws_anchor() { # $1 - tcp/udp # $2 - port # $3 - ip list local iplist family=inet [ "$IPV" = 6 ] && family=inet6 make_comma_list iplist "$3" echo "set reassemble no" [ "$1" = tcp ] && echo "pass in quick $family proto $1 from {$iplist} port $2 flags SA/SA divert-packet port $IPFW_DIVERT_PORT no state" echo "pass in quick $family proto $1 from {$iplist} port $2 no state" echo "pass out quick $family proto $1 to {$iplist} port $2 divert-packet port $IPFW_DIVERT_PORT no state" echo "pass" } opf_prepare_dvtws() { # $1 - tcp/udp # $2 - port # $3 - ip list opf_dvtws_anchor $1 $2 "$3" | pfctl -qf - pfctl -qe } cleanup() { case "$UNAME" in OpenBSD) pf_clean ;; esac } IPT() { $IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@" } IPT_DEL() { $IPTABLES -C "$@" >/dev/null 2>/dev/null && $IPTABLES -D "$@" } IPT_ADD_DEL() { on_off_function IPT IPT_DEL "$@" } IPFW_ADD() { ipfw -qf add $IPFW_RULE_NUM "$@" } IPFW_DEL() { ipfw -qf delete $IPFW_RULE_NUM 2>/dev/null } ipt6_has_raw() { ip6tables -nL -t raw >/dev/null 2>/dev/null } ipt6_has_frag() { ip6tables -A OUTPUT -m frag 2>/dev/null || return 1 ip6tables -D OUTPUT -m frag 2>/dev/null } ipt_has_nfq() { # cannot just check /proc/net/ip_tables_targets because of iptables-nft or modules not loaded yet iptables -A OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null || return 1 iptables -D OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null return 0 } nft_has_nfq() { local res=1 nft delete table ${NFT_TABLE}_test 2>/dev/null nft add table ${NFT_TABLE}_test 2>/dev/null && { nft add chain ${NFT_TABLE}_test test nft add rule ${NFT_TABLE}_test test queue num $QNUM bypass 2>/dev/null && res=0 nft delete table ${NFT_TABLE}_test } return $res } doh_resolve() { # $1 - ip version 4/6 # $2 - hostname # $3 - doh server URL. use $DOH_SERVER if empty "$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 } doh_find_working() { local doh [ -n "$DOH_SERVER" ] && return 0 echo "* searching working DoH server" DOH_SERVER= for doh in $DOH_SERVERS; do echo -n "$doh : " if doh_resolve 4 iana.org $doh >/dev/null 2>/dev/null; then echo OK DOH_SERVER="$doh" return 0 else echo FAIL fi done echo all DoH servers failed return 1 } mdig_vars() { # $1 - ip version 4/6 # $2 - hostname hostvar=$(echo $2 | sed -e 's/[\./?&#@%*$^:~=!()+-]/_/g') cachevar=DNSCACHE_${hostvar}_$1 countvar=${cachevar}_COUNT eval count=\$${countvar} } mdig_cache() { # $1 - ip version 4/6 # $2 - hostname local hostvar cachevar countvar count ip ips mdig_vars "$@" [ -n "$count" ] || { # windows version of mdig outputs 0D0A line ending. remove 0D. if [ "$SECURE_DNS" = 1 ]; then ips="$(echo $2 | doh_resolve $1 $2 | tr -d '\r' | xargs)" else ips="$(echo $2 | "$MDIG" --family=$1 | tr -d '\r' | xargs)" fi [ -n "$ips" ] || return 1 count=0 for ip in $ips; do eval ${cachevar}_$count=$ip count=$(($count+1)) done eval $countvar=$count } return 0 } mdig_resolve() { # $1 - ip version 4/6 # $2 - var to receive result # $3 - hostname, possibly with uri : rutracker.org/xxx/xxxx local hostvar cachevar countvar count n sdom split_by_separator "$3" / sdom mdig_vars "$1" "$sdom" if [ -n "$count" ]; then n=$(random 0 $(($count-1))) eval $2=\$${cachevar}_$n return 0 else mdig_cache "$1" "$sdom" && mdig_resolve "$1" "$2" "$sdom" fi } mdig_resolve_all() { # $1 - ip version 4/6 # $2 - var to receive result # $3 - hostname local hostvar cachevar countvar count ip__ ips__ n sdom split_by_separator "$3" / sdom mdig_vars "$1" "$sdom" if [ -n "$count" ]; then n=0 while [ "$n" -le $count ]; do eval ip__=\$${cachevar}_$n if [ -n "$ips__" ]; then ips__="$ips__ $ip__" else ips__="$ip__" fi n=$(($n + 1)) done eval $2="\$ips__" return 0 else mdig_cache "$1" "$sdom" && mdig_resolve_all "$1" "$2" "$sdom" fi } netcat_setup() { [ -n "$NCAT" ] || { if exists ncat; then NCAT=ncat elif exists nc; then # busybox netcat does not support any required options is_linked_to_busybox nc && return 1 NCAT=nc else return 1 fi } return 0 } netcat_test() { # $1 - ip # $2 - port local cmd netcat_setup && { cmd="$NCAT -z -w 2 $1 $2" echo $cmd $cmd 2>&1 } } tpws_can_fix_seg() { # fix-seg requires kernel 4.6+ "$TPWS" --port 1 --dry-run --fix-seg >/dev/null 2>/dev/null } check_system() { echo \* checking system UNAME=$(uname) SUBSYS= FIX_SEG= local s # can be passed FWTYPE=iptables to override default nftables preference case "$UNAME" in Linux) PKTWS="$NFQWS" PKTWSD=nfqws if [ -x "$TPWS" ] ; then if tpws_can_fix_seg ; then echo tpws supports --fix-seg on this system FIX_SEG='--fix-seg' else echo tpws does not support --fix-seg on this system fi fi linux_fwtype [ "$FWTYPE" = iptables -o "$FWTYPE" = nftables ] || { echo firewall type $FWTYPE not supported in $UNAME exitp 5 } ;; FreeBSD) PKTWS="$DVTWS" PKTWSD=dvtws FWTYPE=ipfw [ -f /etc/platform ] && read SUBSYS /dev/null ;; *) echo $UNAME not supported exitp 5 esac echo $UNAME${SUBSYS:+/$SUBSYS} detected echo -n 'kernel: ' if [ -f "/proc/version" ]; then cat /proc/version else uname -a fi [ -f /etc/os-release ] && { . /etc/os-release [ -n "$PRETTY_NAME" ] && echo "distro: $PRETTY_NAME" [ -n "$OPENWRT_RELEASE" ] && echo "openwrt release: $OPENWRT_RELEASE" [ -n "$OPENWRT_BOARD" ] && echo "openwrt board: $OPENWRT_BOARD" [ -n "$OPENWRT_ARCH" ] && echo "openwrt arch: $OPENWRT_ARCH" } echo firewall type is $FWTYPE echo CURL=$CURL "$CURL" --version } zp_already_running() { case "$UNAME" in CYGWIN) win_process_exists $PKTWSD || win_process_exists goodbyedpi ;; *) process_exists $PKTWSD || process_exists tpws esac } check_already() { echo \* checking already running DPI bypass processes if zp_already_running; then echo "!!! WARNING. some dpi bypass processes already running !!!" echo "!!! WARNING. blockcheck requires all DPI bypass methods disabled !!!" echo "!!! WARNING. pls stop all dpi bypass instances that may interfere with blockcheck !!!" fi } freebsd_module_loaded() { # $1 - module name kldstat -qm "${1}" } freebsd_modules_loaded() { # $1,$2,$3, ... - module names while [ -n "$1" ]; do freebsd_module_loaded $1 || return 1 shift done return 0 } check_prerequisites() { echo \* checking prerequisites [ "$SKIP_PKTWS" = 1 -o "$UNAME" = Darwin -o -x "$PKTWS" ] && [ "$SKIP_TPWS" = 1 -o "$UNAME" = CYGWIN -o -x "$TPWS" ] && [ -x "$MDIG" ] || { local target case $UNAME in Darwin) target="mac" ;; OpenBSD) target="bsd" ;; esac echo $PKTWS or $TPWS or $MDIG is not available. run \"$ZAPRET_BASE/install_bin.sh\" or \`make -C \"$ZAPRET_BASE\" $target\` exitp 6 } local prog progs="$CURL" [ "$SKIP_PKTWS" = 1 ] || { case "$UNAME" in Linux) case "$FWTYPE" in iptables) ipt_has_nfq || { echo NFQUEUE iptables or ip6tables target is missing. pls install modules. exitp 6 } progs="$progs iptables ip6tables" ;; nftables) nft_has_nfq || { echo nftables queue support is not available. pls install modules. exitp 6 } progs="$progs nft" ;; esac ;; FreeBSD) freebsd_modules_loaded ipfw ipdivert || { echo ipfw or ipdivert kernel module not loaded exitp 6 } [ "$(sysctl -qn net.inet.ip.fw.enable)" = 0 -o "$(sysctl -qn net.inet6.ip6.fw.enable)" = 0 ] && { echo ipfw is disabled. use : ipfw enable firewall exitp 6 } pf_is_avail && { pf_save [ "$SUBSYS" = "pfSense" ] && { # pfsense's ipfw may not work without these workarounds sysctl net.inet.ip.pfil.outbound=ipfw,pf 2>/dev/null sysctl net.inet.ip.pfil.inbound=ipfw,pf 2>/dev/null sysctl net.inet6.ip6.pfil.outbound=ipfw,pf 2>/dev/null sysctl net.inet6.ip6.pfil.inbound=ipfw,pf 2>/dev/null pfctl -qd pfctl -qe pf_restore } } progs="$progs ipfw" ;; OpenBSD|Darwin) pf_is_avail || { echo pf is not available exitp 6 } pf_save progs="$progs pfctl" ;; esac } case "$UNAME" in CYGWIN) SKIP_TPWS=1 ;; esac for prog in $progs; do exists $prog || { echo $prog does not exist. please install exitp 6 } done if exists nslookup; then LOOKUP=nslookup elif exists host; then LOOKUP=host else echo nslookup or host does not exist. please install exitp 6 fi } curl_translate_code() { # $1 - code printf $1 case $1 in 0) printf ": ok" ;; 1) printf ": unsupported protocol" ;; 2) printf ": early initialization code failed" ;; 3) printf ": the URL was not properly formatted" ;; 4) printf ": feature not supported by libcurl" ;; 5) printf ": could not resolve proxy" ;; 6) printf ": could not resolve host" ;; 7) printf ": could not connect" ;; 8) printf ": invalid server reply" ;; 9) printf ": remote access denied" ;; 27) printf ": out of memory" ;; 28) printf ": operation timed out" ;; 35) printf ": SSL connect error" ;; esac } curl_supports_tls13() { local r "$CURL" --tlsv1.3 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null # return code 2 = init failed. likely bad command line options [ $? = 2 ] && return 1 # curl can have tlsv1.3 key present but ssl library without TLS 1.3 support # this is online test because there's no other way to trigger library incompatibility case "$CURL" --tlsv1.3 --max-time 1 -Is -o /dev/null https://iana.org 2>/dev/null r=$? [ $r != 4 -a $r != 35 ] } curl_supports_tlsmax() { # supported only in OpenSSL and LibreSSL "$CURL" --version | grep -Fq -e OpenSSL -e LibreSSL -e BoringSSL -e GnuTLS -e quictls || return 1 # supported since curl 7.54 "$CURL" --tls-max 1.2 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null # return code 2 = init failed. likely bad command line options [ $? != 2 ] } curl_supports_connect_to() { "$CURL" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null [ "$?" != 2 ] } curl_supports_http3() { # if it has http3 : curl: (3) HTTP/3 requested for non-HTTPS URL # otherwise : curl: (2) option --http3-only: is unknown "$CURL" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 --http3-only http://127.0.0.1:65535 2>/dev/null [ "$?" != 2 ] } hdrfile_http_code() { # $1 - hdr file sed -nre '1,1 s/^HTTP\/1\.[0,1] ([0-9]+) .*$/\1/p' "$1" } hdrfile_location() { # $1 - hdr file # some DPIs return CRLF line ending tr -d '\015' <"$1" | sed -nre 's/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ ]*([^ ]*)[ ]*$/\1/p' } curl_with_subst_ip() { # $1 - domain # $2 - port # $3 - ip # $4+ - curl params local ip="$3" case "$ip" in *:*) ip="[$ip]" ;; esac local connect_to="--connect-to $1::$ip${2:+:$2}" arg shift ; shift ; shift; [ "$CURL_VERBOSE" = 1 ] && arg="-v" [ "$CURL_CMD" = 1 ] && echo $CURL ${arg:+$arg }$connect_to "$@" ALL_PROXY="$ALL_PROXY" "$CURL" ${arg:+$arg }$connect_to "$@" } curl_with_dig() { # $1 - ip version : 4/6 # $2 - domain name # $3 - port # $4+ - curl params local dom="$2" port=$3 local sdom suri ip split_by_separator "$dom" / sdom suri mdig_resolve $1 ip $sdom shift ; shift ; shift if [ -n "$ip" ]; then curl_with_subst_ip "$sdom" "$port" "$ip" "$@" else return 6 fi } curl_probe() { # $1 - ip version : 4/6 # $2 - domain name # $3 - port # $4 - subst ip # $5+ - curl params local ipv=$1 dom="$2" port=$3 subst=$4 shift; shift; shift; shift if [ -n "$subst" ]; then curl_with_subst_ip "$dom" $port $subst "$@" else curl_with_dig $ipv "$dom" $port "$@" fi } curl_test_http() { # $1 - ip version : 4/6 # $2 - domain name # $3 - subst ip # $4 - "detail" - detail info local code loc hdrt="${HDRTEMP}_${!:-$$}.txt" dom="$(tolower "$2")" curl_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 || { code=$? rm -f "$hdrt" return $code } if [ "$4" = "detail" ] ; then head -n 1 "$hdrt" grep "^[lL]ocation:" "$hdrt" else code=$(hdrfile_http_code "$hdrt") [ "$code" = 301 -o "$code" = 302 -o "$code" = 307 -o "$code" = 308 ] && { loc=$(hdrfile_location "$hdrt") split_by_separator "$dom" / dom tolower "$loc" | grep -qE "^https?://.*$dom(/|$)" || tolower "$loc" | grep -vqE '^https?://' || { echo suspicious redirection $code to : $loc rm -f "$hdrt" return 254 } } fi rm -f "$hdrt" [ "$code" = 400 ] && { # this can often happen if the server receives fake packets it should not receive echo http code $code. likely the server receives fakes. return 254 } return 0 } curl_test_https_tls12() { # $1 - ip version : 4/6 # $2 - domain name # $3 - subst ip # do not use tls 1.3 to make sure server certificate is not encrypted curl_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 } curl_test_https_tls13() { # $1 - ip version : 4/6 # $2 - domain name # $3 - subst ip # force TLS1.3 mode curl_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 } curl_test_http3() { # $1 - ip version : 4/6 # $2 - domain name # force QUIC only mode without tcp curl_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 } ipt_aux_scheme() { # $1 - 1 - add , 0 - del # $2 - tcp/udp # $3 - port # to avoid possible INVALID state drop [ "$2" = tcp ] && IPT_ADD_DEL $1 INPUT -p $2 --sport $3 ! --syn -j ACCEPT local icmp_filter="-p icmp -m icmp --icmp-type" [ "$IPV" = 6 ] && icmp_filter="-p icmpv6 -m icmp6 --icmpv6-type" IPT_ADD_DEL $1 INPUT $icmp_filter time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP # for strategies with incoming packets involved (autottl) IPT_ADD_DEL $1 OUTPUT -p $2 --dport $3 -m conntrack --ctstate INVALID -j ACCEPT if [ "$IPV" = 6 -a -n "$IP6_DEFRAG_DISABLE" ]; then # the only way to reliable disable ipv6 defrag. works only in 4.16+ kernels IPT_ADD_DEL $1 OUTPUT -t raw -p $2 -m frag -j CT --notrack elif [ "$IPV" = 4 ]; then # enable fragments IPT_ADD_DEL $1 OUTPUT -f -j ACCEPT fi # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) # raw table may not be present IPT_ADD_DEL $1 OUTPUT -t raw -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CT --notrack } ipt_scheme() { # $1 - tcp/udp # $2 - port # $3 - ip list local ip $IPTABLES -t mangle -N blockcheck_output 2>/dev/null $IPTABLES -t mangle -F blockcheck_output IPT OUTPUT -t mangle -j blockcheck_output # prevent loop $IPTABLES -t mangle -A blockcheck_output -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j RETURN $IPTABLES -t mangle -A blockcheck_output ! -p $1 -j RETURN $IPTABLES -t mangle -A blockcheck_output -p $1 ! --dport $2 -j RETURN for ip in $3; do $IPTABLES -t mangle -A blockcheck_output -d $ip -j CONNMARK --or-mark $DESYNC_MARK $IPTABLES -t mangle -A blockcheck_output -d $ip -j NFQUEUE --queue-num $QNUM done ipt_aux_scheme 1 $1 $2 } nft_scheme() { # $1 - tcp/udp # $2 - port # $3 - ip list local iplist ipver=$IPV [ "$IPV" = 6 ] || ipver= make_comma_list iplist $3 nft add table inet $NFT_TABLE nft "add chain inet $NFT_TABLE postnat { type filter hook postrouting priority 102; }" nft "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" # for strategies with incoming packets involved (autottl) nft "add chain inet $NFT_TABLE prenat { type filter hook prerouting priority -102; }" # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) nft "add chain inet $NFT_TABLE predefrag { type filter hook output priority -402; }" nft "add rule inet $NFT_TABLE predefrag meta nfproto ipv${IPV} mark and $DESYNC_MARK !=0 notrack" [ "$IPV" = 4 ] && { nft "add rule inet $NFT_TABLE prenat icmp type time-exceeded ct mark and $DESYNC_MARK != 0 drop" nft "add rule inet $NFT_TABLE prenat icmp type time-exceeded ct state invalid drop" } [ "$IPV" = 6 ] && { nft "add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct mark and $DESYNC_MARK != 0 drop" nft "add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct state invalid drop" } } pktws_ipt_prepare() { # $1 - tcp/udp # $2 - port # $3 - ip list local ip case "$FWTYPE" in iptables) ipt_scheme $1 $2 "$3" ;; nftables) nft_scheme $1 $2 "$3" ;; ipfw) # disable PF to avoid interferences pf_is_avail && pfctl -qd for ip in $3; do IPFW_ADD divert $IPFW_DIVERT_PORT $1 from me to $ip $2 proto ip${IPV} out not diverted done ;; opf) opf_prepare_dvtws $1 $2 "$3" ;; windivert) WF="--wf-l3=ipv${IPV} --wf-${1}=$2" rm -f "$IPSET_FILE" for ip in $3; do echo $ip >>"$IPSET_FILE" done ;; esac } pktws_ipt_unprepare() { # $1 - tcp/udp # $2 - port case "$FWTYPE" in iptables) ipt_aux_scheme 0 $1 $2 IPT_DEL OUTPUT -t mangle -j blockcheck_output $IPTABLES -t mangle -F blockcheck_output 2>/dev/null $IPTABLES -t mangle -X blockcheck_output 2>/dev/null ;; nftables) nft delete table inet $NFT_TABLE 2>/dev/null ;; ipfw) IPFW_DEL pf_is_avail && pf_restore ;; opf) pf_restore ;; windivert) unset WF rm -f "$IPSET_FILE" ;; esac } pktws_ipt_prepare_tcp() { # $1 - port # $2 - ip list local ip iplist ipver pktws_ipt_prepare tcp $1 "$2" # for autottl mode case "$FWTYPE" in iptables) $IPTABLES -N blockcheck_input -t mangle 2>/dev/null $IPTABLES -F blockcheck_input -t mangle 2>/dev/null IPT INPUT -t mangle -j blockcheck_input $IPTABLES -t mangle -A blockcheck_input ! -p tcp -j RETURN $IPTABLES -t mangle -A blockcheck_input -p tcp ! --sport $1 -j RETURN $IPTABLES -t mangle -A blockcheck_input -p tcp ! --tcp-flags SYN,ACK SYN,ACK -j RETURN for ip in $2; do $IPTABLES -A blockcheck_input -t mangle -s $ip -j NFQUEUE --queue-num $QNUM done ;; nftables) ipver=$IPV [ "$IPV" = 6 ] || ipver= make_comma_list iplist $2 nft "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" ;; ipfw) for ip in $2; do IPFW_ADD divert $IPFW_DIVERT_PORT tcp from $ip $1 to me proto ip${IPV} tcpflags syn,ack in not diverted done ;; esac } pktws_ipt_unprepare_tcp() { # $1 - port pktws_ipt_unprepare tcp $1 case "$FWTYPE" in iptables) IPT_DEL INPUT -t mangle -j blockcheck_input $IPTABLES -t mangle -F blockcheck_input 2>/dev/null $IPTABLES -t mangle -X blockcheck_input 2>/dev/null ;; esac } pktws_ipt_prepare_udp() { # $1 - port # $2 - ip list pktws_ipt_prepare udp $1 "$2" } pktws_ipt_unprepare_udp() { # $1 - port pktws_ipt_unprepare udp $1 } pktws_start() { case "$UNAME" in Linux) "$NFQWS" --uid $TPWS_UID:$TPWS_GID --dpi-desync-fwmark=$DESYNC_MARK --qnum=$QNUM "$@" >/dev/null & ;; FreeBSD|OpenBSD) "$DVTWS" --port=$IPFW_DIVERT_PORT "$@" >/dev/null & ;; CYGWIN) "$WINWS" $WF --ipset="$IPSET_FILE" "$@" >/dev/null & ;; esac PID=$! # give some time to initialize minsleep } tpws_start() { local uid [ -n "$HAVE_ROOT" ] && uid="--uid $TPWS_UID:$TPWS_GID" "$TPWS" $uid --socks --bind-addr=127.0.0.1 --port=$SOCKS_PORT "$@" >/dev/null & PID=$! # give some time to initialize minsleep } ws_kill() { [ -z "$PID" ] || { killwait -9 $PID 2>/dev/null PID= } } check_domain_port_block() { # $1 - domain # $2 - port local ip ips echo echo \* port block tests ipv$IPV $1:$2 if netcat_setup; then mdig_resolve_all $IPV ips $1 if [ -n "$ips" ]; then for ip in $ips; do if netcat_test $ip $2; then echo $ip connects else echo $ip does not connect. netcat code $? fi done else echo "ipv${IPV} $1 does not resolve" fi else echo suitable netcat not found. busybox nc is not supported. pls install nmap ncat or openbsd netcat. fi } curl_test() { # $1 - test function # $2 - domain # $3 - subst ip # $4 - param of test function local code=0 n=0 p pids if [ "$PARALLEL" = 1 ]; then rm -f "${PARALLEL_OUT}"* for n in $(seq -s ' ' 1 $REPEATS); do $1 "$IPV" $2 $3 "$4" >"${PARALLEL_OUT}_$n" & pids="${pids:+$pids }$!" done n=1 for p in $pids; do [ $REPEATS -gt 1 ] && printf "[attempt $n] " if wait $p; then [ $REPEATS -gt 1 ] && echo 'AVAILABLE' else code=$? cat "${PARALLEL_OUT}_$n" fi n=$(($n+1)) done rm -f "${PARALLEL_OUT}"* else while [ $n -lt $REPEATS ]; do n=$(($n+1)) [ $REPEATS -gt 1 ] && printf "[attempt $n] " if $1 "$IPV" $2 $3 "$4" ; then [ $REPEATS -gt 1 ] && echo 'AVAILABLE' else code=$? [ "$SCANLEVEL" = quick ] && break fi done fi [ "$4" = detail ] || { if [ $code = 254 ]; then echo "UNAVAILABLE" elif [ $code = 0 ]; then echo '!!!!! AVAILABLE !!!!!' else echo "UNAVAILABLE code=$code" fi } return $code } ws_curl_test() { # $1 - ws start function # $2 - test function # $3 - domain # $4,$5,$6, ... - ws params local code ws_start=$1 testf=$2 dom="$3" [ "$SIMULATE" = 1 ] && { n=$(random 0 99) if [ "$n" -lt "$SIM_SUCCESS_RATE" ]; then echo "SUCCESS" return 0 else echo "FAILED" return 7 fi } shift shift shift $ws_start "$@" curl_test $testf "$dom" code=$? ws_kill return $code } tpws_curl_test() { # $1 - test function # $2 - domain # $3,$4,$5, ... - tpws params echo - $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"} local ALL_PROXY="socks5://127.0.0.1:$SOCKS_PORT" ws_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"} local testf=$1 dom="$2" strategy code=$? [ "$code" = 0 ] && { shift; shift; strategy="$@" strategy_append_extra_tpws report_append "$dom" "$testf ipv${IPV}" "tpws ${WF:+$WF }$strategy" } return $code } pktws_curl_test() { # $1 - test function # $2 - domain # $3,$4,$5, ... - nfqws/dvtws params local testf=$1 dom="$2" strategy code shift; shift; echo - $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"} ws_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"} code=$? [ "$code" = 0 ] && { strategy="$@" strategy_append_extra_pktws report_append "$dom" "$testf ipv${IPV}" "$PKTWSD ${WF:+$WF }$strategy" } return $code } strategy_append_extra_pktws() { strategy="${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"}}" } strategy_append_extra_tpws() { strategy="${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"}}" } xxxws_curl_test_update() { # $1 - xxx_curl_test function # $2 - test function # $3 - domain # $4,$5,$6, ... - nfqws/dvtws params local code xxxf=$1 testf=$2 dom="$3" shift shift shift $xxxf $testf "$dom" "$@" code=$? [ $code = 0 ] && strategy="${strategy:-$@}" return $code } pktws_curl_test_update() { xxxws_curl_test_update pktws_curl_test "$@" } tpws_curl_test_update() { xxxws_curl_test_update tpws_curl_test "$@" } report_append() { # $1 - domain # $2 - test function + ipver # $3 - value local hashstr hash hashvar hashcountvar val ct # save resources if only one domain [ "$DOMAINS_COUNT" -gt 1 ] && { hashstr="$2 : $3" hash="$(echo -n "$hashstr" | md5f)" hashvar=RESHASH_${hash} hashcountvar=${hashvar}_COUNTER NRESHASH=${NRESHASH:-0} eval val="\$$hashvar" if [ -n "$val" ]; then eval ct="\$$hashcountvar" ct=$(($ct + 1)) eval $hashcountvar="\$ct" else eval $hashvar=\"$hashstr\" eval $hashcountvar=1 eval RES_$NRESHASH="\$hash" NRESHASH=$(($NRESHASH+1)) fi } NREPORT=${NREPORT:-0} eval REPORT_${NREPORT}=\"$2 $1 : $3\" NREPORT=$(($NREPORT+1)) } report_print() { local n=0 s NREPORT=${NREPORT:-0} while [ $n -lt $NREPORT ]; do eval s=\"\${REPORT_$n}\" echo $s n=$(($n+1)) done } result_intersection_print() { local n=0 hash hashvar hashcountvar ct val while : ; do eval hash=\"\$RES_$n\" [ -n "$hash" ] || break hashvar=RESHASH_${hash} hashcountvar=${hashvar}_COUNTER eval ct=\"\$$hashcountvar\" [ "$ct" = "$DOMAINS_COUNT" ] && { eval val=\"\$$hashvar\" echo "$val" } n=$(($n + 1)) done } report_strategy() { # $1 - test function # $2 - domain # $3 - daemon echo if [ -n "$strategy" ]; then # trim spaces at the end strategy="$(echo "$strategy" | xargs)" echo "!!!!! $1: working strategy found for ipv${IPV} $2 : $3 $strategy !!!!!" echo return 0 else echo "$1: $3 strategy for ipv${IPV} $2 not found" echo report_append "$2" "$1 ipv${IPV}" "$3 not working" return 1 fi } test_has_fakedsplit() { contains "$1" fakedsplit || contains "$1" fakeddisorder } test_has_split() { contains "$1" multisplit || contains "$1" multidisorder || test_has_fakedsplit "$1" } test_has_hostfakesplit() { contains "$1" hostfakesplit } test_has_fake() { [ "$1" = fake ] || starts_with "$1" fake, } warn_fool() { case "$1" in md5sig) echo 'WARNING ! although md5sig fooling worked it will not work on all sites. it typically works only on linux servers.' [ "$2" = "fakedsplit" -o "$2" = "fakeddisorder" ] && \ echo "WARNING ! fakedsplit/fakeddisorder with md5sig fooling and low split position causes MTU overflow with multi-segment TLS (kyber)" ;; datanoack) 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.' ;; ts) 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.' esac } pktws_curl_test_update_vary() { # $1 - test function # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $3 - domain # $4 - desync mode # $5,$6,... - strategy local testf=$1 sec=$2 domain=$3 desync=$4 proto splits= pos fake ret=1 local fake1=- fake2=- fake3=- fake4=- shift; shift; shift; shift proto=http [ "$sec" = 0 ] || proto=tls test_has_fake $desync && { fake1="--dpi-desync-fake-$proto=0x00000000" [ "$sec" = 0 ] || { fake2='--dpi-desync-fake-tls=0x00000000 --dpi-desync-fake-tls=! --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid' # this splits actual fake to '1603' and modified standard fake from offset 2 fake3='--dpi-desync-fake-tls=0x1603 --dpi-desync-fake-tls=!+2 --dpi-desync-fake-tls-mod=rnd,dupsid,rndsni --dpi-desync-fake-tcp-mod=seq' fake4='--dpi-desync-fake-tls-mod=rnd,dupsid,rndsni,padencap' } } if test_has_fakedsplit $desync ; then splits="method+2 midsld" [ "$sec" = 0 ] || splits="1 midsld" # do not send fake first fake1='--dpi-desync-fakedsplit-mod=altorder=1' elif test_has_split $desync ; then splits="method+2 midsld" [ "$sec" = 0 ] || splits="1 midsld 1,midsld" fi test_has_hostfakesplit $desync && { fake1="--dpi-desync-hostfakesplit-mod=altorder=1" fake2="--dpi-desync-hostfakesplit-midhost=midsld" fake3="--dpi-desync-hostfakesplit-mod=altorder=1 --dpi-desync-hostfakesplit-midhost=midsld" } for fake in '' "$fake1" "$fake2" "$fake3" "$fake4" ; do [ "$fake" = "-" ] && continue if [ -n "$splits" ]; then for pos in $splits ; do pktws_curl_test_update $testf "$domain" --dpi-desync=$desync "$@" --dpi-desync-split-pos=$pos $fake && { [ "$SCANLEVEL" = force ] || return 0 ret=0 } done else pktws_curl_test_update $testf "$domain" --dpi-desync=$desync "$@" $fake && { [ "$SCANLEVEL" = force ] || return 0 ret=0 } fi done return $ret } pktws_check_domain_http_bypass_() { # $1 - test function # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $3 - domain local ok ttls attls s f f2 e desync pos fooling frag sec="$2" delta orig splits local need_split need_disorder need_fakedsplit need_hostfakesplit need_fakeddisorder need_fake need_wssize local splits_http='method+2 midsld method+2,midsld' local 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' [ "$sec" = 0 ] && { for s in '--hostcase' '--hostspell=hoSt' '--hostnospace' '--domcase' '--methodeol'; do pktws_curl_test_update $1 $3 $s && [ "$SCANLEVEL" = quick ] && return done } ttls=$(seq -s ' ' $MIN_TTL $MAX_TTL) attls=$(seq -s ' ' $MIN_AUTOTTL_DELTA $MAX_AUTOTTL_DELTA) need_wssize=1 for e in '' '--wssize 1:6'; do need_split= need_disorder= [ -n "$e" ] && { pktws_curl_test_update $1 $3 $e && [ "$SCANLEVEL" = quick ] && return } for desync in multisplit multidisorder; do ok=0 splits="$splits_http" [ "$sec" = 0 ] || splits="$splits_tls" for pos in $splits; do pktws_curl_test_update $1 $3 --dpi-desync=$desync --dpi-desync-split-pos=$pos $e && { [ "$SCANLEVEL" = quick ] && return ok=1 need_wssize=0 [ "$SCANLEVEL" = force ] || break } done [ "$ok" = 1 -a "$SCANLEVEL" != force ] || { case $desync in multisplit) need_split=1 ;; multidisorder) need_disorder=1 ;; esac } done need_fakedsplit=1 need_hostfakesplit=1 need_fakeddisorder=1 need_fake=1 for desync in fake ${need_split:+fakedsplit fake,multisplit fake,fakedsplit hostfakesplit fake,hostfakesplit} ${need_disorder:+fakeddisorder fake,multidisorder fake,fakeddisorder}; do [ "$need_fake" = 0 ] && test_has_fake "$desync" && continue [ "$need_fakedsplit" = 0 ] && contains "$desync" fakedsplit && continue [ "$need_hostfakesplit" = 0 ] && contains "$desync" hostfakesplit && continue [ "$need_fakeddisorder" = 0 ] && contains "$desync" fakeddisorder && continue ok=0 for ttl in $ttls; do # orig-ttl=1 with start/cutoff limiter drops empty ACK packet in response to SYN,ACK. it does not reach DPI or server. # missing ACK is transmitted in the first data packet of TLS/HTTP proto for f in '' '--orig-ttl=1 --orig-mod-start=s1 --orig-mod-cutoff=d1'; do pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=$ttl $f $e && { [ "$SCANLEVEL" = quick ] && return ok=1 need_wssize=0 [ "$SCANLEVEL" = force ] || break } done [ "$ok" = 1 ] && break done # only skip tests if TTL succeeded. do not skip if TTL failed but fooling succeeded [ $ok = 1 -a "$SCANLEVEL" != force ] && { [ "$desync" = fake ] && need_fake=0 [ "$desync" = fakedsplit ] && need_fakedsplit=0 [ "$desync" = hostfakesplit ] && need_hostfakesplit=0 [ "$desync" = fakeddisorder ] && need_fakeddisorder=0 } f= [ "$UNAME" = "OpenBSD" ] || f="badsum" f="$f badseq datanoack ts md5sig" [ "$IPV" = 6 ] && f="$f hopbyhop hopbyhop2" for fooling in $f; do ok=0 f2= pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling $e && { warn_fool $fooling $desync [ "$SCANLEVEL" = quick ] && return need_wssize=0 ok=1 } [ "$fooling" = badseq ] && { [ "$ok" = 1 -a "$SCANLEVEL" != force ] && continue # --dpi-desync-badseq-increment=0 leaves modified by default ack increment pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling --dpi-desync-badseq-increment=0 $e && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } } [ "$fooling" = md5sig ] && { [ "$ok" = 1 -a "$SCANLEVEL" != force ] && continue pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling --dup=1 --dup-cutoff=n2 --dup-fooling=md5sig $e && { warn_fool $fooling $desync echo "HINT ! To avoid possible 1 sec server response delay use --dup-ttl or --dup-autottl and block ICMP time exceeded" [ "$SCANLEVEL" = quick ] && return need_wssize=0 } } done done [ "$IPV" = 6 ] && { f="hopbyhop ${need_split:+hopbyhop,multisplit} ${need_disorder:+hopbyhop,multidisorder} destopt ${need_split:+destopt,multisplit} ${need_disorder:+destopt,multidisorder}" [ -n "$IP6_DEFRAG_DISABLE" ] && f="$f ipfrag1 ${need_split:+ ipfrag1,multisplit} ${need_disorder:+ ipfrag1,multidisorder}" for desync in $f; do pktws_curl_test_update_vary $1 $2 $3 $desync $e && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } done } [ "$need_split" = 1 ] && { # relative markers can be anywhere, even in subsequent packets. first packet can be MTU-full. # make additional split pos "10" to guarantee enough space for seqovl and likely to be before midsld,sniext,... # method is always expected in the beginning of the first packet f="method+2 method+2,midsld" [ "$sec" = 0 ] || f="10 10,sniext+1 10,sniext+4 10,midsld" for pos in $f; do pktws_curl_test_update $1 $3 --dpi-desync=multisplit --dpi-desync-split-pos=$pos --dpi-desync-split-seqovl=1 $e && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } done [ "$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 && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } } [ "$need_disorder" = 1 ] && { if [ "$sec" = 0 ]; then for pos in 'method+1 method+2' 'midsld-1 midsld' 'method+1 method+2,midsld'; do f="$(extract_arg 1 $pos)" f2="$(extract_arg 2 $pos)" pktws_curl_test_update $1 $3 --dpi-desync=multidisorder --dpi-desync-split-pos=$f2 --dpi-desync-split-seqovl=$f $e && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } done else for pos in '1 2' 'sniext sniext+1' 'sniext+3 sniext+4' 'midsld-1 midsld' '1 2,midsld'; do f=$(extract_arg 1 $pos) f2=$(extract_arg 2 $pos) pktws_curl_test_update $1 $3 --dpi-desync=multidisorder --dpi-desync-split-pos=$f2 --dpi-desync-split-seqovl=$f $e && { [ "$SCANLEVEL" = quick ] && return need_wssize=0 } done fi } need_fakedsplit=1 need_fakeddisorder=1 need_hostfakesplit=1 need_fake=1 for desync in fake ${need_split:+fakedsplit fake,multisplit fake,fakedsplit hostfakesplit fake,hostfakesplit} ${need_disorder:+fakeddisorder fake,multidisorder fake,fakeddisorder}; do [ "$need_fake" = 0 ] && test_has_fake "$desync" && continue [ "$need_fakedsplit" = 0 ] && contains "$desync" fakedsplit && continue [ "$need_hostfakesplit" = 0 ] && contains "$desync" hostfakesplit && continue [ "$need_fakeddisorder" = 0 ] && contains "$desync" fakeddisorder && continue ok=0 # orig-ttl=1 with start/cutoff limiter drops empty ACK packet in response to SYN,ACK. it does not reach DPI or server. # missing ACK is transmitted in the first data packet of TLS/HTTP proto for delta in $attls; do for f in '' '--orig-ttl=1 --orig-mod-start=s1 --orig-mod-cutoff=d1'; do pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=1 --dpi-desync-autottl=-$delta $f $e && ok=1 [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break done done [ "$SCANLEVEL" = force ] && { for orig in 1 2 3; do for delta in $attls; do pktws_curl_test_update_vary $1 $2 $3 $desync ${orig:+--orig-autottl=+$orig} --dpi-desync-ttl=1 --dpi-desync-autottl=-$delta $e && ok=1 done [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break done } [ "$ok" = 1 ] && { echo "WARNING ! although autottl worked it requires testing on multiple domains to find out reliable delta" echo "WARNING ! if a reliable delta cannot be found it's a good idea not to use autottl" [ "$SCANLEVEL" = quick ] && return need_wssize=0 [ "$SCANLEVEL" = force ] || { [ "$desync" = fake ] && need_fake=0 [ "$desync" = fakedsplit ] && need_fakedsplit=0 [ "$desync" = hostfakesplit ] && need_hostfakesplit=0 [ "$desync" = fakeddisorder ] && need_fakeddisorder=0 } } done s="http_iana_org.bin" [ "$sec" = 0 ] || s="tls_clienthello_iana_org.bin" for desync in syndata ${need_split:+syndata,multisplit} ${need_disorder:+syndata,multidisorder} ; do pktws_curl_test_update_vary $1 $2 $3 $desync $e && [ "$SCANLEVEL" = quick ] && return pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fake-syndata="$ZAPRET_BASE/files/fake/$s" $e && [ "$SCANLEVEL" = quick ] && return done # do not do wssize test for http and TLS 1.3. it's useless [ "$sec" = 1 ] || break [ "$SCANLEVEL" = force -o "$need_wssize" = 1 ] || break done } pktws_check_domain_http_bypass() { # $1 - test function # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $3 - domain local strategy pktws_check_domain_http_bypass_ "$@" strategy_append_extra_pktws report_strategy $1 $3 $PKTWSD } pktws_check_domain_http3_bypass_() { # $1 - test function # $2 - domain local f desync frag tests rep fake for fake in '' "--dpi-desync-fake-quic=$ZAPRET_BASE/files/fake/quic_initial_www_google_com.bin"; do for rep in '' 2 5 10 20; do pktws_curl_test_update $1 $2 --dpi-desync=fake ${fake:+"$fake" }${rep:+--dpi-desync-repeats=$rep} && [ "$SCANLEVEL" != force ] && { [ "$SCANLEVEL" = quick ] && return break } done done [ "$IPV" = 6 ] && { f="hopbyhop destopt" [ -n "$IP6_DEFRAG_DISABLE" ] && f="$f ipfrag1" for desync in $f; do pktws_curl_test_update $1 $2 --dpi-desync=$desync && [ "$SCANLEVEL" = quick ] && return done } # OpenBSD has checksum issues with fragmented packets [ "$UNAME" != "OpenBSD" ] && [ "$IPV" = 4 -o -n "$IP6_DEFRAG_DISABLE" ] && { for frag in 8 16 24 32 40 64; do tests="ipfrag2" [ "$IPV" = 6 ] && tests="$tests hopbyhop,ipfrag2 destopt,ipfrag2" for desync in $tests; do pktws_curl_test_update $1 $2 --dpi-desync=$desync --dpi-desync-ipfrag-pos-udp=$frag && [ "$SCANLEVEL" = quick ] && return done done } } pktws_check_domain_http3_bypass() { # $1 - test function # $2 - domain local strategy pktws_check_domain_http3_bypass_ "$@" strategy_append_extra_pktws report_strategy $1 $2 $PKTWSD } warn_mss() { [ -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' return 0 } fix_seg() { # $1 - split-pos [ -n "$FIX_SEG" ] && contains "$1" , && echo "$FIX_SEG" } tpws_check_domain_http_bypass_() { # $1 - test function # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $3 - domain local s mss s2 s3 oobdis pos sec="$2" local splits_tls='2 1 sniext+1 sniext+4 host+1 midsld 1,midsld 1,sniext+1,host+1,midsld,endhost-1' local splits_http='method+2 midsld method+2,midsld' # simulteneous oob and disorder works properly only in linux. other systems retransmit oob byte without URG tcp flag and poison tcp stream. [ "$UNAME" = Linux ] && oobdis='--oob --disorder' if [ "$sec" = 0 ]; then for s in '--hostcase' '--hostspell=hoSt' '--hostdot' '--hosttab' '--hostnospace' '--domcase' ; do tpws_curl_test_update $1 $3 $s && [ "$SCANLEVEL" = quick ] && return done for s in 1024 2048 4096 8192 16384 ; do tpws_curl_test_update $1 $3 --hostpad=$s && [ "$SCANLEVEL" != force ] && { [ "$SCANLEVEL" = quick ] && return break } done for s2 in '' '--hostcase' '--oob' '--disorder' ${oobdis:+"$oobdis"}; do for s in $splits_http ; do tpws_curl_test_update $1 $3 --split-pos=$s $(fix_seg $s) $s2 && [ "$SCANLEVEL" != force ] && { [ "$SCANLEVEL" = quick ] && return break } done done for s in '--methodspace' '--unixeol' '--methodeol'; do tpws_curl_test_update $1 $3 $s && [ "$SCANLEVEL" = quick ] && return done else local need_mss=1 for mss in '' 88; do s3=${mss:+--mss=$mss} for s2 in '' '--oob' '--disorder' ${oobdis:+"$oobdis"}; do for pos in $splits_tls; do tpws_curl_test_update $1 $3 --split-pos=$pos $(fix_seg $pos) $s2 $s3 && warn_mss $s3 && [ "$SCANLEVEL" != force ] && { [ "$SCANLEVEL" = quick ] && return need_mss=0 break } done done for s in '' '--oob' '--disorder' ${oobdis:+"$oobdis"}; do for 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 tpws_curl_test_update $1 $3 $s2 $s $s3 && warn_mss $s3 && [ "$SCANLEVEL" != force ] && { [ "$SCANLEVEL" = quick ] && return need_mss=0 break } done done # only linux supports mss [ "$UNAME" = Linux -a "$sec" = 1 ] || break [ "$SCANLEVEL" = force -o "$need_mss" = 1 ] || break done fi } tpws_check_domain_http_bypass() { # $1 - test function # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $3 - domain local strategy tpws_check_domain_http_bypass_ "$@" strategy_append_extra_tpws report_strategy $1 $3 tpws } check_dpi_ip_block() { # $1 - test function # $2 - domain local blocked_dom="$2" local blocked_ip blocked_ips unblocked_ip echo echo "- IP block tests (requires manual interpretation)" echo "> testing $UNBLOCKED_DOM on it's original ip" if curl_test $1 $UNBLOCKED_DOM; then mdig_resolve $IPV unblocked_ip $UNBLOCKED_DOM [ -n "$unblocked_ip" ] || { echo $UNBLOCKED_DOM does not resolve. tests not possible. return 1 } echo "> testing $blocked_dom on $unblocked_ip ($UNBLOCKED_DOM)" curl_test $1 $blocked_dom $unblocked_ip detail mdig_resolve_all $IPV blocked_ips $blocked_dom for blocked_ip in $blocked_ips; do echo "> testing $UNBLOCKED_DOM on $blocked_ip ($blocked_dom)" curl_test $1 $UNBLOCKED_DOM $blocked_ip detail done else echo $UNBLOCKED_DOM is not available. skipping this test. fi } curl_has_reason_to_continue() { # $1 - curl return code for c in 1 2 3 4 6 27 ; do [ $1 = $c ] && return 1 done return 0 } check_domain_prolog() { # $1 - test function # $2 - port # $3 - domain local code [ "$SIMULATE" = 1 ] && return 0 echo echo \* $1 ipv$IPV $3 echo "- checking without DPI bypass" curl_test $1 $3 && { report_append "$3" "$1 ipv${IPV}" "working without bypass" [ "$SCANLEVEL" = force ] || return 1 } code=$? curl_has_reason_to_continue $code || { report_append "$3" "$1 ipv${IPV}" "test aborted, no reason to continue. curl code $(curl_translate_code $code)" return 1 } return 0 } check_domain_http_tcp() { # $1 - test function # $2 - port # $3 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk # $4 - domain local ips # in case was interrupted before pktws_ipt_unprepare_tcp $2 ws_kill check_domain_prolog $1 $2 $4 || return [ "$SKIP_IPBLOCK" = 1 ] || check_dpi_ip_block $1 $4 [ "$SKIP_TPWS" = 1 ] || { echo tpws_check_domain_http_bypass $1 $3 $4 } [ "$SKIP_PKTWS" = 1 ] || { echo echo preparing $PKTWSD redirection mdig_resolve_all $IPV ips $4 pktws_ipt_prepare_tcp $2 "$ips" pktws_check_domain_http_bypass $1 $3 $4 echo clearing $PKTWSD redirection pktws_ipt_unprepare_tcp $2 } } check_domain_http_udp() { # $1 - test function # $2 - port # $3 - domain local ips # in case was interrupted before pktws_ipt_unprepare_udp $2 ws_kill check_domain_prolog $1 $2 $3 || return [ "$SKIP_PKTWS" = 1 ] || { echo echo preparing $PKTWSD redirection mdig_resolve_all $IPV ips $3 pktws_ipt_prepare_udp $2 "$ips" pktws_check_domain_http3_bypass $1 $3 echo clearing $PKTWSD redirection pktws_ipt_unprepare_udp $2 } } check_domain_http() { # $1 - domain check_domain_http_tcp curl_test_http $HTTP_PORT 0 $1 } check_domain_https_tls12() { # $1 - domain check_domain_http_tcp curl_test_https_tls12 $HTTPS_PORT 1 $1 } check_domain_https_tls13() { # $1 - domain check_domain_http_tcp curl_test_https_tls13 $HTTPS_PORT 2 $1 } check_domain_http3() { # $1 - domain check_domain_http_udp curl_test_http3 $QUIC_PORT $1 } configure_ip_version() { if [ "$IPV" = 6 ]; then LOCALHOST=::1 LOCALHOST_IPT=[${LOCALHOST}] IPVV=6 else IPTABLES=iptables LOCALHOST=127.0.0.1 LOCALHOST_IPT=$LOCALHOST IPVV= fi IPTABLES=ip${IPVV}tables } configure_curl_opt() { # wolfssl : --tlsv1.x mandates exact ssl version, tls-max not supported # openssl : --tlsv1.x means "version equal or greater", tls-max supported TLSMAX12= TLSMAX13= curl_supports_tlsmax && { TLSMAX12="--tls-max 1.2" TLSMAX13="--tls-max 1.3" } TLS13= curl_supports_tls13 && TLS13=1 HTTP3= curl_supports_http3 && HTTP3=1 HTTPS_HEAD=-I [ "$CURL_HTTPS_GET" = 1 ] && HTTPS_HEAD= } linux_ipv6_defrag_can_be_disabled() { linux_min_version 4 16 } configure_defrag() { IP6_DEFRAG_DISABLE= [ "$IPVS" = 4 ] && return [ "$UNAME" = "Linux" ] && { linux_ipv6_defrag_can_be_disabled || { echo "WARNING ! ipv6 defrag can only be effectively disabled in linux kernel 4.16+" echo "WARNING ! ipv6 ipfrag tests are disabled" echo return } } case "$FWTYPE" in iptables) if ipt6_has_raw ; then if ipt6_has_frag; then IP6_DEFRAG_DISABLE=1 else echo "WARNING ! ip6tables does not have '-m frag' module, ipv6 ipfrag tests are disabled" echo fi else echo "WARNING ! ip6tables raw table is not available, ipv6 ipfrag tests are disabled" echo fi [ -n "$IP6_DEFRAG_DISABLE" ] && { local ipexe="$(readlink -f $(whichq ip6tables))" if contains "$ipexe" nft; then echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6tables-nft is used. current ip6tables point to : $ipexe" else echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6table_raw kernel module is not loaded with parameter : raw_before_defrag=1" fi echo } ;; *) IP6_DEFRAG_DISABLE=1 ;; esac } ask_params() { echo echo NOTE ! this test should be run with zapret or any other bypass software disabled, without VPN echo curl_supports_connect_to || { echo "installed curl does not support --connect-to option. pls install at least curl 7.49" echo "current curl version:" "$CURL" --version exitp 1 } local dom [ -n "$DOMAINS" ] || { DOMAINS="$DOMAINS_DEFAULT" [ "$BATCH" = 1 ] || { echo "specify domain(s) to test. multiple domains are space separated. URIs are supported (rutracker.org/forum/index.php)" printf "domain(s) (default: $DOMAINS) : " read dom [ -n "$dom" ] && DOMAINS="$dom" } } DOMAINS_COUNT="$(echo "$DOMAINS" | wc -w | trim)" local IPVS_def=4 [ -n "$IPVS" ] || { # yandex public dns pingtest 6 2a02:6b8::feed:0ff && IPVS_def=46 [ "$BATCH" = 1 ] || { printf "ip protocol version(s) - 4, 6 or 46 for both (default: $IPVS_def) : " read IPVS } [ -n "$IPVS" ] || IPVS=$IPVS_def [ "$IPVS" = 4 -o "$IPVS" = 6 -o "$IPVS" = 46 ] || { echo 'invalid ip version(s). should be 4, 6 or 46.' exitp 1 } } [ "$IPVS" = 46 ] && IPVS="4 6" configure_curl_opt [ -n "$ENABLE_HTTP" ] || { ENABLE_HTTP=1 [ "$BATCH" = 1 ] || { echo ask_yes_no_var ENABLE_HTTP "check http" } } [ -n "$ENABLE_HTTPS_TLS12" ] || { ENABLE_HTTPS_TLS12=1 [ "$BATCH" = 1 ] || { echo ask_yes_no_var ENABLE_HTTPS_TLS12 "check https tls 1.2" } } [ -n "$ENABLE_HTTPS_TLS13" ] || { ENABLE_HTTPS_TLS13=0 if [ -n "$TLS13" ]; then [ "$BATCH" = 1 ] || { echo echo "TLS 1.3 uses encrypted ServerHello. DPI cannot check domain name in server response." echo "This can allow more bypass strategies to work." echo "What works for TLS 1.2 will also work for TLS 1.3 but not vice versa." echo "Most sites nowadays support TLS 1.3 but not all. If you can't find a strategy for TLS 1.2 use this test." echo "TLS 1.3 only strategy is better than nothing." ask_yes_no_var ENABLE_HTTPS_TLS13 "check https tls 1.3" } else echo echo "installed curl version does not support TLS 1.3 . tests disabled." fi } [ -n "$ENABLE_HTTP3" ] || { ENABLE_HTTP3=0 if [ -n "$HTTP3" ]; then ENABLE_HTTP3=1 [ "$BATCH" = 1 ] || { echo echo "make sure target domain(s) support QUIC or result will be negative in any case" ask_yes_no_var ENABLE_HTTP3 "check http3 QUIC" } else echo echo "installed curl version does not support http3 QUIC. tests disabled." fi } [ -n "$REPEATS" ] || { [ "$BATCH" = 1 ] || { echo echo "sometimes ISPs use multiple DPIs or load balancing. bypass strategies may work unstable." printf "how many times to repeat each test (default: 1) : " read REPEATS } REPEATS=$((0+${REPEATS:-1})) [ "$REPEATS" = 0 ] && { echo invalid repeat count exitp 1 } } [ -z "$PARALLEL" -a $REPEATS -gt 1 ] && { PARALLEL=0 [ "$BATCH" = 1 ] || { echo echo "parallel scan can greatly increase speed but may also trigger DDoS protection and cause false result" ask_yes_no_var PARALLEL "enable parallel scan" } } PARALLEL=${PARALLEL:-0} [ -n "$SCANLEVEL" ] || { SCANLEVEL=standard [ "$BATCH" = 1 ] || { echo echo quick - scan as fast as possible to reveal any working strategy echo standard - do investigation what works on your DPI echo force - scan maximum despite of result ask_list SCANLEVEL "quick standard force" "$SCANLEVEL" # disable tpws checks by default in quick mode [ "$SCANLEVEL" = quick -a -z "$SKIP_TPWS" -a "$UNAME" != Darwin ] && SKIP_TPWS=1 } } echo configure_defrag } ping_with_fix() { local ret $PING $2 $1 >/dev/null 2>/dev/null ret=$? # can be because of unsupported -4 option if [ "$ret" = 2 -o "$ret" = 64 ]; then ping $2 $1 >/dev/null else return $ret fi } pingtest() { # $1 - ip version : 4 or 6 # $2 - domain or ip # ping command can vary a lot. some implementations have -4/-6 options. others don.t # WARNING ! macos ping6 command does not have timeout option. ping6 will fail local PING=ping ret if [ "$1" = 6 ]; then if exists ping6; then PING=ping6 else PING="ping -6" fi else if [ "$UNAME" = Darwin -o "$UNAME" = FreeBSD -o "$UNAME" = OpenBSD ]; then # ping by default pings ipv4, ping6 only pings ipv6 # in FreeBSD -4/-6 options are supported, in others not PING=ping else # this can be linux or cygwin # 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) PING="ping -4" fi fi case "$UNAME" in Darwin) $PING -c 1 -t 1 $2 >/dev/null 2>/dev/null # WARNING ! macos ping6 command does not have timeout option. ping6 will fail. but without timeout is not an option. ;; OpenBSD) $PING -c 1 -w 1 $2 >/dev/null ;; CYGWIN) if starts_with "$(which ping)" /cygdrive; then # cygwin does not have own ping by default. use windows PING. $PING -n 1 -w 1000 $2 >/dev/null else ping_with_fix $2 '-c 1 -w 1' fi ;; *) ping_with_fix $2 '-c 1 -W 1' ;; esac } dnstest() { # $1 - dns server. empty for system resolver "$LOOKUP" iana.org $1 >/dev/null 2>/dev/null } find_working_public_dns() { local dns for dns in $DNSCHECK_DNS; do pingtest 4 $dns && dnstest $dns && { PUBDNS=$dns return 0 } done return 1 } lookup4() { # $1 - domain # $2 - DNS case "$LOOKUP" in nslookup) if is_linked_to_busybox nslookup; then nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^.*:[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' else nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' fi ;; host) host -t A $1 $2 | grep "has address" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' ;; esac } check_dns_spoof() { # $1 - domain # $2 - public DNS # windows version of mdig outputs 0D0A line ending. remove 0D. echo $1 | "$MDIG" --family=4 | tr -d '\r' >"$DNSCHECK_DIG1" lookup4 $1 $2 >"$DNSCHECK_DIG2" # check whether system resolver returns anything other than public DNS grep -qvFf "$DNSCHECK_DIG2" "$DNSCHECK_DIG1" } check_dns_cleanup() { rm -f "$DNSCHECK_DIG1" "$DNSCHECK_DIG2" "$DNSCHECK_DIGS" 2>/dev/null } check_dns_() { local C1 C2 dom DNS_IS_SPOOFED=0 [ "$SKIP_DNSCHECK" = 1 ] && return 0 echo \* checking DNS [ -f "$DNSCHECK_DIGS" ] && rm -f "$DNSCHECK_DIGS" dnstest || { echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. return 1 } echo system DNS is working if find_working_public_dns ; then echo comparing system resolver to public DNS : $PUBDNS for dom in $DNSCHECK_DOM; do if check_dns_spoof "$dom" $PUBDNS ; then echo $dom : MISMATCH echo -- system resolver : cat "$DNSCHECK_DIG1" echo -- $PUBDNS : cat "$DNSCHECK_DIG2" check_dns_cleanup echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! echo -- DNS CHANGE OR DNSCRYPT MAY BE REQUIRED DNS_IS_SPOOFED=1 return 1 else echo $dom : OK cat "$DNSCHECK_DIG1" >>"$DNSCHECK_DIGS" fi done else echo no working public DNS was found. looks like public DNS blocked. for dom in $DNSCHECK_DOM; do echo $dom; done | "$MDIG" --threads=10 --family=4 >"$DNSCHECK_DIGS" fi echo "checking resolved IP uniqueness for : $DNSCHECK_DOM" echo "censor's DNS can return equal result for multiple blocked domains." C1=$(wc -l <"$DNSCHECK_DIGS") C2=$(sort -u "$DNSCHECK_DIGS" | wc -l) [ "$C1" -eq 0 ] && { echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. check_dns_cleanup return 1 } [ "$C1" = "$C2" ] || { echo system dns resolver has returned equal IPs for some domains checked above \($C1 total, $C2 unique\) echo non-unique IPs : sort "$DNSCHECK_DIGS" | uniq -d echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! echo -- DNSCRYPT MAY BE REQUIRED check_dns_cleanup DNS_IS_SPOOFED=1 return 1 } echo all resolved IPs are unique echo -- DNS looks good echo -- NOTE this check is Russia targeted. In your country other domains may be blocked. check_dns_cleanup return 0 } check_dns() { local r check_dns_ r=$? [ "$DNS_IS_SPOOFED" = 1 ] && SECURE_DNS=${SECURE_DNS:-1} [ "$SECURE_DNS" = 1 ] && { doh_find_working || { echo could not find working DoH server. exiting. exitp 7 } } return $r } unprepare_all() { # make sure we are not in a middle state that impacts connectivity ws_kill wait [ -n "$IPV" ] && { pktws_ipt_unprepare_tcp $HTTP_PORT pktws_ipt_unprepare_tcp $HTTPS_PORT pktws_ipt_unprepare_udp $QUIC_PORT } cleanup rm -f "${HDRTEMP}"* "${PARALLEL_OUT}"* } sigint() { echo echo terminating... unprepare_all exitp 1 } sigint_cleanup() { cleanup exit 1 } sigsilent() { # must not write anything here to stdout unprepare_all exit 1 } fsleep_setup fix_sbin_path check_system check_already # no divert sockets in MacOS [ "$UNAME" = "Darwin" ] && SKIP_PKTWS=1 [ "$UNAME" != CYGWIN -a "$SKIP_PKTWS" != 1 ] && require_root check_prerequisites trap sigint_cleanup INT check_dns check_virt ask_params trap - INT PID= NREPORT= unset WF trap sigint INT trap sigsilent PIPE trap sigsilent HUP for dom in $DOMAINS; do for IPV in $IPVS; do configure_ip_version [ "$ENABLE_HTTP" = 1 ] && { [ "$SKIP_IPBLOCK" = 1 ] || check_domain_port_block $dom $HTTP_PORT check_domain_http $dom } [ "$ENABLE_HTTPS_TLS12" = 1 -o "$ENABLE_HTTPS_TLS13" = 1 ] && [ "$SKIP_IPBLOCK" != 1 ] && check_domain_port_block $dom $HTTPS_PORT [ "$ENABLE_HTTPS_TLS12" = 1 ] && check_domain_https_tls12 $dom [ "$ENABLE_HTTPS_TLS13" = 1 ] && check_domain_https_tls13 $dom [ "$ENABLE_HTTP3" = 1 ] && check_domain_http3 $dom done done trap - HUP trap - PIPE trap - INT cleanup echo echo \* SUMMARY report_print [ "$DOMAINS_COUNT" -gt 1 ] && { echo echo \* COMMON result_intersection_print echo [ "$SCANLEVEL" = force ] || { echo "blockcheck optimizes test sequence. To save time some strategies can be skipped if their test is considered useless." echo "That's why COMMON intersection can miss strategies that would work for all domains." echo "Use \"force\" scan level to test all strategies and generate trustable intersection." echo "Current scan level was \"$SCANLEVEL\"". } } echo echo "Please note this SUMMARY does not guarantee a magic pill for you to copy/paste and be happy." echo "Understanding how strategies work is very desirable." echo "This knowledge allows to understand better which strategies to prefer and which to avoid if possible, how to combine strategies." echo "Blockcheck does it's best to prioritize good strategies but it's not bullet-proof." echo "It was designed not as magic pill maker but as a DPI bypass test tool." exitp 0 ================================================ FILE: common/base.sh ================================================ which() { # on some systems 'which' command is considered deprecated and not installed by default # 'command -v' replacement does not work exactly the same way. it outputs shell aliases if present # $1 - executable name local IFS=: [ "$1" != "${1#/}" ] && [ -x "$1" ] && { echo "$1" return 0 } for p in $PATH; do [ -x "$p/$1" ] && { echo "$p/$1" return 0 } done return 1 } exists() { which "$1" >/dev/null 2>/dev/null } existf() { type "$1" >/dev/null 2>/dev/null } whichq() { which $1 2>/dev/null } exist_all() { while [ -n "$1" ]; do exists "$1" || return 1 shift done return 0 } on_off_function() { # $1 : function name on # $2 : function name off # $3 : 0 - off, 1 - on local F="$1" [ "$3" = "1" ] || F="$2" shift shift shift "$F" "$@" } contains() { # check if substring $2 contains in $1 [ "${1#*$2}" != "$1" ] } starts_with() { # $1 : what # $2 : starts with case "$1" in "$2"*) return 0 ;; esac return 1 } extract_arg() { # $1 - arg number # $2,$3,... - args local n=$1 while [ -n "$1" ]; do shift [ $n -eq 1 ] && { echo "$1"; return 0; } n=$(($n-1)) done return 1 } find_str_in_list() { # $1 - string # $2 - space separated values local v [ -n "$1" ] && { for v in $2; do [ "$v" = "$1" ] && return 0 done } return 1 } end_with_newline() { local c="$(tail -c 1)" [ "$c" = "" ] } trim() { awk '{gsub(/^ +| +$/,"")}1' } split_by_separator() { # $1 - string # $2 - separator # $3 - var name to get "before" part # $4 - var name to get "after" part local before="${1%%$2*}" local after="${1#*$2}" [ "$after" = "$1" ] && after= [ -n "$3" ] && eval $3="\$before" [ -n "$4" ] && eval $4="\$after" } tolower() { echo "$@" | tr 'A-Z' 'a-z' } dir_is_not_empty() { # $1 - directory local n [ -d "$1" ] || return 1 n=$(ls "$1" | wc -c | xargs) [ "$n" != 0 ] } append_separator_list() { # $1 - var name to receive result # $2 - separator # $3 - quoter # $4,$5,... - elements local _var="$1" sep="$2" quo="$3" i eval i="\$$_var" shift; shift; shift while [ -n "$1" ]; do if [ -n "$i" ] ; then i="$i$sep$quo$1$quo" else i="$quo$1$quo" fi shift done eval $_var="\$i" } make_separator_list() { eval $1='' append_separator_list "$@" } make_comma_list() { # $1 - var name to receive result # $2,$3,... - elements local var="$1" shift make_separator_list $var , '' "$@" } make_quoted_comma_list() { # $1 - var name to receive result # $2,$3,... - elements local var="$1" shift make_separator_list $var , '"' "$@" } unique() { local i for i in "$@"; do echo $i; done | sort -u | xargs } is_linked_to_busybox() { local IFS F P IFS=: for path in $PATH; do F=$path/$1 P="$(readlink $F)" if [ -z "$P" ] && [ -x $F ] && [ ! -L $F ]; then return 1; fi [ "${P%busybox*}" != "$P" ] && return done } get_dir_inode() { local dir="$1" [ -L "$dir" ] && dir=$(readlink "$dir") ls -id "$dir" | awk '{print $1}' } linux_min_version() { # $1 - major ver # $2 - minor ver local V1=$(sed -nre 's/^Linux version ([0-9]+)\.[0-9]+.*$/\1/p' /proc/version) local V2=$(sed -nre 's/^Linux version [0-9]+\.([0-9]+).*$/\1/p' /proc/version) [ -n "$V1" -a -n "$V2" ] && [ "$V1" -gt "$1" -o "$V1" -eq "$1" -a "$V2" -ge "$2" ] } linux_get_subsys() { local INIT="$(sed 's/\x0/\n/g' /proc/1/cmdline | head -n 1)" [ -L "$INIT" ] && INIT=$(readlink "$INIT") INIT="$(basename "$INIT")" if [ -f "/etc/openwrt_release" ] && [ "$INIT" = "procd" ] ; then SUBSYS=openwrt elif [ -x "/bin/ndm" ] ; then SUBSYS=keenetic else # generic linux SUBSYS= fi } openwrt_fw3() { [ ! -x /sbin/fw4 -a -x /sbin/fw3 ] } openwrt_fw4() { [ -x /sbin/fw4 ] } openwrt_fw3_integration() { [ "$FWTYPE" = iptables ] && openwrt_fw3 } create_dev_stdin() { [ -e /dev/stdin ] || ln -s /proc/self/fd/0 /dev/stdin } call_for_multiple_items() { # $1 - function to get an item # $2 - variable name to put result into # $3 - space separated parameters to function $1 local i item items for i in $3; do $1 item $i [ -n "$item" ] && { if [ -n "$items" ]; then items="$items $item" else items="$item" fi } done eval $2=\"$items\" } fix_sbin_path() { local IFS=':' printf "%s\n" $PATH | grep -Fxq '/usr/sbin' || PATH="/usr/sbin:$PATH" printf "%s\n" $PATH | grep -Fxq '/sbin' || PATH="/sbin:$PATH" export PATH } # it can calculate floating point expr calc() { LC_ALL=C awk "BEGIN { print $*}"; } fsleep_setup() { [ -n "$FSLEEP" ] || { if sleep 0.001 2>/dev/null; then FSLEEP=1 elif busybox usleep 1 2>/dev/null; then FSLEEP=2 else local errtext="$(read -t 0.001 2>&1)" if [ -z "$errtext" ]; then FSLEEP=3 # newer openwrt has ucode with system function that supports timeout in ms elif ucode -e "system(['sleep','1'], 1)" 2>/dev/null; then FSLEEP=4 # older openwrt may have lua and nixio lua module elif lua -e 'require "nixio".nanosleep(0,1)' 2>/dev/null ; then FSLEEP=5 else FSLEEP=0 fi fi } } msleep() { # $1 - milliseconds case "$FSLEEP" in 1) sleep $(calc $1/1000) ;; 2) busybox usleep $(calc $1*1000) ;; 3) read -t $(calc $1/1000) ;; 4) ucode -e "system(['sleep','2147483647'], $1)" ;; 5) lua -e "require 'nixio'.nanosleep($(($1/1000)),$(calc $1%1000*1000000))" ;; *) sleep $((($1+999)/1000)) esac } minsleep() { msleep 100 } replace_char() { local a="$1" local b="$2" shift; shift echo "$@" | tr "$a" "$b" } replace_str() { local a=$(echo "$1" | sed 's/\//\\\//g') local b=$(echo "$2" | sed 's/\//\\\//g') shift; shift echo "$@" | sed "s/$a/$b/g" } setup_md5() { [ -n "$MD5" ] && return MD5=md5sum exists $MD5 || MD5=md5 } md5f() { setup_md5 $MD5 | cut -d ' ' -f1 } setup_random() { [ -n "$RCUT" ] && return RCUT="cut -c 1-17" # some shells can operate with 32 bit signed int [ $((0x100000000)) = 0 ] && RCUT="cut -c 1-9" } random() { # $1 - min, $2 - max local r rs setup_random if [ -c /dev/urandom ]; then read rs /dev/null elif exists pidof; then pidof $1 >/dev/null else return 1 fi } win_process_exists() { tasklist /NH /FI "IMAGENAME eq ${1}.exe" | grep -q "^${1}.exe" } alloc_num() { # $1 - source var name # $2 - target var name # $3 - min # $4 - max local v eval v="\$$2" # do not replace existing value [ -n "$v" ] && return eval v="\$$1" [ -n "$v" ] || v=$3 eval $2="$v" v=$((v + 1)) [ $v -gt $4 ] && v=$3 eval $1="$v" } std_ports() { TPWS_PORTS_IPT=$(replace_char - : $TPWS_PORTS) NFQWS_PORTS_TCP_IPT=$(replace_char - : $NFQWS_PORTS_TCP) NFQWS_PORTS_TCP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_TCP_KEEPALIVE) NFQWS_PORTS_UDP_IPT=$(replace_char - : $NFQWS_PORTS_UDP) NFQWS_PORTS_UDP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_UDP_KEEPALIVE) } has_bad_ws_options() { # $1 - nfqws/tpws opts contains "$1" "--ipset" && { echo echo "WARNING !!! --ipset parameter is present" echo "It's OK if you only specialize already redirected traffic and also process the rest." echo "If you redirect port X to process several IPs from the list and do nothing with the rest - IT'S VERY INEFFECTIVE !" echo "Kernel ipsets should be used instead. Write custom scripts and filter IPs in kernel." echo } return 1 } check_bad_ws_options() { # $1 - 0 = stop, 1 = start # $2 - nfqws/tpws options if [ "$1" = 1 ] && has_bad_ws_options "$2"; then echo "!!! REFUSING TO USE BAD OPTIONS : $2" help_bad_ws_options return 1 else return 0 fi } help_bad_ws_options() { echo "WARNING ! BAD options detected" } ================================================ FILE: common/custom.sh ================================================ custom_runner() { # $1 - function name # $2+ - params [ "$DISABLE_CUSTOM" = 1 ] && return 0 local n script FUNC=$1 shift [ -d "$CUSTOM_DIR/custom.d" ] && { dir_is_not_empty "$CUSTOM_DIR/custom.d" && { for script in "$CUSTOM_DIR/custom.d/"*; do [ -f "$script" ] || continue unset -f $FUNC . "$script" existf $FUNC && $FUNC "$@" done } } } alloc_tpws_port() { # $1 - target var name alloc_num NUMPOOL_TPWS_PORT $1 910 979 } alloc_qnum() { # $1 - target var name alloc_num NUMPOOL_QNUM $1 65400 65499 } alloc_dnum() { # alloc daemon number # $1 - target var name alloc_num NUMPOOL_DNUM $1 1000 1999 } ================================================ FILE: common/dialog.sh ================================================ read_yes_no() { # $1 - default (Y/N) local A read A [ -z "$A" ] || ([ "$A" != "Y" ] && [ "$A" != "y" ] && [ "$A" != "N" ] && [ "$A" != "n" ]) && A=$1 [ "$A" = "Y" ] || [ "$A" = "y" ] || [ "$A" = "1" ] } ask_yes_no() { # $1 - default (Y/N or 0/1) # $2 - text local DEFAULT=$1 [ "$1" = "1" ] && DEFAULT=Y [ "$1" = "0" ] && DEFAULT=N [ -z "$DEFAULT" ] && DEFAULT=N printf "$2 (default : $DEFAULT) (Y/N) ? " read_yes_no $DEFAULT } ask_yes_no_var() { # $1 - variable name for answer : 0/1 # $2 - text local DEFAULT eval DEFAULT="\$$1" if ask_yes_no "$DEFAULT" "$2"; then eval $1=1 else eval $1=0 fi } ask_list() { # $1 - mode var # $2 - space separated value list # $3 - (optional) default value local M_DEFAULT eval M_DEFAULT="\$$1" local M_DEFAULT_VAR="$M_DEFAULT" local M="" m [ -n "$3" ] && { find_str_in_list "$M_DEFAULT" "$2" || M_DEFAULT="$3" ;} n=1 for m in $2; do echo $n : $m n=$(($n+1)) done printf "your choice (default : $M_DEFAULT) : " read m [ -n "$m" ] && M=$(echo $2 | cut -d ' ' -f$m 2>/dev/null) [ -z "$M" ] && M="$M_DEFAULT" echo selected : $M eval $1="\"$M\"" [ "$M" != "$M_DEFAULT_VAR" ] } ================================================ FILE: common/elevate.sh ================================================ require_root() { local exe preserve_env echo \* checking privileges [ $(id -u) -ne "0" ] && { echo root is required exe="$EXEDIR/$(basename "$0")" exists sudo && { echo elevating with sudo exec sudo -E sh "$exe" } exists su && { echo elevating with su case "$UNAME" in Linux) preserve_env="--preserve-environment" ;; FreeBSD|OpenBSD|Darwin) preserve_env="-m" ;; esac exec su $preserve_env root -c "sh \"$exe\"" } echo su or sudo not found exitp 2 } HAVE_ROOT=1 } ================================================ FILE: common/fwtype.sh ================================================ linux_ipt_avail() { exists iptables && exists ip6tables } linux_maybe_iptables_fwtype() { linux_ipt_avail && FWTYPE=iptables } linux_nft_avail() { exists nft } linux_fwtype() { [ -n "$FWTYPE" ] && return FWTYPE=unsupported linux_get_subsys if [ "$SUBSYS" = openwrt ] ; then # linux kernel is new enough if fw4 is there if [ -x /sbin/fw4 ] && linux_nft_avail ; then FWTYPE=nftables else linux_maybe_iptables_fwtype fi else SUBSYS= # generic linux # flowtable is implemented since kernel 4.16 if linux_nft_avail && linux_min_version 4 16; then FWTYPE=nftables else linux_maybe_iptables_fwtype fi fi export FWTYPE } get_fwtype() { [ -n "$FWTYPE" ] && return local UNAME="$(uname)" case "$UNAME" in Linux) linux_fwtype ;; FreeBSD) if exists ipfw ; then FWTYPE=ipfw else FWTYPE=unsupported fi ;; *) FWTYPE=unsupported ;; esac export FWTYPE } ================================================ FILE: common/installer.sh ================================================ GET_LIST_PREFIX=/ipset/get_ SYSTEMD_DIR=/lib/systemd [ -d "$SYSTEMD_DIR" ] || SYSTEMD_DIR=/usr/lib/systemd [ -d "$SYSTEMD_DIR" ] && SYSTEMD_SYSTEM_DIR="$SYSTEMD_DIR/system" INIT_SCRIPT=/etc/init.d/zapret exitp() { echo echo press enter to continue read A exit $1 } extract_var_def() { # $1 - var name # this sed script parses single or multi line shell var assignments with optional ' or " enclosure sed -n \ "/^$1=\"/ { :s1 /\".*\"/ { p b } N t c1 b s1 :c1 } /^$1='/ { :s2 /'.*'/ { p b } N t c2 b s2 :c2 } /^$1=/p " } replace_var_def() { # $1 - var name # $2 - new val # $3 - conf file # this sed script replaces single or multi line shell var assignments with optional ' or " enclosure local repl if [ -z "$2" ]; then repl="#$1=" elif contains "$2" " "; then repl="$1=\"$2\"" else repl="$1=$2" fi local script=\ "/^#*[[:space:]]*$1=\"/ { :s1 /\".*\"/ { c\\ $repl b } N t c1 b s1 :c1 } /^#*[[:space:]]*$1='/ { :s2 /'.*'/ { c\\ $repl b } N t c2 b s2 :c2 } /^#*[[:space:]]*$1=/c\\ $repl" # there's incompatibility with -i option on MacOS/BSD and busybox/GNU if [ "$UNAME" = "Linux" ]; then sed -i -e "$script" "$3" else sed -i '' -e "$script" "$3" fi } parse_var_checked() { # $1 - file name # $2 - var name local tmp="/tmp/zvar-pid-$$.sh" local v cat "$1" | extract_var_def "$2" >"$tmp" . "$tmp" rm -f "$tmp" eval v="\$$2" # trim v="$(echo "$v" | trim)" eval $2=\""$v"\" } parse_vars_checked() { # $1 - file name # $2,$3,... - var names local f="$1" shift while [ -n "$1" ]; do parse_var_checked "$f" $1 shift done } edit_file() { # $1 - file name local ed="$EDITOR" [ -n "$ed" ] || { for e in mcedit nano vim vi; do exists "$e" && { ed="$e" break } done } [ -n "$ed" ] && "$ed" "$1" } echo_var() { local v eval v="\$$1" if find_str_in_list $1 "$EDITVAR_NEWLINE_VARS"; then echo "$1=\"" echo "$v\"" | tr '\n' ' ' | tr -d '\r' | sed -e 's/^ *//' -e 's/ *$//' -e "s/$EDITVAR_NEWLINE_DELIMETER /$EDITVAR_NEWLINE_DELIMETER\n/g" else if contains "$v" " "; then echo $1=\"$v\" else echo $1=$v fi fi } edit_vars() { # $1,$2,... - var names local n=1 var tmp="/tmp/zvars-pid-$$.txt" rm -f "$tmp" while : ; do eval var="\${$n}" [ -n "$var" ] || break echo_var $var >> "$tmp" n=$(($n+1)) done edit_file "$tmp" && parse_vars_checked "$tmp" "$@" rm -f "$tmp" } list_vars() { while [ -n "$1" ] ; do echo_var $1 shift done echo } openrc_test() { exists rc-update || return 1 # some systems do not usse openrc-init but launch openrc from inittab [ "$INIT" = "openrc-init" ] || grep -qE "sysinit.*openrc" /etc/inittab 2>/dev/null } check_system() { # $1 - nonempty = do not fail on unknown rc system echo \* checking system SYSTEM= SUBSYS= SYSTEMCTL="$(whichq systemctl)" get_fwtype OPENWRT_FW3= OPENWRT_FW4= local info UNAME=$(uname) if [ "$UNAME" = "Linux" ]; then # do not use 'exe' because it requires root local INIT="$(sed 's/\x0/\n/g' /proc/1/cmdline | head -n 1)" [ -L "$INIT" ] && INIT=$(readlink "$INIT") INIT="$(basename "$INIT")" # some distros include systemctl without systemd if [ -d "$SYSTEMD_DIR" ] && [ -x "$SYSTEMCTL" ] && [ "$INIT" = "systemd" ]; then SYSTEM=systemd [ -f "$EXEDIR/init.d/sysv/functions" ] && . "$EXEDIR/init.d/sysv/functions" elif [ -f "/etc/openwrt_release" ] && exists opkg || exists apk && exists uci && [ "$INIT" = "procd" ] ; then SYSTEM=openwrt OPENWRT_PACKAGER=opkg OPENWRT_PACKAGER_INSTALL="opkg install" OPENWRT_PACKAGER_UPDATE="opkg update" exists apk && { OPENWRT_PACKAGER=apk OPENWRT_PACKAGER_INSTALL="apk add" OPENWRT_PACKAGER_UPDATE= } info="package manager $OPENWRT_PACKAGER\n" if openwrt_fw3 ; then OPENWRT_FW3=1 info="${info}firewall fw3" if is_ipt_flow_offload_avail; then info="$info. hardware flow offloading requires iptables." else info="$info. flow offloading unavailable." fi elif openwrt_fw4; then OPENWRT_FW4=1 info="${info}firewall fw4. flow offloading requires nftables." fi [ -f "$EXEDIR/init.d/openwrt/functions" ] && . "$EXEDIR/init.d/openwrt/functions" elif openrc_test; then SYSTEM=openrc [ -f "$EXEDIR/init.d/sysv/functions" ] && . "$EXEDIR/init.d/sysv/functions" else echo system is not either systemd, openrc or openwrt based echo easy installer can set up config settings but can\'t configure auto start echo you have to do it manually. check readme.md for manual setup info. if [ -n "$1" ] || ask_yes_no N "do you want to continue"; then SYSTEM=linux else exitp 5 fi [ -f "$EXEDIR/init.d/sysv/functions" ] && . "$EXEDIR/init.d/sysv/functions" fi linux_get_subsys elif [ "$UNAME" = "Darwin" ]; then SYSTEM=macos [ -f "$EXEDIR/init.d/macos/functions" ] && . "$EXEDIR/init.d/macos/functions" else echo easy installer only supports Linux and MacOS. check readme.md for supported systems and manual setup info. exitp 5 fi echo system is based on $SYSTEM [ -n "$info" ] && printf "${info}\n" } get_free_space_mb() { df -m "$1" | awk '/[0-9]%/{print $(NF-2)}' } get_ram_kb() { grep MemTotal /proc/meminfo | awk '{print $2}' } get_ram_mb() { local R=$(get_ram_kb) echo $(($R/1024)) } crontab_del() { exists crontab || return echo \* removing crontab entry CRONTMP=/tmp/cron.tmp crontab -l >$CRONTMP 2>/dev/null if grep -q "$GET_LIST_PREFIX" $CRONTMP; then echo removing following entries from crontab : grep "$GET_LIST_PREFIX" $CRONTMP grep -v "$GET_LIST_PREFIX" $CRONTMP >$CRONTMP.2 crontab $CRONTMP.2 rm -f $CRONTMP.2 fi rm -f $CRONTMP } crontab_del_quiet() { exists crontab || return CRONTMP=/tmp/cron.tmp crontab -l >$CRONTMP 2>/dev/null if grep -q "$GET_LIST_PREFIX" $CRONTMP; then grep -v "$GET_LIST_PREFIX" $CRONTMP >$CRONTMP.2 crontab $CRONTMP.2 rm -f $CRONTMP.2 fi rm -f $CRONTMP } crontab_add() { # $1 - hour min # $2 - hour max [ -x "$GET_LIST" ] && { echo \* adding crontab entry if exists crontab; then CRONTMP=/tmp/cron.tmp crontab -l >$CRONTMP 2>/dev/null if grep -q "$GET_LIST_PREFIX" $CRONTMP; then echo some entries already exist in crontab. check if this is corrent : grep "$GET_LIST_PREFIX" $CRONTMP else end_with_newline <"$CRONTMP" || echo >>"$CRONTMP" echo "$(random 0 59) $(random $1 $2) */2 * * $GET_LIST" >>$CRONTMP crontab $CRONTMP fi rm -f $CRONTMP else echo '!!! CRON IS ABSENT !!! LISTS AUTO UPDATE WILL NOT WORK !!!' fi } } cron_ensure_running() { # if no crontabs present in /etc/cron openwrt init script does not launch crond. this is default [ "$SYSTEM" = "openwrt" ] && { /etc/init.d/cron enable /etc/init.d/cron start } } service_start_systemd() { echo \* starting zapret service "$SYSTEMCTL" start zapret || { echo could not start zapret service exitp 30 } } service_stop_systemd() { echo \* stopping zapret service "$SYSTEMCTL" daemon-reload "$SYSTEMCTL" disable zapret "$SYSTEMCTL" stop zapret } service_remove_systemd() { echo \* removing zapret service rm -f "$SYSTEMD_SYSTEM_DIR/zapret.service" "$SYSTEMCTL" daemon-reload } timer_remove_systemd() { echo \* removing zapret-list-update timer "$SYSTEMCTL" daemon-reload "$SYSTEMCTL" disable zapret-list-update.timer "$SYSTEMCTL" stop zapret-list-update.timer rm -f "$SYSTEMD_SYSTEM_DIR/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR/zapret-list-update.timer" "$SYSTEMCTL" daemon-reload } install_sysv_init() { # $1 - "0"=disable echo \* installing init script [ -x "$INIT_SCRIPT" ] && { "$INIT_SCRIPT" stop "$INIT_SCRIPT" disable } ln -fs "$INIT_SCRIPT_SRC" "$INIT_SCRIPT" [ "$1" != "0" ] && "$INIT_SCRIPT" enable } install_openrc_init() { # $1 - "0"=disable echo \* installing init script [ -x "$INIT_SCRIPT" ] && { "$INIT_SCRIPT" stop rc-update del zapret } ln -fs "$INIT_SCRIPT_SRC" "$INIT_SCRIPT" [ "$1" != "0" ] && rc-update add zapret } service_remove_openrc() { echo \* removing zapret service [ -x "$INIT_SCRIPT" ] && { rc-update del zapret "$INIT_SCRIPT" stop } rm -f "$INIT_SCRIPT" } service_start_sysv() { [ -x "$INIT_SCRIPT" ] && { echo \* starting zapret service "$INIT_SCRIPT" start || { echo could not start zapret service exitp 30 } } } service_stop_sysv() { [ -x "$INIT_SCRIPT" ] && { echo \* stopping zapret service "$INIT_SCRIPT" stop } } service_remove_sysv() { echo \* removing zapret service [ -x "$INIT_SCRIPT" ] && { "$INIT_SCRIPT" disable "$INIT_SCRIPT" stop } rm -f "$INIT_SCRIPT" } check_kmod() { [ -f "/lib/modules/$(uname -r)/$1.ko" ] } check_package_exists_openwrt() { [ -n "$($OPENWRT_PACKAGER list $1)" ] } check_package_openwrt() { case $OPENWRT_PACKAGER in opkg) [ -n "$(opkg list-installed $1)" ] && return 0 local what="$(opkg whatprovides $1 | tail -n +2 | head -n 1)" [ -n "$what" ] || return 1 [ -n "$(opkg list-installed $what)" ] ;; apk) apk info -e $1 ;; esac } check_packages_openwrt() { for pkg in $@; do check_package_openwrt $pkg || return done } install_openwrt_iface_hook() { echo \* installing ifup hook ln -fs "$OPENWRT_IFACE_HOOK" /etc/hotplug.d/iface } remove_openwrt_iface_hook() { echo \* removing ifup hook rm -f /etc/hotplug.d/iface/??-zapret } openwrt_fw_section_find() { # $1 - fw include postfix # echoes section number i=0 while true do path=$(uci -q get firewall.@include[$i].path) [ -n "$path" ] || break [ "$path" = "$OPENWRT_FW_INCLUDE$1" ] && { echo $i return 0 } i=$(($i+1)) done return 1 } openwrt_fw_section_del() { # $1 - fw include postfix local id="$(openwrt_fw_section_find $1)" [ -n "$id" ] && { uci delete firewall.@include[$id] && uci commit firewall rm -f "$OPENWRT_FW_INCLUDE$1" } } openwrt_fw_section_add() { openwrt_fw_section_find || { uci add firewall include >/dev/null || return echo -1 } } openwrt_fw_section_configure() { local id="$(openwrt_fw_section_add $1)" [ -z "$id" ] || ! uci set firewall.@include[$id].path="$OPENWRT_FW_INCLUDE" || ! uci set firewall.@include[$id].reload="1" || ! uci commit firewall && { echo could not add firewall include exitp 50 } } install_openwrt_firewall() { echo \* installing firewall script $1 echo "linking : $FW_SCRIPT_SRC => $OPENWRT_FW_INCLUDE" ln -fs "$FW_SCRIPT_SRC" "$OPENWRT_FW_INCLUDE" openwrt_fw_section_configure $1 } restart_openwrt_firewall() { echo \* restarting firewall local FW=fw4 [ -n "$OPENWRT_FW3" ] && FW=fw3 exists $FW && $FW -q restart || { echo could not restart firewall $FW } } remove_openwrt_firewall() { echo \* removing firewall script openwrt_fw_section_del # from old zapret versions. now we use single include openwrt_fw_section_del 6 } clear_ipset() { echo "* clearing ipset(s)" # free some RAM "$IPSET_DIR/create_ipset.sh" clear } service_install_macos() { echo \* installing zapret service ln -fs "$ZAPRET_BASE/init.d/macos/zapret.plist" /Library/LaunchDaemons } service_start_macos() { echo \* starting zapret service "$INIT_SCRIPT_SRC" start } service_stop_macos() { echo \* stopping zapret service "$INIT_SCRIPT_SRC" stop } service_remove_macos() { echo \* removing zapret service rm -f /Library/LaunchDaemons/zapret.plist zapret_stop_daemons } remove_macos_firewall() { echo \* removing zapret PF hooks pf_anchors_clear pf_anchors_del pf_anchor_root_del pf_anchor_root_reload } sedi() { # MacOS doesnt support -i without parameter. busybox doesnt support -i with parameter. # its not possible to put "sed -i ''" to a variable and then use it if [ "$SYSTEM" = "macos" ]; then sed -i '' "$@" else sed -i "$@" fi } write_config_var() { # $1 - mode var local M eval M="\$$1" # replace / => \/ #M=${M//\//\\\/} M=$(echo $M | sed 's/\//\\\//g' | trim) grep -q "^[[:space:]]*$1=\|^#*[[:space:]]*$1=" "$ZAPRET_CONFIG" || { # var does not exist in config. add it echo $1= >>"$ZAPRET_CONFIG" } replace_var_def $1 "$M" "$ZAPRET_CONFIG" } no_prereq_exit() { echo could not install prerequisites exitp 6 } check_prerequisites_linux() { echo \* checking prerequisites local s cmd PKGS UTILS req="curl curl" local APTGET DNF YUM PACMAN ZYPPER EOPKG APK case "$FWTYPE" in iptables) req="$req iptables iptables ip6tables iptables ipset ipset" ;; nftables) req="$req nft nftables" ;; esac PKGS=$(for s in $req; do echo $s; done | while read cmd; do read pkg exists $cmd || echo $pkg done | sort -u | xargs) UTILS=$(for s in $req; do echo $s; done | while read cmd; do read pkg echo $cmd done | sort -u | xargs) if [ -z "$PKGS" ] ; then echo required utilities exist : $UTILS else echo \* installing prerequisites echo packages required : $PKGS APTGET=$(whichq apt-get) DNF=$(whichq dnf) YUM=$(whichq yum) PACMAN=$(whichq pacman) ZYPPER=$(whichq zypper) EOPKG=$(whichq eopkg) APK=$(whichq apk) if [ -x "$APTGET" ] ; then "$APTGET" update "$APTGET" install -y --no-install-recommends $PKGS dnsutils || no_prereq_exit elif [ -x "$DNF" ] ; then "$DNF" -y install $PKGS || no_prereq_exit elif [ -x "$YUM" ] ; then "$YUM" -y install $PKGS || no_prereq_exit elif [ -x "$PACMAN" ] ; then "$PACMAN" -Syy "$PACMAN" --noconfirm -S $PKGS || no_prereq_exit elif [ -x "$ZYPPER" ] ; then "$ZYPPER" --non-interactive install $PKGS || no_prereq_exit elif [ -x "$EOPKG" ] ; then "$EOPKG" -y install $PKGS || no_prereq_exit elif [ -x "$APK" ] ; then "$APK" update # for alpine [ "$FWTYPE" = iptables ] && [ -n "$($APK list ip6tables)" ] && PKGS="$PKGS ip6tables" "$APK" add $PKGS || no_prereq_exit else echo supported package manager not found echo you must manually install : $UTILS exitp 5 fi fi } removable_pkgs_openwrt() { local pkg PKGS2 [ -n "$OPENWRT_FW4" ] && PKGS2="$PKGS2 iptables-zz-legacy iptables ip6tables-zz-legacy ip6tables" [ -n "$OPENWRT_FW3" ] && PKGS2="$PKGS2 nftables-json nftables-nojson nftables" PKGS= for pkg in $PKGS2; do check_package_exists_openwrt $pkg && PKGS="${PKGS:+$PKGS }$pkg" done PKGS="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" } openwrt_fix_broken_apk_uninstall_scripts() { # at least in early snapshots with apk removing gnu gzip, sort, ... does not restore links to busybox # system may become unusable exists sort || { echo fixing missing sort; ln -fs /bin/busybox /usr/bin/sort; } exists gzip || { echo fixing missing gzip; ln -fs /bin/busybox /bin/gzip; } exists sleep || { echo fixing missing sleep; ln -fs /bin/busybox /bin/sleep; } } remove_extra_pkgs_openwrt() { local PKGS echo \* remove dependencies removable_pkgs_openwrt echo these packages may have been installed by install_easy.sh : $PKGS ask_yes_no N "do you want to remove them" && { case $OPENWRT_PACKAGER in opkg) opkg remove --autoremove $PKGS ;; apk) apk del $PKGS openwrt_fix_broken_apk_uninstall_scripts ;; esac } } check_prerequisites_openwrt() { echo \* checking prerequisites local PKGS="curl" UPD=0 local pkg_iptables case "$FWTYPE" in iptables) pkg_iptables=iptables check_package_exists_openwrt iptables-zz-legacy && pkg_iptables=iptables-zz-legacy PKGS="$PKGS ipset $pkg_iptables iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra iptables-mod-u32" check_package_exists_openwrt ip6tables-zz-legacy && pkg_iptables=ip6tables-zz-legacy [ "$DISABLE_IPV6" = 1 ] || PKGS="$PKGS $pkg_iptables ip6tables-mod-nat ip6tables-extra" ;; nftables) PKGS="$PKGS nftables kmod-nft-nat kmod-nft-offload kmod-nft-queue" ;; esac if check_packages_openwrt $PKGS ; then echo everything is present else echo \* installing prerequisites $OPENWRT_PACKAGER_UPDATE UPD=1 $OPENWRT_PACKAGER_INSTALL $PKGS || { echo could not install prerequisites exitp 6 } fi is_linked_to_busybox gzip && { echo echo your system uses default busybox gzip. its several times slower than GNU gzip. echo ip/host list scripts will run much faster with GNU gzip echo installer can install GNU gzip but it requires about 100 Kb space if ask_yes_no N "do you want to install GNU gzip"; then [ "$UPD" = "0" ] && { $OPENWRT_PACKAGER_UPDATE UPD=1 } $OPENWRT_PACKAGER_INSTALL --force-overwrite gzip fi } is_linked_to_busybox sort && { echo echo your system uses default busybox sort. its much slower and consumes much more RAM than GNU sort echo ip/host list scripts will run much faster with GNU sort echo installer can install GNU sort but it requires about 100 Kb space if ask_yes_no N "do you want to install GNU sort"; then [ "$UPD" = "0" ] && { $OPENWRT_PACKAGER_UPDATE UPD=1 } $OPENWRT_PACKAGER_INSTALL --force-overwrite coreutils-sort fi } [ "$FSLEEP" = 0 ] && is_linked_to_busybox sleep && { echo echo no methods of sub-second sleep were found. echo if you want to speed up blockcheck install coreutils-sleep. it requires about 40 Kb space if ask_yes_no N "do you want to install COREUTILS sleep"; then [ "$UPD" = "0" ] && { $OPENWRT_PACKAGER_UPDATE UPD=1 } $OPENWRT_PACKAGER_INSTALL --force-overwrite coreutils-sleep fsleep_setup fi } } select_ipv6() { local T=N [ "$DISABLE_IPV6" != '1' ] && T=Y local old6=$DISABLE_IPV6 echo if ask_yes_no $T "enable ipv6 support"; then DISABLE_IPV6=0 else DISABLE_IPV6=1 fi [ "$old6" != "$DISABLE_IPV6" ] && write_config_var DISABLE_IPV6 } select_fwtype() { echo [ $(get_ram_mb) -le 400 ] && { echo WARNING ! you are running a low RAM system echo WARNING ! nft requires lots of RAM to load huge ip sets, much more than ipsets require echo WARNING ! if you need large lists it may be necessary to fall back to iptables+ipset firewall } echo select firewall type : ask_list FWTYPE "iptables nftables" "$FWTYPE" # always write config var to prevent auto discovery every time write_config_var FWTYPE } dry_run_tpws_() { local TPWS="$ZAPRET_BASE/tpws/tpws" echo verifying tpws options "$TPWS" --dry-run ${WS_USER:+--user=$WS_USER} "$@" } dry_run_nfqws_() { local NFQWS="$ZAPRET_BASE/nfq/nfqws" echo verifying nfqws options "$NFQWS" --dry-run ${WS_USER:+--user=$WS_USER} "$@" } dry_run_tpws() { [ "$TPWS_ENABLE" = 1 ] || return 0 local opt="$TPWS_OPT" port=${TPPORT_SOCKS:-988} filter_apply_hostlist_target opt dry_run_tpws_ --port=$port $opt } dry_run_tpws_socks() { [ "$TPWS_SOCKS_ENABLE" = 1 ] || return 0 local opt="$TPWS_SOCKS_OPT" port=${TPPORT:-987} filter_apply_hostlist_target opt dry_run_tpws_ --port=$port --socks $opt } dry_run_nfqws() { [ "$NFQWS_ENABLE" = 1 ] || return 0 local opt="$NFQWS_OPT" qn=${QNUM:-200} filter_apply_hostlist_target opt dry_run_nfqws_ --qnum=$qn $opt } ================================================ FILE: common/ipt.sh ================================================ std_ports ipt_connbytes="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes" IPSET_EXCLUDE="-m set ! --match-set nozapret" IPSET_EXCLUDE6="-m set ! --match-set nozapret6" IPBAN_EXCLUDE="-m set ! --match-set ipban" IPBAN_EXCLUDE6="-m set ! --match-set ipban6" ipt() { iptables $FW_EXTRA_PRE -C "$@" $FW_EXTRA_POST >/dev/null 2>/dev/null || iptables $FW_EXTRA_PRE -I "$@" $FW_EXTRA_POST } ipta() { iptables $FW_EXTRA_PRE -C "$@" $FW_EXTRA_POST >/dev/null 2>/dev/null || iptables $FW_EXTRA_PRE -A "$@" $FW_EXTRA_POST } ipt_del() { iptables $FW_EXTRA_PRE -C "$@" $FW_EXTRA_POST >/dev/null 2>/dev/null && iptables $FW_EXTRA_PRE -D "$@" $FW_EXTRA_POST } ipt_add_del() { on_off_function ipt ipt_del "$@" } ipta_add_del() { on_off_function ipta ipt_del "$@" } ipt6() { ip6tables -C "$@" >/dev/null 2>/dev/null || ip6tables -I "$@" } ipt6a() { ip6tables -C "$@" >/dev/null 2>/dev/null || ip6tables -A "$@" } ipt6_del() { ip6tables -C "$@" >/dev/null 2>/dev/null && ip6tables -D "$@" } ipt6_add_del() { on_off_function ipt6 ipt6_del "$@" } ipt6a_add_del() { on_off_function ipt6a ipt6_del "$@" } is_ipt_flow_offload_avail() { # $1 = '' for ipv4, '6' for ipv6 grep -q FLOWOFFLOAD 2>/dev/null /proc/net/ip$1_tables_targets } filter_apply_ipset_target4() { # $1 - var name of ipv4 iptables filter if [ "$MODE_FILTER" = "ipset" ]; then eval $1="\"\$$1 -m set --match-set zapret dst\"" fi } filter_apply_ipset_target6() { # $1 - var name of ipv6 iptables filter if [ "$MODE_FILTER" = "ipset" ]; then eval $1="\"\$$1 -m set --match-set zapret6 dst\"" fi } filter_apply_ipset_target() { # $1 - var name of ipv4 iptables filter # $2 - var name of ipv6 iptables filter filter_apply_ipset_target4 $1 filter_apply_ipset_target6 $2 } reverse_nfqws_rule_stream() { sed -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" } reverse_nfqws_rule() { echo "$@" | reverse_nfqws_rule_stream } prepare_tpws_fw4() { # otherwise linux kernel will treat 127.0.0.0/8 as "martian" ip and refuse routing to it # 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 [ "$DISABLE_IPV4" = "1" ] || { iptables -N input_rule_zapret 2>/dev/null ipt input_rule_zapret -d $TPWS_LOCALHOST4 -j RETURN ipta input_rule_zapret -d 127.0.0.0/8 -j DROP ipt INPUT ! -i lo -j input_rule_zapret prepare_route_localnet } } unprepare_tpws_fw4() { [ "$DISABLE_IPV4" = "1" ] || { unprepare_route_localnet ipt_del INPUT ! -i lo -j input_rule_zapret iptables -F input_rule_zapret 2>/dev/null iptables -X input_rule_zapret 2>/dev/null } } unprepare_tpws_fw() { unprepare_tpws_fw4 } ipt_mark_filter() { [ -n "$FILTER_MARK" ] && echo "-m mark --mark $FILTER_MARK/$FILTER_MARK" } ipt_print_op() { if [ "$1" = "1" ]; then echo "Inserting ip$4tables rule for $3 : $2" else echo "Deleting ip$4tables rule for $3 : $2" fi } _fw_tpws4() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - tpws port # $4 - lan interface names space separated # $5 - wan interface names space separated [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { local i rule [ "$1" = 1 ] && prepare_tpws_fw4 ipt_print_op $1 "$2" "tpws (port $3)" rule="$(ipt_mark_filter) $2 $IPSET_EXCLUDE dst $IPBAN_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$3" for i in $4 ; do ipt_add_del $1 PREROUTING -t nat -i $i $rule done rule="-m owner ! --uid-owner $WS_USER $rule" if [ -n "$5" ]; then for i in $5; do ipt_add_del $1 OUTPUT -t nat -o $i $rule done else ipt_add_del $1 OUTPUT -t nat $rule fi } } _fw_tpws6() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv6 # $3 - tpws port # $4 - lan interface names space separated # $5 - wan interface names space separated [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { local i rule DNAT6 ipt_print_op $1 "$2" "tpws (port $3)" 6 rule="$(ipt_mark_filter) $2 $IPSET_EXCLUDE6 dst $IPBAN_EXCLUDE6 dst" for i in $4 ; do _dnat6_target $i DNAT6 [ -n "$DNAT6" -a "$DNAT6" != "-" ] && ipt6_add_del $1 PREROUTING -t nat -i $i $rule -j DNAT --to [$DNAT6]:$3 done rule="-m owner ! --uid-owner $WS_USER $rule -j DNAT --to [::1]:$3" if [ -n "$5" ]; then for i in $5; do ipt6_add_del $1 OUTPUT -t nat -o $i $rule done else ipt6_add_del $1 OUTPUT -t nat $rule fi } } fw_tpws() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - iptable filter for ipv6 # $4 - tpws port fw_tpws4 $1 "$2" $4 fw_tpws6 $1 "$3" $4 } _fw_nfqws_post4() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - queue number # $4 - wan interface names space separated [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { local i ipt_print_op $1 "$2" "nfqws postrouting (qnum $3)" rule="$(ipt_mark_filter) -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK $2 $IPSET_EXCLUDE dst -j NFQUEUE --queue-num $3 --queue-bypass" if [ -n "$4" ] ; then for i in $4; do ipt_add_del $1 POSTROUTING -t mangle -o $i $rule done else ipt_add_del $1 POSTROUTING -t mangle $rule fi } } _fw_nfqws_post6() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv6 # $3 - queue number # $4 - wan interface names space separated [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { local i ipt_print_op $1 "$2" "nfqws postrouting (qnum $3)" 6 rule="$(ipt_mark_filter) -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK $2 $IPSET_EXCLUDE6 dst -j NFQUEUE --queue-num $3 --queue-bypass" if [ -n "$4" ] ; then for i in $4; do ipt6_add_del $1 POSTROUTING -t mangle -o $i $rule done else ipt6_add_del $1 POSTROUTING -t mangle $rule fi } } fw_nfqws_post() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - iptable filter for ipv6 # $4 - queue number fw_nfqws_post4 $1 "$2" $4 fw_nfqws_post6 $1 "$3" $4 } _fw_nfqws_pre4() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - queue number # $4 - wan interface names space separated [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { local i ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)" rule="$2 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $3 --queue-bypass" if [ -n "$4" ] ; then for i in $4; do # iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there ipt_add_del $1 INPUT -t mangle -i $i $rule ipt_add_del $1 FORWARD -t mangle -i $i $rule done else ipt_add_del $1 INPUT -t mangle $rule ipt_add_del $1 FORWARD -t mangle $rule fi } } _fw_nfqws_pre6() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv6 # $3 - queue number # $4 - wan interface names space separated [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { local i ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)" 6 rule="$2 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $3 --queue-bypass" if [ -n "$4" ] ; then for i in $4; do # iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there ipt6_add_del $1 INPUT -t mangle -i $i $rule ipt6_add_del $1 FORWARD -t mangle -i $i $rule done else ipt6_add_del $1 INPUT -t mangle $rule ipt6_add_del $1 FORWARD -t mangle $rule fi } } fw_nfqws_pre() { # $1 - 1 - add, 0 - del # $2 - iptable filter for ipv4 # $3 - iptable filter for ipv6 # $4 - queue number fw_nfqws_pre4 $1 "$2" $4 fw_nfqws_pre6 $1 "$3" $4 } fw_reverse_nfqws_rule4() { fw_nfqws_pre4 $1 "$(reverse_nfqws_rule "$2")" $3 } fw_reverse_nfqws_rule6() { fw_nfqws_pre6 $1 "$(reverse_nfqws_rule "$2")" $3 } fw_reverse_nfqws_rule() { # ensure that modes relying on incoming traffic work # $1 - 1 - add, 0 - del # $2 - rule4 # $3 - rule6 # $4 - queue number fw_reverse_nfqws_rule4 $1 "$2" $4 fw_reverse_nfqws_rule6 $1 "$3" $4 } ipt_first_packets() { # $1 - packet count [ -n "$1" -a "$1" != keepalive ] && [ "$1" -ge 1 ] && echo "$ipt_connbytes 1:$1" } ipt_do_nfqws_in_out() { # $1 - 1 - add, 0 - del # $2 - tcp,udp # $3 - ports # $4 - PKT_OUT. special value : 'keepalive' # $5 - PKT_IN local f4 f6 first_packets_only [ -n "$3" ] || return [ -n "$4" -a "$4" != 0 ] && { first_packets_only="$(ipt_first_packets $4)" f4="-p $2 -m multiport --dports $3 $first_packets_only" f6=$f4 filter_apply_ipset_target f4 f6 fw_nfqws_post $1 "$f4" "$f6" $QNUM } [ -n "$5" -a "$5" != 0 ] && { first_packets_only="$(ipt_first_packets $5)" f4="-p $2 -m multiport --dports $3 $first_packets_only" f6=$f4 filter_apply_ipset_target f4 f6 fw_reverse_nfqws_rule $1 "$f4" "$f6" $QNUM } } zapret_do_firewall_standard_tpws_rules_ipt() { # $1 - 1 - add, 0 - del local f4 f6 [ "$TPWS_ENABLE" = 1 -a -n "$TPWS_PORTS" ] && { f4="-p tcp -m multiport --dports $TPWS_PORTS_IPT" f6=$f4 filter_apply_ipset_target f4 f6 fw_tpws $1 "$f4" "$f6" $TPPORT } } zapret_do_firewall_standard_nfqws_rules_ipt() { # $1 - 1 - add, 0 - del [ "$NFQWS_ENABLE" = 1 ] && { ipt_do_nfqws_in_out $1 tcp "$NFQWS_PORTS_TCP_IPT" "$NFQWS_TCP_PKT_OUT" "$NFQWS_TCP_PKT_IN" ipt_do_nfqws_in_out $1 tcp "$NFQWS_PORTS_TCP_KEEPALIVE_IPT" keepalive "$NFQWS_TCP_PKT_IN" ipt_do_nfqws_in_out $1 udp "$NFQWS_PORTS_UDP_IPT" "$NFQWS_UDP_PKT_OUT" "$NFQWS_UDP_PKT_IN" ipt_do_nfqws_in_out $1 udp "$NFQWS_PORTS_UDP_KEEPALIVE_IPT" keepalive "$NFQWS_UDP_PKT_IN" } } zapret_do_firewall_standard_rules_ipt() { # $1 - 1 - add, 0 - del zapret_do_firewall_standard_tpws_rules_ipt $1 zapret_do_firewall_standard_nfqws_rules_ipt $1 } zapret_do_firewall_rules_ipt() { # $1 - 1 - add, 0 - del zapret_do_firewall_standard_rules_ipt $1 custom_runner zapret_custom_firewall $1 zapret_do_icmp_filter $1 } zapret_do_icmp_filter() { # $1 - 1 - add, 0 - del local FW_EXTRA_PRE= FW_EXTRA_POST= [ "$FILTER_TTL_EXPIRED_ICMP" = 1 ] && { [ "$DISABLE_IPV4" = 1 ] || { ipt_add_del $1 POSTROUTING -t mangle -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CONNMARK --or-mark $DESYNC_MARK ipt_add_del $1 INPUT -p icmp -m icmp --icmp-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP ipt_add_del $1 FORWARD -p icmp -m icmp --icmp-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP } [ "$DISABLE_IPV6" = 1 ] || { ipt6_add_del $1 POSTROUTING -t mangle -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CONNMARK --or-mark $DESYNC_MARK ipt6_add_del $1 INPUT -p icmpv6 -m icmp6 --icmpv6-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP ipt6_add_del $1 FORWARD -p icmpv6 -m icmp6 --icmpv6-type time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP } } } zapret_do_firewall_ipt() { # $1 - 1 - add, 0 - del if [ "$1" = 1 ]; then echo Applying iptables else echo Clearing iptables fi # always create ipsets. ip_exclude ipset is required [ "$1" = 1 ] && create_ipset no-update zapret_do_firewall_rules_ipt "$@" if [ "$1" = 1 ] ; then existf flow_offloading_exempt && flow_offloading_exempt else existf flow_offloading_unexempt && flow_offloading_unexempt unprepare_tpws_fw fi return 0 } ================================================ FILE: common/linux_daemons.sh ================================================ standard_mode_tpws_socks() { # $1 - 1 - run, 0 - stop local opt [ "$TPWS_SOCKS_ENABLE" = 1 ] && { opt="--port=$TPPORT_SOCKS $TPWS_SOCKS_OPT" filter_apply_hostlist_target opt do_tpws_socks $1 2 "$opt" } } standard_mode_tpws() { # $1 - 1 - run, 0 - stop local opt [ "$TPWS_ENABLE" = 1 ] && check_bad_ws_options $1 "$TPWS_OPT" && { opt="--port=$TPPORT $TPWS_OPT" filter_apply_hostlist_target opt do_tpws $1 1 "$opt" } } standard_mode_nfqws() { # $1 - 1 - run, 0 - stop local opt [ "$NFQWS_ENABLE" = 1 ] && check_bad_ws_options $1 "$NFQWS_OPT" && { opt="--qnum=$QNUM $NFQWS_OPT" filter_apply_hostlist_target opt do_nfqws $1 3 "$opt" } } standard_mode_daemons() { # $1 - 1 - run, 0 - stop standard_mode_tpws_socks $1 standard_mode_tpws $1 standard_mode_nfqws $1 } zapret_do_daemons() { # $1 - 1 - run, 0 - stop standard_mode_daemons $1 custom_runner zapret_custom_daemons $1 return 0 } zapret_run_daemons() { zapret_do_daemons 1 "$@" } zapret_stop_daemons() { zapret_do_daemons 0 "$@" } ================================================ FILE: common/linux_fw.sh ================================================ set_conntrack_liberal_mode() { [ -n "$SKIP_CONNTRACK_LIBERAL_MODE" ] || sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=$1 } zapret_do_firewall() { linux_fwtype [ "$1" = 1 -a -n "$INIT_FW_PRE_UP_HOOK" ] && $INIT_FW_PRE_UP_HOOK [ "$1" = 0 -a -n "$INIT_FW_PRE_DOWN_HOOK" ] && $INIT_FW_PRE_DOWN_HOOK case "$FWTYPE" in iptables) zapret_do_firewall_ipt "$@" ;; nftables) zapret_do_firewall_nft "$@" ;; esac # russian DPI sends RST,ACK with wrong ACK. # this is sometimes treated by conntrack as invalid and connbytes fw rules do not pass RST packet to nfqws. # switch on liberal mode on zapret firewall start and switch off on zapret firewall stop # this is only required for processing incoming bad RSTs. incoming rules are only applied in autohostlist mode # calling this after firewall because conntrack module can be not loaded before applying conntrack firewall rules [ "$MODE_FILTER" = "autohostlist" ] && set_conntrack_liberal_mode $1 [ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK [ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK return 0 } zapret_apply_firewall() { zapret_do_firewall 1 "$@" } zapret_unapply_firewall() { zapret_do_firewall 0 "$@" } ================================================ FILE: common/linux_iphelper.sh ================================================ # there's no route_localnet for ipv6 # the best we can is to route to link local of the incoming interface # OUTPUT - can DNAT to ::1 # PREROUTING - can't DNAT to ::1. can DNAT to link local of -i interface or to any global addr # not a good idea to expose tpws to the world (bind to ::) # max wait time for the link local ipv6 on the LAN interface LINKLOCAL_WAIT_SEC=${LINKLOCAL_WAIT_SEC:-5} get_ipv6_linklocal() { # $1 - interface name. if empty - any interface if exists ip ; then local dev [ -n "$1" ] && dev="dev $1" ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope link.*$/\1/;t;d' | head -n 1 else ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Link.*$/\1/;t;d' | head -n 1 fi } get_ipv6_global() { # $1 - interface name. if empty - any interface if exists ip ; then local dev [ -n "$1" ] && dev="dev $1" ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope global.*$/\1/;t;d' | head -n 1 else ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Global.*$/\1/;t;d' | head -n 1 fi } iface_is_up() { # $1 - interface name [ -f /sys/class/net/$1/operstate ] || return local state read state /dev/null } nft_list_table() { nft -t list table inet $ZAPRET_NFT_TABLE } nft_create_set() { # $1 - set name # $2 - params nft create set inet $ZAPRET_NFT_TABLE $1 "{ $2 }" 2>/dev/null } nft_del_set() { # $1 - set name nft delete set inet $ZAPRET_NFT_TABLE $1 } nft_flush_set() { # $1 - set name nft flush set inet $ZAPRET_NFT_TABLE $1 } nft_set_exists() { # $1 - set name nft -t list set inet $ZAPRET_NFT_TABLE $1 2>/dev/null >/dev/null } nft_flush_chain() { # $1 - chain name nft flush chain inet $ZAPRET_NFT_TABLE $1 } nft_del_all_chains_from_table() { # $1 - table_name with or without family # delete all chains with possible references to each other # cannot just delete all in the list because of references # avoid infinite loops local chains deleted=1 error=1 while [ -n "$deleted" -a -n "$error" ]; do chains=$(nft -t list table $1 2>/dev/null | sed -nre "s/^[ ]*chain ([^ ]+) \{/\1/p" | xargs) [ -n "$chains" ] || break deleted= error= for chain in $chains; do if nft delete chain $1 $chain 2>/dev/null; then deleted=1 else error=1 fi done done } nft_create_chains() { cat << EOF | nft -f - add chain inet $ZAPRET_NFT_TABLE dnat_output { type nat hook output priority -101; } flush chain inet $ZAPRET_NFT_TABLE dnat_output add chain inet $ZAPRET_NFT_TABLE dnat_pre { type nat hook prerouting priority -101; } flush chain inet $ZAPRET_NFT_TABLE dnat_pre add chain inet $ZAPRET_NFT_TABLE forward { type filter hook forward priority -1; } flush chain inet $ZAPRET_NFT_TABLE forward add chain inet $ZAPRET_NFT_TABLE input { type filter hook input priority -1; } flush chain inet $ZAPRET_NFT_TABLE input add chain inet $ZAPRET_NFT_TABLE flow_offload flush chain inet $ZAPRET_NFT_TABLE flow_offload add chain inet $ZAPRET_NFT_TABLE localnet_protect flush chain inet $ZAPRET_NFT_TABLE localnet_protect add rule inet $ZAPRET_NFT_TABLE localnet_protect ip daddr $TPWS_LOCALHOST4 return comment "route_localnet allow access to tpws" add rule inet $ZAPRET_NFT_TABLE localnet_protect ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection" add rule inet $ZAPRET_NFT_TABLE input iif != lo jump localnet_protect add chain inet $ZAPRET_NFT_TABLE postrouting flush chain inet $ZAPRET_NFT_TABLE postrouting add chain inet $ZAPRET_NFT_TABLE postrouting_hook { type filter hook postrouting priority 99; } flush chain inet $ZAPRET_NFT_TABLE postrouting_hook add rule inet $ZAPRET_NFT_TABLE postrouting_hook mark and $DESYNC_MARK == 0 jump postrouting add chain inet $ZAPRET_NFT_TABLE postnat flush chain inet $ZAPRET_NFT_TABLE postnat add chain inet $ZAPRET_NFT_TABLE postnat_hook { type filter hook postrouting priority 101; } flush chain inet $ZAPRET_NFT_TABLE postnat_hook add rule inet $ZAPRET_NFT_TABLE postnat_hook mark and $DESYNC_MARK == 0 jump postnat add chain inet $ZAPRET_NFT_TABLE prerouting { type filter hook prerouting priority -99; } flush chain inet $ZAPRET_NFT_TABLE prerouting add chain inet $ZAPRET_NFT_TABLE prenat { type filter hook prerouting priority -101; } flush chain inet $ZAPRET_NFT_TABLE prenat add chain inet $ZAPRET_NFT_TABLE predefrag { type filter hook output priority -401; } flush chain inet $ZAPRET_NFT_TABLE predefrag add chain inet $ZAPRET_NFT_TABLE predefrag_nfqws flush chain inet $ZAPRET_NFT_TABLE predefrag_nfqws add rule inet $ZAPRET_NFT_TABLE predefrag mark and $DESYNC_MARK !=0 jump predefrag_nfqws comment "nfqws generated : avoid drop by INVALID conntrack state" add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws mark and $DESYNC_MARK_POSTNAT !=0 notrack comment "postnat traffic" add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws ip frag-off & 0x1fff != 0 notrack comment "ipfrag" add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws exthdr frag exists notrack comment "ipfrag" add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws tcp flags ! syn,rst,ack notrack comment "datanoack" add set inet $ZAPRET_NFT_TABLE lanif { type ifname; } add set inet $ZAPRET_NFT_TABLE wanif { type ifname; } add set inet $ZAPRET_NFT_TABLE wanif6 { type ifname; } add map inet $ZAPRET_NFT_TABLE link_local { type ifname : ipv6_addr; } EOF [ -n "$POSTNAT_ALL" ] && { nft_flush_chain predefrag_nfqws nft_add_rule predefrag_nfqws notrack comment \"do not track nfqws generated packets to avoid nat tampering and defragmentation\" } [ "$FILTER_TTL_EXPIRED_ICMP" = 1 ] && { if is_postnat; then # can be caused by untracked nfqws-generated packets nft_add_rule prerouting icmp type time-exceeded ct state invalid drop else nft_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\" fi [ "$DISABLE_IPV4" = "1" ] || { nft_add_rule prerouting icmp type time-exceeded ct mark and $DESYNC_MARK != 0 drop comment \"nfqws related : prevent ttl expired socket errors\" } [ "$DISABLE_IPV6" = "1" ] || { nft_add_rule prerouting icmpv6 type time-exceeded ct mark and $DESYNC_MARK != 0 drop comment \"nfqws related : prevent ttl expired socket errors\" } } } nft_del_chains() { # do not delete all chains because of additional user hooks # they must be inside zapret table to use nfsets # these chains are newer. do not fail all because chains are not present cat << EOF | nft -f - 2>/dev/null delete chain inet $ZAPRET_NFT_TABLE postrouting_hook delete chain inet $ZAPRET_NFT_TABLE postnat_hook EOF cat << EOF | nft -f - 2>/dev/null delete chain inet $ZAPRET_NFT_TABLE dnat_output delete chain inet $ZAPRET_NFT_TABLE dnat_pre delete chain inet $ZAPRET_NFT_TABLE forward delete chain inet $ZAPRET_NFT_TABLE input delete chain inet $ZAPRET_NFT_TABLE postrouting delete chain inet $ZAPRET_NFT_TABLE postnat delete chain inet $ZAPRET_NFT_TABLE prerouting delete chain inet $ZAPRET_NFT_TABLE prenat delete chain inet $ZAPRET_NFT_TABLE predefrag delete chain inet $ZAPRET_NFT_TABLE predefrag_nfqws delete chain inet $ZAPRET_NFT_TABLE flow_offload delete chain inet $ZAPRET_NFT_TABLE localnet_protect EOF # unfortunately this approach breaks udp desync of the connection initiating packet (new, first one) # delete chain inet $ZAPRET_NFT_TABLE predefrag } nft_del_flowtable() { nft delete flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null } nft_create_or_update_flowtable() { # $1 = flags ('offload' for hw offload) # $2,$3,$4,... - interfaces # can be called multiple times to add interfaces. interfaces can only be added , not removed local flags=$1 devices makelist shift # 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 # warning ! openwrt fixes this in post-21.x snapshots with special nft patch # warning ! in traditional linux distros nft is unpatched and will fail with quoted interface definitions if unfixed [ -n "$flags" ] && flags="flags $flags;" for makelist in make_quoted_comma_list make_comma_list; do $makelist devices "$@" [ -n "$devices" ] && devices="devices={$devices};" nft add flowtable inet $ZAPRET_NFT_TABLE ft "{ hook ingress priority -1; $flags $devices }" && break done } nft_flush_ifsets() { cat << EOF | nft -f - 2>/dev/null flush set inet $ZAPRET_NFT_TABLE lanif flush set inet $ZAPRET_NFT_TABLE wanif flush set inet $ZAPRET_NFT_TABLE wanif6 flush map inet $ZAPRET_NFT_TABLE link_local EOF } nft_flush_link_local() { nft flush map inet $ZAPRET_NFT_TABLE link_local 2>/dev/null } nft_list_ifsets() { nft list set inet $ZAPRET_NFT_TABLE lanif nft list set inet $ZAPRET_NFT_TABLE wanif nft list set inet $ZAPRET_NFT_TABLE wanif6 nft list map inet $ZAPRET_NFT_TABLE link_local nft list flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null } nft_create_firewall() { nft_create_table nft_del_flowtable nft_flush_link_local nft_create_chains } nft_del_firewall() { nft_del_chains nft_del_flowtable nft_flush_link_local # leave ifsets and ipsets because they may be used by custom rules } nft_add_rule() { # $1 - chain # $2,$3,... - rule(s) local chain="$1" shift nft add rule inet $ZAPRET_NFT_TABLE $chain $FW_EXTRA_PRE "$@" } nft_insert_rule() { # $1 - chain # $2,$3,... - rule(s) local chain="$1" shift nft insert rule inet $ZAPRET_NFT_TABLE $chain $FW_EXTRA_PRE "$@" } nft_add_set_element() { # $1 - set or map name # $2 - element [ -z "$2" ] || nft add element inet $ZAPRET_NFT_TABLE $1 "{ $2 }" } nft_add_set_elements() { # $1 - set or map name # $2,$3,... - element(s) local set="$1" elements shift make_comma_list elements "$@" nft_add_set_element $set "$elements" } nft_reverse_nfqws_rule() { echo "$@" | 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" } nft_clean_nfqws_rule() { echo "$@" | sed -e "s/mark and $DESYNC_MARK == 0//g" -e "s/oifname @wanif6//g" -e "s/oifname @wanif//g" } nft_add_nfqws_flow_exempt_rule() { # $1 - rule (must be all filters in one var) local FW_EXTRA_POST= FW_EXTRA_PRE= nft_add_rule flow_offload $(nft_clean_nfqws_rule $1) return comment \"direct flow offloading exemption\" # do not need this because of oifname @wanif/@wanif6 filter in forward chain #nft_add_rule flow_offload $(nft_reverse_nfqws_rule $1) return comment \"reverse flow offloading exemption\" } nft_add_flow_offload_exemption() { # "$1" - rule for ipv4 # "$2" - rule for ipv6 # "$3" - comment local FW_EXTRA_POST= FW_EXTRA_PRE= [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || nft_add_rule flow_offload oifname @wanif $1 ip daddr != @nozapret return comment \"$3\" [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || nft_add_rule flow_offload oifname @wanif6 $2 ip6 daddr != @nozapret6 return comment \"$3\" } nft_apply_flow_offloading() { # ft can be absent nft_add_rule flow_offload meta l4proto "{ tcp, udp }" flow add @ft 2>/dev/null && { nft_add_rule flow_offload meta l4proto "{ tcp, udp }" counter comment \"if offload works here must not be too much traffic\" # allow only outgoing packets to initiate flow offload nft_add_rule forward oifname @wanif jump flow_offload nft_add_rule forward oifname @wanif6 jump flow_offload } } nft_filter_apply_ipset_target4() { # $1 - var name of ipv4 nftables filter if [ "$MODE_FILTER" = "ipset" ]; then eval $1="\"\$$1 ip daddr @zapret\"" fi } nft_filter_apply_ipset_target6() { # $1 - var name of ipv6 nftables filter if [ "$MODE_FILTER" = "ipset" ]; then eval $1="\"\$$1 ip6 daddr @zapret6\"" fi } nft_filter_apply_ipset_target() { # $1 - var name of ipv4 nftables filter # $2 - var name of ipv6 nftables filter nft_filter_apply_ipset_target4 $1 nft_filter_apply_ipset_target6 $2 } nft_mark_filter() { [ -n "$FILTER_MARK" ] && echo "mark and $FILTER_MARK != 0" } nft_script_add_ifset_element() { # $1 - set name # $2 - space separated elements local elements [ -n "$2" ] && { make_quoted_comma_list elements $2 script="${script} add element inet $ZAPRET_NFT_TABLE $1 { $elements }" } } nft_fill_ifsets() { # $1 - space separated lan interface names # $2 - space separated wan interface names # $3 - space separated wan6 interface names # 4,5,6 is needed for pppoe+openwrt case. looks like it's not easily possible to resolve ethernet device behind a pppoe interface # $4 - space separated lan physical interface names (optional) # $5 - space separated wan physical interface names (optional) # $6 - space separated wan6 physical interface names (optional) local script i j ALLDEVS devs b # if large sets exist nft works very ineffectively # looks like it analyzes the whole table blob to find required data pieces # calling all in one shot helps not to waste cpu time many times script="flush set inet $ZAPRET_NFT_TABLE wanif flush set inet $ZAPRET_NFT_TABLE wanif6 flush set inet $ZAPRET_NFT_TABLE lanif" [ "$DISABLE_IPV4" = "1" ] || nft_script_add_ifset_element wanif "$2" [ "$DISABLE_IPV6" = "1" ] || nft_script_add_ifset_element wanif6 "$3" nft_script_add_ifset_element lanif "$1" echo "$script" | nft -f - case "$FLOWOFFLOAD" in software) ALLDEVS=$(unique $1 $2 $3) # unbound flowtable may cause error in older nft version nft_create_or_update_flowtable '' $ALLDEVS 2>/dev/null ;; hardware) ALLDEVS=$(unique $1 $2 $3 $4 $5 $6) # first create unbound flowtable. may cause error in older nft version nft_create_or_update_flowtable 'offload' 2>/dev/null # then add elements. some of them can cause error because unsupported for i in $ALLDEVS; do # bridge members must be added instead of the bridge itself # some members may not support hw offload. example : lan1 lan2 lan3 support, wlan0 wlan1 - not b= devs=$(resolve_lower_devices $i) for j in $devs; do # do not display error if addition failed nft_create_or_update_flowtable 'offload' $j && b=1 2>/dev/null done [ -n "$b" ] || { # no lower devices added ? try to add interface itself nft_create_or_update_flowtable 'offload' $i 2>/dev/null } done ;; esac } nft_only() { linux_fwtype case "$FWTYPE" in nftables) "$@" ;; esac } nft_print_op() { echo "Inserting nftables ipv$3 rule for $2 : $1" } _nft_fw_tpws4() { # $1 - filter ipv4 # $2 - tpws port # $3 - not-empty if wan interface filtering required [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { local filter="$1" port="$2" local mark_filter=$(nft_mark_filter) nft_print_op "$filter" "tpws (port $2)" 4 nft_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 nft_insert_rule dnat_pre iifname @lanif $mark_filter $filter ip daddr != @nozapret ip daddr != @ipban $FW_EXTRA_POST dnat ip to $TPWS_LOCALHOST4:$port prepare_route_localnet } } _nft_fw_tpws6() { # $1 - filter ipv6 # $2 - tpws port # $3 - lan interface names space separated # $4 - not-empty if wan interface filtering required [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { local filter="$1" port="$2" DNAT6 i local mark_filter=$(nft_mark_filter) nft_print_op "$filter" "tpws (port $port)" 6 nft_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 "$3" ] && { nft_insert_rule dnat_pre $mark_filter $filter ip6 daddr != @nozapret6 ip6 daddr != @ipban6 $FW_EXTRA_POST dnat ip6 to iifname map @link_local:$port for i in $3; do _dnat6_target $i DNAT6 # can be multiple tpws processes on different ports [ -n "$DNAT6" -a "$DNAT6" != '-' ] && nft_add_set_element link_local "$i : $DNAT6" done } } } nft_fw_tpws() { # $1 - filter ipv4 # $2 - filter ipv6 # $3 - tpws port nft_fw_tpws4 "$1" $3 nft_fw_tpws6 "$2" $3 } is_postnat() { [ "$POSTNAT" != 0 -o "$POSTNAT_ALL" = 1 ] } get_postchain() { if is_postnat ; then echo -n postnat else echo -n postrouting fi } get_prechain() { if is_postnat ; then echo -n prenat else echo -n prerouting fi } _nft_fw_nfqws_post4() { # $1 - filter ipv4 # $2 - queue number # $3 - not-empty if wan interface filtering required [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { local filter="$1" port="$2" rule chain=$(get_postchain) setmark nft_print_op "$filter" "nfqws postrouting (qnum $port)" 4 rule="${3:+oifname @wanif} $(nft_mark_filter) $filter ip daddr != @nozapret" is_postnat && setmark="meta mark set meta mark or $DESYNC_MARK_POSTNAT" nft_insert_rule $chain $rule $setmark $CONNMARKER $FW_EXTRA_POST queue num $port bypass nft_add_nfqws_flow_exempt_rule "$rule" } } _nft_fw_nfqws_post6() { # $1 - filter ipv6 # $2 - queue number # $3 - not-empty if wan interface filtering required [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { local filter="$1" port="$2" rule chain=$(get_postchain) setmark nft_print_op "$filter" "nfqws postrouting (qnum $port)" 6 rule="${3:+oifname @wanif6} $(nft_mark_filter) $filter ip6 daddr != @nozapret6" is_postnat && setmark="meta mark set meta mark or $DESYNC_MARK_POSTNAT" nft_insert_rule $chain $rule $setmark $CONNMARKER $FW_EXTRA_POST queue num $port bypass nft_add_nfqws_flow_exempt_rule "$rule" } } nft_fw_nfqws_post() { # $1 - filter ipv4 # $2 - filter ipv6 # $3 - queue number nft_fw_nfqws_post4 "$1" $3 nft_fw_nfqws_post6 "$2" $3 } _nft_fw_nfqws_pre4() { # $1 - filter ipv4 # $2 - queue number # $3 - not-empty if wan interface filtering required [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { local filter="$1" port="$2" rule nft_print_op "$filter" "nfqws prerouting (qnum $port)" 4 rule="${3:+iifname @wanif} $filter ip saddr != @nozapret" nft_insert_rule $(get_prechain) $rule $CONNMARKER $FW_EXTRA_POST queue num $port bypass } } _nft_fw_nfqws_pre6() { # $1 - filter ipv6 # $2 - queue number # $3 - not-empty if wan interface filtering required [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { local filter="$1" port="$2" rule nft_print_op "$filter" "nfqws prerouting (qnum $port)" 6 rule="${3:+iifname @wanif6} $filter ip6 saddr != @nozapret6" nft_insert_rule $(get_prechain) $rule $CONNMARKER $FW_EXTRA_POST queue num $port bypass } } nft_fw_nfqws_pre() { # $1 - filter ipv4 # $2 - filter ipv6 # $3 - queue number nft_fw_nfqws_pre4 "$1" $3 nft_fw_nfqws_pre6 "$2" $3 } nft_fw_nfqws_both4() { # $1 - filter ipv4 # $2 - queue number nft_fw_nfqws_post4 "$@" nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $1)" $2 } nft_fw_nfqws_both6() { # $1 - filter ipv6 # $2 - queue number nft_fw_nfqws_post6 "$@" nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $1)" $2 } nft_fw_nfqws_both() { # $1 - filter ipv4 # $2 - filter ipv6 # $3 - queue number nft_fw_nfqws_both4 "$1" "$3" nft_fw_nfqws_both6 "$2" "$3" } zapret_reload_ifsets() { nft_only nft_create_table ; nft_fill_ifsets_overload return 0 } zapret_list_ifsets() { nft_only nft_list_ifsets return 0 } zapret_list_table() { nft_only nft_list_table return 0 } nft_fw_reverse_nfqws_rule4() { nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule "$1")" $2 } nft_fw_reverse_nfqws_rule6() { nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule "$1")" $2 } nft_fw_reverse_nfqws_rule() { # ensure that modes relying on incoming traffic work # $1 - rule4 # $2 - rule6 # $3 - queue number nft_fw_reverse_nfqws_rule4 "$1" $3 nft_fw_reverse_nfqws_rule6 "$2" $3 } nft_first_packets() { # $1 - packet count [ -n "$1" -a "$1" != keepalive ] && [ "$1" -ge 1 ] && { if [ "$1" = 1 ] ; then echo "$nft_connbytes 1" else echo "$nft_connbytes 1-$1" fi } } nft_apply_nfqws_in_out() { # $1 - tcp,udp # $2 - ports # $3 - PKT_OUT. special value : 'keepalive' # $4 - PKT_IN local f4 f6 first_packets_only [ -n "$2" ] || return [ -n "$3" -a "$3" != 0 ] && { first_packets_only="$(nft_first_packets $3)" f4="$1 dport {$2} $first_packets_only" f6=$f4 nft_filter_apply_ipset_target f4 f6 nft_fw_nfqws_post "$f4" "$f6" $QNUM } [ -n "$4" -a "$4" != 0 ] && { first_packets_only="$(nft_first_packets $4)" f4="$1 dport {$2} $first_packets_only" f6=$f4 nft_filter_apply_ipset_target f4 f6 nft_fw_reverse_nfqws_rule "$f4" "$f6" $QNUM } } zapret_apply_firewall_standard_tpws_rules_nft() { local f4 f6 [ "$TPWS_ENABLE" = 1 -a -n "$TPWS_PORTS" ] && { f4="tcp dport {$TPWS_PORTS}" f6=$f4 nft_filter_apply_ipset_target f4 f6 nft_fw_tpws "$f4" "$f6" $TPPORT } } zapret_apply_firewall_standard_nfqws_rules_nft() { [ "$NFQWS_ENABLE" = 1 ] && { nft_apply_nfqws_in_out tcp "$NFQWS_PORTS_TCP" "$NFQWS_TCP_PKT_OUT" "$NFQWS_TCP_PKT_IN" nft_apply_nfqws_in_out tcp "$NFQWS_PORTS_TCP_KEEPALIVE" keepalive "$NFQWS_TCP_PKT_IN" nft_apply_nfqws_in_out udp "$NFQWS_PORTS_UDP" "$NFQWS_UDP_PKT_OUT" "$NFQWS_UDP_PKT_IN" nft_apply_nfqws_in_out udp "$NFQWS_PORTS_UDP_KEEPALIVE" keepalive "$NFQWS_UDP_PKT_IN" } } zapret_apply_firewall_standard_rules_nft() { zapret_apply_firewall_standard_tpws_rules_nft zapret_apply_firewall_standard_nfqws_rules_nft } zapret_apply_firewall_rules_nft() { zapret_apply_firewall_standard_rules_nft custom_runner zapret_custom_firewall_nft } zapret_apply_firewall_nft() { echo Applying nftables create_ipset no-update nft_create_firewall nft_fill_ifsets_overload zapret_apply_firewall_rules_nft [ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && nft_apply_flow_offloading return 0 } zapret_unapply_firewall_nft() { echo Clearing nftables unprepare_route_localnet nft_del_firewall custom_runner zapret_custom_firewall_nft_flush return 0 } zapret_do_firewall_nft() { # $1 - 1 - add, 0 - del if [ "$1" = 0 ] ; then zapret_unapply_firewall_nft else zapret_apply_firewall_nft fi return 0 } # ctmark is not available in POSTNAT mode CONNMARKER= [ "$FILTER_TTL_EXPIRED_ICMP" = 1 ] && is_postnat && CONNMARKER="ct mark set ct mark or $DESYNC_MARK" ================================================ FILE: common/pf.sh ================================================ PF_MAIN="/etc/pf.conf" PF_ANCHOR_DIR="/etc/pf.anchors" PF_ANCHOR_ZAPRET="$PF_ANCHOR_DIR/zapret" PF_ANCHOR_ZAPRET_V4="$PF_ANCHOR_DIR/zapret-v4" PF_ANCHOR_ZAPRET_V6="$PF_ANCHOR_DIR/zapret-v6" std_ports pf_anchor_root_reload() { echo reloading PF root anchor pfctl -qf "$PF_MAIN" } pf_anchor_root() { local patch [ -f "$PF_MAIN" ] && { grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" || { echo patching rdr-anchor in $PF_MAIN patch=1 sed -i '' -e '/^rdr-anchor "com\.apple\/\*"$/i \ rdr-anchor "zapret" ' $PF_MAIN } grep -q '^anchor "zapret"$' "$PF_MAIN" || { echo patching anchor in $PF_MAIN patch=1 sed -i '' -e '/^anchor "com\.apple\/\*"$/i \ anchor "zapret" ' $PF_MAIN } grep -q "^set limit table-entries" "$PF_MAIN" || { echo patching table-entries limit patch=1 sed -i '' -e '/^scrub-anchor "com\.apple\/\*"$/i \ set limit table-entries 5000000 ' $PF_MAIN } grep -q '^anchor "zapret"$' "$PF_MAIN" && grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" && grep -q '^set limit table-entries' "$PF_MAIN" && { if [ -n "$patch" ]; then echo successfully patched $PF_MAIN pf_anchor_root_reload else echo successfully checked zapret anchors in $PF_MAIN fi return 0 } } echo ---------------------------------- echo Automatic $PF_MAIN patching failed. You must apply root anchors manually in your PF config. echo rdr-anchor \"zapret\" echo anchor \"zapret\" echo ---------------------------------- return 1 } pf_anchor_root_del() { sed -i '' -e '/^anchor "zapret"$/d' -e '/^rdr-anchor "zapret"$/d' -e '/^set limit table-entries/d' "$PF_MAIN" } pf_anchor_zapret() { [ "$DISABLE_IPV4" = "1" ] || { if [ -f "$ZIPLIST_EXCLUDE" ]; then echo "table persist file \"$ZIPLIST_EXCLUDE\"" else echo "table persist" fi } [ "$DISABLE_IPV6" = "1" ] || { if [ -f "$ZIPLIST_EXCLUDE6" ]; then echo "table persist file \"$ZIPLIST_EXCLUDE6\"" else echo "table persist" fi } [ "$DISABLE_IPV4" = "1" ] || echo "rdr-anchor \"/zapret-v4\" inet to !" [ "$DISABLE_IPV6" = "1" ] || echo "rdr-anchor \"/zapret-v6\" inet6 to !" [ "$DISABLE_IPV4" = "1" ] || echo "anchor \"/zapret-v4\" inet to !" [ "$DISABLE_IPV6" = "1" ] || echo "anchor \"/zapret-v6\" inet6 to !" } pf_anchor_zapret_tables() { # $1 - variable to receive applied table names # $2/$3 $4/$5 ... table_name/table_file local tblv=$1 local _tbl shift [ "$MODE_FILTER" = "ipset" ] && { while [ -n "$1" ] && [ -n "$2" ] ; do [ -f "$2" ] && { echo "table <$1> file \"$2\"" _tbl="$_tbl<$1> " } shift shift done } [ -n "$_tbl" ] || _tbl="any" eval $tblv="\"\$_tbl\"" } pf_nat_reorder_rules() { # this is dirty hack to move rdr above route-to # use only first word as a key and preserve order within a single key sort -srfk 1,1 } pf_anchor_zapret_v4_tpws() { # $1 - tpws listen port # $2 - rdr ports local rule port="{$2}" for lan in $IFACE_LAN; do for t in $tbl; do echo "rdr on $lan inet proto tcp from any to $t port $port -> 127.0.0.1 port $1" done done echo "rdr on lo0 inet proto tcp from !127.0.0.0/8 to any port $port -> 127.0.0.1 port $1" for t in $tbl; do rule="route-to (lo0 127.0.0.1) inet proto tcp from !127.0.0.0/8 to $t port $port user { >root }" if [ -n "$IFACE_WAN" ] ; then for wan in $IFACE_WAN; do echo "pass out on $wan $rule" done else echo "pass out $rule" fi done } pf_anchor_zapret_v4() { local tbl port [ "$DISABLE_IPV4" = "1" ] || { { pf_anchor_zapret_tables tbl zapret-user "$ZIPLIST_USER" zapret "$ZIPLIST" custom_runner zapret_custom_firewall_v4 [ "$TPWS_ENABLE" = 1 -a -n "$TPWS_PORTS" ] && pf_anchor_zapret_v4_tpws $TPPORT "$TPWS_PORTS_IPT" } | pf_nat_reorder_rules } } pf_anchor_zapret_v6_tpws() { # $1 - tpws listen port # $2 - rdr ports local rule LL_LAN port="{$2}" # LAN link local is only for router for lan in $IFACE_LAN; do LL_LAN=$(get_ipv6_linklocal $lan) [ -n "$LL_LAN" ] && { for t in $tbl; do echo "rdr on $lan inet6 proto tcp from any to $t port $port -> $LL_LAN port $1" done } done echo "rdr on lo0 inet6 proto tcp from !::1 to any port $port -> fe80::1 port $1" for t in $tbl; do rule="route-to (lo0 fe80::1) inet6 proto tcp from !::1 to $t port $port user { >root }" if [ -n "${IFACE_WAN6:-$IFACE_WAN}" ] ; then for wan in ${IFACE_WAN6:-$IFACE_WAN}; do echo "pass out on $wan $rule" done else echo "pass out $rule" fi done } pf_anchor_zapret_v6() { local tbl port [ "$DISABLE_IPV6" = "1" ] || { { pf_anchor_zapret_tables tbl zapret-user "$ZIPLIST_USER" zapret "$ZIPLIST" custom_runner zapret_custom_firewall_v6 [ "$TPWS_ENABLE" = 1 -a -n "$TPWS_PORTS_IPT" ] && pf_anchor_zapret_v6_tpws $TPPORT "$TPWS_PORTS_IPT" } | pf_nat_reorder_rules } } pf_anchors_create() { wait_lan_ll pf_anchor_zapret >"$PF_ANCHOR_ZAPRET" pf_anchor_zapret_v4 >"$PF_ANCHOR_ZAPRET_V4" pf_anchor_zapret_v6 >"$PF_ANCHOR_ZAPRET_V6" } pf_anchors_del() { rm -f "$PF_ANCHOR_ZAPRET" "$PF_ANCHOR_ZAPRET_V4" "$PF_ANCHOR_ZAPRET_V6" } pf_anchors_load() { echo loading zapret anchor from "$PF_ANCHOR_ZAPRET" pfctl -qa zapret -f "$PF_ANCHOR_ZAPRET" || { echo error loading zapret anchor return 1 } if [ "$DISABLE_IPV4" = "1" ]; then echo clearing zapret-v4 anchor pfctl -qa zapret-v4 -F all 2>/dev/null else echo loading zapret-v4 anchor from "$PF_ANCHOR_ZAPRET_V4" pfctl -qa zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4" || { echo error loading zapret-v4 anchor return 1 } fi if [ "$DISABLE_IPV6" = "1" ]; then echo clearing zapret-v6 anchor pfctl -qa zapret-v6 -F all 2>/dev/null else echo loading zapret-v6 anchor from "$PF_ANCHOR_ZAPRET_V6" pfctl -qa zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6" || { echo error loading zapret-v6 anchor return 1 } fi echo successfully loaded PF anchors return 0 } pf_anchors_clear() { echo clearing zapret anchors pfctl -qa zapret-v4 -F all 2>/dev/null pfctl -qa zapret-v6 -F all 2>/dev/null pfctl -qa zapret -F all 2>/dev/null } pf_enable() { echo enabling PF pfctl -qe } pf_table_reload() { echo reloading zapret tables [ "$DISABLE_IPV4" = "1" ] || pfctl -qTl -a zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4" [ "$DISABLE_IPV6" = "1" ] || pfctl -qTl -a zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6" pfctl -qTl -a zapret -f "$PF_ANCHOR_ZAPRET" } ================================================ FILE: common/virt.sh ================================================ get_virt() { local vm s v UNAME UNAME=$(uname) case "$UNAME" in Linux) if exists systemd-detect-virt; then vm=$(systemd-detect-virt --vm) elif [ -f /sys/class/dmi/id/product_name ]; then read s and placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # appends ipset/zapret-hosts-auto.txt as normal list TPWS_SOCKS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " TPWS_ENABLE=0 TPWS_PORTS=80,443 # use and placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # appends ipset/zapret-hosts-auto.txt as normal list TPWS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " NFQWS_ENABLE=0 # redirect outgoing traffic with connbytes limiter applied in both directions. NFQWS_PORTS_TCP=80,443 NFQWS_PORTS_UDP=443 # PKT_OUT means connbytes dir original # PKT_IN means connbytes dir reply # this is --dpi-desync-cutoff=nX kernel mode implementation for linux. it saves a lot of CPU. NFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_TCP_PKT_IN=3 NFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_UDP_PKT_IN=0 # redirect outgoing traffic without connbytes limiter and incoming with connbytes limiter # normally it's needed only for stateless DPI that matches every packet in a single TCP session # typical example are plain HTTP keep alives # this mode can be very CPU consuming. enable with care ! #NFQWS_PORTS_TCP_KEEPALIVE=80 #NFQWS_PORTS_UDP_KEEPALIVE= # use and placeholders to engage standard hostlists and autohostlist in ipset dir # hostlist markers are replaced to empty string if MODE_FILTER does not satisfy # appends ipset/zapret-hosts-auto.txt as normal list NFQWS_OPT=" --filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 " # none,ipset,hostlist,autohostlist MODE_FILTER=none # donttouch,none,software,hardware FLOWOFFLOAD=donttouch # openwrt: specify networks to be treated as LAN. default is "lan" #OPENWRT_LAN="lan lan2 lan3" # openwrt: specify networks to be treated as WAN. default wans are interfaces with default route #OPENWRT_WAN4="wan vpn" #OPENWRT_WAN6="wan6 vpn6" # for routers based on desktop linux and macos. has no effect in openwrt. # CHOOSE LAN and optinally WAN/WAN6 NETWORK INTERFACES # or leave them commented if its not router # it's possible to specify multiple interfaces like this : IFACE_LAN="eth0 eth1 eth2" # if IFACE_WAN6 is not defined it take the value of IFACE_WAN #IFACE_LAN=eth0 #IFACE_WAN=eth1 #IFACE_WAN6="ipsec0 wireguard0 he_net" # should start/stop command of init scripts apply firewall rules ? # not applicable to openwrt with firewall3+iptables INIT_APPLY_FW=1 # firewall apply hooks #INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" #INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" #INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" #INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" # do not work with ipv4 #DISABLE_IPV4=1 # do not work with ipv6 DISABLE_IPV6=1 # drop icmp time exceeded messages for nfqws tampered connections # in POSTNAT mode this can interfere with default mtr/traceroute in tcp or udp mode. use source port not redirected to nfqws # set to 0 if you are not expecting connection breakage due to icmp in response to TCP SYN or UDP FILTER_TTL_EXPIRED_ICMP=1 # select which init script will be used to get ip or host list # possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh # comment if not required #GETLIST= ================================================ FILE: docs/LICENSE.txt ================================================ MIT License Copyright (c) 2016-2024 bol-van Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: docs/bsd.en.md ================================================ ## Table of contents - [Table of contents](#table-of-contents) - [Supported versions](#supported-versions) - [BSD features](#bsd-features) - [FreeBSD](#freebsd) - [`dvtws` quick start](#dvtws-quick-start) - [PF in FreeBSD](#pf-in-freebsd) - [`pfsense`](#pfsense) - [OpenBSD](#openbsd) - [MacOS](#macos) - [MacOS easy install](#macos-easy-install) ## Supported versions FreeBSD 11.x+ , OpenBSD 6.x+, partially MacOS Sierra+ Older versions may work or not. ## BSD features BSD does not have NFQUEUE. Similar mechanism - divert sockets. In BSD compiling the source from nfq directory result in `dvtws` binary instead of `nfqws`. `dvtws` shares most of the code with `nfqws` and offers almost identical parameters. FreeBSD has 3 firewalls: IPFilter, ipfw and Packet Filter (PF). OpenBSD has only PF. To compile sources: - FreeBSD: `make` - OpenBSD: `make bsd` - MacOS: `make mac` Compile all programs: ``` make -C /opt/zapret ``` Divert sockets are internal type sockets in the BSD kernel. They have no relation to network addresses or network packet exchange. They are identified by a port number `1..65535`. Its like queue number in NFQUEUE. Traffic can be diverted to a divert socket using firewall rule. If nobody listens on the specified divert port packets are dropped. Its similar to NFQUEUE without `--queue-bypass`. `ipset/*.sh` scripts work with ipfw lookup tables if ipfw is present. ipfw table is analog to linux `ipset`. Unlike ipsets ipfw tables share v4 an v6 addresses and subnets. - If ipfw is absent scripts check LISTS_RELOAD config variable. - If its present then scripts execute a command from LISTS_RELOAD. - If LISTS_RELOAD=- scripts do not load tables even if ipfw exists. PF can load ip tables from a file. To use this feature with `ipset/*.sh` scripts disable gzip file creation using `GZIP_LISTS=0` directive in the `/opt/zapret/config` file. BSD kernel doesn't implement splice syscall. tpws uses regular recv/send operations with data copying to user space. Its slower but not critical. `tpws` uses nonblocking sockets with linux specific epoll feature. In BSD systems epoll is emulated by epoll-shim library on top of kqueue. `dvtws` uses some programming HACKs, assumptions and knowledge of discovered bugs and limitations. BSD systems have many limitations, version specific features and bugs in low level networking, especially for ipv6. Many years have passed but BSD code still has 15-20 year artificial limiters in the code. `dvtws` uses additinal divert socket(s) for layer 3 packet injection if raw sockets do not allow it. It works for the moment but who knows. Such a usage is not very documented. `mdig` and `ip2net` are fully compatible with BSD. ## FreeBSD Divert sockets require special kernel module `ipdivert`. Write the following to config files: `/boot/loader.conf` (create if absent): ``` ipdivert_load="YES" net.inet.ip.fw.default_to_accept=1 ``` `/etc/rc.conf`: ``` firewall_enable="YES" firewall_script="/etc/rc.firewall.my" ``` `/etc/rc.firewall.my`: ``` ipfw -q -f flush ``` Later you will add ipfw commands to `/etc/rc.firewall.my` to be reapplied after reboot. You can also run zapret daemons from there. Start them with `--daemon` options, for example ``` pkill ^dvtws$ /opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` To restart firewall and daemons run : `/etc/rc.d/ipfw restart` Assume `LAN="em1"`, `WAN="em0"`. `tpws` transparent mode quick start. For all traffic: ``` ipfw delete 100 ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` Process only table zapret with the exception of table nozapret: ``` ipfw delete 100 ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` Tables zapret, nozapret, ipban are created by `ipset/*.sh` scripts the same way as in Linux. Its a good idea to update tables periodically: ``` crontab -e ``` Write the line: ``` 0 12 */2 * * /opt/zapret/ipset/get_config.sh ``` When using `ipfw`, `tpws` does not require special permissions for transparent mode. However without root its not possible to bind to ports less than 1024 and change UID/GID. Without changing UID tpws will run into recursive loop, and that's why its necessary to write ipfw rules with the right UID. Redirecting to ports greater than or equal to 1024 is dangerous. If tpws is not running any unprivileged process can listen to that port and intercept traffic. ### `dvtws` quick start For all traffic: ``` ipfw delete 100 ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0 # required for autottl mode only ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` Process only table zapret with the exception of table nozapret: ``` ipfw delete 100 ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0 # required for autottl mode only ipfw add 100 divert 989 tcp from table\(zapret\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` Reinjection loop avoidance. FreeBSD artificially ignores sockarg for ipv6 in the kernel. This limitation is coming from the ipv6 early age. Code is still in "testing" state. 10-20 years. Everybody forgot about it. `dvtws` sends ipv6 forged frames using another divert socket (HACK). they can be filtered out using 'diverted'. ipv4 frames are filtered using 'sockarg'. ### PF in FreeBSD The setup is similar to OpenBSD, but there are important nuances. 1. PF support is disabled by default in FreeBSD. Use parameter `--enable-pf`. 2. It's not possible to redirect to `::1`. Need to redirect to the link-local address of the incoming interface. Look for fe80:... address in ifconfig and use it for redirection target. 3. pf.conf syntax is a bit different from OpenBSD. 4. How to set maximum table size : sysctl net.pf.request_maxcount=2000000 5. `divert-to` is broken. Loop avoidance scheme does not work. This makes `dvtws` unusable with pf. Someone posted kernel patch but 14-RELEASE is still broken. `/etc/pf.conf`: ``` rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988 rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 ``` Then: ``` /opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force ``` Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work. ### `pfsense` `pfsense` is based on FreeBSD. Binaries from `binaries/freebsd-x64` are compiled in FreeBSD 11 and should work. Use `install_bin.sh`. pfsense uses pf firewall which does not support divert. Fortunately ipfw and ipdivert modules are present and can be kldload-ed. In older versions it's also necessary to change firewall order using sysctl commands. In newer versions those sysctl parameters are absent but the system behaves as required without them. Sometimes pf may limit `dvtws` abilities. It scrubs ip fragments disabling `dvtws` ipfrag2 desync mode. There's autostart script example in `init.d/pfsense`. It should be placed to `/usr/local/etc/rc.d` and edited. Write your ipfw rules and daemon start commands. curl is present by default. You can use it to download `tar.gz` release directly from github. Or you can copy files using sftp. Copy zip with zapret files to `/opt` and unpack there as it's done in other systems. In this case run `dvtws` as `/opt/zapret/nfq/dvtws`. Or just copy `dvtws` to `/usr/local/sbin`. As you wish. `ipset` scripts are working, cron is present. It's possible to renew lists. If you dont like poverty of default repos its possible to enable FreeBSD repo. Change `no` to `yes` in `/usr/local/etc/pkg/repos/FreeBSD.conf` and `/usr/local/etc/pkg/repos/pfSense.conf`. `/usr/local/etc/rc.d/zapret.sh` (chmod 755) ``` #!/bin/sh kldload ipfw kldload ipdivert # for older pfsense versions. newer do not have these sysctls sysctl net.inet.ip.pfil.outbound=ipfw,pf sysctl net.inet.ip.pfil.inbound=ipfw,pf sysctl net.inet6.ip6.pfil.outbound=ipfw,pf sysctl net.inet6.ip6.pfil.inbound=ipfw,pf ipfw delete 100 ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0 pkill ^dvtws$ dvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2 # required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state pfctl -d ; pfctl -e ``` I could not make tpws work from ipfw. Looks like there's some conflict between two firewalls. Only PF redirection works. PF does not allow to freely add and delete rules. Only anchors can be reloaded. To make an anchor work it must be referred from the main ruleset. But its managed by pfsense scripts. One possible solution would be to modify `/etc/inc/filter.inc` as follows: ``` ................. /* MOD */ $natrules .= "# ZAPRET redirection\n"; $natrules .= "rdr-anchor \"zapret\"\n"; $natrules .= "# TFTP proxy\n"; $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; ................. ``` Write the anchor code to `/etc/zapret.anchor`: ``` rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 ``` Replace `fe80::20c:29ff:5ae3:4821` with your link local address of the LAN interface or remove the line if ipv6 is not needed. Autostart `/usr/local/etc/rc.d/zapret.sh`: ``` pfctl -a zapret -f /etc/zapret.anchor pkill ^tpws$ tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2 ``` After reboot check that anchor is created and referred from the main ruleset: ``` [root@pfSense /]# pfctl -s nat no nat proto carp all nat-anchor "natearly/*" all nat-anchor "natrules/*" all ................... no rdr proto carp all rdr-anchor "zapret" all rdr-anchor "tftp-proxy/*" all rdr-anchor "miniupnpd" all [root@pfSense /]# pfctl -s nat -a zapret rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 ``` Also there's a way to add redirect in the pfsense UI and start `tpws` from cron using `@reboot` prefix. This way avoids modification of pfsense code. ## OpenBSD In OpenBSD default `tpws` bind is ipv6 only. To bind to ipv4 specify `--bind-addr=0.0.0.0`. Use `--bind-addr=0.0.0.0 --bind-addr=::` to achieve the same default bind as in others OSes. `tpws` for forwarded traffic only (OLDER OS versions): `/etc/pf.conf`: ``` pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 ``` Then: ``` pfctl -f /etc/pf.conf tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf ``` Its not clear how to do rdr-to outgoing traffic. I could not make route-to scheme work. rdr-to support is done using /dev/pf, that's why transparent mode requires root. `tpws` for forwarded traffic only (NEWER OS versions): ``` pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 ``` Then: ``` pfctl -f /etc/pf.conf tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` tpws must be bound exactly to diverted IPs, not `0.0.0.0` or `::`. It's also not clear how to divert connections from local system. `dvtws` for all traffic: `/etc/pf.conf`: ``` pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 proto tcp from port {80,443} no state pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 ``` Then: ``` pfctl -f /etc/pf.conf ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` `dwtws` only for table zapret with the exception of table nozapret : `/etc/pf.conf`: ``` set limit table-entries 2000000 table file "/opt/zapret/ipset/zapret-ip.txt" table file "/opt/zapret/ipset/zapret-ip-user.txt" table file "/opt/zapret/ipset/zapret-ip-exclude.txt" pass out quick on em0 inet proto tcp to port {80,443} pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state table file "/opt/zapret/ipset/zapret-ip6.txt" table file "/opt/zapret/ipset/zapret-ip-user6.txt" table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" pass out quick on em0 inet6 proto tcp to port {80,443} pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state ``` Then: ``` pfctl -f /etc/pf.conf ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` divert-packet automatically adds the reverse rule. By default also incoming traffic will be passwed to `dvtws`. This is highly undesired because it is waste of cpu resources and speed limiter. The trick with "no state" and "in" rules allows to bypass auto reverse rule. `dvtws` in OpenBSD sends all fakes through a divert socket because raw sockets have critical artificial limitations. Looks like pf automatically prevent reinsertion of diverted frames. Loop problem does not exist. OpenBSD forcibly recomputes tcp checksum after divert. Thats why most likely dpi-desync-fooling=badsum will not work. `dvtws` will warn if you specify this parameter. `ipset` scripts do not reload PF by default. To enable reload specify command in `/opt/zapret/config`: ``` LISTS_RELOAD="pfctl -f /etc/pf.conf" ``` Newer `pfctl` versions can reload tables only: ``` pfctl -Tl -f /etc/pf.conf ``` But OpenBSD 6.8 `pfctl` is old enough and does not support that. Newer FreeBSD do. Don't forget to disable gzip compression: ``` GZIP_LISTS=0 ``` If some list files do not exist and have references in pf.conf it leads to error. You need to exclude those tables from pf.conf and referencing them rules. After configuration is done you can put `ipset` script: ``` crontab -e ``` Then write the line: ``` 0 12 */2 * * /opt/zapret/ipset/get_config.sh ``` ## MacOS Initially, the kernel of this OS was based on BSD. That's why it is still BSD but a lot was modified by Apple. As usual a mass commercial project priorities differ from their free counterparts. Apple guys do what they want. MacOS used to have ipfw but it was removed later and replaced by PF. It looks like divert sockets are internally replaced with raw. Its possible to request a divert socket but it behaves exactly as raw socket with all its BSD inherited + apple specific bugs and feature. The fact is that divert-packet in `/etc/pf.conf` does not work. pfctl binary does not contain the word `divert`. `dvtws` does compile but is useless. After some efforts `tpws` works. Apple has removed some important stuff from their newer SDKs (DIOCNATLOOK) making them undocumented and unsupported. With important definitions copied from an older SDK it was possible to make transparent mode working again. But this is not guaranteed to work in the future versions. Another MacOS unique feature is root requirement while polling `/dev/pf`. By default tpws drops root. Its necessary to specify `--user=root` to stay with root. In other aspects PF behaves very similar to FreeBSD and shares the same pf.conf syntax. In MacOS redirection works both for passthrough and outgoing traffic. Outgoing redirection requires route-to rule. Because tpws is forced to run as root to avoid loop its necessary to exempt root from the redirection. That's why DPI bypass will not work for local requests from root. If you do ipv6 routing you have to get rid of "secured" ipv6 address assignment. "secured" addresses are designed to be permanent and not related to the MAC address. And they really are. Except for link-locals. If you just reboot the system link-locals will not change. But next day they will change. Not necessary to wait so long. Just change the system time to tomorrow and reboot. Link-locals will change (at least they change in vmware guest). Looks like its a kernel bug. Link locals should not change. Its useless and can be harmful. Cant use LL as a gateway. The easiest solution is to disable "secured" addresses. Outgoing connections prefer randomly generated temporary addressesas like in other systems. Put the string `net.inet6.send.opmode=0` to `/etc/sysctl.conf`. If not present - create it. Then reboot the system. If you dont like this solution you can assign an additional static ipv6 address from `fc00::/7` range with `/128` prefix to your LAN interface and use it as the gateway address. `tpws` transparent mode only for outgoing connections. `/etc/pf.conf`: ``` rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } ``` Then: ``` pfctl -ef /etc/pf.conf /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force ``` `tpws` transparent mode for both passthrough and outgoing connections. en1 - LAN. ``` ifconfig en1 | grep fe80 inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 ``` `/etc/pf.conf`: ``` rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } ``` Then: ``` pfctl -ef /etc/pf.conf /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 ``` Build from source : `make -C /opt/zapret mac` `ipset/*.sh` scripts work. ### MacOS easy install `install_easy.sh` supports MacOS Shipped precompiled binaries are built for 64-bit MacOS with `-mmacosx-version-min=10.8` option. They should run on all supported MacOS versions. If no - its easy to build your own. Running `make` automatically installs developer tools. **WARNING**: **Internet sharing is not supported!** Routing is supported but only manually configured through PF. If you enable internet sharing tpws stops functioning. When you disable internet sharing you may lose web site access. To fix: ``` pfctl -f /etc/pf.conf ``` If you need internet sharing use `tpws` socks mode. `launchd` is used for autostart (`/Library/LaunchDaemons/zapret.plist`) Control script: `/opt/zapret/init.d/macos/zapret` The following commands fork with both tpws and firewall (if `INIT_APPLY_FW=1` in config) ``` /opt/zapret/init.d/macos/zapret start /opt/zapret/init.d/macos/zapret stop /opt/zapret/init.d/macos/zapret restart ``` Work with `tpws` only: ``` /opt/zapret/init.d/macos/zapret start-daemons /opt/zapret/init.d/macos/zapret stop-daemons /opt/zapret/init.d/macos/zapret restart-daemons ``` Work with PF only: ``` /opt/zapret/init.d/macos/zapret start-fw /opt/zapret/init.d/macos/zapret stop-fw /opt/zapret/init.d/macos/zapret restart-fw ``` Reloading PF tables: ``` /opt/zapret/init.d/macos/zapret reload-fw-tables ``` Installer configures `LISTS_RELOAD` in the config so `ipset *.sh` scripts automatically reload PF tables. Installer creates cron job for `ipset /get_config.sh`, as in OpenWRT. start-fw script automatically patches `/etc/pf.conf` inserting there `zapret` anchors. Auto patching requires pf.conf with apple anchors preserved. If your `pf.conf` is highly customized and patching fails you will see the warning. Do not ignore it. In that case you need to manually insert "zapret" anchors to your `pf.conf` (keeping the right rule type ordering): ``` rdr-anchor "zapret" anchor "zapret" unistall_easy.sh unpatches pf.conf ``` start-fw creates 3 anchor files in `/etc/pf.anchors` : zapret,zapret-v4,zapret-v6. - Last 2 are referenced by anchor `zapret`. - Tables `nozapret`,`nozapret6` belong to anchor `zapret`. - Tables `zapret`,`zapret-user` belong to anchor `zapret-v4`. - Tables `zapret6`,`apret6-user` belong to anchor `zapret-v6`. If an ip version is disabled then corresponding anchor is empty and is not referenced from the anchor `zapret`. Tables are only created for existing list files in the `ipset` directory. ================================================ FILE: docs/bsd.md ================================================ # Настройка BSD-подобных систем * [Поддерживаемые версии](#поддерживаемые-версии) * [Особенности BSD систем](#особенности-bsd-систем) * [Отсутствие nfqueue](#отсутствие-nfqueue) * [Типы Firewall](#типы-firewall) * [Сборка](#сборка) * [Divert сокеты](#divert-сокеты) * [Lookup Tables](#lookup-tables) * [Загрузка ip таблиц из файла](#загрузка-ip-таблиц-из-файла) * [Отсутствие splice](#отсутствие-splice) * [mdig и ip2net](#mdig-и-ip2net) * [FreeBSD](#freebsd) * [Подгрузка ipdivert](#подгрузка-ipdivert) * [Авто-восстановление правил ipfw и работа в фоне](#авто-восстановление-правил-ipfw-и-работа-в-фоне) * [tpws в прозрачном режиме](#tpws-в-прозрачном-режиме) * [Запуск dvtws](#запуск-dvtws) * [PF в FreeBSD](#pf-в-freebsd) * [pfsense](#pfsense) * [OpenBSD](#openbsd) * [tpws bind на ipv4](#tpws-bind-на-ipv4) * [tpws для проходящего трафика (старые системы)](#tpws-для-проходящего-трафика-старая-схема-не-работает-в-новых-версиях)) * [tpws для проходящего трафика (новые системы)](#tpws-для-проходящего-трафика-новые-системы)) * [Запуск dvtws](#запуск-dvtws) * [Проблемы с badsum](#проблемы-с-badsum) * [Особенность отправки fake пакетов](#особенность-отправки-fake-пакетов) * [Перезагрузка PF таблиц](#перезагрузка-pf-таблиц) * [MacOS](#macos) * [Введение](#введение) * [dvtws бесполезен](#dvtws-бесполезен) * [tpws](#tpws) * [Проблема link-local адреса](#проблема-link-local-адреса) * [Сборка](#сборка) * [Простая установка](#простая-установка) * [Вариант Custom](#вариант-custom) ## Поддерживаемые версии **FreeBSD** 11.x+ , **OpenBSD** 6.x+, частично **MacOS Sierra** + > [!CAUTION] > На более старых может собираться, может не собираться, может работать или не > работать. На **FreeBSD** 10 собирается и работает `dvtws`. С `tpws` есть > проблемы из-за слишком старой версии компилятора clang. Вероятно, будет > работать, если обновить компилятор. Возможна прикрутка к последним версиям > pfsense без веб интерфейса в ручном режиме через консоль. ## Особенности BSD систем ### Отсутствие nfqueue В **BSD** нет `nfqueue`. Похожий механизм - divert sockets. Из каталога [`nfq/`](../nfq/) под **BSD** собирается `dvtws` вместо `nfqws`. Он разделяет с `nfqws` большую часть кода и почти совпадает по параметрам командной строки. ### Типы Firewall **FreeBSD** содержит 3 фаервола : **IPFilter**, **ipfw** и **Packet Filter (PF в дальнейшем)**. **OpenBSD** содержит только **PF**. ### Сборка Под **FreeBSD** `tpws` и `dvtws` собираются через `make`. Под **OpenBSD**: ```sh make bsd ``` Под **MacOS**: ```sh make mac ``` **FreeBSD** make распознает BSDmakefile, **OpenBSD** и **MacOS** - нет. Поэтому там используется отдельный target в Makefile. Сборка всех исходников: ```sh make -C /opt/zapret ``` ### Divert сокеты Divert сокет это внутренний тип сокета ядра **BSD**. Он не привязывается ни к какому сетевому адресу, не участвует в обмене данными через сеть и идентифицируется по номеру порта `1..65535`. Аналогия с номером очереди `NFQUEUE`. На divert сокеты заворачивается трафик посредством правил ipfw или PF. Если в фаерволе есть правило divert, но на divert порту никто не слушает, то пакеты дропаются. Это поведение аналогично правилам `NFQUEUE` без параметра `--queue-bypass`. На **FreeBSD** divert сокеты могут быть только ipv4, хотя на них принимаются и ipv4, и ipv6 фреймы. На **OpenBSD** divert сокеты создаются отдельно для ipv4 и ipv6 и работают только с одной версией `ip` каждый. На **MacOS** похоже, что divert сокеты из ядра вырезаны. См подробнее раздел про **MacOS**. Отсылка в divert сокет работает аналогично отсылке через raw socket на linux. Передается полностью IP фрейм, начиная с ip заголовка. Эти особенности учитываются в `dvtws`. ### Lookup Tables Скрипты [`ipset/*.sh`](../ipset/) при наличии ipfw работают с ipfw lookup tables. Это прямой аналог ipset. lookup tables не разделены на v4 и v6. Они могут содержать v4 и v6 адреса и подсети одновременно. Если ipfw отсутствует, то действие зависит от переменной `LISTS_RELOAD` в config. Если она задана, то выполняется команда из `LISTS_RELOAD`. В противном случае не делается ничего. Если `LISTS_RELOAD=-`, то заполнение таблиц отключается даже при наличии ipfw. ### Загрузка ip таблиц из файла PF может загружать ip таблицы из файла. Чтобы использовать эту возможность следует отключить сжатие gzip для листов через параметр файла config: `GZIP_LISTS=0`. ### Отсутствие splice **BSD** не содержит системного вызова splice. `tpws` работает через переброску данных в user mode в оба конца. Это медленнее, но не критически. Управление асинхронными сокетами в `tpws` основано на linux-specific механизме epoll. В **BSD** для его эмуляции используется epoll-shim - прослойка для эмуляции epoll на базе kqueue. ### mdig и ip2net mdig и ip2net полностью работоспособны в **BSD**. В них нет ничего системо-зависимого. ## FreeBSD ### Подгрузка ipdivert Divert сокеты требуют специального модуля ядра - `ipdivert`. - Поместите следующие строки в `/boot/loader.conf` (создать, если отсутствует): ``` ipdivert_load="YES" net.inet.ip.fw.default_to_accept=1 ``` `/etc/rc.conf`: ``` firewall_enable="YES" firewall_script="/etc/rc.firewall.my" ``` `/etc/rc.firewall.my`: ```sh $ ipfw -q -f flush ``` ### Авто-восстановление правил ipfw и работа в фоне В `/etc/rc.firewall.my` можно дописывать правила ipfw, чтобы они восстанавливались после перезагрузки. Оттуда же можно запускать и демоны zapret, добавив в параметры `--daemon`. Например так: ```sh $ pkill ^dvtws$ $ /opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` Для перезапуска фаервола и демонов достаточно будет сделать: ```sh $ /etc/rc.d/ipfw restart ``` ### tpws в прозрачном режиме Краткая инструкция по запуску `tpws` в прозрачном режиме. > [!NOTE] > Предполагается, что интерфейс LAN называется `em1`, WAN - `em0`. #### Весь трафик ```sh $ ipfw delete 100 $ ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon $ ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon $ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 $ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 $ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` #### Трафик только на таблицу zapret, за исключением таблицы nozapret ```sh $ ipfw delete 100 $ ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 $ ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon $ ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon $ ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 $ ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 $ ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 $ /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` > [!NOTE] > Таблицы zapret, nozapret, ipban создаются скриптами из ipset по аналогии с > Linux. Обновление скриптов можно забить в cron под root: > ```sh > $ crontab -e > ``` > > ``` > <...> > 0 12 */2 * * /opt/zapret/ipset/get_config.sh > ``` > [!CAUTION] > При использовании ipfw `tpws` не требует повышенных привилегий для реализации > прозрачного режима. Однако, без рута невозможен bind на порты `< 1024` и > смена UID/GID. Без смены UID будет рекурсия, поэтому правила ipfw нужно > создавать с учетом UID, под которым работает `tpws`. Переадресация на порты > `>= 1024` может создать угрозу перехвата трафика непривилегированным > процессом, если вдруг `tpws` не запущен. ### Запуск dvtws #### Весь трафик ```sh $ ipfw delete 100 $ ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 # required for autottl mode only $ ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 $ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` #### Трафик только на таблицу zapret, за исключением таблицы nozapret ```sh $ ipfw delete 100 $ ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 $ ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0 # required for autottl mode only $ ipfw add 100 divert 989 tcp from table\(zapret\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 $ /opt/zapret/nfq/dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` ### PF в FreeBSD Настройка аналогична **OpenBSD**, но есть важные нюансы. - В **FreeBSD** поддержка PF в `tpws` отключена по умолчанию. Чтобы ее включить, нужно использовать параметр `--enable-pf`. - Нельзя сделать ipv6 rdr на `::1`. Нужно делать на link-local адрес входящего интерфейса. Смотрите через `ifconfig` адрес `fe80:...` и добавляете в правило. - Синтаксис `pf.conf` немного отличается. Более новая версия PF. - Лимит на количество элементов таблиц задается так: ```sh $ sysctl net.pf.request_maxcount=2000000 ``` - Сломан divert-to. Он работает, но не работает механизм предотвращения зацикливаний. Кто-то уже написал патч, но в `14-RELEASE` проблема все еще есть. Следовательно, на данный момент работа `dvtws` через PF невозможна. `/etc/pf.conf`: ``` rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988 rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 ``` ```sh $ /opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force ``` > [!NOTE] > В PF не выходит делать rdr-to с той же системы, где работает proxy. > Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется. > Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС. > Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**. ### pfsense #### Описание pfsense основан на **FreeBSD** и использует фаервол PF, имеющий проблемы с divert. К счастью, модули ipfw и ipdivert присутствуют в поставке последних версий pfsense. Их можно подгрузить через `kldload`. В некоторых более старых версиях pfsense требуется изменить порядок фаерволов через `sysctl`, сделав ipfw первым. В более новых эти параметры `sysctl` отсутствуют, но система работает как надо и без них. В некоторых случаях фаервол PF может ограничивать возможности `dvtws`, в частности в области фрагментации ip. Присутствуют по умолчанию правила scrub для реассемблинга фрагментов. Бинарики из [`binaries/freebsd-x64`](../binaries/freebsd-x64) собраны под **FreeBSD 11**. Они должны работать и на последующих версиях **FreeBSD**, включая pfsense. Можно пользоваться `install_bin.sh`. #### Автозапуск Пример скрипта автозапуска лежит в [`init.d/pfsense`](../init.d/pfsense). Его следует поместить в `/usr/local/etc/rc.d` и отредактировать на предмет правил ipfw и запуска демонов. Есть встроенный редактор `edit` как более приемлемая альтернатива `vi`. > [!NOTE] > Поскольку `git` отсутствует, копировать файлы удобнее всего через `ssh`. > `curl` присутствует по умолчанию. Можно скопировать zip с файлами zapret и > распаковать в `/opt`, как это делается на других системах. Тогда `dvtws` > нужно запускать как `/opt/zapret/nfq/dvtws`. Либо скопировать только `dvtws` > в `/usr/local/sbin`. Как вам больше нравится. > [!NOTE] > Скрипты ipset работают, крон есть. Можно сделать автообновление листов. > [!NOTE] > Если вас напрягает бедность имеющегося репозитория, можно включить > репозиторий от **FreeBSD**, который по умолчанию выключен. > > Поменяйте `no` на `yes` в `/usr/local/etc/pkg/repos/FreeBSD.conf` > > Можно установить весь привычный софт, включая `git`, чтобы напрямую скачивать > zapret с github. `/usr/local/etc/rc.d/zapret.sh` (chmod `755`): ```sh #!/bin/sh kldload ipfw kldload ipdivert # for older pfsense versions. newer do not have these sysctls sysctl net.inet.ip.pfil.outbound=ipfw,pf sysctl net.inet.ip.pfil.inbound=ipfw,pf sysctl net.inet6.ip6.pfil.outbound=ipfw,pf sysctl net.inet6.ip6.pfil.inbound=ipfw,pf ipfw delete 100 ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 pkill ^dvtws$ dvtws --daemon --port 989 --dpi-desync=multisplit --dpi-desync-split-pos=2 # required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state pfctl -d ; pfctl -e ``` #### Проблемы tpws Что касается `tpws`, то видимо имеется некоторый конфликт двух фаерволов, и правила fwd в ipfw не работают. Работает перенаправление средствами PF как описано в разделе по **FreeBSD**. В PF можно изменять правила только целыми блоками - якорями (anchors). Нельзя просто так добавить или удалить что-то. Но чтобы какой-то anchor был обработан, на него должна быть ссылка из основного набора правил. Его трогать нельзя, иначе порушится весь фаервол. Поэтому придется править код скриптов pfsense. 1. Поправьте `/etc/inc/filter.inc` следующим образом: ``` <...> /* MOD */ $natrules .= "# ZAPRET redirection\n"; $natrules .= "rdr-anchor \"zapret\"\n"; $natrules .= "# TFTP proxy\n"; $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; <...> ``` 2. Напишите файл с содержимым anchor-а (например, `/etc/zapret.anchor`): ``` rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 ``` `fe80::20c:29ff:5ae3:4821` замените на ваш link local адрес LAN интерфейса, либо уберите строчку, если ipv6 не нужен. 3. Добавьте в автозапуск `/usr/local/etc/rc.d/zapret.sh`: ```sh $ pfctl -a zapret -f /etc/zapret.anchor $ pkill ^tpws$ $ tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-pos=2 ``` 4. После перезагрузки проверьте, что правила создались: ```sh $ pfctl -s nat no nat proto carp all nat-anchor "natearly/*" all nat-anchor "natrules/*" all <...> no rdr proto carp all rdr-anchor "zapret" all rdr-anchor "tftp-proxy/*" all rdr-anchor "miniupnpd" all $ pfctl -s nat -a zapret rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 ``` > [!NOTE] > Так же есть более элегантный способ запуска `tpws` через @reboot в cron и > правило перенаправления в UI. Это позволит не редактировать код pfsense. ## OpenBSD ### tpws bind на ipv4 В `tpws` bind по умолчанию только на ipv6. Для bind на ipv4 нужно указать `--bind-addr=0.0.0.0`. Используйте `--bind-addr=0.0.0.0 --bind-addr=::` для достижения того же результата, как в других ОС по умолчанию. Но лучше все же так не делать, а сажать на определенные внутренние адреса или интерфейсы. ### tpws для проходящего трафика (старая схема не работает в новых версиях) В этом варианте tpws обращается явно к редиректору pf и пытается от него получить оригинальный адрес назначения. Как показывает практика, это не работает на новых версиях OpenBSD. Возвращается ошибка ioctl. Последняя проверенная версия, где это работает, - 6.8 . Между 6.8 и 7.4 разработчики сломали этот механизм. `/etc/pf.conf`: ``` pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 ``` ```sh $ pfctl -f /etc/pf.conf $ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf ``` > [!NOTE] > В PF не выходит делать rdr-to с той же системы, где работает proxy. > Вариант с route-to не сохраняет мета информацию. Адрес назначения теряется. > Поэтому этот вариант годится для squid, берущего адрес из протокола прикладного уровня, но не годится для tpws, полагающегося на метаданные ОС. > Поддержка rdr-to реализована через `/dev/pf`, поэтому прозрачный режим **требует root**. ### tpws для проходящего трафика (новые системы) В новых версиях предлагается использовать divert-to вместо rdr-to. Минимально проверенная версия, где это работает, 7.4. Может работать или не работать на более старых - исследование не проводилось. `/etc/pf.conf`: ``` pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 ``` tpws должен иметь бинд на точно такой адрес, который указан в правилах pf. `0.0.0.0` или `::` не работает. ```sh $ pfctl -f /etc/pf.conf $ tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ``` > [!NOTE] > Так же не понятно как делать divert с самой системы, где работает tpws. ### Запуск dvtws #### Весь трафик `/etc/pf.conf`: ``` pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 proto tcp from port {80,443} no state pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state ``` ```sh $ pfctl -f /etc/pf.conf $ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` #### Трафик только на таблицу zapret, за исключением таблицы nozapret `/etc/pf.conf`: ``` set limit table-entries 2000000 table file "/opt/zapret/ipset/zapret-ip.txt" table file "/opt/zapret/ipset/zapret-ip-user.txt" table file "/opt/zapret/ipset/zapret-ip-exclude.txt" pass out quick on em0 inet proto tcp to port {80,443} pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state table file "/opt/zapret/ipset/zapret-ip6.txt" table file "/opt/zapret/ipset/zapret-ip-user6.txt" table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" pass out quick on em0 inet6 proto tcp to port {80,443} pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state ``` ```sh $ pfctl -f /etc/pf.conf $ ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ``` ### Проблемы с badsum **OpenBSD** принудительно пересчитывает tcp checksum после divert, поэтому скорее всего `dpi-desync-fooling=badsum` у вас не заработает. При использовании этого параметра `dvtws` предупредит о возможной проблеме. ### Особенность отправки fake пакетов В **OpenBSD** `dvtws` все фейки отсылает через divert socket, поскольку эта возможность через raw sockets заблокирована. Видимо PF автоматически предотвращает повторный заворот diverted фреймов, поэтому проблемы зацикливания нет. divert-packet автоматически вносит обратное правило для перенаправления. Трюк с no state и in правилом позволяет обойти эту проблему, чтобы напрасно не гнать массивный трафик через `dvtws`. ### Перезагрузка PF таблиц Скрипты из ipset не перезагружают таблицы в PF по умолчанию. Чтобы они это делали, добавьте параметр в `/opt/zapret/config`: ``` LISTS_RELOAD="pfctl -f /etc/pf.conf" ``` Более новые версии `pfctl` понимают команду перезагрузить только таблицы. Но это не относится к **OpenBSD**. В новых **FreeBSD** есть. ```sh $ pfctl -Tl -f /etc/pf.conf ``` > [!IMPORTANT] > Не забудьте выключить сжатие gzip: `GZIP_LISTS=0` > [!IMPORTANT] > Если в вашей конфигурации какого-то файла листа нет, то его необходимо > исключить из правил PF. Если вдруг листа нет, и он задан в pf.conf, будет > ошибка перезагрузки фаервола. > [!NOTE] > После настройки обновление листов можно поместить в cron: > ```sh > $ crontab -e > ``` > > ``` > <...> > 0 12 */2 * * /opt/zapret/ipset/get_config.sh > ``` ## MacOS ### Введение Иначально ядро этой ОС "darwin" основывалось на **BSD**, потому в ней много похожего на другие версии **BSD**. Однако, как и в других массовых коммерческих проектах, приоритеты смещаются в сторону от оригинала. Яблочники что хотят, то и творят. ### dvtws бесполезен Раньше был ipfw, потом его убрали, заменили на PF. Есть сомнения, что divert сокеты в ядре остались. Попытка создать divert socket не выдает ошибок, но полученный сокет ведет себя точно так же, как raw, со всеми его унаследованными косяками + еще яблочно специфическими. В PF divert-packet не работает. Простой grep бинарика `pfctl` показывает, что там нет слова "divert", а в других версиях **BSD** оно есть. `dvtws` собирается, но совершенно бесполезен. ### tpws `tpws` удалось адаптировать, он работоспособен. Получение адреса назначения для прозрачного прокси в PF (`DIOCNATLOOK`) убрали из заголовков в новых SDK, сделав фактически недокументированным. В `tpws` перенесены некоторые определения из более старых версий яблочных SDK. С ними удалось завести прозрачный режим. Однако, что будет в следующих версиях угадать сложно. Гарантий нет. Еще одной особенностью PF в **MacOS** является проверка на рута в момент обращения к `/dev/pf`, чего нет в остальных **BSD**. `tpws` по умолчанию сбрасывает рутовые привилегии. Необходимо явно указать параметр `--user=root`. В остальном PF себя ведет похоже на **FreeBSD**. Синтаксис `pf.conf` тот же. > [!IMPORTANT] > На **MacOS** работает редирект как с проходящего трафика, так и с локальной > системы через route-to. Поскольку `tpws` вынужден работать под root, для > исключения рекурсии приходится пускать исходящий от root трафик напрямую. > Отсюда имеем недостаток - **обход DPI для рута работать НЕ будет**. #### Работа в прозрачном режиме только для исходящих запросов `/etc/pf.conf`: ``` rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } ``` ```sh $ pfctl -ef /etc/pf.conf $ /opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force ``` #### Работа в прозрачном режиме > [!NOTE] > Предполагается, что имя LAN интерфейса - `en1` ```sh $ ifconfig en1 | grep fe80 inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 ``` `/etc/pf.conf`: ``` rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } ``` ```sh $ pfctl -ef /etc/pf.conf $ /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 ``` ### Проблема link-local адреса Если вы пользуетесь **MaсOS** в качестве ipv6 роутера, то нужно будет решить вопрос с регулярно изменяемым link-local адресом. С некоторых версий **MacOS** использует по умолчанию постоянные "secured" ipv6 адреса вместо генерируемых на базе MAC адреса. Все замечательно, но есть одна проблема. Постоянными остаются только global scope адреса. Link locals периодически меняются. Смена завязана на системное время. Перезагрузки адрес не меняют, Но если перевести время на день вперед и перезагрузиться - link local станет другим (по крайней мере в vmware это так). Информации по вопросу крайне мало, но тянет на баг. Не должен меняться link local. Скрывать link local не имеет смысла, а динамический link local нельзя использовать в качестве адреса шлюза. Проще всего отказаться от "secured" адресов. Для этого поместите строчку `net.inet6.send.opmode=0` в `/etc/sysctl.conf` и перезагрузите систему. Все равно для исходящих соединений будут использоваться temporary адреса, как и в других системах. Или вам идея не по вкусу, можно прописать дополнительный статический ipv6 из диапазона маски `fc00::/7` - выберите любой с длиной префикса `128`. Это можно сделать в системных настройках, создав дополнительный адаптер на базе того же сетевого интерфейса, отключить в нем ipv4 и вписать статический ipv6. Он добавится к автоматически настраеваемым. ### Сборка ```sh $ make -C /opt/zapret mac ``` ### Простая установка В **MacOS** поддерживается `install_easy.sh` В комплекте идут бинарики, собраные под 64-bit с опцией `-mmacosx-version-min=10.8`. Они должны работать на всех поддерживаемых версиях **MacOS**. Если вдруг не работают - можно собрать свои. Developer tools ставятся автоматом при запуске `make`. > [!WARNING] > Internet sharing средствами системы **не поддерживается**! > > Поддерживается только роутер, настроенный своими силами через PF. Если вы > вдруг включили шаринг, а потом выключили, то доступ к сайтам может пропасть > совсем. > > Лечение: > ```sh > $ pfctl -f /etc/pf.conf > ``` > > Если вам нужен шаринг интернета, лучше отказаться от прозрачного режима и > использовать socks прокси. Для автостарта используется launchd (`/Library/LaunchDaemons/zapret.plist`) Управляющий скрипт : `/opt/zapret/init.d/macos/zapret` Следующие команды работают с `tpws` и фаерволом одновременно (если `INIT_APPLY_FW=1` в config) ```sh $ /opt/zapret/init.d/macos/zapret start $ /opt/zapret/init.d/macos/zapret stop $ /opt/zapret/init.d/macos/zapret restart ``` Работа только с tpws: ```sh $ /opt/zapret/init.d/macos/zapret start-daemons $ /opt/zapret/init.d/macos/zapret stop-daemons $ /opt/zapret/init.d/macos/zapret restart-daemons ``` Работа только с PF: ```sh $ /opt/zapret/init.d/macos/zapret start-fw $ /opt/zapret/init.d/macos/zapret stop-fw $ /opt/zapret/init.d/macos/zapret restart-fw ``` Перезагрузка всех IP таблиц из файлов: ```sh $ /opt/zapret/init.d/macos/zapret reload-fw-tables ``` > [!NOTE] > Инсталятор настраивает `LISTS_RELOAD` в config, так что скрипты > [`ipset/*.sh`](../ipset/) автоматически перезагружают IP таблицы в PF. > [!NOTE] > Автоматически создается cron job на > [`ipset/get_config.sh`](../ipset/get_config.sh), по аналогии с openwrt. При start-fw скрипт автоматически модицифирует `/etc/pf.conf`, вставляя туда anchors "zapret". Модификация расчитана на `pf.conf`, в котором сохранены дефолтные anchors от apple. Если у вас измененный `pf.conf` и модификация не удалась, об этом будет предупреждение. Не игнорируйте его. В этом случае вам нужно вставить в свой `pf.conf` (в соответствии с порядком типов правил): ``` rdr-anchor "zapret" anchor "zapret" ``` > [!NOTE] > При деинсталяции через `uninstall_easy.sh` модификации `pf.conf` убираются. > [!NOTE] > start-fw создает 3 файла anchors в `/etc/pf.anchors`: `zapret`, `zapret-v4`, > `zapret-v6`. Последние 2 подключаются из anchor "zapret". > [!NOTE] > Таблицы `nozapret` и `nozapret6` принадлежат anchor "zapret". > > Таблицы `zapret` и `zapret-user` в anchor "zapret-v4". > > Таблицы `zapret6` и `zapret6-user` в anchor "zapret-v6". > > Если какая-то версия протокола отключена - соответствующий anchor пустой и не > упоминается в anchor "zapret". Таблицы и правила создаются только на те > листы, которые фактически есть в директории ipset. ### Вариант Custom Так же как и в других системах, поддерживаемых в простом инсталяторе, можно создавать свои custom скрипты. Расположение: `/opt/zapret/init.d/macos/custom` `zapret_custom_daemons()` получает в `$1`: `0` или `1`. `0` = stop, `1` = start custom firewall отличается от linux варианта. Вместо заполнения `iptables` вам нужно сгенерировать правила для `zapret-v4` и `zapret-v6` anchors и выдать их в stdout. Это делается в функциях `zapret_custom_firewall_v4()` и `zapret_custom_firewall_v6()`. Определения таблиц заполняются основным скриптом \- вам это делать не нужно. Можно ссылаться на таблицы `zapret` и `zapret-user` в v4, `zapret6` и `zapret6-user`. Cм. пример [в файле](../init.d/macos/custom.d.examples/50-extra-tpws). ================================================ FILE: docs/bsdfw.txt ================================================ WAN=em0 LAN=em1 FreeBSD IPFW : ipfw delete 100 ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 ipfw delete 100 ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ipfw delete 100 ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 ; required for autottl mode ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 ; udp ipfw add 100 divert 989 udp from any to any 443 out not diverted xmit em0 ipfw delete 100 ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted xmit em0 /opt/zapret/nfq/dvtws --port=989 --debug --dpi-desync=split sample ipfw NAT setup : WAN=em0 LAN=em1 ipfw -q flush ipfw -q nat 1 config if $WAN unreg_only reset ipfw -q add 10 allow ip from any to any via $LAN ipfw -q add 20 allow ip from any to any via lo0 ipfw -q add 300 nat 1 ip4 from any to any in recv $WAN ipfw -q add 301 check-state ipfw -q add 350 skipto 390 tcp from any to any out xmit $WAN setup keep-state ipfw -q add 350 skipto 390 udp from any to any out xmit $WAN keep-state ipfw -q add 360 allow all from any to me in recv $WAN ipfw -q add 390 nat 1 ip4 from any to any out xmit $WAN ipfw -q add 10000 allow ip from any to any Forwarding : sysctl net.inet.ip.forwarding=1 sysctl net.inet6.ip6.forwarding=1 OpenBSD PF : (OLD OpenBSD versions) ; dont know how to rdr-to from local system. doesn't seem to work. only works for routed traffic. /etc/pf.conf pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 pfctl -f /etc/pf.conf /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 --enable-pf (NEW OpenBSD versions) ; dont know how to divert-to from local system /etc/pf.conf pass on em1 inet proto tcp to port {80,443} divert-to 127.0.0.1 port 989 pass on em1 inet6 proto tcp to port {80,443} divert-to ::1 port 989 pfctl -f /etc/pf.conf /opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 ; dvtws works both for routed and local pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 proto tcp from port {80,443} no state pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state pfctl -f /etc/pf.conf ./dvtws --port=989 --dpi-desync=multisplit --dpi-desync-split-pos=2 ; dvtws with table limitations : to zapret,zapret6 but not to nozapret,nozapret6 ; reload tables : pfctl -f /etc/pf.conf set limit table-entries 2000000 table file "/opt/zapret/ipset/zapret-ip.txt" table file "/opt/zapret/ipset/zapret-ip-user.txt" table file "/opt/zapret/ipset/zapret-ip-exclude.txt" pass out quick on em0 inet proto tcp to port {80,443} pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet proto tcp from port {80,443} no state pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state table file "/opt/zapret/ipset/zapret-ip6.txt" table file "/opt/zapret/ipset/zapret-ip-user6.txt" table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" pass out quick on em0 inet6 proto tcp to port {80,443} pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state pass in quick on em0 inet6 proto tcp from port {80,443} no state pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state ================================================ FILE: docs/changes.txt ================================================ v1 Initial release v2 nfqws : command line options change. now using standard getopt. nfqws : added options for window size changing and "Host:" case change ISP support : tested on mns.ru and beeline (corbina) init scripts : rewritten init scripts for simple choise of ISP create_ipset : now using 'ipset restore', it works much faster readme : updated. now using UTF-8 charset. v3 tpws : added transparent proxy (supports TPROXY and DNAT). can help when ISP tracks whole HTTP session, not only the beginning ipset : added zapret-hosts-user.txt which contain user defined host names to be resolved and added to zapret ip list ISP support : dom.ru support via TPROXY/DNAT ISP support : successfully tested sknt.ru on 'domru' configuration other configs will probably also work, but cannot test compile : openwrt compile howto v4 tpws : added ability to insert extra space after http method : "GET /" => "GET /" ISP support : TKT support v5 nfqws : ipv6 support in nfqws v6 ipset : added "get_antizapret.sh" v7 tpws : added ability to insert "." after Host: name v8 openwrt init : removed hotplug.d/firewall because of race conditions. now only use /etc/firewall.user v9 ipban : added ipban ipset. place domains banned by ip to zapret-hosts-user-ipban.txt these IPs must be soxified for both http and https ISP support : tiera support ISP support : added DNS filtering to ubuntu and debian scripts v10 tpws : added split-pos option. split every message at specified position v11 ipset : scripts optimizations v12 nfqws : fix wrong tcp checksum calculation if packet length is odd and platform is big-endian v13 added binaries v14 change get_antizapret script to work with https://github.com/zapret-info/z-i/raw/master/dump.csv filter out 192.168.*, 127.*, 10.* from blocked ips v15 added --hostspell option to nfqws and tpws ISP support : beeline now catches "host" but other spellings still work openwrt/LEDE : changed init script to work with procd tpws, nfqws : minor cosmetic fixes v16 tpws: split-http-req=method : split inside method name, not after ISP support : mns.ru changed split pos to 3 (got redirect page with HEAD req : curl -I ej.ru) v17 ISP support : athome moved from nfqws to tpws because of instability and http request hangs tpws : added options unixeol,methodeol,hosttab v18 tpws,nfqws : added hostnospace option v19 tpws : added hostlist option v20 added ip2net. ip2net groups ips from iplist into subnets and reduces ipset size twice v21 added mdig. get_reestr.sh is *real* again v22 total review of init script logic dropped support of older debian 7 and ubuntu 12/14 systems install_bin.sh : auto binaries preparation docs: readme review. some new topics added, others deleted docs: VPN setup with policy based routing using wireguard docs: wireguard modding guide v23 major init system rewrite openwrt : separate firewall include /etc/firewall.zapret install_easy.sh : easy setup on openwrt, debian, ubuntu, centos, fedora, opensuse v24 separate config from init scripts gzip support in ipset/*.sh and tpws v25 init : move to native systemd units use links to units, init scripts and firewall includes, no more copying v26 ipv6 support tpws : advanced bind options v27 tpws : major connection code rewrite. originally it was derived from not top quality example , with many bugs and potential problems. next generation connection code uses nonblocking sockets. now its in EXPERIMENTAL state. v28 tpws : added socks5 support ipset : major RKN getlist rewrite. added antifilter.network support v29 nfqws : DPI desync attack ip exclude system v30 nfqws : DPI desync attack modes : fake,rst v31 nfqws : DPI desync attack modes : disorder,disorder2,split,split2. nfqws : DPI desync fooling mode : badseq. multiple modes supported v32 tpws : multiple binds init scripts : run only one instance of tpws in any case v33 openwrt : flow offloading support config : MODE refactoring v34 nfqws : dpi-desync 2 mode combos nfqws : dpi-desync without parameter no more supported. previously it meant "fake" nfqws : custom fake http request and tls client hello v35 limited FreeBSD and OpenBSD support v36 full FreeBSD and OpenBSD support v37 limited MacOS support v38 MacOS easy install v39 nfqws: conntrack, wssize v40 init scripts : IFACE_LAN, IFACE_WAN now accept multiple interfaces init scripts : openwrt uses now OPENWRT_LAN parameter to override incoming interfaces for tpws v41 install_easy : openrc support v42 blockcheck.sh v43 nfqws: UDP desync with conntrack support (any-protocol only for now) v44 nfqws: ipfrag v45 nfqws: hop-by-hop ipv6 desync and fooling v46 big startup script refactoring to support nftables and new openwrt snapshot builds with firewall4 v47 nfqws: QUIC initial decryption nfqws: udplen, fakeknown dpi desync modes v48 nfqws, tpws : multiple --hostlist and --hostlist-exclude support launch system, ipset : no more list merging. all lists are passed separately to nfqws and tpws nfqws : udplen fooling supports packet shrinking (negative increment value) v49 QUIC support integrated to the main system and setup v50 DHT protocol support. DPI desync mode 'tamper' for DHT. HEX string support in addition to binary files. v51 tpws --tlsrec attack. v52 autohostlist mode v53 nfqws: tcp session reassemble for TLS ClientHello v54 tpws: out of band send when splitting (--oob) nfqws: autottl nfqws: datanoack fooling nftables: use POSTNAT path for tcp redirections to allow NAT-breaking strategies. use additional mark bit DESYNC_MARK_POSTNAT. v55 tpws: incompatible oob parameter change. it doesn't take oob byte anymore. instead it takes optional protocol filter - http or tls. the same is done with disorder. oob byte can be specified in parameter --oob-data. blockcheck: quick mode, strategy order optimizations, QUIC protocol support nfqws: syndata desync mode v56 tpws: mss fooling tpws: multi thread resolver. eliminates blocks related to hostname resolve. v57 tpws: --nosplice option nfqws: postnat fixes nfqws: --dpi-desync-start option nfqws: packet delay for kyber TLS and QUIC nfqws: --dpi-desync-retrans obsolete nfqws: --qnum is mandatory, no more default queue 0 v58 winws v59 tpws: --split-tls tpws: --tlsrec=sniext nfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls. multi segment TLS support for split. blockcheck: mdig dns cache v60 blockcheck: port block test, partial ip block test nfqws: seqovl split/disorder modes v61 C code cleanups dvtws: do not use raw sockets. use divert. nfqws,tpws: detect TLS 1.2 ClientHello from very old libraries with SSL 3.0 version in record layer nfqws,tpws: debug log to file and syslog tpws: --connect-bind-addr option tpws: log local endpoint (including source port number) for remote leg v62: tpws: connection close logic rewrite. tcp user timeout parameters for local and remote leg. nfqws: multi-strategy v63: tpws: multi-strategy v64: blockcheck: warn if dpi bypass software is already running blockcheck: TPWS_EXTRA, NFQWS_EXTRA init.d: multiple custom scripts v65: init.d: dynamic number allocation for dnum,tpws_port,qnum init.d: FW_EXTRA_PRE, FW_EXTRA_POST init.d: zapret_custom_firewall_nft_flush nfqws,tpws: l7proto and client ip:port info in autohostlist debug log nfqws,tpws: user mode ipset filter support nfqws,tpws: l7proto filter support tpws: fixed MSS apply in transparent mode nfqws: fixed autottl apply if desync profile changed tpws,nfqws: fixed 100% cpu hang on gzipped list with comments ipset: get_refilter_ipsum.sh , get_refilter_domain.sh v66: init.d: rewrite traffic interception and daemon launch parameters in config file. this break compatibility with old versions. init.d: openwrt-minimal : tpws launch for low storage openwrt devices v67: mdig: --dns-make-query, --dns-parse-query for side-channel resolving (DoH) blockcheck: use DoH resolvers if DNS spoof is detected blockcheck: restring fooling to testing domain's IPs nfqws,tpws: internal hostlist deduplication to save RAM nfqws,tpws: hostlist/ipset auto reload on file change. no more HUP. nfqws,tpws: --filter-tcp, --filter-udp take comma separated port range list nfqws,tpws: @ - read config from a file config: marker binaries: remove zapret-winws. add win32. blockcheck, install_easy.sh: preserve user environment variables during elevation blockcheck: do not require root if SKIP_PKTWS=1 v68: docs : move russian version to markdown nfqws,tpws: use alternate $ sign for $ repo: binaries removed from repo. git actions binaries build in releases. uninstall_easy.sh: offer to remove dependencies in openwrt install_easy.sh: allow to download lists in autohostlist filter mode v69: nfqws, tpws: multisplit/multidisorder support. nfqws: name change split->fakedsplit, disorder->fakeddisorder. compat : old names are synonyms nfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls deprecated. compat : these parameters add split point to multisplit. nfqws: --dpi-desync=split2|disorder2 deprecated. compat: they are now synonyms for multisplit/multidisorder nfqws: cancel seqovl if MTU is exceeded (linux only). cancel seqovl for disorder if seqovl>=first_part_size. nfqws: fixed splits in multiple TLS segments. tpws: --split-http-req,--split-tls deprecated. compat : these parameters add split point to multisplit. tpws: --tlsrec now takes pos markers. compat : old names are converted to pos markers tpws: --tlsrec-pos deprecated. compat : sets absolute pos marker nfqws,tpws: chown autohostlist, autohostlist debug log and debug log files after options parse nfqws,tpws: set EXEDIR env var to use in @config (won't work for stadalone winws without /bin/sh) dvtws: set random/increasing ip_id value in generated packets mdig: fixed parsing of DNS reply in windows (stdin is opened as text, not binary) tpws: support compile for android NDK api level >= 21 (Android 5.0) tpws: --fix-seg segmentation fixer repo: build for android NDK api level 21 (Android 5.0) install_easy: support for APK package manager in openwrt blockcheck: removed ignore CA question blockcheck: removed IGNORE_CA, CURL_VERBOSE blockcheck: added CURL_OPT blockcheck: new strategies support blockcheck: test sequence rework blockcheck: view all working strategies in summary v69.1: init.d: keenetic udp fix custom tpws: fixed incorrect hostlist checks v69.2: nfqws,tpws: --skip nfqws: --methodeol init.d: do not use pgrep in sysv for busybox compat v69.3 nfqws,tpws: fixed ipsets and hostlists all progs: version numbers for github, build date/time for self built repo: light release for openwrt and embedded systems repo: sha256sum v69.4 nfqws: fakedsplit/fakeddisorder fakes for both split segments nfqws: --dpi-desync-fakedsplit-pattern v69.5 nfqws,tpws: --dry-run install_easy: check tpws and nfqws options validity v69.6 nfqws: set NETLINK_NO_ENOBUFS to fix possible nfq recv errors init.d: unify custom scripts for linux init.d: new custom scripts : 20-fw-extra, 50-wg4all v69.7 nfqws,tpws: --comment nfqws: trash flood warning winws: exclude empty outgoing ack packets in windivert filter v69.8 winws: accept empty outgoing RST and FIN packets for conntrack needs repo: lexra build v69.9 init.d: exclude ipban from tpws redirection macos: fix install_easy macos: fix national decimal separator in sleep ipset: scripts maintenance v70 blockcheck: override all dialog questions and enable batch mode blockcheck: parallel attempts nfqws: weaken wireguard initiation recognition. use len=148 and data[0]=1 signature nfqws: apply split+seqovl only to the first reasm fragment install_easy: dnf packager support nfqws,tpws: hostlist/ipset track not only file mod time but also file size nfqws,tpws,ipset: return lists reload on HUP nfqws,blockcheck: --dpi-desync-fake-tls-mod v70.1 nfqws: --dpi-desync-fake-tls-mod=dupsid nfqws,tpws: test accessibility of list files after privs drop nfqws,tpws: --version v70.4 nfqws,tpws: ^ prefix in hostlist to disable subdomain matches nfqws,tpws: optional systemd notify support. compile using 'make systemd' nfqws,tpws: systemd instance templates for nfqws and tpws nfqws,tpws: separate droproot from dropcaps tpws: detect WSL 1 and warn about non-working options v70.5 nfqws: multiple --dpi-desync-fake-xxx nfqws: support of inter-packet fragmented QUIC CRYPTO v70.6 nfqws: detect Discord Voice IP discovery packets nfqws: detect STUN message packets nfqws: change SNI to specified value tls mod : --dpi-desync-fake-tls-mod sni= nfqws: update default TLS ClientHello fake. firefox 136.0.4 finger, no kyber, SNI=microsoft.com nfqws: multiple mods for multiple TLS fakes init.d: remove 50-discord blockcheck: use tpws --fix-seg on linux for multiple splits v71 nfqws,tpws: debug tls version, alpn, ech nfqws: --dpi-desync-fake-tls=! means default tls fake nfqws: --dup* nfqws: --orig* nfqws: ipcache of hop count and host names nfqws: --ctrack-disable nfqws: --synack-split nfqws: --autottl=- or --autottl=0:0-0 disable autottl. previous "0" does not work anymore. tpws: ipcache of host names nfqws,tpws: set 1024 repeat limit to fakes and dups nfqws,tpws: do more before daemonize nfqws,tpws: accept multiple gids in --gid nfqws,tpws: display "android" in version string if built for android init.d: remove --ipset parameter prohibition init.d, blockcheck: drop time exceeded icmp for nfqws-related connections blockcheck: some dup and orig-ttl mods blockcheck: PKTWS_EXTRA_PRE blockcheck: report test function and domain every test v71.1 nfqws,tpws: much faster ipset implementation. move from hash to avl tree nfqws,tpws: check list files accessibility with dropped privs in --dry-run mode nfqws,tpws: --debug=android for NDK builds nfqws,tpws: use initgroups instead of setgroups if --user specified nfqws: --filter-ssid (linux-only) install_easy: stop if running embedded release on traditional linux system (some files missing) install_bin: add "read elf" arch detection method binaries: renamed arch dirs in binaries v71.1.1 nfqws: use wireless ext in case nl80211 does not return SSID v71.2 nfqws: apply udp desync to replayed packets with non-zero reasm offset (except fake) blockcheck: display curl version and kernel version install_bin: stop if no binaries found. display help text. winws: increase buffers for port filter tpws: 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. install_easy: warn if --ipset parameter is specified v71.3 init.d: FILTER_MARK nfqws: ts fooling blockcheck: test ts fooling blockcheck: auto enable tcp timestamps in windows tpws,nfqws: special handling of IP-like hostnames. strip :port from hostname:port v71.4 nfqws,tpws: fix possible crashes or high memory usage if hostlist has duplicate hostnames init.d: custom scripts 50-discord-media, 50-stun4all init.d: windivert filters for discord media, stun, wireguard readme: hardware problems description v72 winws: --wf-raw-part nfqws: --dpi-desync=hostfakesplit nfqws: --dpi-desync-fake-tcp-mod=seq nfqws: --dpi-desync-fake-tls=!+offset , --dpi-desync-fake-xxx=[+offset]@filename nfqws: --dpi-desync-fakedsplit-mod=altorder for fakedsplit/fakeddisorder nfqws: --dpi-desync-hostfakesplit-mod=altorder nfqws: fakedsplit/fakeddisorder normalize fake split pattern offset to reasm offset nfqws: optimize ipv4 ip_id. apply ip_id on all OS the same way. blockcheck: print PRETTY_NAME and some OPENWRT_xxx from /etc/os-release blockcheck: new strategies blockcheck: curl test simulation : SIMULATE=1 v72.1 nfqws: --ip-id=seq|seqgroup|rnd|zero blockcheck: MIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA init.d: 50-quic4all custom 72.2 nfqws: --wssize-forced-cutoff nfqws: --orig-tcp-flags, --dup-tcp-flags, --dpi-desync-tcp-flags nfqws: --dup-ip-id 72.3 blockcheck: support URIs blockcheck: CURL_HTTPS_GET=1 suppresses -I curl option for https (HEAD -> GET) 72.4 blockcheck: fix broken dns cache 72.5 nfqws: fix broken l7proto profile rediscovery nfqws: backport from nfqws2 nl80211 ssid discovery fix for newer kernels 72.6 ipset: remove zapret-info based scripts because it's gone blockcheck: fix tpws test regression 72.7 nfqws,tpws: memleak fix mdig: --eagain, --eagain-delay 72.8 nfqws: fix breaking tcp if ts fooling is enabled but no timestamps present 72.9 blockcheck: fix detection of http redirection if domain/URI specified install_easy: fix writing of ask_list variables 72.10 * nfqws2: fix broken wifi ssid update * minor AI fixes 72.12 * github: reduce binaries size * github: use 16K page size for android arm64 build * nfqws: join fragments in quic CRYPTO reconstruction. allow intersections. ================================================ FILE: docs/compile/build_howto_openwrt.txt ================================================ How to compile native programs for use in openwrt ------------------------------------------------- 1) Install required packages to the host system : debian,ubuntu : apt install build-essential patch libncurses-dev python3-distutils unzip gawk wget git fedora: dnf install make patch gcc g++ ncurses-devel git perl Other packages may be required on your distribution. Look for the errors. 2) Download latest SDK for your target platform from https://downloads.openwrt.org examples : curl -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 cd openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64 curl -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 cd openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64 3) Install required libs ./scripts/feeds update base packages ./scripts/feeds install libnetfilter-queue zlib libcap 4) Prepare openwrt package definitions cp -R /opt/zapret/docs/compile/openwrt/. . cp -R /opt/zapret/tpws package/zapret/tpws cp -R /opt/zapret/nfq package/zapret/nfqws cp -R /opt/zapret/mdig package/zapret/mdig cp -R /opt/zapret/ip2net package/zapret/ip2net rm -f package/zapret/tpws/tpws/tpws package/zapret/nfqws/nfq/nfqws package/zapret/mdig/mdig/mdig package/zapret/ip2net/ip2net/ip2net 5) Prepare .config make defconfig If you only need bins without packages comment 'CONFIG_AUTOREMOVE=y' line in .config 6) Compile dynamic build : make package/{tpws,nfqws,mdig,ip2net}/compile static build : make CFLAGS=-static package/{tpws,nfqws,mdig,ip2net}/compile 7) Get result executables only : build_dir/target/ ipk or apk packages : bin/packages/*/base 8) Installing to openwrt to use with zapret zapret with or without binaries should be already installed in /opt/zapret. Install ipk's or apk's with all compiled progs using opkg or apk. Bins are placed to /opt/zapret/binaries/my. Or copy binaries there manually and set chmod 755 to them. Run install_bin.sh or install_easy.sh. They will use bins in 'my' folder. ================================================ FILE: docs/compile/build_howto_unix.txt ================================================ debian,ubuntu : apt install make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev make -C /opt/zapret systemd FreeBSD : make -C /opt/zapret OpenBSD : make -C /opt/zapret bsd MacOS : make -C /opt/zapret mac ================================================ FILE: docs/compile/build_howto_windows.txt ================================================ Windows x64 1) Download latest cygwin for windows 7 curl -O https://www.cygwin.com/setup-x86_64.exe setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 2) During setup install packages : make gcc-core zlib-devel 3) Run Cygwin.bat 4) cd to %ZAPRET_BASE%/nfq cd C:/Users/user/Downloads/zapret/nfq 5) Compile make cygwin64 use winws.exe 6) Take windivert.dll and windivert64.sys here : https://reqrypt.org/download Choose version 2.2.2 for Windows 10 and 2.2.0 for Windows 7. 7) Copy cygwin1.dll, winws.exe, windivert.dll and windivert64.sys to one folder. 8) Run winws.exe from cmd.exe running as administrator. winws will not run from cygwin shell with cygwin1.dll copy in it's folder. winws will not run without cygwin1.dll outside of cygwin shell. ================================================ FILE: docs/compile/openwrt/package/zapret/ip2net/Makefile ================================================ # include $(TOPDIR)/rules.mk PKG_NAME:=ip2net PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk define Package/ip2net SECTION:=net CATEGORY:=Network TITLE:=ip2net SUBMENU:=Zapret endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./ip2net/* $(PKG_BUILD_DIR)/ endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) endef define Package/ip2net/install $(INSTALL_DIR) $(1)/opt/zapret/binaries/my $(INSTALL_BIN) $(PKG_BUILD_DIR)/ip2net $(1)/opt/zapret/binaries/my endef $(eval $(call BuildPackage,ip2net)) ================================================ FILE: docs/compile/openwrt/package/zapret/ip2net/readme.txt ================================================ Copy "ip2net" folder here ! ================================================ FILE: docs/compile/openwrt/package/zapret/mdig/Makefile ================================================ # include $(TOPDIR)/rules.mk PKG_NAME:=mdig PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk define Package/mdig SECTION:=net CATEGORY:=Network TITLE:=mdig SUBMENU:=Zapret endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./mdig/* $(PKG_BUILD_DIR)/ endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) endef define Package/mdig/install $(INSTALL_DIR) $(1)/opt/zapret/binaries/my $(INSTALL_BIN) $(PKG_BUILD_DIR)/mdig $(1)/opt/zapret/binaries/my endef $(eval $(call BuildPackage,mdig)) ================================================ FILE: docs/compile/openwrt/package/zapret/mdig/readme.txt ================================================ Copy "mdig" folder here ! ================================================ FILE: docs/compile/openwrt/package/zapret/nfqws/Makefile ================================================ # include $(TOPDIR)/rules.mk PKG_NAME:=nfqws PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk define Package/nfqws SECTION:=net CATEGORY:=Network TITLE:=nfqws SUBMENU:=Zapret DEPENDS:=+libnetfilter-queue +lmnl +libcap +zlib endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./nfq/* $(PKG_BUILD_DIR)/ endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) endef define Package/nfqws/install $(INSTALL_DIR) $(1)/opt/zapret/binaries/my $(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/binaries/my endef $(eval $(call BuildPackage,nfqws)) ================================================ FILE: docs/compile/openwrt/package/zapret/nfqws/readme.txt ================================================ Copy "nfq" folder here ! ================================================ FILE: docs/compile/openwrt/package/zapret/tpws/Makefile ================================================ # include $(TOPDIR)/rules.mk PKG_NAME:=tpws PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk define Package/tpws SECTION:=net CATEGORY:=Network TITLE:=tpws SUBMENU:=Zapret DEPENDS:=+zlib +libcap endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./tpws/* $(PKG_BUILD_DIR)/ endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) endef define Package/tpws/install $(INSTALL_DIR) $(1)/opt/zapret/binaries/my $(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/binaries/my endef $(eval $(call BuildPackage,tpws)) ================================================ FILE: docs/compile/openwrt/package/zapret/tpws/readme.txt ================================================ Copy "tpws" folder here ! ================================================ FILE: docs/iptables.txt ================================================ For window size changing : iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass iptables -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 For dpi desync attack : iptables -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 iptables -t mangle -I POSTROUTING -p tcp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass iptables -t mangle -I POSTROUTING -p udp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass # auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 iptables -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 iptables -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 For TPROXY : sysctl -w net.ipv4.ip_forward=1 iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE ip -f inet rule add fwmark 1 lookup 100 ip -f inet route add local default dev lo table 100 # prevent loop iptables -t filter -I INPUT -p tcp --dport 988 -j REJECT iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1 iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1 iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 For DNAT : # run tpws as user "tpws". its required to avoid loops. sysctl -w net.ipv4.conf.eth1.route_localnet=1 iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 Reset all iptable rules : iptables -F iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -t raw -F iptables -t raw -X Reset iptable policies : iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -t mangle -P POSTROUTING ACCEPT iptables -t mangle -P PREROUTING ACCEPT iptables -t mangle -P INPUT ACCEPT iptables -t mangle -P FORWARD ACCEPT iptables -t mangle -P OUTPUT ACCEPT iptables -t raw -P PREROUTING ACCEPT iptables -t raw -P OUTPUT ACCEPT ================================================ FILE: docs/nftables.txt ================================================ nftables test cheat sheet simplified rules to test nfqws and tpws For DNAT : # run tpws as user "tpws". its required to avoid loops. nft delete table inet ztest nft create table inet ztest nft add chain inet ztest pre "{type nat hook prerouting priority dstnat;}" nft add rule inet ztest pre tcp dport "{80,443}" redirect to :988 nft add chain inet ztest out "{type nat hook output priority -100;}" nft add rule inet ztest out tcp dport "{80,443}" skuid != tpws redirect to :988 For dpi desync attack : nft delete table inet ztest nft create table inet ztest nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" nft add rule inet ztest post meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-6 queue num 200 bypass nft add rule inet ztest post meta mark and 0x40000000 == 0 udp dport 443 ct original packets 1-6 queue num 200 bypass # auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" nft add rule inet ztest pre tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass show rules : nft list table inet ztest delete table : nft delete table inet ztest ================================================ FILE: docs/nftables_notes.txt ================================================ nftables - это технология, пришедшая на замену iptables. В ней собрали все, что относилось к различным iptables. А их немало. iptables, ip6tables, ebtables, arptables, ipset. Весь код из разрозненных, но похожих компонент, собрали в одно целое с единым синтаксисом. Добавили различные конструкции языка, позволяющие писать правила более лаконично, не повторяя одни и те же команды с небольшими различиями. На nftables можно сделать почти все, что можно было сделать на iptables. Есть то, что можно сделать на nftables, но нельзя на iptables. Есть и наоборот. К сожалению, не обошлось и без боли. Главная боль N1. Очень серьезная, актуальная для openwrt, и решения не видно. ipset-ы позволяли загонять пересекающиеся интервалы ip адресов или подсетей. nftables sets это не позволяют. Любое пересечение вызывает ошибку. Есть auto-merge, но это работает только в user mode в процессе nft, при условии, что весь блок адресов загоняется одной командой и нет пересечений с уже имеющимся контентом в set. Это не было бы критической проблемой, поскольку скрипты zapret и так загоняют ipset целиком. Проблема в катастрофическом расходе памяти при операции загона больших интервальных листов, то есть с подсетями и диапазонами. Чтобы загнать 100000 ipv4 записей, едва хватает 300 Mb памяти устройства. При успехе операции в ядре список столько не занимает, но суть дела это не меняет. Для традиционных linux систем это не проблема, но почти все роутеры загнутся. Приемлемого решения не просматривается. Сделать записи непересекающимися в листах - задача непростая. Потребуется переписать алгоритм auto-merge из nft, но с пониженным расходом памяти. Загонять записи по одному отдельными вызовами nft, игнорируя ошибки, займет вечность. Загонять блоком отдельных команд, игнорируя ошибки, - nft такого не умеет. Похоже, при любой ошибке происходит откат всего скрипта. К тому же при таком подходе будут неточности в итоговом результате. Swap позволяет немного сгладить проблему, но лишь незначительно. Скажем, если вдруг list загоняется без ошибок с 300 Mb памяти и с падением на 256 Mb, то swap спасает. Если памяти становится 200 Mb, то swap уже не спасет. Все равно вызывается OOM killer, заодно убивая и другие процессы, кроме nft, а это уже совсем плохо. Может быть убито что-то важное. Боль N2, не смертельная, но тоже не айс. Какие-то нерациональные алгоритмы разбора таблиц в nft. Например, есть 1 большой set на 100000 элементов и 1 маленький на 2 элемента. Чтобы просто пролистать мелкий set или добавить туда еще что-то nft будет мусолить несколько секунд. Что он делает за это время ? Тащит из ядра огромный блоб, в котором все в куче, и разбирает его, чтобы выделить искомую мелочь ? В какой-то мере удается это сгладить, объединяя несколько команд в единый скрипт. Боль N3 Система nftables построена на виртуальной машине. Правила, которые вы пишите, переводятся в псевдокод VM. Чтобы потом их показать , nft декомпилирует код и переводит в читаемый язык. Это довольно сложно, и регулярно случаются баги, связанные с неверным отображением. Кроме этого, часто встречаются и баги парсера. Например, все версии nft вплоть до 1.0.1 имеют баг, который не разрешает названия интерфейсов в кавычках в определении flowtable. Без кавычек нельзя вставить интерфейсы , имя которых начинается с цифры. OpenWRT решает эту проблему отдельным патчем в snapshot версии, но на традиционных системах и в openwrt 21.x- его нет. Почему бы не наплевать на интерфейсы, начинающиеся с цифры ? Потому что для openwrt 6to4-6to4, 6in4-he-net - обычное явление. На текущий момент этой проблемы в openwrt уже нет, если использовать актуальную версию. Но тем не менее, хоть nft и давно перешел отметку 1.0, всякая мелочь, особенно на не совсем стандартных правилах, регулярно всплывает. Потому чем новее у вас будет версия nft, тем больше там выловлено проблем. Здесь обновления важны, чтобы потом не мучаться из-за давно исправленного велосипеда. Боль N4 Невозможно , не копаясь в других таблицах и хуках, ничего не зная об их содержании, предотвратить DROP или REJECT. Нельзя написать такое правило, которое что-то важное ACCEPTнет, игнорируя остальные хуки во всех таблицах. Если у вас есть какой-то фаервол, и он что-то дропает, то как от этого отказаться, если надо временно что-то принять ? Это особенность netfilter, он так работает, но в iptables есть лишь стандартные таблицы с их хуками, куда можно вставить ACCEPT. Здесь хуков может быть сколько угодно в каких угодно таблицах. Эта проблема частично ломает кайф от независимого управления таблицами. Плюс N1, главный iptables хороши, когда ими пользуется кто-то один. Иначе это проходной двор. Когда есть система управления фаерволом, то приходится как-то к ней прикручиваться, чтобы не нарушить ее работу и управлять правилами синхронно. Нужно уметь внести и удалить отдельные правила когда это нужно, не трогая все остальное. Некоторые системы управления фаерволом вообще не предполагают, чтобы кто-то еще лез в iptables, и это очень сильно портит жизнь. У iptables есть предопределенный набор хуков netfilter с фиксированным приоритетом. В nftables хуков можно создать неограниченное количество с выбранным приоритетом, управляя ими независимо в отдельных таблицах. Система управления фаерволом может работать в одной таблице (fw4 в случае openwrt) и не трогать все остальное. zapret может работать в другой таблице и не трогать систему управления фаерволом. Они друг другу не мешают. Это снимает множество боли. Но есть и исключение. nfset-ы - аналог ipset-ов - нельзя использовать из другой таблицы. Потому если вам нужен ipset, создаваемый zapret скриптами, вам понадобится писать правила в той же таблице. Но нет никакой необходимости влезать в цепочки zapret. Создаете свои цепочки и хуки и делаете в них что угодно. Плюс N2 Возможность выбора приоритета хука позволяет легко решить проблему хаотической и принудительной дефрагментацией L3 ipv6, без танцев с загрузкой модулей ядра со специальными параметрами или перекомпиляцией nftables-nft. Это же позволяет перехватить трафик после SNAT/MASQUERADE, что на iptables невозможно. Атаки на проходящий трафик, ломающие NAT, крайне затруднены на iptables. Плюс N3 Наличие множеств (anonymous/named sets) позволяет не писать кучу однообразных правил там, где в iptables их пришлось бы написать. Плюс N4 Если у вас есть nftables, то там наверняка есть уже все или почти все. Нет кучи разных модулей ядра и .so плагинов для iptables user-mode процесса. Отдельные модули ядра есть, но их меньше, чем в iptables, и openwrt их делит на меньшее число пакетов, большинство из которых и так ставятся по умолчанию. user-mode процесс nft и вовсе неделим. EXE-шник + lib. Плюс N5 Пишут, что nftables работают быстрее. Но это не точно и зависит от много чего. В целом - чем меньше правил, тем выше скорость. Но в nftables правил можно писать меньше, значит скорость тоже может быть выше. У разработчиков есть идея перевести backend nftables на BPF, а это наверняка будет существенно быстрее. Выводы Без больших листов все почти прекрасно. Но большие ip листы убивают все. Не для домашних это роутеров. А ipset-ы к nftables не прикрутить. Зато есть возможность задействовать более продвинутые атаки, конфликтующие с NAT, которые на iptables могут быть невозможны. Делать нечего. Openwrt отошел от iptables. С этим придется как-то жить. Поэтому пришлось сделать для openwrt поддержку и iptables, и nftables (только с версии openwrt 21.xx, в более старых будут проблемы). iptables можно задействовать на любой openwrt версии. Если используется fw3, применяется старый механизм интеграции в fw3. Если он не используется, то правилами iptables управляем как в традиционных linux системах - то есть с возможностью запуска и остановки, а скрипт запуска вносит в том числе и правила iptables. На новых openwrt возможно снести nftables и firewall4 и установить firewall3 и iptables. Если вам никак без больших ip листов на слабой системе, это может быть единственным спасением. ================================================ FILE: docs/quick_start.md ================================================ # Быстрая настройка Linux/OpenWrt > [!CAUTION] > Не пишите в issue вопросы типа "как скопировать файл", "как скачать", "как > запустить" и т.п. То есть все, что касается базовых навыков обращения с ОС > Linux. Эти вопросы будут закрывать сразу. Если у вас подобные вопросы > возникают, рекомендую не использовать данный софт или искать помощь где-то в > другом месте. То же самое могу сказать тем, кто хочет нажать 1 кнопку, чтобы > все заработало, и совсем не хочет читать и изучать. Увы, такое не подвезли и > не подвезут. Ищите другие более простые методы обхода. Этот метод **не для > рядового пользователя**. ## Вступление Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню [readme.md](readme.md). Обход DPI является хакерской методикой. Под этим словом понимается метод, которому оказывается активное противодействие и поэтому автоматически не гарантирована работоспособность в любых условиях и на любых ресурсах, требуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем, и методика может начинать или переставать работать, может потребоваться повторный анализ ситуации. Могут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать. Могут и сломаться отдельные не заблокированные ресурсы. Поэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию. Не будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает. Вариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный. Нет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой. Такие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети. Если даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация. Будем считать, что у вас есть система на базе традиционного **linux** или **openwrt**. Если у вас традиционный linux - задача обойти блокировки только на этой системе, если openwrt - обойти блокировки для подключенных устройств. Это наиболее распространенный случай. ## Настройка > [!TIP] > Чтобы процедура установки сработала в штатном режиме на openwrt, нужно > рассчитывать на свободное место около 1-2 Mb для установки самого zapret и > необходимых дополнительных пакетов. Если места мало и нет возможности его > увеличить за счет `extroot`, возможно придется отказаться от варианта простой > установки и прикручивать в ручном режиме без имеющихся скриптов запуска. > Можно использовать [облегченный `tpws` вариант](../init.d/openwrt-minimal), > либо попробовать засунуть требуемые zapret дополнительные пакеты в сжатый > образ `squashfs` с помощью `image builder` и перешить этим вариантом роутер. 1. Скачайте последний [tar.gz релиз](https://github.com/bol-van/zapret/releases) в /tmp, распакуйте его, затем удалите архив. Для openwrt и прошивок используйте вариант `openwrt-embedded`. Для экономия места в /tmp можно качать через curl в stdout и сразу распаковывать. Пример под openwrt для версии zapret 71.4 (для других URL отличается) : ``` $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4-openwrt-embedded.tar.gz | tar -zx ``` Пример под традиционный linux для версии zapret 71.4 (для других URL отличается) : ``` $ curl -Lo - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx $ wget -O - https://github.com/bol-van/zapret/releases/download/v71.4/zapret-v71.4.tar.gz | tar -zx ``` curl должен быть предварительно установлен. Но он в любом случае понадобится далее. Вариант с wget будет работать только если установленный wget поддерживает https. 2. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret. Гарантированно уберет zapret скрипт `uninstall_easy.sh`. 3. Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. NAT **не** подходит. 4. Выполните однократные действия по установке требуемых пакетов в ОС и настройке исполняемых файлов правильной архитектуры: ```sh $ install_bin.sh $ install_prereq.sh ``` > Вас могут спросить о типе фаервола (iptables/nftables) и использовании > ipv6. Это нужно для установки правильных пакетов в ОС, чтобы не > устанавливать лишнее. 5. Запустите `blockcheck.sh`. Скрипт вначале проверяет DNS. Если выводятся сообщения о подмене адресов, то нужно будет решить проблему с DNS. `blockcheck.sh` перейдет в этом случае на DoH и будет пытаться получить и использовать реальные IP адреса. Но если вы не настроите решение этой проблемы, обход будет работать только для тех программ или ОС, которые сами реализуют механизмы SecureDNS. Для других программ обход работать не будет. > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо > заменой DNS серверов от провайдера на публичные (`1.1.1.1`, `8.8.8.8`), > либо в случае перехвата провайдером обращений к сторонним серверам - через > специальные средства шифрования DNS запросов, такие как `dnscrypt`, `DoT`, > `DoH`. > > Еще один эффективный вариант - использовать ресолвер от yandex > (`77.88.8.88`) на нестандартном порту `1253`. Многие провайдеры не > анализируют обращения к DNS на нестандартных портах. > > Проверить работает ли этот вариант можно так: > ```sh > $ dig -p 53 @77.88.8.88 rutracker.org > $ dig -p 1253 @77.88.8.88 rutracker.org > ``` > > Если DNS действительно подменяется, и ответ на эти 2 команды разный, > значит метод вероятно работает. > > В openwrt DNS на нестандартном порту можно прописать в `/etc/config/dhcp` > таким способом: > > ``` > config dnsmasq > <...> > list server '77.88.8.88#1253' > ``` > > Если настройки IP и DNS получаются автоматически от провайдера, в > `/etc/config/network` найдите секцию интерфейса `wan` и сделайте так: > > ``` > config interface 'wan' > <...> > option peerdns '0' > ``` > > ```sh > $ /etc/init.d/network restart > $ /etc/init.d/dnsmasq restart > ``` > > Если это не подходит, можно перенаправлять обращения на UDP и TCP порты > `53` вашего DNS сервера на `77.88.8.88:1253` средствами > `iptables`/`nftables`. В `/etc/resolv.conf` нельзя прописать DNS на > нестандартном порту. 6. `blockcheck.sh` позволяет выявить рабочую стратегию обхода блокировок. По результатам скрипта нужно понять какой вариант будете использовать : `nfqws` или `tpws` и запомнить найденные стратегии для дальнейшего применения. Следует понимать, что скрипт проверяет доступность только конкретного домена, который вы вводите в начале, конкретной программой curl. У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой. Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber). От этого может зависеть работоспособность стратегий. Обычно остальные домены блокированы подобным образом, **но не факт**. Бывают специальные блокировки. Некоторые параметры требуют тюнинга под "общий знаменатель". В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне желательно**, но это требует понимания как работают стратегии. zapret **не может пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите их через пробел. > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и > включаются в работу хаотичным образом или образом, зависящим от > направления (IP сервера). скрипт не всегда может выдать вам в итогах > оптимальную стратегию, которую надо просто переписать в настройки. В > некоторых случаях надо реально думать что происходит, анализируя результат > на разных стратегиях. > > Далее, имея понимание что работает на http, https, quic нужно > сконструировать параметры запуска `tpws` и/или `nfqws` с использованием > мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии). > > Если кратко, то обычно параметры конструируются так: > ```sh > "--filter-udp=443 'параметры для quic' --new > --filter-tcp=80,443 'объединенные параметры для http и https' " > ``` > > Или так: > ```sh > "--filter-udp=443 'параметры для quic' --new > --filter-tcp=80 'параметры для http' --new > --filter-tcp=443 'параметры для https' " > ``` > > `` и `` так и пишутся. Их не надо на что-то > заменять. Это сделают скрипты запуска, если вы выбрали режим фильтрации по > хостлистам, и уберут в противном случае. Если для какого-то протокола надо > дурить все без стандартного хостлиста - просто уберите оттуда `` > и ``. Можно писать свои параметры `--hostlist` и > `--hostlist-exclude` для дополнительных хостлистов или в профилях > специализаций под конкретный ресурс. В последнем случае стандартный > хостлист там не нужен. Следует избегать указания собственных параметров > `--hostlist` на листы из директории ipset. Эта логика включена в > `` и ``. Отличие `` в том, что > стандартный автолист по этому профилю используется как обычный, то есть > без автоматического добавления доменов. Однако, добавления в других > профилях автоматически отражаются во всех остальных. > > Если стратегии отличаются по версии ip протокола, и вы не можете их > объединить, фильтр пишется так: > ```sh > "--filter-l3=ipv4 --filter-udp=443 'параметры для quic ipv4' --new > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' --new > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' --new > --filter-l3=ipv6 --filter-udp=443 'параметры для quic ipv6' --new > --filter-l3=ipv6 --filter-tcp=80 'параметры для http ipv6' --new > --filter-l3=ipv6 --filter-tcp=443 'параметры для https ipv6' " > ``` > > Но здесь совсем "копи-пастный" вариант. Чем больше вы объедините стратегий и > сократите их общее количество, тем будет лучше. > > Если вам не нужно дурение отдельных протоколов, лучше всего будет убрать > лишние порты из системы перехвата трафика через параметры `TPWS_PORTS`, > `NFQWS_PORTS_TCP`, `NFQWS_PORTS_UDP` и убрать соответствующие им профили > мультистратегии. > > | Протокол | Порт | Примечание | > |---|---|---| > | `tcp` | `80` | `http` соединение | > | `tcp` | `443` | `https` соединение | > | `udp` | `443` | `quic` соединение | > > Если используются методы нулевой фазы десинхронизации (`--mss`, > `--wssize`, `--dpi-desync=syndata`) и режим фильтрации `hostlist`, то все > параметры, относящиеся к этим методам, следует помещать в отдельные > профили мультистратегии, которые получат управление до определения имени > хоста. Необходимо понимать алгоритм работы мультистратегий. Самым надежным > вариантом будет дублирование этих параметров на 2 профиля. Какой-нибудь > сработает в зависимости от параметра `MODE_FILTER`. > > ```sh > "--filter-tcp=80 'параметры для http' --new > --filter-tcp=443 'параметры для https' --wssize 1:6 --new > --filter-tcp=443 --wssize 1:6" > ``` > > В этом примере `wssize` будет применяться всегда к порту tcp `443` вне > зависимости от параметра `MODE_FILTER`. Хостлист будет игнорироваться, > если таковой имеется. К http применять `wssize` вредно и бессмысленно. > > Иногда требуется дописать к стратегиям свои собственные параметры. > Например, нужно изменить количество повторов фейков или задать свой фейк. > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`. > > ```PKTWS_EXTRA="--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin" ./blockcheck.sh``` > > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный > костяк проверки, на который вы можете навешивать свои кустомизации. 7. Запустите скрипт облегченной установки - `install_easy.sh` Выберите `nfqws` и/или `tpws`, затем согласитесь на редактирование параметров. Откроется редактор, куда впишите созданную на предыдущем этапе стратегию. 8. На все остальные вопросы `install_easy.sh` отвечайте согласно выводимой аннотации. 9. Удалите директорию из /tmp, откуда производилась установка. ## Полное удаление 1. Прогоните `/opt/zapret/uninstall_easy.sh`. 2. Cогласитесь на удаление зависимостей в openwrt. 3. Удалите каталог `/opt/zapret`. ## Итог Это минимальная инструкция, чтобы быстро сориентироваться с чего начать. Однако, это не гарантированное решение и в некоторых случаях вы не обойдетесь без знаний и основного "талмуда". Подробности и полное техническое описание расписаны в [README](readme.md). Если ломаются отдельные **не заблокированные** ресурсы, следует вносить их в исключения, либо пользоваться ограничивающим `ipset` или хост листом. Лучше подбирать такие стратегии, которые вызывают минимальные поломки. Есть стратегии довольно безобидные, а есть сильно ломающие, которые подходят только для точечного пробития отдельных ресурсов, когда ничего лучше нет. Хорошая стратегия может сильно ломать из-за плохо подобранных ограничителей для фейков \- ttl, fooling. ================================================ FILE: docs/quick_start_windows.md ================================================ # Быстрая настройка Windows Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню [readme.md](./readme.md). > [!CAUTION] > Как обычно, компьютерная грамотность ложится полностью на вас. > Вы должны уметь работать с консолью windows и иметь минимальные навыки обращения с командными файлами `bat`, `cmd`. > Если грамотность отсутствует и возникает куча _"как?"_ на базовых вещах, значит эта программа не для вас. > Разработчик не будет отвечать на вопросы из серии школы компьютерной грамотности. > Если вы все-таки хотите продолжать, задавайте вопросы в дискуссиях на github или на форумах. > Возможно, кто-то вам поможет. Но не надо писать issue на github. Они будут закрываться сразу. ## Немного разъяснений Обход DPI является хакерской методикой. Под этим словом понимается метод, которому оказывается активное противодействие и поэтому автоматически не гарантирована работоспособность в любых условиях и на любых ресурсах, требуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем, и методика может начинать или переставать работать, может потребоваться повторный анализ ситуации. Могут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать. Могут и сломаться отдельные незаблокированные ресурсы. Поэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию. Не будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает. Вариант, когда вы нашли стратегию где-то в интернете и пытаетесь ее приспособить к своему случаю - заведомо проблемный. Нет универсальной таблетки. Везде ситуация разная. В сети гуляют написанные кем-то откровенные глупости, которые тиражируются массово ничего не понимающей публикой. Такие варианты чаще всего работают нестабильно, только на части ресурсов, только на части провайдеров, не работают вообще или ломают другие ресурсы. В худших случаях еще и устраивают флуд в сети. Если даже вариант когда-то и работал неплохо, завтра он может перестать, а в сети останется устаревшая информация. Особо осторожным нужно быть со сторонними сборками. Там могут быть вирусы. Не в каждой сборке, но уже были замечены скаммеры. Видео на ютубе как просто обойти блокировку, прилагающийся архив, в котором какая-то ерунда, написанная на питоне, скачивающая зловред. Будем считать, что у вас есть windows 7 или выше. Задача - обойти блокировки с самой системы. ## Я ЧТО-ТО ДЕЛАЛ, НЕ ПОМОГЛО, КАК ТЕПЕРЬ ЭТО УДАЛИТЬ Если вы не устанавливали zapret как службу или запланированную задачу (а это требует редактирования cmd файлов), достаточно закрыть окно с winws и запустить windivert_delete.cmd. Альтернатива - перезагрузить компьютер. После чего можно удалить папку с zapret. На этом деинсталляция закончена. Если же вы устанавливали zapret как службу, то вы наверняка знаете как ее удалить. Если вдруг среди того, на что вы нажимали, есть слова "general", "alt", ".bat", "автозапуск", или же есть файлы, которые отсутствуют в оригинальных репозиториях, то это сборка. Вы не получите ответа как ее удалить от разработчика zapret. Спрашивайте самих сборщиков. Разработчик не предоставляет простого решения, этим занимаются сборщики, но они и сами отвечают за свои продукты. ## Настройка 1. Скачайте и распакуйте архив https://github.com/bol-van/zapret-win-bundle/archive/refs/heads/master.zip. 2. Если у вас Windows 7 x64, однократно запустите `win7/install_win7.cmd`. Батник заменит файлы windivert на совместимую с Windows 7 версию. > Для 32-битных систем Windows нет готового полного варианта. > На windows 11 arm64 выполните `arm64/install_arm64.cmd` от имени администратора и перезагрузите компьютер. > Читайте [docs/windows.md](./windows.md) > > Имейте в виду, что антивирусы могут плохо реагировать на windivert. > cygwin так же имеет внушительный список несовместимостей с антивирусами, хотя современные антивирусы > более-менее научились с ним дружить. > Если проблема имеет место , используйте исключения. Если не помогает - отключайте антивирус совсем. 3. Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret. 4. Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. nat не подходит 5. Запустите `blockcheck\blockcheck.cmd`. blockcheck в начале проверяет **DNS**. Если выводятся сообщения о подмене адресов, то нужно будет решить проблему с **DNS**. blockcheck перейдет в этом случае на **DoH** _(DNS over HTTPS)_ и будет пытаться получить и использовать реальные IP адреса. Но если вы не настроите решение этой проблемы, обход будет работать только для тех программ, которые сами реализуют механизмы SecureDNS. Для других программ обход работать не будет. > Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо заменой DNS серверов > от провайдера на публичные (`1.1.1.1`, `8.8.8.8`), либо в случае перехвата провайдером обращений > к сторонним серверам - через специальные средства шифрования DNS запросов, такие как [dnscrypt](https://www.dnscrypt.org/), **DoT** _(DNS over TLS)_, **DoH**. > В современных броузерах чаще всего DoH включен по умолчанию, но curl будет использовать обычный системный DNS. > win11 поддерживает системные DoH из коробки. Они не настроены по умолчанию. > В последних билдах win10 существует неофициальный обходной путь для включения DoH. > Для остальных систем нужно стороннее решение, работающие по принципу DNS proxy. > > Тут все разжевано как и где это включается : https://hackware.ru/?p=13707 6. blockcheck позволяет выявить рабочую стратегию обхода блокировок. Лог скрипта будет сохранен в `blockcheck\blockcheck.log`. Запомните/перепишите найденные стратегии. Следует понимать, что скрипт проверяет доступность только конкретного домена, который вы вводите в начале, конкретной программой curl. У разных клиентов есть свой фингерпринт. У броузеров один, у curl другой. Может применяться или не применяться многопакетный TLS с постквантовой криптографией (kyber). От этого может зависеть работоспособность стратегий. Обычно остальные домены блокированы подобным образом, **но не факт**. Бывают специальные блокировки. Некоторые параметры требуют тюнинга под "общий знаменатель". В большинстве случаев можно объединить несколько стратегий в одну универсальную, и это **крайне желательно**, но это требует понимания как работают стратегии. zapret **не может пробить блокировку по IP адресу**. Для проверки нескольких доменов вводите их через пробел. > Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у > вас может быть несколько маршрутов с различной длиной по ХОПам, с DPI на > разных хопах. Приходится преодолевать целый зоопарк DPI, которые еще и > включаются в работу хаотичным образом или образом, зависящим от > направления (IP сервера). скрипт не всегда может выдать вам в итогах > оптимальную стратегию, которую надо просто переписать в настройки. В > некоторых случаях надо реально думать что происходит, анализируя результат > на разных стратегиях. > > Далее, имея понимание что работает на http, https, quic, нужно сконструировать параметры запуска winws > с использованием мультистратегии. Как работают мультистратегии описано в [readme.md](./readme.md#множественные-стратегии). > > Прежде всего вам нужно собрать фильтр перехватываемого трафика. Это делается через параметры > `--wf-l3`, `--wf-tcp`, `--wf-udp`. > `--wf-l3` относится к версии ip протокола - ipv4 или ipv6. > `--wf-tcp` и `--wf-udp` содержат перечень портов или диапазонов портов через запятую. > > Пример стандартного фильтра для перехвата http, https, quic : `--wf-tcp=80,443` `--wf-udp=443` > > Фильтр должен быть минимально необходимым. Перехват лишнего трафика приведет только к бессмысленному расходованию ресурсов процессора и замедлению интернета. > > Если кратко по мультистратегии, то обычно параметры конструируются так : > ``` > --filter-udp=443 'параметры для quic' --new > --filter-tcp=80,443 'объединенные параметры для http и https' > ``` > > Или так : > ``` > --filter-udp=443 'параметры для quic' --new > --filter-tcp=80 'параметры для http' --new > --filter-tcp=443 'параметры для https' > ``` > > Если стратегии отличаются по версии ip протокола, и вы не можете их объединить, фильтр пишется так : > ``` > --filter-l3=ipv4 --filter-udp=443 "параметры для quic ipv4" --new > --filter-l3=ipv4 --filter-tcp=80 'параметры для http ipv4' --new > --filter-l3=ipv4 --filter-tcp=443 'параметры для https ipv4' --new > --filter-l3=ipv6 --filter-udp=443 "параметры для quic ipv6" --new > --filter-l3=ipv6 --filter-tcp=80 "параметры для http ipv6" --new > --filter-l3=ipv6 --filter-tcp=443 "параметры для https ipv6" > ``` > > Но здесь совсем _"копи-пастный"_ вариант. > Чем больше вы обьедините стратегий и сократите их общее количество, тем будет лучше. > > Если вам не нужно дурение отдельных протоколов, лучше всего будет их убрать из системы перехвата трафика через > параметры `--wf-*` и убрать соответствующие им профили мультистратегии. > tcp 80 - http, tcp 443 - https, udp 443 - quic. > > Если используются методы нулевой фазы десинхронизации (`--mss`, `--wssize`, `--dpi-desync=syndata`) и фильтрация hostlist, > то все параметры, относящиеся к этим методам, следует помещать в отдельные профили мультистратегии, которые получат > управление до определения имени хоста. Необходимо понимать алгоритм работы мультистратегий. > > ``` > --filter-tcp=80 'параметры для http' --new > --filter-tcp=443 'параметры для https' --hostlist=d:/users/user/temp/list.txt --new > --filter-tcp=443 --wssize 1:6 > ``` > > autohostlist профиль приоритетен, поэтому wssize нужно писать туда : > > ``` > --filter-tcp=80 'параметры для http' --new > --filter-tcp=443 'параметры для https' --wssize 1:6 --hostlist-auto=d:/users/user/temp/autolist.txt > ``` > > В этих примерах wssize будет применяться всегда к порту tcp 443, а хостлист будет игнорироваться. > К http применять wssize вредно и бессмысленно. > > Иногда требуется дописать к стратегиям свои собственные параметры. > Например, нужно изменить количество повторов фейков или задать свой фейк. > Это делается через шелл-переменные `PKTWS_EXTRA`, `TPWS_EXTRA`. Пользуйтесь шеллом `cygwin/cygwin-admin.cmd`. > > ```PKTWS_EXTRA="--dpi-desync-repeats=10 --dpi-desync-fake-tls=/tmp/tls.bin" blockcheck``` > > Перебор всех комбинаций может привести к ожиданию неделями, поэтому выбран разумный > костяк проверки, на который вы можете навешивать свои кустомизации. 7. Протестируйте найденные стратегии на winws. Winws следует брать из zapret-winws. Для этого откройте командную строку windows от имени администратора в директории zapret-winws. Проще всего это сделать через `_CMD_ADMIN.cmd`. Он сам поднимет права и зайдет в нужную директорию. 8. Обеспечьте удобную загрузку обхода блокировок. > Есть 2 варианта. Ручной запуск через ярлык или автоматический при старте системы, вне контекста текущего пользователя. > Последний вариант разделяется на запуск через планировщик задач и через службы windows. > > Если хотите ручной запуск, скопируйте `preset_russia.cmd` в `preset_my.cmd` (`<ваше_название>.cmd`) и адаптируйте его под ваши параметра запуска. > Потом можно создать ярлык на рабочем столе на `preset_my.cmd`. Не забудьте, что требуется запускать от имени администратора. > > Но лучше будет сделать неинтерактивный автоматический запуск вместе с системой. > В zapret-winws есть командные файлы `task_*`, предназначенные для управления задачами планировщика. > Там следует поменять содержимое переменной `WINWS1` на свои параметры запуска winws. Пути с пробелами нужно экранировать кавычками с обратным слэшем : `\"`. > После создания задач запустите их. Проверьте, что обход встает после перезагрузки windows. > > Аналогично настраиваются и службы windows. Смотрите `service_*.cmd` 9. Если ломаются отдельные незаблокированные ресурсы, нужно пользоваться ограничивающим ipset или хост листом. Читайте основной талмуд [readme.md](./readme.md) ради подробностей. Но еще лучше будет подбирать такие стратегии, которые ломают минимум. Есть стратегии довольно безобидные, а есть сильно ломающие, которые подходят только для точечного пробития отдельных ресурсов, когда ничего лучше нет. Хорошая стратегия может сильно ломать из-за плохо подобранных ограничителей для фейков - ttl, fooling. > [!CAUTION] > Это минимальная инструкция, чтобы сориентироваться с чего начать. Однако, это - не панацея. > В некоторых случаях вы не обойдетесь без знаний и основного "талмуда". Подробности и полное техническое описание расписаны в [readme.md](./readme.md) ================================================ FILE: docs/readme.en.md ================================================ # SCAMMER WARNING This software is free and open source under [MIT license](./LICENSE.txt). If 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. However, [donations](#donations) are welcome. # Multilanguage/Мультиязычный README ___ [![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.en.md) [![ru](https://img.shields.io/badge/lang-ru-green.svg)](https://github.com/bol-van/zapret/tree/master/docs/readme.md) *** - [What is it for](#what-is-it-for) - [How it works](#how-it-works) - [How to put this into practice in the linux system](#how-to-put-this-into-practice-in-the-linux-system) - [When it will not work](#when-it-will-not-work) - [nfqws](#nfqws) - [DPI desync attack](#dpi-desync-attack) - [Fakes](#fakes) - [Fake mods](#fake-mods) - [TCP segmentation](#tcp-segmentation) - [Sequence numbers overlap](#sequence-numbers-overlap) - [IP_ID assignment](#ip_id-assignment) - [ipv6 specific modes](#ipv6-specific-modes) - [Original modding](#original-modding) - [Duplicates](#duplicates) - [Server reply reaction](#server-reply-reaction) - [SYNDATA mode](#syndata-mode) - [DPI desync combos](#dpi-desync-combos) - [IP cache](#ip-cache) - [CONNTRACK](#conntrack) - [Reassemble](#reassemble) - [UDP support](#udp-support) - [IP fragmentation](#ip-fragmentation) - [Multiple strategies](#multiple-strategies) - [WIFI filtering](#wifi-filtering) - [Virtual machines](#virtual-machines) - [IPTABLES for nfqws](#iptables-for-nfqws) - [NFTABLES for nfqws](#nftables-for-nfqws) - [Flow offloading](#flow-offloading) - [Server side fooling](#server-side-fooling) - [tpws](#tpws) - [TCP segmentation in tpws](#tcp-segmentation-in-tpws) - [TLSREC](#tlsrec) - [MSS](#mss) - [Other tamper options](#other-tamper-options) - [Supplementary options](#supplementary-options) - [Multiple strategies](#multiple-strategies-1) - [IPTABLES for tpws](#iptables-for-tpws) - [NFTABLES for tpws](#nftables-for-tpws) - [Ways to get a list of blocked IP](#ways-to-get-a-list-of-blocked-ip) - [Domain name filtering](#domain-name-filtering) - [**autohostlist** mode](#autohostlist-mode) - [Choosing parameters](#choosing-parameters) - [Screwing to the firewall control system or your launch system](#screwing-to-the-firewall-control-system-or-your-launch-system) - [Installation](#installation) - [Checking ISP](#checking-isp) - [desktop linux system](#desktop-linux-system) - [OpenWRT](#openwrt) - [Android](#android) - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos) - [Windows (WSL)](#windows-wsl) - [Other devices](#other-devices) - [Donations](#donations) *** ## What is it for A stand-alone (without 3rd party servers) DPI circumvention tool. May allow to bypass http(s) website blocking or speed shaping, resist signature tcp/udp protocol discovery. The project is mainly aimed at the Russian audience. Some features of the project are russian reality specific (such as getting list of sites blocked by Roskomnadzor), but most others are common. Mainly OpenWRT targeted but also supports traditional Linux, FreeBSD, OpenBSD, Windows, partially MacOS. Most features are also supported in Windows. ## How it works In the simplest case you are dealing with passive DPI. Passive DPI can read passthrough traffic, inject its own packets, but cannot drop packets. If the request is prohibited the passive DPI will inject its own RST packet and optionally http redirect packet. If fake packets from DPI are only sent to client, you can use iptables commands to drop them if you can write correct filter rules. This requires manual in-deep traffic analysis and tuning for specific ISP. This is how we bypass the consequences of a ban trigger. If the passive DPI sends an RST packet also to the server, there is nothing you can do about it. Your task is to prevent ban trigger from firing up. Iptables alone will not work. This project is aimed at preventing the ban rather than eliminating its consequences. To do that send what DPI does not expect and what breaks its algorithm of recognizing requests and blocking them. Some DPIs cannot recognize the http request if it is divided into TCP segments. For example, a request of the form `GET / HTTP / 1.1 \ r \ nHost: kinozal.tv ......` we send in 2 parts: first go `GET`, then `/ HTTP / 1.1 \ r \ nHost: kinozal.tv .....`. Other DPIs stumble when the `Host:` header is written in another case: for example, `host:`. Sometimes work adding extra space after the method: `GET /` => `GET /` or adding a dot at the end of the host name: `Host: kinozal.tv.` There is also more advanced magic for bypassing DPI at the packet level. ## How to put this into practice in the linux system In short, the options can be classified according to the following scheme: 1. Passive DPI not sending RST to the server. ISP tuned iptables commands can help. This option is out of the scope of the project. If you do not allow ban trigger to fire, then you won’t have to deal with its consequences. 2. Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy. 3. Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets. For options 2 and 3, **tpws** and **nfqws** programs are implemented, respectively. You need to run them with the necessary parameters and redirect certain traffic with iptables or nftables. ## When it will not work * If DNS server returns false responses. ISP can return false IP addresses or not return anything when 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. * If blocking is done by IP. * If a connection passes through a filter capable of reconstructing a TCP connection, and which follows 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. ## nfqws This program is a packet modifier and a NFQUEUE queue handler. For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see [bsd.en.md](./bsd.en.md)). nfqws takes the following parameters: ``` @ ; read file for options. must be the only argument. other options are ignored. --debug=0|1 --dry-run ; verify parameters and exit with code 0 if successful --version ; print version and exit --comment ; any text (ignored) --qnum= --daemon ; daemonize --pidfile= ; write pid to file --user= ; drop root privs --uid=uid[:gid1,gid2,...] ; drop root privs --bind-fix4 ; apply outgoing interface selection fix for generated ipv4 packets --bind-fix6 ; apply outgoing interface selection fix for generated ipv6 packets --wsize=[:] ; set window size. 0 = do not modify. OBSOLETE ! --wssize=[:] ; set window size for server. 0 = do not modify. default scale_factor = 0. --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 --wssize-forced-cutoff=0|1 ; 1(default)=auto cutoff wssize on known protocol --ctrack-timeouts=S:E:F[:U] ; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default 60:300:60:60 --ctrack-disable=[0|1] ; 1 or no argument disables conntrack --ipcache-lifetime= ; time in seconds to keep cached hop count and domain name (default 7200). 0 = no expiration --ipcache-hostname=[0|1] ; 1 or no argument enables ip->hostname caching --hostcase ; change Host: => host: --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" --hostnospace ; remove space after Host: and add it to User-Agent: to preserve packet size --domcase ; mix domain case : Host: TeSt.cOm --methodeol ; add '\n' before method and remove space after Host: --synack-split=[syn|synack|acksyn] ; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN --orig-ttl= ; set TTL for original packets --orig-ttl6= ; set ipv6 hop limit for original packets. by default ttl value is used --orig-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: +5:3-64. "0:0-0" or "-" disables autottl. --orig-autottl6=[[:[-]]|-] ; overrides --orig-autottl for ipv6 only --orig-tcp-flags-set= ; 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 --orig-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) --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 --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 --dup= ; duplicate original packets. send N dups before original. --dup-replace=[0|1] ; 1 or no argument means do not send original, only dups --dup-ttl= ; set TTL for dups --dup-ttl6= ; set ipv6 hop limit for dups. by default ttl value is used --dup-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: -1:3-64. "0:0-0" or "-" disables autottl. --dup-autottl6=[[:[-]]|-] ; overrides --dup-autottl for ipv6 only --dup-tcp-flags-set= ; 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 --dup-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) --dup-fooling=[,] ; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack hopbyhop hopbyhop2 --dup-ts-increment= ; ts fooling TSval signed increment for dup. default -600000 --dup-badseq-increment= ; badseq fooling seq signed increment for dup. default -10000 --dup-badack-increment= ; badseq fooling ackseq signed increment for dup. default -66000 --dup-ip-id=same|zero|seq|rnd ; ipv4 ip_id mode for dupped packets --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 --dup-cutoff=[n|d|s]N ; apply dup to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N --ip-id=zero|seq|seqgroup|rnd ; ipv4 ip_id assignment scheme --dpi-desync=[,][,] ; try to desync dpi state. modes : synack fake fakeknown rst rstack hopbyhop destopt ipfrag1 multisplit multidisorder fakedsplit hostfakesplit fakeddisorder ipfrag2 udplen tamper --dpi-desync-fwmark= ; override fwmark for desync packet. default = 0x40000000 (1073741824) --dpi-desync-ttl= ; set ttl for desync packet --dpi-desync-ttl6= ; set ipv6 hop limit for desync packet. by default ttl value is used. --dpi-desync-autottl=[[:[-]]|-] ; auto ttl mode for both ipv4 and ipv6. default: -1:3-20. "0:0-0" or "-" disables autottl. --dpi-desync-autottl6=[[:[-]]|-] ; overrides --dpi-desync-autottl for ipv6 only --dpi-desync-tcp-flags-set= ; 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 --dpi-desync-tcp-flags-unset= ; unset these tcp flags (flags &= ~value) --dpi-desync-fooling=[,] ; can use multiple comma separated values. modes : none md5sig ts badseq badsum datanoack hopbyhop hopbyhop2 --dpi-desync-repeats= ; send every desync packet N times --dpi-desync-skip-nosni=0|1 ; 1(default)=do not act on ClientHello without SNI (ESNI ?) --dpi-desync-split-pos=N|-N|marker+N|marker-N ; comma separated list of split positions ; markers: method,host,endhost,sld,endsld,midsld,sniext ; full list is only used by multisplit and multidisorder ; fakedsplit/fakeddisorder use first l7-protocol-compatible parameter if present, first abs value otherwise --dpi-desync-split-seqovl=N|-N|marker+N|marker-N ; use sequence overlap before first sent original split segment --dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; pattern for the fake part of overlap --dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; fake pattern for fakedsplit/fakeddisorder --dpi-desync-fakedsplit-mod=mod[,mod] ; mods can be none,altorder=0|1|2|3 + 0|8|16 --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. --dpi-desync-hostfakesplit-mod=mod[,mod] ; can be none, host=, altorder=0|1 --dpi-desync-ipfrag-pos-tcp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 32. --dpi-desync-ipfrag-pos-udp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 8. --dpi-desync-ts-increment= ; ts fooling TSval signed increment. default -600000 --dpi-desync-badseq-increment= ; badseq fooling seq signed increment. default -10000 --dpi-desync-badack-increment= ; badseq fooling ackseq signed increment. default -66000 --dpi-desync-any-protocol=0|1 ; 0(default)=desync only http and tls 1=desync any nonempty data packet --dpi-desync-fake-tcp-mod=mod[,mod] ; comma separated list of tcp fake mods. available mods : none,seq --dpi-desync-fake-http=[+ofs]@|0xHEX ; file containing fake http request --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; file containing fake TLS ClientHello (for https). '!' = standard fake --dpi-desync-fake-tls-mod=mod[,mod] ; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,dupsid,padencap --dpi-desync-fake-unknown=[+ofs]@|0xHEX ; file containing unknown protocol fake payload --dpi-desync-fake-syndata=[+ofs]@|0xHEX ; file containing SYN data payload --dpi-desync-fake-quic=[+ofs]@|0xHEX ; file containing fake QUIC Initial --dpi-desync-fake-wireguard=[+ofs]@|0xHEX ; file containing fake wireguard handshake initiation --dpi-desync-fake-dht=[+ofs]@|0xHEX ; file containing fake DHT (d1..e) --dpi-desync-fake-discord=[+ofs]@|0xHEX ; file containing fake Discord voice connection initiation packet (IP Discovery) --dpi-desync-fake-stun=[+ofs]@|0xHEX ; file containing fake STUN message --dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX ; file containing unknown udp protocol fake payload --dpi-desync-udplen-increment= ; increase or decrease udp packet length by N bytes (default 2). negative values decrease length. --dpi-desync-udplen-pattern=[+ofs]@|0xHEX ; udp tail fill pattern --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 --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 --hostlist= ; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply if not prefixed with `^`, gzip supported, multiple hostlists allowed) --hostlist-domains= ; comma separated fixed domain list --hostlist-exclude= ; 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) --hostlist-exclude-domains= ; comma separated fixed domain list --hostlist-auto= ; detect DPI blocks and build hostlist automatically --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) --hostlist-auto-retrans-threshold= ; how many request retransmissions cause attempt to fail (default : 3) --hostlist-auto-debug= ; debug auto hostlist positives --new ; begin new strategy (new profile) --skip ; do not use this profile --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. --filter-tcp=[~]port1[-port2]|* ; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. comma separated list supported. --filter-udp=[~]port1[-port2]|* ; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list supported. --filter-l7= ; L6-L7 protocol filter. multiple comma separated values allowed. proto: http tls quic wireguard dht discord stun unknown --filter-ssid=ssid1[,ssid2,ssid3,...] ; per profile wifi SSID filter --ipset= ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) --ipset-ip= ; comma separated fixed subnet list --ipset-exclude= ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) --ipset-exclude-ip= ; comma separated fixed subnet list ``` Many parameters dealing with binary data support loading from hex string prefixed by "0x" or from a file. Filename can be "as is" or prefixed with "@". If there's "+number" prefix before "@" it means offset of data inside the file. Offset must be less that data size. ### DPI desync attack The idea is to take original message, modify it, add additional fake information in such a way that the server OS accepts original data only but DPI cannot recostruct original message or sees what it cannot identify as a prohibited request. There's a set of instruments to achieve that goal. It can be fake packets that reach DPI but do not reach server or get rejected by server, TCP segmentation or IP fragmentation. There're attacks based on TCP sequence numbers. Methods can be combined in many ways. ### Fakes Fakes 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. * **md5sig** does not work on all servers. It typically works only on Linux servers. MD5 tcp option requires additional space in TCP header and can cause MTU overflow during fakedsplit/fakeddisorder on low positions when multisegment query (TLS kyber) is transmitted. `nfqws` cannot redistribute data between original TCP segments. The error displayed is 'message too long'. * **badsum** doesn't work if your device is behind NAT which does not pass invalid packets. The most common Linux NAT router configuration does not pass them. Most home routers are Linux based. The default sysctl configuration `net.netfilter.nf_conntrack_checksum=1` causes conntrack to verify tcp and udp checksums and set INVALID state for packets with invalid checksum. Typically, iptables rules include a rule for dropping packets with INVALID state in the FORWARD chain. The combination of these factors does not allow badsum packets to pass through the router. In openwrt mentioned sysctl is set to 0 from the box, in other routers its often left in the default "1" state. For nfqws to work properly through the router set `net.netfilter.nf_conntrack_checksum=0` on the router. System never verifies checksums of locally generated packets so nfqws will always work on the router itself. 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. But usually ISPs pass badsum. Some adapters/switches/drivers enable hardware filtering of rx badsum not allowing it to pass to the OS. This behavior was observed on a Mediatek MT7621 based device. Tried to modify mediatek ethernet driver with no luck, likely hardware enforced limitation. However the device allowed to send badsum packets, problem only existed for passthrough traffic from clients. * **badseq** packets will be dropped by server, but DPI also can ignore them. default badseq increment is set to -10000 because some DPIs drop packets outside of the small tcp window. But this also can cause troubles when `--dpi-desync-any-protocol` is enabled. To be 100% sure fake packet cannot fit to server tcp window consider setting badseq increment to 0x80000000 * **TTL** looks like the best option, but it requires special tuning for each ISP. If DPI is further than local ISP websites you can cut access to them. Manual IP exclude list is required. Its possible to use md5sig with ttl. This way you cant hurt anything, but good chances it will help to open local ISP websites. If automatic solution cannot be found then use `zapret-hosts-user-exclude.txt`. Some router stock firmwares fix outgoing TTL. Without switching this option off TTL fooling will not work. * **hopbyhop** is ipv6 only. This fooling adds empty extension header `hop-by-hop options` or two headers in case of `hopbyhop2`. Packets with two hop-by-hop headers violate RFC and discarded by all operating systems. All OS accept packets with one hop-by-hop header. Some ISPs/operators drop ipv6 packets with hop-by-hop options. Fakes will not be processed by the server either because ISP drops them or because there are two same headers. DPIs may still anaylize packets with one or two hop-by-hop headers. * **datanoack** sends tcp fakes without ACK flag. Servers do not accept this but DPI may accept. This mode may break NAT and may not work with iptables if masquerade is used, even from the router itself. Works with nftables properly. Likely requires external IP address (some ISPs pass these packets through their NAT). * Manipulate **tcp flags** with `--dpi-desync-tcp-flags-set` and `--dpi-desync-tcp-flags-unset`. Invalid tcp flags combination may cause server to drop the packet but DPI can accept it. For example, set SYN in fakes. This may not work with all servers. `datanoack` can be replaced to `--dpi-desync-tcp-flags-unset=ACK`. * **ts** adds to TSval ts increment value (-600000 by default). Servers discard packets with TSval in some range. Practical tests suggest increment between -100 and -0x80000000. Timestamps are generated by client OS. In linux timestamps are enabled by default. In windows by default timestamps are disabled. They can be enabled with this command : `netsh interface tcp set global timestamps=enabled` . ts fooling requires that timestamps are enabled. They must be enabled on every client OS. TSecr is left unmodified. * **autottl** tries to automatically guess hop count to the server and compute TTL by adding some delta value that can be positive or negative. Positive deltas must be preceeded by unary `+` sign. Deltas without any unary sign are treated negative for old versions compatibility reasons. This tech relies on well known TTL default values used by OS : 64,128,255. nfqws needs first incoming packet to see it's TTL. You must redirect it too. If resulting value TTL is outside the range (min,max) then its normalized to min or max. If delta is negative and TTL is longer than guessed hop count or delta is positive and TTL is shorter than guessed hop count then autottl fails and falls back to the fixed value. This can help if multiple DPIs exists on backbone channels, not just near the ISP. Can fail if inbound and outbound paths are not symmetric. `--dpi-desync-fooling` takes multiple comma separated values. Multiple parameters `--dpi-desync-fake-???` are supported except for the `--dpi-desync-fake-syndata`. Fakes are sent in the specified order. `--dpi-desync-repeats` resends each fake. Resulting order would be : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....` ### FAKE mods By default all tcp fakes are sent with the same sequence as original packet. This 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. **nfqws** has built-in TLS fake. It can be customized with `--dpi-desync-fake-tls` option. Customized fake data can be anything - valid TLS Client Hello or arbitrary data. It's possible to use TLS Client Hello with any fingerprint and any SNI. **nfqws** can do some modifications of valid TLS Client Hello fakes in runtime with `--dpi-desync-fake-tls-mod` option. * `none`. Do not do any mods. * `rnd`. Randomize `random` and `session id` fields. Applied on every request. * `rndsni`. Randomize SNI. If SNI >=7 symbols random SLD is applied with known TLD. Otherwise filled with random symbols. Applied only once at startup. * `dupsid`. Copy `session ID` from original TLS Client Hello. Takes precedence over `rnd`. Applied on every request. * `sni=`. Set specified SNI value. Changes TLS fake length, fixes lengths in TLS structure. Applied once at startup before `rndsni`. * `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. By default if custom fake is not defined `rnd,rndsni,dupsid` mods are applied. If defined - `none`. This behaviour is compatible with previous versions with addition of `dupsid`. If multiple TLS fakes are present each one takes the last mod. If a mod is specified after fake it replaces previous mod. This way it's possible to use different mods for every TLS fake. If a mod is set to non-TLS fake it causes error. Use `--dpi-desync-fake-tls-mod=none'. Example : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none' ### TCP segmentation * `multisplit`. split request at specified in `--dpi-desync-split-pos` positions * `multidisorder`. same as `multisplit` but send in reverse order * `fakedsplit`. sequental one position split with fake mix * `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 * `hostfakesplit` (altorder=1). fake host part of the request : before host, random fake host, after host, real host (optionally split this part) * `fakedsplit`. reverse one position split with fake mix `--dpi-desync-fakedsplit-mod=altorder=N` specifies number which influence to the presence of individual fakes in `fakedsplit`/`fakeddisorder`. `fakedsplit` TCP segments of multi-packet messages with split pos : * `altorder=0`. fake 1st segment, 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment * `altorder=1`. 1st segment, fake 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment * `altorder=2`. 1st segment, fake 2nd segment, 2nd segment, fake 2nd segment * `altorder=3`. 1st segment, fake 2nd segment, 2nd segment `fakeddisorder` TCP segments of multi-packet messages with split pos : * `altorder=0`. fake 2nd segment, 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment * `altorder=1`. 2nd segment, fake 2nd segment, fake 1st segment, 1st segment, fake 1st segment * `altorder=2`. 2nd segment, fake 1st segment, 1st segment, fake 1st segment * `altorder=3`. 1st segment, fake 1st segment, 1st segment `fakedsplit`/`fakeddisorder` TCP segments of multi-packet messages without split pos : * `altorder=0`. fake, original, fake * `altorder=8`. original, fake * `altorder=16`. original Resulting `altorder=N` is the sum of two `altorder` parts mentioned above. `--dpi-desync-fakedsplit-pattern` defines data payload of fakes in `fakedsplit`/`fakeddisorder`. By default pattern is simple `0x00`. Offset of split part + offset of current packet in multi-packet message define offset in the pattern. Positions are defined by markers. * **Absolute positive marker** - numeric offset inside one packet or group of packets starting from the start * **Absolute negative marker** - numeric offset inside one packet or group of packets starting from the next byte after the end * **Relative marker** - positive or negative offset relative to a logical position within a packet or group of packets Relative positions : * **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. * **host** - hostname start in a known protocol (http, TLS) * **endhost** - the byte next to the last hostname's byte * **sld** - second level domain start in the hostname * **endsld** - the byte next to the last SLD byte * **midsld** - middle of SLD * **sniext** - start of the data field in the SNI TLS extension. Any extension has 2-byte type and length fields followed by data field. Marker list example : `100,midsld,sniext+1,endhost-2,-10`. When 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. In `multisplit`or `multidisorder` case split is cancelled if no position remained. `fakedsplit` и `fakeddisorder` use only one split position. It's searched from the `--dpi-desync-split-pos` list by a special alorightm. First relative markers are searched. If no suitable found absolute markers are searched. If nothing found position 1 is used. For example, `--dpi-desync-split-pos=method+2,midsld,5` means `method+2` for http, `midsld` for TLS and 5 for others. `--dpi-desync-fakedsplit-mod=altorder=N` switches `fakedsplit` to alternate segment ordering. `hostfakesplit` only fakes hostname part of the request making it hard to destinguish between real and fake host names. It works for tcp protocols with host : TLS and HTTP. Real hostname can be additionally split using `--dpi-desync-hostfakesplit-midhost` marker. For example, `--dpi-desync-hostfakesplit-midhost=midsld`. Position must be within host range or split won't happen. Multi-packet queries are supported if hostname part is not already split. If it is fooling is cancelled. By 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. It's possible to set fake host template : `--dpi-desync-hostfakesplit-mod=host=`. Template 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". If original hostname size is less than template size it will be cut : "habr.com" -> "ogle.com". If original hostname size is larger than template size by one, dot will be appended to the left : "www.xxx.com" => ".google.com".. That's why it's a good idea to use short hostnames in template : "ya.ru", "vk.com", "x.com". `--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. ### Sequence numbers overlap `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). In `split` mode this creates partially in-window packet. OS receives only in-window part. In `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. All unix OS except Solaris preserve last received data. This is not the case for Windows servers and `disorder` with `seqovl` will not work. Disorder requires `seqovl` to be less than split position. Otherwise `seqovl` is not possible and will be cancelled. Method allows to avoid separate fakes. Fakes and real data are mixed. ### IP_ID assignment Some 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. Sequental 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. `ip-id` parameter sets ip_id assignment scheme for a desync profile : * `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. * `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. * `rnd` : assign random ip_id * `zero` : always set zero. Linux and BSD will send zero, Windows will replace zero with it's own counter. ipv6 header lacks ip_id field, `ip-id` parameter ignored for ipv6. ### ipv6 specific modes `hopbyhop`, `destopt` and `ipfrag1` desync modes (they're not the same as `hopbyhop` fooling !) are ipv6 only. One `hop-by-hop`, `destination options` or `fragment` header is added to all desynced packets. Extra header increases packet size and can't be applied to the maximum size packets. If it's not possible to send modified packet original one will be sent. The idea here is that DPI sees 0 in the next header field of the main ipv6 header and does not walk through the extension header chain until transport header is found. `hopbyhop`, `destopt`, `ipfrag1` modes can be used with any second phase mode except `ipfrag1+ipfrag2`. For example, `hopbyhop,multisplit` means split original tcp packet into several pieces and add hop-by-hop header to each. With `hopbyhop,ipfrag2` header sequence will be : `ipv6,hop-by-hop,fragment,tcp/udp`. `ipfrag1` mode may not always work without special preparations. See "IP Fragmentation" notices. ### Original modding Parameters `--orig-ttl` and `--orig-ttl6` allow to set TTL on original packets. All further packet manipulations, e.g. segmentation, take modded original as data source and inherit modded TTL. `--orig-autottl` and `--orig-autottl6` work the same way as `dpi-desync-autottl`, but on original packets. Delta should have unary `+` sign to produce TTL longer than guessed hop count. Otherwise nothing will reach the server. Example : `--orig-autottl=+5:3-64`. `--orig-mod-start` and `--orig-mod-cutoff` specify start and end conditions for original modding. The work the same way as `--dpi-desync-start` and `--dpi-desync-cutoff`. This function can be useful when DPI hunts for fakes and blocks suspicious connections. DPI can compute TTL difference between packets and fire block trigger if it exceedes some threshold. ### Duplicates Duplicates are copies of original packets which are sent before them. Duplicates are enabled by `--dup=N`, where N is dup count. `--dup-replace` disables sending of original. Dups are sent only when original would also be sent without reconstruction. For example, if TCP segmentation happens, original is actually dropped and is being replaced by artificially constructed new packets. Dups are not sent in this case. All dup fooling modes are available : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`. You decide whether these packets need to reach the server and in what form, according to the intended strategy. `--dup-autottl` and `--dup-autottl6` work the same way as `dpi-desync-autottl`. Delta can be preceeded by unary `+` or `-` sign. Example : `--dup-autottl=-2:3-64`. `--dup-start` and `--dup-cutoff` specify start and end conditions for dupping. The work the same way as `--dpi-desync-start` and `--dpi-desync-cutoff`. This function can help if DPI compares some characteristics of fake and original packets and block connection if they differ some way. Fooled duplicates can convince DPI that the whole session has an anomaly. For example, all connection is protected by MD5 signature, not individual packets. ### Server reply reaction There 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. In the disorder variant, a selective acknowledgement (SACK) usually arrives first, then a full ACK. If, instead of ACK or SACK, there is an RST packet with minimal delay, DPI cuts you off at the request stage. If 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. If it does not stop monitoring and persistently checks the ServerHello, `--wssize` parameter may help (see [CONNTRACK](#conntrack)). Otherwise it is hardly possible to overcome this without the help of the server. The best solution is to enable TLS 1.3 support on the server. TLS 1.3 sends the server certificate in encrypted form. This is recommendation to all admins of blocked sites. Enable TLS 1.3. You will give more opportunities to overcome DPI. ### SYNDATA mode Normally 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. Original connections with TFO are not touched because otherwise they would be definitely broken. Without extra parameter payload is 16 zero bytes. ### DPI desync combos `--dpi-desync` takes up to 3 comma separated modes. * 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. * In the 1st phase fakes are sent before original data : `fake`, `rst`, `rstack`. * In the 2nd phase original data is sent in a modified way (for example `fakedsplit` or `ipfrag2`). Modes must be specified in phase ascending order. ### IP cache `ipcache` is the structure in the process memory that stores some information by IP address and interface name key. This information can be used as missing data. Currently it's used in the following cases : 1. 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. 2. 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. This tech is experimental. There's no one-to-one correspondence between IP and domain name. Multiple domains can resolve to the same IP. If 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. Be prepared for unexpected results that can be explained only by reading debug logs. SIGUSR2 forces process to output it's ipcache to stdout. ### CONNTRACK nfqws is equipped with minimalistic connection tracking system (conntrack) It's used if some specific DPI circumvention methods are involved and helps to reassemble multi-packet requests. Conntrack can track connection phase : SYN,ESTABLISHED,FIN , packet counts in both directions , sequence numbers. It can be fed with unidirectional or bidirectional packets. A SYN or SYN,ACK packet creates an entry in the conntrack table. That's why iptables redirection must start with the first packet although can be cut later using connbytes filter. First seen UDP packet creates UDP stream. It defines the stream direction. Then all packets with the same `src_ip,src_port,dst_ip,dst_port` are considered to belong to the same UDP stream. UDP stream exists till inactivity timeout. A connection is deleted from the table as soon as it's no more required to satisfy nfqws needs or when a timeout happens. There're 3 timeouts for each connection state. They can be changed in `--ctrack-timeouts` parameter. `--wssize` changes tcp window size for the server to force it to send split replies. In order for this to affect all server operating systems, it is necessary to change the window size in each outgoing packet before sending the message, the answer to which must be split (for example, TLS ClientHello). That's why conntrack is required to know when to stop applying low window size. If you do not stop and set the low wssize all the time, the speed will drop catastrophically. Linux can overcome this using connbytes filter but other OS may not include similar filter. In http(s) case wssize stops after the first http request or TLS ClientHello unless `--wssize-forced-cutoff=0` is specified. If you deal with a non-http(s) protocol you need `--wssize-cutoff`. It sets the threshold where wssize stops. Threshold can be prefixed with 'n' (packet number starting from 1), 'd' (data packet number starting from 1), 's' (relative sequence number - sent by client bytes + 1). If a http request or TLS ClientHello packet is detected wssize stops immediately ignoring wssize-cutoff option. This action is called "forced wssize cutoff" and can disabled using `--wssize-forced-cutoff=0`. If your protocol is prone to long inactivity, you should increase ESTABLISHED phase timeout using `--ctrack-timeouts`. Default timeout is low - only 5 mins. Don't forget that nfqws feeds with redirected packets. If you have limited redirection with connbytes ESTABLISHED entries can remain in the table until dropped by timeout. To diagnose conntrack state send SIGUSR1 signal to nfqws : `killall -SIGUSR1 nfqws`. nfqws will dump current conntrack table to stdout. Typically, in a SYN packet, client sends TCP extension **scaling factor** in addition to window size. scaling factor is the power of two by which the window size is multiplied : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... The wssize parameter specifies the scaling factor after a colon. Scaling factor can only decrease, increase is blocked to prevent the server from exceeding client's window size. To force a TLS server to fragment ServerHello message to avoid hostname detection on DPI use `--wssize=1:6` The main rule is to set scale_factor as much as possible so that after recovery the final window size becomes the possible maximum. If you set `scale_factor` 64:0, it will be very slow. On the other hand, the server response must not be large enough for the DPI to find what it is looking for. `--wssize` is not applied in desync profiles with hostlist filter because it works since the connection initiation when it's not yet possible to extract the host name. But it works with auto hostlist profiles. `--wssize` may slow down sites and/or increase response time. It's desired to use another methods if possible. `--dpi-desync-cutoff` allows you to set the threshold at which it stops applying dpi-desync. Can be prefixed with 'n', 'd', 's' symbol the same way as `--wssize-cutoff`. Useful with `--dpi-desync-any-protocol=1`. If the connection falls out of the conntrack and `--dpi-desync-cutoff` is set, `dpi desync` will not be applied. Set conntrack timeouts appropriately. ### Reassemble nfqws supports reassemble of TLS and QUIC ClientHello. They can consist of multiple packets if kyber crypto is used (default starting from chromium 124). Chromium randomizes TLS fingerprint. SNI can be in any packet or in-between. Stateful DPIs usually reassemble all packets in the request then apply block decision. If nfqws receives a partial ClientHello it begins reassemble session. Packets are delayed until it's finished. Then they go through desync using fully reassembled message. On any error reassemble is cancelled and all delayed packets are sent immediately without desync. There 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. ### UDP support UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level. Only desync modes `fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1`,`ipfrag2`,`udplen` and `tamper` are applicable. `fake`,`fakeknown`,`hopbyhop`,`destopt`,`ipfrag1` are 1st phase modes, others - 2nd phase. As 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. `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. This option can resist DPIs that track outgoing UDP packet sizes. Requires that application protocol does not depend on udp payload size. QUIC initial packets are recognized. Decryption and hostname extraction is supported so `--hostlist` parameter will work. Wireguard handshake initiation, DHT, STUN and [Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery) packets are also recognized. For other protocols desync use `--dpi-desync-any-protocol`. Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`. Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently. By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`. ### IP fragmentation Modern network can be very hostile to IP fragmentation. Fragmented packets are often not delivered or refragmented/reassembled on the way. Frag position is set independently for tcp and udp. By default 24 and 8, must be multiple of 8. Offset starts from the transport header. tcp fragments are almost always filtered. It's absolutely not suitable for arbitrary websites. udp fragments have good chances to survive but not everywhere. It's good to assume success rate on QUIC between 50..75%. Likely more with your VPS. Sometimes filtered by DDoS protection. There are important nuances when working with fragments in Linux. ipv4 : Linux allows to send ipv4 fragments but standard firewall rules in OUTPUT chain can cause raw send to fail. ipv6 : There's no way for an application to reliably send fragments without defragmentation by conntrack. Sometimes it works, sometimes system defragments packets. Looks like kernels <4.16 have no simple way to solve this problem. Unloading of `nf_conntrack` module and its dependency `nf_defrag_ipv6` helps but this severely impacts functionality. Kernels 4.16+ exclude from defragmentation untracked packets. See `blockcheck.sh` code for example. Sometimes it's required to load `ip6table_raw` kernel module with parameter `raw_before_defrag=1`. In openwrt module parameters are specified after module names separated by space in files located in `/etc/modules.d`. In traditional linux check whether `iptables-legacy` or `iptables-nft` is used. If legacy create the file `/etc/modprobe.d/ip6table_raw.conf` with the following content : ``` options ip6table_raw raw_before_defrag=1 ``` In some linux distros its possible to change current ip6tables using this command: `update-alternatives --config ip6tables`. If you want to stay with `nftables-nft` you need to patch and recompile your version. In `nft.c` find : ``` { .name = "PREROUTING", .type = "filter", .prio = -300, /* NF_IP_PRI_RAW */ .hook = NF_INET_PRE_ROUTING, }, { .name = "OUTPUT", .type = "filter", .prio = -300, /* NF_IP_PRI_RAW */ .hook = NF_INET_LOCAL_OUT, }, ``` and replace -300 to -450. It must be done manually, `blockcheck.sh` cannot auto fix this for you. Or just move to `nftables`. You can create hooks with any priority there. Looks like there's no way to do ipfrag using iptables for forwarded traffic if NAT is present. `MASQUERADE` is terminating target, after it `NFQUEUE` does not work. nfqws sees packets with internal network source address. If fragmented NAT does not process them. This results in attempt to send packets to internet with internal IP address. You need to use nftables instead with hook priority 101 or higher. ### Multiple strategies **nfqws** can apply different strategies to different requests. It's done with multiple desync profiles. Profiles are delimited by the `--new` parameter. First profile is created automatically and does not require `--new`. Each profile has a filter. By default it's empty and profile matches any packet. Filter can have hard parameters : ip version, ipset and tcp/udp port range. Hard parameters are always identified unambiguously even on zero-phase when hostname and L7 are unknown yet. Hostlists can also act as a filter. They can be combined with hard parameters. When a packet comes profiles are matched from the first to the last until first filter condition match. Hard filter is matched first. If it does not match verification goes to the next profile. If a profile matches hard filter , L7 filter and has autohostlist it's selected immediately. If a profile matches hard filter , L7 filter and has normal hostlist(s) and hostname is unknown yet verification goes to the next profile. Otherwise profile hostlist(s) are checked for the hostname. If it matches profile is selected. Otherwise verification goes to the next profile. It's possible that before knowing L7 and hostname connection is served by one profile and after this information is revealed it's switched to another profile. If you use 0-phase desync methods think carefully what can happen during strategy switch. Use `--debug` logging to understand better what **nfqws** does. Profiles are numbered from 1 to N. There's last empty profile in the chain numbered 0. It's used when no filter matched. IMPORTANT : multiple strategies exist only for the case when it's not possible to combine all to one strategy. Copy-pasting blockcheck results of different websites to multiple strategies lead to the mess. This way you may never unblock all resources and only confuse yourself. IMPORTANT : user-mode ipset implementation was not designed as a kernel version replacement. Kernel version is much more effective. It's for the systems that lack ipset support : Windows and Linux without nftables and ipset kernel modules (Android, for example). ### WIFI filtering Wifi interface name is not related to connected SSID. It's possible to connect interface to different SSIDs. They may require different strategies. How to solve this problem ? You can run and stop nfqws instances manually. But you can also automate this. Windows version `winws` has global filter `--ssid-filter`. It connects or disconnects `winws` depending on connected SSIDs. Routing is not take into account. This approach is possible because windivert can have multiple handlers with intersecting filter. If SSID changes one `winws` connects and others disconnect. `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. One must connect when others have already disconnected. Instead, `nfqws` has per-profile `--filter-ssid` parameter. Like `--ssid-filter` it takes comma separated SSID list. `nfqws` maintains ifname->SSID list which is updated not faster than once a second. When a packet comes incoming or outgoing interface name is matched to the SSID and then used in profile selection algorithm. SSID info is taken the same way as `iw dev info` does (nl80211). Unfortunately it's broken since kernel 5.19 and still unfixed in 6.14. In the latter case `iwgetid` way is used (wireless extensions). Wireless extensions are deprecated. Some kernels can be built without wext support. Before using `--filter-ssid` check that any of the mentioned commands can return SSID. ### Virtual machines Most of nfqws packet magic does not work from VMs powered by virtualbox and vmware when network is NATed. Hypervisor forcibly changes TTL and does not forward fake packets. Set up bridge networking. ### IPTABLES for nfqws > [!CAUTION] > 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. This is the common way to redirect some traffic to nfqws : ``` iptables -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 ``` This 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. ``` iptables -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 iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass ``` mark bit is used to prevent loops. **nfqws** sets this mark in each injected packet. It's also necessary for correct injected packet ordering and for deadlock prevention. `autottl` requires incoming `SYN,ACK` packet or first reply packet (it's usually the same). `autohostlist` needs incoming `RST` and `http redirect`. It's possible to build tcp flags and u32 based filter but connbytes is easier. ` iptables -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 ` For QUIC : ``` iptables -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 ``` 6 packets cover possible retransmissions of quic initials and feed `autohostlist` mode. ### NFTABLES for nfqws This is the start configuration : ``` IFACE_WAN=wan nft create table inet ztest nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" nft 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 nft 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 # auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass ``` To 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. ``` IFACE_WAN=wan nft create table inet ztest nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}" nft 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 nft 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 nft add chain inet ztest predefrag "{type filter hook output priority -401;}" nft add rule inet ztest predefrag "mark & 0x40000000 != 0x00000000 notrack" ``` Delete nftable : ``` nft delete table inet ztest ``` ### Flow offloading If 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. Newer linux kernels have software flow offloading (SFO). The story is the same with SFO. In `iptables` flow offloading is controlled by openwrt proprietary extension `FLOWOFFLOAD`. Newer `nftables` implement built-in offloading support. Flow offloading does not interfere with **tpws** and `OUTPUT` traffic. It only breaks nfqws that fools `FORWARD` traffic. ### Server side fooling It's also possible. nfqws is intended for client side attacks. That's why it recognizes direct and reply traffic based on role in connection establishement. If it sees SYN then source IP is client IP. If it sees SYN,ACK then source ip is server IP. For UDP client address is considered as source IP of the first seen packet of src_ip,src_port,dst_ip,dst_port tuple. This does not work correctly on the server side. Client traffic is reply traffic, server traffic is direct traffic. `--wsize` works in any case. It can be used on both client and server. Other techs work only if nfqws treats traffic as direct traffic. To apply them to server originated traffic disable conntrack by `--ctrack-disable` parameter. If a packet is not found in conntrack it's treated as direct and techs like `multidisorder` will be applied. Most of the protocols will not be recognized because protocol recognition system only reacts to client packets. To make things working use `--dpi-desync-any-protocol` with connbytes or packet payload limiter. start/cutoff are unavailable because they are conntrack based. `--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. Client sends SYN,ACK in reply which usually only server does. This makes some DPI's to treat connection establishement roles wrong. They stop to block. See [split handshake](https://nmap.org/misc/split-handshake.pdf). On server side traffic should be redirected to nfqws using source port numbers and original connbytes direction. ## tpws tpws is transparent proxy. ``` @ ; read file for options. must be the only argument. other options are ignored. --debug=0|1|2|syslog|@ ; 1 and 2 means log to console and set debug level. for other targets use --debug-level. --debug-level=0|1|2 ; specify debug level for syslog and @ --dry-run ; verify parameters and exit with code 0 if successful --version ; print version and exit --bind-addr=| ; for v6 link locals append %interface_name : fe80::1%br-lan --bind-iface4= ; bind to the first ipv4 addr of interface --bind-iface6= ; bind to the first ipv6 addr of interface --bind-linklocal=no|unwanted|prefer|force ; no : bind only to global ipv6 ; unwanted (default) : prefer global address, then LL ; prefer : prefer LL, then global ; force : LL only --bind-wait-ifup= ; wait for interface to appear and up --bind-wait-ip= ; after ifup wait for ip address to appear up to N seconds --bind-wait-ip-linklocal= ; accept only link locals first N seconds then any --bind-wait-only ; wait for bind conditions satisfaction then exit. return code 0 if success. --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name --port= ; port number to listen on --socks ; implement socks4/5 proxy instead of transparent proxy --local-rcvbuf= ; SO_RCVBUF for local legs --local-sndbuf= ; SO_SNDBUF for local legs --remote-rcvbuf= ; SO_RCVBUF for remote legs --remote-sndbuf= ; SO_SNDBUF for remote legs --nosplice ; do not use splice to transfer data between sockets --skip-nodelay ; do not set TCP_NODELAY for outgoing connections. incompatible with split. --local-tcp-user-timeout= ; set tcp user timeout for local leg (default : 10, 0 = system default) --remote-tcp-user-timeout= ; set tcp user timeout for remote leg (default : 20, 0 = system default) --fix-seg= ; recover failed TCP segmentation at the cost of slowdown. wait up to N msec. --ipcache-lifetime= ; time in seconds to keep cached domain name (default 7200). 0 = no expiration --ipcache-hostname=[0|1] ; 1 or no argument enables ip->hostname caching --no-resolve ; disable socks5 remote dns --resolver-threads= ; number of resolver worker threads --maxconn= ; max number of local legs --maxfiles= ; max file descriptors (setrlimit). min requirement is (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode. ; its worth to make a reserve with 1.5 multiplier. by default maxfiles is (X*connections)*1.5+16 --max-orphan-time= ; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds --new ; begin new strategy (new profile) --skip ; do not use this profile --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. --filter-tcp=[~]port1[-port2]|* ; TCP port filter. ~ means negation. comma separated list supported. --filter-l7=[http|tls|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed. --ipset= ; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) --ipset-ip= ; comma separated fixed subnet list --ipset-exclude= ; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed) --ipset-exclude-ip= ; comma separated fixed subnet list --hostlist= ; only act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed) --hostlist-domains= ; comma separated fixed domain list --hostlist-exclude= ; do not act on hosts in the list (one host per line, subdomains auto apply if not prefixed with '^', gzip supported, multiple hostlists allowed) --hostlist-exclude-domains= ; comma separated fixed domain list --hostlist-auto= ; detect DPI blocks and build hostlist automatically --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) --hostlist-auto-debug= ; debug auto hostlist positives --split-pos=N|-N|marker+N|marker-N ; comma separated list of split positions ; markers: method,host,endhost,sld,endsld,midsld,sniext --split-any-protocol ; split not only http and TLS --disorder[=http|tls] ; when splitting simulate sending second fragment first --oob[=http|tls] ; when splitting send out of band byte. default is HEX 0x00. --oob-data=|0xHEX ; override default 0x00 OOB byte. --hostcase ; change Host: => host: --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" --hostdot ; add "." after Host: name --hosttab ; add tab after Host: name --hostnospace ; remove space after Host: --hostpad= ; add dummy padding headers before Host: --domcase ; mix domain case after Host: like this : TeSt.cOm --methodspace ; add extra space after method --methodeol ; add end-of-line before method --unixeol ; replace 0D0A to 0A --tlsrec=N|-N|marker+N|marker-N ; make 2 TLS records. split at specified logical part. don't split if SNI is not present. --tlsrec-pos= ; make 2 TLS records. split at specified pos --mss= ; set client MSS. forces server to split messages but significantly decreases speed ! --tamper-start=[n] ; start tampering only from specified outbound stream position. byte pos or block number ('n'). default is 0. --tamper-cutoff=[n] ; do not tamper anymore after specified outbound stream position. byte pos or block number ('n'). default is unlimited. --daemon ; daemonize --pidfile= ; write pid to file --user= ; drop root privs --uid=uid[:gid1,gid2,...] ; drop root privs ``` ### TCP segmentation in tpws **tpws** like **nfqws** supports multiple splits. Split [markers](#tcp-segmentation) are specified in `--split-pos` parameter. On 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. In 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. Since 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. If 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. **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. `--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. `--oob` sends one out-of-band byte in the end of the first split segment. `--oob` and `--disorder` can be combined only in Linux. Others OS do not handle this correctly. ### TLSREC `--tlsrec` allow to split TLS ClientHello into 2 TLS records in one TCP segment. It accepts single pos marker. `--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. ### MSS `--mss` sets TCP_MAXSEG socket option. Client sets this value in MSS TCP option in the SYN packet. Server 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. If it's enough to split TLS 1.2 ServerHello, it may fool DPI that checks certificate domain name. This 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. `--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. ### Other tamper options `--hostpad=` adds padding headers before `Host:` with specified number of bytes. If `` is too large headers are split by 2K. Padding more that 64K is not supported and not accepted by http servers. It's useful against stateful DPI's that reassemble only limited amount of data. Increase padding `` until website works. If minimum working `` is close to MTU then it's likely DPI is not reassembling packets. Then it's better to use regular split instead of `--hostpad`. ### Supplementary options **tpws** can bind to multiple interfaces and IP addresses (up to 32). Port number is always the same. Parameters `--bind-iface*` and `--bind-addr` create new bind. Other parameters `--bind-*` are related to the last bind. link local ipv6 (`fe80::/8`) mode selection : ``` --bind-iface6 --bind-linklocal=no : first selects private address fc00::/7, then global address --bind-iface6 --bind-linklocal=unwanted : first selects private address fc00::/7, then global address, then LL --bind-iface6 --bind-linklocal=prefer : first selects LL, then private address fc00::/7, then global address --bind-iface6 --bind-linklocal=force : select only LL ``` To bind to all ipv4 specify `--bind-addr "0.0.0.0"`, all ipv6 - `::`. `--bind-addr=""` - mean bind to all ipv4 and ipv6. If no binds are specified default bind to all ipv4 and ipv6 addresses is created. To bind to a specific link local address do : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name` The `--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 or not configured. In 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. In 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." To bind to a specific ip when its interface may not be configured yet do : `--bind-addr=192.168.5.3 --bind-wait-ip=20` It's possible to bind to any nonexistent address in transparent mode but in socks mode address must exist. In socks proxy mode no additional system privileges are required. Connections to local IPs of the system where **tpws** runs are prohibited. tpws supports remote dns resolving (curl : `--socks5-hostname` firefox : `socks_remote_dns=true`) , but does it in blocking mode. **tpws** uses async sockets for all activities. Domain names are resolved in multi threaded pool. Resolving does not freeze other connections. But if there're too many requests resolving delays may increase. Number of resolver threads is choosen automatically proportinally to `--maxconn` and can be overriden using `--resolver-threads`. To disable hostname resolve use `--no-resolve` option. ### Multiple strategies **tpws** like **nfqws** supports multiple strategies. They work mostly like with **nfqws** with minimal differences. `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. This 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. Use `--mss` both in hostlist profile and profile without hostlist. Use `curl --socks5` and `curl --socks5-hostname` to issue two kinds of proxy queries. See `--debug` output to test your setup. ### IPTABLES for tpws Use the following rules to redirect TCP connections to 'tpws' : ``` iptables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 iptables -t nat -I PREROUTING -i -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 ``` First rule redirects outgoing from the same system traffic, second redirects passthrough traffic. DNAT to localhost works only in the **OUTPUT** chain and does not work in the **PREROUTING** chain without setting this sysctl : `sysctl -w net.ipv4.conf..route_localnet=1` It'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. This is how to open only single `127.0.0.127` address : ``` iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP ``` Owner filter is required to avoid redirection loops. **tpws** must be run with `--user tpws` parameter. ip6tables work almost the same with minor differences. ipv6 addresses should be enclosed in square brackets : ``` ip6tables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988 ``` There'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. ### NFTABLES for tpws Base nftables scheme : ``` IFACE_WAN=wan IFACE_LAN=br-lan sysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1 nft create table inet ztest nft create chain inet ztest localnet_protect nft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return nft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop nft create chain inet ztest input "{type filter hook input priority filter - 1;}" nft add rule inet ztest input iif != "lo" jump localnet_protect nft create chain inet ztest dnat_output "{type nat hook output priority dstnat;}" nft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 nft create chain inet ztest dnat_pre "{type nat hook prerouting priority dstnat;}" nft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 ``` Delete nftable : ``` nft delete table inet ztest ``` ## Ways to get a list of blocked IP nftables can't work with ipsets. Native nf sets require lots of RAM to load large ip lists with subnets and intervals. In case you're on a low RAM system and need large lists it may be required to fall back to iptables+ipset. 1. Enter the blocked domains to `ipset/zapret-hosts-user.txt` and run `ipset/get_user.sh` At the output, you get `ipset/zapret-ip-user.txt` with IP addresses. 2. `ipset/get_reestr_*.sh`. Russian specific 3. `ipset/get_antifilter_*.sh`. Russian specific 4. `ipset/get_config.sh`. This script calls what is written into the GETLIST variable from the config file. If the variable is not defined, then only lists for ipsets nozapret/nozapret6 are resolved. So, if you're not russian, the only way for you is to manually add blocked domains. Or write your own `ipset/get_iran_blocklist.sh` , if you know where to download this one. On routers, it is not recommended to call these scripts more than once in 2 days to minimize flash memory writes. `ipset/create_ipset.sh` executes forced ipset update. With `no-update` parameter `create_ipset.sh` creates ipset but populate it only if it was actually created. It's useful when multiple subsequent calls are possible to avoid wasting of cpu time redoing the same job. Ipset loading is resource consuming. Its a good idea to call create_ipset without `no-update` parameter only once a several days. Use it with `no-update` option in other cases. ipset scripts automatically call ip2net utility. ip2net helps to reduce ip list size by combining IPs to subnets. Also it cuts invalid IPs from the list. Stored lists are already processed by ip2net. They are error free and ready for loading. `create_ipset.sh` supports loading ip lists from gzip files. First it looks for the filename with the ".gz" extension, such as `zapret-ip.txt.gz`, if not found it falls back to the original name `zapret-ip.txt`. So your own get_iran_blockslist.sh can use "zz" function to produce gz. Study how other russian `get_XXX.sh` work. Gzipping helps saving a lot of precious flash space on embedded systems. User lists are not gzipped because they are not expected to be very large. You can add a list of domains to `ipset/zapret-hosts-user-ipban.txt`. Their ip addresses will be placed in a separate ipset "ipban". It can be used to route connections to transparent proxy "redsocks" or VPN. IPV6: if ipv6 is enabled, then additional txt's are created with the same name, but with a "6" at the end before the extension. `zapret-ip.txt` => `zapret-ip6.txt` The ipsets zapret6 and ipban6 are created. IP EXCLUSION SYSTEM. All scripts resolve `zapret-hosts-user-exclude.txt` file, creating `zapret-ip-exclude.txt` and `zapret-ip-exclude6.txt`. They are the source for ipsets nozapret/nozapret6. All rules created by init scripts are created with these ipsets in mind. The IPs placed in them are not involved in the process. zapret-hosts-user-exclude.txt can contain domains, ipv4 and ipv6 addresses or subnets. FreeBSD. `ipset/*.sh` scripts also work in FreeBSD. Instead of ipset they create ipfw lookup tables with the same names as in Linux. ipfw tables can store both ipv4 and ipv6 addresses and subnets. There's no 4 and 6 separation. LISTS_RELOAD config parameter defines a custom lists reloading command. Its useful on BSD systems with PF. LISTS_RELOAD=- disables reloading ip list backend. ## Domain name filtering An alternative to ipset is to use **tpws** or **nfqws** with a list(s) of domains. Both **tpws** and **nfqws** take any number of include (`--hostlist`) and exclude (`--hostlist-exclude`) domain lists. All lists of the same type are combined internally leaving only 2 lists : include and exclude. Exclude list is checked first. Fooling is cancelled if domain belongs to exclude list. If include list is present and domain does not belong to that list fooling is also cancelled. Empty list means absent list. Otherwise fooling goes on. Launch system looks for 2 include lists : `ipset/zapret-hosts-users.txt.gz` or `ipset/zapret-hosts-users.txt` `ipset/zapret-hosts.txt.gz` or `ipset/zapret-hosts.txt` and 1 exclude list `ipset/zapret-hosts-users-exclude.txt.gz` or `ipset/zapret-hosts-users-exclude.txt` If `MODE_FILTER=hostlist` all present lists are passed to **nfqws** or **tpws**. If all include lists are empty it works like no include lists exist at all. If you need "all except" mode you dont have to delete zapret-hosts-users.txt. Just make it empty. Subdomains auto apply. For example, "ru" in the list affects "\*.ru" . `^` prefix symbol disables subdomain match. **tpws** and **nfqws** automatically reload lists if their modification time or file size is changed. HUP signal forcibly reloads all lists. When filtering by domain name, daemons should run without filtering by ipset. When using large regulator lists estimate the amount of RAM on the router ! ## **autohostlist** mode This mode analyzes both client requests and server replies. If 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. Use exclude hostlist to prevent autohostlist triggering. If it did happen - delete the undesired record from the file. In case of nfqws it's required to redirect both incoming and outgoing traffic to the queue. It's strongly recommended to use connbytes filter or nfqws will process gigabytes of incoming traffic. For the same reason it's not recommended to use autohostlist mode in BSDs. BSDs do not support connbytes or similar mechanism. **nfqws** и **tpws** detect the folowing situations : 1) [nfqws] Multiple retransmissions of the first request inside a TCP session having host. 2) [nfqws,tpws] RST in response to the first request. 3) [nfqws,tpws] HTTP redirect in response to the first http request with 2nd level domain diferent from the original. 4) [tpws] Client closes connection after first request without having server reply (no reponse from server, timeout). To minimize false positives there's fail counter. If in specific time occurs more than specified number of fails the host is added to the list. Then DPI bypass strategy start to apply immediately. For the user autohostlist mode looks like this. When for the first time user visits a blocked website it sees block page, connection reset or browser hangs until timeout, then display a error. User presses multiple times F5 causing browser to retry attempts. After some retries a website opens and next time works as expected. With autohostlist mode it's possible to use bypass strategies that break lots of sites. If a site does not behave like blocked no fooling applies. Otherwise it's nothing to lose. However false positives still can occur in case target website is behaving abnormally (may be due to DDoS attack or server malfunction). If it happens bypass strategy may start to break the website. This situation can only be controlled manually. Remove undesired domain from the autohostlist file. Use exclude hostlist to prevent further auto additions. It's possible to use one auto hostlist with multiple processes. All processes check for file modification time. If a process modified autohostlist, all others will reread it automatically. All processes must run with the same uid. If zapret scripts are used then autohostlist is `ipset/zapret-hosts-auto.txt` and exlude list is `ipset/zapret-hosts-user-exclude.txt`. autohostlist mode includes hostlist mode. You can use `ipset/zapret-hosts-user.txt`. ## Choosing parameters The file `/opt/zapret/config` is used by various components of the system and contains basic settings. It needs to be viewed and edited if necessary. Which firewall type use on linux systems : `nftables` or `iptables`. On traditional systems `nftables` is selected by default if `nft` is installed. On openwrt by default `nftables` is selected on `firewall4` based systems. `FWTYPE=iptables` With `nftables` post-NAT scheme is used by default. It allows more DPI attacks on forwarded traffic. It's possible to use `iptables`-like pre-NAT scheme. **nfqws** will see client source IPs and display them in logs. `#POSTNAT=0` There'are 3 standard options configured separately and independently : `tpws-socks`, **tpws**, **nfqws**. They can be used alone or combined. Custom scripts in `init.d/{sysv,openwrt,macos}/custom.d` are always applied. `tpws-socks` requires daemon parameter configuration but does not require traffic interception. Other standard options require also traffic interception. Each standard option launches single daemon instance. Strategy differiences are managed using multi-profile scheme. Main rule for interception is "intercept required minumum". Everything else only wastes CPU resources and slows down connection. `--ipset` option is prohibited intentionally to disallow easy to use but ineffective user-mode filtering. Use kernel ipsets instead. It may require custom scripts. To use standard updatable hostlists from the `ipset` dir use `` placeholder. It's automatically replaced with hostlist parameters if `MODE_FILTER` variable enables hostlists and is removed otherwise. Standard hostlists are expected in final (fallback) strategies closing groups of filter parameters. Don't use `` in highly specialized profiles. Use your own filter or hostlist(s). `` marker uses standard autohostlist as usual hostlist thus disabling auto additions in this profile. If any other profile adds something this profile accepts the change automatically. Change loop prevention mark bit `DESYNC_MARK=0x40000000` Change postnat scheme mark bit `DESYNC_MARK_POSTNAT=0x20000000` If uncommented pass to zapret only packets marked with this bit `#FILTER_MARK=0x10000000` Bit must be set in your own rules. * iptables - in mangle PREROUTING and mangle OUTPUT before zapret rules (iptables -I _after_ zapret rules application). * nftables - in output and prerouting hooks with priority -102 or lower. Mark criterias can be any. For example, source IP or source interface name. **tpws** socks proxy mode switch `TPWS_SOCKS_ENABLE=0` Listening tcp port for **tpws** proxy mode. `TPPORT_SOCKS=987` **tpws** socks mode parameters ``` TPWS_SOCKS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " " ``` **tpws** transparent mode switch `TPWS_ENABLE=0` **tpws** transparent mode target ports `TPWS_PORTS=80,443` **tpws** transparent mode parameters ``` TPWS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " " ``` **nfqws** enable switch `NFQWS_ENABLE=0` **nfqws** port targets for `connbytes`-limited interception. `connbytes` allows to intercept only starting packets from connections. This is more effective kernel-mode alternative to `nfqws --dpi-desync-cutoff=nX`. ``` NFQWS_PORTS_TCP=80,443 NFQWS_PORTS_UDP=443 ``` How many starting packets should be intercepted to nfqws in each direction ``` NFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_TCP_PKT_IN=3 NFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_UDP_PKT_IN=0 ``` There's kind of traffic that requires interception of entire outgoing stream. Typically it's support for plain http keepalives and stateless DPI. This mode of interception significantly increases CPU utilization. Use with care and only if required. Here you specify port numbers for unlimited interception. It's advised also to remove these ports from `connbytes`-limited interception list. ``` #NFQWS_PORTS_TCP_KEEPALIVE=80 #NFQWS_PORTS_UDP_KEEPALIVE= ``` **nfqws** parameters ``` NFQWS_OPT=" --filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 " ``` Host filtering mode : ``` none - apply fooling to all hosts ipset - limit fooling to hosts from ipset zapret/zapret6 hostlist - limit fooling to hosts from hostlist autohostlist - hostlist mode + blocks auto detection ``` `MODE_FILTER=none` flow offloading control (if supported) ``` donttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading none : always disable system flow offloading setting and dont configure selective flow offloading software : always disable system flow offloading setting and configure selective software flow offloading hardware : always disable system flow offloading setting and configure selective hardware flow offloading ``` `FLOWOFFLOAD=donttouch` The GETLIST parameter tells the install_easy.sh installer which script to call to update the list of blocked ip or hosts. Its called via `get_config.sh` from scheduled tasks (crontab or systemd timer). Put here the name of the script that you will use to update the lists. If not, then the parameter should be commented out. You can individually disable ipv4 or ipv6. If the parameter is commented out or not equal to "1", use of the protocol is permitted. ``` #DISABLE_IPV4=1 DISABLE_IPV6=1 ``` The number of threads for mdig multithreaded DNS resolver (1..100). The more of them, the faster, but will your DNS server be offended by hammering ? `MDIG_THREADS=30` temp directory. Used by ipset/*.sh scripts for large lists processing. /tmp by default. Can be reassigned if /tmp is tmpfs and RAM is low. TMPDIR=/opt/zapret/tmp ipset and nfset options : ``` SET_MAXELEM=262144 IPSET_OPT="hashsize 262144 maxelem 2097152 ``` Kernel automatically increases hashsize if ipset is too large for the current hashsize. This procedure requires internal reallocation and may require additional memory. On low RAM systems it can cause errors. Do not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs. ip2net options. separate for ipv4 and ipv6. ``` IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" ``` autohostlist mode tuning. ``` AUTOHOSTLIST_RETRANS_THRESHOLD=3 AUTOHOSTLIST_FAIL_THRESHOLD=2 AUTOHOSTLIST_FAIL_TIME=60 AUTOHOSTLIST_DEBUG=0 ``` Enable gzip compression for large lists. Used by ipset/*.sh scripts. `GZIP_LISTS=1` Command to reload ip/host lists after update. Comment or leave empty for auto backend selection : ipset or ipfw if present. On BSD systems with PF no auto reloading happens. You must provide your own command. Newer FreeBSD versions support table only reloading : `pfctl -Tl -f /etc/pf.conf` Set to "-" to disable reload. `LISTS_RELOAD="pfctl -f /etc/pf.conf"` In openwrt there's default network `lan`. Only traffic coming from this network is redirected to tpws by default. To override this behaviour set the following variable : `OPENWRT_LAN="lan lan2 lan3"` In openwrt wan interfaces are those having default route. Separately for ipv4 and ipv6. This can be redefined : ``` OPENWRT_WAN4="wan4 vpn" OPENWRT_WAN6="wan6 vpn6" ``` The `INIT_APPLY_FW=1` parameter enables the init script to independently apply iptables rules. With other values or if the parameter is commented out, the rules will not be applied. This is useful if you have a firewall management system, in the settings of which you should tie the rules. Not applicable to `OpenWRT` if used with `firewall3+iptables`. `FILTER_TTL_EXPIRED_ICMP=1` blocks icmp time exceeded messages in response to connections handled by nfqws. Linux closes socket if it receives this icmp in response to SYN packet. Similar mechanism exists for datagram sockets. It's better to disable this if you do not expect problems caused by icmp. The following settings are not relevant for openwrt : If your system works as a router, then you need to enter the names of the internal and external interfaces: ``` IFACE_LAN=eth0 IFACE_WAN=eth1 IFACE_WAN6="henet ipsec0" ``` Multiple interfaces are space separated. IF IFACE_WAN6 is omitted then IFACE_WAN value is taken. IMPORTANT: configuring routing, masquerade, etc. not a zapret task. Only modes that intercept transit traffic are enabled. It's possible to specify multiple interfaces like this : `IFACE_LAN="eth0 eth1 eth2"` ## Screwing to the firewall control system or your launch system If you use some kind of firewall management system, then it may conflict with an existing startup script. When re-applying the rules, it could break the iptables settings from the zapret. In this case, the rules for iptables should be screwed to your firewall separately from running tpws or nfqws. The following calls allow you to apply or remove iptables rules separately: ``` /opt/zapret/init.d/sysv/zapret start_fw /opt/zapret/init.d/sysv/zapret stop_fw /opt/zapret/init.d/sysv/zapret restart_fw ``` And you can start or stop the demons separately from the firewall: ``` /opt/zapret/init.d/sysv/zapret start_daemons /opt/zapret/init.d/sysv/zapret stop_daemons /opt/zapret/init.d/sysv/zapret restart_daemons ``` nftables nearly eliminate conflicts betweeen firewall control systems because they allow separate tables and netfilter hooks. `zapret` nf table is used for zapret purposes. If your system does not touch it everything will likely be OK. Some additional nftables-only calls exist : Lookup `lanif`, `wanif`, `wanif6` and `flow table` interface sets. ``` /opt/zapret/init.d/sysv/zapret list_ifsets ``` Renew `lanif`, `wanif`, `wanif6` and `flow table` interface sets. Taken from `IFACE_LAN`, `IFACE_WAN` config variables on traditional Linux systems. Autoselected on `OpenWRT`. `lanif` can be extended using `OPENWRT_LAN` config variable. ``` /opt/zapret/init.d/sysv/zapret reload_ifsets ``` Calls `nft -t list table inet zapret`. ``` /opt/zapret/init.d/sysv/zapret list_table ``` It's also possible to hook with your script to any stage of zapret firewall processing. The following settings are available in the zapret config file : ``` INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" ``` Hooks are extremely useful if you need nftables sets populated by zapret scripts. nfsets can only belong to one table. You have to write rule there and synchorize them with zapret scripts. ## Installation ### Checking ISP Before running zapret you must discover working bypass strategy. `blockcheck.sh` automates this process. It first checks DNS then tries many strategies finding the working ones. Note that DNS check is mostly Russia targeted. It checks several pre-defined blocked in Russia domains and verifies system DNS answers with public DNS answers. Because ISP can block public DNS or redirect any DNS queries to their servers `blockcheck.sh` also checks that all returned answers are unique. Usually if DNS is blocked ISP returns single ip for all blocked domains to redirect you to their "access denied" page. DoH servers are used automatically for checks if DNS spoof is detected. `blockcheck.sh` works on all systems supported by `zapret`. ### desktop linux system Simple install works on most modern linux distributions with systemd or openrc, OpenWRT and MacOS. Run `install_easy.sh` and answer its questions. ### OpenWRT `install_easy.sh` works on openwrt but there're additional challenges. They are mainly about possibly low flash free space. Simple install will not work if it has no space to install itself and required packages from the repo. Another challenge would be to bring zapret to the router. You can download zip from github and use it. Install openssh-sftp-server and unzip to openwrt and use sftp to transfer the file. It's also not too hard to use 'nc' (netcat) for file transfer. The best way to start is to put zapret dir to `/tmp` and run `/tmp/zapret/install_easy.sh` from there. After installation remove `/tmp/zapret` to free RAM. The absolute minimum for openwrt is 64/8 system, 64/16 is comfortable, 128/extroot is recommended. For low storage openwrt see `init.d/openwrt-minimal`. ### Android Its not possible to use **nfqws** and **tpws** in transparent proxy mode without root privileges. Without root **tpws** can run in `--socks` mode. Android has NFQUEUE and **nfqws** should work. There'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. Although linux binaries work it's recommended to use Android specific ones. They have no problems with user names, local time, DNS, ... Its recommended to use gid 3003 (AID_INET), otherwise **tpws** will not have inet access. Example : `--uid 1:3003` In iptables use : `! --uid-owner 1` instead of `! --uid-owner tpws`. **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. Write your own shell script with iptables and **tpws**, run it using your root manager. Autorun scripts are here : magisk : `/data/adb/service.d` supersu : `/system/su.d` How to run **tpws** on root-less android. You can't write to `/system`, `/data`, can't run from sd card. Selinux prevents running executables in `/data/local/tmp` from apps. Use adb and adb shell. ``` mkdir /data/local/tmp/zapret adb push tpws /data/local/tmp/zapret chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws ``` Now its possible to run `/data/local/tmp/zapret/tpws` from any app such as tasker. ### FreeBSD, OpenBSD, MacOS see [BSD documentation](./bsd.en.md) ### Windows (WSL) see [Windows documentation](./windows.en.md) ### Other devices Author's goal does not include easy supporting as much devices as possibles. Please do not ask for easy supporting firmwares. It requires a lot of work and owning lots of devices. Its counterproductive. As a devices owner its easier for you and should not be too hard if firmware is open. Most closed stock firmwares are not designed for custom usage and sometimes actively prevent it. In the latter case you have to hack into it and reverse engineer. Its not easy. Binaries are universal. They can run on almost all firmwares. You will need : * root shell access. true sh shell, not microtik-like console * startup hook * r/w partition to store binaries and startup script with executable permission (+x) * **tpws** can be run almost anywhere but **nfqws** require kernel support for NFQUEUE. Its missing in most firmwares. * too old 2.6 kernels are unsupported and can cause errors. newer 2.6 kernels are OK. If binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws. First manually debug your scenario. Run iptables + daemon and check if its what you want. Write your own script with iptables magic and run required daemon from there. Put it to startup. Dont ask me how to do it. Its different for all firmwares and requires studying. Find manual or reverse engineer yourself. Check for race conditions. Firmware can clear or modify iptables after your startup script. If this is the case then run another script in background and add some delay there. ## Donations Are welcome here : USDT ERC `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` USDT TRC `TEzAAtn4VhndqEaAyuCM78xh5W2gCjwWEo` BTC `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve` ETH `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` ================================================ FILE: docs/readme.md ================================================ # ВНИМАНИЕ, остерегайтесь мошенников zapret является свободным и open source. Всякий, кто понуждает вас скачивать zapret только с его ресурса, требует удалить ссылки, видео, файлы, обосновывая эти требования авторскими правами, сам нарушает [лицензию](./LICENSE.txt). Однако, это не исключает [добровольные пожертвования](#поддержать-разработчика). # zapret2 Эта версия zapret более не развивается и находится в режиме EOL (End-Of-Life). Никаких новых функций больше не будет. Только багфиксы. [Актуальная версия - zapret 2](https://github.com/bol-van/zapret2) # Multilanguage README [![en](https://img.shields.io/badge/lang-en-red.svg)](./readme.en.md) [![ru](https://img.shields.io/badge/lang-ru-green.svg)](./readme.md) *** - [Зачем это нужно](#зачем-это-нужно) - [Быстрый старт](#быстрый-старт) - [Как это работает](#как-это-работает) - [Что сейчас происходит в России](#что-сейчас-происходит-в-россии) - [Как это реализовать на практике в системе linux](#как-это-реализовать-на-практике-в-системе-linux) - [Когда это работать не будет](#когда-это-работать-не-будет) - [nfqws](#nfqws) - [АТАКА ДЕСИНХРОНИЗАЦИИ DPI](#атака-десинхронизации-dpi) - [ФЕЙКИ](#фейки) - [МОДИФИКАЦИЯ ФЕЙКОВ](#модификация-фейков) - [TCP СЕГМЕНТАЦИЯ](#tcp-сегментация) - [ПЕРЕКРЫТИЕ SEQUENCE NUMBERS](#перекрытие-sequence-numbers) - [НАЗНАЧЕНИЕ IP_ID](#назначение-ip_id) - [СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6](#специфические-режимы-ipv6) - [МОДИФИКАЦИЯ ОРИГИНАЛА](#модификация-оригинала) - [ДУБЛИКАТЫ](#дубликаты) - [КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ](#комбинирование-методов-десинхронизации) - [КЭШ IP](#кэш-ip) - [РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА](#реакция-dpi-на-ответ-сервера) - [РЕЖИМ SYNACK](#режим-synack) - [РЕЖИМ SYNDATA](#режим-syndata) - [ВИРТУАЛЬНЫЕ МАШИНЫ](#виртуальные-машины) - [CONNTRACK](#conntrack) - [РЕАССЕМБЛИНГ](#реассемблинг) - [ПОДДЕРЖКА UDP](#поддержка-udp) - [IP ФРАГМЕНТАЦИЯ](#ip-фрагментация) - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии) - [ФИЛЬТРАЦИЯ ПО WIFI](#фильтрация-по-wifi) - [IPTABLES ДЛЯ NFQWS](#iptables-для-nfqws) - [NFTABLES ДЛЯ NFQWS](#nftables-для-nfqws) - [FLOW OFFLOADING](#flow-offloading) - [ОСОБЕННОСТИ ЖЕЛЕЗОК](#особенности-железок) - [ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА](#дурение-со-стороны-сервера) - [tpws](#tpws) - [TCP СЕГМЕНТАЦИЯ В TPWS](#tcp-сегментация-в-tpws) - [TLSREC](#tlsrec) - [MSS](#mss) - [ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ](#другие-параметры-дурения) - [МНОЖЕСТВЕННЫЕ СТРАТЕГИИ](#множественные-стратегии-1) - [СЛУЖЕБНЫЕ ПАРАМЕТРЫ](#служебные-параметры) - [IPTABLES ДЛЯ TPWS](#iptables-для-tpws) - [NFTABLES ДЛЯ TPWS](#nftables-для-tpws) - [ip2net](#ip2net) - [mdig](#mdig) - [Способы получения списка заблокированных IP](#способы-получения-списка-заблокированных-ip) - [Фильтрация по именам доменов](#фильтрация-по-именам-доменов) - [Режим фильтрации autohostlist](#режим-фильтрации-autohostlist) - [Проверка провайдера](#проверка-провайдера) - [Выбор параметров](#выбор-параметров) - [Прикручивание к системе управления фаерволом или своей системе запуска](#прикручивание-к-системе-управления-фаерволом-или-своей-системе-запуска) - [Вариант custom](#вариант-custom) - [Простая установка](#простая-установка) - [Установка под systemd](#установка-под-systemd) - [Простая установка на openwrt](#простая-установка-на-openwrt) - [Установка на openwrt в режиме острой нехватки места на диске](#установка-на-openwrt-в-режиме-острой-нехватки-места-на-диске) - [Android](#android) - [Мобильные модемы и роутеры huawei](#мобильные-модемы-и-роутеры-huawei) - [FreeBSD, OpenBSD, MacOS](#freebsd-openbsd-macos) - [Windows](#windows) - [Другие прошивки](#другие-прошивки) - [Обход блокировки через сторонний хост](#обход-блокировки-через-сторонний-хост) - [Почему стоит вложиться в покупку VPS](#почему-стоит-вложиться-в-покупку-vps) - [Поддержать разработчика](#поддержать-разработчика) *** ## Зачем это нужно Автономное средство противодействия DPI, которое не требует подключения каких-либо сторонних серверов. Может помочь обойти блокировки или замедление сайтов HTTP(S), сигнатурный анализ TCP и UDP протоколов, например, с целью блокировки VPN. Проект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под OpenWrt. Поддерживаются традиционные Linux-системы, FreeBSD, OpenBSD, частично macOS. В некоторых случаях возможна самостоятельная прикрутка решения к различным прошивкам. Большая часть функционала работает на Windows. ## Быстрый старт - [Linux/openWrt](./quick_start.md) - [Windows](./quick_start_windows.md) ## Как это работает В самом простейшем случае вы имеете дело с пассивным DPI. Пассивный DPI может читать трафик из потока, может инжектить свои пакеты, но не может блокировать проходящие пакеты. Если запрос "плохой", пассивный DPI инжектит пакет RST, опционально дополняя его пакетом HTTP redirect. Если фейк пакет инжектится только для клиента, в этом случае можно обойтись командами iptables для дропа RST и/или редиректа на заглушку по определённым условиям, которые нужно подбирать для каждого провайдера индивидуально. Так мы обходим последствия срабатывания триггера запрета. Если пассивный DPI направляет пакет RST в том числе и серверу, то вы ничего с этим не сможете сделать. Ваша задача — не допустить срабатывания триггера запрета. Одними iptables уже не обойтись. Этот проект нацелен именно на предотвращение срабатывания запрета, а не ликвидацию его последствий. Активный DPI ставится в разрез провода и может дропать пакеты по любым критериям, в том числе распознавать TCP-потоки и блокировать любые пакеты, принадлежащие потоку. Как не допустить срабатывания триггера запрета? Послать то, на что DPI не рассчитывает и что ломает ему алгоритм распознавания запросов и их блокировки. Некоторые DPI не могут распознать HTTP-запрос, если он разделен на TCP-сегменты. Например, запрос вида `GET / HTTP/1.1\r\nHost: kinozal.tv......` мы посылаем двумя частями: сначала идет `GET`, затем `/ HTTP/1.1\r\nHost: kinozal.tv.....`. Другие DPI спотыкаются, когда заголовок `Host:` пишется в другом регистре: например, `host:`. Кое-где работает добавление дополнительного пробела после метода: `GET /` → `GET /` или добавление точки в конце имени хоста: `Host: kinozal.tv.` Существует и более продвинутая магия, направленная на преодоление DPI на пакетном уровне. Подробнее про DPI:\ https://habr.com/ru/post/335436 или https://web.archive.org/web/20230331233644/https://habr.com/ru/post/335436/ \ https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf ## Что сейчас происходит в России Раньше, до внедрения повсеместных систем ТСПУ, использовался зоопарк различных DPI у провайдеров. Какие-то были активными, какие-то пассивными. Сейчас время простых iptables окончательно ушло. Везде активный DPI ТСПУ, но кое-где могут оставаться невыключенными дополнительные старые DPI из зоопарка. В этом случае приходится обходить сразу несколько DPI. Все больше становится внереестровых блокировок, о которых вы узнаете только по факту недоступности чего-либо, в списках этого нет. Применяются блокировки некоторых диапазонов ip адресов (автономный обход невозможен) и протоколов (VPN). На некоторых диапазонах IP используется более строгий фильтр, распознающий попытки обмана через сегментацию. Должно быть это связано с некоторыми сервисами, которые пытаются таким образом обмануть DPI. ## Как это реализовать на практике в системе linux Если кратко, то варианты можно классифицировать по следующей схеме : 1) Пассивный DPI, не отправляющий RST серверу. Помогут индивидуально настраиваемые под провайдера команды iptables. На rutracker в разделе "обход блокировок - другие способы" по этому вопросу существует отдельная тема. В данном проекте не рассматривается. Если вы не допустите срабатывание триггера запрета, то и не придется бороться с его последствиями. 2) Модификация TCP соединения на уровне потока. Реализуется через proxy или transparent proxy. 3) Модификация TCP соединения на уровне пакетов. Реализуется через обработчик очереди NFQUEUE и raw сокеты. Для вариантов 2 и 3 реализованы программы tpws и nfqws соответственно. Чтобы они работали, необходимо их запустить с нужными параметрами и перенаправить на них определенный трафик средствами iptables или nftables. ## Когда это работать не будет * Если подменяется DNS. С этой проблемой легко справиться. * Если блокировка осуществляется по IP. * Если соединение проходит через фильтр, способный реконструировать TCP соединение, и который следует всем стандартам. Например, нас заворачивают на squid. Соединение идет через полноценный стек tcpip операционной системы. Проект нацелен на обман DPI, который в силу ограниченности ресурсов и большого трафика вынужден интерпретировать его лишь ограниченно. Обмануть полноценный стек ОС и полноценные серверные приложения не получится. ## nfqws Эта программа - модификатор пакетов и обработчик очереди NFQUEUE. Для BSD систем существует адаптированный вариант - dvtws, собираемый из тех же исходников (см. [документация BSD](./bsd.md)). ``` @|$ ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются. --debug=0|1 ; 1=выводить отладочные сообщения --dry-run ; проверить опции командной строки и выйти. код 0 - успешная проверка. --version ; вывести версию и выйти --comment ; любой текст (игнорируется) --daemon ; демонизировать прогу --pidfile= ; сохранить PID в файл --user= ; менять uid процесса --uid=uid[:gid] ; менять uid процесса --qnum=N ; номер очереди N --bind-fix4 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv4 пакетов --bind-fix6 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv6 пакетов --ctrack-timeouts=S:E:F[:U] ; таймауты внутреннего conntrack в состояниях SYN, ESTABLISHED, FIN, таймаут udp. по умолчанию 60:300:60:60 --ctrack-disable=[0|1] ; 1 или отсутствие аргумента отключает conntrack --ipcache-lifetime= ; время жизни записей кэша IP в секундах. 0 - без ограничений. --ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы --wsize=[:] ; менять tcp window size на указанный размер в SYN,ACK. если не задан scale_factor, то он не меняется (устарело !) --wssize=[:] ; менять tcp window size на указанный размер в исходящих пакетах. scale_factor по умолчанию 0. (см. conntrack !) --wssize-cutoff=[n|d|s]N ; изменять server window size в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --wssize-forced-cutoff=0|1 ; 1(default)=автоматически отключать wssize в случае обнаружения известного протокола --synack-split=[syn|synack|acksyn] ; выполнить tcp split handshake. вместо SYN,ACK отсылать только SYN, SYN+ACK или ACK+SYN --orig-ttl= ; модифицировать TTL оригинального пакета --orig-ttl6= ; модифицировать ipv6 hop limit оригинальных пакетов. если не указано, используется значение --orig-ttl --orig-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: +5:3-64. "0:0-0" или "-" отключает функцию --orig-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 --orig-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 --orig-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) --orig-mod-start=[n|d|s]N ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N --orig-mod-cutoff=[n|d|s]N ; применять orig-mod только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --dup= ; высылать N дубликатов до оригинала --dup-replace=[0|1] ; 1 или отсутствие аргумента блокирует отправку оригинала. отправляются только дубликаты. --dup-ttl= ; модифицировать TTL дубликатов --dup-ttl6= ; модифицировать ipv6 hop limit дубликатов. если не указано, используется значение --dup-ttl --dup-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: +1:3-64. "0:0-0" или "-" отключает функцию --dup-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 --dup-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 --dup-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) --dup-fooling= ; дополнительные методики как сделать, чтобы дубликат не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2 --dup-ts-increment= ; инкремент TSval для ts. по умолчанию -600000 --dup-badseq-increment= ; инкремент sequence number для badseq. по умолчанию -10000 --dup-badack-increment= ; инкремент ack sequence number для badseq. по умолчанию -66000 --dup-ip-id=same|zero|seq|rnd ; режим назначения ip_id для пакетов dup --dup-start=[n|d|s]N ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N --dup-cutoff=[n|d|s]N ; применять dup только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --hostcase ; менять регистр заголовка "Host:" по умолчанию на "host:". --hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета --methodeol ; добавить перевод строки в unix стиле ('\n') перед методом и убрать пробел из Host: : "GET / ... Host: domain.com" => "\nGET / ... Host:domain.com" --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase --domcase ; домен после Host: сделать таким : TeSt.cOm --ip-id=seq|seqgroup|rnd|zero ; режим назначения ip_id для генерированных пакетов --dpi-desync=[,][, ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000 --dpi-desync-ttl= ; установить ttl для десинхронизирующих пакетов --dpi-desync-ttl6= ; установить ipv6 hop limit для десинхронизирующих пакетов. если не указано, используется значение --dpi-desync-ttl --dpi-desync-autottl=[[:[-]]|-] ; режим auto ttl для ipv4 и ipv6. по умолчанию: 1:3-20. "0:0-0" или "-" отключает функцию --dpi-desync-autottl6=[[:[-]]|-] ; переопределение предыдущего параметра для ipv6 --dpi-desync-tcp-flags-set= ; устанавливать указанные tcp флаги (flags |= value). число , либо список через запятую : FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,AE,R1,R2,R3 --dpi-desync-tcp-flags-unset= ; удалять указанные tcp флаги (flags &= ~value) --dpi-desync-fooling= ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера. none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2 --dpi-desync-repeats= ; посылать каждый генерируемый в nfqws пакет N раз (не влияет на остальные пакеты) --dpi-desync-skip-nosni=0|1 ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI --dpi-desync-split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации в режимах split и disorder --dpi-desync-split-seqovl=N|-N|marker+N|marker-N ; единичный маркер, определяющий величину перекрытия sequence в режимах split и disorder. для split поддерживается только положительное число. --dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; чем заполнять фейковую часть overlap --dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; чем заполнять фейки в fakedsplit/fakeddisorder --dpi-desync-fakedsplit-mod=mod[,mod] ; может быть none, altorder=0|1|2|3 + 0|8|16 --dpi-desync-hostfakesplit-midhost=marker+N|marker-N ; маркер дополнительного разреза сегмента с оригинальным хостом. должен попадать в пределы хоста. --dpi-desync-hostfakesplit-mod=mod[,mod] ; может быть none, host=, altorder=0|1 --dpi-desync-ipfrag-pos-tcp=<8..9216> ; позиция ip фрагментации tcp, начиная с транспортного заголовка. должно быть кратно 8, по умолчанию - 32. --dpi-desync-ipfrag-pos-udp=<8..9216> ; позиция ip фрагментации udp, начиная с транспортного заголовка. должно быть кратно 8, по умолчанию - 8. --dpi-desync-ts-increment= ; инкремент TSval для ts. по умолчанию -600000 --dpi-desync-badseq-increment= ; инкремент sequence number для badseq. по умолчанию -10000 --dpi-desync-badack-increment= ; инкремент ack sequence number для badseq. по умолчанию -66000 --dpi-desync-any-protocol=0|1 ; 0(default)=работать только по http request и tls clienthello 1=по всем непустым пакетам данных --dpi-desync-fake-tcp-mod=mod[,mod] ; список через запятую режимов runtime модификации tcp фейков (любых) : none, seq --dpi-desync-fake-http=[+ofs]@|0xHEX ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному www.iana.org --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному. '!' = стандартный фейк --dpi-desync-fake-tls-mod=mod[,mod] ; список через запятую режимов runtime модификации фейков : none,rnd,rndsni,sni=,dupsid,padencap --dpi-desync-fake-unknown=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт --dpi-desync-fake-syndata=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад пакета SYN для режима десинхронизации syndata --dpi-desync-fake-quic=[+ofs]@|0xHEX ; файл, содержащий фейковый QUIC Initial --dpi-desync-fake-wireguard=[+ofs]@|0xHEX ; файл, содержащий фейковый wireguard handshake initiation --dpi-desync-fake-dht=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад DHT протокола для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-fake-discord=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад Discord протокола нахождения IP адреса для голосовых чатов для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-fake-stun=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад STUN протокола для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-udplen-increment= ; на сколько увеличивать длину udp пейлоада в режиме udplen --dpi-desync-udplen-pattern=[+ofs]@|0xHEX ; чем добивать udp пакет в режиме udplen. по умолчанию - нули --dpi-desync-start=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N --dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'. ; в файле должен быть хост на каждой строке. ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. ; при изменении времени модификации файла он перечитывается автоматически по необходимости ; список может быть запакован в gzip. формат автоматически распознается и разжимается ; списков может быть множество. пустой общий лист = его отсутствие ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. --hostlist-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. --hostlist-exclude= ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам. --hostlist-exclude-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. --hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) --hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) --hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) --hostlist-auto-retrans-threshold= ; сколько ретрансмиссий запроса считать блокировкой (по умолчанию: 3) --hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. --new ; начало новой стратегии (новый профиль) --skip ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров. --filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии --filter-tcp=[~]port1[-port2]|* ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. установка фильтра tcp и неустановка фильтра udp запрещает udp. поддерживается список через запятую. --filter-udp=[~]port1[-port2]|* ; фильтр портов udp для текущей стратегии. ~ означает инверсию. установка фильтра udp и неустановка фильтра tcp запрещает tcp. поддерживается список через запятую. --filter-l7= ; фильтр протокола L6-L7. поддерживается несколько значений через запятую. proto : http tls quic wireguard dht discord stun unknown --filter-ssid=ssid1[,ssid2,ssid3,...] ; фильтр по имени wifi сети (только для linux) --ipset= ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. --ipset-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. --ipset-exclude= ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. --ipset-exclude-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. ``` `--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл. Может быть важен порядок следования опций. `--debug` лучше всего указывать в самом начале. Опции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа `--debug` еще дело не дошло, то сообщения не будут выведены в файл или syslog. При логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. Так что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. Но имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root. В начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл, и у процесса не будет прав на создание файла в его директории, лог больше не будет вестись. Вместо удаления лучше использовать truncate. В шелле это можно сделать через команду ": >filename" Многие параметры, загружающие двоичные данные из файлов, поддерживают загрузку из hex-строки или из файла. hex строка начинается с "0x". Имя файла можно писать как есть или использовать префикс "@". Если перед префиксом "@" указано "+<число>", то это означает смещение полезных данных внутри файла. Файл может загружаться целиком с нулевой позиции, к нему могут применяться модификации, требующие полного файла (TLS), но передача пойдет с позиции offset. offset должен быть меньше длины файла. Если к блоку данных применяется мод, который уменьшает размер данных, и offset окажется не меньше новой длины данных, будет ошибка. ### АТАКА ДЕСИНХРОНИЗАЦИИ DPI Суть ее в следующем. Берется оригинальный запрос, модифицируется, добавляется поддельная информация (фейки) таким образом, чтобы ОС сервера передала серверному процессу оригинальный запрос в неизменном виде, а DPI увидел другое. То, что он блокировать не станет. Сервер видит одно, DPI - другое. DPI не понимает, что передается запрещенный запрос и не блокирует его. Есть арсенал возможностей, чтобы достичь такого результата. Это может быть передача фейк пакетов, чтобы они дошли до DPI, но не дошли до сервера. Может использоваться фрагментация на уровне TCP (сегментация) или на уровне IP. Есть атаки, основанные на игре с tcp sequence numbers или с перепутыванием порядка следования tcp сегментов. Методы могут сочетаться в различных вариантах. ### ФЕЙКИ Фейки - это отдельные сгенерированные nfqws пакеты, несущие ложную информацию для DPI. Они либо не должны дойти до сервера, либо могут дойти, но должны быть им отброшены. Иначе получается слом tcp соединения или нарушение целостности передаваемого потока, что гарантированно приводит к поломке ресурса. Есть ряд методов для решения этой задачи. * `md5sig` добавляет TCP опцию **MD5 signature**. Работает не на всех серверах. Пакеты с md5 обычно отбрасывают только linux. Требуется значительное увеличение длины tcp пакета, чтобы вместить tcp option. При обработке многосегментных запросов (TLS Kyber) первый пакет идет полный под MTU. При fakedsplit/fakeddisorder на небольших позициях отдельные tcp сегменты достаточно велики, чтобы внедрение md5 tcp option вызвало переполнение MTU и ошибку отправки "message too long". `nfqws` не умеет перераспределять данные между tcp сегментами, поэтому надо или отказываться от kyber, или увеличивать сплит-позицию, или отказываться от fakedsplit/fakeddisorder. * `badsum` портит контрольную сумму TCP. Не сработает, если ваше устройство за NAT, который не пропускает пакеты с инвалидной суммой. Наиболее распространенная настройка NAT роутера в Linux их не пропускает. На Linux построено большинство домашних роутеров. Непропускание обеспечивается так : настройка ядра sysctl по умолчанию `net.netfilter.nf_conntrack_checksum=1` заставляет conntrack проверять tcp и udp чексуммы входящих пакетов и выставлять state INVALID для пакетов с инвалидной суммой. Обычно в правилах iptables вставляется правило для дропа пакетов с состоянием INVALID в цепочке FORWARD. Совместное сочетание этих факторов приводит к непрохождению badsum через такой роутер. В OpenWrt из коробки `net.netfilter.nf_conntrack_checksum=0`, в других роутерах часто нет, и не всегда это можно изменить. Чтобы nfqws мог работать через роутер, нужно на нем выставить указанное значение sysctl в 0. nfqws на самом роутере будет работать и без этой настройки, потому что чексумма локально созданных пакетов не проверяется никогда. Если роутер за другим NAT, например провайдерским, и он не пропускает invalid packets вы ничего не сможете с этим сделать. Но обычно провайдеры все же пропускают badsum. На некоторых адаптерах/свитчах/драйверах принудительно включен rx-checksum offload, badsum пакеты отсекаются еще до получения в ОС. В этом случае если что-то и можно сделать, то только модифицировать драйвер, что представляется задачей крайне нетривиальной. Установлено, что так себя ведут некоторые роутеры на базе mediatek. badsum пакеты уходят с клиентской ОС, но роутером не видятся в br-lan через tcpdump. При этом если nfqws выполняется на самом роутере, обход может работать. badsum нормально уходят с внешнего интерфейса. * `badseq` увеличивает TCP sequence number на определенное значение, выводя его тем самым из TCP window. Такие пакеты будут наверняка отброшены принимающим узлом, но так же и DPI, если он ориентируется на sequence numbers. По умолчанию смещение seq выбирается -10000. Практика показала, что некоторые DPI не пропускают seq вне определенного окна. Однако, такое небольшое смещение может вызвать проблемы при существенной потоковой передаче и потере пакетов. Если вы используете `--dpi-desync-any-protocol`, может понадобиться установить badseq increment 0x80000000. Это обеспечит надежную гарантию, что поддельный пакет не вклинится в tcp window на сервере. Так же было замечено, что badseq ломает логику некоторых DPI при анализе http, вызывая зависание соединения. Причем на тех же DPI TLS с badseq работает нормально. * `TTL` казалось бы - лучший вариант, но он требует индивидуальной настройки под каждого провайдера. Если DPI находится дальше локальных сайтов провайдера, то вы можете отрезать себе доступ к ним. Ситуация усугубляется наличием ТСПУ на магистралах, что вынуждает делать TTL достаточно высоким, увеличивая риск пробоя фейка до сервера. Необходим ip exclude list, заполняемый вручную. Вместе с ttl можно применять md5sig. Это ничего не испортит, зато дает неплохой шанс работы сайтов, до которых "плохой" пакет дойдет по TTL. Если не удается найти автоматическое решение, воспользуйтесь файлом `zapret-hosts-user-exclude.txt`. Некоторые стоковые прошивки роутеров фиксируют исходящий TTL, без отключения этой опции через них работать не будет. КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при котором обход еще работает. Это и будет номер хопа вашего DPI. * `hopbyhop` относится только к ipv6. Добавляется ipv6 extenstion header `hop-by-hop options`. В варианте `hopbyhop2` добавляются 2 хедера, что является нарушением стандарта и гарантированно отбрасывается стеком протоколов во всех ОС. Один хедер hop-by-hop принимается всеми ОС, однако на некоторых каналах/провайдерах такие пакеты могут фильтроваться и не доходить. Расчет идет на то, что DPI проанализирует пакет с hop-by-hop, но он либо не дойдет до адресата в силу фильтров провайдера, либо будет отброшен сервером, потому что хедера два. * `datanoack` высылает фейки со снятым tcp флагом ACK. Сервера такое не принимают, а DPI может принять. Эта техника может ломать NAT и не всегда работает с iptables, если используется masquerade, даже с локальной системы (почти всегда на роутерах ipv4). На системах c iptables без masquerade и на nftables работает без ограничений. Экспериментально выяснено, что многие провайдерские NAT не отбрасывают эти пакеты, потому работает даже с внутренним провайдерским IP. Но linux NAT оно не пройдет, так что за домашним роутером эта техника скорее всего не сработает, но может сработать с него. Может сработать и через роутер, если подключение по проводу, и на роутере включено аппаратное ускорение. * Манипуляция tcp флагами с помощью `--dpi-desync-tcp-flags-set` и `--dpi-desync-tcp-flags-unset`. Можно сделать инвалидное сочетание флагов, которое сервер не примет, а DPI - примет. Например, установить SYN в фейках. Но это может работать не на всех серверах. `datanoack` может быть заменен `--dpi-desync-tcp-flags-unset=ACK`. Пакеты с инвалидными флагами могут отбрасываться, проходя через NAT. * `ts` прибавляет к значению TSval таймштампа tcp значение ts increment (по умолчанию -600000). Сервера отбрасывают пакеты с TSval в определенных пределах. По практическим тестам инкремент должен быть где-то от -100 до -0x80000000. timestamps генерирует клиентская ОС. В linux таймштампы включены по умолчанию, в windows выключены по умолчанию. Можно включить через команду `netsh interface tcp set global timestamps=enabled`. ts fooling требует, чтобы таймштампы были включены, иначе работать не будет. Включать надо на каждом клиентском устройстве. TSecr оставляется без изменений. Так же требуется, чтобы сервер понимал timestamps, но это в большинстве случаев так. * `autottl`. Суть режима в автоматическом определении TTL, чтобы пакет почти наверняка прошел DPI и немного не дошел до сервера (`--dpi-desync-autottl`). Или наоборот - TTL едва хватило, чтобы он все-таки дошел до сервера (см `--dup-autottl`, `--orig-autottl`). Берутся базовые значения TTL 64,128,255, смотрится входящий пакет (да, требуется направить первый входящий пакет на nfqws !). Вычисляется длина пути, прибавляется `delta`. delta может быть положительной или отрицательной. Чтобы задать положительную дельту, нужно указать унарный знак **+** перед числом. В случае его отсутствия или при наличии унарного знака **-** дельта считается отрицательной. Если TTL вне диапазона min,max, то берутся значения min,max, чтобы вписаться в диапазон. Если при этом дельта отрицательная и полученный TTL больше длины пути или дельта положительная и полученный TTL меньше длины пути, то автоматизм не сработал и берутся фиксированные значения : `--dpi-desync-ttl`, `--orig-ttl`, `--dup-ttl`. Техника позволяет решить вопрос, когда вся сеть перегорожена шлагбаумами (DPI, ТСПУ) везде где только можно, включая магистралов. Но потенциально может давать сбои. Например, при асимметрии входящего и исходящего канала до конкретного сервера. Некоторые сервера выдают нестандартный TTL (google), потому на них получается полная ерунда. Если не учитывать подобные исключения, то на каких-то провайдерах эта техника будет работать неплохо, на других доставит больше проблем, чем пользы. Где-то может потребоваться тюнинг параметров. Лучше использовать с дополнительным ограничителем. Режимы дурения могут сочетаться в любых комбинациях. `--dpi-desync-fooling` берет множество значений через запятую. Возможно задание множества фейков через повторение параметров `--dpi-desync-fake-???`, кроме `--dpi-desync-fake-syndata`. Фейки будут отосланы в указанном порядке. `--dpi-desync-repeats` повторяет каждый отосланный фейк. Итоговый порядок будет такой : `fake1 fake1 fake1 fake2 fake2 fake2 fake3 fake3 fake3 .....` ### МОДИФИКАЦИЯ ФЕЙКОВ Любые tcp фейки отправляются с исходным sequence по умолчанию, даже если их несколько. Если задать `--dpi-desync-fake-tcp-mod=seq`, то несколько фейков будут отправлены с увеличением sequence number таким образом, как будто они являются tcp сегментами одного фейка. В nfqws зашит базовый вариант фейка для TLS. Его можно переопределить опцией `--dpi-desync-fake-tls`. Переопределение фейков дает возможность использовать любые данные в качестве фейка для TLS. Можно использовать фейковый Client Hello с любым фингерпринтом и с любым SNI. Некоторые модификации можно делать в процессе выполнения с помощью `--dpi-desync-fake-tls-mod`. Часть из них работает при обработке каждого TLS Client Hello и может подстраиваться под отправляемые данные. Модификации требуют наличия полного валидного TLS Client Hello в качестве фейка, они не работают с произвольными данными. * `none`. Не применять никакие модификации. * `rnd`. Рандомизировать поля `random` и `session id`. Выполняется на каждый запрос. * `dupsid`. Копировать `session ID` из передаваемого TLS Client Hello. Имеет приоритет над `rnd`. Выполняется на каждый запрос. * `rndsni`. Рандомизировать SNI. Если SNI >=7 символов, применяется случайный домен 2 уровня с известным TLD, иначе заполняется случайными символами без точки. Выполняется один раз при старте. * `sni=`. Заменить sni на указанное значение. Макс длина SNI - 63 байта. Общая длина TLS фейка и длины в структуре TLS Client Hello меняются. Выполняется один раз при старте. Если сочетается с `rndsni`, выполняется до него. * `padencap`. Расширяется padding extension на размер передаваемого TLS Client Hello (включая многопакетный вариант с kyber). Если padding отсутствует, он добавляется в конец. Если присутствует - требуется, чтобы padding шел последним extension. Правятся все длины, чтобы создать видимость включения передаваемого TLS Client Hello в padding extension. Размер фейка не изменяется. Расчет идет на DPI, который не анализирует sequence numbers должным образом. Выполняется на каждый запрос. По умолчанию если не задан собственный фейк для TLS используются модификации `rnd,rndsni,dupsid`. Если фейк задан, используется `none`. Это соответствует поведению программы более старых версий с добавлением функции `dupsid`. Если задан режим модификации и имеется множество TLS фейков, к каждому из них применяется последний режим модификации. Если режим модификации задан после фейка, то он замещает предыдущий режим. Таким образом можно использовать разные режимы модификации для разных фейков. При невозможности модифицировать фейк на этапе запуска программа завершается с ошибкой. Если сначала идет TLS фейк, для него задан режим однократной модификации, затем идет не TLS фейк, то будет ошибка. Нужно использовать `--dpi-desync-fake-tls-mod=none'. Пример : `--dpi-desync-fake-tls=iana_org.bin --dpi-desync-fake-tls-mod=rndsni --dpi-desync-fake-tls=0xaabbccdd --dpi-desync-fake-tls-mod=none' ### TCP СЕГМЕНТАЦИЯ * `multisplit`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях. * `multidisorder`. нарезаем запрос на указанных в `--dpi-desync-split-pos` позициях и отправляем в обратном порядке. * `fakedsplit`. различные варианты замешивания фейков и оригиналов в прямом порядке * `fakeddisorder`. различные варианты замешивания фейков и оригиналов в обратном порядке * `hostfakesplit` (altorder=0). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал хоста (+ опционально нарезка маркером midhost), фейк хоста, оригинал после хоста * `hostfakesplit` (altorder=1). фейкование части запроса с хостом : оригинал до хоста, фейк хоста, оригинал после хоста, оригинал хоста (+опционально нарезка маркером midhost) * `fakeddisorder`. аналогично `fakedsplit`, только в обратном порядке : фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1 части. Для `fakedsplit` и `fakeddisorder` предусмотрены вариации порядка следования сегментов. Параметр `--dpi-desync-fakedsplit-mod=altorder=N` задает число, влияющее на наличие отдельных фейков : Режимы altorder для `fakedsplit` для части многопакетного запроса, где есть сплит-позиция : * `altorder=0`. фейк 1-й части, 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части * `altorder=1`. 1 часть, фейк 1-й части, фейк 2-й части, 2 часть, фейк 2-й части * `altorder=2`. 1 часть, фейк 2-й части, 2 часть, фейк 2-й части * `altorder=3`. 1 часть, фейк 2-й части, 2 часть Режимы altorder для `fakeddisorder` для части многопакетного запроса, где есть сплит-позиция : * `altorder=0`. фейк 2-й части, 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части * `altorder=1`. 2 часть, фейк 2-й части, фейк 1-й части, 1 часть, фейк 1-й части * `altorder=2`. 2 часть, фейк 1-й части, 1 часть, фейк 1-й части * `altorder=3`. 2 часть, фейк 1-й части, 1 часть Режимы altorder для `fakedsplit` и `fakeddisorder` для части многопакетного запроса, где нет сплит-позиции : * `altorder=0`. фейк, оригинал, фейк * `altorder=8`. оригинал, фейк * `altorder=16`. оригинал Итоговое число `altorder=N` вычисляется как сумма чисел из этих двух групп. По умолчанию `altorder=0`. Содержимое фейков в `fakedsplit`/`fakeddisorder` определяется параметром `--dpi-desync-fakedsplit-pattern` (по умолчанию 0x00). Данные фейков берутся из паттерна со смещением, соответствующим смещению отсылаемых частей, учитывая смещения пакетов в многопакетных запросах. Размеры фейков соответствуют длинам отсылаемых частей. Цель этих режимов - максимально усложнить выявление оригинальных данных среди фейков. Использование `fakedsplit` или `fakeddisorder` на TLS kyber с md5sig fooling может привести к ошибкам "message too long", если позиция сплита мала, поскольку будет превышение MTU из-за md5 tcp option. Режим 'hostfakesplit' имеет задачу минимального вмешательства фейком - как раз по той части запроса, на основании которой DPI принимает решение о блокировке. Конкретно - имени хоста. По умолчанию фейк хоста генерируется каждый раз случайно из набора `[a-z0-9]`. При длине более 7 символов за 3 символа до конца ставится точка, имитируя TLD, а последние 3 символа заполняются одним из нескольких известных TLD. Можно переопределить шаблон генерации с помощью `--dpi-desync-hostfakesplit-mod=host=`. В последнем случае справа всегда будет указанный hostname. Слева он будет дополнен до размера оригинального хоста как поддомен со случайными символами. Пример : "www.networksolutions.com" -> "h8xmdba4tv7a8.google.com". Если размер оригинального хоста меньше шаблона, шаблон будет порезан : "habr.com" -> "ogle.com". Если размер оригинального хоста больше шаблона на 1, получится инвалидный пустой поддомен : "www.xxx.com" => ".google.com". Поэтому стоит использовать максимально короткие хосты из разрешенных : "ya.ru", "vk.com". `--dpi-desync-hostfakesplit-mod=altorder=1` позволяет сменить порядок следования частей на альтернативный вариант. `altorder=1` шлет фрагменты в таком порядке, чтобы при последовательной сборке сегментов на DPI он получил полностью собранный оригинал запроса с подмененным хостом. Реальный хост идет отдельным сегментом уже после. То есть в этом варианте применяется разновидность disorder. Сервер принимает фрагменты с нарушенным порядком sequence. Опционально можно разрезать оригинальный хост. Например, `--dpi-desync-hostfakesplit-midhost=midsld`. Позиция нарезки должна попадать внутрь хоста. Многопакетные запросы поддерживаются только, если исходная нарезка пакетов не включает позиции имени хоста. В последнем случае дурение отменяется. Вариант `fakedsplit` имеет несколько альтернативных порядков нарезки - от 0 до 3. Режим задается в параметре `--dpi-desync-fakedsplit-mod=altorder=N`. Каждый следующий altorder убирает часть фейков. Для определения позиций нарезки используются маркеры. * **Абсолютный положительный маркер** - числовое смещение внутри пакета или группы пакетов от начала. * **Абсолютный отрицательный маркер** - числовое смещение внутри пакета или группы пакетов от следующего за концом байта. -1 указывает на последний байт. * **Относительный маркер** - положительное или отрицательное смещение относительно логической позиции внутри пакета или группы пакетов. Относительные позиции : * **method** - начало метода HTTP ('GET', 'POST', 'HEAD', ...). Метод обычно всегда находится на позиции 0, но может сместиться из-за `--methodeol`. Тогда позиция может стать 1 или 2. * **host** - начало имени хоста в известном протоколе (http, TLS) * **endhost** - байт, следующий за последним байтом имени хоста * **sld** - начало домена 2 уровня в имени хоста * **endsld** - байт, следующий за последним байтом домена 2 уровня в имени хоста * **midsld** - середина домена 2 уровня в имени хоста * **sniext** - начало поля данных SNI extension в TLS. Любой extension состоит из 2-байтовых полей type и length, за ними идет поле данных. Пример списка маркеров : `100,midsld,sniext+1,endhost-2,-10`. При разбиении пакета первым делом происходит ресолвинг маркеров - нахождение всех указанных относительных позиций и применение смещений. Если относительная позиция отсутствует в текущем протоколе, такие позиции не применяются и отбрасываются. Дальше происходит нормализация позиций относительно смещения текущего пакета в группе пакетов (многопакетные запросы TLS с kyber, например). Выкидываются все позиции, выходящие за пределы текущего пакета. Оставшиеся сортируются в порядке возрастания и удаляются дубли. В вариантах `multisplit` и `multidisorder` если не осталось ни одной позиции, разбиение не происходит. Варианты `fakedsplit` и `fakeddisorder` применяют только одну позицию сплита. Ее поиск среди списка `--dpi-desync-split-pos` осуществляется особым образом. Сначала сверяются все относительные маркеры. Если среди них найден подходящий, применяется он. В противном случае сверяются все абсолютные маркеры. Если и среди них ничего не найдено, применяется позиция 1. Например, можно написать `--dpi-desync-split-pos=method+2,midsld,5`. Если протокол http, разбиение будет на позиции `method+2`. Если протокол TLS - на позиции `midsld`. Если протокол неизвестен и включено `--dpi-desync-any-protocol`, разбиение будет на позиции 5. Чтобы все было однозначнее, можно использовать разные профили для разных протоколов и указывать только одну позицию, которая точно есть в этом протоколе. ### ПЕРЕКРЫТИЕ SEQUENCE NUMBERS `seqovl` добавляет в начало одного из TCP сегментов `seqovl` байт со смещенным в минус sequence number на величину `seqovl`. Для `split` - в начало первого сегмента, для `disorder` - в начало предпоследнего отсылаемого сегмента (второго в оригинальном порядке следования). В случае `split` расчет идет на то, что предыдущий отсыл, если он был, уже попал в сокет серверного приложения, поэтому новая пришедшая часть лишь частично находится в пределах текущего окна (in-window). Спереди фейковая часть отбрасывается, а оставшаяся часть содержит оригинал и начинается с начала window, поэтому попадает в сокет. Серверное приложение получает все, что реально отсылает клиент, отбрасывая фейковую out-of-window часть. Но DPI не может этого понять, поэтому у него происходит sequence десинхронизация. Обязательно, чтобы первый сегмент вместе с `seqovl` не превысили длину MTU. Эта ситуация распознается автоматически в Linux, и `seqovl` отменяется. В остальных системах ситуация не распознается, и это приведет к поломке соединения. Поэтому выбирайте первую позицию сплита и `seqovl` таким образом, чтобы MTU не был превышен в любом случае. Иначе дурение может не работать или работать хаотично. Для `disorder` overlap идет на предпоследнюю отсылаемую часть пакета. Для простоты будем считать, что разбиение идет на 2 части, шлются они в порядке "2 1" при оригинальном порядке "1 2". Обязательно, чтобы `seqovl` был меньше позиции первого сплита, иначе все отосланное будет передано в сокет сразу же, включая фейк, ломая протокол прикладного уровня. Такая ситуация легко обнаруживается программой, и `seqovl` отменяется. Увеличение размера пакета невозможно в принципе. При соблюдении условия 2-я часть пакета является полностью in-window, поэтому серверная ОС принимает ее целиком, включая фейк. Но поскольку начальная часть данных из 1 пакета еще не принята, то фейк и реальные данные остаются в памяти ядра, не отправляясь в серверное приложение. Как только приходит 1-я часть пакета, она переписывает фейковую часть в памяти ядра. Ядро получает данные из 1 и 2 части, поэтому далее идет отправка в сокет приложения. Таково поведение всех unix ОС, кроме solaris - оставлять последние принятые данные. Windows оставляет старые данные, поэтому disorder с seqovl будет приводить к зависаниям соединения при работе с Windows серверами. Solaris практически мертв, windows серверов очень немного. Можно использовать листы при необходимости. Метод позволяет обойтись без fooling и TTL. Фейки перемешаны с реальным данными. `fakedsplit/fakeddisorder` по-прежнему добавляют дополнительные отдельные фейки. `seqovl` в варианте `split` может быть только абсолютным положительным значением, поскольку применяется только в первому пакету. В варианте `disorder` допустимо применение всех вариантов маркеров. Они автоматически нормализуются к текущему пакету в серии. Можно сплитать на `midsld` и делать seqovl на `midsld-1`. ### НАЗНАЧЕНИЕ IP_ID Некоторые DPI секут поле ipv4 заголовка ip_id. Защита заключается в распознавании нехарактерного для разных ОС порядка назначения ip_id, но характерного для некоторого anti-DPI софта. Обычно ОС инкрементируют ip_id для каждого следующего пакета. Например, на ТСПУ повторение ненулевых ip_id фейка и не фейка вызывает триггер блока на диапазонах IP `googlevideo.com`. Если отсылаются фейки или дополнительные tcp сегменты, то в любом случае последовательность будет нарушена, поскольку ОС ничего не будет знать о всунутых фейках и не увеличит свой счетчик ip_id на количество фейков или дополнительных tcp сегментов. Чтобы сохранить последовательность, потребовалось бы перехватывать все соединение до конца, что очень затратно по ресурсам. Поэтому после отработки серии генерированных пакетов ip_id возвращается к тому значению, о котором знает ОС. Параметр `ip-id` относится к профилю и задает режим назначения ip_id при отсылке генерированных в nfqws пакетов. * `seq` (по умолчанию) : взять последний ip_id реального пакета. последующие генерированыне пакеты получают увеличенные на 1 ip_id, кроме случая `multidisorder`. для `multidisorder` в пределах сегментов, где есть сплит-позиции, значение ip_id увеличивается на количество частей, затем уменьшается на 1 с каждой отосланной частью. * `seqgroup` : то же, что и `seq`, но фейки того же размера, что и оригинальные сегменты, маскирующиеся под оригинал получают те же ip_id. * `rnd` : всем генерированным пакетам назначать случайный ip_id * `zero` : всем генерированным пакетам назначать ip_id=0 . в этом случае Linux и BSD отошлют 0, Windows назначит последовательные ip_id всем пакетам (тем самым автоматически решается проблема сбоя счетчика пакетов). В заголовках ipv6 поле ip_id отсутствует, параметр игнорируется для ipv6. ### СПЕЦИФИЧЕСКИЕ РЕЖИМЫ IPV6 Режимы десинхронизации `hopbyhop`, `destopt` и `ipfrag1` (не путать с fooling !) относятся только к ipv6 и заключается в добавлении хедера `hop-by-hop options`, `destination options` или `fragment` во все пакеты, попадающие под десинхронизацию. Здесь надо обязательно понимать, что добавление хедера увеличивает размер пакета, потому не может быть применено к пакетам максимального размера. Это имеет место при передаче больших сообщений. В случае невозможности отослать пакет дурение будет отменено, пакет будет выслан в оригинале. Расчет идет на то, что DPI увидит 0 в поле next header основного заголовка `ipv6` и не будет скакать по extension хедерам в поисках транспортного хедера. Таким образом не поймет, что это tcp или udp, и пропустит пакет без анализа. Возможно, какие-то DPI на это купятся. Может сочетаться с любыми режимами 2-й фазы, кроме варианта `ipfrag1+ipfrag2`. Например, `hopbyhop,multisplit` означает разбить tcp пакет на несколько сегментов, в каждый из них добавить hop-by-hop. При `hopbyhop,ipfrag2` последовательность хедеров будет : `ipv6,hop-by-hop`,`fragment`,`tcp/udp`. Режим `ipfrag1` может срабатывать не всегда без специальной подготовки. См. раздел `IP фрагментация`. ### МОДИФИКАЦИЯ ОРИГИНАЛА Параметры `--orig-ttl` и `--orig-ttl6` позволяют изменить TTL оригинальных пакетов. Если дальнейшие манипуляции связаны с оригиналом, например, идет TCP сегментация, то исходными данными являются измененные оригинальные пакеты. То есть в данном примере TCP сегменты пойдут с измененным TTL. Вариант `--orig-autottl` и `--orig-autottl6` работает аналогично `dpi-desync-autottl`, но по оригинальным пакетам. Дельту стоит указывать положительную с унарным знаком `+`, иначе оригинал не дойдет до сервера, и вы вообще ничего не получите. Пример : `--orig-autottl=+5:3-64`. `--orig-mod-start` и `--orig-mod-cutoff` задают ограничитель по началу и концу модификации оригинала. Схема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`. Функция может быть полезна, когда DPI охотится за фейками и блокирует соединение при наличии подозрительных признаков, в частности, измененный TTL у фейка относительно оригинала. ### ДУБЛИКАТЫ Дубликаты - это копии оригинальных пакетов, высылаемые перед ними. Включаются параметром `--dup=N`, где N - количество дублей, не включающее оригинал. `--dup-replace` отключает отсылку оригинала. Отсылка дублей имеет место только в тех случаях, когда высылается и оригинал без реконструкции. Например, если случилась TCP сегментация, то оригинал фактически дропается и заменяется искусственно сконструированными сегментами. Дубли высланы не будут. Это же касается изменения состава хедеров ipv6, режима tamper для DHT и других. Возможно применение всех вариантов дурения, как и для desync : `--dup-ttl`. `--dup-ttl6`, `--dup-fooling`. Нужно ли, чтобы эти пакеты доходили до сервера и в каком виде, решаете вы согласно задуманной стратегии. Вариант `--dup-autottl` и `--dup-autottl6` работает аналогично `dpi-desync-autottl`, но по дублям. Дельту можно указывать положительную с унарным знаком `+`, а можно и отрицательную. Зависит от вашей задумки. Пример : `--dup-autottl=-2:3-64`. `--dup-start` и `--dup-cutoff` задают ограничитель по началу и концу применения стратегии дубликатов. Схема аналогична `--dpi-desync-start` и `--dpi-desync-cutoff`. Функция может помочь, когда DPI сечет разницу в характеристиках фейков и оригинала. Дубликатами можно попытаться заставить DPI принять , что весь сеанс идет аномальным. Например, у нас имеется TCP сеанс с MD5 сразу с первого SYN пакета. Значит последующие MD5 будут восприниматься нормально. ### КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ В параметре dpi-desync можно указать до 3 режимов через запятую. * 0 фаза - предполагает работу на этапе установления соединения : `synack`, `syndata`, `--wsize`, `--wssize`. На эту фазу не действуют фильтры по [hostlist](#множественные-стратегии), кроме случая, описанного [далее](#кэш-ip). * 1 фаза - отсылка чего-либо до оригинального пакета данных : `fake`, `rst`, `rstack`. * 2 фаза - отсылка в модифицированном виде оригинального пакета данных (например, `fakedsplit` или `ipfrag2`). Режимы требуют указания в порядке возрастания номеров фаз. ### КЭШ IP ipcache представляет собой структуру в памяти процесса, позволяющую по ключу IP адреса и имени интерфейса запоминать некоторую информацию, которую впоследствии можно извлечь и использовать как недостающие данные. На текущий момент это применяются в следующих ситуациях : 1. IP,interface => hop count . Кэшируется количество хопов до сервера для последующего применения в autottl прямо с первого пакета, когда еще ответа не было. Пока записи в кэше нет, autottl не будет применен сразу. При повторном запросе до истечения времени жизни записи autottl будет применение сразу. 2. IP => hostname . Кэшируется имя хоста, вне привязки к интерфейсу, для последующего применения в стратегиях нулевой фазы. Режим отключен по умолчанию и включается через параметры `ipcache-hostname`. Данная техника является экспериментальной. Ее проблема в том, что как такового нет однозначного соответствия между доменом и IP. Множество доменов могут ссылаться на тот же IP адрес. При коллизии происходит замещение имени хоста на последний вариант. Домен может скакать по разным IP на CDN. Сейчас один адрес, через час - другой. Эта проблема решается через время жизни записей кэша : `--ipcache-lifetime`. По умолчанию 2 часа. Однако, может случиться и так, что в вашем случае применение техники несет больше пользы, чем проблем. Будьте готовы к непонятному на первый взгляд поведению, которое может быть исследовано только через `--debug` лог. При подаче сигнала SIGUSR2 процесс выводит содержимое ipcache на консоль. ### РЕАКЦИЯ DPI НА ОТВЕТ СЕРВЕРА Есть DPI, которые анализируют ответы от сервера, в частности сертификат из ServerHello, где прописаны домены. Подтверждением доставки ClientHello является ACK пакет от сервера с номером ACK sequence, соответствующим длине ClientHello+1. В варианте disorder обычно приходит сперва частичное подтверждение (SACK), потом полный ACK. Если вместо ACK или SACK идет RST пакет с минимальной задержкой, то DPI вас отсекает еще на этапе вашего запроса. Если RST идет после полного ACK спустя задержку, равную примерно пингу до сервера, тогда вероятно DPI реагирует на ответ сервера. DPI может отстать от потока, если ClientHello его удовлетворил и не проверять ServerHello. Тогда вам повезло. Вариант fake может сработать. Если же он не отстает и упорно проверяет ServerHello, то можно попробовать заставить сервер высылать ServerHello частями через параметр `--wssize` (см. conntrack). Если и это не помогает, то сделать с этим что-либо вряд ли возможно без помощи со стороны сервера. Лучшее решение - включить на сервере поддержку TLS 1.3. В нем сертификат сервера передается в зашифрованном виде. Это рекомендация ко всем админам блокируемых сайтов. Включайте TLS 1.3. Так вы дадите больше возможностей преодолеть DPI. ### РЕЖИМ SYNACK В документации по geneva это называется "TCB turnaround". Попытка ввести DPI в заблуждение относительно ролей клиента и сервера. Поскольку режим нарушает работу NAT, техника может сработать только если между атакующим устройством и DPI нет NAT. Атака не сработает через NAT роутер, но может сработать с него. Для реализации атаки на проходящий трафик требуются nftables и схема [POSTNAT](#nftables-для-nfqws). ### РЕЖИМ SYNDATA Тут все просто. Добавляются данные в пакет SYN. Все ОС их игнорируют, если не используется TCP fast open (TFO), а DPI может воспринять, не разобравшись есть там TFO или нет. Оригинальные соединения с TFO не трогаются, поскольку это их точно сломает. Без уточняющего параметра добавляются 16 нулевых байтов. ### ВИРТУАЛЬНЫЕ МАШИНЫ Изнутри VM от virtualbox и vmware в режиме NAT не работают многие техники пакетной магии nfqws. Принудительно заменяется ttl, не проходят фейк пакеты. Необходимо настроить сеть в режиме bridge. ### CONNTRACK nfqws оснащен ограниченной реализацией слежения за состоянием tcp соединений (conntrack). Он включается для реализации некоторых методов противодействия DPI. conntrack способен следить за фазой соединения : SYN,ESTABLISHED,FIN, количеством пакетов в каждую сторону, sequence numbers. conntrack способен "кормиться" пакетами в обе или только в одну сторону. Соединение попадает в таблицу при обнаружении пакетов с выставленными флагами SYN или SYN,ACK. Поэтому если необходим conntrack, в правилах перенаправления iptables соединение должно идти на nfqws с самого первого пакета, хотя затем может обрываться по фильтру connbytes. Для UDP инициатором попадания в таблицу является первый UDP пакет. Он же и определяет направление потока. Считается, что первый UDP пакет исходит от клиента к серверу. Далее все пакеты с совпадающими `src_ip,src_port,dst_ip,dst_port` считаются принадлежащими этому потоку до истечения времени неактивности. conntrack - простенький, он не писался с учетом всевозможных атак на соединение, он не проверяет пакеты на валидность sequence numbers или чексумму. Его задача - лишь обслуживание нужд nfqws, он обычно кормится только исходящим трафиком, потому нечувствителен к подменам со стороны внешней сети. Соединение удаляется из таблицы, как только отпадает нужда в слежении за ним или по таймауту неактивности. Существуют отдельные таймауты на каждую фазу соединения. Они могут быть изменены параметром `--ctrack-timeouts`. `--wssize` позволяет изменить с клиента размер tcp window для сервера, чтобы он послал следующие ответы разбитыми на части. Чтобы это подействовало на все серверные ОС, необходимо менять window size в каждом исходящем с клиента пакете до отсылки сообщения, ответ на которое должен быть разбит (например, TLS ClientHello). Именно поэтому и необходим conntrack, чтобы знать когда надо остановиться. Если не остановиться и все время устанавливать низкий wssize, скорость упадет катастрофически. В linux это может быть купировано через connbytes, но в BSD системах такой возможности нет. В случае http(s) останавливаемся сразу после отсылки первого http запроса или TLS ClientHello. Если вы имеете дело с не http(s), то вам потребуется параметр `--wssize-cutoff`. Он устанавливает предел, с которого действие wssize прекращается. Префикс d перед номером означает учитывать только пакеты с data payload, префикс s - relative sequence number, проще говоря количество переданных клиентом байтов + 1. Если проскочит пакет с http request или TLS ClientHello, действие wssize прекращается сразу же, не дожидаясь wssize-cutoff, если не указан параметр `--wssize-forced-cutoff=0`. Если ваш протокол склонен к долгому бездействию, следует увеличить таймаут фазы ESTABLISHED через параметр `--ctrack-timeouts`. Таймаут по умолчанию низкий - всего 5 минут. Не забывайте, что nfqws кормится приходящими на него пакетами. Если вы ограничили поступление пакетов через connbytes, то в таблице могут остаться повисшие соединения в фазе ESTABLISHED, которые отвалятся только по таймауту. Для диагностики состояния conntrack пошлите сигнал SIGUSR1 процессу nfqws : `killall -SIGUSR1 nfqws`. Текущая таблица будет выведена nfqws в stdout. Обычно в SYN пакете клиент отсылает кроме window size еще и TCP extension `scaling factor`. **scaling factor** представляет из себя степень двойки, на которую умножается window size : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... В параметре wssize scaling factor указывается через двоеточие. Scaling factor может только снижаться, увеличение заблокировано, чтобы не допустить превышение размера окна со стороны сервера. Для принуждения сервера к фрагментации ServerHello, чтобы избежать просекание имени сервера из сертификата сервера на DPI, лучше всего использовать `--wssize=1:6`. Основное правило - делать `scale_factor` как можно больше, чтобы после восстановления window size итоговый размер окна стал максимально возможным. Если вы сделаете 64:0, будет очень медленно. С другой стороны нельзя допустить, чтобы ответ сервера стал достаточно большим, чтобы DPI нашел там искомое. `--wssize` не работает в профилях с хостлистами, поскольку он действует с самого начала соединения, когда еще нельзя принять решение о попадании в лист. Однако, профиль с auto hostlist может содержать --wssize. `--wssize` может замедлять скорость и/или увеличивать время ответа сайтов, поэтому если есть другие работающие способы обхода DPI, лучше применять их. `--dpi-desync-cutoff` позволяет задать предел, при достижении которого прекращается применение dpi-desync. Доступны префиксы n,d,s по аналогии с `--wssize-cutoff`. Полезно совместно с `--dpi-desync-any-protocol=1`. На склонных к бездействию соединениях следует изменить таймауты conntrack. Если соединение выпало из conntrack и задана опция `--dpi-desync-cutoff`, `dpi desync` применяться не будет. ### РЕАССЕМБЛИНГ nfqws поддерживает реассемблинг некоторых видов запросов. На текущий момент это TLS и QUIC ClientHello. Они бывают длинными, если в chrome включить пост-квантовую криптографию tls-kyber, и занимают, как правило, 2 или 3 пакета. kyber включен по умолчанию, начиная с chromium 124. chrome рандомизирует фингерпринт TLS. SNI может оказаться как в начале, так и в конце, то есть попасть в любой пакет. stateful DPI обычно реассемблирует запрос целиком, и только потом принимает решение о блокировке. В случае получения TLS или QUIC пакета с частичным ClientHello начинается процесс сборки, а пакеты задерживаются и не отсылаются до ее окончания. По окончании сборки пакеты проходит через десинхронизацию на основании полностью собранного ClientHello. При любой ошибке в процессе сборки задержанные пакеты немедленно отсылаются в сеть, а десинхронизация отменяется. Есть специальная поддержка всех вариантов tcp сплита для многосегментного TLS. Если указать позицию сплита больше длины первого пакета, то разбивка происходит не обязательно первого пакета, а того, на который пришлась итоговая позиция. Если, допустим, клиент послал TLS ClientHello длиной 2000, SNI начинается с 1700, и заданы опции `fake,multisplit`, то перед первым пакетом идет fake, затем первый пакет в оригинале, а последний пакет разбивается на 2 сегмента. В итоге имеем фейк в начале и 3 реальных сегмента. ### ПОДДЕРЖКА UDP Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip. Для UDP действуют только режимы десинхронизации `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`, `ipfrag2`, `udplen`, `tamper`. Режимами первой фазы являются `fake`, `fakeknown`, `hopbyhop`, `destopt`, `ipfrag1`. Второй фазы - `ipfrag2`, `udplen`, `tamper`. Как обычно, возможно сочетание режимов первой и второй фазы, но не двух режимов одной фазы. `udplen` увеличивает размер udp пакета на указанное в `--dpi-desync-udplen-increment` количество байтов. Паддинг заполняется нулями по умолчанию, но можно задать свой паттерн. Предназначено для обмана DPI, ориентирующегося на размеры пакетов. Может сработать, если пользовательский протокол не привязан жестко к размеру udp пейлоада. Режим tamper означает модификацию пакетов известных протоколов особенным для протокола образом. На текущий момент работает только с DHT. Поддерживается определение пакетов QUIC Initial с расшифровкой содержимого и имени хоста, то есть параметр `--hostlist` будет работать. Определяются пакеты wireguard handshake initiation, DHT (начинается с 'd1', кончается 'e'), STUN и [Discord Voice IP Discovery](https://discord.com/developers/docs/topics/voice-connections#ip-discovery). Для десинхронизации других протоколов обязательно указывать `--dpi-desync-any-protocol`. Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp можно изменить 4-м параметром в `--ctrack-timeouts`. Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов. По умолчанию fake наполнение - 64 нуля. Можно указать файл в `--dpi-desync-fake-unknown-udp`. ### IP ФРАГМЕНТАЦИЯ Современная сеть практически не пропускает фрагментированные tcp на уровне ip. На udp с этим дело получше, поскольку некоторые udp протоколы могут опираться на этот механизм (IKE старых версий). Однако, кое-где бывает, что режут и фрагментированный udp. Роутеры на базе linux могут самопроизвольно собирать или перефрагментировать пакеты. Позиция фрагментации задается отдельно для tcp и udp. По умолчанию 24 и 8 соответственно, должна быть кратна 8. Смещение считается с транспортного заголовка. Существует ряд моментов вокруг работы с фрагментами на Linux, без понимания которых может ничего не получиться. ipv4 : Linux дает отсылать ipv4 фрагменты, но стандартные настройки iptables в цепочке OUTPUT могут вызывать ошибки отправки. ipv6 : Нет способа для приложения гарантированно отослать фрагменты без дефрагментации в conntrack. На разных системах получается по-разному. Где-то нормально уходят, где-то пакеты дефрагментируются. Для ядер <4.16 похоже, что нет иного способа решить эту проблему, кроме как выгрузить модуль `nf_conntrack`, который подтягивает зависимость `nf_defrag_ipv6`. Он то как раз и выполняет дефрагментацию. Для ядер 4.16+ ситуация чуть лучше. Из дефрагментации исключаются пакеты в состоянии NOTRACK. Чтобы не загромождать описание, смотрите пример решения этой проблемы в `blockcheck.sh`. Иногда требуется подгружать модуль `ip6table_raw` с параметром `raw_before_defrag=1`. В OpenWrt параметры модулей указываются через пробел после их названий в файлах `/etc/modules.d`. В традиционных системах посмотрите используется ли `iptables-legacy` или `iptables-nft`. Если legacy, то нужно создать файл `/etc/modprobe.d/ip6table_raw.conf` с содержимым : ``` options ip6table_raw raw_before_defrag=1 ``` В некоторых традиционных дистрибутивах можно изменить текущий ip6tables через : update-alternatives --config ip6tables Если вы хотите оставаться на iptables-nft, вам придется пересобрать патченную версию. Патч совсем небольшой. В `nft.c` найдите фрагмент: ``` { .name = "PREROUTING", .type = "filter", .prio = -300, /* NF_IP_PRI_RAW */ .hook = NF_INET_PRE_ROUTING, }, { .name = "OUTPUT", .type = "filter", .prio = -300, /* NF_IP_PRI_RAW */ .hook = NF_INET_LOCAL_OUT, }, ``` и замените везде -300 на -450. Это нужно сделать вручную, никакой автоматики в `blockcheck.sh` нет. Либо можно раз и навсегда избавиться от этой проблемы, используя `nftables`. Там можно создать `netfilter hook` с любым приоритетом. Используйте приоритет -401 и ниже. При использовании iptables и NAT, похоже, что нет способа прицепить обработчик очереди после NAT. Пакет попадает в nfqws с source адресом внутренней сети, затем фрагментируется и уже не обрабатывается NAT. Так и уходит во внешнюю сеть с src ip 192.168.x.x. Следовательно, метод не срабатывает. Видимо единственный рабочий метод - отказаться от iptables и использовать nftables. Хук должен быть с приоритетом 101 или выше. ### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ **nfqws** способен по-разному реагировать на различные запросы и применять разные стратегии дурения. Это реализовано посредством поддержки множества профилей дурения. Профили разделяются в командной строке параметром `--new`. Первый профиль создается автоматически. Для него не нужно `--new`. Каждый профиль имеет фильтр. По умолчанию он пуст, то есть профиль удовлетворяет любым условиям. Фильтр может содержать жесткие параметры: версия ip протокола, ipset и порты tcp/udp. Они всегда однозначно идентифицируются даже на нулевой фазе десинхронизации, когда еще хост и L7 неизвестны. В качестве мягкого фильтра могут выступать хост-листы и протокол прикладного уровня (l7). L7 протокол становится известен обычно после первого пакета с данными. При поступлении запроса идет проверка профилей в порядке от первого до последнего до достижения первого совпадения с фильтром. Жесткие параметры фильтра сверяются первыми. При несовпадении идет сразу же переход к следующему профилю. Если какой-то профиль удовлетворяет жесткому фильтру и L7 фильтру и содержит авто-хостлист, он выбирается сразу. Если профиль удовлетворяет жесткому фильтру и L7 фильтру, для него задан хостлист, и у нас еще нет имени хоста, идет переход к следующему профилю. В противном случае идет проверка по хостлистам этого профиля. Если имя хоста удовлетворяет листам, выбирается этот профиль. Иначе идет переход к следующему. Может так случиться, что до получения имени хоста или узнавания L7 протокола соединение идет по одному профилю, а при выяснении этих параметров профиль меняется на лету. Это может произойти даже дважды - при выяснении L7 и имени хоста. Чаще всего это выяснение совмещается в одно действие, поскольку по одному пакету, как правило, узнается и L7, и хост. Поэтому если у вас есть параметры дурения нулевой фазы, тщательно продумывайте что может произойти при переключении стратегии. Смотрите debug log, чтобы лучше понять что делает nfqws. Нумерация профилей идет с 1 до N. Последним в цепочке создается пустой профиль с номером 0. Он используется, когда никакие условия фильтров не совпали. > [!IMPORTANT] > Множественные стратегии создавались только для случаев, когда невозможно обьединить > имеющиеся стратегии для разных ресурсов. Копирование стратегий из blockcheck для разных сайтов > во множество профилей без понимания как они работают приведет к нагромождению параметров, которые все равно > не покроют все возможные заблокированные ресурсы. Вы только увязните в этой каше. > [!IMPORTANT] > user-mode реализация ipset создавалась не как удобная замена *nix версии, реализованной в ядре. > Вариант в ядре работает гораздо эффективнее. Это создавалось для систем без поддержки ipset в ядре. > Конкретно - Windows и ядра Linux, собранные без nftables и ipset модулей ядра. Например, в android нет ipset. ### ФИЛЬТРАЦИЯ ПО WIFI Имя wifi сети никак не связано с сетевым интерфейсом адаптера wifi. Интерфейс один, подключиться можно к любой сети. Для разных сетей разные стратегии. Стратегия от сети A не работает или ломает сеть B. Что делать ? Можно вручную запускать и снимать инстансы nfqws. Но можно поступить иначе. В windows версии winws есть глобальный фильтр `--ssid-filter`. Он включает или отключает инстанс winws в зависимости от подключенности любого адаптера к конкретной wifi сети. При этом не учитывается маршрутизация. Такой подход возможен потому, что к windivert можно прицепить несколько инстансов winws на пересекающихся фильтрах. При смене wifi сети одни будут включаться, другие выключаться. Для linux применяется иное решение. Фильтр `--filter-ssid` относится к конкретному профилю. Невозможно повесить несколько инстансов nfqws на одну и ту же очередь или направить один и тот же трафик на несколько очередей. Подключение и отключение от очереди разных инстансов сопряжено со сложностями синхронизации между ними. Поэтому обрабатывать трафик должен один инстанс, и он должен уметь работать с разными wifi сетями. Это и реализовано в параметре `--filter-ssid`. Он берет список имен wifi сетей (SSID) через запятую аналогично `--ssid-filter` для winws. При выборе профиля имеет значение куда идет конкретный обрабатываемый пакет. На какой интерфейс. Или с какого интерфейса пакет пришел, если он считается входящим. Поэтому даже если у вас часть трафика идет на одну сеть, часть на другую, а часть вообще не идет по wifi, то все это можно настроить. Сканируются все wifi интерфейсы, составляется список interface->SSID. Он обновляется по мере поступления пакетов, но не чаще 1 раза в секунду. ### IPTABLES ДЛЯ NFQWS > [!CAUTION] > Начиная с ядер Linux 6.17 присутствует параметр конфигурации ядра CONFIG_NETFILTER_XTABLES_LEGACY, который по умолчанию в дистрибутиве может быть "not set". Отсутствие этой настройки выключает iptables-legacy. Это часть процесса депрекации iptables. Тем не менее iptables-nft будут работать, поскольку используют backend nftables. iptables для задействования атаки на первые пакеты данных в tcp соединении : ``` iptables -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 ``` Этот вариант применяем, когда DPI не следит за всеми запросами http внутри keep-alive сессии. Если следит, направляем только первый пакет от https и все пакеты от http : ``` iptables -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 iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass ``` mark нужен, чтобы сгенерированный поддельный пакет не попал опять к нам на обработку. nfqws выставляет fwmark при его отсылке. Хотя nfqws способен самостоятельно различать помеченные пакеты, фильтр в iptables по mark нужен при использовании connbytes, чтобы не допустить изменения порядка следования пакетов. Процессинг очереди - процесс отложенный. Если ядро имеет пакеты на отсылку вне очереди - оно их отправляет незамедлительно. Изменение правильного порядка следования пакетов при десинхронизации ломает всю идею. Так же были замечены дедлоки при достаточно большой отсылке пакетов из nfqws и отсутствии mark фильтра. Процесс может зависнуть. Поэтому наличие фильтра по mark в ip/nf tables можно считать обязательным. Почему `--connbytes 1:6` : * 1 - для работы методов десинхронизации 0-й фазы и корректной работы conntrack * 2 - иногда данные идут в 3-м пакете 3-way handshake * 3 - стандартная ситуация приема одного пакета запроса * 4-6 - на случай ретрансмиссии или запроса длиной в несколько пакетов (TLSClientHello с kyber, например) Для режима autottl необходимо перенаправление входящего `SYN,ACK` пакета или первого пакета соединения (что обычно есть то же самое). Для режима autohostlist необходимы входящие RST и http redirect. Можно построить фильтр на tcp flags для выделения `SYN,ACK` и модуле u32 для поиска характерных паттернов http redirect, но проще использовать connbytes для выделения нескольких начальных входящих пакетов. ` iptables -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 ` Для quic : ``` iptables -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 ``` 6 пакетов берется, чтобы покрыть случаи возможных ретрансмиссий quic initial в случае плохой связи или если сервер плохо себя чувствует, а приложение настаивает именно на quic, не переходя на tcp. А так же для работы autohostlist по quic. Однако, autohostlist для quic не рекомендуется. ### NFTABLES ДЛЯ NFQWS Можно начать с базовой конфигурации. ``` IFACE_WAN=wan nft create table inet ztest nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" nft 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 nft 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 # auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" nft add rule inet ztest pre iifname $IFACE_WAN tcp sport "{80,443}" ct reply packets 1-3 queue num 200 bypass ``` Для задействования IP фрагментации и `datanoack` на проходящие пакеты требуется особая конфигурация цепочек, перенаправляющая пакеты после NAT. В скриптах zapret эта схема называется `POSTNAT`, и она возможна только на nftables. Сгенерированные nfqws пакеты требуется на раннем этапе помечать как **notrack**, чтобы они не были испорчены NAT. ``` IFACE_WAN=wan nft create table inet ztest nft add chain inet ztest postnat "{type filter hook postrouting priority srcnat+1;}" nft 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 nft 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 nft add chain inet ztest predefrag "{type filter hook output priority -401;}" nft add rule inet ztest predefrag "mark & 0x40000000 != 0x00000000 notrack" ``` Удаление тестовой таблицы : ``` nft delete table inet ztest ``` ### FLOW OFFLOADING Если ваше устройство поддерживает аппаратное ускорение (flow offloading, hardware nat, hardware acceleration), то iptables могут не работать. При включенном offloading пакет не проходит по обычному пути netfilter. Необходимо или его отключить, или выборочно им управлять. В новых ядрах присутствует software flow offloading (SFO). Пакеты, проходящие через SFO, так же проходят мимо большей части механизмов iptables. При включенном SFO работает DNAT/REDIRECT (tpws). Эти соединения исключаются из offloading. Однако, остальные соединения идут через SFO, потому NFQUEUE будет срабатывать только до помещения соединения в flowtable. Практически это означает, что почти весь функционал nfqws работать не будет. Offload включается через специальный target в iptables `FLOWOFFLOAD` или через flowtable в nftables. Не обязательно пропускать весь трафик через offload. tpws и так обходит offload "by design", а для отработки nfqws достаточно первых нескольких пакетов в tcp соединении или udp сеансе. Пока сеанс не направлен на offload, он процессится обычным образом через полноценный netfilter. Как только срабатывает правило offload по любому входящему или исходящему пакету, весь сеанс окончательно уходит из netfilter в offload. Поэтому скрипты zapret берут правила для NFQUEUE, что они создали, и из них создают exemption правила, которые не дают раньше времени попасть сеансу в offload, а потом его "отпускают". При этом входящим пакетам не дают начать offload, триггером выступают только исходящие пакеты. Эта схема обеспечивает практически нулевой негативный эффект на скорость, одновременно покрывая нужды nfqws и упрощая правила таблиц. OpenWrt не предусматривает выборочного управления offload, поэтому скрипты zapret поддерживают свою систему выборочного управления. iptables target `FLOWOFFLOAD` - это проприетарное изобретение OpenWrt. Управление offload в nftables реализовано в базовом ядре linux без патчей. nftables - единственный способ включения offload на классическом Linux. ### ОСОБЕННОСТИ ЖЕЛЕЗОК На устройствах mediatek замечены 2 проблемы. Драйвер mediatek ethernet отбрасывает tcp и udp пакеты с неверной чексуммой на аппаратном уровне, это не отключается. Как следствие не будет работать fooling badsum через роутер, но будет с него. Другая проблема mediatek, затрагивающая как ethernet, так и wireless, проявляется на udp, когда включен offload rx-gro-list. Пока отсутствует nfqueue, все хорошо. Как только nfqueue появляется, часть пакетов выпадает. Особенно заметно это проявляется на дурении QUIC с kyber.
shell код лечения ``` append_separator_list() { # $1 - var name to receive result # $2 - separator # $3 - quoter # $4,$5,... - elements local _var="$1" sep="$2" quo="$3" i eval i="\$$_var" shift; shift; shift while [ -n "$1" ]; do if [ -n "$i" ] ; then i="$i$sep$quo$1$quo" else i="$quo$1$quo" fi shift done eval $_var="\$i" } resolve_lower_devices() { # $1 - bridge interface name [ -d "/sys/class/net/$1" ] && { find "/sys/class/net/$1" -follow -maxdepth 1 -name "lower_*" | { local l lower lowers while read lower; do lower="$(basename "$lower")" l="${lower#lower_*}" [ "$l" != "$lower" ] && append_separator_list lowers ' ' '' "$l" done printf "$lowers" } } } # it breaks nfqueue lans=$(resolve_lower_devices br-lan) for int in $lans; do ethtool -K $int rx-gro-list off done ```
Этот код нужно вызывать после вставания интерфейса LAN, когда все bridge members уже занесены в bridge. Можно использовать хук в `/etc/hotplug.d/iface`. Должен быть установлен `ethtool`. Проблемы mediatek были подтверждены на MT7621 (TP-Link Archer C6U v1) и MT7981 (Xiaomi AX3000T). Другие чипсеты могут быть так же подвержены проблеме, а могут и не быть. Более широкой статистики нет. ### ДУРЕНИЕ СО СТОРОНЫ СЕРВЕРА Это тоже возможно. nfqws рассчитан на атаку со стороны клиента, поэтому он распознает прямой и обратный трафик на основании роли в установлении tcp соединения. Если проходит SYN, то source IP - это клиент. Если проходит SYN,ACK , то source IP - это сервер. Для UDP клиентом считается source IP первого прошедшего пакета по двум связкам ip-port. На сервере трафиком клиента будет считаться принятый трафик, а трафиком сервера - исходящий. `--wsize` работает в любом случае, он может использоваться как на клиенте, так и на сервере. Остальные техники работают только если nfqws считает трафик трафиком клиента. Поэтому для их применения по исходящему с сервера трафику conntrack нужно выключить параметром `--ctrack-disable`. Если пакет не найден в conntrack, по нему идет работа как по пакету клиента. Большинство протоколов опознаваться не будет, потому что система их опознавания рассчитана на содержание пакетов от клиента. Чтобы задействовать техники типа `fake` или `multisplit` нужно использовать `--dpi-desync-any-protocol` с ограничителем connbytes или с ограничителем на основании содержания пакета или его заголовков. start/cutoff недоступны, поскольку завязаны на conntrack. Техника `synack-split` позволяет разбить tcp сегмент SYN,ACK на отдельные части с SYN и с ACK. В ответ на это клиент шлет SYN,ACK , что обычно характеризует сервер. У некоторых DPI от этого может ломаться алгоритм, и они перестают блокировать запрещенный контент. Здесь [подробное описание](https://nmap.org/misc/split-handshake.pdf) что есть split handshake. Перенаправление трафика обычно идет по номеру source портов и направлению original. original - это исходящий с системы трафик, reply - входящий. ## tpws tpws - это transparent proxy. ``` @|$ ; читать конфигурацию из файла. опция должна быть первой. остальные опции игнорируются. --debug=0|1|2|syslog|@ ; 0,1,2 = логирование на косоль : 0=тихо, 1(default)=подробно, 2=отладка. --debug-level=0|1|2 ; указать уровень логирования для syslog и @ --dry-run ; проверить опции командной строки и выйти. код 0 - успешная проверка. --version ; вывести версию и выйти --daemon ; демонизировать прогу --pidfile= ; сохранить PID в файл --user= ; менять uid процесса --uid=uid[:gid] ; менять uid процесса --bind-addr ; на каком адресе слушать. может быть ipv4 или ipv6 адрес ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan --bind-linklocal=no|unwanted|prefer|force ; no : биндаться только на global ipv6 ; unwanted (default) : предпочтительно global, если нет - LL ; prefer : предпочтительно LL, если нет - global ; force : биндаться только на LL --bind-iface4= ; слушать на первом ipv4 интерфейса iface --bind-iface6= ; слушать на первом ipv6 интерфейса iface --bind-wait-ifup= ; ждать до N секунд появления и поднятия интерфейса --bind-wait-ip= ; ждать до N секунд получения IP адреса (если задан --bind-wait-ifup - время идет после поднятия интерфейса) --bind-wait-ip-linklocal= ; имеет смысл только при задании --bind-wait-ip ; --bind-linklocal=unwanted : согласиться на LL после N секунд ; --bind-linklocal=prefer : согласиться на global address после N секунд --bind-wait-only ; подождать все бинды и выйти. результат 0 в случае успеха, иначе не 0. --connect-bind-addr ; с какого адреса подключаться во внешнюю сеть. может быть ipv4 или ipv6 адрес ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan ; опция может повторяться для v4 и v6 адресов ; опция не отменяет правил маршрутизации ! выбор интерфейса определяется лишь правилами маршрутизации, кроме случая v6 link local. --socks ; вместо прозрачного прокси реализовать socks4/5 proxy --no-resolve ; запретить ресолвинг имен через socks5 --resolve-threads ; количество потоков ресолвера --port= ; на каком порту слушать --maxconn= ; максимальное количество соединений от клиентов к прокси --maxfiles= ; макс количество файловых дескрипторов (setrlimit). мин требование (X*connections+16), где X=6 в tcp proxy mode, X=4 в режиме тамперинга. ; стоит сделать запас с коэффициентом как минимум 1.5. по умолчанию maxfiles (X*connections)*1.5+16 --max-orphan-time= ; если вы запускаете через tpws торрент-клиент с множеством раздач, он пытается установить очень много исходящих соединений, ; большая часть из которых отваливается по таймауту (юзера сидят за NAT, firewall, ...) ; установление соединения в linux может длиться очень долго. локальный конец отвалился, перед этим послав блок данных, ; tpws ждет подключения удаленного конца, чтобы отослать ему этот блок, и зависает надолго. ; настройка позволяет сбрасывать такие подключения через N секунд, теряя блок данных. по умолчанию 5 сек. 0 означает отключить функцию ; эта функция не действует на успешно подключенные ранее соединения --local-rcvbuf= ; SO_RCVBUF для соединений client-proxy --local-sndbuf= ; SO_SNDBUF для соединений client-proxy --remote-rcvbuf= ; SO_RCVBUF для соединений proxy-target --remote-sndbuf= ; SO_SNDBUF для соединений proxy-target --nosplice ; не использовать splice на linux системах --skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split. --local-tcp-user-timeout= ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение) --remote-tcp-user-timeout= ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение) --fix-seg= ; исправлять неудачи tcp сегментации ценой задержек для всех клиентов и замедления. ждать до N мс. по умолчанию 30 мс. --ipcache-lifetime= ; время жизни записей кэша IP в секундах. 0 - без ограничений. --ipcache-hostname=[0|1] ; 1 или отсутствие аргумента включают кэширование имен хостов для применения в стратегиях нулевой фазы --split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации --split-any-protocol ; применять сегментацию к любым пакетам. по умолчанию - только к известным протоколам (http, TLS) --disorder[=http|tls] ; путем манипуляций с сокетом вынуждает отправлять первым второй сегмент разделенного запроса --oob[=http|tls] ; отправить байт out-of-band data (OOB) в конце первой части сплита --oob-data=|0xHEX ; переопределить байт OOB. по умолчанию 0x00. --hostcase ; менять регистр заголовка "Host:". по умолчанию на "host:". --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase --hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." --hosttab ; добавление табуляции после имени хоста : "Host: kinozal.tv\t" --hostnospace ; убрать пробел после "Host:" --hostpad= ; добавить паддинг-хедеров общей длиной перед Host: --domcase ; домен после Host: сделать таким : TeSt.cOm --methodspace ; добавить пробел после метода : "GET /" => "GET /" --methodeol ; добавить перевод строки перед методом : "GET /" => "\r\nGET /" --unixeol ; конвертировать 0D0A в 0A и использовать везде 0A --tlsrec=N|-N|marker+N|marker-N ; разбивка TLS ClientHello на 2 TLS records на указанной позиции. Минимальное смещение - 6. --mss= ; установить MSS для клиента. может заставить сервер разбивать ответы, но существенно снижает скорость --tamper-start=[n] ; начинать дурение только с указанной байтовой позиции или номера блока исходяшего потока (считается позиция начала принятого блока) --tamper-cutoff=[n] ; закончить дурение на указанной байтовой позиции или номере блока исходящего потока (считается позиция начала принятого блока) --hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются, если хост не начинается с '^'. ; в файле должен быть хост на каждой строке. ; список читается при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. ; при изменении времени модификации файла он перечитывается автоматически по необходимости ; список может быть запакован в gzip. формат автоматически распознается и разжимается ; списков может быть множество. пустой общий лист = его отсутствие ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. --hostlist-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. --hostlist-exclude= ; не применять дурение к доменам из листа. может быть множество листов. схема аналогична include листам. --hostlist-exclude-domains= ; фиксированный список доменов через зяпятую. можно использовать # в начале для комментирования отдельных доменов. --hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) --hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) --hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) --hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. --new ; начало новой стратегии (новый профиль) --skip ; не использовать этот профиль . полезно для временной деактивации профиля без удаления параметров. --filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии --filter-tcp=[~]port1[-port2]|* ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. поддерживается список через запятую. --filter-l7=[http|tls|quic|wireguard|dht|unknown] ; фильтр протокола L6-L7. поддерживается несколько значений через запятую. --ipset= ; включающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. --ipset-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. --ipset-exclude= ; исключающий ip list. на каждой строчке ip или cidr ipv4 или ipv6. поддерживается множество листов и gzip. перечитка автоматическая. --ipset-exclude-ip= ; фиксированный список подсетей через запятую. можно использовать # в начале для комментирования отдельных подсетей. ``` ### TCP СЕГМЕНТАЦИЯ В TPWS tpws, как и nfqws, поддерживает множественную сегментацию запросов. Сплит позиции задаются в `--split-pos`. Указываются маркеры через запятую. Описание маркеров см в разделе [nfqws](#tcp-сегментация). На прикладном уровне в общем случае нет гарантированного средства заставить ядро выплюнуть блок данных, порезанным в определенном месте. ОС держит буфер отсылки (SNDBUF) у каждого сокета. Если у сокета включена опция TCP_NODELAY и буфер пуст, то каждый send приводит к отсылке отдельного ip пакета или группы пакетов, если блок не вмещается в один ip пакет. Однако, если в момент send уже имеется неотосланный буфер, то ОС присоединит данные к нему, никакой отсылки отдельным пакетом не будет. Но в этом случае и так нет никакой гарантии, что какой-то блок сообщения пойдет в начале пакета, на что собственно и заточены DPI. Разбиение будет производиться согласно MSS, который зависит от MTU исходящего интерфейса. Таким образом DPI, смотрящие в начало поля данных TCP пакета, будут поломаны в любом случае. Протокол http относится к запрос-ответным протоколам. Новое сообщение посылается только тогда, когда сервер получил запрос и полностью вернул ответ. Значит запрос фактически был не только отослан, но и принят другой стороной, а следовательно буфер отсылки пуст, и следующие 2 send приведут к отсылке сегментов данных разными ip пакетами. Таким образом tpws обеспечивает сплит только за счет раздельных вызовов send, и это обычно работает надежно, если разбивать не на слишком много частей и не на слишком мелкие подряд следующие части. В последнем случае Linux все же может обьединить некоторые части, что приведет к несоответствию реальной сегментации указанным сплит позициям. Другие ОС в этом вопросе ведут себя более предсказуемо. Спонтанного обьединения замечено не было. Поэтому не стоит злоупотреблять сплитами и в особенности мелкими соседними пакетами. Как показывается практика, проблемы могут начаться , если количество сплитов более одного. На каких-то системах наблюдался стабильный результат до 8 сплитов, на других проблемы уже начинались после 2 сплитов. Один сплит работает стабильно, если не является частью массивной потоковой передачи. При неудаче сегментации будет выводиться сообщение `WARNING ! segmentation failed`. Если вы его видите, это повод снизить количество сплит позиций. Если это не вариант, для ядер Linux >=4.6 есть параметр `--fix-seg`. Он позволяет подождать завершение отсылки перед отправкой следующей части. Но этот вариант ломает модель асинхронной обработки событий. Пока идет ожидание, все остальные соединения не обрабатываются и кратковременно подвисают. На практике это может быть совсем небольшое ожидание - менее 10 мс. Выполняется оно только , если происходит split, и в ожидании есть реальная необходимость. В высоконагруженных системах данный вариант не рекомендуется. Но для домашнего использования может подойти, и вы эти задержки даже не заметите. Если вы пытаетесь сплитнуть массивную передачу с `--split-any-protocol`, когда информация поступает быстрее отсылки, то без `--fix-seg` ошибки сегментации будут сыпаться сплошным потоком. Работа по массивному потоку без ограничителей `--tamper-start` и `--tamper-cutoff` обычно лишена смысла. tpws работает на уровне сокетов, поэтому длинный запрос, не вмещающийся в 1 пакет (TLS с kyber), он получает целым блоком. На каждую сплит часть он делает отдельный вызов `send()`. Но ОС не сможет отослать данные в одном пакете, если размер превысит MTU. В случае слишком большого сегмента ОС дополнительно его порежет на более мелкие. Результат должен быть аналогичен nfqws. `--disorder` заставляет слать каждый 2-й пакет с TTL=1, начиная с первого. К серверу приходят все четные пакеты сразу. На остальные ОС делает ретрансмиссию, и они приходят потом. Это само по себе создает дополнительную задержку (200 мс в linux для первой ретрансмиссии). Иным способом сделать disorder в сокет варианте не представляется возможным. Итоговый порядок для 6 сегментов получается `2 4 6 1 3 5`. `--oob` высылает 1 байт out-of-band data после первого сплит сегмента. `oob` в каждом сегменте сплита показал себя ненадежным. Сервер получает oob в сокет. Сочетание `oob` и `disorder` возможно только в Linux. Остальные ОС не умеют с таким справляться. Флаг URG теряется при ретрансмиссиях. Сервер получает oob в сокет. Сочетание этих параметров в ос, кроме Linux, вызывает ошибку на этапе запуска. ### TLSREC `--tlsrec` позволяют внутри одного tcp сегмента разрезать TLS ClientHello на 2 TLS records. Можно использовать стандартный механизм маркеров для задания относительных позиций. `--tlsrec` ломает значительное количество сайтов. Криптобиблиотеки (openssl, ...) на оконечных http серверах без проблем принимают разделенные tls сегменты, но мидлбоксы - не всегда. К мидлбоксам можно отнести CDN или системы ddos-защиты. Поэтому применение `--tlsrec` без ограничителей вряд ли целесообразно. В РФ `--tlsrec` обычно не работает с TLS 1.2, потому что цензор парсит сертификат сервера из ServerHello. Работает только с TLS 1.3, поскольку там эта информация шифруется. Впрочем, сейчас сайтов, не поддерживающих TLS 1.3, осталось немного. ### MSS `--mss` устанавливает опцию сокета TCP_MAXSEG. Клиент выдает это значение в tcp опциях SYN пакета. Сервер в ответ в SYN,ACK выдает свой MSS. На практике сервера обычно снижают размеры отсылаемых ими пакетов, но они все равно не вписываются в низкий MSS, указанный клиентом. Обычно чем больше указал клиент, тем больше шлет сервер. На TLS 1.2 если сервер разбил заброс так, чтобы домен из сертификата не попал в первый пакет, это может обмануть DPI, секущий ответ сервера. Схема может значительно снизить скорость и сработать не на всех сайтах. С фильтром по hostlist совместимо только в [некоторых случаях](#множественные-стратегии-1), когда возможно узнать имя хоста на момент применения дурения. Применяя данную опцию к сайтам TLS1.3, если броузер тоже поддерживает TLS1.3, то вы делаете только хуже. Но нет способа автоматически узнать когда надо применять, когда нет, поскольку MSS идет только в 3-way handshake еще до обмена данными, а версию TLS можно узнать только по ответу сервера, который может привести к реакции DPI. Использовать только когда нет ничего лучше или для отдельных ресурсов. Для http использовать смысла нет, поэтому заводите отдельный desync profile с фильтром по порту 443. Работает только на Linux, не работает на BSD и MacOS. ### ДРУГИЕ ПАРАМЕТРЫ ДУРЕНИЯ Параметр `--hostpad=` добавляет паддинг-хедеров перед `Host:` на указанное количество байтов. Если размер `` слишком большой, то идет разбивка на разные хедеры по 2K. Общий буфер приема http запроса - 64K, больший паддинг не поддерживается, да и http сервера такое уже не принимают. Полезно против DPI, выполняющих реассемблинг TCP с ограниченным буфером. Если техника работает, то после некоторого количества bytes http запрос начнет проходить до сайта. Если при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции TCP сегментации. Если все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения. ### МНОЖЕСТВЕННЫЕ СТРАТЕГИИ Работают аналогично **nfqws**, кроме некоторых моментов. Нет параметра `--filter-udp`, поскольку **tpws** udp не поддерживает. Методы нулевой фазы (`--mss`) могут работать по хостлисту только в двух случаях: если используется режим socks и удаленный ресолвинг хостов через прокси, либо используется система [кэша IP](#кэш-ip) для запоминания соответствия IP->hostname. Работоспособность вашей настройки в одном и том же режиме может зависеть от того, применяет ли клиент удаленный ресолвинг. Это может быть неочевидно. В одной программе работает, в другой - нет. Если вы используете профиль с хостлистом , и вам нужен mss всегда, укажите mss в профиле с хостлистом, создайте еще один профиль без хостлиста, если его еще нет, и в нем еще раз укажите mss. Тогда при любом раскладе будет выполняться mss. Если вам нужен mss по хостлисту, указывайте `--mss` только в профиле с хостлистом и убедитесь в наличии любого из необходимых условий работы в таком режиме. Используйте `curl --socks5` и `curl --socks5-hostname` для проверки вашей стратегии. Смотрите вывод `--debug`, чтобы убедиться в правильности настроек. ### СЛУЖЕБНЫЕ ПАРАМЕТРЫ `--debug` позволяет выводить подробный лог действий на консоль, в syslog или в файл. Может быть важен порядок следования опций. `--debug` лучше всего указывать в самом начале. Опции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа `--debug` еще дело не дошло, то сообщения не будут выведены в файл или syslog. `--debug=0|1|2` позволяют сразу в одном параметре включить логирование на консоль и указать уровень. Сохранено для совместимости с более старыми версиями. Для выбора уровня в режиме syslog или file используйте отдельный параметр `--debug-level`. Если в этих режимах `--debug` не указывать уровень через `--debug-level`, то автоматически назначается уровень 1. При логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. Так что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. Но имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root. В начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл, и у процесса не будет прав на создание файла в его директории, лог больше не будет вестись. Вместо удаления лучше использовать truncate. В шелле это можно сделать через команду ": >filename" tpws может биндаться на множество интерфейсов и IP адресов (до 32 шт). Порт всегда только один. Параметры `--bind-iface*` и `--bind-addr` создают новый бинд. Остальные параметры `--bind-*` относятся к последнему бинду. Для бинда на все ipv4 укажите `--bind-addr "0.0.0.0"`, на все ipv6 - `"::"`. `--bind-addr=""` - биндаемся на все ipv4 и ipv6. Выбор режима использования link local ipv6 адресов (`fe80::/8`) : ``` --bind-iface6 --bind-linklocal=no : сначала приватный адрес fc00::/7, затем глобальный адрес --bind-iface6 --bind-linklocal=unwanted : сначала приватный адрес fc00::/7, затем глобальный адрес, затем link local. --bind-iface6 --bind-linklocal=prefer : сначала link local, затем приватный адрес fc00::/7, затем глобальный адрес. --bind-iface6 --bind-linklocal=force : только link local ``` Если не указано ни одного бинда, то создается бинд по умолчанию на все адреса всех интерфейсов. Для бинда на конкретный link-local address делаем так : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name` Параметры `--bind-wait*` могут помочь в ситуациях, когда нужно взять IP с интерфейса, но его еще нет, он не поднят или не сконфигурирован. В разных системах события ifup ловятся по-разному и не гарантируют, что интерфейс уже получил IP адрес определенного типа. В общем случае не существует единого механизма повеситься на событие типа "на интерфейсе X появился link local address". Для бинда на известный ip, когда еще интерфейс не сконфигурирован, нужно делать так: `--bind-addr=192.168.5.3 --bind-wait-ip=20` В режиме transparent бинд возможен на любой несуществующий адрес, в режиме socks - только на существующий. Параметры rcvbuf и sndbuf позволяют установить setsockopt SO_RCVBUF SO_SNDBUF для локального и удаленного соединения. `--skip-nodelay` может быть полезен, когда tpws используется без дурения, чтобы привести MTU к MTU системы, на которой работает tpws. Это может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения подозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз. `--local-tcp-user-timeout` и `--remote-tcp-user-timeout` устанавливают значение таймаута в секундах для соединений клиент-прокси и прокси-сервер. Этот таймаут соответствует опции сокета linux TCP_USER_TIMEOUT. Под таймаутом подразумевается время, в течение которого буферизированные данные не переданы или на переданные данные не получено подтверждение (ACK) от другой стороны. Этот таймаут никак не касается времени отсутствия какой-либо передачи через сокет лишь потому, что данных для передачи нет. Полезно для сокращения время закрытия подвисших соединений. Поддерживается только на Linux и MacOS. Режим `--socks` не требует повышенных привилегий (кроме бинда на привилегированные порты 1..1023). Поддерживаются версии socks 4 и 5 без авторизации. Версия протокола распознается автоматически. Подключения к IP того же устройства, на котором работает tpws, включая localhost, запрещены. socks5 позволяет удаленно ресолвить хосты (curl : --socks5-hostname firefox : socks_remote_dns=true). tpws поддерживает эту возможность асинхронно, не блокируя процессинг других соединений, используя многопоточный пул ресолверов. Количество потоков определяется автоматически в зависимости от `--maxconn`, но можно задать и вручную через параметр `--resolver-threads`. Запрос к socks выставляется на паузу, пока домен не будет преобразован в ip адрес в одном из потоков ресолвера. Ожидание может быть более длинным, если все потоки заняты. Если задан параметр `--no-resolve`, то подключения по именам хостов запрещаются, а пул ресолверов не создается. Тем самым экономятся ресурсы. ### IPTABLES ДЛЯ TPWS Для перенаправления tcp соединения на transparent proxy используются команды следующего вида : ``` iptables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 iptables -t nat -I PREROUTING -i <внутренний_интерфейс> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 ``` Первая команда для соединений с самой системы, вторая - для проходящих через роутер соединений. DNAT на localhost работает в цепочке OUTPUT, но не работает в цепочке PREROUTING без включения параметра route_localnet : `sysctl -w net.ipv4.conf.<внутренний_интерфейс>.route_localnet=1` Можно использовать `-j REDIRECT --to-port 988` вместо DNAT, однако в этом случае процесс transparent proxy должен слушать на ip адресе входящего интерфейса или на всех адресах. Слушать на всех - не есть хорошо с точки зрения безопасности. Слушать на одном (локальном) можно, но в случае автоматизированного скрипта придется его узнавать, потом динамически вписывать в команду. В любом случае требуются дополнительные усилия. Использование route_localnet тоже имеет потенциальные проблемы с безопасностью. Вы делаете доступным все, что висит на `127.0.0.0/8` для локальной подсети < внутренний_интерфейс>. Службы обычно привязываются к `127.0.0.1`, поэтому можно средствами iptables запретить входящие на `127.0.0.1` не с интерфейса lo, либо повесить tpws на любой другой IP из `127.0.0.0/8`, например на `127.0.0.127`, и разрешить входящие не с lo только на этот IP. ``` iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP ``` Фильтр по owner необходим для исключения рекурсивного перенаправления соединений от самого tpws. tpws запускается под пользователем **tpws**, для него задается исключающее правило. ip6tables работают почти точно так же, как и ipv4, но есть ряд важных нюансов. В DNAT следует брать адрес --to в квадратные скобки. Например : `ip6tables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988` Параметра route_localnet не существует для ipv6. DNAT на localhost (::1) возможен только в цепочке OUTPUT. В цепочке PREROUTING DNAT возможен на любой global address или на link local address того же интерфейса, откуда пришел пакет. NFQUEUE работает без изменений. ### NFTABLES ДЛЯ TPWS Базовая конфигурация : ``` IFACE_WAN=wan IFACE_LAN=br-lan sysctl -w net.ipv4.conf.$IFACE_LAN.route_localnet=1 nft create table inet ztest nft create chain inet ztest localnet_protect nft add rule inet ztest localnet_protect ip daddr 127.0.0.127 return nft add rule inet ztest localnet_protect ip daddr 127.0.0.0/8 drop nft create chain inet ztest input "{type filter hook input priority filter - 1;}" nft add rule inet ztest input iif != "lo" jump localnet_protect nft create chain inet ztest dnat_output "{type nat hook output priority dstnat;}" nft add rule inet ztest dnat_output meta skuid != tpws oifname $IFACE_WAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 nft create chain inet ztest dnat_pre "{type nat hook prerouting priority dstnat;}" nft add rule inet ztest dnat_pre meta iifname $IFACE_LAN tcp dport { 80, 443 } dnat ip to 127.0.0.127:988 ``` Удаление таблицы : ``` nft delete table inet ztest ``` ## ip2net Утилита ip2net предназначена для преобразования ipv4 или ipv6 списка ip в список подсетей с целью сокращения размера списка. Входные данные берутся из stdin, выходные выдаются в `stdout`. ``` -4 ; лист - ipv4 (по умолчанию) -6 ; лист - ipv6 --prefix-length=min[-max] ; диапазон рассматриваемых длин префиксов. например : 22-30 (ipv4), 56-64 (ipv6) --v4-threshold=mul/div ; ipv4 : включать подсети, в которых заполнено по крайней мере mul/div адресов. например : 3/4 --v6-threshold=N ; ipv6 : минимальное количество ip для создания подсети ``` В списке могут присутствовать записи вида ip/prefix и ip1-ip2. Такие записи выкидываются в stdout без изменений. Они принимаются командой ipset. ipset умеет для листов hash:net из ip1-ip2 делать оптимальное покрытие ip/prefix. ipfw из FreeBSD понимает ip/prefix, но не понимает ip1-ip2. ip2net фильтрует входные данные, выкидывая неправильные IP адреса. Выбирается подсеть, в которой присутствует указанный минимум адресов. Для ipv4 минимум задается как процент от размера подсети (mul/div. например, 3/4), для ipv6 минимум задается напрямую. Размер подсети выбирается следующим алгоритмом: Сначала в указанном диапазоне длин префиксов ищутся подсети, в которых количество адресов - максимально. Если таких сетей найдено несколько, берется наименьшая сеть (префикс больше). Например, заданы параметры v6_threshold=2 prefix_length=32-64, имеются следующие ipv6 : ``` 1234:5678:aaaa::5 1234:5678:aaaa::6 1234:5678:aaac::5 Результат будет : 1234:5678:aaa8::/45 ``` Эти адреса так же входят в подсеть /32. Однако, нет смысла проходиться ковровой бомбардировкой, когда те же самые адреса вполне влезают в /45 и их ровно столько же. Если изменить v6_threshold=4, то результат будет: ``` 1234:5678:aaaa::5 1234:5678:aaaa::6 1234:5678:aaac::5 ``` То есть ip не объединятся в подсеть, потому что их слишком мало. Если изменить `prefix_length=56-64`, результат будет: ``` 1234:5678:aaaa::/64 1234:5678:aaac::5 ``` Требуемое процессорное время для вычислений сильно зависит от ширины диапазона длин префиксов, размера искомых подсетей и длины листа. Если ip2net думает слишком долго, не используйте слишком большие подсети и уменьшите диапазон длин префиксов. Учтите, что арифметика mul/div - целочисленная. При превышении разрядной сетки 32 bit результат непредсказуем. Не надо делать такое: 5000000/10000000. 1/2 - гораздо лучше. ## mdig Программа предназначена для многопоточного ресолвинга больших листов через системный DNS. Она берет из stdin список доменов и выводит в stdout результат ресолвинга. Ошибки выводятся в stderr. ``` --threads= ; количество потоков. по умолчанию 1. --family=<4|6|46> ; выбор семейства IP адресов : ipv4, ipv6, ipv4+ipv6 --verbose ; дебаг-лог на консоль --stats=N ; выводить статистику каждые N доменов --log-resolved= ; сохранять успешно отресолвленные домены в файл --log-failed= ; сохранять неудачно отресолвленные домены в файл --dns-make-query= ; вывести в stdout бинарный DNS запрос по домену. если --family=6, запрос будет AAAA, иначе A. --dns-parse-query ; распарсить бинарный DNS ответ и выдать все ivp4 и ipv6 адреса из него в stdout ``` Параметры `--dns-make-query` и `--dns-parse-query` позволяют провести ресолвинг одного домена через произвольный канал. Например, следующим образом можно выполнить DoH запрос, используя лишь mdig и curl : ``` mdig --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 ``` ## Способы получения списка заблокированных IP !!! nftables не могут работать с ipset-ами. Собственный аналогичный механизм требует огромного количество RAM !!! для загрузки больших листов. Например, для загона 100K записей в nfset не хватает даже 256 Mb. !!! Если вам нужны большие листы на домашних роутерах, откатывайтесь на iptables+ipset. 1) Внесите заблокированные домены в `ipset/zapret-hosts-user.txt` и запустите `ipset/get_user.sh` На выходе получите `ipset/zapret-ip-user.txt` с IP адресами. Cкрипты с названием get_reestr_* оперируют дампом реестра заблокированных сайтов : 2) `ipset/get_reestr_resolve.sh` получает список доменов от rublacklist и дальше их ресолвит в ip адреса в файл ipset/zapret-ip.txt.gz. В этом списке есть готовые IP адреса, но судя во всему они там в точности в том виде, что вносит в реестр РосКомПозор. Адреса могут меняться, позор не успевает их обновлять, а провайдеры редко банят по IP : вместо этого они банят http запросы с "нехорошим" заголовком "Host:" вне зависимости от IP адреса. Поэтому скрипт ресолвит все сам, хотя это и занимает много времени. Используется мультипоточный ресолвер mdig (собственная разработка). 3) `ipset/get_reestr_preresolved.sh`. то же самое, что и 2), только берется уже заресолвленый список со стороннего ресурса. 4) `ipset/get_reestr_preresolved_smart.sh`. то же самое, что и 3), с добавлением всего диапазона некоторых автономных систем (прыгающие IP адреса из cloudflare, facebook, ...) и некоторых поддоменов блокируемых сайтов Cкрипты с названием `get_antifilter_*` оперируют списками адресов и масок подсетей с сайтов antifilter.network и antifilter.download : 5) `ipset/get_antifilter_ip.sh`. получает лист https://antifilter.download/list/ip.lst. 6) `ipset/get_antifilter_ipsmart.sh`. получает лист https://antifilter.network/download/ipsmart.lst. умная суммаризация отдельных адресов из ip.lst по маскам от /32 до /22 7) `ipset/get_antifilter_ipsum.sh`. получает лист https://antifilter.download/list/ipsum.lst. суммаризация отдельных адресов из ip.lst по маске /24 8) `ipset/get_antifilter_ipresolve.sh`. получает лист https://antifilter.download/list/ipresolve.lst. пре-ресолвленный список, аналогичный получаемый при помощи get_reestr_resolve. только ipv4. 9) `ipset/get_antifilter_allyouneed.sh`. получает лист https://antifilter.download/list/allyouneed.lst. Суммарный список префиксов, созданный из ipsum.lst и subnet.lst. 10) `ipset/get_refilter_ipsum.sh`. Список берется отсюда : https://github.com/1andrevich/Re-filter-lists Все варианты рассмотренных скриптов автоматически создают и заполняют ipset. Варианты 2-10 дополнительно вызывают вариант 1. 11) `ipset/get_config.sh`. этот скрипт вызывает то, что прописано в переменной GETLIST из файла config Если переменная не определена, то ресолвятся лишь листы для ipset nozapret/nozapret6. Листы РКН все время изменяются. Возникают новые тенденции. Требования к RAM могут меняться. Поэтому необходима нечастая, но все же регулярная ревизия что же вообще у вас происходит на роутере. Или вы можете узнать о проблеме лишь когда у вас начнет постоянно пропадать wifi, и вам придется его перезагружать каждые 2 часа (метод кувалды). Самые щадящие варианты по RAM - `get_antifilter_allyouneed.sh`, `get_antifilter_ipsum.sh`, `get_refilter_*.sh`. Листы `zapret-ip.txt` и `zapret-ipban.txt` сохраняются в сжатом виде в файлы .gz. Это позволяет снизить их размер во много раз и сэкономить место на роутере. Отключить сжатие листов можно параметром конфига GZIP_LISTS=0. На роутерах не рекомендуется вызывать эти скрипты чаще раза за 2 суток, поскольку сохранение идет либо во внутреннюю флэш память роутера, либо в случае extroot - на флэшку. В обоих случаях слишком частая запись может убить флэшку, но если это произойдет с внутренней флэш памятью, то вы просто убьете роутер. Принудительное обновление `ipset` выполняет скрипт `ipset/create_ipset.sh`. Если передан параметр `no-update`, скрипт не обновляет `ipset`, а только создает его при его отсутствии и заполняет. Это полезно, когда могут случиться несколько последовательных вызовов скрипта. Нет смысла несколько раз перезаполнять `ipset`, это длительная операция на больших листах. Листы можно обновлять раз в несколько суток, и только тогда вызывать `create_ipset` без параметра `no-update`. Во всех остальных случаях стоит применять `no-update`. Список РКН уже достиг внушительных размеров в сотни тысяч IP адресов. Поэтому для оптимизации `ipset` применяется утилита `ip2net`. Она берет список отдельных IP адресов и пытается интеллектуально создать из него подсети для сокращения количества адресов. `ip2net` отсекает неправильные записи в листах, гарантируя отсутствие ошибок при их загрузке. `ip2net` написан на языке C, поскольку операция ресурсоемкая. Иные способы роутер может не потянуть. Можно внести список доменов в `ipset/zapret-hosts-user-ipban.txt`. Их ip адреса будут помещены в отдельный ipset `ipban`. Он может использоваться для принудительного завертывания всех соединений на прозрачный proxy `redsocks` или на VPN. **IPV6** : если включен ipv6, то дополнительно создаются листы с таким же именем, но с "6" на конце перед расширением. `zapret-ip.txt` => `zapret-ip6.txt` Создаются ipset-ы zapret6 и ipban6. Листы с antifilter не содержат список ipv6 адресов. **СИСТЕМА ИСКЛЮЧЕНИЯ IP**. Все скрипты ресолвят файл `zapret-hosts-user-exclude.txt`, создавая `zapret-ip-exclude.txt` и `zapret-ip-exclude6.txt`. Они загоняются в ipset-ы nozapret и nozapret6. Все правила, создаваемые init скриптами, создаются с учетом этих ipset. Помещенные в них IP не участвуют в процессе. `zapret-hosts-user-exclude.txt` может содержать домены, ipv4 и ipv6 адреса или подсети. **FreeBSD**. Скрипты ipset/*.sh работают так же на FreeBSD. Вместо ipset они создают lookup таблицы ipfw с аналогичными именами. ipfw таблицы в отличие от ipset могут содержать как ipv4, так и ipv6 адреса и подсети в одной таблице, поэтому разделения нет. Параметр конфига LISTS_RELOAD задает произвольную команду для перезагрузки листов. Это особенно полезно на BSD системах с PF. LISTS_RELOAD=- отключает перезагрузку листов. ## Фильтрация по именам доменов Альтернативой ipset является использование tpws или nfqws со списком доменов. Оба демона принимают неограниченное количество листов include (`--hostlist`) и exclude (`--hostlist-exclude`). Прежде всего проверяются exclude листы. При вхождении в них происходит отказ от дурения. Далее при наличии include листов проверяется домен на вхождение в них. При невхождении в список отказ от дурения. Если все include листы пустые, это приравнивается к отсутствию include листов. Ограничение перестает работать. В иных случаях происходит дурение. Нет ни одного списка - дурение всегда. Есть только exclude список - дурение всех, кроме. Есть только include список - дурение только их. Есть оба - дурение только include, кроме exclude. В системе запуска это обыграно следующим образом. Присутствуют 2 include списка : `ipset/zapret-hosts-user.txt.gz` или `ipset/zapret-hosts-user.txt`, `ipset/zapret-hosts.txt.gz` или `ipset/zapret-hosts.txt` и 1 exclude список `ipset/zapret-hosts-user-exclude.txt.gz` или `ipset/zapret-hosts-user-exclude.txt` При режимах фильтрации `MODE_FILTER=hostlist` или `MODE_FILTER=autohostlist` система запуска передает **nfqws** или **tpws** все листы, файлы которых присутствуют. Передача происходит через замену маркеров `` и `` на реальные параметры `--hostlist`, `--hostlist-exclude`, `--hostlist-auto`. Если вдруг листы include присутствуют, но все они пустые, то работа аналогична отсутствию include листа. Файл есть, но несмотря на это дурится все, кроме exclude. Если вам нужен именно такой режим - не обязательно удалять `zapret-hosts-users.txt`. Достаточно сделать его пустым. Поддомены учитываются автоматически. Например, строчка "ru" вносит в список "\*.ru". Строчка "\*.ru" в списке не сработает. Можно использовать символ `^` в начале хоста, чтобы отказаться от автоматического учета поддоменов. Список доменов РКН может быть получен скриптами ``` ipset/get_reestr_hostlist.sh ipset/get_antizapret_domains.sh ipset/get_reestr_resolvable_domains.sh ipset/get_refilter_domains.sh ``` Он кладется в `ipset/zapret-hosts.txt.gz`. При изменении времени модификации или размера файлов списки перечитываются автоматически. После неатомарных операций изменения можно послать tpws/nfqws сигнал HUP для принудительной перечитки всех листов. При фильтрации по именам доменов демон должен запускаться без фильтрации по ipset. tpws и nfqws решают нужно ли применять дурение в зависимости от хоста, полученного из протокола прикладного уровня (http, tls, quic). При использовании больших списков, в том числе списка РКН, оцените объем RAM на роутере ! Если после запуска демона RAM под завязку или случаются oom, значит нужно отказаться от таких больших списков. ## Режим фильтрации autohostlist Этот режим позволяет проанализировать как запросы со стороны клиента, так и ответы от сервера. Если хост еще не находится ни в каких листах и обнаруживается ситуация, похожая на блокировку, происходит автоматическое добавление хоста в список `autohostlist` как в памяти, так и в файле. **nfqws** или **tpws** сами ведут этот файл. Чтобы какой-то хост не смог попась в `autohostlist` используйте `hostlist-exclude`. Если он все-же туда попал - удалите запись из файла вручную. Процессы автоматически перечитают файл. **tpws**/**nfqws** сами назначают владельцем файла юзера, под которым они работают после сброса привилегий, чтобы иметь возможность обновлять лист. В случае **nfqws** данный режим требует перенаправления в том числе и входящего трафика. Крайне рекомендовано использовать ограничитель `connbytes`, чтобы **nfqws** не обрабатывал гигабайты. По этой же причине не рекомендуется использование режима на BSD системах. Там нет фильтра `connbytes`. На linux системах при использовании nfqws и фильтра connbytes может понадобиться : `sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1` Было замечено, что некоторые DPI в России возвращают RST с неверным ACK. Это принимается tcp/ip стеком linux, но через раз приобретает статус INVALID в conntrack. Поэтому правила с `connbytes` срабатывают через раз, не пересылая RST пакет **nfqws**. Как вообще могут вести себя DPI, получив "плохой запрос" и приняв решение о блокировке: 1) Зависание: просто отмораживается, блокируя прохождение пакетов по TCP каналу. 2) RST: отправляет RST клиенту и/или серверу 3) Редирект: (только для http) отправляет редирект на сайт-заглушку 4) Подмена сертификата: (только для https) полный перехват TLS сеанса с попыткой всунуть что-то свое клиенту. Применяется нечасто, поскольку броузеры на такое ругаются. **nfqws** и **tpws** могут сечь варианты 1-3, 4 они не распознают. В силу специфики работы с отдельными пакетами или с TCP каналом tpws и nfqws распознают эти ситуации по-разному. Что считается ситуацией, похожей на блокировку : 1) **nfqws** Несколько ретрансмиссий первого запроса в TCP сеансе, в котором имеется host. 2) **nfqws,tpws** RST, пришедший в ответ на первый запрос с хостом. 3) **nfqws,tpws** HTTP редирект, пришедший в ответ на первый запрос с хостом, на глобальный адрес с доменом 2 уровня, не совпадающим с доменом 2 уровня оригинального запроса. 4) **tpws** закрытие соединения клиентом после отправки первого запроса с хостом, если не было на него ответа со стороны сервера. Это обычно случается по таймауту, когда нет ответа (случай "зависание"). Чтобы снизить вероятность ложных срабатываний, имеется счетчик ситуаций, похожих на блокировку. Если за определенное время произойдет более определенного их количества, хост считается заблокированным и заносится в `autohostlist`. По нему сразу же начинает работать стратегия по обходу блокировки. Если в процессе счета вебсайт отвечает без признаков блокировки, счетчик сбрасывается. Вероятно, это был временный сбой сайта. На практике работа с данным режимом выглядит так. Первый раз пользователь заходит на сайт и получает заглушку, сброс соединения или броузер подвисает, вываливаясь по таймауту с сообщением о невозможности загрузить страницу. Надо долбить F5, принуждая броузер повторять попытки. После некоторой попытки сайт начинает работать, и дальше он будет работать всегда. С этим режимом можно использовать техники обхода, ломающие значительное количество сайтов. Если сайт не ведет себя как заблокированный, значит обход применен не будет. В противном случае терять все равно нечего. Однако, могут быть временные сбои сервера, приводящие к ситуации, аналогичной блокировке. Могут происходить ложные срабатывания. Если такое произошло, стратегия может начать ломать незаблокированный сайт. Эту ситуацию, увы, придется вам контролировать вручную. Заносите такие домены в `ipset/zapret-hosts-user-exclude.txt`, чтобы избежать повторения. Чтобы впоследствии разобраться почему домен был занесен в лист, можно включить `autohostlist debug log`. Он полезен тем, что работает без постоянного просмотра вывода **nfqws** в режиме debug. В лог заносятся только основные события, ведущие к занесению хоста в лист. По логу можно понять как избежать ложных срабатываний и подходит ли вообще вам этот режим. Можно использовать один `autohostlist` с множеством процессов. Все процессы проверяют время модификации файла. Если файл был изменен в другом процессе, происходит его перечитывание. Все процессы должны работать под одним uid, чтобы были права доступа на файл. Скрипты `zapret` ведут `autohostlist` в `ipset/zapret-hosts-auto.txt`. `install_easy.sh` при апгрейде `zapret` сохраняет этот файл. Режим `autohostlist` включает в себя режим `hostlist`. Можно вести `ipset/zapret-hosts-user.txt`, `ipset/zapret-hosts-user-exclude.txt`. ## Проверка провайдера Перед настройкой нужно провести исследование какую бяку устроил вам ваш провайдер. Нужно выяснить не подменяет ли он DNS и какой метод обхода DPI работает. В этом вам поможет скрипт `blockcheck.sh`. Если DNS подменяется, но провайдер не перехватывает обращения к сторонним DNS, поменяйте DNS на публичный. Например: 8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1, 9.9.9.9 Если DNS подменяется и провайдер перехватывает обращения к сторонним DNS, настройте `dnscrypt`. Еще один эффективный вариант - использовать ресолвер от yandex 77.88.8.88 на нестандартном порту 1253. Многие провайдеры не анализируют обращения к DNS на нестандартных портах. `blockcheck` если видит подмену DNS автоматически переключается на DoH сервера. Следует прогнать `blockcheck` по нескольким заблокированным сайтам и выявить общий характер блокировок. Разные сайты могут быть заблокированы по-разному, нужно искать такую технику, которая работает на большинстве. Чтобы записать вывод `blockcheck.sh` в файл, выполните: `./blockcheck.sh | tee /tmp/blockcheck.txt`. Проанализируйте какие методы дурения DPI работают, в соответствии с ними настройте `/opt/zapret/config`. Имейте в виду, что у провайдеров может быть несколько DPI или запросы могут идти через разные каналы по методу балансировки нагрузки. Балансировка может означать, что на разных ветках разные DPI или они находятся на разных хопах. Такая ситуация может выражаться в нестабильности работы обхода. Дернули несколько раз curl. То работает, то connection reset или редирект. `blockcheck.sh` выдает странноватые результаты. То split работает на 2-м. хопе, то на 4-м. Достоверность результата вызывает сомнения. В этом случае задайте несколько повторов одного и того же теста. Тест будет считаться успешным только, если все попытки пройдут успешно. При использовании `autottl` следует протестировать как можно больше разных доменов. Эта техника может на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах она стабильна, на третьих полный хаос, и проще отказаться. `Blockcheck` имеет 3 уровня сканирования. * `quick` - максимально быстро найти хоть что-то работающее. * `standard` дает возможность провести исследование как и на что реагирует DPI в плане методов обхода. * `force` дает максимум проверок даже в случаях, когда ресурс работает без обхода или с более простыми стратегиями. Есть ряд других параметров, которые не будут спрашиваться в диалоге, но которые можно переопределить через переменные. ``` CURL - замена программы curl CURL_MAX_TIME - время таймаута curl в секундах CURL_MAX_TIME_QUIC - время таймаута curl для quic. если не задано, используется значение CURL_MAX_TIME CURL_MAX_TIME_DOH - время таймаута curl для DoH серверов CURL_CMD=1 - показывать команды curl CURL_OPT - дополнительные параметры curl. `-k` - игнор сертификатов. `-v` - подробный вывод протокола CURL_HTTPS_GET=1 - использовать метод GET вместо HEAD для https DOMAINS - список тестируемых доменов через пробел IPVS=4|6|46 - тестируемые версии ip протокола ENABLE_HTTP=0|1 - включить тест plain http ENABLE_HTTPS_TLS12=0|1 - включить тест https TLS 1.2 ENABLE_HTTPS_TLS13=0|1 - включить тест https TLS 1.3 ENABLE_HTTP3=0|1 - включить тест QUIC REPEATS - количество попыток тестирования PARALLEL=0|1 - включить параллельные попытки. может обидеть сайт из-за долбежки и привести к неверному результату SCANLEVEL=quick|standard|force - уровень сканирования BATCH=1 - пакетный режим без вопросов и ожидания ввода в консоли HTTP_PORT, HTTPS_PORT, QUIC_PORT - номера портов для соответствующих протоколов SKIP_DNSCHECK=1 - отказ от проверки DNS SKIP_IPBLOCK=1 - отказ от тестов блокировки по порту или IP SKIP_TPWS=1 - отказ от тестов tpws SKIP_PKTWS=1 - отказ от тестов nfqws/dvtws/winws PKTWS_EXTRA, TPWS_EXTRA - дополнительные параметры nfqws/dvtws/winws и tpws, указываемые после основной стратегии PKTWS_EXTRA_1 .. PKTWS_EXTRA_9, TPWS_EXTRA_1 .. TPWS_EXTRA_9 - отдельно дополнительные параметры, содержащие пробелы PKTWS_EXTRA_PRE - дополнительные параметры для nfqws/dvtws/winws, указываемые перед основной стратегией PKTWS_EXTRA_PRE_1 .. PKTWS_EXTRA_PRE_9 - отдельно дополнительные параметры, содержащие пробелы SECURE_DNS=0|1 - принудительно выключить или включить DoH DOH_SERVERS - список URL DoH через пробел для автоматического выбора работающего сервера DOH_SERVER - конкретный DoH URL, отказ от поиска UNBLOCKED_DOM - незаблокированный домен, который используется для тестов IP block MIN_TTL,MAX_TTL - пределы тестов с TTL. MAX_TTL=0 отключает тесты. MIN_AUTOTTL_DELTA,MAX_AUTOTTL_DELTA - пределы тестов с autottl по дельте. MAX_AUTOTTL_DELTA=0 отключает тесты. SIMULATE=1 - включить режим симуляции для отладки логики скрипта. отключаются реальные запросы через curl, заменяются рандомным результатом. SIM_SUCCESS_RATE= - вероятность успеха симуляции в процентах ``` Пример запуска с переменными:\ `SECURE_DNS=1 SKIP_TPWS=1 CURL_MAX_TIME=1 CURL=/tmp/curl ./blockcheck.sh` **СКАН ПОРТОВ**\ Если в системе присутствует совместимый `netcat` (ncat от nmap или openbsd ncat. в OpenWrt по умолчанию нет), то выполняется сканирование портов http или https всех IP адресов домена. Если ни один IP не отвечает, то результат очевиден. Можно останавливать сканирование. Автоматически оно не остановится, потому что netcat-ы недостаточно подробно информируют о причинах ошибки. Если доступна только часть IP, то можно ожидать хаотичных сбоев, т.к. подключение идет к случайному адресу из списка. **ПРОВЕРКА НА ЧАСТИЧНЫЙ IP block**\ Под частичным блоком подразумевается ситуация, когда коннект на порты есть, но по определенному транспортному или прикладному протоколу всегда идет реакция DPI вне зависимости от запрашиваемого домена. Эта проверка так же не выдаст автоматического вердикта/решения, потому что может быть очень много вариаций. Вместо этого анализ происходящего возложен на самого пользователя или тех, кто будет читать лог. Суть этой проверки в попытке дернуть неблокированный IP с блокированным доменом и наоборот, анализируя при этом реакцию DPI. Реакция DPI обычно проявляется в виде таймаута (зависание запроса), connection reset или http redirect на заглушку. Любой другой вариант скорее всего говорит об отсутствии реакции DPI. В частности, любые http коды, кроме редиректа, ведущего именно на заглушку, а не куда-то еще. На TLS - ошибки handshake без задержек. Ошибка сертификата может говорить как о реакции DPI с MiTM атакой (подмена сертификата), так и о том, что принимающий сервер неблокированного домена все равно принимает ваш TLS `handshake` с чужим доменом, пытаясь при этом выдать сертификат без запрошенного домена. Требуется дополнительный анализ. Если на заблокированный домен есть реакция на всех IP адресах, значит есть блокировка по домену. Если на неблокированный домен есть реакция на IP адресах блокированного домена, значит имеет место блок по IP. Соответственно, если есть и то, и другое, значит есть и блок по IP, и блок по домену. Неблокированный домен первым делом проверяется на доступность на оригинальном адресе. При недоступности тест отменяется, поскольку он будет неинформативен. Если выяснено, что есть частичный блок по IP на DPI, то скорее всего все остальные тесты будут провалены вне зависимости от стратегий обхода. Но бывают и некоторые исключения. Например, пробитие через `ipv6 option headers`. Или сделать так, чтобы он не мог распознать протокол прикладного уровня. Дальнейшие тесты могут быть не лишены смысла. **ПРИМЕРЫ БЛОКИРОВКИ ТОЛЬКО ПО ДОМЕНУ БЕЗ БЛОКА ПО IP** ``` > testing iana.org on it's original !!!!! AVAILABLE !!!!! > testing rutracker.org on 192.0.43.8 (iana.org) curl: (28) Operation timed out after 1002 milliseconds with 0 bytes received > testing iana.org on 172.67.182.196 (rutracker.org) HTTP/1.1 409 Conflict > testing iana.org on 104.21.32.39 (rutracker.org) HTTP/1.1 409 Conflict > testing iana.org on it's original ip !!!!! AVAILABLE !!!!! > testing rutracker.org on 192.0.43.8 (iana.org) curl: (28) Connection timed out after 1001 milliseconds > testing iana.org on 172.67.182.196 (rutracker.org) curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure > testing iana.org on 104.21.32.39 (rutracker.org) curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure > testing iana.org on it's original ip !!!!! AVAILABLE !!!!! > testing rutracker.org on 192.0.43.8 (iana.org) HTTP/1.1 307 Temporary Redirect Location: https://www.gblnet.net/blocked.php > testing iana.org on 172.67.182.196 (rutracker.org) HTTP/1.1 409 Conflict > testing iana.org on 104.21.32.39 (rutracker.org) HTTP/1.1 409 Conflict > testing iana.org on it's original ip !!!!! AVAILABLE !!!!! > testing rutracker.org on 192.0.43.8 (iana.org) curl: (35) Recv failure: Connection reset by peer > testing iana.org on 172.67.182.196 (rutracker.org) curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure > testing iana.org on 104.21.32.39 (rutracker.org) curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure ``` **ПРИМЕР ПОЛНОГО IP БЛОКА ИЛИ БЛОКА TCP ПОРТА ПРИ ОТСУТСТВИИ БЛОКА ПО ДОМЕНУ** ``` * port block tests ipv4 startmail.com:80 ncat -z -w 1 145.131.90.136 80 145.131.90.136 does not connect. netcat code 1 ncat -z -w 1 145.131.90.152 80 145.131.90.152 does not connect. netcat code 1 * curl_test_http ipv4 startmail.com - checking without DPI bypass curl: (28) Connection timed out after 2002 milliseconds UNAVAILABLE code=28 - IP block tests (requires manual interpretation) > testing iana.org on it's original ip !!!!! AVAILABLE !!!!! > testing startmail.com on 192.0.43.8 (iana.org) HTTP/1.1 302 Found Location: https://www.iana.org/ > testing iana.org on 145.131.90.136 (startmail.com) curl: (28) Connection timed out after 2002 milliseconds > testing iana.org on 145.131.90.152 (startmail.com) curl: (28) Connection timed out after 2002 milliseconds ``` ## Выбор параметров Файл `/opt/zapret/config` используется различными компонентами системы и содержит основные настройки. Его нужно просмотреть и при необходимости отредактировать. На linux системах можно выбрать использовать `iptables` или `nftables`. По умолчанию на традиционных linux выбирается `nftables`, если установлен nft. На OpenWrt по умолчанию выбирается `nftables` на новых версиях с firewall4. `FWTYPE=iptables` На `nftables` можно отключить стандартную схему перехвата трафика после NAT и перейти на перехват до NAT. Это сделает невозможным применение некоторых методов дурения на проходящем трафике как в случае с `iptables`. nfqws начнет получать адреса пакетов из локальной сети и отображать их в логах. `POSTNAT=0` Существует 3 стандартных опции запуска, настраиваемых раздельно и независимо: `tpws-socks`, **tpws**, **nfqws**. Их можно использовать как по отдельности, так и вместе. Например, вам надо сделать комбинацию из методов, доступных только в **tpws** и только в **nfqws**. Их можно задействовать вместе. **tpws** будет прозрачно локализовывать трафик на системе и применять свое дурение, **nfqws** будет дурить трафик, исходящий с самой системы после обработки на **tpws**. А можно на эту же систему повесить без параметров socks proxy, чтобы получать доступ к обходу блокировок через прокси. Таким образом, все 3 режима вполне могут задействоваться вместе. Так же безусловно и независимо, в добавок к стандартным опциям, применяются все custom скрипты в `init.d/{sysv,openwrt,macos}/custom.d`. Однако, при комбинировании tpws и nfqws с пересечением по L3/L4 протоколам не все так просто , как может показаться на первый взгляд. Первым всегда работает tpws, за ним - nfqws. На nfqws попадает уже "задуренный" трафик от tpws. Получается, что дурилка дурит дурилку, и дурилка не срабатывает, потому что ее задурили. Вот такой веселый момент. nfqws перестает распознавать протоколы и применять методы. Некоторые методы дурения от tpws nfqws в состоянии распознать и отработать корректно, но большинство - нет. Решение - использование `--dpi-desync-any-protocol` в nfqws и работа как с неизвестным протоколом. Комбинирование tpws и nfqws является продвинутым вариантом, требующим глубокого понимания происходящего. Очень желательно проанализировать действия nfqws по `--debug` логу. Все ли так, как вы задумали. Одновременное использование tpws и nfqws без пересечения по L3/L4 (то есть nfqws - udp, tpws - tcp или nfqws - port 443, tpws - port 80 или nfqws - ipv4, tpws - ipv6) проблем не представляет. `tpws-socks` требует настройки параметров **tpws**, но не требует перехвата трафика. Остальные опции требуют раздельно настройки перехвата трафика и опции самих демонов. Каждая опция предполагает запуск одного инстанса соответствующего демона. Все различия методов дурения для `http`, `https`, `quic` и т.д. должны быть отражены через схему мультистратегий. В этом смысле настройка похожа на вариант `winws` на Windows, а перенос конфигов не должен представлять больших сложностей. Основное правило настройки перехвата - перехватывайте только необходимый минимум. Любой перехват лишнего - это бессмысленная нагрузка на вашу систему. Опции демонов `--ipset` использовать нужно с умом. Не стоит перехватывать весь трафик, чтобы потом по параметру --ipset выделить лишь горстку IP. Это будет работать, но очень неэффективно с точки зрения нагрузки на систему. Используйте `ipset`-ы режима ядра. При необходимости пишите и задействуйте `custom scripts`. Но если у вас и так идет работа по всем IP, и нужно написать небольшую специализацию по IP, то --ipset вполне уместен. Настройки демонов можно для удобства писать на нескольких строках, используя двойные или одинарные кавычки. Чтобы задействовать стандартные обновляемые хост-листы из каталога `ipset`, используйте маркер . Он будет заменен на параметры, соответствующие режиму MODE_FILTER, и будут подставлены реально существующие файлы. Если MODE_FILTER не предполагает стандартного хостлиста, будет заменен на пустую строку. Стандартные хостлисты следует вставлять в финальных стратегиях (стратегиях по умолчанию), закрывающих цепочки по группе параметров фильтра. Таких мест может быть несколько. Не нужно использовать в узких специализациях и в тех профилях, по которым точно не будет проходить трафик с известными протоколами, откуда поддерживается извлечение имени хоста (`http`, `tls`, `quic`). - это вариация, при которой стандартный автолист используется как обычный. То есть на этом профиле не происходит автоматическое добавление заблокированных доменов. Но если на другом профиле что-то будет добавлено, то этот профиль примет изменения автоматически. ***Изменение бита mark для предотвращения зацикливания***\ `DESYNC_MARK=0x40000000` ***Изменение бита mark для пометки пакетов, проходящих по POSTNAT схеме (только nftables)***\ `DESYNC_MARK_POSTNAT=0x20000000` ***Если раскоментировано, пометка пакетов, которые должны быть обработаны zapret.***\ `#FILTER_MARK=0x10000000` Бит должен быть установлен вашими собственными правилами. * Для iptables - в цепочках mangle PREROUTING и mangle OUTPUT перед правилами zapret (iptables -I _после_ применения правил zapret). * Для nftables - в хуках output и prerouting с приоритетом -102 или ниже. Критерии пометки любые. Например, IP адрес или интерфейс источника. Это ответ на вопрос "как мне сделать, чтобы телик не ходил через zapret или чтобы через него ходил только мой комп". ***Включение стандартной опции tpws в режиме socks***\ `TPWS_SOCKS_ENABLE=0` ***На каком порту будет слушать tpws socks. прослушивается только localhost и LAN***\ `TPPORT_SOCKS=987` ***Параметры tpws для режима socks*** ``` TPWS_SOCKS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " ``` ***Включение стандартной опции tpws в прозрачном режиме***\ `TPWS_ENABLE=0` ***Какие tcp порты следует перенаправлять на tpws***\ `TPWS_PORTS=80,443` ***Параметры tpws для прозрачного режима*** ``` TPWS_OPT=" --filter-tcp=80 --methodeol --new --filter-tcp=443 --split-pos=1,midsld --disorder " ``` ***Включение стандартной опции nfqws***\ `NFQWS_ENABLE=0` ***Какие tcp и udp порты следует перенаправлять на nfqws с использованием connbytes ограничителя*** connbytes позволяет из каждого соединения перенаправить только заданное количество начальных пакетов по каждому направлению - на вход и на выход. Это более эффективная kernel-mode замена параметра nfqws `--dpi-desync-cutoff=nX`. ``` NFQWS_PORTS_TCP=80,443 NFQWS_PORTS_UDP=443 ``` ***Сколько начальных входящих и исходящих пакетов нужно перенаправлять на nfqws по каждому направлению*** ``` NFQWS_TCP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_TCP_PKT_IN=3 NFQWS_UDP_PKT_OUT=$((6+$AUTOHOSTLIST_RETRANS_THRESHOLD)) NFQWS_UDP_PKT_IN=0 ``` ***Задать порты для перенаправления на nfqws без connbytes ограничителя***\ Есть трафик, исходящий сеанс для которого необходимо перенаправлять весь без ограничителей. Типичное применение - поддержка http keepalives на stateless DPI. Это существенно нагружает процессор. Использовать только если понимаете зачем. Чаще всего это не нужно. Входящий трафик ограничивается по connbytes через параметры PKT_IN. Если указываете здесь какие-то порты, желательно их убрать из версии с connbytes ограничителем ``` NFQWS_PORTS_TCP_KEEPALIVE=80 NFQWS_PORTS_UDP_KEEPALIVE= ``` ***Параметры nfqws*** ``` NFQWS_OPT=" --filter-tcp=80 --dpi-desync=fake,multisplit --dpi-desync-split-pos=method+2 --dpi-desync-fooling=md5sig --new --filter-tcp=443 --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-fooling=badseq,md5sig --new --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 ``` ***Режим фильтрации хостов:*** ``` none - применять дурение ко всем хостам ipset - ограничить дурение ipset-ом zapret/zapret6 hostlist - ограничить дурение списком хостов из файла autohostlist - режим hostlist + распознавание блокировок и ведение автоматического листа ``` `MODE_FILTER=none` ***Настройка системы управления выборочным traffic offload (только если поддерживается)*** ``` donttouch: выборочное управление отключено, используется системная настройка, простой инсталлятор выключает системную настройку, если она не совместима с выбранным режимом none: выборочное управление отключено, простой инсталлятор выключает системную настройку software: выборочное управление включено в режиме software, простой инсталлятор выключает системную настройку hardware: выборочное управление включено в режиме hardware, простой инсталлятор выключает системную настройку ``` `FLOWOFFLOAD=donttouch` Параметр `GETLIST` указывает инсталлятору `install_easy.sh` какой скрипт дергать для обновления списка заблокированных ip или хостов. Он же вызывается через `get_config.sh` из запланированных заданий (crontab или systemd timer). Поместите сюда название скрипта, который будете использовать для обновления листов. Если не нужно, то параметр следует закомментировать. Можно индивидуально отключить ipv4 или ipv6. Если параметр закомментирован или не равен "1", использование протокола разрешено. ``` DISABLE_IPV4=1 DISABLE_IPV6=1 ``` Количество потоков для многопоточного DNS ресолвера mdig (1..100). Чем их больше, тем быстрее, но не обидится ли на долбежку ваш DNS сервер?\ `MDIG_THREADS=30` Место для хранения временных файлов. При скачивании огромных реестров в `/tmp` места может не хватить. Если файловая система на нормальном носителе (не встроенная память роутера), то можно указать место на флэшке или диске. `TMPDIR=/opt/zapret/tmp` ***Опции для создания ipset-ов и nfset-ов*** ``` SET_MAXELEM=262144 IPSET_OPT="hashsize 262144 maxelem 2097152" ``` Хук, позволяющий внести ip адреса динамически. $1 = имя таблицы\ Адреса выводятся в stdout. В случае nfset автоматически решается проблема возможного пересечения интервалов.\ `IPSET_HOOK="/etc/zapret.ipset.hook"` ***ПРО РУГАНЬ в dmesg по поводу нехватки памяти.*** Может так случиться, что памяти в системе достаточно, но при попытке заполнить огромный `ipset` ядро начинает громко ругаться, `ipset` заполняется не полностью.\ Вероятная причина в том, что превышается `hashsize`, заданный при создании `ipset` (create_ipset.sh). Происходит переаллокация списка, не находится непрерывных фрагментов памяти нужной длины. Это лечится увеличением `hashsize`. Но чем больше `hashsize`, тем больше занимает `ipset` в памяти. Задавать слишком большой `hashsize` для недостаточно больших списков нецелесообразно. ***Опции для вызова ip2net. Отдельно для листов ipv4 и ipv6.*** ``` IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" ``` ***Настройка режима autohostlist.*** При увеличении AUTOHOSTLIST_RETRANS_THRESHOLD и использовании nfqws следует пересмотреть значения параметров NFQWS_TCP_PKT_OUT и NFQWS_UDP_PKT_OUT. Все ретрансмиссии должны быть получены nfqws, иначе триггер "зависание запроса" не сработает. ``` AUTOHOSTLIST_RETRANS_THRESHOLD=3 AUTOHOSTLIST_FAIL_THRESHOLD=3 AUTOHOSTLIST_FAIL_TIME=60 AUTOHOSTLIST_DEBUG=0 ``` ***Включить или выключить сжатие больших листов в скриптах ipset/\*.sh.*** `GZIP_LISTS=1` ***Команда для перезагрузки ip таблиц фаервола.*** Если не указано или пустое, выбирается автоматически ipset или ipfw при их наличии. На BSD системах с PF нет автоматической загрузки. Там нужно указать команду явно: `pfctl -f /etc/pf.conf` На более новых pfctl (есть в новых FreeBSD, нет в OpenBSD 6.8) можно дать команду загрузки только таблиц: `pfctl -Tl -f /etc/pf.conf` "-" означает отключение загрузки листов даже при наличии поддерживаемого backend. ``` LISTS_RELOAD="pfctl -f /etc/pf.conf" LISTS_RELOAD=- ``` В OpenWrt существует сеть по умолчанию 'lan'. Только трафик с этой сети будет перенаправлен на tpws. Но возможно задать другие сети или список сетей:\ `OPENWRT_LAN="lan lan2 lan3"` В OpenWrt в качестве wan берутся интерфейсы, имеющие default route. Отдельно для ipv4 и ipv6. Это можно переопределить: ``` OPENWRT_WAN4="wan4 vpn" OPENWRT_WAN6="wan6 vpn6" ``` Параметр `INIT_APPLY_FW=1` разрешает init скрипту самостоятельно применять правила iptables.\ При иных значениях или если параметр закомментирован, правила применены не будут.\ Это полезно, если у вас есть система управления фаерволом, в настройки которой и следует прикрутить правила.\ На OpenWrt неприменимо при использовании firewall3+iptables. `FILTER_TTL_EXPIRED_ICMP=1` включает механизмы блокировки пакетов icmp time exceeded, высылаемые роутерами по пути следования пакета в ответ на исчерпание TTL/HL. В linux соединение обрывается системой, если в ответ на первый пакет (для tcp - SYN) пришел такой icmp. Аналогичная схема имеется и в datagram сокетах. Блокировка icmp идет исключительно за счет средств iptables/nftables. Чтобы не трогать весь трафик, в режиме PRENAT используется connmark для пометки сеансов, над которыми поработал nfqws. В режиме POSTNAT так сделать нельзя, поэтому помечаются все сеансы, заворачиваемые на nfqws. Настройку лучше отключить, если вы не ожидаете проблем от icmp, тк в этом случае будет меньше ненужных вмешательств в трафик. ***Следующие настройки не актуальны для openwrt:*** Если ваша система работает как роутер, то нужно вписать названия внутренних и внешних интерфейсов: ``` IFACE_LAN=eth0 IFACE_WAN=eth1 IFACE_WAN6="henet ipsec0" ``` Несколько интерфейсов могут быть вписаны через пробел. Если IFACE_WAN6 не задан, то берется значение IFACE_WAN. > [!IMPORTANT] > Настройка маршрутизации, маскарада и т.д. не входит в задачу zapret. > Включаются только режимы, обеспечивающие перехват транзитного трафика. > Возможно определить несколько интерфейсов следующим образом: `IFACE_LAN="eth0 eth1 eth2"` ## Прикручивание к системе управления фаерволом или своей системе запуска Если вы используете какую-то систему управления фаерволом, то она может вступать в конфликт с имеющимся скриптом запуска. При повторном применении правил она могла бы поломать настройки iptables от zapret. В этом случае правила для iptables должны быть прикручены к вашему фаерволу отдельно от запуска tpws или nfqws. _Следующие вызовы позволяют применить или убрать правила iptables отдельно:_ ``` /opt/zapret/init.d/sysv/zapret start_fw /opt/zapret/init.d/sysv/zapret stop_fw /opt/zapret/init.d/sysv/zapret restart_fw ``` _А так можно запустить или остановить демоны отдельно от фаервола:_ ``` /opt/zapret/init.d/sysv/zapret start_daemons /opt/zapret/init.d/sysv/zapret stop_daemons /opt/zapret/init.d/sysv/zapret restart_daemons ``` `nftables` сводят практически на нет конфликты между разными системами управления, поскольку позволяют использовать независимые таблицы и хуки. Используется отдельная nf-таблица "zapret". Если ваша система ее не будет трогать, скорее всего все будет нормально. _Для `nftables` предусмотрено несколько дополнительных вызовов:_ Посмотреть set-ы интерфейсов, относящихся к lan, wan и wan6. По ним идет завертывание трафика. А так же таблицу flow table с именами интерфейсов ingress hook.\ `/opt/zapret/init.d/sysv/zapret list_ifsets` Обновить set-ы интерфейсов, относящихся к lan, wan и wan6. Для традиционных linux список интерфейсов берется из переменных конфига IFACE_LAN, IFACE_WAN. Для OpenWrt определяется автоматически. Множество lanif может быть расширено параметром OPENWRT_LAN. Все интерфейсы lan и wan так же добавляются в ingress hook от flow table.\ `/opt/zapret/init.d/sysv/zapret reload_ifsets` Просмотр таблицы без содержимого set-ов. Вызывает `nft -t list table inet zapret`\ `/opt/zapret/init.d/sysv/zapret list_table` _Так же возможно прицепиться своим скриптом к любой стадии применения и снятия фаервола со стороны zapret скриптов:_ ``` INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" ``` Эти настройки доступны в config. Может быть полезно, если вам нужно использовать nftables set-ы, например `ipban`/`ipban6`. nfset-ы принадлежат только одной таблице, следовательно вам придется писать правила для таблицы zapret, а значит нужно синхронизироваться с применением/снятием правил со стороны zapret скриптов. ## Вариант custom custom скрипты - это маленькие shell программы, управляющие нестандартными режимами применения zapret или частными случаями, которые не могут быть интегрированы в основную часть без загромождения и замусоривания кода. Для применения custom следует помещать файлы в следующие директории в зависимости от вашей системы: ``` /opt/zapret/init.d/sysv/custom.d /opt/zapret/init.d/openwrt/custom.d /opt/zapret/init.d/macos/custom.d ``` Директория будет просканирована в алфавитном порядке, и каждый скрипт будет применен. В `init.d` имеется `custom.d.examples.linux`, в `init.d/macos` - `custom.d.examples`. Это готовые скрипты, которые можно копировать в `custom.d`. Их можно взять за основу для написания собственных. ***Для linux пишется код в функции*** ``` zapret_custom_daemons zapret_custom_firewall zapret_custom_firewall_nft zapret_custom_firewall_nft_flush ``` ***Для macos*** ``` zapret_custom_daemons zapret_custom_firewall_v4 zapret_custom_firewall_v6 ``` zapret_custom_daemons поднимает демоны **nfqws**/**tpws** в нужном вам количестве и с нужными вам параметрами. В первом параметре передается код операции: 1 = запуск, 0 = останов. Схема запуска демонов в OpenWrt отличается - используется procd. Поэтому логика останова отсутствует за ненадобностью, останов никогда не вызывается. zapret_custom_firewall поднимает и убирает правила `iptables`. В первом параметре передается код операции: 1 = запуск, 0 = останов. zapret_custom_firewall_nft поднимает правила nftables. Логика останова отсутствует за ненадобностью. Стандартные цепочки zapret удаляются автоматически. Однако, sets и правила из ваших собственных цепочек не удаляются. Их нужно подчистить в zapret_custom_firewall_nft_flush. Если set-ов и собственных цепочек у вас нет, функцию можно не определять или оставить пустой. Если вам не нужны iptables или nftables - можете не писать соответствующую функцию. В linux можно использовать локальные переменные `FW_EXTRA_PRE` и `FW_EXTRA_POST`.\ `FW_EXTRA_PRE` добавляет код к правилам ip/nf tables до кода, генерируемого функциями-хелперами.\ `FW_EXTRA_POST` добавляет код после. В linux функции-хелперы добавляют правило в начало цепочек, то есть перед уже имеющимися. Поэтому специализации должны идти после более общих вариантов. Для macos правило обратное. Там правила добавляются в конец. По этой же причине фаервол в Linux сначала применяется в стандартном режиме, потом custom, а в MacOS сначала custom, потом стандартный режим. В macos firewall-функции ничего сами никуда не заносят. Их задача - лишь выдать текст в stdout, содержащий правила для pf-якоря. Остальное сделает обертка. Особо обратите внимание на номер демона в функциях `run_daemon` , `do_daemon`, `do_tpws`, `do_tpws_socks`, `do_nfqws` , номера портов **tpws** и очередей **nfqueue**. Они должны быть уникальными во всех скриптах. При накладке будет ошибка. Поэтому используйте функции динамического получения этих значений из пула. `custom` скрипты могут использовать переменные из `config`. Можно помещать в `config` свои переменные и задействовать их в скриптах. Можно использовать функции-хелперы. Они являются частью общего пространства функций shell. Полезные функции можно взять из примеров скриптов. Так же смотрите `common/*.sh`. Используя хелпер функции, вы избавитесь от необходимости учитывать все возможные случаи типа наличия/отсутствия ipv6, является ли система роутером, имена интерфейсов, ...Хелперы это учитывают. Вам нужно сосредоточиться лишь на фильтрах `{ip,nf}tables` и параметрах демонов. ## Простая установка `install_easy.sh` автоматизирует ручные варианты процедур установки. Он поддерживает OpenWrt, linux системы на базе systemd или openrc и MacOS. Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". Если система запуска поддерживается, но используется не поддерживаемый инсталлятором менеджер пакетов или названия пакетов не соответствуют прописанным в инсталлятор, пакеты нужно установить вручную. Всегда требуется curl. `ipset` - только для режима `iptables`, для `nftables` - не нужен. Для совсем обрезанных дистрибутивов (alpine) требуется отдельно установить `iptables` и `ip6tables`, либо `nftables`. В комплекте идут статические бинарники для большинства архитектур. Какой-то из них подойдет с вероятностью 99%. Но если у вас экзотическая система, инсталлятор попробует собрать бинарники сам через make. Для этого нужны gcc, make и необходимые **-dev** пакеты. Можно форсировать режим компиляции следующим вызовом: `install_easy.sh make` Под OpenWrt все уже сразу готово для использования системы в качестве роутера. Имена интерфейсов WAN и LAN известны из настроек системы. Под другими системами роутер вы настраиваете самостоятельно. Инсталлятор в это не вмешивается. Инсталлятор в зависимости от выбранного режима может спросить LAN и WAN интерфейсы. Нужно понимать, что заворот проходящего трафика на **tpws** в прозрачном режиме происходит до выполнения маршрутизации, следовательно возможна фильтрация по LAN и невозможна по WAN. Решение о завороте на **tpws** локального исходящего трафика принимается после выполнения маршрутизации, следовательно ситуация обратная: LAN не имеет смысла, фильтрация по WAN возможна. Заворот на **nfqws** происходит всегда после маршрутизации, поэтому к нему применима только фильтрация по WAN. Возможность прохождения трафика в том или ином направлении настраивается вами в процессе конфигурации роутера. Деинсталляция выполняется через `uninstall_easy.sh`. После выполнения деинсталляции можно удалить каталог `/opt/zapret`. ## Установка под systemd Если вам нравится systemd и хочется максимально под него заточиться, можно отказаться от скриптов запуска zapret и поднимать инстансы `tpws` и `nfqws` как отдельные юниты systemd. При этом вам придется вручную написать правила iptables/nftables и каким-то образом их поднимать. Например, написать дополнительный systemd unit для этого. Так же требуется собрать бинарники особым образом через `make systemd`. В комплекте zapret есть шаблоны `init.d/systemd/{nfqws@.service,tpws@.service}`. Краткий перечень команд для их использования приведен в комментариях в этих файлах. ## Простая установка на openwrt Работает только если у вас на роутере достаточно места. Копируем zapret на роутер в `/tmp`. Запускаем установщик:\ `sh /tmp/zapret/install_easy.sh` Он скопирует в `/opt/zapret` только необходимый минимум файлов. После успешной установки можно удалить zapret из tmp для освобождения RAM:\ `rm -r /tmp/zapret` Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". Система простой инсталяции заточена на любое умышленное или неумышленное изменение прав доступа на файлы. Устойчива к репаку под windows. После копирования в `/opt` права будут принудительно восстановлены. ## Установка на openwrt в режиме острой нехватки места на диске Требуется около 120-200 кб на диске. Придется отказаться от всего, кроме **tpws**. **Инструкция для openwrt 22 и выше с nftables** Никаких зависимостей устанавливать не нужно. ***Установка:*** 1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt. 2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`. 3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws` 4) Отредактируйте `/etc/config/tpws` * Если не нужен ipv6, отредактируйте `/etc/nftables.d/90-tpws.nft` и закомментируйте строки с редиректом ipv6. 5) `/etc/init.d/tpws enable` 6) `/etc/init.d/tpws start` 7) `fw4 restart` ***Полное удаление:*** 1) `/etc/init.d/tpws disable` 2) `/etc/init.d/tpws stop` 3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws /usr/bin/tpws` 4) `fw4 restart` **Инструкция для openwrt 21 и ниже с iptables** ***Установите зависимости:*** 1) `opkg update` 2) `opkg install iptables-mod-extra` * только для IPV6: `opkg install ip6tables-mod-nat` Убедитесь, что в `/etc/firewall.user` нет ничего значимого. Если есть - не следуйте слепо инструкции. Объедините код или создайте свой `firewall include` в `/etc/config/firewall`. ***Установка:*** 1) Скопируйте все из `init.d/openwrt-minimal/tpws/*` в корень openwrt. 2) Скопируйте бинарник **tpws** подходящей архитектуры в `/usr/bin/tpws`. 3) Установите права на файлы: `chmod 755 /etc/init.d/tpws /usr/bin/tpws` 4) Отредактируйте `/etc/config/tpws` * Если не нужен ipv6, отредактируйте /etc/firewall.user и установите там DISABLE_IPV6=1. 5) `/etc/init.d/tpws enable` 6) `/etc/init.d/tpws start` 7) `fw3 restart` ***Полное удаление:*** 1) `/etc/init.d/tpws disable` 2) `/etc/init.d/tpws stop` 3) `rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws` 4) `touch /etc/firewall.user` 5) `fw3 restart` ## Android Без рута забудьте про nfqws и tpws в режиме transparent proxy. tpws будет работать только в режиме `--socks`. Ядра Android имеют поддержку NFQUEUE. nfqws работает. В стоковых ядрах нет поддержки ipset. В общем случае сложность задачи по поднятию ipset варьируется от "не просто" до "почти невозможно". Если только вы не найдете готовое собранное ядро под ваш девайс. tpws будет работать в любом случае, он не требует чего-либо особенного. Хотя linux варианты под Android работают, рекомендуется использовать специально собранные под bionic бинарники. У них не будет проблем с DNS, с локальным временем и именами юзеров и групп.\ Рекомендуется использовать gid 3003 (AID_INET). Иначе можете получить permission denied на создание сокета. Например: `--uid 1:3003`\ В iptables укажите: `! --uid-owner 1` вместо `! --uid-owner tpws`.\ Напишите шелл скрипт с iptables и tpws, запускайте его средствами вашего рут менеджера. Скрипты автозапуска лежат тут:\ magisk : /data/adb/service.d\ supersu: /system/su.d **nfqws** может иметь такой глюк. При запуске с uid по умолчанию (0x7FFFFFFF) при условии работы на сотовом интерфейсе и отключенном кабеле внешнего питания система может частично виснуть. Перестает работать тач и кнопки, но анимация на экране может продолжаться. Если экран был погашен, то включить его кнопкой power невозможно. Изменение UID на низкий (--uid 1 подойдет) позволяет решить эту проблему. Глюк был замечен на android 8.1 на девайсе, основанном на платформе mediatek. Ответ на вопрос куда поместить tpws на android без рута, чтобы потом его запускать из приложений. Файл заливаем через adb shell в /data/local/tmp/, лучше всего в субфолдер. ``` mkdir /data/local/tmp/zapret adb push tpws /data/local/tmp/zapret chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws ``` Как найти стратегию обхода сотового оператора: проще всего раздать инет на комп. Для этого подойдет любая поддерживаемая ОС. Подключите android через USB кабель к компу и включите режим модема. Прогоните стандартную процедуру blockcheck. При переносе правил на телефон уменьшить TTL на 1, если правила с TTL присутствуют в стратегии. Если проверялось на windows, убрать параметры `--wf-*`. Работа blockcheck в android shell не поддерживается, но имея рута можно развернуть rootfs какого-нибудь дистрибутива linux. Это лучше всего делать с компа через adb shell. Если компа нет, то развертка chroot - единственный вариант, хотя и неудобный. Подойдет что-то легковесное, например, alpine или даже OpenWrt. Если это не эмулятор android, то универсальная архитектура - arm (любой вариант). Если вы точно знаете, что ОС у вас 64-разрядная, то лучше вместо arm - arm64. Выяснить архитектуру можно командой `uname -a`. ``` mount --bind /dev /data/linux/dev mount --bind /proc /data/linux/proc mount --bind /sys /data/linux/sys chroot /data/linux ``` Первым делом вам нужно будет один раз настроить DNS. Сам он не заведется. `echo nameserver 1.1.1.1 >/etc/resolv.conf` Далее нужно средствами пакетного менеджера установить iptables-legacy. Обязательно **НЕ** iptables-nft, который, как правило, присутствует по умолчанию. В ядре android нет nftables.\ `ls -la $(which iptables)`\ Линк должен указывать на legacy вариант. Если нет, значит устанавливайте нужные пакеты вашего дистрибутива, и убеждайтесь в правильности ссылок.\ `iptables -S`\ Так можно проверить, что ваш `iptables` увидел то, что туда насовал android. `iptables-nft` выдаст ошибку. Далее качаем zapret в `/opt/zapret`. Обычные действия с `install_prereq.sh`, `install_bin.sh`, `blockcheck.sh`. Учтите, что стратегии обхода сотового оператора и домашнего wifi вероятно будут разные. Выделить сотового оператора легко через параметр iptables `-o <имя интерфейса>`. Имя может быть, например, `ccmni0`. Его легко увидеть через `ifconfig`. Wifi сеть - обычно `wlan0`. Переключать blockcheck между оператором и wifi можно вместе со всем инетом - включив или выключив wifi. Если найдете стратегию для wifi и впишите ее в автостарт, то при подключении к другому wifi она может не сработать или вовсе что-то поломать, потому подумайте стоит ли. Может быть лучше сделать скрипты типа "запустить обход домашнего wifi", "снять обход домашнего wifi", и пользоваться ими по необходимости из терминала. Но домашний wifi лучше все-же обходить на роутере. ## Мобильные модемы и роутеры huawei Устройства типа E3372, E8372, E5770 разделяют общую идеологию построения системы. Имеются 2 вычислительных ядра. Одно ядро выполняет vxworks, другое - linux. На 4pda имеются модифицированные прошивки с telnet и adb. Их и нужно использовать. Дальнейшие утверждения проверены на E8372. На других может быть аналогично или похоже. Присутствуют дополнительные аппаратные блоки для offload-а сетевых функций. Не весь трафик идет через linux. Исходящий трафик с самого модема проходит цепочку OUTPUT нормально, на FORWARD =>wan часть пакетов выпадает из tcpdump. tpws работает обычным образом. `nfqueue` поломан, можно собрать фиксящий модуль https://github.com/im-0/unfuck-nfqueue-on-e3372h, используя исходники с huawei open source. Исходники содержат тулчейн и полусобирающееся, неактуальное ядро. Конфиг можно взять с рабочего модема из `/proc/config.gz`. С помощью этих исходников умельцы могут собрать модуль `unfuck_nfqueue.ko`. После его применения NFQUEUE и nfqws для arm работают нормально. Чтобы избежать проблемы с offload-ом при использовании nfqws, следует комбинировать tpws в режиме tcp proxy и nfqws. Правила NFQUEUE пишутся для цепочки OUTPUT. connbytes придется опускать, поскольку модуля в ядре нет. Но это не смертельно. Скрипт автозапуска - `/system/etc/autorun.sh`. Создайте свой скрипт настройки zapret, запускайте из конца autorun.sh через "&". Скрипт должен в начале делать sleep 5, чтобы дождаться поднятия сети и iptables от huawei. > [!WARNING] > На этом модеме происходят хаотические сбросы соединений tcp по непонятным причинам. > Выглядит это так, если запускать curl с самого модема: ``` curl www.ru curl: (7) Failed to connect to www.ru port 80: Host is unreachable ``` Возникает ошибка сокета EHOSTUNREACH (errno -113). То же самое видно в tpws. В броузере не подгружаются части веб страниц, картинки, стили. В tcpdump на внешнем интерфейсе eth_x виден только единственный и безответный SYN пакет, без сообщений ICMP. ОС каким-то образом узнает о невозможности установить TCP соединение и выдает ошибку. Если выполнять подключение с клиента, то SYN пропадают, соединение не устанавливается. ОС клиента проводит ретрансмиссию, и с какого-то раза подключение удается. Поэтому без tcp проксирования в этой ситуации сайты тупят, но загружаются, а с проксированием подключение выполняется, но вскоре сбрасывается без каких-либо данных, и броузеры не пытаются установить его заново. Поэтому качество броузинга с tpws может быть хуже, но дело не в tpws. Частота сбросов заметно возрастает, если запущен торент клиент, имеется много tcp соединений. Однако, причина не в переполнении таблицы conntrack. Увеличение лимитов и очистка conntrack не помогают. Предположительно эта особенность связана с обработкой пакетов сброса соединения в hardware offload. Точного ответа на вопрос у меня нет. Если вы знаете - поделитесь, пожалуйста. Чтобы не ухудшать качество броузинга, можно фильтровать заворот на tpws по ip фильтру. Поддержка ipset отсутствует. Значит, все, что можно сделать - создать индивидуальные правила на небольшое количество хостов. Некоторые наброски скриптов присутствуют в [files/huawei](./../files/huawei/). _Не готовое решение!_ Смотрите, изучайте, приспосабливайте.\ Здесь можно скачать готовые полезные статические бинарники для arm, включая curl : https://github.com/bol-van/bins ## FreeBSD, OpenBSD, MacOS Описано в [документации BSD](./bsd.md) ## Windows Описано в [документации Windows](./windows.md) ## Другие прошивки Для статических бинарников не имеет значения на чем они запущены: PC, android, приставка, роутер, любой другой девайс. Подойдет любая прошивка, дистрибутив linux. Статические бинарники запустятся на всем. Им нужно только ядро с необходимыми опциями сборки или модулями. Но кроме бинарников в проекте используются еще и скрипты, в которых задействуются некоторые стандартные программы. Основные причины почему нельзя просто так взять и установить эту систему на что угодно: * отсутствие доступа к девайсу через shell * отсутствие рута * отсутствие раздела r/w для записи и энергонезависимого хранения файлов * отсутствие возможности поставить что-то в автозапуск * отсутствие cron * неотключаемый flow offload или другая проприетарщина в netfilter * недостаток модулей ядра или опций его сборки * недостаток модулей iptables (/usr/lib/iptables/lib*.so) * недостаток стандартных программ (типа ipset, curl) или их кастрированность (облегченная замена) * кастрированный или нестандартный шелл sh Если в вашей прошивке есть все необходимое, то вы можете адаптировать zapret под ваш девайс в той или иной степени. Может быть у вас не получится поднять все части системы, однако вы можете хотя бы попытаться поднять tpws и завернуть на него через -j REDIRECT весь трафик на порт 80. Если вам есть куда записать tpws, есть возможность выполнять команды при старте, то как минимум это вы сделать сможете. Скорее всего поддержка REDIRECT в ядре есть. Она точно есть на любом роутере, на других устройствах под вопросом. NFQUEUE, ipset на большинстве прошивок отсутствуют из-за ненужности. Пересобрать ядро или модули для него будет скорее всего достаточно трудно. Для этого вам необходимо будет по крайней мере получить исходники вашей прошивки. User mode компоненты могут быть привнесены относительно безболезненно, если есть место куда их записать. Специально для девайсов, имеющих область r/w, существует проект entware. Некоторые прошивки даже имеют возможность его облегченной установки через веб интерфейс. entware содержит репозиторий user-mode компонент, которые устанавливаются в /opt. С их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра. Можно попытаться использовать sysv init script таким образом, как это описано в разделе "Прикручивание к системе управления фаерволом или своей системе запуска". В случае ругани на отсутствие каких-то базовых программ, их следует восполнить посредством entware. Перед запуском скрипта путь к дополнительным программам должен быть помещен в PATH. _Подробное описание настроек для других прошивок выходит за рамки данного проекта._ OpenWrt является одной из немногих относительно полноценных linux систем для embedded devices. Она характеризуется следующими вещами, которые и послужили основой выбора именно этой прошивки: * полный root доступ к девайсу через shell. на заводских прошивках чаще всего отсутствует, на многих альтернативных есть * корень r/w. это практически уникальная особенность OpenWrt. заводские и большинство альтернативных прошивок построены на базе squashfs root (r/o), а конфигурация хранится в специально отформатированной области встроенной памяти, называемой nvram. не имеющие r/w корня системы сильно кастрированы. они не имеют возможности доустановки ПО из репозитория без специальных вывертов и заточены в основном на чуть более продвинутого, чем обычно, пользователя и управление имеющимся функционалом через веб интерфейс, но функционал фиксированно ограничен. альтернативные прошивки, как правило, могут монтировать r/w раздел в какую-то область файловой системы, заводские обычно могут монтировать лишь флэшки, подключенные к USB, и не факт, что есть поддержка unix файловых системы. может быть поддержка только fat и ntfs. * возможность выноса корневой файловой системы на внешний носитель (extroot) или создания на нем оверлея (overlay) * наличие менеджера пакетов opkg и репозитория софта * flow offload предсказуемо, стандартно и выборочно управляем, а так же отключаем * в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно. * в репозитории есть все модули iptables, их можно доустановить через opkg * в репозитории есть огромное количество стандартных программ и дополнительного софта * наличие SDK, позволяющего собрать недостающее ## Обход блокировки через сторонний хост Если не работает автономный обход, приходится перенаправлять трафик через сторонний хост. Предлагается использовать прозрачный редирект через socks5 посредством `iptables+redsocks`, либо `iptables+iproute+vpn`. Настройка варианта с redsocks на OpenWrt описана в [redsocks.txt](./redsocks.txt). Настройка варианта с `iproute+wireguard` - в [wireguard_iproute_openwrt.txt](./wireguard_iproute_openwrt.txt). ## Почему стоит вложиться в покупку VPS VPS — это виртуальный сервер. Существует огромное множество датацентров, предлагающих данную услугу. На VPS могут выполняться какие угодно задачи. От простого веб-сайта до навороченной системы собственной разработки. Можно использовать VPS и для поднятия собственного VPN или прокси. Сама широта возможных способов применения и распространенность услуги сводят к минимуму возможности регуляторов по бану сервисов такого типа. Да, если введут белые списки, то решение загнется, но это будет уже другая реальность, в которой придется изобретать иные решения. Пока этого не сделали, никто не будет банить хостинги просто потому, что они предоставляют хостинг услуги. Вы, как индивидуум, скорее всего, никому не нужны. Подумайте чем вы отличаетесь от известного VPN провайдера. VPN-провайдер предоставляет _простую_ и _доступную_ услугу по обходу блокировок для масс. Этот факт делает его первоочередной целью блокировки. РКН направит уведомление, после отказа сотрудничать заблокирует VPN. Предоплаченная сумма пропадет. У регуляторов нет и никогда не будет ресурсов для тотальной проверки каждого сервера в сети. Возможен китайский расклад, при котором DPI выявляет VPN-протоколы и динамически банит IP серверов, предоставляющих нелицензированный VPN. Но имея знания, голову, вы всегда можете обфусцировать VPN трафик или применить другие типы VPN, более устойчивые к анализу на DPI, или просто менее широкоизвестные, а следовательно с меньшей вероятностью обнаруживаемые регулятором. У вас есть свобода делать на вашем VPS все что вы захотите, адаптируясь к новым условиям. Да, это потребует знаний. Вам выбирать учиться и держать ситуацию под контролем, когда вам ничего запретить не могут, или покориться системе. VPS можно приобрести в множестве мест. Существуют специализированные на поиске предложений VPS порталы.\ Например, [вот этот](https://vps.today). Для персонального VPN сервера обычно достаточно самой минимальной конфигурации, но с безлимитным трафиком или с большим лимитом по трафику (терабайты). Важен и тип VPS. OpenVZ подойдёт для OpenVPN, но вы не поднимете на нем WireGuard, IPsec, то есть все, что требует kernel mode. Для kernel mode требуется тип виртуализации, предполагающий запуск полноценного экземпляра ОС linux вместе с ядром. Подойдут KVM, Xen, Hyper-V, VMware. По цене можно найти предложения, которые будут дешевле готовой VPN услуги, но при этом вы сам хозяин в своей лавке и не рискуете попасть под бан регулятора, разве что «заодно» — под ковровую бомбардировку с баном миллионов IP. Кроме того, если вам совсем все кажется сложным, прочитанное вызывает ступор и вы точно знаете, что ничего из описанного сделать не сможете, то вы сможете хотя бы использовать динамическое перенаправление портов SSH для получения шифрованного SOCKS-прокси и прописать его в браузер. Знания linux не нужны совсем. Это вариант наименее напряжный для чайников, хотя и не самый удобный в использовании. ## Поддержать разработчика USDT ERC `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` USDT TRC `TEzAAtn4VhndqEaAyuCM78xh5W2gCjwWEo` BTC `bc1qhqew3mrvp47uk2vevt5sctp7p2x9m7m5kkchve` ETH `0x3d52Ce15B7Be734c53fc9526ECbAB8267b63d66E` ================================================ FILE: docs/redsocks.txt ================================================ Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего понимания смысла, то маловероятно, что у вас что-то заработает. Не тратьте свое время напрасно. Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. Прозрачный выборочный заворот tcp соединений на роутере через socks Tor поддерживает "из коробки" режим transparent proxy. Это можно использовать в теории, но практически - только на роутерах с 128 мб памяти и выше. И тор еще и тормозной. Другой вариант напрашивается, если у вас есть доступ к какой-нибудь unix системе с SSH, где сайты не блокируются. Например, у вас есть VPS вне России. Понятийно требуются следующие шаги : 1) Выделять IP, на которые надо проксировать трафик. У нас уже имеется ipset "zapret", технология создания которого отработана. 2) Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks. 3) Установить transparent соксификатор. Redsocks прекрасно подошел на эту роль. 4) Завернуть через iptables или nftables трафик с порта назначения 443 и на ip адреса из ipset/nfset 'zapret' на соксификатор Тоже самое сделать с ipset/nfset 'ipban' для всех tcp портов. Буду рассматривать систему на базе openwrt, где уже установлена система обхода dpi "zapret". Если вам не нужны функции обхода DPI, его можно не включать. Обновление фильтра от этого не зависит. * Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks Т.к. дефолтный dropbear клиент не поддерживает создание socks, то для начала придется заменить dropbear ssh client на openssh : пакеты openssh-client и openssh-client-utils. Устанавливать их нужно с опцией opkg --force-overwrite, поскольку они перепишут ssh клиент от dropbear. После установки пакетов расслабим неоправданно жестокие права : chmod 755 /etc/ssh. Следует создать пользователя, под которым будем крутить ssh client. Допустим, это будет 'proxy'. Сначала установить пакеты shadow-useradd и shadow-su. ------------------ useradd -s /bin/false -d /home/proxy proxy mkdir -p /home/proxy chown proxy:proxy /home/proxy ------------------ Сгенерируем ключ RSA для доступа к ssh серверу. ------------------ su -s /bin/ash proxy cd mkdir -m 700 .ssh cd .ssh ssh-keygen ls exit ------------------ Должны получиться файлы id_rsa и id_rsa.pub. Строчку из id_rsa.pub следует добавить на ssh сервер в файл $HOME/.ssh/authorized_keys. Более подробно о доступе к ssh через авторизацию по ключам : https://beget.com/ru/articles/ssh_by_key Предположим, ваш ssh сервер - vps.mydomain.com, пользователь называется 'proxy'. Проверить подключение можно так : ssh -N -D 1098 -l proxy vps.mydomain.com. Сделайте это под пользователем "proxy", поскольку при первом подключении ssh спросит о правильности hostkey. Соединение может отвалиться в любой момент, поэтому нужно зациклить запуск ssh. Для этого лучший вариант - использовать procd - упрощенная замена systemd на openwrt версий BB и выше. --- /etc/init.d/socks_vps --- #!/bin/sh /etc/rc.common START=50 STOP=50 USE_PROCD=1 USERNAME=proxy COMMAND="ssh -N -D 1098 -l proxy vps.mydomain.com" start_service() { procd_open_instance procd_set_param user $USERNAME procd_set_param respawn 10 10 0 procd_set_param command $COMMAND procd_close_instance } ----------------------------- Этому файлу нужно дать права : chmod +x /etc/init.d/socks_vps Запуск : /etc/init.d/socks_vps start Останов : /etc/init.d/socks_vps stop Включить автозагрузку : /etc/init.d/socks_vps enable Проверка : curl -4 --socks5 127.0.0.1:1098 https://rutracker.org * Организовать прозрачную соксификацию Установить пакет redsocks. Конфиг : -- /etc/redsocks.conf : --- base { log_debug = off; log_info = on; log = "syslog:local7"; daemon = on; user = nobody; group = nogroup; redirector = iptables; } redsocks { local_ip = 127.0.0.127; local_port = 1099; ip = 127.0.0.1; port = 1098; type = socks5; } --------------------------- После чего перезапускаем : /etc/init.d/redsocks restart Смотрим появился ли листенер : netstat -tnlp | grep 1099 В zapret для перенаправления DNAT на интерфейс lo используется 127.0.0.127. Ко всем остальным адресам из 127.0.0.0/8 DNAT может быть заблокирован. Читайте readme.txt про route_localnet. * Завертывание соединений через iptables !! Версии OpenWRT до 21.02 включительно используют iptables + fw3. Более новые перешили на nftables по умолчанию. !! В новых OpenWRT можно снести firewall4 и nftables, заменив их на firewall3 + iptables !! Инструкция относится только к openwrt, где используется iptables. Будем завертывать любые tcp соединения на ip из ipset "ipban" и https на ip из ipset "zapret", за исключением ip из ipset "nozapret". --- /etc/firewall.user ----- SOXIFIER_PORT=1099 . /opt/zapret/init.d/openwrt/functions create_ipset no-update network_find_wan4_all wan_iface for ext_iface in $wan_iface; do network_get_device ext_device $ext_iface 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 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 done prepare_route_localnet ipt 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 ipt 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 ---------------------------- Внести параметр "reload" в указанное место : --- /etc/config/firewall --- config include option path '/etc/firewall.user' option reload '1' ---------------------------- Перезапуск firewall : /etc/init.d/firewall restart * Завертывание соединений через nftables !! Только для версий OpenWRT старше 21.02 nftables не могут использовать ipset. Вместо ipset существует аналог - nfset. nfset является частью таблицы nftable и принадлежит только к ней. Адресация nfset из другой nftable невозможна. Скрипты ipset/* в случае nftables используют nfset-ы в таблице zapret. Чтобы использовать эти nfset-ы в своих правилах, необходимо синхронизироваться с их созданием и вносить свои цепочки в nftable "zapret". Для этого существуют хуки - скрипты, вызываемые из zapret на определенных стадиях инициализации фаервола. Раскомментируйте в /opt/zapret/config строчку INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" Создайте файл /etc/firewall.zapret.hook.post_up и присвойте ему chmod 755. --- /etc/firewall.zapret.hook.post_up --- #!/bin/sh SOXIFIER_PORT=1099 . /opt/zapret/init.d/openwrt/functions cat << EOF | nft -f - 2>/dev/null delete chain inet $ZAPRET_NFT_TABLE my_output delete chain inet $ZAPRET_NFT_TABLE my_prerouting EOF prepare_route_localnet cat << EOF | nft -f - add chain inet $ZAPRET_NFT_TABLE my_output { type nat hook output priority -102; } flush chain inet $ZAPRET_NFT_TABLE my_output 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 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 add chain inet $ZAPRET_NFT_TABLE my_prerouting { type nat hook prerouting priority -102; } flush chain inet $ZAPRET_NFT_TABLE my_prerouting 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 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 EOF ---------------------------- Перезапуск firewall : /etc/init.d/zapret restart_fw * Проверка Все, теперь можно проверять : /etc/init.d/redsocks stop curl -4 https://rutracker.org # должно обломаться с надписью "Connection refused". если не обламывается - значит ip адрес rutracker.org не в ipset, # либо не сработали правила фаервола. например, из-за не установленных модулей ipt /etc/init.d/redsocks start curl -4 https://rutracker.org # должно выдать страницу ================================================ FILE: docs/windows.en.md ================================================ ### tpws Using `WSL` (Windows subsystem for Linux) it's possible to run `tpws` in socks mode under rather new builds of windows 10 and windows server. Its not required to install any linux distributions as suggested in most articles. tpws is static binary. It doesn't need a distribution. Install `WSL` : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all` From release copy `binaries/linux-x86_64/tpws_wsl.tgz` to the target system. Run : `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz` Run tpws : `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 ` Configure socks as `127.0.0.1:1080` in a browser or another program. Cleanup : `wsl --unregister tpws` Tested in windows 10 build 19041 (20.04) with WSL1. `--oob` , `--mss` and `--disorder` do not work. RST detection in autohostlist scheme may not work. WSL may glitch with splice. `--nosplice` may be required. ### winws `winws` is `nfqws` version for windows. It's based on `windivert`. Most functions are working. Large ip filters (ipsets) are not possible. Forwarded traffic and connection sharing are not supported. Administrator rights are required. Working with packet filter consists of two parts 1. In-kernel packet selection and passing selected packets to a packet filter in user mode. In *nix it's done by `iptables`, `nftables`, `pf`, `ipfw`. 2. User mode packet filter processes packets and does DPI bypass magic. Windows does not have part 1. No `iptables` exist. That's why 3rd party packet redirector is used. It's called `windivert`. It works starting from `windows 7`. Kernel driver is signed but it may require to disable secure boot or update windows 7. Read below for windows 7 windivert signing info. Task of `iptables` is done inside `winws` through `windivert` filters. `Windivert` has it's own [filter language](https://reqrypt.org/windivert-doc.html#filter_language). `winws` can automate filter construction using simple ip version and port filter. Raw filters are also supported. ``` --wf-iface=[:] ; numeric network interface and subinterface indexes --wf-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. --wf-tcp=[~]port1[-port2] ; TCP port filter. ~ means negation. multiple comma separated values allowed. --wf-udp=[~]port1[-port2] ; UDP port filter. ~ means negation. multiple comma separated values allowed. --wf-raw-part=|@ ; partial raw windivert filter string or filename --wf-filter-lan=0|1 ; add excluding filter for non-global IP (default : 1) --wf-raw=|@ ; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part --wf-save= ; save windivert filter string to a file and exit --ssid-filter=ssid1[,ssid2,ssid3,...] ; enable winws only if any of specified wifi SSIDs connected --nlm-filter=net1[,net2,net3,...] ; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted. --nlm-list[=all] ; list Network List Manager (NLM) networks. connected only or all. ``` `--wf-l3`, `--wf-tcp`, `--wf-udp` can take multiple comma separated arguments. Interface indexes can be discovered using this command : `netsh int ip show int` If 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. `--wf-raw-part` specifies partial windivert filter. Multiple filter parts are supported. They can also be combined with `--wf-tcp`,`--wf-udp`. `--wf-raw` specifies full windivert filter that replaces `--wf-tcp`,`--wf-udp`,`--wf-raw-part`. Kernel filtering with windivert language is much more effective than passing massive amount of traffic to winws. Use it if possible to save CPU resources. Multiple `winws` processes are allowed. However, it's discouraged to intersect their filters. `--ssid-filter` allows to enable `winws` only if specified wifi networks are connected. `winws` auto detects SSID appearance and disappearance. SSID 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. If 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. `--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". NLM 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. That's why NLM is more universal than `ssid-filter`. `Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`. If you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`. `Cygwin` is required for `blockcheck.sh` support but `winws` itself can be run standalone without cygwin. How to get `windows 7` and `winws` compatible `cygwin` : ``` curl -O https://www.cygwin.com/setup-x86_64.exe setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 ``` You must choose to install `curl`. To compile from sources install `gcc-core`,`make`,`zlib-devel`. Make from directory `nfq` using `make cygwin64` or `make cygwin32` for 64 and 32 bit versions. `winws` requires `cygwin1.dll`, `windivert.dll`, `windivert64.sys` or `windivert32.sys`. You can take them from `binaries/windows-x86_64` or `binaries/windows-x86`. There's no `arm64` signed `windivert` driver and no `cygwin`. But it's possible to use unsigned driver version in test mode and user mode components with x64 emulation. x64 emulation requires `windows 11` and not supported in `windows 10`. ### windows 7 windivert signing Requirements for windows driver signing have changed in 2021. Official free updates of windows 7 ended in 2020. After 2020 for the years paid updates were available (ESU). One of the updates from ESU enables signatures used in windivert 2.2.2-A. There are several options : 1. Take `windivert64.sys` and `windivert.dll` version `2.2.0-C` or `2.2.0-D` from [here](https://reqrypt.org/download). Replace these 2 files in every location they are present. In `zapret-win-bundle` they are in `zapret-winws` и `blockcheck/zapret/nfq` folders. However this option still requires 10+ year old patch that enables SHA256 signatures. If you're using win bundle you can simply run `win7\install_win7.cmd` 3. [Hack ESU](https://hackandpwn.com/windows-7-esu-patching) 4. Use `UpdatePack7R2` from simplix : https://blog.simplix.info If you are in Russia or Belarus temporary change region in Control Panel. ### blockcheck `blockcheck.sh` is written in posix shell and uses some standard posix utilites. Windows does not have them. To execute `blockcheck.sh` use `cygwin` command prompt run as administrator. It's not possible to use `WSL`. It's not the same as `cygwin`. First run once `install_bin.sh` then `blockcheck.sh`. Backslashes in windows paths shoud be doubled. Or use cygwin path notation. ``` cd "C:\\Users\\vasya" cd "C:/Users/vasya" cd "/cygdrive/c/Users/vasya" ``` `Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`. If you want to run `winws` from `cygwin` delete, rename or move `cygwin1.dll`. `Cygwin` is required only for `blockcheck.sh`. Standalone `winws` can be run without it. To simplify things it's advised to use `zapret-win-bundle`. ### zapret-win-bundle To make your life easier there's ready to use [bundle](https://github.com/bol-van/zapret-win-bundle) with `cygwin`,`blockcheck` and `winws`. * `/zapret-winws` - standalone version of `winws` for everyday use. does not require any other folders. * `/zapret-winws/_CMD_ADMIN.cmd` - open `cmd` as administrator in the current folder * `/blockcheck/blockcheck.cmd` - run `blockcheck` with logging to `blockcheck/blockcheck.log` * `/cygwin/cygwin.cmd` - run `cygwin` shell as current user * `/cygwin/cygwin-admin.cmd` - run `cygwin` shell as administrator There're aliases in cygwin shell for `winws`,`blockcheck`,`ip2net`,`mdig`. No need to mess with paths. It's possible to send signals to `winws` using standard unix utilites : `pidof,kill,killall,pgrep,pkill`. `Cygwin` shares common process list per `cygwin1.dll` copy. If you run a `winws` from `zapret-winws` you won't be able to `kill` it because this folder contain its own copy of `cygwin1.dll`. It's possible to use `cygwin` shell to make `winws` debug log. Use `tee` command like this : ``` winws --debug --wf-tcp=80,443 | tee winws.log unix2dos winws.log ``` `winws.log` will be in `cygwin/home/`. `unix2dos` helps with `windows 7` notepad. It's not necessary in `Windows 10` and later. Because 32-bit systems are rare nowadays `zapret-win-bundle` exists only for `Windows x64/arm64`. ### auto start To start `winws` with windows use windows task scheduler. There are `task_*.cmd` batch files in `binaries/windows-x86-64/zapret-winws`. They create, remove, start and stop scheduled task `winws1`. They must be run as administrator. Edit `task_create.cmd` and write your `winws` parameters to `%WINWS1%` variable. If you need multiple `winws` instances clone the code in all cmd files to support multiple tasks `winws1,winws2,winws3,...`. Tasks can also be controlled from GUI `taskschd.msc`. Also you can use windows services the same way with `service_*.cmd`. ### Windows Server winws is linked against wlanapi.dll which is absent by default. To solve this problem run power shell as administrator and execute command `Install-WindowsFeature -Name Wireless-Networking`. Then reboot the system. ================================================ FILE: docs/windows.md ================================================ # Windows ## tpws Запуск tpws возможен только в Linux варианте под **WSL** _(Windows Subsystem for Linux)_. Нативного варианта под Windows нет, поскольку он использует epoll, которого под windows не существует. tpws в режиме socks можно запускать под более-менее современными билдами windows 10 и windows server с установленным WSL. Совсем не обязательно устанавливать дистрибутив убунту, как вам напишут почти в каждой статье про WSL, которую вы найдете в сети. tpws - статический бинарик, ему дистрибутив не нужен. Установить WSL : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all` Из релиза скопировать на целевую систему `binaries/linux-x86_64/tpws_wsl.tgz`. Выполнить : `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz` Запустить : `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <параметры_дурения>` Прописать socks `127.0.0.1:1080` в браузер или другую программу. Удаление : `wsl --unregister tpws` > [!NOTE] > Проверено на windows 10 build 19041 (20.04) под WSL1. На WSL2 эти команды могут не сработать. Если у вас есть WSL2, значит у вас есть работающая виртуалка с linux. Если вы умеете с ней обращаться, tpws на ней запустить возможно без всяких проблем. Возможные проблемы: - Не работают функции `--oob` и `--mss` из-за ограничений реализации WSL. `--disorder` не работает из-за особенностей tcp/ip стека windows. - Может не срабатывать детект RST в autohostlist. - WSL может глючить со splice, приводя к зацикливанию процесса. Может потребоваться `--nosplice`. - Не поддерживается tcp user timeout. Чтобы избавиться от сообщений об ошибке добавляйте : `--local-tcp-user-timeout=0 --remote-tcp-user-timeout=0`. Эти сообщения только информативные, на работу они не влияют. ## winws Это вариант пакетного фильтра nfqws для Windows, построенный на базе windivert. Все функции работоспособны, однако функционал ipset в ядре отсутствует. Он реализован в user mode. Фильтры по большому количеству IP адресов невозможны. Работа с проходящим трафиком, например в случае "расшаривания" соединения, невозможна. Для работы с windivert требуются права администратора. Специфические для unix параметры, такие как `--uid`, `--user` и тд, исключены. Все остальные параметры аналогичны nfqws и dvtws. Работа с пакетным фильтром основана на двух действиях : 1) Выделение перенаправляемого трафика в режиме ядра и передача его пакетному фильтру в user mode. 2) Собственно обработка перенаправленных пакетов в пакетном фильтре. В windows отсутствуют встроенные средства для перенаправления трафика, такие как _iptables_, _nftables_, _pf_ или _ipfw_. Поэтому используется сторонний драйвер ядра windivert. Он работает, начиная с windows 7. На системах с включенным secure boot могут быть проблемы из-за подписи драйвера. В этом случае отключите `secureboot` или включите режим `testsigning`. На windows 7, вероятно, будут проблемы с загрузкой windivert. Читайте ниже соответствующий раздел. Задача _iptables_ в **winws** решается внутренними средствами через фильтры windivert. У windivert существует собственный язык фильтров, похожий на язык фильтров wireshark. [Документация по фильтрам windivert.](https://reqrypt.org/windivert-doc.html#filter_language) Чтобы не писать сложные фильтры вручную, предусмотрены различные упрощенные варианты автоматического построения фильтров. ``` --wf-iface=[.] ; числовые индексы интерфейса и суб-интерфейса --wf-l3=ipv4|ipv6 ; фильтр L3 протоколов. по умолчанию включены ipv4 и ipv6. --wf-tcp=[~]port1[-port2] ; фильтр портов для tcp. ~ означает отрицание --wf-udp=[~]port1[-port2] ; фильтр портов для udp. ~ означает отрицание --wf-raw-part=|@ ; частичный windivert фильтр из параметра или из файла. имени файла предшествует символ @. может быть множество частей. сочетается с --wf-tcp,--wf-udp. --wf-filter-lan=0|1 ; отфильтровывать адреса назначения, не являющиеся глобальными inet адресами ipv4 или ipv6. по умолчанию - 1. --wf-raw=|@ ; полный windivert фильтр из параметра или из файла. имени файла предшествует символ @. замещает --wf-raw-part,--wf-tcp,--wf-udp. --wf-save= ; сохранить сконструированный фильтр windivert в файл для последующей правки вручную --ssid-filter=ssid1[,ssid2,ssid3,...] ; включать winws только когда подключена любая из указанных wifi сетей --nlm-filter=net1[,net2,net3,...] ; включать winws только когда подключена любая из указанных сетей NLM --nlm-list[=all] ; вывести список сетей NLM. по умолчанию только подключенных, all - всех. ``` Параметры `--wf-l3`, `--wf-tcp`, `--wf-udp` могут брать несколько значений через запятую. Номера интерфейсов можно узнать так : `netsh int ip show int`. Некоторых типы соединений там не увидеть. В этом случае запускайте **winws** с параметром `--debug` и смотрите IfIdx там. SubInterface используется windivert, но практически всегда **0**, его можно не указывать. Вероятно, он нужен в редких случаях. Конструктор стандартных фильтров автоматически включает входящие tcp пакеты с tcp synack и tcp rst для корректной работы функций autottl и autohostlist. При включении autohostlist так же перенаправляются пакеты данных с http redirect с кодами 302 и 307. Если не указаное иное, добавляется фильтр на исключение не-интернет адресов ipv4 и ipv6. Для сложных нестандартных сценариев могут потребоваться свои фильтры. Полный фильтр --wf-raw замещает все остальное. Частичные фильтры `--wf-raw-part` совместимы друг с другом и `--wf-tcp` и `--wf-udp`. Они позволяют исключить написание громоздких полных фильтров, сосредоточившись лишь на добавлении какого-то особенного пейлоада. `--wf-save` позволяет записать итоговый windivert фильтр в файл. Максимальный размер фильтра - **16 Kb**. Фильтрация windivert производится в ядре. Это несравнимо легче по ресурсам, чем перенаправлять пакеты в пространство user mode, чтобы winws принимал решение. Поэтому пользуйтесь по максимуму возможностями windivert. Например, если вам нужно дурить wireguard на все порты, вам придется перенаправить все порты на winws. Или же написать windivert фильтр, который отсечет wireguard по содержимому пакета. Разница в нагрузке на процессор колоссальна. В первом случае - до 100% одного ядра cpu в зависимости от объема исходящего udp трафика (привет, торрент и uTP), во втором - близко к 0. Кроме нагрузки на процессор еще можете порезать себе скорость, тк одно ядро не будет справляться с обработкой вашего гигабитного интернета. А на старых ноутах еще и получите самолетный вой системы охлаждения, приводящий к ее износу. Можно запускать несколько процессов **winws** с разными стратегиями. Однако, не следует делать пересекающиеся фильтры. В `--ssid-filter` можно через запятую задать неограниченное количество имен wifi сетей (**SSID**). Если задана хотя бы одна сеть, то winws включается только, если подключен указанный **SSID**. Если **SSID** исчезает, winws отключается. Если **SSID** появляется снова, winws включается. Это нужно, чтобы можно было применять раздельное дурение к каждой отдельной wifi сети. Названия сетей должны быть написаны в том регистре, в котором их видит система. Сравнение идет с учетом регистра! При этом нет никаких проверок куда реально идет трафик. Если одновременно подключен, допустим, ethernet, и трафик идет туда, то дурение включается и выключается просто по факту наличия wifi сети, на которую трафик может и не идти. И это может сломать дурение на ethernet. Поэтому полезно так же будет добавить фильтр `--wf-iface` на индекс интерфейса wifi адаптера, чтобы не трогать другой трафик. `--nlm-filter` аналогичен `--ssid-filter`, но работает с именами или GUIDами сетей Network List Manager (NLM). Это те сети, которые вы видите в панели управления в разделе "Центр управления сетями и общим доступом". Под сетью подразумевается не конкретный адаптер, а именно сетевое окружение конкретного подключения. Обычно проверяется mac адрес шлюза. К сети можно подключиться через любой адаптер, и она останется той же самой. Если подключиться, допустим, к разными роутерам по кабелю, то будут разные сети. А если к одному роутеру через 2 разных сетевых карточки на том же компе - будет одна сеть. NLM абстрагирует типы сетевых адаптеров. Он работает как с wifi, так и с ethernet и любыми другими. Поэтому это более универсальный метод, чем **SSID** фильтр. Однако, есть и неприятная сторона. В windows 7 вы легко могли ткнуть на иконку сети и выбрать тип : private или public. Там же вы могли посмотреть список сетей и объединить их. Чтобы, допустим, вы могли подключаться по кабелю и wifi к одному роутеру, и система эти подключения воспринимала как одну сеть. В следующих версиях windows они эти возможности сильно порезали. Похоже нет встроенных средств полноценно управлять network locations в win10/11. Кое-что есть в **powershell**. Можно поковыряться напрямую в реестре здесь : `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList` Нужно менять ProfileGUID в `Signatures\Unmanaged`. Имена можно поменять в Profiles. Есть кое-какие сторонние утилиты. Кое-что находится, позволяющее посмотреть и удалить network profiles, но не объединить. Факт, что в ms они это сильно испортили. Движок network list все тот же, и он способен на все то, что было в win7. Можно не бороться с этой проблемой, а просто указывать через запятую те названия сетей или GUIDы, которые выбрала система. Или если у вас только wifi, то использовать `--ssid-filter`. Там хотя бы есть гарантия, что **SSID** соответствуют реальности, а система их не назвала как-то по-своему. Если в путях присутствуют национальные символы, то при вызове winws из `cmd` или `bat` кодировку нужно использовать **OEM**. Для русского языка это 866. Пути с пробелами нужно брать в кавычки. При использовании опции @ кодировка в файле должна быть **UTF-8** без **BOM mark**. Существует неочевидный момент, касаемый запуска **winws** из cygwin shell\`а. Если в директории, где находится winws, находится копия `cygwin1.dll`, **winws** не запустится. Если нужен запуск под cygwin, то следует удалить или переместить `cygwin1.dll` из `binaries/windows-x86_64`. Это нужно для работы blockcheck. Из cygwin шелла можно посылать winws сигналы через `kill` точно так же, как в `*nix`. Как получить совместимый с windows 7 и winws cygwin : `curl -O https://www.cygwin.com/setup-x86_64.exe` `setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215` > [!IMPORTANT] > Следует выбрать установку curl. Для сборки из исходников требуется _gcc-core_,_make_,_zlib-devel_. Собирать из директории nfq командой `make cygwin64` или `make cygwin32` для 64 и 32 битных версий соответственно. **winws** требует `cygwin1.dll`, `windivert.dll`, `windivert64.sys` или `windivert32.sys`. Их можно взять из `binaries/win64` и `binaries/win32`. Для _arm64_ windows нет подписанного драйвера windivert и нет cygwin. Однако, эмуляция x64 windows 11 позволяет использовать все, кроме WinDivert64.sys без изменений. Но при этом надо заменить WinDivert64.sys на неподписанную _arm64_ версию и установить режим testsigning. ## Windows 7 и windivert Требования к подписи драйверов windows изменились в 2021 году. Официальные бесплатные обновления windows 7 закончились в 2020. После этого несколько лет продолжали идти платные обновления по программе **ESU**. Именно в этих **ESU** обновлениях находится обновление ядра windows 7, позволяющее загрузить драйвер _windivert 2.2.2-A_, который идет в поставке zapret. Поэтому варианты следующие : 1) Взять `windivert64.sys` и `windivert.dll` версии _2.2.0-C_ или _2.2.0-D_ отсюда : https://reqrypt.org/download и заменить эти 2 файла. В [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). Надо менять в обоих местах. Альтернативный вариант при использовании win bundle - запустить `win7\install_win7.cmd` > [!NOTE] > Этот вариант проверен и должен работать. Тем не менее патч 10 летней давности, который включает SHA256 сигнатуры, все еще необходим. 2) Взломать **ESU** : https://hackandpwn.com/windows-7-esu-patching/ http://www.bifido.net/tweaks-and-scripts/8-extended-security-updates-installer.html и обновить систему 3) Использовать готовый патчер-взламыватель ESU - "BypassESU". Его надо искать по названию. 4) Использовать UpdatePack7R2 от simplix : https://blog.simplix.info > [!WARNING] > Но с этим паком есть проблема. Автор из Украины, он очень обиделся на русских. > Если в панели управления стоит регион RU или BY, появляется неприятный диалог. > Чтобы эту проблему обойти, можно поставить временно любой другой регион, потом вернуть. > Так же нет никаких гарантий, что автор не насовал туда какой-то зловредный код. > Использовать на свой страх и риск. Более безопасный вариант - скачать последнюю нормальную довоенную версию : 22.2.10 https://nnmclub.to/forum/viewtopic.php?t=1530323 Ее достаточно, чтобы _windivert 2.2.2-A_ заработал на windows 7. ## blockcheck `blockcheck.sh` написан на _posix shell_ и требует некоторых стандартных утилит _posix_. В windows, естественно, этого нет. Потому просто так запустить `blockcheck.sh` невозможно. Для этого требуется скачать и установить _cygwin_ так , как описано в предыдущем разделе. Следует запустить от имени администратора _cygwin shell_ через `cygwin.bat`. В нем нужно пройти в директорию с zapret. Обратные слэши путей windows нужно удваивать, менять на прямые слэши, либо использовать отображение на unix path. Корректные варианты : - `cd C:\\Users\\vasya` - `cd C:/Users/vasya` - `cd /cygdrive/c/Users/vasya` Существует неочевидный момент, каcаемый запуска **winws** из _cygwin_ шелла. Если в директории, где находится **winws**, есть копия `cygwin1.dll`, **winws** не запустится. Нужно переименовать файл `cygwin1.dll`. Далее все как в _*nix_ : 1 раз `./install_bin.sh` , затем `./blockcheck.sh`. WSL использовать нельзя, это не то же самое. _cygwin_ для обычной работы **winws** не нужен. Однако, хотя такой способ и работает, использование **winws** сильно облегчает [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle). Там нет проблемы с `cygwin.dll`. ## Zapret-win-bundle Можно не возиться с _cygwin_, а взять готовый пакет, включающий в себя _cygwin_ и _blockcheck_ : https://github.com/bol-van/zapret-win-bundle Там сделан максимум удобств для сосредоточения на самом zapret, исключая возню с установкой _cygwin_, заходами в директории, запусками под администратором и прочими сугубо техническими моментами, в которых могут быть ошибки и непонимания, а новичок без базиса знаний может и вовсе запутаться. `/zapret-winws` - здесь все, что нужно для запуска winws в повседневном рабочем режиме. остальное не нужно.\ `/zapret-winws/_CMD_ADMIN.cmd` - получить командную строку cmd в этой директории от имени администратора для тестирования **winws** с параметрами, вводимыми вручную\ `/blockcheck/blockcheck.cmd` - достаточно кликнуть по нему, чтобы пошел _blockcheck_ с записью лога в `blockcheck/blockcheck.log`\ `/cygwin/cygwin.cmd` - запуск среды _cygwin bash_ под текущим пользователем\ `/cygwin/cygwin-admin.cmd` - запуск среды _cygwin bash_ под администратором В среде _cygwin_ уже настроены alias-ы на winws,blockcheck,ip2net,mdig. С путями возиться не нужно! > [!TIP] > Из cygwin можно не только тестировать winws, но и посылать сигналы. > Доступны команды: >- `pidof` >- `kill` >- `killall` >- `pgrep` >- `pkill` Но важно понимать, что таким образом не выйдет посылать сигналы **winws**, запущенному из _zapret-winws_, поскольку там свой `cygwin1.dll`, и они не разделяют общее пространство процессов unix. _zapret-winws_ - это отдельный комплект для повседневного использования, не требующий что-то еще, но и не связанный со _средой cygwin_. Специально для посылки сигналов winws в _zapret-winws_ присутствует killall.exe. Среду cygwin можно использовать для записи в файл дебаг-лога winws. Для этого пользуйтесь командой tee. `winws --debug --wf-tcp=80,443 | tee winws.log` `winws.log` будет в `cygwin/home/<имя_пользователя>` Если у вас windows 7, то блокнот не поймет переводы строк в стиле unix. Воспользуйтесь командой `unix2dos winws.log` > [!CAUTION] > Поскольку 32-битные windows мало востребованы, _zapret-win-bundle_ существует только в варианте для windows _x64/arm64_. ## Автозапуск winws Для запуска **winws** вместе с windows есть 2 варианта. Планировщик задач или службы windows. Можно создавать задачи и управлять ими через консольную программу schtasks. В директории `binaries/windows-x86_64/winws` подготовлены файлы `task_*.cmd` . В них реализовано создание, удаление, старт и стоп одной копии процесса winws с параметрами из переменной `%WINWS1%`. Исправьте параметры на нужную вам стратегию. Если для разных фильтров применяется разная стратегия, размножьте код для задач _winws1_,_winws2_,_winws3_,_..._ Аналогично настраивается вариант запуска через службы windows. Смотрите `service_*.cmd`. Все батники требуется запускать от имени администратора. Управлять задачами можно так же из графической программы управления планировщиком `taskschd.msc` ## Особенности Windows Server winws слинкован с wlanapi.dll, который по умолчанию не установлен в windows server. Для решения этой проблемы запустите power shell под администратором и выполните команду `Install-WindowsFeature -Name Wireless-Networking`. После чего перезагрузите систему. ================================================ FILE: docs/wireguard_iproute_openwrt.txt ================================================ Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего понимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно. Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. Есть возможность поднять свой VPN сервер ? Не хотим использовать redsocks ? Хотим завертывать на VPN только часть трафика ? Например, из ipset zapret только порт tcp:443, из ipban - весь трафик, не только tcp ? Да, с VPN такое возможно. Опишу понятийно как настраивается policy based routing в openwrt на примере wireguard. Вместо wireguard можно использовать openvpn или любой другой. Но wireguard прекрасен сразу несколькими вещами. Главная из которых - в разы большая скорость, даже немного превышающая ipsec. Ведь openvpn основан на tun, а tun - всегда в разы медленнее решения в kernel mode, и если для PC оно может быть не так актуально, для soho роутеров - более чем. Wireguard может дать 50 mbps там, где openvpn еле тащит 10. Но есть и дополнительное требование. Wireguard работает в ядре, значит ядро должно быть под вашим контролем. vps на базе openvz не подойдет. Нужен xen, kvm, любой другой вариант, где загружается ваше собственное ядро, а не используется общее, разделяемое на множество vps. Понятийно необходимо выполнить следующие шаги : 1) Поднять vpn сервер. 2) Настроить vpn клиент. Результат этого шага - получение поднятого интерфейса vpn. Будь то wireguard, openvpn или любой другой тип vpn. 3) Создать такую схему маршрутизации, при которой пакеты, помечаемые особым mark, попадают на vpn, а остальные идут обычным способом. 4) Создать правила, выставляющие mark для всего трафика, который необходимо рулить на vpn. Критерии могут быть любые, ограниченные лишь возможностями iptables и вашим воображением. Будем считать наш vpn сервер находится на ip 91.15.68.202. Вешать его будем на udp порт 12345. На этот же порт будем вешать и клиентов. Сервер работает под debian 9 или выше. Клиент работает под openwrt. Для vpn отведем подсеть 192.168.254.0/24. --- Если нет своего сервера --- Но есть конфиг от VPN провайдера или от друга "Васи", который захотел с вами поделиться. Тогда вам не надо настраивать сервер, задача упрощается. Делается невозможным вариант настройки без masquerade (см ниже). Из конфига вытаскиваете приватный ключ своего пира и публичный ключ сервера, ip/host/port сервера, используете их в настройках openwrt вместо сгенеренных самостоятельно. --- Поднятие сервера --- Wireguard был включен в ядро linux с версии 5.6. Если у вас ядро >=5.6, то достаточно установить пакет wireguard-tools. Он содержит user-mode компоненты wireguard. Посмотрите, возможно в вашем дистрибутиве ядро по умолчанию более старое, но в репозитории имеются бэкпорты новых версий. Лучше будет обновить ядро из репозитория. В репозитории может быть пакет wireguard-dkms. Это автоматизированное средство сборки wireguard с исходников, в том числе модуль ядра. Можно пользоваться им. Иначе вам придется собрать wireguard самому. Ядро должно быть не ниже 3.10. На сервере должны быть установлены заголовки ядра (linux-headers-...) и компилятор gcc. # git clone --depth 1 https://git.zx2c4.com/wireguard-linux-compat # cd wireguard-linux-compat/src # make # strip --strip-debug wireguard.ko # sudo make install wireguard основан на понятии криптороутинга. Каждый пир (сервер - тоже пир) имеет пару открытый/закрытый ключ. Закрытый ключ остается у пира, открытый прописывается у его партнера. Каждый пир авторизует другого по знанию приватного ключа, соответствующего прописанному у него публичному ключу. Протокол построен таким образом, что на все неправильные udp пакеты не следует ответа. Не знаешь приватный ключ ? Не смог послать правильный запрос ? Долбись сколько влезет, я тебе ничего не отвечу. Это защищает от активного пробинга со стороны DPI и просто экономит ресурсы. Значит первым делом нужно создать 2 пары ключей : для сервера и для клиента. wg genkey генерит приватный ключ, wg pubkey получает из него публичный ключ. # wg genkey oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= # echo oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= | wg pubkey bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= # wg genkey OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= # echo OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= | wg pubkey EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg= Пишем конфиг --/etc/wireguard/wgvps.conf------------------- [Interface] PrivateKey = OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= ListenPort = 12345 [Peer] #Endpoint = PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= AllowedIPs = 192.168.254.3 PersistentKeepalive=20 ---------------------------------------------- Wireguard - минималистичный vpn. В нем нет никаких средств для автоконфигурации ip. Все придется прописывать руками. В wgvps.conf должны быть перечислены все пиры с их публичными ключами, а так же прописаны допустимые для них ip адреса. Назначим нашему клиенту 192.168.254.3. Сервер будет иметь ip 192.168.254.1. Endpoint должен быть прописан хотя бы на одном пире. Если endpoint настроен для пира, то wireguard будет периодически пытаться к нему подключиться. В схеме клиент/сервер у сервера можно не прописывать endpoint-ы пиров, что позволит менять ip и быть за nat. Endpoint пира настраивается динамически после успешной фазы проверки ключа. Включаем маршрутизацию : # echo net.ipv4.ip_forward = 1 >>/etc/sysctl.conf # sysctl -p Интерфейс конфигурируется стандартно для дебианоподобных систем : --/etc/network/interfaces.d/wgvps------------- auto wgvps iface wgvps inet static address 192.168.254.1 netmask 255.255.255.0 pre-up ip link add $IFACE type wireguard pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf post-up iptables -t nat -A POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE post-up iptables -A FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu post-down iptables -D FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu post-down iptables -t nat -D POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE post-down ip link del $IFACE ---------------------------------------------- Поднятие через ifup wgvps, опускание через ifdown wgvps. При поднятии интерфейса заодно настраивается nat. eth0 здесь означает интерфейс vpn сервера с инетовским ip адресом. Если у вас какая-то система управления фаерволом, то надо настройку nat прикручивать туда. Пример написан для простейшего случая, когда никаких ограничений нет, таблицы iptables пустые. Чтобы посмотреть текущие настройки wireguard, запустите 'wg' без параметров. --- Поднятие клиента --- # opkg update # opkg install wireguard-tools Добавляем записи в конфиги. --/etc/config/network-------------------------- config interface 'wgvps' option proto 'wireguard' option auto '1' option private_key 'oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0=' option listen_port '12345' option metric '9' option mtu '1420' config wireguard_wgvps option public_key 'EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg=' list allowed_ips '0.0.0.0/0' option endpoint_host '91.15.68.202' option endpoint_port '12345' option route_allowed_ips '0' option persistent_keepalive '20' config interface 'wgvps_ip' option proto 'static' option ifname '@wgvps' list ipaddr '192.168.254.3/24' config route option interface 'wgvps' option target '0.0.0.0/0' option table '100' config rule option mark '0x800/0x800' option priority '100' option lookup '100' ------------------------------------------------ --/etc/config/firewall-------------------------- config zone option name 'tunvps' option output 'ACCEPT' option input 'REJECT' option masq '1' option mtu_fix '1' option forward 'REJECT' option network 'wgvps wgvps_ip' config forwarding option dest 'tunvps' option src 'lan' config rule option name 'Allow-ICMP-tunvps' option src 'tunvps' option proto 'icmp' option target 'ACCEPT' config rule option target 'ACCEPT' option src 'wan' option proto 'udp' option family 'ipv4' option src_port '12345' option src_ip '91.15.68.202' option name 'WG-VPS' ------------------------------------------------ Что тут было сделано : *) Настроен интерфейс wireguard. Указан собственный приватный ключ. *) Настроен пир-партнер с указанием его публичного ключа и endpoint (ip:port нашего сервера) такая настройка заставит периодически долбиться на сервер по указанному ip route_allowed_ip '0' запрещает автоматическое создание маршрута allowed_ips '0.0.0.0/0' разрешает пакеты с любым адресом источника. ведь мы собираемся подключаться к любым ip в инете persistent_keepalive '20' помогает исключить дропание mapping на nat-е, если мы сидим за ним, да и вообще полезная вещь, чтобы не было подвисших пиров *) Статическая конфигурация ip интерфейса wgvps. *) Маршрут default route на wgvps в отдельной таблице маршрутизации с номером 100. Аналог команды ip route add .. table 100 *) Правило использовать таблицу 100 при выставлении в mark бита 0x800. Аналог команды ip rule. *) Отдельная зона фаервола для VPN - 'tunvps'. В принципе ее можно не создавать, можете приписать интерфейс к зоне wan. Но в случае с отдельной зоной можно настроить особые правила на подключения с vpn сервера в сторону клиента. *) Разрешение форвардинга между локалкой за роутером и wgvps. *) Разрешение принимать icmp от vpn сервера, включая пинги. ICMP жизненно важны для правильного функционирования ip сети ! *) И желательно проткнуть дырку в фаерволе, чтобы принимать пакеты wireguard со стороны инетовского ip vpn сервера. Конечно, оно скорее всего заработает и так, потому что первый пакет пойдет от клиента к серверу и тем самым создаст запись в conntrack. Все дальнейшие пакеты в обе стороны подпадут под состояние ESTABLISHED и будут пропущены. Запись будет поддерживаться за счет периодических запросов keep alive. Но если вы вдруг уберете keep alive или выставите таймаут, превышающий udp таймаут в conntrack, то могут начаться ошибки, висы и переподключения. Если же в фаерволе проткнута дырка, то пакеты от сервера не будут заблокированы ни при каких обстоятельствах. # /etc/init.d/firewall restart # ifup wgvps # ifconfig wgvps # ping 192.168.254.1 Если все хорошо, должны ходить пинги. С сервера не помешает : # ping 192.168.254.3 --- Подготовка zapret --- Выполните install_easy.sh. Он настроит режим обхода DPI. Если обход DPI не нужен - не включайте tpws и nfqws. Так же инсталятор заресолвит домены из ipset/zapret-hosts-user-ipban.txt и внесет крон-джоб для периодического обновления ip. Если вы используете в своих правилах ipset zapret, то он ресолвится и обновляется только, если выбран режим фильтрации обхода DPI по ipset. По сути он вам нужен исключительно, если обход DPI не помогает. Например, удается как-то пробить http, но не удается пробить https. И при этом вы хотите, чтобы на VPN направлялись только ip из скачанного ip листа, в добавок к заресолвленному ipset/zapret-hosts-user.txt. Именно этот случай и рассмотрен в данном примере. Если это не так, то убирайте правила с портом 443 из нижеприведенных правил iptables/nftables. Если не хотите ограничиваться листом, и хотите направлять все на порт 443, то уберите фильтры из правил iptables/nftables, связанные с ipset/nfset "zapret". Фильтрация по именам доменов (MODE_FILTER=hostlist) невозможна средствами iptables/nftables. Она производится исключительно в tpws и nfqws по результатам анализа протокола прикладного уровня, иногда достаточно сложного, связанного с дешифровкой пакета (QUIC). Скачиваются листы с именами доменов, не ip адресами. ipset/zapret-hosts-user.txt не ресолвится, а используется как hostlist. Потому вам нельзя расчитывать на ipset zapret. Тем не менее при выборе этого режима фильтрации , либо вовсе при ее отсутствии (MODE_FILTER=none), ipset/zapret-hosts-user-ipban.txt все равно ресолвится. Вы всегда можете расчитывать на ipset/nfset "ipban", "nozapret". "nozapret" - это ipset/nfset, связанный с системой исключения ip. Сюда загоняется все из ipset/zapret-hosts-user-exclude.txt после ресолвинга. Его учет крайне желателен, чтобы вдруг из скачанного листа не просочились записи, например, 192.168.0.0/16 и не заставили лезть туда через VPN. Хотя скрипты получения листов и пытаются отсечь IP локалок, но так будет намного надежнее. --- Маркировка трафика --- Завернем на vpn все из ipset zapret на tcp:443 и все из ipban. OUTPUT относится к исходящим с роутера пакетам, PREROUTING - ко всем остальным. Если с самого роутера ничего заруливать не надо, можно опустить часть, отвечающую за OUTPUT. --/etc/firewall.user---------------------------- . /opt/zapret/init.d/openwrt/functions create_ipset no-update network_find_wan4_all wan_iface for ext_iface in $wan_iface; do network_get_device DEVICE $ext_iface ipt 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 ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 done network_get_device DEVICE lan ipt 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 ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 ------------------------------------------------ # /etc/init.d/firewall restart Чтобы правила обновлялись в процессе поднятия интерфейсов в системе, нужно внести параметр "reload" в указанное место : --- /etc/config/firewall --- config include option path '/etc/firewall.user' option reload '1' ---------------------------- --- Маркировка трафика nftables --- В новых openwrt по умолчанию установлен nftables, iptables отсутствует. Есть вариант снести nftables + fw4 и заменить их на iptables + fw3. Веб интерфейс luci понимает прозрачно и fw3, и fw4. Однако, при установке iptables и fw3 новые пакеты будут устанавливаться без сжатия squashfs. Убедитесь, что у вас достаточно места. Либо сразу настраивайте образ через image builder. Фаервол fw4 работает в одноименной nftable - "inet fw4". "inet" означает, что таблица принимает и ipv4, и ipv6. Поскольку для маркировки трафика используется nfset, принадлежащий таблице zapret, цепочки необходимо помещать в ту же таблицу. Для синхронизации лучше всего использовать хук INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" Параметр нужно раскоментировать в /opt/zapret/config. Далее надо создать указанный файл и дать ему chmod 755. --/etc/firewall.zapret.hook.post_up---------------------------- #!/bin/sh ZAPRET_NFT_TABLE=zapret cat << EOF | nft -f - 2>/dev/null delete chain inet $ZAPRET_NFT_TABLE my_output delete chain inet $ZAPRET_NFT_TABLE my_prerouting EOF cat << EOF | nft -f - add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } flush chain inet $ZAPRET_NFT_TABLE my_output add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 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 add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } flush chain inet $ZAPRET_NFT_TABLE my_prerouting add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 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 EOF ------------------------------------------------ # /etc/init.d/zapret restart_fw Проверка правил : # /etc/init.d/zapret list_table или # nft -t list table inet zapret Должны быть цепочки my_prerouting и my_output. Проверка заполнения nfsets : # nft list set inet zapret zapret # nft list set inet zapret ipban # nft list set inet zapret nozapret Проверка заполнения множеств lanif, wanif, wanif6, link_local : # /etc/init.d/zapret list_ifsets Должны присутствовать имена интерфейсов во множествах lanif, wanif. wanif6 заполняется только при включении ipv6. link_local нужен только для tpws при включении ipv6. --- По поводу двойного NAT --- В описанной конфигурации nat выполняется дважды : на роутере-клиенте происходит замена адреса источника из LAN на 192.168.254.3 и на сервере замена 192.168.254.3 на внешний адрес сервера в инете. Зачем так делать ? Исключительно для простоты настройки. Или на случай, если сервер wireguard не находится под вашим контролем. Делать для вас нижеописанные настройки никто не будет с вероятностью, близкой к 100%. Если сервер wireguard - ваш, и вы готовы чуток еще поднапрячься и не хотите двойного nat, то можете вписать в /etc/config/firewall "masq '0'", на сервер дописать маршрут до вашей подсети lan. Чтобы не делать это для каждого клиента, можно отвести под всех клиентов диапазон 192.168.0.0-192.168.127.255 и прописать его одним маршрутом. --/etc/network/interfaces.d/wgvps------------- post-up ip route add dev $IFACE 192.168.0.0/17 post-down ip route del dev $IFACE 192.168.0.0/17 ---------------------------------------------- Так же необходимо указать wireguard дополнительные разрешенные ip для peer : --/etc/wireguard/wgvps.conf------------------- [Peer] PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= AllowedIPs = 192.168.254.3, 192.168.2.0/24 ---------------------------------------------- Всем клиентам придется назначать различные диапазоны адресов в lan и индивидуально прописывать AllowedIPs для каждого peer. # ifdown wgvps ; ifup wgvps На клиенте разрешим форвард icmp, чтобы работал пинг и корректно определялось mtu. --/etc/config/firewall-------------------------- config rule option name 'Allow-ICMP-tunvps' option src 'tunvps' option dest 'lan' option proto 'icmp' option target 'ACCEPT' ------------------------------------------------ Существуют еще два неочевидных нюанса. Первый из них касается пакетов с самого роутера (цепочка OUTPUT). Адрес источника выбирается по особому алгоритму, если программа явно его не задала, еще до этапа iptables. Он берется с интерфейса, куда бы пошел пакет при нормальном раскладе. Обратная маршрутизация с VPN станет невозможной, да и wireguard такие пакеты порежет, поскольку они не вписываются в AllowedIPs. Никаким мистическим образом автоматом source address не поменяется. В прошлом варианте настройки проблема решалось через маскарад. Сейчас же маскарада нет. Потому все же придется его делать в случае, когда пакет изначально направился бы через wan, а мы его завертываем на VPN. Помечаем такие пакеты марком 0x1000. Если вам не актуальны исходящие с самого роутера, то можно ничего не менять. Другой нюанс связан с обработкой проброшенных на vps портов, соединения по которым приходят как входящие с интерфейса wgvps. Представьте себе, что вы пробросили порт 2222. Кто-то подключается с адреса 1.2.3.4. Вам приходит пакет SYN 1.2.3.4:51723=>192.168.2.2:2222. По правилам маршрутизации он пойдет в локалку. 192.168.2.2 его обработает, ответит пакетом ACK 192.168.2.2:2222=>1.2.3.4:51723. Этот пакет придет на роутер. И куда он дальше пойдет ? Если он не занесен в ipban, то согласно правилам машрутизации он пойдет по WAN интерфейсу, а не по исходному wgvps. Чтобы решить эту проблему, необходимо воспользоваться CONNMARK. Существуют 2 отдельных марка : fwmark и connmark. connmark относится к соединению, fwmark - к пакету. Трэкингом соединений занимается conntrack. Посмотреть его таблицу можно командой "conntrack -L". Там же найдете connmark : mark=xxxx. Как только видим приходящий с wgvps пакет с новым соединением, отмечаем его connmark как 0x800/0x800. При этом fwmark не меняется, иначе бы пакет тут же бы завернулся обратно на wgvps согласно ip rule. Если к нам приходит пакет с какого-то другого интерфейса, то восстанавливаем его connmark в fwmark по маске 0x800. И теперь он подпадает под правило ip rule, заворачиваясь на wgvps, что и требовалось. Альтернативное решение - использовать на VPSке для проброса портов не только DNAT, но и SNAT/MASQUERADE. Тогда source address будет заменен на 192.168.254.1. Он по таблице маршрутизации пойдет на wgvps. Но в этом случае клиентские программы, на которые осуществляется проброс портов, не будут видеть реальный IP подключенца. --/etc/firewall.user---------------------------- . /opt/zapret/init.d/openwrt/functions create_ipset no-update network_find_wan4_all wan_iface for ext_iface in $wan_iface; do network_get_device DEVICE $ext_iface ipt 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 ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 ipt OUTPUT -t mangle -o $DEVICE -j MARK --set-mark 0x1000/0x1000 done network_get_device DEVICE lan ipt 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 ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 # do masquerade for OUTPUT to ensure correct outgoing address ipt postrouting_tunvps_rule -t nat -m mark --mark 0x1000/0x1000 -j MASQUERADE # incoming from wgvps network_get_device DEVICE wgvps ipt PREROUTING -t mangle ! -i $DEVICE -j CONNMARK --restore-mark --nfmask 0x800 --ctmask 0x800 ipt PREROUTING -t mangle -i $DEVICE -m conntrack --ctstate NEW -j CONNMARK --set-mark 0x800/0x800 ------------------------------------------------ # /etc/init.d/firewall restart Вариант nftables : --/etc/firewall.zapret.hook.post_up---------------------------- #!/bin/sh ZAPRET_NFT_TABLE=zapret DEVICE=wgvps cat << EOF | nft -f - 2>/dev/null delete chain inet $ZAPRET_NFT_TABLE my_output delete chain inet $ZAPRET_NFT_TABLE my_prerouting delete chain inet $ZAPRET_NFT_TABLE my_nat EOF cat << EOF | nft -f - add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } flush chain inet $ZAPRET_NFT_TABLE my_output add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 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 add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta mark set mark or 0x1000 add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } flush chain inet $ZAPRET_NFT_TABLE my_prerouting add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname $DEVICE ct state new ct mark set ct mark or 0x800 add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname != $DEVICE meta mark set ct mark and 0x800 add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 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 add chain inet $ZAPRET_NFT_TABLE my_nat { type nat hook postrouting priority 100 ; } flush chain inet $ZAPRET_NFT_TABLE my_nat add rule inet $ZAPRET_NFT_TABLE my_nat oifname $DEVICE mark and 0x1000 == 0x1000 masquerade EOF ------------------------------------------------ # /etc/init.d/zapret restart_fw К сожалению, здесь возможности nftables немного хромают. Полноценного эквивалента CONNMARK --restore-mark --nfmask не существует. Оригинал iptables предполагал копирование одного бита 0x800 из connmark в mark. Лучшее, что можно сделать в nftables, это копирование одного бита с занулением всех остальных. Сложные выражения типа "meta mark set mark and ~0x800 or (ct mark and 0x800)" nft не понимает. Об этом же говорит попытка перевода через iptables-translate. Сейчас уже можно с vpn сервера пингануть ip адрес внутри локалки клиента. Пинги должны ходить. Отсутствие двойного NAT значительно облегчает проброс портов с внешнего IP vpn сервера в локалку какого-либо клиента. Для этого надо выполнить 2 действия : добавить разрешение в фаервол на клиенте и сделать dnat на сервере. Пример форварда портов 5001 и 5201 на 192.168.2.2 : --/etc/config/firewall-------------------------- config rule option target 'ACCEPT' option src 'tunvps' option dest 'lan' option proto 'tcp udp' option dest_port '5001 5201' option dest_ip '192.168.2.2' option name 'IPERF' ------------------------------------------------ # /etc/init.d/firewall restart # /etc/init.d/zapret restart_fw --/etc/network/interfaces.d/wgvps------------- post-up iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 post-up iptables -t nat -A PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 post-down iptables -t nat -D PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 post-down iptables -t nat -D PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 ---------------------------------------------- # ifdown wgvps ; ifup wgvps Пример приведен для iperf и iperf3, чтобы показать как пробрасывать несколько портов tcp+udp с минимальным количеством команд. Проброс tcp и udp порта так же необходим для полноценной работы bittorrent клиента, чтобы работали входящие. --- Как мне отправлять на vpn весь трафик с bittorrent ? --- Можно поступить так : посмотрите порт в настройках torrent клиента, убедитесь, что не поставлено "случайный порт", добавьте на роутер правило маркировки по порту источника. Но мне предпочтительно иное решение. На windows есть замечательная возможность прописать правило установки поля качества обслуживания в заголовках ip пакетов в зависимости от процесса-источника. Для windows 7/2008R2 необходимо будет установить ключик реестра и перезагрузить комп : # reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\QoS /v "Do not use NLA" /t REG_SZ /d "1" Редактировать политику можно в : gpedit.msc -> Computer Configuration -> Windows Settings -> Policy-based QoS На win 10 ключик реестра больше не работает, правила qos в gpedit применяются только для профиля домена. Необходимо пользоваться командой powershell New-NetQosPolicy. Гуглите хелп по ней. Пример : # powershell New-NetQosPolicy -Name "torrent" -AppPathNameMatchCondition "qbittorrent.exe" -DSCPAction 1 Однозначно требуется проверка в wireshark или netmon успешности установки поля dscp. Если там по-прежнему 0x00, значит что-то не сработало. 0x04 означает DSCP=1 (dscp находится в старших 6 битах). На роутере в фаер прописываем правило : --/etc/config/firewall-------------------------- config rule option target 'MARK' option src 'lan' option proto 'all' option extra '-m dscp --dscp 1' option name 'route-dscp-1' option set_mark '0x0800/0x0800' ------------------------------------------------ # /etc/init.d/firewall restart Теперь все с полем dscp "1" идет на vpn. Клиент сам решает какой трафик ему нужно забрасывать на vpn, перенастраивать роутер не нужно. На linux клиенте проще всего будет выставлять dscp в iptables по номеру порта источника : --/etc/rc.local--------------------------------- iptables -A OUTPUT -t mangle -p tcp --sport 23444 -j DSCP --set-dscp 1 iptables -A OUTPUT -t mangle -p udp --sport 23444 -j DSCP --set-dscp 1 ------------------------------------------------ можно привязываться к pid процесса, но тогда нужно перенастраивать iptables при каждом перезапуске торент клиента, это требует рута, и все становится очень неудобно. --- Автоматизация проброса портов через miniupnd --- Да, его тоже можно использовать на vps. Только как всегда есть нюансы. miniupnpd поддерживает 3 протокола IGD : upnp,nat-pmp и pcp. upnp и pcp работают через мультикаст, который не пройдет через wgvps. nat-pmp работает через посылку специальных сообщений на udp:5351 на default gateway. Обычно их обслуживает miniupnpd на роутере. При создании lease miniupnpd добавляет правила для проброса портов в цепочку iptables MINIUPNPD, при потери lease - убирает. udp:5351 можно перенаправить на vpn сервер через DNAT, чтобы их обрабатывал miniupnpd там. Но вы должны иметь однозначный критерий перенаправления. Если вы решили завернуть на vpn все, то проблем нет. Пробрасываем udp:5351 безусловно. Если у вас идет перенаправление только с торрент, то необходимо к условию перенаправления добавить условия, выделяющие torrent трафик из прочего. Или по dscp, или по sport. Чтобы запросы от остальных программ обрабатывались miniupnpd на роутере. Если какая-то программа создаст lease не там, где нужно, то входящий трафик до нее не дойдет. На роутере стоит запретить протокол upnp, чтобы торрент клиент не удовлетворился запросом, обслуженным по upnp на роутере, и пытался использовать nat-pmp. --/etc/config/upnp-------------------------- config upnpd 'config' ..... option enable_upnp '0' ------------------------------------------------ /etc/init.d/miniupnpd restart Делаем проброс порта на роутере. Для простоты изложения будем считать, что на vpn у нас завернут весь трафик. Если это не так, то следует добавить фильтр в "config redirect". Заодно выделяем диапазон портов для торрент клиентов. Порт в торент клиенте следует прописать какой-то из этого диапазона. ------------------------------------------------ config redirect option enabled '1' option target 'DNAT' option src 'lan' option dest 'tunvps' option proto 'udp' option src_dport '5351' option dest_ip '192.168.254.1' option dest_port '5351' option name 'NAT-PMP' option reflection '0' config rule option enabled '1' option target 'ACCEPT' option src 'tunvps' option dest 'lan' option name 'tunvps-torrent' option dest_port '28000-28009' ------------------------------------------------ /etc/init.d/firewall reload На сервере : apt install miniupnpd --- /etc/miniupnpd/miniupnpd.conf -------- enable_natpmp=yes enable_upnp=no lease_file=/var/log/upnp.leases system_uptime=yes clean_ruleset_threshold=10 clean_ruleset_interval=600 force_igd_desc_v1=no listening_ip=192.168.254.1/16 ext_ifname=eth0 ------------------------------------------ systemctl restart miniupnpd listening_ip прописан именно таким образом, чтобы обозначить диапазон разрешенных IP. С других IP он не будет обрабатывать запросы на редирект. В ext_ifname впишите название inet интерфейса на сервере. Запускаем торрент клиент. Попутно смотрим в tcpdump весь путь udp:5351 до сервера и обратно. Смотрим syslog сервера на ругань от miniupnpd. Если все ок, то можем проверить редиректы : iptables -t nat -nL MINIUPNPD С какого-нибудь другого хоста (не vpn сервер, не ваше подключение) можно попробовать telnet-нуться на проброшенный порт. Должно установиться соединение. Или качайте торент и смотрите в пирах флаг "I" (incoming). Если "I" есть и по ним идет закачка, значит все в порядке. ОСОБЕННОСТЬ НОВЫХ DEBIAN : по умолчанию используются iptables-nft. miniupnpd работает с iptables-legacy. ЛЕЧЕНИЕ : update-alternatives --set iptables /usr/sbin/iptables-legacy ================================================ FILE: files/huawei/E8372/run-zapret-hostlist ================================================ #!/system/bin/busybox sh # download hostlist from http(s) (need curl, its absent by default), # feed it to zapret. save flash write cycles u="https://your.host.com/censorship/hoslist.txt" SCRIPT=$(readlink -f "$0") EXEDIR=$(dirname "$SCRIPT") d=/data/censorship [ -d $d ] || mkdir $d f=$d/hostlist.txt t=/hostlist.txt curl -k --fail --max-time 10 -o "$t" "$u" && { if [ -s "$t" ]; then m1=$(md5sum "$t" | cut -d ' ' -f 1) m2=$(md5sum "$f" | cut -d ' ' -f 1) echo $m1 $m2 if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then echo updating hostlist cp -f "$t" "$f" else echo hostlist was not changed. keeping old copy fi else echo downloaded hostlist is empty. disabling zapret rm "$f" fi } rm -f "$t" "$EXEDIR/unzapret" [ -s "$f" ] && exec "$EXEDIR/zapret" "--hostlist=$f" ================================================ FILE: files/huawei/E8372/run-zapret-ip ================================================ #!/system/bin/busybox sh # download hostlist from http(s) (need curl, its absent by default), # resolve to ip list, feed to zapret-ip. save flash write cycles u="https://your.host.com/censorship/hoslist.txt" SCRIPT=$(readlink -f "$0") EXEDIR=$(dirname "$SCRIPT") d=/data/censorship [ -d $d ] || mkdir $d f=$d/hostlist.txt t=/hostlist.txt i=/iplist.txt curl -k --fail --max-time 10 -o "$t" "$u" && { if [ -s "$t" ]; then m1=$(md5sum "$t" | cut -d ' ' -f 1) m2=$(md5sum "$f" | cut -d ' ' -f 1) echo $m1 $m2 if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then echo updating hostlist cp -f "$t" "$f" else echo hostlist was not changed. keeping old copy fi else echo downloaded hostlist is empty. disabling zapret rm "$f" fi } rm -f "$t" "$EXEDIR/unzapret-ip" [ -s "$f" ] && { mdig --threads=10 --family=4 <"$f" >"$i" [ -s "$i" ] && exec "$EXEDIR/zapret-ip" "$i" } ================================================ FILE: files/huawei/E8372/unzapret ================================================ #!/system/bin/busybox sh rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" iptables -C $rule 2>/dev/null && iptables -D $rule killall tpws rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" iptables -C $rule 2>/dev/null && iptables -D $rule killall nfqws ================================================ FILE: files/huawei/E8372/unzapret-ip ================================================ #!/system/bin/busybox sh rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" iptables -C $rule 2>/dev/null && iptables -D $rule iptables -F tpws -t nat iptables -X tpws -t nat killall tpws rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" iptables -C $rule 2>/dev/null && iptables -D $rule killall nfqws ================================================ FILE: files/huawei/E8372/zapret ================================================ #!/system/bin/busybox sh # $1 - additional parameters for nfqws insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" iptables -C $rule 2>/dev/null || iptables -I $rule tpws --uid 1:3003 --port=1 --daemon rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" iptables -C $rule 2>/dev/null || iptables -I $rule nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon $1 ================================================ FILE: files/huawei/E8372/zapret-ip ================================================ #!/system/bin/busybox sh # $1 - ip list file. create individual rules for tpws redirection. ipset is not available [ -z "$1" ] && { echo need iplist file as parameter exit 1 } insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null tpws --maxconn=1024 --uid 1:3003 --port=1 --daemon REDIR="-j REDIRECT --to-port 1" iptables -F tpws -t nat iptables -X tpws -t nat iptables -N tpws -t nat iptables -A tpws -t nat -d 192.168.0.0/16 -j RETURN while read ip; do echo redirecting $ip iptables -A tpws -t nat -d $ip -p tcp $REDIR done <"$1" rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" iptables -C $rule 2>/dev/null || iptables -I $rule nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" iptables -C $rule 2>/dev/null || iptables -I $rule ================================================ FILE: init.d/custom.d.examples.linux/10-keenetic-udp-fix ================================================ # This script fixes keenetic issue with nfqws generated udp packets # Keenetic uses proprietary ndmmark and does not masquerade without this mark # If not masqueraded packets go to WAN with LAN IP and get dropped by ISP # It's advised to set IFACE_WAN in config zapret_custom_firewall() { # $1 - 1 - add, 0 - stop local wan wanif rule [ "$DISABLE_IPV4" = "1" ] || { # use IFACE_WAN if defined. if not - search for interfaces with default route. wanif=${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)} for wan in $wanif; do rule="-o $wan -p udp -m mark --mark $DESYNC_MARK/$DESYNC_MARK" ipt_print_op $1 "$rule" "keenetic udp fix" ipt_add_del $1 POSTROUTING -t nat $rule -j MASQUERADE done } } ================================================ FILE: init.d/custom.d.examples.linux/20-fw-extra ================================================ # this custom script runs standard mode with extra firewall rules # config: use TPWS_ENABLE_OVERRIDE, NFQWS_ENABLE_OVERRIDE to enable standard mode daemons # standard and override switches cannot be enabled simultaneously ! TPWS_ENABLE_OVERRIDE=${TPWS_ENABLE_OVERRIDE:-0} NFQWS_ENABLE_OVERRIDE=${NFQWS_ENABLE_OVERRIDE:-0} # config: some if these values must be set in config. not setting any of these makes this script meaningless. # pre vars put ipt/nft code to the rule beginning #FW_EXTRA_PRE_TPWS_IPT= #FW_EXTRA_PRE_TPWS_NFT= #FW_EXTRA_PRE_NFQWS_IPT="-m mark --mark 0x10000000/0x10000000" #FW_EXTRA_PRE_NFQWS_NFT="mark and 0x10000000 != 0" # post vars put ipt/nft code to the rule end #FW_EXTRA_POST_TPWS_IPT= #FW_EXTRA_POST_TPWS_NFT= #FW_EXTRA_POST_NFQWS_IPT= #FW_EXTRA_POST_NFQWS_NFT= check_std_intersect() { [ "$TPWS_ENABLE_OVERRIDE" = 1 -a "$TPWS_ENABLE" = 1 ] && { echo "ERROR ! both TPWS_ENABLE_OVERRIDE and TPWS_ENABLE are enabled" return 1 } [ "$NFQWS_ENABLE_OVERRIDE" = 1 -a "$NFQWS_ENABLE" = 1 ] && { echo "ERROR ! both NFQWS_ENABLE_OVERRIDE and NFQWS_ENABLE are enabled" return 1 } return 0 } zapret_custom_daemons() { # $1 - 1 - add, 0 - stop check_std_intersect || return local TPWS_SOCKS_ENABLE=0 TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE standard_mode_daemons "$1" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop check_std_intersect || return local FW_EXTRA_PRE FW_EXTRA_POST TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE FW_EXTRA_PRE="$FW_EXTRA_PRE_TPWS_IPT" FW_EXTRA_POST="$FW_EXTRA_POST_TPWS_IPT" zapret_do_firewall_standard_tpws_rules_ipt $1 FW_EXTRA_PRE="$FW_EXTRA_PRE_NFQWS_IPT" FW_EXTRA_POST="$FW_EXTRA_POST_NFQWS_IPT" zapret_do_firewall_standard_nfqws_rules_ipt $1 } zapret_custom_firewall_nft() { # stop logic is not required check_std_intersect || return local FW_EXTRA_PRE FW_EXTRA_POST TPWS_ENABLE=$TPWS_ENABLE_OVERRIDE NFQWS_ENABLE=$NFQWS_ENABLE_OVERRIDE FW_EXTRA_PRE="$FW_EXTRA_PRE_TPWS_NFT" FW_EXTRA_POST="$FW_EXTRA_POST_TPWS_NFT" zapret_apply_firewall_standard_tpws_rules_nft FW_EXTRA_PRE="$FW_EXTRA_PRE_NFQWS_NFT" FW_EXTRA_POST="$FW_EXTRA_POST_NFQWS_NFT" zapret_apply_firewall_standard_nfqws_rules_nft } ================================================ FILE: init.d/custom.d.examples.linux/50-dht4all ================================================ # this custom script runs desync to DHT packets with udp payload length >=5 , without ipset/hostlist filtering # 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) # can override in config : NFQWS_OPT_DESYNC_DHT="${NFQWS_OPT_DESYNC_DHT:---dpi-desync=tamper}" alloc_dnum DNUM_DHT4ALL alloc_qnum QNUM_DHT4ALL zapret_custom_daemons() { # $1 - 1 - add, 0 - stop local opt="--qnum=$QNUM_DHT4ALL $NFQWS_OPT_DESYNC_DHT" do_nfqws $1 $DNUM_DHT4ALL "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f uf4 uf6 local first_packet_only="$ipt_connbytes 1:1" f='-p udp -m u32 --u32' uf4='0>>22&0x3C@4>>16=13:0xFFFF&&0>>22&0x3C@8>>16=0x6431:0x6432' uf6='44>>16=13:0xFFFF&&48>>16=0x6431:0x6432' fw_nfqws_post $1 "$f $uf4 $first_packet_only" "$f $uf6 $first_packet_only" $QNUM_DHT4ALL } zapret_custom_firewall_nft() { # stop logic is not required local f local first_packet_only="$nft_connbytes 1" f="udp length ge 13 meta l4proto udp @ih,0,16 0x6431-0x6432" nft_fw_nfqws_post "$f $first_packet_only" "$f $first_packet_only" $QNUM_DHT4ALL } ================================================ FILE: init.d/custom.d.examples.linux/50-discord-media ================================================ # this custom script runs desync to all discord media packets # 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) # can override in config : NFQWS_OPT_DESYNC_DISCORD_MEDIA="${NFQWS_OPT_DESYNC_DISCORD_MEDIA:---dpi-desync=fake --dpi-desync-repeats=2}" DISCORD_MEDIA_PORT_RANGE="${DISCORD_MEDIA_PORT_RANGE:-50000-50099}" alloc_dnum DNUM_DISCORD_MEDIA alloc_qnum QNUM_DISCORD_MEDIA zapret_custom_daemons() { # $1 - 1 - add, 0 - stop local opt="--qnum=$QNUM_DISCORD_MEDIA $NFQWS_OPT_DESYNC_DISCORD_MEDIA" do_nfqws $1 $DNUM_DISCORD_MEDIA "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local DISABLE_IPV6=1 local port_range=$(replace_char - : $DISCORD_MEDIA_PORT_RANGE) local f="-p udp --dport $port_range -m u32 --u32" # this is simplified test to skip writing monstrous rule. instead of checking 64 bytes for zeroes only check 2 dwords for zero fw_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 } zapret_custom_firewall_nft() { # stop logic is not required local DISABLE_IPV6=1 local 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" nft_fw_nfqws_post "$f" '' $QNUM_DISCORD_MEDIA } ================================================ FILE: init.d/custom.d.examples.linux/50-nfqws-ipset ================================================ # this custom script demonstrates how to launch extra nfqws instance limited by ipset # can override in config : NFQWS_MY1_OPT="${NFQWS_MY1_OPT:---filter-udp=* --dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-any-protocol --new --filter-tcp=* --dpi-desync=multisplit}" NFQWS_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}" NFQWS_MY1_SUBNETS6="${NFQWS_MY1_SUBNETS6:-2a00:1450::/29}" NFQWS_MY1_PORTS_TCP=${NFQWS_MY1_PORTS_TCP:-$NFQWS_PORTS_TCP} NFQWS_MY1_PORTS_UDP=${NFQWS_MY1_PORTS_UDP:-$NFQWS_PORTS_UDP} NFQWS_MY1_TCP_PKT_OUT=${NFQWS_MY1_TCP_PKT_OUT:-$NFQWS_TCP_PKT_OUT} NFQWS_MY1_UDP_PKT_OUT=${NFQWS_MY1_UDP_PKT_OUT:-$NFQWS_UDP_PKT_OUT} NFQWS_MY1_TCP_PKT_IN=${NFQWS_MY1_TCP_PKT_IN:-$NFQWS_TCP_PKT_IN} NFQWS_MY1_UDP_PKT_IN=${NFQWS_MY1_UDP_PKT_IN:-$NFQWS_UDP_PKT_IN} NFQWS_MY1_IPSET_SIZE=${NFQWS_MY1_IPSET_SIZE:-4096} NFQWS_MY1_IPSET_OPT="${NFQWS_MY1_IPSET_OPT:-hash:net hashsize 8192 maxelem $NFQWS_MY1_IPSET_SIZE}" alloc_dnum DNUM_NFQWS_MY1 alloc_qnum QNUM_NFQWS_MY1 NFQWS_MY1_NAME4=my1nfqws4 NFQWS_MY1_NAME6=my1nfqws6 zapret_custom_daemons() { # $1 - 1 - run, 0 - stop local opt="--qnum=$QNUM_NFQWS_MY1 $NFQWS_MY1_OPT" do_nfqws $1 $DNUM_NFQWS_MY1 "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f4 f6 subnet local NFQWS_MY1_PORTS_TCP=$(replace_char - : $NFQWS_MY1_PORTS_TCP) local NFQWS_MY1_PORTS_UDP=$(replace_char - : $NFQWS_MY1_PORTS_UDP) [ "$1" = 1 -a "$DISABLE_IPV4" != 1 ] && { ipset create $NFQWS_MY1_NAME4 $NFQWS_MY1_IPSET_OPT family inet 2>/dev/null ipset flush $NFQWS_MY1_NAME4 for subnet in $NFQWS_MY1_SUBNETS4; do echo add $NFQWS_MY1_NAME4 $subnet done | ipset -! restore } [ "$1" = 1 -a "$DISABLE_IPV6" != 1 ] && { ipset create $NFQWS_MY1_NAME6 $NFQWS_MY1_IPSET_OPT family inet6 2>/dev/null ipset flush $NFQWS_MY1_NAME6 for subnet in $NFQWS_MY1_SUBNETS6; do echo add $NFQWS_MY1_NAME6 $subnet done | ipset -! restore } [ -n "$NFQWS_MY1_PORTS_TCP" ] && { [ -n "$NFQWS_MY1_TCP_PKT_OUT" -a "$NFQWS_MY1_TCP_PKT_OUT" != 0 ] && { f4="-p tcp -m multiport --dports $NFQWS_MY1_PORTS_TCP $ipt_connbytes 1:$NFQWS_MY1_TCP_PKT_OUT -m set --match-set" f6="$f4 $NFQWS_MY1_NAME6 dst" f4="$f4 $NFQWS_MY1_NAME4 dst" fw_nfqws_post $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } [ -n "$NFQWS_MY1_TCP_PKT_IN" -a "$NFQWS_MY1_TCP_PKT_IN" != 0 ] && { f4="-p tcp -m multiport --sports $NFQWS_MY1_PORTS_TCP $ipt_connbytes 1:$NFQWS_MY1_TCP_PKT_IN -m set --match-set" f6="$f4 $NFQWS_MY1_NAME6 src" f4="$f4 $NFQWS_MY1_NAME4 src" fw_nfqws_pre $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } } [ -n "$NFQWS_MY1_PORTS_UDP" ] && { [ -n "$NFQWS_MY1_UDP_PKT_OUT" -a "$NFQWS_MY1_UDP_PKT_OUT" != 0 ] && { f4="-p udp -m multiport --dports $NFQWS_MY1_PORTS_UDP $ipt_connbytes 1:$NFQWS_MY1_UDP_PKT_OUT -m set --match-set" f6="$f4 $NFQWS_MY1_NAME6 dst" f4="$f4 $NFQWS_MY1_NAME4 dst" fw_nfqws_post $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } [ -n "$NFQWS_MY1_UDP_PKT_IN" -a "$NFQWS_MY1_UDP_PKT_IN" != 0 ] && { f4="-p udp -m multiport --sports $NFQWS_MY1_PORTS_UDP $ipt_connbytes 1:$NFQWS_MY1_UDP_PKT_IN -m set --match-set" f6="$f4 $NFQWS_MY1_NAME6 src" f4="$f4 $NFQWS_MY1_NAME4 src" fw_nfqws_pre $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } } [ "$1" = 1 ] || { ipset destroy $NFQWS_MY1_NAME4 2>/dev/null ipset destroy $NFQWS_MY1_NAME6 2>/dev/null } } zapret_custom_firewall_nft() { local f4 f6 subnets local first_packets_only="$nft_connbytes 1-$NFQWS_MY1_PKT_OUT" [ "$DISABLE_IPV4" != 1 ] && { make_comma_list subnets $NFQWS_MY1_SUBNETS4 nft_create_set $NFQWS_MY1_NAME4 "type ipv4_addr; size $NFQWS_MY1_IPSET_SIZE; auto-merge; flags interval;" nft_flush_set $NFQWS_MY1_NAME4 nft_add_set_element $NFQWS_MY1_NAME4 "$subnets" } [ "$DISABLE_IPV6" != 1 ] && { make_comma_list subnets $NFQWS_MY1_SUBNETS6 nft_create_set $NFQWS_MY1_NAME6 "type ipv6_addr; size $NFQWS_MY1_IPSET_SIZE; auto-merge; flags interval;" nft_flush_set $NFQWS_MY1_NAME6 nft_add_set_element $NFQWS_MY1_NAME6 "$subnets" } [ -n "$NFQWS_MY1_PORTS_TCP" ] && { [ -n "$NFQWS_MY1_TCP_PKT_OUT" -a "$NFQWS_MY1_TCP_PKT_OUT" != 0 ] && { f4="tcp dport {$NFQWS_MY1_PORTS_TCP} $(nft_first_packets $NFQWS_MY1_TCP_PKT_OUT)" f6="$f4 ip6 daddr @$NFQWS_MY1_NAME6" f4="$f4 ip daddr @$NFQWS_MY1_NAME4" nft_fw_nfqws_post $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } [ -n "$NFQWS_MY1_TCP_PKT_IN" -a "$NFQWS_MY1_TCP_PKT_IN" != 0 ] && { f4="tcp sport {$NFQWS_MY1_PORTS_TCP} $(nft_first_packets $NFQWS_MY1_TCP_PKT_IN)" f6="$f4 ip6 saddr @$NFQWS_MY1_NAME6" f4="$f4 ip saddr @$NFQWS_MY1_NAME4" nft_fw_nfqws_pre $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } } [ -n "$NFQWS_MY1_PORTS_UDP" ] && { [ -n "$NFQWS_MY1_UDP_PKT_OUT" -a "$NFQWS_MY1_UDP_PKT_OUT" != 0 ] && { f4="udp dport {$NFQWS_MY1_PORTS_UDP} $(nft_first_packets $NFQWS_MY1_UDP_PKT_OUT)" f6="$f4 ip6 daddr @$NFQWS_MY1_NAME6" f4="$f4 ip daddr @$NFQWS_MY1_NAME4" nft_fw_nfqws_post $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } [ -n "$NFQWS_MY1_UDP_PKT_IN" -a "$NFQWS_MY1_UDP_PKT_IN" != 0 ] && { f4="udp sport {$NFQWS_MY1_PORTS_UDP} $(nft_first_packets $NFQWS_MY1_UDP_PKT_IN)" f6="$f4 ip6 saddr @$NFQWS_MY1_NAME6" f4="$f4 ip saddr @$NFQWS_MY1_NAME4" nft_fw_nfqws_pre $1 "$f4" "$f6" $QNUM_NFQWS_MY1 } } } zapret_custom_firewall_nft_flush() { # this function is called after all nft fw rules are deleted # however sets are not deleted. it's desired to clear sets here. nft_del_set $NFQWS_MY1_NAME4 2>/dev/null nft_del_set $NFQWS_MY1_NAME6 2>/dev/null } ================================================ FILE: init.d/custom.d.examples.linux/50-quic4all ================================================ # this custom script runs desync to all IETF QUIC initials # 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) # can override in config : NFQWS_OPT_DESYNC_QUIC="${NFQWS_OPT_DESYNC_QUIC:---dpi-desync=fake --dpi-desync-repeats=2}" alloc_dnum DNUM_QUIC4ALL alloc_qnum QNUM_QUIC4ALL zapret_custom_daemons() { # $1 - 1 - add, 0 - stop local opt="--qnum=$QNUM_QUIC4ALL $NFQWS_OPT_DESYNC_QUIC" do_nfqws $1 $DNUM_QUIC4ALL "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f='-p udp -m u32 --u32' fw_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 } zapret_custom_firewall_nft() { # stop logic is not required local f="udp length >= 264 @ih,0,4 0xC @ih,8,32 0x00000001" nft_fw_nfqws_post "$f" "$f" $QNUM_QUIC4ALL } ================================================ FILE: init.d/custom.d.examples.linux/50-stun4all ================================================ # this custom script runs desync to all stun packets # 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) # can override in config : NFQWS_OPT_DESYNC_STUN="${NFQWS_OPT_DESYNC_STUN:---dpi-desync=fake --dpi-desync-repeats=2}" alloc_dnum DNUM_STUN4ALL alloc_qnum QNUM_STUN4ALL zapret_custom_daemons() { # $1 - 1 - add, 0 - stop local opt="--qnum=$QNUM_STUN4ALL $NFQWS_OPT_DESYNC_STUN" do_nfqws $1 $DNUM_STUN4ALL "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f='-p udp -m u32 --u32' fw_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 } zapret_custom_firewall_nft() { # stop logic is not required local f="udp length >= 28 @ih,32,32 0x2112A442 @ih,0,2 0 @ih,30,2 0" nft_fw_nfqws_post "$f" "$f" $QNUM_STUN4ALL } ================================================ FILE: init.d/custom.d.examples.linux/50-tpws-ipset ================================================ # this custom script demonstrates how to launch extra tpws instance limited by ipset # can override in config : TPWS_MY1_OPT="${TPWS_MY1_OPT:---oob --split-pos=midsld}" TPWS_MY1_PORTS=${TPWS_MY1_PORTS:-$TPWS_PORTS} TPWS_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}" TPWS_MY1_SUBNETS6="${TPWS_MY1_SUBNETS6:-2607:F8B0::/32 2a00:1450:4000::/37}" TPWS_MY1_IPSET_SIZE=${TPWS_MY1_IPSET_SIZE:-4096} TPWS_MY1_IPSET_OPT="${TPWS_MY1_IPSET_OPT:-hash:net hashsize 8192 maxelem $TPWS_MY1_IPSET_SIZE}" alloc_dnum DNUM_TPWS_MY1 alloc_tpws_port PORT_TPWS_MY1 TPWS_MY1_NAME4=my1tpws4 TPWS_MY1_NAME6=my1tpws6 zapret_custom_daemons() { # $1 - 1 - run, 0 - stop local opt="--port=$PORT_TPWS_MY1 $TPWS_MY1_OPT" do_tpws $1 $DNUM_TPWS_MY1 "$opt" } zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f4 f6 subnet local PORTS_IPT=$(replace_char - : $TPWS_MY1_PORTS) [ "$1" = 1 -a "$DISABLE_IPV4" != 1 ] && { ipset create $TPWS_MY1_NAME4 $TPWS_MY1_IPSET_OPT family inet 2>/dev/null ipset flush $TPWS_MY1_NAME4 for subnet in $TPWS_MY1_SUBNETS4; do echo add $TPWS_MY1_NAME4 $subnet done | ipset -! restore } [ "$1" = 1 -a "$DISABLE_IPV6" != 1 ] && { ipset create $TPWS_MY1_NAME6 $TPWS_MY1_IPSET_OPT family inet6 2>/dev/null ipset flush $TPWS_MY1_NAME6 for subnet in $TPWS_MY1_SUBNETS6; do echo add $TPWS_MY1_NAME6 $subnet done | ipset -! restore } f4="-p tcp -m multiport --dports $PORTS_IPT -m set --match-set" f6="$f4 $TPWS_MY1_NAME6 dst" f4="$f4 $TPWS_MY1_NAME4 dst" fw_tpws $1 "$f4" "$f6" $PORT_TPWS_MY1 [ "$1" = 1 ] || { ipset destroy $TPWS_MY1_NAME4 2>/dev/null ipset destroy $TPWS_MY1_NAME6 2>/dev/null } } zapret_custom_firewall_nft() { local f4 f6 subnets [ "$DISABLE_IPV4" != 1 ] && { make_comma_list subnets $TPWS_MY1_SUBNETS4 nft_create_set $TPWS_MY1_NAME4 "type ipv4_addr; size $TPWS_MY1_IPSET_SIZE; auto-merge; flags interval;" nft_flush_set $TPWS_MY1_NAME4 nft_add_set_element $TPWS_MY1_NAME4 "$subnets" } [ "$DISABLE_IPV6" != 1 ] && { make_comma_list subnets $TPWS_MY1_SUBNETS6 nft_create_set $TPWS_MY1_NAME6 "type ipv6_addr; size $TPWS_MY1_IPSET_SIZE; auto-merge; flags interval;" nft_flush_set $TPWS_MY1_NAME6 nft_add_set_element $TPWS_MY1_NAME6 "$subnets" } f4="tcp dport {$TPWS_MY1_PORTS}" f6="$f4 ip6 daddr @$TPWS_MY1_NAME6" f4="$f4 ip daddr @$TPWS_MY1_NAME4" nft_fw_tpws "$f4" "$f6" $PORT_TPWS_MY1 } zapret_custom_firewall_nft_flush() { # this function is called after all nft fw rules are deleted # however sets are not deleted. it's desired to clear sets here. nft_del_set $TPWS_MY1_NAME4 2>/dev/null nft_del_set $TPWS_MY1_NAME6 2>/dev/null } ================================================ FILE: init.d/custom.d.examples.linux/50-wg4all ================================================ # this custom script runs desync to all wireguard handshake initiation packets # NOTE: this works for original wireguard and may not work for 3rd party implementations such as xray # 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) # can override in config : NFQWS_OPT_DESYNC_WG="${NFQWS_OPT_DESYNC_WG:---dpi-desync=fake --dpi-desync-repeats=2}" alloc_dnum DNUM_WG4ALL alloc_qnum QNUM_WG4ALL zapret_custom_daemons() { # $1 - 1 - add, 0 - stop local opt="--qnum=$QNUM_WG4ALL $NFQWS_OPT_DESYNC_WG" do_nfqws $1 $DNUM_WG4ALL "$opt" } # size = 156 (8 udp header + 148 payload) && payload starts with 0x01000000 zapret_custom_firewall() { # $1 - 1 - run, 0 - stop local f='-p udp -m u32 --u32' fw_nfqws_post $1 "$f 0>>22&0x3C@4>>16=0x9c&&0>>22&0x3C@8=0x01000000" "$f 44>>16=0x9c&&48=0x01000000" $QNUM_WG4ALL } zapret_custom_firewall_nft() { # stop logic is not required local f="udp length 156 @ih,0,32 0x01000000" nft_fw_nfqws_post "$f" "$f" $QNUM_WG4ALL } ================================================ FILE: init.d/macos/custom.d/.keep ================================================ ================================================ FILE: init.d/macos/custom.d.examples/50-extra-tpws ================================================ # this script is an example describing how to run tpws on a custom port TPWS_OPT_EXTRA=${TPWS_OPT_EXTRA:---split-pos=2} DPORTS_EXTRA=${DPORTS_EXTRA:-20443,20444,30000-30009} alloc_dnum DNUM_EXTRA_TPWS alloc_tpws_port TPPORT_EXTRA_TPWS zapret_custom_daemons() { # $1 - 1 - run, 0 - stop local opt="--user=root --port=$TPPORT_EXTRA_TPWS" tpws_apply_binds opt opt="$opt $TPWS_OPT_EXTRA" filter_apply_hostlist_target opt do_daemon $1 $DNUM_EXTRA_TPWS "$TPWS" "$opt" } # custom firewall functions echo rules for zapret-v4 and zapret-v6 anchors # they come after automated table definitions. so you can use ... zapret_custom_firewall_v4() { pf_anchor_zapret_v4_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA) } zapret_custom_firewall_v6() { pf_anchor_zapret_v6_tpws $TPPORT_EXTRA_TPWS $(replace_char - : $DPORTS_EXTRA) } ================================================ FILE: init.d/macos/functions ================================================ # init script functions library for macos ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/pf.sh" . "$ZAPRET_BASE/common/list.sh" . "$ZAPRET_BASE/common/custom.sh" CUSTOM_DIR="$ZAPRET_RW/init.d/macos" IPSET_DIR=$ZAPRET_BASE/ipset . "$IPSET_DIR/def.sh" PIDDIR=/var/run [ -n "$TPPORT" ] || TPPORT=988 [ -n "$TPPORT_SOCKS" ] || TPPORT=987 [ -n "$WS_USER" ] || WS_USER=daemon TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" [ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/macos/custom" [ -f "$CUSTOM_SCRIPT" ] && . "$CUSTOM_SCRIPT" run_daemon() { # $1 - daemon number : 1,2,3,... # $2 - daemon # $3 - daemon args # use $PIDDIR/$DAEMONBASE$1.pid as pidfile local DAEMONBASE="$(basename "$2")" local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" local ARGS="--daemon --pidfile=$PIDFILE $3" [ -f "$PIDFILE" ] && pgrep -qF "$PIDFILE" && { echo Already running $1: $2 return 0 } echo "Starting daemon $1: $2 $ARGS" "$2" $ARGS } stop_daemon() { # $1 - daemon number : 1,2,3,... # $2 - daemon # use $PIDDIR/$DAEMONBASE$1.pid as pidfile local PID local DAEMONBASE="$(basename "$2")" local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" [ -f "$PIDFILE" ] && read PID <"$PIDFILE" [ -n "$PID" ] && { echo "Stopping daemon $1: $2 (PID=$PID)" kill $PID rm -f "$PIDFILE" } return 0 } do_daemon() { # $1 - 1 - run, 0 - stop on_off_function run_daemon stop_daemon "$@" } tpws_apply_binds() { local o [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" [ "$DISABLE_IPV6" = "1" ] || { for i in lo0 $IFACE_LAN; do o="$o --bind-iface6=$i --bind-linklocal=force $TPWS_WAIT" done } eval $1="\"\$$1 $o\"" } tpws_apply_socks_binds() { local o [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" for lan in $IFACE_LAN; do [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" done eval $1="\"\$$1 $o\"" } wait_interface_ll() { echo waiting for an ipv6 link local address on $1 ... "$TPWS" --bind-wait-only --bind-iface6=$1 --bind-linklocal=force $TPWS_WAIT } wait_lan_ll() { [ "$DISABLE_IPV6" != "1" ] && { for lan in $IFACE_LAN; do wait_interface_ll $lan >&2 || { echo "wait interface failed on $lan" return 1 } done } return 0 } get_ipv6_linklocal() { ifconfig $1 | sed -nEe 's/^.*inet6 (fe80:[a-f0-9:]+).*/\1/p' } zapret_do_firewall() { # $1 - 1 - add, 0 - del [ "$1" = 1 -a -n "$INIT_FW_PRE_UP_HOOK" ] && $INIT_FW_PRE_UP_HOOK [ "$1" = 0 -a -n "$INIT_FW_PRE_DOWN_HOOK" ] && $INIT_FW_PRE_DOWN_HOOK if [ "$1" = "1" ] ; then pf_anchor_root || return 1 pf_anchors_create pf_anchors_load || return 1 pf_enable else pf_anchors_clear fi [ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK [ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK return 0 } zapret_apply_firewall() { zapret_do_firewall 1 "$@" } zapret_unapply_firewall() { zapret_do_firewall 0 "$@" } zapret_restart_firewall() { zapret_unapply_firewall "$@" zapret_apply_firewall "$@" } standard_mode_daemons() { local opt if [ "$1" = "1" ] && [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] ; then echo "both ipv4 and ipv6 are disabled. nothing to do" else [ "$TPWS_ENABLE" = 1 ] && check_bad_ws_options $1 "$TPWS_OPT" && { opt="--user=root --port=$TPPORT" tpws_apply_binds opt opt="$opt $TPWS_OPT" filter_apply_hostlist_target opt do_daemon $1 1 "$TPWS" "$opt" } [ "$TPWS_SOCKS_ENABLE" = 1 ] && { opt="--socks --user=$WS_USER --port=$TPPORT_SOCKS" tpws_apply_socks_binds opt opt="$opt $TPWS_SOCKS_OPT" filter_apply_hostlist_target opt do_daemon $1 2 "$TPWS" "$opt" } fi } zapret_do_daemons() { # $1 - 1 - run, 0 - stop standard_mode_daemons $1 custom_runner zapret_custom_daemons $1 return 0 } zapret_run_daemons() { zapret_do_daemons 1 "$@" } zapret_stop_daemons() { zapret_do_daemons 0 "$@" } zapret_restart_daemons() { zapret_stop_daemons "$@" zapret_run_daemons "$@" } ================================================ FILE: init.d/macos/zapret ================================================ #!/bin/sh EXEDIR="$(dirname "$0")" ZAPRET_BASE="$EXEDIR/../.." ZAPRET_BASE="$(cd "$ZAPRET_BASE"; pwd)" . "$EXEDIR/functions" case "$1" in start) zapret_run_daemons [ "$INIT_APPLY_FW" != "1" ] || zapret_apply_firewall ;; stop) [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall zapret_stop_daemons ;; restart) "$0" stop "$0" start ;; start-fw|start_fw) zapret_apply_firewall ;; stop-fw|stop_fw) zapret_unapply_firewall ;; restart-fw|stop_fw) zapret_restart_firewall ;; reload-fw-tables|reload_fw_tables) pf_table_reload ;; start-daemons|start_daemons) zapret_run_daemons ;; stop-daemons|stop_daemons) zapret_stop_daemons ;; restart-daemons|restart_daemons) zapret_restart_daemons ;; *) N="$SCRIPT/$NAME" echo "Usage: $N {start|stop|start-fw|stop-fw|restart-fw|reload-fw-tables|start-daemons|stop-daemons|restart-daemons}" >&2 exit 1 ;; esac ================================================ FILE: init.d/macos/zapret.plist ================================================ Label zapret LaunchOnlyOnce ProgramArguments /opt/zapret/init.d/macos/zapret start RunAtLoad ================================================ FILE: init.d/openrc/zapret ================================================ #!/sbin/openrc-run # zapret openrc to sysv adapter # on some systems (alpine) for unknown reason non-openrc-run scripts are not started from /etc/init.d EXEDIR=$(dirname "$RC_SERVICE") EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE="$EXEDIR/../.." ZAPRET_INIT="$ZAPRET_BASE/init.d/sysv/zapret" extra_commands="start_fw stop_fw restart_fw start_daemons stop_daemons restart_daemons reload_ifsets list_ifsets list_table" description="extra commands :" description_stop_fw="Stop zapret firewall" description_start_fw="Start zapret firewall" description_restart_fw="Restart zapret firewall" description_reload_ifsets="Reload interface lists (nftables only)" description_list_ifsets="Display interface lists (nftables only)" description_list_table="Display zapret nftable (nftables only)" description_stop_daemons="Stop zapret daemons only" description_start_daemons="Start zapret daemons only" description_restart_daemons="Restart zapret firewall only" depend() { rc-service -e networking && need networking } start() { "$ZAPRET_INIT" start } stop() { "$ZAPRET_INIT" stop } start_fw() { "$ZAPRET_INIT" start_fw } stop_fw() { "$ZAPRET_INIT" stop_fw } restart_fw() { "$ZAPRET_INIT" restart_fw } start_daemons() { "$ZAPRET_INIT" start_daemons } stop_daemons() { "$ZAPRET_INIT" stop_daemons } restart_daemons() { "$ZAPRET_INIT" restart_daemons } reload_ifsets() { "$ZAPRET_INIT" reload_ifsets } list_ifsets() { "$ZAPRET_INIT" list_ifsets } list_table() { "$ZAPRET_INIT" list_table } ================================================ FILE: init.d/openwrt/90-zapret ================================================ #!/bin/sh ZAPRET=/etc/init.d/zapret check_lan() { IS_LAN= [ -n "$OPENWRT_LAN" ] || OPENWRT_LAN=lan for lan in $OPENWRT_LAN; do [ "$INTERFACE" = "$lan" ] && { IS_LAN=1 break } done } check_need_to_reload_tpws6() { # tpws6 dnat target nft map can only be reloaded within firewall apply procedure # interface ifsets (wanif, wanif6, lanif) can be reloaded independently check_lan RELOAD_TPWS6= [ "$ACTION" = "ifup" -a "$DISABLE_IPV6" != 1 -a -n "$IS_LAN" ] && \ if [ "$TPWS_ENABLE" = 1 ] || dir_is_not_empty "$CUSTOM_DIR/custom.d"; then RELOAD_TPWS6=1; fi } [ -n "$INTERFACE" ] && [ "$ACTION" = ifup -o "$ACTION" = ifdown ] && [ -x "$ZAPRET" ] && "$ZAPRET" enabled && { SCRIPT=$(readlink "$ZAPRET") if [ -n "$SCRIPT" ]; then EXEDIR=$(dirname "$SCRIPT") ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") else ZAPRET_BASE=/opt/zapret fi ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} CUSTOM_DIR="$ZAPRET_RW/init.d/openwrt" . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/fwtype.sh" check_need_to_reload_tpws6 [ -n "$RELOAD_TPWS6" ] && { logger -t zapret restarting daemons due to $ACTION of $INTERFACE to update tpws6 dnat target "$ZAPRET" restart_daemons } linux_fwtype case "$FWTYPE" in nftables) if [ -n "$RELOAD_TPWS6" ] ; then logger -t zapret reloading nftables due to $ACTION of $INTERFACE to update tpws6 dnat target "$ZAPRET" restart_fw else logger -t zapret reloading nftables ifsets due to $ACTION of $INTERFACE "$ZAPRET" reload_ifsets fi ;; iptables) openwrt_fw3 || { logger -t zapret reloading iptables due to $ACTION of $INTERFACE "$ZAPRET" restart_fw } ;; esac } ================================================ FILE: init.d/openwrt/custom.d/.keep ================================================ ================================================ FILE: init.d/openwrt/firewall.zapret ================================================ SCRIPT=$(readlink /etc/init.d/zapret) if [ -n "$SCRIPT" ]; then EXEDIR=$(dirname "$SCRIPT") ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") else ZAPRET_BASE=/opt/zapret fi . "$ZAPRET_BASE/init.d/openwrt/functions" zapret_apply_firewall ================================================ FILE: init.d/openwrt/functions ================================================ . /lib/functions/network.sh ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/linux_iphelper.sh" . "$ZAPRET_BASE/common/ipt.sh" . "$ZAPRET_BASE/common/nft.sh" . "$ZAPRET_BASE/common/linux_fw.sh" . "$ZAPRET_BASE/common/linux_daemons.sh" . "$ZAPRET_BASE/common/list.sh" . "$ZAPRET_BASE/common/custom.sh" CUSTOM_DIR="$ZAPRET_RW/init.d/openwrt" [ -n "$QNUM" ] || QNUM=200 [ -n "$TPPORT" ] || TPPORT=988 [ -n "$TPPORT_SOCKS" ] || TPPORT_SOCKS=987 [ -n "$WS_USER" ] || WS_USER=daemon [ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000 [ -n "$DESYNC_MARK_POSTNAT" ] || DESYNC_MARK_POSTNAT=0x20000000 [ -n "$OPENWRT_LAN" ] || OPENWRT_LAN=lan TPWS_LOCALHOST4=127.0.0.127 IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh" # can be multiple ipv6 outgoing interfaces # uplink from isp, tunnelbroker, vpn, ... # want them all. who knows what's the real one that blocks sites # dont want any manual configuration - want to do it automatically # standard network_find_wan[6] return only the first # we use low level function from network.sh to avoid this limitation # it can change theoretically and stop working network_find_wan4_all() { if [ -n "$OPENWRT_WAN4" ]; then eval $1="\$OPENWRT_WAN4" else __network_ifstatus "$1" "" "[@.route[@.target='0.0.0.0' && !@.table]].interface" "" 10 2>/dev/null && return network_find_wan $1 fi } network_find_wan_all() { network_find_wan4_all "$@" } network_find_wan6_all() { if [ -n "$OPENWRT_WAN6" ]; then eval $1="\$OPENWRT_WAN6" else __network_ifstatus "$1" "" "[@.route[@.target='::' && !@.table]].interface" "" 10 2>/dev/null && return network_find_wan6 $1 fi } network_find_wanX_devices() { # $1 - ip version: 4 or 6 # $2 - variable to put result to local ifaces network_find_wan${1}_all ifaces call_for_multiple_items network_get_device $2 "$ifaces" } dnat6_target() { # $1 - lan network name # $2 - var to store target ip6 network_is_up $1 || { [ -n "$2" ] && eval $2='' return } local DEVICE network_get_device DEVICE $1 _dnat6_target $DEVICE $2 } set_route_localnet() { # $1 - 1 = enable, 0 = disable local DLAN call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" _set_route_localnet $1 $DLAN } fw_nfqws_prepost_x() { # $1 - 1 - add, 0 - del # $2 - filter # $3 - queue number # $4 - 4/6 # $5 - post/pre local ifaces DWAN network_find_wan${4}_all ifaces call_for_multiple_items network_get_device DWAN "$ifaces" [ -n "$DWAN" ] && _fw_nfqws_${5}${4} $1 "$2" $3 "$(unique $DWAN)" } fw_nfqws_post4() { fw_nfqws_prepost_x $1 "$2" $3 4 post } fw_nfqws_post6() { fw_nfqws_prepost_x $1 "$2" $3 6 post } fw_nfqws_pre4() { fw_nfqws_prepost_x $1 "$2" $3 4 pre } fw_nfqws_pre6() { fw_nfqws_prepost_x $1 "$2" $3 6 pre } fw_tpws_x() { # $1 - 1 - add, 0 - del # $2 - filter # $3 - tpws port # $4 - ip version : 4 or 6 local ifaces DLAN DWAN call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" network_find_wan${4}_all ifaces call_for_multiple_items network_get_device DWAN "$ifaces" [ -n "$DWAN" ] && _fw_tpws${4} $1 "$2" $3 "$DLAN" "$(unique $DWAN)" } fw_tpws4() { fw_tpws_x $1 "$2" $3 4 } fw_tpws6() { fw_tpws_x $1 "$2" $3 6 } create_ipset() { echo "Creating ip list table (firewall type $FWTYPE)" "$IPSET_CR" "$@" } list_nfqws_rules() { # $1 = '' for ipv4, '6' for ipv6 ip$1tables -S POSTROUTING -t mangle | \ grep -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" | \ sed -re 's/^-A POSTROUTING (.*) -j NFQUEUE.*$/\1/' -e "s/-m mark ! --mark $DESYNC_MARK\/$DESYNC_MARK//" } apply_flow_offloading_enable_rule() { # $1 = '' for ipv4, '6' for ipv6 local i off='-j FLOWOFFLOAD' [ "$FLOWOFFLOAD" = "hardware" ] && off="$off --hw" i="forwarding_rule_zapret -m comment --comment zapret_traffic_offloading_enable -m conntrack --ctstate RELATED,ESTABLISHED $off" echo enabling ipv${1:-4} flow offloading : $i ip$1tables -A $i } apply_flow_offloading_exempt_rule() { # $1 = '' for ipv4, '6' for ipv6 local i v v=$1 shift i="forwarding_rule_zapret $@ -m comment --comment zapret_traffic_offloading_exemption -j RETURN" echo applying ipv${v:-4} flow offloading exemption : $i ip${v}tables -A $i } flow_offloading_unexempt_v() { # $1 = '' for ipv4, '6' for ipv6 local DWAN network_find_wanX_devices ${1:-4} DWAN for i in $DWAN; do ipt$1_del FORWARD -o $i -j forwarding_rule_zapret ; done ip$1tables -F forwarding_rule_zapret 2>/dev/null ip$1tables -X forwarding_rule_zapret 2>/dev/null } flow_offloading_exempt_v() { # $1 = '' for ipv4, '6' for ipv6 is_ipt_flow_offload_avail $1 || return 0 flow_offloading_unexempt_v $1 [ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && { ip$1tables -N forwarding_rule_zapret # remove outgoing interface list_nfqws_rules $1 | sed -re 's/-o +[^ ]+//g' | while read rule; do apply_flow_offloading_exempt_rule "$1" $rule done apply_flow_offloading_enable_rule $1 # only outgoing to WAN packets trigger flow offloading local DWAN network_find_wanX_devices ${1:-4} DWAN for i in $DWAN; do ipt$1 FORWARD -o $i -j forwarding_rule_zapret; done } return 0 } flow_offloading_exempt() { [ "$DISABLE_IPV4" = "1" ] || flow_offloading_exempt_v [ "$DISABLE_IPV6" = "1" ] || flow_offloading_exempt_v 6 } flow_offloading_unexempt() { [ "$DISABLE_IPV4" = "1" ] || flow_offloading_unexempt_v [ "$DISABLE_IPV6" = "1" ] || flow_offloading_unexempt_v 6 } nft_fill_ifsets_overload() { local ifaces DLAN DWAN DWAN6 PDLAN PDWAN PDWAN6 call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" call_for_multiple_items network_get_physdev PDLAN "$OPENWRT_LAN" network_find_wan4_all ifaces call_for_multiple_items network_get_device DWAN "$ifaces" call_for_multiple_items network_get_physdev PDWAN "$ifaces" network_find_wan6_all ifaces call_for_multiple_items network_get_device DWAN6 "$ifaces" call_for_multiple_items network_get_physdev PDWAN6 "$ifaces" nft_fill_ifsets "$DLAN" "$DWAN" "$DWAN6" "$PDLAN" "$PDWAN" "$PDWAN6" } nft_fw_tpws4() { _nft_fw_tpws4 "$1" $2 always_apply_wan_filter } nft_fw_tpws6() { local DLAN call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" _nft_fw_tpws6 "$1" $2 "$DLAN" always_apply_wan_filter } nft_fw_nfqws_post4() { _nft_fw_nfqws_post4 "$1" $2 always_apply_wan_filter } nft_fw_nfqws_post6() { _nft_fw_nfqws_post6 "$1" $2 always_apply_wan_filter } nft_fw_nfqws_pre4() { _nft_fw_nfqws_pre4 "$1" $2 always_apply_wan_filter } nft_fw_nfqws_pre6() { _nft_fw_nfqws_pre6 "$1" $2 always_apply_wan_filter } ================================================ FILE: init.d/openwrt/zapret ================================================ #!/bin/sh /etc/rc.common USE_PROCD=1 # after network START=21 my_extra_command() { local cmd="$1" local help="$2" local extra="$(printf "%-16s%s" "${cmd}" "${help}")" EXTRA_HELP="${EXTRA_HELP} ${extra} " EXTRA_COMMANDS="${EXTRA_COMMANDS} ${cmd}" } my_extra_command stop_fw "Stop zapret firewall (noop in iptables+fw3 case)" my_extra_command start_fw "Start zapret firewall (noop in iptables+fw3 case)" my_extra_command restart_fw "Restart zapret firewall (noop in iptables+fw3 case)" my_extra_command reload_ifsets "Reload interface lists (nftables only)" my_extra_command list_ifsets "Display interface lists (nftables only)" my_extra_command list_table "Display zapret nftable (nftables only)" my_extra_command stop_daemons "Stop zapret daemons only (=stop in iptables+fw3 case)" my_extra_command start_daemons "Start zapret daemons only (=start in iptables+fw3 case)" my_extra_command restart_daemons "Restart zapret firewall only (=restart in iptables+fw3 case)" SCRIPT=$(readlink /etc/init.d/zapret) if [ -n "$SCRIPT" ]; then EXEDIR=$(dirname "$SCRIPT") ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") else ZAPRET_BASE=/opt/zapret fi . "$ZAPRET_BASE/init.d/openwrt/functions" # !!!!! in old openwrt 21.x- with iptables firewall rules are configured separately # !!!!! in new openwrt >21.x with nftables firewall is configured here PIDDIR=/var/run [ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws" NFQWS_OPT_BASE="--user=$WS_USER --dpi-desync-fwmark=$DESYNC_MARK" [ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" TPWS_OPT_BASE="--user=$WS_USER" TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4" TPWS_OPT_BASE6="--bind-addr=::1" TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3" run_daemon() { # $1 - daemon string id or number. can use 1,2,3,... # $2 - daemon # $3 - daemon args # use $PIDDIR/$DAEMONBASE$1.pid as pidfile local DAEMONBASE="$(basename "$2")" echo "Starting daemon $1: $2 $3" procd_open_instance procd_set_param command $2 $3 procd_set_param pidfile $PIDDIR/$DAEMONBASE$1.pid procd_close_instance } run_tpws() { [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 local OPT="$TPWS_OPT_BASE" local DEVICE [ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4" [ "$DISABLE_IPV6" = "1" ] || { OPT="$OPT $TPWS_OPT_BASE6" for lan in $OPENWRT_LAN; do network_get_device DEVICE $lan [ -n "$DEVICE" ] && OPT="$OPT --bind-iface6=$DEVICE $TPWS_OPT_BASE6_PRE" done } run_daemon $1 "$TPWS" "$OPT $2" } do_tpws() { [ "$1" = 0 ] || { shift; run_tpws "$@"; } } run_tpws_socks() { [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 local opt="$TPWS_OPT_BASE --socks" tpws_apply_socks_binds opt run_daemon $1 "$TPWS" "$opt $2" } do_tpws_socks() { [ "$1" = 0 ] || { shift; run_tpws_socks "$@"; } } tpws_apply_socks_binds() { local o [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" for lan in $OPENWRT_LAN; do network_get_device DEVICE $lan [ -n "$DEVICE" ] || continue [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$DEVICE $TPWS_WAIT" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$DEVICE --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" done eval $1="\"\$$1 $o\"" } run_nfqws() { run_daemon $1 "$NFQWS" "$NFQWS_OPT_BASE $2" } do_nfqws() { [ "$1" = 0 ] || { shift; run_nfqws "$@"; } } start_daemons_procd() { standard_mode_daemons 1 custom_runner zapret_custom_daemons 1 return 0 } start_daemons() { rc_procd start_daemons_procd "$@" } stop_daemons() { local svc="$(basename ${basescript:-$initscript})" procd_running "$svc" "$1" && procd_kill "$svc" "$1" } restart_daemons() { stop_daemons start_daemons } start_fw() { zapret_apply_firewall } stop_fw() { zapret_unapply_firewall } restart_fw() { stop_fw start_fw } reload_ifsets() { zapret_reload_ifsets } list_ifsets() { zapret_list_ifsets } list_table() { zapret_list_table } start_service() { start_daemons_procd [ "$INIT_APPLY_FW" != "1" ] || { linux_fwtype openwrt_fw3_integration || start_fw } } stop_service() { # this procedure is called from stop() # stop() already stop daemons [ "$INIT_APPLY_FW" != "1" ] || { linux_fwtype openwrt_fw3_integration || stop_fw } } ================================================ FILE: init.d/openwrt-minimal/readme.txt ================================================ Minimal tpws startup script for low storage openwrt. --- openwrt with NFTABLES (22+) Make sure you are running openwrt with nftables, not iptables. No opkg dependencies required ! * install : Copy everything from tpws directory to the root of the router. Copy tpws binary for your architecture to /usr/bin/tpws Set proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws EDIT /etc/config/tpws If you don't want ipv6 : edit /etc/nftables.d and comment lines with ipv6 redirect /etc/init.d/tpws enable /etc/init.d/tpws start fw4 restart * full uninstall : /etc/init.d/tpws disable /etc/init.d/tpws stop rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws fw4 restart --- openwrt with IPTABLES (21-) Make sure you are running openwrt with iptables, not nftables. Make sure you do not have anything valuable in /etc/firewall.user. If you have - do not blindly follow instruction in firewall.user part. Merge the code instead or setup your own firewall include in /etc/config/firewall. opkg update opkg install iptables-mod-extra IPV6 ONLY : opkg install ip6tables-mod-nat * install : Copy everything from tpws directory to the root of the router. Copy tpws binary for your architecture to /usr/bin/tpws Set proper access rights : chmod 755 /etc/init.d/tpws /usr/bin/tpws EDIT /etc/config/tpws If you don't want ipv6 : edit /etc/firewall.user and set DISABLE_IPV6=1 /etc/init.d/tpws enable /etc/init.d/tpws start fw3 restart * full uninstall : /etc/init.d/tpws disable /etc/init.d/tpws stop rm -f /etc/nftables.d/90-tpws.nft /etc/firewall.user /etc/init.d/tpws touch /etc/firewall.user fw3 restart ================================================ FILE: init.d/openwrt-minimal/tpws/etc/config/tpws ================================================ config global defaults option user daemon option tpws /usr/bin/tpws config tpws option port 900 option opt '--split-pos=2 --oob' option enabled 1 config tpws option port 901 option opt '--split-tls=sni --disorder' option enabled 0 ================================================ FILE: init.d/openwrt-minimal/tpws/etc/firewall.user ================================================ DISABLE_IPV6=0 TP_PORT=900 TP_USER=daemon EXCLUDE4="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" EXCLUDE6="fc00::/7 fe80::/10 ::1" IPTS="iptables ip6tables" [ "$DISABLE_IPV6" = 1 ] && IPTS=iptables exists() { which "$1" >/dev/null 2>/dev/null } ipt() { $IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@" } redirect_port() { ipt tpws -t nat -p tcp --dport $1 -j REDIRECT --to-port $2 } redirect() { redirect_port 80 $TP_PORT redirect_port 443 $TP_PORT } for IPTABLES in $IPTS; do $IPTABLES -t nat -N tpws 2>/dev/null $IPTABLES -t nat -F tpws redirect done for net in $EXCLUDE4; do iptables -t nat -I tpws -d $net -j RETURN done [ "$DISABLE_IPV6" = 1 ] || { for net in $EXCLUDE6; do ip6tables -t nat -I tpws -d $net -j RETURN done } for IPTABLES in $IPTS; do ipt PREROUTING -t nat -j tpws ipt OUTPUT -t nat -m owner ! --uid-owner $TP_USER -j tpws done ================================================ FILE: init.d/openwrt-minimal/tpws/etc/init.d/tpws ================================================ #!/bin/sh /etc/rc.common TPWS_DEFAULT=/usr/bin/tpws TPWS_USER_DEFAULT=daemon START=99 STOP=01 USE_PROCD=1 tpws_instance() { config_get "$@" local enabled port opt config_get_bool enabled "$1" enabled 0 [ "$enabled" -eq 1 ] || return 1 config_get port "$1" port config_get opt "$1" opt local COMMAND="$TPWS --user=$TPWS_USER --port=$port $opt" procd_open_instance procd_set_param command $COMMAND procd_close_instance } start_service() { config_load tpws config_get TPWS_USER defaults user $TPWS_USER_DEFAULT config_get TPWS defaults tpws $TPWS_DEFAULT config_foreach tpws_instance tpws } ================================================ FILE: init.d/openwrt-minimal/tpws/etc/nftables.d/90-tpws.nft ================================================ set tpws_exclude4 { type ipv4_addr; flags interval; auto-merge; 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 } } set tpws_exclude6 { type ipv6_addr; flags interval; auto-merge; elements = { fc00::/7, fe80::/10, ::1 } } chain tpws_pre { type nat hook prerouting priority dstnat; policy accept; tcp dport {80,443} ip daddr != @tpws_exclude4 redirect to :900 tcp dport {80,443} ip6 daddr != @tpws_exclude6 redirect to :900 } chain tpws_out { type nat hook output priority -100; policy accept; tcp dport {80,443} skuid != daemon ip daddr != @tpws_exclude4 redirect to :900 tcp dport {80,443} skuid != daemon ip6 daddr != @tpws_exclude6 redirect to :900 } ================================================ FILE: init.d/pfsense/zapret.sh ================================================ #!/bin/sh # this file should be placed to /usr/local/etc/rc.d and chmod 755 # prepare system kldload ipfw kldload ipdivert # for older pfsense versions. newer do not have these sysctls sysctl net.inet.ip.pfil.outbound=ipfw,pf sysctl net.inet.ip.pfil.inbound=ipfw,pf sysctl net.inet6.ip6.pfil.outbound=ipfw,pf sysctl net.inet6.ip6.pfil.inbound=ipfw,pf # required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state pfctl -d ; pfctl -e # add ipfw rules and start daemon ipfw delete 100 ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg pkill ^dvtws$ dvtws --daemon --port 989 --dpi-desync=multisplit ================================================ FILE: init.d/runit/zapret/finish ================================================ #!/bin/sh /opt/zapret/init.d/sysv/zapret stop ================================================ FILE: init.d/runit/zapret/run ================================================ #!/bin/sh /opt/zapret/init.d/sysv/zapret start exec chpst -b zapret sleep infinity ================================================ FILE: init.d/s6/zapret/down ================================================ #!/bin/execlineb -P exec /opt/zapret/init.d/sysv/zapret stop ================================================ FILE: init.d/s6/zapret/type ================================================ oneshot ================================================ FILE: init.d/s6/zapret/up ================================================ #!/bin/execlineb -P exec /opt/zapret/init.d/sysv/zapret start ================================================ FILE: init.d/systemd/nfqws@.service ================================================ # Example systemd service unit for nfqws. Adjust for your installation. # WARNING ! This unit requires to compile nfqws using `make systemd` # WARNING ! This makefile target enables special systemd notify support. # PREPARE # install build depends # make -C /opt/zapret systemd # cp nfqws@service /lib/systemd/system # systemctl daemon-reload # MANAGE INSTANCE # prepare /etc/zapret/nfqws1.conf with nfqws parameters # systemctl start nfqws@nfqws1 # systemctl status nfqws@nfqws1 # systemctl restart nfqws@nfqws1 # systemctl enable nfqws@nfqws1 # systemctl disable nfqws@nfqws1 # systemctl stop nfqws@nfqws1 # DELETE # rm /lib/systemd/system/nfqws@.service # systemctl daemon-reload [Unit] After=network.target [Service] Type=notify Restart=on-failure ExecSearchPath=/opt/zapret/binaries/my ExecStart=nfqws @${CONFIG_DIR}/${INSTANCE}.conf Environment=CONFIG_DIR=/etc/zapret Environment=INSTANCE=%i RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET LockPersonality=true MemoryDenyWriteExecute=true PrivateDevices=true PrivateMounts=true PrivateTmp=true ProcSubset=pid ProtectClock=true ProtectControlGroups=true ProtectHome=true ProtectHostname=true ProtectKernelLogs=true ProtectKernelModules=true ProtectKernelTunables=true ProtectProc=invisible ProtectSystem=full RemoveIPC=true RestrictNamespaces=true RestrictRealtime=true RestrictSUIDSGID=true UMask=0077 [Install] WantedBy=multi-user.target ================================================ FILE: init.d/systemd/tpws@.service ================================================ # Example systemd service unit for tpws. Adjust for your installation. # WARNING ! This unit requires to compile tpws using `make systemd` # WARNING ! This makefile target enables special systemd notify support. # PREPARE # install build depends # make -C /opt/zapret systemd # cp tpws@service /lib/systemd/system # systemctl daemon-reload # MANAGE INSTANCE # prepare /etc/zapret/tpws1.conf with tpws parameters # systemctl start tpws@tpws1 # systemctl status tpws@tpws1 # systemctl restart tpws@tpws1 # systemctl enable tpws@tpws1 # systemctl disable tpws@tpws1 # systemctl stop tpws@tpws1 # DELETE # rm /lib/systemd/system/tpws@.service # systemctl daemon-reload [Unit] After=network.target [Service] Type=notify Restart=on-failure ExecSearchPath=/opt/zapret/binaries/my ExecStart=tpws @${CONFIG_DIR}/${INSTANCE}.conf Environment=CONFIG_DIR=/etc/zapret Environment=INSTANCE=%i RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET6 AF_INET LockPersonality=true MemoryDenyWriteExecute=true PrivateDevices=true PrivateMounts=true PrivateTmp=true ProcSubset=pid ProtectClock=true ProtectControlGroups=true ProtectHome=true ProtectHostname=true ProtectKernelLogs=true ProtectKernelModules=true ProtectProc=invisible ProtectSystem=full RemoveIPC=true RestrictNamespaces=true RestrictRealtime=true RestrictSUIDSGID=true UMask=0077 [Install] WantedBy=multi-user.target ================================================ FILE: init.d/systemd/zapret-list-update.service ================================================ [Unit] Description=zapret ip/host list update [Service] Restart=no IgnoreSIGPIPE=no KillMode=control-group GuessMainPID=no RemainAfterExit=no ExecStart=/opt/zapret/ipset/get_config.sh [Install] WantedBy=multi-user.target ================================================ FILE: init.d/systemd/zapret-list-update.timer ================================================ [Unit] Description=zapret ip/host list update timer [Timer] OnCalendar=*-*-2,4,6,8,10,12,14,16,18,20,22,24,26,28,30 00:00:00 RandomizedDelaySec=86400 Persistent=true Unit=zapret-list-update.service [Install] WantedBy=timers.target ================================================ FILE: init.d/systemd/zapret.service ================================================ [Unit] After=network-online.target Wants=network-online.target [Service] Type=forking Restart=no TimeoutSec=30sec IgnoreSIGPIPE=no KillMode=none GuessMainPID=no RemainAfterExit=no ExecStart=/opt/zapret/init.d/sysv/zapret start ExecStop=/opt/zapret/init.d/sysv/zapret stop [Install] WantedBy=multi-user.target ================================================ FILE: init.d/sysv/custom.d/.keep ================================================ ================================================ FILE: init.d/sysv/functions ================================================ # init script functions library for desktop linux systems ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/linux_iphelper.sh" . "$ZAPRET_BASE/common/ipt.sh" . "$ZAPRET_BASE/common/nft.sh" . "$ZAPRET_BASE/common/linux_fw.sh" . "$ZAPRET_BASE/common/linux_daemons.sh" . "$ZAPRET_BASE/common/list.sh" . "$ZAPRET_BASE/common/custom.sh" CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" user_exists() { id -u $1 >/dev/null 2>/dev/null } useradd_compat() { # $1 - username # skip for readonly systems [ -w "/etc" ] && { if exists useradd ; then useradd --no-create-home --system --shell /bin/false $1 elif is_linked_to_busybox adduser ; then # some systems may miss nogroup group in /etc/group # adduser fails if it's absent and no group is specified addgroup nogroup 2>/dev/null # busybox has special adduser syntax adduser -S -H -D $1 elif exists adduser; then adduser --no-create-home --system --disabled-login $1 fi } user_exists $1 } prepare_user() { # $WS_USER is required to prevent redirection of the traffic originating from TPWS itself # otherwise infinite loop will occur # also its good idea not to run tpws as root user_exists $WS_USER || { # fallback to daemon if we cant add WS_USER useradd_compat $WS_USER || { for user in daemon nobody; do user_exists $user && { WS_USER=$user return 0 } done return 1 } } } # this complex user selection allows to survive in any locked/readonly/minimalistic environment [ -n "$WS_USER" ] || WS_USER=tpws if prepare_user; then USEROPT="--user=$WS_USER" else WS_USER=1 USEROPT="--uid $WS_USER:$WS_USER" fi PIDDIR=/var/run IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh" [ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000 [ -n "$DESYNC_MARK_POSTNAT" ] || DESYNC_MARK_POSTNAT=0x20000000 [ -n "$QNUM" ] || QNUM=200 [ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws" NFQWS_OPT_BASE="$USEROPT --dpi-desync-fwmark=$DESYNC_MARK" [ -n "$TPPORT" ] || TPPORT=988 [ -n "$TPPORT_SOCKS" ] || TPPORT_SOCKS=987 [ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" TPWS_LOCALHOST4=127.0.0.127 TPWS_OPT_BASE="$USEROPT" TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4" TPWS_OPT_BASE6="--bind-addr=::1" TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" # 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 TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3" dnat6_target() { _dnat6_target "$@" } set_route_localnet() { _set_route_localnet $1 $IFACE_LAN } fw_nfqws_post4() { _fw_nfqws_post4 $1 "$2" $3 "$IFACE_WAN" } fw_nfqws_post6() { _fw_nfqws_post6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" } fw_nfqws_pre4() { _fw_nfqws_pre4 $1 "$2" $3 "$IFACE_WAN" } fw_nfqws_pre6() { _fw_nfqws_pre6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" } fw_tpws4() { _fw_tpws4 $1 "$2" $3 "$IFACE_LAN" "$IFACE_WAN" } fw_tpws6() { _fw_tpws6 $1 "$2" $3 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" } nft_fw_tpws4() { _nft_fw_tpws4 "$1" $2 "$IFACE_WAN" } nft_fw_tpws6() { _nft_fw_tpws6 "$1" $2 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" } nft_fw_nfqws_post4() { _nft_fw_nfqws_post4 "$1" $2 "$IFACE_WAN" } nft_fw_nfqws_post6() { _nft_fw_nfqws_post6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" } nft_fw_nfqws_pre4() { _nft_fw_nfqws_pre4 "$1" $2 "$IFACE_WAN" } nft_fw_nfqws_pre6() { _nft_fw_nfqws_pre6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" } nft_fill_ifsets_overload() { nft_fill_ifsets "$IFACE_LAN" "$IFACE_WAN" "${IFACE_WAN6:-$IFACE_WAN}" } run_daemon() { # $1 - daemon number : 1,2,3,... # $2 - daemon # $3 - daemon args # use $PIDDIR/$DAEMONBASE$1.pid as pidfile local DAEMONBASE="$(basename "$2")" local PID= PIDFILE=$PIDDIR/$DAEMONBASE$1.pid echo "Starting daemon $1: $2 $3" [ -f "$PIDFILE" ] && { read PID <"$PIDFILE" [ -d "/proc/$PID" ] || PID= } if [ -n "$PID" ]; then echo already running else "$2" $3 >/dev/null & PID=$! if [ -n "$PID" ]; then echo $PID >$PIDFILE else echo could not start daemon $1 : $2 $3 false fi fi } stop_daemon() { # $1 - daemon number : 1,2,3,... # $2 - daemon # use $PIDDIR/$DAEMONBASE$1.pid as pidfile local DAEMONBASE="$(basename "$2")" local PID PIDFILE=$PIDDIR/$DAEMONBASE$1.pid echo "Stopping daemon $1: $2" if [ -f "$PIDFILE" ]; then read PID <"$PIDFILE" kill $PID rm -f "$PIDFILE" else echo no pidfile : $PIDFILE fi } do_daemon() { # $1 - 1 - run, 0 - stop on_off_function run_daemon stop_daemon "$@" } do_tpws() { # $1 : 1 - run, 0 - stop # $2 : daemon number # $3 : daemon args [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 local OPT="$TPWS_OPT_BASE" [ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4" [ "$DISABLE_IPV6" = "1" ] || { OPT="$OPT $TPWS_OPT_BASE6" for lan in $IFACE_LAN; do OPT="$OPT --bind-iface6=$lan $TPWS_OPT_BASE6_PRE" done } do_daemon $1 $2 "$TPWS" "$OPT $3" } do_tpws_socks() { # $1 : 1 - run, 0 - stop # $2 : daemon number # $3 : daemon args [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 local opt="$TPWS_OPT_BASE --socks" tpws_apply_socks_binds opt do_daemon $1 $2 "$TPWS" "$opt $3" } do_nfqws() { # $1 : 1 - run, 0 - stop # $2 : daemon number # $3 : daemon args do_daemon $1 $2 "$NFQWS" "$NFQWS_OPT_BASE $3" } tpws_apply_socks_binds() { local o [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" for lan in $IFACE_LAN; do [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" done eval $1="\"\$$1 $o\"" } create_ipset() { echo "Creating ip list table (firewall type $FWTYPE)" "$IPSET_CR" "$@" } ================================================ FILE: init.d/sysv/zapret ================================================ #!/bin/sh ### BEGIN INIT INFO # Provides: zapret # Required-Start: $local_fs $network # Required-Stop: $local_fs $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 ### END INIT INFO SCRIPT=$(readlink -f "$0") EXEDIR=$(dirname "$SCRIPT") ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") . "$EXEDIR/functions" NAME=zapret DESC=anti-zapret do_start() { zapret_run_daemons [ "$INIT_APPLY_FW" != "1" ] || { zapret_apply_firewall; } } do_stop() { zapret_stop_daemons [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall } case "$1" in start) do_start ;; stop) do_stop ;; restart) do_stop do_start ;; start-fw|start_fw) zapret_apply_firewall ;; stop-fw|stop_fw) zapret_unapply_firewall ;; restart-fw|restart_fw) zapret_unapply_firewall zapret_apply_firewall ;; start-daemons|start_daemons) zapret_run_daemons ;; stop-daemons|stop_daemons) zapret_stop_daemons ;; restart-daemons|restart_daemons) zapret_stop_daemons zapret_run_daemons ;; reload-ifsets|reload_ifsets) zapret_reload_ifsets ;; list-ifsets|list_ifsets) zapret_list_ifsets ;; list-table|list_table) zapret_list_table ;; *) echo "Usage: $SCRIPT {start|stop|restart|start-fw|stop-fw|restart-fw|start-daemons|stop-daemons|restart-daemons|reload-ifsets|list-ifsets|list-table}" >&2 exit 1 ;; esac exit 0 ================================================ FILE: init.d/windivert.filter.examples/README.txt ================================================ Цель этих фильтров - отсекать полезную нагрузку в режиме ядра, не насилуя процессор перенаправлением целого потока на winws. Задействуются через `winws --wf-raw-part=@filename`. Может быть несколько частичных фильтров. Они могут сочетаться с --wf-tcp и --wf-udp. Однако, язык фильтров windivert не содержит операций с битовыми полями, сдвигов и побитовой логики. Поэтому фильтры получились более слабыми, способными передавать неправильную нагрузку. Дофильтрация производится силами winws. Описание языка фильтров : https://reqrypt.org/windivert-doc.html#filter_language Пример инстанса для пробития медиапотоков в discord : `winws --wf-raw-part=@windivert_part.discord_media.txt --wf-raw-part=@windivert_part.stun.txt --filter-l7=stun,discord --dpi-desync=fake` These filters are invoked using `winws --wf-raw-part=@filename`. Multiple filter parts are supported. They can be combined with --wf-tcp and --wf-udp. Filters are kernel mode and save great amount of CPU. However windivert cannot filter by bit fields, lacks shift and bitwise logic operations. Filters are relaxed and can pass wrong payloads. Finer filtering is done by winws. ================================================ FILE: init.d/windivert.filter.examples/windivert_part.discord_media.txt ================================================ outbound and ip and udp.DstPort>=50000 and udp.DstPort<=50099 and udp.PayloadLength=74 and udp.Payload32[0]=0x00010046 and udp.Payload32[2]=0 and udp.Payload32[3]=0 and udp.Payload32[4]=0 and udp.Payload32[5]=0 and udp.Payload32[6]=0 and udp.Payload32[7]=0 and udp.Payload32[8]=0 and udp.Payload32[9]=0 and udp.Payload32[10]=0 and udp.Payload32[11]=0 and udp.Payload32[12]=0 and udp.Payload32[13]=0 and udp.Payload32[14]=0 and udp.Payload32[15]=0 and udp.Payload32[16]=0 and udp.Payload32[17]=0 ================================================ FILE: init.d/windivert.filter.examples/windivert_part.quic_initial_ietf.txt ================================================ outbound and udp.PayloadLength>=256 and udp.Payload[0]>=0xC0 and udp.Payload[0]<0xD0 and udp.Payload[1]=0 and udp.Payload16[1]=0 and udp.Payload[4]=1 ================================================ FILE: init.d/windivert.filter.examples/windivert_part.stun.txt ================================================ outbound and udp.PayloadLength>=20 and udp.Payload32[1]=0x2112A442 and udp.Payload[0]<0x40 ================================================ FILE: init.d/windivert.filter.examples/windivert_part.wireguard.txt ================================================ outbound and udp.PayloadLength=148 and udp.Payload[0]=0x01 ================================================ FILE: install_bin.sh ================================================ #!/bin/sh EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" BINS=binaries BINDIR="$EXEDIR/$BINS" ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} . "$ZAPRET_BASE/common/base.sh" read_elf_arch() { # $1 - elf file local arch=$(dd if="$1" count=2 bs=1 skip=18 2>/dev/null | hexdump -e '2/1 "%02x"') local bit=$(dd if="$1" count=1 bs=1 skip=4 2>/dev/null | hexdump -e '1/1 "%02x"') echo $bit$arch } select_test_method() { local f ELF TEST=run # ash and dash try to execute invalid executables as a script. they interpret binary garbage with possible negative consequences # bash and zsh do not do this if exists bash; then TEST=bash elif exists zsh && [ "$UNAME" != CYGWIN ] ; then TEST=zsh elif [ "$UNAME" != Darwin -a "$UNAME" != CYGWIN ]; then if exists hexdump && exists dd; then # macos does not use ELF TEST=elf ELF= ELF_ARCH= for f in /bin/sh /system/bin/sh; do [ -x "$f" ] && { ELF=$f break } done [ -n "$ELF" ] && ELF_ARCH=$(read_elf_arch "$ELF") [ -n "$ELF_ARCH" ] && return fi # find does not use its own shell exec # it uses execvp(). in musl libc it does not call shell, in glibc it DOES call /bin/sh # that's why prefer bash or zsh if present. otherwise it's our last chance if exists find; then TEST=find FIND=find elif exists busybox; then busybox find /jGHUa3fh1A 2>/dev/null # 127 - command not found [ "$?" = 127 ] || { TEST=find FIND="busybox find" } fi fi } disable_antivirus() { # $1 - dir [ "$UNAME" = Darwin ] && find "$1" -maxdepth 1 -type f -perm +111 -exec xattr -d com.apple.quarantine {} \; 2>/dev/null } check_dir() { local dir="$BINDIR/$1" local exe="$dir/ip2net" local out if [ -f "$exe" ]; then if [ -x "$exe" ]; then disable_antivirus "$dir" case $TEST in bash) out=$(echo 0.0.0.0 | bash -c "\"$exe"\" 2>/dev/null) [ -n "$out" ] ;; zsh) out=$(echo 0.0.0.0 | zsh -c "\"$exe\"" 2>/dev/null) [ -n "$out" ] ;; elf) out=$(read_elf_arch "$exe") [ "$ELF_ARCH" = "$out" ] && { # exec test to verify it actually works. no illegal instruction or crash. out=$(echo 0.0.0.0 | "$exe" 2>/dev/null) [ -n "$out" ] } ;; find) out=$(echo 0.0.0.0 | $FIND "$dir" -maxdepth 1 -name ip2net -exec {} \; 2>/dev/null) [ -n "$out" ] ;; run) out=$(echo 0.0.0.0 | "$exe" 2>/dev/null) [ -n "$out" ] ;; *) false ;; esac else echo >&2 "$exe is not executable. set proper chmod." return 1 fi else echo >&2 "$exe is absent" return 2 fi } # link or copy executables. uncomment either ln or cp, comment other ccp() { local F="$(basename "$1")" [ -d "$ZAPRET_BASE/$2" ] || mkdir "$ZAPRET_BASE/$2" [ -f "$ZAPRET_BASE/$2/$F" ] && rm -f "$ZAPRET_BASE/$2/$F" ln -fs "../$BINS/$1" "$ZAPRET_BASE/$2" && echo linking : "../$BINS/$1" =\> "$ZAPRET_BASE/$2" #cp -f "../$BINS/$1" "$ZAPRET_BASE/$2" && echo copying : "../$BINS/$1" =\> "$ZAPRET_BASE/$2" } UNAME=$(uname) [ "$1" = getarch ] || if [ ! -d "$BINDIR" ] || ! dir_is_not_empty "$BINDIR" ]; then echo "no binaries found" case $UNAME in Linux) echo "you need to download release from github or build binaries from source" echo "building from source requires debian/ubuntu packages : make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev" echo "libsystemd-dev required only on systemd based systems" echo "on distributions with other package manager find dev package analogs" echo "to compile on systems with systemd : make systemd" echo "to compile on other systems : make" ;; Darwin) echo "you need to download release from github or build binaries from source" echo "to compile : make mac" ;; FreeBSD) echo "you need to download release from github or build binaries from source" echo "to compile : make" ;; OpenBSD) echo "to compile : make bsd" ;; CYGWIN*) echo "you need to download release from github or build binaries from source" echo "to compile : read docs" echo "to make things easier use zapret-win-bundle" ;; esac exit 1 fi unset PKTWS case $UNAME in Linux) ARCHLIST="my linux-x86_64 linux-x86 linux-arm64 linux-arm linux-mips64 linux-mipsel linux-mips linux-lexra linux-ppc" PKTWS=nfqws ;; Darwin) ARCHLIST="my mac64" ;; FreeBSD) ARCHLIST="my freebsd-x86_64" PKTWS=dvtws ;; CYGWIN*) UNAME=CYGWIN ARCHLIST="windows-x86_64 windows-x86" PKTWS=winws ;; *) ARCHLIST="my" esac select_test_method if [ "$1" = "getarch" ]; then for arch in $ARCHLIST do [ -d "$BINDIR/$arch" ] || continue if check_dir $arch; then echo $arch exit 0 fi done else echo "using arch detect method : $TEST${ELF_ARCH:+ $ELF_ARCH}" for arch in $ARCHLIST do [ -d "$BINDIR/$arch" ] || continue if check_dir $arch; then echo $arch is OK echo installing binaries ... ccp $arch/ip2net ip2net ccp $arch/mdig mdig [ -n "$PKTWS" ] && ccp $arch/$PKTWS nfq [ "$UNAME" = CYGWIN ] || ccp $arch/tpws tpws exit 0 else echo $arch is NOT OK fi done echo no compatible binaries found fi exit 1 ================================================ FILE: install_easy.sh ================================================ #!/bin/sh # automated script for easy installing zapret EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} ZAPRET_TARGET=${ZAPRET_TARGET:-/opt/zapret} ZAPRET_TARGET_RW=${ZAPRET_RW:-"$ZAPRET_TARGET"} ZAPRET_TARGET_CONFIG="$ZAPRET_TARGET_RW/config" ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" IPSET_DIR="$ZAPRET_BASE/ipset" [ -f "$ZAPRET_CONFIG" ] || { ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" } . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/elevate.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/dialog.sh" . "$ZAPRET_BASE/common/ipt.sh" . "$ZAPRET_BASE/common/installer.sh" . "$ZAPRET_BASE/common/virt.sh" . "$ZAPRET_BASE/common/list.sh" GET_LIST="$IPSET_DIR/get_config.sh" check_readonly_system() { local RO echo \* checking readonly system case $SYSTEM in systemd) [ -w "$SYSTEMD_SYSTEM_DIR" ] || RO=1 ;; openrc) [ -w "$(dirname "$INIT_SCRIPT")" ] || RO=1 ;; esac [ -z "$RO" ] || { echo '!!! READONLY SYSTEM DETECTED !!!' echo '!!! WILL NOT BE ABLE TO CONFIGURE STARTUP !!!' echo '!!! MANUAL STARTUP CONFIGURATION IS REQUIRED !!!' ask_yes_no N "do you want to continue" || exitp 5 } } check_source() { local bad=0 echo \* checking source files case $SYSTEM in systemd) [ -f "$EXEDIR/init.d/systemd/zapret.service" ] || bad=1 ;; openrc) [ -f "$EXEDIR/init.d/openrc/zapret" ] || bad=1 ;; macos) [ -f "$EXEDIR/init.d/macos/zapret" ] || bad=1 ;; esac [ "$bad" = 1 ] && { echo 'some critical files are missing' echo 'are you sure you are not using embedded release ? you need full version for traditional systems' exitp 5 } } check_bins() { echo \* checking executables fix_perms_bin_test "$EXEDIR" local arch="$(get_bin_arch)" local make_target local cf="-march=native" [ "$FORCE_BUILD" = "1" ] && { echo forced build mode if [ "$arch" = "my" ]; then echo already compiled else arch="" fi } if [ -n "$arch" ] ; then echo found architecture "\"$arch\"" elif [ -f "$EXEDIR/Makefile" ] && exists make; then echo trying to compile case $SYSTEM in macos) make_target=mac cf= ;; systemd) make_target=systemd ;; esac CFLAGS="${cf:+$cf }${CFLAGS}" OPTIMIZE=-O2 make -C "$EXEDIR" $make_target || { echo could not compile make -C "$EXEDIR" clean exitp 8 } echo compiled else echo build tools not found exitp 8 fi } call_install_bin() { sh "$EXEDIR/install_bin.sh" $1 } get_bin_arch() { call_install_bin getarch } install_binaries() { echo \* installing binaries call_install_bin || { echo compatible binaries not found exitp 8 } } ws_opt_validate() { # ПРИМЕЧАНИЕ ДЛЯ РАСПРОСТРАНИТЕЛЕЙ КОПИПАСТЫ # ЭТОТ КОД СДЕЛАН СПЕЦИАЛЬНО ДЛЯ ВАС, ЧТОБЫ ВЫ НЕ ПОСТИЛИ В СЕТЬ ПЛОХИЕ РЕЦЕПТЫ # ЕСЛИ ВАМ ХОЧЕТСЯ ЕГО УДАЛИТЬ И НАПИСАТЬ ИНСТРУКЦИЮ КАК ЕГО УДАЛЯТЬ, ВЫ ДЕЛАЕТЕ ХРЕНОВУЮ УСЛУГУ. НАПИШИТЕ ЛУЧШЕ custom script. # custom script - ЭТО ФАЙЛИК, КОТОРЫЙ ДОСТАТОЧНО СКОПИРОВАТЬ В НУЖНУЮ ДИРЕКТОРИЮ, ЧТОБЫ ОН СДЕЛАЛ ТОЖЕ САМОЕ, НО ЭФФЕКТИВНО. # ФИЛЬТРАЦИЯ ПО IPSET В ЯДРЕ НЕСРАВНИМО ЭФФЕКТИВНЕЕ, ЧЕМ ПЕРЕКИДЫВАТЬ ВСЕ ПАКЕТЫ В nfqws И ТАМ ФИЛЬТРОВАТЬ # --ipset СУЩЕСТВУЕТ ТОЛЬКО ДЛЯ ВИНДЫ И LINUX СИСТЕМ БЕЗ ipset (НАПРИМЕР, Android). # И ТОЛЬКО ПО ЭТОЙ ПРИЧИНЕ ОНО НЕ ВЫКИНУТО ПОЛНОСТЬЮ ИЗ LINUX ВЕРСИИ has_bad_ws_options "$1" && { help_bad_ws_options return 1 } return 0 } tpws_opt_validate() { ws_opt_validate "$1" || return 1 dry_run_tpws || { echo invalid tpws options return 1 } } tpws_socks_opt_validate() { # --ipset allowed here dry_run_tpws_socks || { echo invalid tpws options return 1 } } nfqws_opt_validate() { ws_opt_validate "$1" || return 1 dry_run_nfqws || { echo invalid nfqws options return 1 } } select_mode_group() { # $1 - ENABLE var name # $2 - ask text # $3 - vars # $4 - validator func # $5 - validator func param var local enabled var v edited bad Y param echo ask_yes_no_var $1 "$2" write_config_var $1 eval enabled=\$$1 [ "$enabled" = 1 ] && { echo while : ; do list_vars $3 bad=0; Y=N [ -n "$4" ] && { eval param="\$$5" $4 "$param"; bad=$? [ "$bad" = 1 ] && Y=Y } ask_yes_no $Y "do you want to edit the options" || { [ "$bad" = 1 ] && { echo installer will not allow to use bad options. exiting. exitp 3 } [ -n "$edited" ] && { for var in $3; do write_config_var $var done } break } edit_vars $3 edited=1 echo ..edited.. done } } select_mode_tpws_socks() { local EDITVAR_NEWLINE_DELIMETER="--new" EDITVAR_NEWLINE_VARS="TPWS_SOCKS_OPT" select_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 } select_mode_tpws() { local EDITVAR_NEWLINE_DELIMETER="--new" EDITVAR_NEWLINE_VARS="TPWS_OPT" select_mode_group TPWS_ENABLE "enable tpws transparent mode ?" "TPWS_PORTS TPWS_OPT" tpws_opt_validate TPWS_OPT } select_mode_nfqws() { local EDITVAR_NEWLINE_DELIMETER="--new" EDITVAR_NEWLINE_VARS="NFQWS_OPT" select_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 } select_mode_mode() { select_mode_tpws_socks select_mode_tpws [ "$UNAME" = Linux ] && select_mode_nfqws echo echo "current custom scripts in $CUSTOM_DIR/custom.d:" [ -d "$CUSTOM_DIR/custom.d" ] && ls "$CUSTOM_DIR/custom.d" echo "Make sure this is ok" echo } select_mode_filter() { local filter="none ipset hostlist autohostlist" echo echo select filtering : ask_list MODE_FILTER "$filter" none && write_config_var MODE_FILTER } select_mode() { select_mode_filter select_mode_mode select_mode_iface } select_getlist() { if [ "$MODE_FILTER" = "ipset" -o "$MODE_FILTER" = "hostlist" -o "$MODE_FILTER" = "autohostlist" ]; then local D=N [ -n "$GETLIST" ] && D=Y echo if ask_yes_no $D "do you want to auto download ip/host list"; then if [ "$MODE_FILTER" = "hostlist" -o "$MODE_FILTER" = "autohostlist" ] ; then GETLISTS="get_refilter_domains.sh get_antizapret_domains.sh get_reestr_resolvable_domains.sh" GETLIST_DEF="get_antizapret_domains.sh" else GETLISTS="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" GETLIST_DEF="get_antifilter_allyouneed.sh" fi ask_list GETLIST "$GETLISTS" "$GETLIST_DEF" && write_config_var GETLIST return fi fi GETLIST="" write_config_var GETLIST } ask_config() { select_mode select_getlist } ask_config_offload() { [ "$FWTYPE" = nftables ] || is_ipt_flow_offload_avail && { echo echo flow offloading can greatly increase speed on slow devices and high speed links \(usually 150+ mbits\) if [ "$SYSTEM" = openwrt ]; then echo unfortuantely its not compatible with most nfqws options. nfqws traffic must be exempted from flow offloading. echo donttouch = disable system flow offloading setting if nfqws mode was selected, dont touch it otherwise and dont configure selective flow offloading echo none = always disable system flow offloading setting and dont configure selective flow offloading echo software = always disable system flow offloading setting and configure selective software flow offloading echo hardware = always disable system flow offloading setting and configure selective hardware flow offloading else echo offloading is applicable only to forwarded traffic. it has no effect on outgoing traffic echo hardware flow offloading is available only on specific supporting hardware. most likely will not work on a generic system fi echo offloading breaks traffic shaper echo select flow offloading : local options="none software hardware" local default="none" [ "$SYSTEM" = openwrt ] && { options="donttouch none software hardware" default="donttouch" } ask_list FLOWOFFLOAD "$options" $default && write_config_var FLOWOFFLOAD } } ask_config_tmpdir() { # ask tmpdir change for low ram systems with enough free disk space [ -n "$GETLIST" ] && [ $(get_free_space_mb "$EXEDIR/tmp") -ge 128 ] && [ $(get_ram_mb) -le 400 ] && { echo echo /tmp in openwrt is tmpfs. on low RAM systems there may be not enough RAM to store downloaded files echo default tmpfs has size of 50% RAM echo "RAM : $(get_ram_mb) Mb" echo "DISK : $(get_free_space_mb "$EXEDIR/tmp") Mb" echo select temp file location [ -z "$TMPDIR" ] && TMPDIR=/tmp ask_list TMPDIR "/tmp $EXEDIR/tmp" && { [ "$TMPDIR" = "/tmp" ] && TMPDIR= write_config_var TMPDIR } } } nft_flow_offload() { [ "$UNAME" = Linux -a "$FWTYPE" = nftables ] && [ "$FLOWOFFLOAD" = software -o "$FLOWOFFLOAD" = hardware ] } ask_iface() { # $1 - var to ask # $2 - additional name for empty string synonim local ifs i0 def new eval def="\$$1" [ -n "$2" ] && i0="$2 " case $SYSTEM in macos) ifs="$(ifconfig -l)" ;; *) ifs="$(ls /sys/class/net)" ;; esac [ -z "$def" ] && eval $1="$2" ask_list $1 "$i0$ifs" && { eval new="\$$1" [ "$new" = "$2" ] && eval $1="" write_config_var $1 } } ask_iface_lan() { echo LAN interface : local opt nft_flow_offload || opt=NONE ask_iface IFACE_LAN $opt } ask_iface_wan() { echo WAN interface : local opt nft_flow_offload || opt=ANY ask_iface IFACE_WAN $opt } select_mode_iface() { # openwrt has its own interface management scheme # filter just creates ip tables, no daemons involved # nfqws sits in POSTROUTING chain and unable to filter by incoming interface # tpws redirection works in PREROUTING chain # in tpws-socks mode IFACE_LAN specifies additional bind interface for the socks listener # it's not possible to instruct tpws to route outgoing connection to an interface (OS routing table decides) # custom mode can also benefit from interface names (depends on custom script code) [ "$SYSTEM" = "openwrt" ] && return ask_iface_lan ask_iface_wan } default_files() { # $1 - ro location # $2 - rw location (can be equal to $1) [ -d "$2/ipset" ] || mkdir -p "$2/ipset" [ -f "$2/ipset/zapret-hosts-user-exclude.txt" ] || cp "$1/ipset/zapret-hosts-user-exclude.txt.default" "$2/ipset/zapret-hosts-user-exclude.txt" [ -f "$2/ipset/zapret-hosts-user.txt" ] || echo nonexistent.domain >> "$2/ipset/zapret-hosts-user.txt" [ -f "$2/ipset/zapret-hosts-user-ipban.txt" ] || touch "$2/ipset/zapret-hosts-user-ipban.txt" for dir in openwrt sysv macos; do [ -d "$1/init.d/$dir" ] && { [ -d "$2/init.d/$dir" ] || mkdir -p "$2/init.d/$dir" [ -d "$2/init.d/$dir/custom.d" ] || mkdir -p "$2/init.d/$dir/custom.d" } done } copy_all() { local dir cp -R "$1" "$2" [ -d "$2/tmp" ] || mkdir "$2/tmp" } copy_openwrt() { local ARCH="$(get_bin_arch)" local BINDIR="$1/binaries/$ARCH" local file [ -d "$2" ] || mkdir -p "$2" mkdir "$2/tpws" "$2/nfq" "$2/ip2net" "$2/mdig" "$2/binaries" "$2/binaries/$ARCH" "$2/init.d" "$2/tmp" "$2/files" cp -R "$1/files/fake" "$2/files" cp -R "$1/common" "$1/ipset" "$2" cp -R "$1/init.d/openwrt" "$1/init.d/custom.d.examples.linux" "$2/init.d" cp "$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" cp "$BINDIR/tpws" "$BINDIR/nfqws" "$BINDIR/ip2net" "$BINDIR/mdig" "$2/binaries/$ARCH" } fix_perms_bin_test() { [ -d "$1" ] || return find "$1/binaries" -name ip2net ! -perm -111 -exec chmod +x {} \; } fix_perms() { [ -d "$1" ] || return find "$1" -type d -exec chmod 755 {} \; find "$1" -type f -exec chmod 644 {} \; local chow case "$UNAME" in Linux) chow=root:root ;; *) chow=root:wheel esac chown -R $chow "$1" find "$1/binaries" '(' -name tpws -o -name dvtws -o -name nfqws -o -name ip2net -o -name mdig ')' -exec chmod 755 {} \; for f in \ install_bin.sh \ blockcheck.sh \ install_easy.sh \ install_prereq.sh \ files/huawei/E8372/zapret-ip \ files/huawei/E8372/unzapret-ip \ files/huawei/E8372/run-zapret-hostlist \ files/huawei/E8372/unzapret \ files/huawei/E8372/zapret \ files/huawei/E8372/run-zapret-ip \ ipset/get_exclude.sh \ ipset/clear_lists.sh \ ipset/get_refilter_domains.sh \ ipset/get_refilter_ipsum.sh \ ipset/get_antifilter_ipresolve.sh \ ipset/get_reestr_resolvable_domains.sh \ ipset/get_config.sh \ ipset/get_reestr_preresolved.sh \ ipset/get_user.sh \ ipset/get_antifilter_allyouneed.sh \ ipset/get_reestr_resolve.sh \ ipset/create_ipset.sh \ ipset/get_reestr_hostlist.sh \ ipset/get_ipban.sh \ ipset/get_antifilter_ipsum.sh \ ipset/get_antifilter_ipsmart.sh \ ipset/get_antizapret_domains.sh \ ipset/get_reestr_preresolved_smart.sh \ ipset/get_antifilter_ip.sh \ init.d/pfsense/zapret.sh \ init.d/macos/zapret \ init.d/runit/zapret/run \ init.d/runit/zapret/finish \ init.d/openrc/zapret \ init.d/sysv/zapret \ init.d/openwrt/zapret \ init.d/openwrt-minimal/tpws/etc/init.d/tpws \ uninstall_easy.sh \ ; do chmod 755 "$1/$f" 2>/dev/null ; done } _backup_settings() { local i=0 for f in "$@"; do # safety check [ -z "$f" -o "$f" = "/" ] && continue [ -f "$ZAPRET_TARGET/$f" ] && cp -f "$ZAPRET_TARGET/$f" "/tmp/zapret-bkp-$i" [ -d "$ZAPRET_TARGET/$f" ] && cp -rf "$ZAPRET_TARGET/$f" "/tmp/zapret-bkp-$i" i=$(($i+1)) done } _restore_settings() { local i=0 for f in "$@"; do # safety check [ -z "$f" -o "$f" = "/" ] && continue [ -f "/tmp/zapret-bkp-$i" ] && { mv -f "/tmp/zapret-bkp-$i" "$ZAPRET_TARGET/$f" || rm -f "/tmp/zapret-bkp-$i" } [ -d "/tmp/zapret-bkp-$i" ] && { [ -d "$ZAPRET_TARGET/$f" ] && rm -r "$ZAPRET_TARGET/$f" mv -f "/tmp/zapret-bkp-$i" "$ZAPRET_TARGET/$f" || rm -r "/tmp/zapret-bkp-$i" } i=$(($i+1)) done } backup_restore_settings() { # $1 - 1 - backup, 0 - restore local mode=$1 on_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" } config_is_obsolete() { [ -f "$1" ] && grep -qE "^[[:space:]]*NFQWS_OPT_DESYNC=|^[[:space:]]*MODE_HTTP=|^[[:space:]]*MODE_HTTPS=|^[[:space:]]*MODE_QUIC=|^[[:space:]]*MODE=" "$1" } check_location() { # $1 - copy function echo \* checking location # use inodes in case something is linked if [ -d "$ZAPRET_TARGET" ] && [ $(get_dir_inode "$EXEDIR") = $(get_dir_inode "$ZAPRET_TARGET") ]; then config_is_obsolete "$ZAPRET_CONFIG" && { echo config file $ZAPRET_CONFIG is obsolete. cannot continue. exitp 3 } default_files "$ZAPRET_TARGET" "$ZAPRET_RW" else local obsolete=0 rwdir=0 config_is_obsolete "$ZAPRET_TARGET_CONFIG" && obsolete=1 [ $(get_dir_inode "$ZAPRET_BASE") = $(get_dir_inode "$ZAPRET_RW") ] || rwdir=1 [ $rwdir = 1 -a $obsolete = 1 ] && { echo config file in custom ZAPRET_RW directory is obsolete : $ZAPRET_TARGET_CONFIG echo you need to edit or delete it to continue. also check for obsolete custom scripts. exitp 3 } echo echo easy install is supported only from default location : $ZAPRET_TARGET echo currently its run from $EXEDIR if ask_yes_no N "do you want the installer to copy it for you"; then local keep=N if [ -d "$ZAPRET_TARGET" ]; then echo echo installer found existing $ZAPRET_TARGET echo directory needs to be replaced. config and custom scripts can be kept or replaced with clean version if ask_yes_no N "do you want to delete all files there and copy this version"; then echo if [ $obsolete = 1 ] ; then echo obsolete config is detected : $ZAPRET_TARGET_RW ask_yes_no N "impossible to keep config, custom scripts and user lists. do you want to delete them ?" || { echo refused to delete config in $ZAPRET_TARGET. exiting exitp 3 } elif [ $rwdir != 1 ]; then ask_yes_no Y "keep config, custom scripts and user lists" && keep=Y [ "$keep" = "Y" ] && backup_restore_settings 1 fi rm -r "$ZAPRET_TARGET" else echo refused to overwrite $ZAPRET_TARGET. exiting exitp 3 fi fi local B="$(dirname "$ZAPRET_TARGET")" [ -d "$B" ] || mkdir -p "$B" $1 "$EXEDIR" "$ZAPRET_TARGET" fix_perms "$ZAPRET_TARGET" [ "$keep" = "Y" ] && backup_restore_settings 0 echo relaunching itself from $ZAPRET_TARGET exec "$ZAPRET_TARGET/$(basename "$0")" else echo copying aborted. exiting exitp 3 fi fi echo running from $EXEDIR } service_install_systemd() { echo \* installing zapret service if [ -w "$SYSTEMD_SYSTEM_DIR" ] ; then rm -f "$INIT_SCRIPT" cp -f "$EXEDIR/init.d/systemd/zapret.service" "$SYSTEMD_SYSTEM_DIR" "$SYSTEMCTL" daemon-reload "$SYSTEMCTL" enable zapret || { echo could not enable systemd service exitp 20 } else echo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!' fi } timer_install_systemd() { echo \* installing zapret-list-update timer if [ -w "$SYSTEMD_SYSTEM_DIR" ] ; then "$SYSTEMCTL" disable zapret-list-update.timer "$SYSTEMCTL" stop zapret-list-update.timer cp -f "$EXEDIR/init.d/systemd/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR" cp -f "$EXEDIR/init.d/systemd/zapret-list-update.timer" "$SYSTEMD_SYSTEM_DIR" "$SYSTEMCTL" daemon-reload "$SYSTEMCTL" enable zapret-list-update.timer || { echo could not enable zapret-list-update.timer exitp 20 } "$SYSTEMCTL" start zapret-list-update.timer || { echo could not start zapret-list-update.timer exitp 30 } else echo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!' fi } download_list() { [ -x "$GET_LIST" ] && { echo \* downloading blocked ip/host list # can be txt or txt.gz "$IPSET_DIR/clear_lists.sh" "$GET_LIST" } } dnstest() { # $1 - dns server. empty for system resolver nslookup w3.org $1 >/dev/null 2>/dev/null } check_dns() { echo \* checking DNS dnstest || { echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. return 1 } echo system DNS is working return 0 } install_systemd() { INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" check_bins require_root check_readonly_system check_location copy_all check_dns check_virt service_stop_systemd select_fwtype check_prerequisites_linux install_binaries select_ipv6 ask_config_offload ask_config service_install_systemd download_list # in case its left from old version of zapret crontab_del_quiet # now we use systemd timers timer_install_systemd service_start_systemd } _install_sysv() { # $1 - install init script CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" check_bins require_root check_readonly_system check_location copy_all check_dns check_virt service_stop_sysv select_fwtype check_prerequisites_linux install_binaries select_ipv6 ask_config_offload ask_config $1 download_list crontab_del_quiet # desktop system. more likely up at daytime crontab_add 10 22 service_start_sysv } install_sysv() { INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" _install_sysv install_sysv_init } install_openrc() { INIT_SCRIPT_SRC="$EXEDIR/init.d/openrc/zapret" _install_sysv install_openrc_init } install_linux() { INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" check_bins require_root check_location copy_all check_dns check_virt select_fwtype check_prerequisites_linux install_binaries select_ipv6 ask_config_offload ask_config download_list crontab_del_quiet # desktop system. more likely up at daytime crontab_add 10 22 echo echo '!!! WARNING. YOUR SETUP IS INCOMPLETE !!!' echo you must manually add to auto start : $INIT_SCRIPT_SRC start echo make sure it\'s executed after your custom/firewall iptables configuration echo "if your system uses sysv init : ln -fs $INIT_SCRIPT_SRC /etc/init.d/zapret ; chkconfig zapret on" } deoffload_openwrt_firewall() { echo \* checking flow offloading [ "$FWTYPE" = "nftables" ] || is_ipt_flow_offload_avail || { echo unavailable return } local fo=$(uci -q get firewall.@defaults[0].flow_offloading) if [ "$fo" = "1" ] ; then local mod=0 printf "system wide flow offloading detected. " case $FLOWOFFLOAD in donttouch) if [ "$NFQWS_ENABLE" = "1" ]; then echo its incompatible with nfqws tcp data tampering. disabling uci set firewall.@defaults[0].flow_offloading=0 mod=1 else if dir_is_not_empty "$CUSTOM_DIR/custom.d" ; then echo echo !!! CUSTOM SCRIPTS ARE PRESENT !!! only you can decide whether flow offloading is compatible. echo !!! CUSTOM SCRIPTS ARE PRESENT !!! if they use nfqws they will not work. you have to disable system-wide offloading. else echo its compatible with selected options. not disabling fi fi ;; *) echo zapret will disable system wide offloading setting and add selective rules if required uci set firewall.@defaults[0].flow_offloading=0 mod=1 esac [ "$mod" = "1" ] && uci commit firewall else echo system wide software flow offloading disabled. ok fi } install_openwrt() { INIT_SCRIPT_SRC="$EXEDIR/init.d/openwrt/zapret" CUSTOM_DIR="$ZAPRET_RW/init.d/openwrt" FW_SCRIPT_SRC="$EXEDIR/init.d/openwrt/firewall.zapret" OPENWRT_FW_INCLUDE=/etc/firewall.zapret OPENWRT_IFACE_HOOK="$EXEDIR/init.d/openwrt/90-zapret" check_bins require_root check_location copy_openwrt install_binaries check_dns check_virt local FWTYPE_OLD=$FWTYPE echo \* stopping current firewall rules/daemons "$INIT_SCRIPT_SRC" stop_fw "$INIT_SCRIPT_SRC" stop_daemons select_fwtype select_ipv6 check_prerequisites_openwrt ask_config ask_config_tmpdir ask_config_offload # stop and reinstall sysv init install_sysv_init [ "$FWTYPE_OLD" != "$FWTYPE" -a "$FWTYPE_OLD" = iptables -a -n "$OPENWRT_FW3" ] && remove_openwrt_firewall # free some RAM clear_ipset download_list crontab_del_quiet # router system : works 24/7. night is the best time crontab_add 0 6 cron_ensure_running install_openwrt_iface_hook # in case of nftables or iptables without fw3 sysv init script also controls firewall [ -n "$OPENWRT_FW3" -a "$FWTYPE" = iptables ] && install_openwrt_firewall service_start_sysv deoffload_openwrt_firewall restart_openwrt_firewall } remove_pf_zapret_hooks() { echo \* removing zapret PF hooks pf_anchors_clear } macos_fw_reload_trigger_clear() { LISTS_RELOAD= write_config_var LISTS_RELOAD } macos_fw_reload_trigger_set() { LISTS_RELOAD="$INIT_SCRIPT_SRC reload-fw-tables" write_config_var LISTS_RELOAD } install_macos() { INIT_SCRIPT_SRC="$EXEDIR/init.d/macos/zapret" CUSTOM_DIR="$ZAPRET_RW/init.d/macos" # compile before root check_bins require_root check_location copy_all service_stop_macos remove_pf_zapret_hooks install_binaries check_dns select_ipv6 ask_config service_install_macos macos_fw_reload_trigger_clear # gzip lists are incompatible with PF GZIP_LISTS=0 write_config_var GZIP_LISTS download_list macos_fw_reload_trigger_set crontab_del_quiet # desktop system. more likely up at daytime crontab_add 10 22 service_start_macos } # build binaries, do not use precompiled [ "$1" = "make" ] && FORCE_BUILD=1 umask 0022 fix_sbin_path fsleep_setup check_system check_source case $SYSTEM in systemd) install_systemd ;; openrc) install_openrc ;; linux) install_linux ;; openwrt) install_openwrt ;; macos) install_macos ;; esac exitp 0 ================================================ FILE: install_prereq.sh ================================================ #!/bin/sh # install prerequisites EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" [ -f "$ZAPRET_CONFIG" ] || { ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" } . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/elevate.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/dialog.sh" . "$ZAPRET_BASE/common/installer.sh" . "$ZAPRET_BASE/common/ipt.sh" umask 0022 fix_sbin_path fsleep_setup check_system accept_unknown_rc [ $UNAME = "Linux" ] || { echo no prerequisites required for $UNAME exitp 0 } require_root case $UNAME in Linux) select_fwtype case $SYSTEM in openwrt) select_ipv6 check_prerequisites_openwrt ;; *) check_prerequisites_linux ;; esac ;; esac exitp 0 ================================================ FILE: ip2net/Makefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto CFLAGS_BSD = -Wno-address-of-packed-member CFLAGS_WIN = -static LIBS = LIBS_WIN = -lws2_32 SRC_FILES = ip2net.c qsort.c all: ip2net ip2net: $(SRC_FILES) $(CC) -s $(CFLAGS) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS) systemd: ip2net android: ip2net bsd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o ip2net $(SRC_FILES) $(LIBS) $(LDFLAGS) mac: $(SRC_FILES) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2neta $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS) $(LDFLAGS) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2netx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS) $(LDFLAGS) strip ip2neta ip2netx lipo -create -output ip2net ip2netx ip2neta rm -f ip2netx ip2neta win: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o ip2net $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS) clean: rm -f ip2net *.o ================================================ FILE: ip2net/ip2net.c ================================================ // group ipv4/ipv6 list from stdout into subnets // each line must contain either ip or ip/bitcount // valid ip/bitcount and ip1-ip2 are passed through without modification // ips are groupped into subnets // can be compiled in mingw. msvc not supported because of absent getopt #include #include #include #include #include #ifdef _WIN32 #undef _WIN32_WINNT #define _WIN32_WINNT 0x600 #include #include #include #else #include #include #include #endif #include #include "qsort.h" #define ALLOC_STEP 16384 // minimum subnet fill percent is PCTMULT/PCTDIV (for example 3/4) #define DEFAULT_PCTMULT 3 #define DEFAULT_PCTDIV 4 // subnet search range in "zero bit count" // means search start from /(32-ZCT_MAX) to /(32-ZCT_MIN) #define DEFAULT_V4_ZCT_MAX 10 // /22 #define DEFAULT_V4_ZCT_MIN 2 // /30 #define DEFAULT_V6_ZCT_MAX 72 // /56 #define DEFAULT_V6_ZCT_MIN 64 // /64 // must be no less than N ipv6 in subnet #define DEFAULT_V6_THRESHOLD 5 static int ucmp(const void * a, const void * b, void *arg) { if (*(uint32_t*)a < *(uint32_t*)b) return -1; else if (*(uint32_t*)a > *(uint32_t*)b) return 1; else return 0; } static uint32_t mask_from_bitcount(uint32_t zct) { return zct<32 ? ~((1u << zct) - 1) : 0; } // make presorted array unique. return number of unique items. // 1,1,2,3,3,0,0,0 (ct=8) => 1,2,3,0 (ct=4) static uint32_t unique(uint32_t *pu, uint32_t ct) { uint32_t i, j, u; for (i = j = 0; j < ct; i++) { u = pu[j++]; for (; j < ct && pu[j] == u; j++); pu[i] = u; } return i; } #if defined(__GNUC__) && !defined(__llvm__) __attribute__((optimize ("no-strict-aliasing"))) #endif static int cmp6(const void * a, const void * b, void *arg) { // this function is critical for sort performance // on big endian systems cpu byte order is equal to network byte order // no conversion required. it's possible to improve speed by using big size compares // on little endian systems byte conversion also gives better result than byte comparision // 64-bit archs often have cpu command to reverse byte order // assume that a and b are properly aligned #if defined(__BYTE_ORDER__) && ((__BYTE_ORDER__==__ORDER_BIG_ENDIAN__) || (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)) uint64_t aa,bb; #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ aa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[0]); bb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[0]); #else aa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[0]; bb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[0]; #endif if (aa < bb) return -1; else if (aa > bb) return 1; else { #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ aa = __builtin_bswap64(((uint64_t*)((struct in6_addr *)a)->s6_addr)[1]); bb = __builtin_bswap64(((uint64_t*)((struct in6_addr *)b)->s6_addr)[1]); #else aa = ((uint64_t*)((struct in6_addr *)a)->s6_addr)[1]; bb = ((uint64_t*)((struct in6_addr *)b)->s6_addr)[1]; #endif return aa < bb ? -1 : aa > bb ? 1 : 0; } #else // fallback case for (uint8_t i = 0; i < sizeof(((struct in6_addr *)0)->s6_addr); i++) { if (((struct in6_addr *)a)->s6_addr[i] < ((struct in6_addr *)b)->s6_addr[i]) return -1; else if (((struct in6_addr *)a)->s6_addr[i] > ((struct in6_addr *)b)->s6_addr[i]) return 1; } return 0; #endif } // make presorted array unique. return number of unique items. static uint32_t unique6(struct in6_addr *pu, uint32_t ct) { uint32_t i, j, k; for (i = j = 0; j < ct; i++) { for (k = j++; j < ct && !memcmp(pu + j, pu + k, sizeof(struct in6_addr)); j++); pu[i] = pu[k]; } return i; } static void mask_from_bitcount6_make(uint32_t zct, struct in6_addr *a) { if (zct >= 128) memset(a->s6_addr,0x00,16); else { int32_t n = (127 - zct) >> 3; memset(a->s6_addr,0xFF,n); memset(a->s6_addr+n,0x00,16-n); a->s6_addr[n] = ~((1u << (zct & 7)) - 1); } } static struct in6_addr ip6_mask[129]; static void mask_from_bitcount6_prepare(void) { for (int zct=0;zct<=128;zct++) mask_from_bitcount6_make(zct, ip6_mask+zct); } static inline const struct in6_addr *mask_from_bitcount6(uint32_t zct) { return ip6_mask+zct; } /* // this is "correct" solution for strict aliasing feature // but I don't like this style of coding // write what I don't mean to force smart optimizer to do what it's best // it produces better code sometimes but not on all compilers/versions/archs // sometimes it even generates real memcpy calls (mips32,arm32) // so I will not do it static void ip6_and(const struct in6_addr *a, const struct in6_addr *b, struct in6_addr *result) { uint64_t a_addr[2], b_addr[2]; memcpy(a_addr, a->s6_addr, 16); memcpy(b_addr, b->s6_addr, 16); a_addr[0] &= b_addr[0]; a_addr[1] &= b_addr[1]; memcpy(result->s6_addr, a_addr, 16); } */ // YES, from my point of view C should work as a portable assembler. It must do what I instruct it to do. // 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" // result = a & b // assume that a and b are properly aligned #if defined(__GNUC__) && !defined(__llvm__) __attribute__((optimize ("no-strict-aliasing"))) #endif static void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result) { #ifdef __SIZEOF_INT128__ // gcc and clang have 128 bit int types on some 64-bit archs. take some advantage *((unsigned __int128*)result->s6_addr) = *((unsigned __int128*)a->s6_addr) & *((unsigned __int128*)b->s6_addr); #else ((uint64_t*)result->s6_addr)[0] = ((uint64_t*)a->s6_addr)[0] & ((uint64_t*)b->s6_addr)[0]; ((uint64_t*)result->s6_addr)[1] = ((uint64_t*)a->s6_addr)[1] & ((uint64_t*)b->s6_addr)[1]; #endif } static void rtrim(char *s) { if (s) for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'); p--) *p = '\0'; } static struct params_s { bool ipv6; uint32_t pctmult, pctdiv; // for v4 uint32_t zct_min, zct_max; // for v4 and v6 uint32_t v6_threshold; // for v6 } params; static void exithelp(void) { printf( " -4\t\t\t\t; ipv4 list (default)\n" " -6\t\t\t\t; ipv6 list\n" " --prefix-length=min[-max]\t; consider prefix lengths from 'min' to 'max'. examples : 22-30 (ipv4), 56-64 (ipv6)\n" " --v4-threshold=mul/div\t\t; ipv4 only : include subnets with more than mul/div ips. example : 3/4\n" " --v6-threshold=N\t\t; ipv6 only : include subnets with more than N v6 ips. example : 5\n" ); exit(1); } #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) #define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) #else #define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) #endif enum opt_indices { IDX_HELP, IDX_H, IDX_4, IDX_6, IDX_PREFIX_LENGTH, IDX_V4_THRESHOLD, IDX_V6_THRESHOLD, IDX_LAST, }; static const struct option long_options[] = { [IDX_HELP] = {"help", no_argument, 0, 0}, [IDX_H] = {"h", no_argument, 0, 0}, [IDX_4] = {"4", no_argument, 0, 0}, [IDX_6] = {"6", no_argument, 0, 0}, [IDX_PREFIX_LENGTH] = {"prefix-length", required_argument, 0, 0}, [IDX_V4_THRESHOLD] = {"v4-threshold", required_argument, 0, 0}, [IDX_V6_THRESHOLD] = {"v6-threshold", required_argument, 0, 0}, [IDX_LAST] = {NULL, 0, NULL, 0}, }; static void parse_params(int argc, char *argv[]) { int option_index = 0; int v, i; uint32_t plen1=-1, plen2=-1; memset(¶ms, 0, sizeof(params)); params.pctmult = DEFAULT_PCTMULT; params.pctdiv = DEFAULT_PCTDIV; params.v6_threshold = DEFAULT_V6_THRESHOLD; while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) { if (v) exithelp(); switch (option_index) { case IDX_HELP: case IDX_H: PRINT_VER; exithelp(); break; case IDX_4: params.ipv6 = false; break; case IDX_6: params.ipv6 = true; break; case IDX_PREFIX_LENGTH: i = sscanf(optarg,"%u-%u",&plen1,&plen2); if (i == 1) plen2 = plen1; if (i<=0 || plen2=params.pctdiv) { fprintf(stderr, "invalid parameter for v4-threshold : %s\n", optarg); exit(1); } break; case IDX_V6_THRESHOLD: i = sscanf(optarg, "%u", ¶ms.v6_threshold); if (i != 1 || params.v6_threshold<1) { fprintf(stderr, "invalid parameter for v6-threshold : %s\n", optarg); exit(1); } break; } } if (plen1 != -1 && ((!params.ipv6 && (plen1>31 || plen2>31)) || (params.ipv6 && (plen1>127 || plen2>127)))) { fprintf(stderr, "invalid parameter for prefix-length\n"); exit(1); } params.zct_min = params.ipv6 ? plen2==-1 ? DEFAULT_V6_ZCT_MIN : 128-plen2 : plen2==-1 ? DEFAULT_V4_ZCT_MIN : 32-plen2; params.zct_max = params.ipv6 ? plen1==-1 ? DEFAULT_V6_ZCT_MAX : 128-plen1 : plen1==-1 ? DEFAULT_V4_ZCT_MAX : 32-plen1; } int main(int argc, char **argv) { char str[256],d; uint32_t ipct = 0, iplist_size = 0, pos = 0, p, zct, ip_ct, pos_end; parse_params(argc, argv); if (params.ipv6) // ipv6 { char *s; struct in6_addr a, *iplist = NULL, *iplist_new; while (fgets(str, sizeof(str), stdin)) { rtrim(str); d = 0; if ((s = strchr(str, '/')) || (s = strchr(str, '-'))) { d = *s; *s = '\0'; } if (inet_pton(AF_INET6, str, &a)) { if (d=='/') { // we have subnet ip6/y // output it as is if (sscanf(s + 1, "%u", &zct)==1 && zct!=128) { if (zct<128) printf("%s/%u\n", str, zct); continue; } } else if (d=='-') { if (inet_pton(AF_INET6, s+1, &a)) printf("%s-%s\n", str, s+1); continue; } if (ipct >= iplist_size) { iplist_size += ALLOC_STEP; iplist_new = (struct in6_addr*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size)); if (!iplist_new) { free(iplist); fprintf(stderr, "out of memory\n"); return 100; } iplist = iplist_new; } iplist[ipct++] = a; } } gnu_quicksort(iplist, ipct, sizeof(*iplist), cmp6, NULL); ipct = unique6(iplist, ipct); mask_from_bitcount6_prepare(); /* for(uint32_t i=0;i= params.zct_min; zct--) { mask = mask_from_bitcount6(zct); ip6_and(iplist + pos, mask, &ip_start); for (p = pos + 1, ip_ct = 1; p < ipct; p++, ip_ct++) { ip6_and(iplist + p, mask, &ip); if (memcmp(&ip_start, &ip, sizeof(ip))) break; } if (ip_ct == 1) break; if (ip_ct >= params.v6_threshold) { // network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets if (!ip_ct_best || ip_ct == ip_ct_best) { ip_ct_best = ip_ct; zct_best = zct; pos_end = p; } else break; } } if (zct_best) // network was found ip6_and(iplist + pos, mask_from_bitcount6(zct_best), &ip_start); else ip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip inet_ntop(AF_INET6, &ip_start, str, sizeof(str)); printf(zct_best ? "%s/%u\n" : "%s\n", str, 128 - zct_best); pos = pos_end; } free(iplist); } else // ipv4 { uint32_t u1,u2,u3,u4, u11,u22,u33,u44, ip; uint32_t *iplist = NULL, *iplist_new, i; while (fgets(str, sizeof(str), stdin)) { if ((i = sscanf(str, "%u.%u.%u.%u-%u.%u.%u.%u", &u1, &u2, &u3, &u4, &u11, &u22, &u33, &u44)) >= 8 && !(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00) && !(u11 & 0xFFFFFF00) && !(u22 & 0xFFFFFF00) && !(u33 & 0xFFFFFF00) && !(u44 & 0xFFFFFF00)) { printf("%u.%u.%u.%u-%u.%u.%u.%u\n", u1, u2, u3, u4, u11, u22, u33, u44); } else if ((i = sscanf(str, "%u.%u.%u.%u/%u", &u1, &u2, &u3, &u4, &zct)) >= 4 && !(u1 & 0xFFFFFF00) && !(u2 & 0xFFFFFF00) && !(u3 & 0xFFFFFF00) && !(u4 & 0xFFFFFF00)) { if (i == 5 && zct != 32) { // we have subnet x.x.x.x/y // output it as is if valid, ignore otherwise if (zct < 32) printf("%u.%u.%u.%u/%u\n", u1, u2, u3, u4, zct); } else { ip = u1 << 24 | u2 << 16 | u3 << 8 | u4; if (ipct >= iplist_size) { iplist_size += ALLOC_STEP; iplist_new = (uint32_t*)(iplist ? realloc(iplist, sizeof(*iplist)*iplist_size) : malloc(sizeof(*iplist)*iplist_size)); if (!iplist_new) { free(iplist); fprintf(stderr, "out of memory\n"); return 100; } iplist = iplist_new; } iplist[ipct++] = ip; } } } gnu_quicksort(iplist, ipct, sizeof(*iplist), ucmp, NULL); ipct = unique(iplist, ipct); while (pos < ipct) { uint32_t mask, ip_start, ip_end, subnet_ct; uint32_t ip_ct_best = 0, zct_best = 0; // find smallest network with maximum ip coverage with no less than mul/div percent addresses for (zct = params.zct_max; zct >= params.zct_min; zct--) { mask = mask_from_bitcount(zct); ip_start = iplist[pos] & mask; subnet_ct = ~mask + 1; if (iplist[pos] > (ip_start + subnet_ct*(params.pctdiv - params.pctmult) / params.pctdiv)) continue; // ip is higher than (1-PCT). definitely coverage is not enough. skip searching ip_end = ip_start | ~mask; for (p=pos+1, ip_ct=1; p < ipct && iplist[p] <= ip_end; p++) ip_ct++; // count ips within subnet range if (ip_ct == 1) break; if (ip_ct >= (subnet_ct*params.pctmult / params.pctdiv)) { // network found. but is there smaller network with the same ip_ct ? dont do carpet bombing if possible, use smaller subnets if (!ip_ct_best || ip_ct == ip_ct_best) { ip_ct_best = ip_ct; zct_best = zct; pos_end = p; } else break; } } if (zct_best) ip_start = iplist[pos] & mask_from_bitcount(zct_best); else ip_start = iplist[pos], pos_end = pos + 1; // network not found, use single ip u1 = ip_start >> 24; u2 = (ip_start >> 16) & 0xFF; u3 = (ip_start >> 8) & 0xFF; u4 = ip_start & 0xFF; printf(zct_best ? "%u.%u.%u.%u/%u\n" : "%u.%u.%u.%u\n", u1, u2, u3, u4, 32 - zct_best); pos = pos_end; } free(iplist); } return 0; } ================================================ FILE: ip2net/qsort.c ================================================ /* Copyright (C) 1991-2018 Free Software Foundation, Inc. This file is part of the GNU C Library. Written by Douglas C. Schmidt (schmidt@ics.uci.edu). The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ /* If you consider tuning this algorithm, you should consult first: Engineering a sort function; Jon Bentley and M. Douglas McIlroy; Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */ //#include #include #include //#include #include "qsort.h" /* Byte-wise swap two items of size SIZE. */ #define SWAP(a, b, size) \ do \ { \ size_t __size = (size); \ char *__a = (a), *__b = (b); \ do \ { \ char __tmp = *__a; \ *__a++ = *__b; \ *__b++ = __tmp; \ } while (--__size > 0); \ } while (0) /* Discontinue quicksort algorithm when partition gets below this size. This particular magic number was chosen to work best on a Sun 4/260. */ #define MAX_THRESH 4 /* Stack node declarations used to store unfulfilled partition obligations. */ typedef struct { char *lo; char *hi; } stack_node; /* The next 4 #defines implement a very fast in-line stack abstraction. */ /* The stack needs log (total_elements) entries (we could even subtract log(MAX_THRESH)). Since total_elements has type size_t, we get as upper bound for log (total_elements): bits per byte (CHAR_BIT) * sizeof(size_t). */ #define STACK_SIZE (CHAR_BIT * sizeof(size_t)) #define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top)) #define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi))) #define STACK_NOT_EMPTY (stack < top) /* Order size using quicksort. This implementation incorporates four optimizations discussed in Sedgewick: 1. Non-recursive, using an explicit stack of pointer that store the next array partition to sort. To save time, this maximum amount of space required to store an array of SIZE_MAX is allocated on the stack. Assuming a 32-bit (64 bit) integer for size_t, this needs only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes). Pretty cheap, actually. 2. Chose the pivot element using a median-of-three decision tree. This reduces the probability of selecting a bad pivot value and eliminates certain extraneous comparisons. 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving insertion sort to order the MAX_THRESH items within each partition. This is a big win, since insertion sort is faster for small, mostly sorted array segments. 4. The larger of the two sub-partitions is always pushed onto the stack first, with the algorithm then concentrating on the smaller partition. This *guarantees* no more than log (total_elems) stack size is needed (actually O(1) in this case)! */ void gnu_quicksort (void *const pbase, size_t total_elems, size_t size, __gnu_compar_d_fn_t cmp, void *arg) { char *base_ptr = (char *) pbase; const size_t max_thresh = MAX_THRESH * size; if (total_elems == 0) /* Avoid lossage with unsigned arithmetic below. */ return; if (total_elems > MAX_THRESH) { char *lo = base_ptr; char *hi = &lo[size * (total_elems - 1)]; stack_node stack[STACK_SIZE]; stack_node *top = stack; PUSH (NULL, NULL); while (STACK_NOT_EMPTY) { char *left_ptr; char *right_ptr; /* Select median value from among LO, MID, and HI. Rearrange LO and HI so the three values are sorted. This lowers the probability of picking a pathological pivot value and skips a comparison for both the LEFT_PTR and RIGHT_PTR in the while loops. */ char *mid = lo + size * ((hi - lo) / size >> 1); if ((*cmp) ((void *) mid, (void *) lo, arg) < 0) SWAP (mid, lo, size); if ((*cmp) ((void *) hi, (void *) mid, arg) < 0) SWAP (mid, hi, size); else goto jump_over; if ((*cmp) ((void *) mid, (void *) lo, arg) < 0) SWAP (mid, lo, size); jump_over:; left_ptr = lo + size; right_ptr = hi - size; /* Here's the famous ``collapse the walls'' section of quicksort. Gotta like those tight inner loops! They are the main reason that this algorithm runs much faster than others. */ do { while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0) left_ptr += size; while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0) right_ptr -= size; if (left_ptr < right_ptr) { SWAP (left_ptr, right_ptr, size); if (mid == left_ptr) mid = right_ptr; else if (mid == right_ptr) mid = left_ptr; left_ptr += size; right_ptr -= size; } else if (left_ptr == right_ptr) { left_ptr += size; right_ptr -= size; break; } } while (left_ptr <= right_ptr); /* Set up pointers for next iteration. First determine whether left and right partitions are below the threshold size. If so, ignore one or both. Otherwise, push the larger partition's bounds on the stack and continue sorting the smaller one. */ if ((size_t) (right_ptr - lo) <= max_thresh) { if ((size_t) (hi - left_ptr) <= max_thresh) /* Ignore both small partitions. */ POP (lo, hi); else /* Ignore small left partition. */ lo = left_ptr; } else if ((size_t) (hi - left_ptr) <= max_thresh) /* Ignore small right partition. */ hi = right_ptr; else if ((right_ptr - lo) > (hi - left_ptr)) { /* Push larger left partition indices. */ PUSH (lo, right_ptr); lo = left_ptr; } else { /* Push larger right partition indices. */ PUSH (left_ptr, hi); hi = right_ptr; } } } /* Once the BASE_PTR array is partially sorted by quicksort the rest is completely sorted using insertion sort, since this is efficient for partitions below MAX_THRESH size. BASE_PTR points to the beginning of the array to sort, and END_PTR points at the very last element in the array (*not* one beyond it!). */ #define min(x, y) ((x) < (y) ? (x) : (y)) { char *const end_ptr = &base_ptr[size * (total_elems - 1)]; char *tmp_ptr = base_ptr; char *thresh = min(end_ptr, base_ptr + max_thresh); char *run_ptr; /* Find smallest element in first threshold and place it at the array's beginning. This is the smallest array element, and the operation speeds up insertion sort's inner loop. */ for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size) if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0) tmp_ptr = run_ptr; if (tmp_ptr != base_ptr) SWAP (tmp_ptr, base_ptr, size); /* Insertion sort, running from left-hand-side up to right-hand-side. */ run_ptr = base_ptr + size; while ((run_ptr += size) <= end_ptr) { tmp_ptr = run_ptr - size; while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0) tmp_ptr -= size; tmp_ptr += size; if (tmp_ptr != run_ptr) { char *trav; trav = run_ptr + size; while (--trav >= run_ptr) { char c = *trav; char *hi, *lo; for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo) *hi = *lo; *hi = c; } } } } } ================================================ FILE: ip2net/qsort.h ================================================ #pragma once // GNU qsort is 2x faster than musl typedef int (*__gnu_compar_d_fn_t) (const void *, const void *, void *); void gnu_quicksort (void *const pbase, size_t total_elems, size_t size, __gnu_compar_d_fn_t cmp, void *arg); ================================================ FILE: ipset/antifilter.helper ================================================ get_antifilter() { # $1 - list url # $2 - target file local ZIPLISTTMP="$TMPDIR/zapret-ip.txt" [ "$DISABLE_IPV4" != "1" ] && { curl --fail --max-time 150 --connect-timeout 20 --max-filesize 41943040 -k -L "$1" | cut_local >"$ZIPLISTTMP" && { dlsize=$(LC_ALL=C LANG=C wc -c "$ZIPLISTTMP" | xargs | cut -f 1 -d ' ') if [ $dlsize -lt 102400 ]; then echo list file is too small. can be bad. exit 2 fi ip2net4 <"$ZIPLISTTMP" | zz "$2" rm -f "$ZIPLISTTMP" } } } ================================================ FILE: ipset/clear_lists.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" rm -f "$ZIPLIST"* "$ZIPLIST6"* "$ZIPLIST_USER" "$ZIPLIST_USER6" "$ZIPLIST_IPBAN"* "$ZIPLIST_IPBAN6"* "$ZIPLIST_USER_IPBAN" "$ZIPLIST_USER_IPBAN6" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6" "$ZHOSTLIST"* ================================================ FILE: ipset/create_ipset.sh ================================================ #!/bin/sh # create ipset or ipfw table from resolved ip's # $1=no-update - do not update ipset, only create if its absent # $1=clear - clear ipset EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" . "$EXEDIR/def.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/nft.sh" IPSET_CMD="$TMPDIR/ipset_cmd.txt" IPSET_SAVERAM_CHUNK_SIZE=20000 IPSET_SAVERAM_MIN_FILESIZE=131072 NFSET_TEMP="$TMPDIR/nfset_temp.txt" NFSET_SAVERAM_MIN_FILESIZE=16384 NFSET_SAVERAM_CHUNK_SIZE=1000 IPSET_HOOK_TEMP="$TMPDIR/ipset_hook.txt" while [ -n "$1" ]; do [ "$1" = "no-update" ] && NO_UPDATE=1 [ "$1" = "clear" ] && DO_CLEAR=1 shift done file_extract_lines() { # $1 - filename # $2 - from line (starting with 0) # $3 - line count # awk "{ err=1 } NR < $(($2+1)) { next } { print; err=0 } NR == $(($2+$3)) { exit err } END {exit err}" "$1" $AWK "NR < $(($2+1)) { next } { print } NR == $(($2+$3)) { exit }" "$1" } ipset_restore_chunked() { # $1 - filename # $2 - chunk size local pos lines [ -f "$1" ] || return lines=$(wc -l <"$1") pos=$lines while [ "$pos" -gt "0" ]; do pos=$((pos-$2)) [ "$pos" -lt "0" ] && pos=0 file_extract_lines "$1" $pos $2 | ipset -! restore sed -i "$(($pos+1)),$ d" "$1" done } ipset_get_script() { # $1 - ipset name sed -nEe "s/^.+$/add $1 &/p" } ipset_get_script_from_file() { # $1 - filename # $2 - ipset name zzcat "$1" | sort -u | ipset_get_script $2 } ipset_restore() { # $1 - ipset name # $2 - filename zzexist "$2" || return local fsize=$(zzsize "$2") local svram=0 # do not saveram small files. file can also be gzipped [ "$SAVERAM" = "1" ] && [ "$fsize" -ge "$IPSET_SAVERAM_MIN_FILESIZE" ] && svram=1 local T="Adding to ipset $1 " [ "$svram" = "1" ] && T="$T (saveram)" T="$T : $f" echo $T if [ "$svram" = "1" ]; then ipset_get_script_from_file "$2" "$1" >"$IPSET_CMD" ipset_restore_chunked "$IPSET_CMD" $IPSET_SAVERAM_CHUNK_SIZE rm -f "$IPSET_CMD" else ipset_get_script_from_file "$2" "$1" | ipset -! restore fi } create_ipset() { if [ "$1" -eq "6" ]; then FAMILY=inet6 else FAMILY=inet fi ipset create $2 $3 $4 family $FAMILY 2>/dev/null || { [ "$NO_UPDATE" = "1" ] && return 0 } ipset flush $2 [ "$DO_CLEAR" = "1" ] || { for f in "$5" "$6" ; do ipset_restore "$2" "$f" done [ -n "$IPSET_HOOK" ] && $IPSET_HOOK $2 | ipset_get_script $2 | ipset -! restore } return 0 } nfset_get_script_multi() { # $1 - set name # $2,$3,... - filenames # all in one shot. this allows to merge overlapping ranges # good but eats lots of RAM local set=$1 nonempty N=1 f shift # first we need to make sure at least one element exists or nft will fail while : do eval f=\$$N [ -n "$f" ] || break nonempty=$(zzexist "$f" && zzcat "$f" 2>/dev/null | head -n 1) [ -n "$nonempty" ] && break N=$(($N+1)) done [ -n "$nonempty" ] && { echo "add element inet $ZAPRET_NFT_TABLE $set {" while [ -n "$1" ]; do zzexist "$1" && zzcat "$1" | sed -nEe "s/^.+$/&,/p" shift done echo "}" } } nfset_restore() { # $1 - set name # $2,$3,... - filenames echo "Adding to nfset $1 : $2 $3 $4 $5" local hookfile [ -n "$IPSET_HOOK" ] && { $IPSET_HOOK $1 >"$IPSET_HOOK_TEMP" [ -s "$IPSET_HOOK_TEMP" ] && hookfile=$IPSET_HOOK_TEMP } nfset_get_script_multi "$@" $hookfile | nft -f - rm -f "$IPSET_HOOK_TEMP" } create_nfset() { # $1 - family # $2 - set name # $3 - maxelem # $4,$5 - list files local policy [ $SAVERAM = "1" ] && policy="policy memory;" nft_create_set $2 "type ipv${1}_addr; size $3; flags interval; auto-merge; $policy" || { [ "$NO_UPDATE" = "1" ] && return 0 nft flush set inet $ZAPRET_NFT_TABLE $2 } [ "$DO_CLEAR" = "1" ] || { nfset_restore $2 $4 $5 } return 0 } add_ipfw_table() { # $1 - table name sed -nEe "s/^.+$/table $1 add &/p" | ipfw -q /dev/stdin } populate_ipfw_table() { # $1 - table name # $2 - ip list file zzexist "$2" || return zzcat "$2" | sort -u | add_ipfw_table $1 } create_ipfw_table() { # $1 - table name # $2 - table options # $3,$4, ... - ip list files. can be v4,v6 or mixed local name=$1 ipfw table "$name" create $2 2>/dev/null || { [ "$NO_UPDATE" = "1" ] && return 0 } ipfw -q table $1 flush shift shift [ "$DO_CLEAR" = "1" ] || { while [ -n "$1" ]; do echo "Adding to ipfw table $name : $1" populate_ipfw_table $name "$1" shift done [ -n "$IPSET_HOOK" ] && $IPSET_HOOK $name | add_ipfw_table $name } return 0 } print_reloading_backend() { # $1 - backend name local s="reloading $1 backend" if [ "$NO_UPDATE" = 1 ]; then s="$s (no-update)" elif [ "$DO_CLEAR" = 1 ]; then s="$s (clear)" else s="$s (forced-update)" fi echo $s } oom_adjust_high get_fwtype if [ -n "$LISTS_RELOAD" ] ; then if [ "$LISTS_RELOAD" = "-" ] ; then echo not reloading ip list backend true else echo executing custom ip list reload command : $LISTS_RELOAD $LISTS_RELOAD [ -n "$IPSET_HOOK" ] && $IPSET_HOOK fi else case "$FWTYPE" in iptables) # ipset seem to buffer the whole script to memory # on low RAM system this can cause oom errors # in SAVERAM mode we feed script lines in portions starting from the end, while truncating source file to free /tmp space # only /tmp is considered tmpfs. other locations mean tmpdir was redirected to a disk SAVERAM=0 [ "$TMPDIR" = "/tmp" ] && { RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}') [ "$RAMSIZE" -lt "110000" ] && SAVERAM=1 } print_reloading_backend ipset [ "$DISABLE_IPV4" != "1" ] && { create_ipset 4 $ZIPSET hash:net "$IPSET_OPT" "$ZIPLIST" "$ZIPLIST_USER" create_ipset 4 $ZIPSET_IPBAN hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" create_ipset 4 $ZIPSET_EXCLUDE hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" } [ "$DISABLE_IPV6" != "1" ] && { create_ipset 6 $ZIPSET6 hash:net "$IPSET_OPT" "$ZIPLIST6" "$ZIPLIST_USER6" create_ipset 6 $ZIPSET_IPBAN6 hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" create_ipset 6 $ZIPSET_EXCLUDE6 hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6" } true ;; nftables) nft_create_table && { SAVERAM=0 RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}') [ "$RAMSIZE" -lt "420000" ] && SAVERAM=1 print_reloading_backend "nftables set" [ "$DISABLE_IPV4" != "1" ] && { create_nfset 4 $ZIPSET $SET_MAXELEM "$ZIPLIST" "$ZIPLIST_USER" create_nfset 4 $ZIPSET_IPBAN $SET_MAXELEM "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" create_nfset 4 $ZIPSET_EXCLUDE $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE" } [ "$DISABLE_IPV6" != "1" ] && { create_nfset 6 $ZIPSET6 $SET_MAXELEM "$ZIPLIST6" "$ZIPLIST_USER6" create_nfset 6 $ZIPSET_IPBAN6 $SET_MAXELEM "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" create_nfset 6 $ZIPSET_EXCLUDE6 $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE6" } true } ;; ipfw) print_reloading_backend "ipfw table" if [ "$DISABLE_IPV4" != "1" ] && [ "$DISABLE_IPV6" != "1" ]; then create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" "$ZIPLIST6" "$ZIPLIST_USER6" create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6" elif [ "$DISABLE_IPV4" != "1" ]; then create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" elif [ "$DISABLE_IPV6" != "1" ]; then create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST6" "$ZIPLIST_USER6" create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6" else create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" fi true ;; *) echo no supported ip list backend found true ;; esac fi ================================================ FILE: ipset/def.sh ================================================ EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE=${ZAPRET_BASE:-"$(cd "$EXEDIR/.."; pwd)"} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} IPSET_RW_DIR="$ZAPRET_RW/ipset" [ -f "$ZAPRET_CONFIG" ] && . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" [ -z "$TMPDIR" ] && TMPDIR=/tmp [ -z "$GZIP_LISTS" ] && GZIP_LISTS=1 [ -z "$SET_MAXELEM" ] && SET_MAXELEM=262144 [ -z "$IPSET_OPT" ] && IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM" [ -z "$SET_MAXELEM_EXCLUDE" ] && SET_MAXELEM_EXCLUDE=65536 [ -z "$IPSET_OPT_EXCLUDE" ] && IPSET_OPT_EXCLUDE="hashsize 1024 maxelem $SET_MAXELEM_EXCLUDE" [ -z "$IPFW_TABLE_OPT" ] && IPFW_TABLE_OPT="algo addr:radix" [ -z "$IPFW_TABLE_OPT_EXCLUDE" ] && IPFW_TABLE_OPT_EXCLUDE="algo addr:radix" ZIPSET=zapret ZIPSET6=zapret6 ZIPSET_EXCLUDE=nozapret ZIPSET_EXCLUDE6=nozapret6 ZIPLIST="$IPSET_RW_DIR/zapret-ip.txt" ZIPLIST6="$IPSET_RW_DIR/zapret-ip6.txt" ZIPLIST_EXCLUDE="$IPSET_RW_DIR/zapret-ip-exclude.txt" ZIPLIST_EXCLUDE6="$IPSET_RW_DIR/zapret-ip-exclude6.txt" ZIPLIST_USER="$IPSET_RW_DIR/zapret-ip-user.txt" ZIPLIST_USER6="$IPSET_RW_DIR/zapret-ip-user6.txt" ZUSERLIST="$IPSET_RW_DIR/zapret-hosts-user.txt" ZHOSTLIST="$IPSET_RW_DIR/zapret-hosts.txt" ZIPSET_IPBAN=ipban ZIPSET_IPBAN6=ipban6 ZIPLIST_IPBAN="$IPSET_RW_DIR/zapret-ip-ipban.txt" ZIPLIST_IPBAN6="$IPSET_RW_DIR/zapret-ip-ipban6.txt" ZIPLIST_USER_IPBAN="$IPSET_RW_DIR/zapret-ip-user-ipban.txt" ZIPLIST_USER_IPBAN6="$IPSET_RW_DIR/zapret-ip-user-ipban6.txt" ZUSERLIST_IPBAN="$IPSET_RW_DIR/zapret-hosts-user-ipban.txt" ZUSERLIST_EXCLUDE="$IPSET_RW_DIR/zapret-hosts-user-exclude.txt" [ -n "$IP2NET" ] || IP2NET="$ZAPRET_BASE/ip2net/ip2net" [ -n "$MDIG" ] || MDIG="$ZAPRET_BASE/mdig/mdig" [ -z "$MDIG_THREADS" ] && MDIG_THREADS=30 # BSD grep is damn slow with -f option. prefer GNU grep (ggrep) if present # MacoS in cron does not include /usr/local/bin to PATH if [ -x /usr/local/bin/ggrep ] ; then GREP=/usr/local/bin/ggrep elif [ -x /usr/local/bin/grep ] ; then GREP=/usr/local/bin/grep elif exists ggrep; then GREP=$(whichq ggrep) else GREP=$(whichq grep) fi # GNU awk is faster if exists gawk; then AWK=gawk else AWK=awk fi grep_supports_b() { # \b does not work with BSD grep $GREP --version 2>&1 | $GREP -qE "BusyBox|GNU" } get_ip_regex() { 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]))?' 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]))?' # good but too slow # 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]))?' # grep_supports_b && { # REG_IPV4="\b$REG_IPV4\b" # REG_IPV6="\b$REG_IPV6\b" # } } ip2net4() { if [ -x "$IP2NET" ]; then "$IP2NET" -4 $IP2NET_OPT4 else sort -u fi } ip2net6() { if [ -x "$IP2NET" ]; then "$IP2NET" -6 $IP2NET_OPT6 else sort -u fi } zzexist() { [ -f "$1.gz" ] || [ -f "$1" ] } zztest() { gzip -t "$1" 2>/dev/null } zzcat() { if [ -f "$1.gz" ]; then gunzip -c "$1.gz" elif [ -f "$1" ]; then if zztest "$1"; then gunzip -c "$1" else cat "$1" fi fi } zz() { if [ "$GZIP_LISTS" = "1" ]; then gzip -c >"$1.gz" rm -f "$1" else cat >"$1" rm -f "$1.gz" fi } zzsize() { local f="$1" [ -f "$1.gz" ] && f="$1.gz" if [ -f "$f" ]; then wc -c <"$f" | xargs else printf 0 fi } zzcopy() { local is_gz=0 zztest "$1" && is_gz=1 if [ "$GZIP_LISTS" = 1 -a $is_gz = 1 ]; then cp "$1" "${2}.gz" elif [ "$GZIP_LISTS" != 1 -a $is_gz != 1 ]; then cp "$1" "$2" else zzcat "$1" | zz "$2" fi } digger() { # $1 - family (4|6) # $2 - s=enable mdig stats if [ -x "$MDIG" ]; then local cmd [ "$2" = "s" ] && cmd=--stats=1000 "$MDIG" --family=$1 --threads=$MDIG_THREADS $cmd else local A=A [ "$1" = "6" ] && A=AAAA dig $A +short +time=8 +tries=2 -f - | $GREP -E '^[^;].*[^\.]$' fi } filedigger() { # $1 - hostlist # $2 - family (4|6) >&2 echo digging $(wc -l <"$1" | xargs) ipv$2 domains : "$1" zzcat "$1" | digger $2 s } flush_dns_cache() { echo clearing all known DNS caches if exists killall; then killall -HUP dnsmasq 2>/dev/null # MacOS killall -HUP mDNSResponder 2>/dev/null elif exists pkill; then pkill -HUP ^dnsmasq$ else echo no mass killer available ! cant flush dnsmasq fi if exists rndc; then rndc flush fi if exists systemd-resolve; then systemd-resolve --flush-caches fi } dnstest() { local ip="$(echo w3.org | digger 46)" [ -n "$ip" ] } dnstest_with_cache_clear() { flush_dns_cache if dnstest ; then echo DNS is working return 0 else echo "! DNS is not working" return 1 fi } cut_local() { $GREP -vE '^192\.168\.|^127\.|^10\.' } cut_local6() { $GREP -vE '^::|^fc..:|^fd..:|^fe8.:|^fe9.:|^fea.:|^feb.:|^FC..:|^FD..:|^FE8.:|^FE9.:|^FEA.:|^FEB.:' } oom_adjust_high() { [ -f /proc/$$/oom_score_adj ] && { echo setting high oom kill priority echo -n 100 >/proc/$$/oom_score_adj } } getexclude() { oom_adjust_high dnstest_with_cache_clear || return [ -f "$ZUSERLIST_EXCLUDE" ] && { [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST_EXCLUDE" 4 | sort -u > "$ZIPLIST_EXCLUDE" [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST_EXCLUDE" 6 | sort -u > "$ZIPLIST_EXCLUDE6" } return 0 } _get_ipban() { [ -f "$ZUSERLIST_IPBAN" ] && { [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST_IPBAN" 4 | cut_local | sort -u > "$ZIPLIST_USER_IPBAN" [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST_IPBAN" 6 | cut_local6 | sort -u > "$ZIPLIST_USER_IPBAN6" } } getuser() { getexclude || return [ -f "$ZUSERLIST" ] && { [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST" 4 | cut_local | sort -u > "$ZIPLIST_USER" [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST" 6 | cut_local6 | sort -u > "$ZIPLIST_USER6" } _get_ipban return 0 } getipban() { getexclude || return _get_ipban return 0 } hup_zapret_daemons() { echo forcing zapret daemons to reload their hostlist if exists killall; then killall -HUP tpws nfqws dvtws 2>/dev/null elif exists pkill; then pkill -HUP ^tpws$ pkill -HUP ^nfqws$ pkill -HUP ^dvtws$ else echo no mass killer available ! cant HUP zapret daemons fi } ================================================ FILE: ipset/get_antifilter_allyouneed.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser && { . "$IPSET_DIR/antifilter.helper" get_antifilter https://antifilter.download/list/allyouneed.lst "$ZIPLIST" } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_antifilter_ip.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser && { . "$IPSET_DIR/antifilter.helper" get_antifilter https://antifilter.download/list/ip.lst "$ZIPLIST" } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_antifilter_ipresolve.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser && { . "$IPSET_DIR/antifilter.helper" get_antifilter https://antifilter.download/list/ipresolve.lst "$ZIPLIST" } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_antifilter_ipsmart.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser && { . "$IPSET_DIR/antifilter.helper" get_antifilter https://antifilter.network/download/ipsmart.lst "$ZIPLIST" } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_antifilter_ipsum.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser && { . "$IPSET_DIR/antifilter.helper" get_antifilter https://antifilter.download/list/ipsum.lst "$ZIPLIST" } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_antizapret_domains.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" # useful in case ipban set is used in custom scripts FAIL= getipban || FAIL=1 "$IPSET_DIR/create_ipset.sh" [ -n "$FAIL" ] && exit ZURL=https://antizapret.prostovpn.org:8443/domains-export.txt ZDOM="$TMPDIR/zapret.txt" curl -H "Accept-Encoding: gzip" -k --fail --max-time 600 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL" | gunzip - >"$ZDOM" || { echo domain list download failed exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$ZDOM" | xargs | cut -f 1 -d ' ') if test $dlsize -lt 102400; then echo list file is too small. can be bad. exit 2 fi sort -u "$ZDOM" | zz "$ZHOSTLIST" rm -f "$ZDOM" hup_zapret_daemons exit 0 ================================================ FILE: ipset/get_config.sh ================================================ #!/bin/sh # run script specified in config IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" [ -f "$IPSET_DIR/../config" ] && . "$IPSET_DIR/../config" [ -z "$GETLIST" ] && GETLIST=get_ipban.sh [ -x "$IPSET_DIR/$GETLIST" ] && exec "$IPSET_DIR/$GETLIST" ================================================ FILE: ipset/get_exclude.sh ================================================ #!/bin/sh # resolve user host list IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getexclude "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_ipban.sh ================================================ #!/bin/sh # resolve only ipban user host list IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getipban "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_reestr_preresolved.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" TMPLIST="$TMPDIR/list.txt" BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" URL4="$BASEURL/reestr_resolved4.txt" URL6="$BASEURL/reestr_resolved6.txt" #IPB4="$BASEURL/reestr_ipban4.txt" #IPB6="$BASEURL/reestr_ipban6.txt" dl() { # $1 - url # $2 - file # $3 - minsize # $4 - maxsize curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || { echo list download failed : $1 exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') if test $dlsize -lt $3; then echo list is too small : $dlsize bytes. can be bad. exit 2 fi zzcopy "$TMPLIST" "$2" rm -f "$TMPLIST" } getuser && { [ "$DISABLE_IPV4" != "1" ] && { dl "$URL4" "$ZIPLIST" 4096 4194304 # dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 } [ "$DISABLE_IPV6" != "1" ] && { dl "$URL6" "$ZIPLIST6" 2048 4194304 # dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 } } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_reestr_preresolved_smart.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" TMPLIST="$TMPDIR/list.txt" BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" URL4="$BASEURL/reestr_smart4.txt" URL6="$BASEURL/reestr_smart6.txt" #IPB4="$BASEURL/reestr_ipban4.txt" #IPB6="$BASEURL/reestr_ipban6.txt" dl() { # $1 - url # $2 - file # $3 - minsize # $4 - maxsize curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || { echo list download failed : $1 exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') if test $dlsize -lt $3; then echo list is too small : $dlsize bytes. can be bad. exit 2 fi zzcopy "$TMPLIST" "$2" rm -f "$TMPLIST" } getuser && { [ "$DISABLE_IPV4" != "1" ] && { dl "$URL4" "$ZIPLIST" 4096 4194304 # dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 } [ "$DISABLE_IPV6" != "1" ] && { dl "$URL6" "$ZIPLIST6" 2048 4194304 # dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 } } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_reestr_resolvable_domains.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" TMPLIST="$TMPDIR/list_nethub.txt" BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" URL="$BASEURL/reestr_hostname_resolvable.txt" #IPB4="$BASEURL/reestr_ipban4.txt" #IPB6="$BASEURL/reestr_ipban6.txt" dl() { # $1 - url # $2 - file # $3 - minsize # $4 - maxsize curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || { echo list download failed : $1 exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') if test $dlsize -lt $3; then echo list is too small : $dlsize bytes. can be bad. exit 2 fi zzcopy "$TMPLIST" "$2" rm -f "$TMPLIST" } dl "$URL" "$ZHOSTLIST" 65536 67108864 hup_zapret_daemons #[ "$DISABLE_IPV4" != "1" ] && dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 #[ "$DISABLE_IPV6" != "1" ] && dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 getipban "$IPSET_DIR/create_ipset.sh" exit 0 ================================================ FILE: ipset/get_refilter_domains.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" TMPLIST="$TMPDIR/list.txt" URL="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/domains_all.lst" dl() { # $1 - url # $2 - file # $3 - minsize # $4 - maxsize curl -L -H "Accept-Encoding: gzip" -k --fail --max-time 60 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || { echo list download failed : $1 exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') if test $dlsize -lt $3; then echo list is too small : $dlsize bytes. can be bad. exit 2 fi zzcopy "$TMPLIST" "$2" rm -f "$TMPLIST" } # useful in case ipban set is used in custom scripts FAIL= getipban || FAIL=1 "$IPSET_DIR/create_ipset.sh" [ -n "$FAIL" ] && exit dl "$URL" "$ZHOSTLIST" 32768 4194304 hup_zapret_daemons exit 0 ================================================ FILE: ipset/get_refilter_ipsum.sh ================================================ #!/bin/sh IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" TMPLIST="$TMPDIR/list.txt" URL="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/ipsum.lst" dl() { # $1 - url # $2 - file # $3 - minsize # $4 - maxsize curl -L -H "Accept-Encoding: gzip" -k --fail --max-time 60 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || { echo list download failed : $1 exit 2 } dlsize=$(LC_ALL=C LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') if test $dlsize -lt $3; then echo list is too small : $dlsize bytes. can be bad. exit 2 fi zzcopy "$TMPLIST" "$2" rm -f "$TMPLIST" } getuser && { [ "$DISABLE_IPV4" != "1" ] && { dl "$URL" "$ZIPLIST" 32768 4194304 } } "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/get_user.sh ================================================ #!/bin/sh # resolve user host list IPSET_DIR="$(dirname "$0")" IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" . "$IPSET_DIR/def.sh" getuser "$IPSET_DIR/create_ipset.sh" ================================================ FILE: ipset/zapret-hosts-user-exclude.txt.default ================================================ 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 100.64.0.0/10 ::1 fc00::/7 fe80::/10 ================================================ FILE: mdig/Makefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 $(OPTIMIZE) CFLAGS_BSD = -Wno-address-of-packed-member CFLAGS_WIN = -static LIBS = -lpthread LIBS_ANDROID = LIBS_WIN = -lws2_32 SRC_FILES = *.c all: mdig mdig: $(SRC_FILES) $(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS) systemd: mdig android: $(SRC_FILES) $(CC) -s $(CFLAGS) -o mdig $(SRC_FILES) $(LIBS_ANDROID) $(LDFLAGS) bsd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o mdig $(SRC_FILES) $(LIBS) $(LDFLAGS) mac: $(SRC_FILES) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdiga $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdigx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) strip mdiga mdigx lipo -create -output mdig mdigx mdiga rm -f mdigx mdiga win: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_WIN) -o mdig $(SRC_FILES) $(LIBS_WIN) $(LDFLAGS) clean: rm -f mdig *.o ================================================ FILE: mdig/mdig.c ================================================ // multi thread dns resolver // domain list stdout // errors, verbose >stderr // transparent for valid ip or ip/subnet of allowed address family #define _GNU_SOURCE #include #include #include #include #include #include #include #include #ifdef _WIN32 #undef _WIN32_WINNT #define _WIN32_WINNT 0x600 #include #include #include #include #else #include #include #include #include #include #endif #include #define RESOLVER_EAGAIN_ATTEMPTS 10 #define RESOLVER_EAGAIN_DELAY 500 static void trimstr(char *s) { char *p; for (p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'); p--) *p = '\0'; } static const char* eai_str(int r) { switch (r) { case EAI_NONAME: return "EAI_NONAME"; case EAI_AGAIN: return "EAI_AGAIN"; #ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY: return "EAI_ADDRFAMILY"; #endif #ifdef EAI_NODATA case EAI_NODATA: return "EAI_NODATA"; #endif case EAI_BADFLAGS: return "EAI_BADFLAGS"; case EAI_FAIL: return "EAI_FAIL"; case EAI_MEMORY: return "EAI_MEMORY"; case EAI_FAMILY: return "EAI_FAMILY"; case EAI_SERVICE: return "EAI_SERVICE"; case EAI_SOCKTYPE: return "EAI_SOCKTYPE"; #ifdef EAI_SYSTEM case EAI_SYSTEM: return "EAI_SYSTEM"; #endif default: return "UNKNOWN"; } } static bool dom_valid(char *dom) { if (!dom || *dom=='.') return false; for (; *dom; dom++) if (!(*dom == '.' || *dom == '-' || *dom == '_' || (*dom >= '0' && *dom <= '9') || (*dom >= 'a' && *dom <= 'z') || (*dom >= 'A' && *dom <= 'Z'))) return false; return true; } static void invalid_domain_beautify(char *dom) { for (int i = 0; *dom && i < 64; i++, dom++) if (*dom < 0x20 || (*dom & 0x80)) *dom = '?'; if (*dom) *dom = 0; } #define FAMILY4 1 #define FAMILY6 2 static struct { char verbose; char family; int threads, eagain, eagain_delay; time_t start_time; pthread_mutex_t flock; pthread_mutex_t slock; // stats lock int stats_every, stats_ct, stats_ct_ok; // stats FILE *F_log_resolved, *F_log_failed; } glob; // get next domain. return 0 if failure static char interlocked_get_dom(char *dom, size_t size) { char *s; pthread_mutex_lock(&glob.flock); s = fgets(dom, size, stdin); pthread_mutex_unlock(&glob.flock); if (!s) return 0; trimstr(s); return 1; } static void interlocked_fprintf(FILE *stream, const char * format, ...) { if (stream) { va_list args; va_start(args, format); pthread_mutex_lock(&glob.flock); vfprintf(stream, format, args); pthread_mutex_unlock(&glob.flock); va_end(args); } } #define ELOG(format, ...) interlocked_fprintf(stderr, "[%d] " format "\n", tid, ##__VA_ARGS__) #define VLOG(format, ...) {if (glob.verbose) ELOG(format, ##__VA_ARGS__);} static void print_addrinfo(struct addrinfo *ai) { char str[64]; while (ai) { switch (ai->ai_family) { case AF_INET: if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str))) interlocked_fprintf(stdout, "%s\n", str); break; case AF_INET6: if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str))) interlocked_fprintf(stdout, "%s\n", str); break; } ai = ai->ai_next; } } static void stat_print(int ct, int ct_ok) { if (glob.stats_every > 0) { time_t tm = time(NULL)-glob.start_time; interlocked_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); } } static void stat_plus(bool is_ok) { int ct, ct_ok; if (glob.stats_every > 0) { pthread_mutex_lock(&glob.slock); ct = ++glob.stats_ct; ct_ok = glob.stats_ct_ok += is_ok; pthread_mutex_unlock(&glob.slock); if (!(ct % glob.stats_every)) stat_print(ct, ct_ok); } } static uint16_t GetAddrFamily(const char *saddr) { struct in_addr a4; struct in6_addr a6; if (inet_pton(AF_INET, saddr, &a4)) return AF_INET; else if (inet_pton(AF_INET6, saddr, &a6)) return AF_INET6; return 0; } static void *t_resolver(void *arg) { int tid = (int)(size_t)arg; int i, r; char dom[256]; bool is_ok; struct addrinfo hints, *result; struct timespec ts_eagain = { .tv_sec = glob.eagain_delay/1000, .tv_nsec=glob.eagain_delay%1000*1000000 }; VLOG("started"); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = (glob.family == FAMILY4) ? AF_INET : (glob.family == FAMILY6) ? AF_INET6 : AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; while (interlocked_get_dom(dom, sizeof(dom))) { is_ok = false; if (*dom) { uint16_t family; char *s_mask, s_ip[sizeof(dom)]; strncpy(s_ip, dom, sizeof(s_ip)); s_mask = strchr(s_ip, '/'); if (s_mask) *s_mask++ = 0; family = GetAddrFamily(s_ip); if (family) { if ((family == AF_INET && (glob.family & FAMILY4)) || (family == AF_INET6 && (glob.family & FAMILY6))) { unsigned int mask=0; bool mask_needed = false; if (s_mask) { if (sscanf(s_mask, "%u", &mask)==1) { switch (family) { case AF_INET: is_ok = mask <= 32; mask_needed = mask < 32; break; case AF_INET6: is_ok = mask <= 128; mask_needed = mask < 128; break; } } } else is_ok = true; if (is_ok) interlocked_fprintf(stdout, mask_needed ? "%s/%u\n" : "%s\n", s_ip, mask); else VLOG("bad ip/subnet %s", dom); } else VLOG("wrong address family %s", s_ip); } else if (dom_valid(dom)) { VLOG("resolving %s", dom); for (i = 0; i < glob.eagain; i++) { if ((r = getaddrinfo(dom, NULL, &hints, &result))) { VLOG("failed to resolve %s : result %d (%s)", dom, r, eai_str(r)); if (r == EAI_AGAIN) { nanosleep(&ts_eagain, NULL); continue; // temporary failure. should retry } } else { print_addrinfo(result); freeaddrinfo(result); is_ok = true; } break; } } else if (glob.verbose) { char dom2[sizeof(dom)]; strcpy(dom2,dom); invalid_domain_beautify(dom2); VLOG("invalid domain : %s", dom2); } interlocked_fprintf(is_ok ? glob.F_log_resolved : glob.F_log_failed,"%s\n",dom); } stat_plus(is_ok); } VLOG("ended"); return NULL; } static int run_threads(void) { int i, thread; pthread_t *t; glob.stats_ct = glob.stats_ct_ok = 0; time(&glob.start_time); if (pthread_mutex_init(&glob.flock, NULL) != 0) { fprintf(stderr, "mutex init failed\n"); return 10; } if (pthread_mutex_init(&glob.slock, NULL) != 0) { fprintf(stderr, "mutex init failed\n"); pthread_mutex_destroy(&glob.flock); return 10; } t = (pthread_t*)malloc(sizeof(pthread_t)*glob.threads); if (!t) { fprintf(stderr, "out of memory\n"); pthread_mutex_destroy(&glob.slock); pthread_mutex_destroy(&glob.flock); return 11; } for (thread = 0; thread < glob.threads; thread++) { if (pthread_create(t + thread, NULL, t_resolver, (void*)(size_t)thread)) { interlocked_fprintf(stderr, "failed to create thread #%d\n", thread); break; } } for (i = 0; i < thread; i++) { pthread_join(t[i], NULL); } free(t); stat_print(glob.stats_ct, glob.stats_ct_ok); pthread_mutex_destroy(&glob.slock); pthread_mutex_destroy(&glob.flock); return thread ? 0 : 12; } // slightly patched musl code size_t dns_mk_query_blob(uint8_t op, const char *dname, uint8_t class, uint8_t type, uint8_t *buf, size_t buflen) { int i, j; uint16_t id; struct timespec ts; size_t l = strnlen(dname, 255); size_t n; if (l && dname[l-1]=='.') l--; if (l && dname[l-1]=='.') return 0; n = 17+l+!!l; if (l>253 || buflen15u) return 0; /* Construct query template - ID will be filled later */ memset(buf, 0, n); buf[2] = (op<<3) | 1; buf[5] = 1; memcpy((char *)buf+13, dname, l); for (i=13; buf[i]; i=j+1) { for (j=i; buf[j] && buf[j] != '.'; j++); if (j-i-1u > 62u) return 0; buf[i-1] = j-i; } buf[i+1] = type; buf[i+3] = class; /* Make a reasonably unpredictable id */ clock_gettime(CLOCK_REALTIME, &ts); id = (uint16_t)ts.tv_nsec + (uint16_t)(ts.tv_nsec>>16); buf[0] = id>>8; buf[1] = id; return n; } int dns_make_query(const char *dom, char family) { uint8_t q[280]; size_t l = dns_mk_query_blob(0, dom, 1, family == FAMILY6 ? 28 : 1, q, sizeof(q)); if (!l) { fprintf(stderr, "could not make DNS query\n"); return 1; } #ifdef _WIN32 _setmode(_fileno(stdout), _O_BINARY); #endif if (fwrite(q,l,1,stdout)!=1) { fprintf(stderr, "could not write DNS query blob to stdout\n"); return 10; } return 0; } bool dns_parse_print(const uint8_t *a, size_t len) { // check of minimum header length and response flag uint16_t k, dlen, qcount = a[4]<<8 | a[5], acount = a[6]<<8 | a[7]; char s_ip[40]; if (len<12 || !(a[2]&0x80)) return false; a+=12; len-=12; for(k=0;klen) return false; // skip to next label len -= *a+1; a += *a+1; } if (len<5) return false; // skip zero length label, type, class a+=5; len-=5; } for(k=0;k\t\t; ipv4, ipv6, ipv4+ipv6\n" " --threads=\n" " --eagain=\t; how many times to retry if EAI_AGAIN received. default %u\n" " --eagain-delay=\t\t; time in msec to wait between EAI_AGAIN attempts. default %u\n" " --verbose\t\t\t; print query progress to stderr\n" " --stats=N\t\t\t; print resolve stats to stderr every N domains\n" " --log-resolved=\t\t; log successfully resolved domains to a file\n" " --log-failed=\t\t; log failed domains to a file\n" " --dns-make-query=\t; output to stdout binary blob with DNS query. use --family to specify ip version.\n" " --dns-parse-query\t\t; read from stdin binary DNS answer blob and parse it to ipv4/ipv6 addresses\n", RESOLVER_EAGAIN_ATTEMPTS, RESOLVER_EAGAIN_DELAY ); exit(1); } #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) #define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) #else #define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) #endif enum opt_indices { IDX_HELP, IDX_EAGAIN, IDX_EAGAIN_DELAY, IDX_THREADS, IDX_FAMILY, IDX_VERBOSE, IDX_STATS, IDX_LOG_RESOLVED, IDX_LOG_FAILED, IDX_DNS_MAKE_QUERY, IDX_DNS_PARSE_QUERY, IDX_LAST, }; static const struct option long_options[] = { [IDX_HELP] = {"help", no_argument, 0, 0}, [IDX_THREADS] = {"threads", required_argument, 0, 0}, [IDX_EAGAIN] = {"eagain", required_argument, 0, 0}, [IDX_EAGAIN_DELAY] = {"eagain-delay", required_argument, 0, 0}, [IDX_FAMILY] = {"family", required_argument, 0, 0}, [IDX_VERBOSE] = {"verbose", no_argument, 0, 0}, [IDX_STATS] = {"stats", required_argument, 0, 0}, [IDX_LOG_RESOLVED] = {"log-resolved", required_argument, 0, 0}, [IDX_LOG_FAILED] = {"log-failed", required_argument, 0, 0}, [IDX_DNS_MAKE_QUERY] = {"dns-make-query", required_argument, 0, 0}, [IDX_DNS_PARSE_QUERY] = {"dns-parse-query", no_argument, 0, 0}, [IDX_LAST] = {NULL, 0, NULL, 0}, }; int main(int argc, char **argv) { int r, v, option_index = 0; char fn1[256],fn2[256]; char dom[256]; memset(&glob, 0, sizeof(glob)); *fn1 = *fn2 = *dom = 0; glob.family = FAMILY4; glob.threads = 1; glob.eagain = RESOLVER_EAGAIN_ATTEMPTS; glob.eagain_delay = RESOLVER_EAGAIN_DELAY; while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) { if (v) exithelp(); switch (option_index) { case IDX_HELP: PRINT_VER; exithelp(); break; case IDX_THREADS: glob.threads = atoi(optarg); if (glob.threads <= 0 || glob.threads > 100) { fprintf(stderr, "thread number must be within 1..100\n"); return 1; } break; case IDX_EAGAIN: glob.eagain = atoi(optarg); if (glob.eagain <= 0 || glob.eagain > 1000) { fprintf(stderr, "eagain must be within 1..1000\n"); return 1; } break; case IDX_EAGAIN_DELAY: glob.eagain_delay = atoi(optarg); if (glob.eagain_delay < 0 || glob.eagain_delay > 100000) { fprintf(stderr, "eagain-delay must be within 0..100000\n"); return 1; } break; case IDX_FAMILY: if (!strcmp(optarg, "4")) glob.family = FAMILY4; else if (!strcmp(optarg, "6")) glob.family = FAMILY6; else if (!strcmp(optarg, "46")) glob.family = FAMILY4 | FAMILY6; else { fprintf(stderr, "ip family must be 4,6 or 46\n"); return 1; } break; case IDX_VERBOSE: glob.verbose = '\1'; break; case IDX_STATS: glob.stats_every = optarg ? atoi(optarg) : 0; break; case IDX_LOG_RESOLVED: strncpy(fn1,optarg,sizeof(fn1)); fn1[sizeof(fn1)-1] = 0; break; case IDX_LOG_FAILED: strncpy(fn2,optarg,sizeof(fn2)); fn2[sizeof(fn2)-1] = 0; break; case IDX_DNS_MAKE_QUERY: strncpy(dom,optarg,sizeof(dom)); dom[sizeof(dom)-1] = 0; break; case IDX_DNS_PARSE_QUERY: return dns_parse_query(); } } #ifdef _WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData)) { fprintf(stderr,"WSAStartup failed\n"); return 4; } #endif if (*dom) return dns_make_query(dom, glob.family); if (*fn1) { glob.F_log_resolved = fopen(fn1,"wt"); if (!glob.F_log_resolved) { fprintf(stderr,"failed to create %s\n",fn1); r=5; goto ex; } } if (*fn2) { glob.F_log_failed = fopen(fn2,"wt"); if (!glob.F_log_failed) { fprintf(stderr,"failed to create %s\n",fn2); r=5; goto ex; } } r = run_threads(); ex: if (glob.F_log_resolved) fclose(glob.F_log_resolved); if (glob.F_log_failed) fclose(glob.F_log_failed); return r; } ================================================ FILE: nfq/BSDmakefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto -Wno-address-of-packed-member LIBS = -lz SRC_FILES = *.c crypto/*.c all: dvtws dvtws: $(SRC_FILES) $(CC) $(CFLAGS) -o dvtws $(SRC_FILES) $(LIBS) $(LDFLAGS) clean: rm -f dvtws ================================================ FILE: nfq/Makefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -ffunction-sections -fdata-sections CFLAGS_SYSTEMD = -DUSE_SYSTEMD CFLAGS_BSD = -Wno-address-of-packed-member CFLAGS_CYGWIN = -Wno-address-of-packed-member -static LDFLAGS += -flto=auto LDFLAGS_ANDROID = -Wl,--gc-sections -llog LDFLAGS_BSD = -Wl,--gc-sections LDFLAGS_LINUX = -Wl,--gc-sections LDFLAGS_MAC = -Wl,-dead_strip LDFLAGS_WIN = -Wl,--gc-sections LIBS_LINUX = -lz -lnetfilter_queue -lnfnetlink -lmnl LIBS_SYSTEMD = -lsystemd LIBS_BSD = -lz LIBS_CYGWIN = -lz -Lwindows/windivert -Iwindows -lwlanapi -lole32 -loleaut32 LIBS_CYGWIN32 = -lwindivert32 LIBS_CYGWIN64 = -lwindivert64 RES_CYGWIN32 = windows/res/32/winmanifest.o windows/res/32/winicon.o RES_CYGWIN64 = windows/res/64/winmanifest.o windows/res/64/winicon.o SRC_FILES = *.c crypto/*.c all: nfqws nfqws: $(SRC_FILES) $(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_LINUX) systemd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LIBS_SYSTEMD) $(LDFLAGS) $(LDFLAGS_LINUX) android: $(SRC_FILES) $(CC) -s $(CFLAGS) -o nfqws $(SRC_FILES) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_ANDROID) bsd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o dvtws $(SRC_FILES) $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_BSD) mac: $(SRC_FILES) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsa $(SRC_FILES) -target arm64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_MAC) $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsx $(SRC_FILES) -target x86_64-apple-macos10.8 $(LIBS_BSD) $(LDFLAGS) $(LDFLAGS_MAC) strip dvtwsa dvtwsx lipo -create -output dvtws dvtwsx dvtwsa rm -f dvtwsx dvtwsa cygwin64: $(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN64) $(RES_CYGWIN64) $(LDFLAGS) $(LDFLAGS_WIN) cygwin32: $(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LIBS_CYGWIN) $(LIBS_CYGWIN32) $(RES_CYGWIN32) $(LDFLAGS) $(LDFLAGS_WIN) cygwin: cygwin64 clean: rm -f nfqws dvtws winws.exe ================================================ FILE: nfq/checksum.c ================================================ #define _GNU_SOURCE #include "checksum.h" #include //#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) //#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) static uint16_t from64to16(uint64_t x) { uint32_t u = (uint32_t)(uint16_t)x + (uint16_t)(x>>16) + (uint16_t)(x>>32) + (uint16_t)(x>>48); return (uint16_t)u + (uint16_t)(u>>16); } // this function preserves data alignment requirements (otherwise it will be damn slow on mips arch) // and uses 64-bit arithmetics to improve speed // taken from linux source code static uint16_t do_csum(const uint8_t * buff, size_t len) { uint8_t odd; size_t count; uint64_t result,w,carry=0; uint16_t u16; if (!len) return 0; odd = (uint8_t)(1 & (size_t)buff); if (odd) { // any endian compatible u16 = 0; *((uint8_t*)&u16+1) = *buff; result = u16; len--; buff++; } else result = 0; count = len >> 1; /* nr of 16-bit words.. */ if (count) { if (2 & (size_t) buff) { result += *(uint16_t *) buff; count--; len -= 2; buff += 2; } count >>= 1; /* nr of 32-bit words.. */ if (count) { if (4 & (size_t) buff) { result += *(uint32_t *) buff; count--; len -= 4; buff += 4; } count >>= 1; /* nr of 64-bit words.. */ if (count) { do { w = *(uint64_t *) buff; count--; buff += 8; result += carry; result += w; carry = (w > result); } while (count); result += carry; result = (result & 0xffffffff) + (result >> 32); } if (len & 4) { result += *(uint32_t *) buff; buff += 4; } } if (len & 2) { result += *(uint16_t *) buff; buff += 2; } } if (len & 1) { // any endian compatible u16 = 0; *(uint8_t*)&u16 = *buff; result += u16; } u16 = from64to16(result); if (odd) u16 = ((u16 >> 8) & 0xff) | ((u16 & 0xff) << 8); return u16; } uint16_t csum_partial(const void *buff, size_t len) { return do_csum(buff,len); } uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum) { return ~from64to16((uint64_t)saddr + daddr + sum + htonl(len+proto)); } uint16_t ip4_compute_csum(const void *buff, size_t len) { return ~from64to16(do_csum(buff,len)); } void ip4_fix_checksum(struct ip *ip) { ip->ip_sum = 0; ip->ip_sum = ip4_compute_csum(ip, ip->ip_hl<<2); } uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum) { uint64_t a = (uint64_t)sum + htonl(len+proto) + *(uint32_t*)saddr + *((uint32_t*)saddr+1) + *((uint32_t*)saddr+2) + *((uint32_t*)saddr+3) + *(uint32_t*)daddr + *((uint32_t*)daddr+1) + *((uint32_t*)daddr+2) + *((uint32_t*)daddr+3); return ~from64to16(a); } void tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr) { tcp->th_sum = 0; tcp->th_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_TCP,csum_partial(tcp,len)); } void tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) { tcp->th_sum = 0; tcp->th_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_TCP,csum_partial(tcp,len)); } void tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr) { if (ip) tcp4_fix_checksum(tcp, len, &ip->ip_src, &ip->ip_dst); else if (ip6hdr) tcp6_fix_checksum(tcp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); } void udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr) { udp->uh_sum = 0; udp->uh_sum = csum_tcpudp_magic(src_addr->s_addr,dest_addr->s_addr,len,IPPROTO_UDP,csum_partial(udp,len)); } void udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) { udp->uh_sum = 0; udp->uh_sum = csum_ipv6_magic(src_addr,dest_addr,len,IPPROTO_UDP,csum_partial(udp,len)); } void udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr) { if (ip) udp4_fix_checksum(udp, len, &ip->ip_src, &ip->ip_dst); else if (ip6hdr) udp6_fix_checksum(udp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); } ================================================ FILE: nfq/checksum.h ================================================ #pragma once #include #include #include #include #define __FAVOR_BSD #include #include #include #include uint16_t csum_partial(const void *buff, size_t len); uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum); uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum); uint16_t ip4_compute_csum(const void *buff, size_t len); void ip4_fix_checksum(struct ip *ip); void tcp4_fix_checksum(struct tcphdr *tcp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr); void tcp6_fix_checksum(struct tcphdr *tcp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); void tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr); void udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr); void udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); void udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr); ================================================ FILE: nfq/conntrack.c ================================================ #include "conntrack.h" #include "darkmagic.h" #include #include #undef uthash_nonfatal_oom #define uthash_nonfatal_oom(elt) ut_oom_recover(elt) static bool oom = false; static void ut_oom_recover(void *elem) { oom = true; } static const char *connstate_s[]={"SYN","ESTABLISHED","FIN"}; static void connswap(const t_conn *c, t_conn *c2) { memset(c2,0,sizeof(*c2)); c2->l3proto = c->l3proto; c2->l4proto = c->l4proto; c2->src = c->dst; c2->dst = c->src; c2->sport = c->dport; c2->dport = c->sport; } void ConntrackClearHostname(t_ctrack *track) { free(track->hostname); track->hostname = NULL; track->hostname_is_ip = false; } static void ConntrackClearTrack(t_ctrack *track) { ConntrackClearHostname(track); ReasmClear(&track->reasm_orig); rawpacket_queue_destroy(&track->delayed); } static void ConntrackFreeElem(t_conntrack_pool *elem) { ConntrackClearTrack(&elem->track); free(elem); } static void ConntrackPoolDestroyPool(t_conntrack_pool **pp) { t_conntrack_pool *elem, *tmp; HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); } } void ConntrackPoolDestroy(t_conntrack *p) { ConntrackPoolDestroyPool(&p->pool); } void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp) { p->timeout_syn = timeout_syn; p->timeout_established = timeout_established; p->timeout_fin = timeout_fin; p->timeout_udp= timeout_udp; p->t_purge_interval = purge_interval; time(&p->t_last_purge); p->pool = NULL; } void ConntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) { memset(c,0,sizeof(*c)); if (ip) { c->l3proto = IPPROTO_IP; c->dst.ip = bReverse ? ip->ip_src : ip->ip_dst; c->src.ip = bReverse ? ip->ip_dst : ip->ip_src; } else if (ip6) { c->l3proto = IPPROTO_IPV6; c->dst.ip6 = bReverse ? ip6->ip6_src : ip6->ip6_dst; c->src.ip6 = bReverse ? ip6->ip6_dst : ip6->ip6_src; } else c->l3proto = -1; extract_ports(tcphdr, udphdr, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport); } static t_conntrack_pool *ConntrackPoolSearch(t_conntrack_pool *p, const t_conn *c) { t_conntrack_pool *t; HASH_FIND(hh, p, c, sizeof(*c), t); return t; } static void ConntrackInitTrack(t_ctrack *t) { memset(t,0,sizeof(*t)); t->scale_orig = t->scale_reply = SCALE_NONE; time(&t->t_start); rawpacket_queue_init(&t->delayed); } static void ConntrackReInitTrack(t_ctrack *t) { ConntrackClearTrack(t); ConntrackInitTrack(t); } static t_conntrack_pool *ConntrackNew(t_conntrack_pool **pp, const t_conn *c) { t_conntrack_pool *ctnew; if (!(ctnew = malloc(sizeof(*ctnew)))) return NULL; ctnew->conn = *c; oom = false; HASH_ADD(hh, *pp, conn, sizeof(*c), ctnew); if (oom) { free(ctnew); return NULL; } ConntrackInitTrack(&ctnew->track); return ctnew; } // non-tcp packets are passed with tcphdr=NULL but len_payload filled static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr *tcphdr, uint32_t len_payload) { uint8_t scale; if (bReverse) { t->pcounter_reply++; t->pdcounter_reply+=!!len_payload; } else { t->pcounter_orig++; t->pdcounter_orig+=!!len_payload; } if (tcphdr) { if (tcp_syn_segment(tcphdr)) { if (t->state!=SYN) ConntrackReInitTrack(t); // erase current entry t->seq0 = ntohl(tcphdr->th_seq); } else if (tcp_synack_segment(tcphdr)) { // ignore SA dups uint32_t seq0 = ntohl(tcphdr->th_ack)-1; if (t->state!=SYN && t->seq0!=seq0) ConntrackReInitTrack(t); // erase current entry if (!t->seq0) t->seq0 = seq0; t->ack0 = ntohl(tcphdr->th_seq); } else if (tcphdr->th_flags & (TH_FIN|TH_RST)) { t->state = FIN; } else { if (t->state==SYN) { t->state=ESTABLISHED; if (!bReverse && !t->ack0) t->ack0 = ntohl(tcphdr->th_ack)-1; } } scale = tcp_find_scale_factor(tcphdr); if (bReverse) { t->pos_orig = t->seq_last = ntohl(tcphdr->th_ack); t->ack_last = ntohl(tcphdr->th_seq); t->pos_reply = t->ack_last + len_payload; t->winsize_reply = ntohs(tcphdr->th_win); if (scale!=SCALE_NONE) t->scale_reply = scale; } else { t->seq_last = ntohl(tcphdr->th_seq); t->pos_orig = t->seq_last + len_payload; t->pos_reply = t->ack_last = ntohl(tcphdr->th_ack); t->winsize_orig = ntohs(tcphdr->th_win); if (scale!=SCALE_NONE) t->scale_orig = scale; } } else { if (bReverse) { t->ack_last=t->pos_reply; t->pos_reply+=len_payload; } else { t->seq_last=t->pos_orig; t->pos_orig+=len_payload; } } time(&t->t_last); } static 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) { t_conn conn,connswp; t_conntrack_pool *ctr; ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); if ((ctr=ConntrackPoolSearch(*pp,&conn))) { if (bReverse) *bReverse = false; if (ctrack) *ctrack = &ctr->track; return true; } else { connswap(&conn,&connswp); if ((ctr=ConntrackPoolSearch(*pp,&connswp))) { if (bReverse) *bReverse = true; if (ctrack) *ctrack = &ctr->track; return true; } } return false; } bool 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) { return ConntrackPoolDoubleSearchPool(&p->pool, ip, ip6, tcphdr, udphdr, ctrack, bReverse); } static 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) { t_conn conn, connswp; t_conntrack_pool *ctr; bool b_rev; ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); if ((ctr=ConntrackPoolSearch(*pp,&conn))) { ConntrackFeedPacket(&ctr->track, (b_rev=false), tcphdr, len_payload); goto ok; } else { connswap(&conn,&connswp); if ((ctr=ConntrackPoolSearch(*pp,&connswp))) { ConntrackFeedPacket(&ctr->track, (b_rev=true), tcphdr, len_payload); goto ok; } } b_rev = tcphdr && tcp_synack_segment(tcphdr); if ((tcphdr && tcp_syn_segment(tcphdr)) || b_rev || udphdr) { if ((ctr=ConntrackNew(pp, b_rev ? &connswp : &conn))) { ConntrackFeedPacket(&ctr->track, b_rev, tcphdr, len_payload); goto ok; } } return false; ok: if (ctrack) *ctrack = &ctr->track; if (bReverse) *bReverse = b_rev; return true; } bool 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) { return ConntrackPoolFeedPool(&p->pool,ip,ip6,tcphdr,udphdr,len_payload,ctrack,bReverse); } static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) { t_conn conn, connswp; t_conntrack_pool *t; ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); if (!(t=ConntrackPoolSearch(*pp,&conn))) { connswap(&conn,&connswp); t=ConntrackPoolSearch(*pp,&connswp); } if (!t) return false; HASH_DEL(*pp, t); ConntrackFreeElem(t); return true; } bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) { return ConntrackPoolDropPool(&p->pool,ip,ip6,tcphdr,udphdr); } void ConntrackPoolPurge(t_conntrack *p) { time_t tidle, tnow = time(NULL); t_conntrack_pool *t, *tmp; if ((tnow - p->t_last_purge)>=p->t_purge_interval) { HASH_ITER(hh, p->pool , t, tmp) { tidle = tnow - t->track.t_last; if ( t->track.b_cutoff || (t->conn.l4proto==IPPROTO_TCP && ( (t->track.state==SYN && tidle>=p->timeout_syn) || (t->track.state==ESTABLISHED && tidle>=p->timeout_established) || (t->track.state==FIN && tidle>=p->timeout_fin)) ) || (t->conn.l4proto==IPPROTO_UDP && tidle>=p->timeout_udp) ) { HASH_DEL(p->pool, t); ConntrackFreeElem(t); } } p->t_last_purge = tnow; } } static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsize) { if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0; } void ConntrackPoolDump(const t_conntrack *p) { t_conntrack_pool *t, *tmp; char sa1[40],sa2[40]; time_t tnow = time(NULL); HASH_ITER(hh, p->pool, t, tmp) { taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1)); taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2)); printf("%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 ", proto_name(t->conn.l4proto), sa1, t->conn.sport, sa2, t->conn.dport, t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-", (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), (unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig, (unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply); if (t->conn.l4proto==IPPROTO_TCP) printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d", t->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0, t->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0, t->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig, t->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply); else printf("rseq=%u pos_orig=%u rack=%u pos_reply=%u", t->track.seq_last, t->track.pos_orig, t->track.ack_last, t->track.pos_reply); printf(" req_retrans=%u cutoff=%u wss_cutoff=%u desync_cutoff=%u dup_cutoff=%u orig_cutoff=%u hostname=%s l7proto=%s\n", t->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)); }; } void ReasmClear(t_reassemble *reasm) { free(reasm->packet); reasm->packet = NULL; reasm->size = reasm->size_present = 0; } bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start) { reasm->packet = malloc(size_requested); if (!reasm->packet) return false; reasm->size = size_requested; reasm->size_present = 0; reasm->seq = seq_start; return true; } bool ReasmResize(t_reassemble *reasm, size_t new_size) { uint8_t *p = realloc(reasm->packet, new_size); if (!p) return false; reasm->packet = p; reasm->size = new_size; if (reasm->size_present > new_size) reasm->size_present = new_size; return true; } bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len) { if (reasm->seq!=seq) return false; // fail session if out of sequence size_t szcopy; szcopy = reasm->size - reasm->size_present; if (lenpacket + reasm->size_present, payload, szcopy); reasm->size_present += szcopy; reasm->seq += (uint32_t)szcopy; return true; } bool ReasmHasSpace(t_reassemble *reasm, size_t len) { return (reasm->size_present+len)<=reasm->size; } ================================================ FILE: nfq/conntrack.h ================================================ #pragma once // this conntrack is not bullet-proof // its designed to satisfy dpi desync needs only #include #include #include #include #include #include #define __FAVOR_BSD #include #include #include #include #include "packet_queue.h" #include "protocol.h" //#define HASH_BLOOM 20 #define HASH_NONFATAL_OOM 1 #undef HASH_FUNCTION #define HASH_FUNCTION HASH_BER #include "uthash.h" #define RETRANS_COUNTER_STOP ((uint8_t)-1) typedef union { struct in_addr ip; struct in6_addr ip6; } t_addr; typedef struct { t_addr src, dst; uint16_t sport,dport; uint8_t l3proto; // IPPROTO_IP, IPPROTO_IPV6 uint8_t l4proto; // IPPROTO_TCP, IPPROTO_UDP } t_conn; // this structure helps to reassemble continuous packets streams. it does not support out-of-orders typedef struct { uint8_t *packet; // allocated for size during reassemble request. requestor must know the message size. uint32_t seq; // current seq number. if a packet comes with an unexpected seq - it fails reassemble session. size_t size; // expected message size. success means that we have received exactly 'size' bytes and have them in 'packet' size_t size_present; // how many bytes already stored in 'packet' } t_reassemble; // SYN - SYN or SYN/ACK received // ESTABLISHED - any except SYN or SYN/ACK received // FIN - FIN or RST received typedef enum {SYN=0, ESTABLISHED, FIN} t_connstate; typedef struct { bool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache struct desync_profile *dp; // desync profile cache bool dp_search_complete; // common state time_t t_start, t_last; uint64_t pcounter_orig, pcounter_reply; // packet counter uint64_t pdcounter_orig, pdcounter_reply; // data packet counter (with payload) uint32_t pos_orig, pos_reply; // TCP: seq_last+payload, ack_last+payload UDP: sum of all seen payload lenghts including current uint32_t seq_last, ack_last; // TCP: last seen seq and ack UDP: sum of all seen payload lenghts NOT including current // tcp only state, not used in udp t_connstate state; uint32_t seq0, ack0; // starting seq and ack uint16_t winsize_orig, winsize_reply; // last seen window size uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none uint8_t req_retrans_counter; // number of request retransmissions bool req_seq_present,req_seq_finalized,req_seq_abandoned; uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions) uint8_t incoming_ttl, desync_autottl, orig_autottl, dup_autottl; bool b_autottl_discovered; bool b_cutoff; // mark for deletion bool b_wssize_cutoff, b_desync_cutoff, b_dup_cutoff, b_orig_mod_cutoff; uint16_t ip_id; t_l7proto l7proto; bool l7proto_discovered; char *hostname; bool hostname_is_ip; bool hostname_discovered; bool hostname_ah_check; // should perform autohostlist checks t_reassemble reasm_orig; struct rawpacket_tailhead delayed; } t_ctrack; typedef struct { t_ctrack track; UT_hash_handle hh; // makes this structure hashable t_conn conn; // key } t_conntrack_pool; typedef struct { // inactivity time to purge an entry in each connection state uint32_t timeout_syn,timeout_established,timeout_fin,timeout_udp; time_t t_purge_interval, t_last_purge; t_conntrack_pool *pool; } t_conntrack; void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp); void ConntrackPoolDestroy(t_conntrack *p); bool 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); // do not create, do not update. only find existing bool 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); bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); void CaonntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); void ConntrackPoolDump(const t_conntrack *p); void ConntrackPoolPurge(t_conntrack *p); void ConntrackClearHostname(t_ctrack *track); bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start); bool ReasmResize(t_reassemble *reasm, size_t new_size); void ReasmClear(t_reassemble *reasm); // false means reassemble session has failed and we should ReasmClear() it bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len); // check if it has enough space to buffer 'len' bytes bool ReasmHasSpace(t_reassemble *reasm, size_t len); inline static bool ReasmIsEmpty(t_reassemble *reasm) {return !reasm->size;} inline static bool ReasmIsFull(t_reassemble *reasm) {return !ReasmIsEmpty(reasm) && (reasm->size==reasm->size_present);} ================================================ FILE: nfq/crypto/aes-gcm.c ================================================ #include "aes-gcm.h" int 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) { int ret = 0; gcm_context ctx; gcm_initialize(); if (!(ret = gcm_setkey(&ctx, key, (const uint)key_len))) { ret = gcm_crypt_and_tag(&ctx, mode, iv, iv_len, adata, adata_len, input, output, input_length, atag, atag_len); gcm_zero_ctx(&ctx); } return ret; } ================================================ FILE: nfq/crypto/aes-gcm.h ================================================ #pragma once #include "gcm.h" // mode : AES_ENCRYPT, AES_DECRYPT int 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); ================================================ FILE: nfq/crypto/aes.c ================================================ /****************************************************************************** * * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL * * This is a simple and straightforward implementation of the AES Rijndael * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus * of this work was correctness & accuracy. It is written in 'C' without any * particular focus upon optimization or speed. It should be endian (memory * byte order) neutral since the few places that care are handled explicitly. * * This implementation of Rijndael was created by Steven M. Gibson of GRC.com. * * It is intended for general purpose use, but was written in support of GRC's * reference implementation of the SQRL (Secure Quick Reliable Login) client. * * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html * * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. * *******************************************************************************/ #include "aes.h" static int aes_tables_inited = 0; // run-once flag for performing key // expasion table generation (see below) /* * The following static local tables must be filled-in before the first use of * the GCM or AES ciphers. They are used for the AES key expansion/scheduling * and once built are read-only and thread safe. The "gcm_initialize" function * must be called once during system initialization to populate these arrays * for subsequent use by the AES key scheduler. If they have not been built * before attempted use, an error will be returned to the caller. * * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since * GCM uses AES in counter-mode, where the AES cipher output is XORed with * the GCM input, we ONLY NEED AES encryption. Thus, to save space AES * decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h. */ // We always need our forward tables static uchar FSb[256]; // Forward substitution box (FSb) static uint32_t FT0[256]; // Forward key schedule assembly tables static uint32_t FT1[256]; static uint32_t FT2[256]; static uint32_t FT3[256]; #if AES_DECRYPTION // We ONLY need reverse for decryption static uchar RSb[256]; // Reverse substitution box (RSb) static uint32_t RT0[256]; // Reverse key schedule assembly tables static uint32_t RT1[256]; static uint32_t RT2[256]; static uint32_t RT3[256]; #endif /* AES_DECRYPTION */ static uint32_t RCON[10]; // AES round constants /* * Platform Endianness Neutralizing Load and Store Macro definitions * AES wants platform-neutral Little Endian (LE) byte ordering */ #define GET_UINT32_LE(n,b,i) { \ (n) = ( (uint32_t) (b)[(i) ] ) \ | ( (uint32_t) (b)[(i) + 1] << 8 ) \ | ( (uint32_t) (b)[(i) + 2] << 16 ) \ | ( (uint32_t) (b)[(i) + 3] << 24 ); } #define PUT_UINT32_LE(n,b,i) { \ (b)[(i) ] = (uchar) ( (n) ); \ (b)[(i) + 1] = (uchar) ( (n) >> 8 ); \ (b)[(i) + 2] = (uchar) ( (n) >> 16 ); \ (b)[(i) + 3] = (uchar) ( (n) >> 24 ); } /* * AES forward and reverse encryption round processing macros */ #define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ { \ X0 = *RK++ ^ FT0[ ( Y0 ) & 0xFF ] ^ \ FT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ FT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ FT3[ ( Y3 >> 24 ) & 0xFF ]; \ \ X1 = *RK++ ^ FT0[ ( Y1 ) & 0xFF ] ^ \ FT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ FT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ FT3[ ( Y0 >> 24 ) & 0xFF ]; \ \ X2 = *RK++ ^ FT0[ ( Y2 ) & 0xFF ] ^ \ FT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ FT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ FT3[ ( Y1 >> 24 ) & 0xFF ]; \ \ X3 = *RK++ ^ FT0[ ( Y3 ) & 0xFF ] ^ \ FT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ FT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ FT3[ ( Y2 >> 24 ) & 0xFF ]; \ } #define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ { \ X0 = *RK++ ^ RT0[ ( Y0 ) & 0xFF ] ^ \ RT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ RT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ RT3[ ( Y1 >> 24 ) & 0xFF ]; \ \ X1 = *RK++ ^ RT0[ ( Y1 ) & 0xFF ] ^ \ RT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ RT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ RT3[ ( Y2 >> 24 ) & 0xFF ]; \ \ X2 = *RK++ ^ RT0[ ( Y2 ) & 0xFF ] ^ \ RT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ RT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ RT3[ ( Y3 >> 24 ) & 0xFF ]; \ \ X3 = *RK++ ^ RT0[ ( Y3 ) & 0xFF ] ^ \ RT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ RT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ RT3[ ( Y0 >> 24 ) & 0xFF ]; \ } /* * These macros improve the readability of the key * generation initialization code by collapsing * repetitive common operations into logical pieces. */ #define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 ) #define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) ) #define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 ) #define MIX(x,y) { y = ( (y << 1) | (y >> 7) ) & 0xFF; x ^= y; } #define CPY128 { *RK++ = *SK++; *RK++ = *SK++; \ *RK++ = *SK++; *RK++ = *SK++; } /****************************************************************************** * * AES_INIT_KEYGEN_TABLES * * Fills the AES key expansion tables allocated above with their static * data. This is not "per key" data, but static system-wide read-only * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once * at system initialization to setup the tables for all subsequent use. * ******************************************************************************/ void aes_init_keygen_tables(void) { int i, x, y, z; // general purpose iteration and computation locals int pow[256]; int log[256]; if (aes_tables_inited) return; // fill the 'pow' and 'log' tables over GF(2^8) for (i = 0, x = 1; i < 256; i++) { pow[i] = x; log[x] = i; x = (x ^ XTIME(x)) & 0xFF; } // compute the round constants for (i = 0, x = 1; i < 10; i++) { RCON[i] = (uint32_t)x; x = XTIME(x) & 0xFF; } // fill the forward and reverse substitution boxes FSb[0x00] = 0x63; #if AES_DECRYPTION // whether AES decryption is supported RSb[0x63] = 0x00; #endif /* AES_DECRYPTION */ for (i = 1; i < 256; i++) { x = y = pow[255 - log[i]]; MIX(x, y); MIX(x, y); MIX(x, y); MIX(x, y); FSb[i] = (uchar)(x ^= 0x63); #if AES_DECRYPTION // whether AES decryption is supported RSb[x] = (uchar)i; #endif /* AES_DECRYPTION */ } // generate the forward and reverse key expansion tables for (i = 0; i < 256; i++) { x = FSb[i]; y = XTIME(x) & 0xFF; z = (y ^ x) & 0xFF; FT0[i] = ((uint32_t)y) ^ ((uint32_t)x << 8) ^ ((uint32_t)x << 16) ^ ((uint32_t)z << 24); FT1[i] = ROTL8(FT0[i]); FT2[i] = ROTL8(FT1[i]); FT3[i] = ROTL8(FT2[i]); #if AES_DECRYPTION // whether AES decryption is supported x = RSb[i]; RT0[i] = ((uint32_t)MUL(0x0E, x)) ^ ((uint32_t)MUL(0x09, x) << 8) ^ ((uint32_t)MUL(0x0D, x) << 16) ^ ((uint32_t)MUL(0x0B, x) << 24); RT1[i] = ROTL8(RT0[i]); RT2[i] = ROTL8(RT1[i]); RT3[i] = ROTL8(RT2[i]); #endif /* AES_DECRYPTION */ } aes_tables_inited = 1; // flag that the tables have been generated } // to permit subsequent use of the AES cipher /****************************************************************************** * * AES_SET_ENCRYPTION_KEY * * This is called by 'aes_setkey' when we're establishing a key for * subsequent encryption. We give it a pointer to the encryption * context, a pointer to the key, and the key's length in bytes. * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits). * ******************************************************************************/ int aes_set_encryption_key(aes_context *ctx, const uchar *key, uint keysize) { uint i; // general purpose iteration local uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer for (i = 0; i < (keysize >> 2); i++) { GET_UINT32_LE(RK[i], key, i << 2); } switch (ctx->rounds) { case 10: for (i = 0; i < 10; i++, RK += 4) { RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[3] >> 8) & 0xFF]) ^ ((uint32_t)FSb[(RK[3] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[3] >> 24) & 0xFF] << 16) ^ ((uint32_t)FSb[(RK[3]) & 0xFF] << 24); RK[5] = RK[1] ^ RK[4]; RK[6] = RK[2] ^ RK[5]; RK[7] = RK[3] ^ RK[6]; } break; case 12: for (i = 0; i < 8; i++, RK += 6) { RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[5] >> 8) & 0xFF]) ^ ((uint32_t)FSb[(RK[5] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[5] >> 24) & 0xFF] << 16) ^ ((uint32_t)FSb[(RK[5]) & 0xFF] << 24); RK[7] = RK[1] ^ RK[6]; RK[8] = RK[2] ^ RK[7]; RK[9] = RK[3] ^ RK[8]; RK[10] = RK[4] ^ RK[9]; RK[11] = RK[5] ^ RK[10]; } break; case 14: for (i = 0; i < 7; i++, RK += 8) { RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[7] >> 8) & 0xFF]) ^ ((uint32_t)FSb[(RK[7] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[7] >> 24) & 0xFF] << 16) ^ ((uint32_t)FSb[(RK[7]) & 0xFF] << 24); RK[9] = RK[1] ^ RK[8]; RK[10] = RK[2] ^ RK[9]; RK[11] = RK[3] ^ RK[10]; RK[12] = RK[4] ^ ((uint32_t)FSb[(RK[11]) & 0xFF]) ^ ((uint32_t)FSb[(RK[11] >> 8) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[11] >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(RK[11] >> 24) & 0xFF] << 24); RK[13] = RK[5] ^ RK[12]; RK[14] = RK[6] ^ RK[13]; RK[15] = RK[7] ^ RK[14]; } break; default: return -1; } return(0); } #if AES_DECRYPTION // whether AES decryption is supported /****************************************************************************** * * AES_SET_DECRYPTION_KEY * * This is called by 'aes_setkey' when we're establishing a * key for subsequent decryption. We give it a pointer to * the encryption context, a pointer to the key, and the key's * length in bits. Valid lengths are: 128, 192, or 256 bits. * ******************************************************************************/ int aes_set_decryption_key(aes_context *ctx, const uchar *key, uint keysize) { int i, j; aes_context cty; // a calling aes context for set_encryption_key uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer uint32_t *SK; int ret; cty.rounds = ctx->rounds; // initialize our local aes context cty.rk = cty.buf; // round count and key buf pointer if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0) return(ret); SK = cty.rk + cty.rounds * 4; CPY128 // copy a 128-bit block from *SK to *RK for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) { for (j = 0; j < 4; j++, SK++) { *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^ RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]]; } } CPY128 // copy a 128-bit block from *SK to *RK memset(&cty, 0, sizeof(aes_context)); // clear local aes context return(0); } #endif /* AES_DECRYPTION */ /****************************************************************************** * * AES_SETKEY * * Invoked to establish the key schedule for subsequent encryption/decryption * ******************************************************************************/ int aes_setkey(aes_context *ctx, // AES context provided by our caller int mode, // ENCRYPT or DECRYPT flag const uchar *key, // pointer to the key uint keysize) // key length in bytes { // since table initialization is not thread safe, we could either add // system-specific mutexes and init the AES key generation tables on // demand, or ask the developer to simply call "gcm_initialize" once during // application startup before threading begins. That's what we choose. if (!aes_tables_inited) return (-1); // fail the call when not inited. ctx->mode = mode; // capture the key type we're creating ctx->rk = ctx->buf; // initialize our round key pointer switch (keysize) // set the rounds count based upon the keysize { case 16: ctx->rounds = 10; break; // 16-byte, 128-bit key case 24: ctx->rounds = 12; break; // 24-byte, 192-bit key case 32: ctx->rounds = 14; break; // 32-byte, 256-bit key default: return(-1); } #if AES_DECRYPTION if (mode == AES_DECRYPT) // expand our key for encryption or decryption return(aes_set_decryption_key(ctx, key, keysize)); else /* ENCRYPT */ #endif /* AES_DECRYPTION */ return(aes_set_encryption_key(ctx, key, keysize)); } /****************************************************************************** * * AES_CIPHER * * Perform AES encryption and decryption. * The AES context will have been setup with the encryption mode * and all keying information appropriate for the task. * ******************************************************************************/ int aes_cipher(aes_context *ctx, const uchar input[16], uchar output[16]) { int i; uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals RK = ctx->rk; GET_UINT32_LE(X0, input, 0); X0 ^= *RK++; // load our 128-bit GET_UINT32_LE(X1, input, 4); X1 ^= *RK++; // input buffer in a storage GET_UINT32_LE(X2, input, 8); X2 ^= *RK++; // memory endian-neutral way GET_UINT32_LE(X3, input, 12); X3 ^= *RK++; #if AES_DECRYPTION // whether AES decryption is supported if (ctx->mode == AES_DECRYPT) { for (i = (ctx->rounds >> 1) - 1; i > 0; i--) { AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); } AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); X0 = *RK++ ^ \ ((uint32_t)RSb[(Y0) & 0xFF]) ^ ((uint32_t)RSb[(Y3 >> 8) & 0xFF] << 8) ^ ((uint32_t)RSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y1 >> 24) & 0xFF] << 24); X1 = *RK++ ^ \ ((uint32_t)RSb[(Y1) & 0xFF]) ^ ((uint32_t)RSb[(Y0 >> 8) & 0xFF] << 8) ^ ((uint32_t)RSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y2 >> 24) & 0xFF] << 24); X2 = *RK++ ^ \ ((uint32_t)RSb[(Y2) & 0xFF]) ^ ((uint32_t)RSb[(Y1 >> 8) & 0xFF] << 8) ^ ((uint32_t)RSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y3 >> 24) & 0xFF] << 24); X3 = *RK++ ^ \ ((uint32_t)RSb[(Y3) & 0xFF]) ^ ((uint32_t)RSb[(Y2 >> 8) & 0xFF] << 8) ^ ((uint32_t)RSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y0 >> 24) & 0xFF] << 24); } else /* ENCRYPT */ { #endif /* AES_DECRYPTION */ for (i = (ctx->rounds >> 1) - 1; i > 0; i--) { AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3); } AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); X0 = *RK++ ^ \ ((uint32_t)FSb[(Y0) & 0xFF]) ^ ((uint32_t)FSb[(Y1 >> 8) & 0xFF] << 8) ^ ((uint32_t)FSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y3 >> 24) & 0xFF] << 24); X1 = *RK++ ^ \ ((uint32_t)FSb[(Y1) & 0xFF]) ^ ((uint32_t)FSb[(Y2 >> 8) & 0xFF] << 8) ^ ((uint32_t)FSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y0 >> 24) & 0xFF] << 24); X2 = *RK++ ^ \ ((uint32_t)FSb[(Y2) & 0xFF]) ^ ((uint32_t)FSb[(Y3 >> 8) & 0xFF] << 8) ^ ((uint32_t)FSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y1 >> 24) & 0xFF] << 24); X3 = *RK++ ^ \ ((uint32_t)FSb[(Y3) & 0xFF]) ^ ((uint32_t)FSb[(Y0 >> 8) & 0xFF] << 8) ^ ((uint32_t)FSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y2 >> 24) & 0xFF] << 24); #if AES_DECRYPTION // whether AES decryption is supported } #endif /* AES_DECRYPTION */ PUT_UINT32_LE(X0, output, 0); PUT_UINT32_LE(X1, output, 4); PUT_UINT32_LE(X2, output, 8); PUT_UINT32_LE(X3, output, 12); return(0); } /* end of aes.c */ ================================================ FILE: nfq/crypto/aes.h ================================================ /****************************************************************************** * * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL * * This is a simple and straightforward implementation of the AES Rijndael * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus * of this work was correctness & accuracy. It is written in 'C' without any * particular focus upon optimization or speed. It should be endian (memory * byte order) neutral since the few places that care are handled explicitly. * * This implementation of Rijndael was created by Steven M. Gibson of GRC.com. * * It is intended for general purpose use, but was written in support of GRC's * reference implementation of the SQRL (Secure Quick Reliable Login) client. * * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html * * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. * *******************************************************************************/ #pragma once /******************************************************************************/ #define AES_DECRYPTION 0 // whether AES decryption is supported /******************************************************************************/ #include #define AES_ENCRYPT 1 // specify whether we're encrypting #define AES_DECRYPT 0 // or decrypting #if defined(_MSC_VER) #include typedef UINT32 uint32_t; #else #include #endif typedef unsigned char uchar; // add some convienent shorter types typedef unsigned int uint; /****************************************************************************** * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use ******************************************************************************/ void aes_init_keygen_tables(void); /****************************************************************************** * AES_CONTEXT : cipher context / holds inter-call data ******************************************************************************/ typedef struct { int mode; // 1 for Encryption, 0 for Decryption int rounds; // keysize-based rounds count uint32_t *rk; // pointer to current round key uint32_t buf[68]; // key expansion buffer } aes_context; /****************************************************************************** * AES_SETKEY : called to expand the key for encryption or decryption ******************************************************************************/ int aes_setkey(aes_context *ctx, // pointer to context int mode, // 1 or 0 for Encrypt/Decrypt const uchar *key, // AES input key uint keysize); // size in bytes (must be 16, 24, 32 for // 128, 192 or 256-bit keys respectively) // returns 0 for success /****************************************************************************** * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data ******************************************************************************/ int aes_cipher(aes_context *ctx, // pointer to context const uchar input[16], // 128-bit block to en/decipher uchar output[16]); // 128-bit output result block // returns 0 for success ================================================ FILE: nfq/crypto/gcm.c ================================================ /****************************************************************************** * * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL * * This is a simple and straightforward implementation of AES-GCM authenticated * encryption. The focus of this work was correctness & accuracy. It is written * in straight 'C' without any particular focus upon optimization or speed. It * should be endian (memory byte order) neutral since the few places that care * are handled explicitly. * * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. * * It is intended for general purpose use, but was written in support of GRC's * reference implementation of the SQRL (Secure Quick Reliable Login) client. * * See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ * gcm/gcm-revised-spec.pdf * * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. * *******************************************************************************/ #include "gcm.h" #include "aes.h" /****************************************************************************** * ==== IMPLEMENTATION WARNING ==== * * This code was developed for use within SQRL's fixed environmnent. Thus, it * is somewhat less "general purpose" than it would be if it were designed as * a general purpose AES-GCM library. Specifically, it bothers with almost NO * error checking on parameter limits, buffer bounds, etc. It assumes that it * is being invoked by its author or by someone who understands the values it * expects to receive. Its behavior will be undefined otherwise. * * All functions that might fail are defined to return 'ints' to indicate a * problem. Most do not do so now. But this allows for error propagation out * of internal functions if robust error checking should ever be desired. * ******************************************************************************/ /* Calculating the "GHASH" * * There are many ways of calculating the so-called GHASH in software, each with * a traditional size vs performance tradeoff. The GHASH (Galois field hash) is * an intriguing construction which takes two 128-bit strings (also the cipher's * block size and the fundamental operation size for the system) and hashes them * into a third 128-bit result. * * Many implementation solutions have been worked out that use large precomputed * table lookups in place of more time consuming bit fiddling, and this approach * can be scaled easily upward or downward as needed to change the time/space * tradeoff. It's been studied extensively and there's a solid body of theory and * practice. For example, without using any lookup tables an implementation * might obtain 119 cycles per byte throughput, whereas using a simple, though * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13 * cycles per byte. * * And Intel's processors have, since 2010, included an instruction which does * the entire 128x128->128 bit job in just several 64x64->128 bit pieces. * * Since SQRL is interactive, and only processing a few 128-bit blocks, I've * settled upon a relatively slower but appealing small-table compromise which * folds a bunch of not only time consuming but also bit twiddling into a simple * 16-entry table which is attributed to Victor Shoup's 1996 work while at * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf * See, also section 4.1 of the "gcm-revised-spec" cited above. */ /* * This 16-entry table of pre-computed constants is used by the * GHASH multiplier to improve over a strictly table-free but * significantly slower 128x128 bit multiple within GF(2^128). */ static const uint64_t last4[16] = { 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 }; /* * Platform Endianness Neutralizing Load and Store Macro definitions * GCM wants platform-neutral Big Endian (BE) byte ordering */ #define GET_UINT32_BE(n,b,i) { \ (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ | ( (uint32_t) (b)[(i) + 1] << 16 ) \ | ( (uint32_t) (b)[(i) + 2] << 8 ) \ | ( (uint32_t) (b)[(i) + 3] ); } #define PUT_UINT32_BE(n,b,i) { \ (b)[(i) ] = (uchar) ( (n) >> 24 ); \ (b)[(i) + 1] = (uchar) ( (n) >> 16 ); \ (b)[(i) + 2] = (uchar) ( (n) >> 8 ); \ (b)[(i) + 3] = (uchar) ( (n) ); } /****************************************************************************** * * GCM_INITIALIZE * * Must be called once to initialize the GCM library. * * At present, this only calls the AES keygen table generator, which expands * the AES keying tables for use. This is NOT A THREAD-SAFE function, so it * MUST be called during system initialization before a multi-threading * environment is running. * ******************************************************************************/ int gcm_initialize(void) { aes_init_keygen_tables(); return(0); } /****************************************************************************** * * GCM_MULT * * Performs a GHASH operation on the 128-bit input vector 'x', setting * the 128-bit output vector to 'x' times H using our precomputed tables. * 'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field. * ******************************************************************************/ static void gcm_mult(gcm_context *ctx, // pointer to established context const uchar x[16], // pointer to 128-bit input vector uchar output[16]) // pointer to 128-bit output vector { int i; uchar lo, hi, rem; uint64_t zh, zl; lo = (uchar)(x[15] & 0x0f); hi = (uchar)(x[15] >> 4); zh = ctx->HH[lo]; zl = ctx->HL[lo]; for (i = 15; i >= 0; i--) { lo = (uchar)(x[i] & 0x0f); hi = (uchar)(x[i] >> 4); if (i != 15) { rem = (uchar)(zl & 0x0f); zl = (zh << 60) | (zl >> 4); zh = (zh >> 4); zh ^= (uint64_t)last4[rem] << 48; zh ^= ctx->HH[lo]; zl ^= ctx->HL[lo]; } rem = (uchar)(zl & 0x0f); zl = (zh << 60) | (zl >> 4); zh = (zh >> 4); zh ^= (uint64_t)last4[rem] << 48; zh ^= ctx->HH[hi]; zl ^= ctx->HL[hi]; } PUT_UINT32_BE(zh >> 32, output, 0); PUT_UINT32_BE(zh, output, 4); PUT_UINT32_BE(zl >> 32, output, 8); PUT_UINT32_BE(zl, output, 12); } /****************************************************************************** * * GCM_SETKEY * * This is called to set the AES-GCM key. It initializes the AES key * and populates the gcm context's pre-calculated HTables. * ******************************************************************************/ int gcm_setkey(gcm_context *ctx, // pointer to caller-provided gcm context const uchar *key, // pointer to the AES encryption key const uint keysize) // size in bytes (must be 16, 24, 32 for // 128, 192 or 256-bit keys respectively) { int ret, i, j; uint64_t hi, lo; uint64_t vl, vh; unsigned char h[16]; memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided GCM context memset(h, 0, 16); // initialize the block to encrypt // encrypt the null 128-bit block to generate a key-based value // which is then used to initialize our GHASH lookup tables if ((ret = aes_setkey(&ctx->aes_ctx, AES_ENCRYPT, key, keysize)) != 0) return(ret); if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return(ret); GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian GET_UINT32_BE(lo, h, 4); vh = (uint64_t)hi << 32 | lo; GET_UINT32_BE(hi, h, 8); GET_UINT32_BE(lo, h, 12); vl = (uint64_t)hi << 32 | lo; ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128) ctx->HH[8] = vh; ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128) ctx->HL[0] = 0; for (i = 4; i > 0; i >>= 1) { uint32_t T = (uint32_t)(vl & 1) * 0xe1000000U; vl = (vh << 63) | (vl >> 1); vh = (vh >> 1) ^ ((uint64_t)T << 32); ctx->HL[i] = vl; ctx->HH[i] = vh; } for (i = 2; i < 16; i <<= 1) { uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i; vh = *HiH; vl = *HiL; for (j = 1; j < i; j++) { HiH[j] = vh ^ ctx->HH[j]; HiL[j] = vl ^ ctx->HL[j]; } } return(0); } /****************************************************************************** * * GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH. * * SETKEY: * * START: Sets the Encryption/Decryption mode. * Accepts the initialization vector and additional data. * * UPDATE: Encrypts or decrypts the plaintext or ciphertext. * * FINISH: Performs a final GHASH to generate the authentication tag. * ****************************************************************************** * * GCM_START * * Given a user-provided GCM context, this initializes it, sets the encryption * mode, and preprocesses the initialization vector and additional AEAD data. * ******************************************************************************/ int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context int mode, // AES_ENCRYPT or AES_DECRYPT const uchar *iv, // pointer to initialization vector size_t iv_len, // IV length in bytes (should == 12) const uchar *add, // ptr to additional AEAD data (NULL if none) size_t add_len) // length of additional AEAD data (bytes) { int ret; // our error return if the AES encrypt fails uchar work_buf[16]; // XOR source built from provided IV if len != 16 const uchar *p; // general purpose array pointer size_t use_len; // byte count to process, up to 16 bytes size_t i; // local loop iterator // since the context might be reused under the same key // we zero the working buffers for this next new process memset(ctx->y, 0x00, sizeof(ctx->y)); memset(ctx->buf, 0x00, sizeof(ctx->buf)); ctx->len = 0; ctx->add_len = 0; ctx->mode = mode; // set the GCM encryption/decryption mode ctx->aes_ctx.mode = AES_ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the 'y' buff ctx->y[15] = 1; // start "counting" from 1 (not 0) } else // if we don't have a 12-byte IV, we GHASH whatever we've been given { memset(work_buf, 0x00, 16); // clear the working buffer PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into buffer p = iv; while (iv_len > 0) { use_len = (iv_len < 16) ? iv_len : 16; for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i]; gcm_mult(ctx, ctx->y, ctx->y); iv_len -= use_len; p += use_len; } for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i]; gcm_mult(ctx, ctx->y, ctx->y); } if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0) return(ret); ctx->add_len = add_len; p = add; while (add_len > 0) { use_len = (add_len < 16) ? add_len : 16; for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i]; gcm_mult(ctx, ctx->buf, ctx->buf); add_len -= use_len; p += use_len; } return(0); } /****************************************************************************** * * GCM_UPDATE * * This is called once or more to process bulk plaintext or ciphertext data. * We give this some number of bytes of input and it returns the same number * of output bytes. If called multiple times (which is fine) all but the final * invocation MUST be called with length mod 16 == 0. (Only the final call can * have a partial block length of < 128 bits.) * ******************************************************************************/ int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context size_t length, // length, in bytes, of data to process const uchar *input, // pointer to source data uchar *output) // pointer to destination data { int ret; // our error return if the AES encrypt fails uchar ectr[16]; // counter-mode cipher output for XORing size_t use_len; // byte count to process, up to 16 bytes size_t i; // local loop iterator ctx->len += length; // bump the GCM context's running length count while (length > 0) { // clamp the length to process at 16 bytes use_len = (length < 16) ? length : 16; // increment the context's 128-bit IV||Counter 'y' vector for (i = 16; i > 12; i--) if (++ctx->y[i - 1] != 0) break; // encrypt the context's 'y' vector under the established key if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0) return(ret); // encrypt or decrypt the input to the output if (ctx->mode == AES_ENCRYPT) { for (i = 0; i < use_len; i++) { // XOR the cipher's ouptut vector (ectr) with our input output[i] = (uchar)(ectr[i] ^ input[i]); // now we mix in our data into the authentication hash. // if we're ENcrypting we XOR in the post-XOR (output) // results, but if we're DEcrypting we XOR in the input // data ctx->buf[i] ^= output[i]; } } else { for (i = 0; i < use_len; i++) { // but if we're DEcrypting we XOR in the input data first, // i.e. before saving to ouput data, otherwise if the input // and output buffer are the same (inplace decryption) we // would not get the correct auth tag ctx->buf[i] ^= input[i]; // XOR the cipher's ouptut vector (ectr) with our input output[i] = (uchar)(ectr[i] ^ input[i]); } } gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation length -= use_len; // drop the remaining byte count to process input += use_len; // bump our input pointer forward output += use_len; // bump our output pointer forward } return(0); } /****************************************************************************** * * GCM_FINISH * * This is called once after all calls to GCM_UPDATE to finalize the GCM. * It performs the final GHASH to produce the resulting authentication TAG. * ******************************************************************************/ int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context uchar *tag, // pointer to buffer which receives the tag size_t tag_len) // length, in bytes, of the tag-receiving buf { uchar work_buf[16]; uint64_t orig_len = ctx->len * 8; uint64_t orig_add_len = ctx->add_len * 8; size_t i; if (tag_len>16) return -1; if (tag_len) memcpy(tag, ctx->base_ectr, tag_len); if (orig_len || orig_add_len) { memset(work_buf, 0x00, 16); PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0); PUT_UINT32_BE((orig_add_len), work_buf, 4); PUT_UINT32_BE((orig_len >> 32), work_buf, 8); PUT_UINT32_BE((orig_len), work_buf, 12); for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i]; gcm_mult(ctx, ctx->buf, ctx->buf); for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i]; } return(0); } /****************************************************************************** * * GCM_CRYPT_AND_TAG * * This either encrypts or decrypts the user-provided data and, either * way, generates an authentication tag of the requested length. It must be * called with a GCM context whose key has already been set with GCM_SETKEY. * * The user would typically call this explicitly to ENCRYPT a buffer of data * and optional associated data, and produce its an authentication tag. * * To reverse the process the user would typically call the companion * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided * authentication tag. The GCM_AUTH_DECRYPT function calls this function * to perform its decryption and tag generation, which it then compares. * ******************************************************************************/ int gcm_crypt_and_tag( gcm_context *ctx, // gcm context with key already setup int mode, // cipher direction: AES_ENCRYPT or AES_DECRYPT const uchar *iv, // pointer to the 12-byte initialization vector size_t iv_len, // byte length if the IV. should always be 12 const uchar *add, // pointer to the non-ciphered additional data size_t add_len, // byte length of the additional AEAD data const uchar *input, // pointer to the cipher data source uchar *output, // pointer to the cipher data destination size_t length, // byte length of the cipher data uchar *tag, // pointer to the tag to be generated size_t tag_len) // byte length of the tag to be generated { /* assuming that the caller has already invoked gcm_setkey to prepare the gcm context with the keying material, we simply invoke each of the three GCM sub-functions in turn... */ if (tag_len>16) return -1; int ret; if ((ret=gcm_start(ctx, mode, iv, iv_len, add, add_len))) return ret; if ((ret=gcm_update(ctx, length, input, output))) return ret; return gcm_finish(ctx, tag, tag_len); } /****************************************************************************** * * GCM_AUTH_DECRYPT * * This DECRYPTS a user-provided data buffer with optional associated data. * It then verifies a user-supplied authentication tag against the tag just * re-created during decryption to verify that the data has not been altered. * * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption * and authentication tag generation. * ******************************************************************************/ int gcm_auth_decrypt( gcm_context *ctx, // gcm context with key already setup const uchar *iv, // pointer to the 12-byte initialization vector size_t iv_len, // byte length if the IV. should always be 12 const uchar *add, // pointer to the non-ciphered additional data size_t add_len, // byte length of the additional AEAD data const uchar *input, // pointer to the cipher data source uchar *output, // pointer to the cipher data destination size_t length, // byte length of the cipher data const uchar *tag, // pointer to the tag to be authenticated size_t tag_len) // byte length of the tag <= 16 { uchar check_tag[16]; // the tag generated and returned by decryption int diff; // an ORed flag to detect authentication errors size_t i; // our local iterator int ret; if (tag_len>16) return -1; /* we use GCM_DECRYPT_AND_TAG (above) to perform our decryption (which is an identical XORing to reverse the previous one) and also to re-generate the matching authentication tag */ if ((ret = gcm_crypt_and_tag(ctx, AES_DECRYPT, iv, iv_len, add, add_len, input, output, length, check_tag, tag_len))) return ret; // now we verify the authentication tag in 'constant time' for (diff = 0, i = 0; i < tag_len; i++) diff |= tag[i] ^ check_tag[i]; if (diff) { // see whether any bits differed? memset(output, 0, length); // if so... wipe the output data return(GCM_AUTH_FAILURE); // return GCM_AUTH_FAILURE } return 0; } /****************************************************************************** * * GCM_ZERO_CTX * * The GCM context contains both the GCM context and the AES context. * This includes keying and key-related material which is security- * sensitive, so it MUST be zeroed after use. This function does that. * ******************************************************************************/ void gcm_zero_ctx(gcm_context *ctx) { // zero the context originally provided to us memset(ctx, 0, sizeof(gcm_context)); } ================================================ FILE: nfq/crypto/gcm.h ================================================ /****************************************************************************** * * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL * * This is a simple and straightforward implementation of AES-GCM authenticated * encryption. The focus of this work was correctness & accuracy. It is written * in straight 'C' without any particular focus upon optimization or speed. It * should be endian (memory byte order) neutral since the few places that care * are handled explicitly. * * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. * * It is intended for general purpose use, but was written in support of GRC's * reference implementation of the SQRL (Secure Quick Reliable Login) client. * * See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \ * gcm/gcm-revised-spec.pdf * * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. * *******************************************************************************/ #pragma once #define GCM_AUTH_FAILURE 0x55555555 // authentication failure #include "aes.h" // gcm_context includes aes_context #if defined(_MSC_VER) #include typedef UINT32 uint32_t; typedef UINT64 uint64_t; #else #include #endif /****************************************************************************** * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx ******************************************************************************/ typedef struct { int mode; // cipher direction: encrypt/decrypt uint64_t len; // cipher data length processed so far uint64_t add_len; // total add data length uint64_t HL[16]; // precalculated lo-half HTable uint64_t HH[16]; // precalculated hi-half HTable uchar base_ectr[16]; // first counter-mode cipher output for tag uchar y[16]; // the current cipher-input IV|Counter value uchar buf[16]; // buf working value aes_context aes_ctx; // cipher context used } gcm_context; /****************************************************************************** * GCM_CONTEXT : MUST be called once before ANY use of this library ******************************************************************************/ int gcm_initialize(void); /****************************************************************************** * GCM_SETKEY : sets the GCM (and AES) keying material for use ******************************************************************************/ int gcm_setkey(gcm_context *ctx, // caller-provided context ptr const uchar *key, // pointer to cipher key const uint keysize // size in bytes (must be 16, 24, 32 for // 128, 192 or 256-bit keys respectively) ); // returns 0 for success /****************************************************************************** * * GCM_CRYPT_AND_TAG * * This either encrypts or decrypts the user-provided data and, either * way, generates an authentication tag of the requested length. It must be * called with a GCM context whose key has already been set with GCM_SETKEY. * * The user would typically call this explicitly to ENCRYPT a buffer of data * and optional associated data, and produce its an authentication tag. * * To reverse the process the user would typically call the companion * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided * authentication tag. The GCM_AUTH_DECRYPT function calls this function * to perform its decryption and tag generation, which it then compares. * ******************************************************************************/ int gcm_crypt_and_tag( gcm_context *ctx, // gcm context with key already setup int mode, // cipher direction: ENCRYPT (1) or DECRYPT (0) const uchar *iv, // pointer to the 12-byte initialization vector size_t iv_len, // byte length if the IV. should always be 12 const uchar *add, // pointer to the non-ciphered additional data size_t add_len, // byte length of the additional AEAD data const uchar *input, // pointer to the cipher data source uchar *output, // pointer to the cipher data destination size_t length, // byte length of the cipher data uchar *tag, // pointer to the tag to be generated size_t tag_len); // byte length of the tag to be generated /****************************************************************************** * * GCM_AUTH_DECRYPT * * This DECRYPTS a user-provided data buffer with optional associated data. * It then verifies a user-supplied authentication tag against the tag just * re-created during decryption to verify that the data has not been altered. * * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption * and authentication tag generation. * ******************************************************************************/ int gcm_auth_decrypt( gcm_context *ctx, // gcm context with key already setup const uchar *iv, // pointer to the 12-byte initialization vector size_t iv_len, // byte length if the IV. should always be 12 const uchar *add, // pointer to the non-ciphered additional data size_t add_len, // byte length of the additional AEAD data const uchar *input, // pointer to the cipher data source uchar *output, // pointer to the cipher data destination size_t length, // byte length of the cipher data const uchar *tag, // pointer to the tag to be authenticated size_t tag_len); // byte length of the tag <= 16 /****************************************************************************** * * GCM_START * * Given a user-provided GCM context, this initializes it, sets the encryption * mode, and preprocesses the initialization vector and additional AEAD data. * ******************************************************************************/ int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context int mode, // ENCRYPT (1) or DECRYPT (0) const uchar *iv, // pointer to initialization vector size_t iv_len, // IV length in bytes (should == 12) const uchar *add, // pointer to additional AEAD data (NULL if none) size_t add_len); // length of additional AEAD data (bytes) /****************************************************************************** * * GCM_UPDATE * * This is called once or more to process bulk plaintext or ciphertext data. * We give this some number of bytes of input and it returns the same number * of output bytes. If called multiple times (which is fine) all but the final * invocation MUST be called with length mod 16 == 0. (Only the final call can * have a partial block length of < 128 bits.) * ******************************************************************************/ int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context size_t length, // length, in bytes, of data to process const uchar *input, // pointer to source data uchar *output); // pointer to destination data /****************************************************************************** * * GCM_FINISH * * This is called once after all calls to GCM_UPDATE to finalize the GCM. * It performs the final GHASH to produce the resulting authentication TAG. * ******************************************************************************/ int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 size_t tag_len); // length, in bytes, of the tag-receiving buf /****************************************************************************** * * GCM_ZERO_CTX * * The GCM context contains both the GCM context and the AES context. * This includes keying and key-related material which is security- * sensitive, so it MUST be zeroed after use. This function does that. * ******************************************************************************/ void gcm_zero_ctx(gcm_context *ctx); ================================================ FILE: nfq/crypto/hkdf.c ================================================ /**************************** hkdf.c ***************************/ /***************** See RFC 6234 for details. *******************/ /* Copyright (c) 2011 IETF Trust and the persons identified as */ /* authors of the code. All rights reserved. */ /* See sha.h for terms of use and redistribution. */ /* * Description: * This file implements the HKDF algorithm (HMAC-based * Extract-and-Expand Key Derivation Function, RFC 5869), * expressed in terms of the various SHA algorithms. */ #include "sha.h" #include #include /* * hkdf * * Description: * This function will generate keying material using HKDF. * * Parameters: * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * salt[ ]: [in] * The optional salt value (a non-secret random value); * if not provided (salt == NULL), it is set internally * to a string of HashLen(whichSha) zeros. * salt_len: [in] * The length of the salt value. (Ignored if salt == NULL.) * ikm[ ]: [in] * Input keying material. * ikm_len: [in] * The length of the input keying material. * info[ ]: [in] * The optional context and application specific information. * If info == NULL or a zero-length string, it is ignored. * info_len: [in] * The length of the optional context and application specific * information. (Ignored if info == NULL.) * okm[ ]: [out] * Where the HKDF is to be stored. * okm_len: [in] * The length of the buffer to hold okm. * okm_len must be <= 255 * USHABlockSize(whichSha) * * Notes: * Calls hkdfExtract() and hkdfExpand(). * * Returns: * sha Error Code. * */ int hkdf(SHAversion whichSha, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *info, size_t info_len, uint8_t okm[], size_t okm_len) { uint8_t prk[USHAMaxHashSize]; int ret; if ((ret=hkdfExtract(whichSha, salt, salt_len, ikm, ikm_len, prk))) return ret; return hkdfExpand(whichSha, prk, USHAHashSize(whichSha), info, info_len, okm, okm_len); } /* * hkdfExtract * * Description: * This function will perform HKDF extraction. * * Parameters: * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * salt[ ]: [in] * The optional salt value (a non-secret random value); * if not provided (salt == NULL), it is set internally * to a string of HashLen(whichSha) zeros. * salt_len: [in] * The length of the salt value. (Ignored if salt == NULL.) * ikm[ ]: [in] * Input keying material. * ikm_len: [in] * The length of the input keying material. * prk[ ]: [out] * Array where the HKDF extraction is to be stored. * Must be larger than USHAHashSize(whichSha); * * Returns: * sha Error Code. * */ int hkdfExtract(SHAversion whichSha, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, uint8_t prk[USHAMaxHashSize]) { unsigned char nullSalt[USHAMaxHashSize]; if (salt == 0) { salt = nullSalt; salt_len = USHAHashSize(whichSha); memset(nullSalt, '\0', salt_len); } return hmac(whichSha, ikm, ikm_len, salt, salt_len, prk); } /* * hkdfExpand * * Description: * This function will perform HKDF expansion. * * Parameters: * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * prk[ ]: [in] * The pseudo-random key to be expanded; either obtained * directly from a cryptographically strong, uniformly * distributed pseudo-random number generator, or as the * output from hkdfExtract(). * prk_len: [in] * The length of the pseudo-random key in prk; * should at least be equal to USHAHashSize(whichSHA). * info[ ]: [in] * The optional context and application specific information. * If info == NULL or a zero-length string, it is ignored. * info_len: [in] * The length of the optional context and application specific * information. (Ignored if info == NULL.) * okm[ ]: [out] * Where the HKDF is to be stored. * okm_len: [in] * The length of the buffer to hold okm. * okm_len must be <= 255 * USHABlockSize(whichSha) * * Returns: * sha Error Code. * */ int hkdfExpand(SHAversion whichSha, const uint8_t prk[], size_t prk_len, const unsigned char *info, size_t info_len, uint8_t okm[], size_t okm_len) { size_t hash_len, N; unsigned char T[USHAMaxHashSize]; size_t Tlen, where, i; int ret; if (info == 0) { info = (const unsigned char *)""; info_len = 0; } if (!okm || !okm_len) return shaBadParam; hash_len = USHAHashSize(whichSha); if (prk_len < hash_len) return shaBadParam; N = okm_len / hash_len; if ((okm_len % hash_len) != 0) N++; if (N > 255) return shaBadParam; Tlen = 0; where = 0; for (i = 1; i <= N; i++) { HMACContext context; unsigned char c = i; if ((ret=hmacReset(&context, whichSha, prk, prk_len))) return ret; if ((ret=hmacInput(&context, T, Tlen))) return ret; if ((ret=hmacInput(&context, info, info_len))) return ret; if ((ret=hmacInput(&context, &c, 1))) return ret; if ((ret=hmacResult(&context, T))) return ret; memcpy(okm + where, T, (i != N) ? hash_len : (okm_len - where)); where += hash_len; Tlen = hash_len; } return shaSuccess; } /* * hkdfReset * * Description: * This function will initialize the hkdfContext in preparation * for key derivation using the modular HKDF interface for * arbitrary length inputs. * * Parameters: * context: [in/out] * The context to reset. * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * salt[ ]: [in] * The optional salt value (a non-secret random value); * if not provided (salt == NULL), it is set internally * to a string of HashLen(whichSha) zeros. * salt_len: [in] * The length of the salt value. (Ignored if salt == NULL.) * * Returns: * sha Error Code. * */ int hkdfReset(HKDFContext *context, enum SHAversion whichSha, const unsigned char *salt, size_t salt_len) { unsigned char nullSalt[USHAMaxHashSize]; if (!context) return shaNull; context->whichSha = whichSha; context->hashSize = USHAHashSize(whichSha); if (salt == 0) { salt = nullSalt; salt_len = context->hashSize; memset(nullSalt, '\0', salt_len); } return hmacReset(&context->hmacContext, whichSha, salt, salt_len); } /* * hkdfInput * * Description: * This function accepts an array of octets as the next portion * of the input keying material. It may be called multiple times. * * Parameters: * context: [in/out] * The HKDF context to update. * ikm[ ]: [in] * An array of octets representing the next portion of * the input keying material. * ikm_len: [in] * The length of ikm. * * Returns: * sha Error Code. * */ int hkdfInput(HKDFContext *context, const unsigned char *ikm, size_t ikm_len) { if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; return hmacInput(&context->hmacContext, ikm, ikm_len); } /* * hkdfFinalBits * * Description: * This function will add in any final bits of the * input keying material. * * Parameters: * context: [in/out] * The HKDF context to update * ikm_bits: [in] * The final bits of the input keying material, in the upper * portion of the byte. (Use 0b###00000 instead of 0b00000### * to input the three bits ###.) * ikm_bit_count: [in] * The number of bits in message_bits, between 1 and 7. * * Returns: * sha Error Code. */ int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits, unsigned int ikm_bit_count) { if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; return hmacFinalBits(&context->hmacContext, ikm_bits, ikm_bit_count); } /* * hkdfResult * * Description: * This function will finish the HKDF extraction and perform the * final HKDF expansion. * * Parameters: * context: [in/out] * The HKDF context to use to calculate the HKDF hash. * prk[ ]: [out] * An optional location to store the HKDF extraction. * Either NULL, or pointer to a buffer that must be * larger than USHAHashSize(whichSha); * info[ ]: [in] * The optional context and application specific information. * If info == NULL or a zero-length string, it is ignored. * info_len: [in] * The length of the optional context and application specific * information. (Ignored if info == NULL.) * okm[ ]: [out] * Where the HKDF is to be stored. * okm_len: [in] * The length of the buffer to hold okm. * okm_len must be <= 255 * USHABlockSize(whichSha) * * Returns: * sha Error Code. * */ int hkdfResult(HKDFContext *context, uint8_t prk[USHAMaxHashSize], const unsigned char *info, size_t info_len, uint8_t okm[], size_t okm_len) { uint8_t prkbuf[USHAMaxHashSize]; int ret; if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; if (!okm) return context->Corrupted = shaBadParam; if (!prk) prk = prkbuf; if (!(ret = hmacResult(&context->hmacContext, prk))) ret = hkdfExpand(context->whichSha, prk, context->hashSize, info, info_len, okm, okm_len); context->Computed = 1; return context->Corrupted = ret; } ================================================ FILE: nfq/crypto/hmac.c ================================================ /**************************** hmac.c ***************************/ /***************** See RFC 6234 for details. *******************/ /* Copyright (c) 2011 IETF Trust and the persons identified as */ /* authors of the code. All rights reserved. */ /* See sha.h for terms of use and redistribution. */ /* * Description: * This file implements the HMAC algorithm (Keyed-Hashing for * Message Authentication, [RFC 2104]), expressed in terms of * the various SHA algorithms. */ #include "sha.h" #include /* * hmac * * Description: * This function will compute an HMAC message digest. * * Parameters: * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * message_array[ ]: [in] * An array of octets representing the message. * Note: in RFC 2104, this parameter is known * as 'text'. * length: [in] * The length of the message in message_array. * key[ ]: [in] * The secret shared key. * key_len: [in] * The length of the secret shared key. * digest[ ]: [out] * Where the digest is to be returned. * NOTE: The length of the digest is determined by * the value of whichSha. * * Returns: * sha Error Code. * */ int hmac(SHAversion whichSha, const unsigned char *message_array, size_t length, const unsigned char *key, size_t key_len, uint8_t digest[USHAMaxHashSize]) { HMACContext context; int ret; if ((ret=hmacReset(&context, whichSha, key, key_len))) return ret; if ((ret=hmacInput(&context, message_array, length))) return ret; return hmacResult(&context, digest); } /* * hmacReset * * Description: * This function will initialize the hmacContext in preparation * for computing a new HMAC message digest. * * Parameters: * context: [in/out] * The context to reset. * whichSha: [in] * One of SHA1, SHA224, SHA256, SHA384, SHA512 * key[ ]: [in] * The secret shared key. * key_len: [in] * The length of the secret shared key. * * Returns: * sha Error Code. * */ int hmacReset(HMACContext *context, enum SHAversion whichSha, const unsigned char *key, size_t key_len) { size_t i, blocksize, hashsize; int ret; /* inner padding - key XORd with ipad */ unsigned char k_ipad[USHA_Max_Message_Block_Size]; /* temporary buffer when keylen > blocksize */ unsigned char tempkey[USHAMaxHashSize]; if (!context) return shaNull; context->Computed = 0; context->Corrupted = shaSuccess; blocksize = context->blockSize = USHABlockSize(whichSha); hashsize = context->hashSize = USHAHashSize(whichSha); context->whichSha = whichSha; /* * If key is longer than the hash blocksize, * reset it to key = HASH(key). */ if (key_len > blocksize) { USHAContext tcontext; if ((ret=USHAReset(&tcontext, whichSha)) || (ret=USHAInput(&tcontext, key, key_len)) || (ret=USHAResult(&tcontext, tempkey))) return ret; key = tempkey; key_len = hashsize; } /* * The HMAC transform looks like: * * SHA(K XOR opad, SHA(K XOR ipad, text)) * * where K is an n byte key, 0-padded to a total of blocksize bytes, * ipad is the byte 0x36 repeated blocksize times, * opad is the byte 0x5c repeated blocksize times, * and text is the data being protected. */ /* store key into the pads, XOR'd with ipad and opad values */ for (i = 0; i < key_len; i++) { k_ipad[i] = key[i] ^ 0x36; context->k_opad[i] = key[i] ^ 0x5c; } /* remaining pad bytes are '\0' XOR'd with ipad and opad values */ for (; i < blocksize; i++) { k_ipad[i] = 0x36; context->k_opad[i] = 0x5c; } /* perform inner hash */ /* init context for 1st pass */ if (!(ret = USHAReset(&context->shaContext, whichSha))) /* and start with inner pad */ ret = USHAInput(&context->shaContext, k_ipad, blocksize); return context->Corrupted = ret; } /* * hmacInput * * Description: * This function accepts an array of octets as the next portion * of the message. It may be called multiple times. * * Parameters: * context: [in/out] * The HMAC context to update. * text[ ]: [in] * An array of octets representing the next portion of * the message. * text_len: [in] * The length of the message in text. * * Returns: * sha Error Code. * */ int hmacInput(HMACContext *context, const unsigned char *text, size_t text_len) { if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; /* then text of datagram */ return context->Corrupted = USHAInput(&context->shaContext, text, text_len); } /* * hmacFinalBits * * Description: * This function will add in any final bits of the message. * * Parameters: * context: [in/out] * The HMAC context to update. * message_bits: [in] * The final bits of the message, in the upper portion of the * byte. (Use 0b###00000 instead of 0b00000### to input the * three bits ###.) * length: [in] * The number of bits in message_bits, between 1 and 7. * * Returns: * sha Error Code. */ int hmacFinalBits(HMACContext *context, uint8_t bits, unsigned int bit_count) { if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; /* then final bits of datagram */ return context->Corrupted = USHAFinalBits(&context->shaContext, bits, bit_count); } /* * hmacResult * * Description: * This function will return the N-byte message digest into the * Message_Digest array provided by the caller. * * Parameters: * context: [in/out] * The context to use to calculate the HMAC hash. * digest[ ]: [out] * Where the digest is returned. * NOTE 2: The length of the hash is determined by the value of * whichSha that was passed to hmacReset(). * * Returns: * sha Error Code. * */ int hmacResult(HMACContext *context, uint8_t *digest) { int ret; if (!context) return shaNull; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; /* finish up 1st pass */ /* (Use digest here as a temporary buffer.) */ if (!(ret=USHAResult(&context->shaContext, digest)) && /* perform outer SHA */ /* init context for 2nd pass */ !(ret=USHAReset(&context->shaContext, context->whichSha)) && /* start with outer pad */ !(ret=USHAInput(&context->shaContext, context->k_opad, context->blockSize)) && /* then results of 1st hash */ !(ret=USHAInput(&context->shaContext, digest, context->hashSize))) /* finish up 2nd pass */ ret=USHAResult(&context->shaContext, digest); context->Computed = 1; return context->Corrupted = ret; } ================================================ FILE: nfq/crypto/sha-private.h ================================================ /************************ sha-private.h ************************/ /***************** See RFC 6234 for details. *******************/ #pragma once /* * These definitions are defined in FIPS 180-3, section 4.1. * Ch() and Maj() are defined identically in sections 4.1.1, * 4.1.2, and 4.1.3. * * The definitions used in FIPS 180-3 are as follows: */ #ifndef USE_MODIFIED_MACROS #define SHA_Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) #define SHA_Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) #else /* USE_MODIFIED_MACROS */ /* * The following definitions are equivalent and potentially faster. */ #define SHA_Ch(x, y, z) (((x) & ((y) ^ (z))) ^ (z)) #define SHA_Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) #endif /* USE_MODIFIED_MACROS */ #define SHA_Parity(x, y, z) ((x) ^ (y) ^ (z)) ================================================ FILE: nfq/crypto/sha.h ================================================ /**************************** sha.h ****************************/ /***************** See RFC 6234 for details. *******************/ /* Copyright (c) 2011 IETF Trust and the persons identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Internet Society, IETF or IETF Trust, nor the names of specific contributors, may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once /* * Description: * This file implements the Secure Hash Algorithms * as defined in the U.S. National Institute of Standards * and Technology Federal Information Processing Standards * Publication (FIPS PUB) 180-3 published in October 2008 * and formerly defined in its predecessors, FIPS PUB 180-1 * and FIP PUB 180-2. * * A combined document showing all algorithms is available at * http://csrc.nist.gov/publications/fips/ * fips180-3/fips180-3_final.pdf * * The five hashes are defined in these sizes: * SHA-1 20 byte / 160 bit * SHA-224 28 byte / 224 bit * SHA-256 32 byte / 256 bit * SHA-384 48 byte / 384 bit * SHA-512 64 byte / 512 bit * * Compilation Note: * These files may be compiled with two options: * USE_32BIT_ONLY - use 32-bit arithmetic only, for systems * without 64-bit integers * * USE_MODIFIED_MACROS - use alternate form of the SHA_Ch() * and SHA_Maj() macros that are equivalent * and potentially faster on many systems * */ #include #include /* * If you do not have the ISO standard stdint.h header file, then you * must typedef the following: * name meaning * uint64_t unsigned 64-bit integer * uint32_t unsigned 32-bit integer * uint8_t unsigned 8-bit integer (i.e., unsigned char) * int_least16_t integer of >= 16 bits * * See stdint-example.h */ #ifndef _SHA_enum_ #define _SHA_enum_ /* * All SHA functions return one of these values. */ enum { shaSuccess = 0, shaNull, /* Null pointer parameter */ shaInputTooLong, /* input data too long */ shaStateError, /* called Input after FinalBits or Result */ shaBadParam /* passed a bad parameter */ }; #endif /* _SHA_enum_ */ /* * These constants hold size information for each of the SHA * hashing operations */ enum { SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64, SHA256_Message_Block_Size = 64, USHA_Max_Message_Block_Size = SHA256_Message_Block_Size, SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32, USHAMaxHashSize = SHA256HashSize, SHA1HashSizeBits = 160, SHA224HashSizeBits = 224, SHA256HashSizeBits = 256, USHAMaxHashSizeBits = SHA256HashSizeBits }; /* * These constants are used in the USHA (Unified SHA) functions. */ typedef enum SHAversion { SHA224, SHA256 } SHAversion; /* * This structure will hold context information for the SHA-256 * hashing operation. */ typedef struct SHA256Context { uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */ uint32_t Length_High; /* Message length in bits */ uint32_t Length_Low; /* Message length in bits */ int_least16_t Message_Block_Index; /* Message_Block array index */ /* 512-bit message blocks */ uint8_t Message_Block[SHA256_Message_Block_Size]; int Computed; /* Is the hash computed? */ int Corrupted; /* Cumulative corruption code */ } SHA256Context; /* * This structure will hold context information for the SHA-224 * hashing operation. It uses the SHA-256 structure for computation. */ typedef struct SHA256Context SHA224Context; /* * This structure holds context information for all SHA * hashing operations. */ typedef struct USHAContext { int whichSha; /* which SHA is being used */ union { SHA224Context sha224Context; SHA256Context sha256Context; } ctx; } USHAContext; /* * This structure will hold context information for the HMAC * keyed-hashing operation. */ typedef struct HMACContext { int whichSha; /* which SHA is being used */ int hashSize; /* hash size of SHA being used */ int blockSize; /* block size of SHA being used */ USHAContext shaContext; /* SHA context */ unsigned char k_opad[USHA_Max_Message_Block_Size]; /* outer padding - key XORd with opad */ int Computed; /* Is the MAC computed? */ int Corrupted; /* Cumulative corruption code */ } HMACContext; /* * This structure will hold context information for the HKDF * extract-and-expand Key Derivation Functions. */ typedef struct HKDFContext { int whichSha; /* which SHA is being used */ HMACContext hmacContext; int hashSize; /* hash size of SHA being used */ unsigned char prk[USHAMaxHashSize]; /* pseudo-random key - output of hkdfInput */ int Computed; /* Is the key material computed? */ int Corrupted; /* Cumulative corruption code */ } HKDFContext; /* * Function Prototypes */ /* SHA-224 */ int SHA224Reset(SHA224Context *); int SHA224Input(SHA224Context *, const uint8_t *bytes, unsigned int bytecount); int SHA224FinalBits(SHA224Context *, uint8_t bits, unsigned int bit_count); int SHA224Result(SHA224Context *, uint8_t Message_Digest[SHA224HashSize]); /* SHA-256 */ int SHA256Reset(SHA256Context *); int SHA256Input(SHA256Context *, const uint8_t *bytes, unsigned int bytecount); int SHA256FinalBits(SHA256Context *, uint8_t bits, unsigned int bit_count); int SHA256Result(SHA256Context *, uint8_t Message_Digest[SHA256HashSize]); /* Unified SHA functions, chosen by whichSha */ int USHAReset(USHAContext *context, SHAversion whichSha); int USHAInput(USHAContext *context, const uint8_t *bytes, unsigned int bytecount); int USHAFinalBits(USHAContext *context, uint8_t bits, unsigned int bit_count); int USHAResult(USHAContext *context, uint8_t Message_Digest[USHAMaxHashSize]); int USHABlockSize(enum SHAversion whichSha); int USHAHashSize(enum SHAversion whichSha); /* * HMAC Keyed-Hashing for Message Authentication, RFC 2104, * for all SHAs. * This interface allows a fixed-length text input to be used. */ int hmac(SHAversion whichSha, /* which SHA algorithm to use */ const unsigned char *text, /* pointer to data stream */ size_t text_len, /* length of data stream */ const unsigned char *key, /* pointer to authentication key */ size_t key_len, /* length of authentication key */ uint8_t digest[USHAMaxHashSize]); /* caller digest to fill in */ /* * HMAC Keyed-Hashing for Message Authentication, RFC 2104, * for all SHAs. * This interface allows any length of text input to be used. */ int hmacReset(HMACContext *context, enum SHAversion whichSha, const unsigned char *key, size_t key_len); int hmacInput(HMACContext *context, const unsigned char *text, size_t text_len); int hmacFinalBits(HMACContext *context, uint8_t bits, unsigned int bit_count); int hmacResult(HMACContext *context, uint8_t digest[USHAMaxHashSize]); /* * HKDF HMAC-based Extract-and-Expand Key Derivation Function, * RFC 5869, for all SHAs. */ int hkdf(SHAversion whichSha, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *info, size_t info_len, uint8_t okm[ ], size_t okm_len); int hkdfExtract(SHAversion whichSha, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, uint8_t prk[USHAMaxHashSize]); int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ], size_t prk_len, const unsigned char *info, size_t info_len, uint8_t okm[ ], size_t okm_len); /* * HKDF HMAC-based Extract-and-Expand Key Derivation Function, * RFC 5869, for all SHAs. * This interface allows any length of text input to be used. */ int hkdfReset(HKDFContext *context, enum SHAversion whichSha, const unsigned char *salt, size_t salt_len); int hkdfInput(HKDFContext *context, const unsigned char *ikm, size_t ikm_len); int hkdfFinalBits(HKDFContext *context, uint8_t ikm_bits, unsigned int ikm_bit_count); int hkdfResult(HKDFContext *context, uint8_t prk[USHAMaxHashSize], const unsigned char *info, size_t info_len, uint8_t okm[USHAMaxHashSize], size_t okm_len); ================================================ FILE: nfq/crypto/sha224-256.c ================================================ /************************* sha224-256.c ************************/ /***************** See RFC 6234 for details. *******************/ /* Copyright (c) 2011 IETF Trust and the persons identified as */ /* authors of the code. All rights reserved. */ /* See sha.h for terms of use and redistribution. */ /* * Description: * This file implements the Secure Hash Algorithms SHA-224 and * SHA-256 as defined in the U.S. National Institute of Standards * and Technology Federal Information Processing Standards * Publication (FIPS PUB) 180-3 published in October 2008 * and formerly defined in its predecessors, FIPS PUB 180-1 * and FIP PUB 180-2. * * A combined document showing all algorithms is available at * http://csrc.nist.gov/publications/fips/ * fips180-3/fips180-3_final.pdf * * The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit * message digests for a given data stream. It should take about * 2**n steps to find a message with the same digest as a given * message and 2**(n/2) to find any two messages with the same * digest, when n is the digest size in bits. Therefore, this * algorithm can serve as a means of providing a * "fingerprint" for a message. * * Portability Issues: * SHA-224 and SHA-256 are defined in terms of 32-bit "words". * This code uses (included via "sha.h") to define 32- * and 8-bit unsigned integer types. If your C compiler does not * support 32-bit unsigned integers, this code is not * appropriate. * * Caveats: * SHA-224 and SHA-256 are designed to work with messages less * than 2^64 bits long. This implementation uses SHA224/256Input() * to hash the bits that are a multiple of the size of an 8-bit * octet, and then optionally uses SHA224/256FinalBits() * to hash the final few bits of the input. */ #include "sha.h" #include "sha-private.h" /* Define the SHA shift, rotate left, and rotate right macros */ #define SHA256_SHR(bits,word) ((word) >> (bits)) #define SHA256_ROTL(bits,word) \ (((word) << (bits)) | ((word) >> (32-(bits)))) #define SHA256_ROTR(bits,word) \ (((word) >> (bits)) | ((word) << (32-(bits)))) /* Define the SHA SIGMA and sigma macros */ #define SHA256_SIGMA0(word) \ (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word)) #define SHA256_SIGMA1(word) \ (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word)) #define SHA256_sigma0(word) \ (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word)) #define SHA256_sigma1(word) \ (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word)) /* * Add "length" to the length. * Set Corrupted when overflow has occurred. */ static int SHA224_256AddLength(SHA256Context *context, uint32_t length) { uint32_t addTemp = context->Length_Low; if (((context->Length_Low += length) < addTemp) && (++(context)->Length_High == 0)) context->Corrupted = shaInputTooLong; return context->Corrupted; } /* Local Function Prototypes */ static int SHA224_256Reset(SHA256Context *context, uint32_t *H0); static void SHA224_256ProcessMessageBlock(SHA256Context *context); static void SHA224_256Finalize(SHA256Context *context, uint8_t Pad_Byte); static void SHA224_256PadMessage(SHA256Context *context, uint8_t Pad_Byte); static int SHA224_256ResultN(SHA256Context *context, uint8_t Message_Digest[ ], int HashSize); /* Initial Hash Values: FIPS 180-3 section 5.3.2 */ static uint32_t SHA224_H0[SHA256HashSize/4] = { 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4 }; /* Initial Hash Values: FIPS 180-3 section 5.3.3 */ static uint32_t SHA256_H0[SHA256HashSize/4] = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 }; /* * SHA224Reset * * Description: * This function will initialize the SHA224Context in preparation * for computing a new SHA224 message digest. * * Parameters: * context: [in/out] * The context to reset. * * Returns: * sha Error Code. */ int SHA224Reset(SHA224Context *context) { return SHA224_256Reset(context, SHA224_H0); } /* * SHA224Input * * Description: * This function accepts an array of octets as the next portion * of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_array[ ]: [in] * An array of octets representing the next portion of * the message. * length: [in] * The length of the message in message_array. * * Returns: * sha Error Code. * */ int SHA224Input(SHA224Context *context, const uint8_t *message_array, unsigned int length) { return SHA256Input(context, message_array, length); } /* * SHA224FinalBits * * Description: * This function will add in any final bits of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_bits: [in] * The final bits of the message, in the upper portion of the * byte. (Use 0b###00000 instead of 0b00000### to input the * three bits ###.) * length: [in] * The number of bits in message_bits, between 1 and 7. * * Returns: * sha Error Code. */ int SHA224FinalBits(SHA224Context *context, uint8_t message_bits, unsigned int length) { return SHA256FinalBits(context, message_bits, length); } /* * SHA224Result * * Description: * This function will return the 224-bit message digest * into the Message_Digest array provided by the caller. * NOTE: * The first octet of hash is stored in the element with index 0, * the last octet of hash in the element with index 27. * * Parameters: * context: [in/out] * The context to use to calculate the SHA hash. * Message_Digest[ ]: [out] * Where the digest is returned. * * Returns: * sha Error Code. */ int SHA224Result(SHA224Context *context, uint8_t Message_Digest[SHA224HashSize]) { return SHA224_256ResultN(context, Message_Digest, SHA224HashSize); } /* * SHA256Reset * * Description: * This function will initialize the SHA256Context in preparation * for computing a new SHA256 message digest. * * Parameters: * context: [in/out] * The context to reset. * * Returns: * sha Error Code. */ int SHA256Reset(SHA256Context *context) { return SHA224_256Reset(context, SHA256_H0); } /* * SHA256Input * * Description: * This function accepts an array of octets as the next portion * of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_array[ ]: [in] * An array of octets representing the next portion of * the message. * length: [in] * The length of the message in message_array. * * Returns: * sha Error Code. */ int SHA256Input(SHA256Context *context, const uint8_t *message_array, unsigned int length) { if (!context) return shaNull; if (!length) return shaSuccess; if (!message_array) return shaNull; if (context->Computed) return context->Corrupted = shaStateError; if (context->Corrupted) return context->Corrupted; while (length--) { context->Message_Block[context->Message_Block_Index++] = *message_array; if ((SHA224_256AddLength(context, 8) == shaSuccess) && (context->Message_Block_Index == SHA256_Message_Block_Size)) SHA224_256ProcessMessageBlock(context); message_array++; } return context->Corrupted; } /* * SHA256FinalBits * * Description: * This function will add in any final bits of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_bits: [in] * The final bits of the message, in the upper portion of the * byte. (Use 0b###00000 instead of 0b00000### to input the * three bits ###.) * length: [in] * The number of bits in message_bits, between 1 and 7. * * Returns: * sha Error Code. */ int SHA256FinalBits(SHA256Context *context, uint8_t message_bits, unsigned int length) { static uint8_t masks[8] = { /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80, /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0, /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8, /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE }; static uint8_t markbit[8] = { /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40, /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10, /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04, /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01 }; if (!context) return shaNull; if (!length) return shaSuccess; if (context->Corrupted) return context->Corrupted; if (context->Computed) return context->Corrupted = shaStateError; if (length >= 8) return context->Corrupted = shaBadParam; SHA224_256AddLength(context, length); SHA224_256Finalize(context, (uint8_t) ((message_bits & masks[length]) | markbit[length])); return context->Corrupted; } /* * SHA256Result * * Description: * This function will return the 256-bit message digest * into the Message_Digest array provided by the caller. * NOTE: * The first octet of hash is stored in the element with index 0, * the last octet of hash in the element with index 31. * * Parameters: * context: [in/out] * The context to use to calculate the SHA hash. * Message_Digest[ ]: [out] * Where the digest is returned. * * Returns: * sha Error Code. */ int SHA256Result(SHA256Context *context, uint8_t Message_Digest[SHA256HashSize]) { return SHA224_256ResultN(context, Message_Digest, SHA256HashSize); } /* * SHA224_256Reset * * Description: * This helper function will initialize the SHA256Context in * preparation for computing a new SHA-224 or SHA-256 message digest. * * Parameters: * context: [in/out] * The context to reset. * H0[ ]: [in] * The initial hash value array to use. * * Returns: * sha Error Code. */ static int SHA224_256Reset(SHA256Context *context, uint32_t *H0) { if (!context) return shaNull; context->Length_High = context->Length_Low = 0; context->Message_Block_Index = 0; context->Intermediate_Hash[0] = H0[0]; context->Intermediate_Hash[1] = H0[1]; context->Intermediate_Hash[2] = H0[2]; context->Intermediate_Hash[3] = H0[3]; context->Intermediate_Hash[4] = H0[4]; context->Intermediate_Hash[5] = H0[5]; context->Intermediate_Hash[6] = H0[6]; context->Intermediate_Hash[7] = H0[7]; context->Computed = 0; context->Corrupted = shaSuccess; return shaSuccess; } /* * SHA224_256ProcessMessageBlock * * Description: * This helper function will process the next 512 bits of the * message stored in the Message_Block array. * * Parameters: * context: [in/out] * The SHA context to update. * * Returns: * Nothing. * * Comments: * Many of the variable names in this code, especially the * single character names, were used because those were the * names used in the Secure Hash Standard. */ static void SHA224_256ProcessMessageBlock(SHA256Context *context) { /* Constants defined in FIPS 180-3, section 4.2.2 */ static const uint32_t K[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; int t, t4; /* Loop counter */ uint32_t temp1, temp2; /* Temporary word value */ uint32_t W[64]; /* Word sequence */ uint32_t A, B, C, D, E, F, G, H; /* Word buffers */ /* * Initialize the first 16 words in the array W */ for (t = t4 = 0; t < 16; t++, t4 += 4) W[t] = (((uint32_t)context->Message_Block[t4]) << 24) | (((uint32_t)context->Message_Block[t4 + 1]) << 16) | (((uint32_t)context->Message_Block[t4 + 2]) << 8) | (((uint32_t)context->Message_Block[t4 + 3])); for (t = 16; t < 64; t++) W[t] = SHA256_sigma1(W[t-2]) + W[t-7] + SHA256_sigma0(W[t-15]) + W[t-16]; A = context->Intermediate_Hash[0]; B = context->Intermediate_Hash[1]; C = context->Intermediate_Hash[2]; D = context->Intermediate_Hash[3]; E = context->Intermediate_Hash[4]; F = context->Intermediate_Hash[5]; G = context->Intermediate_Hash[6]; H = context->Intermediate_Hash[7]; for (t = 0; t < 64; t++) { temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t]; temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C); H = G; G = F; F = E; E = D + temp1; D = C; C = B; B = A; A = temp1 + temp2; } context->Intermediate_Hash[0] += A; context->Intermediate_Hash[1] += B; context->Intermediate_Hash[2] += C; context->Intermediate_Hash[3] += D; context->Intermediate_Hash[4] += E; context->Intermediate_Hash[5] += F; context->Intermediate_Hash[6] += G; context->Intermediate_Hash[7] += H; context->Message_Block_Index = 0; } /* * SHA224_256Finalize * * Description: * This helper function finishes off the digest calculations. * * Parameters: * context: [in/out] * The SHA context to update. * Pad_Byte: [in] * The last byte to add to the message block before the 0-padding * and length. This will contain the last bits of the message * followed by another single bit. If the message was an * exact multiple of 8-bits long, Pad_Byte will be 0x80. * * Returns: * sha Error Code. */ static void SHA224_256Finalize(SHA256Context *context, uint8_t Pad_Byte) { int i; SHA224_256PadMessage(context, Pad_Byte); /* message may be sensitive, so clear it out */ for (i = 0; i < SHA256_Message_Block_Size; ++i) context->Message_Block[i] = 0; context->Length_High = 0; /* and clear length */ context->Length_Low = 0; context->Computed = 1; } /* * SHA224_256PadMessage * * Description: * According to the standard, the message must be padded to the next * even multiple of 512 bits. The first padding bit must be a '1'. * The last 64 bits represent the length of the original message. * All bits in between should be 0. This helper function will pad * the message according to those rules by filling the * Message_Block array accordingly. When it returns, it can be * assumed that the message digest has been computed. * * Parameters: * context: [in/out] * The context to pad. * Pad_Byte: [in] * The last byte to add to the message block before the 0-padding * and length. This will contain the last bits of the message * followed by another single bit. If the message was an * exact multiple of 8-bits long, Pad_Byte will be 0x80. * * Returns: * Nothing. */ static void SHA224_256PadMessage(SHA256Context *context, uint8_t Pad_Byte) { /* * Check to see if the current message block is too small to hold * the initial padding bits and length. If so, we will pad the * block, process it, and then continue padding into a second * block. */ if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) { context->Message_Block[context->Message_Block_Index++] = Pad_Byte; while (context->Message_Block_Index < SHA256_Message_Block_Size) context->Message_Block[context->Message_Block_Index++] = 0; SHA224_256ProcessMessageBlock(context); } else context->Message_Block[context->Message_Block_Index++] = Pad_Byte; while (context->Message_Block_Index < (SHA256_Message_Block_Size-8)) context->Message_Block[context->Message_Block_Index++] = 0; /* * Store the message length as the last 8 octets */ context->Message_Block[56] = (uint8_t)(context->Length_High >> 24); context->Message_Block[57] = (uint8_t)(context->Length_High >> 16); context->Message_Block[58] = (uint8_t)(context->Length_High >> 8); context->Message_Block[59] = (uint8_t)(context->Length_High); context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24); context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16); context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8); context->Message_Block[63] = (uint8_t)(context->Length_Low); SHA224_256ProcessMessageBlock(context); } /* * SHA224_256ResultN * * Description: * This helper function will return the 224-bit or 256-bit message * digest into the Message_Digest array provided by the caller. * NOTE: * The first octet of hash is stored in the element with index 0, * the last octet of hash in the element with index 27/31. * * Parameters: * context: [in/out] * The context to use to calculate the SHA hash. * Message_Digest[ ]: [out] * Where the digest is returned. * HashSize: [in] * The size of the hash, either 28 or 32. * * Returns: * sha Error Code. */ static int SHA224_256ResultN(SHA256Context *context, uint8_t Message_Digest[ ], int HashSize) { int i; if (!context) return shaNull; if (!Message_Digest) return shaNull; if (context->Corrupted) return context->Corrupted; if (!context->Computed) SHA224_256Finalize(context, 0x80); for (i = 0; i < HashSize; ++i) Message_Digest[i] = (uint8_t) (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) )); return shaSuccess; } ================================================ FILE: nfq/crypto/usha.c ================================================ /**************************** usha.c ***************************/ /***************** See RFC 6234 for details. *******************/ /* Copyright (c) 2011 IETF Trust and the persons identified as */ /* authors of the code. All rights reserved. */ /* See sha.h for terms of use and redistribution. */ /* * Description: * This file implements a unified interface to the SHA algorithms. */ #include "sha.h" /* * USHAReset * * Description: * This function will initialize the SHA Context in preparation * for computing a new SHA message digest. * * Parameters: * context: [in/out] * The context to reset. * whichSha: [in] * Selects which SHA reset to call * * Returns: * sha Error Code. * */ int USHAReset(USHAContext *context, enum SHAversion whichSha) { if (!context) return shaNull; context->whichSha = whichSha; switch (whichSha) { case SHA224: return SHA224Reset((SHA224Context*)&context->ctx); case SHA256: return SHA256Reset((SHA256Context*)&context->ctx); default: return shaBadParam; } } /* * USHAInput * * Description: * This function accepts an array of octets as the next portion * of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_array: [in] * An array of octets representing the next portion of * the message. * length: [in] * The length of the message in message_array. * * Returns: * sha Error Code. * */ int USHAInput(USHAContext *context, const uint8_t *bytes, unsigned int bytecount) { if (!context) return shaNull; switch (context->whichSha) { case SHA224: return SHA224Input((SHA224Context*)&context->ctx, bytes, bytecount); case SHA256: return SHA256Input((SHA256Context*)&context->ctx, bytes, bytecount); default: return shaBadParam; } } /* * USHAFinalBits * * Description: * This function will add in any final bits of the message. * * Parameters: * context: [in/out] * The SHA context to update. * message_bits: [in] * The final bits of the message, in the upper portion of the * byte. (Use 0b###00000 instead of 0b00000### to input the * three bits ###.) * length: [in] * The number of bits in message_bits, between 1 and 7. * * Returns: * sha Error Code. */ int USHAFinalBits(USHAContext *context, uint8_t bits, unsigned int bit_count) { if (!context) return shaNull; switch (context->whichSha) { case SHA224: return SHA224FinalBits((SHA224Context*)&context->ctx, bits, bit_count); case SHA256: return SHA256FinalBits((SHA256Context*)&context->ctx, bits, bit_count); default: return shaBadParam; } } /* * USHAResult * * Description: * This function will return the message digest of the appropriate * bit size, as returned by USHAHashSizeBits(whichSHA) for the * 'whichSHA' value used in the preceeding call to USHAReset, * into the Message_Digest array provided by the caller. * * Parameters: * context: [in/out] * The context to use to calculate the SHA-1 hash. * Message_Digest: [out] * Where the digest is returned. * * Returns: * sha Error Code. * */ int USHAResult(USHAContext *context, uint8_t Message_Digest[USHAMaxHashSize]) { if (!context) return shaNull; switch (context->whichSha) { case SHA224: return SHA224Result((SHA224Context*)&context->ctx, Message_Digest); case SHA256: return SHA256Result((SHA256Context*)&context->ctx, Message_Digest); default: return shaBadParam; } } /* * USHABlockSize * * Description: * This function will return the blocksize for the given SHA * algorithm. * * Parameters: * whichSha: * which SHA algorithm to query * * Returns: * block size * */ int USHABlockSize(enum SHAversion whichSha) { switch (whichSha) { case SHA224: return SHA224_Message_Block_Size; default: case SHA256: return SHA256_Message_Block_Size; } } /* * USHAHashSize * * Description: * This function will return the hashsize for the given SHA * algorithm. * * Parameters: * whichSha: * which SHA algorithm to query * * Returns: * hash size * */ int USHAHashSize(enum SHAversion whichSha) { switch (whichSha) { case SHA224: return SHA224HashSize; default: case SHA256: return SHA256HashSize; } } ================================================ FILE: nfq/darkmagic.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #ifndef IP_NODEFRAG // for very old toolchains #define IP_NODEFRAG 22 #endif #include "darkmagic.h" #include "helpers.h" #include "params.h" #include "nfqws.h" #ifdef __CYGWIN__ #include #include #ifndef ERROR_INVALID_IMAGE_HASH #define ERROR_INVALID_IMAGE_HASH __MSABI_LONG(577) #endif #endif #ifdef __linux__ #include #include #include #include #define _LINUX_IF_H // prevent conflict between linux/if.h and net/if.h in old gcc 4.x #include #include #endif uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment) { return htonl(ntohl(netorder_value)+cpuorder_increment); } uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment) { return htons(ntohs(netorder_value)+cpuorder_increment); } bool ip_has_df(const struct ip *ip) { return ip && !!(ntohs(ip->ip_off) & IP_DF); } uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind) { uint8_t *t = (uint8_t*)(tcp+1); uint8_t *end = (uint8_t*)tcp + (tcp->th_off<<2); while(t=end || t[1]<2 || (t+t[1])>end) return NULL; if (*t==kind) return t; t+=t[1]; break; } } return NULL; } uint32_t *tcp_find_timestamps(struct tcphdr *tcp) { uint8_t *t = tcp_find_option(tcp,8); return (t && t[1]==10) ? (uint32_t*)(t+2) : NULL; } uint8_t tcp_find_scale_factor(const struct tcphdr *tcp) { uint8_t *scale = tcp_find_option((struct tcphdr*)tcp,3); // tcp option 3 - scale factor if (scale && scale[1]==3) return scale[2]; return SCALE_NONE; } bool tcp_has_fastopen(const struct tcphdr *tcp) { uint8_t *opt; // new style RFC7413 opt = tcp_find_option((struct tcphdr*)tcp, 34); if (opt) return true; // old style RFC6994 opt = tcp_find_option((struct tcphdr*)tcp, 254); return opt && opt[1]>=4 && opt[2]==0xF9 && opt[3]==0x89; } uint16_t tcp_find_mss(struct tcphdr *tcp) { uint8_t *t = tcp_find_option(tcp,2); return (t && t[1]==4) ? *(uint16_t*)(t+2) : 0; } bool tcp_has_sack(struct tcphdr *tcp) { uint8_t *t = tcp_find_option(tcp,4); return !!t; } // n prefix (nsport, nwsize) means network byte order static void fill_tcphdr( struct tcphdr *tcp, uint32_t fooling, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nsport, uint16_t ndport, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, uint16_t data_len) { char *tcpopt = (char*)(tcp+1); uint8_t t=0; memset(tcp,0,sizeof(*tcp)); tcp->th_sport = nsport; tcp->th_dport = ndport; if (fooling & FOOL_BADSEQ) { tcp->th_seq = net32_add(nseq,badseq_increment); tcp->th_ack = net32_add(nack_seq,badseq_ack_increment); } else { tcp->th_seq = nseq; tcp->th_ack = nack_seq; } tcp->th_off = 5; if ((fooling & FOOL_DATANOACK) && !(tcp_flags & (TH_SYN|TH_RST)) && data_len) tcp_flags &= ~TH_ACK; tcp->th_flags = (uint8_t)tcp_flags; tcp->th_x2 = (tcp_flags>>8) & 0xF; tcp->th_win = nwsize; if (nmss) { tcpopt[t++] = 2; // kind tcpopt[t++] = 4; // len *(uint16_t*)(tcpopt+t) = nmss; t+=2; } if (sack) { tcpopt[t++] = 4; // kind tcpopt[t++] = 2; // len } if (fooling & FOOL_MD5SIG) { tcpopt[t] = 19; // kind tcpopt[t+1] = 18; // len *(uint32_t*)(tcpopt+t+2)=random(); *(uint32_t*)(tcpopt+t+6)=random(); *(uint32_t*)(tcpopt+t+10)=random(); *(uint32_t*)(tcpopt+t+14)=random(); t+=18; } if (timestamps) { tcpopt[t] = 8; // kind tcpopt[t+1] = 10; // len memcpy(tcpopt+t+2,timestamps,8); // forge TSval, keep TSecr if (fooling & FOOL_TS) *(uint32_t*)(tcpopt+t+2) = net32_add(*(uint32_t*)(tcpopt+t+2),ts_increment); t+=10; } if (scale_factor!=SCALE_NONE) { tcpopt[t++]=3; tcpopt[t++]=3; tcpopt[t++]=scale_factor; } while (t&3) tcpopt[t++]=1; // noop tcp->th_off += t>>2; tcp->th_sum = 0; } static uint16_t tcpopt_len(bool sack, bool mss, uint32_t fooling, const uint32_t *timestamps, uint8_t scale_factor) { uint16_t t=0; if (sack) t+=2; if (mss) t+=4; if (fooling & FOOL_MD5SIG) t+=18; if (timestamps) t+=10; if (scale_factor!=SCALE_NONE) t+=3; return (t+3)&~3; } // n prefix (nsport, nwsize) means network byte order static void fill_udphdr(struct udphdr *udp, uint16_t nsport, uint16_t ndport, uint16_t len_payload) { udp->uh_sport = nsport; udp->uh_dport = ndport; udp->uh_ulen = htons(len_payload+sizeof(struct udphdr)); udp->uh_sum = 0; } static 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) { ip->ip_tos = tos; ip->ip_sum = 0; ip->ip_off = DF ? htons(IP_DF) : 0; ip->ip_v = 4; ip->ip_hl = 5; ip->ip_len = htons(pktlen); ip->ip_id = ip_id; ip->ip_ttl = ttl; ip->ip_p = proto; ip->ip_src = *src; ip->ip_dst = *dst; } static 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) { ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(ntohl(flow_label) & 0x0FFFFFFF | 0x60000000); ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen); ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = proto; ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; ip6->ip6_src = *src; ip6->ip6_dst = *dst; } bool prepare_tcp_segment4( const struct sockaddr_in *src, const struct sockaddr_in *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { uint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor); uint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; uint16_t pktlen = sizeof(struct ip) + ip_payload_len; if (pktlen>*buflen) return false; struct ip *ip = (struct ip*)buf; struct tcphdr *tcp = (struct tcphdr*)(ip+1); uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_TCP, DF, ttl, tos, ip_id); fill_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); memcpy(payload,data,len); tcp4_fix_checksum(tcp,ip_payload_len,&ip->ip_src,&ip->ip_dst); if (fooling & FOOL_BADSUM) tcp->th_sum^=(uint16_t)(1+random()%0xFFFF); *buflen = pktlen; return true; } bool prepare_tcp_segment6( const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, uint8_t ttl, uint32_t flow_label, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { uint16_t tcpoptlen = tcpopt_len(sack,!!nmss,fooling,timestamps,scale_factor); uint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; uint16_t ip_payload_len = transport_payload_len + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + 16*!!(fooling & FOOL_HOPBYHOP2) + 8*!!(fooling & FOOL_DESTOPT) + 8*!!(fooling & FOOL_IPFRAG1); uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; if (pktlen>*buflen) return false; struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; struct tcphdr *tcp = (struct tcphdr*)(ip6+1); uint8_t proto = IPPROTO_TCP, *nexttype = NULL; if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) { struct ip6_hbh *hbh = (struct ip6_hbh*)tcp; tcp = (struct tcphdr*)((uint8_t*)tcp+8); memset(hbh,0,8); // extra HOPBYHOP header. standard violation if (fooling & FOOL_HOPBYHOP2) { hbh = (struct ip6_hbh*)tcp; tcp = (struct tcphdr*)((uint8_t*)tcp+8); memset(hbh,0,8); } hbh->ip6h_nxt = IPPROTO_TCP; nexttype = &hbh->ip6h_nxt; proto = IPPROTO_HOPOPTS; } if (fooling & FOOL_DESTOPT) { struct ip6_dest *dest = (struct ip6_dest*)tcp; tcp = (struct tcphdr*)((uint8_t*)tcp+8); memset(dest,0,8); dest->ip6d_nxt = IPPROTO_TCP; if (nexttype) *nexttype = IPPROTO_DSTOPTS; else proto = IPPROTO_DSTOPTS; nexttype = &dest->ip6d_nxt; } if (fooling & FOOL_IPFRAG1) { struct ip6_frag *frag = (struct ip6_frag*)tcp; tcp = (struct tcphdr*)((uint8_t*)tcp+sizeof(struct ip6_frag)); frag->ip6f_nxt = IPPROTO_TCP; frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); frag->ip6f_reserved = 0; frag->ip6f_offlg = 0; if (nexttype) *nexttype = IPPROTO_FRAGMENT; else proto = IPPROTO_FRAGMENT; } uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); fill_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); memcpy(payload,data,len); tcp6_fix_checksum(tcp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); if (fooling & FOOL_BADSUM) tcp->th_sum^=(1+random()%0xFFFF); *buflen = pktlen; return true; } bool prepare_tcp_segment( const struct sockaddr *src, const struct sockaddr *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t flow_label, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? prepare_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) : (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? prepare_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) : false; } // padlen<0 means payload shrinking bool prepare_udp_segment4( const struct sockaddr_in *src, const struct sockaddr_in *dst, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range if (padlen<0) { len+=padlen; padlen=0; } uint16_t datalen = (uint16_t)(len + padlen); uint16_t ip_payload_len = sizeof(struct udphdr) + datalen; uint16_t pktlen = sizeof(struct ip) + ip_payload_len; if (pktlen>*buflen) return false; struct ip *ip = (struct ip*)buf; struct udphdr *udp = (struct udphdr*)(ip+1); uint8_t *payload = (uint8_t*)(udp+1); fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_UDP, DF, ttl, tos, ip_id); fill_udphdr(udp, src->sin_port, dst->sin_port, datalen); memcpy(payload,data,len); if (padding) fill_pattern(payload+len,padlen,padding,padding_size,0); else memset(payload+len,0,padlen); udp4_fix_checksum(udp,ip_payload_len,&ip->ip_src,&ip->ip_dst); if (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF); *buflen = pktlen; return true; } bool prepare_udp_segment6( const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, uint8_t ttl, uint32_t flow_label, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range if (padlen<0) { len+=padlen; padlen=0; } uint16_t datalen = (uint16_t)(len + padlen); uint16_t transport_payload_len = sizeof(struct udphdr) + datalen; uint16_t ip_payload_len = transport_payload_len + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + 16*!!(fooling & FOOL_HOPBYHOP2) + 8*!!(fooling & FOOL_DESTOPT) + 8*!!(fooling & FOOL_IPFRAG1); uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; if (pktlen>*buflen) return false; struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; struct udphdr *udp = (struct udphdr*)(ip6+1); uint8_t proto = IPPROTO_UDP, *nexttype = NULL; if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) { struct ip6_hbh *hbh = (struct ip6_hbh*)udp; udp = (struct udphdr*)((uint8_t*)udp+8); memset(hbh,0,8); // extra HOPBYHOP header. standard violation if (fooling & FOOL_HOPBYHOP2) { hbh = (struct ip6_hbh*)udp; udp = (struct udphdr*)((uint8_t*)udp+8); memset(hbh,0,8); } hbh->ip6h_nxt = IPPROTO_UDP; nexttype = &hbh->ip6h_nxt; proto = IPPROTO_HOPOPTS; } if (fooling & FOOL_DESTOPT) { struct ip6_dest *dest = (struct ip6_dest*)udp; udp = (struct udphdr*)((uint8_t*)udp+8); memset(dest,0,8); dest->ip6d_nxt = IPPROTO_UDP; if (nexttype) *nexttype = IPPROTO_DSTOPTS; else proto = IPPROTO_DSTOPTS; nexttype = &dest->ip6d_nxt; } if (fooling & FOOL_IPFRAG1) { struct ip6_frag *frag = (struct ip6_frag*)udp; udp = (struct udphdr*)((uint8_t*)udp+sizeof(struct ip6_frag)); frag->ip6f_nxt = IPPROTO_UDP; frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); frag->ip6f_reserved = 0; frag->ip6f_offlg = 0; if (nexttype) *nexttype = IPPROTO_FRAGMENT; else proto = IPPROTO_FRAGMENT; } uint8_t *payload = (uint8_t*)(udp+1); fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); fill_udphdr(udp, src->sin6_port, dst->sin6_port, datalen); memcpy(payload,data,len); if (padding) fill_pattern(payload+len,padlen,padding,padding_size,0); else memset(payload+len,0,padlen); udp6_fix_checksum(udp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); if (fooling & FOOL_BADSUM) udp->uh_sum^=(1+random()%0xFFFF); *buflen = pktlen; return true; } bool prepare_udp_segment( const struct sockaddr *src, const struct sockaddr *dst, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t flow_label, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen) { return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? prepare_udp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,DF,ttl,tos,ip_id,fooling,padding,padding_size,padlen,data,len,buf,buflen) : (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? prepare_udp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,ttl,flow_label,fooling,padding,padding_size,padlen,data,len,buf,buflen) : false; } bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen) { if ((len_pkt+8)<=*buflen && len_pkt>=sizeof(struct ip6_hdr)) { struct ip6_hdr *ip6 = (struct ip6_hdr *)buf; struct ip6_ext *hdr = (struct ip6_ext*)(ip6+1); *ip6 = *(struct ip6_hdr*)data_pkt; memset(hdr,0,8); memcpy((uint8_t*)hdr+8, data_pkt+sizeof(struct ip6_hdr), len_pkt-sizeof(struct ip6_hdr)); hdr->ip6e_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = type; ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = net16_add(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen, 8); *buflen = len_pkt + 8; return true; } return false; } // split ipv4 packet into 2 fragments at data payload position frag_pos bool ip_frag4( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size) { uint16_t hdrlen, payload_len; // frag_pos must be 8-byte aligned if (frag_pos & 7 || pkt_size < sizeof(struct ip)) return false; payload_len = htons(((struct ip *)pkt)->ip_len); hdrlen = ((struct ip *)pkt)->ip_hl<<2; if (payload_len>pkt_size || hdrlen>pkt_size || hdrlen>payload_len) return false; payload_len -= hdrlen; if (frag_pos>=payload_len || *pkt1_size<(hdrlen+frag_pos) || *pkt2_size<(hdrlen+payload_len-frag_pos)) return false; memcpy(pkt1, pkt, hdrlen+frag_pos); ((struct ip*)pkt1)->ip_off = htons(IP_MF); ((struct ip*)pkt1)->ip_len = htons(hdrlen+frag_pos); if (ident!=(uint32_t)-1) ((struct ip*)pkt1)->ip_id = (uint16_t)ident; *pkt1_size=hdrlen+frag_pos; ip4_fix_checksum((struct ip *)pkt1); memcpy(pkt2, pkt, hdrlen); memcpy(pkt2+hdrlen, pkt+hdrlen+frag_pos, payload_len-frag_pos); ((struct ip*)pkt2)->ip_off = htons((uint16_t)frag_pos>>3 & IP_OFFMASK); ((struct ip*)pkt2)->ip_len = htons(hdrlen+payload_len-frag_pos); if (ident!=(uint32_t)-1) ((struct ip*)pkt2)->ip_id = (uint16_t)ident; *pkt2_size=hdrlen+payload_len-frag_pos; ip4_fix_checksum((struct ip *)pkt2); return true; } bool ip_frag6( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size) { size_t payload_len, unfragmentable; uint8_t *last_header_type; uint8_t proto; struct ip6_frag *frag; const uint8_t *payload; if (frag_pos & 7 || pkt_size < sizeof(struct ip6_hdr)) return false; payload_len = sizeof(struct ip6_hdr) + htons(((struct ip6_hdr*)pkt)->ip6_ctlun.ip6_un1.ip6_un1_plen); if (pkt_size < payload_len) return false; payload = pkt; proto_skip_ipv6((uint8_t**)&payload, &payload_len, &proto, &last_header_type); unfragmentable = payload - pkt; //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); if (frag_pos>=payload_len || *pkt1_size<(unfragmentable + sizeof(struct ip6_frag) + frag_pos) || *pkt2_size<(unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos)) { return false; } memcpy(pkt1, pkt, unfragmentable); ((struct ip6_hdr*)pkt1)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + frag_pos); pkt1[last_header_type - pkt] = IPPROTO_FRAGMENT; frag = (struct ip6_frag*)(pkt1 + unfragmentable); frag->ip6f_nxt = proto; frag->ip6f_reserved = 0; frag->ip6f_offlg = IP6F_MORE_FRAG; frag->ip6f_ident = ident; memcpy(frag+1, pkt + unfragmentable, frag_pos); *pkt1_size = unfragmentable + sizeof(struct ip6_frag) + frag_pos; memcpy(pkt2, pkt, sizeof(struct ip6_hdr)); ((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); pkt2[last_header_type - pkt] = IPPROTO_FRAGMENT; frag = (struct ip6_frag*)(pkt2 + unfragmentable); frag->ip6f_nxt = proto; frag->ip6f_reserved = 0; frag->ip6f_offlg = htons(frag_pos); frag->ip6f_ident = ident; memcpy(frag+1, pkt + unfragmentable + frag_pos, payload_len - frag_pos); *pkt2_size = unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos; return true; } bool ip_frag( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size) { if (proto_check_ipv4(pkt,pkt_size)) return ip_frag4(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); else if (proto_check_ipv6(pkt,pkt_size)) return ip_frag6(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); else return false; } bool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl) { if (ttl) { if (ip) { if (ip->ip_ttl!=ttl) { ip->ip_ttl = ttl; ip4_fix_checksum(ip); return true; } } else if (ip6) { if (ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim!=ttl) { ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; return true; } } } return false; } void apply_tcp_flags(struct tcphdr *tcp, uint16_t fl) { if (tcp) { tcp->th_flags = (uint8_t)fl; tcp->th_x2 = (fl>>8) & 0xF; } } uint16_t get_tcp_flags(const struct tcphdr *tcp) { return tcp->th_flags | (tcp->th_x2<<8); } void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport) { if (sport) *sport = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0); if (dport) *dport = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0); if (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : -1; } void 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) { if (ip) { struct sockaddr_in *si; if (dst) { si = (struct sockaddr_in*)dst; si->sin_family = AF_INET; si->sin_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; si->sin_addr = ip->ip_dst; } if (src) { si = (struct sockaddr_in*)src; si->sin_family = AF_INET; si->sin_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; si->sin_addr = ip->ip_src; } } else if (ip6hdr) { struct sockaddr_in6 *si; if (dst) { si = (struct sockaddr_in6*)dst; si->sin6_family = AF_INET6; si->sin6_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; si->sin6_addr = ip6hdr->ip6_dst; si->sin6_flowinfo = 0; si->sin6_scope_id = 0; } if (src) { si = (struct sockaddr_in6*)src; si->sin6_family = AF_INET6; si->sin6_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; si->sin6_addr = ip6hdr->ip6_src; si->sin6_flowinfo = 0; si->sin6_scope_id = 0; } } } const char *proto_name(uint8_t proto) { switch(proto) { case IPPROTO_TCP: return "tcp"; case IPPROTO_UDP: return "udp"; case IPPROTO_ICMP: return "icmp"; case IPPROTO_ICMPV6: return "icmp6"; case IPPROTO_IGMP: return "igmp"; case IPPROTO_ESP: return "esp"; case IPPROTO_AH: return "ah"; case IPPROTO_IPV6: return "6in4"; case IPPROTO_IPIP: return "4in4"; #ifdef IPPROTO_GRE case IPPROTO_GRE: return "gre"; #endif #ifdef IPPROTO_SCTP case IPPROTO_SCTP: return "sctp"; #endif default: return NULL; } } static void str_proto_name(char *s, size_t s_len, uint8_t proto) { const char *name = proto_name(proto); if (name) snprintf(s,s_len,"%s",name); else snprintf(s,s_len,"%u",proto); } uint16_t family_from_proto(uint8_t l3proto) { switch(l3proto) { case IPPROTO_IP: return AF_INET; case IPPROTO_IPV6: return AF_INET6; default: return -1; } } static void str_srcdst_ip(char *s, size_t s_len, const void *saddr,const void *daddr) { char s_ip[16],d_ip[16]; *s_ip=*d_ip=0; inet_ntop(AF_INET, saddr, s_ip, sizeof(s_ip)); inet_ntop(AF_INET, daddr, d_ip, sizeof(d_ip)); snprintf(s,s_len,"%s => %s",s_ip,d_ip); } void str_ip(char *s, size_t s_len, const struct ip *ip) { char ss[35],s_proto[16]; str_srcdst_ip(ss,sizeof(ss),&ip->ip_src,&ip->ip_dst); str_proto_name(s_proto,sizeof(s_proto),ip->ip_p); snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip->ip_ttl); } void print_ip(const struct ip *ip) { char s[66]; str_ip(s,sizeof(s),ip); printf("%s",s); } void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr) { char s_ip[40],d_ip[40]; *s_ip=*d_ip=0; inet_ntop(AF_INET6, saddr, s_ip, sizeof(s_ip)); inet_ntop(AF_INET6, daddr, d_ip, sizeof(d_ip)); snprintf(s,s_len,"%s => %s",s_ip,d_ip); } void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto) { char ss[83],s_proto[16]; str_srcdst_ip6(ss,sizeof(ss),&ip6hdr->ip6_src,&ip6hdr->ip6_dst); str_proto_name(s_proto,sizeof(s_proto),proto); snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip6hdr->ip6_hlim); } void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto) { char s[128]; str_ip6hdr(s,sizeof(s),ip6hdr,proto); printf("%s",s); } void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr) { char flags[7],*f=flags; if (tcphdr->th_flags & TH_SYN) *f++='S'; if (tcphdr->th_flags & TH_ACK) *f++='A'; if (tcphdr->th_flags & TH_RST) *f++='R'; if (tcphdr->th_flags & TH_FIN) *f++='F'; if (tcphdr->th_flags & TH_PUSH) *f++='P'; if (tcphdr->th_flags & TH_URG) *f++='U'; *f=0; snprintf(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)); } void print_tcphdr(const struct tcphdr *tcphdr) { char s[80]; str_tcphdr(s,sizeof(s),tcphdr); printf("%s",s); } void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr) { snprintf(s,s_len,"sport=%u dport=%u",htons(udphdr->uh_sport),htons(udphdr->uh_dport)); } void print_udphdr(const struct udphdr *udphdr) { char s[30]; str_udphdr(s,sizeof(s),udphdr); printf("%s",s); } bool proto_check_ipv4(const uint8_t *data, size_t len) { return len >= 20 && (data[0] & 0xF0) == 0x40 && len >= ((data[0] & 0x0F) << 2); } // move to transport protocol void proto_skip_ipv4(uint8_t **data, size_t *len) { size_t l; l = (**data & 0x0F) << 2; *data += l; *len -= l; } bool proto_check_tcp(const uint8_t *data, size_t len) { return len >= 20 && len >= ((data[12] & 0xF0) >> 2); } void proto_skip_tcp(uint8_t **data, size_t *len) { size_t l; l = ((*data)[12] & 0xF0) >> 2; *data += l; *len -= l; } bool proto_check_udp(const uint8_t *data, size_t len) { return len >= 8 && len>=(data[4]<<8 | data[5]); } void proto_skip_udp(uint8_t **data, size_t *len) { *data += 8; *len -= 8; } bool proto_check_ipv6(const uint8_t *data, size_t len) { return len >= 40 && (data[0] & 0xF0) == 0x60 && (len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length } // move to transport protocol // proto_type = 0 => error void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type) { size_t hdrlen; uint8_t HeaderType; if (proto_type) *proto_type = 0; // put error in advance HeaderType = (*data)[6]; // NextHeader field if (proto_type) *proto_type = HeaderType; if (last_header_type) *last_header_type = (*data)+6; *data += 40; *len -= 40; // skip ipv6 base header while (*len > 0) // need at least one byte for NextHeader field { switch (HeaderType) { case 0: // Hop-by-Hop Options case 43: // routing case 60: // Destination Options case 135: // mobility case 139: // Host Identity Protocol Version v2 case 140: // Shim6 if (*len < 2) return; // error hdrlen = 8 + ((*data)[1] << 3); break; case 44: // fragment. length fixed to 8, hdrlen field defined as reserved hdrlen = 8; break; case 51: // authentication // special case. length in ah header is in 32-bit words minus 2 if (*len < 2) return; // error hdrlen = 8 + ((*data)[1] << 2); break; case 59: // no next header return; // error default: // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit if (proto_type) *proto_type = HeaderType; return; } if (*len < hdrlen) return; // error HeaderType = **data; if (last_header_type) *last_header_type = *data; // advance to the next header location *len -= hdrlen; *data += hdrlen; } // we have garbage } void proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis) { memset(dis,0,sizeof(*dis)); dis->data_pkt = data; dis->len_pkt = len; if (proto_check_ipv4(data, len)) { dis->ip = (struct ip *) data; dis->proto = dis->ip->ip_p; proto_skip_ipv4(&data, &len); } else if (proto_check_ipv6(data, len)) { dis->ip6 = (struct ip6_hdr *) data; proto_skip_ipv6(&data, &len, &dis->proto, NULL); } else { return; } if (dis->proto==IPPROTO_TCP && proto_check_tcp(data, len)) { dis->tcp = (struct tcphdr *) data; dis->transport_len = len; proto_skip_tcp(&data, &len); dis->data_payload = data; dis->len_payload = len; } else if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len)) { dis->udp = (struct udphdr *) data; dis->transport_len = len; proto_skip_udp(&data, &len); dis->data_payload = data; dis->len_payload = len; } } bool tcp_synack_segment(const struct tcphdr *tcphdr) { /* check for set bits in TCP hdr */ return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == (TH_ACK|TH_SYN)); } bool tcp_syn_segment(const struct tcphdr *tcphdr) { /* check for set bits in TCP hdr */ return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_SYN); } bool tcp_ack_segment(const struct tcphdr *tcphdr) { /* check for set bits in TCP hdr */ return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_ACK); } void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor) { uint8_t *scale,scale_factor_old; if (scale_factor!=SCALE_NONE) { scale = tcp_find_option(tcp,3); // tcp option 3 - scale factor if (scale && scale[1]==3) // length should be 3 { scale_factor_old=scale[2]; // do not allow increasing scale factor if (scale_factor>=scale_factor_old) DLOG("Scale factor %u unchanged\n", scale_factor_old); else { scale[2]=scale_factor; DLOG("Scale factor change %u => %u\n", scale_factor_old, scale_factor); } } } } // scale_factor=SCALE_NONE - do not change void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor) { uint16_t winsize_old; winsize_old = htons(tcp->th_win); // << scale_factor; tcp->th_win = htons(winsize); DLOG("Window size change %u => %u\n", winsize_old, winsize); tcp_rewrite_wscale(tcp, scale_factor); } #ifdef __CYGWIN__ static HANDLE w_filter = NULL; static OVERLAPPED ovl = { .hEvent = NULL }; static const struct str_list_head *wlan_filter_ssid = NULL, *nlm_filter_net = NULL; static DWORD logical_net_filter_tick=0; uint32_t w_win32_error=0; INetworkListManager* pNetworkListManager=NULL; static void guid2str(const GUID *guid, char *str) { snprintf(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]); } static bool str2guid(const char* str, GUID *guid) { unsigned int u[11],k; if (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)) return false; guid->Data1 = u[0]; if ((u[1] & 0xFFFF0000) || (u[2] & 0xFFFF0000)) return false; guid->Data2 = (USHORT)u[1]; guid->Data3 = (USHORT)u[2]; for (k = 0; k < 8; k++) { if (u[k+3] & 0xFFFFFF00) return false; guid->Data4[k] = (UCHAR)u[k+3]; } return true; } static const char *sNetworkCards="SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; // get adapter name from guid string static bool AdapterID2Name(const GUID *guid,char *name,DWORD name_len) { char sguid[39],sidx[32],val[256]; HKEY hkNetworkCards,hkCard; DWORD dwIndex,dwLen; bool bRet = false; WCHAR namew[128]; DWORD namew_len; if (name_len<2) return false; if ((w_win32_error = RegOpenKeyExA(HKEY_LOCAL_MACHINE,sNetworkCards,0,KEY_ENUMERATE_SUB_KEYS,&hkNetworkCards)) == ERROR_SUCCESS) { guid2str(guid, sguid+1); sguid[0]='{'; sguid[37]='}'; sguid[38]='\0'; for (dwIndex=0;;dwIndex++) { dwLen=sizeof(sidx)-1; w_win32_error = RegEnumKeyExA(hkNetworkCards,dwIndex,sidx,&dwLen,NULL,NULL,NULL,NULL); if (w_win32_error == ERROR_SUCCESS) { sidx[dwLen]='\0'; if ((w_win32_error = RegOpenKeyExA(hkNetworkCards,sidx,0,KEY_QUERY_VALUE,&hkCard)) == ERROR_SUCCESS) { dwLen=sizeof(val)-1; if ((w_win32_error = RegQueryValueExA(hkCard,"ServiceName",NULL,NULL,val,&dwLen)) == ERROR_SUCCESS) { val[dwLen]='\0'; if (!strcmp(val,sguid)) { namew_len = sizeof(namew)-sizeof(WCHAR); if ((w_win32_error = RegQueryValueExW(hkCard,L"Description",NULL,NULL,(LPBYTE)namew,&namew_len)) == ERROR_SUCCESS) { namew[namew_len/sizeof(WCHAR)]=L'\0'; if (WideCharToMultiByte(CP_UTF8, 0, namew, -1, name, name_len, NULL, NULL)) bRet = true; } } } RegCloseKey(hkCard); } if (bRet) break; } else break; } RegCloseKey(hkNetworkCards); } return bRet; } bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter) { win_dark_deinit(); if (LIST_EMPTY(ssid_filter)) ssid_filter=NULL; if (LIST_EMPTY(nlm_filter)) nlm_filter=NULL; if (nlm_filter) { if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) { if (FAILED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) { CoUninitialize(); return false; } } else return false; } nlm_filter_net = nlm_filter; wlan_filter_ssid = ssid_filter; return true; } bool win_dark_deinit(void) { if (pNetworkListManager) { pNetworkListManager->lpVtbl->Release(pNetworkListManager); pNetworkListManager = NULL; } if (nlm_filter_net) CoUninitialize(); wlan_filter_ssid = nlm_filter_net = NULL; } bool nlm_list(bool bAll) { bool bRet = true; if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) { INetworkListManager* pNetworkListManager; if (SUCCEEDED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) { IEnumNetworks* pEnumNetworks; if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_ALL, &pEnumNetworks))) { INetwork *pNet; INetworkConnection *pConn; IEnumNetworkConnections *pEnumConnections; VARIANT_BOOL bIsConnected, bIsConnectedInet; NLM_NETWORK_CATEGORY category; GUID idNet, idAdapter; BSTR bstrName; char Name[128],Name2[128]; int connected; for (connected = 1; connected >= !bAll; connected--) { for (;;) { if (FAILED(w_win32_error = pEnumNetworks->lpVtbl->Next(pEnumNetworks, 1, &pNet, NULL))) { bRet = false; break; } if (w_win32_error != S_OK) break; if (SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnected(pNet, &bIsConnected)) && SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnectedToInternet(pNet, &bIsConnectedInet)) && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetCategory(pNet, &category)) && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) { if (!!bIsConnected == connected) { if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) { printf("Name : %s", Name); if (bIsConnected) printf(" (connected)"); if (bIsConnectedInet) printf(" (inet)"); printf(" (%s)\n", category==NLM_NETWORK_CATEGORY_PUBLIC ? "public" : category==NLM_NETWORK_CATEGORY_PRIVATE ? "private" : category==NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED ? "domain" : "unknown"); guid2str(&idNet, Name); printf("NetID : %s\n", Name); if (connected && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkConnections(pNet, &pEnumConnections))) { while ((w_win32_error = pEnumConnections->lpVtbl->Next(pEnumConnections, 1, &pConn, NULL))==S_OK) { if (SUCCEEDED(w_win32_error = pConn->lpVtbl->GetAdapterId(pConn,&idAdapter))) { guid2str(&idAdapter, Name); if (AdapterID2Name(&idAdapter,Name2,sizeof(Name2))) printf("Adapter : %s (%s)\n", Name2, Name); else printf("Adapter : %s\n", Name); } pConn->lpVtbl->Release(pConn); } pEnumConnections->lpVtbl->Release(pEnumConnections); } printf("\n"); } else { w_win32_error = HRESULT_FROM_WIN32(GetLastError()); bRet = false; } } SysFreeString(bstrName); } else bRet = false; pNet->lpVtbl->Release(pNet); if (!bRet) break; } if (!bRet) break; pEnumNetworks->lpVtbl->Reset(pEnumNetworks); } pEnumNetworks->lpVtbl->Release(pEnumNetworks); } else bRet = false; pNetworkListManager->lpVtbl->Release(pNetworkListManager); } else bRet = false; } else bRet = false; CoUninitialize(); return bRet; } static bool nlm_filter_match(const struct str_list_head *nlm_list) { // no filter given. always matches. if (!nlm_list || LIST_EMPTY(nlm_list)) { w_win32_error = 0; return true; } bool bRet = true, bMatch = false; IEnumNetworks* pEnum; if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_CONNECTED, &pEnum))) { INetwork* pNet; GUID idNet,g; BSTR bstrName; char Name[128]; struct str_list *nlm; for (;;) { if (FAILED(w_win32_error = pEnum->lpVtbl->Next(pEnum, 1, &pNet, NULL))) { bRet = false; break; } if (w_win32_error != S_OK) break; if (SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) { if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) { LIST_FOREACH(nlm, nlm_list, next) { bMatch = !strcmp(Name,nlm->str) || str2guid(nlm->str,&g) && !memcmp(&idNet,&g,sizeof(GUID)); if (bMatch) break; } } else { w_win32_error = HRESULT_FROM_WIN32(GetLastError()); bRet = false; } SysFreeString(bstrName); } else bRet = false; pNet->lpVtbl->Release(pNet); if (!bRet || bMatch) break; } pEnum->lpVtbl->Release(pEnum); } else bRet = false; return bRet && bMatch; } static bool wlan_filter_match(const struct str_list_head *ssid_list) { DWORD dwCurVersion; HANDLE hClient = NULL; PWLAN_INTERFACE_INFO_LIST pIfList = NULL; PWLAN_INTERFACE_INFO pIfInfo; PWLAN_CONNECTION_ATTRIBUTES pConnectInfo; DWORD connectInfoSize, k; bool bRes; struct str_list *ssid; size_t len; // no filter given. always matches. if (!ssid_list || LIST_EMPTY(ssid_list)) { w_win32_error = 0; return true; } w_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient); if (w_win32_error != ERROR_SUCCESS) goto fail; w_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList); if (w_win32_error != ERROR_SUCCESS) goto fail; for (k = 0; k < pIfList->dwNumberOfItems; k++) { pIfInfo = pIfList->InterfaceInfo + k; if (pIfInfo->isState == wlan_interface_state_connected) { w_win32_error = WlanQueryInterface(hClient, &pIfInfo->InterfaceGuid, wlan_intf_opcode_current_connection, NULL, &connectInfoSize, (PVOID *)&pConnectInfo, NULL); if (w_win32_error != ERROR_SUCCESS) goto fail; // printf("%s\n", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID); LIST_FOREACH(ssid, ssid_list, next) { len = strlen(ssid->str); if (len==pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str,pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID,len)) { WlanFreeMemory(pConnectInfo); goto found; } } WlanFreeMemory(pConnectInfo); } } w_win32_error = 0; fail: bRes = false; ex: if (pIfList) WlanFreeMemory(pIfList); if (hClient) WlanCloseHandle(hClient, 0); return bRes; found: w_win32_error = 0; bRes = true; goto ex; } bool logical_net_filter_match(void) { return wlan_filter_match(wlan_filter_ssid) && nlm_filter_match(nlm_filter_net); } static bool logical_net_filter_match_rate_limited(void) { DWORD dwTick = GetTickCount() / 1000; if (logical_net_filter_tick == dwTick) return true; logical_net_filter_tick = dwTick; return logical_net_filter_match(); } static HANDLE windivert_init_filter(const char *filter, UINT64 flags) { LPSTR errormessage = NULL; HANDLE h, hMutex; const char *mutex_name = "Global\\winws_windivert_mutex"; // windivert driver start in windivert.dll has race conditions hMutex = CreateMutexA(NULL,TRUE,mutex_name); if (hMutex && GetLastError()==ERROR_ALREADY_EXISTS) WaitForSingleObject(hMutex,INFINITE); h = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags); w_win32_error = GetLastError(); if (hMutex) { ReleaseMutex(hMutex); CloseHandle(hMutex); SetLastError(w_win32_error); } if (h != INVALID_HANDLE_VALUE) return h; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, w_win32_error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPSTR)&errormessage, 0, NULL); DLOG_ERR("windivert: error opening filter: %s", errormessage); LocalFree(errormessage); if (w_win32_error == ERROR_INVALID_IMAGE_HASH) DLOG_ERR("windivert: try to disable secure boot and install OS patches\n"); return NULL; } void rawsend_cleanup(void) { if (w_filter) { CancelIoEx(w_filter,&ovl); WinDivertClose(w_filter); w_filter=NULL; } if (ovl.hEvent) { CloseHandle(ovl.hEvent); ovl.hEvent=NULL; } } bool windivert_init(const char *filter) { rawsend_cleanup(); w_filter = windivert_init_filter(filter, 0); if (w_filter) { ovl.hEvent = CreateEventW(NULL,FALSE,FALSE,NULL); if (!ovl.hEvent) { w_win32_error = GetLastError(); rawsend_cleanup(); return false; } return true; } return false; } static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) { UINT recv_len; DWORD err; DWORD rd; char c; if (bQuit) { errno=EINTR; return false; } if (!logical_net_filter_match_rate_limited()) { errno=ENODEV; return false; } usleep(0); if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl)) { *len = recv_len; return true; } for(;;) { w_win32_error = GetLastError(); switch(w_win32_error) { case ERROR_IO_PENDING: // make signals working while (WaitForSingleObject(ovl.hEvent,50)==WAIT_TIMEOUT) { if (bQuit) { errno=EINTR; return false; } if (!logical_net_filter_match_rate_limited()) { errno=ENODEV; return false; } usleep(0); } if (!GetOverlappedResult(hFilter,&ovl,&rd,TRUE)) continue; *len = rd; return true; case ERROR_INSUFFICIENT_BUFFER: errno = ENOBUFS; break; case ERROR_NO_DATA: errno = ESHUTDOWN; break; default: errno = EIO; } break; } return false; } bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) { return windivert_recv_filter(w_filter,packet,len,wa); } static bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) { bool b = WinDivertSend(hFilter,packet,(UINT)len,NULL,wa); w_win32_error = GetLastError(); return b; } bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) { return windivert_send_filter(w_filter,packet,len,wa); } bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) { WINDIVERT_ADDRESS wa; memset(&wa,0,sizeof(wa)); // pseudo interface id IfIdx.SubIfIdx if (sscanf(ifout,"%u.%u",&wa.Network.IfIdx,&wa.Network.SubIfIdx)!=2) { errno = EINVAL; return false; } wa.Outbound=1; wa.IPChecksum=1; wa.TCPChecksum=1; wa.UDPChecksum=1; wa.IPv6 = (dst->sa_family==AF_INET6); return windivert_send(data,len,&wa); } #else // *nix static int rawsend_sock4=-1, rawsend_sock6=-1; static bool b_bind_fix4=false, b_bind_fix6=false; static void rawsend_clean_sock(int *sock) { if (sock && *sock!=-1) { close(*sock); *sock=-1; } } void rawsend_cleanup(void) { rawsend_clean_sock(&rawsend_sock4); rawsend_clean_sock(&rawsend_sock6); } static int *rawsend_family_sock(sa_family_t family) { switch(family) { case AF_INET: return &rawsend_sock4; case AF_INET6: return &rawsend_sock6; default: return NULL; } } #ifdef BSD int socket_divert(sa_family_t family) { int fd; #ifdef __FreeBSD__ // freebsd14+ way // don't want to use ifdefs with os version to make binaries compatible with all versions fd = socket(PF_DIVERT, SOCK_RAW, 0); if (fd==-1 && (errno==EPROTONOSUPPORT || errno==EAFNOSUPPORT || errno==EPFNOSUPPORT)) #endif // freebsd13- or openbsd way fd = socket(family, SOCK_RAW, IPPROTO_DIVERT); return fd; } static int rawsend_socket_divert(sa_family_t family) { // HACK HACK HACK HACK HACK HACK HACK HACK // FreeBSD doesnt allow IP_HDRINCL for IPV6 // OpenBSD doesnt allow rawsending tcp frames // we either have to go to the link layer (its hard, possible problems arise, compat testing, ...) or use some HACKING // from my point of view disabling direct ability to send ip frames is not security. its SHIT int fd = socket_divert(family); if (fd!=-1 && !set_socket_buffers(fd,4096,RAW_SNDBUF)) { close(fd); return -1; } return fd; } static int rawsend_sendto_divert(sa_family_t family, int sock, const void *buf, size_t len) { struct sockaddr_storage sa; socklen_t slen; #ifdef __FreeBSD__ // since FreeBSD 14 it requires hardcoded ipv4 values, although can also send ipv6 frames family = AF_INET; slen = sizeof(struct sockaddr_in); #else // OpenBSD requires correct family and size switch(family) { case AF_INET: slen = sizeof(struct sockaddr_in); break; case AF_INET6: slen = sizeof(struct sockaddr_in6); break; default: return -1; } #endif memset(&sa,0,slen); sa.ss_family = family; return sendto(sock, buf, len, 0, (struct sockaddr*)&sa, slen); } #endif static int rawsend_socket_raw(int domain, int proto) { int fd = socket(domain, SOCK_RAW, proto); if (fd!=-1) { #ifdef __linux__ int s=RAW_SNDBUF/2; int r=2048; #else int s=RAW_SNDBUF; int r=4096; #endif if (!set_socket_buffers(fd,r,s)) { close(fd); return -1; } } return fd; } static bool set_socket_fwmark(int sock, uint32_t fwmark) { #ifdef BSD #ifdef SO_USER_COOKIE if (setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &fwmark, sizeof(fwmark)) == -1) { DLOG_PERROR("rawsend: setsockopt(SO_USER_COOKIE)"); return false; } #endif #elif defined(__linux__) if (setsockopt(sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1) { DLOG_PERROR("rawsend: setsockopt(SO_MARK)"); return false; } #endif return true; } static int rawsend_socket(sa_family_t family) { int *sock = rawsend_family_sock(family); if (!sock) return -1; if (*sock==-1) { int yes=1,pri=6; //printf("rawsend_socket: family %d",family); #ifdef __FreeBSD__ // IPPROTO_RAW with ipv6 in FreeBSD always returns EACCES on sendto. // must use IPPROTO_TCP for ipv6. IPPROTO_RAW works for ipv4 // divert sockets are always v4 but accept both v4 and v6 *sock = rawsend_socket_divert(AF_INET); #elif defined(__OpenBSD__) || defined (__APPLE__) // OpenBSD does not allow sending TCP frames through raw sockets // I dont know about macos. They have dropped ipfw in recent versions and their PF does not support divert-packet *sock = rawsend_socket_divert(family); #else *sock = rawsend_socket_raw(family, IPPROTO_RAW); #endif if (*sock==-1) { DLOG_PERROR("rawsend: socket()"); return -1; } #ifdef __linux__ if (setsockopt(*sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1) { DLOG_PERROR("rawsend: setsockopt(SO_PRIORITY)"); goto exiterr; } if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_NODEFRAG, &yes, sizeof(yes)) == -1) { DLOG_PERROR("rawsend: setsockopt(IP_NODEFRAG)"); goto exiterr; } if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_FREEBIND, &yes, sizeof(yes)) == -1) { DLOG_PERROR("rawsend: setsockopt(IP_FREEBIND)"); goto exiterr; } if (family==AF_INET6 && setsockopt(*sock, SOL_IPV6, IPV6_FREEBIND, &yes, sizeof(yes)) == -1) { //DLOG_PERROR("rawsend: setsockopt(IPV6_FREEBIND)"); // dont error because it's supported only from kernel 4.15 } #endif } return *sock; exiterr: rawsend_clean_sock(sock); return -1; } bool rawsend_preinit(bool bind_fix4, bool bind_fix6) { b_bind_fix4 = bind_fix4; b_bind_fix6 = bind_fix6; // allow ipv6 disabled systems return rawsend_socket(AF_INET)!=-1 && (rawsend_socket(AF_INET6)!=-1 || errno==EAFNOSUPPORT); } bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) { ssize_t bytes; int sock=rawsend_socket(dst->sa_family); if (sock==-1) return false; if (!set_socket_fwmark(sock,fwmark)) return false; int salen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); struct sockaddr_storage dst2; memcpy(&dst2,dst,salen); if (dst->sa_family==AF_INET6) ((struct sockaddr_in6 *)&dst2)->sin6_port = 0; // or will be EINVAL in linux #if defined(BSD) bytes = rawsend_sendto_divert(dst->sa_family,sock,data,len); if (bytes==-1) { DLOG_PERROR("rawsend: sendto_divert"); return false; } return true; #else #ifdef __linux__ struct sockaddr_storage sa_src; switch(dst->sa_family) { case AF_INET: if (!b_bind_fix4) goto nofix; extract_endpoints(data,NULL,NULL,NULL, &sa_src, NULL); break; case AF_INET6: if (!b_bind_fix6) goto nofix; extract_endpoints(NULL,data,NULL,NULL, &sa_src, NULL); break; default: return false; // should not happen } //printf("family %u dev %s bind : ", dst->sa_family, ifout); print_sockaddr((struct sockaddr *)&sa_src); printf("\n"); if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifout, ifout ? strlen(ifout)+1 : 0) == -1) { DLOG_PERROR("rawsend: setsockopt(SO_BINDTODEVICE)"); return false; } if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) { DLOG_PERROR("rawsend: bind (ignoring)"); // do not fail. this can happen regardless of IP_FREEBIND // rebind to any address memset(&sa_src,0,sizeof(sa_src)); sa_src.ss_family = dst->sa_family; if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) { DLOG_PERROR("rawsend: bind to any"); return false; } } nofix: #endif // normal raw socket sendto bytes = sendto(sock, data, len, 0, (struct sockaddr*)&dst2, salen); if (bytes==-1) { char s[40]; snprintf(s,sizeof(s),"rawsend: sendto (%zu)",len); DLOG_PERROR(s); return false; } return true; #endif } #endif // not CYGWIN bool rawsend_rp(const struct rawpacket *rp) { return rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,rp->packet,rp->len); } bool rawsend_queue(struct rawpacket_tailhead *q) { struct rawpacket *rp; bool b; for (b=true; (rp=rawpacket_dequeue(q)) ; rawpacket_free(rp)) b &= rawsend_rp(rp); return b; } #if defined(HAS_FILTER_SSID) && defined(__linux__) // linux-specific wlan retrieval implementation typedef void netlink_prepare_nlh_cb_t(struct nlmsghdr *nlh, void *param); static 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) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct genlmsghdr *genl; ssize_t rd; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = type; nlh->nlmsg_flags = flags; genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); genl->cmd = cmd; genl->version = version; if (cb_prepare_nlh) cb_prepare_nlh(nlh, prepare_data); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { DLOG_PERROR("mnl_socket_sendto"); return false; } while ((rd=mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) { switch(mnl_cb_run(buf, rd, 0, 0, cb_data, data)) { case MNL_CB_STOP: return true; case MNL_CB_OK: break; default: return false; } } return false; } static void wlan_id_prepare(struct nlmsghdr *nlh, void *param) { mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, "nl80211"); } static int wlan_id_attr_cb(const struct nlattr *attr, void *data) { if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) { DLOG_PERROR("mnl_attr_type_valid"); return MNL_CB_ERROR; } switch(mnl_attr_get_type(attr)) { case CTRL_ATTR_FAMILY_ID: if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) { DLOG_PERROR("mnl_attr_validate(family_id)"); return MNL_CB_ERROR; } *((uint16_t*)data) = mnl_attr_get_u16(attr); break; } return MNL_CB_OK; } static int wlan_id_cb(const struct nlmsghdr *nlh, void *data) { return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_id_attr_cb, data); } static uint16_t wlan_get_family_id(struct mnl_socket* nl) { uint16_t id; return 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; } static int wlan_info_attr_cb(const struct nlattr *attr, void *data) { struct wlan_interface *wlan = (struct wlan_interface *)data; switch(mnl_attr_get_type(attr)) { case NL80211_ATTR_IFINDEX: if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { DLOG_PERROR("mnl_attr_validate(ifindex)"); return MNL_CB_ERROR; } wlan->ifindex = mnl_attr_get_u32(attr); break; case NL80211_ATTR_SSID: if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) { DLOG_PERROR("mnl_attr_validate(ssid)"); return MNL_CB_ERROR; } snprintf(wlan->ssid,sizeof(wlan->ssid),"%s",mnl_attr_get_str(attr)); break; case NL80211_ATTR_IFNAME: if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) { DLOG_PERROR("mnl_attr_validate(ifname)"); return MNL_CB_ERROR; } snprintf(wlan->ifname,sizeof(wlan->ifname),"%s",mnl_attr_get_str(attr)); break; } return MNL_CB_OK; } struct wlan_info_req { struct wlan_interface_collection *wc; bool bReqSSID; }; static int wlan_info_cb(const struct nlmsghdr *nlh, void *data) { int ret; struct wlan_info_req *wr = (struct wlan_info_req*)data; if (wr->wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK; memset(wr->wc->wlan + wr->wc->count,0,sizeof(struct wlan_interface)); ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), wlan_info_attr_cb, wr->wc->wlan + wr->wc->count); if (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) wr->wc->count++; return ret; } static bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w, bool bReqSSID) { struct wlan_info_req req = { .bReqSSID = bReqSSID, .wc = w }; return 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); } static void scan_prepare(struct nlmsghdr *nlh, void *param) { mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, *(int*)param); } static uint8_t *find_ie(uint8_t *buf, size_t len, uint8_t ie) { while (len>=2) { if (len<(2+buf[1])) break; if (buf[0]==ie) return buf; buf+=buf[1]+2; len-=buf[1]+2; } return NULL; } static int scan_info_attr_cb(const struct nlattr *attr, void *data) { struct wlan_interface *wlan = (struct wlan_interface *)data; const struct nlattr *nested; uint8_t *payload, *ie; uint16_t payload_len; bool ok; switch(mnl_attr_get_type(attr)) { case NL80211_ATTR_IFINDEX: if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { DLOG_PERROR("mnl_attr_validate"); return MNL_CB_ERROR; } wlan->ifindex = mnl_attr_get_u32(attr); if (!if_indextoname(wlan->ifindex, wlan->ifname)) DLOG_PERROR("if_indextoname"); break; case NL80211_ATTR_BSS: if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0) { DLOG_PERROR("mnl_attr_validate"); return MNL_CB_ERROR; } ok = false; mnl_attr_for_each_nested(nested, attr) { if (mnl_attr_get_type(nested)==NL80211_BSS_STATUS) { uint32_t status = mnl_attr_get_u32(nested); if (status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_AUTHENTICATED || status==NL80211_BSS_STATUS_IBSS_JOINED) { ok=1; break; } } } if (!ok) break; mnl_attr_for_each_nested(nested, attr) { switch(mnl_attr_get_type(nested)) { case NL80211_BSS_INFORMATION_ELEMENTS: payload_len = mnl_attr_get_payload_len(nested); payload = mnl_attr_get_payload(nested); ie = find_ie(payload,payload_len,0); if (ie) { uint8_t l = ie[1]; if (l>=(sizeof(wlan->ssid))) l=sizeof(wlan->ssid)-1; memcpy(wlan->ssid,ie+2,l); wlan->ssid[l]=0; } break; } } break; } return MNL_CB_OK; } static int scan_info_cb(const struct nlmsghdr *nlh, void *data) { int ret; struct wlan_interface_collection *wc = (struct wlan_interface_collection*)data; if (wc->count>=WLAN_INTERFACE_MAX) return MNL_CB_OK; memset(wc->wlan+wc->count,0,sizeof(wc->wlan[0])); ret = mnl_attr_parse(nlh, sizeof(struct genlmsghdr), scan_info_attr_cb, wc->wlan+wc->count); if (ret>=0 && *wc->wlan[wc->count].ssid && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex) wc->count++; return ret; } static bool scan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w) { struct wlan_interface_collection wc_all = { .count = 0 }; // wlan_info does not return ssid since kernel 5.19 // it's used to enumerate all wifi interfaces then call scan_info on each if (!wlan_info(nl, wlan_family_id, &wc_all, false)) return false; w->count=0; for(int i=0;icount;i++) if (!strcmp(w->wlan[i].ifname,ifname)) return w->wlan[i].ssid; } return NULL; } const char *wlan_ifidx2ssid(const struct wlan_interface_collection *w,int ifidx) { int i; for (i=0;icount;i++) if (w->wlan[i].ifindex == ifidx) return w->wlan[i].ssid; return NULL; } const char *wlan_ssid_search_ifname(const char *ifname) { return wlan_ifname2ssid(&wlans,ifname); } const char *wlan_ssid_search_ifidx(int ifidx) { return wlan_ifidx2ssid(&wlans,ifidx); } #endif uint8_t hop_count_guess(uint8_t ttl) { // 18.65.168.125 ( cloudfront ) 255 // 157.254.246.178 128 // 1.1.1.1 64 // guess original ttl. consider path lengths less than 32 hops uint8_t orig; if (ttl>223) orig=255; else if (ttl<128 && ttl>96) orig=128; else if (ttl<64 && ttl>32) orig=64; else return 0; return orig - ttl; } // return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling uint8_t autottl_eval(uint8_t hop_count, const autottl *attl) { uint8_t fake; int d; d = (int)hop_count + attl->delta; if (dmin) fake=attl->min; else if (d>attl->max) fake=attl->max; else fake=(uint8_t)d; if (attl->delta<0 && fake>=hop_count || attl->delta>=0 && fakesin_port; if (bOutbound) ip->ip_dst = target4->sin_addr; else ip->ip_src = target4->sin_addr; ip4_fix_checksum(ip); } else if (ip6 && target6) { nport = target6->sin6_port; if (bOutbound) ip6->ip6_dst = target6->sin6_addr; else ip6->ip6_src = target6->sin6_addr; } else return; if (nport) { if (tcphdr) { if (bOutbound) tcphdr->th_dport = nport; else tcphdr->th_sport = nport; } if (udphdr) { if (bOutbound) udphdr->uh_dport = nport; else udphdr->uh_sport = nport; } } } void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) { if (!(verdict & VERDICT_NOCSUM)) { #ifdef __CYGWIN__ // always fix csum for windivert. original can be partial or bad if ((verdict & VERDICT_MASK)!=VERDICT_DROP) #elif defined(__FreeBSD__) // FreeBSD tend to pass ipv6 frames with wrong checksum if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) #else // if original packet was tampered earlier it needs checksum fixed if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) #endif tcp_fix_checksum(tcphdr,transport_len,ip,ip6hdr); } } void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) { if (!(verdict & VERDICT_NOCSUM)) { #ifdef __CYGWIN__ // always fix csum for windivert. original can be partial or bad if ((verdict & VERDICT_MASK)!=VERDICT_DROP) #elif defined(__FreeBSD__) // FreeBSD tend to pass ipv6 frames with wrong checksum if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) #else // if original packet was tampered earlier it needs checksum fixed if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) #endif udp_fix_checksum(udphdr,transport_len,ip,ip6hdr); } } void dbgprint_socket_buffers(int fd) { if (params.debug) { int v; socklen_t sz; sz = sizeof(int); if (!getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &v, &sz)) DLOG("fd=%d SO_RCVBUF=%d\n", fd, v); sz = sizeof(int); if (!getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &v, &sz)) DLOG("fd=%d SO_SNDBUF=%d\n", fd, v); } } bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) { DLOG("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n", fd, rcvbuf, sndbuf); if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0) { DLOG_PERROR("setsockopt (SO_RCVBUF)"); return false; } if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0) { DLOG_PERROR("setsockopt (SO_SNDBUF)"); return false; } dbgprint_socket_buffers(fd); return true; } ================================================ FILE: nfq/darkmagic.h ================================================ #pragma once #include "nfqws.h" #include "checksum.h" #include "packet_queue.h" #include "pools.h" #include #include #include #include #include #include #define __FAVOR_BSD #include #include #include #include #ifndef IPV6_FREEBIND #define IPV6_FREEBIND 78 #endif #ifdef __CYGWIN__ #define INITGUID #include "windivert/windivert.h" #endif #ifndef IPPROTO_DIVERT #define IPPROTO_DIVERT 258 #endif #ifndef AF_DIVERT #define AF_DIVERT 44 /* divert(4) */ #endif #ifndef PF_DIVERT #define PF_DIVERT AF_DIVERT #endif // returns netorder value uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment); uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment); #define FOOL_NONE 0x00 #define FOOL_MD5SIG 0x01 #define FOOL_BADSUM 0x02 #define FOOL_TS 0x04 #define FOOL_BADSEQ 0x08 #define FOOL_HOPBYHOP 0x10 #define FOOL_HOPBYHOP2 0x20 #define FOOL_DESTOPT 0x40 #define FOOL_IPFRAG1 0x80 #define FOOL_DATANOACK 0x100 #define SCALE_NONE ((uint8_t)-1) #define VERDICT_PASS 0 #define VERDICT_MODIFY 1 #define VERDICT_DROP 2 #define VERDICT_MASK 3 #define VERDICT_NOCSUM 4 #define VERDICT_GARBAGE 8 #define IP4_TOS(ip_header) (ip_header ? ip_header->ip_tos : 0) #define IP4_IP_ID(ip_header) (ip_header ? ip_header->ip_id : 0) #define IP6_FLOW(ip6_header) (ip6_header ? ip6_header->ip6_ctlun.ip6_un1.ip6_un1_flow : 0) // seq and wsize have network byte order bool prepare_tcp_segment4( const struct sockaddr_in *src, const struct sockaddr_in *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool prepare_tcp_segment6( const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, uint8_t ttl, uint32_t flow_label, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool prepare_tcp_segment( const struct sockaddr *src, const struct sockaddr *dst, uint16_t tcp_flags, bool sack, uint16_t nmss, uint32_t nseq, uint32_t nack_seq, uint16_t nwsize, uint8_t scale_factor, uint32_t *timestamps, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t flow_label, uint32_t fooling, uint32_t ts_increment, uint32_t badseq_increment, uint32_t badseq_ack_increment, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool prepare_udp_segment4( const struct sockaddr_in *src, const struct sockaddr_in *dst, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool prepare_udp_segment6( const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, uint8_t ttl, uint32_t flow_label, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool prepare_udp_segment( const struct sockaddr *src, const struct sockaddr *dst, bool DF, uint8_t ttl, uint8_t tos, uint16_t ip_id, uint32_t flow_label, uint32_t fooling, const uint8_t *padding, size_t padding_size, int padlen, const void *data, uint16_t len, uint8_t *buf, size_t *buflen); bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen); // ipv4: ident==-1 - copy ip_id from original ipv4 packet bool ip_frag4( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size); bool ip_frag6( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size); bool ip_frag( const uint8_t *pkt, size_t pkt_size, size_t frag_pos, uint32_t ident, uint8_t *pkt1, size_t *pkt1_size, uint8_t *pkt2, size_t *pkt2_size); bool rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl); uint16_t get_tcp_flags(const struct tcphdr *tcp); void apply_tcp_flags(struct tcphdr *tcp, uint16_t fl); void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport); void 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); uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind); uint32_t *tcp_find_timestamps(struct tcphdr *tcp); uint8_t tcp_find_scale_factor(const struct tcphdr *tcp); uint16_t tcp_find_mss(struct tcphdr *tcp); bool tcp_has_sack(struct tcphdr *tcp); bool tcp_has_fastopen(const struct tcphdr *tcp); bool ip_has_df(const struct ip *ip); #ifdef __CYGWIN__ extern uint32_t w_win32_error; bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter); bool win_dark_deinit(void); bool logical_net_filter_match(void); bool nlm_list(bool bAll); bool windivert_init(const char *filter); bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa); bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa); #else // should pre-do it if dropping privileges. otherwise its not necessary bool rawsend_preinit(bool bind_fix4, bool bind_fix6); #endif // auto creates internal socket and uses it for subsequent calls bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len); bool rawsend_rp(const struct rawpacket *rp); // return trues if all packets were send successfully bool rawsend_queue(struct rawpacket_tailhead *q); // cleans up socket autocreated by rawsend void rawsend_cleanup(void); #ifdef BSD int socket_divert(sa_family_t family); #endif const char *proto_name(uint8_t proto); uint16_t family_from_proto(uint8_t l3proto); void print_ip(const struct ip *ip); void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto); void print_tcphdr(const struct tcphdr *tcphdr); void print_udphdr(const struct udphdr *udphdr); void str_ip(char *s, size_t s_len, const struct ip *ip); void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto); void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr); void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr); void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr); bool proto_check_ipv4(const uint8_t *data, size_t len); void proto_skip_ipv4(uint8_t **data, size_t *len); bool proto_check_ipv6(const uint8_t *data, size_t len); void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type); bool proto_check_tcp(const uint8_t *data, size_t len); void proto_skip_tcp(uint8_t **data, size_t *len); bool proto_check_udp(const uint8_t *data, size_t len); void proto_skip_udp(uint8_t **data, size_t *len); struct dissect { uint8_t *data_pkt; size_t len_pkt; struct ip *ip; struct ip6_hdr *ip6; uint8_t proto; struct tcphdr *tcp; struct udphdr *udp; size_t transport_len; uint8_t *data_payload; size_t len_payload; }; void proto_dissect_l3l4(uint8_t *data, size_t len,struct dissect *dis); bool tcp_synack_segment(const struct tcphdr *tcphdr); bool tcp_syn_segment(const struct tcphdr *tcphdr); bool tcp_ack_segment(const struct tcphdr *tcphdr); // scale_factor=SCALE_NONE - do not change void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor); void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor); typedef struct { int8_t delta; uint8_t min, max; } autottl; #define AUTOTTL_ENABLED(a) ((a).delta || (a).min || (a).max) uint8_t hop_count_guess(uint8_t ttl); uint8_t autottl_eval(uint8_t hop_count, const autottl *attl); void 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); void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); void dbgprint_socket_buffers(int fd); bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); #ifdef HAS_FILTER_SSID struct wlan_interface { int ifindex; char ifname[IFNAMSIZ], ssid[33]; }; #define WLAN_INTERFACE_MAX 16 struct wlan_interface_collection { int count; struct wlan_interface wlan[WLAN_INTERFACE_MAX]; }; extern struct wlan_interface_collection wlans; void wlan_info_deinit(void); bool wlan_info_init(void); bool wlan_info_get_rate_limited(void); const char *wlan_ssid_search_ifname(const char *ifname); const char *wlan_ssid_search_ifidx(int ifidx); #endif ================================================ FILE: nfq/desync.c ================================================ #define _GNU_SOURCE #include #include #include "desync.h" #include "protocol.h" #include "params.h" #include "helpers.h" #include "hostlist.h" #include "ipset.h" #include "conntrack.h" const char *fake_http_request_default = "GET / HTTP/1.1\r\nHost: www.iana.org\r\n" "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n" "Accept-Encoding: gzip, deflate, br\r\n\r\n"; // SNI - www.microsoft.com const uint8_t fake_tls_clienthello_default[680] = { 0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41, 0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f, 0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b, 0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2, 0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba, 0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65, 0x80, 0x5d, 0x40, 0x38, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16, 0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01, 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad, 0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4, 0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4, 0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8, 0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca, 0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60, 0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07, 0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f, 0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00, 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x0d, 0x00, 0x18, 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe, 0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62, 0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce, 0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11, 0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27, 0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51, 0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b, 0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11, 0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8, 0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0, 0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe, 0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45, 0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc, 0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50, 0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5, 0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5, 0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf, 0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b, 0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16, 0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2, 0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51, 0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0, 0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4, 0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb, 0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b }; #define PKTDATA_MAXDUMP 32 #define IP_MAXDUMP 80 #define TCP_MAX_REASM 16384 #define UDP_MAX_REASM 16384 static void TLSDebugHandshake(const uint8_t *tls, size_t sz) { if (!params.debug) return; if (sz < 6) return; const uint8_t *ext; size_t len, len2; uint16_t v_handshake = pntoh16(tls + 4), v, v2; DLOG("TLS handshake version : %s\n", TLSVersionStr(v_handshake)); if (TLSFindExtInHandshake(tls, sz, 43, &ext, &len, false)) { if (len) { len2 = ext[0]; if (len2 < len) { for (ext++, len2 &= ~1; len2; len2 -= 2, ext += 2) { v = pntoh16(ext); DLOG("TLS supported versions ext : %s\n", TLSVersionStr(v)); } } } } else DLOG("TLS supported versions ext : not present\n"); if (TLSFindExtInHandshake(tls, sz, 16, &ext, &len, false)) { if (len >= 2) { len2 = pntoh16(ext); if (len2 <= (len - 2)) { char s[32]; for (ext += 2; len2;) { v = *ext; ext++; len2--; if (v <= len2) { v2 = v < sizeof(s) ? v : sizeof(s) - 1; memcpy(s, ext, v2); s[v2] = 0; DLOG("TLS ALPN ext : %s\n", s); len2 -= v; ext += v; } else break; } } } } else DLOG("TLS ALPN ext : not present\n"); DLOG("TLS ECH ext : %s\n", TLSFindExtInHandshake(tls, sz, 65037, NULL, NULL, false) ? "present" : "not present"); } static void TLSDebug(const uint8_t *tls, size_t sz) { if (!params.debug) return; if (sz < 11) return; DLOG("TLS record layer version : %s\n", TLSVersionStr(pntoh16(tls + 1))); size_t reclen = TLSRecordLen(tls); if (reclen < sz) sz = reclen; // correct len if it has more data than the first tls record has TLSDebugHandshake(tls + 5, sz - 5); } bool desync_valid_zero_stage(enum dpi_desync_mode mode) { return mode == DESYNC_SYNACK || mode == DESYNC_SYNDATA; } bool desync_valid_first_stage(enum dpi_desync_mode mode) { return mode == DESYNC_FAKE || mode == DESYNC_FAKE_KNOWN || mode == DESYNC_RST || mode == DESYNC_RSTACK || mode == DESYNC_HOPBYHOP || mode == DESYNC_DESTOPT || mode == DESYNC_IPFRAG1; } bool desync_only_first_stage(enum dpi_desync_mode mode) { return false; } bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode) { return mode == DESYNC_NONE || mode == DESYNC_FAKEDDISORDER || mode == DESYNC_FAKEDSPLIT || mode == DESYNC_MULTISPLIT || mode == DESYNC_MULTIDISORDER || mode == DESYNC_HOSTFAKESPLIT || mode == DESYNC_IPFRAG2; } bool desync_valid_second_stage_udp(enum dpi_desync_mode mode) { return mode == DESYNC_NONE || mode == DESYNC_UDPLEN || mode == DESYNC_TAMPER || mode == DESYNC_IPFRAG2; } bool desync_valid_second_stage(enum dpi_desync_mode mode) { return desync_valid_second_stage_tcp(mode) || desync_valid_second_stage_udp(mode); } enum dpi_desync_mode desync_mode_from_string(const char *s) { if (!s) return DESYNC_NONE; else if (!strcmp(s, "fake")) return DESYNC_FAKE; else if (!strcmp(s, "fakeknown")) return DESYNC_FAKE_KNOWN; else if (!strcmp(s, "rst")) return DESYNC_RST; else if (!strcmp(s, "rstack")) return DESYNC_RSTACK; else if (!strcmp(s, "synack")) return DESYNC_SYNACK; else if (!strcmp(s, "syndata")) return DESYNC_SYNDATA; else if (!strcmp(s, "fakeddisorder") || !strcmp(s, "disorder")) return DESYNC_FAKEDDISORDER; else if (!strcmp(s, "fakedsplit") || !strcmp(s, "split")) return DESYNC_FAKEDSPLIT; else if (!strcmp(s, "multisplit") || !strcmp(s, "split2")) return DESYNC_MULTISPLIT; else if (!strcmp(s, "multidisorder") || !strcmp(s, "disorder2")) return DESYNC_MULTIDISORDER; else if (!strcmp(s, "hostfakesplit")) return DESYNC_HOSTFAKESPLIT; else if (!strcmp(s, "ipfrag2")) return DESYNC_IPFRAG2; else if (!strcmp(s, "hopbyhop")) return DESYNC_HOPBYHOP; else if (!strcmp(s, "destopt")) return DESYNC_DESTOPT; else if (!strcmp(s, "ipfrag1")) return DESYNC_IPFRAG1; else if (!strcmp(s, "udplen")) return DESYNC_UDPLEN; else if (!strcmp(s, "tamper")) return DESYNC_TAMPER; return DESYNC_INVALID; } static bool dp_match( struct desync_profile *dp, uint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { bool bHostlistsEmpty; if (bCheckDone) *bCheckDone = false; if (!HostlistsReloadCheckForProfile(dp)) return false; if ((dest->sa_family == AF_INET && !dp->filter_ipv4) || (dest->sa_family == AF_INET6 && !dp->filter_ipv6)) // L3 filter does not match return false; if ((l3proto == IPPROTO_TCP && !port_filters_in_range(&dp->pf_tcp, saport(dest))) || (l3proto == IPPROTO_UDP && !port_filters_in_range(&dp->pf_udp, saport(dest)))) // L4 filter does not match return false; if (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) // L7 filter does not match return false; #ifdef HAS_FILTER_SSID if (!LIST_EMPTY(&dp->filter_ssid) && !strlist_search(&dp->filter_ssid, ssid)) return false; #endif bHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp); if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty) // avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown. return false; if (!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)) // target ip does not match return false; // autohostlist profile matching l3/l4/l7 filter always win if (dp->hostlist_auto) return true; if (bHostlistsEmpty) // profile without hostlist filter wins return true; else { // if hostlists are present profile matches only if hostname is known and satisfy profile hostlists if (hostname) { if (bCheckDone) *bCheckDone = true; bool b; b = HostlistCheck(dp, hostname, bNoSubdom, bExcluded, true); if (bCheckResult) *bCheckResult = b; return b; } } return false; } static struct desync_profile *dp_find( struct desync_profile_list_head *head, uint8_t l3proto, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { struct desync_profile_list *dpl; if (params.debug) { char ip_port[48]; ntop46_port(dest, ip_port, sizeof(ip_port)); DLOG("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 : ""); } if (bCheckDone) *bCheckDone = false; LIST_FOREACH(dpl, head, next) { if (dp_match(&dpl->dp, l3proto, dest, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded)) { DLOG("desync profile %d matches\n", dpl->dp.n); return &dpl->dp; } } DLOG("desync profile not found\n"); return NULL; } // auto creates internal socket and uses it for subsequent calls static bool rawsend_rep(int repeats, const struct sockaddr* dst, uint32_t fwmark, const char *ifout, const void *data, size_t len) { for (int i = 0; i < repeats; i++) if (!rawsend(dst, fwmark, ifout, data, len)) return false; return true; } static uint64_t cutoff_get_limit(const t_ctrack *ctrack, char mode) { switch (mode) { case 'n': return ctrack->pcounter_orig; case 'd': return ctrack->pdcounter_orig; case 's': return ctrack->seq_last - ctrack->seq0; default: return 0; } } static bool cutoff_test(const t_ctrack *ctrack, uint64_t cutoff, char mode) { return cutoff && cutoff_get_limit(ctrack, mode) >= cutoff; } static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto) { if (ctrack && ctrack->dp) { if (proto == IPPROTO_TCP) ctrack->b_wssize_cutoff |= cutoff_test(ctrack, ctrack->dp->wssize_cutoff, ctrack->dp->wssize_cutoff_mode); ctrack->b_desync_cutoff |= cutoff_test(ctrack, ctrack->dp->desync_cutoff, ctrack->dp->desync_cutoff_mode); ctrack->b_dup_cutoff |= cutoff_test(ctrack, ctrack->dp->dup_cutoff, ctrack->dp->dup_cutoff_mode); ctrack->b_orig_mod_cutoff |= cutoff_test(ctrack, ctrack->dp->orig_mod_cutoff, ctrack->dp->orig_mod_cutoff_mode); // in MULTI STRATEGY concept conntrack entry holds desync profile // we do not want to remove conntrack entries ASAP anymore /* // we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached // do not drop udp entry because it will be recreated when next packet arrives if (proto==IPPROTO_TCP) ctrack->b_cutoff |= \ (!ctrack->dp->wssize || ctrack->b_wssize_cutoff) && (!ctrack->dp->desync_cutoff || ctrack->b_desync_cutoff) && (!ctrack->hostname_ah_check || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP) && ReasmIsEmpty(&ctrack->reasm_orig); */ } } static void wssize_cutoff(t_ctrack *ctrack) { if (ctrack) { ctrack->b_wssize_cutoff = true; maybe_cutoff(ctrack, IPPROTO_TCP); } } static void forced_wssize_cutoff(t_ctrack *ctrack) { if (ctrack && ctrack->dp && !ctrack->b_wssize_cutoff) { DLOG("forced wssize-cutoff\n"); wssize_cutoff(ctrack); } } static void ctrack_stop_retrans_counter(t_ctrack *ctrack) { if (ctrack && ctrack->hostname_ah_check) { ctrack->req_retrans_counter = RETRANS_COUNTER_STOP; maybe_cutoff(ctrack, IPPROTO_TCP); } } static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto) { if (hostname) { hostfail_pool *fail_counter; fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (fail_counter) { HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); DLOG("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter reset. website is working.", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); } } } // return true if retrans trigger fires static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold, const char *client_ip_port, t_l7proto l7proto) { if (ctrack && ctrack->dp && ctrack->hostname_ah_check && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP) { if (l4proto == IPPROTO_TCP) { if (!ctrack->req_seq_finalized || ctrack->req_seq_abandoned) return false; if (!seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end)) { DLOG("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); ctrack_stop_retrans_counter(ctrack); auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, l7proto); return false; } } ctrack->req_retrans_counter++; if (ctrack->req_retrans_counter >= threshold) { DLOG("req retrans threshold reached : %u/%u\n", ctrack->req_retrans_counter, threshold); ctrack_stop_retrans_counter(ctrack); return true; } DLOG("req retrans counter : %u/%u\n", ctrack->req_retrans_counter, threshold); } return false; } static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto) { hostfail_pool *fail_counter; fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (!fail_counter) { fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); if (!fail_counter) { DLOG_ERR("HostFailPoolAdd: out of memory\n"); return; } } fail_counter->counter++; DLOG("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n, hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); HOSTLIST_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); if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) { DLOG("auto hostlist (profile %d) : fail threshold reached. about to add %s to auto hostlist\n", dp->n, hostname); HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); DLOG("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); bool bExcluded = false; if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded) { DLOG("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename); HOSTLIST_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); if (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0)) { DLOG_ERR("StrPoolAddStr out of memory\n"); return; } if (!append_to_list_file(dp->hostlist_auto->filename, hostname)) { DLOG_PERROR("write to auto hostlist"); return; } if (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig)) DLOG_PERROR("file_mod_signature"); } else { DLOG("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : NOT adding, duplicate detected", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); } } } static void process_retrans_fail(t_ctrack *ctrack, uint8_t proto, const struct sockaddr *client) { char client_ip_port[48]; if (*params.hostlist_auto_debuglog) ntop46_port((struct sockaddr*)client, client_ip_port, sizeof(client_ip_port)); else *client_ip_port = 0; if (ctrack && ctrack->dp && ctrack->hostname && auto_hostlist_retrans(ctrack, proto, ctrack->dp->hostlist_auto_retrans_threshold, client_ip_port, ctrack->l7proto)) { HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : retrans threshold reached", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); } } static bool send_delayed(t_ctrack *ctrack) { if (!rawpacket_queue_empty(&ctrack->delayed)) { DLOG("SENDING %u delayed packets\n", rawpacket_queue_count(&ctrack->delayed)); return rawsend_queue(&ctrack->delayed); } return true; } static 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) { ReasmClear(reasm); if (sz <= szMax) { uint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : 0; if (ReasmInit(reasm, sz, seq)) { ReasmFeed(reasm, seq, data_payload, len_payload); DLOG("starting reassemble. now we have %zu/%zu\n", reasm->size_present, reasm->size); return true; } else DLOG("reassemble init failed. out of memory\n"); } else DLOG("unexpected large payload for reassemble: size=%zu\n", sz); return false; } static 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) { return reasm_start(ctrack, &ctrack->reasm_orig, proto, sz, szMax, data_payload, len_payload); } static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, const uint8_t *data_payload, size_t len_payload) { if (ctrack && !ReasmIsEmpty(reasm)) { uint32_t seq = (proto == IPPROTO_TCP) ? ctrack->seq_last : (uint32_t)reasm->size_present; if (ReasmFeed(reasm, seq, data_payload, len_payload)) { DLOG("reassemble : feeding data payload size=%zu. now we have %zu/%zu\n", len_payload, reasm->size_present, reasm->size); return true; } else { ReasmClear(reasm); DLOG("reassemble session failed\n"); send_delayed(ctrack); } } return false; } static bool reasm_orig_feed(t_ctrack *ctrack, uint8_t proto, const uint8_t *data_payload, size_t len_payload) { return reasm_feed(ctrack, &ctrack->reasm_orig, proto, data_payload, len_payload); } static void reasm_orig_stop(t_ctrack *ctrack, const char *dlog_msg) { if (ctrack) { if (!ReasmIsEmpty(&ctrack->reasm_orig)) { DLOG("%s", dlog_msg); ReasmClear(&ctrack->reasm_orig); } send_delayed(ctrack); } } static void reasm_orig_cancel(t_ctrack *ctrack) { reasm_orig_stop(ctrack, "reassemble session cancelled\n"); } static void reasm_orig_fin(t_ctrack *ctrack) { reasm_orig_stop(ctrack, "reassemble session finished\n"); } static uint8_t ct_new_postnat_fix(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, const struct tcphdr *tcp) { #ifdef __linux__ // if used in postnat chain, dropping initial packet will cause conntrack connection teardown // so we need to workaround this. // SYN and SYN,ACK checks are for conntrack-less mode if (ctrack && ctrack->pcounter_orig == 1 || tcp && (tcp_syn_segment(tcp) || tcp_synack_segment(tcp))) { DLOG("applying linux postnat conntrack workaround\n"); // make ip protocol invalid and low TTL if (ip6) { ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = 255; ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = 1; } if (ip) { // this likely also makes ipv4 header checksum invalid ip->ip_p = 255; ip->ip_ttl = 1; } return VERDICT_MODIFY | VERDICT_NOCSUM; } #endif return VERDICT_DROP; } static bool check_desync_interval(const struct desync_profile *dp, const t_ctrack *ctrack) { if (dp) { if (dp->desync_start) { if (ctrack) { if (!cutoff_test(ctrack, dp->desync_start, dp->desync_start_mode)) { DLOG("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); return false; } DLOG("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); } else { DLOG("not desyncing. desync-start is set but conntrack entry is missing\n"); return false; } } if (dp->desync_cutoff) { if (ctrack) { if (ctrack->b_desync_cutoff) { DLOG("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); return false; } DLOG("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); } else { DLOG("not desyncing. desync-cutoff is set but conntrack entry is missing\n"); return false; } } } return true; } static bool process_desync_interval(const struct desync_profile *dp, t_ctrack *ctrack) { if (check_desync_interval(dp, ctrack)) return true; else { reasm_orig_cancel(ctrack); return false; } } static bool check_dup_interval(const struct desync_profile *dp, const t_ctrack *ctrack) { if (dp) { if (dp->dup_start) { if (ctrack) { if (!cutoff_test(ctrack, dp->dup_start, dp->dup_start_mode)) { DLOG("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); return false; } DLOG("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); } else { DLOG("not duping. dup-start is set but conntrack entry is missing\n"); return false; } } if (dp->dup_cutoff) { if (ctrack) { if (ctrack->b_dup_cutoff) { DLOG("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); return false; } DLOG("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); } else { DLOG("not duping. dup-cutoff is set but conntrack entry is missing\n"); return false; } } } return true; } static bool check_orig_mod_interval(const struct desync_profile *dp, const t_ctrack *ctrack) { if (dp) { if (dp->orig_mod_start) { if (ctrack) { if (!cutoff_test(ctrack, dp->orig_mod_start, dp->orig_mod_start_mode)) { DLOG("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); return false; } DLOG("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); } else { DLOG("not modding original. orig-mod-start is set but conntrack entry is missing\n"); return false; } } if (dp->orig_mod_cutoff) { if (ctrack) { if (ctrack->b_orig_mod_cutoff) { DLOG("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); return false; } DLOG("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); } else { DLOG("not modding original. orig-mod-cutoff is set but conntrack entry is missing\n"); return false; } } } return true; } static bool replay_queue(struct rawpacket_tailhead *q); static size_t pos_normalize(size_t split_pos, size_t reasm_offset, size_t len_payload) { return (split_pos > reasm_offset && (split_pos - reasm_offset) < len_payload) ? split_pos - reasm_offset : 0; } static uint8_t autottl_guess(autottl *attl, uint8_t hop_count, const char *attl_kind) { if (AUTOTTL_ENABLED(*attl)) { uint8_t autottl = autottl_eval(hop_count, attl); if (autottl) DLOG("%s autottl: guessed %u\n", attl_kind, autottl); else DLOG("%s autottl: could not guess\n", attl_kind); return autottl; } else return 0; } static void autottl_discover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) { if (ctrack && params.autottl_present && !ctrack->b_autottl_discovered) { ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface); if (!ipc) { DLOG_ERR("ipcache: out of memory\n"); return; } if (ctrack->incoming_ttl) { uint8_t old_hops = ipc->hops; ipc->hops = hop_count_guess(ctrack->incoming_ttl); DLOG("incoming hops guessed %u\n", ipc->hops); if (old_hops != ipc->hops) DLOG("updated autottl cache\n"); } else if (ipc->hops) DLOG("using cached hops %u\n", ipc->hops); else DLOG("hop count unknown\n"); if (ipc->hops) { ctrack->desync_autottl = autottl_guess(a6 ? &ctrack->dp->desync_autottl6 : &ctrack->dp->desync_autottl, ipc->hops, "desync"); ctrack->orig_autottl = autottl_guess(a6 ? &ctrack->dp->orig_autottl6 : &ctrack->dp->orig_autottl, ipc->hops, "orig"); ctrack->dup_autottl = autottl_guess(a6 ? &ctrack->dp->dup_autottl6 : &ctrack->dp->dup_autottl, ipc->hops, "dup"); } ctrack->b_autottl_discovered = true; } } static void autottl_rediscover(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) { if (ctrack) { ctrack->b_autottl_discovered = false; autottl_discover(ctrack, a4, a6, iface); } } static bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip) { if (!params.cache_hostname) return true; ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL); if (!ipc) { DLOG_ERR("ipcache_put_hostname: out of memory\n"); return false; } if (!ipc->hostname || strcmp(ipc->hostname, hostname)) { free(ipc->hostname); if (!(ipc->hostname = strdup(hostname))) { DLOG_ERR("ipcache_put_hostname: out of memory\n"); return false; } ipc->hostname_is_ip = hostname_is_ip; DLOG("hostname cached (is_ip=%u): %s\n", hostname_is_ip, hostname); } return true; } static 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) { if (!params.cache_hostname) { *hostname = 0; return true; } ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL); if (!ipc) { DLOG_ERR("ipcache_get_hostname: out of memory\n"); return false; } if (ipc->hostname) { DLOG("got cached hostname (is_ip=%u): %s\n", ipc->hostname_is_ip, ipc->hostname); snprintf(hostname, hostname_buf_len, "%s", ipc->hostname); if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip; } else *hostname = 0; return true; } static uint16_t IP4_IP_ID_FIX(const struct ip *ip, t_ip_id_mode mode) { if (ip) { switch(mode) { case IPID_SEQ: case IPID_SEQ_GROUP: return ip->ip_id ? ip->ip_id : (uint16_t)random(); case IPID_SAME: return ip->ip_id; case IPID_RND: return (uint16_t)(random()%0xFFFF + 1); default: break; } } return 0; } static uint16_t IP4_IP_ID_ADD(uint16_t ip_id, uint16_t inc, t_ip_id_mode mode) { switch(mode) { case IPID_SEQ_GROUP: case IPID_SEQ: if (ip_id) { ip_id = net16_add(ip_id, inc); if (!ip_id) ip_id = net16_add(ip_id, ((int16_t)inc) < 0 ? -1 : 1); // do not allow zero } case IPID_SAME: return ip_id; case IPID_RND: return (uint16_t)(random()%0xFFFF + 1);; default: return 0; } } #define IP4_IP_ID_NEXT(ip_id,mode) IP4_IP_ID_ADD(ip_id,+1,mode) #define IP4_IP_ID_PREV(ip_id,mode) IP4_IP_ID_ADD(ip_id,-1,mode) // fake_mod buffer must at least sizeof(desync_profile->fake_tls) // size does not change // return : true - altered, false - not altered static 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) { bool b = false; if (modcache) // it's filled only if it's TLS { if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP) { size_t sz_rec = pntoh16(fake_data + 3) + payload_len; size_t sz_handshake = pntoh24(fake_data + 6) + payload_len; size_t sz_ext = pntoh16(fake_data + modcache->extlen_offset) + payload_len; size_t sz_pad = pntoh16(fake_data + modcache->padlen_offset) + payload_len; if ((sz_rec & ~0xFFFF) || (sz_handshake & ~0xFFFFFF) || (sz_ext & ~0xFFFF) || (sz_pad & ~0xFFFF)) DLOG("fake[%d] cannot apply padencap tls mod. length overflow.\n", fake_n); else { memcpy(fake_mod, fake_data, fake_data_size); phton16(fake_mod + 3, (uint16_t)sz_rec); phton24(fake_mod + 6, (uint32_t)sz_handshake); phton16(fake_mod + modcache->extlen_offset, (uint16_t)sz_ext); phton16(fake_mod + modcache->padlen_offset, (uint16_t)sz_pad); b = true; DLOG("fake[%d] applied padencap tls mod. sizes increased by %zu bytes.\n", fake_n, payload_len); } } if (tls_mod->mod & FAKE_TLS_MOD_RND) { if (!b) memcpy(fake_mod, fake_data, fake_data_size); fill_random_bytes(fake_mod + 11, 32); // random fill_random_bytes(fake_mod + 44, fake_mod[43]); // session id b = true; DLOG("fake[%d] applied rnd tls mod\n", fake_n); } if (tls_mod->mod & FAKE_TLS_MOD_DUP_SID) { if (payload_len < 44) DLOG("fake[%d] cannot apply dupsid tls mod. data payload is too short.\n", fake_n); else if (fake_data[43] != payload[43]) DLOG("fake[%d] cannot apply dupsid tls mod. fake and orig session id length mismatch : %u!=%u.\n", fake_n, fake_data[43], payload[43]); else if (payload_len < (44 + payload[43])) DLOG("fake[%d] cannot apply dupsid tls mod. data payload is not valid.\n", fake_n); else { if (!b) memcpy(fake_mod, fake_data, fake_data_size); memcpy(fake_mod + 44, payload + 44, fake_mod[43]); // session id b = true; DLOG("fake[%d] applied dupsid tls mod\n", fake_n); } } } return b; } static bool rewrite_tcp_flags(uint16_t *flags, uint16_t unset, uint16_t set, const char *what) { if (set || unset) { uint16_t fl_new = *flags & ~unset | set; if (fl_new!=*flags) { DLOG("rewrite %s tcp flags 0x%03X => 0x%03X\n", what, *flags, fl_new); *flags = fl_new; return true; } } return false; } static uint8_t orig_mod(const struct desync_profile *dp, const t_ctrack *ctrack, struct dissect *dis) { uint8_t ttl, ttl_orig; bool bModded = false; if (check_orig_mod_interval(dp, ctrack)) { ttl = (ctrack && ctrack->orig_autottl) ? ctrack->orig_autottl : dis->ip6 ? dp->orig_mod_ttl6 : dp->orig_mod_ttl; if (ttl) { ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; if (ttl_orig != ttl) { DLOG("rewrite original packet ttl %u => %u\n", ttl_orig, ttl); rewrite_ttl(dis->ip, dis->ip6, ttl); bModded = true; } } if (dis->tcp) { uint16_t flags = get_tcp_flags(dis->tcp); if (rewrite_tcp_flags(&flags, dp->orig_tcp_flags_unset, dp->orig_tcp_flags_set, "original")) { apply_tcp_flags(dis->tcp,flags); bModded = true; } } } return bModded; } static bool orig_send_rewrite( uint32_t fwmark, const char *ifout, const struct sockaddr *dst, uint8_t ttl_orig, uint8_t ttl_fake, const struct desync_profile *dp, const struct dissect *dis) { unsigned int k; // here we avoid heavy ops and preserve exact tcp options structure if (ttl_fake == ttl_orig) DLOG("sending %u dups\n", dp->dup_repeats); else DLOG("sending %u dups with ttl rewrite %u => %u\n", dp->dup_repeats, ttl_orig, ttl_fake); rewrite_ttl(dis->ip, dis->ip6, ttl_fake); // send dups for (k = 0; k < dp->dup_repeats; k++) { if (!rawsend(dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) { rewrite_ttl(dis->ip, dis->ip6, ttl_orig); return false; } } rewrite_ttl(dis->ip, dis->ip6, ttl_orig); return true; } // return : true - orig was sent completely, false - should send orig another way static 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) { if (dp->dup_repeats || bForceSend) { unsigned int k; uint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100]; size_t len; uint16_t ip_id, nmss; struct sockaddr_storage src, dst; uint8_t ttl_orig, ttl_dup, scale_factor; uint16_t flags_dup; uint32_t *timestamps; bool sack, DF, bTF; extract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst); ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); if (dp->dup_repeats && check_dup_interval(dp, ctrack)) { ttl_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)); flags_dup = dis->tcp->th_flags; bTF = rewrite_tcp_flags(&flags_dup, dp->dup_tcp_flags_unset, dp->dup_tcp_flags_set, "dup"); if (bTF || dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME)) { scale_factor = tcp_find_scale_factor(dis->tcp); timestamps = tcp_find_timestamps(dis->tcp); sack = tcp_has_sack(dis->tcp); nmss = tcp_find_mss(dis->tcp); ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); len = sizeof(pkt); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_dup, sack, nmss, dis->tcp->th_seq, dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, ip_has_df(dis->ip), ttl_dup, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->dup_fooling_mode, dp->dup_ts_increment, dp->dup_badseq_increment, dp->dup_badseq_ack_increment, dis->data_payload, dis->len_payload, pkt, &len)) { DLOG_ERR("dup: packet reconstruct failed\n"); return false; } DLOG("sending %u dups with packet reconstruct. ttl %u => %u\n", dp->dup_repeats, ttl_orig, ttl_dup); // send dups for (k = 0; k < dp->dup_repeats; k++) { if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len)) return false; ip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode); if (dis->ip) ((struct ip*)pkt)->ip_id = ip_id; } } else { if (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_dup, dp, dis)) return false; } if (dp->dup_replace) DLOG("NOT sending original because of dup_replace\n"); else { DLOG("sending original ttl %u\n", ttl_orig); if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) return false; } return true; } if (bForceSend) { DLOG("sending original ttl %u\n", ttl_orig); if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) return false; return true; } } return false; } // return : true - orig was sent completely, false - should send orig another way static 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) { if (dp->dup_repeats || bForceSend) { unsigned int k; uint8_t pkt[DPI_DESYNC_MAX_FAKE_LEN + 100]; size_t len; uint16_t ip_id; struct sockaddr_storage src, dst; uint8_t ttl_orig, ttl_fake; extract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst); ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); if (dp->dup_repeats && check_dup_interval(dp, ctrack)) { ttl_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)); if (dp->dup_fooling_mode || (dis->ip && dp->dup_ip_id_mode!=IPID_SAME)) { ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); len = sizeof(pkt); if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, ip_has_df(dis->ip), ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->dup_fooling_mode, NULL, 0, 0, dis->data_payload, dis->len_payload, pkt, &len)) { DLOG_ERR("dup: packet reconstruct failed\n"); return false; } DLOG("sending %u dups with packet reconstruct. ttl %u => %u\n", dp->dup_repeats, ttl_orig, ttl_fake); // send dups for (k = 0; k < dp->dup_repeats; k++) { if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, pkt, len)) return false; ip_id = IP4_IP_ID_NEXT(ip_id,dp->dup_ip_id_mode); if (dis->ip) ((struct ip*)pkt)->ip_id = ip_id; } } else { if (!orig_send_rewrite(fwmark, ifout, (struct sockaddr *)&dst, ttl_orig, ttl_fake, dp, dis)) return false; } if (dp->dup_replace) DLOG("NOT sending original because of dup_replace\n"); else { DLOG("sending original ttl %u\n", ttl_orig); if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) return false; } return true; } if (bForceSend) { DLOG("sending original ttl %u\n", ttl_orig); if (!rawsend((struct sockaddr *)&dst, fwmark, ifout, dis->data_pkt, dis->len_pkt)) return false; return true; } } return false; } static 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) { uint8_t verdict = VERDICT_PASS; // additional safety check if (!!dis->ip == !!dis->ip6) return verdict; struct desync_profile *dp = NULL; t_ctrack *ctrack = NULL, *ctrack_replay = NULL; bool bReverse = false; bool bFake = false; struct sockaddr_storage src, dst; uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100]; size_t pkt1_len, pkt2_len, pkt3_len; uint8_t ttl_orig, ttl_fake, scale_factor; uint32_t *timestamps; bool bSack, DF; uint16_t nmss; char host[256]; const char *ifname = NULL, *ssid = NULL; uint32_t desync_fwmark = fwmark | params.desync_fwmark; extract_endpoints(dis->ip, dis->ip6, dis->tcp, NULL, &src, &dst); timestamps = tcp_find_timestamps(dis->tcp); DF = ip_has_df(dis->ip); ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; if (replay) { // in replay mode conntrack_replay is not NULL and ctrack is NULL //ConntrackPoolDump(¶ms.conntrack); if (!ConntrackPoolDoubleSearch(¶ms.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, &ctrack_replay, &bReverse) || bReverse) return verdict; ifname = bReverse ? ifin : ifout; #ifdef HAS_FILTER_SSID ssid = wlan_ssid_search_ifname(ifname); if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); #endif dp = ctrack_replay->dp; if (dp) DLOG("using cached desync profile %d\n", dp->n); else if (!ctrack_replay->dp_search_complete) { dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL); ctrack_replay->dp_search_complete = true; } if (!dp) { DLOG("matching desync profile not found\n"); return verdict; } } else { // in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack if (!params.ctrack_disable) { ConntrackPoolPurge(¶ms.conntrack); if (ConntrackPoolFeed(¶ms.conntrack, dis->ip, dis->ip6, dis->tcp, NULL, dis->len_payload, &ctrack, &bReverse)) { dp = ctrack->dp; ctrack_replay = ctrack; } } ifname = bReverse ? ifin : ifout; #ifdef HAS_FILTER_SSID ssid = wlan_ssid_search_ifname(ifname); if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); #endif if (dp) DLOG("using cached desync profile %d\n", dp->n); else if (!ctrack || !ctrack->dp_search_complete) { const char *hostname = NULL; bool hostname_is_ip = false; if (ctrack) { hostname = ctrack->hostname; hostname_is_ip = ctrack->hostname_is_ip; if (!hostname && !bReverse) { if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host) if (!(hostname = ctrack_replay->hostname = strdup(host))) DLOG_ERR("strdup(host): out of memory\n"); } } dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL); if (ctrack) { ctrack->dp = dp; ctrack->dp_search_complete = true; } } if (!dp) { DLOG("matching desync profile not found\n"); return verdict; } maybe_cutoff(ctrack, IPPROTO_TCP); HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); //ConntrackPoolDump(¶ms.conntrack); if (tcp_synack_segment(dis->tcp)) { if (dp->wsize) { tcp_rewrite_winsize(dis->tcp, dp->wsize, dp->wscale); verdict = VERDICT_MODIFY; } if (dp->synack_split == SS_SYN) { DLOG("split SYNACK : clearing ACK bit\n"); dis->tcp->th_flags &= ~TH_ACK; verdict = VERDICT_MODIFY; } } if (bReverse) { if (ctrack && !ctrack->incoming_ttl) { ctrack->incoming_ttl = ttl_orig; DLOG("incoming TTL %u\n", ttl_orig); autottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin); } // process reply packets for auto hostlist mode // by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked // we only process first-sequence replies. do not react to subsequent redirects or RSTs if (ctrack && ctrack->hostname && ctrack->hostname_ah_check && (ctrack->ack_last - ctrack->ack0) == 1) { bool bFail = false; char client_ip_port[48]; if (*params.hostlist_auto_debuglog) ntop46_port((struct sockaddr*)&dst, client_ip_port, sizeof(client_ip_port)); else *client_ip_port = 0; if (dis->tcp->th_flags & TH_RST) { DLOG("incoming RST detected for hostname %s\n", ctrack->hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : incoming RST", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); bFail = true; } else if (dis->len_payload && ctrack->l7proto == HTTP) { if (IsHttpReply(dis->data_payload, dis->len_payload)) { DLOG("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); bFail = HttpReplyLooksLikeDPIRedirect(dis->data_payload, dis->len_payload, ctrack->hostname); if (bFail) { DLOG("redirect to another domain detected. possibly DPI redirect.\n"); HOSTLIST_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)); } else DLOG("local or in-domain redirect detected. it's not a DPI redirect.\n"); } else { // received not http reply. do not monitor this connection anymore DLOG("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); } } if (bFail) auto_hostlist_failed(dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); else if (dis->len_payload) auto_hostlist_reset_fail_counter(dp, ctrack->hostname, client_ip_port, ctrack->l7proto); if (dis->tcp->th_flags & TH_RST) ConntrackClearHostname(ctrack); // do not react to further dup RSTs } return verdict; // nothing to do. do not waste cpu } autottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); if (orig_mod(dp, ctrack, dis)) // ttl can change ! verdict = VERDICT_MODIFY; if (dp->wssize) { if (ctrack) { if (ctrack->b_wssize_cutoff) { DLOG("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); } else { if (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); tcp_rewrite_winsize(dis->tcp, dp->wssize, dp->wsscale); verdict = VERDICT_MODIFY; } } else { DLOG("not changing wssize. wssize is set but conntrack entry is missing\n"); } } if ((dp->synack_split == SS_SYNACK || dp->synack_split == SS_ACKSYN) && tcp_synack_segment(dis->tcp)) { // reconstruct required dis->tcp->th_flags &= ~TH_ACK; tcp_fix_checksum(dis->tcp, dis->transport_len, dis->ip, dis->ip6); char ss[2], i; if (dp->synack_split == SS_SYNACK) { ss[0] = 'S'; ss[1] = 'A'; } else { ss[0] = 'A'; ss[1] = 'S'; } pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode), IP6_FLOW(dis->ip6), FOOL_NONE, 0, 0, 0, NULL, 0, pkt1, &pkt1_len)) { DLOG_ERR("cannot prepare split SYNACK ACK part\n"); goto send_orig; } for (int i = 0; i < 2; i++) { switch (ss[i]) { case 'S': DLOG("split SYNACK : SYN\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, dis->data_pkt, dis->len_pkt)) goto send_orig; break; case 'A': DLOG("split SYNACK : ACK\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; break; } } return VERDICT_DROP; } // start and cutoff limiters if (!process_desync_interval(dp, ctrack)) goto send_orig; } // !replay ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; ttl_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)); uint16_t flags_orig = get_tcp_flags(dis->tcp); scale_factor = tcp_find_scale_factor(dis->tcp); bSack = tcp_has_sack(dis->tcp); nmss = tcp_find_mss(dis->tcp); uint16_t ip_id=0; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; if (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); if (!replay) { if (tcp_syn_segment(dis->tcp)) { switch (dp->desync_mode0) { case DESYNC_SYNACK: pkt1_len = sizeof(pkt1); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, NULL, 0, pkt1, &pkt1_len)) { goto send_orig; } DLOG("sending fake SYNACK\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; break; case DESYNC_SYNDATA: // make sure we are not breaking TCP fast open if (tcp_has_fastopen(dis->tcp)) { DLOG("received SYN with TCP fast open option. syndata desync is not applied.\n"); break; } if (dis->len_payload) { DLOG("received SYN with data payload. syndata desync is not applied.\n"); break; } pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), 0, 0, 0, 0, dp->fake_syndata, dp->fake_syndata_size, pkt1, &pkt1_len)) { goto send_orig; } ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending SYN with fake data : "); hexdump_limited_dlog(dp->fake_syndata, dp->fake_syndata_size, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; verdict = VERDICT_DROP; break; default: break; } // can do nothing else with SYN packet goto send_orig; } } // !replay if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload) { struct blob_collection_head *fake; uint8_t *p, *phost = NULL; const uint8_t *rdata_payload = dis->data_payload; size_t rlen_payload = dis->len_payload; size_t split_pos, seqovl_pos; size_t multisplit_pos[MAX_SPLITS]; int multisplit_count; int i; bool bHaveHost = false, bHostIsIp = false; t_l7proto l7proto = UNKNOWN; if (replay) { rdata_payload = ctrack_replay->reasm_orig.packet; rlen_payload = ctrack_replay->reasm_orig.size_present; } else if (reasm_orig_feed(ctrack, IPPROTO_TCP, dis->data_payload, dis->len_payload)) { rdata_payload = ctrack->reasm_orig.packet; rlen_payload = ctrack->reasm_orig.size_present; } process_retrans_fail(ctrack, IPPROTO_TCP, (struct sockaddr*)&src); if (IsHttp(rdata_payload, rlen_payload)) { DLOG("packet contains HTTP request\n"); l7proto = HTTP; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; // we do not reassemble http reasm_orig_cancel(ctrack); if (!dp->wssize_no_forced_cutoff) forced_wssize_cutoff(ctrack); bHaveHost = HttpExtractHost(rdata_payload, rlen_payload, host, sizeof(host)); if (!bHaveHost) { DLOG("not applying tampering to HTTP without Host:\n"); goto send_orig; } if (ctrack) { // we do not reassemble http if (!ctrack->req_seq_present) { ctrack->req_seq_start = ctrack->seq_last; ctrack->req_seq_end = ctrack->pos_orig - 1; ctrack->req_seq_present = ctrack->req_seq_finalized = true; DLOG("req retrans : tcp seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end); } } } else if (IsTLSClientHello(rdata_payload, rlen_payload, TLS_PARTIALS_ENABLE)) { bool bReqFull = IsTLSRecordFull(rdata_payload, rlen_payload); DLOG(bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n"); l7proto = TLS; if (bReqFull) TLSDebug(rdata_payload, rlen_payload); bHaveHost = TLSHelloExtractHost(rdata_payload, rlen_payload, host, sizeof(host), TLS_PARTIALS_ENABLE); if (ctrack) { if (!ctrack->l7proto) ctrack->l7proto = l7proto; // do not reasm retransmissions if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !ctrack->req_seq_abandoned && !(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))) { // do not reconstruct unexpected large payload (they are feeding garbage ?) if (!reasm_orig_start(ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload)) { reasm_orig_cancel(ctrack); goto send_orig; } } if (!ctrack->req_seq_finalized) { if (!ctrack->req_seq_present) { // lower bound of request seq interval ctrack->req_seq_start = ctrack->seq_last; ctrack->req_seq_present = true; } // upper bound of request seq interval // it can grow on every packet until request is complete. then interval is finalized and never touched again. ctrack->req_seq_end = ctrack->pos_orig - 1; DLOG("req retrans : seq interval %u-%u\n", ctrack->req_seq_start, ctrack->req_seq_end); ctrack->req_seq_finalized |= bReqFull; } if (!dp->wssize_no_forced_cutoff && (bReqFull || ReasmIsEmpty(&ctrack->reasm_orig))) forced_wssize_cutoff(ctrack); if (!ReasmIsEmpty(&ctrack->reasm_orig)) { verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); reasm_orig_cancel(ctrack); goto send_orig; } if (ReasmIsFull(&ctrack->reasm_orig)) { replay_queue(&ctrack->delayed); reasm_orig_fin(ctrack); } return VERDICT_DROP; } } if (dp->desync_skip_nosni && !bHaveHost) { DLOG("not applying tampering to TLS ClientHello without hostname in the SNI\n"); reasm_orig_cancel(ctrack); goto send_orig; } } if (ctrack && ctrack->req_seq_finalized) { uint32_t dseq = ctrack->seq_last - ctrack->req_seq_end; // do not react to 32-bit overflowed sequence numbers. allow 16 Mb grace window then cutoff. if (dseq >= 0x1000000 && !(dseq & 0x80000000)) ctrack->req_seq_abandoned = true; } if (bHaveHost) { bHostIsIp = strip_host_to_ip(host); DLOG("hostname: %s\n", host); } bool bDiscoveredL7; if (ctrack_replay) { if ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN)) ctrack_replay->l7proto_discovered = true; } else bDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN; if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered); if (bDiscoveredHostname) { DLOG("discovered hostname\n"); if (ctrack_replay) { free(ctrack_replay->hostname); ctrack_replay->hostname = strdup(host); ctrack_replay->hostname_is_ip = bHostIsIp; if (!ctrack_replay->hostname) { DLOG_ERR("hostname dup : out of memory"); reasm_orig_cancel(ctrack); goto send_orig; } ctrack_replay->hostname_discovered = true; if (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp)) { reasm_orig_cancel(ctrack); goto send_orig; } } } bool bCheckDone = false, bCheckResult = false, bCheckExcluded = false; if (bDiscoveredL7 || bDiscoveredHostname) { struct desync_profile *dp_prev = dp; dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, ctrack_replay ? ctrack_replay->l7proto : l7proto, ssid, &bCheckDone, &bCheckResult, &bCheckExcluded); if (ctrack_replay) { ctrack_replay->dp = dp; ctrack_replay->dp_search_complete = true; ctrack_replay->bCheckDone = bCheckDone; ctrack_replay->bCheckResult = bCheckResult; ctrack_replay->bCheckExcluded = bCheckExcluded; } if (!dp) { reasm_orig_cancel(ctrack); goto send_orig; } if (dp != dp_prev) { DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); autottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); // re-evaluate start/cutoff limiters if (replay) { if (orig_mod(dp, ctrack_replay, dis)) // ttl can change ! verdict = VERDICT_MODIFY; } else { maybe_cutoff(ctrack, IPPROTO_TCP); if (orig_mod(dp, ctrack, dis)) // ttl can change ! verdict = VERDICT_MODIFY; if (!process_desync_interval(dp, ctrack)) { reasm_orig_cancel(ctrack); goto send_orig; } } ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; ttl_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)); } } else if (ctrack_replay) { bCheckDone = ctrack_replay->bCheckDone; bCheckResult = ctrack_replay->bCheckResult; bCheckExcluded = ctrack_replay->bCheckExcluded; } if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp)) { if (!bCheckDone) bCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false); if (bCheckResult) ctrack_stop_retrans_counter(ctrack_replay); else { if (ctrack_replay) { ctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded; if (!ctrack_replay->hostname_ah_check) ctrack_stop_retrans_counter(ctrack_replay); } DLOG("not applying tampering to this request\n"); reasm_orig_cancel(ctrack); goto send_orig; } } if (l7proto == UNKNOWN) { if (!dp->desync_any_proto) { DLOG("not applying tampering to unknown protocol\n"); reasm_orig_cancel(ctrack); goto send_orig; } DLOG("applying tampering to unknown protocol\n"); } if ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase || dp->methodeol) && HttpFindHost(&phost, dis->data_payload, dis->len_payload)) { if (dp->hostcase) { DLOG("modifying Host: => %c%c%c%c:\n", dp->hostspell[0], dp->hostspell[1], dp->hostspell[2], dp->hostspell[3]); memcpy(phost, dp->hostspell, 4); verdict = VERDICT_MODIFY; } if (dp->domcase) { DLOG("mixing domain case\n"); for (p = phost + 5; p < (dis->data_payload + dis->len_payload) && *p != '\r' && *p != '\n'; p++) *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); verdict = VERDICT_MODIFY; } uint8_t *pua; if (dp->hostnospace) { if ((pua = (uint8_t*)memmem(dis->data_payload, dis->len_payload, "\r\nUser-Agent: ", 14)) && (pua = (uint8_t*)memmem(pua + 1, dis->len_payload - (pua - dis->data_payload) - 1, "\r\n", 2))) { DLOG("removing space after Host: and adding it to User-Agent:\n"); if (pua > phost) { memmove(phost + 5, phost + 6, pua - phost - 6); pua[-1] = ' '; } else { memmove(pua + 1, pua, phost - pua + 5); *pua = ' '; } verdict = VERDICT_MODIFY; } else DLOG("cannot do hostnospace because valid User-Agent: not found\n"); } else if (dp->methodeol) { if (phost[5] == ' ' || phost[5] == '\t') { DLOG("removing space after Host: and adding '\\n' before method\n"); memmove(dis->data_payload + 1, dis->data_payload, phost - dis->data_payload + 5); dis->data_payload[0] = '\n'; verdict = VERDICT_MODIFY; } else DLOG("cannot do methodeol because there's no space or tab after Host:\n"); } } if (dp->desync_mode == DESYNC_NONE) { reasm_orig_cancel(ctrack); goto send_orig; } if (params.debug) { char s1[48], s2[48]; ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); DLOG("dpi desync src=%s dst=%s\n", s1, s2); } switch (l7proto) { case HTTP: fake = &dp->fake_http; break; case TLS: fake = &dp->fake_tls; break; default: fake = &dp->fake_unknown; break; } if (dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER) { split_pos = 0; ResolveMultiPos(rdata_payload, rlen_payload, l7proto, dp->splits, dp->split_count, multisplit_pos, &multisplit_count); if (params.debug) { if (multisplit_count) { DLOG("multisplit pos: "); for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); DLOG("\n"); } else DLOG("all multisplit pos are outside of this packet\n"); } if (multisplit_count) { int j; for (i = j = 0; i < multisplit_count; i++) { multisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload); if (multisplit_pos[j]) j++; } multisplit_count = j; if (params.debug) { if (multisplit_count) { DLOG("normalized multisplit pos: "); for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); DLOG("\n"); } else DLOG("all multisplit pos are outside of this packet\n"); } } } else if (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER) { multisplit_count = 0; // first look for non-abs split for (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++) if (dp->splits[i].marker != PM_ABS) split_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i); // second look for abs split if (!split_pos) for (i = 0, split_pos = 0; i < dp->split_count && !split_pos; i++) if (dp->splits[i].marker == PM_ABS) split_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, dp->splits + i); if (!split_pos) split_pos = 1; DLOG("regular split pos: %zu\n", split_pos); if (!split_pos || split_pos > rlen_payload) split_pos = 1; split_pos = pos_normalize(split_pos, reasm_offset, dis->len_payload); if (split_pos) DLOG("normalized regular split pos : %zu\n", split_pos); else DLOG("regular split pos is outside of this packet\n"); } else if (dp->desync_mode == DESYNC_HOSTFAKESPLIT || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT) { struct proto_pos splits[2] = { {.marker = PM_HOST,.pos = 0}, {.marker = PM_HOST_END,.pos = 0} }; split_pos = 0; ResolveMultiPos(rdata_payload, rlen_payload, l7proto, splits, 2, multisplit_pos, &multisplit_count); if (multisplit_count != 2) { DLOG("hostfakesplit: host and endhost positions not found\n"); multisplit_count = 0; } else { int j; for (i = j = 0; i < multisplit_count; i++) { multisplit_pos[j] = pos_normalize(multisplit_pos[i], reasm_offset, dis->len_payload); if (multisplit_pos[j]) j++; } multisplit_count = j; if (multisplit_count != 2) { DLOG("hostfakesplit: host or endhost are outside of this packet\n"); multisplit_count = 0; } else { DLOG("normalized hostfakesplit pos: "); for (i = 0; i < multisplit_count; i++) DLOG("%zu ", multisplit_pos[i]); DLOG("\n"); multisplit_pos[2] = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->hostfakesplit_midhost); if (multisplit_pos[2]) { multisplit_pos[2] = pos_normalize(multisplit_pos[2], reasm_offset, dis->len_payload); if (multisplit_pos[2] > multisplit_pos[0] && multisplit_pos[2] < multisplit_pos[1]) { DLOG("normalized hostfakesplit midhost pos: %zu\n", multisplit_pos[2]); multisplit_count++; } } } } } else { multisplit_count = 0; split_pos = 0; } if (dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_MULTISPLIT) { // split seqovl only uses absolute positive values seqovl_pos = (dp->seqovl.marker == PM_ABS && dp->seqovl.pos > 0) ? dp->seqovl.pos : 0; if (seqovl_pos) DLOG("seqovl : %zu\n", seqovl_pos); } else if (dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_MULTIDISORDER || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_MULTIDISORDER) { seqovl_pos = ResolvePos(rdata_payload, rlen_payload, l7proto, &dp->seqovl); seqovl_pos = pos_normalize(seqovl_pos, reasm_offset, dis->len_payload); if (seqovl_pos) DLOG("normalized seqovl : %zu\n", seqovl_pos); } else seqovl_pos = 0; uint32_t fooling_orig = FOOL_NONE; uint16_t flags_fake = flags_orig; rewrite_tcp_flags(&flags_fake, dp->desync_tcp_flags_unset, dp->desync_tcp_flags_set, "desync"); switch (dp->desync_mode) { case DESYNC_FAKE_KNOWN: if (reasm_offset) break; if (l7proto == UNKNOWN) { DLOG("not applying fake because of unknown protocol\n"); break; } case DESYNC_FAKE: if (reasm_offset) break; { struct blob_item *fake_item; size_t fake_size; uint8_t *fake_data; uint8_t fake_data_buf[FAKE_MAX_TCP]; int n = 0; uint32_t sequence, sequence0; sequence = sequence0 = ntohl(dis->tcp->th_seq); LIST_FOREACH(fake_item, fake, next) { n++; switch (l7proto) { case TLS: if ((fake_item->size <= sizeof(fake_data_buf)) && runtime_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)) { fake_data = fake_data_buf; break; } default: fake_data = fake_item->data; } fake_data += fake_item->offset; fake_size = fake_item->size - fake_item->offset; pkt1_len = sizeof(pkt1); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, fake_data, fake_size, pkt1, &pkt1_len)) { reasm_orig_cancel(ctrack); goto send_orig; } DLOG("sending fake[%d] seq=+%u : ", n, sequence - sequence0); hexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) { reasm_orig_cancel(ctrack); goto send_orig; } ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); if (dp->tcp_mod.seq) sequence += fake_size; } } bFake = true; break; case DESYNC_RST: case DESYNC_RSTACK: if (reasm_offset) break; pkt1_len = sizeof(pkt1); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, NULL, 0, pkt1, &pkt1_len)) { reasm_orig_cancel(ctrack); goto send_orig; } DLOG("sending fake RST/RSTACK\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) { reasm_orig_cancel(ctrack); goto send_orig; } ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); bFake = true; break; case DESYNC_HOPBYHOP: case DESYNC_DESTOPT: case DESYNC_IPFRAG1: fooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; if (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_tcp(dp->desync_mode2) || (!split_pos && (dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER)) || (multisplit_count < 2 && dp->desync_mode2 == DESYNC_HOSTFAKESPLIT) || (!multisplit_count && (dp->desync_mode2 == DESYNC_MULTISPLIT || dp->desync_mode2 == DESYNC_MULTIDISORDER)))) { reasm_orig_cancel(ctrack); rdata_payload = NULL; pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, dis->data_payload, dis->len_payload, pkt1, &pkt1_len)) { goto send_orig; } DLOG("resending original packet with extension header\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; // this mode is final, no other options available return VERDICT_DROP; } default: pkt1_len = 0; break; } // we do not need reasm buffer anymore reasm_orig_cancel(ctrack); rdata_payload = NULL; enum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2; switch (desync_mode) { case DESYNC_HOSTFAKESPLIT: // can be 2 or 3 split pos // if 2 split pos : host, endhost // if 3 split pos : host, endhost, midhost if (multisplit_count >= 2) { uint8_t *seg; size_t seg_len, host_size, pos_host, pos_endhost, pos_split_host, sz; uint8_t *fakehost; uint16_t ip_id_after_host; seg = dis->data_payload; seg_len = dis->len_payload; pos_host = multisplit_pos[0]; pos_endhost = multisplit_pos[1]; pos_split_host = multisplit_count >= 3 ? multisplit_pos[2] : 0; host_size = pos_endhost - pos_host; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; // before_host segment pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg, pos_host, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending hostfakesplit before_host part 0-%zu len=%zu : ", pos_host - 1, pos_host); hexdump_limited_dlog(seg, pos_host, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; if (!(fakehost = malloc(host_size + 1))) { DLOG("fakehost out of memory\n"); goto send_orig; } if (*dp->hfs_mod.host) { if (host_size <= dp->hfs_mod.host_size) { // "google.com" => "gle.com" // "google.com" => "google.com" memcpy(fakehost, dp->hfs_mod.host + dp->hfs_mod.host_size - host_size, host_size); } else { // "google.com" => "nb4auv9.google.com" // "google.com" => ".google.com" sz = host_size - dp->hfs_mod.host_size; memcpy(fakehost + sz, dp->hfs_mod.host, dp->hfs_mod.host_size); fakehost[--sz] = '.'; if (sz) { fill_random_az(fakehost, 1); sz--; } fill_random_az09(fakehost + 1, sz); } } else { fill_random_az(fakehost, 1); fill_random_az09(fakehost + 1, host_size - 1); if (host_size >= 7) { fakehost[host_size - 4] = '.'; memcpy(fakehost + host_size - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3); } } fakehost[host_size] = 0; DLOG("generated fake host: %s\n", fakehost); // pkt2: fake_host segment pkt2_len = sizeof(pkt2); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_fake, false, 0, net32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, fakehost, host_size, pkt2, &pkt2_len)) goto send_orig_clean; if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending hostfakesplit fake host %zu-%zu len=%zu : ", pos_host, pos_endhost - 1, host_size); hexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) goto send_orig_clean; ip_id_after_host = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); if (pos_split_host) ip_id_after_host = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode); // pkt3: after_host segment pkt3_len = sizeof(pkt3); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, pos_endhost), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_orig, IP4_TOS(dis->ip), ip_id_after_host, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg + pos_endhost, seg_len - pos_endhost, pkt3, &pkt3_len)) goto send_orig_clean; if (dp->hfs_mod.ordering == 1) { DLOG("sending hostfakesplit after_host part %zu-%zu len=%zu : ", pos_endhost, seg_len - 1, seg_len - pos_endhost); hexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len)) goto send_orig_clean; } sz = pos_split_host ? pos_split_host - pos_host : host_size; pkt1_len = sizeof(pkt1); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, pos_host), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg + pos_host, sz, pkt1, &pkt1_len)) goto send_orig_clean; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending hostfakesplit real host %s%zu-%zu len=%zu : ", pos_split_host ? "part 1 " : "", pos_host, pos_host + sz - 1, sz); hexdump_limited_dlog(seg + pos_host, sz, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig_clean; if (pos_split_host) { sz = pos_endhost - pos_split_host; pkt1_len = sizeof(pkt1); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, pos_split_host), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg + pos_split_host, sz, pkt1, &pkt1_len)) goto send_orig_clean; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending hostfakesplit real host part 2 %zu-%zu len=%zu : ", pos_split_host, pos_endhost - 1, sz); hexdump_limited_dlog(seg + pos_split_host, sz, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig_clean; } if (dp->hfs_mod.ordering == 0) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) { if (dis->ip) ((struct ip*)pkt2)->ip_id = ip_id; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } DLOG("sending hostfakesplit fake(2) host %zu-%zu len=%zu : ", pos_host, pos_endhost - 1, host_size); hexdump_limited_dlog(fakehost, host_size, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) goto send_orig_clean; if (dp->ip_id_mode!=IPID_SEQ_GROUP) { ip_id_after_host = ip_id; if (dis->ip) ((struct ip*)pkt3)->ip_id = ip_id; } DLOG("sending hostfakesplit after_host part %zu-%zu len=%zu : ", pos_endhost, seg_len - 1, seg_len - pos_endhost); hexdump_limited_dlog(seg + pos_endhost, seg_len - pos_endhost, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt3, pkt3_len)) goto send_orig_clean; } free(fakehost); if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_after_host,dp->ip_id_mode); return VERDICT_DROP; send_orig_clean: free(fakehost); goto send_orig; } else goto unsplitted_part; case DESYNC_MULTISPLIT: if (multisplit_count) { uint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg; size_t seg_len, from, to; unsigned int seqovl; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; for (i = 0, from = 0; i <= multisplit_count; i++) { to = i == multisplit_count ? dis->len_payload : multisplit_pos[i]; // do seqovl only to the first packet // otherwise it's prone to race condition on server side // what happens first : server pushes socket buffer to process or another packet with seqovl arrives seqovl = (i == 0 && reasm_offset == 0) ? seqovl_pos : 0; #ifdef __linux__ // only linux return error if MTU is exceeded for (;; seqovl = 0) { #endif if (seqovl) { seg_len = to - from + seqovl; if (seg_len > sizeof(ovlseg)) { DLOG("seqovl is too large"); goto send_orig; } fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); memcpy(ovlseg + seqovl, dis->data_payload + from, to - from); seg = ovlseg; } else { seqovl = 0; seg = dis->data_payload + from; seg_len = to - from; } pkt1_len = sizeof(pkt1); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg, seg_len, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending multisplit part %d %zu-%zu len=%zu seqovl=%u : ", i + 1, from, to - 1, to - from, seqovl); hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) { #ifdef __linux__ if (errno == EMSGSIZE && seqovl) { DLOG("MTU exceeded. cancelling seqovl.\n"); continue; } #endif goto send_orig; } #ifdef __linux__ break; } #endif from = to; } if (replay) ctrack_replay->ip_id = ip_id; return VERDICT_DROP; } else goto unsplitted_part; case DESYNC_MULTIDISORDER: if (multisplit_count) { uint8_t ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], *seg; size_t seg_len, from, to; unsigned int seqovl; uint16_t ip_id_end; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; ip_id_end = ip_id = IP4_IP_ID_ADD(ip_id, (uint16_t)multisplit_count, dp->ip_id_mode); for (i = multisplit_count - 1, to = dis->len_payload; i >= -1; i--) { from = i >= 0 ? multisplit_pos[i] : 0; seg = dis->data_payload + from; seg_len = to - from; seqovl = 0; // do seqovl only to the second packet // otherwise sequence overlap becomes problematic. overlap algorithm is not too obvious. // real observations revealed that server can receive overlap junk instead of real data if (i == 0) { if (seqovl_pos >= from) DLOG("seqovl>=split_pos (%zu>=%zu). cancelling seqovl for part %d.\n", seqovl_pos, from, i + 2); else { seqovl = seqovl_pos; seg_len = to - from + seqovl; if (seg_len > sizeof(ovlseg)) { DLOG("seqovl is too large"); goto send_orig; } fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); memcpy(ovlseg + seqovl, dis->data_payload + from, to - from); seg = ovlseg; } } pkt1_len = sizeof(pkt1); if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, false, 0, net32_add(dis->tcp->th_seq, from - seqovl), dis->tcp->th_ack, dis->tcp->th_win, scale_factor, timestamps, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, 0, 0, 0, seg, seg_len, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_PREV(ip_id,dp->ip_id_mode); DLOG("sending multisplit part %d %zu-%zu len=%zu seqovl=%u : ", i + 2, from, to - 1, to - from, seqovl); hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; to = from; } if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id_end,dp->ip_id_mode); return VERDICT_DROP; } else goto unsplitted_part; case DESYNC_FAKEDDISORDER: { uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], fakeseg2[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg; size_t seg_len, fakeseg2_len; unsigned int seqovl; int order; if (dis->len_payload > sizeof(pat)) { DLOG("packet is too large\n"); goto send_orig; } if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; seqovl = 0; if (split_pos) { order = dp->fs_mod.ordering & 3; if (seqovl_pos >= split_pos) DLOG("seqovl>=split_pos (%zu>=%zu). cancelling seqovl.\n", seqovl_pos, split_pos); else seqovl = seqovl_pos; } else { order = (dp->fs_mod.ordering >> 3) & 3; } fill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset); if (seqovl) { seg_len = dis->len_payload - split_pos + seqovl; if (seg_len > sizeof(fakeseg)) { DLOG("seqovl is too large\n"); goto send_orig; } fill_pattern(fakeseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); memcpy(fakeseg + seqovl, dis->data_payload + split_pos, dis->len_payload - split_pos); seg = fakeseg; } else { seg = dis->data_payload + split_pos; seg_len = dis->len_payload - split_pos; } fakeseg2_len = sizeof(fakeseg2); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, pat + split_pos, dis->len_payload - split_pos, fakeseg2, &fakeseg2_len)) goto send_orig; if (order == 0) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending fake(1) 2nd out-of-order tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len)) goto send_orig; } pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, seg, seg_len, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("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); hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; if (order <= 1) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) { if (dis->ip) ((struct ip*)fakeseg2)->ip_id = ip_id; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } DLOG("sending fake(2) 2nd out-of-order tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg2, fakeseg2_len)) goto send_orig; } if (split_pos) { seg_len = sizeof(fakeseg); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, pat, split_pos, fakeseg, &seg_len)) goto send_orig; if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending fake(1) 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len)) goto send_orig; pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, dis->data_payload, split_pos, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); hexdump_limited_dlog(dis->data_payload, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; if (order <= 2) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) { if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } DLOG("sending fake(2) 1st out-of-order tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, seg_len)) goto send_orig; } } if (replay) ctrack_replay->ip_id = ip_id; return VERDICT_DROP; } case DESYNC_FAKEDSPLIT: { uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN + 100], ovlseg[DPI_DESYNC_MAX_FAKE_LEN + 100], pat[DPI_DESYNC_MAX_FAKE_LEN], *seg; size_t fakeseg_len, seg_len; int order; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; if (dis->len_payload > sizeof(pat)) { DLOG("packet is too large\n"); goto send_orig; } fill_pattern(pat, dis->len_payload, dp->fsplit_pattern, dp->fsplit_pattern_size, reasm_offset); if (split_pos) { order = dp->fs_mod.ordering & 3; } else { order = (dp->fs_mod.ordering >> 3) & 3; split_pos = dis->len_payload; } fakeseg_len = sizeof(fakeseg); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, pat, split_pos, fakeseg, &fakeseg_len)) goto send_orig; if (order == 0) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending fake(1) 1st tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) goto send_orig; } unsigned int seqovl = (reasm_offset || split_pos>=dis->len_payload) ? 0 : seqovl_pos; #ifdef __linux__ // only linux return error if MTU is exceeded for (;; seqovl = 0) { #endif if (seqovl) { seg_len = split_pos + seqovl; if (seg_len > sizeof(ovlseg)) { DLOG("seqovl is too large\n"); goto send_orig; } fill_pattern(ovlseg, seqovl, dp->seqovl_pattern, sizeof(dp->seqovl_pattern), 0); memcpy(ovlseg + seqovl, dis->data_payload, split_pos); seg = ovlseg; } else { seg = dis->data_payload; seg_len = split_pos; } pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, seg, seg_len, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending 1st tcp segment 0-%zu len=%zu seqovl=%u : ", split_pos - 1, split_pos, seqovl); hexdump_limited_dlog(seg, seg_len, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) { #ifdef __linux__ if (errno == EMSGSIZE && seqovl) { DLOG("MTU exceeded. cancelling seqovl.\n"); continue; } #endif goto send_orig; } #ifdef __linux__ break; } #endif if (order <= 1) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) { if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } DLOG("sending fake(2) 1st tcp segment 0-%zu len=%zu : ", split_pos - 1, split_pos); hexdump_limited_dlog(pat, split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) goto send_orig; } if (split_pos < dis->len_payload) { fakeseg_len = sizeof(fakeseg); if (!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, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, pat + split_pos, dis->len_payload - split_pos, fakeseg, &fakeseg_len)) goto send_orig; if (dp->ip_id_mode!=IPID_SEQ_GROUP) ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending fake(1) 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) goto send_orig; pkt1_len = sizeof(pkt1); if (!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, DF, ttl_orig, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), fooling_orig, dp->desync_ts_increment, dp->desync_badseq_increment, dp->desync_badseq_ack_increment, dis->data_payload + split_pos, dis->len_payload - split_pos, pkt1, &pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); DLOG("sending 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); hexdump_limited_dlog(dis->data_payload + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; if (order <= 2) { if (dp->ip_id_mode!=IPID_SEQ_GROUP) { if (dis->ip) ((struct ip*)fakeseg)->ip_id = ip_id; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } DLOG("sending fake(2) 2nd tcp segment %zu-%zu len=%zu : ", split_pos, dis->len_payload - 1, dis->len_payload - split_pos); hexdump_limited_dlog(pat + split_pos, dis->len_payload - split_pos, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, fakeseg, fakeseg_len)) goto send_orig; } } if (replay) ctrack_replay->ip_id = ip_id; return VERDICT_DROP; } case DESYNC_IPFRAG2: if (reasm_offset) goto unsplitted_part; else { verdict_tcp_csum_fix(verdict, dis->tcp, dis->transport_len, dis->ip, dis->ip6); uint8_t *pkt_orig; size_t pkt_orig_len; uint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF); size_t ipfrag_pos = (dp->desync_ipfrag_pos_tcp && dp->desync_ipfrag_pos_tcp < dis->transport_len) ? dp->desync_ipfrag_pos_tcp : 24; pkt1_len = sizeof(pkt1); pkt2_len = sizeof(pkt2); if (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT)) { pkt_orig_len = sizeof(pkt3); if (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len)) goto send_orig; pkt_orig = pkt3; } else { pkt_orig = dis->data_pkt; pkt_orig_len = dis->len_pkt; } if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) goto send_orig; DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos - 1, ipfrag_pos); hexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos); hexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) goto send_orig; if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); return VERDICT_DROP; } default: break; } } send_orig: if ((verdict & VERDICT_MASK) == VERDICT_DROP) verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp); else if (tcp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake)) verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, dis->tcp); return verdict; unsplitted_part: if (replay && dis->ip && ctrack_replay->ip_id) { DLOG("changing ip_id of unsplitted part\n"); dis->ip->ip_id = ctrack_replay->ip_id; ctrack_replay->ip_id = IP4_IP_ID_NEXT(ctrack_replay->ip_id,dp->ip_id_mode); return VERDICT_MODIFY; } goto send_orig; } // return : true - should continue, false - should stop with verdict static bool quic_reasm_cancel(t_ctrack *ctrack, const char *reason) { reasm_orig_cancel(ctrack); if (ctrack && ctrack->dp && ctrack->dp->desync_any_proto) { DLOG("%s. applying tampering because desync_any_proto is set\n", reason); return true; } else { DLOG("%s. not applying tampering because desync_any_proto is not set\n", reason); return false; } } static 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) { uint8_t verdict = VERDICT_PASS; // additional safety check if (!!dis->ip == !!dis->ip6) return verdict; struct desync_profile *dp = NULL; t_ctrack *ctrack = NULL, *ctrack_replay = NULL; bool bReverse = false; bool bFake = false; struct sockaddr_storage src, dst; uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt2[DPI_DESYNC_MAX_FAKE_LEN + 100], pkt3[DPI_DESYNC_MAX_FAKE_LEN + 100]; size_t pkt1_len, pkt2_len; uint8_t ttl_orig, ttl_fake; bool DF; char host[256]; t_l7proto l7proto = UNKNOWN; const char *ifname = NULL, *ssid = NULL; extract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst); if (replay) { // in replay mode conntrack_replay is not NULL and ctrack is NULL //ConntrackPoolDump(¶ms.conntrack); if (!ConntrackPoolDoubleSearch(¶ms.conntrack, dis->ip, dis->ip6, NULL, dis->udp, &ctrack_replay, &bReverse) || bReverse) return verdict; ifname = bReverse ? ifin : ifout; #ifdef HAS_FILTER_SSID ssid = wlan_ssid_search_ifname(ifname); if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); #endif dp = ctrack_replay->dp; if (dp) DLOG("using cached desync profile %d\n", dp->n); else if (!ctrack_replay->dp_search_complete) { dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, ctrack_replay->l7proto, ssid, NULL, NULL, NULL); ctrack_replay->dp_search_complete = true; } if (!dp) { DLOG("matching desync profile not found\n"); return verdict; } } else { // in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack if (!params.ctrack_disable) { ConntrackPoolPurge(¶ms.conntrack); if (ConntrackPoolFeed(¶ms.conntrack, dis->ip, dis->ip6, NULL, dis->udp, dis->len_payload, &ctrack, &bReverse)) { dp = ctrack->dp; ctrack_replay = ctrack; } } ifname = bReverse ? ifin : ifout; #ifdef HAS_FILTER_SSID ssid = wlan_ssid_search_ifname(ifname); if (ssid) DLOG("found ssid for %s : %s\n", ifname, ssid); #endif if (dp) DLOG("using cached desync profile %d\n", dp->n); else if (!ctrack || !ctrack->dp_search_complete) { const char *hostname = NULL; bool hostname_is_ip = false; if (ctrack) { hostname = ctrack->hostname; hostname_is_ip = ctrack->hostname_is_ip; if (!hostname && !bReverse) { if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host) if (!(hostname = ctrack_replay->hostname = strdup(host))) DLOG_ERR("strdup(host): out of memory\n"); } } dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, hostname, hostname_is_ip, ctrack ? ctrack->l7proto : UNKNOWN, ssid, NULL, NULL, NULL); if (ctrack) { ctrack->dp = dp; ctrack->dp_search_complete = true; } } if (!dp) { DLOG("matching desync profile not found\n"); return verdict; } maybe_cutoff(ctrack, IPPROTO_UDP); HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); //ConntrackPoolDump(¶ms.conntrack); if (bReverse) { if (ctrack) { ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; if (!ctrack->incoming_ttl) { DLOG("incoming TTL %u\n", ttl_orig); ctrack->incoming_ttl = ttl_orig; autottl_rediscover(ctrack, dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, ifin); } } return verdict; // nothing to do. do not waste cpu } autottl_discover(ctrack, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); if (orig_mod(dp, ctrack, dis)) // ttl can change ! verdict = VERDICT_MODIFY; // start and cutoff limiters if (!process_desync_interval(dp, ctrack)) goto send_orig; } uint32_t desync_fwmark = fwmark | params.desync_fwmark; DF = ip_has_df(dis->ip); if (dis->len_payload) { struct blob_collection_head *fake; bool bHaveHost = false, bHostIsIp = false; uint16_t ip_id=0; if (replay && ctrack_replay->ip_id) ip_id = ctrack_replay->ip_id; if (!ip_id) ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); if (IsQUICInitial(dis->data_payload, dis->len_payload)) { DLOG("packet contains QUIC initial\n"); l7proto = QUIC; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; uint8_t clean[16384], *pclean; size_t clean_len; if (replay) { clean_len = ctrack_replay->reasm_orig.size_present; pclean = ctrack_replay->reasm_orig.packet; } else { clean_len = sizeof(clean); pclean = QUICDecryptInitial(dis->data_payload, dis->len_payload, clean, &clean_len) ? clean : NULL; } if (pclean) { if (ctrack && !ReasmIsEmpty(&ctrack->reasm_orig)) { if (ReasmHasSpace(&ctrack->reasm_orig, clean_len)) { reasm_orig_feed(ctrack, IPPROTO_UDP, clean, clean_len); pclean = ctrack->reasm_orig.packet; clean_len = ctrack->reasm_orig.size_present; } else { DLOG("QUIC reasm is too long. cancelling.\n"); reasm_orig_cancel(ctrack); goto send_orig; // cannot be first packet } } uint8_t defrag[UDP_MAX_REASM]; size_t hello_offset, hello_len, defrag_len = sizeof(defrag); bool bFull; if (QUICDefragCrypto(pclean, clean_len, defrag, &defrag_len, &bFull)) { if (bFull) { DLOG("QUIC initial contains CRYPTO with full fragment coverage\n"); bool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len); bool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag + hello_offset, hello_len) : false; DLOG(bIsHello ? bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n" : "packet does not contain TLS ClientHello\n"); if (bReqFull) TLSDebugHandshake(defrag + hello_offset, hello_len); if (ctrack) { if (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig)) { // preallocate max buffer to avoid reallocs that cause memory copy if (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len)) { reasm_orig_cancel(ctrack); goto send_orig; } } if (!ReasmIsEmpty(&ctrack->reasm_orig)) { verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); reasm_orig_cancel(ctrack); goto send_orig; } if (bReqFull) { replay_queue(&ctrack->delayed); reasm_orig_fin(ctrack); } return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); } } if (bIsHello) { bHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE); if (!bHaveHost && dp->desync_skip_nosni) { reasm_orig_cancel(ctrack); DLOG("not applying tampering to QUIC ClientHello without hostname in the SNI\n"); goto send_orig; } } else { if (!quic_reasm_cancel(ctrack, "QUIC initial without ClientHello")) goto send_orig; } } else { DLOG("QUIC initial contains CRYPTO with partial fragment coverage\n"); if (ctrack) { if (ReasmIsEmpty(&ctrack->reasm_orig)) { // preallocate max buffer to avoid reallocs that cause memory copy if (!reasm_orig_start(ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len)) { reasm_orig_cancel(ctrack); goto send_orig; } } verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); reasm_orig_cancel(ctrack); goto send_orig; } return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); } if (!quic_reasm_cancel(ctrack, "QUIC initial fragmented CRYPTO")) goto send_orig; } } else { // defrag failed if (!quic_reasm_cancel(ctrack, "QUIC initial defrag CRYPTO failed")) goto send_orig; } } else { // decrypt failed if (!quic_reasm_cancel(ctrack, "QUIC initial decryption failed")) goto send_orig; } } else // not QUIC initial { // received payload without host. it means we are out of the request retransmission phase. stop counter ctrack_stop_retrans_counter(ctrack); reasm_orig_cancel(ctrack); if (IsWireguardHandshakeInitiation(dis->data_payload, dis->len_payload)) { DLOG("packet contains wireguard handshake initiation\n"); l7proto = WIREGUARD; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; } else if (IsDhtD1(dis->data_payload, dis->len_payload)) { DLOG("packet contains DHT d1...e\n"); l7proto = DHT; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; } else if (IsDiscordIpDiscoveryRequest(dis->data_payload, dis->len_payload)) { DLOG("packet contains discord voice IP discovery\n"); l7proto = DISCORD; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; } else if (IsStunMessage(dis->data_payload, dis->len_payload)) { DLOG("packet contains STUN message\n"); l7proto = STUN; if (ctrack && ctrack->l7proto == UNKNOWN) ctrack->l7proto = l7proto; } else { if (!dp->desync_any_proto) { DLOG("not applying tampering to unknown protocol\n"); goto send_orig; } DLOG("applying tampering to unknown protocol\n"); } } if (bHaveHost) { bHostIsIp = strip_host_to_ip(host); DLOG("hostname: %s\n", host); } bool bDiscoveredL7; if (ctrack_replay) { if ((bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != UNKNOWN)) ctrack_replay->l7proto_discovered = true; } else bDiscoveredL7 = !ctrack_replay && l7proto != UNKNOWN; if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname_discovered); if (bDiscoveredHostname) { DLOG("discovered hostname\n"); if (ctrack_replay) { ctrack_replay->hostname_discovered = true; free(ctrack_replay->hostname); ctrack_replay->hostname = strdup(host); ctrack_replay->hostname_is_ip = bHostIsIp; if (!ctrack_replay->hostname) { DLOG_ERR("hostname dup : out of memory"); goto send_orig; } if (!ipcache_put_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, bHostIsIp)) goto send_orig; } } bool bCheckDone = false, bCheckResult = false, bCheckExcluded = false; if (bDiscoveredL7 || bDiscoveredHostname) { struct desync_profile *dp_prev = dp; dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, ctrack_replay ? ctrack_replay->l7proto : l7proto, ssid, &bCheckDone, &bCheckResult, &bCheckExcluded); if (ctrack_replay) { ctrack_replay->dp = dp; ctrack_replay->dp_search_complete = true; ctrack_replay->bCheckDone = bCheckDone; ctrack_replay->bCheckResult = bCheckResult; ctrack_replay->bCheckExcluded = bCheckExcluded; } if (!dp) { reasm_orig_cancel(ctrack); goto send_orig; } if (dp != dp_prev) { DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); autottl_rediscover(ctrack_replay, dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, ifout); ip_id = IP4_IP_ID_FIX(dis->ip,dp->ip_id_mode); // re-evaluate start/cutoff limiters if (replay) { if (orig_mod(dp, ctrack_replay, dis)) // ttl can change ! verdict = VERDICT_MODIFY; } else { maybe_cutoff(ctrack, IPPROTO_UDP); if (orig_mod(dp, ctrack, dis)) // ttl can change ! verdict = VERDICT_MODIFY; if (!process_desync_interval(dp, ctrack)) goto send_orig; } } } else if (ctrack_replay) { bCheckDone = ctrack_replay->bCheckDone; bCheckResult = ctrack_replay->bCheckResult; bCheckExcluded = ctrack_replay->bCheckExcluded; } if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp)) { if (!bCheckDone) bCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false); if (bCheckResult) ctrack_stop_retrans_counter(ctrack_replay); else { if (ctrack_replay) { ctrack_replay->hostname_ah_check = dp->hostlist_auto && !bCheckExcluded; if (ctrack_replay->hostname_ah_check) { // first request is not retrans if (!bDiscoveredHostname && !reasm_offset) process_retrans_fail(ctrack_replay, IPPROTO_UDP, (struct sockaddr*)&src); } } DLOG("not applying tampering to this request\n"); goto send_orig; } } // desync profile may have changed after hostname was revealed switch (l7proto) { case QUIC: fake = &dp->fake_quic; break; case WIREGUARD: fake = &dp->fake_wg; break; case DHT: fake = &dp->fake_dht; break; case DISCORD: fake = &dp->fake_discord; break; case STUN: fake = &dp->fake_stun; break; default: fake = &dp->fake_unknown_udp; break; } ttl_orig = dis->ip ? dis->ip->ip_ttl : dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim; ttl_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)); uint32_t fooling_orig = FOOL_NONE; if (params.debug) { char s1[48], s2[48]; ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); DLOG("dpi desync src=%s dst=%s\n", s1, s2); } switch (dp->desync_mode) { case DESYNC_FAKE_KNOWN: if (l7proto == UNKNOWN) { DLOG("not applying fake because of unknown protocol\n"); break; } case DESYNC_FAKE: if (!reasm_offset) { size_t fake_size; uint8_t *fake_data; struct blob_item *fake_item; int n = 0; LIST_FOREACH(fake_item, fake, next) { n++; fake_data = fake_item->data + fake_item->offset; fake_size = fake_item->size - fake_item->offset; pkt1_len = sizeof(pkt1); if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_fake, IP4_TOS(dis->ip), ip_id, IP6_FLOW(dis->ip6), dp->desync_fooling_mode, NULL, 0, 0, fake_data, fake_size, pkt1, &pkt1_len)) { goto send_orig; } DLOG("sending fake[%d] : ", n); hexdump_limited_dlog(fake_data, fake_size, PKTDATA_MAXDUMP); DLOG("\n"); if (!rawsend_rep(dp->desync_repeats, (struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); } bFake = true; } break; case DESYNC_HOPBYHOP: case DESYNC_DESTOPT: case DESYNC_IPFRAG1: fooling_orig = (dp->desync_mode == DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (dp->desync_mode == DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; if (dis->ip6 && (dp->desync_mode2 == DESYNC_NONE || !desync_valid_second_stage_udp(dp->desync_mode2))) { pkt1_len = sizeof(pkt1); if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, DF, ttl_orig, 0, 0, IP6_FLOW(dis->ip6), fooling_orig, NULL, 0, 0, dis->data_payload, dis->len_payload, pkt1, &pkt1_len)) { goto send_orig; } DLOG("resending original packet with extension header\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; // this mode is final, no other options available return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); } break; default: pkt1_len = 0; break; } enum dpi_desync_mode desync_mode = dp->desync_mode2 == DESYNC_NONE ? dp->desync_mode : dp->desync_mode2; switch (desync_mode) { case DESYNC_UDPLEN: pkt1_len = sizeof(pkt1); if (!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)) { DLOG("could not construct packet with modified length. too large ?\n"); break; } DLOG("resending original packet with increased by %d length\n", dp->udplen_increment); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); case DESYNC_TAMPER: if (IsDhtD1(dis->data_payload, dis->len_payload)) { size_t szbuf, szcopy; memcpy(pkt2, "d2:001:x", 8); pkt2_len = 8; szbuf = sizeof(pkt2) - pkt2_len; szcopy = dis->len_payload - 1; if (szcopy > szbuf) { DLOG("packet is too long to tamper"); break; } memcpy(pkt2 + pkt2_len, dis->data_payload + 1, szcopy); pkt2_len += szcopy; pkt1_len = sizeof(pkt1); if (!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)) { DLOG("could not construct packet with modified length. too large ?\n"); break; } DLOG("resending tampered DHT\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); } else { DLOG("payload is not tamperable\n"); break; } case DESYNC_IPFRAG2: { verdict_udp_csum_fix(verdict, dis->udp, dis->transport_len, dis->ip, dis->ip6); uint8_t *pkt_orig; size_t pkt_orig_len; uint32_t ident = dis->ip ? ip_id ? ip_id : htons(1 + random() % 0xFFFF) : htonl(1 + random() % 0xFFFFFFFF); size_t ipfrag_pos = (dp->desync_ipfrag_pos_udp && dp->desync_ipfrag_pos_udp < dis->transport_len) ? dp->desync_ipfrag_pos_udp : sizeof(struct udphdr); pkt1_len = sizeof(pkt1); pkt2_len = sizeof(pkt2); if (dis->ip6 && (fooling_orig == FOOL_HOPBYHOP || fooling_orig == FOOL_DESTOPT)) { pkt_orig_len = sizeof(pkt3); if (!ip6_insert_simple_hdr(fooling_orig == FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, dis->data_pkt, dis->len_pkt, pkt3, &pkt_orig_len)) goto send_orig; pkt_orig = pkt3; } else { pkt_orig = dis->data_pkt; pkt_orig_len = dis->len_pkt; } if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) goto send_orig; DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos - 1, ipfrag_pos); hexdump_limited_dlog(pkt1, pkt1_len, IP_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt1, pkt1_len)) goto send_orig; DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, dis->transport_len - 1, dis->transport_len - ipfrag_pos); hexdump_limited_dlog(pkt2, pkt2_len, IP_MAXDUMP); DLOG("\n"); if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout, pkt2, pkt2_len)) goto send_orig; if (replay) ctrack_replay->ip_id = IP4_IP_ID_NEXT(ip_id,dp->ip_id_mode); return ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); } default: break; } } send_orig: if ((verdict & VERDICT_MASK) == VERDICT_DROP) verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); else if (udp_orig_send(verdict, desync_fwmark, ifout, dp, ctrack_replay, dis, bFake)) verdict = ct_new_postnat_fix(ctrack, dis->ip, dis->ip6, NULL); return verdict; } static void packet_debug(bool replay, const struct dissect *dis) { if (params.debug) { if (replay) DLOG("REPLAY "); if (dis->ip) { char s[66]; str_ip(s, sizeof(s), dis->ip); DLOG("IP4: %s", s); } else if (dis->ip6) { char s[128]; str_ip6hdr(s, sizeof(s), dis->ip6, dis->proto); DLOG("IP6: %s", s); } if (dis->tcp) { char s[80]; str_tcphdr(s, sizeof(s), dis->tcp); DLOG(" %s\n", s); if (dis->len_payload) { DLOG("TCP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } } else if (dis->udp) { char s[30]; str_udphdr(s, sizeof(s), dis->udp); DLOG(" %s\n", s); if (dis->len_payload) { DLOG("UDP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } } else DLOG("\n"); } } static 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) { struct dissect dis; uint8_t verdict = VERDICT_PASS; proto_dissect_l3l4(data_pkt, *len_pkt, &dis); if (!!dis.ip != !!dis.ip6) { packet_debug(replay, &dis); switch (dis.proto) { case IPPROTO_TCP: if (dis.tcp) { verdict = dpi_desync_tcp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis); verdict_tcp_csum_fix(verdict, dis.tcp, dis.transport_len, dis.ip, dis.ip6); } break; case IPPROTO_UDP: if (dis.udp) { verdict = dpi_desync_udp_packet_play(replay, reasm_offset, fwmark, ifin, ifout, &dis); verdict_udp_csum_fix(verdict, dis.udp, dis.transport_len, dis.ip, dis.ip6); } break; } *len_pkt = dis.len_pkt; } return verdict; } uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) { ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); return dpi_desync_packet_play(false, 0, fwmark, ifin, ifout, data_pkt, len_pkt); } static bool replay_queue(struct rawpacket_tailhead *q) { struct rawpacket *rp; size_t offset; unsigned int i; bool b = true; for (i = 1, offset = 0; (rp = rawpacket_dequeue(q)); offset += rp->len_payload, rawpacket_free(rp), i++) { DLOG("REPLAYING delayed packet #%u offset %zu\n", i, offset); uint8_t verdict = dpi_desync_packet_play(true, offset, rp->fwmark, rp->ifin, rp->ifout, rp->packet, &rp->len); switch (verdict & VERDICT_MASK) { case VERDICT_MODIFY: DLOG("SENDING delayed packet #%u modified\n", i); b &= rawsend_rp(rp); break; case VERDICT_PASS: DLOG("SENDING delayed packet #%u unmodified\n", i); b &= rawsend_rp(rp); break; case VERDICT_DROP: DLOG("DROPPING delayed packet #%u\n", i); break; } } return b; } ================================================ FILE: nfq/desync.h ================================================ #pragma once #include "darkmagic.h" #include #include #define __FAVOR_BSD #include #include #include #include #ifdef __linux__ #define DPI_DESYNC_FWMARK_DEFAULT 0x40000000 #else #define DPI_DESYNC_FWMARK_DEFAULT 512 #endif #define DPI_DESYNC_MAX_FAKE_LEN 9216 enum dpi_desync_mode { DESYNC_NONE=0, DESYNC_INVALID, DESYNC_FAKE, DESYNC_FAKE_KNOWN, DESYNC_RST, DESYNC_RSTACK, DESYNC_SYNACK, DESYNC_SYNDATA, DESYNC_FAKEDSPLIT, DESYNC_FAKEDDISORDER, DESYNC_MULTISPLIT, DESYNC_MULTIDISORDER, DESYNC_HOSTFAKESPLIT, DESYNC_IPFRAG2, DESYNC_HOPBYHOP, DESYNC_DESTOPT, DESYNC_IPFRAG1, DESYNC_UDPLEN, DESYNC_TAMPER }; extern const char *fake_http_request_default; extern const uint8_t fake_tls_clienthello_default[680]; void randomize_default_tls_payload(uint8_t *p); enum dpi_desync_mode desync_mode_from_string(const char *s); bool desync_valid_zero_stage(enum dpi_desync_mode mode); bool desync_valid_first_stage(enum dpi_desync_mode mode); bool desync_only_first_stage(enum dpi_desync_mode mode); bool desync_valid_second_stage(enum dpi_desync_mode mode); bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode); bool desync_valid_second_stage_udp(enum dpi_desync_mode mode); uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt); ================================================ FILE: nfq/gzip.c ================================================ #include "gzip.h" #include #include #include #define ZCHUNK 16384 #define BUFMIN 128 #define BUFCHUNK (1024*128) int z_readfile(FILE *F, char **buf, size_t *size) { z_stream zs; int r; unsigned char in[ZCHUNK]; size_t bufsize; void *newbuf; memset(&zs, 0, sizeof(zs)); *buf = NULL; bufsize = *size = 0; r = inflateInit2(&zs, 47); if (r != Z_OK) return r; do { zs.avail_in = fread(in, 1, sizeof(in), F); if (ferror(F)) { r = Z_ERRNO; goto zerr; } if (!zs.avail_in) break; zs.next_in = in; do { if ((bufsize - *size) < BUFMIN) { bufsize += BUFCHUNK; newbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize); if (!newbuf) { r = Z_MEM_ERROR; goto zerr; } *buf = newbuf; } zs.avail_out = bufsize - *size; zs.next_out = (unsigned char*)(*buf + *size); r = inflate(&zs, Z_NO_FLUSH); if (r != Z_OK && r != Z_STREAM_END) goto zerr; *size = bufsize - zs.avail_out; } while (r == Z_OK && zs.avail_in); } while (r == Z_OK); if (*size < bufsize) { // free extra space if ((newbuf = realloc(*buf, *size))) *buf = newbuf; } inflateEnd(&zs); return Z_OK; zerr: inflateEnd(&zs); free(*buf); *buf = NULL; return r; } bool is_gzip(FILE* F) { unsigned char magic[2]; bool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B; fseek(F, 0, SEEK_SET); return b; } ================================================ FILE: nfq/gzip.h ================================================ #pragma once #include #include #include int z_readfile(FILE *F,char **buf,size_t *size); bool is_gzip(FILE* F); ================================================ FILE: nfq/helpers.c ================================================ #define _GNU_SOURCE #include "helpers.h" #include #include #include #include #include #include #include #include int unique_size_t(size_t *pu, int ct) { size_t i, j, u; for (i = j = 0; j < ct; i++) { u = pu[j++]; for (; j < ct && pu[j] == u; j++); pu[i] = u; } return i; } static int cmp_size_t(const void * a, const void * b) { return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b; } void qsort_size_t(size_t *array,size_t ct) { qsort(array,ct,sizeof(*array),cmp_size_t); } void rtrim(char *s) { if (s) for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; } void replace_char(char *s, char from, char to) { for(;*s;s++) if (*s==from) *s=to; } char *strncasestr(const char *s, const char *find, size_t slen) { char c, sc; size_t len; if ((c = *find++) != '\0') { len = strlen(find); do { do { if (slen-- < 1 || (sc = *s++) == '\0') return NULL; } while (toupper(c) != toupper(sc)); if (len > slen) return NULL; } while (strncasecmp(s, find, len) != 0); s--; } return (char *)s; } bool load_file(const char *filename, void *buffer, size_t *buffer_size) { FILE *F; F = fopen(filename, "rb"); if (!F) return false; *buffer_size = fread(buffer, 1, *buffer_size, F); if (ferror(F)) { fclose(F); return false; } fclose(F); return true; } bool load_file_nonempty(const char *filename, void *buffer, size_t *buffer_size) { bool b = load_file(filename, buffer, buffer_size); return b && *buffer_size; } bool save_file(const char *filename, const void *buffer, size_t buffer_size) { FILE *F; F = fopen(filename, "wb"); if (!F) return false; fwrite(buffer, 1, buffer_size, F); if (ferror(F)) { fclose(F); return false; } fclose(F); return true; } bool append_to_list_file(const char *filename, const char *s) { FILE *F = fopen(filename,"at"); if (!F) return false; bool bOK = fprintf(F,"%s\n",s)>0; fclose(F); return bOK; } void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen) { unsigned int target_bitlen = target_bytelen<<3; unsigned int bitlen = target_bitlen>3; if ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen); memcpy(target,source,bytelen); if ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1)); } // " [fd00::1]" => "fd00::1" // "[fd00::1]:8000" => "fd00::1" // "127.0.0.1" => "127.0.0.1" // " 127.0.0.1:8000" => "127.0.0.1" // " vk.com:8000" => "vk.com" // return value: true - host is ip addr bool strip_host_to_ip(char *host) { size_t l; char *h,*p; uint8_t addr[16]; for (h = host ; *h==' ' || *h=='\t' ; h++); l = strlen(h); if (l>=2) { if (*h=='[') { // ipv6 ? for (p=++h ; *p && *p!=']' ; p++); if (*p==']') { l = p-h; memmove(host,h,l); host[l]=0; return inet_pton(AF_INET6, host, addr)>0; } } else { if (inet_pton(AF_INET6, h, addr)>0) { // ipv6 ? if (host!=h) { l = strlen(h); memmove(host,h,l); host[l]=0; } return true; } else { // ipv4 ? for (p=h ; *p && *p!=':' ; p++); l = p-h; if (host!=h) memmove(host,h,l); host[l]=0; return inet_pton(AF_INET, host, addr)>0; } } } return false; } void ntop46(const struct sockaddr *sa, char *str, size_t len) { if (!len) return; *str = 0; switch (sa->sa_family) { case AF_INET: inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); break; case AF_INET6: inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); break; default: snprintf(str, len, "UNKNOWN_FAMILY_%d", sa->sa_family); } } void ntop46_port(const struct sockaddr *sa, char *str, size_t len) { char ip[40]; ntop46(sa, ip, sizeof(ip)); switch (sa->sa_family) { case AF_INET: snprintf(str, len, "%s:%u", ip, ntohs(((struct sockaddr_in*)sa)->sin_port)); break; case AF_INET6: snprintf(str, len, "[%s]:%u", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); break; default: snprintf(str, len, "%s", ip); } } void print_sockaddr(const struct sockaddr *sa) { char ip_port[48]; ntop46_port(sa, ip_port, sizeof(ip_port)); printf("%s", ip_port); } bool pton4_port(const char *s, struct sockaddr_in *sa) { char ip[16],*p; size_t l; unsigned int u; p = strchr(s,':'); if (!p) return false; l = p-s; if (l<7 || l>15) return false; memcpy(ip,s,l); ip[l]=0; p++; sa->sin_family = AF_INET; if (inet_pton(AF_INET,ip,&sa->sin_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; sa->sin_port = htons((uint16_t)u); return true; } bool pton6_port(const char *s, struct sockaddr_in6 *sa) { char ip[40],*p; size_t l; unsigned int u; if (*s++!='[') return false; p = strchr(s,']'); if (!p || p[1]!=':') return false; l = p-s; if (l<2 || l>39) return false; p+=2; memcpy(ip,s,l); ip[l]=0; sa->sin6_family = AF_INET6; if (inet_pton(AF_INET6,ip,&sa->sin6_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; sa->sin6_port = htons((uint16_t)u); sa->sin6_flowinfo = 0; sa->sin6_scope_id = 0; return true; } uint16_t saport(const struct sockaddr *sa) { return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port : sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); } uint64_t pntoh64(const void *p) { return (uint64_t)*((const uint8_t *)(p)+0) << 56 | (uint64_t)*((const uint8_t *)(p)+1) << 48 | (uint64_t)*((const uint8_t *)(p)+2) << 40 | (uint64_t)*((const uint8_t *)(p)+3) << 32 | (uint64_t)*((const uint8_t *)(p)+4) << 24 | (uint64_t)*((const uint8_t *)(p)+5) << 16 | (uint64_t)*((const uint8_t *)(p)+6) << 8 | (uint64_t)*((const uint8_t *)(p)+7) << 0; } void phton64(uint8_t *p, uint64_t v) { p[0] = (uint8_t)(v >> 56); p[1] = (uint8_t)(v >> 48); p[2] = (uint8_t)(v >> 40); p[3] = (uint8_t)(v >> 32); p[4] = (uint8_t)(v >> 24); p[5] = (uint8_t)(v >> 16); p[6] = (uint8_t)(v >> 8); p[7] = (uint8_t)(v >> 0); } bool seq_within(uint32_t s, uint32_t s1, uint32_t s2) { return (s2>=s1 && s>=s1 && s<=s2) || (s2=s1)); } bool ipv6_addr_is_zero(const struct in6_addr *a) { return !memcmp(a,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",16); } #define INVALID_HEX_DIGIT ((uint8_t)-1) static inline uint8_t parse_hex_digit(char c) { return (c>='0' && c<='9') ? c-'0' : (c>='a' && c<='f') ? c-'a'+0xA : (c>='A' && c<='F') ? c-'A'+0xA : INVALID_HEX_DIGIT; } static inline bool parse_hex_byte(const char *s, uint8_t *pbyte) { uint8_t u,l; u = parse_hex_digit(s[0]); l = parse_hex_digit(s[1]); if (u==INVALID_HEX_DIGIT || l==INVALID_HEX_DIGIT) { *pbyte=0; return false; } else { *pbyte=(u<<4) | l; return true; } } bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size) { uint8_t *pe = pbuf+*size; *size=0; while(pbufsize ? size : bufsize; memcpy(buf,pattern+offset,size); buf += size; bufsize -= size; } while (bufsize) { size = bufsize>patsize ? patsize : bufsize; memcpy(buf,pattern,size); buf += size; bufsize -= size; } } int fprint_localtime(FILE *F) { struct tm t; time_t now; time(&now); localtime_r(&now,&t); return 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); } time_t file_mod_time(const char *filename) { struct stat st; return stat(filename,&st)==-1 ? 0 : st.st_mtime; } bool file_mod_signature(const char *filename, file_mod_sig *ms) { struct stat st; if (stat(filename,&st)==-1) { FILE_MOD_RESET(ms); return false; } ms->mod_time=st.st_mtime; ms->size=st.st_size; return true; } bool file_open_test(const char *filename, int flags) { int fd = open(filename,flags); if (fd>=0) { close(fd); return true; } return false; } bool pf_in_range(uint16_t port, const port_filter *pf) { return port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg); } bool pf_parse(const char *s, port_filter *pf) { unsigned int v1,v2; char c; if (!s) return false; if (*s=='*' && s[1]==0) { pf->from=1; pf->to=0xFFFF; return true; } if (*s=='~') { pf->neg=true; s++; } else pf->neg=false; if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2) { if (v1>65535 || v2>65535 || v1>v2) return false; pf->from=(uint16_t)v1; pf->to=(uint16_t)v2; } else if (sscanf(s,"%u%c",&v1,&c)==1) { if (v1>65535) return false; pf->to=pf->from=(uint16_t)v1; } else return false; // deny all case if (!pf->from && !pf->to) pf->neg=true; return true; } bool pf_is_empty(const port_filter *pf) { return !pf->neg && !pf->from && !pf->to; } void fill_random_bytes(uint8_t *p,size_t sz) { size_t k,sz16 = sz>>1; for(k=0;kaddr, s_ip, sizeof(s_ip)); snprintf(s,s_len,cidr->preflen<32 ? "%s/%u" : "%s", s_ip, cidr->preflen); } void print_cidr4(const struct cidr4 *cidr) { char s[19]; str_cidr4(s,sizeof(s),cidr); printf("%s",s); } void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) { char s_ip[40]; *s_ip=0; inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); snprintf(s,s_len,cidr->preflen<128 ? "%s/%u" : "%s", s_ip, cidr->preflen); } void print_cidr6(const struct cidr6 *cidr) { char s[44]; str_cidr6(s,sizeof(s),cidr); printf("%s",s); } bool parse_cidr4(char *s, struct cidr4 *cidr) { char *p,d; bool b; unsigned int plen; if ((p = strchr(s, '/'))) { if (sscanf(p + 1, "%u", &plen)!=1 || plen>32) return false; cidr->preflen = (uint8_t)plen; d=*p; *p=0; // backup char } else cidr->preflen = 32; b = (inet_pton(AF_INET, s, &cidr->addr)==1); if (p) *p=d; // restore char return b; } bool parse_cidr6(char *s, struct cidr6 *cidr) { char *p,d; bool b; unsigned int plen; if ((p = strchr(s, '/'))) { if (sscanf(p + 1, "%u", &plen)!=1 || plen>128) return false; cidr->preflen = (uint8_t)plen; d=*p; *p=0; // backup char } else cidr->preflen = 128; b = (inet_pton(AF_INET6, s, &cidr->addr)==1); if (p) *p=d; // restore char return b; } ================================================ FILE: nfq/helpers.h ================================================ #pragma once #include #include #include #include #include #include #include #include #define UNARY_PLUS(v) (v>0 ? "+" : "") // this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes. typedef union { struct sockaddr_in sa4; // size 16 struct sockaddr_in6 sa6; // size 28 char _align[32]; // force 16-byte alignment for ip6_and int128 ops } sockaddr_in46; int unique_size_t(size_t *pu, int ct); void qsort_size_t(size_t *array,size_t ct); void rtrim(char *s); void replace_char(char *s, char from, char to); char *strncasestr(const char *s,const char *find, size_t slen); bool load_file(const char *filename,void *buffer,size_t *buffer_size); bool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size); bool save_file(const char *filename, const void *buffer, size_t buffer_size); bool append_to_list_file(const char *filename, const char *s); void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen); bool strip_host_to_ip(char *host); void print_sockaddr(const struct sockaddr *sa); void ntop46(const struct sockaddr *sa, char *str, size_t len); void ntop46_port(const struct sockaddr *sa, char *str, size_t len); bool pton4_port(const char *s, struct sockaddr_in *sa); bool pton6_port(const char *s, struct sockaddr_in6 *sa); uint16_t saport(const struct sockaddr *sa); bool seq_within(uint32_t s, uint32_t s1, uint32_t s2); uint64_t pntoh64(const void *p); void phton64(uint8_t *p, uint64_t v); bool ipv6_addr_is_zero(const struct in6_addr *a); static inline uint16_t pntoh16(const uint8_t *p) { return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; } static inline void phton16(uint8_t *p, uint16_t v) { p[0] = (uint8_t)(v >> 8); p[1] = v & 0xFF; } static inline uint32_t pntoh24(const uint8_t *p) { return ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; } static inline void phton24(uint8_t *p, uint32_t v) { p[0] = (uint8_t)(v>>16); p[1] = (uint8_t)(v>>8); p[2] = (uint8_t)v; } static inline uint32_t pntoh32(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size); void fill_pattern(uint8_t *buf,size_t bufsize,const void *pattern,size_t patsize,size_t offset); int fprint_localtime(FILE *F); typedef struct { time_t mod_time; off_t size; } file_mod_sig; #define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size)) #define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig)) bool file_mod_signature(const char *filename, file_mod_sig *ms); time_t file_mod_time(const char *filename); bool file_open_test(const char *filename, int flags); typedef struct { uint16_t from,to; bool neg; } port_filter; bool pf_in_range(uint16_t port, const port_filter *pf); bool pf_parse(const char *s, port_filter *pf); bool pf_is_empty(const port_filter *pf); void fill_random_bytes(uint8_t *p,size_t sz); void fill_random_az(uint8_t *p,size_t sz); void fill_random_az09(uint8_t *p,size_t sz); void set_console_io_buffering(void); bool set_env_exedir(const char *argv0); struct cidr4 { struct in_addr addr; uint8_t preflen; }; struct cidr6 { struct in6_addr addr; uint8_t preflen; }; void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); void print_cidr4(const struct cidr4 *cidr); void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); void print_cidr6(const struct cidr6 *cidr); bool parse_cidr4(char *s, struct cidr4 *cidr); bool parse_cidr6(char *s, struct cidr6 *cidr); ================================================ FILE: nfq/hostlist.c ================================================ #include #include "hostlist.h" #include "gzip.h" #include "helpers.h" // inplace tolower() and add to pool static bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct) { char *p=*s; // comment line if ( *p == '#' || *p == ';' || *p == '/' || *p == '\r' || *p == '\n') { // advance until eol for (; pfilename) { file_mod_sig fsig; if (!file_mod_signature(hfile->filename, &fsig)) { // stat() error DLOG_PERROR("file_mod_signature"); DLOG_ERR("cannot access hostlist file '%s'. in-memory content remains unchanged.\n",hfile->filename); return true; } if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date HostlistPoolDestroy(&hfile->hostlist); if (!AppendHostList(&hfile->hostlist, hfile->filename)) { HostlistPoolDestroy(&hfile->hostlist); return false; } hfile->mod_sig=fsig; } return true; } static bool LoadHostLists(struct hostlist_files_head *list) { bool bres=true; struct hostlist_file *hfile; LIST_FOREACH(hfile, list, next) { if (!LoadHostList(hfile)) // at least one failed bres=false; } return bres; } bool NonEmptyHostlist(hostlist_pool **hostlist) { // add impossible hostname if the list is empty return *hostlist ? true : HostlistPoolAddStrLen(hostlist, "@&()", 4, 0); } static void MakeAutolistsNonEmpty() { struct desync_profile_list *dpl; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { if (dpl->dp.hostlist_auto) NonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist); } } bool LoadAllHostLists() { if (!LoadHostLists(¶ms.hostlists)) return false; MakeAutolistsNonEmpty(); return true; } static bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains) { if (hostlist) { const char *p = host; const struct hostlist_pool *hp; bool bHostFull=true; while (p) { DLOG("hostlist check for %s : ", p); hp = HostlistPoolGetStr(hostlist, p); if (hp) { if ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull) { DLOG("negative : strict_mismatch : %s != %s\n", p, host); } else { DLOG("positive\n"); return true; } } else DLOG("negative\n"); if (no_match_subdomains) break; p = strchr(p, '.'); if (p) p++; bHostFull = false; } } return false; } static bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists) { struct hostlist_item *item; LIST_FOREACH(item, hostlists, next) { if (!LoadHostList(item->hfile)) return false; } MakeAutolistsNonEmpty(); return true; } bool HostlistsReloadCheckForProfile(const struct desync_profile *dp) { return HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude); } // return : true = apply fooling, false = do not apply static 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) { struct hostlist_item *item; if (excluded) *excluded = false; if (!bSkipReloadCheck) if (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude)) return false; LIST_FOREACH(item, hostlists_exclude, next) { DLOG("[%s] exclude ", item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) { if (excluded) *excluded = true; return false; } } // old behavior compat: all include lists are empty means check passes if (!hostlist_collection_is_empty(hostlists)) { LIST_FOREACH(item, hostlists, next) { DLOG("[%s] include ", item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) return true; } return false; } return true; } // return : true = apply fooling, false = do not apply bool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) { DLOG("* hostlist check for profile %d\n",dp->n); return HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck); } static struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename) { struct hostlist_file *hfile; if (filename) { if (!(hfile=hostlist_files_search(hostlists, filename))) if (!(hfile=hostlist_files_add(hostlists, filename))) return NULL; if (!hostlist_collection_search(hl_collection, filename)) if (!hostlist_collection_add(hl_collection, hfile)) return NULL; } else { if (!(hfile=hostlist_files_add(hostlists, NULL))) return NULL; if (!hostlist_collection_add(hl_collection, hfile)) return NULL; } return hfile; } struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename) { /* if (filename && !file_mod_time(filename)) { DLOG_ERR("cannot access hostlist file '%s'\n",filename); return NULL; } */ return RegisterHostlist_( ¶ms.hostlists, bExclude ? &dp->hl_collection_exclude : &dp->hl_collection, filename); } void HostlistsDebug() { if (!params.debug) return; struct hostlist_file *hfile; struct desync_profile_list *dpl; struct hostlist_item *hl_item; LIST_FOREACH(hfile, ¶ms.hostlists, next) { if (hfile->filename) DLOG("hostlist file %s%s\n",hfile->filename,hfile->hostlist ? "" : " (empty)"); else DLOG("hostlist fixed%s\n",hfile->hostlist ? "" : " (empty)"); } LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { LIST_FOREACH(hl_item, &dpl->dp.hl_collection, next) if (hl_item->hfile!=dpl->dp.hostlist_auto) { if (hl_item->hfile->filename) DLOG("profile %d include hostlist %s%s\n",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); else DLOG("profile %d include fixed hostlist%s\n",dpl->dp.n, hl_item->hfile->hostlist ? "" : " (empty)"); } LIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next) { if (hl_item->hfile->filename) DLOG("profile %d exclude hostlist %s%s\n",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); else DLOG("profile %d exclude fixed hostlist%s\n",dpl->dp.n,hl_item->hfile->hostlist ? "" : " (empty)"); } if (dpl->dp.hostlist_auto) DLOG("profile %d auto hostlist %s%s\n",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? "" : " (empty)"); } } ================================================ FILE: nfq/hostlist.h ================================================ #pragma once #include #include "pools.h" #include "params.h" bool AppendHostlistItem(hostlist_pool **hostlist, char *s); bool AppendHostList(hostlist_pool **hostlist, const char *filename); bool LoadAllHostLists(); bool NonEmptyHostlist(hostlist_pool **hostlist); // return : true = apply fooling, false = do not apply bool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck); struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename); bool HostlistsReloadCheckForProfile(const struct desync_profile *dp); void HostlistsDebug(); #define ResetAllHostlistsModTime() hostlist_files_reset_modtime(¶ms.hostlists) ================================================ FILE: nfq/ipset.c ================================================ #include #include "ipset.h" #include "gzip.h" #include "helpers.h" // inplace tolower() and add to pool static bool addpool(ipset *ips, char **s, const char *end, int *ct) { char *p, cidr[128]; size_t l; struct cidr4 c4; struct cidr6 c6; // advance until eol for (p=*s; p=sizeof(cidr)) l=sizeof(cidr)-1; memcpy(cidr,*s,l); cidr[l]=0; rtrim(cidr); if (parse_cidr4(cidr,&c4)) { if (!ipset4AddCidr(&ips->ips4, &c4)) { ipsetDestroy(ips); return false; } if (ct) (*ct)++; } else if (parse_cidr6(cidr,&c6)) { if (!ipset6AddCidr(&ips->ips6, &c6)) { ipsetDestroy(ips); return false; } if (ct) (*ct)++; } else DLOG_ERR("bad ip or subnet : %s\n",cidr); } // advance to the next line for (; pfilename) { file_mod_sig fsig; if (!file_mod_signature(hfile->filename, &fsig)) { // stat() error DLOG_PERROR("file_mod_signature"); DLOG_ERR("cannot access ipset file '%s'. in-memory content remains unchanged.\n",hfile->filename); return true; } if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date ipsetDestroy(&hfile->ipset); if (!AppendIpset(&hfile->ipset, hfile->filename)) { ipsetDestroy(&hfile->ipset); return false; } hfile->mod_sig=fsig; } return true; } static bool LoadIpsets(struct ipset_files_head *list) { bool bres=true; struct ipset_file *hfile; LIST_FOREACH(hfile, list, next) { if (!LoadIpset(hfile)) // at least one failed bres=false; } return bres; } bool LoadAllIpsets() { return LoadIpsets(¶ms.ipsets); } static bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6) { char s_ip[40]; bool bInSet=false; if (!!ipv4 != !!ipv6) { *s_ip=0; if (ipv4) { if (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip)); if (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32); } if (ipv6) { if (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip)); if (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128); } DLOG("ipset check for %s : %s\n", s_ip, bInSet ? "positive" : "negative"); } else // ipv4 and ipv6 are both empty or non-empty DLOG("ipset check error !!!!!!!! ipv4=%p ipv6=%p\n",ipv4,ipv6); return bInSet; } static bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets) { struct ipset_item *item; LIST_FOREACH(item, ipsets, next) { if (!LoadIpset(item->hfile)) return false; } return true; } bool IpsetsReloadCheckForProfile(const struct desync_profile *dp) { return IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude); } static 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) { struct ipset_item *item; if (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude)) return false; LIST_FOREACH(item, ips_exclude, next) { DLOG("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return false; } // old behavior compat: all include lists are empty means check passes if (!ipset_collection_is_empty(ips)) { LIST_FOREACH(item, ips, next) { DLOG("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return true; } return false; } return true; } bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) { if (PROFILE_IPSETS_ABSENT(dp)) return true; DLOG("* ipset check for profile %d\n",dp->n); return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6); } static struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename) { struct ipset_file *hfile; if (filename) { if (!(hfile=ipset_files_search(ipsets, filename))) if (!(hfile=ipset_files_add(ipsets, filename))) return NULL; if (!ipset_collection_search(ips_collection, filename)) if (!ipset_collection_add(ips_collection, hfile)) return NULL; } else { if (!(hfile=ipset_files_add(ipsets, NULL))) return NULL; if (!ipset_collection_add(ips_collection, hfile)) return NULL; } return hfile; } struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename) { if (filename && !file_mod_time(filename)) { DLOG_ERR("cannot access ipset file '%s'\n",filename); return NULL; } return RegisterIpset_( ¶ms.ipsets, bExclude ? &dp->ips_collection_exclude : &dp->ips_collection, filename); } static const char *dbg_ipset_fill(const ipset *ips) { if (ips->ips4) if (ips->ips6) return "ipv4+ipv6"; else return "ipv4"; else if (ips->ips6) return "ipv6"; else return "empty"; } void IpsetsDebug() { if (!params.debug) return; struct ipset_file *hfile; struct desync_profile_list *dpl; struct ipset_item *ips_item; LIST_FOREACH(hfile, ¶ms.ipsets, next) { if (hfile->filename) DLOG("ipset file %s (%s)\n",hfile->filename,dbg_ipset_fill(&hfile->ipset)); else DLOG("ipset fixed (%s)\n",dbg_ipset_fill(&hfile->ipset)); } LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { LIST_FOREACH(ips_item, &dpl->dp.ips_collection, next) if (ips_item->hfile->filename) DLOG("profile %d include ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); else DLOG("profile %d include fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); LIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next) if (ips_item->hfile->filename) DLOG("profile %d exclude ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); else DLOG("profile %d exclude fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); } } ================================================ FILE: nfq/ipset.h ================================================ #pragma once #include #include #include "params.h" #include "pools.h" bool LoadAllIpsets(); bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename); void IpsetsDebug(); bool AppendIpsetItem(ipset *ips, char *ip); #define ResetAllIpsetModTime() ipset_files_reset_modtime(¶ms.ipsets) ================================================ FILE: nfq/kavl.h ================================================ /* The MIT License Copyright (c) 2018 by Attractive Chaos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* An example: #include #include #include #include "kavl.h" struct my_node { char key; KAVL_HEAD(struct my_node) head; }; #define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key)) KAVL_INIT(my, struct my_node, head, my_cmp) int main(void) { const char *str = "MNOLKQOPHIA"; // from wiki, except a duplicate struct my_node *root = 0; int i, l = strlen(str); for (i = 0; i < l; ++i) { // insert in the input order struct my_node *q, *p = malloc(sizeof(*p)); p->key = str[i]; q = kavl_insert(my, &root, p, 0); if (p != q) free(p); // if already present, free } kavl_itr_t(my) itr; kavl_itr_first(my, root, &itr); // place at first do { // traverse const struct my_node *p = kavl_at(&itr); putchar(p->key); free((void*)p); // free node } while (kavl_itr_next(my, &itr)); putchar('\n'); return 0; } */ #ifndef KAVL_H #define KAVL_H #ifdef __STRICT_ANSI__ #define inline __inline__ #endif #define KAVL_MAX_DEPTH 64 #define kavl_size(head, p) ((p)? (p)->head.size : 0) #define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0) #define KAVL_HEAD(__type) \ struct { \ __type *p[2]; \ signed char balance; /* balance factor */ \ unsigned size; /* #elements in subtree */ \ } #define __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \ const __type *p = root; \ unsigned cnt = 0; \ while (p != 0) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ if (cmp < 0) p = p->__head.p[0]; \ else if (cmp > 0) p = p->__head.p[1]; \ else break; \ } \ if (cnt_) *cnt_ = cnt; \ return (__type*)p; \ } #define __KAVL_ROTATE(suf, __type, __head) \ /* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \ static inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \ int opp = 1 - dir; /* opposite direction */ \ __type *q = p->__head.p[opp]; \ unsigned size_p = p->__head.size; \ p->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \ q->__head.size = size_p; \ p->__head.p[opp] = q->__head.p[dir]; \ q->__head.p[dir] = p; \ return q; \ } \ /* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \ static inline __type *kavl_rotate2_##suf(__type *p, int dir) { \ int b1, opp = 1 - dir; \ __type *q = p->__head.p[opp], *r = q->__head.p[dir]; \ unsigned size_x_dir = kavl_size_child(__head, r, dir); \ r->__head.size = p->__head.size; \ p->__head.size -= q->__head.size - size_x_dir; \ q->__head.size -= size_x_dir + 1; \ p->__head.p[opp] = r->__head.p[dir]; \ r->__head.p[dir] = p; \ q->__head.p[dir] = r->__head.p[opp]; \ r->__head.p[opp] = q; \ b1 = dir == 0? +1 : -1; \ if (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \ else if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \ else q->__head.balance = b1, p->__head.balance = 0; \ r->__head.balance = 0; \ return r; \ } #define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \ unsigned char stack[KAVL_MAX_DEPTH]; \ __type *path[KAVL_MAX_DEPTH]; \ __type *bp, *bq; \ __type *p, *q, *r = 0; /* _r_ is potentially the new root */ \ int i, which = 0, top, b1, path_len; \ unsigned cnt = 0; \ bp = *root_, bq = 0; \ /* find the insertion location */ \ for (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ if (cmp == 0) { \ if (cnt_) *cnt_ = cnt; \ return p; \ } \ if (p->__head.balance != 0) \ bq = q, bp = p, top = 0; \ stack[top++] = which = (cmp > 0); \ path[path_len++] = p; \ } \ if (cnt_) *cnt_ = cnt; \ x->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \ if (q == 0) *root_ = x; \ else q->__head.p[which] = x; \ if (bp == 0) return x; \ for (i = 0; i < path_len; ++i) ++path[i]->__head.size; \ for (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \ if (stack[top] == 0) --p->__head.balance; \ else ++p->__head.balance; \ if (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \ /* re-balance */ \ which = (bp->__head.balance < 0); \ b1 = which == 0? +1 : -1; \ q = bp->__head.p[1 - which]; \ if (q->__head.balance == b1) { \ r = kavl_rotate1_##suf(bp, which); \ q->__head.balance = bp->__head.balance = 0; \ } else r = kavl_rotate2_##suf(bp, which); \ if (bq == 0) *root_ = r; \ else bq->__head.p[bp != bq->__head.p[0]] = r; \ return x; \ } #define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \ __type *p, *path[KAVL_MAX_DEPTH], fake; \ unsigned char dir[KAVL_MAX_DEPTH]; \ int i, d = 0, cmp; \ unsigned cnt = 0; \ fake.__head.p[0] = *root_, fake.__head.p[1] = 0; \ if (cnt_) *cnt_ = 0; \ if (x) { \ for (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \ int which = (cmp > 0); \ if (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \ dir[d] = which; \ path[d++] = p; \ p = p->__head.p[which]; \ if (p == 0) { \ if (cnt_) *cnt_ = 0; \ return 0; \ } \ } \ cnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \ } else { \ for (p = &fake, cnt = 1; p; p = p->__head.p[0]) \ dir[d] = 0, path[d++] = p; \ p = path[--d]; \ } \ if (cnt_) *cnt_ = cnt; \ for (i = 1; i < d; ++i) --path[i]->__head.size; \ if (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \ path[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \ } else { \ __type *q = p->__head.p[1]; \ if (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \ q->__head.p[0] = p->__head.p[0]; \ q->__head.balance = p->__head.balance; \ path[d-1]->__head.p[dir[d-1]] = q; \ path[d] = q, dir[d++] = 1; \ q->__head.size = p->__head.size - 1; \ } else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \ __type *r; \ int e = d++; /* backup _d_ */\ for (;;) { \ dir[d] = 0; \ path[d++] = q; \ r = q->__head.p[0]; \ if (r->__head.p[0] == 0) break; \ q = r; \ } \ r->__head.p[0] = p->__head.p[0]; \ q->__head.p[0] = r->__head.p[1]; \ r->__head.p[1] = p->__head.p[1]; \ r->__head.balance = p->__head.balance; \ path[e-1]->__head.p[dir[e-1]] = r; \ path[e] = r, dir[e] = 1; \ for (i = e + 1; i < d; ++i) --path[i]->__head.size; \ r->__head.size = p->__head.size - 1; \ } \ } \ while (--d > 0) { \ __type *q = path[d]; \ int which, other, b1 = 1, b2 = 2; \ which = dir[d], other = 1 - which; \ if (which) b1 = -b1, b2 = -b2; \ q->__head.balance += b1; \ if (q->__head.balance == b1) break; \ else if (q->__head.balance == b2) { \ __type *r = q->__head.p[other]; \ if (r->__head.balance == -b1) { \ path[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \ } else { \ path[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \ if (r->__head.balance == 0) { \ r->__head.balance = -b1; \ q->__head.balance = b1; \ break; \ } else r->__head.balance = q->__head.balance = 0; \ } \ } \ } \ *root_ = fake.__head.p[0]; \ return p; \ } #define kavl_free(__type, __head, __root, __free) do { \ __type *_p, *_q; \ for (_p = __root; _p; _p = _q) { \ if (_p->__head.p[0] == 0) { \ _q = _p->__head.p[1]; \ __free(_p); \ } else { \ _q = _p->__head.p[0]; \ _p->__head.p[0] = _q->__head.p[1]; \ _q->__head.p[1] = _p; \ } \ } \ } while (0) #define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \ struct kavl_itr_##suf { \ const __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \ }; \ __scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \ const __type *p; \ for (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \ *++itr->top = p; \ itr->right = (*itr->top)->__head.p[1]; \ } \ __scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \ const __type *p = root; \ itr->top = itr->stack - 1; \ while (p != 0) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \ else if (cmp > 0) p = p->__head.p[1]; \ else break; \ } \ if (p) { \ *++itr->top = p; \ itr->right = p->__head.p[1]; \ return 1; \ } else if (itr->top >= itr->stack) { \ itr->right = (*itr->top)->__head.p[1]; \ return 0; \ } else return 0; \ } \ __scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \ for (;;) { \ const __type *p; \ for (p = itr->right, --itr->top; p; p = p->__head.p[0]) \ *++itr->top = p; \ if (itr->top < itr->stack) return 0; \ itr->right = (*itr->top)->__head.p[1]; \ return 1; \ } \ } /** * Insert a node to the tree * * @param suf name suffix used in KAVL_INIT() * @param proot pointer to the root of the tree (in/out: root may change) * @param x node to insert (in) * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) * * @return _x_ if not present in the tree, or the node equal to x. */ #define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt) /** * Find a node in the tree * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param x node value to find (in) * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) * * @return node equal to _x_ if present, or NULL if absent */ #define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt) /** * Delete a node from the tree * * @param suf name suffix used in KAVL_INIT() * @param proot pointer to the root of the tree (in/out: root may change) * @param x node value to delete; if NULL, delete the first node (in) * * @return node removed from the tree if present, or NULL if absent */ #define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt) #define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0) #define kavl_itr_t(suf) struct kavl_itr_##suf /** * Place the iterator at the smallest object * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param itr iterator */ #define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr) /** * Place the iterator at the object equal to or greater than the query * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param x query (in) * @param itr iterator (out) * * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is * larger than all objects in the tree */ #define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr) /** * Move to the next object in order * * @param itr iterator (modified) * * @return 1 if there is a next object; 0 otherwise */ #define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr) /** * Return the pointer at the iterator * * @param itr iterator * * @return pointer if present; NULL otherwise */ #define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top) #define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \ __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ __KAVL_ROTATE(suf, __type, __head) \ __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ __KAVL_ITR(suf, __scope, __type, __head, __cmp) #define KAVL_INIT(suf, __type, __head, __cmp) \ KAVL_INIT2(suf,, __type, __head, __cmp) #endif ================================================ FILE: nfq/nfqws.c ================================================ #define _GNU_SOURCE #include "nfqws.h" #include "sec.h" #include "desync.h" #include "helpers.h" #include "checksum.h" #include "params.h" #include "protocol.h" #include "hostlist.h" #include "ipset.h" #include "gzip.h" #include "pools.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ #include "win.h" #endif #ifdef USE_SYSTEMD #include #endif #ifdef __linux__ #include #define NF_DROP 0 #define NF_ACCEPT 1 #endif #define CTRACK_T_SYN 60 #define CTRACK_T_FIN 60 #define CTRACK_T_EST 300 #define CTRACK_T_UDP 60 #define MAX_CONFIG_FILE_SIZE 16384 struct params_s params; static bool bReload = false; #ifdef __CYGWIN__ bool bQuit = false; #endif static void onhup(int sig) { printf("HUP received ! Lists will be reloaded.\n"); bReload = true; } static void ReloadCheck() { if (bReload) { ResetAllHostlistsModTime(); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed. this is fatal.\n"); exit(1); } ResetAllIpsetModTime(); if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed. this is fatal.\n"); exit(1); } bReload = false; } } static void onusr1(int sig) { printf("\nCONNTRACK DUMP\n"); ConntrackPoolDump(¶ms.conntrack); printf("\n"); } static void onusr2(int sig) { printf("\nHOSTFAIL POOL DUMP\n"); struct desync_profile_list *dpl; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { printf("\nDESYNC PROFILE %d\n", dpl->dp.n); HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); } if (params.autottl_present || params.cache_hostname) { printf("\nIPCACHE\n"); ipcachePrint(¶ms.ipcache); } printf("\n"); } static void pre_desync(void) { signal(SIGHUP, onhup); signal(SIGUSR1, onusr1); signal(SIGUSR2, onusr2); } static uint8_t processPacketData(uint32_t *mark, const char *ifin, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) { #ifdef __linux__ if (*mark & params.desync_fwmark) { DLOG("ignoring generated packet\n"); return VERDICT_PASS; } #endif return dpi_desync_packet(*mark, ifin, ifout, data_pkt, len_pkt); } static bool test_list_files() { struct hostlist_file *hfile; struct ipset_file *ifile; LIST_FOREACH(hfile, ¶ms.hostlists, next) if (hfile->filename && !file_open_test(hfile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access hostlist file '%s'\n", hfile->filename); return false; } LIST_FOREACH(ifile, ¶ms.ipsets, next) if (ifile->filename && !file_open_test(ifile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access ipset file '%s'\n", ifile->filename); return false; } return true; } #ifdef __linux__ static int nfq_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie) { int id, ilen; size_t len; struct nfqnl_msg_packet_hdr *ph; uint8_t *data; uint32_t ifidx_out, ifidx_in; char ifout[IFNAMSIZ], ifin[IFNAMSIZ]; ph = nfq_get_msg_packet_hdr(nfa); id = ph ? ntohl(ph->packet_id) : 0; uint32_t mark = nfq_get_nfmark(nfa); ilen = nfq_get_payload(nfa, &data); ifidx_out = nfq_get_outdev(nfa); *ifout = 0; if (ifidx_out) if_indextoname(ifidx_out, ifout); ifidx_in = nfq_get_indev(nfa); *ifin = 0; if (ifidx_in) if_indextoname(ifidx_in, ifin); DLOG("\npacket: id=%d len=%d mark=%08X ifin=%s(%u) ifout=%s(%u)\n", id, ilen, mark, ifin, ifidx_in, ifout, ifidx_out); if (ilen >= 0) { len = ilen; uint8_t verdict = processPacketData(&mark, ifin, ifout, data, &len); switch (verdict & VERDICT_MASK) { case VERDICT_MODIFY: DLOG("packet: id=%d pass modified. len=%zu\n", id, len); return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)len, data); case VERDICT_DROP: DLOG("packet: id=%d drop\n", id); return nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL); } } DLOG("packet: id=%d pass unmodified\n", id); return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); } static void nfq_deinit(struct nfq_handle **h, struct nfq_q_handle **qh) { if (*qh) { DLOG_CONDUP("unbinding from queue %u\n", params.qnum); nfq_destroy_queue(*qh); *qh = NULL; } if (*h) { DLOG_CONDUP("closing library handle\n"); nfq_close(*h); *h = NULL; } } static bool nfq_init(struct nfq_handle **h, struct nfq_q_handle **qh) { nfq_deinit(h, qh); DLOG_CONDUP("opening library handle\n"); *h = nfq_open(); if (!*h) { DLOG_PERROR("nfq_open()"); goto exiterr; } DLOG_CONDUP("unbinding existing nf_queue handler for AF_INET (if any)\n"); if (nfq_unbind_pf(*h, AF_INET) < 0) { DLOG_PERROR("nfq_unbind_pf()"); goto exiterr; } DLOG_CONDUP("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); if (nfq_bind_pf(*h, AF_INET) < 0) { DLOG_PERROR("nfq_bind_pf()"); goto exiterr; } DLOG_CONDUP("binding this socket to queue '%u'\n", params.qnum); *qh = nfq_create_queue(*h, params.qnum, &nfq_cb, ¶ms); if (!*qh) { DLOG_PERROR("nfq_create_queue()"); goto exiterr; } DLOG_CONDUP("setting copy_packet mode\n"); if (nfq_set_mode(*qh, NFQNL_COPY_PACKET, 0xffff) < 0) { DLOG_PERROR("can't set packet_copy mode"); goto exiterr; } if (nfq_set_queue_maxlen(*qh, Q_MAXLEN) < 0) { DLOG_PERROR("can't set queue maxlen"); goto exiterr; } // accept packets if they cant be handled if (nfq_set_queue_flags(*qh, NFQA_CFG_F_FAIL_OPEN, NFQA_CFG_F_FAIL_OPEN)) { DLOG_ERR("can't set queue flags. its OK on linux <3.6\n"); // dot not fail. not supported on old linuxes <3.6 } nfnl_rcvbufsiz(nfq_nfnlh(*h), Q_RCVBUF); DLOG_CONDUP("initializing raw sockets bind-fix4=%u bind-fix6=%u\n", params.bind_fix4, params.bind_fix6); if (!rawsend_preinit(params.bind_fix4, params.bind_fix6)) goto exiterr; int yes = 1, fd = nfq_fd(*h); #if defined SOL_NETLINK && defined NETLINK_NO_ENOBUFS if (setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &yes, sizeof(yes)) == -1) DLOG_PERROR("setsockopt(NETLINK_NO_ENOBUFS)"); #endif return true; exiterr: nfq_deinit(h, qh); return false; } static void notify_ready(void) { #ifdef USE_SYSTEMD int r = sd_notify(0, "READY=1"); if (r < 0) DLOG_ERR("sd_notify: %s\n", strerror(-r)); #endif } static int nfq_main(void) { uint8_t buf[16384] __attribute__((aligned)); struct nfq_handle *h = NULL; struct nfq_q_handle *qh = NULL; int fd, e; ssize_t rd; FILE *Fpid = NULL; if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) { DLOG_PERROR("create pidfile"); return 1; } if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count) || !dropcaps()) goto err; print_id(); if (params.droproot && !test_list_files()) goto err; if (!nfq_init(&h, &qh)) goto err; #ifdef HAS_FILTER_SSID if (params.filter_ssid_present) { if (!wlan_info_init()) { DLOG_ERR("cannot initialize wlan info capture\n"); goto err; } DLOG("wlan info capture initialized\n"); } #endif if (params.daemon) daemonize(); sec_harden(); if (Fpid) { if (fprintf(Fpid, "%d", getpid()) <= 0) { DLOG_PERROR("write pidfile"); goto err; } fclose(Fpid); Fpid = NULL; } pre_desync(); notify_ready(); fd = nfq_fd(h); do { while ((rd = recv(fd, buf, sizeof(buf), 0)) >= 0) { ReloadCheck(); #ifdef HAS_FILTER_SSID if (params.filter_ssid_present) if (!wlan_info_get_rate_limited()) DLOG_ERR("cannot get wlan info\n"); #endif if (rd) { int r = nfq_handle_packet(h, (char *)buf, (int)rd); if (r<0) DLOG_ERR("nfq_handle_packet result %d, errno %d : %s\n", r, errno, strerror(errno)); } else DLOG("recv from nfq returned 0 !\n"); } e = errno; DLOG_ERR("recv: recv=%zd errno %d\n", rd, e); errno = e; DLOG_PERROR("recv"); // do not fail on ENOBUFS } while (e == ENOBUFS); nfq_deinit(&h, &qh); #ifdef HAS_FILTER_SSID wlan_info_deinit(); #endif return 0; err: if (Fpid) fclose(Fpid); nfq_deinit(&h, &qh); #ifdef HAS_FILTER_SSID wlan_info_deinit(); #endif return 1; } #elif defined(BSD) static int dvt_main(void) { uint8_t buf[16384] __attribute__((aligned)); struct sockaddr_storage sa_from; int fd[2] = { -1,-1 }; // 4,6 int i, r, res = 1, fdct = 1, fdmax; unsigned int id = 0; socklen_t socklen; ssize_t rd, wr; fd_set fdset; FILE *Fpid = NULL; if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) { DLOG_PERROR("create pidfile"); return 1; } { struct sockaddr_in bp4; bp4.sin_family = AF_INET; bp4.sin_port = htons(params.port); bp4.sin_addr.s_addr = INADDR_ANY; DLOG_CONDUP("creating divert4 socket\n"); fd[0] = socket_divert(AF_INET); if (fd[0] == -1) { DLOG_PERROR("socket (DIVERT4)"); goto exiterr; } DLOG_CONDUP("binding divert4 socket\n"); if (bind(fd[0], (struct sockaddr*)&bp4, sizeof(bp4)) < 0) { DLOG_PERROR("bind (DIVERT4)"); goto exiterr; } } #ifdef __OpenBSD__ { // in OpenBSD must use separate divert sockets for ipv4 and ipv6 struct sockaddr_in6 bp6; memset(&bp6, 0, sizeof(bp6)); bp6.sin6_family = AF_INET6; bp6.sin6_port = htons(params.port); DLOG_CONDUP("creating divert6 socket\n"); fd[1] = socket_divert(AF_INET6); if (fd[1] == -1) { DLOG_PERROR("socket (DIVERT6)"); goto exiterr; } DLOG_CONDUP("binding divert6 socket\n"); if (bind(fd[1], (struct sockaddr*)&bp6, sizeof(bp6)) < 0) { DLOG_PERROR("bind (DIVERT6)"); goto exiterr; } fdct++; } #endif fdmax = (fd[0] > fd[1] ? fd[0] : fd[1]) + 1; DLOG_CONDUP("initializing raw sockets\n"); if (!rawsend_preinit(false, false)) goto exiterr; if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count)) goto exiterr; print_id(); if (params.droproot && !test_list_files()) goto exiterr; if (params.daemon) daemonize(); if (Fpid) { if (fprintf(Fpid, "%d", getpid()) <= 0) { DLOG_PERROR("write pidfile"); goto exiterr; } fclose(Fpid); Fpid = NULL; } pre_desync(); for (;;) { FD_ZERO(&fdset); for (i = 0; i < fdct; i++) FD_SET(fd[i], &fdset); r = select(fdmax, &fdset, NULL, NULL, NULL); if (r == -1) { if (errno == EINTR) { // a signal received continue; } DLOG_PERROR("select"); goto exiterr; } for (i = 0; i < fdct; i++) { if (FD_ISSET(fd[i], &fdset)) { socklen = sizeof(sa_from); rd = recvfrom(fd[i], buf, sizeof(buf), 0, (struct sockaddr*)&sa_from, &socklen); if (rd < 0) { DLOG_PERROR("recvfrom"); goto exiterr; } else if (rd > 0) { uint32_t mark = 0; uint8_t verdict; size_t len = rd; ReloadCheck(); DLOG("\npacket: id=%u len=%zu\n", id, len); verdict = processPacketData(&mark, NULL, NULL, buf, &len); switch (verdict & VERDICT_MASK) { case VERDICT_PASS: case VERDICT_MODIFY: if ((verdict & VERDICT_MASK) == VERDICT_PASS) DLOG("packet: id=%u reinject unmodified\n", id); else DLOG("packet: id=%u reinject modified len=%zu\n", id, len); wr = sendto(fd[i], buf, len, 0, (struct sockaddr*)&sa_from, socklen); if (wr < 0) DLOG_PERROR("reinject sendto"); else if (wr != len) DLOG_ERR("reinject sendto: not all data was reinjected. received %zu, sent %zd\n", len, wr); break; default: DLOG("packet: id=%u drop\n", id); } id++; } else { DLOG("unexpected zero size recvfrom\n"); } } } } res = 0; exiterr: if (Fpid) fclose(Fpid); if (fd[0] != -1) close(fd[0]); if (fd[1] != -1) close(fd[1]); return res; } #elif defined (__CYGWIN__) static int win_main(const char *windivert_filter) { size_t len; unsigned int id; uint8_t verdict; bool bOutbound; uint8_t packet[16384]; uint32_t mark; WINDIVERT_ADDRESS wa; char ifname[IFNAMSIZ]; if (params.daemon) daemonize(); if (*params.pidfile && !writepid(params.pidfile)) { DLOG_ERR("could not write pidfile"); return ERROR_TOO_MANY_OPEN_FILES; // code 4 = The system cannot open the file } if (!win_dark_init(¶ms.ssid_filter, ¶ms.nlm_filter)) { DLOG_ERR("win_dark_init failed. win32 error %u (0x%08X)\n", w_win32_error, w_win32_error); return w_win32_error; } pre_desync(); for (;;) { if (!logical_net_filter_match()) { DLOG_CONDUP("logical network is not present. waiting it to appear.\n"); do { if (bQuit) { DLOG("QUIT requested\n"); win_dark_deinit(); return 0; } usleep(500000); } while (!logical_net_filter_match()); DLOG_CONDUP("logical network now present\n"); } if (!windivert_init(windivert_filter)) { win_dark_deinit(); return w_win32_error; } DLOG_CONDUP("windivert initialized. capture is started.\n"); for (id = 0;; id++) { len = sizeof(packet); if (!windivert_recv(packet, &len, &wa)) { if (errno == ENOBUFS) { DLOG("windivert: ignoring too large packet\n"); continue; // too large packet } else if (errno == ENODEV) { DLOG_CONDUP("logical network disappeared. deinitializing windivert.\n"); rawsend_cleanup(); break; } else if (errno == EINTR) { DLOG("QUIT requested\n"); win_dark_deinit(); return 0; } DLOG_ERR("windivert: recv failed. errno %d\n", errno); win_dark_deinit(); return w_win32_error; } ReloadCheck(); *ifname = 0; snprintf(ifname, sizeof(ifname), "%u.%u", wa.Network.IfIdx, wa.Network.SubIfIdx); DLOG("\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); if (wa.Impostor) { DLOG("windivert: passing impostor packet\n"); verdict = VERDICT_PASS; } else if (wa.Loopback) { DLOG("windivert: passing loopback packet\n"); verdict = VERDICT_PASS; } else { mark = 0; // pseudo interface id IfIdx.SubIfIdx verdict = processPacketData(&mark, ifname, ifname, packet, &len); } switch (verdict & VERDICT_MASK) { case VERDICT_PASS: case VERDICT_MODIFY: if ((verdict & VERDICT_MASK) == VERDICT_PASS) DLOG("packet: id=%u reinject unmodified\n", id); else DLOG("packet: id=%u reinject modified len=%zu\n", id, len); if (!windivert_send(packet, len, &wa)) DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); break; default: DLOG("packet: id=%u drop\n", id); } } } win_dark_deinit(); return 0; } #endif // multiple OS divert handlers static void exit_clean(int code) { cleanup_params(¶ms); exit(code); } static bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids) { unsigned int u; char c, *p, *e; *gid_count = 0; if ((e = strchr(optarg, ':'))) *e++ = 0; if (sscanf(opt, "%u", &u) != 1) return false; *uid = (uid_t)u; for (p = e; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (p) { if (sscanf(p, "%u", &u) != 1) return false; if (*gid_count >= max_gids) return false; gid[(*gid_count)++] = (gid_t)u; } if (e) *e++ = c; p = e; } return true; } static bool parse_ws_scale_factor(char *s, uint16_t *wsize, uint8_t *wscale) { int v; char *p; if ((p = strchr(s, ':'))) *p++ = 0; v = atoi(s); if (v < 0 || v>65535) { DLOG_ERR("bad wsize\n"); return false; } *wsize = (uint16_t)v; if (p && *p) { v = atoi(p); if (v < 0 || v>255) { DLOG_ERR("bad wscale\n"); return false; } *wscale = (uint8_t)v; } return true; } static bool parse_cutoff(const char *opt, unsigned int *value, char *mode) { *mode = (*opt == 'n' || *opt == 'd' || *opt == 's') ? *opt++ : 'n'; return sscanf(opt, "%u", value) > 0; } static bool parse_net32_signed(const char *opt, uint32_t *value) { if (((opt[0] == '0' && opt[1] == 'x') || (opt[0] == '-' && opt[1] == '0' && opt[2] == 'x')) && sscanf(opt + 2 + (opt[0] == '-'), "%X", (int32_t*)value) > 0) { if (opt[0] == '-') *value = -*value; return true; } else { return sscanf(opt, "%d", (int32_t*)value) > 0; } } static void load_file_or_exit(const char *filename, void *buf, size_t *size, size_t *offset) { size_t ofs; // 0xaabbcc // filename // @filename // +123@filename if (offset) *offset = 0; if (filename[0] == '0' && filename[1] == 'x') { if (!parse_hex_str(filename + 2, buf, size) || !*size) { DLOG_ERR("invalid hex string: %s\n", filename + 2); exit_clean(1); } DLOG("read %zu bytes from hex string\n", *size); } else { ofs = 0; if (filename[0] == '+') { filename++; if (sscanf(filename, "%zu", &ofs) != 1) { DLOG("offset read error: %s\n", filename); exit_clean(1); } while (*filename && *filename != '@') filename++; if (*filename == '@') filename++; } else if (filename[0] == '@') filename++; if (!load_file_nonempty(filename, buf, size)) { DLOG_ERR("could not read %s\n", filename); exit_clean(1); } DLOG("read %zu bytes from '%s'. offset=%zu\n", *size, filename, ofs); if (ofs >= *size) { DLOG("'%s' : offset %zu is out of data range %zu\n", filename, ofs, *size); exit_clean(1); } if (offset) *offset = ofs; else memmove(buf, (uint8_t*)buf + ofs, *size -= ofs); } } static bool parse_autottl(const char *s, autottl *t, int8_t def_delta, uint8_t def_min, uint8_t def_max) { bool neg = true; unsigned int delta, min, max; t->delta = def_delta; t->min = def_min; t->max = def_max; if (s) { // "-" means disable if (s[0] == '-' && s[1] == 0) memset(t, 0, sizeof(*t)); else { max = t->max; if (*s == '+') { neg = false; s++; } else if (*s == '-') s++; switch (sscanf(s, "%u:%u-%u", &delta, &min, &max)) { case 3: if ((delta && !max) || max > 255) return false; t->max = (uint8_t)max; case 2: if ((delta && !min) || min > 255 || min > max) return false; t->min = (uint8_t)min; case 1: if (delta > 127) return false; t->delta = (int8_t)(neg ? -delta : delta); break; default: return false; } } } return true; } static bool parse_l7_list(char *opt, uint32_t *l7) { char *e, *p, c; for (p = opt, *l7 = 0; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!strcmp(p, "http")) *l7 |= L7_PROTO_HTTP; else if (!strcmp(p, "tls")) *l7 |= L7_PROTO_TLS; else if (!strcmp(p, "quic")) *l7 |= L7_PROTO_QUIC; else if (!strcmp(p, "wireguard")) *l7 |= L7_PROTO_WIREGUARD; else if (!strcmp(p, "dht")) *l7 |= L7_PROTO_DHT; else if (!strcmp(p, "discord")) *l7 |= L7_PROTO_DISCORD; else if (!strcmp(p, "stun")) *l7 |= L7_PROTO_STUN; else if (!strcmp(p, "unknown")) *l7 |= L7_PROTO_UNKNOWN; else return false; if (e) *e++ = c; p = e; } return true; } static bool parse_pf_list(char *opt, struct port_filters_head *pfl) { char *e, *p, c; port_filter pf; bool b; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } b = pf_parse(p, &pf) && port_filter_add(pfl, &pf); if (e) *e++ = c; if (!b) return false; p = e; } return true; } static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) { char *e, *p, c; for (p = opt, *ipv4 = *ipv6 = false; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!strcmp(p, "ipv4")) *ipv4 = true; else if (!strcmp(p, "ipv6")) *ipv6 = true; else return false; if (e) *e++ = c; p = e; } return true; } static bool parse_httpreqpos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "method")) { sp->marker = PM_HTTP_METHOD; sp->pos = 2; } else if (!strcmp(s, "host")) { sp->marker = PM_HOST; sp->pos = 1; } else return false; return true; } static bool parse_tlspos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "sni")) { sp->marker = PM_HOST; sp->pos = 1; } else if (!strcmp(s, "sniext")) { sp->marker = PM_SNI_EXT; sp->pos = 1; } else if (!strcmp(s, "snisld")) { sp->marker = PM_HOST_MIDSLD; sp->pos = 0; } else return false; return true; } static bool parse_int16(const char *p, int16_t *v) { if (*p == '+' || *p == '-' || *p >= '0' && *p <= '9') { int i = atoi(p); *v = (int16_t)i; return *v == i; // check overflow } return false; } static bool parse_posmarker(const char *opt, uint8_t *posmarker) { if (!strcmp(opt, "host")) *posmarker = PM_HOST; else if (!strcmp(opt, "endhost")) *posmarker = PM_HOST_END; else if (!strcmp(opt, "sld")) *posmarker = PM_HOST_SLD; else if (!strcmp(opt, "midsld")) *posmarker = PM_HOST_MIDSLD; else if (!strcmp(opt, "endsld")) *posmarker = PM_HOST_ENDSLD; else if (!strcmp(opt, "method")) *posmarker = PM_HTTP_METHOD; else if (!strcmp(opt, "sniext")) *posmarker = PM_SNI_EXT; else return false; return true; } static bool parse_split_pos(char *opt, struct proto_pos *split) { if (parse_int16(opt, &split->pos)) { split->marker = PM_ABS; return !!split->pos; } else { char c, *p = opt; bool b; for (; *opt && *opt != '+' && *opt != '-'; opt++); c = *opt; *opt = 0; b = parse_posmarker(p, &split->marker); *opt = c; if (!b) return false; if (*opt) return parse_int16(opt, &split->pos); else split->pos = 0; } return true; } static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count) { char c, *e, *p; for (p = opt, *split_count = 0; p && *split_count < splits_size; (*split_count)++) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!parse_split_pos(p, splits + *split_count)) return false; if (e) *e++ = c; p = e; } if (p) return false; // too much splits return true; } static bool parse_domain_list(char *opt, hostlist_pool **pp) { char *e, *p, c; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (*p && !AppendHostlistItem(pp, p)) return false; if (e) *e++ = c; p = e; } return true; } static bool parse_ip_list(char *opt, ipset *pp) { char *e, *p, c; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (*p && !AppendIpsetItem(pp, p)) return false; if (e) *e++ = c; p = e; } return true; } static bool parse_tlsmod_list(char *opt, struct fake_tls_mod *tls_mod) { char *e, *e2, *p, c, c2; tls_mod->mod &= FAKE_TLS_MOD_SAVE_MASK; tls_mod->mod |= FAKE_TLS_MOD_SET; for (p = opt; p; ) { for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); if ((e = strchr(e2, ','))) { c = *e; *e = 0; } if (*e2 == '=') { c2 = *e2; *e2 = 0; } else e2 = NULL; if (!strcmp(p, "rnd")) tls_mod->mod |= FAKE_TLS_MOD_RND; else if (!strcmp(p, "rndsni")) tls_mod->mod |= FAKE_TLS_MOD_RND_SNI; else if (!strcmp(p, "sni")) { tls_mod->mod |= FAKE_TLS_MOD_SNI; if (!e2 || !e2[1] || e2[1] == ',') goto err; strncpy(tls_mod->sni, e2 + 1, sizeof(tls_mod->sni) - 1); tls_mod->sni[sizeof(tls_mod->sni) - 1 - 1] = 0; } else if (!strcmp(p, "padencap")) tls_mod->mod |= FAKE_TLS_MOD_PADENCAP; else if (!strcmp(p, "dupsid")) tls_mod->mod |= FAKE_TLS_MOD_DUP_SID; else if (strcmp(p, "none")) goto err; if (e2) *e2 = c2; if (e) *e++ = c; p = e; } return true; err: if (e2) *e2 = c2; if (e) *e++ = c; return false; } static bool parse_hostfakesplit_mod(char *opt, struct hostfakesplit_mod *hfs_mod) { char *e, *e2, *p, c, c2; for (p = opt; p; ) { for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); if ((e = strchr(e2, ','))) { c = *e; *e = 0; } if (*e2 == '=') { c2 = *e2; *e2 = 0; } else e2 = NULL; if (!strcmp(p, "host")) { if (!e2 || !e2[1] || e2[1] == ',') goto err; strncpy(hfs_mod->host, e2 + 1, sizeof(hfs_mod->host) - 1); hfs_mod->host[sizeof(hfs_mod->host) - 1 - 1] = 0; hfs_mod->host_size = strlen(hfs_mod->host); // cache value } else if (!strcmp(p, "altorder")) { if (!e2 || !e2[1] || e2[1] == ',') goto err; hfs_mod->ordering = atoi(e2 + 1); if (hfs_mod->ordering < 0 || hfs_mod->ordering>1) goto err; } else if (strcmp(p, "none")) goto err; if (e2) *e2 = c2; if (e) *e++ = c; p = e; } return true; err: if (e2) *e2 = c2; if (e) *e++ = c; return false; } static bool parse_fakedsplit_mod(char *opt, struct fakedsplit_mod *fs_mod) { char *e, *e2, *p, c, c2; for (p = opt; p; ) { for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); if ((e = strchr(e2, ','))) { c = *e; *e = 0; } if (*e2 == '=') { c2 = *e2; *e2 = 0; } else e2 = NULL; if (!strcmp(p, "altorder")) { if (!e2 || !e2[1] || e2[1] == ',') goto err; fs_mod->ordering = atoi(e2 + 1); // unsplitted altorder in 0x03 mask. 0x04 reserved. splitted altorder in 0x18 mask if ((fs_mod->ordering & 0xFFFFFFE4) || (((fs_mod->ordering>>3) & 3)>2)) goto err; } else if (strcmp(p, "none")) goto err; if (e2) *e2 = c2; if (e) *e++ = c; p = e; } return true; err: if (e2) *e2 = c2; if (e) *e++ = c; return false; } static bool parse_tcpmod(char *opt, struct tcp_mod *tcp_mod) { char *e, *e2, *p, c, c2; for (p = opt; p; ) { for (e2 = p; *e2 && *e2 != ',' && *e2 != '='; e2++); if ((e = strchr(e2, ','))) { c = *e; *e = 0; } if (*e2 == '=') { c2 = *e2; *e2 = 0; } else e2 = NULL; if (!strcmp(p, "seq")) { tcp_mod->seq = true; } else if (strcmp(p, "none")) goto err; if (e2) *e2 = c2; if (e) *e++ = c; p = e; } return true; err: if (e2) *e2 = c2; if (e) *e++ = c; return false; } static bool parse_fooling(char *opt, unsigned int *fooling_mode) { char *e, *p = opt; while (p) { e = strchr(p, ','); if (e) *e++ = 0; if (!strcmp(p, "md5sig")) *fooling_mode |= FOOL_MD5SIG; else if (!strcmp(p, "ts")) *fooling_mode |= FOOL_TS; else if (!strcmp(p, "badsum")) *fooling_mode |= FOOL_BADSUM; else if (!strcmp(p, "badseq")) *fooling_mode |= FOOL_BADSEQ; else if (!strcmp(p, "datanoack")) *fooling_mode |= FOOL_DATANOACK; else if (!strcmp(p, "hopbyhop")) *fooling_mode |= FOOL_HOPBYHOP; else if (!strcmp(p, "hopbyhop2")) *fooling_mode |= FOOL_HOPBYHOP2; else if (strcmp(p, "none")) return false; p = e; } return true; } static bool parse_strlist(char *opt, struct str_list_head *list) { char *e, *p = optarg; while (p) { e = strchr(p, ','); if (e) *e++ = 0; if (*p && !strlist_add(list, p)) return false; p = e; } return true; } static bool parse_tcpflags(char *opt, uint16_t *fl) { unsigned int u; char *e, *p, c; if (sscanf(optarg, "0x%X", &u)<=0 && sscanf(optarg, "%u", &u)<=0) { *fl=0; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!strcasecmp(p, "FIN")) *fl |= TH_FIN; else if (!strcasecmp(p, "SYN")) *fl |= TH_SYN; else if (!strcasecmp(p, "RST")) *fl |= TH_RST; else if (!strcasecmp(p, "PSH") || !strcasecmp(p, "PUSH")) *fl |= TH_PUSH; else if (!strcasecmp(p, "ACK")) *fl |= TH_ACK; else if (!strcasecmp(p, "URG")) *fl |= TH_URG; else if (!strcasecmp(p, "ECE")) *fl |= 0x40; else if (!strcasecmp(p, "CWR")) *fl |= 0x80; else if (!strcasecmp(p, "AE") || !strcasecmp(p, "AECN") || !strcasecmp(p, "ACCECN")) *fl |= 0x100; else if (!strcasecmp(p, "R1")) *fl |= 0x200; else if (!strcasecmp(p, "R2")) *fl |= 0x400; else if (!strcasecmp(p, "R3")) *fl |= 0x800; else return false; if (e) *e++ = c; p = e; } return true; } else { *fl = u & 0xFFF; return *fl==u; } } static void split_compat(struct desync_profile *dp) { if (!dp->split_count) { dp->splits[dp->split_count].marker = PM_ABS; dp->splits[dp->split_count].pos = 2; dp->split_count++; } if ((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)) { DLOG_ERR("split seqovl supports only absolute positive positions\n"); exit_clean(1); } } static void SplitDebug(void) { struct desync_profile_list *dpl; const struct desync_profile *dp; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { dp = &dpl->dp; for (int x = 0; x < dp->split_count; x++) DLOG("profile %d multisplit %s %d\n", dp->n, posmarker_name(dp->splits[x].marker), dp->splits[x].pos); if (!PROTO_POS_EMPTY(&dp->seqovl)) DLOG("profile %d seqovl %s %d\n", dp->n, posmarker_name(dp->seqovl.marker), dp->seqovl.pos); if (!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); } } static 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) { const uint8_t *ext; size_t extlen; modcache->extlen_offset = modcache->padlen_offset = 0; if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_PADENCAP)) { if (!TLSFindExtLen(fake_tls, *fake_tls_size, &modcache->extlen_offset)) { DLOG_ERR("profile %d fake[%d] padencap set but tls fake structure invalid\n", profile_n, fake_n); return false; } DLOG("profile %d fake[%d] tls extensions length offset : %zu\n", profile_n, fake_n, modcache->extlen_offset); if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI)) { size_t slen; if (!TLSFindExt(fake_tls, *fake_tls_size, 0, &ext, &extlen, false)) { DLOG_ERR("profile %d fake[%d] sni mod is set but tls fake does not have SNI\n", profile_n, fake_n); return false; } uint8_t *sniext = fake_tls + (ext - fake_tls); if (!TLSAdvanceToHostInSNI(&ext, &extlen, &slen)) { DLOG_ERR("profile %d fake[%d] sni set but tls fake has invalid SNI structure\n", profile_n, fake_n); return false; } uint8_t *sni = fake_tls + (ext - fake_tls); if (tls_mod->mod & FAKE_TLS_MOD_SNI) { size_t slen_new = strlen(tls_mod->sni); ssize_t slen_delta = slen_new - slen; char *s1 = NULL; if (params.debug) { if ((s1 = malloc(slen + 1))) { memcpy(s1, sni, slen); s1[slen] = 0; } } if (slen_delta) { if ((*fake_tls_size + slen_delta) > fake_tls_buf_size) { DLOG_ERR("profile %d fake[%d] not enough space for new SNI\n", profile_n, fake_n); free(s1); return false; } memmove(sni + slen_new, sni + slen, fake_tls + *fake_tls_size - (sni + slen)); phton16(fake_tls + 3, (uint16_t)(pntoh16(fake_tls + 3) + slen_delta)); phton24(fake_tls + 6, (uint32_t)(pntoh24(fake_tls + 6) + slen_delta)); phton16(fake_tls + modcache->extlen_offset, (uint16_t)(pntoh16(fake_tls + modcache->extlen_offset) + slen_delta)); phton16(sniext - 2, (uint16_t)(pntoh16(sniext - 2) + slen_delta)); phton16(sniext, (uint16_t)(pntoh16(sniext) + slen_delta)); phton16(sni - 2, (uint16_t)(pntoh16(sni - 2) + slen_delta)); *fake_tls_size += slen_delta; slen = slen_new; } DLOG("profile %d fake[%d] change SNI : %s => %s size_delta=%zd\n", profile_n, fake_n, s1, tls_mod->sni, slen_delta); free(s1); memcpy(sni, tls_mod->sni, slen_new); } if (tls_mod->mod & FAKE_TLS_MOD_RND_SNI) { if (!slen) { DLOG_ERR("profile %d fake[%d] rndsni set but tls fake has zero sized SNI\n", profile_n, fake_n); return false; } char *s1 = NULL, *s2 = NULL; if (params.debug) { if ((s1 = malloc(slen + 1))) { memcpy(s1, sni, slen); s1[slen] = 0; } } fill_random_az(sni, 1); if (slen >= 7) // domain name in SNI must be at least 3 chars long to enable xxx.tls randomization { fill_random_az09(sni + 1, slen - 5); sni[slen - 4] = '.'; memcpy(sni + slen - 3, tld[random() % (sizeof(tld) / sizeof(*tld))], 3); } else fill_random_az09(sni + 1, slen - 1); if (params.debug) { if (s1 && (s2 = malloc(slen + 1))) { memcpy(s2, sni, slen); s2[slen] = 0; DLOG("profile %d fake[%d] generated random SNI : %s -> %s\n", profile_n, fake_n, s1, s2); } free(s1); free(s2); } } } if (tls_mod->mod & FAKE_TLS_MOD_PADENCAP) { if (TLSFindExt(fake_tls, *fake_tls_size, 21, &ext, &extlen, false)) { if ((ext - fake_tls + extlen) != *fake_tls_size) { DLOG_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); return false; } modcache->padlen_offset = ext - fake_tls - 2; DLOG("profile %d fake[%d] tls padding ext is present, padding length offset %zu\n", profile_n, fake_n, modcache->padlen_offset); } else { if ((*fake_tls_size + 4) > fake_tls_buf_size) { DLOG_ERR("profile %d fake[%d] tls padding is absent and there's no space to add it\n", profile_n, fake_n); return false; } phton16(fake_tls + *fake_tls_size, 21); *fake_tls_size += 2; modcache->padlen_offset = *fake_tls_size; phton16(fake_tls + *fake_tls_size, 0); *fake_tls_size += 2; phton16(fake_tls + modcache->extlen_offset, pntoh16(fake_tls + modcache->extlen_offset) + 4); phton16(fake_tls + 3, pntoh16(fake_tls + 3) + 4); // increase tls record len phton24(fake_tls + 6, pntoh24(fake_tls + 6) + 4); // increase tls handshake len DLOG("profile %d fake[%d] tls padding is absent. added. padding length offset %zu\n", profile_n, fake_n, modcache->padlen_offset); } } } return true; } static bool onetime_tls_mod(struct desync_profile *dp) { struct blob_item *fake_tls; struct fake_tls_mod *tls_mod; int n = 0; LIST_FOREACH(fake_tls, &dp->fake_tls, next) { ++n; tls_mod = (struct fake_tls_mod *)fake_tls->extra2; if (!tls_mod) continue; if (dp->n && !(tls_mod->mod & (FAKE_TLS_MOD_SET | FAKE_TLS_MOD_CUSTOM_FAKE))) tls_mod->mod |= FAKE_TLS_MOD_RND | FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_DUP_SID; // old behavior compat + dup_sid if (!(tls_mod->mod & ~FAKE_TLS_MOD_SAVE_MASK)) continue; if (!IsTLSClientHello(fake_tls->data, fake_tls->size, false) || (fake_tls->size < (44 + fake_tls->data[43]))) // has session id ? { DLOG("profile %d fake[%d] tls mod set but tls fake structure invalid.\n", dp->n, n); return false; } if (!fake_tls->extra) { fake_tls->extra = malloc(sizeof(struct fake_tls_mod_cache)); if (!fake_tls->extra) return false; } if (!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)) return false; if (fake_tls->offset >= fake_tls->size) { DLOG("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); return false; } } return true; } static struct blob_item *load_blob_to_collection(const char *filename, struct blob_collection_head *blobs, size_t max_size, size_t size_reserve) { struct blob_item *blob = blob_collection_add(blobs); uint8_t *p; if (!blob || (!(blob->data = malloc(max_size + size_reserve)))) { DLOG_ERR("out of memory\n"); exit_clean(1); } blob->size = max_size; load_file_or_exit(filename, blob->data, &blob->size, &blob->offset); p = realloc(blob->data, blob->size + size_reserve); if (!p) { DLOG_ERR("out of memory\n"); exit_clean(1); } blob->data = p; blob->size_buf = blob->size + size_reserve; return blob; } static 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) { if (offset >= sz) { DLOG_ERR("offset %zu is out of data range %zu\n", offset, sz); exit_clean(1); } struct blob_item *blob = blob_collection_add(blobs); if (!blob || (!(blob->data = malloc(sz + size_reserve)))) { DLOG_ERR("out of memory\n"); exit_clean(1); } blob->size = sz; blob->size_buf = sz + size_reserve; blob->offset = offset; memcpy(blob->data, data, sz); return blob; } #ifdef __CYGWIN__ static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) { char *e, *p, c, s1[64]; port_filter pf; int n; if (len < 3) return false; for (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!pf_parse(p, &pf)) return false; if (pf.from == pf.to) snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from); else snprintf(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); if (n) strncat(buf, " or ", len - strlen(buf) - 1); strncat(buf, s1, len - strlen(buf) - 1); if (e) *e++ = c; p = e; } strncat(buf, ")", len - strlen(buf) - 1); return true; } #define DIVERT_NO_LOCALNETSv4_DST "(" \ "(ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and " \ "(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and " \ "(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and " \ "(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and " \ "(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255))" #define DIVERT_NO_LOCALNETSv4_SRC "(" \ "(ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and " \ "(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and " \ "(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and " \ "(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and " \ "(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255))" #define DIVERT_NO_LOCALNETSv6_DST "(" \ "(ipv6.DstAddr > ::1) and " \ "(ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and " \ "(ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and " \ "(ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and " \ "(ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))" #define DIVERT_NO_LOCALNETSv6_SRC "(" \ "(ipv6.SrcAddr > ::1) and " \ "(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and " \ "(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and " \ "(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and " \ "(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0))" #define DIVERT_NO_LOCALNETS_SRC "(" DIVERT_NO_LOCALNETSv4_SRC " or " DIVERT_NO_LOCALNETSv6_SRC ")" #define DIVERT_NO_LOCALNETS_DST "(" DIVERT_NO_LOCALNETSv4_DST " or " DIVERT_NO_LOCALNETSv6_DST ")" #define DIVERT_TCP_NOT_EMPTY "(!tcp or tcp.Syn or tcp.Rst or tcp.Fin or tcp.PayloadLength>0)" #define DIVERT_TCP_INBOUNDS "(tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin)" // HTTP/1.? 30(2|7) #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)" #define DIVERT_PROLOG "!impostor and !loopback" static bool wf_make_filter( char *wf, size_t len, unsigned int IfIdx, unsigned int SubIfIdx, bool ipv4, bool ipv6, const char *pf_tcp_src, const char *pf_tcp_dst, const char *pf_udp_src, const char *pf_udp_dst, const struct str_list_head *wf_raw_part, bool bFilterOutLAN) { char pf_dst_buf[8192], iface[64]; struct str_list *wfpart; int n; const char *pf_dst; const char *f_tcpin = *pf_tcp_src ? dp_list_have_autohostlist(¶ms.desync_profiles) ? "(" DIVERT_TCP_INBOUNDS " or (" DIVERT_HTTP_REDIRECT "))" : DIVERT_TCP_INBOUNDS : ""; const char *f_tcp_not_empty = (*pf_tcp_src && !dp_list_need_all_out(¶ms.desync_profiles)) ? DIVERT_TCP_NOT_EMPTY " and " : ""; snprintf(iface, sizeof(iface), " ifIdx=%u and subIfIdx=%u and", IfIdx, SubIfIdx); snprintf(wf, len, "%s and%s%s\n(", DIVERT_PROLOG, IfIdx ? iface : "", ipv4 ? ipv6 ? "" : " ip and" : " ipv6 and" ); n = 0; if (!LIST_EMPTY(wf_raw_part)) { LIST_FOREACH(wfpart, wf_raw_part, next) { snprintf(wf + strlen(wf), len - strlen(wf), "%s(\n%s\n )", n ? "\n or\n " : "\n ", wfpart->str); n++; } } if (*pf_tcp_src || *pf_udp_src) { if (*pf_tcp_src && *pf_udp_src) { snprintf(pf_dst_buf, sizeof(pf_dst_buf), "(%s or %s)", pf_tcp_dst, pf_udp_dst); pf_dst = pf_dst_buf; } else pf_dst = *pf_tcp_dst ? pf_tcp_dst : pf_udp_dst; snprintf(wf + strlen(wf), len - strlen(wf), n++ ? "\n or\n " : "\n "); snprintf(wf + strlen(wf), len - strlen(wf), "(\n (outbound and %s%s)\n or\n (inbound and tcp%s%s%s%s%s)\n )", f_tcp_not_empty, pf_dst, *pf_tcp_src ? "" : " and false", *f_tcpin ? " and " : "", *f_tcpin ? f_tcpin : "", *pf_tcp_src ? " and " : "", *pf_tcp_src ? pf_tcp_src : ""); } strncat(wf, "\n)", len - strlen(wf) - 1); if (bFilterOutLAN) snprintf(wf + strlen(wf), len - strlen(wf), "\nand\n(\n outbound and %s\n or\n inbound and %s\n)\n", ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_DST : DIVERT_NO_LOCALNETSv4_DST : DIVERT_NO_LOCALNETSv6_DST, ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_SRC : DIVERT_NO_LOCALNETSv4_SRC : DIVERT_NO_LOCALNETSv6_SRC); return true; } static unsigned int hash_jen(const void *data, unsigned int len) { unsigned int hash; HASH_JEN(data, len, hash); return hash; } #endif static void exithelp(void) { printf( #if !defined( __OpenBSD__) && !defined(__ANDROID__) " @|$\t\t\t\t; read file for options. must be the only argument. other options are ignored.\n\n" #endif #ifdef __ANDROID__ " --debug=0|1|syslog|android|@\n" #else " --debug=0|1|syslog|@\n" #endif " --version\t\t\t\t\t\t; print version and exit\n" " --dry-run\t\t\t\t\t\t; verify parameters and exit with code 0 if successful\n" " --comment=any_text\n" #ifdef __linux__ " --qnum=\n" #elif defined(BSD) " --port=\t\t\t\t\t\t; divert port\n" #endif " --daemon\t\t\t\t\t\t; daemonize\n" " --pidfile=\t\t\t\t\t; write pid to file\n" #ifndef __CYGWIN__ " --user=\t\t\t\t\t; drop root privs\n" " --uid=uid[:gid1,gid2,...]\t\t\t\t; drop root privs\n" #endif #ifdef __linux__ " --bind-fix4\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv4 packets\n" " --bind-fix6\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv6 packets\n" #endif " --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" " --ctrack-disable=[0|1]\t\t\t\t\t; 1 or no argument disables conntrack\n" " --ipcache-lifetime=\t\t\t\t; time in seconds to keep cached hop count and domain name (default %u). 0 = no expiration\n" " --ipcache-hostname=[0|1]\t\t\t\t; 1 or no argument enables ip->hostname caching\n" #ifdef __CYGWIN__ "\nWINDIVERT FILTER:\n" " --wf-iface=[.]\t\t\t\t; numeric network interface and subinterface indexes\n" " --wf-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --wf-tcp=[~]port1[-port2]\t\t\t\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-udp=[~]port1[-port2]\t\t\t\t; UDP port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-raw-part=|@\t\t\t; partial raw windivert filter string or filename\n" " --wf-filter-lan=0|1\t\t\t\t\t; add excluding filter for non-global IP (default : 1)\n" " --wf-raw=|@\t\t\t\t; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part\n" " --wf-save=\t\t\t\t\t; save windivert filter string to a file and exit\n" "\nLOGICAL NETWORK FILTER:\n" " --ssid-filter=ssid1[,ssid2,ssid3,...]\t\t\t; enable winws only if any of specified wifi SSIDs connected\n" " --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" " --nlm-list[=all]\t\t\t\t\t; list Network List Manager (NLM) networks. connected only or all.\n" #endif "\nMULTI-STRATEGY:\n" " --new\t\t\t\t\t\t\t; begin new strategy\n" " --skip\t\t\t\t\t\t\t; do not use this strategy\n" " --filter-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --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" " --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" " --filter-l7=[http|tls|quic|wireguard|dht|discord|stun|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed.\n" #ifdef HAS_FILTER_SSID " --filter-ssid=ssid1[,ssid2,ssid3,...]\t\t\t; per profile wifi SSID filter\n" #endif " --ipset=\t\t\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-ip=\t\t\t\t\t; comma separated fixed subnet list\n" " --ipset-exclude=\t\t\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-exclude-ip=\t\t\t\t; comma separated fixed subnet list\n" "\nHOSTLIST FILTER:\n" " --hostlist=\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" " --hostlist-domains=\t\t\t; comma separated fixed domain list\n" " --hostlist-exclude=\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" " --hostlist-exclude-domains=\t\t; comma separated fixed domain list\n" " --hostlist-auto=\t\t\t\t; detect DPI blocks and build hostlist automatically\n" " --hostlist-auto-fail-threshold=\t\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" " --hostlist-auto-fail-time=\t\t\t; all failed attemps must be within these seconds (default : %d)\n" " --hostlist-auto-retrans-threshold=\t\t; how many request retransmissions cause attempt to fail (default : %d)\n" " --hostlist-auto-debug=\t\t\t; debug auto hostlist positives\n" "\nTAMPER:\n" " --wsize=[:]\t\t\t; set window size. 0 = do not modify. OBSOLETE !\n" " --wssize=[:]\t\t; set window size for server. 0 = do not modify. default scale_factor = 0.\n" " --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" " --wssize-forced-cutoff=0|1\t\t\t\t; 1(default)=auto cutoff wssize on known protocol\n" " --synack-split=[syn|synack|acksyn]\t\t\t; perform TCP split handshake : send SYN only, SYN+ACK or ACK+SYN\n" " --orig-ttl=\t\t\t\t\t; set TTL for original packets\n" " --orig-ttl6=\t\t\t\t\t; set ipv6 hop limit for original packets. by default ttl value is used\n" " --orig-autottl=[[:[-]]|-]\t\t; auto ttl mode for both ipv4 and ipv6. default: +%d:%u-%u\n" " --orig-autottl6=[[:[-]]|-]\t\t; overrides --orig-autottl for ipv6 only\n" " --orig-tcp-flags-set=\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" " --orig-tcp-flags-unset=\t\t; unset these tcp flags (flags &= ~value)\n" " --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" " --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" " --dup=\t\t\t\t\t\t; duplicate original packets. send N dups before original.\n" " --dup-replace=[0|1]\t\t\t\t\t; 1 or no argument means do not send original, only dups\n" " --dup-ttl=\t\t\t\t\t; set TTL for dups\n" " --dup-ttl6=\t\t\t\t\t; set ipv6 hop limit for dups. by default ttl value is used\n" " --dup-autottl=[[:[-]]|-]\t\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\n" " --dup-autottl6=[[:[-]]|-]\t\t; overrides --dup-autottl for ipv6 only\n" " --dup-tcp-flags-set=\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" " --dup-tcp-flags-unset=\t\t; unset these tcp flags (flags &= ~value)\n" " --dup-fooling=[,]\t\t\t\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n" " --dup-ts-increment=\t\t\t\t; ts fooling TSval signed increment for dup. default %d\n" " --dup-badseq-increment=\t\t\t; badseq fooling seq signed increment for dup. default %d\n" " --dup-badack-increment=\t\t\t; badseq fooling ackseq signed increment for dup. default %d\n" " --dup-ip-id=same|zero|seq|rnd\t\t\t\t; ipv4 ip_id mode for dupped packets\n" " --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" " --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" " --hostcase\t\t\t\t\t\t; change Host: => host:\n" " --hostspell\t\t\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" " --hostnospace\t\t\t\t\t\t; remove space after Host: and add it to User-Agent: to preserve packet size\n" " --domcase\t\t\t\t\t\t; mix domain case : Host: TeSt.cOm\n" " --methodeol\t\t\t\t\t\t; add '\\n' before method and remove space from Host:\n" " --ip-id=zero|seq|seqgroup|rnd\t\t\t\t; ipv4 ip_id assignment scheme\n" " --dpi-desync=[,][,]\t\t; try to desync dpi state. modes :\n" "\t\t\t\t\t\t\t; synack syndata fake fakeknown rst rstack hopbyhop destopt ipfrag1\n" "\t\t\t\t\t\t\t; multisplit multidisorder fakedsplit fakeddisorder hostfakesplit ipfrag2 udplen tamper\n" #ifdef __linux__ " --dpi-desync-fwmark=\t\t\t; override fwmark for desync packet. default = 0x%08X (%u)\n" #elif defined(SO_USER_COOKIE) " --dpi-desync-sockarg=\t\t\t; override sockarg (SO_USER_COOKIE) for desync packet. default = 0x%08X (%u)\n" #endif " --dpi-desync-ttl=\t\t\t\t\t; set ttl for fakes packets\n" " --dpi-desync-ttl6=\t\t\t\t; set ipv6 hop limit for fake packet. by default --dpi-desync-ttl value is used.\n" " --dpi-desync-autottl=[[:[-]]|-]\t; auto ttl mode for both ipv4 and ipv6. default: %d:%u-%u\n" " --dpi-desync-autottl6=[[:[-]]|-]\t; overrides --dpi-desync-autottl for ipv6 only\n" " --dpi-desync-tcp-flags-set=\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" " --dpi-desync-tcp-flags-unset=\t; unset these tcp flags (flags &= ~value)\n" " --dpi-desync-fooling=[,]\t\t\t; can use multiple comma separated values. modes : none md5sig badseq badsum datanoack ts hopbyhop hopbyhop2\n" " --dpi-desync-repeats=\t\t\t\t; send every desync packet N times\n" " --dpi-desync-skip-nosni=0|1\t\t\t\t; 1(default)=do not act on ClientHello without SNI\n" " --dpi-desync-split-pos=N|-N|marker+N|marker-N\t\t; comma separated list of split positions\n" "\t\t\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n" "\t\t\t\t\t\t\t; full list is only used by multisplit and multidisorder\n" "\t\t\t\t\t\t\t; 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\t; use sequence overlap before first sent original split segment\n" " --dpi-desync-split-seqovl-pattern=[+ofs]@|0xHEX ; pattern for the fake part of overlap\n" " --dpi-desync-fakedsplit-pattern=[+ofs]@|0xHEX ; fake pattern for fakedsplit/fakeddisorder\n" " --dpi-desync-fakedsplit-mod=mod[,mod]\t\t\t; mods can be none,altorder=0|1|2|3 + 0|8|16\n" " --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" " --dpi-desync-hostfakesplit-mod=mod[,mod]\t\t; mods can be none,host=,altorder=0|1\n" " --dpi-desync-ipfrag-pos-udp=<8..%u>\t\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" " --dpi-desync-ipfrag-pos-tcp=<8..%u>\t\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" " --dpi-desync-ts-increment=\t\t\t; ts fooling TSval signed increment. default %d\n" " --dpi-desync-badseq-increment=\t\t; badseq fooling seq signed increment. default %d\n" " --dpi-desync-badack-increment=\t\t; badseq fooling ackseq signed increment. default %d\n" " --dpi-desync-any-protocol=0|1\t\t\t\t; 0(default)=desync only http and tls 1=desync any nonempty data packet\n" " --dpi-desync-fake-tcp-mod=mod[,mod]\t\t\t; comma separated list of tcp fake mods. available mods : none,seq\n" " --dpi-desync-fake-http=[+ofs]@|0xHEX\t\t; fake http request\n" " --dpi-desync-fake-tls=[+ofs]@|0xHEX|![+offset] ; fake TLS ClientHello (for https)\n" " --dpi-desync-fake-tls-mod=mod[,mod]\t\t\t; comma separated list of TLS fake mods. available mods : none,rnd,rndsni,sni=,dupsid,padencap\n" " --dpi-desync-fake-unknown=[+ofs]@|0xHEX\t; unknown protocol fake payload\n" " --dpi-desync-fake-syndata=[+ofs]@|0xHEX\t; SYN data payload\n" " --dpi-desync-fake-quic=[+ofs]@|0xHEX\t\t; fake QUIC Initial\n" " --dpi-desync-fake-wireguard=[+ofs]@|0xHEX\t; fake wireguard handshake initiation\n" " --dpi-desync-fake-dht=[+ofs]@|0xHEX\t\t; DHT protocol fake payload (d1...e)\n" " --dpi-desync-fake-discord=[+ofs]@|0xHEX\t; discord protocol fake payload (Voice IP Discovery)\n" " --dpi-desync-fake-stun=[+ofs]@|0xHEX\t\t; STUN protocol fake payload\n" " --dpi-desync-fake-unknown-udp=[+ofs]@|0xHEX\t; unknown udp protocol fake payload\n" " --dpi-desync-udplen-increment=\t\t\t; increase or decrease udp packet length by N bytes (default %u). negative values decrease length.\n" " --dpi-desync-udplen-pattern=[+ofs]@|0xHEX\t; udp tail fill pattern\n" " --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" " --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", CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP, IPCACHE_LIFETIME, HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX, TS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT, #if defined(__linux__) || defined(SO_USER_COOKIE) DPI_DESYNC_FWMARK_DEFAULT, DPI_DESYNC_FWMARK_DEFAULT, #endif AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX, DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT, DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT, TS_INCREMENT_DEFAULT, BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT, UDPLEN_INCREMENT_DEFAULT ); exit(1); } static void exithelp_clean(void) { cleanup_params(¶ms); exithelp(); } #if !defined( __OpenBSD__) && !defined(__ANDROID__) // no static to not allow optimizer to inline this func (save stack) void config_from_file(const char *filename) { // config from a file char buf[MAX_CONFIG_FILE_SIZE]; buf[0] = 'x'; // fake argv[0] buf[1] = ' '; size_t bufsize = sizeof(buf) - 3; if (!load_file(filename, buf + 2, &bufsize)) { DLOG_ERR("could not load config file '%s'\n", filename); exit_clean(1); } buf[bufsize + 2] = 0; // wordexp fails if it sees \t \n \r between args replace_char(buf, '\n', ' '); replace_char(buf, '\r', ' '); replace_char(buf, '\t', ' '); if (wordexp(buf, ¶ms.wexp, WRDE_NOCMD)) { DLOG_ERR("failed to split command line options from file '%s'\n", filename); exit_clean(1); } } #endif void check_dp(const struct desync_profile *dp) { // only linux has connbytes limiter if ((dp->desync_any_proto && !dp->desync_cutoff && (dp->desync_mode == DESYNC_FAKE || dp->desync_mode == DESYNC_RST || dp->desync_mode == DESYNC_RSTACK || dp->desync_mode == DESYNC_FAKEDSPLIT || dp->desync_mode == DESYNC_FAKEDDISORDER || dp->desync_mode == DESYNC_HOSTFAKESPLIT || dp->desync_mode2 == DESYNC_FAKEDSPLIT || dp->desync_mode2 == DESYNC_FAKEDDISORDER || dp->desync_mode2 == DESYNC_HOSTFAKESPLIT)) || dp->dup_repeats && !dp->dup_cutoff) { #ifdef __linux__ DLOG_CONDUP("WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\n", dp->n); DLOG_CONDUP("WARNING !!! it's completely ok if connbytes or payload based ip/nf tables limiter is applied. Make sure it exists.\n"); #else DLOG_CONDUP("WARNING !!! possible TRASH FLOOD configuration detected in profile %d\n", dp->n); DLOG_CONDUP("WARNING !!! in profile %d you are using --dpi-desync-any-protocol without --dpi-desync-cutoff or --dup without --dup-cutoff\n", dp->n); DLOG_CONDUP("WARNING !!! fakes or dups will be sent on every processed packet\n"); DLOG_CONDUP("WARNING !!! make sure it's really what you want\n"); #ifdef __CYGWIN__ DLOG_CONDUP("WARNING !!! in most cases this is acceptable only with custom payload based windivert filter (--wf-raw, --wf-raw-part)\n"); #endif #endif } } #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) #ifdef __ANDROID__ #define PRINT_VER printf("github android version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) #else #define PRINT_VER printf("github version %s (%s)\n\n", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH)) #endif #else #ifdef __ANDROID__ #define PRINT_VER printf("self-built android version %s %s\n\n", __DATE__, __TIME__) #else #define PRINT_VER printf("self-built version %s %s\n\n", __DATE__, __TIME__) #endif #endif enum opt_indices { IDX_DEBUG, IDX_DRY_RUN, IDX_VERSION, IDX_COMMENT, #ifdef __linux__ IDX_QNUM, #elif defined(BSD) IDX_PORT, #endif IDX_DAEMON, IDX_PIDFILE, #ifndef __CYGWIN__ IDX_USER, IDX_UID, #endif IDX_WSIZE, IDX_WSSIZE, IDX_WSSIZE_CUTOFF, IDX_WSSIZE_FORCED_CUTOFF, IDX_SYNACK_SPLIT, IDX_CTRACK_TIMEOUTS, IDX_CTRACK_DISABLE, IDX_IPCACHE_LIFETIME, IDX_IPCACHE_HOSTNAME, IDX_HOSTCASE, IDX_HOSTSPELL, IDX_HOSTNOSPACE, IDX_DOMCASE, IDX_METHODEOL, IDX_IP_ID, IDX_DPI_DESYNC, #ifdef __linux__ IDX_DPI_DESYNC_FWMARK, #elif defined(SO_USER_COOKIE) IDX_DPI_DESYNC_SOCKARG, #endif IDX_DUP, IDX_DUP_TTL, IDX_DUP_TTL6, IDX_DUP_AUTOTTL, IDX_DUP_AUTOTTL6, IDX_DUP_TCP_FLAGS_SET, IDX_DUP_TCP_FLAGS_UNSET, IDX_DUP_FOOLING, IDX_DUP_TS_INCREMENT, IDX_DUP_BADSEQ_INCREMENT, IDX_DUP_BADACK_INCREMENT, IDX_DUP_REPLACE, IDX_DUP_IP_ID, IDX_DUP_START, IDX_DUP_CUTOFF, IDX_ORIG_TTL, IDX_ORIG_TTL6, IDX_ORIG_AUTOTTL, IDX_ORIG_AUTOTTL6, IDX_ORIG_TCP_FLAGS_SET, IDX_ORIG_TCP_FLAGS_UNSET, IDX_ORIG_MOD_START, IDX_ORIG_MOD_CUTOFF, IDX_DPI_DESYNC_TTL, IDX_DPI_DESYNC_TTL6, IDX_DPI_DESYNC_AUTOTTL, IDX_DPI_DESYNC_AUTOTTL6, IDX_DPI_DESYNC_TCP_FLAGS_SET, IDX_DPI_DESYNC_TCP_FLAGS_UNSET, IDX_DPI_DESYNC_FOOLING, IDX_DPI_DESYNC_REPEATS, IDX_DPI_DESYNC_SKIP_NOSNI, IDX_DPI_DESYNC_SPLIT_POS, IDX_DPI_DESYNC_SPLIT_HTTP_REQ, IDX_DPI_DESYNC_SPLIT_TLS, IDX_DPI_DESYNC_SPLIT_SEQOVL, IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN, IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN, IDX_DPI_DESYNC_FAKEDSPLIT_MOD, IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST, IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD, IDX_DPI_DESYNC_IPFRAG_POS_TCP, IDX_DPI_DESYNC_IPFRAG_POS_UDP, IDX_DPI_DESYNC_TS_INCREMENT, IDX_DPI_DESYNC_BADSEQ_INCREMENT, IDX_DPI_DESYNC_BADACK_INCREMENT, IDX_DPI_DESYNC_ANY_PROTOCOL, IDX_DPI_DESYNC_FAKE_TCP_MOD, IDX_DPI_DESYNC_FAKE_HTTP, IDX_DPI_DESYNC_FAKE_TLS, IDX_DPI_DESYNC_FAKE_TLS_MOD, IDX_DPI_DESYNC_FAKE_UNKNOWN, IDX_DPI_DESYNC_FAKE_SYNDATA, IDX_DPI_DESYNC_FAKE_QUIC, IDX_DPI_DESYNC_FAKE_WIREGUARD, IDX_DPI_DESYNC_FAKE_DHT, IDX_DPI_DESYNC_FAKE_DISCORD, IDX_DPI_DESYNC_FAKE_STUN, IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP, IDX_DPI_DESYNC_UDPLEN_INCREMENT, IDX_DPI_DESYNC_UDPLEN_PATTERN, IDX_DPI_DESYNC_CUTOFF, IDX_DPI_DESYNC_START, IDX_HOSTLIST, IDX_HOSTLIST_DOMAINS, IDX_HOSTLIST_EXCLUDE, IDX_HOSTLIST_EXCLUDE_DOMAINS, IDX_HOSTLIST_AUTO, IDX_HOSTLIST_AUTO_FAIL_THRESHOLD, IDX_HOSTLIST_AUTO_FAIL_TIME, IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, IDX_HOSTLIST_AUTO_DEBUG, IDX_NEW, IDX_SKIP, IDX_FILTER_L3, IDX_FILTER_TCP, IDX_FILTER_UDP, IDX_FILTER_L7, #ifdef HAS_FILTER_SSID IDX_FILTER_SSID, #endif IDX_IPSET, IDX_IPSET_IP, IDX_IPSET_EXCLUDE, IDX_IPSET_EXCLUDE_IP, #ifdef __linux__ IDX_BIND_FIX4, IDX_BIND_FIX6, #elif defined(__CYGWIN__) IDX_WF_IFACE, IDX_WF_L3, IDX_WF_TCP, IDX_WF_UDP, IDX_WF_RAW, IDX_WF_RAW_PART, IDX_WF_FILTER_LAN, IDX_WF_SAVE, IDX_SSID_FILTER, IDX_NLM_FILTER, IDX_NLM_LIST, #endif IDX_LAST, }; static const struct option long_options[] = { [IDX_DEBUG] = {"debug", optional_argument, 0, 0}, [IDX_DRY_RUN] = {"dry-run", no_argument, 0, 0}, [IDX_VERSION] = {"version", no_argument, 0, 0}, [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, #ifdef __linux__ [IDX_QNUM] = {"qnum", required_argument, 0, 0}, #elif defined(BSD) [IDX_PORT] = {"port", required_argument, 0, 0}, #endif [IDX_DAEMON] = {"daemon", no_argument, 0, 0}, [IDX_PIDFILE] = {"pidfile", required_argument, 0, 0}, #ifndef __CYGWIN__ [IDX_USER] = {"user", required_argument, 0, 0}, [IDX_UID] = {"uid", required_argument, 0, 0}, #endif [IDX_WSIZE] = {"wsize", required_argument, 0, 0}, [IDX_WSSIZE] = {"wssize", required_argument, 0, 0}, [IDX_WSSIZE_CUTOFF] = {"wssize-cutoff", required_argument, 0, 0}, [IDX_WSSIZE_FORCED_CUTOFF] = {"wssize-forced-cutoff", required_argument, 0, 0}, [IDX_SYNACK_SPLIT] = {"synack-split", optional_argument, 0, 0}, [IDX_CTRACK_TIMEOUTS] = {"ctrack-timeouts", required_argument, 0, 0}, [IDX_CTRACK_DISABLE] = {"ctrack-disable", optional_argument, 0, 0}, [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, [IDX_HOSTCASE] = {"hostcase", no_argument, 0, 0}, [IDX_HOSTSPELL] = {"hostspell", required_argument, 0, 0}, [IDX_HOSTNOSPACE] = {"hostnospace", no_argument, 0, 0}, [IDX_DOMCASE] = {"domcase", no_argument, 0, 0}, [IDX_METHODEOL] = {"methodeol", no_argument, 0, 0}, [IDX_IP_ID] = {"ip-id", required_argument, 0, 0}, [IDX_DPI_DESYNC] = {"dpi-desync", required_argument, 0, 0}, #ifdef __linux__ [IDX_DPI_DESYNC_FWMARK] = {"dpi-desync-fwmark", required_argument, 0, 0}, #elif defined(SO_USER_COOKIE) [IDX_DPI_DESYNC_SOCKARG] = {"dpi-desync-sockarg", required_argument, 0, 0}, #endif [IDX_DUP] = {"dup", required_argument, 0, 0}, [IDX_DUP_TTL] = {"dup-ttl", required_argument, 0, 0}, [IDX_DUP_TTL6] = {"dup-ttl6", required_argument, 0, 0}, [IDX_DUP_AUTOTTL] = {"dup-autottl", optional_argument, 0, 0}, [IDX_DUP_AUTOTTL6] = {"dup-autottl6", optional_argument, 0, 0}, [IDX_DUP_TCP_FLAGS_SET] = {"dup-tcp-flags-set", optional_argument, 0, 0}, [IDX_DUP_TCP_FLAGS_UNSET] = {"dup-tcp-flags-unset", optional_argument, 0, 0}, [IDX_DUP_FOOLING] = {"dup-fooling", required_argument, 0, 0}, [IDX_DUP_TS_INCREMENT] = {"dup-ts-increment", required_argument, 0, 0}, [IDX_DUP_BADSEQ_INCREMENT] = {"dup-badseq-increment", required_argument, 0, 0}, [IDX_DUP_BADACK_INCREMENT] = {"dup-badack-increment", required_argument, 0, 0}, [IDX_DUP_REPLACE] = {"dup-replace", optional_argument, 0, 0}, [IDX_DUP_IP_ID] = {"dup-ip-id", required_argument, 0, 0}, [IDX_DUP_START] = {"dup-start", required_argument, 0, 0}, [IDX_DUP_CUTOFF] = {"dup-cutoff", required_argument, 0, 0}, [IDX_ORIG_TTL] = {"orig-ttl", required_argument, 0, 0}, [IDX_ORIG_TTL6] = {"orig-ttl6", required_argument, 0, 0}, [IDX_ORIG_AUTOTTL] = {"orig-autottl", optional_argument, 0, 0}, [IDX_ORIG_AUTOTTL6] = {"orig-autottl6", optional_argument, 0, 0}, [IDX_ORIG_TCP_FLAGS_SET] = {"orig-tcp-flags-set", optional_argument, 0, 0}, [IDX_ORIG_TCP_FLAGS_UNSET] = {"orig-tcp-flags-unset", optional_argument, 0, 0}, [IDX_ORIG_MOD_START] = {"orig-mod-start", required_argument, 0, 0}, [IDX_ORIG_MOD_CUTOFF] = {"orig-mod-cutoff", required_argument, 0, 0}, [IDX_DPI_DESYNC_TTL] = {"dpi-desync-ttl", required_argument, 0, 0}, [IDX_DPI_DESYNC_TTL6] = {"dpi-desync-ttl6", required_argument, 0, 0}, [IDX_DPI_DESYNC_AUTOTTL] = {"dpi-desync-autottl", optional_argument, 0, 0}, [IDX_DPI_DESYNC_AUTOTTL6] = {"dpi-desync-autottl6", optional_argument, 0, 0}, [IDX_DPI_DESYNC_TCP_FLAGS_SET] = {"dpi-desync-tcp-flags-set", optional_argument, 0, 0}, [IDX_DPI_DESYNC_TCP_FLAGS_UNSET] = {"dpi-desync-tcp-flags-unset", optional_argument, 0, 0}, [IDX_DPI_DESYNC_FOOLING] = {"dpi-desync-fooling", required_argument, 0, 0}, [IDX_DPI_DESYNC_REPEATS] = {"dpi-desync-repeats", required_argument, 0, 0}, [IDX_DPI_DESYNC_SKIP_NOSNI] = {"dpi-desync-skip-nosni", optional_argument, 0, 0}, [IDX_DPI_DESYNC_SPLIT_POS] = {"dpi-desync-split-pos", required_argument, 0, 0}, [IDX_DPI_DESYNC_SPLIT_HTTP_REQ] = {"dpi-desync-split-http-req", required_argument, 0, 0}, [IDX_DPI_DESYNC_SPLIT_TLS] = {"dpi-desync-split-tls", required_argument, 0, 0}, [IDX_DPI_DESYNC_SPLIT_SEQOVL] = {"dpi-desync-split-seqovl", required_argument, 0, 0}, [IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN] = {"dpi-desync-split-seqovl-pattern", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN] = {"dpi-desync-fakedsplit-pattern", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKEDSPLIT_MOD] = {"dpi-desync-fakedsplit-mod", required_argument, 0, 0}, [IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST] = {"dpi-desync-hostfakesplit-midhost", required_argument, 0, 0}, [IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD] = {"dpi-desync-hostfakesplit-mod", required_argument, 0, 0}, [IDX_DPI_DESYNC_IPFRAG_POS_TCP] = {"dpi-desync-ipfrag-pos-tcp", required_argument, 0, 0}, [IDX_DPI_DESYNC_IPFRAG_POS_UDP] = {"dpi-desync-ipfrag-pos-udp", required_argument, 0, 0}, [IDX_DPI_DESYNC_TS_INCREMENT] = {"dpi-desync-ts-increment", required_argument, 0, 0}, [IDX_DPI_DESYNC_BADSEQ_INCREMENT] = {"dpi-desync-badseq-increment", required_argument, 0, 0}, [IDX_DPI_DESYNC_BADACK_INCREMENT] = {"dpi-desync-badack-increment", required_argument, 0, 0}, [IDX_DPI_DESYNC_ANY_PROTOCOL] = {"dpi-desync-any-protocol", optional_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_TCP_MOD] = {"dpi-desync-fake-tcp-mod", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_HTTP] = {"dpi-desync-fake-http", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_TLS] = {"dpi-desync-fake-tls", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_TLS_MOD] = {"dpi-desync-fake-tls-mod", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_UNKNOWN] = {"dpi-desync-fake-unknown", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_SYNDATA] = {"dpi-desync-fake-syndata", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_QUIC] = {"dpi-desync-fake-quic", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_WIREGUARD] = {"dpi-desync-fake-wireguard", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_DHT] = {"dpi-desync-fake-dht", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_DISCORD] = {"dpi-desync-fake-discord", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_STUN] = {"dpi-desync-fake-stun", required_argument, 0, 0}, [IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP] = {"dpi-desync-fake-unknown-udp", required_argument, 0, 0}, [IDX_DPI_DESYNC_UDPLEN_INCREMENT] = {"dpi-desync-udplen-increment", required_argument, 0, 0}, [IDX_DPI_DESYNC_UDPLEN_PATTERN] = {"dpi-desync-udplen-pattern", required_argument, 0, 0}, [IDX_DPI_DESYNC_CUTOFF] = {"dpi-desync-cutoff", required_argument, 0, 0}, [IDX_DPI_DESYNC_START] = {"dpi-desync-start", required_argument, 0, 0}, [IDX_HOSTLIST] = {"hostlist", required_argument, 0, 0}, [IDX_HOSTLIST_DOMAINS] = {"hostlist-domains", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE] = {"hostlist-exclude", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE_DOMAINS] = {"hostlist-exclude-domains", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO] = {"hostlist-auto", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0}, [IDX_NEW] = {"new", no_argument, 0, 0}, [IDX_SKIP] = {"skip", no_argument, 0, 0}, [IDX_FILTER_L3] = {"filter-l3", required_argument, 0, 0}, [IDX_FILTER_TCP] = {"filter-tcp", required_argument, 0, 0}, [IDX_FILTER_UDP] = {"filter-udp", required_argument, 0, 0}, [IDX_FILTER_L7] = {"filter-l7", required_argument, 0, 0}, #ifdef HAS_FILTER_SSID [IDX_FILTER_SSID] = {"filter-ssid", required_argument, 0, 0}, #endif [IDX_IPSET] = {"ipset", required_argument, 0, 0}, [IDX_IPSET_IP] = {"ipset-ip", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE] = {"ipset-exclude", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE_IP] = {"ipset-exclude-ip", required_argument, 0, 0}, #ifdef __linux__ [IDX_BIND_FIX4] = {"bind-fix4", no_argument, 0, 0}, [IDX_BIND_FIX6] = {"bind-fix6", no_argument, 0, 0}, #elif defined(__CYGWIN__) [IDX_WF_IFACE] = {"wf-iface", required_argument, 0, 0}, [IDX_WF_L3] = {"wf-l3", required_argument, 0, 0}, [IDX_WF_TCP] = {"wf-tcp", required_argument, 0, 0}, [IDX_WF_UDP] = {"wf-udp", required_argument, 0, 0}, [IDX_WF_RAW] = {"wf-raw", required_argument, 0, 0}, [IDX_WF_RAW_PART] = {"wf-raw-part", required_argument, 0, 0}, [IDX_WF_FILTER_LAN] = {"wf-filter-lan", required_argument, 0, 0}, [IDX_WF_SAVE] = {"wf-save", required_argument, 0, 0}, [IDX_SSID_FILTER] = {"ssid-filter", required_argument, 0, 0}, [IDX_NLM_FILTER] = {"nlm-filter", required_argument, 0, 0}, [IDX_NLM_LIST] = {"nlm-list", optional_argument, 0, 0}, #endif [IDX_LAST] = {NULL, 0, NULL, 0}, }; int main(int argc, char **argv) { set_console_io_buffering(); set_env_exedir(argv[0]); #ifdef __CYGWIN__ if (service_run(argc, argv)) { // we were running as service. now exit. return 0; } #endif int result, v; int option_index = 0; bool bSkip = false, bDry = false; struct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL; struct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL; #ifdef __CYGWIN__ char 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]; bool wf_ipv4 = true, wf_ipv6 = true, wf_filter_lan = true; unsigned int IfIdx = 0, SubIfIdx = 0; unsigned 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; *windivert_filter = *wf_pf_tcp_src = *wf_pf_tcp_dst = *wf_pf_udp_src = *wf_pf_udp_dst = *wf_save_file = 0; #endif srandom(time(NULL)); aes_init_keygen_tables(); // required for aes PRINT_VER; memset(¶ms, 0, sizeof(params)); struct desync_profile_list *dpl; struct desync_profile *dp; int desync_profile_count = 0; if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = ++desync_profile_count; #ifdef __linux__ params.qnum = -1; #elif defined(BSD) params.port = 0; #endif params.desync_fwmark = DPI_DESYNC_FWMARK_DEFAULT; params.ctrack_t_syn = CTRACK_T_SYN; params.ctrack_t_est = CTRACK_T_EST; params.ctrack_t_fin = CTRACK_T_FIN; params.ctrack_t_udp = CTRACK_T_UDP; params.ipcache_lifetime = IPCACHE_LIFETIME; LIST_INIT(¶ms.hostlists); LIST_INIT(¶ms.ipsets); #ifdef __CYGWIN__ LIST_INIT(¶ms.ssid_filter); LIST_INIT(¶ms.nlm_filter); LIST_INIT(¶ms.wf_raw_part); #else if (can_drop_root()) { params.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid params.gid_count = 1; params.droproot = true; } #endif #if !defined( __OpenBSD__) && !defined(__ANDROID__) if (argc >= 2 && (argv[1][0] == '@' || argv[1][0] == '$')) { config_from_file(argv[1] + 1); argv = params.wexp.we_wordv; argc = params.wexp.we_wordc; } #endif if (argc < 2) exithelp_clean(); while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) { if (v) { if (bDry) exit_clean(1); else exithelp_clean(); } switch (option_index) { case IDX_DEBUG: if (optarg) { if (*optarg == '@') { strncpy(params.debug_logfile, optarg + 1, sizeof(params.debug_logfile)); params.debug_logfile[sizeof(params.debug_logfile) - 1] = 0; FILE *F = fopen(params.debug_logfile, "wt"); if (!F) { fprintf(stderr, "cannot create %s\n", params.debug_logfile); exit_clean(1); } fclose(F); params.debug = true; params.debug_target = LOG_TARGET_FILE; } else if (!strcmp(optarg, "syslog")) { params.debug = true; params.debug_target = LOG_TARGET_SYSLOG; openlog(progname, LOG_PID, LOG_USER); } #ifdef __ANDROID__ else if (!strcmp(optarg, "android")) { if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_ANDROID; } #endif else if (optarg[0] >= '0' && optarg[0] <= '1') { params.debug = atoi(optarg); params.debug_target = LOG_TARGET_CONSOLE; } else { fprintf(stderr, "invalid debug mode : %s\n", optarg); exit_clean(1); } } else { params.debug = true; params.debug_target = LOG_TARGET_CONSOLE; } break; case IDX_DRY_RUN: bDry = true; break; case IDX_VERSION: exit_clean(0); break; case IDX_COMMENT: break; #ifdef __linux__ case IDX_QNUM: params.qnum = atoi(optarg); if (params.qnum < 0 || params.qnum>65535) { DLOG_ERR("bad qnum\n"); exit_clean(1); } break; #elif defined(BSD) case IDX_PORT: { int i = atoi(optarg); if (i <= 0 || i > 65535) { DLOG_ERR("bad port number\n"); exit_clean(1); } params.port = (uint16_t)i; } break; #endif case IDX_DAEMON: params.daemon = true; break; case IDX_PIDFILE: snprintf(params.pidfile, sizeof(params.pidfile), "%s", optarg); break; #ifndef __CYGWIN__ case IDX_USER: { free(params.user); params.user = NULL; struct passwd *pwd = getpwnam(optarg); if (!pwd) { DLOG_ERR("non-existent username supplied\n"); exit_clean(1); } params.uid = pwd->pw_uid; params.gid[0] = pwd->pw_gid; params.gid_count = 1; if (!(params.user = strdup(optarg))) { DLOG_ERR("strdup: out of memory\n"); exit_clean(1); } params.droproot = true; break; } case IDX_UID: free(params.user); params.user = NULL; if (!parse_uid(optarg, ¶ms.uid, params.gid, ¶ms.gid_count, MAX_GIDS)) { DLOG_ERR("--uid should be : uid[:gid,gid,...]\n"); exit_clean(1); } if (!params.gid_count) { params.gid[0] = 0x7FFFFFFF; params.gid_count = 1; } params.droproot = true; break; #endif case IDX_WSIZE: if (!parse_ws_scale_factor(optarg, &dp->wsize, &dp->wscale)) exit_clean(1); break; case IDX_WSSIZE: if (!parse_ws_scale_factor(optarg, &dp->wssize, &dp->wsscale)) exit_clean(1); break; case IDX_WSSIZE_CUTOFF: if (!parse_cutoff(optarg, &dp->wssize_cutoff, &dp->wssize_cutoff_mode)) { DLOG_ERR("invalid wssize-cutoff value\n"); exit_clean(1); } break; case IDX_WSSIZE_FORCED_CUTOFF: dp->wssize_no_forced_cutoff = !atoi(optarg); break; case IDX_SYNACK_SPLIT: dp->synack_split = SS_SYN; if (optarg) { if (!strcmp(optarg, "synack")) dp->synack_split = SS_SYNACK; else if (!strcmp(optarg, "acksyn")) dp->synack_split = SS_ACKSYN; else if (strcmp(optarg, "syn")) { DLOG_ERR("invalid synack-split value\n"); exit_clean(1); } } break; case IDX_CTRACK_TIMEOUTS: if (sscanf(optarg, "%u:%u:%u:%u", ¶ms.ctrack_t_syn, ¶ms.ctrack_t_est, ¶ms.ctrack_t_fin, ¶ms.ctrack_t_udp) < 3) { DLOG_ERR("invalid ctrack-timeouts value\n"); exit_clean(1); } break; case IDX_CTRACK_DISABLE: params.ctrack_disable = !optarg || atoi(optarg); break; case IDX_IPCACHE_LIFETIME: if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime) != 1) { DLOG_ERR("invalid ipcache-lifetime value\n"); exit_clean(1); } break; case IDX_IPCACHE_HOSTNAME: params.cache_hostname = !optarg || atoi(optarg); break; case IDX_HOSTCASE: dp->hostcase = true; break; case IDX_HOSTSPELL: if (strlen(optarg) != 4) { DLOG_ERR("hostspell must be exactly 4 chars long\n"); exit_clean(1); } dp->hostcase = true; memcpy(dp->hostspell, optarg, 4); break; case IDX_HOSTNOSPACE: if (dp->methodeol) { DLOG_ERR("--hostnospace and --methodeol are incompatible\n"); exit_clean(1); } dp->hostnospace = true; break; case IDX_DOMCASE: dp->domcase = true; break; case IDX_METHODEOL: if (dp->hostnospace) { DLOG_ERR("--hostnospace and --methodeol are incompatible\n"); exit_clean(1); } dp->methodeol = true; break; case IDX_IP_ID: if (!strcmp(optarg,"zero")) dp->ip_id_mode = IPID_ZERO; else if (!strcmp(optarg,"seq")) dp->ip_id_mode = IPID_SEQ; else if (!strcmp(optarg,"seqgroup")) dp->ip_id_mode = IPID_SEQ_GROUP; else if (!strcmp(optarg,"rnd")) dp->ip_id_mode = IPID_RND; else { DLOG_ERR("invalid ip_id mode : %s\n",optarg); exit_clean(1); } break; case IDX_DPI_DESYNC: { char *mode = optarg, *mode2, *mode3; mode2 = mode ? strchr(mode, ',') : NULL; if (mode2) *mode2++ = 0; mode3 = mode2 ? strchr(mode2, ',') : NULL; if (mode3) *mode3++ = 0; dp->desync_mode0 = desync_mode_from_string(mode); if (desync_valid_zero_stage(dp->desync_mode0)) { mode = mode2; mode2 = mode3; mode3 = NULL; } else { dp->desync_mode0 = DESYNC_NONE; } dp->desync_mode = desync_mode_from_string(mode); dp->desync_mode2 = desync_mode_from_string(mode2); if (dp->desync_mode0 == DESYNC_INVALID || dp->desync_mode == DESYNC_INVALID || dp->desync_mode2 == DESYNC_INVALID) { DLOG_ERR("invalid dpi-desync mode\n"); exit_clean(1); } if (mode3) { DLOG_ERR("invalid desync combo : %s+%s+%s\n", mode, mode2, mode3); exit_clean(1); } if (dp->desync_mode2 && (desync_only_first_stage(dp->desync_mode) || !(desync_valid_first_stage(dp->desync_mode) && desync_valid_second_stage(dp->desync_mode2)))) { DLOG_ERR("invalid desync combo : %s+%s\n", mode, mode2); exit_clean(1); } #if defined(__OpenBSD__) if (dp->desync_mode == DESYNC_IPFRAG2 || dp->desync_mode2 == DESYNC_IPFRAG2) { DLOG_ERR("OpenBSD has checksum issues with fragmented packets. ipfrag disabled.\n"); exit_clean(1); } #endif } break; #if defined(__linux__) case IDX_DPI_DESYNC_FWMARK: #elif defined(SO_USER_COOKIE) case IDX_DPI_DESYNC_SOCKARG: #endif #if defined(__linux__) || defined(SO_USER_COOKIE) params.desync_fwmark = 0; if (sscanf(optarg, "0x%X", ¶ms.desync_fwmark) <= 0) sscanf(optarg, "%u", ¶ms.desync_fwmark); if (!params.desync_fwmark) { DLOG_ERR("fwmark/sockarg should be decimal or 0xHEX and should not be zero\n"); exit_clean(1); } break; #endif case IDX_DUP: if (sscanf(optarg, "%u", &dp->dup_repeats) < 1 || dp->dup_repeats > 1024) { DLOG_ERR("dup-repeats must be within 0..1024\n"); exit_clean(1); } break; case IDX_DUP_TTL: dp->dup_ttl = (uint8_t)atoi(optarg); break; case IDX_DUP_TTL6: dp->dup_ttl6 = (uint8_t)atoi(optarg); break; case IDX_DUP_AUTOTTL: if (!parse_autottl(optarg, &dp->dup_autottl, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX)) { DLOG_ERR("dup-autottl value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_DUP_AUTOTTL6: if (!parse_autottl(optarg, &dp->dup_autottl6, AUTOTTL_DEFAULT_DUP_DELTA, AUTOTTL_DEFAULT_DUP_MIN, AUTOTTL_DEFAULT_DUP_MAX)) { DLOG_ERR("dup-autottl6 value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_DUP_TCP_FLAGS_SET: if (!parse_tcpflags(optarg, &dp->dup_tcp_flags_set)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_DUP_TCP_FLAGS_UNSET: if (!parse_tcpflags(optarg, &dp->dup_tcp_flags_unset)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_DUP_REPLACE: dp->dup_replace = !optarg || atoi(optarg); break; case IDX_DUP_FOOLING: if (!parse_fooling(optarg, &dp->dup_fooling_mode)) { DLOG_ERR("fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\n"); exit_clean(1); } break; case IDX_DUP_TS_INCREMENT: if (!parse_net32_signed(optarg, &dp->dup_ts_increment)) { DLOG_ERR("dup-ts-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DUP_BADSEQ_INCREMENT: if (!parse_net32_signed(optarg, &dp->dup_badseq_increment)) { DLOG_ERR("dup-badseq-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DUP_BADACK_INCREMENT: if (!parse_net32_signed(optarg, &dp->dup_badseq_ack_increment)) { DLOG_ERR("dup-badack-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DUP_CUTOFF: if (!parse_cutoff(optarg, &dp->dup_cutoff, &dp->dup_cutoff_mode)) { DLOG_ERR("invalid dup-cutoff value\n"); exit_clean(1); } break; case IDX_DUP_START: if (!parse_cutoff(optarg, &dp->dup_start, &dp->dup_start_mode)) { DLOG_ERR("invalid dup-start value\n"); exit_clean(1); } break; case IDX_DUP_IP_ID: if (!strcmp(optarg,"zero")) dp->dup_ip_id_mode = IPID_ZERO; else if (!strcmp(optarg,"same")) dp->dup_ip_id_mode = IPID_SAME; else if (!strcmp(optarg,"seq")) dp->dup_ip_id_mode = IPID_SEQ; else if (!strcmp(optarg,"rnd")) dp->dup_ip_id_mode = IPID_RND; else { DLOG_ERR("invalid dup ip_id mode : %s\n",optarg); exit_clean(1); } break; case IDX_ORIG_TTL: dp->orig_mod_ttl = (uint8_t)atoi(optarg); break; case IDX_ORIG_TTL6: dp->orig_mod_ttl6 = (uint8_t)atoi(optarg); break; case IDX_ORIG_AUTOTTL: if (!parse_autottl(optarg, &dp->orig_autottl, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX)) { DLOG_ERR("orig-autottl value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_ORIG_AUTOTTL6: if (!parse_autottl(optarg, &dp->orig_autottl6, AUTOTTL_DEFAULT_ORIG_DELTA, AUTOTTL_DEFAULT_ORIG_MIN, AUTOTTL_DEFAULT_ORIG_MAX)) { DLOG_ERR("orig-autottl6 value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_ORIG_TCP_FLAGS_SET: if (!parse_tcpflags(optarg, &dp->orig_tcp_flags_set)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_ORIG_TCP_FLAGS_UNSET: if (!parse_tcpflags(optarg, &dp->orig_tcp_flags_unset)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_ORIG_MOD_CUTOFF: if (!parse_cutoff(optarg, &dp->orig_mod_cutoff, &dp->orig_mod_cutoff_mode)) { DLOG_ERR("invalid orig-mod-cutoff value\n"); exit_clean(1); } break; case IDX_ORIG_MOD_START: if (!parse_cutoff(optarg, &dp->orig_mod_start, &dp->orig_mod_start_mode)) { DLOG_ERR("invalid orig-mod-start value\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_TTL: dp->desync_ttl = (uint8_t)atoi(optarg); break; case IDX_DPI_DESYNC_TTL6: dp->desync_ttl6 = (uint8_t)atoi(optarg); break; case IDX_DPI_DESYNC_AUTOTTL: if (!parse_autottl(optarg, &dp->desync_autottl, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX)) { DLOG_ERR("dpi-desync-autottl value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_DPI_DESYNC_AUTOTTL6: if (!parse_autottl(optarg, &dp->desync_autottl6, AUTOTTL_DEFAULT_DESYNC_DELTA, AUTOTTL_DEFAULT_DESYNC_MIN, AUTOTTL_DEFAULT_DESYNC_MAX)) { DLOG_ERR("dpi-desync-autottl6 value error\n"); exit_clean(1); } params.autottl_present = true; break; case IDX_DPI_DESYNC_TCP_FLAGS_SET: if (!parse_tcpflags(optarg, &dp->desync_tcp_flags_set)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_TCP_FLAGS_UNSET: if (!parse_tcpflags(optarg, &dp->desync_tcp_flags_unset)) { DLOG_ERR("invalid tcp flags\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_FOOLING: if (!parse_fooling(optarg, &dp->desync_fooling_mode)) { DLOG_ERR("fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_REPEATS: if (sscanf(optarg, "%u", &dp->desync_repeats) < 1 || !dp->desync_repeats || dp->desync_repeats > 1024) { DLOG_ERR("dpi-desync-repeats must be within 1..1024\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_SKIP_NOSNI: dp->desync_skip_nosni = !optarg || atoi(optarg); break; case IDX_DPI_DESYNC_SPLIT_POS: { int ct; if (!parse_split_pos_list(optarg, dp->splits + dp->split_count, MAX_SPLITS - dp->split_count, &ct)) { DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n", dp->split_count, MAX_SPLITS, optarg); exit_clean(1); } dp->split_count += ct; break; } case IDX_DPI_DESYNC_SPLIT_HTTP_REQ: // obsolete arg DLOG_CONDUP("WARNING ! --dpi-desync-split-http-req is deprecated. use --dpi-desync-split-pos with markers.\n", MAX_SPLITS); if (dp->split_count >= MAX_SPLITS) { DLOG_ERR("Too much splits. max splits: %u\n", MAX_SPLITS); exit_clean(1); } if (!parse_httpreqpos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for dpi-desync-split-http-req\n"); exit_clean(1); } dp->split_count++; break; case IDX_DPI_DESYNC_SPLIT_TLS: // obsolete arg DLOG_CONDUP("WARNING ! --dpi-desync-split-tls is deprecated. use --dpi-desync-split-pos with markers.\n", MAX_SPLITS); if (dp->split_count >= MAX_SPLITS) { DLOG_ERR("Too much splits. max splits: %u\n", MAX_SPLITS); exit_clean(1); } if (!parse_tlspos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for dpi-desync-split-tls\n"); exit_clean(1); } dp->split_count++; break; case IDX_DPI_DESYNC_SPLIT_SEQOVL: if (!strcmp(optarg, "0")) { // allow zero = disable seqovl dp->seqovl.marker = PM_ABS; dp->seqovl.pos = 0; } else if (!parse_split_pos(optarg, &dp->seqovl)) { DLOG_ERR("Invalid argument for dpi-desync-split-seqovl\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_SPLIT_SEQOVL_PATTERN: { char buf[sizeof(dp->seqovl_pattern)]; size_t sz = sizeof(buf); load_file_or_exit(optarg, buf, &sz, NULL); fill_pattern(dp->seqovl_pattern, sizeof(dp->seqovl_pattern), buf, sz, 0); break; } case IDX_DPI_DESYNC_FAKEDSPLIT_PATTERN: { free(dp->fsplit_pattern); if (!(dp->fsplit_pattern = malloc(dp->fsplit_pattern_size=32768))) { DLOG_ERR("out of memory\n"); exit_clean(1); } load_file_or_exit(optarg, dp->fsplit_pattern, &dp->fsplit_pattern_size, NULL); dp->fsplit_pattern = realloc(dp->fsplit_pattern, dp->fsplit_pattern_size); break; } case IDX_DPI_DESYNC_FAKEDSPLIT_MOD: if (!parse_fakedsplit_mod(optarg, &dp->fs_mod)) { DLOG_ERR("Invalid fakedsplit mod : %s\n", optarg); exit_clean(1); } break; case IDX_DPI_DESYNC_HOSTFAKESPLIT_MIDHOST: if (!strcmp(optarg, "0")) { // allow zero = disable midhost split dp->hostfakesplit_midhost.marker = PM_ABS; dp->hostfakesplit_midhost.pos = 0; } else if (!parse_split_pos(optarg, &dp->hostfakesplit_midhost)) { DLOG_ERR("Invalid argument for dpi-desync-hostfakesplit-midhost\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_HOSTFAKESPLIT_MOD: if (!parse_hostfakesplit_mod(optarg, &dp->hfs_mod)) { DLOG_ERR("Invalid hostfakesplit mod : %s\n", optarg); exit_clean(1); } break; case IDX_DPI_DESYNC_IPFRAG_POS_TCP: if (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) { DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be within 1..%u range\n", DPI_DESYNC_MAX_FAKE_LEN); exit_clean(1); } if (dp->desync_ipfrag_pos_tcp & 7) { DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be multiple of 8\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_IPFRAG_POS_UDP: if (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) { DLOG_ERR("dpi-desync-ipfrag-pos-udp must be within 1..%u range\n", DPI_DESYNC_MAX_FAKE_LEN); exit_clean(1); } if (dp->desync_ipfrag_pos_udp & 7) { DLOG_ERR("dpi-desync-ipfrag-pos-udp must be multiple of 8\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_TS_INCREMENT: if (!parse_net32_signed(optarg, &dp->desync_ts_increment)) { DLOG_ERR("dpi-desync-ts-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_BADSEQ_INCREMENT: if (!parse_net32_signed(optarg, &dp->desync_badseq_increment)) { DLOG_ERR("dpi-desync-badseq-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_BADACK_INCREMENT: if (!parse_net32_signed(optarg, &dp->desync_badseq_ack_increment)) { DLOG_ERR("dpi-desync-badack-increment should be signed decimal or signed 0xHEX\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_ANY_PROTOCOL: dp->desync_any_proto = !optarg || atoi(optarg); break; case IDX_DPI_DESYNC_FAKE_TCP_MOD: if (!parse_tcpmod(optarg, &dp->tcp_mod)) { DLOG_ERR("Invalid tcp mod : %s\n", optarg); exit_clean(1); } break; case IDX_DPI_DESYNC_FAKE_HTTP: load_blob_to_collection(optarg, &dp->fake_http, FAKE_MAX_TCP, 0); break; case IDX_DPI_DESYNC_FAKE_TLS: { if (optarg[0] == '!' && (optarg[1] == 0 || optarg[1] == '+')) dp->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); else dp->tls_fake_last = load_blob_to_collection(optarg, &dp->fake_tls, FAKE_MAX_TCP, 4 + sizeof(dp->tls_mod_last.sni)); if (!(dp->tls_fake_last->extra2 = malloc(sizeof(struct fake_tls_mod)))) { DLOG_ERR("out of memory\n"); exit_clean(1); } struct fake_tls_mod *tls_mod = (struct fake_tls_mod*)dp->tls_fake_last->extra2; *tls_mod = dp->tls_mod_last; tls_mod->mod |= FAKE_TLS_MOD_CUSTOM_FAKE; } break; case IDX_DPI_DESYNC_FAKE_TLS_MOD: if (!parse_tlsmod_list(optarg, &dp->tls_mod_last)) { DLOG_ERR("Invalid tls mod : %s\n", optarg); exit_clean(1); } if (dp->tls_fake_last) *(struct fake_tls_mod*)dp->tls_fake_last->extra2 = dp->tls_mod_last; break; case IDX_DPI_DESYNC_FAKE_UNKNOWN: load_blob_to_collection(optarg, &dp->fake_unknown, FAKE_MAX_TCP, 0); break; case IDX_DPI_DESYNC_FAKE_SYNDATA: dp->fake_syndata_size = sizeof(dp->fake_syndata); load_file_or_exit(optarg, dp->fake_syndata, &dp->fake_syndata_size, NULL); break; case IDX_DPI_DESYNC_FAKE_QUIC: load_blob_to_collection(optarg, &dp->fake_quic, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_FAKE_WIREGUARD: load_blob_to_collection(optarg, &dp->fake_wg, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_FAKE_DHT: load_blob_to_collection(optarg, &dp->fake_dht, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_FAKE_DISCORD: load_blob_to_collection(optarg, &dp->fake_discord, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_FAKE_STUN: load_blob_to_collection(optarg, &dp->fake_stun, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_FAKE_UNKNOWN_UDP: load_blob_to_collection(optarg, &dp->fake_unknown_udp, FAKE_MAX_UDP, 0); break; case IDX_DPI_DESYNC_UDPLEN_INCREMENT: if (sscanf(optarg, "%d", &dp->udplen_increment) < 1 || dp->udplen_increment > 0x7FFF || dp->udplen_increment < -0x8000) { DLOG_ERR("dpi-desync-udplen-increment must be integer within -32768..32767 range\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_UDPLEN_PATTERN: { char buf[sizeof(dp->udplen_pattern)]; size_t sz = sizeof(buf); load_file_or_exit(optarg, buf, &sz, NULL); fill_pattern(dp->udplen_pattern, sizeof(dp->udplen_pattern), buf, sz, 0); } break; case IDX_DPI_DESYNC_CUTOFF: if (!parse_cutoff(optarg, &dp->desync_cutoff, &dp->desync_cutoff_mode)) { DLOG_ERR("invalid desync-cutoff value\n"); exit_clean(1); } break; case IDX_DPI_DESYNC_START: if (!parse_cutoff(optarg, &dp->desync_start, &dp->desync_start_mode)) { DLOG_ERR("invalid desync-start value\n"); exit_clean(1); } break; case IDX_HOSTLIST: if (bSkip) break; if (!RegisterHostlist(dp, false, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_DOMAINS: if (bSkip) break; if (!anon_hl && !(anon_hl = RegisterHostlist(dp, false, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } break; case IDX_HOSTLIST_EXCLUDE: if (bSkip) break; if (!RegisterHostlist(dp, true, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_EXCLUDE_DOMAINS: if (bSkip) break; if (!anon_hl_exclude && !(anon_hl_exclude = RegisterHostlist(dp, true, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl_exclude->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO: if (bSkip) break; if (dp->hostlist_auto) { DLOG_ERR("only one auto hostlist per profile is supported\n"); exit_clean(1); } { FILE *F = fopen(optarg, "a+b"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } bool bGzip = is_gzip(F); fclose(F); if (bGzip) { DLOG_ERR("gzipped auto hostlists are not supported\n"); exit_clean(1); } } if (!(dp->hostlist_auto = RegisterHostlist(dp, false, optarg))) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_FAIL_THRESHOLD: dp->hostlist_auto_fail_threshold = atoi(optarg); if (dp->hostlist_auto_fail_threshold < 1 || dp->hostlist_auto_fail_threshold>20) { DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_FAIL_TIME: dp->hostlist_auto_fail_time = atoi(optarg); if (dp->hostlist_auto_fail_time < 1) { DLOG_ERR("auto hostlist fail time is not valid\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD: dp->hostlist_auto_retrans_threshold = atoi(optarg); if (dp->hostlist_auto_retrans_threshold < 2 || dp->hostlist_auto_retrans_threshold>10) { DLOG_ERR("auto hostlist fail threshold must be within 2..10\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_DEBUG: { FILE *F = fopen(optarg, "a+t"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } fclose(F); strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; } break; case IDX_NEW: if (bSkip) { dp_clear(dp); dp_init(dp); dp->n = desync_profile_count; bSkip = false; } else { check_dp(dp); if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = ++desync_profile_count; } anon_hl = anon_hl_exclude = NULL; anon_ips = anon_ips_exclude = NULL; break; case IDX_SKIP: bSkip = true; break; case IDX_FILTER_L3: if (!wf_make_l3(optarg, &dp->filter_ipv4, &dp->filter_ipv6)) { DLOG_ERR("bad value for --filter-l3\n"); exit_clean(1); } break; case IDX_FILTER_TCP: if (!parse_pf_list(optarg, &dp->pf_tcp)) { DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } // deny tcp if not set if (!port_filters_deny_if_empty(&dp->pf_udp)) exit_clean(1); break; case IDX_FILTER_UDP: if (!parse_pf_list(optarg, &dp->pf_udp)) { DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } // deny tcp if not set if (!port_filters_deny_if_empty(&dp->pf_tcp)) exit_clean(1); break; case IDX_FILTER_L7: if (!parse_l7_list(optarg, &dp->filter_l7)) { DLOG_ERR("Invalid l7 filter : %s\n", optarg); exit_clean(1); } break; #ifdef HAS_FILTER_SSID case IDX_FILTER_SSID: if (!parse_strlist(optarg, &dp->filter_ssid)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } params.filter_ssid_present = true; break; #endif case IDX_IPSET: if (bSkip) break; if (!RegisterIpset(dp, false, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } break; case IDX_IPSET_IP: if (bSkip) break; if (!anon_ips && !(anon_ips = RegisterIpset(dp, false, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } break; case IDX_IPSET_EXCLUDE: if (bSkip) break; if (!RegisterIpset(dp, true, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } break; case IDX_IPSET_EXCLUDE_IP: if (bSkip) break; if (!anon_ips_exclude && !(anon_ips_exclude = RegisterIpset(dp, true, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips_exclude->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } break; #ifdef __linux__ case IDX_BIND_FIX4: params.bind_fix4 = true; break; case IDX_BIND_FIX6: params.bind_fix6 = true; break; #elif defined(__CYGWIN__) case IDX_WF_IFACE: if (!sscanf(optarg, "%u.%u", &IfIdx, &SubIfIdx)) { DLOG_ERR("bad value for --wf-iface\n"); exit_clean(1); } break; case IDX_WF_L3: if (!wf_make_l3(optarg, &wf_ipv4, &wf_ipv6)) { DLOG_ERR("bad value for --wf-l3\n"); exit_clean(1); } break; case IDX_WF_TCP: hash_wf_tcp = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "tcp", "SrcPort", wf_pf_tcp_src, sizeof(wf_pf_tcp_src)) || !wf_make_pf(optarg, "tcp", "DstPort", wf_pf_tcp_dst, sizeof(wf_pf_tcp_dst))) { DLOG_ERR("bad value for --wf-tcp\n"); exit_clean(1); } break; case IDX_WF_UDP: hash_wf_udp = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "udp", "SrcPort", wf_pf_udp_src, sizeof(wf_pf_udp_src)) || !wf_make_pf(optarg, "udp", "DstPort", wf_pf_udp_dst, sizeof(wf_pf_udp_dst))) { DLOG_ERR("bad value for --wf-udp\n"); exit_clean(1); } break; case IDX_WF_RAW: hash_wf_raw = hash_jen(optarg, strlen(optarg)); if (optarg[0] == '@') { size_t sz = sizeof(windivert_filter) - 1; load_file_or_exit(optarg, windivert_filter, &sz, NULL); windivert_filter[sz] = 0; } else { strncpy(windivert_filter, optarg, sizeof(windivert_filter)); windivert_filter[sizeof(windivert_filter) - 1] = '\0'; } break; case IDX_WF_RAW_PART: hash_wf_raw_part ^= hash_jen(optarg, strlen(optarg)); { char wfpart[sizeof(windivert_filter)]; if (optarg[0] == '@') { size_t sz = sizeof(wfpart) - 1; load_file_or_exit(optarg, wfpart, &sz, NULL); wfpart[sz] = 0; } else { strncpy(wfpart, optarg, sizeof(wfpart)); wfpart[sizeof(wfpart) - 1] = '\0'; } if (!strlist_add(¶ms.wf_raw_part, wfpart)) { DLOG_ERR("out of memory\n"); exit_clean(1); } } break; case IDX_WF_FILTER_LAN: wf_filter_lan = !!atoi(optarg); break; case IDX_WF_SAVE: strncpy(wf_save_file, optarg, sizeof(wf_save_file)); wf_save_file[sizeof(wf_save_file) - 1] = '\0'; break; case IDX_SSID_FILTER: hash_ssid_filter = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.ssid_filter)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } break; case IDX_NLM_FILTER: hash_nlm_filter = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.nlm_filter)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } break; case IDX_NLM_LIST: if (!nlm_list(optarg && !strcmp(optarg, "all"))) { DLOG_ERR("could not get list of NLM networks\n"); exit_clean(1); } exit_clean(0); #endif } } if (bSkip) { LIST_REMOVE(dpl, next); dp_entry_destroy(dpl); desync_profile_count--; } else check_dp(dp); // do not need args from file anymore #if !defined( __OpenBSD__) && !defined(__ANDROID__) cleanup_args(¶ms); #endif argv = NULL; argc = 0; #ifdef __linux__ if (params.qnum < 0) { DLOG_ERR("Need queue number (--qnum)\n"); exit_clean(1); } #elif defined(BSD) if (!params.port) { DLOG_ERR("Need divert port (--port)\n"); exit_clean(1); } #endif DLOG("adding low-priority default empty desync profile\n"); // add default empty profile if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n", desync_profile_count); #ifndef __CYGWIN__ if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1)) fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1)) DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); #endif LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { dp = &dpl->dp; // not specified - use desync_ttl value instead if (dp->desync_ttl6 == 0xFF) dp->desync_ttl6 = dp->desync_ttl; if (dp->dup_ttl6 == 0xFF) dp->dup_ttl6 = dp->dup_ttl; if (dp->orig_mod_ttl6 == 0xFF) dp->orig_mod_ttl6 = dp->orig_mod_ttl; if (!dp->fsplit_pattern) { if ((dp->fsplit_pattern=calloc(64,1))) dp->fsplit_pattern_size=64; else { DLOG_ERR("out of memory\n"); exit_clean(1); } } if (!AUTOTTL_ENABLED(dp->desync_autottl6)) dp->desync_autottl6 = dp->desync_autottl; if (!AUTOTTL_ENABLED(dp->orig_autottl6)) dp->orig_autottl6 = dp->orig_autottl; if (!AUTOTTL_ENABLED(dp->dup_autottl6)) dp->dup_autottl6 = dp->dup_autottl; if (AUTOTTL_ENABLED(dp->desync_autottl)) DLOG("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); if (AUTOTTL_ENABLED(dp->desync_autottl6)) DLOG("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); if (AUTOTTL_ENABLED(dp->orig_autottl)) DLOG("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); if (AUTOTTL_ENABLED(dp->orig_autottl6)) DLOG("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); if (AUTOTTL_ENABLED(dp->dup_autottl)) DLOG("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); if (AUTOTTL_ENABLED(dp->dup_autottl6)) DLOG("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); split_compat(dp); if (!dp_fake_defaults(dp)) { DLOG_ERR("could not fill fake defaults\n"); exit_clean(1); } if (!onetime_tls_mod(dp)) { DLOG_ERR("could not mod tls\n"); exit_clean(1); } #ifndef __CYGWIN__ if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1)) DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); #endif } if (!test_list_files()) exit_clean(1); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed\n"); exit_clean(1); } if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed\n"); exit_clean(1); } DLOG("\nlists summary:\n"); HostlistsDebug(); IpsetsDebug(); DLOG("\nsplits summary:\n"); SplitDebug(); DLOG("\n"); #ifdef __CYGWIN__ if (!*windivert_filter) { if (!*wf_pf_tcp_src && !*wf_pf_udp_src && LIST_EMPTY(¶ms.wf_raw_part)) { DLOG_ERR("windivert filter : must specify port or/and partial raw filter\n"); exit_clean(1); } if (!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, ¶ms.wf_raw_part, wf_filter_lan)) { DLOG_ERR("windivert filter : could not make filter\n"); exit_clean(1); } } DLOG("windivert filter size: %zu\nwindivert filter:\n%s\n", strlen(windivert_filter), windivert_filter); if (*wf_save_file) { if (save_file(wf_save_file, windivert_filter, strlen(windivert_filter))) { DLOG_ERR("windivert filter: raw filter saved to %s\n", wf_save_file); exit_clean(0); } else { DLOG_ERR("windivert filter: could not save raw filter to %s\n", wf_save_file); exit_clean(1); } } HANDLE hMutexArg; { char mutex_name[128]; snprintf(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); hMutexArg = CreateMutexA(NULL, TRUE, mutex_name); if (hMutexArg && GetLastError() == ERROR_ALREADY_EXISTS) { CloseHandle(hMutexArg); hMutexArg = NULL; DLOG_ERR("A copy of winws is already running with the same filter\n"); goto exiterr; } } #endif if (bDry) { #ifndef __CYGWIN__ if (params.droproot) { if (!droproot(params.uid, params.user, params.gid, params.gid_count)) exit_clean(1); #ifdef __linux__ if (!dropcaps()) exit_clean(1); #endif print_id(); if (!test_list_files()) exit_clean(1); } #endif DLOG_CONDUP("command line parameters verified\n"); exit_clean(0); } if (params.ctrack_disable) DLOG_CONDUP("conntrack disabled ! some functions will not work. make sure it's what you want.\n"); else { DLOG("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); ConntrackPoolInit(¶ms.conntrack, 10, params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp); } if (params.autottl_present || params.cache_hostname) DLOG("ipcache lifetime %us\n", params.ipcache_lifetime); #ifdef __linux__ result = nfq_main(); #elif defined(BSD) result = dvt_main(); #elif defined(__CYGWIN__) result = win_main(windivert_filter); #else #error unsupported OS #endif ex: rawsend_cleanup(); cleanup_params(¶ms); #ifdef __CYGWIN__ if (hMutexArg) { ReleaseMutex(hMutexArg); CloseHandle(hMutexArg); } #endif return result; exiterr: result = 1; goto ex; } ================================================ FILE: nfq/nfqws.h ================================================ #pragma once #include #ifdef __linux__ #define HAS_FILTER_SSID 1 #endif #ifdef __CYGWIN__ extern bool bQuit; #endif int main(int argc, char *argv[]); ================================================ FILE: nfq/packet_queue.c ================================================ #include #include #include #include "packet_queue.h" void rawpacket_queue_init(struct rawpacket_tailhead *q) { TAILQ_INIT(q); } void rawpacket_free(struct rawpacket *rp) { if (rp) free(rp->packet); free(rp); } struct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q) { struct rawpacket *rp; rp = TAILQ_FIRST(q); if (rp) TAILQ_REMOVE(q, rp, next); return rp; } void rawpacket_queue_destroy(struct rawpacket_tailhead *q) { struct rawpacket *rp; while((rp = rawpacket_dequeue(q))) rawpacket_free(rp); } struct 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) { struct rawpacket *rp = malloc(sizeof(struct rawpacket)); if (!rp) return NULL; rp->packet = malloc(len); if (!rp->packet) { free(rp); return NULL; } rp->dst = *dst; rp->fwmark = fwmark; if (ifin) snprintf(rp->ifin,sizeof(rp->ifin),"%s",ifin); else *rp->ifin = 0; if (ifout) snprintf(rp->ifout,sizeof(rp->ifout),"%s",ifout); else *rp->ifout = 0; memcpy(rp->packet,data,len); rp->len=len; rp->len_payload=len_payload; TAILQ_INSERT_TAIL(q, rp, next); return rp; } unsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q) { const struct rawpacket *rp; unsigned int ct=0; TAILQ_FOREACH(rp, q, next) ct++; return ct; } bool rawpacket_queue_empty(const struct rawpacket_tailhead *q) { return !TAILQ_FIRST(q); } ================================================ FILE: nfq/packet_queue.h ================================================ #pragma once #include #include #include #include #include struct rawpacket { struct sockaddr_storage dst; char ifin[IFNAMSIZ], ifout[IFNAMSIZ]; uint32_t fwmark; size_t len, len_payload; uint8_t *packet; TAILQ_ENTRY(rawpacket) next; }; TAILQ_HEAD(rawpacket_tailhead, rawpacket); void rawpacket_queue_init(struct rawpacket_tailhead *q); void rawpacket_queue_destroy(struct rawpacket_tailhead *q); bool rawpacket_queue_empty(const struct rawpacket_tailhead *q); unsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q); struct 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); struct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q); void rawpacket_free(struct rawpacket *rp); ================================================ FILE: nfq/params.c ================================================ #include "params.h" #include #include #include #ifdef __ANDROID__ #include #endif #include "pools.h" #include "desync.h" #ifdef BSD const char *progname = "dvtws"; #elif defined(__CYGWIN__) const char *progname = "winws"; #elif defined(__linux__) const char *progname = "nfqws"; #else #error UNKNOWN_SYSTEM_TIME #endif const char * tld[6] = { "com","org","net","edu","gov","biz" }; int DLOG_FILE(FILE *F, const char *format, va_list args) { return vfprintf(F, format, args); } int DLOG_CON(const char *format, int syslog_priority, va_list args) { return DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args); } int DLOG_FILENAME(const char *filename, const char *format, va_list args) { int r; FILE *F = fopen(filename,"at"); if (F) { r = DLOG_FILE(F, format, args); fclose(F); } else r=-1; return r; } typedef void (*f_log_function)(int priority, const char *line); static char log_buf[1024]; static size_t log_buf_sz=0; static void syslog_log_function(int priority, const char *line) { syslog(priority,"%s",line); } #ifdef __ANDROID__ static enum android_LogPriority syslog_priority_to_android(int priority) { enum android_LogPriority ap; switch(priority) { case LOG_INFO: case LOG_NOTICE: ap=ANDROID_LOG_INFO; break; case LOG_ERR: ap=ANDROID_LOG_ERROR; break; case LOG_WARNING: ap=ANDROID_LOG_WARN; break; case LOG_EMERG: case LOG_ALERT: case LOG_CRIT: ap=ANDROID_LOG_FATAL; break; case LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break; default: ap=ANDROID_LOG_UNKNOWN; } return ap; } static void android_log_function(int priority, const char *line) { __android_log_print(syslog_priority_to_android(priority), progname, "%s", line); } #endif static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args) { if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0) { log_buf_sz=strlen(log_buf); // log when buffer is full or buffer ends with \n if (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\n')) { log_function(syslog_priority,log_buf); log_buf_sz = 0; } } } static int DLOG_VA(const char *format, int syslog_priority, bool condup, va_list args) { int r=0; va_list args2; if (condup && !(params.debug && params.debug_target==LOG_TARGET_CONSOLE)) { va_copy(args2,args); DLOG_CON(format,syslog_priority,args2); va_end(args2); } if (params.debug) { switch(params.debug_target) { case LOG_TARGET_CONSOLE: r = DLOG_CON(format,syslog_priority,args); break; case LOG_TARGET_FILE: r = DLOG_FILENAME(params.debug_logfile,format,args); break; case LOG_TARGET_SYSLOG: // skip newlines log_buffered(syslog_log_function,syslog_priority,format,args); r = 1; break; #ifdef __ANDROID__ case LOG_TARGET_ANDROID: // skip newlines log_buffered(android_log_function,syslog_priority,format,args); r = 1; break; #endif default: break; } } return r; } int DLOG(const char *format, ...) { int r; va_list args; va_start(args, format); r = DLOG_VA(format, LOG_DEBUG, false, args); va_end(args); return r; } int DLOG_CONDUP(const char *format, ...) { int r; va_list args; va_start(args, format); r = DLOG_VA(format, LOG_DEBUG, true, args); va_end(args); return r; } int DLOG_ERR(const char *format, ...) { int r; va_list args; va_start(args, format); r = DLOG_VA(format, LOG_ERR, true, args); va_end(args); return r; } int DLOG_PERROR(const char *s) { return DLOG_ERR("%s: %s\n", s, strerror(errno)); } int LOG_APPEND(const char *filename, const char *format, va_list args) { int r; FILE *F = fopen(filename,"at"); if (F) { fprint_localtime(F); fprintf(F, " : "); r = vfprintf(F, format, args); fprintf(F, "\n"); fclose(F); } else r=-1; return r; } int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) { if (*params.hostlist_auto_debuglog) { int r; va_list args; va_start(args, format); r = LOG_APPEND(params.hostlist_auto_debuglog, format, args); va_end(args); return r; } else return 0; } void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) { size_t k; bool bcut = false; if (size > limit) { size = limit; bcut = true; } if (!size) return; for (k = 0; k < size; k++) DLOG("%02X ", data[k]); DLOG(bcut ? "... : " : ": "); for (k = 0; k < size; k++) DLOG("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.'); if (bcut) DLOG(" ..."); } void dp_init(struct desync_profile *dp) { LIST_INIT(&dp->hl_collection); LIST_INIT(&dp->hl_collection_exclude); LIST_INIT(&dp->ips_collection); LIST_INIT(&dp->ips_collection_exclude); LIST_INIT(&dp->pf_tcp); LIST_INIT(&dp->pf_udp); #ifdef HAS_FILTER_SSID LIST_INIT(&dp->filter_ssid); #endif memcpy(dp->hostspell, "host", 4); // default hostspell dp->desync_skip_nosni = true; dp->desync_ipfrag_pos_udp = IPFRAG_UDP_DEFAULT; dp->desync_ipfrag_pos_tcp = IPFRAG_TCP_DEFAULT; dp->desync_repeats = 1; dp->fake_syndata_size = 16; dp->wscale=-1; // default - dont change scale factor (client) dp->desync_ttl6 = dp->dup_ttl6 = dp->orig_mod_ttl6 = 0xFF; // unused dp->desync_ts_increment = dp->dup_ts_increment = TS_INCREMENT_DEFAULT; dp->desync_badseq_increment = dp->dup_badseq_increment = BADSEQ_INCREMENT_DEFAULT; dp->desync_badseq_ack_increment = dp->dup_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT; dp->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 dp->udplen_increment = UDPLEN_INCREMENT_DEFAULT; dp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; dp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; dp->hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT; dp->filter_ipv4 = dp->filter_ipv6 = true; dp->dup_ip_id_mode = IPID_SAME; } bool dp_fake_defaults(struct desync_profile *dp) { struct blob_item *item; if (blob_collection_empty(&dp->fake_http)) if (!blob_collection_add_blob(&dp->fake_http,fake_http_request_default,strlen(fake_http_request_default),0,0)) return false; if (blob_collection_empty(&dp->fake_tls)) { if (!(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))) return false; if (!(item->extra2 = malloc(sizeof(struct fake_tls_mod)))) return false; *(struct fake_tls_mod*)item->extra2 = dp->tls_mod_last; } if (blob_collection_empty(&dp->fake_unknown)) { if (!(item=blob_collection_add_blob(&dp->fake_unknown,NULL,256,0,0))) return false; memset(item->data,0,item->size); } if (blob_collection_empty(&dp->fake_quic)) { if (!(item=blob_collection_add_blob(&dp->fake_quic,NULL,620,0,0))) return false; memset(item->data,0,item->size); item->data[0] = 0x40; } struct blob_collection_head **fake,*fakes_z64[] = {&dp->fake_wg, &dp->fake_dht, &dp->fake_discord, &dp->fake_stun, &dp->fake_unknown_udp,NULL}; for(fake=fakes_z64;*fake;fake++) { if (blob_collection_empty(*fake)) { if (!(item=blob_collection_add_blob(*fake,NULL,64,0,0))) return false; memset(item->data,0,item->size); } } return true; } struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) { struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list)); if (!entry) return NULL; dp_init(&entry->dp); // add to the tail struct desync_profile_list *dpn,*dpl=LIST_FIRST(head); if (dpl) { while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; LIST_INSERT_AFTER(dpl, entry, next); } else LIST_INSERT_HEAD(head, entry, next); return entry; } static void dp_clear_dynamic(struct desync_profile *dp) { free(dp->fsplit_pattern); hostlist_collection_destroy(&dp->hl_collection); hostlist_collection_destroy(&dp->hl_collection_exclude); ipset_collection_destroy(&dp->ips_collection); ipset_collection_destroy(&dp->ips_collection_exclude); port_filters_destroy(&dp->pf_tcp); port_filters_destroy(&dp->pf_udp); #ifdef HAS_FILTER_SSID strlist_destroy(&dp->filter_ssid); #endif HostFailPoolDestroy(&dp->hostlist_auto_fail_counters); struct 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}; for(fake=fakes;*fake;fake++) blob_collection_destroy(*fake); } void dp_clear(struct desync_profile *dp) { dp_clear_dynamic(dp); memset(dp,0,sizeof(*dp)); } void dp_entry_destroy(struct desync_profile_list *entry) { dp_clear_dynamic(&entry->dp); free(entry); } void dp_list_destroy(struct desync_profile_list_head *head) { struct desync_profile_list *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); dp_entry_destroy(entry); } } bool dp_list_have_autohostlist(struct desync_profile_list_head *head) { struct desync_profile_list *dpl; LIST_FOREACH(dpl, head, next) if (dpl->dp.hostlist_auto) return true; return false; } // check if we need empty outgoing ACK bool dp_list_need_all_out(struct desync_profile_list_head *head) { struct desync_profile_list *dpl; LIST_FOREACH(dpl, head, next) if (dpl->dp.dup_repeats || PROFILE_HAS_ORIG_MOD(&dpl->dp)) return true; return false; } #if !defined( __OpenBSD__) && !defined(__ANDROID__) void cleanup_args(struct params_s *params) { wordfree(¶ms->wexp); } #endif void cleanup_params(struct params_s *params) { #if !defined( __OpenBSD__) && !defined(__ANDROID__) cleanup_args(params); #endif ConntrackPoolDestroy(¶ms->conntrack); dp_list_destroy(¶ms->desync_profiles); hostlist_files_destroy(¶ms->hostlists); ipset_files_destroy(¶ms->ipsets); ipcacheDestroy(¶ms->ipcache); #ifdef __CYGWIN__ strlist_destroy(¶ms->ssid_filter); strlist_destroy(¶ms->nlm_filter); strlist_destroy(¶ms->wf_raw_part); #else free(params->user); params->user=NULL; #endif } ================================================ FILE: nfq/params.h ================================================ #pragma once #include "nfqws.h" #include "pools.h" #include "conntrack.h" #include "desync.h" #include "protocol.h" #include "helpers.h" #include #include #include #include #include #include #include #include #if !defined( __OpenBSD__) && !defined(__ANDROID__) #include #endif #define TLS_PARTIALS_ENABLE true #define RAW_SNDBUF (64*1024) // in bytes #define Q_MAXLEN 4986 // in packets #define Q_RCVBUF (1024*1024) // in bytes #define BADSEQ_INCREMENT_DEFAULT -10000 #define BADSEQ_ACK_INCREMENT_DEFAULT -66000 #define TS_INCREMENT_DEFAULT -600000 #define IPFRAG_UDP_DEFAULT 8 #define IPFRAG_TCP_DEFAULT 32 #define UDPLEN_INCREMENT_DEFAULT 2 #define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 #define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 #define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3 #define IPCACHE_LIFETIME 7200 #define AUTOTTL_DEFAULT_DESYNC_DELTA -1 #define AUTOTTL_DEFAULT_DESYNC_MIN 3 #define AUTOTTL_DEFAULT_DESYNC_MAX 20 #define AUTOTTL_DEFAULT_ORIG_DELTA +5 #define AUTOTTL_DEFAULT_ORIG_MIN 3 #define AUTOTTL_DEFAULT_ORIG_MAX 64 #define AUTOTTL_DEFAULT_DUP_DELTA -1 #define AUTOTTL_DEFAULT_DUP_MIN 3 #define AUTOTTL_DEFAULT_DUP_MAX 64 #define MAX_SPLITS 64 #define FAKE_TLS_MOD_SAVE_MASK 0x0F #define FAKE_TLS_MOD_SET 0x01 #define FAKE_TLS_MOD_CUSTOM_FAKE 0x02 #define FAKE_TLS_MOD_RND 0x10 #define FAKE_TLS_MOD_DUP_SID 0x20 #define FAKE_TLS_MOD_RND_SNI 0x40 #define FAKE_TLS_MOD_SNI 0x80 #define FAKE_TLS_MOD_PADENCAP 0x100 #define FAKE_MAX_TCP 1460 #define FAKE_MAX_UDP 1472 #define MAX_GIDS 64 extern const char * tld[6]; enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID }; struct fake_tls_mod_cache { size_t extlen_offset, padlen_offset; }; struct fake_tls_mod { char sni[128]; uint32_t mod; }; struct hostfakesplit_mod { char host[128]; size_t host_size; int ordering; }; struct fakedsplit_mod { int ordering; }; struct tcp_mod { bool seq; }; typedef enum {SS_NONE=0,SS_SYN,SS_SYNACK,SS_ACKSYN} t_synack_split; typedef enum {IPID_SEQ=0,IPID_SEQ_GROUP,IPID_RND,IPID_ZERO,IPID_SAME} t_ip_id_mode; struct desync_profile { int n; // number of the profile uint16_t wsize,wssize; uint8_t wscale,wsscale; char wssize_cutoff_mode; // n - packets, d - data packets, s - relative sequence bool wssize_no_forced_cutoff; unsigned int wssize_cutoff; t_synack_split synack_split; t_ip_id_mode ip_id_mode; bool hostcase, hostnospace, domcase, methodeol; char hostspell[4]; enum dpi_desync_mode desync_mode0,desync_mode,desync_mode2; bool desync_retrans,desync_skip_nosni,desync_any_proto; unsigned int desync_repeats,desync_ipfrag_pos_tcp,desync_ipfrag_pos_udp; // multisplit struct proto_pos splits[MAX_SPLITS]; int split_count; struct proto_pos seqovl,hostfakesplit_midhost; char dup_start_mode, dup_cutoff_mode; // n - packets, d - data packets, s - relative sequence bool dup_replace; unsigned int dup_start, dup_cutoff; unsigned int dup_repeats; uint8_t dup_ttl, dup_ttl6; uint32_t dup_fooling_mode; uint32_t dup_ts_increment, dup_badseq_increment, dup_badseq_ack_increment; autottl dup_autottl, dup_autottl6; uint16_t dup_tcp_flags_set, dup_tcp_flags_unset; t_ip_id_mode dup_ip_id_mode; char orig_mod_start_mode, orig_mod_cutoff_mode; // n - packets, d - data packets, s - relative sequence unsigned int orig_mod_start, orig_mod_cutoff; uint8_t orig_mod_ttl, orig_mod_ttl6; autottl orig_autottl, orig_autottl6; uint16_t orig_tcp_flags_set, orig_tcp_flags_unset; char desync_start_mode, desync_cutoff_mode; // n - packets, d - data packets, s - relative sequence unsigned int desync_start, desync_cutoff; uint8_t desync_ttl, desync_ttl6; autottl desync_autottl, desync_autottl6; uint32_t desync_fooling_mode; uint32_t desync_ts_increment, desync_badseq_increment, desync_badseq_ack_increment; uint16_t desync_tcp_flags_set, desync_tcp_flags_unset; struct blob_collection_head fake_http,fake_tls,fake_unknown,fake_unknown_udp,fake_quic,fake_wg,fake_dht,fake_discord,fake_stun; uint8_t fake_syndata[FAKE_MAX_TCP],seqovl_pattern[FAKE_MAX_TCP],udplen_pattern[FAKE_MAX_UDP]; uint8_t *fsplit_pattern; size_t fake_syndata_size, fsplit_pattern_size; struct fake_tls_mod tls_mod_last; struct blob_item *tls_fake_last; struct hostfakesplit_mod hfs_mod; struct fakedsplit_mod fs_mod; struct tcp_mod tcp_mod; int udplen_increment; bool filter_ipv4,filter_ipv6; struct port_filters_head pf_tcp,pf_udp; uint32_t filter_l7; // L7_PROTO_* bits #ifdef HAS_FILTER_SSID // per profile ssid filter // annot use global filter because it's not possible to bind multiple instances to a single queue // it's possible to run multiple winws instances on the same windivert filter, but it's not the case for linux struct str_list_head filter_ssid; #endif // list of pointers to ipsets struct ipset_collection_head ips_collection, ips_collection_exclude; // list of pointers to hostlist files struct hostlist_collection_head hl_collection, hl_collection_exclude; // pointer to autohostlist. NULL if no autohostlist for the profile. struct hostlist_file *hostlist_auto; int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold; hostfail_pool *hostlist_auto_fail_counters; }; #define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&(dp)->ips_collection) && !LIST_FIRST(&(dp)->ips_collection_exclude)) #define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&(dp)->ips_collection) && ipset_collection_is_empty(&(dp)->ips_collection_exclude)) #define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&(dp)->hl_collection) && hostlist_collection_is_empty(&(dp)->hl_collection_exclude)) #define PROFILE_HAS_ORIG_MOD(dp) ((dp)->orig_mod_ttl || (dp)->orig_mod_ttl6 || (dp)->orig_tcp_flags_set || (dp)->orig_tcp_flags_unset) struct desync_profile_list { struct desync_profile dp; LIST_ENTRY(desync_profile_list) next; }; LIST_HEAD(desync_profile_list_head, desync_profile_list); struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head); void dp_entry_destroy(struct desync_profile_list *entry); void dp_list_destroy(struct desync_profile_list_head *head); bool dp_list_have_autohostlist(struct desync_profile_list_head *head); bool dp_list_need_all_out(struct desync_profile_list_head *head); void dp_init(struct desync_profile *dp); bool dp_fake_defaults(struct desync_profile *dp); void dp_clear(struct desync_profile *dp); struct params_s { #if !defined( __OpenBSD__) && !defined(__ANDROID__) wordexp_t wexp; // for file based config #endif enum log_target debug_target; char debug_logfile[PATH_MAX]; bool debug; bool daemon; #ifdef __linux__ int qnum; #elif defined(BSD) uint16_t port; // divert port #endif char bind_fix4,bind_fix6; uint32_t desync_fwmark; // unused in BSD struct desync_profile_list_head desync_profiles; #ifdef __CYGWIN__ struct str_list_head ssid_filter,nlm_filter; struct str_list_head wf_raw_part; #else bool droproot; char *user; uid_t uid; gid_t gid[MAX_GIDS]; int gid_count; #endif char pidfile[PATH_MAX]; char hostlist_auto_debuglog[PATH_MAX]; // hostlist files with data for all profiles struct hostlist_files_head hostlists; // ipset files with data for all profiles struct ipset_files_head ipsets; unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp; t_conntrack conntrack; bool ctrack_disable; bool autottl_present; #ifdef HAS_FILTER_SSID bool filter_ssid_present; #endif bool cache_hostname; unsigned int ipcache_lifetime; ip_cache ipcache; }; extern struct params_s params; extern const char *progname; #if !defined( __OpenBSD__) && !defined(__ANDROID__) void cleanup_args(struct params_s *params); #endif void cleanup_params(struct params_s *params); int DLOG(const char *format, ...); int DLOG_ERR(const char *format, ...); int DLOG_PERROR(const char *s); int DLOG_CONDUP(const char *format, ...); int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); ================================================ FILE: nfq/pools.c ================================================ #define _GNU_SOURCE #include "pools.h" #include #include #include #include #define DESTROY_STR_POOL(etype, ppool) \ etype *elem, *tmp; \ HASH_ITER(hh, *ppool, elem, tmp) { \ free(elem->str); \ HASH_DEL(*ppool, elem); \ free(elem); \ } #define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \ etype *elem; \ if (!(elem = (etype*)malloc(sizeof(etype)))) \ return false; \ if (!(elem->str = malloc(keystr_len + 1))) \ { \ free(elem); \ return false; \ } \ memcpy(elem->str, keystr, keystr_len); \ elem->str[keystr_len] = 0; \ oom = false; \ HASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \ if (oom) \ { \ free(elem->str); \ free(elem); \ return false; \ } #define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \ etype *elem_find; \ HASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \ if (!elem_find) { \ ADD_STR_POOL(etype,ppool,keystr,keystr_len); \ elem->flags = flg; \ } #undef uthash_nonfatal_oom #define uthash_nonfatal_oom(elt) ut_oom_recover(elt) static bool oom = false; static void ut_oom_recover(void *elem) { oom = true; } // for not zero terminated strings bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags) { ADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags) return true; } // for zero terminated strings bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags) { return HostlistPoolAddStrLen(pp, s, strlen(s), flags); } hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s) { hostlist_pool *elem; HASH_FIND_STR(p, s, elem); return elem; } bool HostlistPoolCheckStr(hostlist_pool *p, const char *s) { return !!HostlistPoolGetStr(p,s); } void HostlistPoolDestroy(hostlist_pool **pp) { DESTROY_STR_POOL(hostlist_pool, pp) } void HostFailPoolDestroy(hostfail_pool **pp) { DESTROY_STR_POOL(hostfail_pool, pp) } hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time) { size_t slen = strlen(s); ADD_STR_POOL(hostfail_pool, pp, s, slen) elem->expire = time(NULL) + fail_time; elem->counter = 0; return elem; } hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s) { hostfail_pool *elem; HASH_FIND_STR(p, s, elem); return elem; } void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem) { free(elem->str); HASH_DEL(*p, elem); free(elem); } void HostFailPoolPurge(hostfail_pool **pp) { hostfail_pool *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *pp, elem, tmp) { if (now >= elem->expire) HostFailPoolDel(pp, elem); } } static time_t host_fail_purge_prev=0; void HostFailPoolPurgeRateLimited(hostfail_pool **pp) { time_t now = time(NULL); // do not purge too often to save resources if (host_fail_purge_prev != now) { HostFailPoolPurge(pp); host_fail_purge_prev = now; } } void HostFailPoolDump(hostfail_pool *p) { hostfail_pool *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, p, elem, tmp) printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now); } bool strlist_add(struct str_list_head *head, const char *filename) { struct str_list *entry = malloc(sizeof(struct str_list)); if (!entry) return false; entry->str = strdup(filename); if (!entry->str) { free(entry); return false; } LIST_INSERT_HEAD(head, entry, next); return true; } static void strlist_entry_destroy(struct str_list *entry) { free(entry->str); free(entry); } void strlist_destroy(struct str_list_head *head) { struct str_list *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); strlist_entry_destroy(entry); } } bool strlist_search(const struct str_list_head *head, const char *str) { struct str_list *entry; if (str) { LIST_FOREACH(entry, head, next) { if (!strcmp(entry->str, str)) return true; } } return false; } struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename) { struct hostlist_file *entry = malloc(sizeof(struct hostlist_file)); if (entry) { if (filename) { if (!(entry->filename = strdup(filename))) { free(entry); return false; } } else entry->filename = NULL; FILE_MOD_RESET(&entry->mod_sig); entry->hostlist = NULL; LIST_INSERT_HEAD(head, entry, next); } return entry; } static void hostlist_files_entry_destroy(struct hostlist_file *entry) { free(entry->filename); HostlistPoolDestroy(&entry->hostlist); free(entry); } void hostlist_files_destroy(struct hostlist_files_head *head) { struct hostlist_file *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); hostlist_files_entry_destroy(entry); } } struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename) { struct hostlist_file *hfile; LIST_FOREACH(hfile, head, next) { if (hfile->filename && !strcmp(hfile->filename,filename)) return hfile; } return NULL; } void hostlist_files_reset_modtime(struct hostlist_files_head *list) { struct hostlist_file *hfile; LIST_FOREACH(hfile, list, next) FILE_MOD_RESET(&hfile->mod_sig); } struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile) { struct hostlist_item *entry = malloc(sizeof(struct hostlist_item)); if (entry) { entry->hfile = hfile; LIST_INSERT_HEAD(head, entry, next); } return entry; } void hostlist_collection_destroy(struct hostlist_collection_head *head) { struct hostlist_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename) { struct hostlist_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) return item; } return NULL; } bool hostlist_collection_is_empty(const struct hostlist_collection_head *head) { const struct hostlist_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->hostlist) return false; } return true; } static int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q) { unsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen; unsigned int df = bitlen & 7, bytes = bitlen >> 3; int cmp = memcmp(p->data, q->data, bytes); if (cmp || !df) return cmp; uint8_t c1 = p->data[bytes] >> (8 - df); uint8_t c2 = q->data[bytes] >> (8 - df); return c1data); free(e); } } void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen) { struct kavl_bit_elem temp = { .bitlen = bitlen, .data = (uint8_t*)data }; kavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0)); } void kavl_bit_destroy(struct kavl_bit_elem **hdr) { while (*hdr) { struct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr); if (!e) break; kavl_bit_destroy_elem(e); } free(*hdr); } struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size) { if (!struct_size) struct_size=sizeof(struct kavl_bit_elem); struct kavl_bit_elem *v, *e = calloc(1, struct_size); if (!e) return 0; e->bitlen = bitlen; e->data = data; v = kavl_insert(kavl_bit, hdr, e, 0); while (e != v && e->bitlen < v->bitlen) { kavl_bit_delete(hdr, v->data, v->bitlen); v = kavl_insert(kavl_bit, hdr, e, 0); } if (e != v) kavl_bit_destroy_elem(e); return v; } struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen) { struct kavl_bit_elem temp = { .bitlen = bitlen, .data = (uint8_t*)data }; return kavl_find(kavl_bit, hdr, &temp, 0); } static bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen) { uint8_t bytelen = (preflen+7)>>3; uint8_t *abuf = malloc(bytelen); if (!abuf) return false; memcpy(abuf,a,bytelen); if (!kavl_bit_add(ipset,abuf,preflen,0)) { free(abuf); return false; } return true; } bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen) { return !!kavl_bit_get(ipset,a,preflen); } bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen) { if (preflen>32) return false; return ipset_kavl_add(ipset,a,preflen); } void ipset4Print(struct kavl_bit_elem *ipset) { if (!ipset) return; struct cidr4 c; const struct kavl_bit_elem *elem; kavl_itr_t(kavl_bit) itr; kavl_itr_first(kavl_bit, ipset, &itr); do { elem = kavl_at(&itr); c.preflen = elem->bitlen; expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); print_cidr4(&c); printf("\n"); } while (kavl_itr_next(kavl_bit, &itr)); } bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen) { return !!kavl_bit_get(ipset,a,preflen); } bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen) { if (preflen>128) return false; return ipset_kavl_add(ipset,a,preflen); } void ipset6Print(struct kavl_bit_elem *ipset) { if (!ipset) return; struct cidr6 c; const struct kavl_bit_elem *elem; kavl_itr_t(kavl_bit) itr; kavl_itr_first(kavl_bit, ipset, &itr); do { elem = kavl_at(&itr); c.preflen = elem->bitlen; expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); print_cidr6(&c); printf("\n"); } while (kavl_itr_next(kavl_bit, &itr)); } void ipsetDestroy(ipset *ipset) { kavl_bit_destroy(&ipset->ips4); kavl_bit_destroy(&ipset->ips6); } void ipsetPrint(ipset *ipset) { ipset4Print(ipset->ips4); ipset6Print(ipset->ips6); } struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename) { struct ipset_file *entry = malloc(sizeof(struct ipset_file)); if (entry) { if (filename) { if (!(entry->filename = strdup(filename))) { free(entry); return false; } } else entry->filename = NULL; FILE_MOD_RESET(&entry->mod_sig); memset(&entry->ipset,0,sizeof(entry->ipset)); LIST_INSERT_HEAD(head, entry, next); } return entry; } static void ipset_files_entry_destroy(struct ipset_file *entry) { free(entry->filename); ipsetDestroy(&entry->ipset); free(entry); } void ipset_files_destroy(struct ipset_files_head *head) { struct ipset_file *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); ipset_files_entry_destroy(entry); } } struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename) { struct ipset_file *hfile; LIST_FOREACH(hfile, head, next) { if (hfile->filename && !strcmp(hfile->filename,filename)) return hfile; } return NULL; } void ipset_files_reset_modtime(struct ipset_files_head *list) { struct ipset_file *hfile; LIST_FOREACH(hfile, list, next) FILE_MOD_RESET(&hfile->mod_sig); } struct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile) { struct ipset_item *entry = malloc(sizeof(struct ipset_item)); if (entry) { entry->hfile = hfile; LIST_INSERT_HEAD(head, entry, next); } return entry; } void ipset_collection_destroy(struct ipset_collection_head *head) { struct ipset_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename) { struct ipset_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) return item; } return NULL; } bool ipset_collection_is_empty(const struct ipset_collection_head *head) { const struct ipset_item *item; LIST_FOREACH(item, head, next) { if (!IPSET_EMPTY(&item->hfile->ipset)) return false; } return true; } bool port_filter_add(struct port_filters_head *head, const port_filter *pf) { struct port_filter_item *entry = malloc(sizeof(struct port_filter_item)); if (entry) { entry->pf = *pf; LIST_INSERT_HEAD(head, entry, next); } return entry; } void port_filters_destroy(struct port_filters_head *head) { struct port_filter_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } bool port_filters_in_range(const struct port_filters_head *head, uint16_t port) { const struct port_filter_item *item; if (!LIST_FIRST(head)) return true; LIST_FOREACH(item, head, next) { if (pf_in_range(port, &item->pf)) return true; } return false; } bool port_filters_deny_if_empty(struct port_filters_head *head) { port_filter pf; if (LIST_FIRST(head)) return true; return pf_parse("0",&pf) && port_filter_add(head,&pf); } struct blob_item *blob_collection_add(struct blob_collection_head *head) { struct blob_item *entry = calloc(1,sizeof(struct blob_item)); if (entry) { // insert to the end struct blob_item *itemc,*iteml=LIST_FIRST(head); if (iteml) { while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; LIST_INSERT_AFTER(iteml, entry, next); } else LIST_INSERT_HEAD(head, entry, next); } return entry; } struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset) { if (offset>=size) return NULL; struct blob_item *entry = calloc(1,sizeof(struct blob_item)); if (!entry) return NULL; if (!(entry->data = malloc(size+size_reserve))) { free(entry); return NULL; } if (data) memcpy(entry->data,data,size); entry->size = size; entry->size_buf = size+size_reserve; entry->offset = offset; // insert to the end struct blob_item *itemc,*iteml=LIST_FIRST(head); if (iteml) { while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; LIST_INSERT_AFTER(iteml, entry, next); } else LIST_INSERT_HEAD(head, entry, next); return entry; } void blob_collection_destroy(struct blob_collection_head *head) { struct blob_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry->extra); free(entry->extra2); free(entry->data); free(entry); } } bool blob_collection_empty(const struct blob_collection_head *head) { return !LIST_FIRST(head); } static void ipcache_item_touch(ip_cache_item *item) { time(&item->last); } static void ipcache_item_init(ip_cache_item *item) { ipcache_item_touch(item); item->hostname = NULL; item->hostname_is_ip = false; item->hops = 0; } static void ipcache_item_destroy(ip_cache_item *item) { free(item->hostname); } static void ipcache4Destroy(ip_cache4 **ipcache) { ip_cache4 *elem, *tmp; HASH_ITER(hh, *ipcache, elem, tmp) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } static void ipcache4Key(ip4if *key, const struct in_addr *a, const char *iface) { memset(key,0,sizeof(*key)); // make sure everything is zero key->addr = *a; if (iface) snprintf(key->iface,sizeof(key->iface),"%s",iface); } static ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a, const char *iface) { ip_cache4 *entry; struct ip4if key; ipcache4Key(&key,a,iface); HASH_FIND(hh, ipcache, &key, sizeof(key), entry); return entry; } static ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a, const char *iface) { // avoid dups ip_cache4 *entry = ipcache4Find(*ipcache,a,iface); if (entry) return entry; // already included entry = malloc(sizeof(ip_cache4)); if (!entry) return NULL; ipcache4Key(&entry->key,a,iface); oom = false; HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); if (oom) { free(entry); return NULL; } ipcache_item_init(&entry->data); return entry; } static void ipcache4Print(ip_cache4 *ipcache) { char s_ip[16]; time_t now; ip_cache4 *ipc, *tmp; time(&now); HASH_ITER(hh, ipcache , ipc, tmp) { *s_ip=0; inet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip)); printf("%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)); } } static void ipcache6Destroy(ip_cache6 **ipcache) { ip_cache6 *elem, *tmp; HASH_ITER(hh, *ipcache, elem, tmp) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } static void ipcache6Key(ip6if *key, const struct in6_addr *a, const char *iface) { memset(key,0,sizeof(*key)); // make sure everything is zero key->addr = *a; if (iface) snprintf(key->iface,sizeof(key->iface),"%s",iface); } static ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a, const char *iface) { ip_cache6 *entry; ip6if key; ipcache6Key(&key,a,iface); HASH_FIND(hh, ipcache, &key, sizeof(key), entry); return entry; } static ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a, const char *iface) { // avoid dups ip_cache6 *entry = ipcache6Find(*ipcache,a,iface); if (entry) return entry; // already included entry = malloc(sizeof(ip_cache6)); if (!entry) return NULL; ipcache6Key(&entry->key,a,iface); oom = false; HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); if (oom) { free(entry); return NULL; } ipcache_item_init(&entry->data); return entry; } static void ipcache6Print(ip_cache6 *ipcache) { char s_ip[40]; time_t now; ip_cache6 *ipc, *tmp; time(&now); HASH_ITER(hh, ipcache , ipc, tmp) { *s_ip=0; inet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip)); printf("%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)); } } void ipcacheDestroy(ip_cache *ipcache) { ipcache4Destroy(&ipcache->ipcache4); ipcache6Destroy(&ipcache->ipcache6); } void ipcachePrint(ip_cache *ipcache) { ipcache4Print(ipcache->ipcache4); ipcache6Print(ipcache->ipcache6); } ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) { ip_cache4 *ipcache4; ip_cache6 *ipcache6; if (a4) { if ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4,iface))) { ipcache_item_touch(&ipcache4->data); return &ipcache4->data; } } else if (a6) { if ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6,iface))) { ipcache_item_touch(&ipcache6->data); return &ipcache6->data; } } return NULL; } static void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime) { ip_cache4 *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *ipcache, elem, tmp) { if (now >= (elem->data.last + lifetime)) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } } static void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime) { ip_cache6 *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *ipcache, elem, tmp) { if (now >= (elem->data.last + lifetime)) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } } static void ipcache_purge(ip_cache *ipcache, time_t lifetime) { if (lifetime) // 0 = no expire { ipcache4_purge(&ipcache->ipcache4, lifetime); ipcache6_purge(&ipcache->ipcache6, lifetime); } } static time_t ipcache_purge_prev=0; void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime) { time_t now = time(NULL); // do not purge too often to save resources if (ipcache_purge_prev != now) { ipcache_purge(ipcache, lifetime); ipcache_purge_prev = now; } } ================================================ FILE: nfq/pools.h ================================================ #pragma once #include #include #include #include #include #include "helpers.h" //#define HASH_BLOOM 20 #define HASH_NONFATAL_OOM 1 #define HASH_FUNCTION HASH_BER #include "uthash.h" #include "kavl.h" #define HOSTLIST_POOL_FLAG_STRICT_MATCH 1 typedef struct hostlist_pool { char *str; /* key */ uint32_t flags; /* custom data */ UT_hash_handle hh; /* makes this structure hashable */ } hostlist_pool; void HostlistPoolDestroy(hostlist_pool **pp); bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags); bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags); hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s); struct str_list { char *str; LIST_ENTRY(str_list) next; }; LIST_HEAD(str_list_head, str_list); bool strlist_add(struct str_list_head *head, const char *filename); void strlist_destroy(struct str_list_head *head); bool strlist_search(const struct str_list_head *head, const char *str); typedef struct hostfail_pool { char *str; /* key */ int counter; /* value */ time_t expire; /* when to expire record (unixtime) */ UT_hash_handle hh; /* makes this structure hashable */ } hostfail_pool; void HostFailPoolDestroy(hostfail_pool **pp); hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); void HostFailPoolPurge(hostfail_pool **pp); void HostFailPoolPurgeRateLimited(hostfail_pool **pp); void HostFailPoolDump(hostfail_pool *p); struct hostlist_file { char *filename; file_mod_sig mod_sig; hostlist_pool *hostlist; LIST_ENTRY(hostlist_file) next; }; LIST_HEAD(hostlist_files_head, hostlist_file); struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename); void hostlist_files_destroy(struct hostlist_files_head *head); struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename); void hostlist_files_reset_modtime(struct hostlist_files_head *list); struct hostlist_item { struct hostlist_file *hfile; LIST_ENTRY(hostlist_item) next; }; LIST_HEAD(hostlist_collection_head, hostlist_item); struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile); void hostlist_collection_destroy(struct hostlist_collection_head *head); struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename); bool hostlist_collection_is_empty(const struct hostlist_collection_head *head); struct kavl_bit_elem { unsigned int bitlen; uint8_t *data; KAVL_HEAD(struct kavl_bit_elem) head; }; struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen); struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size); void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen); void kavl_bit_destroy(struct kavl_bit_elem **hdr); // combined ipset ipv4 and ipv6 typedef struct ipset { struct kavl_bit_elem *ips4,*ips6; } ipset; #define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6) bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen); static inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr) { return ipset4Add(ipset,&cidr->addr,cidr->preflen); } bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen); void ipset4Print(struct kavl_bit_elem *ipset); bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen); static inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr) { return ipset6Add(ipset,&cidr->addr,cidr->preflen); } bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen); void ipset6Print(struct kavl_bit_elem *ipset); void ipsetDestroy(ipset *ipset); void ipsetPrint(ipset *ipset); struct ipset_file { char *filename; file_mod_sig mod_sig; ipset ipset; LIST_ENTRY(ipset_file) next; }; LIST_HEAD(ipset_files_head, ipset_file); struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename); void ipset_files_destroy(struct ipset_files_head *head); struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename); void ipset_files_reset_modtime(struct ipset_files_head *list); struct ipset_item { struct ipset_file *hfile; LIST_ENTRY(ipset_item) next; }; LIST_HEAD(ipset_collection_head, ipset_item); struct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile); void ipset_collection_destroy(struct ipset_collection_head *head); struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename); bool ipset_collection_is_empty(const struct ipset_collection_head *head); struct port_filter_item { port_filter pf; LIST_ENTRY(port_filter_item) next; }; LIST_HEAD(port_filters_head, port_filter_item); bool port_filter_add(struct port_filters_head *head, const port_filter *pf); void port_filters_destroy(struct port_filters_head *head); bool port_filters_in_range(const struct port_filters_head *head, uint16_t port); bool port_filters_deny_if_empty(struct port_filters_head *head); struct blob_item { uint8_t *data; // main data blob size_t size; // main data blob size size_t size_buf;// main data blob allocated size size_t offset; // optional offset to useful data void *extra; // any data without size void *extra2; // any data without size LIST_ENTRY(blob_item) next; }; LIST_HEAD(blob_collection_head, blob_item); struct blob_item *blob_collection_add(struct blob_collection_head *head); struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve, size_t offset); void blob_collection_destroy(struct blob_collection_head *head); bool blob_collection_empty(const struct blob_collection_head *head); typedef struct ip4if { char iface[IFNAMSIZ]; struct in_addr addr; } ip4if; typedef struct ip6if { char iface[IFNAMSIZ]; struct in6_addr addr; } ip6if; typedef struct ip_cache_item { time_t last; char *hostname; bool hostname_is_ip; uint8_t hops; } ip_cache_item; typedef struct ip_cache4 { ip4if key; ip_cache_item data; UT_hash_handle hh; /* makes this structure hashable */ } ip_cache4; typedef struct ip_cache6 { ip6if key; ip_cache_item data; UT_hash_handle hh; /* makes this structure hashable */ } ip_cache6; typedef struct ip_cache { ip_cache4 *ipcache4; ip_cache6 *ipcache6; } ip_cache; ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6, const char *iface); void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime); void ipcacheDestroy(ip_cache *ipcache); void ipcachePrint(ip_cache *ipcache); ================================================ FILE: nfq/protocol.c ================================================ #define _GNU_SOURCE #include "protocol.h" #include "helpers.h" #include "params.h" #include #include #include #include // find N level domain static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len) { int i; const uint8_t *p1,*p2; for (i=1,p2=dom+dlen;idom && *p2!='.'; p2--); if (p2<=dom) return false; } for (p1=p2-1 ; p1>dom && *p1!='.'; p1--); if (*p1=='.') p1++; if (p) *p = p1; if (len) *len = p2-p1; return true; } const char *l7proto_str(t_l7proto l7) { switch(l7) { case HTTP: return "http"; case TLS: return "tls"; case QUIC: return "quic"; case WIREGUARD: return "wireguard"; case DHT: return "dht"; case DISCORD: return "discord"; case STUN: return "stun"; default: return "unknown"; } } bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) { return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) || (l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) || (l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) || (l7proto==DHT && (filter_l7 & L7_PROTO_DHT)) || (l7proto==DISCORD && (filter_l7 & L7_PROTO_DISCORD)) || (l7proto==STUN && (filter_l7 & L7_PROTO_STUN)); } bool IsHostMarker(uint8_t posmarker) { switch(posmarker) { case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: return true; default: return false; } } const char *posmarker_name(uint8_t posmarker) { switch(posmarker) { case PM_ABS: return "abs"; case PM_HOST: return "host"; case PM_HOST_END: return "endhost"; case PM_HOST_SLD: return "sld"; case PM_HOST_MIDSLD: return "midsld"; case PM_HOST_ENDSLD: return "endsld"; case PM_HTTP_METHOD: return "method"; case PM_SNI_EXT: return "sniext"; default: return "?"; } } static size_t CheckPos(size_t sz, ssize_t offset) { return (offset>=0 && offsetmarker, sp->pos, data, sz); case TLS: return TLSPos(sp->marker, sp->pos, data, sz); default: return AnyProtoPos(sp->marker, sp->pos, data, sz); } } void 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) { int i,j; for(i=j=0;i14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' && data[9]>='0' && data[9]<='9' && data[10]>='0' && data[10]<='9' && data[11]>='0' && data[11]<='9'; } int HttpReplyCode(const uint8_t *data, size_t len) { return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0'); } bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf) { const uint8_t *p, *s, *e = data + len; p = (uint8_t*)strncasestr((char*)data, header, len); if (!p) return false; p += strlen(header); while (p < e && (*p == ' ' || *p == '\t')) p++; s = p; while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++; if (s > p) { size_t slen = s - p; if (buf && len_buf) { if (slen >= len_buf) slen = len_buf - 1; for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]); buf[slen] = 0; } return true; } return false; } bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) { return HttpExtractHeader(data, len, "\nHost:", host, len_host); } // DPI redirects are global redirects to another domain bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host) { char loc[256],*redirect_host, *p; int code; if (!host || !*host) return false; code = HttpReplyCode(data,len); if ((code!=302 && code!=307) || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false; // something like : https://censor.net/badpage.php?reason=denied&source=RKN if (!strncmp(loc,"http://",7)) redirect_host=loc+7; else if (!strncmp(loc,"https://",8)) redirect_host=loc+8; else return false; // somethinkg like : censor.net/badpage.php?reason=denied&source=RKN for(p=redirect_host; *p && *p!='/' ; p++); *p=0; if (!*redirect_host) return false; // somethinkg like : censor.net // extract 2nd level domains const char *dhost, *drhost; if (!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)) return false; // compare 2nd level domains return strcasecmp(dhost, drhost)!=0; } size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) { const uint8_t *method, *host=NULL, *p; size_t offset_host,len_host; ssize_t offset; int i; switch(posmarker) { case PM_HTTP_METHOD: // recognize some tpws pre-applied hacks method=data; if (sz<10) break; if (*method=='\n' || *method=='\r') method++; if (*method=='\n' || *method=='\r') method++; for (p=method,i=0; i<9 && *p>='A' && *p<='Z'; i++,p++); if (i<3 || *p!=' ') break; return CheckPos(sz,method-data+pos); case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: if (HttpFindHostConst(&host,data,sz) && (host-data+7)>12)==((tlsver>>4)&0xF))) ? "GREASE" : "UNKNOWN"; } } uint16_t TLSRecordDataLen(const uint8_t *data) { return pntoh16(data + 3); } size_t TLSRecordLen(const uint8_t *data) { return TLSRecordDataLen(data) + 5; } bool IsTLSRecordFull(const uint8_t *data, size_t len) { return TLSRecordLen(data)<=len; } bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) { return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); } size_t TLSHandshakeLen(const uint8_t *data) { return data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length } bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len) { return len>=4 && data[0]==0x01 && TLSHandshakeLen(data)>0; } bool IsTLSHandshakeFull(const uint8_t *data, size_t len) { return (4+TLSHandshakeLen(data))<=len; } bool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off) { // +0 // u8 HandshakeType: ClientHello // u24 Length // u16 Version // c[32] random // u8 SessionIDLength // // u16 CipherSuitesLength // // u8 CompressionMethodsLength // // u16 ExtensionsLength size_t l; l = 1 + 3 + 2 + 32; // SessionIDLength if (len < (l + 1)) return false; l += data[l] + 1; // CipherSuitesLength if (len < (l + 2)) return false; l += pntoh16(data + l) + 2; // CompressionMethodsLength if (len < (l + 1)) return false; l += data[l] + 1; // ExtensionsLength if (len < (l + 2)) return false; *off = l; return true; } bool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off) { if (!TLSFindExtLenOffsetInHandshake(data+5,len-5,off)) return false; *off+=5; return true; } // bPartialIsOK=true - accept partial packets not containing the whole TLS message bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) { // +0 // u8 HandshakeType: ClientHello // u24 Length // u16 Version // c[32] random // u8 SessionIDLength // // u16 CipherSuitesLength // // u8 CompressionMethodsLength // // u16 ExtensionsLength size_t l; if (!bPartialIsOK && !IsTLSHandshakeFull(data,len)) return false; if (!TLSFindExtLenOffsetInHandshake(data,len,&l)) return false; data += l; len -= l; l = pntoh16(data); data += 2; len -= 2; if (bPartialIsOK) { if (len < l) l = len; } else { if (len < l) return false; } while (l >= 4) { uint16_t etype = pntoh16(data); size_t elen = pntoh16(data + 2); data += 4; l -= 4; if (l < elen) break; if (etype == type) { if (ext && len_ext) { *ext = data; *len_ext = elen; } return true; } data += elen; l -= elen; } return false; } bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) { // +0 // u8 ContentType: Handshake // u16 Version: TLS1.0 // u16 Length size_t reclen; if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; reclen=TLSRecordLen(data); if (reclen= len_host) slen = len_host - 1; for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); host[slen] = 0; } return true; } bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) { const uint8_t *ext; size_t elen; if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; return TLSExtractHostFromExt(ext, elen, host, len_host); } bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) { const uint8_t *ext; size_t elen; if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; return TLSExtractHostFromExt(ext, elen, host, len_host); } size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) { size_t elen; const uint8_t *ext, *p; size_t offset_host,len_host; ssize_t offset; switch(posmarker) { case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: case PM_SNI_EXT: if (TLSFindExt(data,sz,0,&ext,&elen,TLS_PARTIALS_ENABLE)) { if (posmarker==PM_SNI_EXT) { return CheckPos(sz,ext-data+pos); } else { if (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host)) return 0; offset_host = ext-data; return HostPos(posmarker,pos,data,sz,offset_host,len_host); } } return 0; default: return AnyProtoPos(posmarker,pos,data,sz); } } static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value) { switch (*tvb >> 6) { case 0: /* 0b00 => 1 byte length (6 bits Usable) */ if (value) *value = *tvb & 0x3F; return 1; case 1: /* 0b01 => 2 bytes length (14 bits Usable) */ if (value) *value = pntoh16(tvb) & 0x3FFF; return 2; case 2: /* 0b10 => 4 bytes length (30 bits Usable) */ if (value) *value = pntoh32(tvb) & 0x3FFFFFFF; return 4; case 3: /* 0b11 => 8 bytes length (62 bits Usable) */ if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF; return 8; } // impossible case if (*value) *value = 0; return 0; } static uint8_t tvb_get_size(uint8_t tvb) { return 1 << (tvb >> 6); } bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len) { size_t offset = 1; uint64_t coff, clen; if (len < 3 || *data != 6) return false; if ((offset+tvb_get_size(data[offset])) >= len) return false; offset += tvb_get_varint(data + offset, &coff); // offset must be 0 if it's a full segment, not just a chunk if (coff || (offset+tvb_get_size(data[offset])) >= len) return false; offset += tvb_get_varint(data + offset, &clen); if ((offset + clen) > len || !IsTLSHandshakeClientHello(data+offset,clen)) return false; if (hello_offset) *hello_offset = offset; if (hello_len) *hello_len = (size_t)clen; return true; } /* Returns the QUIC draft version or 0 if not applicable. */ uint8_t QUICDraftVersion(uint32_t version) { /* IETF Draft versions */ if ((version >> 8) == 0xff0000) return (uint8_t)version; /* Facebook mvfst, based on draft -22. */ if (version == 0xfaceb001) return 22; /* Facebook mvfst, based on draft -27. */ if (version == 0xfaceb002 || version == 0xfaceb00e) return 27; /* GQUIC Q050, T050 and T051: they are not really based on any drafts, * but we must return a sensible value */ if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) return 27; /* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 "Versions that follow the pattern 0x?a?a?a?a are reserved for use in forcing version negotiation to be exercised" It is tricky to return a correct draft version: such number is primarily used to select a proper salt (which depends on the version itself), but we don't have a real version here! Let's hope that we need to handle only latest drafts... */ if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) return 29; /* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the final draft version */ if (version == 0x00000001) return 34; /* QUIC Version 2 */ /* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */ if ((version == 0x709A50C4) || (version == 0x6b3343cf)) return 100; return 0; } static bool is_quic_draft_max(uint32_t draft_version, uint8_t max_version) { return draft_version && draft_version <= max_version; } static bool is_quic_v2(uint32_t version) { return (version == 0x709A50C4) || (version == 0x6b3343cf); } static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, const char *label, uint8_t *out, size_t out_len) { uint8_t hkdflabel[64]; size_t label_size = strlen(label); if (label_size > 255) return false; size_t hkdflabel_size = 2 + 1 + label_size + 1; if (hkdflabel_size > sizeof(hkdflabel)) return false; phton16(hkdflabel, out_len); hkdflabel[2] = (uint8_t)label_size; memcpy(hkdflabel + 3, label, label_size); hkdflabel[3 + label_size] = 0; return !hkdfExpand(SHA256, secret, secret_len, hkdflabel, hkdflabel_size, out, out_len); } static bool quic_derive_initial_secret(const quic_cid_t *cid, uint8_t *client_initial_secret, uint32_t version) { /* * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 * * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) * * client_initial_secret = HKDF-Expand-Label(initial_secret, * "client in", "", Hash.length) * server_initial_secret = HKDF-Expand-Label(initial_secret, * "server in", "", Hash.length) * * Hash for handshake packets is SHA-256 (output size 32). */ static const uint8_t handshake_salt_draft_22[20] = { 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a }; static const uint8_t handshake_salt_draft_23[20] = { 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, }; static const uint8_t handshake_salt_draft_29[20] = { 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 }; static const uint8_t handshake_salt_v1[20] = { 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a }; static const uint8_t hanshake_salt_draft_q50[20] = { 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 }; static const uint8_t hanshake_salt_draft_t50[20] = { 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 }; static const uint8_t hanshake_salt_draft_t51[20] = { 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d }; static const uint8_t handshake_salt_v2[20] = { 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9 }; int err; const uint8_t *salt; uint8_t secret[USHAMaxHashSize]; uint8_t draft_version = QUICDraftVersion(version); if (version == 0x51303530) { salt = hanshake_salt_draft_q50; } else if (version == 0x54303530) { salt = hanshake_salt_draft_t50; } else if (version == 0x54303531) { salt = hanshake_salt_draft_t51; } else if (is_quic_draft_max(draft_version, 22)) { salt = handshake_salt_draft_22; } else if (is_quic_draft_max(draft_version, 28)) { salt = handshake_salt_draft_23; } else if (is_quic_draft_max(draft_version, 32)) { salt = handshake_salt_draft_29; } else if (is_quic_draft_max(draft_version, 34)) { salt = handshake_salt_v1; } else { salt = handshake_salt_v2; } err = hkdfExtract(SHA256, salt, 20, cid->cid, cid->len, secret); if (err) return false; if (client_initial_secret && !quic_hkdf_expand_label(secret, SHA256HashSize, "tls13 client in", client_initial_secret, SHA256HashSize)) return false; return true; } bool QUICIsLongHeader(const uint8_t *data, size_t len) { return len>=9 && !!(*data & 0x80); } uint32_t QUICExtractVersion(const uint8_t *data, size_t len) { // long header, fixed bit, type=initial return QUICIsLongHeader(data, len) ? ntohl(*(uint32_t*)(data + 1)) : 0; } bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid) { if (!QUICIsLongHeader(data,len) || !data[5] || data[5] > QUIC_MAX_CID_LENGTH || (6+data[5])>len) return false; cid->len = data[5]; memcpy(&cid->cid, data + 6, data[5]); return true; } bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len) { uint32_t ver = QUICExtractVersion(data, data_len); if (!ver) return false; quic_cid_t dcid; if (!QUICExtractDCID(data, data_len, &dcid)) return false; uint8_t client_initial_secret[SHA256HashSize]; if (!quic_derive_initial_secret(&dcid, client_initial_secret, ver)) return false; uint8_t aeskey[16], aesiv[12], aeshp[16]; bool v1_label = !is_quic_v2(ver); if (!quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic key" : "tls13 quicv2 key", aeskey, sizeof(aeskey)) || !quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic iv" : "tls13 quicv2 iv", aesiv, sizeof(aesiv)) || !quic_hkdf_expand_label(client_initial_secret, SHA256HashSize, v1_label ? "tls13 quic hp" : "tls13 quicv2 hp", aeshp, sizeof(aeshp))) { return false; } uint64_t payload_len,token_len,pn_offset; pn_offset = 1 + 4 + 1 + data[5]; if (pn_offset >= data_len) return false; // SCID length pn_offset += 1 + data[pn_offset]; if (pn_offset >= data_len || (pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false; // token length pn_offset += tvb_get_varint(data + pn_offset, &token_len); pn_offset += token_len; if (pn_offset >= data_len || (pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false; pn_offset += tvb_get_varint(data + pn_offset, &payload_len); if (payload_len<20 || (pn_offset + payload_len)>data_len) return false; uint8_t sample_enc[16]; aes_context ctx; if (aes_setkey(&ctx, 1, aeshp, sizeof(aeshp)) || aes_cipher(&ctx, data + pn_offset + 4, sample_enc)) return false; uint8_t mask[5]; memcpy(mask, sample_enc, sizeof(mask)); uint8_t packet0 = data[0] ^ (mask[0] & 0x0f); uint8_t pkn_len = (packet0 & 0x03) + 1; uint8_t pkn_bytes[4]; memcpy(pkn_bytes, data + pn_offset, pkn_len); uint32_t pkn = 0; for (uint8_t i = 0; i < pkn_len; i++) pkn |= (uint32_t)(pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); phton64(aesiv + sizeof(aesiv) - 8, pntoh64(aesiv + sizeof(aesiv) - 8) ^ pkn); uint64_t cryptlen = payload_len - pkn_len - 16; if (cryptlen > *clean_len) return false; *clean_len = (size_t)cryptlen; const uint8_t *decrypt_begin = data + pn_offset + pkn_len; uint8_t atag[16],header[2048]; uint64_t header_len = pn_offset + pkn_len; if (header_len > sizeof(header)) return false; // not likely header will be so large memcpy(header, data, header_len); header[0] = packet0; for(uint8_t i = 0; i < pkn_len; i++) header[header_len - 1 - i] = (uint8_t)(pkn >> (8 * i)); if (aes_gcm_crypt(AES_DECRYPT, clean, decrypt_begin, cryptlen, aeskey, sizeof(aeskey), aesiv, sizeof(aesiv), header, header_len, atag, sizeof(atag))) return false; // check if message was decrypted correctly : good keys , no data corruption return !memcmp(data + pn_offset + pkn_len + cryptlen, atag, 16); } struct range64 { uint64_t offset,len; }; #define MAX_DEFRAG_PIECES 128 static int cmp_range64(const void * a, const void * b) { return (((struct range64*)a)->offset < ((struct range64*)b)->offset) ? -1 : (((struct range64*)a)->offset > ((struct range64*)b)->offset) ? 1 : 0; } bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull) { // Crypto frame can be split into multiple chunks // chromium randomly splits it and pads with zero/one bytes to force support the standard // mozilla does not split if (*defrag_len<10) return false; uint8_t *defrag_data = defrag+10; size_t defrag_data_len = *defrag_len-10; uint8_t ft; uint64_t offset,sz,szmax=0,zeropos=0,pos=0; bool found=false; struct range64 ranges[MAX_DEFRAG_PIECES]; int i,j,range=0; while(pos1) // 00 - padding, 01 - ping { if (ft!=6) return false; // dont want to know all possible frame type formats if (pos>=clean_len) return false; if (range>=MAX_DEFRAG_PIECES) return false; if ((pos+tvb_get_size(clean[pos])>=clean_len)) return false; pos += tvb_get_varint(clean+pos, &offset); if ((pos+tvb_get_size(clean[pos])>clean_len)) return false; pos += tvb_get_varint(clean+pos, &sz); if ((pos+sz)>clean_len) return false; if ((offset+sz)>defrag_data_len) return false; // defrag buf overflow // remove exact duplicates early to save cpu for(i=0;i zeropos) zeropos=offset+sz; found=true; if ((offset+sz) > szmax) szmax = offset+sz; memcpy(defrag_data+offset,clean+pos,sz); ranges[range].offset = offset; ranges[range].len = sz; range++; skip_range: pos+=sz; } } if (found) { qsort(ranges, range, sizeof(*ranges), cmp_range64); // for(i=0 ; i0) { for (j=0,i=1; i < range; i++) { uint64_t current_end = ranges[j].offset + ranges[j].len; uint64_t next_start = ranges[i].offset; uint64_t next_end = ranges[i].offset + ranges[i].len; if (next_start <= current_end) ranges[j].len = MAX(next_end,current_end) - ranges[j].offset; else ranges[++j] = ranges[i]; } range = j+1; } // for(i=0 ; i QUIC_MAX_CID_LENGTH) return false; offset += 1 + data[offset]; if (offset>=len) return false; // SCID if (data[offset] > QUIC_MAX_CID_LENGTH) return false; offset += 1 + data[offset]; // token length if (offset>=len || (offset + tvb_get_size(data[offset])) > len) return false; offset += tvb_get_varint(data + offset, &sz); offset += sz; if (offset >= len) return false; // payload length sz2 = tvb_get_size(data[offset]); if ((offset + sz2) > len) return false; tvb_get_varint(data + offset, &sz); offset += sz2 + sz; if (offset > len) return false; return true; } bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len) { return len==148 && data[0]==1; } bool IsDhtD1(const uint8_t *data, size_t len) { return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e'; } bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len) { return len==74 && data[0]==0 && data[1]==1 && data[2]==0 && data[3]==70 && !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); // address is not set in request } bool IsStunMessage(const uint8_t *data, size_t len) { return len>=20 && // header size (data[0]&0xC0)==0 && // 2 most significant bits must be zeroes (data[3]&3)==0 && // length must be a multiple of 4 pntoh32(data+4)==0x2112A442 && // magic cookie pntoh16(data+2)<=(len-20); } ================================================ FILE: nfq/protocol.h ================================================ #pragma once #include #include #include #include "crypto/sha.h" #include "crypto/aes-gcm.h" #include "helpers.h" typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT, DISCORD, STUN} t_l7proto; #define L7_PROTO_HTTP 0x00000001 #define L7_PROTO_TLS 0x00000002 #define L7_PROTO_QUIC 0x00000004 #define L7_PROTO_WIREGUARD 0x00000008 #define L7_PROTO_DHT 0x00000010 #define L7_PROTO_DISCORD 0x00000020 #define L7_PROTO_STUN 0x00000040 #define L7_PROTO_UNKNOWN 0x80000000 const char *l7proto_str(t_l7proto l7); bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7); // pos markers #define PM_ABS 0 #define PM_HOST 1 #define PM_HOST_END 2 #define PM_HOST_SLD 3 #define PM_HOST_MIDSLD 4 #define PM_HOST_ENDSLD 5 #define PM_HTTP_METHOD 6 #define PM_SNI_EXT 7 struct proto_pos { int16_t pos; uint8_t marker; }; #define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) bool IsHostMarker(uint8_t posmarker); const char *posmarker_name(uint8_t posmarker); size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp); void 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); extern const char *http_methods[9]; const char *HttpMethod(const uint8_t *data, size_t len); bool IsHttp(const uint8_t *data, size_t len); bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs); bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs); // header must be passed like this : "\nHost:" bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf); bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host); bool IsHttpReply(const uint8_t *data, size_t len); const char *HttpFind2ndLevelDomain(const char *host); // must be pre-checked by IsHttpReply int HttpReplyCode(const uint8_t *data, size_t len); // must be pre-checked by IsHttpReply bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host); const char *TLSVersionStr(uint16_t tlsver); uint16_t TLSRecordDataLen(const uint8_t *data); size_t TLSRecordLen(const uint8_t *data); bool IsTLSRecordFull(const uint8_t *data, size_t len); bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); size_t TLSHandshakeLen(const uint8_t *data); bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len); bool IsTLSHandshakeFull(const uint8_t *data, size_t len); bool TLSAdvanceToHostInSNI(const uint8_t **ext, size_t *elen, size_t *slen); bool TLSFindExtLen(const uint8_t *data, size_t len, size_t *off); bool TLSFindExtLenOffsetInHandshake(const uint8_t *data, size_t len, size_t *off); bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len); bool IsDhtD1(const uint8_t *data, size_t len); bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len); bool IsStunMessage(const uint8_t *data, size_t len); #define QUIC_MAX_CID_LENGTH 20 typedef struct quic_cid { uint8_t len; uint8_t cid[QUIC_MAX_CID_LENGTH]; } quic_cid_t; bool IsQUICInitial(const uint8_t *data, size_t len); bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len); bool QUICIsLongHeader(const uint8_t *data, size_t len); uint32_t QUICExtractVersion(const uint8_t *data, size_t len); uint8_t QUICDraftVersion(uint32_t version); bool QUICExtractDCID(const uint8_t *data, size_t len, quic_cid_t *cid); bool QUICDecryptInitial(const uint8_t *data, size_t data_len, uint8_t *clean, size_t *clean_len); // returns true if crypto frames were found . bFull = true if crypto frame fragments have full coverage bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len, bool *bFull); //bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello); ================================================ FILE: nfq/sec.c ================================================ #define _GNU_SOURCE #include #include #include "sec.h" #include #include #include #include "params.h" #ifdef __linux__ #include #include #include #include // __X32_SYSCALL_BIT defined in linux/unistd.h #include #include #include /************ SECCOMP ************/ // block most of the undesired syscalls to harden against code execution static long blocked_syscalls[] = { #ifdef SYS_execv SYS_execv, #endif SYS_execve, #ifdef SYS_execveat SYS_execveat, #endif #ifdef SYS_exec_with_loader SYS_exec_with_loader, #endif #ifdef SYS_clone SYS_clone, #endif #ifdef SYS_clone2 SYS_clone2, #endif #ifdef SYS_clone3 SYS_clone3, #endif #ifdef SYS_osf_execve SYS_osf_execve, #endif #ifdef SYS_fork SYS_fork, #endif #ifdef SYS_vfork SYS_vfork, #endif #ifdef SYS_uselib SYS_uselib, #endif #ifdef SYS_unlink SYS_unlink, #endif SYS_unlinkat, #ifdef SYS_chmod SYS_chmod, #endif SYS_fchmod,SYS_fchmodat, #ifdef SYS_chown SYS_chown, #endif #ifdef SYS_chown32 SYS_chown32, #endif SYS_fchown, #ifdef SYS_fchown32 SYS_fchown32, #endif #ifdef SYS_lchown SYS_lchown, #endif #ifdef SYS_lchown32 SYS_lchown32, #endif SYS_fchownat, #ifdef SYS_symlink SYS_symlink, #endif SYS_symlinkat, #ifdef SYS_link SYS_link, #endif SYS_linkat, SYS_truncate, #ifdef SYS_truncate64 SYS_truncate64, #endif SYS_ftruncate, #ifdef SYS_ftruncate64 SYS_ftruncate64, #endif #ifdef SYS_mknod SYS_mknod, #endif SYS_mknodat, #ifdef SYS_mkdir SYS_mkdir, #endif SYS_mkdirat, #ifdef SYS_rmdir SYS_rmdir, #endif #ifdef SYS_rename SYS_rename, #endif #ifdef SYS_renameat2 SYS_renameat2, #endif #ifdef SYS_renameat SYS_renameat, #endif #ifdef SYS_readdir SYS_readdir, #endif #ifdef SYS_getdents SYS_getdents, #endif #ifdef SYS_getdents64 SYS_getdents64, #endif #ifdef SYS_process_vm_readv SYS_process_vm_readv, #endif #ifdef SYS_process_vm_writev SYS_process_vm_writev, #endif #ifdef SYS_process_madvise SYS_process_madvise, #endif #ifdef SYS_tkill SYS_tkill, #endif #ifdef SYS_tgkill SYS_tgkill, #endif SYS_kill, SYS_ptrace }; #define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) { filter->code = code; filter->jt = jt; filter->jf = jf; filter->k = k; } // deny all blocked syscalls static bool set_seccomp(void) { #ifdef __X32_SYSCALL_BIT #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) #else #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) #endif struct sock_filter sockf[SECCOMP_PROG_SIZE]; struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; int i,idx=0; set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); #ifdef __X32_SYSCALL_BIT // x86 only set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail #else set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); #endif /* // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr */ for(i=0 ; i= 0; } bool sec_harden(void) { bool bRes = true; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); bRes = false; } #if ARCH_NR!=0 if (!set_seccomp()) { DLOG_PERROR("seccomp"); if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n"); bRes = false; } #endif return bRes; } bool checkpcap(uint64_t caps) { if (!caps) return true; // no special caps reqd struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; struct __user_cap_data_struct cd[2]; uint32_t c0 = (uint32_t)caps; uint32_t c1 = (uint32_t)(caps>>32); return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1; } bool setpcap(uint64_t caps) { struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; struct __user_cap_data_struct cd[2]; cd[0].effective = cd[0].permitted = (uint32_t)caps; cd[0].inheritable = 0; cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32); cd[1].inheritable = 0; return !capset(&ch,cd); } int getmaxcap(void) { int maxcap = CAP_LAST_CAP; FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); if (F) { int n = fscanf(F, "%d", &maxcap); fclose(F); } return maxcap; } bool dropcaps(void) { uint64_t caps = (1< #include #ifdef __linux__ #include #include #include bool checkpcap(uint64_t caps); bool setpcap(uint64_t caps); int getmaxcap(void); bool dropcaps(void); #define syscall_nr (offsetof(struct seccomp_data, nr)) #define arch_nr (offsetof(struct seccomp_data, arch)) #define syscall_arg(x) (offsetof(struct seccomp_data, args[x])) #ifndef __AUDIT_ARCH_64BIT #define __AUDIT_ARCH_64BIT 0x80000000 #endif #ifndef __AUDIT_ARCH_LE #define __AUDIT_ARCH_LE 0x40000000 #endif #ifndef EM_RISCV #define EM_RISCV 243 #endif #ifndef AUDIT_ARCH_RISCV64 #define AUDIT_ARCH_RISCV64 (EM_RISCV | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif #ifndef EM_LOONGARCH #define EM_LOONGARCH 258 #endif #ifndef AUDIT_ARCH_LOONGARCH64 #define AUDIT_ARCH_LOONGARCH64 (EM_LOONGARCH | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif #if defined(__aarch64__) # define ARCH_NR AUDIT_ARCH_AARCH64 #elif defined(__amd64__) # define ARCH_NR AUDIT_ARCH_X86_64 #elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__)) # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_ARM # else # define ARCH_NR AUDIT_ARCH_ARMEB # endif #elif defined(__i386__) # define ARCH_NR AUDIT_ARCH_I386 #elif defined(__mips__) #if _MIPS_SIM == _MIPS_SIM_ABI32 # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_MIPSEL # else # define ARCH_NR AUDIT_ARCH_MIPS # endif #elif _MIPS_SIM == _MIPS_SIM_ABI64 # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_MIPSEL64 # else # define ARCH_NR AUDIT_ARCH_MIPS64 # endif #else # error "Unsupported mips abi" #endif #elif defined(__PPC64__) # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_PPC64LE # else # define ARCH_NR AUDIT_ARCH_PPC64 # endif #elif defined(__PPC__) # define ARCH_NR AUDIT_ARCH_PPC #elif __riscv && __riscv_xlen == 64 # define ARCH_NR AUDIT_ARCH_RISCV64 #elif defined(__loongarch__) && __loongarch_grlen == 64 # define ARCH_NR AUDIT_ARCH_LOONGARCH64 #elif defined(__e2k__) # define ARCH_NR AUDIT_ARCH_E2K #else # error "Platform does not support seccomp filter yet" #endif #endif #ifndef __CYGWIN__ bool sec_harden(void); bool can_drop_root(void); bool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count); void print_id(void); #endif void daemonize(void); bool writepid(const char *filename); ================================================ FILE: nfq/uthash.h ================================================ /* Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: nfq/win.c ================================================ #ifdef __CYGWIN__ #include #include "win.h" #include "nfqws.h" #define SERVICE_NAME "winws" static SERVICE_STATUS ServiceStatus; static SERVICE_STATUS_HANDLE hStatus = NULL; static int service_argc = 0; static char **service_argv = NULL; void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))); bool service_run(int argc, char *argv[]) { SERVICE_TABLE_ENTRY ServiceTable[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main}, {NULL, NULL} }; service_argc = argc; service_argv = argv; return StartServiceCtrlDispatcherA(ServiceTable); } static void service_set_status(DWORD state) { ServiceStatus.dwCurrentState = state; SetServiceStatus(hStatus, &ServiceStatus); } // Control handler function void service_controlhandler(DWORD request) { switch (request) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: bQuit = true; ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; break; } SetServiceStatus(hStatus, &ServiceStatus); } void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) { ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 1; ServiceStatus.dwWaitHint = 0; hStatus = RegisterServiceCtrlHandlerA( SERVICE_NAME, (LPHANDLER_FUNCTION)service_controlhandler); if (hStatus == (SERVICE_STATUS_HANDLE)0) { // Registering Control Handler failed return; } SetServiceStatus(hStatus, &ServiceStatus); // Calling main with saved argc & argv ServiceStatus.dwWin32ExitCode = (DWORD)main(service_argc, service_argv); ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(hStatus, &ServiceStatus); return; } #endif ================================================ FILE: nfq/win.h ================================================ #pragma once #ifdef __CYGWIN__ #include bool service_run(int argc, char *argv[]); #endif ================================================ FILE: nfq/windows/windivert/windivert.h ================================================ /* * windivert.h * (C) 2019, all rights reserved, * * This file is part of WinDivert. * * WinDivert is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * WinDivert is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __WINDIVERT_H #define __WINDIVERT_H #ifndef WINDIVERT_KERNEL #include #endif /* WINDIVERT_KERNEL */ #ifndef WINDIVERTEXPORT #define WINDIVERTEXPORT extern __declspec(dllimport) #endif /* WINDIVERTEXPORT */ #ifdef __MINGW32__ #define __in #define __in_opt #define __out #define __out_opt #define __inout #define __inout_opt #include #define INT8 int8_t #define UINT8 uint8_t #define INT16 int16_t #define UINT16 uint16_t #define INT32 int32_t #define UINT32 uint32_t #define INT64 int64_t #define UINT64 uint64_t #endif /* __MINGW32__ */ #ifdef __cplusplus extern "C" { #endif /****************************************************************************/ /* WINDIVERT API */ /****************************************************************************/ /* * WinDivert layers. */ typedef enum { WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */ WINDIVERT_LAYER_NETWORK_FORWARD = 1,/* Network layer (forwarded packets) */ WINDIVERT_LAYER_FLOW = 2, /* Flow layer. */ WINDIVERT_LAYER_SOCKET = 3, /* Socket layer. */ WINDIVERT_LAYER_REFLECT = 4, /* Reflect layer. */ } WINDIVERT_LAYER, *PWINDIVERT_LAYER; /* * WinDivert NETWORK and NETWORK_FORWARD layer data. */ typedef struct { UINT32 IfIdx; /* Packet's interface index. */ UINT32 SubIfIdx; /* Packet's sub-interface index. */ } WINDIVERT_DATA_NETWORK, *PWINDIVERT_DATA_NETWORK; /* * WinDivert FLOW layer data. */ typedef struct { UINT64 EndpointId; /* Endpoint ID. */ UINT64 ParentEndpointId; /* Parent endpoint ID. */ UINT32 ProcessId; /* Process ID. */ UINT32 LocalAddr[4]; /* Local address. */ UINT32 RemoteAddr[4]; /* Remote address. */ UINT16 LocalPort; /* Local port. */ UINT16 RemotePort; /* Remote port. */ UINT8 Protocol; /* Protocol. */ } WINDIVERT_DATA_FLOW, *PWINDIVERT_DATA_FLOW; /* * WinDivert SOCKET layer data. */ typedef struct { UINT64 EndpointId; /* Endpoint ID. */ UINT64 ParentEndpointId; /* Parent Endpoint ID. */ UINT32 ProcessId; /* Process ID. */ UINT32 LocalAddr[4]; /* Local address. */ UINT32 RemoteAddr[4]; /* Remote address. */ UINT16 LocalPort; /* Local port. */ UINT16 RemotePort; /* Remote port. */ UINT8 Protocol; /* Protocol. */ } WINDIVERT_DATA_SOCKET, *PWINDIVERT_DATA_SOCKET; /* * WinDivert REFLECTION layer data. */ typedef struct { INT64 Timestamp; /* Handle open time. */ UINT32 ProcessId; /* Handle process ID. */ WINDIVERT_LAYER Layer; /* Handle layer. */ UINT64 Flags; /* Handle flags. */ INT16 Priority; /* Handle priority. */ } WINDIVERT_DATA_REFLECT, *PWINDIVERT_DATA_REFLECT; /* * WinDivert address. */ #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) #endif typedef struct { INT64 Timestamp; /* Packet's timestamp. */ UINT32 Layer:8; /* Packet's layer. */ UINT32 Event:8; /* Packet event. */ UINT32 Sniffed:1; /* Packet was sniffed? */ UINT32 Outbound:1; /* Packet is outound? */ UINT32 Loopback:1; /* Packet is loopback? */ UINT32 Impostor:1; /* Packet is impostor? */ UINT32 IPv6:1; /* Packet is IPv6? */ UINT32 IPChecksum:1; /* Packet has valid IPv4 checksum? */ UINT32 TCPChecksum:1; /* Packet has valid TCP checksum? */ UINT32 UDPChecksum:1; /* Packet has valid UDP checksum? */ UINT32 Reserved1:8; UINT32 Reserved2; union { WINDIVERT_DATA_NETWORK Network; /* Network layer data. */ WINDIVERT_DATA_FLOW Flow; /* Flow layer data. */ WINDIVERT_DATA_SOCKET Socket; /* Socket layer data. */ WINDIVERT_DATA_REFLECT Reflect; /* Reflect layer data. */ UINT8 Reserved3[64]; }; } WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS; #ifdef _MSC_VER #pragma warning(pop) #endif /* * WinDivert events. */ typedef enum { WINDIVERT_EVENT_NETWORK_PACKET = 0, /* Network packet. */ WINDIVERT_EVENT_FLOW_ESTABLISHED = 1, /* Flow established. */ WINDIVERT_EVENT_FLOW_DELETED = 2, /* Flow deleted. */ WINDIVERT_EVENT_SOCKET_BIND = 3, /* Socket bind. */ WINDIVERT_EVENT_SOCKET_CONNECT = 4, /* Socket connect. */ WINDIVERT_EVENT_SOCKET_LISTEN = 5, /* Socket listen. */ WINDIVERT_EVENT_SOCKET_ACCEPT = 6, /* Socket accept. */ WINDIVERT_EVENT_SOCKET_CLOSE = 7, /* Socket close. */ WINDIVERT_EVENT_REFLECT_OPEN = 8, /* WinDivert handle opened. */ WINDIVERT_EVENT_REFLECT_CLOSE = 9, /* WinDivert handle closed. */ } WINDIVERT_EVENT, *PWINDIVERT_EVENT; /* * WinDivert flags. */ #define WINDIVERT_FLAG_SNIFF 0x0001 #define WINDIVERT_FLAG_DROP 0x0002 #define WINDIVERT_FLAG_RECV_ONLY 0x0004 #define WINDIVERT_FLAG_READ_ONLY WINDIVERT_FLAG_RECV_ONLY #define WINDIVERT_FLAG_SEND_ONLY 0x0008 #define WINDIVERT_FLAG_WRITE_ONLY WINDIVERT_FLAG_SEND_ONLY #define WINDIVERT_FLAG_NO_INSTALL 0x0010 #define WINDIVERT_FLAG_FRAGMENTS 0x0020 /* * WinDivert parameters. */ typedef enum { WINDIVERT_PARAM_QUEUE_LENGTH = 0, /* Packet queue length. */ WINDIVERT_PARAM_QUEUE_TIME = 1, /* Packet queue time. */ WINDIVERT_PARAM_QUEUE_SIZE = 2, /* Packet queue size. */ WINDIVERT_PARAM_VERSION_MAJOR = 3, /* Driver version (major). */ WINDIVERT_PARAM_VERSION_MINOR = 4, /* Driver version (minor). */ } WINDIVERT_PARAM, *PWINDIVERT_PARAM; #define WINDIVERT_PARAM_MAX WINDIVERT_PARAM_VERSION_MINOR /* * WinDivert shutdown parameter. */ typedef enum { WINDIVERT_SHUTDOWN_RECV = 0x1, /* Shutdown recv. */ WINDIVERT_SHUTDOWN_SEND = 0x2, /* Shutdown send. */ WINDIVERT_SHUTDOWN_BOTH = 0x3, /* Shutdown recv and send. */ } WINDIVERT_SHUTDOWN, *PWINDIVERT_SHUTDOWN; #define WINDIVERT_SHUTDOWN_MAX WINDIVERT_SHUTDOWN_BOTH #ifndef WINDIVERT_KERNEL /* * Open a WinDivert handle. */ WINDIVERTEXPORT HANDLE WinDivertOpen( __in const char *filter, __in WINDIVERT_LAYER layer, __in INT16 priority, __in UINT64 flags); /* * Receive (read) a packet from a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertRecv( __in HANDLE handle, __out_opt VOID *pPacket, __in UINT packetLen, __out_opt UINT *pRecvLen, __out_opt WINDIVERT_ADDRESS *pAddr); /* * Receive (read) a packet from a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertRecvEx( __in HANDLE handle, __out_opt VOID *pPacket, __in UINT packetLen, __out_opt UINT *pRecvLen, __in UINT64 flags, __out WINDIVERT_ADDRESS *pAddr, __inout_opt UINT *pAddrLen, __inout_opt LPOVERLAPPED lpOverlapped); /* * Send (write/inject) a packet to a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertSend( __in HANDLE handle, __in const VOID *pPacket, __in UINT packetLen, __out_opt UINT *pSendLen, __in const WINDIVERT_ADDRESS *pAddr); /* * Send (write/inject) a packet to a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertSendEx( __in HANDLE handle, __in const VOID *pPacket, __in UINT packetLen, __out_opt UINT *pSendLen, __in UINT64 flags, __in const WINDIVERT_ADDRESS *pAddr, __in UINT addrLen, __inout_opt LPOVERLAPPED lpOverlapped); /* * Shutdown a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertShutdown( __in HANDLE handle, __in WINDIVERT_SHUTDOWN how); /* * Close a WinDivert handle. */ WINDIVERTEXPORT BOOL WinDivertClose( __in HANDLE handle); /* * Set a WinDivert handle parameter. */ WINDIVERTEXPORT BOOL WinDivertSetParam( __in HANDLE handle, __in WINDIVERT_PARAM param, __in UINT64 value); /* * Get a WinDivert handle parameter. */ WINDIVERTEXPORT BOOL WinDivertGetParam( __in HANDLE handle, __in WINDIVERT_PARAM param, __out UINT64 *pValue); #endif /* WINDIVERT_KERNEL */ /* * WinDivert constants. */ #define WINDIVERT_PRIORITY_HIGHEST 30000 #define WINDIVERT_PRIORITY_LOWEST (-WINDIVERT_PRIORITY_HIGHEST) #define WINDIVERT_PARAM_QUEUE_LENGTH_DEFAULT 4096 #define WINDIVERT_PARAM_QUEUE_LENGTH_MIN 32 #define WINDIVERT_PARAM_QUEUE_LENGTH_MAX 16384 #define WINDIVERT_PARAM_QUEUE_TIME_DEFAULT 2000 /* 2s */ #define WINDIVERT_PARAM_QUEUE_TIME_MIN 100 /* 100ms */ #define WINDIVERT_PARAM_QUEUE_TIME_MAX 16000 /* 16s */ #define WINDIVERT_PARAM_QUEUE_SIZE_DEFAULT 4194304 /* 4MB */ #define WINDIVERT_PARAM_QUEUE_SIZE_MIN 65535 /* 64KB */ #define WINDIVERT_PARAM_QUEUE_SIZE_MAX 33554432 /* 32MB */ #define WINDIVERT_BATCH_MAX 0xFF /* 255 */ #define WINDIVERT_MTU_MAX (40 + 0xFFFF) /****************************************************************************/ /* WINDIVERT HELPER API */ /****************************************************************************/ #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4214) #endif /* * IPv4/IPv6/ICMP/ICMPv6/TCP/UDP header definitions. */ typedef struct { UINT8 HdrLength:4; UINT8 Version:4; UINT8 TOS; UINT16 Length; UINT16 Id; UINT16 FragOff0; UINT8 TTL; UINT8 Protocol; UINT16 Checksum; UINT32 SrcAddr; UINT32 DstAddr; } WINDIVERT_IPHDR, *PWINDIVERT_IPHDR; #define WINDIVERT_IPHDR_GET_FRAGOFF(hdr) \ (((hdr)->FragOff0) & 0xFF1F) #define WINDIVERT_IPHDR_GET_MF(hdr) \ ((((hdr)->FragOff0) & 0x0020) != 0) #define WINDIVERT_IPHDR_GET_DF(hdr) \ ((((hdr)->FragOff0) & 0x0040) != 0) #define WINDIVERT_IPHDR_GET_RESERVED(hdr) \ ((((hdr)->FragOff0) & 0x0080) != 0) #define WINDIVERT_IPHDR_SET_FRAGOFF(hdr, val) \ do \ { \ (hdr)->FragOff0 = (((hdr)->FragOff0) & 0x00E0) | \ ((val) & 0xFF1F); \ } \ while (FALSE) #define WINDIVERT_IPHDR_SET_MF(hdr, val) \ do \ { \ (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFDF) | \ (((val) & 0x0001) << 5); \ } \ while (FALSE) #define WINDIVERT_IPHDR_SET_DF(hdr, val) \ do \ { \ (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFFBF) | \ (((val) & 0x0001) << 6); \ } \ while (FALSE) #define WINDIVERT_IPHDR_SET_RESERVED(hdr, val) \ do \ { \ (hdr)->FragOff0 = (((hdr)->FragOff0) & 0xFF7F) | \ (((val) & 0x0001) << 7); \ } \ while (FALSE) typedef struct { UINT8 TrafficClass0:4; UINT8 Version:4; UINT8 FlowLabel0:4; UINT8 TrafficClass1:4; UINT16 FlowLabel1; UINT16 Length; UINT8 NextHdr; UINT8 HopLimit; UINT32 SrcAddr[4]; UINT32 DstAddr[4]; } WINDIVERT_IPV6HDR, *PWINDIVERT_IPV6HDR; #define WINDIVERT_IPV6HDR_GET_TRAFFICCLASS(hdr) \ ((((hdr)->TrafficClass0) << 4) | ((hdr)->TrafficClass1)) #define WINDIVERT_IPV6HDR_GET_FLOWLABEL(hdr) \ ((((UINT32)(hdr)->FlowLabel0) << 16) | ((UINT32)(hdr)->FlowLabel1)) #define WINDIVERT_IPV6HDR_SET_TRAFFICCLASS(hdr, val) \ do \ { \ (hdr)->TrafficClass0 = ((UINT8)(val) >> 4); \ (hdr)->TrafficClass1 = (UINT8)(val); \ } \ while (FALSE) #define WINDIVERT_IPV6HDR_SET_FLOWLABEL(hdr, val) \ do \ { \ (hdr)->FlowLabel0 = (UINT8)((val) >> 16); \ (hdr)->FlowLabel1 = (UINT16)(val); \ } \ while (FALSE) typedef struct { UINT8 Type; UINT8 Code; UINT16 Checksum; UINT32 Body; } WINDIVERT_ICMPHDR, *PWINDIVERT_ICMPHDR; typedef struct { UINT8 Type; UINT8 Code; UINT16 Checksum; UINT32 Body; } WINDIVERT_ICMPV6HDR, *PWINDIVERT_ICMPV6HDR; typedef struct { UINT16 SrcPort; UINT16 DstPort; UINT32 SeqNum; UINT32 AckNum; UINT16 Reserved1:4; UINT16 HdrLength:4; UINT16 Fin:1; UINT16 Syn:1; UINT16 Rst:1; UINT16 Psh:1; UINT16 Ack:1; UINT16 Urg:1; UINT16 Reserved2:2; UINT16 Window; UINT16 Checksum; UINT16 UrgPtr; } WINDIVERT_TCPHDR, *PWINDIVERT_TCPHDR; typedef struct { UINT16 SrcPort; UINT16 DstPort; UINT16 Length; UINT16 Checksum; } WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR; #ifdef _MSC_VER #pragma warning(pop) #endif /* * Flags for WinDivertHelperCalcChecksums() */ #define WINDIVERT_HELPER_NO_IP_CHECKSUM 1 #define WINDIVERT_HELPER_NO_ICMP_CHECKSUM 2 #define WINDIVERT_HELPER_NO_ICMPV6_CHECKSUM 4 #define WINDIVERT_HELPER_NO_TCP_CHECKSUM 8 #define WINDIVERT_HELPER_NO_UDP_CHECKSUM 16 #ifndef WINDIVERT_KERNEL /* * Hash a packet. */ WINDIVERTEXPORT UINT64 WinDivertHelperHashPacket( __in const VOID *pPacket, __in UINT packetLen, __in UINT64 seed #ifdef __cplusplus = 0 #endif ); /* * Parse IPv4/IPv6/ICMP/ICMPv6/TCP/UDP headers from a raw packet. */ WINDIVERTEXPORT BOOL WinDivertHelperParsePacket( __in const VOID *pPacket, __in UINT packetLen, __out_opt PWINDIVERT_IPHDR *ppIpHdr, __out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr, __out_opt UINT8 *pProtocol, __out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr, __out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr, __out_opt PWINDIVERT_TCPHDR *ppTcpHdr, __out_opt PWINDIVERT_UDPHDR *ppUdpHdr, __out_opt PVOID *ppData, __out_opt UINT *pDataLen, __out_opt PVOID *ppNext, __out_opt UINT *pNextLen); /* * Parse an IPv4 address. */ WINDIVERTEXPORT BOOL WinDivertHelperParseIPv4Address( __in const char *addrStr, __out_opt UINT32 *pAddr); /* * Parse an IPv6 address. */ WINDIVERTEXPORT BOOL WinDivertHelperParseIPv6Address( __in const char *addrStr, __out_opt UINT32 *pAddr); /* * Format an IPv4 address. */ WINDIVERTEXPORT BOOL WinDivertHelperFormatIPv4Address( __in UINT32 addr, __out char *buffer, __in UINT bufLen); /* * Format an IPv6 address. */ WINDIVERTEXPORT BOOL WinDivertHelperFormatIPv6Address( __in const UINT32 *pAddr, __out char *buffer, __in UINT bufLen); /* * Calculate IPv4/IPv6/ICMP/ICMPv6/TCP/UDP checksums. */ WINDIVERTEXPORT BOOL WinDivertHelperCalcChecksums( __inout VOID *pPacket, __in UINT packetLen, __out_opt WINDIVERT_ADDRESS *pAddr, __in UINT64 flags); /* * Decrement the TTL/HopLimit. */ WINDIVERTEXPORT BOOL WinDivertHelperDecrementTTL( __inout VOID *pPacket, __in UINT packetLen); /* * Compile the given filter string. */ WINDIVERTEXPORT BOOL WinDivertHelperCompileFilter( __in const char *filter, __in WINDIVERT_LAYER layer, __out_opt char *object, __in UINT objLen, __out_opt const char **errorStr, __out_opt UINT *errorPos); /* * Evaluate the given filter string. */ WINDIVERTEXPORT BOOL WinDivertHelperEvalFilter( __in const char *filter, __in const VOID *pPacket, __in UINT packetLen, __in const WINDIVERT_ADDRESS *pAddr); /* * Format the given filter string. */ WINDIVERTEXPORT BOOL WinDivertHelperFormatFilter( __in const char *filter, __in WINDIVERT_LAYER layer, __out char *buffer, __in UINT bufLen); /* * Byte ordering. */ WINDIVERTEXPORT UINT16 WinDivertHelperNtohs( __in UINT16 x); WINDIVERTEXPORT UINT16 WinDivertHelperHtons( __in UINT16 x); WINDIVERTEXPORT UINT32 WinDivertHelperNtohl( __in UINT32 x); WINDIVERTEXPORT UINT32 WinDivertHelperHtonl( __in UINT32 x); WINDIVERTEXPORT UINT64 WinDivertHelperNtohll( __in UINT64 x); WINDIVERTEXPORT UINT64 WinDivertHelperHtonll( __in UINT64 x); WINDIVERTEXPORT void WinDivertHelperNtohIPv6Address( __in const UINT *inAddr, __out UINT *outAddr); WINDIVERTEXPORT void WinDivertHelperHtonIPv6Address( __in const UINT *inAddr, __out UINT *outAddr); /* * Old names to be removed in the next version. */ WINDIVERTEXPORT void WinDivertHelperNtohIpv6Address( __in const UINT *inAddr, __out UINT *outAddr); WINDIVERTEXPORT void WinDivertHelperHtonIpv6Address( __in const UINT *inAddr, __out UINT *outAddr); #endif /* WINDIVERT_KERNEL */ #ifdef __cplusplus } #endif #endif /* __WINDIVERT_H */ ================================================ FILE: tmp/.keep ================================================ ================================================ FILE: tpws/BSDmakefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto LIBS = -lz -lpthread SRC_FILES = *.c all: tpws tpws: $(SRC_FILES) $(CC) $(CFLAGS) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) clean: rm -f tpws *.o ================================================ FILE: tpws/Makefile ================================================ CC ?= cc OPTIMIZE ?= -Os CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto -ffunction-sections -fdata-sections CFLAGS_SYSTEMD = -DUSE_SYSTEMD CFLAGS_BSD = -Wno-address-of-packed-member LDFLAGS += -flto=auto LDFLAGS_ANDROID = -Wl,--gc-sections -llog LDFLAGS_BSD = -Wl,--gc-sections LDFLAGS_LINUX = -Wl,--gc-sections LDFLAGS_MAC = -Wl,-dead_strip LIBS = -lz -lpthread LIBS_SYSTEMD = -lsystemd LIBS_ANDROID = -lz SRC_FILES = *.c SRC_FILES_ANDROID = $(SRC_FILES) andr/*.c all: tpws tpws: $(SRC_FILES) $(CC) -s $(CFLAGS) -o tpws $(SRC_FILES) $(LIBS) $(LDFLAGS) $(LDFLAGS_LINUX) systemd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_SYSTEMD) -o tpws $(SRC_FILES) $(LIBS) $(LIBS_SYSTEMD) $(LDFLAGS) $(LDFLAGS_LINUX) android: $(SRC_FILES) $(CC) -s $(CFLAGS) -o tpws $(SRC_FILES_ANDROID) $(LIBS_ANDROID) $(LDFLAGS) $(LDFLAGS_ANDROID) bsd: $(SRC_FILES) $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LIBS) $(LDFLAGS) $(LDFLAGS_BSD) mac: $(SRC_FILES) $(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) $(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) strip tpwsa tpwsx lipo -create -output tpws tpwsx tpwsa rm -f tpwsx tpwsa clean: rm -f tpws *.o ================================================ FILE: tpws/andr/_musl_license.txt ================================================ Code in this dir is taken from musl libc to support old android versions <7.0 musl as a whole is licensed under the following standard MIT license: ---------------------------------------------------------------------- Copyright 2005-2020 Rich Felker, et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------------------- ================================================ FILE: tpws/andr/getifaddrs.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "netlink.h" #define IFADDRS_HASH_SIZE 64 /* getifaddrs() reports hardware addresses with PF_PACKET that implies * struct sockaddr_ll. But e.g. Infiniband socket address length is * longer than sockaddr_ll.ssl_addr[8] can hold. Use this hack struct * to extend ssl_addr - callers should be able to still use it. */ struct sockaddr_ll_hack { unsigned short sll_family, sll_protocol; int sll_ifindex; unsigned short sll_hatype; unsigned char sll_pkttype, sll_halen; unsigned char sll_addr[24]; }; union sockany { struct sockaddr sa; struct sockaddr_ll_hack ll; struct sockaddr_in v4; struct sockaddr_in6 v6; }; struct ifaddrs_storage { struct ifaddrs ifa; struct ifaddrs_storage *hash_next; union sockany addr, netmask, ifu; unsigned int index; char name[IFNAMSIZ+1]; }; struct ifaddrs_ctx { struct ifaddrs *first; struct ifaddrs *last; struct ifaddrs_storage *hash[IFADDRS_HASH_SIZE]; }; void freeifaddrs(struct ifaddrs *ifp) { struct ifaddrs *n; while (ifp) { n = ifp->ifa_next; free(ifp); ifp = n; } } static void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex) { uint8_t *dst; int len; switch (af) { case AF_INET: dst = (uint8_t*) &sa->v4.sin_addr; len = 4; break; case AF_INET6: dst = (uint8_t*) &sa->v6.sin6_addr; len = 16; if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr)) sa->v6.sin6_scope_id = ifindex; break; default: return; } if (addrlen < len) return; sa->sa.sa_family = af; memcpy(dst, addr, len); *r = &sa->sa; } static void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen) { uint8_t addr[16] = {0}; int i; if (prefixlen > 8*sizeof(addr)) prefixlen = 8*sizeof(addr); i = prefixlen / 8; memset(addr, 0xff, i); if (i < sizeof(addr)) addr[i++] = 0xff << (8 - (prefixlen % 8)); copy_addr(r, af, sa, addr, sizeof(addr), 0); } static void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype) { if (addrlen > sizeof(sa->ll.sll_addr)) return; sa->ll.sll_family = AF_PACKET; sa->ll.sll_ifindex = ifindex; sa->ll.sll_hatype = hatype; sa->ll.sll_halen = addrlen; memcpy(sa->ll.sll_addr, addr, addrlen); *r = &sa->sa; } static int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h) { struct ifaddrs_ctx *ctx = pctx; struct ifaddrs_storage *ifs, *ifs0; struct ifinfomsg *ifi = NLMSG_DATA(h); struct ifaddrmsg *ifa = NLMSG_DATA(h); struct rtattr *rta; int stats_len = 0; if (h->nlmsg_type == RTM_NEWLINK) { for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { if (rta->rta_type != IFLA_STATS) continue; stats_len = RTA_DATALEN(rta); break; } } else { for (ifs0 = ctx->hash[ifa->ifa_index % IFADDRS_HASH_SIZE]; ifs0; ifs0 = ifs0->hash_next) if (ifs0->index == ifa->ifa_index) break; if (!ifs0) return 0; } ifs = calloc(1, sizeof(struct ifaddrs_storage) + stats_len); if (ifs == 0) return -1; if (h->nlmsg_type == RTM_NEWLINK) { ifs->index = ifi->ifi_index; ifs->ifa.ifa_flags = ifi->ifi_flags; for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { switch (rta->rta_type) { case IFLA_IFNAME: if (RTA_DATALEN(rta) < sizeof(ifs->name)) { memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); ifs->ifa.ifa_name = ifs->name; } break; case IFLA_ADDRESS: copy_lladdr(&ifs->ifa.ifa_addr, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); break; case IFLA_BROADCAST: copy_lladdr(&ifs->ifa.ifa_broadaddr, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); break; case IFLA_STATS: ifs->ifa.ifa_data = (void*)(ifs+1); memcpy(ifs->ifa.ifa_data, RTA_DATA(rta), RTA_DATALEN(rta)); break; } } if (ifs->ifa.ifa_name) { unsigned int bucket = ifs->index % IFADDRS_HASH_SIZE; ifs->hash_next = ctx->hash[bucket]; ctx->hash[bucket] = ifs; } } else { ifs->ifa.ifa_name = ifs0->ifa.ifa_name; ifs->ifa.ifa_flags = ifs0->ifa.ifa_flags; for (rta = NLMSG_RTA(h, sizeof(*ifa)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { switch (rta->rta_type) { case IFA_ADDRESS: /* If ifa_addr is already set we, received an IFA_LOCAL before * so treat this as destination address */ if (ifs->ifa.ifa_addr) copy_addr(&ifs->ifa.ifa_dstaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); else copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); break; case IFA_BROADCAST: copy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); break; case IFA_LOCAL: /* If ifa_addr is set and we get IFA_LOCAL, assume we have * a point-to-point network. Move address to correct field. */ if (ifs->ifa.ifa_addr) { ifs->ifu = ifs->addr; ifs->ifa.ifa_dstaddr = &ifs->ifu.sa; memset(&ifs->addr, 0, sizeof(ifs->addr)); } copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); break; case IFA_LABEL: if (RTA_DATALEN(rta) < sizeof(ifs->name)) { memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); ifs->ifa.ifa_name = ifs->name; } break; } } if (ifs->ifa.ifa_addr) gen_netmask(&ifs->ifa.ifa_netmask, ifa->ifa_family, &ifs->netmask, ifa->ifa_prefixlen); } if (ifs->ifa.ifa_name) { if (!ctx->first) ctx->first = &ifs->ifa; if (ctx->last) ctx->last->ifa_next = &ifs->ifa; ctx->last = &ifs->ifa; } else { free(ifs); } return 0; } int getifaddrs(struct ifaddrs **ifap) { struct ifaddrs_ctx _ctx, *ctx = &_ctx; int r; memset(ctx, 0, sizeof *ctx); r = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx); if (r == 0) *ifap = ctx->first; else freeifaddrs(ctx->first); return r; } ================================================ FILE: tpws/andr/ifaddrs.h ================================================ #pragma once #include #if __ANDROID_API__ < 24 void freeifaddrs(struct ifaddrs *); int getifaddrs(struct ifaddrs **); #endif ================================================ FILE: tpws/andr/netlink.c ================================================ #include #include #include #include #include #include "netlink.h" static int __netlink_enumerate(int fd, unsigned int seq, int type, int af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx) { struct nlmsghdr *h; union { uint8_t buf[8192]; struct { struct nlmsghdr nlh; struct rtgenmsg g; } req; struct nlmsghdr reply; } u; int r, ret; memset(&u.req, 0, sizeof(u.req)); u.req.nlh.nlmsg_len = sizeof(u.req); u.req.nlh.nlmsg_type = type; u.req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; u.req.nlh.nlmsg_seq = seq; u.req.g.rtgen_family = af; r = send(fd, &u.req, sizeof(u.req), 0); if (r < 0) return r; while (1) { r = recv(fd, u.buf, sizeof(u.buf), MSG_DONTWAIT); if (r <= 0) return -1; for (h = &u.reply; NLMSG_OK(h, (void*)&u.buf[r]); h = NLMSG_NEXT(h)) { if (h->nlmsg_type == NLMSG_DONE) return 0; if (h->nlmsg_type == NLMSG_ERROR) return -1; ret = cb(ctx, h); if (ret) return ret; } } } int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx) { int fd, r; fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); if (fd < 0) return -1; r = __netlink_enumerate(fd, 1, RTM_GETLINK, link_af, cb, ctx); if (!r) r = __netlink_enumerate(fd, 2, RTM_GETADDR, addr_af, cb, ctx); close(fd); return r; } ================================================ FILE: tpws/andr/netlink.h ================================================ #include /* linux/netlink.h */ #define NETLINK_ROUTE 0 struct nlmsghdr { uint32_t nlmsg_len; uint16_t nlmsg_type; uint16_t nlmsg_flags; uint32_t nlmsg_seq; uint32_t nlmsg_pid; }; #define NLM_F_REQUEST 1 #define NLM_F_MULTI 2 #define NLM_F_ACK 4 #define NLM_F_ROOT 0x100 #define NLM_F_MATCH 0x200 #define NLM_F_ATOMIC 0x400 #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) #define NLMSG_NOOP 0x1 #define NLMSG_ERROR 0x2 #define NLMSG_DONE 0x3 #define NLMSG_OVERRUN 0x4 /* linux/rtnetlink.h */ #define RTM_NEWLINK 16 #define RTM_GETLINK 18 #define RTM_NEWADDR 20 #define RTM_GETADDR 22 struct rtattr { unsigned short rta_len; unsigned short rta_type; }; struct rtgenmsg { unsigned char rtgen_family; }; struct ifinfomsg { unsigned char ifi_family; unsigned char __ifi_pad; unsigned short ifi_type; int ifi_index; unsigned ifi_flags; unsigned ifi_change; }; /* linux/if_link.h */ #define IFLA_ADDRESS 1 #define IFLA_BROADCAST 2 #define IFLA_IFNAME 3 #define IFLA_STATS 7 /* linux/if_addr.h */ struct ifaddrmsg { uint8_t ifa_family; uint8_t ifa_prefixlen; uint8_t ifa_flags; uint8_t ifa_scope; uint32_t ifa_index; }; #define IFA_ADDRESS 1 #define IFA_LOCAL 2 #define IFA_LABEL 3 #define IFA_BROADCAST 4 /* musl */ #define NETLINK_ALIGN(len) (((len)+3) & ~3) #define NLMSG_DATA(nlh) ((void*)((char*)(nlh)+sizeof(struct nlmsghdr))) #define NLMSG_DATALEN(nlh) ((nlh)->nlmsg_len-sizeof(struct nlmsghdr)) #define NLMSG_DATAEND(nlh) ((char*)(nlh)+(nlh)->nlmsg_len) #define NLMSG_NEXT(nlh) (struct nlmsghdr*)((char*)(nlh)+NETLINK_ALIGN((nlh)->nlmsg_len)) #define NLMSG_OK(nlh,end) ((char*)(end)-(char*)(nlh) >= sizeof(struct nlmsghdr)) #define RTA_DATA(rta) ((void*)((char*)(rta)+sizeof(struct rtattr))) #define RTA_DATALEN(rta) ((rta)->rta_len-sizeof(struct rtattr)) #define RTA_DATAEND(rta) ((char*)(rta)+(rta)->rta_len) #define RTA_NEXT(rta) (struct rtattr*)((char*)(rta)+NETLINK_ALIGN((rta)->rta_len)) #define RTA_OK(rta,end) ((char*)(end)-(char*)(rta) >= sizeof(struct rtattr)) #define NLMSG_RTA(nlh,len) ((void*)((char*)(nlh)+sizeof(struct nlmsghdr)+NETLINK_ALIGN(len))) #define NLMSG_RTAOK(rta,nlh) RTA_OK(rta,NLMSG_DATAEND(nlh)) int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx); ================================================ FILE: tpws/epoll-shim/include/sys/epoll.h ================================================ #ifndef SHIM_SYS_EPOLL_H #define SHIM_SYS_EPOLL_H #ifdef __cplusplus extern "C" { #endif #include #include #include #if defined(__NetBSD__) #include #elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__APPLE__) #include #endif #define EPOLL_CLOEXEC O_CLOEXEC #define EPOLL_NONBLOCK O_NONBLOCK enum EPOLL_EVENTS { __EPOLL_DUMMY }; #define EPOLLIN 0x001 #define EPOLLPRI 0x002 #define EPOLLOUT 0x004 #define EPOLLRDNORM 0x040 #define EPOLLNVAL 0x020 #define EPOLLRDBAND 0x080 #define EPOLLWRNORM 0x100 #define EPOLLWRBAND 0x200 #define EPOLLMSG 0x400 #define EPOLLERR 0x008 #define EPOLLHUP 0x010 #define EPOLLRDHUP 0x2000 #define EPOLLEXCLUSIVE (1U<<28) #define EPOLLWAKEUP (1U<<29) #define EPOLLONESHOT (1U<<30) #define EPOLLET (1U<<31) #define EPOLL_CTL_ADD 1 #define EPOLL_CTL_DEL 2 #define EPOLL_CTL_MOD 3 typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; epoll_data_t data; } #ifdef __x86_64__ __attribute__ ((__packed__)) #endif ; int epoll_create(int); int epoll_create1(int); int epoll_ctl(int, int, int, struct epoll_event *); int epoll_wait(int, struct epoll_event *, int, int); int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); #ifndef SHIM_SYS_SHIM_HELPERS #define SHIM_SYS_SHIM_HELPERS #include /* IWYU pragma: keep */ extern int epoll_shim_close(int); #define close epoll_shim_close #endif #ifdef __cplusplus } #endif #endif /* sys/epoll.h */ ================================================ FILE: tpws/epoll-shim/src/epoll.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include "epoll_shim_ctx.h" #ifdef __NetBSD__ #define ppoll pollts #endif // TODO(jan): Remove this once the definition is exposed in in // all supported FreeBSD versions. #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (0) #endif static errno_t epollfd_close(FDContextMapNode *node) { return epollfd_ctx_terminate(&node->ctx.epollfd); } static FDContextVTable const epollfd_vtable = { .read_fun = fd_context_default_read, .write_fun = fd_context_default_write, .close_fun = epollfd_close, }; static FDContextMapNode * epoll_create_impl(errno_t *ec) { FDContextMapNode *node; node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); if (!node) { return NULL; } node->flags = 0; if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/ node->fd)) != 0) { goto fail; } node->vtable = &epollfd_vtable; return node; fail: epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); (void)fd_context_map_node_destroy(node); return NULL; } static int epoll_create_common(void) { FDContextMapNode *node; errno_t ec; node = epoll_create_impl(&ec); if (!node) { errno = ec; return -1; } return node->fd; } int epoll_create(int size) { if (size <= 0) { errno = EINVAL; return -1; } return epoll_create_common(); } int epoll_create1(int flags) { if (flags & ~EPOLL_CLOEXEC) { errno = EINVAL; return -1; } return epoll_create_common(); } static errno_t epoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev) { if (!ev && op != EPOLL_CTL_DEL) { return EFAULT; } FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node || node->vtable != &epollfd_vtable) { struct stat sb; return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; } return epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev); } int epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) { errno_t ec = epoll_ctl_impl(fd, op, fd2, ev); if (ec != 0) { errno = ec; return -1; } return 0; } static bool is_no_wait_deadline(struct timespec const *deadline) { return (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0); } static errno_t epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs) { errno_t ec; for (;;) { if ((ec = epollfd_ctx_wait(epollfd, /**/ ev, cnt, actual_cnt)) != 0) { return ec; } if (*actual_cnt || is_no_wait_deadline(deadline)) { return 0; } struct timespec timeout; if (deadline) { struct timespec current_time; if (clock_gettime(CLOCK_MONOTONIC, /**/ ¤t_time) < 0) { return errno; } timespecsub(deadline, ¤t_time, &timeout); if (timeout.tv_sec < 0 || is_no_wait_deadline(&timeout)) { return 0; } } (void)pthread_mutex_lock(&epollfd->mutex); nfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size); size_t size; if (__builtin_mul_overflow(nfds, sizeof(struct pollfd), &size)) { ec = ENOMEM; (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } struct pollfd *pfds = malloc(size); if (!pfds) { ec = errno; (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } epollfd_ctx_fill_pollfds(epollfd, pfds); (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); ++epollfd->nr_polling_threads; (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); (void)pthread_mutex_unlock(&epollfd->mutex); /* * This surfaced a race condition when * registering/unregistering poll-only fds. The tests should * still succeed if this is enabled. */ #if 0 usleep(500000); #endif int n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs); if (n < 0) { ec = errno; } free(pfds); (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); --epollfd->nr_polling_threads; if (epollfd->nr_polling_threads == 0) { (void)pthread_cond_signal( &epollfd->nr_polling_threads_cond); } (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); if (n < 0) { return ec; } } } static errno_t timeout_to_deadline(struct timespec *deadline, int to) { assert(to >= 0); if (to == 0) { *deadline = (struct timespec){0, 0}; } else if (to > 0) { if (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) { return errno; } if (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1, &deadline->tv_sec)) { return EINVAL; } deadline->tv_sec -= 1; deadline->tv_nsec += (to % 1000) * 1000000L; if (deadline->tv_nsec >= 1000000000) { deadline->tv_nsec -= 1000000000; deadline->tv_sec += 1; } } return 0; } static errno_t epoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to, sigset_t const *sigs, int *actual_cnt) { if (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) { return EINVAL; } FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node || node->vtable != &epollfd_vtable) { struct stat sb; return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; } struct timespec deadline; errno_t ec; if (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) { return ec; } return epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt, actual_cnt, (to >= 0) ? &deadline : NULL, sigs); } int epoll_pwait(int fd, struct epoll_event *ev, int cnt, int to, sigset_t const *sigs) { int actual_cnt; errno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt); if (ec != 0) { errno = ec; return -1; } return actual_cnt; } int epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) { return epoll_pwait(fd, ev, cnt, to, NULL); } ================================================ FILE: tpws/epoll-shim/src/epoll_shim_ctx.c ================================================ #include "epoll_shim_ctx.h" #include #include #include #include #include static void fd_context_map_node_init(FDContextMapNode *node, int kq) { node->fd = kq; node->vtable = NULL; } static FDContextMapNode * fd_context_map_node_create(int kq, errno_t *ec) { FDContextMapNode *node; node = malloc(sizeof(FDContextMapNode)); if (!node) { *ec = errno; return NULL; } fd_context_map_node_init(node, kq); return node; } static errno_t fd_context_map_node_terminate(FDContextMapNode *node, bool close_fd) { errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0; if (close_fd && close(node->fd) < 0) { ec = ec ? ec : errno; } return ec; } errno_t fd_context_map_node_destroy(FDContextMapNode *node) { errno_t ec = fd_context_map_node_terminate(node, true); free(node); return ec; } /**/ errno_t fd_context_default_read(FDContextMapNode *node, /**/ void *buf, size_t nbytes, size_t *bytes_transferred) { (void)node; (void)buf; (void)nbytes; (void)bytes_transferred; return EINVAL; } errno_t fd_context_default_write(FDContextMapNode *node, /**/ void const *buf, size_t nbytes, size_t *bytes_transferred) { (void)node; (void)buf; (void)nbytes; (void)bytes_transferred; return EINVAL; } /**/ static int fd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2) { return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); } RB_PROTOTYPE_STATIC(fd_context_map_, fd_context_map_node_, entry, fd_context_map_node_cmp); RB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry, fd_context_map_node_cmp); EpollShimCtx epoll_shim_ctx = { .fd_context_map = RB_INITIALIZER(&fd_context_map), .mutex = PTHREAD_MUTEX_INITIALIZER, }; static FDContextMapNode * epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq, errno_t *ec) { FDContextMapNode *node; { FDContextMapNode find; find.fd = kq; node = RB_FIND(fd_context_map_, /**/ &epoll_shim_ctx->fd_context_map, &find); } if (node) { /* * If we get here, someone must have already closed the old fd * with a normal 'close()' call, i.e. not with our * 'epoll_shim_close()' wrapper. The fd inside the node * refers now to the new kq we are currently creating. We * must not close it, but we must clean up the old context * object! */ (void)fd_context_map_node_terminate(node, false); fd_context_map_node_init(node, kq); } else { node = fd_context_map_node_create(kq, ec); if (!node) { return NULL; } void *colliding_node = RB_INSERT(fd_context_map_, &epoll_shim_ctx->fd_context_map, node); (void)colliding_node; assert(colliding_node == NULL); } return node; } FDContextMapNode * epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec) { FDContextMapNode *node; int kq = kqueue(); if (kq < 0) { *ec = errno; return NULL; } (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); node = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec); (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); if (!node) { close(kq); } return node; } static FDContextMapNode * epoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd) { FDContextMapNode *node; FDContextMapNode find; find.fd = fd; node = RB_FIND(fd_context_map_, /**/ &epoll_shim_ctx->fd_context_map, &find); return node; } FDContextMapNode * epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd) { FDContextMapNode *node; (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); return node; } FDContextMapNode * epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd) { FDContextMapNode *node; (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); if (node) { RB_REMOVE(fd_context_map_, /**/ &epoll_shim_ctx->fd_context_map, node); } (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); return node; } void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, FDContextMapNode *node) { (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); RB_REMOVE(fd_context_map_, /**/ &epoll_shim_ctx->fd_context_map, node); (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); } /**/ int epoll_shim_close(int fd) { FDContextMapNode *node; node = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd); if (!node) { return close(fd); } errno_t ec = fd_context_map_node_destroy(node); if (ec != 0) { errno = ec; return -1; } return 0; } ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes) { FDContextMapNode *node; node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node) { return read(fd, buf, nbytes); } if (nbytes > SSIZE_MAX) { errno = EINVAL; return -1; } size_t bytes_transferred; errno_t ec = node->vtable->read_fun(node, /**/ buf, nbytes, &bytes_transferred); if (ec != 0) { errno = ec; return -1; } return (ssize_t)bytes_transferred; } ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes) { FDContextMapNode *node; node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node) { return write(fd, buf, nbytes); } if (nbytes > SSIZE_MAX) { errno = EINVAL; return -1; } size_t bytes_transferred; errno_t ec = node->vtable->write_fun(node, /**/ buf, nbytes, &bytes_transferred); if (ec != 0) { errno = ec; return -1; } return (ssize_t)bytes_transferred; } ================================================ FILE: tpws/epoll-shim/src/epoll_shim_ctx.h ================================================ #ifndef EPOLL_SHIM_CTX_H_ #define EPOLL_SHIM_CTX_H_ #include "fix.h" #include #include #include "epollfd_ctx.h" #include "eventfd_ctx.h" #include "signalfd_ctx.h" #include "timerfd_ctx.h" struct fd_context_map_node_; typedef struct fd_context_map_node_ FDContextMapNode; typedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/ void *buf, size_t nbytes, size_t *bytes_transferred); typedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/ const void *buf, size_t nbytes, size_t *bytes_transferred); typedef errno_t (*fd_context_close_fun)(FDContextMapNode *node); typedef struct { fd_context_read_fun read_fun; fd_context_write_fun write_fun; fd_context_close_fun close_fun; } FDContextVTable; errno_t fd_context_default_read(FDContextMapNode *node, /**/ void *buf, size_t nbytes, size_t *bytes_transferred); errno_t fd_context_default_write(FDContextMapNode *node, /**/ void const *buf, size_t nbytes, size_t *bytes_transferred); struct fd_context_map_node_ { RB_ENTRY(fd_context_map_node_) entry; int fd; int flags; union { EpollFDCtx epollfd; EventFDCtx eventfd; TimerFDCtx timerfd; SignalFDCtx signalfd; } ctx; FDContextVTable const *vtable; }; errno_t fd_context_map_node_destroy(FDContextMapNode *node); /**/ typedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap; typedef struct { FDContextMap fd_context_map; pthread_mutex_t mutex; } EpollShimCtx; extern EpollShimCtx epoll_shim_ctx; FDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec); FDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd); FDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd); void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, FDContextMapNode *node); /**/ int epoll_shim_close(int fd); ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes); ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes); #endif ================================================ FILE: tpws/epoll-shim/src/epollfd_ctx.c ================================================ #include "epollfd_ctx.h" #include #if defined(__FreeBSD__) #include #endif #include #include #include #include #include #if defined(__DragonFly__) /* For TAILQ_FOREACH_SAFE. */ #include #endif #include #include #include #include #include #include #include static RegisteredFDsNode * registered_fds_node_create(int fd) { RegisteredFDsNode *node; node = malloc(sizeof(*node)); if (!node) { return NULL; } *node = (RegisteredFDsNode){.fd = fd, .self_pipe = {-1, -1}}; return node; } static void registered_fds_node_destroy(RegisteredFDsNode *node) { if (node->self_pipe[0] >= 0 && node->self_pipe[1] >= 0) { (void)close(node->self_pipe[0]); (void)close(node->self_pipe[1]); } free(node); } typedef struct { int evfilt_read; int evfilt_write; int evfilt_except; } NeededFilters; static NeededFilters get_needed_filters(RegisteredFDsNode *fd2_node) { NeededFilters needed_filters; needed_filters.evfilt_except = 0; if (fd2_node->node_type == NODE_TYPE_FIFO) { if (fd2_node->node_data.fifo.readable && fd2_node->node_data.fifo.writable) { needed_filters.evfilt_read = !!( fd2_node->events & EPOLLIN); needed_filters.evfilt_write = !!( fd2_node->events & EPOLLOUT); if (fd2_node->events == 0) { needed_filters.evfilt_read = fd2_node->eof_state ? 1 : EV_CLEAR; } } else if (fd2_node->node_data.fifo.readable) { needed_filters.evfilt_read = !!( fd2_node->events & EPOLLIN); needed_filters.evfilt_write = 0; if (needed_filters.evfilt_read == 0) { needed_filters.evfilt_read = fd2_node->eof_state ? 1 : EV_CLEAR; } } else if (fd2_node->node_data.fifo.writable) { needed_filters.evfilt_read = 0; needed_filters.evfilt_write = !!( fd2_node->events & EPOLLOUT); if (needed_filters.evfilt_write == 0) { needed_filters.evfilt_write = fd2_node->eof_state ? 1 : EV_CLEAR; } } else { __builtin_unreachable(); } goto out; } if (fd2_node->node_type == NODE_TYPE_KQUEUE) { needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); needed_filters.evfilt_write = 0; assert(fd2_node->eof_state == 0); if (needed_filters.evfilt_read == 0) { needed_filters.evfilt_read = EV_CLEAR; } goto out; } if (fd2_node->node_type == NODE_TYPE_SOCKET) { needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); if (needed_filters.evfilt_read == 0 && (fd2_node->events & EPOLLRDHUP)) { needed_filters.evfilt_read = (fd2_node->eof_state & EOF_STATE_READ_EOF) ? 1 : EV_CLEAR; } #ifdef EVFILT_EXCEPT needed_filters.evfilt_except = !!(fd2_node->events & EPOLLPRI); #else if (needed_filters.evfilt_read == 0 && (fd2_node->events & EPOLLPRI)) { needed_filters.evfilt_read = fd2_node->pollpri_active ? 1 : EV_CLEAR; } #endif needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); /* Let's use EVFILT_READ to drive the POLLHUP. */ if (fd2_node->eof_state == (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { if (needed_filters.evfilt_read != 1 && needed_filters.evfilt_write != 1) { needed_filters.evfilt_read = 1; } if (needed_filters.evfilt_read) { needed_filters.evfilt_write = 0; } else { needed_filters.evfilt_read = 0; } } /* We need something to detect POLLHUP. */ if (fd2_node->eof_state == 0 && needed_filters.evfilt_read == 0 && needed_filters.evfilt_write == 0) { needed_filters.evfilt_read = EV_CLEAR; } if (fd2_node->eof_state == EOF_STATE_READ_EOF) { if (needed_filters.evfilt_write == 0) { needed_filters.evfilt_write = EV_CLEAR; } } if (fd2_node->eof_state == EOF_STATE_WRITE_EOF) { if (needed_filters.evfilt_read == 0) { needed_filters.evfilt_read = EV_CLEAR; } } goto out; } needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); if (fd2_node->events == 0) { needed_filters.evfilt_read = fd2_node->eof_state ? 1 : EV_CLEAR; } out: if (fd2_node->is_edge_triggered) { if (needed_filters.evfilt_read) { needed_filters.evfilt_read = EV_CLEAR; } if (needed_filters.evfilt_write) { needed_filters.evfilt_write = EV_CLEAR; } if (needed_filters.evfilt_except) { needed_filters.evfilt_except = EV_CLEAR; } } assert(needed_filters.evfilt_read || needed_filters.evfilt_write); assert(needed_filters.evfilt_read == 0 || needed_filters.evfilt_read == 1 || needed_filters.evfilt_read == EV_CLEAR); assert(needed_filters.evfilt_write == 0 || needed_filters.evfilt_write == 1 || needed_filters.evfilt_write == EV_CLEAR); assert(needed_filters.evfilt_except == 0 || needed_filters.evfilt_except == 1 || needed_filters.evfilt_except == EV_CLEAR); return needed_filters; } static void registered_fds_node_update_flags_from_epoll_event(RegisteredFDsNode *fd2_node, struct epoll_event *ev) { fd2_node->events = ev->events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLOUT); fd2_node->data = ev->data; fd2_node->is_edge_triggered = ev->events & EPOLLET; fd2_node->is_oneshot = ev->events & EPOLLONESHOT; if (fd2_node->is_oneshot) { fd2_node->is_edge_triggered = true; } } static errno_t registered_fds_node_add_self_trigger(RegisteredFDsNode *fd2_node, EpollFDCtx *epollfd) { struct kevent kevs[1]; #ifdef EVFILT_USER EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ EV_ADD | EV_CLEAR, 0, 0, fd2_node); #else if (fd2_node->self_pipe[0] < 0 && fd2_node->self_pipe[1] < 0) { if (pipe2(fd2_node->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { errno_t ec = errno; fd2_node->self_pipe[0] = fd2_node->self_pipe[1] = -1; return ec; } assert(fd2_node->self_pipe[0] >= 0); assert(fd2_node->self_pipe[1] >= 0); } EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ EV_ADD | EV_CLEAR, 0, 0, fd2_node); #endif if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { return errno; } return 0; } static void registered_fds_node_trigger_self(RegisteredFDsNode *fd2_node, EpollFDCtx *epollfd) { #ifdef EVFILT_USER struct kevent kevs[1]; EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ 0, NOTE_TRIGGER, 0, fd2_node); (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); #else (void)epollfd; assert(fd2_node->self_pipe[1] >= 0); char c = 0; (void)write(fd2_node->self_pipe[1], &c, 1); #endif } static void registered_fds_node_feed_event(RegisteredFDsNode *fd2_node, EpollFDCtx *epollfd, struct kevent const *kev) { int revents = 0; if (fd2_node->node_type == NODE_TYPE_POLL) { assert(fd2_node->revents == 0); #ifdef EVFILT_USER assert(kev->filter == EVFILT_USER); #else char c[32]; while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { } #endif struct pollfd pfd = { .fd = fd2_node->fd, .events = (short)fd2_node->events, }; revents = poll(&pfd, 1, 0) < 0 ? EPOLLERR : pfd.revents; fd2_node->revents = revents & POLLNVAL ? 0 : (uint32_t)revents; assert(!(fd2_node->revents & ~(uint32_t)(POLLIN | POLLOUT | POLLERR | POLLHUP))); return; } if (fd2_node->node_type == NODE_TYPE_FIFO && #ifdef EVFILT_USER kev->filter == EVFILT_USER #else (fd2_node->self_pipe[0] >= 0 && kev->ident == (uintptr_t)fd2_node->self_pipe[0]) #endif ) { assert(fd2_node->revents == 0); assert(!fd2_node->has_evfilt_read); assert(!fd2_node->has_evfilt_write); assert(!fd2_node->has_evfilt_except); NeededFilters needed_filters = get_needed_filters(fd2_node); assert(needed_filters.evfilt_write); struct kevent nkev[1]; EV_SET(&nkev[0], fd2_node->fd, EVFILT_WRITE, EV_ADD | (needed_filters.evfilt_write & EV_CLEAR) | EV_RECEIPT, 0, 0, fd2_node); if (kevent(epollfd->kq, nkev, 1, nkev, 1, NULL) != 1 || nkev[0].data != 0) { revents = EPOLLERR | EPOLLOUT; if (!fd2_node->is_edge_triggered) { registered_fds_node_trigger_self(fd2_node, epollfd); } goto out; } else { fd2_node->has_evfilt_write = true; return; } } #ifdef EVFILT_EXCEPT assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE || kev->filter == EVFILT_EXCEPT); #else assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE); #endif assert((int)kev->ident == fd2_node->fd); if (kev->filter == EVFILT_READ) { revents |= EPOLLIN; #ifndef EVFILT_EXCEPT if (fd2_node->events & EPOLLPRI) { struct pollfd pfd = { .fd = fd2_node->fd, .events = POLLPRI, }; if ((poll(&pfd, 1, 0) == 1) && (pfd.revents & POLLPRI)) { revents |= EPOLLPRI; fd2_node->pollpri_active = true; } else { fd2_node->pollpri_active = false; } } #endif } else if (kev->filter == EVFILT_WRITE) { revents |= EPOLLOUT; } #ifdef EVFILT_EXCEPT else if (kev->filter == EVFILT_EXCEPT) { assert((kev->fflags & NOTE_OOB) != 0); revents |= EPOLLPRI; goto out; } #endif if (fd2_node->node_type == NODE_TYPE_SOCKET) { if (kev->filter == EVFILT_READ) { if (kev->flags & EV_EOF) { fd2_node->eof_state |= EOF_STATE_READ_EOF; } else { fd2_node->eof_state &= ~EOF_STATE_READ_EOF; } } else if (kev->filter == EVFILT_WRITE) { if (kev->flags & EV_EOF) { fd2_node->eof_state |= EOF_STATE_WRITE_EOF; } else { fd2_node->eof_state &= ~EOF_STATE_WRITE_EOF; } } } else { if (kev->filter == EVFILT_READ) { if (kev->flags & EV_EOF) { fd2_node->eof_state = EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF; } else { fd2_node->eof_state = 0; } } else if (kev->filter == EVFILT_WRITE) { if (kev->flags & EV_EOF) { fd2_node->eof_state = EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF; } else { fd2_node->eof_state = 0; } } } if (kev->flags & EV_ERROR) { revents |= EPOLLERR; } if (kev->flags & EV_EOF) { if (kev->fflags) { revents |= EPOLLERR; } } if (fd2_node->eof_state) { int epoll_event; if (fd2_node->node_type == NODE_TYPE_FIFO) { if (kev->filter == EVFILT_READ) { epoll_event = EPOLLHUP; if (kev->data == 0) { revents &= ~EPOLLIN; } } else if (kev->filter == EVFILT_WRITE) { if (fd2_node->has_evfilt_read) { assert( fd2_node->node_data.fifo.readable); assert( fd2_node->node_data.fifo.writable); /* * Any non-zero revents must have come * from the EVFILT_READ filter. It * could either be "POLLIN", * "POLLIN | POLLHUP" or "POLLHUP", so * we know if there is data to read. * But we also know that the FIFO is * done, so set POLLHUP because it * would be set anyway. * * If revents is zero, not setting it * will simply ignore this EVFILT_WRITE * and wait for the next EVFILT_READ * (which will be EOF). */ if (fd2_node->revents != 0) { fd2_node->revents |= POLLHUP; } return; } epoll_event = EPOLLERR; if (kev->data < PIPE_BUF) { revents &= ~EPOLLOUT; } } else { __builtin_unreachable(); } } else if (fd2_node->node_type == NODE_TYPE_SOCKET) { epoll_event = 0; if (fd2_node->eof_state & EOF_STATE_READ_EOF) { epoll_event |= EPOLLIN | EPOLLRDHUP; } if (fd2_node->eof_state & EOF_STATE_WRITE_EOF) { epoll_event |= EPOLLOUT; } if (fd2_node->eof_state == (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { epoll_event |= EPOLLHUP; } } else { epoll_event = EPOLLHUP; } revents |= epoll_event; } out: fd2_node->revents |= (uint32_t)revents; fd2_node->revents &= (fd2_node->events | EPOLLHUP | EPOLLERR); if (fd2_node->revents && (uintptr_t)fd2_node->fd == kev->ident) { if (kev->filter == EVFILT_READ) { fd2_node->got_evfilt_read = true; } else if (kev->filter == EVFILT_WRITE) { fd2_node->got_evfilt_write = true; } #ifdef EVFILT_EXCEPT else if (kev->filter == EVFILT_EXCEPT) { fd2_node->got_evfilt_except = true; } #endif } } static void registered_fds_node_register_for_completion(int *kq, RegisteredFDsNode *fd2_node) { struct kevent kev[3]; int n = 0; if (fd2_node->has_evfilt_read && !fd2_node->got_evfilt_read) { EV_SET(&kev[n++], fd2_node->fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); } if (fd2_node->has_evfilt_write && !fd2_node->got_evfilt_write) { EV_SET(&kev[n++], fd2_node->fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); } if (fd2_node->has_evfilt_except && !fd2_node->got_evfilt_except) { #ifdef EVFILT_EXCEPT EV_SET(&kev[n++], fd2_node->fd, EVFILT_EXCEPT, EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_OOB, 0, fd2_node); #else assert(0); #endif } if (n == 0) { return; } if (*kq < 0) { *kq = kqueue(); } if (*kq >= 0) { (void)kevent(*kq, kev, n, kev, n, NULL); } } static void registered_fds_node_complete(int kq) { if (kq < 0) { return; } struct kevent kevs[32]; int n; while ((n = kevent(kq, /**/ NULL, 0, kevs, 32, &(struct timespec){0, 0})) > 0) { for (int i = 0; i < n; ++i) { RegisteredFDsNode *fd2_node = (RegisteredFDsNode *)kevs[i].udata; registered_fds_node_feed_event(fd2_node, NULL, &kevs[i]); } } (void)close(kq); } static int fd_cmp(RegisteredFDsNode *e1, RegisteredFDsNode *e2) { return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); } RB_PROTOTYPE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); RB_GENERATE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq) { errno_t ec; *epollfd = (EpollFDCtx){ .kq = kq, .registered_fds = RB_INITIALIZER(®istered_fds), .self_pipe = {-1, -1}, }; TAILQ_INIT(&epollfd->poll_fds); if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { return ec; } if ((ec = pthread_mutex_init(&epollfd->nr_polling_threads_mutex, NULL)) != 0) { pthread_mutex_destroy(&epollfd->mutex); return ec; } if ((ec = pthread_cond_init(&epollfd->nr_polling_threads_cond, NULL)) != 0) { pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); pthread_mutex_destroy(&epollfd->mutex); return ec; } return 0; } errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd) { errno_t ec = 0; errno_t ec_local; ec_local = pthread_cond_destroy(&epollfd->nr_polling_threads_cond); ec = ec ? ec : ec_local; ec_local = pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); ec = ec ? ec : ec_local; ec_local = pthread_mutex_destroy(&epollfd->mutex); ec = ec ? ec : ec_local; RegisteredFDsNode *np; RegisteredFDsNode *np_temp; RB_FOREACH_SAFE(np, registered_fds_set_, &epollfd->registered_fds, np_temp) { RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, np); registered_fds_node_destroy(np); } free(epollfd->kevs); free(epollfd->pfds); if (epollfd->self_pipe[0] >= 0 && epollfd->self_pipe[1] >= 0) { (void)close(epollfd->self_pipe[0]); (void)close(epollfd->self_pipe[1]); } return ec; } static errno_t epollfd_ctx_make_kevs_space(EpollFDCtx *epollfd, size_t cnt) { assert(cnt > 0); if (cnt <= epollfd->kevs_length) { return 0; } size_t size; if (__builtin_mul_overflow(cnt, sizeof(struct kevent), &size)) { return ENOMEM; } struct kevent *new_kevs = realloc(epollfd->kevs, size); if (!new_kevs) { return errno; } epollfd->kevs = new_kevs; epollfd->kevs_length = cnt; return 0; } static errno_t epollfd_ctx_make_pfds_space(EpollFDCtx *epollfd) { size_t cnt = 1 + epollfd->poll_fds_size; if (cnt <= epollfd->pfds_length) { return 0; } size_t size; if (__builtin_mul_overflow(cnt, sizeof(struct pollfd), &size)) { return ENOMEM; } struct pollfd *new_pfds = realloc(epollfd->pfds, size); if (!new_pfds) { return errno; } epollfd->pfds = new_pfds; epollfd->pfds_length = cnt; return 0; } static errno_t epollfd_ctx__add_self_trigger(EpollFDCtx *epollfd) { struct kevent kevs[1]; #ifdef EVFILT_USER EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); #else if (epollfd->self_pipe[0] < 0 && epollfd->self_pipe[1] < 0) { if (pipe2(epollfd->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { errno_t ec = errno; epollfd->self_pipe[0] = epollfd->self_pipe[1] = -1; return ec; } assert(epollfd->self_pipe[0] >= 0); assert(epollfd->self_pipe[1] >= 0); } EV_SET(&kevs[0], epollfd->self_pipe[0], EVFILT_READ, /**/ EV_ADD | EV_CLEAR, 0, 0, 0); #endif if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { return errno; } return 0; } static void epollfd_ctx__trigger_self(EpollFDCtx *epollfd) { #ifdef EVFILT_USER struct kevent kevs[1]; EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); #else assert(epollfd->self_pipe[0] >= 0); assert(epollfd->self_pipe[1] >= 0); char c = 0; (void)write(epollfd->self_pipe[1], &c, 1); #endif } static void epollfd_ctx__trigger_repoll(EpollFDCtx *epollfd) { (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); unsigned long nr_polling_threads = epollfd->nr_polling_threads; (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); if (nr_polling_threads == 0) { return; } epollfd_ctx__trigger_self(epollfd); (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); while (epollfd->nr_polling_threads != 0) { pthread_cond_wait(&epollfd->nr_polling_threads_cond, &epollfd->nr_polling_threads_mutex); } (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); #ifndef EVFILT_USER char c[32]; while (read(epollfd->self_pipe[0], c, sizeof(c)) >= 0) { } #endif } static void epollfd_ctx__remove_node_from_kq(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) { if (fd2_node->is_on_pollfd_list) { TAILQ_REMOVE(&epollfd->poll_fds, fd2_node, pollfd_list_entry); fd2_node->is_on_pollfd_list = false; assert(epollfd->poll_fds_size != 0); --epollfd->poll_fds_size; epollfd_ctx__trigger_repoll(epollfd); } if (fd2_node->self_pipe[0] >= 0) { struct kevent kevs[1]; EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ EV_DELETE, 0, 0, 0); (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); char c[32]; while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { } } if (fd2_node->node_type == NODE_TYPE_POLL) { #ifdef EVFILT_USER struct kevent kevs[1]; EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ EV_DELETE, 0, 0, 0); (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); #endif } else { struct kevent kevs[3]; int fd2 = fd2_node->fd; EV_SET(&kevs[0], fd2, EVFILT_READ, /**/ EV_DELETE | EV_RECEIPT, 0, 0, 0); EV_SET(&kevs[1], fd2, EVFILT_WRITE, /**/ EV_DELETE | EV_RECEIPT, 0, 0, 0); #ifdef EVFILT_USER EV_SET(&kevs[2], (uintptr_t)fd2_node, EVFILT_USER, /**/ EV_DELETE | EV_RECEIPT, 0, 0, 0); #endif (void)kevent(epollfd->kq, kevs, 3, kevs, 3, NULL); fd2_node->has_evfilt_read = false; fd2_node->has_evfilt_write = false; fd2_node->has_evfilt_except = false; } } static errno_t epollfd_ctx__register_events(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) { errno_t ec = 0; /* Only sockets support EPOLLRDHUP and EPOLLPRI. */ if (fd2_node->node_type != NODE_TYPE_SOCKET) { fd2_node->events &= ~(uint32_t)EPOLLRDHUP; fd2_node->events &= ~(uint32_t)EPOLLPRI; } int const fd2 = fd2_node->fd; struct kevent kev[4] = { {.data = 0}, {.data = 0}, {.data = 0}, {.data = 0}, }; assert(fd2 >= 0); int evfilt_read_index = -1; int evfilt_write_index = -1; if (fd2_node->node_type != NODE_TYPE_POLL) { if (fd2_node->is_registered) { epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); } int n = 0; assert(!fd2_node->has_evfilt_read); assert(!fd2_node->has_evfilt_write); assert(!fd2_node->has_evfilt_except); NeededFilters needed_filters = get_needed_filters(fd2_node); if (needed_filters.evfilt_read) { fd2_node->has_evfilt_read = true; evfilt_read_index = n; EV_SET(&kev[n++], fd2, EVFILT_READ, EV_ADD | (needed_filters.evfilt_read & EV_CLEAR), 0, 0, fd2_node); } if (needed_filters.evfilt_write) { fd2_node->has_evfilt_write = true; evfilt_write_index = n; EV_SET(&kev[n++], fd2, EVFILT_WRITE, EV_ADD | (needed_filters.evfilt_write & EV_CLEAR), 0, 0, fd2_node); } assert(n != 0); if (needed_filters.evfilt_except) { #ifdef EVFILT_EXCEPT fd2_node->has_evfilt_except = true; EV_SET(&kev[n++], fd2, EVFILT_EXCEPT, EV_ADD | (needed_filters.evfilt_except & EV_CLEAR), NOTE_OOB, 0, fd2_node); #else assert(0); #endif } for (int i = 0; i < n; ++i) { kev[i].flags |= EV_RECEIPT; } int ret = kevent(epollfd->kq, kev, n, kev, n, NULL); if (ret < 0) { ec = errno; goto out; } assert(ret == n); for (int i = 0; i < n; ++i) { assert((kev[i].flags & EV_ERROR) != 0); } } /* Check for fds that only support poll. */ if (((fd2_node->node_type == NODE_TYPE_OTHER && kev[0].data == ENODEV) || fd2_node->node_type == NODE_TYPE_POLL)) { assert((fd2_node->events & /**/ ~(uint32_t)(EPOLLIN | EPOLLOUT)) == 0); assert(fd2_node->is_registered || fd2_node->node_type == NODE_TYPE_OTHER); fd2_node->has_evfilt_read = false; fd2_node->has_evfilt_write = false; fd2_node->has_evfilt_except = false; fd2_node->node_type = NODE_TYPE_POLL; if ((ec = registered_fds_node_add_self_trigger(fd2_node, epollfd)) != 0) { goto out; } if (!fd2_node->is_on_pollfd_list) { if ((ec = /**/ epollfd_ctx__add_self_trigger(epollfd)) != 0) { goto out; } TAILQ_INSERT_TAIL(&epollfd->poll_fds, fd2_node, pollfd_list_entry); fd2_node->is_on_pollfd_list = true; ++epollfd->poll_fds_size; } /* This is outside the above if because poll ".events" might * have changed which needs a retriggering. */ epollfd_ctx__trigger_repoll(epollfd); goto out; } for (int i = 0; i < 4; ++i) { if (kev[i].data != 0) { if ((kev[i].data == EPIPE #ifdef __NetBSD__ || kev[i].data == EBADF #endif ) && i == evfilt_write_index && fd2_node->node_type == NODE_TYPE_FIFO) { fd2_node->eof_state = EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF; fd2_node->has_evfilt_write = false; if (evfilt_read_index < 0) { if ((ec = registered_fds_node_add_self_trigger( fd2_node, epollfd)) != 0) { goto out; } registered_fds_node_trigger_self( fd2_node, epollfd); } } else { ec = (int)kev[i].data; goto out; } } } ec = 0; out: return ec; } static void epollfd_ctx_remove_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) { epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, fd2_node); assert(epollfd->registered_fds_size > 0); --epollfd->registered_fds_size; registered_fds_node_destroy(fd2_node); } #if defined(__FreeBSD__) static void modify_fifo_rights_from_capabilities(RegisteredFDsNode *fd2_node) { assert(fd2_node->node_data.fifo.readable); assert(fd2_node->node_data.fifo.writable); cap_rights_t rights; memset(&rights, 0, sizeof(rights)); if (cap_rights_get(fd2_node->fd, &rights) == 0) { cap_rights_t test_rights; cap_rights_init(&test_rights, CAP_READ); bool has_read_rights = cap_rights_contains(&rights, &test_rights); cap_rights_init(&test_rights, CAP_WRITE); bool has_write_rights = cap_rights_contains(&rights, &test_rights); if (has_read_rights != has_write_rights) { fd2_node->node_data.fifo.readable = has_read_rights; fd2_node->node_data.fifo.writable = has_write_rights; } } } #endif static errno_t epollfd_ctx_add_node(EpollFDCtx *epollfd, int fd2, struct epoll_event *ev, struct stat const *statbuf) { RegisteredFDsNode *fd2_node = registered_fds_node_create(fd2); if (!fd2_node) { return ENOMEM; } if (S_ISFIFO(statbuf->st_mode)) { int tmp; if (ioctl(fd2_node->fd, FIONREAD, &tmp) < 0 && errno == ENOTTY) { #ifdef __FreeBSD__ /* * On FreeBSD we need to distinguish between kqueues * and native eventfds. */ if (ioctl(fd2_node->fd, FIONBIO, &tmp) < 0 && errno == ENOTTY) { fd2_node->node_type = NODE_TYPE_KQUEUE; } else { fd2_node->node_type = NODE_TYPE_OTHER; } #else fd2_node->node_type = NODE_TYPE_KQUEUE; #endif } else { fd2_node->node_type = NODE_TYPE_FIFO; int fl = fcntl(fd2, F_GETFL, 0); if (fl < 0) { errno_t ec = errno; registered_fds_node_destroy(fd2_node); return ec; } fl &= O_ACCMODE; if (fl == O_RDWR) { fd2_node->node_data.fifo.readable = true; fd2_node->node_data.fifo.writable = true; #if defined(__FreeBSD__) modify_fifo_rights_from_capabilities(fd2_node); #endif } else if (fl == O_WRONLY) { fd2_node->node_data.fifo.writable = true; } else if (fl == O_RDONLY) { fd2_node->node_data.fifo.readable = true; } else { registered_fds_node_destroy(fd2_node); return EINVAL; } } } else if (S_ISSOCK(statbuf->st_mode)) { fd2_node->node_type = NODE_TYPE_SOCKET; } else { /* May also be NODE_TYPE_POLL, will be checked when registering. */ fd2_node->node_type = NODE_TYPE_OTHER; } registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); void *colliding_node = RB_INSERT(registered_fds_set_, &epollfd->registered_fds, fd2_node); (void)colliding_node; assert(colliding_node == NULL); ++epollfd->registered_fds_size; errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); if (ec != 0) { epollfd_ctx_remove_node(epollfd, fd2_node); return ec; } fd2_node->is_registered = true; return 0; } static errno_t epollfd_ctx_modify_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node, struct epoll_event *ev) { registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); assert(fd2_node->is_registered); errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); if (ec != 0) { epollfd_ctx_remove_node(epollfd, fd2_node); return ec; } return 0; } static errno_t epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) { assert(op == EPOLL_CTL_DEL || ev != NULL); if (epollfd->kq == fd2) { return EINVAL; } if (op != EPOLL_CTL_DEL && ((ev->events & ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLRDHUP | /**/ EPOLLPRI | /* unsupported by FreeBSD's kqueue! */ EPOLLHUP | EPOLLERR | /**/ EPOLLET | EPOLLONESHOT)))) { return EINVAL; } RegisteredFDsNode *fd2_node; { RegisteredFDsNode find; find.fd = fd2; fd2_node = RB_FIND(registered_fds_set_, /**/ &epollfd->registered_fds, &find); } struct stat statbuf; if (fstat(fd2, &statbuf) < 0) { errno_t ec = errno; /* If the fstat fails for any reason we must clear * internal state to avoid EEXIST errors in future * calls to epoll_ctl. */ if (fd2_node) { epollfd_ctx_remove_node(epollfd, fd2_node); } return ec; } errno_t ec; if (op == EPOLL_CTL_ADD) { ec = fd2_node ? EEXIST : epollfd_ctx_add_node(epollfd, fd2, ev, &statbuf); } else if (op == EPOLL_CTL_DEL) { ec = !fd2_node ? ENOENT : (epollfd_ctx_remove_node(epollfd, fd2_node), 0); } else if (op == EPOLL_CTL_MOD) { ec = !fd2_node ? ENOENT : epollfd_ctx_modify_node(epollfd, fd2_node, ev); } else { ec = EINVAL; } return ec; } void epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds) { pfds[0] = (struct pollfd){.fd = epollfd->kq, .events = POLLIN}; RegisteredFDsNode *poll_node; size_t i = 1; TAILQ_FOREACH(poll_node, &epollfd->poll_fds, pollfd_list_entry) { pfds[i++] = (struct pollfd){ .fd = poll_node->fd, .events = poll_node->node_type == NODE_TYPE_POLL ? (short)poll_node->events : POLLPRI, }; } } errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) { errno_t ec; (void)pthread_mutex_lock(&epollfd->mutex); ec = epollfd_ctx_ctl_impl(epollfd, op, fd2, ev); (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } static errno_t epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt) { errno_t ec; assert(cnt >= 1); ec = epollfd_ctx_make_pfds_space(epollfd); if (ec != 0) { return ec; } epollfd_ctx_fill_pollfds(epollfd, epollfd->pfds); int n = poll(epollfd->pfds, (nfds_t)(1 + epollfd->poll_fds_size), 0); if (n < 0) { return errno; } if (n == 0) { *actual_cnt = 0; return 0; } { RegisteredFDsNode *poll_node, *tmp_poll_node; size_t i = 1; TAILQ_FOREACH_SAFE(poll_node, &epollfd->poll_fds, pollfd_list_entry, tmp_poll_node) { struct pollfd *pfd = &epollfd->pfds[i++]; if (pfd->revents & POLLNVAL) { epollfd_ctx_remove_node(epollfd, poll_node); } else if (pfd->revents) { registered_fds_node_trigger_self(poll_node, epollfd); } } } again:; /* * Each registered fd can produce a maximum of 3 kevents. If * the provided space in 'ev' is large enough to hold results * for all registered fds, provide enough space for the kevent * call as well. Add some wiggle room for the 'poll only fd' * notification mechanism. */ if ((size_t)cnt >= epollfd->registered_fds_size) { if (__builtin_add_overflow(cnt, 1, &cnt)) { return ENOMEM; } if (__builtin_mul_overflow(cnt, 3, &cnt)) { return ENOMEM; } } ec = epollfd_ctx_make_kevs_space(epollfd, (size_t)cnt); if (ec != 0) { return ec; } struct kevent *kevs = epollfd->kevs; assert(kevs != NULL); n = kevent(epollfd->kq, NULL, 0, kevs, cnt, &(struct timespec){0, 0}); if (n < 0) { return errno; } int j = 0; for (int i = 0; i < n; ++i) { RegisteredFDsNode *fd2_node = (RegisteredFDsNode *)kevs[i].udata; if (!fd2_node) { #ifdef EVFILT_USER assert(kevs[i].filter == EVFILT_USER); #else assert(kevs[i].filter == EVFILT_READ); #endif assert(kevs[i].udata == 0); continue; } uint32_t old_revents = fd2_node->revents; NeededFilters old_needed_filters = get_needed_filters( fd2_node); registered_fds_node_feed_event(fd2_node, epollfd, &kevs[i]); if (fd2_node->node_type != NODE_TYPE_POLL && !(fd2_node->is_edge_triggered && fd2_node->eof_state == (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF) && fd2_node->node_type != NODE_TYPE_FIFO)) { NeededFilters needed_filters = get_needed_filters( fd2_node); if (old_needed_filters.evfilt_read != needed_filters.evfilt_read || old_needed_filters.evfilt_write != needed_filters.evfilt_write) { if (epollfd_ctx__register_events(epollfd, fd2_node) != 0) { epollfd_ctx__remove_node_from_kq( epollfd, fd2_node); } } } if (fd2_node->revents && !old_revents) { ev[j++].data.ptr = fd2_node; } } { int completion_kq = -1; for (int i = 0; i < j; ++i) { RegisteredFDsNode *fd2_node = (RegisteredFDsNode *)ev[i].data.ptr; if (n == cnt || fd2_node->is_edge_triggered) { registered_fds_node_register_for_completion( &completion_kq, fd2_node); } } registered_fds_node_complete(completion_kq); } for (int i = 0; i < j; ++i) { RegisteredFDsNode *fd2_node = (RegisteredFDsNode *)ev[i].data.ptr; ev[i].events = fd2_node->revents; ev[i].data = fd2_node->data; fd2_node->revents = 0; fd2_node->got_evfilt_read = false; fd2_node->got_evfilt_write = false; fd2_node->got_evfilt_except = false; if (fd2_node->is_oneshot) { epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); } } if (n && j == 0) { goto again; } *actual_cnt = j; return 0; } errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt) { errno_t ec; (void)pthread_mutex_lock(&epollfd->mutex); ec = epollfd_ctx_wait_impl(epollfd, ev, cnt, actual_cnt); (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } ================================================ FILE: tpws/epoll-shim/src/epollfd_ctx.h ================================================ #ifndef EPOLLFD_CTX_H_ #define EPOLLFD_CTX_H_ #include "fix.h" #define SHIM_SYS_SHIM_HELPERS #include #include #include #include #include #include #include #include struct registered_fds_node_; typedef struct registered_fds_node_ RegisteredFDsNode; typedef enum { EOF_STATE_READ_EOF = 0x01, EOF_STATE_WRITE_EOF = 0x02, } EOFState; typedef enum { NODE_TYPE_FIFO = 1, NODE_TYPE_SOCKET = 2, NODE_TYPE_KQUEUE = 3, NODE_TYPE_OTHER = 4, NODE_TYPE_POLL = 5, } NodeType; struct registered_fds_node_ { RB_ENTRY(registered_fds_node_) entry; TAILQ_ENTRY(registered_fds_node_) pollfd_list_entry; int fd; epoll_data_t data; bool is_registered; bool has_evfilt_read; bool has_evfilt_write; bool has_evfilt_except; bool got_evfilt_read; bool got_evfilt_write; bool got_evfilt_except; NodeType node_type; union { struct { bool readable; bool writable; } fifo; } node_data; int eof_state; bool pollpri_active; uint16_t events; uint32_t revents; bool is_edge_triggered; bool is_oneshot; bool is_on_pollfd_list; int self_pipe[2]; }; typedef TAILQ_HEAD(pollfds_list_, registered_fds_node_) PollFDList; typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet; typedef struct { int kq; // non owning pthread_mutex_t mutex; PollFDList poll_fds; size_t poll_fds_size; RegisteredFDsSet registered_fds; size_t registered_fds_size; struct kevent *kevs; size_t kevs_length; struct pollfd *pfds; size_t pfds_length; pthread_mutex_t nr_polling_threads_mutex; pthread_cond_t nr_polling_threads_cond; unsigned long nr_polling_threads; int self_pipe[2]; } EpollFDCtx; errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq); errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd); void epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds); errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev); errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt); #endif ================================================ FILE: tpws/epoll-shim/src/eventfd_ctx.h ================================================ #ifndef EVENTFD_CTX_H_ #define EVENTFD_CTX_H_ #include "fix.h" #include #include #include #include #define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) typedef struct { int kq_; // non owning int flags_; pthread_mutex_t mutex_; bool is_signalled_; int self_pipe_[2]; // only used if EVFILT_USER is not available uint_least64_t counter_; } EventFDCtx; errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, int flags); errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); #endif ================================================ FILE: tpws/epoll-shim/src/fix.c ================================================ #include "fix.h" #ifdef __APPLE__ #include int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask) { // macos does not implement ppoll // this is a hacky ppoll shim. only for tpws which does not require sigmask if (sigmask) { errno = EINVAL; return -1; } return poll(fds,nfds,tmo_p ? tmo_p->tv_sec*1000 + tmo_p->tv_nsec/1000000 : -1); } #endif ================================================ FILE: tpws/epoll-shim/src/fix.h ================================================ #pragma once #ifndef _ERRNO_T_DEFINED #define _ERRNO_T_DEFINED typedef int errno_t; #endif #ifdef __APPLE__ #include #include #include struct itimerspec { struct timespec it_interval; struct timespec it_value; }; int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask); #endif ================================================ FILE: tpws/epoll-shim/src/signalfd_ctx.h ================================================ #ifndef SIGNALFD_CTX_H_ #define SIGNALFD_CTX_H_ #include "fix.h" #include #include #include typedef struct { int kq; // non owning } SignalFDCtx; errno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs); errno_t signalfd_ctx_terminate(SignalFDCtx *signalfd); errno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident); #endif ================================================ FILE: tpws/epoll-shim/src/timerfd_ctx.h ================================================ #ifndef TIMERFD_CTX_H_ #define TIMERFD_CTX_H_ #include "fix.h" #include #include #include #include #include #include typedef struct { int kq; // non owning int flags; pthread_mutex_t mutex; int clockid; /* * Next expiration time, absolute (clock given by clockid). * If it_interval is != 0, it is a periodic timer. * If it_value is == 0, the timer is disarmed. */ struct itimerspec current_itimerspec; uint64_t nr_expirations; } TimerFDCtx; errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid); errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd); errno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, struct itimerspec const *new, struct itimerspec *old); errno_t timerfd_ctx_gettime(TimerFDCtx *timerfd, struct itimerspec *cur); errno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value); #endif ================================================ FILE: tpws/gzip.c ================================================ #include "gzip.h" #include #include #include #define ZCHUNK 16384 #define BUFMIN 128 #define BUFCHUNK (1024*128) int z_readfile(FILE *F, char **buf, size_t *size) { z_stream zs; int r; unsigned char in[ZCHUNK]; size_t bufsize; void *newbuf; memset(&zs, 0, sizeof(zs)); *buf = NULL; bufsize = *size = 0; r = inflateInit2(&zs, 47); if (r != Z_OK) return r; do { zs.avail_in = fread(in, 1, sizeof(in), F); if (ferror(F)) { r = Z_ERRNO; goto zerr; } if (!zs.avail_in) break; zs.next_in = in; do { if ((bufsize - *size) < BUFMIN) { bufsize += BUFCHUNK; newbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize); if (!newbuf) { r = Z_MEM_ERROR; goto zerr; } *buf = newbuf; } zs.avail_out = bufsize - *size; zs.next_out = (unsigned char*)(*buf + *size); r = inflate(&zs, Z_NO_FLUSH); if (r != Z_OK && r != Z_STREAM_END) goto zerr; *size = bufsize - zs.avail_out; } while (r == Z_OK && zs.avail_in); } while (r == Z_OK); if (*size < bufsize) { // free extra space if ((newbuf = realloc(*buf, *size))) *buf = newbuf; } inflateEnd(&zs); return Z_OK; zerr: inflateEnd(&zs); free(*buf); *buf = NULL; return r; } bool is_gzip(FILE* F) { unsigned char magic[2]; bool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B; fseek(F, 0, SEEK_SET); return b; } ================================================ FILE: tpws/gzip.h ================================================ #pragma once #include #include #include int z_readfile(FILE *F,char **buf,size_t *size); bool is_gzip(FILE* F); ================================================ FILE: tpws/helpers.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __ANDROID__ #include "andr/ifaddrs.h" #else #include #endif #include "helpers.h" #ifdef __linux__ #include #endif #include "linux_compat.h" int unique_size_t(size_t *pu, int ct) { size_t i, j, u; for (i = j = 0; j < ct; i++) { u = pu[j++]; for (; j < ct && pu[j] == u; j++); pu[i] = u; } return i; } static int cmp_size_t(const void * a, const void * b) { return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b; } void qsort_size_t(size_t *array, size_t ct) { qsort(array, ct, sizeof(*array), cmp_size_t); } void rtrim(char *s) { if (s) for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; } void replace_char(char *s, char from, char to) { for (; *s; s++) if (*s == from) *s = to; } char *strncasestr(const char *s, const char *find, size_t slen) { char c, sc; size_t len; if ((c = *find++) != '\0') { len = strlen(find); do { do { if (slen-- < 1 || (sc = *s++) == '\0') return NULL; } while (toupper(c) != toupper(sc)); if (len > slen) return NULL; } while (strncasecmp(s, find, len) != 0); s--; } return (char *)s; } bool str_ends_with(const char *s, const char *suffix) { size_t slen = strlen(s); size_t suffix_len = strlen(suffix); return suffix_len <= slen && !strcmp(s + slen - suffix_len, suffix); } bool load_file(const char *filename, void *buffer, size_t *buffer_size) { FILE *F; F = fopen(filename, "rb"); if (!F) return false; *buffer_size = fread(buffer, 1, *buffer_size, F); if (ferror(F)) { fclose(F); return false; } fclose(F); return true; } bool append_to_list_file(const char *filename, const char *s) { FILE *F = fopen(filename, "at"); if (!F) return false; bool bOK = fprintf(F, "%s\n", s) > 0; fclose(F); return bOK; } void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen) { unsigned int target_bitlen = target_bytelen<<3; unsigned int bitlen = target_bitlen>3; if ((target_bytelen-bytelen)>=1) memset(target+bytelen,0,target_bytelen-bytelen); memcpy(target,source,bytelen); if ((bitlen &= 7)) ((uint8_t*)target)[bytelen] = ((uint8_t*)source)[bytelen] & (~((1 << (8-bitlen)) - 1)); } // " [fd00::1]" => "fd00::1" // "[fd00::1]:8000" => "fd00::1" // "127.0.0.1" => "127.0.0.1" // " 127.0.0.1:8000" => "127.0.0.1" // " vk.com:8000" => "vk.com" // return value: true - host is ip addr bool strip_host_to_ip(char *host) { size_t l; char *h,*p; uint8_t addr[16]; for (h = host ; *h==' ' || *h=='\t' ; h++); l = strlen(h); if (l>=2) { if (*h=='[') { // ipv6 ? for (p=++h ; *p && *p!=']' ; p++); if (*p==']') { l = p-h; memmove(host,h,l); host[l]=0; return inet_pton(AF_INET6, host, addr)>0; } } else { if (inet_pton(AF_INET6, h, addr)>0) { // ipv6 ? if (host!=h) { l = strlen(h); memmove(host,h,l); host[l]=0; } return true; } else { // ipv4 ? for (p=h ; *p && *p!=':' ; p++); l = p-h; if (host!=h) memmove(host,h,l); host[l]=0; return inet_pton(AF_INET, host, addr)>0; } } } return false; } void ntop46(const struct sockaddr *sa, char *str, size_t len) { if (!len) return; *str = 0; switch (sa->sa_family) { case AF_INET: inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); break; case AF_INET6: inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); break; default: snprintf(str, len, "UNKNOWN_FAMILY_%d", sa->sa_family); } } void ntop46_port(const struct sockaddr *sa, char *str, size_t len) { char ip[40]; ntop46(sa, ip, sizeof(ip)); switch (sa->sa_family) { case AF_INET: snprintf(str, len, "%s:%u", ip, ntohs(((struct sockaddr_in*)sa)->sin_port)); break; case AF_INET6: snprintf(str, len, "[%s]:%u", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); break; default: snprintf(str, len, "%s", ip); } } void print_sockaddr(const struct sockaddr *sa) { char ip_port[48]; ntop46_port(sa, ip_port, sizeof(ip_port)); printf("%s", ip_port); } // -1 = error, 0 = not local, 1 = local bool check_local_ip(const struct sockaddr *saddr) { struct ifaddrs *addrs, *a; if (is_localnet(saddr)) return true; if (getifaddrs(&addrs) < 0) return false; a = addrs; bool bres = false; while (a) { if (a->ifa_addr && sacmp(a->ifa_addr, saddr)) { bres = true; break; } a = a->ifa_next; } freeifaddrs(addrs); return bres; } void print_addrinfo(const struct addrinfo *ai) { char str[64]; while (ai) { switch (ai->ai_family) { case AF_INET: if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str))) printf("%s\n", str); break; case AF_INET6: if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str))) printf("%s\n", str); break; } ai = ai->ai_next; } } bool saismapped(const struct sockaddr_in6 *sa) { // ::ffff:1.2.3.4 return !memcmp(sa->sin6_addr.s6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12); } bool samappedcmp(const struct sockaddr_in *sa1, const struct sockaddr_in6 *sa2) { return saismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr + 12, &sa1->sin_addr.s_addr, 4); } bool sacmp(const struct sockaddr *sa1, const struct sockaddr *sa2) { return (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))) || (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))) || (sa1->sa_family == AF_INET && sa2->sa_family == AF_INET6 && samappedcmp((struct sockaddr_in*)sa1, (struct sockaddr_in6*)sa2)) || (sa1->sa_family == AF_INET6 && sa2->sa_family == AF_INET && samappedcmp((struct sockaddr_in*)sa2, (struct sockaddr_in6*)sa1)); } uint16_t saport(const struct sockaddr *sa) { return htons(sa->sa_family == AF_INET ? ((struct sockaddr_in*)sa)->sin_port : sa->sa_family == AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); } bool saconvmapped(struct sockaddr_storage *a) { if ((a->ss_family == AF_INET6) && saismapped((struct sockaddr_in6*)a)) { uint32_t ip4 = IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr); uint16_t port = ((struct sockaddr_in6*)a)->sin6_port; a->ss_family = AF_INET; ((struct sockaddr_in*)a)->sin_addr.s_addr = ip4; ((struct sockaddr_in*)a)->sin_port = port; return true; } return false; } void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: memcpy(sa_dest, sa, sizeof(struct sockaddr_in)); break; case AF_INET6: memcpy(sa_dest, sa, sizeof(struct sockaddr_in6)); break; default: sa_dest->ss_family = 0; } } void sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa) { sacopy((struct sockaddr_storage*)sa_dest, sa); } bool is_localnet(const struct sockaddr *a) { // match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0 return (a->sa_family == AF_INET && (IN_LOOPBACK(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)) || INADDR_ANY == ntohl((((struct sockaddr_in *)a)->sin_addr.s_addr)))) || (a->sa_family == AF_INET6 && (IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)a)->sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)a)->sin6_addr) || (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)a)->sin6_addr) && (IN_LOOPBACK(ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))) || INADDR_ANY == ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr)))))); } bool is_linklocal(const struct sockaddr_in6 *a) { // fe80::/10 return a->sin6_addr.s6_addr[0] == 0xFE && (a->sin6_addr.s6_addr[1] & 0xC0) == 0x80; } bool is_private6(const struct sockaddr_in6* a) { // fc00::/7 return (a->sin6_addr.s6_addr[0] & 0xFE) == 0xFC; } bool set_keepalive(int fd) { int yes = 1; return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1; } bool set_ttl(int fd, int ttl) { return setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != -1; } bool set_hl(int fd, int hl) { return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl)) != -1; } bool set_ttl_hl(int fd, int ttl) { bool b1, b2; // try to set both but one may fail if family is wrong b1 = set_ttl(fd, ttl); b2 = set_hl(fd, ttl); return b1 || b2; } int get_so_error(int fd) { // getsockopt(SO_ERROR) clears error int errn; socklen_t optlen = sizeof(errn); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) errn = errno; return errn; } int fprint_localtime(FILE *F) { struct tm t; time_t now; time(&now); localtime_r(&now, &t); return 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); } time_t file_mod_time(const char *filename) { struct stat st; return stat(filename, &st) == -1 ? 0 : st.st_mtime; } bool file_mod_signature(const char *filename, file_mod_sig *ms) { struct stat st; if (stat(filename,&st)==-1) { FILE_MOD_RESET(ms); return false; } ms->mod_time=st.st_mtime; ms->size=st.st_size; return true; } bool file_open_test(const char *filename, int flags) { int fd = open(filename,flags); if (fd>=0) { close(fd); return true; } return false; } bool pf_in_range(uint16_t port, const port_filter *pf) { return port && (((!pf->from && !pf->to) || (port >= pf->from && port <= pf->to)) ^ pf->neg); } bool pf_parse(const char *s, port_filter *pf) { unsigned int v1, v2; char c; if (!s) return false; if (*s == '*' && s[1] == 0) { pf->from = 1; pf->to = 0xFFFF; return true; } if (*s == '~') { pf->neg = true; s++; } else pf->neg = false; if (sscanf(s, "%u-%u%c", &v1, &v2, &c) == 2) { if (v1 > 65535 || v2 > 65535 || v1 > v2) return false; pf->from = (uint16_t)v1; pf->to = (uint16_t)v2; } else if (sscanf(s, "%u%c", &v1, &c) == 1) { if (v1 > 65535) return false; pf->to = pf->from = (uint16_t)v1; } else return false; // deny all case if (!pf->from && !pf->to) pf->neg = true; return true; } bool pf_is_empty(const port_filter *pf) { return !pf->neg && !pf->from && !pf->to; } void set_console_io_buffering(void) { setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); } bool set_env_exedir(const char *argv0) { char *s, *d; bool bOK = false; if ((s = strdup(argv0))) { if ((d = dirname(s))) setenv("EXEDIR", s, 1); free(s); } return bOK; } void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr) { char s_ip[16]; *s_ip = 0; inet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip)); snprintf(s, s_len, cidr->preflen < 32 ? "%s/%u" : "%s", s_ip, cidr->preflen); } void print_cidr4(const struct cidr4 *cidr) { char s[19]; str_cidr4(s, sizeof(s), cidr); printf("%s", s); } void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) { char s_ip[40]; *s_ip = 0; inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); snprintf(s, s_len, cidr->preflen < 128 ? "%s/%u" : "%s", s_ip, cidr->preflen); } void print_cidr6(const struct cidr6 *cidr) { char s[44]; str_cidr6(s, sizeof(s), cidr); printf("%s", s); } bool parse_cidr4(char *s, struct cidr4 *cidr) { char *p, d; bool b; unsigned int plen; if ((p = strchr(s, '/'))) { if (sscanf(p + 1, "%u", &plen) != 1 || plen > 32) return false; cidr->preflen = (uint8_t)plen; d = *p; *p = 0; // backup char } else cidr->preflen = 32; b = (inet_pton(AF_INET, s, &cidr->addr) == 1); if (p) *p = d; // restore char return b; } bool parse_cidr6(char *s, struct cidr6 *cidr) { char *p, d; bool b; unsigned int plen; if ((p = strchr(s, '/'))) { if (sscanf(p + 1, "%u", &plen) != 1 || plen > 128) return false; cidr->preflen = (uint8_t)plen; d = *p; *p = 0; // backup char } else cidr->preflen = 128; b = (inet_pton(AF_INET6, s, &cidr->addr) == 1); if (p) *p = d; // restore char return b; } void msleep(unsigned int ms) { struct timespec time = { .tv_nsec = (ms % 1000) * 1000000, .tv_sec = ms / 1000 }; nanosleep(&time, 0); } #ifdef __linux__ bool socket_supports_notsent() { int sfd; struct tcp_info_new tcpi; sfd = socket(AF_INET,SOCK_STREAM,0); if (sfd<0) return false; socklen_t ts = sizeof(tcpi); if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0) { close(sfd); return false; } close(sfd); return ts>=((char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes)); } bool socket_has_notsent(int sfd) { struct tcp_info_new tcpi; socklen_t ts = sizeof(tcpi); if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0) return false; if (tcpi.tcpi_state != 1) // TCP_ESTABLISHED return false; size_t s = (char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes); if (ts < s) // old structure version return false; return !!tcpi.tcpi_notsent_bytes; } bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms) { struct timespec tres; unsigned int mtick; if (wasted_ms) *wasted_ms=0; if (!socket_has_notsent(sfd)) return true; if (clock_getres(CLOCK_MONOTONIC,&tres)) { tres.tv_nsec = 10000000; tres.tv_sec = 0; } mtick = (unsigned int)(tres.tv_sec*1000) + (unsigned int)(tres.tv_nsec/1000000); if (mtick<1) mtick=1; for(;;) { msleep(mtick); if (wasted_ms) *wasted_ms+=mtick; if (!socket_has_notsent(sfd)) return true; if (delay_ms<=mtick) break; delay_ms-=mtick; } return false; } int is_wsl(void) { struct utsname buf; if (uname(&buf) != 0) return -1; if (strcmp(buf.sysname, "Linux") != 0) return 0; if (str_ends_with(buf.release, "microsoft-standard-WSL2")) return 2; if (str_ends_with(buf.release, "-Microsoft")) return 1; return 0; } #endif ================================================ FILE: tpws/helpers.h ================================================ #pragma once #include #include #include #include #include #include #include #include // this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes. typedef union { sa_family_t sa_family; struct sockaddr_in sa4; // size 16 struct sockaddr_in6 sa6; // size 28 } sockaddr_in46; int unique_size_t(size_t *pu, int ct); void qsort_size_t(size_t *array,size_t ct); void rtrim(char *s); void replace_char(char *s, char from, char to); char *strncasestr(const char *s,const char *find, size_t slen); bool str_ends_with(const char *s, const char *suffix); bool load_file(const char *filename,void *buffer,size_t *buffer_size); bool append_to_list_file(const char *filename, const char *s); void expand_bits(void *target, const void *source, unsigned int source_bitlen, unsigned int target_bytelen); bool strip_host_to_ip(char *host); void ntop46(const struct sockaddr *sa, char *str, size_t len); void ntop46_port(const struct sockaddr *sa, char *str, size_t len); void print_sockaddr(const struct sockaddr *sa); void print_addrinfo(const struct addrinfo *ai); bool check_local_ip(const struct sockaddr *saddr); bool saismapped(const struct sockaddr_in6 *sa); bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2); bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2); uint16_t saport(const struct sockaddr *sa); // true = was converted bool saconvmapped(struct sockaddr_storage *a); void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa); void sa46copy(sockaddr_in46 *sa_dest, const struct sockaddr *sa); bool is_localnet(const struct sockaddr *a); bool is_linklocal(const struct sockaddr_in6* a); bool is_private6(const struct sockaddr_in6* a); bool set_keepalive(int fd); bool set_ttl(int fd, int ttl); bool set_hl(int fd, int hl); bool set_ttl_hl(int fd, int ttl); int get_so_error(int fd); // alignment-safe functions static inline uint16_t pntoh16(const uint8_t *p) { return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; } static inline void phton16(uint8_t *p, uint16_t v) { p[0] = (uint8_t)(v>>8); p[1] = (uint8_t)v; } int fprint_localtime(FILE *F); typedef struct { time_t mod_time; off_t size; } file_mod_sig; #define FILE_MOD_COMPARE(ms1,ms2) (((ms1)->mod_time==(ms2)->mod_time) && ((ms1)->size==(ms2)->size)) #define FILE_MOD_RESET(ms) memset(ms,0,sizeof(file_mod_sig)) bool file_mod_signature(const char *filename, file_mod_sig *ms); time_t file_mod_time(const char *filename); bool file_open_test(const char *filename, int flags); typedef struct { uint16_t from,to; bool neg; } port_filter; bool pf_in_range(uint16_t port, const port_filter *pf); bool pf_parse(const char *s, port_filter *pf); bool pf_is_empty(const port_filter *pf); void set_console_io_buffering(void); bool set_env_exedir(const char *argv0); #ifndef IN_LOOPBACK #define IN_LOOPBACK(a) ((((uint32_t) (a)) & 0xff000000) == 0x7f000000) #endif #ifdef __GNUC__ #define IN6_EXTRACT_MAP4(a) \ (__extension__ \ ({ const struct in6_addr *__a = (const struct in6_addr *) (a); \ (((const uint32_t *) (__a))[3]); })) #else #define IN6_EXTRACT_MAP4(a) (((const uint32_t *) (a))[3]) #endif struct cidr4 { struct in_addr addr; uint8_t preflen; }; struct cidr6 { struct in6_addr addr; uint8_t preflen; }; void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); void print_cidr4(const struct cidr4 *cidr); void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); void print_cidr6(const struct cidr6 *cidr); bool parse_cidr4(char *s, struct cidr4 *cidr); bool parse_cidr6(char *s, struct cidr6 *cidr); void msleep(unsigned int ms); #ifdef __linux__ bool socket_supports_notsent(); bool socket_has_notsent(int sfd); bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms); int is_wsl(); #endif ================================================ FILE: tpws/hostlist.c ================================================ #include #include "hostlist.h" #include "gzip.h" #include "helpers.h" // inplace tolower() and add to pool static bool addpool(hostlist_pool **hostlist, char **s, const char *end, int *ct) { char *p=*s; // comment line if ( *p == '#' || *p == ';' || *p == '/' || *p == '\r' || *p == '\n') { // advance until eol for (; pfilename) { file_mod_sig fsig; if (!file_mod_signature(hfile->filename, &fsig)) { // stat() error DLOG_PERROR("file_mod_signature"); DLOG_ERR("cannot access hostlist file '%s'. in-memory content remains unchanged.\n",hfile->filename); return true; } if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date HostlistPoolDestroy(&hfile->hostlist); if (!AppendHostList(&hfile->hostlist, hfile->filename)) { HostlistPoolDestroy(&hfile->hostlist); return false; } hfile->mod_sig=fsig; } return true; } static bool LoadHostLists(struct hostlist_files_head *list) { bool bres=true; struct hostlist_file *hfile; LIST_FOREACH(hfile, list, next) { if (!LoadHostList(hfile)) // at least one failed bres=false; } return bres; } bool NonEmptyHostlist(hostlist_pool **hostlist) { // add impossible hostname if the list is empty return *hostlist ? true : HostlistPoolAddStrLen(hostlist, "@&()", 4, 0); } static void MakeAutolistsNonEmpty() { struct desync_profile_list *dpl; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { if (dpl->dp.hostlist_auto) NonEmptyHostlist(&dpl->dp.hostlist_auto->hostlist); } } bool LoadAllHostLists() { if (!LoadHostLists(¶ms.hostlists)) return false; MakeAutolistsNonEmpty(); return true; } static bool SearchHostList(hostlist_pool *hostlist, const char *host, bool no_match_subdomains) { if (hostlist) { const char *p = host; const struct hostlist_pool *hp; bool bHostFull=true; while (p) { VPRINT("hostlist check for %s : ", p); hp = HostlistPoolGetStr(hostlist, p); if (hp) { if ((hp->flags & HOSTLIST_POOL_FLAG_STRICT_MATCH) && !bHostFull) { VPRINT("negative : strict_mismatch : %s != %s\n", p, host); } else { VPRINT("positive\n"); return true; } } else VPRINT("negative\n"); if (no_match_subdomains) break; p = strchr(p, '.'); if (p) p++; bHostFull = false; } } return false; } static bool HostlistsReloadCheck(const struct hostlist_collection_head *hostlists) { struct hostlist_item *item; LIST_FOREACH(item, hostlists, next) { if (!LoadHostList(item->hfile)) return false; } MakeAutolistsNonEmpty(); return true; } bool HostlistsReloadCheckForProfile(const struct desync_profile *dp) { return HostlistsReloadCheck(&dp->hl_collection) && HostlistsReloadCheck(&dp->hl_collection_exclude); } // return : true = apply fooling, false = do not apply static 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) { struct hostlist_item *item; if (excluded) *excluded = false; if (!bSkipReloadCheck) if (!HostlistsReloadCheck(hostlists) || !HostlistsReloadCheck(hostlists_exclude)) return false; LIST_FOREACH(item, hostlists_exclude, next) { VPRINT("[%s] exclude ", item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) { if (excluded) *excluded = true; return false; } } // old behavior compat: all include lists are empty means check passes if (!hostlist_collection_is_empty(hostlists)) { LIST_FOREACH(item, hostlists, next) { VPRINT("[%s] include ", item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchHostList(item->hfile->hostlist, host, no_match_subdomains)) return true; } return false; } return true; } // return : true = apply fooling, false = do not apply bool HostlistCheck(const struct desync_profile *dp, const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck) { VPRINT("* hostlist check for profile %d\n",dp->n); return HostlistCheck_(&dp->hl_collection, &dp->hl_collection_exclude, host, no_match_subdomains, excluded, bSkipReloadCheck); } static struct hostlist_file *RegisterHostlist_(struct hostlist_files_head *hostlists, struct hostlist_collection_head *hl_collection, const char *filename) { struct hostlist_file *hfile; if (filename) { if (!(hfile=hostlist_files_search(hostlists, filename))) if (!(hfile=hostlist_files_add(hostlists, filename))) return NULL; if (!hostlist_collection_search(hl_collection, filename)) if (!hostlist_collection_add(hl_collection, hfile)) return NULL; } else { if (!(hfile=hostlist_files_add(hostlists, NULL))) return NULL; if (!hostlist_collection_add(hl_collection, hfile)) return NULL; } return hfile; } struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename) { /* if (filename && !file_mod_time(filename)) { DLOG_ERR("cannot access hostlist file '%s'\n",filename); return NULL; } */ return RegisterHostlist_( ¶ms.hostlists, bExclude ? &dp->hl_collection_exclude : &dp->hl_collection, filename); } void HostlistsDebug() { if (!params.debug) return; struct hostlist_file *hfile; struct desync_profile_list *dpl; struct hostlist_item *hl_item; LIST_FOREACH(hfile, ¶ms.hostlists, next) { if (hfile->filename) VPRINT("hostlist file %s%s\n",hfile->filename,hfile->hostlist ? "" : " (empty)"); else VPRINT("hostlist fixed%s\n",hfile->hostlist ? "" : " (empty)"); } LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { LIST_FOREACH(hl_item, &dpl->dp.hl_collection, next) if (hl_item->hfile!=dpl->dp.hostlist_auto) { if (hl_item->hfile->filename) VPRINT("profile %d include hostlist %s%s\n",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); else VPRINT("profile %d include fixed hostlist%s\n",dpl->dp.n, hl_item->hfile->hostlist ? "" : " (empty)"); } LIST_FOREACH(hl_item, &dpl->dp.hl_collection_exclude, next) { if (hl_item->hfile->filename) VPRINT("profile %d exclude hostlist %s%s\n",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); else VPRINT("profile %d exclude fixed hostlist%s\n",dpl->dp.n,hl_item->hfile->hostlist ? "" : " (empty)"); } if (dpl->dp.hostlist_auto) VPRINT("profile %d auto hostlist %s%s\n",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? "" : " (empty)"); } } ================================================ FILE: tpws/hostlist.h ================================================ #pragma once #include #include "pools.h" #include "params.h" bool AppendHostlistItem(hostlist_pool **hostlist, char *s); bool AppendHostList(hostlist_pool **hostlist, const char *filename); bool LoadAllHostLists(); bool NonEmptyHostlist(hostlist_pool **hostlist); // return : true = apply fooling, false = do not apply bool HostlistCheck(const struct desync_profile *dp,const char *host, bool no_match_subdomains, bool *excluded, bool bSkipReloadCheck); struct hostlist_file *RegisterHostlist(struct desync_profile *dp, bool bExclude, const char *filename); bool HostlistsReloadCheckForProfile(const struct desync_profile *dp); void HostlistsDebug(); #define ResetAllHostlistsModTime() hostlist_files_reset_modtime(¶ms.hostlists) ================================================ FILE: tpws/ipset.c ================================================ #include #include "ipset.h" #include "gzip.h" #include "helpers.h" // inplace tolower() and add to pool static bool addpool(ipset *ips, char **s, const char *end, int *ct) { char *p, cidr[128]; size_t l; struct cidr4 c4; struct cidr6 c6; // advance until eol for (p=*s; p=sizeof(cidr)) l=sizeof(cidr)-1; memcpy(cidr,*s,l); cidr[l]=0; rtrim(cidr); if (parse_cidr4(cidr,&c4)) { if (!ipset4AddCidr(&ips->ips4, &c4)) { ipsetDestroy(ips); return false; } if (ct) (*ct)++; } else if (parse_cidr6(cidr,&c6)) { if (!ipset6AddCidr(&ips->ips6, &c6)) { ipsetDestroy(ips); return false; } if (ct) (*ct)++; } else DLOG_ERR("bad ip or subnet : %s\n",cidr); } // advance to the next line for (; pfilename) { file_mod_sig fsig; if (!file_mod_signature(hfile->filename, &fsig)) { // stat() error DLOG_PERROR("file_mod_signature"); DLOG_ERR("cannot access ipset file '%s'. in-memory content remains unchanged.\n",hfile->filename); return true; } if (FILE_MOD_COMPARE(&hfile->mod_sig,&fsig)) return true; // up to date ipsetDestroy(&hfile->ipset); if (!AppendIpset(&hfile->ipset, hfile->filename)) { ipsetDestroy(&hfile->ipset); return false; } hfile->mod_sig=fsig; } return true; } static bool LoadIpsets(struct ipset_files_head *list) { bool bres=true; struct ipset_file *hfile; LIST_FOREACH(hfile, list, next) { if (!LoadIpset(hfile)) // at least one failed bres=false; } return bres; } bool LoadAllIpsets() { return LoadIpsets(¶ms.ipsets); } static bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6) { char s_ip[40]; bool bInSet=false; if (!!ipv4 != !!ipv6) { *s_ip=0; if (ipv4) { if (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip)); if (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32); } if (ipv6) { if (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip)); if (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128); } VPRINT("ipset check for %s : %s\n", s_ip, bInSet ? "positive" : "negative"); } else // ipv4 and ipv6 are both empty or non-empty VPRINT("ipset check error !!!!!!!! ipv4=%p ipv6=%p\n",ipv4,ipv6); return bInSet; } static bool IpsetsReloadCheck(const struct ipset_collection_head *ipsets) { struct ipset_item *item; LIST_FOREACH(item, ipsets, next) { if (!LoadIpset(item->hfile)) return false; } return true; } bool IpsetsReloadCheckForProfile(const struct desync_profile *dp) { return IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude); } static 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) { struct ipset_item *item; if (!IpsetsReloadCheck(ips) || !IpsetsReloadCheck(ips_exclude)) return false; LIST_FOREACH(item, ips_exclude, next) { VPRINT("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return false; } // old behavior compat: all include lists are empty means check passes if (!ipset_collection_is_empty(ips)) { LIST_FOREACH(item, ips, next) { VPRINT("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return true; } return false; } return true; } bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) { if (PROFILE_IPSETS_ABSENT(dp)) return true; VPRINT("* ipset check for profile %d\n",dp->n); return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6); } static struct ipset_file *RegisterIpset_(struct ipset_files_head *ipsets, struct ipset_collection_head *ips_collection, const char *filename) { struct ipset_file *hfile; if (filename) { if (!(hfile=ipset_files_search(ipsets, filename))) if (!(hfile=ipset_files_add(ipsets, filename))) return NULL; if (!ipset_collection_search(ips_collection, filename)) if (!ipset_collection_add(ips_collection, hfile)) return NULL; } else { if (!(hfile=ipset_files_add(ipsets, NULL))) return NULL; if (!ipset_collection_add(ips_collection, hfile)) return NULL; } return hfile; } struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename) { if (filename && !file_mod_time(filename)) { DLOG_ERR("cannot access ipset file '%s'\n",filename); return NULL; } return RegisterIpset_( ¶ms.ipsets, bExclude ? &dp->ips_collection_exclude : &dp->ips_collection, filename); } static const char *dbg_ipset_fill(const ipset *ips) { if (ips->ips4) if (ips->ips6) return "ipv4+ipv6"; else return "ipv4"; else if (ips->ips6) return "ipv6"; else return "empty"; } void IpsetsDebug() { if (!params.debug) return; struct ipset_file *hfile; struct desync_profile_list *dpl; struct ipset_item *ips_item; LIST_FOREACH(hfile, ¶ms.ipsets, next) { if (hfile->filename) VPRINT("ipset file %s (%s)\n",hfile->filename,dbg_ipset_fill(&hfile->ipset)); else VPRINT("ipset fixed (%s)\n",dbg_ipset_fill(&hfile->ipset)); } LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { LIST_FOREACH(ips_item, &dpl->dp.ips_collection, next) if (ips_item->hfile->filename) VPRINT("profile %d include ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); else VPRINT("profile %d include fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); LIST_FOREACH(ips_item, &dpl->dp.ips_collection_exclude, next) if (ips_item->hfile->filename) VPRINT("profile %d exclude ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); else VPRINT("profile %d exclude fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); } } ================================================ FILE: tpws/ipset.h ================================================ #pragma once #include #include #include "params.h" #include "pools.h" bool LoadAllIpsets(); bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename); void IpsetsDebug(); bool AppendIpsetItem(ipset *ips, char *ip); #define ResetAllIpsetModTime() ipset_files_reset_modtime(¶ms.ipsets) ================================================ FILE: tpws/kavl.h ================================================ /* The MIT License Copyright (c) 2018 by Attractive Chaos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* An example: #include #include #include #include "kavl.h" struct my_node { char key; KAVL_HEAD(struct my_node) head; }; #define my_cmp(p, q) (((q)->key < (p)->key) - ((p)->key < (q)->key)) KAVL_INIT(my, struct my_node, head, my_cmp) int main(void) { const char *str = "MNOLKQOPHIA"; // from wiki, except a duplicate struct my_node *root = 0; int i, l = strlen(str); for (i = 0; i < l; ++i) { // insert in the input order struct my_node *q, *p = malloc(sizeof(*p)); p->key = str[i]; q = kavl_insert(my, &root, p, 0); if (p != q) free(p); // if already present, free } kavl_itr_t(my) itr; kavl_itr_first(my, root, &itr); // place at first do { // traverse const struct my_node *p = kavl_at(&itr); putchar(p->key); free((void*)p); // free node } while (kavl_itr_next(my, &itr)); putchar('\n'); return 0; } */ #ifndef KAVL_H #define KAVL_H #ifdef __STRICT_ANSI__ #define inline __inline__ #endif #define KAVL_MAX_DEPTH 64 #define kavl_size(head, p) ((p)? (p)->head.size : 0) #define kavl_size_child(head, q, i) ((q)->head.p[(i)]? (q)->head.p[(i)]->head.size : 0) #define KAVL_HEAD(__type) \ struct { \ __type *p[2]; \ signed char balance; /* balance factor */ \ unsigned size; /* #elements in subtree */ \ } #define __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_find_##suf(const __type *root, const __type *x, unsigned *cnt_) { \ const __type *p = root; \ unsigned cnt = 0; \ while (p != 0) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ if (cmp < 0) p = p->__head.p[0]; \ else if (cmp > 0) p = p->__head.p[1]; \ else break; \ } \ if (cnt_) *cnt_ = cnt; \ return (__type*)p; \ } #define __KAVL_ROTATE(suf, __type, __head) \ /* one rotation: (a,(b,c)q)p => ((a,b)p,c)q */ \ static inline __type *kavl_rotate1_##suf(__type *p, int dir) { /* dir=0 to left; dir=1 to right */ \ int opp = 1 - dir; /* opposite direction */ \ __type *q = p->__head.p[opp]; \ unsigned size_p = p->__head.size; \ p->__head.size -= q->__head.size - kavl_size_child(__head, q, dir); \ q->__head.size = size_p; \ p->__head.p[opp] = q->__head.p[dir]; \ q->__head.p[dir] = p; \ return q; \ } \ /* two consecutive rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r */ \ static inline __type *kavl_rotate2_##suf(__type *p, int dir) { \ int b1, opp = 1 - dir; \ __type *q = p->__head.p[opp], *r = q->__head.p[dir]; \ unsigned size_x_dir = kavl_size_child(__head, r, dir); \ r->__head.size = p->__head.size; \ p->__head.size -= q->__head.size - size_x_dir; \ q->__head.size -= size_x_dir + 1; \ p->__head.p[opp] = r->__head.p[dir]; \ r->__head.p[dir] = p; \ q->__head.p[dir] = r->__head.p[opp]; \ r->__head.p[opp] = q; \ b1 = dir == 0? +1 : -1; \ if (r->__head.balance == b1) q->__head.balance = 0, p->__head.balance = -b1; \ else if (r->__head.balance == 0) q->__head.balance = p->__head.balance = 0; \ else q->__head.balance = b1, p->__head.balance = 0; \ r->__head.balance = 0; \ return r; \ } #define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \ unsigned char stack[KAVL_MAX_DEPTH]; \ __type *path[KAVL_MAX_DEPTH]; \ __type *bp, *bq; \ __type *p, *q, *r = 0; /* _r_ is potentially the new root */ \ int i, which = 0, top, b1, path_len; \ unsigned cnt = 0; \ bp = *root_, bq = 0; \ /* find the insertion location */ \ for (p = bp, q = bq, top = path_len = 0; p; q = p, p = p->__head.p[which]) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp >= 0) cnt += kavl_size_child(__head, p, 0) + 1; \ if (cmp == 0) { \ if (cnt_) *cnt_ = cnt; \ return p; \ } \ if (p->__head.balance != 0) \ bq = q, bp = p, top = 0; \ stack[top++] = which = (cmp > 0); \ path[path_len++] = p; \ } \ if (cnt_) *cnt_ = cnt; \ x->__head.balance = 0, x->__head.size = 1, x->__head.p[0] = x->__head.p[1] = 0; \ if (q == 0) *root_ = x; \ else q->__head.p[which] = x; \ if (bp == 0) return x; \ for (i = 0; i < path_len; ++i) ++path[i]->__head.size; \ for (p = bp, top = 0; p != x; p = p->__head.p[stack[top]], ++top) /* update balance factors */ \ if (stack[top] == 0) --p->__head.balance; \ else ++p->__head.balance; \ if (bp->__head.balance > -2 && bp->__head.balance < 2) return x; /* no re-balance needed */ \ /* re-balance */ \ which = (bp->__head.balance < 0); \ b1 = which == 0? +1 : -1; \ q = bp->__head.p[1 - which]; \ if (q->__head.balance == b1) { \ r = kavl_rotate1_##suf(bp, which); \ q->__head.balance = bp->__head.balance = 0; \ } else r = kavl_rotate2_##suf(bp, which); \ if (bq == 0) *root_ = r; \ else bq->__head.p[bp != bq->__head.p[0]] = r; \ return x; \ } #define __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_erase_##suf(__type **root_, const __type *x, unsigned *cnt_) { \ __type *p, *path[KAVL_MAX_DEPTH], fake; \ unsigned char dir[KAVL_MAX_DEPTH]; \ int i, d = 0, cmp; \ unsigned cnt = 0; \ fake.__head.p[0] = *root_, fake.__head.p[1] = 0; \ if (cnt_) *cnt_ = 0; \ if (x) { \ for (cmp = -1, p = &fake; cmp; cmp = __cmp(x, p)) { \ int which = (cmp > 0); \ if (cmp > 0) cnt += kavl_size_child(__head, p, 0) + 1; \ dir[d] = which; \ path[d++] = p; \ p = p->__head.p[which]; \ if (p == 0) { \ if (cnt_) *cnt_ = 0; \ return 0; \ } \ } \ cnt += kavl_size_child(__head, p, 0) + 1; /* because p==x is not counted */ \ } else { \ for (p = &fake, cnt = 1; p; p = p->__head.p[0]) \ dir[d] = 0, path[d++] = p; \ p = path[--d]; \ } \ if (cnt_) *cnt_ = cnt; \ for (i = 1; i < d; ++i) --path[i]->__head.size; \ if (p->__head.p[1] == 0) { /* ((1,.)2,3)4 => (1,3)4; p=2 */ \ path[d-1]->__head.p[dir[d-1]] = p->__head.p[0]; \ } else { \ __type *q = p->__head.p[1]; \ if (q->__head.p[0] == 0) { /* ((1,2)3,4)5 => ((1)2,4)5; p=3 */ \ q->__head.p[0] = p->__head.p[0]; \ q->__head.balance = p->__head.balance; \ path[d-1]->__head.p[dir[d-1]] = q; \ path[d] = q, dir[d++] = 1; \ q->__head.size = p->__head.size - 1; \ } else { /* ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 */ \ __type *r; \ int e = d++; /* backup _d_ */\ for (;;) { \ dir[d] = 0; \ path[d++] = q; \ r = q->__head.p[0]; \ if (r->__head.p[0] == 0) break; \ q = r; \ } \ r->__head.p[0] = p->__head.p[0]; \ q->__head.p[0] = r->__head.p[1]; \ r->__head.p[1] = p->__head.p[1]; \ r->__head.balance = p->__head.balance; \ path[e-1]->__head.p[dir[e-1]] = r; \ path[e] = r, dir[e] = 1; \ for (i = e + 1; i < d; ++i) --path[i]->__head.size; \ r->__head.size = p->__head.size - 1; \ } \ } \ while (--d > 0) { \ __type *q = path[d]; \ int which, other, b1 = 1, b2 = 2; \ which = dir[d], other = 1 - which; \ if (which) b1 = -b1, b2 = -b2; \ q->__head.balance += b1; \ if (q->__head.balance == b1) break; \ else if (q->__head.balance == b2) { \ __type *r = q->__head.p[other]; \ if (r->__head.balance == -b1) { \ path[d-1]->__head.p[dir[d-1]] = kavl_rotate2_##suf(q, which); \ } else { \ path[d-1]->__head.p[dir[d-1]] = kavl_rotate1_##suf(q, which); \ if (r->__head.balance == 0) { \ r->__head.balance = -b1; \ q->__head.balance = b1; \ break; \ } else r->__head.balance = q->__head.balance = 0; \ } \ } \ } \ *root_ = fake.__head.p[0]; \ return p; \ } #define kavl_free(__type, __head, __root, __free) do { \ __type *_p, *_q; \ for (_p = __root; _p; _p = _q) { \ if (_p->__head.p[0] == 0) { \ _q = _p->__head.p[1]; \ __free(_p); \ } else { \ _q = _p->__head.p[0]; \ _p->__head.p[0] = _q->__head.p[1]; \ _q->__head.p[1] = _p; \ } \ } \ } while (0) #define __KAVL_ITR(suf, __scope, __type, __head, __cmp) \ struct kavl_itr_##suf { \ const __type *stack[KAVL_MAX_DEPTH], **top, *right; /* _right_ points to the right child of *top */ \ }; \ __scope void kavl_itr_first_##suf(const __type *root, struct kavl_itr_##suf *itr) { \ const __type *p; \ for (itr->top = itr->stack - 1, p = root; p; p = p->__head.p[0]) \ *++itr->top = p; \ itr->right = (*itr->top)->__head.p[1]; \ } \ __scope int kavl_itr_find_##suf(const __type *root, const __type *x, struct kavl_itr_##suf *itr) { \ const __type *p = root; \ itr->top = itr->stack - 1; \ while (p != 0) { \ int cmp; \ cmp = __cmp(x, p); \ if (cmp < 0) *++itr->top = p, p = p->__head.p[0]; \ else if (cmp > 0) p = p->__head.p[1]; \ else break; \ } \ if (p) { \ *++itr->top = p; \ itr->right = p->__head.p[1]; \ return 1; \ } else if (itr->top >= itr->stack) { \ itr->right = (*itr->top)->__head.p[1]; \ return 0; \ } else return 0; \ } \ __scope int kavl_itr_next_##suf(struct kavl_itr_##suf *itr) { \ for (;;) { \ const __type *p; \ for (p = itr->right, --itr->top; p; p = p->__head.p[0]) \ *++itr->top = p; \ if (itr->top < itr->stack) return 0; \ itr->right = (*itr->top)->__head.p[1]; \ return 1; \ } \ } /** * Insert a node to the tree * * @param suf name suffix used in KAVL_INIT() * @param proot pointer to the root of the tree (in/out: root may change) * @param x node to insert (in) * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) * * @return _x_ if not present in the tree, or the node equal to x. */ #define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt) /** * Find a node in the tree * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param x node value to find (in) * @param cnt number of nodes smaller than or equal to _x_; can be NULL (out) * * @return node equal to _x_ if present, or NULL if absent */ #define kavl_find(suf, root, x, cnt) kavl_find_##suf(root, x, cnt) /** * Delete a node from the tree * * @param suf name suffix used in KAVL_INIT() * @param proot pointer to the root of the tree (in/out: root may change) * @param x node value to delete; if NULL, delete the first node (in) * * @return node removed from the tree if present, or NULL if absent */ #define kavl_erase(suf, proot, x, cnt) kavl_erase_##suf(proot, x, cnt) #define kavl_erase_first(suf, proot) kavl_erase_##suf(proot, 0, 0) #define kavl_itr_t(suf) struct kavl_itr_##suf /** * Place the iterator at the smallest object * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param itr iterator */ #define kavl_itr_first(suf, root, itr) kavl_itr_first_##suf(root, itr) /** * Place the iterator at the object equal to or greater than the query * * @param suf name suffix used in KAVL_INIT() * @param root root of the tree * @param x query (in) * @param itr iterator (out) * * @return 1 if find; 0 otherwise. kavl_at(itr) is NULL if and only if query is * larger than all objects in the tree */ #define kavl_itr_find(suf, root, x, itr) kavl_itr_find_##suf(root, x, itr) /** * Move to the next object in order * * @param itr iterator (modified) * * @return 1 if there is a next object; 0 otherwise */ #define kavl_itr_next(suf, itr) kavl_itr_next_##suf(itr) /** * Return the pointer at the iterator * * @param itr iterator * * @return pointer if present; NULL otherwise */ #define kavl_at(itr) ((itr)->top < (itr)->stack? 0 : *(itr)->top) #define KAVL_INIT2(suf, __scope, __type, __head, __cmp) \ __KAVL_FIND(suf, __scope, __type, __head, __cmp) \ __KAVL_ROTATE(suf, __type, __head) \ __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ __KAVL_ERASE(suf, __scope, __type, __head, __cmp) \ __KAVL_ITR(suf, __scope, __type, __head, __cmp) #define KAVL_INIT(suf, __type, __head, __cmp) \ KAVL_INIT2(suf,, __type, __head, __cmp) #endif ================================================ FILE: tpws/linux_compat.h ================================================ #ifdef __linux__ #include #ifndef TCP_USER_TIMEOUT #define TCP_USER_TIMEOUT 18 #endif #ifndef IP6T_SO_ORIGINAL_DST #define IP6T_SO_ORIGINAL_DST 80 #endif #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #endif // workaround for old headers struct tcp_info_new { __u8 tcpi_state; __u8 tcpi_ca_state; __u8 tcpi_retransmits; __u8 tcpi_probes; __u8 tcpi_backoff; __u8 tcpi_options; __u8 tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4; __u8 tcpi_delivery_rate_app_limited : 1, tcpi_fastopen_client_fail : 2; __u32 tcpi_rto; __u32 tcpi_ato; __u32 tcpi_snd_mss; __u32 tcpi_rcv_mss; __u32 tcpi_unacked; __u32 tcpi_sacked; __u32 tcpi_lost; __u32 tcpi_retrans; __u32 tcpi_fackets; /* Times. */ __u32 tcpi_last_data_sent; __u32 tcpi_last_ack_sent; /* Not remembered, sorry. */ __u32 tcpi_last_data_recv; __u32 tcpi_last_ack_recv; /* Metrics. */ __u32 tcpi_pmtu; __u32 tcpi_rcv_ssthresh; __u32 tcpi_rtt; __u32 tcpi_rttvar; __u32 tcpi_snd_ssthresh; __u32 tcpi_snd_cwnd; __u32 tcpi_advmss; __u32 tcpi_reordering; __u32 tcpi_rcv_rtt; __u32 tcpi_rcv_space; __u32 tcpi_total_retrans; __u64 tcpi_pacing_rate; __u64 tcpi_max_pacing_rate; __u64 tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ __u64 tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ __u32 tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */ __u32 tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */ __u32 tcpi_notsent_bytes; __u32 tcpi_min_rtt; __u32 tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */ __u32 tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */ __u64 tcpi_delivery_rate; __u64 tcpi_busy_time; /* Time (usec) busy sending data */ __u64 tcpi_rwnd_limited; /* Time (usec) limited by receive window */ __u64 tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */ __u32 tcpi_delivered; __u32 tcpi_delivered_ce; __u64 tcpi_bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut */ __u64 tcpi_bytes_retrans; /* RFC4898 tcpEStatsPerfOctetsRetrans */ __u32 tcpi_dsack_dups; /* RFC4898 tcpEStatsStackDSACKDups */ __u32 tcpi_reord_seen; /* reordering events seen */ __u32 tcpi_rcv_ooopack; /* Out-of-order packets received */ __u32 tcpi_snd_wnd; /* peer's advertised receive window after * scaling (bytes) */ __u32 tcpi_rcv_wnd; /* local advertised receive window after * scaling (bytes) */ __u32 tcpi_rehash; /* PLB or timeout triggered rehash attempts */ __u16 tcpi_total_rto; /* Total number of RTO timeouts, including * SYN/SYN-ACK and recurring timeouts. */ __u16 tcpi_total_rto_recoveries; /* Total number of RTO * recoveries, including any * unfinished recovery. */ __u32 tcpi_total_rto_time; /* Total time spent in RTO recoveries * in milliseconds, including any * unfinished recovery. */ }; #endif ================================================ FILE: tpws/macos/net/pfvar.h ================================================ #pragma once #include #include // taken from an older apple SDK // some fields are different from BSDs #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) enum { PF_INOUT, PF_IN, PF_OUT, PF_FWD }; struct pf_addr { union { struct in_addr v4; struct in6_addr v6; u_int8_t addr8[16]; u_int16_t addr16[8]; u_int32_t addr32[4]; } pfa; /* 128-bit address */ #define v4 pfa.v4 #define v6 pfa.v6 #define addr8 pfa.addr8 #define addr16 pfa.addr16 #define addr32 pfa.addr32 }; union pf_state_xport { u_int16_t port; u_int16_t call_id; u_int32_t spi; }; struct pfioc_natlook { struct pf_addr saddr; struct pf_addr daddr; struct pf_addr rsaddr; struct pf_addr rdaddr; union pf_state_xport sxport; union pf_state_xport dxport; union pf_state_xport rsxport; union pf_state_xport rdxport; sa_family_t af; u_int8_t proto; u_int8_t proto_variant; u_int8_t direction; }; ================================================ FILE: tpws/macos/sys/tree.h ================================================ /* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ /* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ /* $FreeBSD: releng/12.2/sys/sys/tree.h 326256 2017-11-27 15:01:59Z pfg $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SYS_TREE_H_ #define _SYS_TREE_H_ #include /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ #define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } #define SPLAY_INITIALIZER(root) \ { NULL } #define SPLAY_INIT(root) do { \ (root)->sph_root = NULL; \ } while (/*CONSTCOND*/ 0) #define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } #define SPLAY_LEFT(elm, field) (elm)->field.spe_left #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right #define SPLAY_ROOT(head) (head)->sph_root #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ #define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (/*CONSTCOND*/ 0) #define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (/*CONSTCOND*/ 0) #define SPLAY_LINKLEFT(head, tmp, field) do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (/*CONSTCOND*/ 0) #define SPLAY_LINKRIGHT(head, tmp, field) do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (/*CONSTCOND*/ 0) #define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ } while (/*CONSTCOND*/ 0) /* Generates prototypes and inline functions */ #define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type * \ name##_SPLAY_FIND(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return(NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type * \ name##_SPLAY_NEXT(struct name *head, struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type * \ name##_SPLAY_MIN_MAX(struct name *head, int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ #define SPLAY_GENERATE(name, type, field, cmp) \ struct type * \ name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if(__comp < 0) { \ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type * \ name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void \ name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0){ \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } #define SPLAY_NEGINF -1 #define SPLAY_INF 1 #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) #define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) #define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) #define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); \ (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* Macros that define a red-black tree */ #define RB_HEAD(name, type) \ struct name { \ struct type *rbh_root; /* root of the tree */ \ } #define RB_INITIALIZER(root) \ { NULL } #define RB_INIT(root) do { \ (root)->rbh_root = NULL; \ } while (/*CONSTCOND*/ 0) #define RB_BLACK 0 #define RB_RED 1 #define RB_ENTRY(type) \ struct { \ struct type *rbe_left; /* left element */ \ struct type *rbe_right; /* right element */ \ struct type *rbe_parent; /* parent element */ \ int rbe_color; /* node color */ \ } #define RB_LEFT(elm, field) (elm)->field.rbe_left #define RB_RIGHT(elm, field) (elm)->field.rbe_right #define RB_PARENT(elm, field) (elm)->field.rbe_parent #define RB_COLOR(elm, field) (elm)->field.rbe_color #define RB_ROOT(head) (head)->rbh_root #define RB_EMPTY(head) (RB_ROOT(head) == NULL) #define RB_SET(elm, parent, field) do { \ RB_PARENT(elm, field) = parent; \ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ RB_COLOR(elm, field) = RB_RED; \ } while (/*CONSTCOND*/ 0) #define RB_SET_BLACKRED(black, red, field) do { \ RB_COLOR(black, field) = RB_BLACK; \ RB_COLOR(red, field) = RB_RED; \ } while (/*CONSTCOND*/ 0) #ifndef RB_AUGMENT #define RB_AUGMENT(x) do {} while (0) #endif #define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ (tmp) = RB_RIGHT(elm, field); \ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_LEFT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (/*CONSTCOND*/ 0) #define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ (tmp) = RB_LEFT(elm, field); \ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_RIGHT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (/*CONSTCOND*/ 0) /* Generates prototypes and inline functions */ #define RB_PROTOTYPE(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) #define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) #define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \ RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \ RB_PROTOTYPE_INSERT(name, type, attr); \ RB_PROTOTYPE_REMOVE(name, type, attr); \ RB_PROTOTYPE_FIND(name, type, attr); \ RB_PROTOTYPE_NFIND(name, type, attr); \ RB_PROTOTYPE_NEXT(name, type, attr); \ RB_PROTOTYPE_PREV(name, type, attr); \ RB_PROTOTYPE_MINMAX(name, type, attr); #define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ attr void name##_RB_INSERT_COLOR(struct name *, struct type *) #define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *) #define RB_PROTOTYPE_REMOVE(name, type, attr) \ attr struct type *name##_RB_REMOVE(struct name *, struct type *) #define RB_PROTOTYPE_INSERT(name, type, attr) \ attr struct type *name##_RB_INSERT(struct name *, struct type *) #define RB_PROTOTYPE_FIND(name, type, attr) \ attr struct type *name##_RB_FIND(struct name *, struct type *) #define RB_PROTOTYPE_NFIND(name, type, attr) \ attr struct type *name##_RB_NFIND(struct name *, struct type *) #define RB_PROTOTYPE_NEXT(name, type, attr) \ attr struct type *name##_RB_NEXT(struct type *) #define RB_PROTOTYPE_PREV(name, type, attr) \ attr struct type *name##_RB_PREV(struct type *) #define RB_PROTOTYPE_MINMAX(name, type, attr) \ attr struct type *name##_RB_MINMAX(struct name *, int) /* Main rb operation. * Moves node close to the key of elm to top */ #define RB_GENERATE(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp,) #define RB_GENERATE_STATIC(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) #define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ RB_GENERATE_INSERT(name, type, field, cmp, attr) \ RB_GENERATE_REMOVE(name, type, field, attr) \ RB_GENERATE_FIND(name, type, field, cmp, attr) \ RB_GENERATE_NFIND(name, type, field, cmp, attr) \ RB_GENERATE_NEXT(name, type, field, attr) \ RB_GENERATE_PREV(name, type, field, attr) \ RB_GENERATE_MINMAX(name, type, field, attr) #define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ attr void \ name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ { \ struct type *parent, *gparent, *tmp; \ while ((parent = RB_PARENT(elm, field)) != NULL && \ RB_COLOR(parent, field) == RB_RED) { \ gparent = RB_PARENT(parent, field); \ if (parent == RB_LEFT(gparent, field)) { \ tmp = RB_RIGHT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_RIGHT(parent, field) == elm) { \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_RIGHT(head, gparent, tmp, field); \ } else { \ tmp = RB_LEFT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_LEFT(parent, field) == elm) { \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_LEFT(head, gparent, tmp, field); \ } \ } \ RB_COLOR(head->rbh_root, field) = RB_BLACK; \ } #define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ attr void \ name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ { \ struct type *tmp; \ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ elm != RB_ROOT(head)) { \ if (RB_LEFT(parent, field) == elm) { \ tmp = RB_RIGHT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = RB_RIGHT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ struct type *oleft; \ if ((oleft = RB_LEFT(tmp, field)) \ != NULL) \ RB_COLOR(oleft, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_RIGHT(head, tmp, oleft, field);\ tmp = RB_RIGHT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_RIGHT(tmp, field)) \ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_LEFT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } else { \ tmp = RB_LEFT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = RB_LEFT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ struct type *oright; \ if ((oright = RB_RIGHT(tmp, field)) \ != NULL) \ RB_COLOR(oright, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_LEFT(head, tmp, oright, field);\ tmp = RB_LEFT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_LEFT(tmp, field)) \ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_RIGHT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } \ } \ if (elm) \ RB_COLOR(elm, field) = RB_BLACK; \ } #define RB_GENERATE_REMOVE(name, type, field, attr) \ attr struct type * \ name##_RB_REMOVE(struct name *head, struct type *elm) \ { \ struct type *child, *parent, *old = elm; \ int color; \ if (RB_LEFT(elm, field) == NULL) \ child = RB_RIGHT(elm, field); \ else if (RB_RIGHT(elm, field) == NULL) \ child = RB_LEFT(elm, field); \ else { \ struct type *left; \ elm = RB_RIGHT(elm, field); \ while ((left = RB_LEFT(elm, field)) != NULL) \ elm = left; \ child = RB_RIGHT(elm, field); \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ if (RB_PARENT(elm, field) == old) \ parent = elm; \ (elm)->field = (old)->field; \ if (RB_PARENT(old, field)) { \ if (RB_LEFT(RB_PARENT(old, field), field) == old)\ RB_LEFT(RB_PARENT(old, field), field) = elm;\ else \ RB_RIGHT(RB_PARENT(old, field), field) = elm;\ RB_AUGMENT(RB_PARENT(old, field)); \ } else \ RB_ROOT(head) = elm; \ RB_PARENT(RB_LEFT(old, field), field) = elm; \ if (RB_RIGHT(old, field)) \ RB_PARENT(RB_RIGHT(old, field), field) = elm; \ if (parent) { \ left = parent; \ do { \ RB_AUGMENT(left); \ } while ((left = RB_PARENT(left, field)) != NULL); \ } \ goto color; \ } \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ color: \ if (color == RB_BLACK) \ name##_RB_REMOVE_COLOR(head, parent, child); \ return (old); \ } \ #define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ /* Inserts a node into the RB tree */ \ attr struct type * \ name##_RB_INSERT(struct name *head, struct type *elm) \ { \ struct type *tmp; \ struct type *parent = NULL; \ int comp = 0; \ tmp = RB_ROOT(head); \ while (tmp) { \ parent = tmp; \ comp = (cmp)(elm, parent); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ RB_SET(elm, parent, field); \ if (parent != NULL) { \ if (comp < 0) \ RB_LEFT(parent, field) = elm; \ else \ RB_RIGHT(parent, field) = elm; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = elm; \ name##_RB_INSERT_COLOR(head, elm); \ return (NULL); \ } #define RB_GENERATE_FIND(name, type, field, cmp, attr) \ /* Finds the node with the same key as elm */ \ attr struct type * \ name##_RB_FIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (NULL); \ } #define RB_GENERATE_NFIND(name, type, field, cmp, attr) \ /* Finds the first node greater than or equal to the search key */ \ attr struct type * \ name##_RB_NFIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *res = NULL; \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) { \ res = tmp; \ tmp = RB_LEFT(tmp, field); \ } \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (res); \ } #define RB_GENERATE_NEXT(name, type, field, attr) \ /* ARGSUSED */ \ attr struct type * \ name##_RB_NEXT(struct type *elm) \ { \ if (RB_RIGHT(elm, field)) { \ elm = RB_RIGHT(elm, field); \ while (RB_LEFT(elm, field)) \ elm = RB_LEFT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } #define RB_GENERATE_PREV(name, type, field, attr) \ /* ARGSUSED */ \ attr struct type * \ name##_RB_PREV(struct type *elm) \ { \ if (RB_LEFT(elm, field)) { \ elm = RB_LEFT(elm, field); \ while (RB_RIGHT(elm, field)) \ elm = RB_RIGHT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } #define RB_GENERATE_MINMAX(name, type, field, attr) \ attr struct type * \ name##_RB_MINMAX(struct name *head, int val) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *parent = NULL; \ while (tmp) { \ parent = tmp; \ if (val < 0) \ tmp = RB_LEFT(tmp, field); \ else \ tmp = RB_RIGHT(tmp, field); \ } \ return (parent); \ } #define RB_NEGINF -1 #define RB_INF 1 #define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) #define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) #define RB_FIND(name, x, y) name##_RB_FIND(x, y) #define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) #define RB_NEXT(name, x, y) name##_RB_NEXT(y) #define RB_PREV(name, x, y) name##_RB_PREV(y) #define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) #define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) #define RB_FOREACH(x, name, head) \ for ((x) = RB_MIN(name, head); \ (x) != NULL; \ (x) = name##_RB_NEXT(x)) #define RB_FOREACH_FROM(x, name, y) \ for ((x) = (y); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_SAFE(x, name, head, y) \ for ((x) = RB_MIN(name, head); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_REVERSE(x, name, head) \ for ((x) = RB_MAX(name, head); \ (x) != NULL; \ (x) = name##_RB_PREV(x)) #define RB_FOREACH_REVERSE_FROM(x, name, y) \ for ((x) = (y); \ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ for ((x) = RB_MAX(name, head); \ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ (x) = (y)) #endif /* _SYS_TREE_H_ */ ================================================ FILE: tpws/params.c ================================================ #include "params.h" #include #include #include #ifdef __ANDROID__ #include #endif const char *progname = "tpws"; int DLOG_FILE(FILE *F, const char *format, va_list args) { return vfprintf(F, format, args); } int DLOG_CON(const char *format, int syslog_priority, va_list args) { return DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args); } int DLOG_FILENAME(const char *filename, const char *format, va_list args) { int r; FILE *F = fopen(filename,"at"); if (F) { r = DLOG_FILE(F, format, args); fclose(F); } else r=-1; return r; } typedef void (*f_log_function)(int priority, const char *line); static char log_buf[1024]; static size_t log_buf_sz=0; static void syslog_log_function(int priority, const char *line) { syslog(priority,"%s",log_buf); } #ifdef __ANDROID__ static enum android_LogPriority syslog_priority_to_android(int priority) { enum android_LogPriority ap; switch(priority) { case LOG_INFO: case LOG_NOTICE: ap=ANDROID_LOG_INFO; break; case LOG_ERR: ap=ANDROID_LOG_ERROR; break; case LOG_WARNING: ap=ANDROID_LOG_WARN; break; case LOG_EMERG: case LOG_ALERT: case LOG_CRIT: ap=ANDROID_LOG_FATAL; break; case LOG_DEBUG: ap=ANDROID_LOG_DEBUG; break; default: ap=ANDROID_LOG_UNKNOWN; } return ap; } static void android_log_function(int priority, const char *line) { __android_log_print(syslog_priority_to_android(priority), progname, "%s", line); } #endif static void log_buffered(f_log_function log_function, int syslog_priority, const char *format, va_list args) { if (vsnprintf(log_buf+log_buf_sz,sizeof(log_buf)-log_buf_sz,format,args)>0) { log_buf_sz=strlen(log_buf); // log when buffer is full or buffer ends with \n if (log_buf_sz>=(sizeof(log_buf)-1) || (log_buf_sz && log_buf[log_buf_sz-1]=='\n')) { log_function(syslog_priority,log_buf); log_buf_sz = 0; } } } static int DLOG_VA(const char *format, int syslog_priority, bool condup, int level, va_list args) { int r=0; va_list args2; if (condup && !(params.debug>=level && params.debug_target==LOG_TARGET_CONSOLE)) { va_copy(args2,args); DLOG_CON(format,syslog_priority,args2); va_end(args2); } if (params.debug>=level) { switch(params.debug_target) { case LOG_TARGET_CONSOLE: r = DLOG_CON(format,syslog_priority,args); break; case LOG_TARGET_FILE: r = DLOG_FILENAME(params.debug_logfile,format,args); break; case LOG_TARGET_SYSLOG: // skip newlines log_buffered(syslog_log_function,syslog_priority,format,args); r = 1; break; #ifdef __ANDROID__ case LOG_TARGET_ANDROID: // skip newlines log_buffered(android_log_function,syslog_priority,format,args); r = 1; break; #endif default: break; } } return r; } int DLOG(const char *format, int level, ...) { int r; va_list args; va_start(args, level); r = DLOG_VA(format, LOG_DEBUG, false, level, args); va_end(args); return r; } int DLOG_CONDUP(const char *format, ...) { int r; va_list args; va_start(args, format); r = DLOG_VA(format, LOG_DEBUG, true, 1, args); va_end(args); return r; } int DLOG_ERR(const char *format, ...) { int r; va_list args; va_start(args, format); r = DLOG_VA(format, LOG_ERR, true, 1, args); va_end(args); return r; } int DLOG_PERROR(const char *s) { return DLOG_ERR("%s: %s\n", s, strerror(errno)); } int LOG_APPEND(const char *filename, const char *format, va_list args) { int r; FILE *F = fopen(filename,"at"); if (F) { fprint_localtime(F); fprintf(F, " : "); r = vfprintf(F, format, args); fprintf(F, "\n"); fclose(F); } else r=-1; return r; } int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) { if (*params.hostlist_auto_debuglog) { int r; va_list args; va_start(args, format); r = LOG_APPEND(params.hostlist_auto_debuglog, format, args); va_end(args); return r; } else return 0; } void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) { size_t k; bool bcut = false; if (size > limit) { size = limit; bcut = true; } if (!size) return; for (k = 0; k < size; k++) VPRINT("%02X ", data[k]); VPRINT(bcut ? "... : " : ": "); for (k = 0; k < size; k++) VPRINT("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.'); if (bcut) VPRINT(" ..."); } void dp_init(struct desync_profile *dp) { LIST_INIT(&dp->hl_collection); LIST_INIT(&dp->hl_collection_exclude); LIST_INIT(&dp->ips_collection); LIST_INIT(&dp->ips_collection_exclude); LIST_INIT(&dp->pf_tcp); dp->filter_ipv4 = dp->filter_ipv6 = true; memcpy(dp->hostspell, "host", 4); // default hostspell dp->hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; dp->hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; } struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) { struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list)); if (!entry) return NULL; dp_init(&entry->dp); // add to the tail struct desync_profile_list *dpn,*dpl=LIST_FIRST(head); if (dpl) { while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; LIST_INSERT_AFTER(dpl, entry, next); } else LIST_INSERT_HEAD(head, entry, next); return entry; } static void dp_clear_dynamic(struct desync_profile *dp) { hostlist_collection_destroy(&dp->hl_collection); hostlist_collection_destroy(&dp->hl_collection_exclude); ipset_collection_destroy(&dp->ips_collection); ipset_collection_destroy(&dp->ips_collection_exclude); port_filters_destroy(&dp->pf_tcp); HostFailPoolDestroy(&dp->hostlist_auto_fail_counters); } void dp_clear(struct desync_profile *dp) { dp_clear_dynamic(dp); memset(dp,0,sizeof(*dp)); } void dp_entry_destroy(struct desync_profile_list *entry) { dp_clear_dynamic(&entry->dp); free(entry); } void dp_list_destroy(struct desync_profile_list_head *head) { struct desync_profile_list *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); dp_entry_destroy(entry); } } #if !defined( __OpenBSD__) && !defined(__ANDROID__) void cleanup_args(struct params_s *params) { wordfree(¶ms->wexp); } #endif void cleanup_params(struct params_s *params) { #if !defined( __OpenBSD__) && !defined(__ANDROID__) cleanup_args(params); #endif dp_list_destroy(¶ms->desync_profiles); hostlist_files_destroy(¶ms->hostlists); ipset_files_destroy(¶ms->ipsets); ipcacheDestroy(¶ms->ipcache); free(params->user); params->user=NULL; } ================================================ FILE: tpws/params.h ================================================ #pragma once #include #include #include #include #include #include #include #if !defined( __OpenBSD__) && !defined(__ANDROID__) #include #endif #include "tpws.h" #include "pools.h" #include "helpers.h" #include "protocol.h" #define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 #define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 #define FIX_SEG_DEFAULT_MAX_WAIT 50 #define IPCACHE_LIFETIME 7200 #define MAX_GIDS 64 enum bindll { unwanted=0, no, prefer, force }; #define MAX_BINDS 32 struct bind_s { char bindaddr[64],bindiface[IF_NAMESIZE]; bool bind_if6; enum bindll bindll; int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll; }; #define MAX_SPLITS 16 enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID }; struct desync_profile { int n; // number of the profile bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase; int hostpad; char hostspell[4]; bool split_any_protocol; bool disorder, disorder_http, disorder_tls; bool oob, oob_http, oob_tls; uint8_t oob_byte; // multisplit struct proto_pos splits[MAX_SPLITS]; int split_count; struct proto_pos tlsrec; int mss; bool tamper_start_n,tamper_cutoff_n; unsigned int tamper_start,tamper_cutoff; bool filter_ipv4,filter_ipv6; struct port_filters_head pf_tcp; uint32_t filter_l7; // L7_PROTO_* bits // list of pointers to ipsets struct ipset_collection_head ips_collection, ips_collection_exclude; // list of pointers to hostlist files struct hostlist_collection_head hl_collection, hl_collection_exclude; // pointer to autohostlist. NULL if no autohostlist for the profile. struct hostlist_file *hostlist_auto; int hostlist_auto_fail_threshold, hostlist_auto_fail_time; hostfail_pool *hostlist_auto_fail_counters; }; #define PROFILE_IPSETS_ABSENT(dp) (!LIST_FIRST(&dp->ips_collection) && !LIST_FIRST(&dp->ips_collection_exclude)) #define PROFILE_IPSETS_EMPTY(dp) (ipset_collection_is_empty(&dp->ips_collection) && ipset_collection_is_empty(&dp->ips_collection_exclude)) #define PROFILE_HOSTLISTS_EMPTY(dp) (hostlist_collection_is_empty(&dp->hl_collection) && hostlist_collection_is_empty(&dp->hl_collection_exclude)) struct desync_profile_list { struct desync_profile dp; LIST_ENTRY(desync_profile_list) next; }; LIST_HEAD(desync_profile_list_head, desync_profile_list); struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head); void dp_entry_destroy(struct desync_profile_list *entry); void dp_list_destroy(struct desync_profile_list_head *head); void dp_init(struct desync_profile *dp); void dp_clear(struct desync_profile *dp); struct params_s { #if !defined( __OpenBSD__) && !defined(__ANDROID__) wordexp_t wexp; // for file based config #endif int debug; enum log_target debug_target; char debug_logfile[PATH_MAX]; struct bind_s binds[MAX_BINDS]; int binds_last; bool bind_wait_only; uint16_t port; struct sockaddr_in connect_bind4; struct sockaddr_in6 connect_bind6; char connect_bind6_ifname[IF_NAMESIZE]; uint8_t proxy_type; unsigned int fix_seg; bool fix_seg_avail; bool no_resolve; bool skip_nodelay; bool daemon; bool droproot; char *user; uid_t uid; gid_t gid[MAX_GIDS]; int gid_count; char pidfile[PATH_MAX]; int maxconn,resolver_threads,maxfiles,max_orphan_time; int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; #if defined(__linux__) || defined(__APPLE__) int tcp_user_timeout_local,tcp_user_timeout_remote; #endif #if defined(BSD) bool pf_enable; #endif #ifdef SPLICE_PRESENT bool nosplice; #endif int ttl_default; char hostlist_auto_debuglog[PATH_MAX]; // hostlist files with data for all profiles struct hostlist_files_head hostlists; // ipset files with data for all profiles struct ipset_files_head ipsets; bool tamper; // any tamper option is set bool tamper_lim; // tamper-start or tamper-cutoff set in any profile struct desync_profile_list_head desync_profiles; unsigned int ipcache_lifetime; bool cache_hostname; ip_cache ipcache; }; extern struct params_s params; extern const char *progname; #if !defined( __OpenBSD__) && !defined(__ANDROID__) void cleanup_args(struct params_s *params); #endif void cleanup_params(struct params_s *params); int DLOG(const char *format, int level, ...); int DLOG_CONDUP(const char *format, ...); int DLOG_ERR(const char *format, ...); int DLOG_PERROR(const char *s); int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); #define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__) #define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__) ================================================ FILE: tpws/pools.c ================================================ #define _GNU_SOURCE #include "pools.h" #include #include #include #include #define DESTROY_STR_POOL(etype, ppool) \ etype *elem, *tmp; \ HASH_ITER(hh, *ppool, elem, tmp) { \ free(elem->str); \ HASH_DEL(*ppool, elem); \ free(elem); \ } #define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \ etype *elem; \ if (!(elem = (etype*)malloc(sizeof(etype)))) \ return false; \ if (!(elem->str = malloc(keystr_len + 1))) \ { \ free(elem); \ return false; \ } \ memcpy(elem->str, keystr, keystr_len); \ elem->str[keystr_len] = 0; \ oom = false; \ HASH_ADD_KEYPTR(hh, *ppool, elem->str, keystr_len, elem); \ if (oom) \ { \ free(elem->str); \ free(elem); \ return false; \ } #define ADD_HOSTLIST_POOL(etype, ppool, keystr, keystr_len, flg) \ etype *elem_find; \ HASH_FIND(hh, *ppool, keystr, keystr_len, elem_find); \ if (!elem_find) { \ ADD_STR_POOL(etype,ppool,keystr,keystr_len); \ elem->flags = flg; \ } #undef uthash_nonfatal_oom #define uthash_nonfatal_oom(elt) ut_oom_recover(elt) static bool oom = false; static void ut_oom_recover(void *elem) { oom = true; } // for not zero terminated strings bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags) { ADD_HOSTLIST_POOL(hostlist_pool, pp, s, slen, flags) return true; } // for zero terminated strings bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags) { return HostlistPoolAddStrLen(pp, s, strlen(s), flags); } hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s) { hostlist_pool *elem; HASH_FIND_STR(p, s, elem); return elem; } bool HostlistPoolCheckStr(hostlist_pool *p, const char *s) { return !!HostlistPoolGetStr(p,s); } void HostlistPoolDestroy(hostlist_pool **pp) { DESTROY_STR_POOL(hostlist_pool, pp) } void HostFailPoolDestroy(hostfail_pool **pp) { DESTROY_STR_POOL(hostfail_pool, pp) } hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time) { size_t slen = strlen(s); ADD_STR_POOL(hostfail_pool, pp, s, slen) elem->expire = time(NULL) + fail_time; elem->counter = 0; return elem; } hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s) { hostfail_pool *elem; HASH_FIND_STR(p, s, elem); return elem; } void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem) { free(elem->str); HASH_DEL(*p, elem); free(elem); } void HostFailPoolPurge(hostfail_pool **pp) { hostfail_pool *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *pp, elem, tmp) { if (now >= elem->expire) HostFailPoolDel(pp, elem); } } static time_t host_fail_purge_prev=0; void HostFailPoolPurgeRateLimited(hostfail_pool **pp) { time_t now = time(NULL); // do not purge too often to save resources if (host_fail_purge_prev != now) { HostFailPoolPurge(pp); host_fail_purge_prev = now; } } void HostFailPoolDump(hostfail_pool *p) { hostfail_pool *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, p, elem, tmp) printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now); } bool strlist_add(struct str_list_head *head, const char *filename) { struct str_list *entry = malloc(sizeof(struct str_list)); if (!entry) return false; entry->str = strdup(filename); if (!entry->str) { free(entry); return false; } LIST_INSERT_HEAD(head, entry, next); return true; } static void strlist_entry_destroy(struct str_list *entry) { free(entry->str); free(entry); } void strlist_destroy(struct str_list_head *head) { struct str_list *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); strlist_entry_destroy(entry); } } struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename) { struct hostlist_file *entry = malloc(sizeof(struct hostlist_file)); if (entry) { if (filename) { if (!(entry->filename = strdup(filename))) { free(entry); return false; } } else entry->filename = NULL; FILE_MOD_RESET(&entry->mod_sig); entry->hostlist = NULL; LIST_INSERT_HEAD(head, entry, next); } return entry; } static void hostlist_files_entry_destroy(struct hostlist_file *entry) { free(entry->filename); HostlistPoolDestroy(&entry->hostlist); free(entry); } void hostlist_files_destroy(struct hostlist_files_head *head) { struct hostlist_file *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); hostlist_files_entry_destroy(entry); } } struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename) { struct hostlist_file *hfile; LIST_FOREACH(hfile, head, next) { if (hfile->filename && !strcmp(hfile->filename,filename)) return hfile; } return NULL; } void hostlist_files_reset_modtime(struct hostlist_files_head *list) { struct hostlist_file *hfile; LIST_FOREACH(hfile, list, next) FILE_MOD_RESET(&hfile->mod_sig); } struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile) { struct hostlist_item *entry = malloc(sizeof(struct hostlist_item)); if (entry) { entry->hfile = hfile; LIST_INSERT_HEAD(head, entry, next); } return entry; } void hostlist_collection_destroy(struct hostlist_collection_head *head) { struct hostlist_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename) { struct hostlist_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) return item; } return NULL; } bool hostlist_collection_is_empty(const struct hostlist_collection_head *head) { const struct hostlist_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->hostlist) return false; } return true; } static int kavl_bit_cmp(const struct kavl_bit_elem *p, const struct kavl_bit_elem *q) { unsigned int bitlen = q->bitlen < p->bitlen ? q->bitlen : p->bitlen; unsigned int df = bitlen & 7, bytes = bitlen >> 3; int cmp = memcmp(p->data, q->data, bytes); if (cmp || !df) return cmp; uint8_t c1 = p->data[bytes] >> (8 - df); uint8_t c2 = q->data[bytes] >> (8 - df); return c1data); free(e); } } void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen) { struct kavl_bit_elem temp = { .bitlen = bitlen, .data = (uint8_t*)data }; kavl_bit_destroy_elem(kavl_erase(kavl_bit, hdr, &temp, 0)); } void kavl_bit_destroy(struct kavl_bit_elem **hdr) { while (*hdr) { struct kavl_bit_elem *e = kavl_erase_first(kavl_bit, hdr); if (!e) break; kavl_bit_destroy_elem(e); } free(*hdr); } struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size) { if (!struct_size) struct_size=sizeof(struct kavl_bit_elem); struct kavl_bit_elem *v, *e = calloc(1, struct_size); if (!e) return 0; e->bitlen = bitlen; e->data = data; v = kavl_insert(kavl_bit, hdr, e, 0); while (e != v && e->bitlen < v->bitlen) { kavl_bit_delete(hdr, v->data, v->bitlen); v = kavl_insert(kavl_bit, hdr, e, 0); } if (e != v) kavl_bit_destroy_elem(e); return v; } struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen) { struct kavl_bit_elem temp = { .bitlen = bitlen, .data = (uint8_t*)data }; return kavl_find(kavl_bit, hdr, &temp, 0); } static bool ipset_kavl_add(struct kavl_bit_elem **ipset, const void *a, uint8_t preflen) { uint8_t bytelen = (preflen+7)>>3; uint8_t *abuf = malloc(bytelen); if (!abuf) return false; memcpy(abuf,a,bytelen); if (!kavl_bit_add(ipset,abuf,preflen,0)) { free(abuf); return false; } return true; } bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen) { return !!kavl_bit_get(ipset,a,preflen); } bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen) { if (preflen>32) return false; return ipset_kavl_add(ipset,a,preflen); } void ipset4Print(struct kavl_bit_elem *ipset) { if (!ipset) return; struct cidr4 c; const struct kavl_bit_elem *elem; kavl_itr_t(kavl_bit) itr; kavl_itr_first(kavl_bit, ipset, &itr); do { elem = kavl_at(&itr); c.preflen = elem->bitlen; expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); print_cidr4(&c); printf("\n"); } while (kavl_itr_next(kavl_bit, &itr)); } bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen) { return !!kavl_bit_get(ipset,a,preflen); } bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen) { if (preflen>128) return false; return ipset_kavl_add(ipset,a,preflen); } void ipset6Print(struct kavl_bit_elem *ipset) { if (!ipset) return; struct cidr6 c; const struct kavl_bit_elem *elem; kavl_itr_t(kavl_bit) itr; kavl_itr_first(kavl_bit, ipset, &itr); do { elem = kavl_at(&itr); c.preflen = elem->bitlen; expand_bits(&c.addr, elem->data, elem->bitlen, sizeof(c.addr)); print_cidr6(&c); printf("\n"); } while (kavl_itr_next(kavl_bit, &itr)); } void ipsetDestroy(ipset *ipset) { kavl_bit_destroy(&ipset->ips4); kavl_bit_destroy(&ipset->ips6); } void ipsetPrint(ipset *ipset) { ipset4Print(ipset->ips4); ipset6Print(ipset->ips6); } struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename) { struct ipset_file *entry = malloc(sizeof(struct ipset_file)); if (entry) { if (filename) { if (!(entry->filename = strdup(filename))) { free(entry); return false; } } else entry->filename = NULL; FILE_MOD_RESET(&entry->mod_sig); memset(&entry->ipset,0,sizeof(entry->ipset)); LIST_INSERT_HEAD(head, entry, next); } return entry; } static void ipset_files_entry_destroy(struct ipset_file *entry) { free(entry->filename); ipsetDestroy(&entry->ipset); free(entry); } void ipset_files_destroy(struct ipset_files_head *head) { struct ipset_file *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); ipset_files_entry_destroy(entry); } } struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename) { struct ipset_file *hfile; LIST_FOREACH(hfile, head, next) { if (hfile->filename && !strcmp(hfile->filename,filename)) return hfile; } return NULL; } void ipset_files_reset_modtime(struct ipset_files_head *list) { struct ipset_file *hfile; LIST_FOREACH(hfile, list, next) FILE_MOD_RESET(&hfile->mod_sig); } struct ipset_item *ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile) { struct ipset_item *entry = malloc(sizeof(struct ipset_item)); if (entry) { entry->hfile = hfile; LIST_INSERT_HEAD(head, entry, next); } return entry; } void ipset_collection_destroy(struct ipset_collection_head *head) { struct ipset_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename) { struct ipset_item *item; LIST_FOREACH(item, head, next) { if (item->hfile->filename && !strcmp(item->hfile->filename,filename)) return item; } return NULL; } bool ipset_collection_is_empty(const struct ipset_collection_head *head) { const struct ipset_item *item; LIST_FOREACH(item, head, next) { if (!IPSET_EMPTY(&item->hfile->ipset)) return false; } return true; } bool port_filter_add(struct port_filters_head *head, const port_filter *pf) { struct port_filter_item *entry = malloc(sizeof(struct port_filter_item)); if (entry) { entry->pf = *pf; LIST_INSERT_HEAD(head, entry, next); } return entry; } void port_filters_destroy(struct port_filters_head *head) { struct port_filter_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry); } } bool port_filters_in_range(const struct port_filters_head *head, uint16_t port) { const struct port_filter_item *item; if (!LIST_FIRST(head)) return true; LIST_FOREACH(item, head, next) { if (pf_in_range(port, &item->pf)) return true; } return false; } bool port_filters_deny_if_empty(struct port_filters_head *head) { port_filter pf; if (LIST_FIRST(head)) return true; return pf_parse("0",&pf) && port_filter_add(head,&pf); } struct blob_item *blob_collection_add(struct blob_collection_head *head) { struct blob_item *entry = calloc(1,sizeof(struct blob_item)); if (entry) { // insert to the end struct blob_item *itemc,*iteml=LIST_FIRST(head); if (iteml) { while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; LIST_INSERT_AFTER(iteml, entry, next); } else LIST_INSERT_HEAD(head, entry, next); } return entry; } struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve) { struct blob_item *entry = calloc(1,sizeof(struct blob_item)); if (!entry) return NULL; if (!(entry->data = malloc(size+size_reserve))) { free(entry); return NULL; } if (data) memcpy(entry->data,data,size); entry->size = size; entry->size_buf = size+size_reserve; // insert to the end struct blob_item *itemc,*iteml=LIST_FIRST(head); if (iteml) { while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; LIST_INSERT_AFTER(iteml, entry, next); } else LIST_INSERT_HEAD(head, entry, next); return entry; } void blob_collection_destroy(struct blob_collection_head *head) { struct blob_item *entry; while ((entry = LIST_FIRST(head))) { LIST_REMOVE(entry, next); free(entry->extra); free(entry->extra2); free(entry->data); free(entry); } } bool blob_collection_empty(const struct blob_collection_head *head) { return !LIST_FIRST(head); } static void ipcache_item_touch(ip_cache_item *item) { time(&item->last); } static void ipcache_item_init(ip_cache_item *item) { ipcache_item_touch(item); item->hostname = NULL; item->hostname_is_ip = false; } static void ipcache_item_destroy(ip_cache_item *item) { free(item->hostname); } static void ipcache4Destroy(ip_cache4 **ipcache) { ip_cache4 *elem, *tmp; HASH_ITER(hh, *ipcache, elem, tmp) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } static void ipcache4Key(ip4if *key, const struct in_addr *a) { memset(key,0,sizeof(*key)); // make sure everything is zero key->addr = *a; } static ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a) { ip_cache4 *entry; struct ip4if key; ipcache4Key(&key,a); HASH_FIND(hh, ipcache, &key, sizeof(key), entry); return entry; } static ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a) { // avoid dups ip_cache4 *entry = ipcache4Find(*ipcache,a); if (entry) return entry; // already included entry = malloc(sizeof(ip_cache4)); if (!entry) return NULL; ipcache4Key(&entry->key,a); oom = false; HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); if (oom) { free(entry); return NULL; } ipcache_item_init(&entry->data); return entry; } static void ipcache4Print(ip_cache4 *ipcache) { char s_ip[16]; time_t now; ip_cache4 *ipc, *tmp; time(&now); HASH_ITER(hh, ipcache , ipc, tmp) { *s_ip=0; inet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip)); printf("%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)); } } static void ipcache6Destroy(ip_cache6 **ipcache) { ip_cache6 *elem, *tmp; HASH_ITER(hh, *ipcache, elem, tmp) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } static void ipcache6Key(ip6if *key, const struct in6_addr *a) { memset(key,0,sizeof(*key)); // make sure everything is zero key->addr = *a; } static ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a) { ip_cache6 *entry; ip6if key; ipcache6Key(&key,a); HASH_FIND(hh, ipcache, &key, sizeof(key), entry); return entry; } static ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a) { // avoid dups ip_cache6 *entry = ipcache6Find(*ipcache,a); if (entry) return entry; // already included entry = malloc(sizeof(ip_cache6)); if (!entry) return NULL; ipcache6Key(&entry->key,a); oom = false; HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); if (oom) { free(entry); return NULL; } ipcache_item_init(&entry->data); return entry; } static void ipcache6Print(ip_cache6 *ipcache) { char s_ip[40]; time_t now; ip_cache6 *ipc, *tmp; time(&now); HASH_ITER(hh, ipcache , ipc, tmp) { *s_ip=0; inet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip)); printf("%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)); } } void ipcacheDestroy(ip_cache *ipcache) { ipcache4Destroy(&ipcache->ipcache4); ipcache6Destroy(&ipcache->ipcache6); } void ipcachePrint(ip_cache *ipcache) { ipcache4Print(ipcache->ipcache4); ipcache6Print(ipcache->ipcache6); } ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6) { ip_cache4 *ipcache4; ip_cache6 *ipcache6; if (a4) { if ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4))) { ipcache_item_touch(&ipcache4->data); return &ipcache4->data; } } else if (a6) { if ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6))) { ipcache_item_touch(&ipcache6->data); return &ipcache6->data; } } return NULL; } static void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime) { ip_cache4 *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *ipcache, elem, tmp) { if (now >= (elem->data.last + lifetime)) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } } static void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime) { ip_cache6 *elem, *tmp; time_t now = time(NULL); HASH_ITER(hh, *ipcache, elem, tmp) { if (now >= (elem->data.last + lifetime)) { HASH_DEL(*ipcache, elem); ipcache_item_destroy(&elem->data); free(elem); } } } static void ipcache_purge(ip_cache *ipcache, time_t lifetime) { if (lifetime) // 0 = no expire { ipcache4_purge(&ipcache->ipcache4, lifetime); ipcache6_purge(&ipcache->ipcache6, lifetime); } } static time_t ipcache_purge_prev=0; void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime) { time_t now = time(NULL); // do not purge too often to save resources if (ipcache_purge_prev != now) { ipcache_purge(ipcache, lifetime); ipcache_purge_prev = now; } } ================================================ FILE: tpws/pools.h ================================================ #pragma once #include #include #include #include #include #include "helpers.h" //#define HASH_BLOOM 20 #define HASH_NONFATAL_OOM 1 #define HASH_FUNCTION HASH_BER #include "uthash.h" #include "kavl.h" #define HOSTLIST_POOL_FLAG_STRICT_MATCH 1 typedef struct hostlist_pool { char *str; /* key */ uint32_t flags; /* custom data */ UT_hash_handle hh; /* makes this structure hashable */ } hostlist_pool; void HostlistPoolDestroy(hostlist_pool **pp); bool HostlistPoolAddStr(hostlist_pool **pp, const char *s, uint32_t flags); bool HostlistPoolAddStrLen(hostlist_pool **pp, const char *s, size_t slen, uint32_t flags); hostlist_pool *HostlistPoolGetStr(hostlist_pool *p, const char *s); struct str_list { char *str; LIST_ENTRY(str_list) next; }; LIST_HEAD(str_list_head, str_list); typedef struct hostfail_pool { char *str; /* key */ int counter; /* value */ time_t expire; /* when to expire record (unixtime) */ UT_hash_handle hh; /* makes this structure hashable */ } hostfail_pool; void HostFailPoolDestroy(hostfail_pool **pp); hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); void HostFailPoolPurge(hostfail_pool **pp); void HostFailPoolPurgeRateLimited(hostfail_pool **pp); void HostFailPoolDump(hostfail_pool *p); bool strlist_add(struct str_list_head *head, const char *filename); void strlist_destroy(struct str_list_head *head); struct hostlist_file { char *filename; file_mod_sig mod_sig; hostlist_pool *hostlist; LIST_ENTRY(hostlist_file) next; }; LIST_HEAD(hostlist_files_head, hostlist_file); struct hostlist_file *hostlist_files_add(struct hostlist_files_head *head, const char *filename); void hostlist_files_destroy(struct hostlist_files_head *head); struct hostlist_file *hostlist_files_search(struct hostlist_files_head *head, const char *filename); void hostlist_files_reset_modtime(struct hostlist_files_head *list); struct hostlist_item { struct hostlist_file *hfile; LIST_ENTRY(hostlist_item) next; }; LIST_HEAD(hostlist_collection_head, hostlist_item); struct hostlist_item *hostlist_collection_add(struct hostlist_collection_head *head, struct hostlist_file *hfile); void hostlist_collection_destroy(struct hostlist_collection_head *head); struct hostlist_item *hostlist_collection_search(struct hostlist_collection_head *head, const char *filename); bool hostlist_collection_is_empty(const struct hostlist_collection_head *head); struct kavl_bit_elem { unsigned int bitlen; uint8_t *data; KAVL_HEAD(struct kavl_bit_elem) head; }; struct kavl_bit_elem *kavl_bit_get(const struct kavl_bit_elem *hdr, const void *data, unsigned int bitlen); struct kavl_bit_elem *kavl_bit_add(struct kavl_bit_elem **hdr, void *data, unsigned int bitlen, size_t struct_size); void kavl_bit_delete(struct kavl_bit_elem **hdr, const void *data, unsigned int bitlen); void kavl_bit_destroy(struct kavl_bit_elem **hdr); // combined ipset ipv4 and ipv6 typedef struct ipset { struct kavl_bit_elem *ips4,*ips6; } ipset; #define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6) bool ipset4Add(struct kavl_bit_elem **ipset, const struct in_addr *a, uint8_t preflen); static inline bool ipset4AddCidr(struct kavl_bit_elem **ipset, const struct cidr4 *cidr) { return ipset4Add(ipset,&cidr->addr,cidr->preflen); } bool ipset4Check(const struct kavl_bit_elem *ipset, const struct in_addr *a, uint8_t preflen); void ipset4Print(struct kavl_bit_elem *ipset); bool ipset6Add(struct kavl_bit_elem **ipset, const struct in6_addr *a, uint8_t preflen); static inline bool ipset6AddCidr(struct kavl_bit_elem **ipset, const struct cidr6 *cidr) { return ipset6Add(ipset,&cidr->addr,cidr->preflen); } bool ipset6Check(const struct kavl_bit_elem *ipset, const struct in6_addr *a, uint8_t preflen); void ipset6Print(struct kavl_bit_elem *ipset); void ipsetDestroy(ipset *ipset); void ipsetPrint(ipset *ipset); struct ipset_file { char *filename; file_mod_sig mod_sig; ipset ipset; LIST_ENTRY(ipset_file) next; }; LIST_HEAD(ipset_files_head, ipset_file); struct ipset_file *ipset_files_add(struct ipset_files_head *head, const char *filename); void ipset_files_destroy(struct ipset_files_head *head); struct ipset_file *ipset_files_search(struct ipset_files_head *head, const char *filename); void ipset_files_reset_modtime(struct ipset_files_head *list); struct ipset_item { struct ipset_file *hfile; LIST_ENTRY(ipset_item) next; }; LIST_HEAD(ipset_collection_head, ipset_item); struct ipset_item * ipset_collection_add(struct ipset_collection_head *head, struct ipset_file *hfile); void ipset_collection_destroy(struct ipset_collection_head *head); struct ipset_item *ipset_collection_search(struct ipset_collection_head *head, const char *filename); bool ipset_collection_is_empty(const struct ipset_collection_head *head); struct port_filter_item { port_filter pf; LIST_ENTRY(port_filter_item) next; }; LIST_HEAD(port_filters_head, port_filter_item); bool port_filter_add(struct port_filters_head *head, const port_filter *pf); void port_filters_destroy(struct port_filters_head *head); bool port_filters_in_range(const struct port_filters_head *head, uint16_t port); bool port_filters_deny_if_empty(struct port_filters_head *head); struct blob_item { uint8_t *data; // main data blob size_t size; // main data blob size size_t size_buf;// main data blob allocated size void *extra; // any data without size void *extra2; // any data without size LIST_ENTRY(blob_item) next; }; LIST_HEAD(blob_collection_head, blob_item); struct blob_item *blob_collection_add(struct blob_collection_head *head); struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve); void blob_collection_destroy(struct blob_collection_head *head); bool blob_collection_empty(const struct blob_collection_head *head); typedef struct ip4if { struct in_addr addr; } ip4if; typedef struct ip6if { struct in6_addr addr; } ip6if; typedef struct ip_cache_item { time_t last; char *hostname; bool hostname_is_ip; } ip_cache_item; typedef struct ip_cache4 { ip4if key; ip_cache_item data; UT_hash_handle hh; /* makes this structure hashable */ } ip_cache4; typedef struct ip_cache6 { ip6if key; ip_cache_item data; UT_hash_handle hh; /* makes this structure hashable */ } ip_cache6; typedef struct ip_cache { ip_cache4 *ipcache4; ip_cache6 *ipcache6; } ip_cache; ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6); void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime); void ipcacheDestroy(ip_cache *ipcache); void ipcachePrint(ip_cache *ipcache); ================================================ FILE: tpws/protocol.c ================================================ #define _GNU_SOURCE #include "protocol.h" #include "helpers.h" #include #include #include #include // find N level domain static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **p, size_t *len) { int i; const uint8_t *p1,*p2; for (i=1,p2=dom+dlen;idom && *p2!='.'; p2--); if (p2<=dom) return false; } for (p1=p2-1 ; p1>dom && *p1!='.'; p1--); if (*p1=='.') p1++; if (p) *p = p1; if (len) *len = p2-p1; return true; } const char *l7proto_str(t_l7proto l7) { switch(l7) { case HTTP: return "http"; case TLS: return "tls"; case QUIC: return "quic"; case WIREGUARD: return "wireguard"; case DHT: return "dht"; default: return "unknown"; } } bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) { return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) || (l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) || (l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) || (l7proto==DHT && (filter_l7 & L7_PROTO_DHT)); } #define PM_ABS 0 #define PM_HOST 1 #define PM_HOST_END 2 #define PM_HOST_SLD 3 #define PM_HOST_MIDSLD 4 #define PM_HOST_ENDSLD 5 #define PM_HTTP_METHOD 6 #define PM_SNI_EXT 7 bool IsHostMarker(uint8_t posmarker) { switch(posmarker) { case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: return true; default: return false; } } const char *posmarker_name(uint8_t posmarker) { switch(posmarker) { case PM_ABS: return "abs"; case PM_HOST: return "host"; case PM_HOST_END: return "endhost"; case PM_HOST_SLD: return "sld"; case PM_HOST_MIDSLD: return "midsld"; case PM_HOST_ENDSLD: return "endsld"; case PM_HTTP_METHOD: return "method"; case PM_SNI_EXT: return "sniext"; default: return "?"; } } static size_t CheckPos(size_t sz, ssize_t offset) { return (offset>=0 && offsetmarker, sp->pos, data, sz); case TLS: return TLSPos(sp->marker, sp->pos, data, sz); default: return AnyProtoPos(sp->marker, sp->pos, data, sz); } } void 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) { int i,j; for(i=j=0;i14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' && data[9]>='0' && data[9]<='9' && data[10]>='0' && data[10]<='9' && data[11]>='0' && data[11]<='9'; } int HttpReplyCode(const uint8_t *data, size_t len) { return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0'); } bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf) { const uint8_t *p, *s, *e = data + len; p = (uint8_t*)strncasestr((char*)data, header, len); if (!p) return false; p += strlen(header); while (p < e && (*p == ' ' || *p == '\t')) p++; s = p; while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++; if (s > p) { size_t slen = s - p; if (buf && len_buf) { if (slen >= len_buf) slen = len_buf - 1; for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]); buf[slen] = 0; } return true; } return false; } bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) { return HttpExtractHeader(data, len, "\nHost:", host, len_host); } // DPI redirects are global redirects to another domain bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host) { char loc[256],*redirect_host, *p; int code; if (!host || !*host) return false; code = HttpReplyCode(data,len); if ((code!=302 && code!=307) || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false; // something like : https://censor.net/badpage.php?reason=denied&source=RKN if (!strncmp(loc,"http://",7)) redirect_host=loc+7; else if (!strncmp(loc,"https://",8)) redirect_host=loc+8; else return false; // somethinkg like : censor.net/badpage.php?reason=denied&source=RKN for(p=redirect_host; *p && *p!='/' ; p++); *p=0; if (!*redirect_host) return false; // somethinkg like : censor.net // extract 2nd level domains const char *dhost, *drhost; if (!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)) return false; // compare 2nd level domains return strcasecmp(dhost, drhost)!=0; } size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) { const uint8_t *method, *host=NULL, *p; size_t offset_host,len_host; ssize_t offset; int i; switch(posmarker) { case PM_HTTP_METHOD: // recognize some tpws pre-applied hacks method=data; if (sz<10) break; if (*method=='\n' || *method=='\r') method++; if (*method=='\n' || *method=='\r') method++; for (p=method,i=0;i<7;i++) if (*p>='A' && *p<='Z') p++; if (i<3 || *p!=' ') break; return CheckPos(sz,method-data+pos); case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: if (HttpFindHostConst(&host,data,sz) && (host-data+7)>12)==((tlsver>>4)&0xF))) ? "GREASE" : "UNKNOWN"; } } uint16_t TLSRecordDataLen(const uint8_t *data) { return pntoh16(data + 3); } size_t TLSRecordLen(const uint8_t *data) { return TLSRecordDataLen(data) + 5; } bool IsTLSRecordFull(const uint8_t *data, size_t len) { return TLSRecordLen(data)<=len; } bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) { return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); } // bPartialIsOK=true - accept partial packets not containing the whole TLS message bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) { // +0 // u8 HandshakeType: ClientHello // u24 Length // u16 Version // c[32] random // u8 SessionIDLength // // u16 CipherSuitesLength // // u8 CompressionMethodsLength // // u16 ExtensionsLength size_t l, ll; l = 1 + 3 + 2 + 32; // SessionIDLength if (len < (l + 1)) return false; if (!bPartialIsOK) { ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length if (len < (ll + 4)) return false; } l += data[l] + 1; // CipherSuitesLength if (len < (l + 2)) return false; l += pntoh16(data + l) + 2; // CompressionMethodsLength if (len < (l + 1)) return false; l += data[l] + 1; // ExtensionsLength if (len < (l + 2)) return false; data += l; len -= l; l = pntoh16(data); data += 2; len -= 2; if (bPartialIsOK) { if (len < l) l = len; } else { if (len < l) return false; } while (l >= 4) { uint16_t etype = pntoh16(data); size_t elen = pntoh16(data + 2); data += 4; l -= 4; if (l < elen) break; if (etype == type) { if (ext && len_ext) { *ext = data; *len_ext = elen; } return true; } data += elen; l -= elen; } return false; } bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) { // +0 // u8 ContentType: Handshake // u16 Version: TLS1.0 // u16 Length size_t reclen; if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; reclen=TLSRecordLen(data); if (reclen= len_host) slen = len_host - 1; for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); host[slen] = 0; } return true; } bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) { const uint8_t *ext; size_t elen; if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; return TLSExtractHostFromExt(ext, elen, host, len_host); } bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) { const uint8_t *ext; size_t elen; if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; return TLSExtractHostFromExt(ext, elen, host, len_host); } // find N level domain in SNI static bool TLSHelloFindNLDInSNI(const uint8_t *ext, size_t elen, int level, const uint8_t **p, size_t *len) { size_t slen; return TLSAdvanceToHostInSNI(&ext,&elen,&slen) && FindNLD(ext,slen,level,p,len); } // find the middle of second level domain (SLD) in SNI ext : www.sobaka.ru => aka.ru // return false if SNI ext is bad or SLD is not found static bool TLSHelloFindMiddleOfSLDInSNI(const uint8_t *ext, size_t elen, const uint8_t **p) { size_t len; if (!TLSHelloFindNLDInSNI(ext,elen,2,p,&len)) return false; // in case of one letter SLD (x.com) we split at '.' to prevent appearance of the whole SLD *p = (len==1) ? *p+1 : *p+len/2; return true; } size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz) { size_t elen; const uint8_t *ext, *p; size_t offset_host,len_host; ssize_t offset; switch(posmarker) { case PM_HOST: case PM_HOST_END: case PM_HOST_SLD: case PM_HOST_MIDSLD: case PM_HOST_ENDSLD: case PM_SNI_EXT: if (TLSFindExt(data,sz,0,&ext,&elen,false)) { if (posmarker==PM_SNI_EXT) { return CheckPos(sz,ext-data+pos); } else { if (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host)) return 0; offset_host = ext-data; return HostPos(posmarker,pos,data,sz,offset_host,len_host); } } return 0; default: return AnyProtoPos(posmarker,pos,data,sz); } } ================================================ FILE: tpws/protocol.h ================================================ #pragma once #include #include #include typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto; #define L7_PROTO_HTTP 0x00000001 #define L7_PROTO_TLS 0x00000002 #define L7_PROTO_QUIC 0x00000004 #define L7_PROTO_WIREGUARD 0x00000008 #define L7_PROTO_DHT 0x00000010 #define L7_PROTO_UNKNOWN 0x80000000 const char *l7proto_str(t_l7proto l7); bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7); // pos markers #define PM_ABS 0 #define PM_HOST 1 #define PM_HOST_END 2 #define PM_HOST_SLD 3 #define PM_HOST_MIDSLD 4 #define PM_HOST_ENDSLD 5 #define PM_HTTP_METHOD 6 #define PM_SNI_EXT 7 struct proto_pos { int16_t pos; uint8_t marker; }; #define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) bool IsHostMarker(uint8_t posmarker); const char *posmarker_name(uint8_t posmarker); size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp); void 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); extern const char *http_methods[9]; const char *HttpMethod(const uint8_t *data, size_t len); bool IsHttp(const uint8_t *data, size_t len); bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs); bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs); // header must be passed like this : "\nHost:" bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf); bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host); bool IsHttpReply(const uint8_t *data, size_t len); const char *HttpFind2ndLevelDomain(const char *host); // must be pre-checked by IsHttpReply int HttpReplyCode(const uint8_t *data, size_t len); // must be pre-checked by IsHttpReply bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host); const char *TLSVersionStr(uint16_t tlsver); uint16_t TLSRecordDataLen(const uint8_t *data); size_t TLSRecordLen(const uint8_t *data); bool IsTLSRecordFull(const uint8_t *data, size_t len); bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); ================================================ FILE: tpws/redirect.c ================================================ #include "redirect.h" #include #include #include #include #include #include #include #include "params.h" #include "helpers.h" #include "linux_compat.h" #ifdef __linux__ #include #endif #if defined(BSD) #include #include static int redirector_fd=-1; void redir_close(void) { if (redirector_fd!=-1) { close(redirector_fd); redirector_fd = -1; DBGPRINT("closed redirector\n"); } } static bool redir_open_private(const char *fname, int flags) { redir_close(); redirector_fd = open(fname, flags); if (redirector_fd < 0) { DLOG_PERROR("redir_openv_private"); return false; } DBGPRINT("opened redirector %s\n",fname); return true; } bool redir_init(void) { return params.pf_enable ? redir_open_private("/dev/pf", O_RDONLY) : true; } static bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst) { struct pfioc_natlook nl; struct sockaddr_storage asa2; if (redirector_fd==-1) return false; if (params.debug>=2) { char s[48],s2[48]; *s=0; ntop46_port(accept_sa, s, sizeof(s)); *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); DBGPRINT("destination_from_pf %s %s\n",s,s2); } saconvmapped(orig_dst); if (accept_sa->sa_family==AF_INET6 && orig_dst->ss_family==AF_INET) { memcpy(&asa2,accept_sa,sizeof(struct sockaddr_in6)); saconvmapped(&asa2); accept_sa = (struct sockaddr*)&asa2; } if (params.debug>=2) { char s[48],s2[48]; *s=0; ntop46_port(accept_sa, s, sizeof(s)); *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); DBGPRINT("destination_from_pf (saconvmapped) %s %s\n",s,s2); } if (accept_sa->sa_family!=orig_dst->ss_family) { DBGPRINT("accept_sa and orig_dst sa_family mismatch : %d %d\n", accept_sa->sa_family, orig_dst->ss_family); return false; } memset(&nl, 0, sizeof(nl)); nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; nl.af = orig_dst->ss_family; switch(orig_dst->ss_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)orig_dst; nl.daddr.v4.s_addr = sin->sin_addr.s_addr; nl.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr; #ifdef __APPLE__ nl.sxport.port = ((struct sockaddr_in*)accept_sa)->sin_port; nl.dxport.port = sin->sin_port; #else nl.sport = ((struct sockaddr_in*)accept_sa)->sin_port; nl.dport = sin->sin_port; #endif } break; case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)orig_dst; nl.daddr.v6 = sin6->sin6_addr; nl.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr; #ifdef __APPLE__ nl.sxport.port = ((struct sockaddr_in6*)accept_sa)->sin6_port; nl.dxport.port = sin6->sin6_port; #else nl.sport = ((struct sockaddr_in6*)accept_sa)->sin6_port; nl.dport = sin6->sin6_port; #endif } break; default: DBGPRINT("destination_from_pf : unexpected address family %d\n",orig_dst->ss_family); return false; } if (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0) { DLOG_PERROR("ioctl(DIOCNATLOOK) failed"); return false; } DBGPRINT("destination_from_pf : got orig dest addr from pf\n"); switch(nl.af) { case AF_INET: orig_dst->ss_family = nl.af; #ifdef __APPLE__ ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdxport.port; #else ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdport; #endif ((struct sockaddr_in*)orig_dst)->sin_addr = nl.rdaddr.v4; break; case AF_INET6: orig_dst->ss_family = nl.af; #ifdef __APPLE__ ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdxport.port; #else ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdport; #endif ((struct sockaddr_in6*)orig_dst)->sin6_addr = nl.rdaddr.v6; break; default: DBGPRINT("destination_from_pf : DIOCNATLOOK returned unexpected address family %d\n",nl.af); return false; } return true; } #else bool redir_init(void) {return true;} void redir_close(void) {}; #endif //Store the original destination address in orig_dst bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst) { char orig_dst_str[INET6_ADDRSTRLEN]; socklen_t addrlen = sizeof(*orig_dst); int r; memset(orig_dst, 0, addrlen); //For UDP transparent proxying: //Set IP_RECVORIGDSTADDR socket option for getting the original //destination of a datagram #ifdef __linux__ // DNAT r=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); if (r<0) r = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); if (r<0) { DBGPRINT("both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n"); #endif // TPROXY : socket is bound to original destination r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen); if (r<0) { DLOG_PERROR("getsockname"); return false; } if (orig_dst->ss_family==AF_INET6) ((struct sockaddr_in6*)orig_dst)->sin6_scope_id=0; // or MacOS will not connect() #ifdef BSD if (params.pf_enable && !destination_from_pf(accept_sa, orig_dst)) DBGPRINT("pf filter destination_from_pf failed\n"); #endif #ifdef __linux__ } #endif if (saconvmapped(orig_dst)) DBGPRINT("Original destination : converted ipv6 mapped address to ipv4\n"); if (params.debug) { if (orig_dst->ss_family == AF_INET) { inet_ntop(AF_INET, &(((struct sockaddr_in*) orig_dst)->sin_addr), orig_dst_str, INET_ADDRSTRLEN); VPRINT("Original destination for socket fd=%d : %s:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port)); } else if (orig_dst->ss_family == AF_INET6) { inet_ntop(AF_INET6,&(((struct sockaddr_in6*) orig_dst)->sin6_addr), orig_dst_str, INET6_ADDRSTRLEN); VPRINT("Original destination for socket fd=%d : [%s]:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port)); } } return true; } ================================================ FILE: tpws/redirect.h ================================================ #pragma once #include #include #include bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst); bool redir_init(void); void redir_close(void); ================================================ FILE: tpws/resolver.c ================================================ #define _GNU_SOURCE #include "resolver.h" #include "params.h" #include #include #include #include #include #include #include #include #include #include #define SIG_BREAK SIGUSR1 #ifdef __APPLE__ static const char *sem_name="/tpws_resolver"; #endif TAILQ_HEAD(resolve_tailhead, resolve_item); typedef struct { int fd_signal_pipe; sem_t *sem; #ifndef __APPLE__ sem_t _sem; #endif struct resolve_tailhead resolve_list; pthread_mutex_t resolve_list_lock; int threads; pthread_t *thread; bool bInit, bStop; } t_resolver; static t_resolver resolver = { .bInit = false }; #define rlist_lock pthread_mutex_lock(&resolver.resolve_list_lock) #define rlist_unlock pthread_mutex_unlock(&resolver.resolve_list_lock) static void resolver_clear_list(void) { struct resolve_item *ri; for (;;) { ri = TAILQ_FIRST(&resolver.resolve_list); if (!ri) break; TAILQ_REMOVE(&resolver.resolve_list, ri, next); free(ri); } } int resolver_thread_count(void) { return resolver.bInit ? resolver.threads : 0; } static void *resolver_thread(void *arg) { int r; sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, SIG_BREAK); //printf("resolver_thread %d start\n",syscall(SYS_gettid)); for(;;) { if (resolver.bStop) break; r = sem_wait(resolver.sem); if (resolver.bStop) break; if (r) { if (errno!=EINTR) { DLOG_PERROR("sem_wait (resolver_thread)"); break; // fatal err } } else { struct resolve_item *ri; ssize_t wr; rlist_lock; ri = TAILQ_FIRST(&resolver.resolve_list); if (ri) TAILQ_REMOVE(&resolver.resolve_list, ri, next); rlist_unlock; if (ri) { struct addrinfo *ai,hints; char sport[6]; //printf("THREAD %d GOT JOB %s\n", syscall(SYS_gettid), ri->dom); snprintf(sport,sizeof(sport),"%u",ri->port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; // unfortunately getaddrinfo cannot be interrupted with a signal. we cannot cancel a query ri->ga_res = getaddrinfo(ri->dom,sport,&hints,&ai); if (!ri->ga_res) { if (ai->ai_addrlen>sizeof(ri->ss)) { DLOG_ERR("getaddrinfo returned too large address\n"); ri->ga_res = EAI_FAIL; } else memcpy(&ri->ss, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); } //printf("THREAD %d END JOB %s FIRST=%p\n", syscall(SYS_gettid), ri->dom, TAILQ_FIRST(&resolver.resolve_list)); // never interrupt this pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); wr = write(resolver.fd_signal_pipe,&ri,sizeof(void*)); if (wr<0) { free(ri); DLOG_PERROR("write resolve_pipe"); } else if (wr!=sizeof(void*)) { // partial pointer write is FATAL. in any case it will cause pointer corruption and coredump free(ri); DLOG_ERR("write resolve_pipe : not full write\n"); exit(1000); } pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); } } } //printf("resolver_thread %d exit\n",syscall(SYS_gettid)); return NULL; } static void sigbreak(int sig) { } void resolver_deinit(void) { if (resolver.bInit) { resolver.bStop = true; // wait all threads to terminate for (int t = 0; t < resolver.threads; t++) pthread_kill(resolver.thread[t], SIGUSR1); for (int t = 0; t < resolver.threads; t++) { pthread_kill(resolver.thread[t], SIGUSR1); pthread_join(resolver.thread[t], NULL); } pthread_mutex_destroy(&resolver.resolve_list_lock); free(resolver.thread); #ifdef __APPLE__ sem_close(resolver.sem); #else sem_destroy(resolver.sem); #endif resolver_clear_list(); memset(&resolver,0,sizeof(resolver)); } } bool resolver_init(int threads, int fd_signal_pipe) { int t; struct sigaction action; if (threads<1 || resolver.bInit) return false; memset(&resolver,0,sizeof(resolver)); resolver.bInit = true; #ifdef __APPLE__ // MacOS does not support unnamed semaphores char sn[64]; snprintf(sn,sizeof(sn),"%s_%d",sem_name,getpid()); resolver.sem = sem_open(sn,O_CREAT,0600,0); if (resolver.sem==SEM_FAILED) { DLOG_PERROR("sem_open"); goto ex; } // unlink immediately to remove tails sem_unlink(sn); #else if (sem_init(&resolver._sem,0,0)==-1) { DLOG_PERROR("sem_init"); goto ex; } resolver.sem = &resolver._sem; #endif if (pthread_mutex_init(&resolver.resolve_list_lock, NULL)) goto ex; resolver.fd_signal_pipe = fd_signal_pipe; TAILQ_INIT(&resolver.resolve_list); // start as many threads as we can up to specified number resolver.thread = malloc(sizeof(pthread_t)*threads); if (!resolver.thread) goto ex; memset(&action,0,sizeof(action)); action.sa_handler = sigbreak; sigaction(SIG_BREAK, &action, NULL); pthread_attr_t attr; if (pthread_attr_init(&attr)) goto ex; // set minimum thread stack size if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>32768 ? PTHREAD_STACK_MIN : 32768)) { pthread_attr_destroy(&attr); goto ex; } for(t=0, resolver.threads=threads ; tdom,dom,sizeof(ri->dom)); ri->dom[sizeof(ri->dom)-1] = 0; ri->port = port; ri->ptr = ptr; rlist_lock; TAILQ_INSERT_TAIL(&resolver.resolve_list, ri, next); rlist_unlock; if (sem_post(resolver.sem)<0) { DLOG_PERROR("resolver_queue sem_post"); free(ri); return NULL; } return ri; } ================================================ FILE: tpws/resolver.h ================================================ #pragma once #include #include #include #include #include #include "helpers.h" struct resolve_item { char dom[256]; // request dom sockaddr_in46 ss; // resolve result int ga_res; // getaddrinfo result code uint16_t port; // request port void *ptr; TAILQ_ENTRY(resolve_item) next; }; struct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr); void resolver_deinit(void); bool resolver_init(int threads, int fd_signal_pipe); int resolver_thread_count(void); ================================================ FILE: tpws/sec.c ================================================ #define _GNU_SOURCE #include #include #include "sec.h" #include #include #include #ifdef __linux__ #include #include #include #include // __X32_SYSCALL_BIT defined in linux/unistd.h #include #include #include /************ SECCOMP ************/ // block most of the undesired syscalls to harden against code execution static long blocked_syscalls[] = { #ifdef SYS_execv SYS_execv, #endif SYS_execve, #ifdef SYS_execveat SYS_execveat, #endif #ifdef SYS_exec_with_loader SYS_exec_with_loader, #endif #ifdef SYS_osf_execve SYS_osf_execve, #endif #ifdef SYS_uselib SYS_uselib, #endif #ifdef SYS_unlink SYS_unlink, #endif SYS_unlinkat, #ifdef SYS_chmod SYS_chmod, #endif SYS_fchmod,SYS_fchmodat, #ifdef SYS_chown SYS_chown, #endif #ifdef SYS_chown32 SYS_chown32, #endif SYS_fchown, #ifdef SYS_fchown32 SYS_fchown32, #endif #ifdef SYS_lchown SYS_lchown, #endif #ifdef SYS_lchown32 SYS_lchown32, #endif SYS_fchownat, #ifdef SYS_symlink SYS_symlink, #endif SYS_symlinkat, #ifdef SYS_link SYS_link, #endif SYS_linkat, SYS_truncate, #ifdef SYS_truncate64 SYS_truncate64, #endif SYS_ftruncate, #ifdef SYS_ftruncate64 SYS_ftruncate64, #endif #ifdef SYS_mknod SYS_mknod, #endif SYS_mknodat, #ifdef SYS_mkdir SYS_mkdir, #endif SYS_mkdirat, #ifdef SYS_rmdir SYS_rmdir, #endif #ifdef SYS_rename SYS_rename, #endif #ifdef SYS_renameat2 SYS_renameat2, #endif #ifdef SYS_renameat SYS_renameat, #endif #ifdef SYS_readdir SYS_readdir, #endif #ifdef SYS_getdents SYS_getdents, #endif #ifdef SYS_getdents64 SYS_getdents64, #endif #ifdef SYS_process_vm_readv SYS_process_vm_readv, #endif #ifdef SYS_process_vm_writev SYS_process_vm_writev, #endif #ifdef SYS_process_madvise SYS_process_madvise, #endif SYS_kill, SYS_ptrace }; #define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) { filter->code = code; filter->jt = jt; filter->jf = jf; filter->k = k; } // deny all blocked syscalls static bool set_seccomp(void) { #ifdef __X32_SYSCALL_BIT #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) #else #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) #endif struct sock_filter sockf[SECCOMP_PROG_SIZE]; struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; int i,idx=0; set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); #ifdef __X32_SYSCALL_BIT // x86 only set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail #else set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); #endif /* // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr */ for(i=0 ; i= 0; } bool sec_harden(void) { bool bRes = true; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); bRes = false; } #if ARCH_NR!=0 if (!set_seccomp()) { DLOG_PERROR("seccomp"); if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n"); bRes = false; } #endif return bRes; } bool checkpcap(uint64_t caps) { if (!caps) return true; // no special caps reqd struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; struct __user_cap_data_struct cd[2]; uint32_t c0 = (uint32_t)caps; uint32_t c1 = (uint32_t)(caps>>32); return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1; } bool setpcap(uint64_t caps) { struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; struct __user_cap_data_struct cd[2]; cd[0].effective = cd[0].permitted = (uint32_t)caps; cd[0].inheritable = 0; cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32); cd[1].inheritable = 0; return !capset(&ch,cd); } int getmaxcap(void) { int maxcap = CAP_LAST_CAP; FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); if (F) { int n = fscanf(F, "%d", &maxcap); fclose(F); } return maxcap; } bool dropcaps(void) { uint64_t caps = 0; int maxcap = getmaxcap(); if (setpcap(caps|(1< #include #ifdef __linux__ #include #include #include bool checkpcap(uint64_t caps); bool setpcap(uint64_t caps); int getmaxcap(void); bool dropcaps(void); #define syscall_nr (offsetof(struct seccomp_data, nr)) #define arch_nr (offsetof(struct seccomp_data, arch)) #define syscall_arg(x) (offsetof(struct seccomp_data, args[x])) #ifndef __AUDIT_ARCH_64BIT #define __AUDIT_ARCH_64BIT 0x80000000 #endif #ifndef __AUDIT_ARCH_LE #define __AUDIT_ARCH_LE 0x40000000 #endif #ifndef EM_RISCV #define EM_RISCV 243 #endif #ifndef AUDIT_ARCH_RISCV64 #define AUDIT_ARCH_RISCV64 (EM_RISCV | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif #ifndef EM_LOONGARCH #define EM_LOONGARCH 258 #endif #ifndef AUDIT_ARCH_LOONGARCH64 #define AUDIT_ARCH_LOONGARCH64 (EM_LOONGARCH | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif #if defined(__aarch64__) # define ARCH_NR AUDIT_ARCH_AARCH64 #elif defined(__amd64__) # define ARCH_NR AUDIT_ARCH_X86_64 #elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__)) # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_ARM # else # define ARCH_NR AUDIT_ARCH_ARMEB # endif #elif defined(__i386__) # define ARCH_NR AUDIT_ARCH_I386 #elif defined(__mips__) #if _MIPS_SIM == _MIPS_SIM_ABI32 # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_MIPSEL # else # define ARCH_NR AUDIT_ARCH_MIPS # endif #elif _MIPS_SIM == _MIPS_SIM_ABI64 # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_MIPSEL64 # else # define ARCH_NR AUDIT_ARCH_MIPS64 # endif #else # error "Unsupported mips abi" #endif #elif defined(__PPC64__) # if __BYTE_ORDER == __LITTLE_ENDIAN # define ARCH_NR AUDIT_ARCH_PPC64LE # else # define ARCH_NR AUDIT_ARCH_PPC64 # endif #elif defined(__PPC__) # define ARCH_NR AUDIT_ARCH_PPC #elif __riscv && __riscv_xlen == 64 # define ARCH_NR AUDIT_ARCH_RISCV64 #elif defined(__loongarch__) && __loongarch_grlen == 64 # define ARCH_NR AUDIT_ARCH_LOONGARCH64 #elif defined(__e2k__) # define ARCH_NR AUDIT_ARCH_E2K #else # error "Platform does not support seccomp filter yet" #endif #endif bool sec_harden(void); bool can_drop_root(); bool droproot(uid_t uid, const char *user, const gid_t *gid, int gid_count); void print_id(void); void daemonize(void); bool writepid(const char *filename); ================================================ FILE: tpws/socks.h ================================================ #pragma once #include #include #pragma pack(push,1) #define S4_CMD_CONNECT 1 #define S4_CMD_BIND 2 typedef struct { uint8_t ver,cmd; uint16_t port; uint32_t ip; } s4_req; #define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4) #define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT) #define S4_REP_OK 90 #define S4_REP_FAILED 91 typedef struct { uint8_t zero,rep; uint16_t port; uint32_t ip; } s4_rep; #define S5_AUTH_NONE 0 #define S5_AUTH_GSSAPI 1 #define S5_AUTH_USERPASS 2 #define S5_AUTH_UNACCEPTABLE 0xFF typedef struct { uint8_t ver,nmethods,methods[255]; } s5_handshake; #define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods)) typedef struct { uint8_t ver,method; } s5_handshake_ack; #define S5_CMD_CONNECT 1 #define S5_CMD_BIND 2 #define S5_CMD_UDP_ASSOC 3 #define S5_ATYP_IP4 1 #define S5_ATYP_DOM 3 #define S5_ATYP_IP6 4 typedef struct { uint8_t ver,cmd,rsv,atyp; union { struct { struct in_addr addr; uint16_t port; } d4; struct { struct in6_addr addr; uint16_t port; } d6; struct { uint8_t len; char domport[255+2]; // max hostname + binary port } dd; }; } s5_req; #define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5) #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)))) #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)))) #define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0) #define S5_REP_OK 0 #define S5_REP_GENERAL_FAILURE 1 #define S5_REP_NOT_ALLOWED_BY_RULESET 2 #define S5_REP_NETWORK_UNREACHABLE 3 #define S5_REP_HOST_UNREACHABLE 4 #define S5_REP_CONN_REFUSED 5 #define S5_REP_TTL_EXPIRED 6 #define S5_REP_COMMAND_NOT_SUPPORTED 7 #define S5_REP_ADDR_TYPE_NOT_SUPPORTED 8 typedef struct { uint8_t ver,rep,rsv,atyp; union { struct { struct in_addr addr; uint16_t port; } d4; }; } s5_rep; #pragma pack(pop) ================================================ FILE: tpws/tamper.c ================================================ #define _GNU_SOURCE #include #include #include "tamper.h" #include "hostlist.h" #include "ipset.h" #include "protocol.h" #include "helpers.h" #include "pools.h" #define PKTDATA_MAXDUMP 32 void packet_debug(const uint8_t *data, size_t sz) { hexdump_limited_dlog(data, sz, PKTDATA_MAXDUMP); VPRINT("\n"); } static void TLSDebugHandshake(const uint8_t *tls,size_t sz) { if (!params.debug) return; if (sz<6) return; const uint8_t *ext; size_t len,len2; uint16_t v_handshake=pntoh16(tls+4), v, v2; VPRINT("TLS handshake version : %s\n",TLSVersionStr(v_handshake)); if (TLSFindExtInHandshake(tls,sz,43,&ext,&len,false)) { if (len) { len2 = ext[0]; if (len2=2) { len2 = pntoh16(ext); if (len2<=(len-2)) { char s[32]; for(ext+=2; len2 ;) { v = *ext; ext++; len2--; if (v<=len2) { v2 = vhostname || strcmp(ipc->hostname,hostname)) { free(ipc->hostname); if (!(ipc->hostname = strdup(hostname))) { DLOG_ERR("ipcache_put_hostname: out of memory\n"); return false; } ipc->hostname_is_ip = hostname_is_ip; VPRINT("hostname cached (is_ip=%u): %s\n", hostname_is_ip, hostname); } return true; } static 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) { if (!params.cache_hostname) { *hostname = 0; return true; } ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache,a4,a6); if (!ipc) { DLOG_ERR("ipcache_get_hostname: out of memory\n"); return false; } if (ipc->hostname) { VPRINT("got cached hostname (is_ip=%u): %s\n", ipc->hostname_is_ip, ipc->hostname); snprintf(hostname,hostname_buf_len,"%s",ipc->hostname); if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip; } else *hostname = 0; return true; } static bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto) { bool bHostlistsEmpty; if (!HostlistsReloadCheckForProfile(dp)) return false; if ((dest->sa_family==AF_INET && !dp->filter_ipv4) || (dest->sa_family==AF_INET6 && !dp->filter_ipv6)) // L3 filter does not match return false; if (!port_filters_in_range(&dp->pf_tcp,saport(dest))) // L4 filter does not match return false; if (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) // L7 filter does not match return false; bHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp); if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty) // avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown. return false; if (!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)) // target ip does not match return false; // autohostlist profile matching l3/l4/l7 filter always win if (dp->hostlist_auto) return true; if (bHostlistsEmpty) // profile without hostlist filter wins return true; else if (hostname) // if hostlists are present profile matches only if hostname is known and satisfy profile hostlists return HostlistCheck(dp, hostname, bNoSubdom, NULL, true); return false; } static struct desync_profile *dp_find(struct desync_profile_list_head *head, const struct sockaddr *dest, const char *hostname, bool bNoSubdom, t_l7proto l7proto) { struct desync_profile_list *dpl; if (params.debug) { char ip_port[48]; ntop46_port(dest, ip_port,sizeof(ip_port)); VPRINT("desync profile search for tcp target=%s l7proto=%s hostname='%s'\n", ip_port, l7proto_str(l7proto), hostname ? hostname : ""); } LIST_FOREACH(dpl, head, next) { if (dp_match(&dpl->dp,dest,hostname,bNoSubdom,l7proto)) { VPRINT("desync profile %d matches\n",dpl->dp.n); return &dpl->dp; } } VPRINT("desync profile not found\n"); return NULL; } void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) { ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); if (!ctrack->hostname) { char host[256]; if (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) if (!(ctrack->hostname=strdup(host))) DLOG_ERR("hostname dup : out of memory"); } ctrack->dp = dp_find(¶ms.desync_profiles, dest, ctrack->hostname, ctrack->hostname_is_ip, ctrack->l7proto); } // segment buffer has at least 5 extra bytes to extend data block void 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) { uint8_t *p, *pp, *pHost = NULL; size_t method_len = 0, pos, tpos, orig_size=*size; const char *method; bool bHaveHost = false, bHostIsIp = false; char *pc, Host[256]; t_l7proto l7proto; DBGPRINT("tamper_out\n"); if (!ctrack->dp) return; if (params.debug) { char ip_port[48]; ntop46_port(dest,ip_port,sizeof(ip_port)); VPRINT("tampering tcp segment with size %zu to %s\n", *size, ip_port); if (ctrack->dp) VPRINT("using cached desync profile %d\n",ctrack->dp->n); if (ctrack->hostname) VPRINT("connection hostname: %s\n", ctrack->hostname); } if (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6) { DLOG_ERR("tamper_out dest family unknown\n"); return; } if (multisplit_count) *multisplit_count=0; if (split_flags) *split_flags=0; if ((method = HttpMethod(segment,*size))) { method_len = strlen(method)-2; VPRINT("Data block looks like http request start : %s\n", method); l7proto=HTTP; if (HttpFindHost(&pHost,segment,*size)) { p = pHost + 5; while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++; pp = p; while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++; memcpy(Host, p, pp - p); Host[pp - p] = '\0'; bHaveHost = true; for(pc = Host; *pc; pc++) *pc=tolower(*pc); } } else if (IsTLSClientHello(segment,*size,false)) { VPRINT("Data block contains TLS ClientHello\n"); l7proto=TLS; TLSDebug(segment,*size); bHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false); } else { VPRINT("Data block contains unknown payload\n"); l7proto = UNKNOWN; } if (bHaveHost) { bHostIsIp = strip_host_to_ip(Host); VPRINT("request hostname: %s\n", Host); if (!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)) DLOG_ERR("ipcache_put_hostname: out of memory"); } bool bDiscoveredL7 = ctrack->l7proto==UNKNOWN && l7proto!=UNKNOWN; if (bDiscoveredL7) { VPRINT("discovered l7 protocol\n"); ctrack->l7proto=l7proto; } bool bDiscoveredHostname = bHaveHost && !ctrack->hostname_discovered; if (bDiscoveredHostname) { VPRINT("discovered hostname\n"); free(ctrack->hostname); if (!(ctrack->hostname=strdup(Host))) { DLOG_ERR("strdup hostname : out of memory\n"); return; } ctrack->hostname_is_ip = bHostIsIp; ctrack->hostname_discovered = true; } if (bDiscoveredL7 || bDiscoveredHostname) { struct desync_profile *dp_prev = ctrack->dp; apply_desync_profile(ctrack, dest); if (ctrack->dp!=dp_prev) { VPRINT("desync profile changed by revealed l7 protocol or hostname !\n"); ctrack->b_host_checked = ctrack->b_ah_check = false; } } if (l7proto!=UNKNOWN && ctrack->dp->hostlist_auto) { if (bHaveHost && !ctrack->b_host_checked) { bool bHostExcluded; ctrack->b_host_matches = HostlistCheck(ctrack->dp, Host, bHostIsIp, &bHostExcluded, false); ctrack->b_host_checked = true; if (!ctrack->b_host_matches) ctrack->b_ah_check = !bHostExcluded; } if (!ctrack->b_host_matches) { VPRINT("Not acting on this request\n"); return; } } switch(l7proto) { case HTTP: if (ctrack->dp->unixeol) { p = pp = segment; while ((p = memmem(p, segment + *size - p, "\r\n", 2))) { *p = '\n'; p++; memmove(p, p + 1, segment + *size - p - 1); (*size)--; if (pp == (p - 1)) { // probably end of http headers VPRINT("Found double EOL at pos %td. Stop replacing.\n", pp - segment); break; } pp = p; } pHost = NULL; // invalidate } if (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size) { VPRINT("Adding EOL before method\n"); if (ctrack->dp->unixeol) { memmove(segment + 1, segment, *size); (*size)++;; segment[0] = '\n'; } else { memmove(segment + 2, segment, *size); *size += 2; segment[0] = '\r'; segment[1] = '\n'; } pHost = NULL; // invalidate } if (ctrack->dp->methodspace && *sizedp->hostdot || ctrack->dp->hosttab) && *sizedp->hostdot ? "dot" : "tab", pos); memmove(p + 1, p, *size - pos); *p = ctrack->dp->hostdot ? '.' : '\t'; // insert dot or tab (*size)++; // block will grow by 1 byte } } if (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size)) { p = pHost + 5; pos = p - segment; VPRINT("Mixing domain case at pos %zu\n",pos); for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++) *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); } if (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ') { p = pHost + 6; pos = p - segment; VPRINT("Removing space before host name at pos %zu\n", pos); memmove(p - 1, p, *size - pos); (*size)--; // block will shrink by 1 byte } if (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size)) { VPRINT("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); memcpy(pHost, ctrack->dp->hostspell, 4); } if (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size)) { // add : XXXXX: dp->unixeol ? 8 : 9; size_t hostpad = ctrack->dp->hostpaddp->hostpad; if ((hsize+*size)>segment_buffer_size) VPRINT("could not add host padding : buffer too small\n"); else { if ((hostpad+*size)>segment_buffer_size) { hostpad=segment_buffer_size-*size; VPRINT("host padding reduced to %zu bytes : buffer too small\n", hostpad); } else VPRINT("host padding with %zu bytes\n", hostpad); p = pHost; pos = p - segment; memmove(p + hostpad, p, *size - pos); (*size) += hostpad; while(hostpad) { #define MAX_HDR_SIZE 2048 size_t padsize = hostpad > hsize ? hostpad-hsize : 0; if (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE; // if next header would be too small then add extra padding to the current one if ((hostpad-padsize-hsize)dp->unixeol) *p++='\n'; else { *p++='\r'; *p++='\n'; } hostpad-=hsize+padsize; } pHost = NULL; // invalidate } } if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); if (split_flags) { if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB; } break; case TLS: if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); if ((5+*size)<=segment_buffer_size) { tpos = ResolvePos(segment, *size, l7proto, &ctrack->dp->tlsrec); if (tpos>5) { // construct 2 TLS records from one uint16_t l = pntoh16(segment+3); // length if (l>=2) { int i; size_t dlen; // length is checked in IsTLSClientHello and cannot exceed buffer size if ((tpos-5)>=l) tpos=5+1; VPRINT("making 2 TLS records at pos %zu\n",tpos); memmove(segment+tpos+5,segment+tpos,*size-tpos); segment[tpos] = segment[0]; segment[tpos+1] = segment[1]; segment[tpos+2] = segment[2]; phton16(segment+tpos+3,l-(tpos-5)); phton16(segment+3,tpos-5); *size += 5; VPRINT("-2nd TLS record: "); dlen = tpos<16 ? tpos : 16; packet_debug(segment+tpos-dlen,dlen); VPRINT("+2nd TLS record: "); packet_debug(segment+tpos,*size-tpos); // fix split positions after tlsrec. increase split pos by tlsrec header size (5 bytes) if (multisplit_pos) for(i=0;i<*multisplit_count;i++) if (multisplit_pos[i]>tpos) multisplit_pos[i]+=5; } } } if (split_flags) { if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB; } break; default: if (multisplit_pos && ctrack->dp->split_any_protocol) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); } if (split_flags) { if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER; if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB; } if (orig_size!=*size) { VPRINT("segment size changed: %zu -> %zu\n", orig_size, *size); } if (params.debug && multisplit_count && *multisplit_count) { VPRINT("multisplit pos: "); for (int i=0;i<*multisplit_count;i++) VPRINT("%zu ",multisplit_pos[i]); VPRINT("\n"); } } static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto) { if (hostname) { hostfail_pool *fail_counter; fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (fail_counter) { HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); VPRINT("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : fail counter reset. website is working.", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); } } } static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto) { hostfail_pool *fail_counter; fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (!fail_counter) { fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); if (!fail_counter) { DLOG_ERR("HostFailPoolAdd: out of memory\n"); return; } } fail_counter->counter++; VPRINT("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); HOSTLIST_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); if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) { VPRINT("auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\n", dp->n , hostname); HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); VPRINT("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); bool bExcluded=false; if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded) { VPRINT("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename); HOSTLIST_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); if (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0)) { DLOG_ERR("StrPoolAddStr out of memory\n"); return; } if (!append_to_list_file(dp->hostlist_auto->filename, hostname)) { DLOG_PERROR("write to auto hostlist:"); return; } if (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig)) DLOG_PERROR("file_mod_signature"); } else { VPRINT("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : NOT adding, duplicate detected", hostname, dp->n, client_ip_port, l7proto_str(l7proto)); } } } void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size) { DBGPRINT("tamper_in hostname=%s\n", ctrack->hostname); bool bFail=false; char client_ip_port[48]; if (*params.hostlist_auto_debuglog) ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); else *client_ip_port=0; if (ctrack->dp && ctrack->b_ah_check) { HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); if (ctrack->l7proto==HTTP && ctrack->hostname) { if (IsHttpReply(segment,*size)) { VPRINT("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname); if (bFail) { VPRINT("redirect to another domain detected. possibly DPI redirect.\n"); HOSTLIST_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)); } else VPRINT("local or in-domain redirect detected. it's not a DPI redirect.\n"); } else { // received not http reply. do not monitor this connection anymore VPRINT("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); } if (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); } if (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, ctrack->l7proto); } ctrack->bTamperInCutoff = true; } void rst_in(t_ctrack *ctrack, const struct sockaddr *client) { DBGPRINT("rst_in hostname=%s\n", ctrack->hostname); char client_ip_port[48]; if (*params.hostlist_auto_debuglog) ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); else *client_ip_port=0; if (ctrack->dp && ctrack->b_ah_check) { HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); if (!ctrack->bTamperInCutoff && ctrack->hostname) { VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname); HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client %s : proto %s : incoming RST", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(ctrack->l7proto)); auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); } } } void hup_out(t_ctrack *ctrack, const struct sockaddr *client) { DBGPRINT("hup_out hostname=%s\n", ctrack->hostname); char client_ip_port[48]; if (*params.hostlist_auto_debuglog) ntop46_port((struct sockaddr*)client,client_ip_port,sizeof(client_ip_port)); else *client_ip_port=0; if (ctrack->dp && ctrack->b_ah_check) { HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); if (!ctrack->bTamperInCutoff && ctrack->hostname) { // local leg dropped connection after first request. probably due to timeout. VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname); HOSTLIST_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)); auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto); } } } ================================================ FILE: tpws/tamper.h ================================================ #pragma once #include #include #include #include "params.h" #define SPLIT_FLAG_DISORDER 0x01 #define SPLIT_FLAG_OOB 0x02 typedef struct { // common state t_l7proto l7proto; bool bTamperInCutoff; bool b_host_checked,b_host_matches,b_ah_check; bool hostname_discovered; bool hostname_is_ip; char *hostname; struct desync_profile *dp; // desync profile cache } t_ctrack; void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest); bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip); void 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); void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size); // connection reset by remote leg void rst_in(t_ctrack *ctrack, const struct sockaddr *client); // local leg closed connection (timeout waiting response ?) void hup_out(t_ctrack *ctrack, const struct sockaddr *client); void packet_debug(const uint8_t *data, size_t sz); ================================================ FILE: tpws/tpws.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __ANDROID__ #include "andr/ifaddrs.h" #else #include #endif #include "tpws.h" #ifdef BSD #include #endif #include "tpws_conn.h" #include "hostlist.h" #include "ipset.h" #include "params.h" #include "sec.h" #include "redirect.h" #include "helpers.h" #include "gzip.h" #include "pools.h" #define MAX_CONFIG_FILE_SIZE 16384 struct params_s params; static bool bReload=false; static void onhup(int sig) { printf("HUP received ! Lists will be reloaded.\n"); bReload=true; } void ReloadCheck() { if (bReload) { ResetAllHostlistsModTime(); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed. this is fatal.\n"); exit(1); } ResetAllIpsetModTime(); if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed. this is fatal.\n"); exit(1); } bReload=false; } } static void onusr2(int sig) { printf("\nHOSTFAIL POOL DUMP\n"); struct desync_profile_list *dpl; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { printf("\nDESYNC PROFILE %d\n",dpl->dp.n); HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); } if (params.cache_hostname) { printf("\nIPCACHE\n"); ipcachePrint(¶ms.ipcache); } printf("\n"); } static int8_t block_sigpipe(void) { sigset_t sigset; memset(&sigset, 0, sizeof(sigset)); //Get the old sigset, add SIGPIPE and update sigset if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) { DLOG_PERROR("sigprocmask (get)"); return -1; } if (sigaddset(&sigset, SIGPIPE) == -1) { DLOG_PERROR("sigaddset"); return -1; } if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) { DLOG_PERROR("sigprocmask (set)"); return -1; } return 0; } static bool test_list_files() { struct hostlist_file *hfile; struct ipset_file *ifile; LIST_FOREACH(hfile, ¶ms.hostlists, next) if (hfile->filename && !file_open_test(hfile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access hostlist file '%s'\n",hfile->filename); return false; } LIST_FOREACH(ifile, ¶ms.ipsets, next) if (ifile->filename && !file_open_test(ifile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access ipset file '%s'\n",ifile->filename); return false; } return true; } static bool is_interface_online(const char *ifname) { struct ifreq ifr; int sock; if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1) return false; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ-1] = 0; ioctl(sock, SIOCGIFFLAGS, &ifr); close(sock); return !!(ifr.ifr_flags & IFF_UP); } static int get_default_ttl(void) { int sock,ttl=0; socklen_t optlen=sizeof(ttl); if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))!=-1) { getsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, &optlen); close(sock); } return ttl; } static void exithelp(void) { printf( #if !defined( __OpenBSD__) && !defined(__ANDROID__) " @|$\t\t; read file for options. must be the only argument. other options are ignored.\n\n" #endif " --bind-addr=|\t; for v6 link locals append %%interface_name\n" " --bind-iface4=\t\t; bind to the first ipv4 addr of interface\n" " --bind-iface6=\t\t; bind to the first ipv6 addr of interface\n" " --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\n" " --bind-wait-ifup=\t\t\t; wait for interface to appear and up\n" " --bind-wait-ip=\t\t\t; after ifup wait for ip address to appear up to N seconds\n" " --bind-wait-ip-linklocal=\t\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n" " --bind-wait-only\t\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n" " * multiple binds are supported. each bind-addr, bind-iface* start new bind\n" " --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name\n" " --port=\t\t\t\t; only one port number for all binds is supported\n" " --socks\t\t\t\t; implement socks4/5 proxy instead of transparent proxy\n" " --no-resolve\t\t\t\t; disable socks5 remote dns ability\n" " --resolver-threads=\t\t; number of resolver worker threads\n" " --local-rcvbuf=\n" " --local-sndbuf=\n" " --remote-rcvbuf=\n" " --remote-sndbuf=\n" #ifdef SPLICE_PRESENT " --nosplice\t\t\t\t; do not use splice to transfer data between sockets\n" #endif " --skip-nodelay\t\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n" #if defined(__linux__) || defined(__APPLE__) " --local-tcp-user-timeout=\t; set tcp user timeout for local leg (default : %d, 0 = system default)\n" " --remote-tcp-user-timeout=\t; set tcp user timeout for remote leg (default : %d, 0 = system default)\n" #endif " --maxconn=\n" #ifdef SPLICE_PRESENT " --maxfiles=\t\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n" #else " --maxfiles=\t\t; should be at least (connections*2+16)\n" #endif " --max-orphan-time=\t\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n" " --daemon\t\t\t\t; daemonize\n" " --pidfile=\t\t\t; write pid to file\n" " --user=\t\t\t; drop root privs\n" " --uid=uid[:gid1,gid2,...]\t\t; drop root privs\n" #if defined(__FreeBSD__) || defined(__OpenBSD__) " --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n" #endif #if defined(__linux__) " --fix-seg=\t\t\t; fix segmentation failures at the cost of possible slowdown. wait up to N msec (default %u)\n" #endif " --ipcache-lifetime=\t\t; time in seconds to keep cached domain name (default %u). 0 = no expiration\n" " --ipcache-hostname=[0|1]\t\t; 1 or no argument enables ip->hostname caching\n" #ifdef __ANDROID__ " --debug=0|1|2|syslog|android|@ ; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" #else " --debug=0|1|2|syslog|@\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" #endif " --debug-level=0|1|2\t\t\t; specify debug level\n" " --dry-run\t\t\t\t; verify parameters and exit with code 0 if successful\n" " --version\t\t\t\t; print version and exit\n" " --comment=any_text\n" "\nMULTI-STRATEGY:\n" " --new\t\t\t\t\t; begin new strategy\n" " --skip\t\t\t\t\t; do not use this strategy\n" " --filter-l3=ipv4|ipv6\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --filter-tcp=[~]port1[-port2]|*\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\n" " --filter-l7=[http|tls|unknown]\t\t; L6-L7 protocol filter. multiple comma separated values allowed.\n" " --ipset=\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-ip=\t\t\t; comma separated fixed subnet list\n" " --ipset-exclude=\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-exclude-ip=\t\t; comma separated fixed subnet list\n" "\nHOSTLIST FILTER:\n" " --hostlist=\t\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-domains=\t; comma separated fixed domain list\n" " --hostlist-exclude=\t\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-exclude-domains= ; comma separated fixed domain list\n" " --hostlist-auto=\t\t; detect DPI blocks and build hostlist automatically\n" " --hostlist-auto-fail-threshold=\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" " --hostlist-auto-fail-time=\t; all failed attemps must be within these seconds (default : %d)\n" " --hostlist-auto-debug=\t; debug auto hostlist positives\n" "\nTAMPER:\n" " --split-pos=N|-N|marker+N|marker-N\t; comma separated list of split positions\n" "\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n" " --split-any-protocol\t\t\t; split not only http and TLS\n" #if defined(BSD) && !defined(__APPLE__) " --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" #else " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first\n" #endif " --oob[=http|tls]\t\t\t; when splitting send out of band byte. default is HEX 0x00.\n" " --oob-data=|0xHEX\t\t; override default 0x00 OOB byte.\n" " --hostcase\t\t\t\t; change Host: => host:\n" " --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" " --hostdot\t\t\t\t; add \".\" after Host: name\n" " --hosttab\t\t\t\t; add tab after Host: name\n" " --hostnospace\t\t\t\t; remove space after Host:\n" " --hostpad=\t\t\t; add dummy padding headers before Host:\n" " --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n" " --methodspace\t\t\t\t; add extra space after method\n" " --methodeol\t\t\t\t; add end-of-line before method\n" " --unixeol\t\t\t\t; replace 0D0A to 0A\n" " --tlsrec=N|-N|marker+N|marker-N\t; make 2 TLS records. split records at specified position.\n" #ifdef __linux__ " --mss=\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n" #endif " --tamper-start=[n]\t\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\n" " --tamper-cutoff=[n]\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n", #if defined(__linux__) || defined(__APPLE__) DEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE, #endif #ifdef __linux__ FIX_SEG_DEFAULT_MAX_WAIT, #endif IPCACHE_LIFETIME, HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT ); exit(1); } static void exithelp_clean(void) { cleanup_params(¶ms); exithelp(); } static void exit_clean(int code) { cleanup_params(¶ms); exit(code); } static void nextbind_clean(void) { params.binds_last++; if (params.binds_last>=MAX_BINDS) { DLOG_ERR("maximum of %d binds are supported\n",MAX_BINDS); exit_clean(1); } } static void checkbind_clean(void) { if (params.binds_last<0) { DLOG_ERR("start new bind with --bind-addr,--bind-iface*\n"); exit_clean(1); } } void save_default_ttl(void) { if (!params.ttl_default) { params.ttl_default = get_default_ttl(); if (!params.ttl_default) { DLOG_ERR("could not get default ttl\n"); exit_clean(1); } } } static bool parse_httpreqpos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "method")) { sp->marker = PM_HTTP_METHOD; sp->pos=2; } else if (!strcmp(s, "host")) { sp->marker = PM_HOST; sp->pos=1; } else return false; return true; } static bool parse_tlspos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "sni")) { sp->marker = PM_HOST; sp->pos=1; } else if (!strcmp(s, "sniext")) { sp->marker = PM_SNI_EXT; sp->pos=1; } else if (!strcmp(s, "snisld")) { sp->marker = PM_HOST_MIDSLD; sp->pos=0; } else return false; return true; } static bool parse_int16(const char *p, int16_t *v) { if (*p=='+' || *p=='-' || *p>='0' && *p<='9') { int i = atoi(p); *v = (int16_t)i; return *v==i; // check overflow } return false; } static bool parse_posmarker(const char *opt, uint8_t *posmarker) { if (!strcmp(opt,"host")) *posmarker = PM_HOST; else if (!strcmp(opt,"endhost")) *posmarker = PM_HOST_END; else if (!strcmp(opt,"sld")) *posmarker = PM_HOST_SLD; else if (!strcmp(opt,"midsld")) *posmarker = PM_HOST_MIDSLD; else if (!strcmp(opt,"endsld")) *posmarker = PM_HOST_ENDSLD; else if (!strcmp(opt,"method")) *posmarker = PM_HTTP_METHOD; else if (!strcmp(opt,"sniext")) *posmarker = PM_SNI_EXT; else return false; return true; } static bool parse_split_pos(char *opt, struct proto_pos *split) { if (parse_int16(opt,&split->pos)) { split->marker = PM_ABS; return !!split->pos; } else { char c,*p=opt; bool b; for (; *opt && *opt!='+' && *opt!='-'; opt++); c=*opt; *opt=0; b=parse_posmarker(p,&split->marker); *opt=c; if (!b) return false; if (*opt) return parse_int16(opt,&split->pos); else split->pos = 0; } return true; } static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count) { char c,*e,*p; for (p=opt, *split_count=0 ; p && *split_countdp; for(x=0;xsplit_count;x++) VPRINT("profile %d multisplit %s %d\n",dp->n,posmarker_name(dp->splits[x].marker),dp->splits[x].pos); if (!PROTO_POS_EMPTY(&dp->tlsrec)) VPRINT("profile %d tlsrec %s %d\n",dp->n,posmarker_name(dp->tlsrec.marker),dp->tlsrec.pos); } } static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) { char *e,*p,c; for (p=opt,*ipv4=*ipv6=false ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } if (!strcmp(p,"ipv4")) *ipv4 = true; else if (!strcmp(p,"ipv6")) *ipv6 = true; else return false; if (e) *e++=c; p = e; } return true; } static bool parse_l7_list(char *opt, uint32_t *l7) { char *e,*p,c; for (p=opt,*l7=0 ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } if (!strcmp(p,"http")) *l7 |= L7_PROTO_HTTP; else if (!strcmp(p,"tls")) *l7 |= L7_PROTO_TLS; else if (!strcmp(p,"unknown")) *l7 |= L7_PROTO_UNKNOWN; else return false; if (e) *e++=c; p = e; } return true; } static bool parse_pf_list(char *opt, struct port_filters_head *pfl) { char *e,*p,c; port_filter pf; bool b; for (p=opt ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } b = pf_parse(p,&pf) && port_filter_add(pfl,&pf); if (e) *e++=c; if (!b) return false; p = e; } return true; } static bool parse_domain_list(char *opt, hostlist_pool **pp) { char *e,*p,c; for (p=opt ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } if (*p && !AppendHostlistItem(pp,p)) return false; if (e) *e++=c; p = e; } return true; } static bool parse_ip_list(char *opt, ipset *pp) { char *e,*p,c; for (p=opt ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } if (*p && !AppendIpsetItem(pp,p)) return false; if (e) *e++=c; p = e; } return true; } static bool parse_uid(const char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids) { unsigned int u; char c, *p, *e; *gid_count=0; if ((e = strchr(optarg,':'))) *e++=0; if (sscanf(opt,"%u",&u)!=1) return false; *uid = (uid_t)u; for (p=e ; p ; ) { if ((e = strchr(p,','))) { c=*e; *e=0; } if (p) { if (sscanf(p,"%u",&u)!=1) return false; if (*gid_count>=max_gids) return false; gid[(*gid_count)++] = (gid_t)u; } if (e) *e++=c; p = e; } return true; } #if !defined( __OpenBSD__) && !defined(__ANDROID__) // no static to not allow optimizer to inline this func (save stack) void config_from_file(const char *filename) { // config from a file char buf[MAX_CONFIG_FILE_SIZE]; buf[0]='x'; // fake argv[0] buf[1]=' '; size_t bufsize=sizeof(buf)-3; if (!load_file(filename,buf+2,&bufsize)) { DLOG_ERR("could not load config file '%s'\n",filename); exit_clean(1); } buf[bufsize+2]=0; // wordexp fails if it sees \t \n \r between args replace_char(buf,'\n',' '); replace_char(buf,'\r',' '); replace_char(buf,'\t',' '); if (wordexp(buf, ¶ms.wexp, WRDE_NOCMD)) { DLOG_ERR("failed to split command line options from file '%s'\n",filename); exit_clean(1); } } #endif #ifndef __linux__ static bool check_oob_disorder(const struct desync_profile *dp) { return !( dp->oob && (dp->disorder || dp->disorder_http || dp->disorder_tls) || dp->oob_http && (dp->disorder || dp->disorder_http) || dp->oob_tls && (dp->disorder || dp->disorder_tls)); } #endif enum opt_indices { IDX_HELP, IDX_H, IDX_BIND_ADDR, IDX_BIND_IFACE4, IDX_BIND_IFACE6, IDX_BIND_LINKLOCAL, IDX_BIND_WAIT_IFUP, IDX_BIND_WAIT_IP, IDX_BIND_WAIT_IP_LINKLOCAL, IDX_BIND_WAIT_ONLY, IDX_PORT, IDX_DAEMON, IDX_USER, IDX_UID, IDX_MAXCONN, IDX_MAXFILES, IDX_MAX_ORPHAN_TIME, IDX_IPCACHE_LIFETIME, IDX_IPCACHE_HOSTNAME, IDX_HOSTCASE, IDX_HOSTSPELL, IDX_HOSTDOT, IDX_HOSTNOSPACE, IDX_HOSTPAD, IDX_DOMCASE, IDX_SPLIT_HTTP_REQ, IDX_SPLIT_TLS, IDX_SPLIT_POS, IDX_SPLIT_ANY_PROTOCOL, IDX_DISORDER, IDX_OOB, IDX_OOB_DATA, IDX_METHODSPACE, IDX_METHODEOL, IDX_HOSTTAB, IDX_UNIXEOL, IDX_TLSREC, IDX_TLSREC_POS, IDX_HOSTLIST, IDX_HOSTLIST_DOMAINS, IDX_HOSTLIST_EXCLUDE, IDX_HOSTLIST_EXCLUDE_DOMAINS, IDX_HOSTLIST_AUTO, IDX_HOSTLIST_AUTO_FAIL_THRESHOLD, IDX_HOSTLIST_AUTO_FAIL_TIME, IDX_HOSTLIST_AUTO_DEBUG, IDX_PIDFILE, IDX_DEBUG, IDX_DEBUG_LEVEL, IDX_DRY_RUN, IDX_VERSION, IDX_COMMENT, IDX_LOCAL_RCVBUF, IDX_LOCAL_SNDBUF, IDX_REMOTE_RCVBUF, IDX_REMOTE_SNDBUF, IDX_SOCKS, IDX_NO_RESOLVE, IDX_RESOLVER_THREADS, IDX_SKIP_NODELAY, IDX_TAMPER_START, IDX_TAMPER_CUTOFF, IDX_CONNECT_BIND_ADDR, IDX_NEW, IDX_SKIP, IDX_FILTER_L3, IDX_FILTER_TCP, IDX_FILTER_L7, IDX_IPSET, IDX_IPSET_IP, IDX_IPSET_EXCLUDE, IDX_IPSET_EXCLUDE_IP, #if defined(__FreeBSD__) || defined(__OpenBSD__) IDX_ENABLE_PF, #elif defined(__APPLE__) IDX_LOCAL_TCP_USER_TIMEOUT, IDX_REMOTE_TCP_USER_TIMEOUT, #elif defined(__linux__) IDX_LOCAL_TCP_USER_TIMEOUT, IDX_REMOTE_TCP_USER_TIMEOUT, IDX_MSS, IDX_FIX_SEG, #ifdef SPLICE_PRESENT IDX_NOSPLICE, #endif #endif IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, // ignored. for nfqws command line compatibility IDX_LAST, }; static const struct option long_options[] = { [IDX_HELP] = {"help", no_argument, 0, 0}, [IDX_H] = {"h", no_argument, 0, 0}, [IDX_BIND_ADDR] = {"bind-addr", required_argument, 0, 0}, [IDX_BIND_IFACE4] = {"bind-iface4", required_argument, 0, 0}, [IDX_BIND_IFACE6] = {"bind-iface6", required_argument, 0, 0}, [IDX_BIND_LINKLOCAL] = {"bind-linklocal", required_argument, 0, 0}, [IDX_BIND_WAIT_IFUP] = {"bind-wait-ifup", required_argument, 0, 0}, [IDX_BIND_WAIT_IP] = {"bind-wait-ip", required_argument, 0, 0}, [IDX_BIND_WAIT_IP_LINKLOCAL] = {"bind-wait-ip-linklocal", required_argument, 0, 0}, [IDX_BIND_WAIT_ONLY] = {"bind-wait-only", no_argument, 0, 0}, [IDX_PORT] = {"port", required_argument, 0, 0}, [IDX_DAEMON] = {"daemon", no_argument, 0, 0}, [IDX_USER] = {"user", required_argument, 0, 0}, [IDX_UID] = {"uid", required_argument, 0, 0}, [IDX_MAXCONN] = {"maxconn", required_argument, 0, 0}, [IDX_MAXFILES] = {"maxfiles", required_argument, 0, 0}, [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, [IDX_MAX_ORPHAN_TIME] = {"max-orphan-time", required_argument, 0, 0}, [IDX_HOSTCASE] = {"hostcase", no_argument, 0, 0}, [IDX_HOSTSPELL] = {"hostspell", required_argument, 0, 0}, [IDX_HOSTDOT] = {"hostdot", no_argument, 0, 0}, [IDX_HOSTNOSPACE] = {"hostnospace", no_argument, 0, 0}, [IDX_HOSTPAD] = {"hostpad", required_argument, 0, 0}, [IDX_DOMCASE] = {"domcase", no_argument, 0, 0}, [IDX_SPLIT_HTTP_REQ] = {"split-http-req", required_argument, 0, 0}, [IDX_SPLIT_TLS] = {"split-tls", required_argument, 0, 0}, [IDX_SPLIT_POS] = {"split-pos", required_argument, 0, 0}, [IDX_SPLIT_ANY_PROTOCOL] = {"split-any-protocol", optional_argument, 0, 0}, [IDX_DISORDER] = {"disorder", optional_argument, 0, 0}, [IDX_OOB] = {"oob", optional_argument, 0, 0}, [IDX_OOB_DATA] = {"oob-data", required_argument, 0, 0}, [IDX_METHODSPACE] = {"methodspace", no_argument, 0, 0}, [IDX_METHODEOL] = {"methodeol", no_argument, 0, 0}, [IDX_HOSTTAB] = {"hosttab", no_argument, 0, 0}, [IDX_UNIXEOL] = {"unixeol", no_argument, 0, 0}, [IDX_TLSREC] = {"tlsrec", required_argument, 0, 0}, [IDX_TLSREC_POS] = {"tlsrec-pos", required_argument, 0, 0}, [IDX_HOSTLIST] = {"hostlist", required_argument, 0, 0}, [IDX_HOSTLIST_DOMAINS] = {"hostlist-domains", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE] = {"hostlist-exclude", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE_DOMAINS] = {"hostlist-exclude-domains", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO] = {"hostlist-auto", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0}, [IDX_PIDFILE] = {"pidfile", required_argument, 0, 0}, [IDX_DEBUG] = {"debug", optional_argument, 0, 0}, [IDX_DEBUG_LEVEL] = {"debug-level", required_argument, 0, 0}, [IDX_DRY_RUN] = {"dry-run", no_argument, 0, 0}, [IDX_VERSION] = {"version", no_argument, 0, 0}, [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, [IDX_LOCAL_RCVBUF] = {"local-rcvbuf", required_argument, 0, 0}, [IDX_LOCAL_SNDBUF] = {"local-sndbuf", required_argument, 0, 0}, [IDX_REMOTE_RCVBUF] = {"remote-rcvbuf", required_argument, 0, 0}, [IDX_REMOTE_SNDBUF] = {"remote-sndbuf", required_argument, 0, 0}, [IDX_SOCKS] = {"socks", no_argument, 0, 0}, [IDX_NO_RESOLVE] = {"no-resolve", no_argument, 0, 0}, [IDX_RESOLVER_THREADS] = {"resolver-threads", required_argument, 0, 0}, [IDX_SKIP_NODELAY] = {"skip-nodelay", no_argument, 0, 0}, [IDX_TAMPER_START] = {"tamper-start", required_argument, 0, 0}, [IDX_TAMPER_CUTOFF] = {"tamper-cutoff", required_argument, 0, 0}, [IDX_CONNECT_BIND_ADDR] = {"connect-bind-addr", required_argument, 0, 0}, [IDX_NEW] = {"new", no_argument, 0, 0}, [IDX_SKIP] = {"skip", no_argument, 0, 0}, [IDX_FILTER_L3] = {"filter-l3", required_argument, 0, 0}, [IDX_FILTER_TCP] = {"filter-tcp", required_argument, 0, 0}, [IDX_FILTER_L7] = {"filter-l7", required_argument, 0, 0}, [IDX_IPSET] = {"ipset", required_argument, 0, 0}, [IDX_IPSET_IP] = {"ipset-ip", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE] = {"ipset-exclude", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE_IP] = {"ipset-exclude-ip", required_argument, 0, 0}, #if defined(__FreeBSD__) || defined(__OpenBSD__) [IDX_ENABLE_PF] = {"enable-pf", no_argument, 0, 0}, #elif defined(__APPLE__) [IDX_LOCAL_TCP_USER_TIMEOUT] = {"local-tcp-user-timeout", required_argument, 0, 0}, [IDX_REMOTE_TCP_USER_TIMEOUT] = {"remote-tcp-user-timeout", required_argument, 0, 0}, #elif defined(__linux__) [IDX_LOCAL_TCP_USER_TIMEOUT] = {"local-tcp-user-timeout", required_argument, 0, 0}, [IDX_REMOTE_TCP_USER_TIMEOUT] = {"remote-tcp-user-timeout", required_argument, 0, 0}, [IDX_MSS] = {"mss", required_argument, 0, 0}, [IDX_FIX_SEG] = {"fix-seg", optional_argument, 0, 0}, #ifdef SPLICE_PRESENT [IDX_NOSPLICE] = {"nosplice", no_argument, 0, 0}, #endif #endif [IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", optional_argument, 0, 0}, [IDX_LAST] = {NULL, 0, NULL, 0}, }; void parse_params(int argc, char *argv[]) { int option_index = 0; int v, i; bool bSkip=false, bDry=false; struct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL; struct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL; memset(¶ms, 0, sizeof(params)); params.maxconn = DEFAULT_MAX_CONN; params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; params.binds_last = -1; params.ipcache_lifetime = IPCACHE_LIFETIME; #if defined(__linux__) || defined(__APPLE__) params.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL; params.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE; #endif #if defined(__APPLE__) params.pf_enable = true; // OpenBSD and MacOS have no other choice #endif #ifdef __linux__ params.fix_seg_avail = socket_supports_notsent(); #endif LIST_INIT(¶ms.hostlists); LIST_INIT(¶ms.ipsets); if (can_drop_root()) { params.uid = params.gid[0] = 0x7FFFFFFF; // default uid:gid params.gid_count = 1; params.droproot = true; } struct desync_profile_list *dpl; struct desync_profile *dp; int desync_profile_count=0; if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = ++desync_profile_count; #if !defined( __OpenBSD__) && !defined(__ANDROID__) if (argc>=2 && (argv[1][0]=='@' || argv[1][0]=='$')) { config_from_file(argv[1]+1); argv=params.wexp.we_wordv; argc=params.wexp.we_wordc; } #endif while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) { if (v) { if (bDry) exit_clean(1); else exithelp_clean(); } switch (option_index) { case IDX_HELP: case IDX_H: exithelp_clean(); break; case IDX_BIND_ADDR: nextbind_clean(); { char *p = strchr(optarg,'%'); if (p) { *p=0; strncpy(params.binds[params.binds_last].bindiface, p+1, sizeof(params.binds[params.binds_last].bindiface)); } strncpy(params.binds[params.binds_last].bindaddr, optarg, sizeof(params.binds[params.binds_last].bindaddr)); } params.binds[params.binds_last].bindaddr[sizeof(params.binds[params.binds_last].bindaddr) - 1] = 0; break; case IDX_BIND_IFACE4: nextbind_clean(); params.binds[params.binds_last].bind_if6=false; strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; break; case IDX_BIND_IFACE6: nextbind_clean(); params.binds[params.binds_last].bind_if6=true; strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; break; case IDX_BIND_LINKLOCAL: checkbind_clean(); params.binds[params.binds_last].bindll = true; if (!strcmp(optarg, "no")) params.binds[params.binds_last].bindll=no; else if (!strcmp(optarg, "prefer")) params.binds[params.binds_last].bindll=prefer; else if (!strcmp(optarg, "force")) params.binds[params.binds_last].bindll=force; else if (!strcmp(optarg, "unwanted")) params.binds[params.binds_last].bindll=unwanted; else { DLOG_ERR("invalid parameter in bind-linklocal : %s\n",optarg); exit_clean(1); } break; case IDX_BIND_WAIT_IFUP: checkbind_clean(); params.binds[params.binds_last].bind_wait_ifup = atoi(optarg); break; case IDX_BIND_WAIT_IP: checkbind_clean(); params.binds[params.binds_last].bind_wait_ip = atoi(optarg); break; case IDX_BIND_WAIT_IP_LINKLOCAL: checkbind_clean(); params.binds[params.binds_last].bind_wait_ip_ll = atoi(optarg); break; case IDX_BIND_WAIT_ONLY: params.bind_wait_only = true; break; case IDX_PORT: i = atoi(optarg); if (i <= 0 || i > 65535) { DLOG_ERR("bad port number\n"); exit_clean(1); } params.port = (uint16_t)i; break; case IDX_DAEMON: params.daemon = true; break; case IDX_USER: { free(params.user); params.user=NULL; struct passwd *pwd = getpwnam(optarg); if (!pwd) { DLOG_ERR("non-existent username supplied\n"); exit_clean(1); } params.uid = pwd->pw_uid; params.gid[0]=pwd->pw_gid; params.gid_count=1; if (!(params.user=strdup(optarg))) { DLOG_ERR("strdup: out of memory\n"); exit_clean(1); } params.droproot = true; break; } case IDX_UID: free(params.user); params.user=NULL; if (!parse_uid(optarg,¶ms.uid,params.gid,¶ms.gid_count,MAX_GIDS)) { DLOG_ERR("--uid should be : uid[:gid,gid,...]\n"); exit_clean(1); } if (!params.gid_count) { params.gid[0] = 0x7FFFFFFF; params.gid_count = 1; } params.droproot = true; break; case IDX_MAXCONN: params.maxconn = atoi(optarg); if (params.maxconn <= 0 || params.maxconn > 10000) { DLOG_ERR("bad maxconn\n"); exit_clean(1); } break; case IDX_MAXFILES: params.maxfiles = atoi(optarg); if (params.maxfiles < 0) { DLOG_ERR("bad maxfiles\n"); exit_clean(1); } break; case IDX_MAX_ORPHAN_TIME: params.max_orphan_time = atoi(optarg); if (params.max_orphan_time < 0) { DLOG_ERR("bad max_orphan_time\n"); exit_clean(1); } break; case IDX_IPCACHE_LIFETIME: if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime)!=1) { DLOG_ERR("invalid ipcache-lifetime value\n"); exit_clean(1); } break; case IDX_IPCACHE_HOSTNAME: params.cache_hostname = !optarg || !!atoi(optarg); break; case IDX_HOSTCASE: dp->hostcase = true; params.tamper = true; break; case IDX_HOSTSPELL: if (strlen(optarg) != 4) { DLOG_ERR("hostspell must be exactly 4 chars long\n"); exit_clean(1); } dp->hostcase = true; memcpy(dp->hostspell, optarg, 4); params.tamper = true; break; case IDX_HOSTDOT: dp->hostdot = true; params.tamper = true; break; case IDX_HOSTNOSPACE: dp->hostnospace = true; params.tamper = true; break; case IDX_HOSTPAD: dp->hostpad = atoi(optarg); params.tamper = true; break; case IDX_DOMCASE: dp->domcase = true; params.tamper = true; break; case IDX_SPLIT_HTTP_REQ: DLOG_CONDUP("WARNING ! --split-http-req is deprecated. use --split-pos with markers.\n",MAX_SPLITS); if (dp->split_count>=MAX_SPLITS) { DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); exit_clean(1); } if (!parse_httpreqpos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for split-http-req\n"); exit_clean(1); } dp->split_count++; params.tamper = true; break; case IDX_SPLIT_TLS: // obsolete arg DLOG_CONDUP("WARNING ! --split-tls is deprecated. use --split-pos with markers.\n",MAX_SPLITS); if (dp->split_count>=MAX_SPLITS) { DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); exit_clean(1); } if (!parse_tlspos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for split-tls\n"); exit_clean(1); } dp->split_count++; params.tamper = true; break; case IDX_SPLIT_POS: { int ct; if (!parse_split_pos_list(optarg,dp->splits+dp->split_count,MAX_SPLITS-dp->split_count,&ct)) { DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n",dp->split_count,MAX_SPLITS,optarg); exit_clean(1); } dp->split_count += ct; } params.tamper = true; break; case IDX_SPLIT_ANY_PROTOCOL: dp->split_any_protocol = true; break; case IDX_DISORDER: if (optarg) { if (!strcmp(optarg,"http")) dp->disorder_http=true; else if (!strcmp(optarg,"tls")) dp->disorder_tls=true; else { DLOG_ERR("Invalid argument for disorder\n"); exit_clean(1); } } else dp->disorder = true; #ifndef __linux__ if (!check_oob_disorder(dp)) { DLOG_ERR("--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\n"); exit_clean(1); } #endif break; case IDX_OOB: if (optarg) { if (!strcmp(optarg,"http")) dp->oob_http=true; else if (!strcmp(optarg,"tls")) dp->oob_tls=true; else { DLOG_ERR("Invalid argument for oob\n"); exit_clean(1); } } else dp->oob = true; #ifndef __linux__ if (!check_oob_disorder(dp)) { DLOG_ERR("--oob and --disorder work simultaneously only in linux. in this system it's guaranteed to fail.\n"); exit_clean(1); } #endif break; case IDX_OOB_DATA: { size_t l = strlen(optarg); unsigned int bt; if (l==1) dp->oob_byte = (uint8_t)*optarg; else if (l!=4 || sscanf(optarg,"0x%02X",&bt)!=1) { DLOG_ERR("Invalid argument for oob-data\n"); exit_clean(1); } else dp->oob_byte = (uint8_t)bt; } break; case IDX_METHODSPACE: dp->methodspace = true; params.tamper = true; break; case IDX_METHODEOL: dp->methodeol = true; params.tamper = true; break; case IDX_HOSTTAB: dp->hosttab = true; params.tamper = true; break; case IDX_UNIXEOL: dp->unixeol = true; params.tamper = true; break; case IDX_TLSREC: if (!parse_split_pos(optarg, &dp->tlsrec) && !parse_tlspos(optarg, &dp->tlsrec)) { DLOG_ERR("Invalid argument for tlsrec\n"); exit_clean(1); } params.tamper = true; break; case IDX_TLSREC_POS: // obsolete arg i = atoi(optarg); dp->tlsrec.marker = PM_ABS; dp->tlsrec.pos = (int16_t)i; if (!dp->tlsrec.pos || i!=dp->tlsrec.pos) { DLOG_ERR("Invalid argument for tlsrec-pos\n"); exit_clean(1); } params.tamper = true; break; case IDX_HOSTLIST: if (bSkip) break; if (!RegisterHostlist(dp, false, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } params.tamper = true; break; case IDX_HOSTLIST_DOMAINS: if (bSkip) break; if (!anon_hl && !(anon_hl=RegisterHostlist(dp, false, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } params.tamper = true; break; case IDX_HOSTLIST_EXCLUDE: if (bSkip) break; if (!RegisterHostlist(dp, true, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } params.tamper = true; break; case IDX_HOSTLIST_EXCLUDE_DOMAINS: if (bSkip) break; if (!anon_hl_exclude && !(anon_hl_exclude=RegisterHostlist(dp, true, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl_exclude->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } params.tamper = true; break; case IDX_HOSTLIST_AUTO: if (bSkip) break; if (dp->hostlist_auto) { DLOG_ERR("only one auto hostlist per profile is supported\n"); exit_clean(1); } { FILE *F = fopen(optarg,"a+b"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } bool bGzip = is_gzip(F); fclose(F); if (bGzip) { DLOG_ERR("gzipped auto hostlists are not supported\n"); exit_clean(1); } } if (!(dp->hostlist_auto=RegisterHostlist(dp, false, optarg))) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice. break; case IDX_HOSTLIST_AUTO_FAIL_THRESHOLD: dp->hostlist_auto_fail_threshold = atoi(optarg); if (dp->hostlist_auto_fail_threshold<1 || dp->hostlist_auto_fail_threshold>20) { DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_FAIL_TIME: dp->hostlist_auto_fail_time = atoi(optarg); if (dp->hostlist_auto_fail_time<1) { DLOG_ERR("auto hostlist fail time is not valid\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_DEBUG: { FILE *F = fopen(optarg,"a+t"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } fclose(F); strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; } break; case IDX_PIDFILE: snprintf(params.pidfile,sizeof(params.pidfile),"%s",optarg); break; case IDX_DEBUG: if (optarg) { if (*optarg=='@') { strncpy(params.debug_logfile,optarg+1,sizeof(params.debug_logfile)); params.debug_logfile[sizeof(params.debug_logfile)-1] = 0; FILE *F = fopen(params.debug_logfile,"wt"); if (!F) { fprintf(stderr, "cannot create %s\n", params.debug_logfile); exit_clean(1); } if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_FILE; } else if (!strcmp(optarg,"syslog")) { if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_SYSLOG; openlog(progname,LOG_PID,LOG_USER); } #ifdef __ANDROID__ else if (!strcmp(optarg,"android")) { if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_ANDROID; } #endif else if (optarg[0]>='0' && optarg[0]<='2') { params.debug = atoi(optarg); params.debug_target = LOG_TARGET_CONSOLE; } else { fprintf(stderr, "invalid debug mode : %s\n", optarg); exit_clean(1); } } else { params.debug = 1; params.debug_target = LOG_TARGET_CONSOLE; } break; case IDX_DEBUG_LEVEL: params.debug = atoi(optarg); break; case IDX_DRY_RUN: bDry = true; break; case IDX_VERSION: exit_clean(0); break; case IDX_COMMENT: break; case IDX_LOCAL_RCVBUF: #ifdef __linux__ params.local_rcvbuf = atoi(optarg)/2; #else params.local_rcvbuf = atoi(optarg); #endif break; case IDX_LOCAL_SNDBUF: #ifdef __linux__ params.local_sndbuf = atoi(optarg)/2; #else params.local_sndbuf = atoi(optarg); #endif break; case IDX_REMOTE_RCVBUF: #ifdef __linux__ params.remote_rcvbuf = atoi(optarg)/2; #else params.remote_rcvbuf = atoi(optarg); #endif break; case IDX_REMOTE_SNDBUF: #ifdef __linux__ params.remote_sndbuf = atoi(optarg)/2; #else params.remote_sndbuf = atoi(optarg); #endif break; case IDX_SOCKS: params.proxy_type = CONN_TYPE_SOCKS; break; case IDX_NO_RESOLVE: params.no_resolve = true; break; case IDX_RESOLVER_THREADS: params.resolver_threads = atoi(optarg); if (params.resolver_threads<1 || params.resolver_threads>300) { DLOG_ERR("resolver-threads must be within 1..300\n"); exit_clean(1); } break; case IDX_SKIP_NODELAY: params.skip_nodelay = true; break; case IDX_TAMPER_START: { const char *p=optarg; if (*p=='n') { dp->tamper_start_n=true; p++; } else dp->tamper_start_n=false; dp->tamper_start = atoi(p); } params.tamper_lim = true; break; case IDX_TAMPER_CUTOFF: { const char *p=optarg; if (*p=='n') { dp->tamper_cutoff_n=true; p++; } else dp->tamper_cutoff_n=false; dp->tamper_cutoff = atoi(p); } params.tamper_lim = true; break; case IDX_CONNECT_BIND_ADDR: { char *p = strchr(optarg,'%'); if (p) *p++=0; if (inet_pton(AF_INET, optarg, ¶ms.connect_bind4.sin_addr)) { params.connect_bind4.sin_family = AF_INET; } else if (inet_pton(AF_INET6, optarg, ¶ms.connect_bind6.sin6_addr)) { params.connect_bind6.sin6_family = AF_INET6; if (p && *p) { // copy interface name for delayed resolution strncpy(params.connect_bind6_ifname,p,sizeof(params.connect_bind6_ifname)); params.connect_bind6_ifname[sizeof(params.connect_bind6_ifname)-1]=0; } } else { DLOG_ERR("bad bind addr : %s\n", optarg); exit_clean(1); } } break; case IDX_NEW: if (bSkip) { dp_clear(dp); dp_init(dp); dp->n = desync_profile_count; bSkip = false; } else { if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = ++desync_profile_count; } anon_hl = anon_hl_exclude = NULL; anon_ips = anon_ips_exclude = NULL; break; case IDX_SKIP: bSkip = true; break; case IDX_FILTER_L3: if (!wf_make_l3(optarg,&dp->filter_ipv4,&dp->filter_ipv6)) { DLOG_ERR("bad value for --filter-l3\n"); exit_clean(1); } break; case IDX_FILTER_TCP: if (!parse_pf_list(optarg,&dp->pf_tcp)) { DLOG_ERR("Invalid port filter : %s\n",optarg); exit_clean(1); } break; case IDX_FILTER_L7: if (!parse_l7_list(optarg,&dp->filter_l7)) { DLOG_ERR("Invalid l7 filter : %s\n",optarg); exit_clean(1); } break; case IDX_IPSET: if (bSkip) break; if (!RegisterIpset(dp, false, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } params.tamper = true; break; case IDX_IPSET_IP: if (bSkip) break; if (!anon_ips && !(anon_ips=RegisterIpset(dp, false, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } params.tamper = true; break; case IDX_IPSET_EXCLUDE: if (bSkip) break; if (!RegisterIpset(dp, true, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } params.tamper = true; break; case IDX_IPSET_EXCLUDE_IP: if (bSkip) break; if (!anon_ips_exclude && !(anon_ips_exclude=RegisterIpset(dp, true, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips_exclude->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } params.tamper = true; break; #if defined(__FreeBSD__) || defined(__OpenBSD__) case IDX_ENABLE_PF: params.pf_enable = true; break; #elif defined(__linux__) || defined(__APPLE__) case IDX_LOCAL_TCP_USER_TIMEOUT: params.tcp_user_timeout_local = atoi(optarg); if (params.tcp_user_timeout_local<0 || params.tcp_user_timeout_local>86400) { DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); exit_clean(1); } break; case IDX_REMOTE_TCP_USER_TIMEOUT: params.tcp_user_timeout_remote = atoi(optarg); if (params.tcp_user_timeout_remote<0 || params.tcp_user_timeout_remote>86400) { DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); exit_clean(1); } break; #endif #if defined(__linux__) case IDX_MSS: // this option does not work in any BSD and MacOS. OS may accept but it changes nothing dp->mss = atoi(optarg); if (dp->mss<88 || dp->mss>32767) { DLOG_ERR("Invalid value for MSS. Linux accepts MSS 88-32767.\n"); exit_clean(1); } break; case IDX_FIX_SEG: if (!params.fix_seg_avail) { DLOG_ERR("--fix-seg is supported since kernel 4.6\n"); exit_clean(1); } if (optarg) { i = atoi(optarg); if (i < 0 || i > 1000) { DLOG_ERR("fix_seg value must be within 0..1000\n"); exit_clean(1); } params.fix_seg = i; } else params.fix_seg = FIX_SEG_DEFAULT_MAX_WAIT; break; #ifdef SPLICE_PRESENT case IDX_NOSPLICE: params.nosplice = true; break; #endif #endif } } if (bSkip) { LIST_REMOVE(dpl,next); dp_entry_destroy(dpl); desync_profile_count--; } if (!params.bind_wait_only && !params.port) { DLOG_ERR("Need port number\n"); exit_clean(1); } if (params.binds_last<=0) { params.binds_last=0; // default bind to all } if (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50; VPRINT("adding low-priority default empty desync profile\n"); // add default empty profile if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count); save_default_ttl(); if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1)) fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1)) DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); #ifdef __linux__ bool bHasMSS=false, bHasOOB=false, bHasDisorder=false; #endif LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { dp = &dpl->dp; if (params.skip_nodelay && dp->split_count) { DLOG_ERR("Cannot split with --skip-nodelay\n"); exit_clean(1); } if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1)) DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); #ifdef __linux__ if (dp->mss) bHasMSS=true; if (dp->oob || dp->oob_http || dp->oob_tls) bHasOOB=true; if (dp->disorder || dp->disorder_http || dp->disorder_tls) bHasDisorder=true; #endif } #ifdef __linux__ if (is_wsl()==1) { if (!params.nosplice) DLOG_CONDUP("WARNING ! WSL1 may have problems with splice. Consider using `--nosplice`.\n"); if (bHasMSS) DLOG_CONDUP("WARNING ! WSL1 does not support MSS socket option. MSS will likely fail.\n"); if (bHasOOB) DLOG_CONDUP("WARNING ! WSL1 does not support OOB. OOB will likely fail.\n"); if (bHasDisorder) DLOG_CONDUP("WARNING ! Windows retransmits whole TCP segment. Disorder will not function properly.\n"); fflush(stdout); } #endif if (!test_list_files()) exit_clean(1); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed\n"); exit_clean(1); } if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed\n"); exit_clean(1); } VPRINT("\nlists summary:\n"); HostlistsDebug(); IpsetsDebug(); VPRINT("\nsplits summary:\n"); SplitDebug(); VPRINT("\n"); #if !defined( __OpenBSD__) && !defined(__ANDROID__) // do not need args from file anymore cleanup_args(¶ms); #endif if (bDry) { if (params.droproot) { if (!droproot(params.uid,params.user,params.gid,params.gid_count)) exit_clean(1); #ifdef __linux__ if (!dropcaps()) exit_clean(1); #endif print_id(); if (!test_list_files()) exit_clean(1); } DLOG_CONDUP("command line parameters verified\n"); exit_clean(0); } } static bool find_listen_addr(struct sockaddr_storage *salisten, const char *bindiface, bool bind_if6, enum bindll bindll, int *if_index) { struct ifaddrs *addrs,*a; bool found=false; if (getifaddrs(&addrs)<0) return false; // for ipv6 preference order // bind-linklocal-1 : link-local,any // bind-linklocal=0 : private,global,link-local for(int pass=0;pass<3;pass++) { a = addrs; while (a) { if (a->ifa_addr) { if (a->ifa_addr->sa_family==AF_INET && *bindiface && !bind_if6 && !strcmp(a->ifa_name, bindiface)) { salisten->ss_family = AF_INET; memcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr)); found=true; goto ex; } // ipv6 links locals are fe80::/10 else if (a->ifa_addr->sa_family==AF_INET6 && ((!*bindiface && (bindll==prefer || bindll==force)) || (*bindiface && bind_if6 && !strcmp(a->ifa_name, bindiface))) && ((bindll==force && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || (bindll==prefer && ((pass==0 && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || pass==2)) || (bindll==no && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)))) || (bindll==unwanted && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || pass==2))) ) { salisten->ss_family = AF_INET6; memcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr)); if (if_index) *if_index = if_nametoindex(a->ifa_name); found=true; goto ex; } } a = a->ifa_next; } } ex: freeifaddrs(addrs); return found; } static bool read_system_maxfiles(rlim_t *maxfile) { #ifdef __linux__ FILE *F; int n; uintmax_t um; if (!(F=fopen("/proc/sys/fs/file-max","r"))) return false; n=fscanf(F,"%ju",&um); fclose(F); if (n != 1) return false; *maxfile = (rlim_t)um; return true; #elif defined(BSD) int maxfiles,mib[2]={CTL_KERN, KERN_MAXFILES}; size_t len = sizeof(maxfiles); if (sysctl(mib,2,&maxfiles,&len,NULL,0)==-1) return false; *maxfile = (rlim_t)maxfiles; return true; #else return false; #endif } static bool write_system_maxfiles(rlim_t maxfile) { #ifdef __linux__ FILE *F; int n; if (!(F=fopen("/proc/sys/fs/file-max","w"))) return false; n=fprintf(F,"%ju",(uintmax_t)maxfile); fclose(F); return !!n; #elif defined(BSD) int maxfiles=(int)maxfile,mib[2]={CTL_KERN, KERN_MAXFILES}; if (sysctl(mib,2,NULL,0,&maxfiles,sizeof(maxfiles))==-1) return false; return true; #else return false; #endif } static bool set_ulimit(void) { rlim_t fdmax,fdmin_system,cur_lim=0; int n; if (!params.maxfiles) { // 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 // additional 1/2 for unpaired remote legs sending buffers // 16 for listen_fd, epoll, hostlist, ... #ifdef SPLICE_PRESENT fdmax = (rlim_t)(params.nosplice ? 2 : (params.tamper && !params.tamper_lim ? 4 : 6)) * (rlim_t)params.maxconn; #else fdmax = 2 * params.maxconn; #endif fdmax += fdmax/2 + 16; } else fdmax = params.maxfiles; fdmin_system = fdmax + 4096; DBGPRINT("set_ulimit : fdmax=%ju fdmin_system=%ju\n",(uintmax_t)fdmax,(uintmax_t)fdmin_system); if (!read_system_maxfiles(&cur_lim)) return false; DBGPRINT("set_ulimit : current system file-max=%ju\n",(uintmax_t)cur_lim); if (cur_lim 0) { int sec=0; if (!is_interface_online(params.binds[i].bindiface)) { DLOG_CONDUP("waiting for ifup of %s for up to %d second(s)...\n",params.binds[i].bindiface,params.binds[i].bind_wait_ifup); do { sleep(1); sec++; } while (!is_interface_online(params.binds[i].bindiface) && sec=params.binds[i].bind_wait_ifup) { DLOG_CONDUP("wait timed out\n"); goto exiterr; } } } if (!(if_index = if_nametoindex(params.binds[i].bindiface)) && params.binds[i].bind_wait_ip<=0) { DLOG_CONDUP("bad iface %s\n",params.binds[i].bindiface); goto exiterr; } } list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip; if (*params.binds[i].bindaddr) { if (inet_pton(AF_INET, params.binds[i].bindaddr, &((struct sockaddr_in*)(&list[i].salisten))->sin_addr)) { list[i].salisten.ss_family = AF_INET; } else if (inet_pton(AF_INET6, params.binds[i].bindaddr, &((struct sockaddr_in6*)(&list[i].salisten))->sin6_addr)) { list[i].salisten.ss_family = AF_INET6; list[i].ipv6_only = 1; } else { DLOG_CONDUP("bad bind addr : %s\n", params.binds[i].bindaddr); goto exiterr; } } else { if (*params.binds[i].bindiface || params.binds[i].bindll) { bool found; enum bindll bindll_1; int sec=0; if (params.binds[i].bind_wait_ip > 0) { DLOG_CONDUP("waiting for ip on %s for up to %d second(s)...\n", *params.binds[i].bindiface ? params.binds[i].bindiface : "", params.binds[i].bind_wait_ip); if (params.binds[i].bind_wait_ip_ll>0) { if (params.binds[i].bindll==prefer) DLOG_CONDUP("during the first %d second(s) accepting only link locals...\n", params.binds[i].bind_wait_ip_ll); else if (params.binds[i].bindll==unwanted) DLOG_CONDUP("during the first %d second(s) accepting only ipv6 globals...\n", params.binds[i].bind_wait_ip_ll); } } for(;;) { // allow, no, prefer, force bindll_1 = (params.binds[i].bindll==prefer && sec=params.binds[i].bind_wait_ip) break; sleep(1); sec++; } if (!found) { DLOG_CONDUP("suitable ip address not found\n"); goto exiterr; } list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip - sec; list[i].ipv6_only=1; } else { list[i].salisten.ss_family = AF_INET6; // leave sin6_addr zero } } if (list[i].salisten.ss_family == AF_INET6) { list[i].salisten_len = sizeof(struct sockaddr_in6); ((struct sockaddr_in6*)(&list[i].salisten))->sin6_port = htons(params.port); if (is_linklocal((struct sockaddr_in6*)(&list[i].salisten))) ((struct sockaddr_in6*)(&list[i].salisten))->sin6_scope_id = if_index; } else { list[i].salisten_len = sizeof(struct sockaddr_in); ((struct sockaddr_in*)(&list[i].salisten))->sin_port = htons(params.port); } } if (params.bind_wait_only) { DLOG_CONDUP("bind wait condition satisfied\n"); exit_v = 0; goto exiterr; } if (params.proxy_type==CONN_TYPE_TRANSPARENT && !redir_init()) { DLOG_ERR("could not initialize redirector !!!\n"); goto exiterr; } for(i=0;i<=params.binds_last;i++) { if (params.debug) { ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); VPRINT("Binding %d to %s\n",i,ip_port); } if ((listen_fd[i] = socket(list[i].salisten.ss_family, SOCK_STREAM, 0)) == -1) { DLOG_PERROR("socket"); goto exiterr; } #ifndef __OpenBSD__ // in OpenBSD always IPV6_ONLY for wildcard sockets if ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1) { DLOG_PERROR("setsockopt (IPV6_ONLY)"); goto exiterr; } #endif if (setsockopt(listen_fd[i], SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { DLOG_PERROR("setsockopt (SO_REUSEADDR)"); goto exiterr; } //Mark that this socket can be used for transparent proxying //This allows the socket to accept connections for non-local IPs if (params.proxy_type==CONN_TYPE_TRANSPARENT) { #ifdef __linux__ if (setsockopt(listen_fd[i], SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1) { DLOG_PERROR("setsockopt (IP_TRANSPARENT)"); goto exiterr; } #elif defined(BSD) && defined(SO_BINDANY) if (setsockopt(listen_fd[i], SOL_SOCKET, SO_BINDANY, &yes, sizeof(yes)) == -1) { DLOG_PERROR("setsockopt (SO_BINDANY)"); goto exiterr; } #endif } if (!set_socket_buffers(listen_fd[i], params.local_rcvbuf, params.local_sndbuf)) goto exiterr; if (!params.local_rcvbuf) { // HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ? int v; socklen_t sz=sizeof(int); if (!getsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,&sz)) { v/=2; setsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,sizeof(int)); } } bool bBindBug=false; for(;;) { if (bind(listen_fd[i], (struct sockaddr *)&list[i].salisten, list[i].salisten_len) == -1) { // in linux strange behaviour was observed // just after ifup and address assignment there's short window when bind() can't bind to addresses got from getifaddrs() // it does not happen to transparent sockets because they can bind to any non-existend ip // also only ipv6 seem to be buggy this way if (errno==EADDRNOTAVAIL && params.proxy_type!=CONN_TYPE_TRANSPARENT && list[i].bind_wait_ip_left) { if (!bBindBug) { ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); DLOG_CONDUP("address %s is not available. will retry for %d sec\n",ip_port,list[i].bind_wait_ip_left); bBindBug=true; } sleep(1); list[i].bind_wait_ip_left--; continue; } DLOG_PERROR("bind"); goto exiterr; } break; } if (listen(listen_fd[i], BACKLOG) == -1) { DLOG_PERROR("listen"); goto exiterr; } } if (params.cache_hostname) VPRINT("ipcache lifetime %us\n", params.ipcache_lifetime); DLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); if (!params.tamper) DLOG_CONDUP("TCP proxy mode (no tampering)\n"); if (*params.pidfile && !(Fpid=fopen(params.pidfile,"w"))) { DLOG_PERROR("create pidfile"); goto exiterr; } set_ulimit(); if (params.droproot && !droproot(params.uid,params.user,params.gid,params.gid_count)) goto exiterr; #ifdef __linux__ if (!dropcaps()) goto exiterr; #endif print_id(); if (params.droproot && !test_list_files()) goto exiterr; if (params.daemon) daemonize(); sec_harden(); if (Fpid) { if (fprintf(Fpid, "%d", getpid())<=0) { DLOG_PERROR("write pidfile"); goto exiterr; } fclose(Fpid); Fpid=NULL; } //splice() causes the process to receive the SIGPIPE-signal if one part (for //example a socket) is closed during splice(). I would rather have splice() //fail and return -1, so blocking SIGPIPE. if (block_sigpipe() == -1) { DLOG_ERR("Could not block SIGPIPE signal\n"); goto exiterr; } signal(SIGHUP, onhup); signal(SIGUSR2, onusr2); retval = event_loop(listen_fd,params.binds_last+1); exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS; DLOG_CONDUP("Exiting\n"); exiterr: if (Fpid) fclose(Fpid); redir_close(); for(i=0;i<=params.binds_last;i++) if (listen_fd[i]!=-1) close(listen_fd[i]); cleanup_params(¶ms); return exit_v; } ================================================ FILE: tpws/tpws.h ================================================ #pragma once #ifdef __linux__ #define SPLICE_PRESENT #endif #include void ReloadCheck(); ================================================ FILE: tpws/tpws_conn.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_SYSTEMD #include #endif #include "tpws.h" #include "tpws_conn.h" #include "redirect.h" #include "tamper.h" #include "socks.h" #include "helpers.h" #include "hostlist.h" #include "linux_compat.h" static void notify_ready(void) { #ifdef USE_SYSTEMD int r = sd_notify(0, "READY=1"); if (r < 0) DLOG_ERR("sd_notify: %s\n", strerror(-r)); #endif } // keep separate legs counter. counting every time thousands of legs can consume cpu static int legs_local, legs_remote; /* static void count_legs(struct tailhead *conn_list) { tproxy_conn_t *conn = NULL; legs_local = legs_remote = 0; TAILQ_FOREACH(conn, conn_list, conn_ptrs) conn->remote ? legs_remote++ : legs_local++; } */ static void print_legs(void) { VPRINT("Legs : local:%d remote:%d\n", legs_local, legs_remote); } static bool socks5_send_rep(int fd,uint8_t rep) { s5_rep s5rep; memset(&s5rep,0,sizeof(s5rep)); s5rep.ver = 5; s5rep.rep = rep; s5rep.atyp = S5_ATYP_IP4; return send(fd,&s5rep,sizeof(s5rep),MSG_DONTWAIT)==sizeof(s5rep); } static bool socks5_send_rep_errno(int fd,int errn) { uint8_t rep; switch(errn) { case 0: rep=S5_REP_OK; break; case ECONNREFUSED: rep=S5_REP_CONN_REFUSED; break; case ENETUNREACH: rep=S5_REP_NETWORK_UNREACHABLE; break; case ETIMEDOUT: case EHOSTUNREACH: rep=S5_REP_HOST_UNREACHABLE; break; default: rep=S5_REP_GENERAL_FAILURE; } return socks5_send_rep(fd,rep); } static bool socks4_send_rep(int fd, uint8_t rep) { s4_rep s4rep; memset(&s4rep, 0, sizeof(s4rep)); s4rep.rep = rep; return send(fd, &s4rep, sizeof(s4rep), MSG_DONTWAIT) == sizeof(s4rep); } static bool socks4_send_rep_errno(int fd, int errn) { return socks4_send_rep(fd, errn ? S4_REP_FAILED : S4_REP_OK); } static bool socks_send_rep(uint8_t ver, int fd, uint8_t rep5) { return ver==5 ? socks5_send_rep(fd, rep5) : socks4_send_rep(fd, rep5 ? S4_REP_FAILED : S4_REP_OK); } static bool socks_send_rep_errno(uint8_t ver, int fd, int errn) { return ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn); } static bool cork(int fd, int enable) { #ifdef __linux__ int e = errno; if (setsockopt(fd, SOL_TCP, TCP_CORK, &enable, sizeof(enable))<0) { DLOG_PERROR("setsockopt (TCP_CORK)"); errno = e; return false; } errno = e; #endif return true; } ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl) { ssize_t wr; if (!params.skip_nodelay) { int ttl_apply = ttl ? ttl : params.ttl_default; DBGPRINT("send_with_ttl %d fd=%d\n",ttl,fd); if (!set_ttl_hl(fd, ttl_apply)) //DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); DLOG_ERR("could not set ttl %d to fd=%d\n",ttl_apply,fd); cork(fd,true); } wr = send(fd, buf, len, flags); if (!params.skip_nodelay) cork(fd,false); return wr; } static bool send_buffer_create(send_buffer_t *sb, const void *data, size_t len, size_t extra_bytes, int flags, int ttl) { if (sb->data) { DLOG_ERR("FATAL : send_buffer_create but buffer is not empty\n"); exit(1); } sb->data = malloc(len + extra_bytes); if (!sb->data) { DBGPRINT("send_buffer_create failed\n"); return false; } if (data) memcpy(sb->data,data,len); sb->len = len; sb->pos = 0; sb->ttl = ttl; sb->flags = flags; return true; } static bool send_buffer_realloc(send_buffer_t *sb, size_t extra_bytes) { if (sb->data) { uint8_t *p = (uint8_t*)realloc(sb->data, sb->len + extra_bytes); if (p) { sb->data = p; DBGPRINT("reallocated send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); return true; } else { DBGPRINT("failed to realloc send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); } } return false; } static void send_buffer_free(send_buffer_t *sb) { free(sb->data); sb->data = NULL; } static void send_buffers_free(send_buffer_t *sb_array, int count) { for (int i=0;iwr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); } static bool send_buffer_present(send_buffer_t *sb) { return !!sb->data; } static bool send_buffers_present(send_buffer_t *sb_array, int count) { for(int i=0;idata + sb->pos, sb->len - sb->pos, sb->flags, sb->ttl); DBGPRINT("send_buffer_send len=%zu pos=%zu wr=%zd err=%d\n",sb->len,sb->pos,wr,errno); if (wr>0) { sb->pos += wr; if (sb->pos >= sb->len) { send_buffer_free(sb); } } else if (wr<0 && errno==EAGAIN) wr=0; return wr; } static ssize_t send_buffers_send(send_buffer_t *sb_array, int count, int fd, size_t *real_wr) { ssize_t wr,twr=0; for (int i=0;iconn_type==CONN_TYPE_SOCKS && conn->socks_state!=S_TCP); } static bool conn_partner_alive(tproxy_conn_t *conn) { return conn->partner && conn->partner->state!=CONN_CLOSED; } static bool conn_buffers_present(tproxy_conn_t *conn) { return send_buffers_present(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); } static ssize_t conn_buffers_send(tproxy_conn_t *conn) { size_t wr,real_twr; wr = send_buffers_send(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]), conn->fd, &real_twr); conn->twr += real_twr; return wr; } static bool conn_has_unsent(tproxy_conn_t *conn) { return conn->wr_unsent || conn_buffers_present(conn); } static int conn_bytes_unread(tproxy_conn_t *conn) { int numbytes=-1; ioctl(conn->fd, FIONREAD, &numbytes); return numbytes; } static bool conn_has_unsent_pair(tproxy_conn_t *conn) { return conn_has_unsent(conn) || (conn_partner_alive(conn) && conn_has_unsent(conn->partner)); } static bool conn_shutdown(tproxy_conn_t *conn) { conn->bShutdown = true; if (shutdown(conn->fd,SHUT_WR)<0) { DLOG_PERROR("shutdown"); return false; } return true; } static ssize_t send_or_buffer(send_buffer_t *sb, int fd, const void *buf, size_t len, int flags, int ttl) { ssize_t wr=0; if (len) { wr = send_with_ttl(fd, buf, len, flags, ttl); if (wr<0 && errno==EAGAIN) wr=0; if (wr>=0 && wr=2) { int v; socklen_t sz; sz=sizeof(int); if (!getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&v,&sz)) DBGPRINT("fd=%d SO_RCVBUF=%d\n",fd,v); sz=sizeof(int); if (!getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&v,&sz)) DBGPRINT("fd=%d SO_SNDBUF=%d\n",fd,v); } } bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) { DBGPRINT("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n",fd,rcvbuf,sndbuf); if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0) { DLOG_PERROR("setsockopt (SO_RCVBUF)"); return false; } if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0) { DLOG_PERROR("setsockopt (SO_SNDBUF)"); return false; } dbgprint_socket_buffers(fd); return true; } static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err) { // if proxy mode acknowledge connection request // conn = remote. conn->partner = local if (!conn->remote || !conn_partner_alive(conn)) return false; bool bres = true; switch(conn->partner->conn_type) { case CONN_TYPE_SOCKS: if (conn->partner->socks_state==S_WAIT_CONNECTION) { conn->partner->socks_state=S_TCP; bres = socks_send_rep_errno(conn->partner->socks_ver,conn->partner->fd,sock_err); DBGPRINT("socks connection acknowledgement. bres=%d remote_errn=%d remote_fd=%d local_fd=%d\n",bres,sock_err,conn->fd,conn->partner->fd); } break; } return bres; } #if defined(__linux__) || defined(__APPLE__) static void set_user_timeout(int fd, int timeout) { #ifdef __linux__ if (timeout>0) { int msec = 1000*timeout; if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &msec, sizeof(int)) <0) DLOG_PERROR("setsockopt (TCP_USER_TIMEOUT)"); } #elif defined(__APPLE__) if (timeout>0 && setsockopt(fd, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &timeout, sizeof(int)) <0) DLOG_PERROR("setsockopt (TCP_RXT_CONNDROPTIME)"); #endif } #else #define set_user_timeout(fd,timeout) #endif //Createas a socket and initiates the connection to the host specified by //remote_addr. //Returns -1 if something fails, >0 on success (socket fd). static int connect_remote(const struct sockaddr *remote_addr, int mss) { int remote_fd = 0, yes = 1, no = 0; if((remote_fd = socket(remote_addr->sa_family, SOCK_STREAM, 0)) < 0) { DLOG_PERROR("socket (connect_remote)"); return -1; } // Use NONBLOCK to avoid slow connects affecting the performance of other connections // separate fcntl call to comply with macos if (fcntl(remote_fd, F_SETFL, O_NONBLOCK)<0) { DLOG_PERROR("socket set O_NONBLOCK (connect_remote)"); close(remote_fd); return -1; } if (setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { DLOG_PERROR("setsockopt (SO_REUSEADDR, connect_remote)"); close(remote_fd); return -1; } if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf)) { close(remote_fd); return -1; } if (!set_keepalive(remote_fd)) { DLOG_PERROR("set_keepalive"); close(remote_fd); return -1; } if (setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, params.skip_nodelay ? &no : &yes, sizeof(int)) <0) { DLOG_PERROR("setsockopt (TCP_NODELAY, connect_remote)"); close(remote_fd); return -1; } #ifdef __linux__ if (mss) { VPRINT("Setting MSS %d\n", mss); if (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(int)) <0) { DLOG_PERROR("setsockopt (TCP_MAXSEG, connect_remote)"); close(remote_fd); return -1; } } #endif // if no bind address specified - address family will be 0 in params_connect_bindX if(remote_addr->sa_family == params.connect_bind4.sin_family) { if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind4, sizeof(struct sockaddr_in)) == -1) { DLOG_PERROR("bind on connect"); close(remote_fd); return -1; } } else if(remote_addr->sa_family == params.connect_bind6.sin6_family) { if (*params.connect_bind6_ifname && !params.connect_bind6.sin6_scope_id) { params.connect_bind6.sin6_scope_id=if_nametoindex(params.connect_bind6_ifname); if (!params.connect_bind6.sin6_scope_id) { DLOG_ERR("interface name not found : %s\n", params.connect_bind6_ifname); close(remote_fd); return -1; } } if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind6, sizeof(struct sockaddr_in6)) == -1) { DLOG_PERROR("bind on connect"); close(remote_fd); return -1; } } set_user_timeout(remote_fd, params.tcp_user_timeout_remote); if (connect(remote_fd, remote_addr, remote_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0) { if(errno != EINPROGRESS) { DLOG_PERROR("connect (connect_remote)"); close(remote_fd); return -1; } } DBGPRINT("Connecting remote fd=%d\n",remote_fd); return remote_fd; } static bool connect_remote_conn(tproxy_conn_t *conn) { int mss=0; if (conn->track.hostname) if (!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)) DLOG_ERR("ipcache_put_hostname: out of memory"); apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); if (conn->track.dp && conn->track.dp->mss) { mss = conn->track.dp->mss; if (conn->track.dp->hostlist_auto) { if (conn->track.hostname) { bool bHostExcluded; conn->track.b_host_matches = HostlistCheck(conn->track.dp, conn->track.hostname, conn->track.hostname_is_ip, &bHostExcluded, false); conn->track.b_host_checked = true; if (!conn->track.b_host_matches) { conn->track.b_ah_check = !bHostExcluded; mss = 0; } } } } return (conn->partner->fd = connect_remote((struct sockaddr *)&conn->dest, mss))>=0; } //Free resources occupied by this connection static void free_conn(tproxy_conn_t *conn) { if (!conn) return; if (conn->fd) close(conn->fd); if (conn->splice_pipe[0]) { close(conn->splice_pipe[0]); close(conn->splice_pipe[1]); } conn_free_buffers(conn); if (conn->partner) conn->partner->partner=NULL; free(conn->track.hostname); if (conn->socks_ri) conn->socks_ri->ptr = NULL; // detach conn free(conn); } static tproxy_conn_t *new_conn(int fd, bool remote) { tproxy_conn_t *conn; //Create connection object and fill in information if((conn = (tproxy_conn_t*) calloc(1, sizeof(tproxy_conn_t))) == NULL) { DLOG_ERR("Could not allocate memory for connection\n"); return NULL; } conn->state = CONN_UNAVAILABLE; conn->fd = fd; conn->remote = remote; #ifdef SPLICE_PRESENT // if dont tamper - both legs are spliced, create 2 pipes // otherwise create pipe only in local leg if (!params.nosplice && ( !remote || !params.tamper || params.tamper_lim ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) { DLOG_ERR("Could not create the splice pipe\n"); free_conn(conn); return NULL; } #endif return conn; } static bool epoll_set(tproxy_conn_t *conn, uint32_t events) { struct epoll_event ev; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = (void*) conn; DBGPRINT("epoll_set fd=%d events=%08X\n",conn->fd,events); if(epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, &ev)==-1 && epoll_ctl(conn->efd, EPOLL_CTL_ADD, conn->fd, &ev)==-1) { DLOG_PERROR("epoll_ctl (add/mod)"); return false; } return true; } static bool epoll_del(tproxy_conn_t *conn) { struct epoll_event ev; memset(&ev, 0, sizeof(ev)); DBGPRINT("epoll_del fd=%d\n",conn->fd); if(epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, &ev)==-1) { DLOG_PERROR("epoll_ctl (del)"); return false; } return true; } static bool epoll_update_flow(tproxy_conn_t *conn) { if (conn->bFlowInPrev==conn->bFlowIn && conn->bFlowOutPrev==conn->bFlowOut && conn->bPrevRdhup==(conn->state==CONN_RDHUP)) return true; // unchanged, no need to syscall DBGPRINT("SET FLOW fd=%d to in=%d out=%d state_rdhup=%d\n",conn->fd,conn->bFlowIn,conn->bFlowOut,conn->state==CONN_RDHUP); uint32_t evtmask = (conn->state==CONN_RDHUP ? 0 : EPOLLRDHUP)|(conn->bFlowIn?EPOLLIN:0)|(conn->bFlowOut?EPOLLOUT:0); if (!epoll_set(conn, evtmask)) return false; conn->bFlowInPrev = conn->bFlowIn; conn->bFlowOutPrev = conn->bFlowOut; conn->bPrevRdhup = (conn->state==CONN_RDHUP); return true; } static bool epoll_set_flow(tproxy_conn_t *conn, bool bFlowIn, bool bFlowOut) { conn->bFlowIn = bFlowIn; conn->bFlowOut = bFlowOut; return epoll_update_flow(conn); } //Acquires information, initiates a connect and initialises a new connection //object. Return NULL if anything fails, pointer to object otherwise static 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) { struct sockaddr_storage orig_dst; tproxy_conn_t *conn; if (proxy_type==CONN_TYPE_TRANSPARENT) { if(!get_dest_addr(local_fd, accept_sa, &orig_dst)) { DLOG_ERR("Could not get destination address\n"); close(local_fd); return NULL; } if (check_local_ip((struct sockaddr*)&orig_dst) && saport((struct sockaddr*)&orig_dst)==listen_port) { VPRINT("Dropping connection to local address to the same port to avoid loop\n"); close(local_fd); return NULL; } } // socket buffers inherited from listen_fd dbgprint_socket_buffers(local_fd); if(!set_keepalive(local_fd)) { DLOG_PERROR("set_keepalive"); close(local_fd); return 0; } if(!(conn = new_conn(local_fd, false))) { close(local_fd); return NULL; } conn->conn_type = proxy_type; // only local connection has proxy_type. remote is always in tcp mode conn->state = CONN_AVAILABLE; // accepted connection is immediately available conn->efd = efd; socklen_t salen=sizeof(conn->client); getpeername(conn->fd,(struct sockaddr *)&conn->client,&salen); if (proxy_type==CONN_TYPE_TRANSPARENT) { sa46copy(&conn->dest, (struct sockaddr *)&orig_dst); if(!(conn->partner = new_conn(0, true))) { free_conn(conn); return NULL; } conn->partner->partner = conn; conn->partner->efd = efd; conn->partner->client = conn->client; conn->partner->dest = conn->dest; if (!connect_remote_conn(conn)) { DLOG_ERR("Failed to connect\n"); free_conn(conn->partner); free_conn(conn); return NULL; } //remote_fd is connecting. Non-blocking connects are signaled as done by //socket being marked as ready for writing if (!epoll_set(conn->partner, EPOLLOUT)) { free_conn(conn->partner); free_conn(conn); return NULL; } } //Transparent proxy mode : // Local socket can be closed while waiting for connection attempt. I need // to detect this when waiting for connect() to complete. However, I dont // want to get EPOLLIN-events, as I dont want to receive any data before // remote connection is established //Proxy mode : I need to service proxy protocol // remote connection not started until proxy handshake is complete if (!epoll_set(conn, proxy_type==CONN_TYPE_TRANSPARENT ? EPOLLRDHUP : (EPOLLIN|EPOLLRDHUP))) { free_conn(conn->partner); free_conn(conn); return NULL; } TAILQ_INSERT_HEAD(conn_list, conn, conn_ptrs); legs_local++; if (conn->partner) { TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); legs_remote++; } return conn; } //Checks if a connection attempt was successful or not //Returns true if successfull, false if not static bool check_connection_attempt(tproxy_conn_t *conn, int efd) { int errn = 0; socklen_t optlen = sizeof(errn); if (conn->state!=CONN_UNAVAILABLE || !conn->remote) { // locals are connected since accept // remote need to be checked only once return true; } // check the connection was sucessfull. it means its not in in SO_ERROR state if(getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) { DLOG_PERROR("getsockopt (SO_ERROR)"); return false; } if (!errn) { if (params.debug>=1) { sockaddr_in46 sa; socklen_t salen=sizeof(sa); char ip_port[48]; if (getsockname(conn->fd,(struct sockaddr *)&sa,&salen)) *ip_port=0; else ntop46_port((struct sockaddr*)&sa,ip_port,sizeof(ip_port)); VPRINT("Socket fd=%d (remote) connected from : %s\n", conn->fd, ip_port); } if (!epoll_set_flow(conn, true, false) || (conn_partner_alive(conn) && !epoll_set_flow(conn->partner, true, false))) { return false; } conn->state = CONN_AVAILABLE; } proxy_remote_conn_ack(conn,get_so_error(conn->fd)); return !errn; } static bool epoll_set_flow_pair(tproxy_conn_t *conn) { bool bHasUnsent = conn_has_unsent(conn); bool bHasUnsentPartner = conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false; DBGPRINT("epoll_set_flow_pair fd=%d remote=%d partner_fd=%d bHasUnsent=%d bHasUnsentPartner=%d state_rdhup=%d\n", conn->fd , conn->remote, conn_partner_alive(conn) ? conn->partner->fd : 0, bHasUnsent, bHasUnsentPartner, conn->state==CONN_RDHUP); if (!epoll_set_flow(conn, !bHasUnsentPartner && (conn->state != CONN_RDHUP), bHasUnsent)) return false; if (conn_partner_alive(conn)) { if (!epoll_set_flow(conn->partner, !bHasUnsent && (conn->partner->state != CONN_RDHUP), bHasUnsentPartner)) return false; } return true; } static bool handle_unsent(tproxy_conn_t *conn) { ssize_t wr; DBGPRINT("+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); #ifdef SPLICE_PRESENT if (!params.nosplice && conn->wr_unsent) { wr = splice(conn->splice_pipe[0], NULL, conn->fd, NULL, conn->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); DBGPRINT("splice unsent=%zd wr=%zd err=%d\n",conn->wr_unsent,wr,errno); if (wr<0) { if (errno==EAGAIN) wr=0; else return false; } conn->twr += wr; conn->wr_unsent -= wr; } #endif if (!conn->wr_unsent && conn_buffers_present(conn)) { wr=conn_buffers_send(conn); DBGPRINT("conn_buffers_send wr=%zd\n",wr); if (wr<0) return false; } if (!conn_has_unsent(conn) && conn_partner_alive(conn) && conn->partner->state==CONN_RDHUP) { if (!conn->bShutdown) { DBGPRINT("fd=%d no more has unsent. partner in RDHUP state. executing delayed shutdown.\n", conn->fd); if (!conn_shutdown(conn)) { DBGPRINT("emergency connection close due to failed shutdown\n"); return false; } } if (conn->state==CONN_RDHUP && !conn_has_unsent(conn->partner)) { DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); return false; } } return epoll_set_flow_pair(conn); } static bool proxy_mode_connect_remote(tproxy_conn_t *conn, struct tailhead *conn_list) { int remote_fd; if (params.debug>=1) { char ip_port[48]; ntop46_port((struct sockaddr *)&conn->dest,ip_port,sizeof(ip_port)); VPRINT("socks target for fd=%d is : %s\n", conn->fd, ip_port); } if (check_local_ip((struct sockaddr *)&conn->dest)) { VPRINT("Dropping connection to local address for security reasons\n"); socks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET); return false; } if (!(conn->partner = new_conn(remote_fd, true))) { close(remote_fd); DLOG_ERR("socks out-of-memory (1)\n"); socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); return false; } conn->partner->partner = conn; conn->partner->efd = conn->efd; conn->partner->client = conn->client; conn->partner->dest = conn->dest; if (!connect_remote_conn(conn)) { free_conn(conn->partner); conn->partner = NULL; DLOG_ERR("socks failed to connect (1) errno=%d\n", errno); socks_send_rep_errno(conn->socks_ver, conn->fd, errno); return false; } if (!epoll_set(conn->partner, EPOLLOUT)) { DLOG_ERR("socks epoll_set error %d\n", errno); free_conn(conn->partner); conn->partner = NULL; socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); return false; } TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); legs_remote++; print_legs(); DBGPRINT("S_WAIT_CONNECTION\n"); conn->socks_state = S_WAIT_CONNECTION; return true; } static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) { // To simplify things I dont care about buffering. If message splits, I just hang up // in proxy mode messages are short. they can be split only intentionally. all normal programs send them in one packet ssize_t rd,wr; char buf[sizeof(s5_req)]; // s5_req - the largest possible req // receive proxy control message rd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); DBGPRINT("handle_proxy_mode rd=%zd\n",rd); if (rd<1) return false; // hangup switch(conn->conn_type) { case CONN_TYPE_SOCKS: switch(conn->socks_state) { case S_WAIT_HANDSHAKE: DBGPRINT("S_WAIT_HANDSHAKE\n"); if (buf[0] != 5 && buf[0] != 4) return false; // unknown socks version conn->socks_ver = buf[0]; DBGPRINT("socks version %u\n", conn->socks_ver); if (conn->socks_ver==5) { s5_handshake *m = (s5_handshake*)buf; s5_handshake_ack ack; uint8_t k; ack.ver=5; if (!S5_REQ_HANDHSHAKE_VALID(m,rd)) { DBGPRINT("socks5 proxy handshake invalid\n"); return false; } for (k=0;knmethods;k++) if (m->methods[k]==S5_AUTH_NONE) break; if (k>=m->nmethods) { DBGPRINT("socks5 client wants authentication but we dont support\n"); ack.method=S5_AUTH_UNACCEPTABLE; wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); return false; } DBGPRINT("socks5 recv valid handshake\n"); ack.method=S5_AUTH_NONE; wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); if (wr!=sizeof(ack)) { DBGPRINT("socks5 handshake ack send error. wr=%zd errno=%d\n",wr,errno); return false; } DBGPRINT("socks5 send handshake ack OK\n"); conn->socks_state=S_WAIT_REQUEST; return true; } else { // socks4 does not have separate handshake phase. it starts with connect request // ipv6 and domain resolving are not supported s4_req *m = (s4_req*)buf; if (!S4_REQ_HEADER_VALID(m, rd)) { DBGPRINT("socks4 request invalid\n"); return false; } if (m->cmd!=S4_CMD_CONNECT) { // BIND is not supported DBGPRINT("socks4 unsupported command %02X\n", m->cmd); socks4_send_rep(conn->fd, S4_REP_FAILED); return false; } if (!S4_REQ_CONNECT_VALID(m, rd)) { DBGPRINT("socks4 connect request invalid\n"); socks4_send_rep(conn->fd, S4_REP_FAILED); return false; } if (!m->port) { DBGPRINT("socks4 zero port\n"); socks4_send_rep(conn->fd, S4_REP_FAILED); return false; } if (m->ip==htonl(1)) // special ip 0.0.0.1 { VPRINT("socks4a protocol not supported\n"); socks4_send_rep(conn->fd, S4_REP_FAILED); return false; } ((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET; ((struct sockaddr_in*)&conn->dest)->sin_port = m->port; ((struct sockaddr_in*)&conn->dest)->sin_addr.s_addr = m->ip; return proxy_mode_connect_remote(conn, conn_list); } break; case S_WAIT_REQUEST: DBGPRINT("S_WAIT_REQUEST\n"); { s5_req *m = (s5_req*)buf; if (!S5_REQ_HEADER_VALID(m,rd)) { DBGPRINT("socks5 request invalid\n"); return false; } if (m->cmd!=S5_CMD_CONNECT) { // BIND and UDP are not supported DBGPRINT("socks5 unsupported command %02X\n", m->cmd); socks5_send_rep(conn->fd,S5_REP_COMMAND_NOT_SUPPORTED); return false; } if (!S5_REQ_CONNECT_VALID(m,rd)) { DBGPRINT("socks5 connect request invalid\n"); return false; } DBGPRINT("socks5 recv valid connect request\n"); switch(m->atyp) { case S5_ATYP_IP4: ((struct sockaddr_in*)&conn->dest)->sin_family = AF_INET; ((struct sockaddr_in*)&conn->dest)->sin_port = m->d4.port; ((struct sockaddr_in*)&conn->dest)->sin_addr = m->d4.addr; break; case S5_ATYP_IP6: ((struct sockaddr_in6*)&conn->dest)->sin6_family = AF_INET6; ((struct sockaddr_in6*)&conn->dest)->sin6_port = m->d6.port; ((struct sockaddr_in6*)&conn->dest)->sin6_addr = m->d6.addr; ((struct sockaddr_in6*)&conn->dest)->sin6_flowinfo = 0; ((struct sockaddr_in6*)&conn->dest)->sin6_scope_id = 0; break; case S5_ATYP_DOM: { uint16_t port; if (params.no_resolve) { VPRINT("socks5 hostname resolving disabled\n"); socks5_send_rep(conn->fd,S5_REP_NOT_ALLOWED_BY_RULESET); return false; } port=S5_PORT_FROM_DD(m,rd); if (!port) { VPRINT("socks5 no port is given\n"); socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); return false; } m->dd.domport[m->dd.len] = 0; DBGPRINT("socks5 queue resolve hostname '%s' port '%u'\n",m->dd.domport,port); conn->socks_ri = resolver_queue(m->dd.domport,port,conn); if (!conn->socks_ri) { VPRINT("socks5 could not queue resolve item\n"); socks5_send_rep(conn->fd,S5_REP_GENERAL_FAILURE); return false; } conn->socks_state=S_WAIT_RESOLVE; DBGPRINT("S_WAIT_RESOLVE\n"); return true; } break; default: return false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp } return proxy_mode_connect_remote(conn,conn_list); } break; case S_WAIT_RESOLVE: DBGPRINT("socks received message while in S_WAIT_RESOLVE. hanging up\n"); break; case S_WAIT_CONNECTION: DBGPRINT("socks received message while in S_WAIT_CONNECTION. hanging up\n"); break; default: DBGPRINT("socks received message while in an unexpected connection state\n"); break; } break; } return false; } static bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list) { tproxy_conn_t *conn = (tproxy_conn_t *)ri->ptr; if (conn && (conn->state != CONN_CLOSED)) { if (conn->socks_state==S_WAIT_RESOLVE) { DBGPRINT("resolve_complete %s. getaddrinfo result %d\n", ri->dom, ri->ga_res); if (ri->ga_res) { socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); return false;; } else { if (!conn->track.hostname) { DBGPRINT("resolve_complete put hostname : %s\n", ri->dom); if (!(conn->track.hostname = strdup(ri->dom))) DLOG_ERR("dup hostname: out of memory\n"); } sa46copy(&conn->dest, (struct sockaddr *)&ri->ss); return proxy_mode_connect_remote(conn,conn_list); } } else DLOG_ERR("resolve_complete: conn in wrong socks_state !!! (%s)\n", ri->dom); } else DBGPRINT("resolve_complete: orphaned resolve for %s\n", ri->dom); return true; } static bool in_tamper_out_range(tproxy_conn_t *conn) { if (!conn->track.dp) return true; bool in_range = \ ((conn->track.dp->tamper_start_n ? (conn->tnrd+1) : conn->trd) >= conn->track.dp->tamper_start && (!conn->track.dp->tamper_cutoff || (conn->track.dp->tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < conn->track.dp->tamper_cutoff)); DBGPRINT("tamper_out range check. stream pos %" PRIu64 "(n%" PRIu64 "). tamper range %s%u-%s%u (%s)\n", conn->trd, conn->tnrd+1, conn->track.dp ? conn->track.dp->tamper_start_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_start : 0, conn->track.dp ? conn->track.dp->tamper_cutoff_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_cutoff : 0, in_range ? "IN RANGE" : "OUT OF RANGE"); return in_range; } static 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) { if (multisplit_count) *multisplit_count=0; if (params.tamper) { if (conn->remote) { if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff) tamper_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client,segment,segment_buffer_size,segment_size); } else { if (in_tamper_out_range(conn)) tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,multisplit_pos,multisplit_count,split_flags); } } } // buffer must have at least one extra byte for OOB static ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte) { ssize_t wr; if (oob) { uint8_t oob_save; oob_save = buf[len]; buf[len] = oob_byte; wr = send_with_ttl(fd, buf, len+1, MSG_OOB, ttl); buf[len] = oob_save; if (wr<0 && errno==EAGAIN) wr=0; } else wr = send_with_ttl(fd, buf, len, 0, ttl); return wr; } static unsigned int segfail_count=0; static time_t segfail_report_time=0; static void report_segfail(void) { time_t now = time(NULL); segfail_count++; if (now==segfail_report_time) VPRINT("WARNING ! segmentation failed. total fails : %u\n", segfail_count); else { DLOG_ERR("WARNING ! segmentation failed. total fails : %u\n", segfail_count); segfail_report_time = now; } } #define RD_BLOCK_SIZE 65536 #define MAX_WASTE (1024*1024) static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt) { int numbytes; ssize_t rd = 0, wr = 0; size_t bs; DBGPRINT("+handle_epoll\n"); if (!conn_in_tcp_mode(conn)) { if (!(evt & EPOLLIN)) return true; // nothing to read return handle_proxy_mode(conn,conn_list); } if (!handle_unsent(conn)) return false; // error if (!conn_partner_alive(conn) && !conn_has_unsent(conn)) return false; // when no partner, we only waste read and send unsent if (!(evt & EPOLLIN)) return true; // nothing to read if (!conn_partner_alive(conn)) { // throw it to a black hole uint8_t waste[65070]; uint64_t trd=0; while((rd=recv(conn->fd, waste, sizeof(waste), MSG_DONTWAIT))>0 && trdtrd+=rd; } DBGPRINT("wasted recv=%zd all_rd=%" PRIu64 " err=%d\n",rd,trd,errno); return true; } // do not receive new until old is sent if (conn_has_unsent(conn->partner)) return true; bool oom=false; numbytes=conn_bytes_unread(conn); DBGPRINT("numbytes=%d\n",numbytes); if (numbytes>0) { DBGPRINT("%s leg fd=%d stream pos : %" PRIu64 "(n%" PRIu64 ")/%" PRIu64 "\n", conn->remote ? "remote" : "local", conn->fd, conn->trd,conn->tnrd+1,conn->twr); #ifdef SPLICE_PRESENT if (!params.nosplice && (!params.tamper || (conn->remote && conn->partner->track.bTamperInCutoff) || (!conn->remote && !in_tamper_out_range(conn)))) { // incoming data from remote leg we splice without touching // pipe is in the local leg, so its in conn->partner->splice_pipe // if we dont tamper - splice both legs rd = splice(conn->fd, NULL, conn->partner->splice_pipe[1], NULL, SPLICE_LEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); DBGPRINT("splice fd=%d remote=%d len=%d rd=%zd err=%d\n",conn->fd,conn->remote,SPLICE_LEN,rd,errno); if (rd<0 && errno==EAGAIN) rd=0; if (rd>0) { conn->tnrd++; conn->trd += rd; conn->partner->wr_unsent += rd; wr = splice(conn->partner->splice_pipe[0], NULL, conn->partner->fd, NULL, conn->partner->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); DBGPRINT("splice fd=%d remote=%d wr=%zd err=%d\n",conn->partner->fd,conn->partner->remote,wr,errno); if (wr<0 && errno==EAGAIN) wr=0; if (wr>0) { conn->partner->wr_unsent -= wr; conn->partner->twr += wr; } } } else #endif { // incoming data from local leg uint8_t buf[RD_BLOCK_SIZE + 6]; rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); DBGPRINT("recv fd=%d rd=%zd err=%d\n",conn->fd, rd,errno); if (rd<0 && errno==EAGAIN) rd=0; if (rd>0) { size_t multisplit_pos[MAX_SPLITS]; int multisplit_count; uint8_t split_flags; bs = rd; // tamper needs to know stream position of the block start tamper(conn, buf, sizeof(buf), &bs, multisplit_pos, &multisplit_count, &split_flags); // increase after tamper conn->tnrd++; conn->trd+=rd; if (multisplit_count) { ssize_t from,to,len; int i; bool bApplyDisorder, bApplyOOB; for (i=0,from=0;i<=multisplit_count;i++) { to = i==multisplit_count ? bs : multisplit_pos[i]; bApplyDisorder = !(i & 1) && ipartner->fd, params.fix_seg, &wasted); if (wasted) VPRINT("WARNING ! wasted %u ms to fix segmenation\n", wasted); if (!bWaitOK) report_segfail(); } else { if (socket_has_notsent(conn->partner->fd)) report_segfail(); } } #endif VPRINT("Sending multisplit part %d %zd-%zd (len %zd)%s%s : ", i+1, from, to, len, bApplyDisorder ? " with disorder" : "", bApplyOOB ? " with OOB" : ""); packet_debug(buf+from,len); wr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0); if (wr<0) break; conn->partner->twr += wr; if (wrpartner->wr_buf, conn->partner->fd, buf+from, bs-from, 0, 0); if (wr>0) conn->partner->twr += wr; break; } from = to; } } else { wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs, 0, 0); DBGPRINT("send_or_buffer(3) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); if (wr>0) conn->partner->twr += wr; } if (wr<0 && errno==ENOMEM) oom=true; } } if (!epoll_set_flow_pair(conn)) return false; } DBGPRINT("-handle_epoll rd=%zd wr=%zd\n",rd,wr); if (oom) DBGPRINT("handle_epoll: OUT_OF_MEMORY\n"); // do not fail if partner fails. // if partner fails there will be another epoll event with EPOLLHUP or EPOLLERR return rd>=0 && !oom; } static bool remove_closed_connections(int efd, struct tailhead *close_list) { tproxy_conn_t *conn = NULL; bool bRemoved = false; while ((conn = TAILQ_FIRST(close_list))) { TAILQ_REMOVE(close_list, conn, conn_ptrs); epoll_del(conn); VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) closed, connection removed. total_read=%" PRIu64 " total_write=%" PRIu64 " event_count=%u\n", conn->fd, conn->partner ? conn->partner->fd : 0, conn->remote, conn->trd, conn->twr, conn->event_count); if (conn->remote) legs_remote--; else legs_local--; free_conn(conn); bRemoved = true; } return bRemoved; } // move to close list connection and its partner static void close_tcp_conn(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) { if (conn->state != CONN_CLOSED) { conn->state = CONN_CLOSED; TAILQ_REMOVE(conn_list, conn, conn_ptrs); TAILQ_INSERT_TAIL(close_list, conn, conn_ptrs); } } static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) { if (conn_partner_alive(conn)) { int numbytes=conn_bytes_unread(conn); DBGPRINT("read_all_and_buffer(%d) numbytes=%d\n",buffer_number,numbytes); if (numbytes>0) { if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 6, 0, 0)) { ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT); if (rd>0) { conn->trd+=rd; conn->partner->wr_buf[buffer_number].len = rd; conn->partner->bFlowOut = true; tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+6, &conn->partner->wr_buf[buffer_number].len, NULL, NULL, NULL); if (epoll_update_flow(conn->partner)) return true; } send_buffer_free(conn->partner->wr_buf+buffer_number); } } } return false; } static bool conn_timed_out(tproxy_conn_t *conn) { if (conn->orphan_since && conn->state==CONN_UNAVAILABLE) { time_t timediff = time(NULL) - conn->orphan_since; return timediff>=params.max_orphan_time; } else return false; } static void conn_close_timed_out(struct tailhead *conn_list, struct tailhead *close_list) { tproxy_conn_t *c,*cnext = NULL; DBGPRINT("conn_close_timed_out\n"); c = TAILQ_FIRST(conn_list); while(c) { cnext = TAILQ_NEXT(c,conn_ptrs); if (conn_timed_out(c)) { DBGPRINT("closing timed out connection: fd=%d remote=%d\n",c->fd,c->remote); close_tcp_conn(conn_list,close_list,c); } c = cnext; } } static void conn_close_both(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) { if (conn_partner_alive(conn)) close_tcp_conn(conn_list,close_list,conn->partner); close_tcp_conn(conn_list,close_list,conn); } static void conn_close_with_partner_check(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) { close_tcp_conn(conn_list,close_list,conn); if (conn_partner_alive(conn)) { if (!conn_has_unsent(conn->partner)) close_tcp_conn(conn_list,close_list,conn->partner); else if (conn->partner->remote && conn->partner->state==CONN_UNAVAILABLE && params.max_orphan_time) // time out only remote legs that are not connected yet conn->partner->orphan_since = time(NULL); } } static bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list, int fd) { ssize_t rd; struct resolve_item *ri; bool b; rd = read(fd,&ri,sizeof(void*)); if (rd<0) { DLOG_PERROR("resolve_pipe read"); return false; } else if (rd!=sizeof(void*)) { // partial pointer read is FATAL. in any case it will cause pointer corruption and coredump DLOG_ERR("resolve_pipe not full read %zd\n",rd); exit(1000); } b = resolve_complete(ri, conn_list); *conn = (tproxy_conn_t *)ri->ptr; if (*conn) (*conn)->socks_ri = NULL; free(ri); return b; } int event_loop(const int *listen_fd, size_t listen_fd_ct) { int retval = 0, num_events = 0; int tmp_fd = 0; //Used to temporarily hold the accepted file descriptor tproxy_conn_t *conn = NULL; int efd=0, i; struct epoll_event ev, events[MAX_EPOLL_EVENTS]; struct tailhead conn_list, close_list; time_t tm,last_timeout_check=0; tproxy_conn_t *listen_conn = NULL; size_t sct; struct sockaddr_storage accept_sa; socklen_t accept_salen; int resolve_pipe[2]; if (!listen_fd_ct) return -1; resolve_pipe[0]=resolve_pipe[1]=0; legs_local = legs_remote = 0; //Initialize queue (remember that TAILQ_HEAD just defines the struct) TAILQ_INIT(&conn_list); TAILQ_INIT(&close_list); if ((efd = epoll_create(1)) == -1) { DLOG_PERROR("epoll_create"); return -1; } if (!(listen_conn=calloc(listen_fd_ct,sizeof(*listen_conn)))) { DLOG_PERROR("calloc listen_conn"); return -1; } //Start monitoring listen sockets memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; for(sct=0;sctevent_count++; if (conn->listener) { DBGPRINT("\nEVENT mask %08X fd=%d accept\n",events[i].events,conn->fd); accept_salen = sizeof(accept_sa); //Accept new connection #if defined (__APPLE__) // macos does not have accept4() tmp_fd = accept(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen); #else tmp_fd = accept4(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen, SOCK_NONBLOCK); #endif if (tmp_fd < 0) { DLOG_PERROR("Failed to accept connection"); } else if (legs_local >= params.maxconn) // each connection has 2 legs - local and remote { close(tmp_fd); VPRINT("Too many local legs : %d\n", legs_local); } #if defined (__APPLE__) // separate fcntl call to comply with macos else if (fcntl(tmp_fd, F_SETFL, O_NONBLOCK) < 0) { DLOG_PERROR("socket set O_NONBLOCK (accept)"); close(tmp_fd); } #endif else if (!(conn=add_tcp_connection(efd, &conn_list, tmp_fd, (struct sockaddr*)&accept_sa, params.port, params.proxy_type))) { // add_tcp_connection closes fd in case of failure VPRINT("Failed to add connection\n"); } else { print_legs(); if (params.debug>=1) { char ip_port[48]; ntop46_port((struct sockaddr*)&conn->client,ip_port,sizeof(ip_port)); VPRINT("Socket fd=%d (local) connected from %s\n", conn->fd, ip_port); } set_user_timeout(conn->fd, params.tcp_user_timeout_local); } } else { DBGPRINT("\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); if (conn->state != CONN_CLOSED) { if (events[i].events & (EPOLLHUP|EPOLLERR)) { int errn = get_so_error(conn->fd); const char *se; switch (events[i].events & (EPOLLHUP|EPOLLERR)) { case EPOLLERR: se="EPOLLERR"; break; case EPOLLHUP: se="EPOLLHUP"; break; case EPOLLHUP|EPOLLERR: se="EPOLLERR EPOLLHUP"; break; default: se=NULL; } VPRINT("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)); proxy_remote_conn_ack(conn,errn); read_all_and_buffer(conn,3); if (errn==ECONNRESET && conn_partner_alive(conn)) { if (conn->remote && params.tamper) rst_in(&conn->partner->track,(struct sockaddr*)&conn->partner->client); struct linger lin; lin.l_onoff=1; lin.l_linger=0; DBGPRINT("setting LINGER=0 to partner to force mirrored RST close\n"); if (setsockopt(conn->partner->fd,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))<0) DLOG_PERROR("setsockopt (SO_LINGER)"); } conn_close_with_partner_check(&conn_list,&close_list,conn); continue; } if (events[i].events & EPOLLOUT) { if (!check_connection_attempt(conn, efd)) { VPRINT("Connection attempt failed for fd=%d\n", conn->fd); conn_close_both(&conn_list,&close_list,conn); continue; } } if (events[i].events & EPOLLRDHUP) { DBGPRINT("EPOLLRDHUP\n"); read_all_and_buffer(conn,2); if (!conn->remote && params.tamper) hup_out(&conn->track,(struct sockaddr*)&conn->client); conn->state = CONN_RDHUP; // only writes. do not receive RDHUP anymore if (conn_has_unsent(conn)) { DBGPRINT("conn fd=%d has unsent\n", conn->fd); epoll_set_flow(conn,false,true); } else { DBGPRINT("conn fd=%d has no unsent\n", conn->fd); conn->bFlowIn = false; epoll_update_flow(conn); if (conn_partner_alive(conn)) { if (conn_has_unsent(conn->partner)) DBGPRINT("partner has unset. partner shutdown delayed.\n"); else { DBGPRINT("partner has no unsent. shutting down partner.\n"); if (!conn_shutdown(conn->partner)) { DBGPRINT("emergency connection close due to failed shutdown\n"); conn_close_with_partner_check(&conn_list,&close_list,conn); } if (conn->partner->state==CONN_RDHUP) { DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); conn_close_with_partner_check(&conn_list,&close_list,conn); } } } else { DBGPRINT("partner is absent or not alive. closing.\n"); close_tcp_conn(&conn_list,&close_list,conn); } } continue; } if (events[i].events & (EPOLLIN|EPOLLOUT)) { const char *se; switch (events[i].events & (EPOLLIN|EPOLLOUT)) { case EPOLLIN: se="EPOLLIN"; break; case EPOLLOUT: se="EPOLLOUT"; break; case EPOLLIN|EPOLLOUT: se="EPOLLIN EPOLLOUT"; break; default: se=NULL; } if (se) DBGPRINT("%s\n",se); // will not receive this until successful check_connection_attempt() if (!handle_epoll(conn, &conn_list, events[i].events)) { DBGPRINT("handle_epoll false\n"); conn_close_with_partner_check(&conn_list,&close_list,conn); continue; } if ((conn->state == CONN_RDHUP) && conn_partner_alive(conn) && !conn->partner->bShutdown && !conn_has_unsent(conn)) { DBGPRINT("conn fd=%d has no unsent. shutting down partner.\n", conn->fd); if (!conn_shutdown(conn->partner)) { DBGPRINT("emergency connection close due to failed shutdown\n"); conn_close_with_partner_check(&conn_list,&close_list,conn); continue; } } } } } } tm = time(NULL); if (last_timeout_check!=tm) { // limit whole list lookups to once per second last_timeout_check=tm; conn_close_timed_out(&conn_list,&close_list); } if (remove_closed_connections(efd, &close_list)) { // at least one leg was removed. recount legs print_legs(); } } ex: if (efd) close(efd); free(listen_conn); resolver_deinit(); if (resolve_pipe[0]) close(resolve_pipe[0]); if (resolve_pipe[1]) close(resolve_pipe[1]); return retval; } ================================================ FILE: tpws/tpws_conn.h ================================================ #pragma once #include #include #include #include #include "tamper.h" #include "params.h" #include "resolver.h" #define BACKLOG 10 #define MAX_EPOLL_EVENTS 64 #define IP_TRANSPARENT 19 //So that application compiles on OpenWRT #define SPLICE_LEN 65536 #define DEFAULT_MAX_CONN 512 #define DEFAULT_MAX_ORPHAN_TIME 5 #define DEFAULT_TCP_USER_TIMEOUT_LOCAL 10 #define DEFAULT_TCP_USER_TIMEOUT_REMOTE 20 int event_loop(const int *listen_fd, size_t listen_fd_ct); //Three different states of a connection enum{ CONN_UNAVAILABLE=0, // connecting CONN_AVAILABLE, // operational CONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked CONN_CLOSED // will be deleted soon }; typedef uint8_t conn_state_t; // data in a send_buffer can be sent in several stages // pos indicates size of already sent data // when pos==len its time to free buffer struct send_buffer { uint8_t *data; size_t len,pos; int ttl, flags; }; typedef struct send_buffer send_buffer_t; enum{ CONN_TYPE_TRANSPARENT=0, CONN_TYPE_SOCKS }; typedef uint8_t conn_type_t; struct tproxy_conn { bool listener; // true - listening socket. false = connecion socket bool remote; // false - accepted, true - connected int efd; // epoll fd int fd; int splice_pipe[2]; conn_state_t state; conn_type_t conn_type; sockaddr_in46 client, dest; // ip:port of client, ip:port of target struct tproxy_conn *partner; // other leg time_t orphan_since; // socks5 state machine enum { S_WAIT_HANDSHAKE=0, S_WAIT_REQUEST, S_WAIT_RESOLVE, S_WAIT_CONNECTION, S_TCP } socks_state; uint8_t socks_ver; struct resolve_item *socks_ri; // these value are used in flow control. we do not use ET (edge triggered) polling // if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time bool bFlowIn,bFlowOut, bShutdown, bFlowInPrev,bFlowOutPrev, bPrevRdhup; // total read,write uint64_t trd,twr, tnrd; // number of epoll_wait events unsigned int event_count; // connection is either spliced or send/recv // spliced connection have pipe buffering but also can have send_buffer's // pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1 // send/recv connection do not have pipe and wr_unsent is meaningless, always 0 ssize_t wr_unsent; // unsent bytes in the pipe // buffer 0 : send before split_pos // buffer 1 : send after split_pos // buffer 2 : after RDHUP read all and buffer to the partner // buffer 3 : after HUP read all and buffer to the partner // (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL) // all buffers are sent strictly from 0 to countof(wr_buf)-1 // buffer cannot be sent if there is unsent data in a lower buffer struct send_buffer wr_buf[4]; t_ctrack track; //Create the struct which contains ptrs to next/prev element TAILQ_ENTRY(tproxy_conn) conn_ptrs; }; typedef struct tproxy_conn tproxy_conn_t; //Define the struct tailhead (code in sys/queue.h is quite intuitive) //Use tail queue for efficient delete TAILQ_HEAD(tailhead, tproxy_conn); bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); ================================================ FILE: tpws/uthash.h ================================================ /* Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ ================================================ FILE: uninstall_easy.sh ================================================ #!/bin/sh # automated script for easy uninstalling zapret EXEDIR="$(dirname "$0")" EXEDIR="$(cd "$EXEDIR"; pwd)" ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" IPSET_DIR="$ZAPRET_BASE/ipset" [ -f "$ZAPRET_CONFIG" ] || { ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" } . "$ZAPRET_CONFIG" . "$ZAPRET_BASE/common/base.sh" . "$ZAPRET_BASE/common/elevate.sh" . "$ZAPRET_BASE/common/fwtype.sh" . "$ZAPRET_BASE/common/dialog.sh" . "$ZAPRET_BASE/common/ipt.sh" . "$ZAPRET_BASE/common/nft.sh" . "$ZAPRET_BASE/common/pf.sh" . "$ZAPRET_BASE/common/installer.sh" remove_systemd() { clear_ipset service_stop_systemd service_remove_systemd timer_remove_systemd nft_del_table crontab_del } remove_openrc() { clear_ipset service_remove_openrc nft_del_table crontab_del } remove_linux() { INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" clear_ipset echo \* executing sysv init stop "$INIT_SCRIPT_SRC" stop nft_del_table crontab_del echo echo '!!! WARNING. YOUR UNINSTALL IS INCOMPLETE !!!' echo 'you must manually remove zapret auto start from your system' } remove_openwrt() { OPENWRT_FW_INCLUDE=/etc/firewall.zapret clear_ipset service_remove_sysv remove_openwrt_firewall remove_openwrt_iface_hook nft_del_table restart_openwrt_firewall crontab_del remove_extra_pkgs_openwrt echo echo to fully remove zapret : rm -r \"$ZAPRET_BASE\" } remove_macos() { remove_macos_firewall service_remove_macos crontab_del } fix_sbin_path check_system require_root [ "$SYSTEM" = "macos" ] && . "$EXEDIR/init.d/macos/functions" case $SYSTEM in systemd) remove_systemd ;; openrc) remove_openrc ;; linux) remove_linux ;; openwrt) remove_openwrt ;; macos) remove_macos ;; esac exitp 0