commit ecdaed3dcc918942f89453b4a76bc6690b16bfea Author: bol-van Date: Fri Nov 21 22:49:11 2025 +0300 start diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5350ea1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto eol=lf +*.cmd eol=crlf +*.bat eol=crlf +init.d/windivert.filter.examples/** eol=crlf +files/** binary diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3d13ac9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,569 @@ +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/**' + # - 'nfq2/**' + +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: arm-unknown-linux-musleabi + # - arch: armhf + # tool: arm-unknown-linux-musleabihf + # - arch: armv7 + # tool: armv7-unknown-linux-musleabi + # - arch: armv7hf + # tool: armv7-unknown-linux-musleabihf + # - arch: mips64el + # tool: mips64el-unknown-linux-musl + - arch: mips64 + tool: mips64-unknown-linux-musl + # - arch: mipsel + # tool: mipsel-unknown-linux-musl + - arch: mipselsf + tool: mipsel-unknown-linux-muslsf + # - arch: mips + # tool: mips-unknown-linux-musl + - arch: mipssf + tool: mips-unknown-linux-muslsf + # - arch: ppc64 + # tool: powerpc64-unknown-linux-musl + - 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: zapret2 + + - name: Set up build tools + env: + ARCH: ${{ matrix.arch }} + TOOL: ${{ matrix.tool }} + REPO: ${{ matrix.arch == 'lexra' && matrix.repo || 'spvkgn/musl-cross' }} + DIR: ${{ matrix.arch == 'lexra' && matrix.dir || matrix.tool }} + run: | + sudo dpkg --add-architecture i386 + sudo apt update -qq + if [[ "$ARCH" == lexra ]]; then + sudo apt install -y libcap-dev libc6:i386 zlib1g:i386 + URL=https://github.com/$REPO/raw/refs/heads/master/$DIR.txz + else + # luajit buildvm requires 32 bit executable on host platform for 32 bit cross targets + sudo apt install -y libcap-dev libc6-dev gcc-multilib + 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 }} + LUA_VER: 5.4 + LUA_RELEASE: 5.4.8 + LUAJIT_VER: 2.1 + LUAJIT_RELEASE: 2.1-20250826 + LUAJIT_LUAVER: 5.1 + 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 + + if [[ "$ARCH" == lexra ]] || [[ "$ARCH" == ppc ]]; then + # use classic lua + wget -qO- https://www.lua.org/ftp/lua-${LUA_RELEASE}.tar.gz | tar -xz + ( + cd lua-${LUA_RELEASE} + make CC=$CC CFLAGS="-Os -flto=auto $CFLAGS" linux -j$(nproc) + make install INSTALL_TOP=$DEPS_DIR INSTALL_BIN=$DEPS_DIR/bin INSTALL_INC=$DEPS_DIR/include/lua${LUA_VER} INSTALL_LIB=$DEPS_DIR/lib + ) + LJIT=0 + LCFLAGS="-I${DEPS_DIR}/include/lua${LUA_VER}" + LLIB="-L${DEPS_DIR}/lib -llua" + else + # luajit + wget -qO- https://github.com/openresty/luajit2/archive/refs/tags/v${LUAJIT_RELEASE}.tar.gz | tar -xz + case "$ARCH" in + *64*) + HOSTCC="cc" + ;; + *) + HOSTCC="cc -m32" + esac + ( + cd luajit2-* + make BUILDMODE=static HOST_CC="$HOSTCC" CROSS= CC="$CC" TARGET_AR="$AR rcus" TARGET_STRIP=$STRIP CFLAGS="-Os -s -flto=auto $CFLAGS" -j$(nproc) + make install PREFIX= DESTDIR=$DEPS_DIR + ) + LJIT=1 + LCFLAGS="-I${DEPS_DIR}/include/luajit-${LUAJIT_VER}" + LLIB="-L${DEPS_DIR}/lib -lluajit-${LUAJIT_LUAVER}" + fi + + # 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="-Os -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="-Os -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 + + # zapret2 + CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -static-libgcc -static -I$DEPS_DIR/include $CFLAGS" \ + LDFLAGS="-L$DEPS_DIR/lib $LDFLAGS" \ + make -C zapret2 LUA_JIT=$LJIT LUA_CFLAGS="$LCFLAGS" LUA_LIB="$LLIB" -j$(nproc) + + tar -C zapret2/binaries/my -cJf zapret2-linux-$ARCH.tar.xz . + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: zapret2-linux-${{ matrix.arch }} + path: zapret2-*.tar.xz + 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: zapret2 + + - name: Set up build tools + run: | + sudo dpkg --add-architecture i386 + sudo apt update -qq + # luajit buildvm requires 32 bit executable on host platform for 32 bit cross targets + sudo apt install -y gcc-multilib + + - name: Build + env: + ABI: ${{ matrix.abi }} + API: 21 + TARGET: ${{ matrix.target }} + GH_TOKEN: ${{ github.token }} + LUAJIT_VER: 2.1 + LUAJIT_RELEASE: 2.1-20250826 + LUAJIT_LUAVER: 5.1 + 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 + + # luajit + wget -qO- https://github.com/openresty/luajit2/archive/refs/tags/v${LUAJIT_RELEASE}.tar.gz | tar -xz + case "$ABI" in + *64*) + HOSTCC="cc" + ;; + *) + HOSTCC="cc -m32" + esac + ( + cd luajit2-* + make BUILDMODE=static HOST_CC="$HOSTCC" CROSS= CC="$CC" TARGET_AR="$AR rcus" TARGET_STRIP=$STRIP CFLAGS="-Os -flto=auto $CFLAGS" -j$(nproc) + make install PREFIX= DESTDIR=$DEPS_DIR + ) + LJIT=1 + LCFLAGS="-I${DEPS_DIR}/include/luajit-${LUAJIT_VER}" + LLIB="-L${DEPS_DIR}/lib -lluajit-${LUAJIT_LUAVER}" + + # 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 ../zapret2/.github/workflows/libnetfilter_queue-android.patch + + for i in libmnl libnfnetlink libnetfilter_queue ; do + ( + cd $i-* + CFLAGS="-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 + + # zapret2 + CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -I$DEPS_DIR/include" \ + LDFLAGS="-L$DEPS_DIR/lib" \ + make -C zapret2 LUA_JIT=$LJIT LUA_CFLAGS="$LCFLAGS" LUA_LIB="$LLIB" -j$(nproc) android + + # 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 zapret2/binaries/my/* + zip zapret2-android-$ABI.zip -j zapret2/binaries/my/* + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: zapret2-android-${{ matrix.abi }} + path: zapret2-*.zip + if-no-files-found: error + + + build-freebsd: + name: FreeBSD ${{ matrix.arch }} + runs-on: ubuntu-latest + env: + DEPS_DIR: /workdir/deps + LUAJIT_VER: 2.1 + LUAJIT_RELEASE: 2.1-20250826 + LUAJIT_LUAVER: 5.1 + 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 xz tar build-base + + - name: Build luajit + env: + TARGET: ${{ matrix.target }} + ARCH: ${{ matrix.arch }} + CC: ${{ matrix.target }}-freebsd11-clang + run: | + + wget -qO- https://github.com/openresty/luajit2/archive/refs/tags/v${LUAJIT_RELEASE}.tar.gz | tar -xz + ( + cd luajit2-* + make BUILDMODE=static HOST_CC=gcc CC=$CC CFLAGS="-Os -flto=auto $CFLAGS" + make install PREFIX= DESTDIR=$DEPS_DIR + ) + + - name: Build zapret2 + env: + TARGET: ${{ matrix.target }} + ARCH: ${{ matrix.arch }} + CC: ${{ matrix.target }}-freebsd11-clang + LJIT: 1 + run: | + export CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }}" + + LCFLAGS="-I${DEPS_DIR}/include/luajit-${LUAJIT_VER}" + LLIB="-L${DEPS_DIR}/lib -lluajit-${LUAJIT_LUAVER}" + settarget $TARGET-freebsd11 + + make CC=$CC LUA_JIT=$LJIT LUA_CFLAGS="$LCFLAGS" LUA_LIB="$LLIB" bsd -j$(nproc) + tar -C binaries/my -cJf zapret2-freebsd-$ARCH.tar.xz . + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: zapret2-freebsd-${{ matrix.arch }} + path: zapret2-*.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: zapret2 + + - 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 zapret2 + 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 luajit + env: + LUAJIT_RELEASE: 2.1-20250826 + shell: C:\cygwin\bin\bash.exe -eo pipefail '{0}' + run: >- + export MAKEFLAGS=-j$(nproc) && + wget -q https://github.com/openresty/luajit2/archive/refs/tags/v${LUAJIT_RELEASE}.tar.gz && + tar -xzf v${LUAJIT_RELEASE}.tar.gz && + rm -f v${LUAJIT_RELEASE}.tar.gz && + make -C luajit2-${LUAJIT_RELEASE} BUILDMODE=static CFLAGS="-Os -s" && + make -C luajit2-${LUAJIT_RELEASE} install + + - 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 zapret2 && + make -C nfq2 ${TARGET} && + cp -a nfq2/winws2.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 zapret2-win-${{ matrix.arch }}.zip -j output/* + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: zapret2-win-${{ matrix.arch }} + path: zapret2-*.zip + if-no-files-found: error + + + release: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + needs: [ build-linux, build-windows, build-freebsd, build-android ] + permissions: + contents: write + runs-on: ubuntu-latest + env: + repo_dir: zapret2-${{ 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: zapret2-* + + - 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 ]] && [[ $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 ;; + *-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 init.d/{openrc,pfsense,runit,s6,systemd} \ + nfq2 ip2net mdig docs 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: | + ### zapret2 ${{ github.ref_name }} + files: | + zapret2*.tar.gz + zapret2*.zip + sha256sum.txt diff --git a/.github/workflows/libnetfilter_queue-android.patch b/.github/workflows/libnetfilter_queue-android.patch new file mode 100644 index 0000000..a0ce64b --- /dev/null +++ b/.github/workflows/libnetfilter_queue-android.patch @@ -0,0 +1,41 @@ +--- 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..407170e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/config +ip2net/ip2net +mdig/mdig +nfq2/dvtws2 +nfq2/nfqws2 +nfq2/winws2.exe +nfq2/WinDivert* +tpws/tpws +binaries/my/ +ipset/zapret-ip*.txt +ipset/zapret-ip*.gz +ipset/zapret-hosts*.txt +ipset/zapret-hosts*.gz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dc93b2e --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +DIRS := nfq2 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 + +clean: + @[ -d "$(TGT)" ] && rm -rf "$(TGT)" ; \ + for dir in $(DIRS); do \ + $(MAKE) -C "$$dir" clean; \ + done diff --git a/common/base.sh b/common/base.sh new file mode 100644 index 0000000..5ed6ec3 --- /dev/null +++ b/common/base.sh @@ -0,0 +1,451 @@ +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=: + 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" +} + +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() +{ + NFQWS2_PORTS_TCP_IPT=$(replace_char - : $NFQWS_PORTS_TCP) + NFQWS2_PORTS_TCP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_TCP_KEEPALIVE) + NFQWS2_PORTS_UDP_IPT=$(replace_char - : $NFQWS_PORTS_UDP) + NFQWS2_PORTS_UDP_KEEPALIVE_IPT=$(replace_char - : $NFQWS_PORTS_UDP_KEEPALIVE) +} + +has_bad_ws_options() +{ + # $1 - nfqws2 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 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" +} diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt new file mode 100644 index 0000000..3972b1f --- /dev/null +++ b/docs/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2025 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. diff --git a/docs/compile/build_howto_openwrt.txt b/docs/compile/build_howto_openwrt.txt new file mode 100644 index 0000000..cfc3b3f --- /dev/null +++ b/docs/compile/build_howto_openwrt.txt @@ -0,0 +1,107 @@ +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 luajit + +4) If you need static build edit `feeds/packages/lang/luajit/Makefile` + +change BUILDMODE from "dynamic" to "mixed" + +add '$(CP) $(PKG_INSTALL_DIR)/usr/lib/*a $(1)/usr/lib/' +after '$(CP) $(PKG_INSTALL_DIR)/usr/lib/*so* $(1)/usr/lib/' + +should look like : + +define Build/Compile + $(MAKE) $(PKG_JOBS) -C $(PKG_BUILD_DIR) \ + HOST_CC="$(HOSTCC) $(HOST_CFLAGS) $(HOST_BITS)" \ + CROSS="$(TARGET_CROSS)" \ + DPREFIX=$(PKG_INSTALL_DIR)/usr \ + PREFIX=/usr \ + TARGET_SYS=Linux \ + TARGET_CFLAGS="$(TARGET_CFLAGS)" \ + BUILDMODE=mixed + rm -rf $(PKG_INSTALL_DIR) + mkdir -p $(PKG_INSTALL_DIR) + $(MAKE) -C $(PKG_BUILD_DIR) \ + DPREFIX=$(PKG_INSTALL_DIR)/usr \ + PREFIX=/usr \ + TARGET_SYS=Linux \ + install +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include/luajit-2.1 + $(CP) $(PKG_INSTALL_DIR)/usr/include/luajit-2.1/*.{h,hpp} $(1)/usr/include/luajit-2.1 + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/*so* $(1)/usr/lib/ + $(CP) $(PKG_INSTALL_DIR)/usr/lib/*a $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/pkgconfig + $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/luajit.pc $(1)/usr/lib/pkgconfig/ + $(CP) $(PKG_INSTALL_DIR)/usr/bin/luajit-$(PKG_VERSION) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) +endef + +5) Prepare openwrt package definitions + +cp -R /opt/zapret2/docs/compile/openwrt/. . +cp -R /opt/zapret2/nfq2 package/zapret/nfqws2 +cp -R /opt/zapret2/mdig package/zapret/mdig +cp -R /opt/zapret2/ip2net package/zapret/ip2net + +6) Prepare .config + +make defconfig + +If you only need bins without packages comment 'CONFIG_AUTOREMOVE=y' line in .config +Change 'y' to 'n' in Config.in + +should look like : + +config AUTOREMOVE + bool "Automatic removal of build directories" + default n + +and in Config-build.in +should look like : + +config AUTOREMOVE + bool + default n + +7) Compile + +dynamic build : make package/{nfqws2,mdig,ip2net}/compile +static build : make CFLAGS=-static LDFLAGS=-lgcc_eh package/{nfqws2,mdig,ip2net}/compile + +8) Get result + +executables only : build_dir/target/ +ipk or apk packages : bin/packages/*/base + +9) Installing to openwrt to use with zapret + +zapret with or without binaries should be already installed in /opt/zapret2. +Install ipk's or apk's with all compiled progs using opkg or apk. +Bins are placed to /opt/zapret2/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. diff --git a/docs/compile/build_howto_unix.txt b/docs/compile/build_howto_unix.txt new file mode 100644 index 0000000..dcee076 --- /dev/null +++ b/docs/compile/build_howto_unix.txt @@ -0,0 +1,16 @@ +debian,ubuntu : + +apt install make gcc zlib1g-dev libcap-dev libnetfilter-queue-dev libmnl-dev libsystemd-dev libluajit2-5.1-dev +make -C /opt/zapret2 systemd + +FreeBSD : + +pkg search luajit-2 +# see what's the version available +pkg install luajit-2.1.0.20250728 +make -C /opt/zapret2 + +OpenBSD : + +pkg_add luajit gmake bsd +gmake -C /opt/zapret2 diff --git a/docs/compile/build_howto_windows.txt b/docs/compile/build_howto_windows.txt new file mode 100644 index 0000000..f85ff82 --- /dev/null +++ b/docs/compile/build_howto_windows.txt @@ -0,0 +1,36 @@ +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) install and compile luajit from here : https://github.com/openresty/luajit2 + +download latest releast, unpack, cd to it's directory + +make BUILDMODE=static CFLAGS="-Os" +make install + +5) cd to %ZAPRET_BASE%/nfq + +cd C:/Users/user/Downloads/zapret2/nfq + +6) Compile nfqws2 + +make cygwin64 + +use winws2.exe + +7) 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. + +8) Copy cygwin1.dll, winws2.exe, windivert.dll and windivert64.sys to one folder. + +9) Run winws2.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. diff --git a/docs/compile/openwrt/package/zapret/ip2net/Makefile b/docs/compile/openwrt/package/zapret/ip2net/Makefile new file mode 100644 index 0000000..bec7c00 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/ip2net/Makefile @@ -0,0 +1,32 @@ +# + +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)) + diff --git a/docs/compile/openwrt/package/zapret/ip2net/readme.txt b/docs/compile/openwrt/package/zapret/ip2net/readme.txt new file mode 100644 index 0000000..a5674f5 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/ip2net/readme.txt @@ -0,0 +1 @@ +Copy "ip2net" folder here ! diff --git a/docs/compile/openwrt/package/zapret/mdig/Makefile b/docs/compile/openwrt/package/zapret/mdig/Makefile new file mode 100644 index 0000000..e587539 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/mdig/Makefile @@ -0,0 +1,32 @@ +# + +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)) + diff --git a/docs/compile/openwrt/package/zapret/mdig/readme.txt b/docs/compile/openwrt/package/zapret/mdig/readme.txt new file mode 100644 index 0000000..b1865b2 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/mdig/readme.txt @@ -0,0 +1 @@ +Copy "mdig" folder here ! diff --git a/docs/compile/openwrt/package/zapret/nfqws2/Makefile b/docs/compile/openwrt/package/zapret/nfqws2/Makefile new file mode 100644 index 0000000..8972229 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws2/Makefile @@ -0,0 +1,46 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=nfqws2 +PKG_RELEASE:=1 + +LUA_JIT?=1 + +ifeq ($(LUA_JIT),1) + LUAJIT_VER?=2.1 + LUA_VER?=5.1 + LUA_DEP:=luajit + #LUA_DEP:=luajit2 + LUA_INCLUDE:=-I$(STAGING_DIR)/usr/include/luajit-$(LUAJIT_VER) + LUA_LIBRARY:=-L$(STAGING_DIR)/usr/lib -lluajit-$(LUA_VER) +else + LUA_VER?=5.3 + LUA_DEP:=lua$(LUA_VER) + LUA_INCLUDE:=-I$(STAGING_DIR)/usr/include/lua$(LUA_VER) + LUA_LIBRARY:=-L$(STAGING_DIR)/usr/lib -llua$(LUA_VER) +endif + +include $(INCLUDE_DIR)/package.mk + +define Package/nfqws2 + SECTION:=net + CATEGORY:=Network + TITLE:=nfqws2 + SUBMENU:=Zapret + DEPENDS:=+libnetfilter-queue +lmnl +libcap +zlib +$(LUA_DEP) +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) LUA_CFLAGS="$(LUA_INCLUDE)" LUA_LIB="$(LUA_LIBRARY)" +endef + +define Package/nfqws2/install + $(INSTALL_DIR) $(1)/opt/zapret/binaries/my + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws2 $(1)/opt/zapret/binaries/my +endef + +$(eval $(call BuildPackage,nfqws2)) diff --git a/docs/compile/openwrt/package/zapret/nfqws2/readme.txt b/docs/compile/openwrt/package/zapret/nfqws2/readme.txt new file mode 100644 index 0000000..4c5b89f --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws2/readme.txt @@ -0,0 +1 @@ +Copy "nfq" folder here ! diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..75688f9 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,290 @@ +# zapret2 v0.1 + +## Зачем это нужно + +Автономное средство противодействия DPI, которое не требует подключения каких-либо сторонних серверов. Может помочь +обойти блокировки или замедление сайтов HTTP(S), сигнатурный анализ TCP и UDP протоколов, например, с целью блокировки +VPN. Может использоваться для частичной прозрачной обфускации протоколов. + +Проект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под OpenWrt. Поддерживаются +традиционные Linux-системы, FreeBSD, OpenBSD, Windows. В некоторых случаях возможна самостоятельная прикрутка +решения к различным прошивкам. + +## Чем это отличается от zapret1 + +zapret2 является дальнейшим развитием проекта zapret. +Проблема его основной части *nfqws1* в том, что он перегружен опциями и в условиях нарастающего противостояния регулятора и пользователей +не обеспечивает достаточную гибкость воздействия на трафик. +Обход DPI требует все более тонких и специфических воздействий, которые меняются со временем, а старые перестают работать. + +Стратегии - это программы, управляющие сценарием атаки на DPI. В *nfqws1* они зашиваются в C код. Написание C кода - занятие нелегкое, +требующее достаточной квалификации разработчика и времени. + +Цель *nfqws2* - сделать так, чтобы программы стратегий мог написать любой человек, владеющий знаниями в области сетей, понимающий уязвимости DPI +или хотя бы область , в которой их можно искать, плюс владеющий базовыми навыками программирования. + +*nfqws2* оставляет в себе практически тот же функционал - распознавание протоколов, реассемблинг, дешифровка, управление профилями, хостлисты, ipset-ы, базовая фильтрация. +Но он полностью лишается возможностей самостоятельно воздействовать на трафик. Часть "дурения" переносится в скриптовой язык программирования LUA. + +LUA код получает от C кода структурированное представление приходящих пакетов в виде дерева (диссекты), подобного тем, что вы видите в wireshark. +Туда же приходят результаты сборки или дешифровки частей некоторых протоколов (tls, quic). +С код предоставляет функции-хелперы, позволяющие отсылать пакеты, работать с двоичными данными, разбирать TLS, искать маркер-позции и т.д. +Имеется библиотека хелперов, написанных на LUA, а так же готовая библиотека программ атаки на DPI (стратегий), реализующая функции *nfqws1* в расширенном варианте +и с большей гибкостью. + +Вы всегда сможете взять и дописать что-то свое. В этом и есть смысл, чтобы борьбой с DPI смог заняться любой, кто разбирается в пакетах. +Мог "потыкать" его, проверить свои идеи. А потом поделиться с друзьями своим решением "одного клика". +zapret2 - инструмент для таких энтузиастов. Но это не готовое решение для чайников. Проект не ставит себе целью сделать все простым для всех. +Автор считает, что это невозможно в принципе по обьективным причинам. + + +## С чего начать + +Хотелось бы избежать "талмуда" на главной странице. Поэтому начнем со способа запуска *nfqws2* и описания способов портирования стратегий *nfqws1* - как в *nfqws2* сделать то же самое, что можно было в *nfqws1*. +Когда вы поймете как это работает, вы можете посмотреть LUA код, находящийся "под капотом". Разобрать как он работает, попробовать написать что-то свое. +"талмуд" обязательно будет, как он есть у любых более-менее сложных проектов. Он нужен как справочник. + +### Механика обработки трафика + +Изначально сетевой трафик в любой ОС появляется в ядре. Первая задача - извлечь его оттуда и перенаправить на процесс *nfqws2*. +Эта задача решается в Linux с помощью iptables и nftables, в BSD - ipfw и pf, в Windows - windivert. +Процесс перенаправления трафика из ядра отнимает достаточно много ресурсов, поэтому лучше всего отфильтровать как можно больше прямо в нем. + +Для экспериментов на Linux можно начать со следующих nftables, которые перенаправят начальные пакеты соединений на порты tcp 80,443 и udp 443 в очередь NFQUEUE с номером 200. + +``` +nft delete table inet ztest +nft create table inet ztest +nft add chain inet ztest post "{type filter hook postrouting priority 101;}" +nft add rule inet ztest post meta mark and 0x40000000 == 0 tcp dport "{80,443}" ct original packets 1-12 queue num 200 bypass +nft add rule inet ztest post meta mark and 0x40000000 == 0 udp dport "{443}" ct original packets 1-12 queue num 200 bypass + +sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 +nft add chain inet ztest pre "{type filter hook prerouting priority -101;}" +nft add rule inet ztest pre meta mark and 0x40000000 == 0 tcp sport "{80,443}" ct reply packets 1-12 queue num 200 bypass +nft add rule inet ztest pre meta mark and 0x40000000 == 0 udp sport "{443}" ct reply packets 1-12 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" +``` + +В windows функция перехвата вшита прямо в код движка для windows, который называется *winws2*. Он использует драйвер windivert. +Для перехвата портов целиком используются параметры `--wf-tcp-in`, `--wf-tcp-out`, `--wf-udp-in`, `--wf-udp-out`. +Они относятся к протоколам tcp или udp, к входящим или исходящим пакетам. Например, `--wf-tcp-out=80,443`. +Для более точного перехвата пишутся фильтры на языке фильтров windivert. Он похож на язык фильтров tcpdump или wireshark. +Фильтры отдаются *winws2* в параметрах `--wf-raw-part`. Конструктор фильтров обьединяет все указанные опции перехвата в +единый raw фильтр и запускает перехват windivert. + +К сожалению, самый болезненный недостаток windivert (а так же BSD ipfw и pf) - отсутствие ограничителя на номер пакета в соединении (connbytes в iptables, ct packets в nftables). +windivert вообще не отслеживает соединения. Поэтому если перехватывать порт целиком, то все соединение по указанному направлению +пойдет на перехват, что нелегко для процессора, если там передаются многие мегабайты. +Поэтому по возможности пишите собственные фильтры windivert, проверяющие тип пейлоада хотя бы частично. Дофильтрацию может выполнить *winws2*. + +Дальше под рутом нужно запустить *nfqws2* с параметрами командной строки. Они строятся примерно следующим образом : + +``` +nfqws2 --qnum 200 --debug --lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua \ + --filter-tcp=80,443 --filter-l7=tls,http \ + --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \ + --payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5 \ + --payload=tls_client_hello,http_req --lua-desync=multisplit:pos=1:seqovl=5:seqovl_pattern=0x1603030000 +``` + +Данный пример предполагает, что в той же директории находятся файлы `zapret-lib.lua` - библиотека хелперов на LUA и `zapret-antidpi.lua` - библиотека базовых стратегий. +`--lua-init` может содержать LUA код в виде строки. Так удобно писать простой код, например присвоить константу переменной, чтобы не создавать файлы ради этой мелочи. +Либо подцепляется файл, если значение параметра начинается с `@`. Код из `--lua-init` выполняется 1 раз при старте. + +Далее указаны параметры `--lua-desync`. Они содержат имя LUA функции, вызываемой при обработке каждого пакета, проходящего через профиль мульистратегии. +После двоеточия и через двоеточия следуют параметры для данной функции в формате `param[=value]`. В примере реализована стратегия + +``` +nfqws --qnum 200 --debug \ +--filter-tcp=80,443 --filter-l7=tls,http \ + --dpi-desync=fake,multisplit --dpi-desync-fooling=md5sig --dpi-desync-split-pos=1,midsld \ + --dpi-desync-split-seqovl=5 --dpi-desync-split-seqovl-pattern=0x1603030000 \ + --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid +``` + +Что сразу заметно - это наличие понятия "payload". В *nfqws1* были только протоколы соединения, которые участвовали в фильтрации профилей. +Они так же остались в *nfqws2*, но введено другое понятие - тип пейлоада. Пейлоад - это содержание текущего пакета. +Тип пейлоада - тип данных, содержащихся в пакете или группе пакетов. Например, протокол соединения может быть tls, а пейлоады - tls_client_hello, tls_server_hello, unknown. + +Другое важное отличие - отсутствие жестко определенных фаз десинхронизации. То, что вы раньше писали как `fake,multisplit` реализуется двумя +последовательно вызываемыми LUA функциями. Их может быть столько, сколько нужно, учитывая логику прохождения пакетов и операций с ними, и у каждой могут быть свои параметры. +Может даже несколько раз вызываться одна и так же функция с разными параметрами. Так, например, можно послать несколько фейков, причем с разными фулингами. +Конкретный вызов `--lua-desync` функции называется инстансом. Инстанс - это связка имени функции, номера вызова внутри профиля и номера самого профиля. +Это похоже на одну программу, которую можно запустить много раз с разными параметрами. + +Другое немаловажное отличие - поддержка автоматической tcp сегментации. Вам больше не нужно думать о размерах отсылаемых tcp пакетов. +По каждому соединению отслеживается MSS. Если пакет не влезает в MSS, выполняется сегментация. +Например, это может случиться при отправке tls фейка с kyber. Или если вы режете kyber tls так, что одна из частей получается размером 1600 байт, +что, очевидно, не влезает в MTU. Или если вы задали seqovl=10000. В *nfqws1* такое значение вызвало бы ошибку. В *nfqws2* будет отправлено +несколько tcp сегментов с начальным sequence -10000 общим размером 10000 байт, в последнем из которых будет кусок оригинального сообщения. + +В *nfqws2* нет жестко зашитых параметров кастомных фейков типа `--dpi-desync-fake-tls`, `dpi-desync-fake-http` и тд. +Вместо них есть блобы. Блоб (blob) - это переменная LUA типа *string*, содержащая блок двоичных данных произвольной длины. От 1 байта до гигабайтов. +*nfqws2* автоматически инициализирует блобы со стандартными фейками tls, http, quic, как это и было в *nfqws1*. +Блобы могут быть заданы как hex-строка прямо в параметре desync функции, либо пред-загружены при старте с помощью параметра `--blob=name:0xHEX|[+ofs]@filename` + +Что касается профилей мультистратегии и хостлистов , то они остались практически в неизменном виде. За исключением одной тонкости автолиста. +Теперь профиль с автолистом берет на себя только те соединения, для которых уже известен хост. Пока хоста нет - они проходят мимо. +В *nfqws1* они падали на профиль с автолистом. + +``` +nfqws2 --qnum 200 --debug --lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua \ +--filter-tcp=80 --filter-l7=http --hostlist=mylist1.txt --lua-desync=multisplit --new \ +--filter-tcp=80 --filter-l7=http --hostlist-exclude=mylist2.txt --lua-desync=fake:blob=0x00000000:ip_ttl=5:ip6_ttl=3 --lua-desync=multidisorder:pos=5,endhost-1 --new \ +--filter-tcp=443 --filter-l7=tls --hostlist=mylist1.txt --lua-desync=multidisorder +``` + +Параметры *nfqws1* start/cutoff (`--dpi-desync-start`, `--dpi-desync-cutoff`, ...) теперь называются диапазонами (ranges). +Остались только 2 range : `--in-range` и `--out-range`. Они относятся к входящему и исходящему направлению соответственно. +Да, теперь можно полноценно работать как с входящими пакетами, так и с исходящими. Есть и специальный режим для сервера - `--server`, который +адаптирует интерпретацию IP адресов и портов источника/приемника, чтобы корректно работали ipset-ы и фильтры. + +range задается как `mX-mY`, `mX;tag=ca565d7bd4e24a6d80c631d395ee117e +To: +Call-ID: a5e53e302b2d4a4d83c1455527c882c3 +CSeq: 26538 REGISTER +User-Agent: MicroSIP/3.21.5 +Contact: +Expires: 300 +Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS +Content-Length: 0 + diff --git a/files/fake/stun.bin b/files/fake/stun.bin new file mode 100644 index 0000000..a97b5ac Binary files /dev/null and b/files/fake/stun.bin differ diff --git a/files/fake/tls_clienthello_gosuslugi_ru.bin b/files/fake/tls_clienthello_gosuslugi_ru.bin new file mode 100644 index 0000000..d9e0f4a Binary files /dev/null and b/files/fake/tls_clienthello_gosuslugi_ru.bin differ diff --git a/files/fake/tls_clienthello_iana_org.bin b/files/fake/tls_clienthello_iana_org.bin new file mode 100644 index 0000000..e641d73 Binary files /dev/null and b/files/fake/tls_clienthello_iana_org.bin differ diff --git a/files/fake/tls_clienthello_iana_org_bigsize.bin b/files/fake/tls_clienthello_iana_org_bigsize.bin new file mode 100644 index 0000000..cdeac80 Binary files /dev/null and b/files/fake/tls_clienthello_iana_org_bigsize.bin differ diff --git a/files/fake/tls_clienthello_rutracker_org_kyber.bin b/files/fake/tls_clienthello_rutracker_org_kyber.bin new file mode 100644 index 0000000..9ccc5fc Binary files /dev/null and b/files/fake/tls_clienthello_rutracker_org_kyber.bin differ diff --git a/files/fake/tls_clienthello_sberbank_ru.bin b/files/fake/tls_clienthello_sberbank_ru.bin new file mode 100644 index 0000000..59571bb Binary files /dev/null and b/files/fake/tls_clienthello_sberbank_ru.bin differ diff --git a/files/fake/tls_clienthello_vk_com.bin b/files/fake/tls_clienthello_vk_com.bin new file mode 100644 index 0000000..ec908c2 Binary files /dev/null and b/files/fake/tls_clienthello_vk_com.bin differ diff --git a/files/fake/tls_clienthello_vk_com_kyber.bin b/files/fake/tls_clienthello_vk_com_kyber.bin new file mode 100644 index 0000000..92c639e Binary files /dev/null and b/files/fake/tls_clienthello_vk_com_kyber.bin differ diff --git a/files/fake/tls_clienthello_www_google_com.bin b/files/fake/tls_clienthello_www_google_com.bin new file mode 100644 index 0000000..62985b6 Binary files /dev/null and b/files/fake/tls_clienthello_www_google_com.bin differ diff --git a/files/fake/wireguard_initiation.bin b/files/fake/wireguard_initiation.bin new file mode 100644 index 0000000..8055863 Binary files /dev/null and b/files/fake/wireguard_initiation.bin differ diff --git a/files/fake/wireguard_response.bin b/files/fake/wireguard_response.bin new file mode 100644 index 0000000..c4597de Binary files /dev/null and b/files/fake/wireguard_response.bin differ diff --git a/files/fake/zero_1024.bin b/files/fake/zero_1024.bin new file mode 100644 index 0000000..06d7405 Binary files /dev/null and b/files/fake/zero_1024.bin differ diff --git a/files/fake/zero_256.bin b/files/fake/zero_256.bin new file mode 100644 index 0000000..65f57c2 Binary files /dev/null and b/files/fake/zero_256.bin differ diff --git a/files/fake/zero_512.bin b/files/fake/zero_512.bin new file mode 100644 index 0000000..a64a5a9 Binary files /dev/null and b/files/fake/zero_512.bin differ diff --git a/install_bin.sh b/install_bin.sh new file mode 100755 index 0000000..f76328d --- /dev/null +++ b/install_bin.sh @@ -0,0 +1,218 @@ +#!/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 and 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 libluajit2-5.1-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=nfqws2 + ;; + Darwin) + ARCHLIST="my mac64" + ;; + FreeBSD) + ARCHLIST="my freebsd-x86_64" + PKTWS=dvtws2 + ;; + CYGWIN*) + UNAME=CYGWIN + ARCHLIST="windows-x86_64 windows-x86" + PKTWS=winws2 + ;; + *) + 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 nfq2 + exit 0 + else + echo $arch is NOT OK + fi + done + echo no compatible binaries found +fi + +exit 1 diff --git a/ip2net/Makefile b/ip2net/Makefile new file mode 100644 index 0000000..3c44256 --- /dev/null +++ b/ip2net/Makefile @@ -0,0 +1,33 @@ +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 diff --git a/ip2net/ip2net.c b/ip2net/ip2net.c new file mode 100644 index 0000000..77d7139 --- /dev/null +++ b/ip2net/ip2net.c @@ -0,0 +1,516 @@ +// 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 ? ~((1 << 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] = ~((1 << (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; +} diff --git a/ip2net/qsort.c b/ip2net/qsort.c new file mode 100644 index 0000000..2ee1185 --- /dev/null +++ b/ip2net/qsort.c @@ -0,0 +1,250 @@ +/* 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; + } + } + } + } +} diff --git a/ip2net/qsort.h b/ip2net/qsort.h new file mode 100644 index 0000000..f537ab7 --- /dev/null +++ b/ip2net/qsort.h @@ -0,0 +1,6 @@ +#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); diff --git a/lua/zapret-antidpi.lua b/lua/zapret-antidpi.lua new file mode 100644 index 0000000..40646b2 --- /dev/null +++ b/lua/zapret-antidpi.lua @@ -0,0 +1,866 @@ +--[[ + +NFQWS2 ANTIDPI LIBRARY + +--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua +--lua-desync=func1:arg1[=val1]:arg2[=val2] --lua-desync=func2:arg1[=val1]:arg2[=val2] .... --lua-desync=funcN:arg1[=val1]:arg2[=val2] + +BLOBS + +blobs can be 0xHEX, field name in desync or global var +standard way to bring binary data to lua code is using the "--blob" parameter of nfqws2 +dynamic blobs can be inside desync table. one function can prepare data for next functions. + +STANDARD FUNCTION ARGS + +standard direction : + +* dir = in|out|any + +standard fooling : + +* ip_ttl=N - set ipv.ip_ttl to N +* ip6_ttl=N - set ip6.ip6_hlim to N +* ip_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl +* ip6_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl + +* ip6_hopbyhop[=hex] - add hopbyhop ipv6 header with optional data. data size must be 6+N*8. all zero by default. +* ip6_hopbyhop2 - add 2 hopbyhop ipv6 headers with optional data. data size must be 6+N*8. all zero by default. +* ip6_destopt[=hex] - add destopt ipv6 header with optional data. data size must be 6+N*8. all zero by default. +* ip6_routing[=hex] - add routing ipv6 header with optional data. data size must be 6+N*8. all zero by default. +* ip6_ah[=hex] - add authentication ipv6 header with optional data. data size must be 6+N*4. 0000 + 4 random bytes by default. + +* tcp_seq=N - add N to tcp.th_seq +* tcp_ack=N - add N to tcp.th_ack +* tcp_ts=N - add N to timestamp value +* tcp_md5[=hex] - add MD5 header with optional 16-byte data. all zero by default. +* tcp_flags_set= - set tcp flags in comma separated list +* tcp_unflags_set= - unset tcp flags in comma separated list + +* fool - custom fooling function : fool_func(dis, fooling_options) + +standard reconstruct : + +* badsum - make L4 checksum invalid + +standard rawsend : + +* repeats - how many time send the packet +* ifout - override outbound interface (if --bind_fix4, --bind-fix6 enabled) +* fwmark - override fwmark. desync mark bit(s) will be set unconditionally + +standard payload : + +* payload - comma separarated list of allowed payload types. if not present - allow non-empty known payloads. + +standard ip_id : + +* ip_id - seq|rnd|zero|none +* ip_id_conn - in 'seq' mode save current ip_id in track.lua_state to use it between packets + +standard ipfrag : + +* ipfrag - ipfrag function name. "ipfrag2" by default if empty +* ipfrag2 : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8 +* ipfrag2 : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32 +* ipfrag2 : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default. +* ipfrag2 : ipfrag_disorder - send fragments from last to first + +]] + + +-- dummy test function. does nothing. +-- no args +function pass(ctx, desync) + DLOG("pass") +end + +-- prints desync to DLOG +function pktdebug(ctx, desync) + DLOG("desync:") + var_debug(desync) +end + +-- drop packet +-- standard args : direction +function drop(ctx, desync) + direction_cutoff_opposite(ctx, desync, "any") + if direction_check(desync, "any") then + DLOG("drop") + return VERDICT_DROP + end +end + +-- nfqws1 : "--dup" +-- standard args : direction, fooling, ip_id, rawsend, reconstruct +function send(ctx, desync) + direction_cutoff_opposite(ctx, desync, "any") + if direction_check(desync, "any") then + DLOG("send") + local dis = deepcopy(desync.dis) + apply_fooling(desync, dis) + apply_ip_id(desync, dis, nil, "none") + -- it uses rawsend, reconstruct and ipfrag options + rawsend_dissect_ipfrag(dis, desync_opts(desync)) + end +end + +-- nfqws1 : "--orig" +-- apply modification to current packet +-- standard args : direction, fooling, ip_id, rawsend, reconstruct +function pktmod(ctx, desync) + direction_cutoff_opposite(ctx, desync, "any") + if direction_check(desync, "any") then + -- apply to current packet + apply_fooling(desync) + apply_ip_id(desync, nil, nil, "none") + DLOG("pktmod: applied") + return VERDICT_MODIFY + end +end + +-- nfqws1 : "--domcase" +-- standard args : direction +function http_domcase(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if desync.l7payload=="http_req" and direction_check(desync) then + local host_range = resolve_multi_pos(desync.dis.payload,desync.l7payload,"host,endhost") + if #host_range == 2 then + local host = string.sub(desync.dis.payload,host_range[1],host_range[2]-1) + local newhost="", i + for i = 1, #host do + newhost=newhost..((i%2)==0 and string.lower(string.sub(host,i,i)) or string.upper(string.sub(host,i,i))) + end + DLOG("http_domcase: "..host.." => "..newhost) + desync.dis.payload = string.sub(desync.dis.payload, 1, host_range[1]-1)..newhost..string.sub(desync.dis.payload, host_range[2]) + return VERDICT_MODIFY + else + DLOG("http_domcase: cannot find host range") + end + end +end + +-- nfqws1 : "--hostcase" +-- standard args : direction +-- arg : spell= . spelling of the "Host" header. must be exactly 4 chars long +function http_hostcase(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if desync.l7payload=="http_req" and direction_check(desync) then + local spell = desync.arg.spell or "host" + if #spell ~= 4 then + error("http_hostcase: invalid host spelling '"..spell.."'") + else + local hdis = http_dissect_req(desync.dis.payload) + if hdis.headers.host then + DLOG("http_hostcase: 'Host:' => '"..spell.."'") + desync.dis.payload = string.sub(desync.dis.payload,1,hdis.headers.host.pos_start-1)..spell..string.sub(desync.dis.payload,hdis.headers.host.pos_header_end+1) + return VERDICT_MODIFY + else + DLOG("http_hostcase: 'Host:' header not found") + end + end + end +end + +-- nfqws1 : "--methodeol" +-- standard args : direction +function http_methodeol(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if desync.l7payload=="http_req" and direction_check(desync) then + local hdis = http_dissect_req(desync.dis.payload) + local ua = hdis.headers["user-agent"] + if ua then + if (ua.pos_end - ua.pos_value_start) < 2 then + DLOG("http_methodeol: 'User-Agent:' header is too short") + else + DLOG("http_methodeol: applied") + desync.dis.payload="\r\n"..string.sub(desync.dis.payload,1,ua.pos_end-2)..(string.sub(desync.dis.payload,ua.pos_end+1) or ""); + return VERDICT_MODIFY + end + else + DLOG("http_methodeol: 'User-Agent:' header not found") + end + end +end + +-- nfqws1 : "--synack-split" +-- standard args : rawsend, reconstruct, ipfrag +-- arg : mode=syn|synack|acksyn . "synack" by default +function synack_split(ctx, desync) + if desync.dis.tcp then + if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then + local mode = desync.arg.mode or "synack" + local options = desync_opts(desync) + if mode=="syn" then + local dis = deepcopy(desync.dis) + dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK)) + DLOG("synack_split: sending SYN") + if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end + return VERDICT_DROP + elseif mode=="synack" then + local dis = deepcopy(desync.dis) + dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK)) + DLOG("synack_split: sending SYN") + if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end + dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_SYN)) + DLOG("synack_split: sending ACK") + if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end + return VERDICT_DROP + elseif mode=="acksyn" then + local dis = deepcopy(desync.dis) + dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_SYN)) + DLOG("synack_split: sending ACK") + if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end + dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK)) + DLOG("synack_split: sending SYN") + if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end + return VERDICT_DROP + else + error("synack_split: bad mode '"..mode.."'") + end + else + instance_cutoff(ctx) -- mission complete + end + else + instance_cutoff(ctx) + end +end + +-- nfqws1 : "--dpi-desync=synack" +-- standard args : rawsend, reconstruct, ipfrag +function synack(ctx, desync) + if desync.dis.tcp then + if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then + local dis = deepcopy(desync.dis) + dis.tcp.th_flags = bitor(dis.tcp.th_flags, TH_ACK) + DLOG("synack: sending") + rawsend_dissect_ipfrag(dis, desync_opts(desync)) + else + instance_cutoff(ctx) -- mission complete + end + else + instance_cutoff(ctx) + end +end + + +-- nfqws1 : "--wssize" +-- arg : wsize=N . tcp window size +-- arg : scale=N . tcp option scale factor +function wsize(ctx, desync) + if desync.dis.tcp then + if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then + if wsize_rewrite(desync.dis, desync.arg) then + return VERDICT_MODIFY + end + else + instance_cutoff(ctx) -- mission complete + end + else + instance_cutoff(ctx) + end +end + +-- nfqws1 : "--wsize" +-- standard args : direction +-- arg : wsize=N . tcp window size +-- arg : scale=N . tcp option scale factor +-- arg : forced_cutoff= - comma separated list of payloads that trigger forced wssize cutoff. by default - any non-empty payload +function wssize(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + local verdict = VERDICT_PASS + direction_cutoff_opposite(ctx, desync) + if direction_check(desync) then + if wsize_rewrite(desync.dis, desync.arg) then + verdict = VERDICT_MODIFY + end + if #desync.dis.payload>0 and (not desync.arg.forced_cutoff or in_list(desync.arg.forced_cutoff, desync.l7payload)) then + DLOG("wssize: forced cutoff") + instance_cutoff(ctx) + end + end + return verdict +end + +-- nfqws1 : "--dpi-desync=syndata" +-- standard args : fooling, rawsend, reconstruct, ipfrag +-- arg : blob= - fake payload. must fit to single packet. no segmentation possible. default - 16 zero bytes. +-- arg : tls_mod= - comma separated list of tls mods : rnd,rndsni,sni=,dupsid,padencap +function syndata(ctx, desync) + if desync.dis.tcp then + if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then + local dis = deepcopy(desync.dis) + dis.payload = blob(desync, desync.arg.blob, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + apply_fooling(desync, dis) + if desync.arg.tls_mod then + dis.payload = tls_mod(dis.payload, desync.arg.tls_mod, nil) + end + if b_debug then DLOG("syndata: "..hexdump_dlog(dis.payload)) end + if rawsend_dissect_ipfrag(dis, desync_opts(desync)) then + return VERDICT_DROP + end + else + instance_cutoff(ctx) -- mission complete + end + else + instance_cutoff(ctx) + end +end + +-- nfqws1 : "--dpi-desync=fake" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag +-- arg : blob= - fake payload +-- arg : tls_mod= - comma separated list of tls mods : rnd,rndsni,sni=,dupsid,padencap +function fake(ctx, desync) + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + if direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + if not desync.arg.blob then + error("fake: 'blob' arg required") + end + local fake_payload = blob(desync, desync.arg.blob) + if desync.reasm_data and desync.arg.tls_mod then + fake_payload = tls_mod(fake_payload, desync.arg.tls_mod, desync.reasm_data) + end + -- check debug to save CPU + if b_debug then DLOG("fake: "..hexdump_dlog(fake_payload)) end + rawsend_payload_segmented(desync,fake_payload) + else + DLOG("fake: not acting on further replay pieces") + end + end +end + +-- nfqws1 : "--dpi-desync=multisplit" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag +-- arg : pos= . position marker list. for example : "1,host,midsld+1,-10" +-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero) +-- arg : seqovl_pattern= . override pattern +function multisplit(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + local spos = desync.arg.pos or "2" + -- check debug to save CPU + if b_debug then DLOG("multisplit: split pos: "..spos) end + local pos = resolve_multi_pos(data, desync.l7payload, spos) + if b_debug then DLOG("multisplit: resolved split pos: "..table.concat(zero_based_pos(pos)," ")) end + delete_pos_1(pos) -- cannot split at the first byte + if #pos>0 then + for i=0,#pos do + local pos_start = pos[i] or 1 + local pos_end = i<#pos and pos[i+1]-1 or #data + local part = string.sub(data,pos_start,pos_end) + local seqovl=0 + if i==0 and desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then + seqovl = tonumber(desync.arg.seqovl) + local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00" + part = pattern(pat,1,seqovl)..part + end + if b_debug then DLOG("multisplit: sending part "..(i+1).." "..(pos_start-1).."-"..(pos_end-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos_start-1-seqovl) then + return VERDICT_PASS + end + end + replay_drop_set(desync) + return VERDICT_DROP + else + DLOG("multisplit: no valid split positions") + end + else + DLOG("multisplit: not acting on further replay pieces") + end + -- drop replayed packets if reasm was sent successfully in splitted form + if replay_drop(desync) then + return VERDICT_DROP + end + end +end + +-- nfqws1 : "--dpi-desync=multidisorder" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag +-- arg : pos= . position marker list. example : "1,host,midsld+1,-10" +-- arg : seqovl=N . decrease seq number of the second segment in the original order by N and fill N bytes with pattern (default - all zero). N must be less than the first split pos. +-- arg : seqovl_pattern= . override pattern +function multidisorder(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + local spos = desync.arg.pos or "2" + -- check debug to save CPU + if b_debug then DLOG("multidisorder: split pos: "..spos) end + local pos = resolve_multi_pos(data, desync.l7payload, spos) + if b_debug then DLOG("multidisorder: resolved split pos: "..table.concat(zero_based_pos(pos)," ")) end + delete_pos_1(pos) -- cannot split at the first byte + if #pos>0 then + for i=#pos,0,-1 do + local pos_start = pos[i] or 1 + local pos_end = i<#pos and pos[i+1]-1 or #data + local part = string.sub(data,pos_start,pos_end) + local seqovl=0 + if i==1 and desync.arg.seqovl then + seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl) + if not seqovl then + DLOG("multidisorder: seqovl cancelled because could not resolve marker '"..desync.arg.seqovl.."'") + seqovl = 0 + else + seqovl = seqovl - 1 + if seqovl>=(pos[1]-1) then + DLOG("multidisorder: seqovl cancelled because seqovl "..seqovl.." is not less than the first split pos "..(pos[1]-1)) + seqovl = 0 + else + local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00" + part = pattern(pat,1,seqovl)..part + end + end + end + if b_debug then DLOG("multidisorder: sending part "..(i+1).." "..(pos_start-1).."-"..(pos_end-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos_start-1-seqovl) then + return VERDICT_PASS + end + end + replay_drop_set(desync) + return VERDICT_DROP + else + DLOG("multidisorder: no valid split positions") + end + else + DLOG("multidisorder: not acting on further replay pieces") + end + -- drop replayed packets if reasm was sent successfully in splitted form + if replay_drop(desync) then + return VERDICT_DROP + end + end +end + +-- nfqws1 : "--dpi-desync=hostfakesplit" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES. +-- arg : host= - hostname template. generate hosts like "random.template". example : e8nzn.vk.com +-- arg : midhost= - additionally split segment containing host at specified posmarker. must be within host+1 .. endhost-1 or split won't happen. example : "midsld" +-- arg : nofake1, nofake2 - do not send individual fakes +-- arg : disorder_after= - send after_host part in 2 disordered segments. if posmarker is empty string use marker "-1" +function hostfakesplit(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + local pos = resolve_range(data, desync.l7payload, "host,endhost-1", true) + if pos then + if b_debug then DLOG("hostfakesplit: resolved host range: "..table.concat(zero_based_pos(pos)," ")) end + + -- do not apply fooling to original parts except tcp_ts_up but apply ip_id + local part, fakehost + local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}} + local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg} + + part = string.sub(data,1,pos[1]-1) + if b_debug then DLOG("hostfakesplit: sending before_host part 0-"..(pos[1]-2).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,0, opts_orig) then return VERDICT_PASS end + + fakehost = genhost(pos[2]-pos[1]+1, desync.arg.host) + + if not desync.arg.nofake1 then + if b_debug then DLOG("hostfakesplit: sending fake host part (1) "..(pos[1]-1).."-"..(pos[2]-1).." len="..#fakehost.." : "..hexdump_dlog(fakehost)) end + if not rawsend_payload_segmented(desync,fakehost,pos[1]-1, opts_fake) then return VERDICT_PASS end + end + + local midhost + if desync.arg.midhost then + midhost = resolve_pos(data,desync.l7payload,desync.arg.midhost) + if not midhost then + DLOG("hostfakesplit: cannot resolve midhost marker '"..desync.arg.midhost.."'") + end + DLOG("hosfakesplit: midhost marker resolved to "..midhost) + if midhost<=pos[1] or midhost>pos[2] then + DLOG("hostfakesplit: midhost is not inside the host range") + midhost = nil + end + end + -- if present apply ipfrag only to real host parts. fakes and parts outside of the host must be visible to DPI. + if midhost then + part = string.sub(data,pos[1],midhost-1) + if b_debug then DLOG("hostfakesplit: sending real host part 1 "..(pos[1]-1).."-"..(midhost-2).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos[1]-1, opts_orig) then return VERDICT_PASS end + + part = string.sub(data,midhost,pos[2]) + if b_debug then DLOG("hostfakesplit: sending real host part 2 "..(midhost-1).."-"..(pos[2]-1).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,midhost-1, opts_orig) then return VERDICT_PASS end + else + part = string.sub(data,pos[1],pos[2]) + if b_debug then DLOG("hostfakesplit: sending real host part "..(pos[1]-1).."-"..(pos[2]-1).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos[1]-1, opts_orig) then return VERDICT_PASS end + end + + if not desync.arg.nofake2 then + if b_debug then DLOG("hostfakesplit: sending fake host part (2) "..(pos[1]-1).."-"..(pos[2]-1).." len="..#fakehost.." : "..hexdump_dlog(fakehost)) end + if not rawsend_payload_segmented(desync,fakehost,pos[1]-1, opts_fake) then return VERDICT_PASS end + end + + local disorder_after_pos + if desync.arg.disorder_after then + disorder_after_pos = resolve_pos(data, desync.l7payload, desync.arg.disorder_after=="" and "-1" or desync.arg.disorder_after) + if disorder_after_pos then + -- pos[2] points to the last letter of the host starting from 1 + if disorder_after_pos<=(pos[2]+1) then + DLOG("hostfakesplit: disorder_after marker '"..(disorder_after_pos-1).."' resolved to pos not after after_host pos "..pos[2]) + disorder_after_pos = nil + end + + else + DLOG("hostfakesplit: could not resolve disorder_after marker '"..desync.arg.disorder_after.."'") + end + end + if disorder_after_pos then + part = string.sub(data,disorder_after_pos) + if b_debug then DLOG("hostfakesplit: sending after_host part (2) "..(disorder_after_pos-1).."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,disorder_after_pos-1, opts_orig) then return VERDICT_PASS end + + part = string.sub(data,pos[2]+1,disorder_after_pos-1) + if b_debug then DLOG("hostfakesplit: sending after_host part (1) "..pos[2].."-"..(disorder_after_pos-2).." len="..#part.." : "..hexdump_dlog(part)) end + else + part = string.sub(data,pos[2]+1) + if b_debug then DLOG("hostfakesplit: sending after_host part "..pos[2].."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end + end + if not rawsend_payload_segmented(desync,part,pos[2], opts_orig) then return VERDICT_PASS end + + replay_drop_set(desync) + return VERDICT_DROP + else + DLOG("hostfakesplit: host range cannot be resolved") + end + else + DLOG("hostfakesplit: not acting on further replay pieces") + end + -- drop replayed packets if reasm was sent successfully in splitted form + if replay_drop(desync) then + return VERDICT_DROP + end + end +end + +-- nfqws1 : "--dpi-desync=fakedsplit" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES. +-- arg : pos= - split position marker +-- arg : nofake1, nofake2, nofake3, nofake4 - do not send individual fakes +-- arg : pattern= . fill fake parts with this pattern +-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero) +-- arg : seqovl_pattern= . override seqovl pattern +function fakedsplit(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + local spos = desync.arg.pos or "2" + local pos = resolve_pos(data, desync.l7payload, spos) + if pos then + if pos == 1 then + DLOG("multidisorder: split pos resolved to 0. cannot split.") + else + if b_debug then DLOG("fakedsplit: resolved split pos: "..tostring(pos-1)) end + + -- do not apply fooling to original parts except tcp_ts_up but apply ip_id + local fake, fakepat, part, pat + local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}} + local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg} + + fakepat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00" + + -- first fake + fake = pattern(fakepat,1,pos-1) + + if not desync.arg.nofake1 then + if b_debug then DLOG("fakedsplit: sending fake part 1 (1) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end + end + + -- first real + part = string.sub(data,1,pos-1) + local seqovl=0 + if desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then + seqovl = tonumber(desync.arg.seqovl) + pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00" + part = pattern(pat,1,seqovl)..part + end + if b_debug then DLOG("fakedsplit: sending real part 1 : 0-"..(pos-2).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,-seqovl, opts_orig) then return VERDICT_PASS end + + -- first fake again + if not desync.arg.nofake2 then + if b_debug then DLOG("fakedsplit: sending fake part 1 (2) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end + end + + -- second fake + fake = pattern(fakepat,pos,#data-pos+1) + if not desync.arg.nofake3 then + if b_debug then DLOG("fakedsplit: sending fake part 2 (1) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end + end + + -- second real + part = string.sub(data,pos) + if b_debug then DLOG("fakedsplit: sending real part 2 : "..(pos-1).."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos-1, opts_orig) then return VERDICT_PASS end + + -- second fake again + if not desync.arg.nofake4 then + if b_debug then DLOG("fakedsplit: sending fake part 2 (2) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end + end + + replay_drop_set(desync) + return VERDICT_DROP + end + else + DLOG("fakedsplit: cannot resolve pos '"..desync.arg.pos.."'") + end + else + DLOG("fakedsplit: not acting on further replay pieces") + end + -- drop replayed packets if reasm was sent successfully in splitted form + if replay_drop(desync) then + return VERDICT_DROP + end + end +end + +-- nfqws1 : "--dpi-desync=fakeddisorder" +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES. +-- arg : pos= - split position marker +-- arg : nofake1, nofake2, nofake3, nofake4 - do not send individual fakes +-- arg : pattern= . fill fake parts with this pattern +-- arg : seqovl=N . decrease seq number of the second segment by N and fill N bytes with pattern (default - all zero). N must be less than the split pos. +-- arg : seqovl_pattern= . override seqovl pattern +function fakeddisorder(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + local spos = desync.arg.pos or "2" + local pos = resolve_pos(data, desync.l7payload, spos) + if pos then + if pos == 1 then + DLOG("multidisorder: split pos resolved to 0. cannot split.") + else + if b_debug then DLOG("fakeddisorder: resolved split pos: "..tostring(pos-1)) end + + -- do not apply fooling to original parts except tcp_ts_up but apply ip_id + local fake, part, pat + local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}} + local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg} + + fakepat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00" + + -- second fake + fake = pattern(fakepat,pos,#data-pos+1) + if not desync.arg.nofake1 then + if b_debug then DLOG("fakeddisorder: sending fake part 2 (1) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end + end + + -- second real + part = string.sub(data,pos) + local seqovl = 0 + if desync.arg.seqovl then + seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl) + if seqovl then + seqovl = seqovl - 1 + if seqovl>=(pos-1) then + DLOG("fakeddisorder: seqovl cancelled because seqovl "..seqovl.." is not less than the split pos "..(pos-1)) + seqovl = 0 + else + local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00" + part = pattern(pat,1,seqovl)..part + end + else + DLOG("fakeddisorder: seqovl cancelled because could not resolve marker '"..desync.arg.seqovl.."'") + seqovl = 0 + end + end + if b_debug then DLOG("fakeddisorder: sending real part 2 : "..(pos-1).."-"..(#data-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,pos-1-seqovl, opts_orig) then return VERDICT_PASS end + + -- second fake again + if not desync.arg.nofake2 then + if b_debug then DLOG("fakeddisorder: sending fake part 2 (2) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end + end + + -- first fake + fake = pattern(fakepat,1,pos-1) + if not desync.arg.nofake3 then + if b_debug then DLOG("fakeddisorder: sending fake part 1 (1) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end + end + + -- first real + part = string.sub(data,1,pos-1) + if b_debug then DLOG("fakeddisorder: sending real part 1 : 0-"..(pos-2).." len="..#part.." : "..hexdump_dlog(part)) end + if not rawsend_payload_segmented(desync,part,0, opts_orig) then return VERDICT_PASS end + + -- first fake again + if not desync.arg.nofake4 then + if b_debug then DLOG("fakeddisorder: sending fake part 1 (2) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end + if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end + end + + replay_drop_set(desync) + return VERDICT_DROP + end + else + DLOG("fakeddisorder: cannot resolve pos '"..desync.arg.pos.."'") + end + else + DLOG("fakeddisorder: not acting on further replay pieces") + end + -- drop replayed packets if reasm was sent successfully in splitted form + if replay_drop(desync) then + return VERDICT_DROP + end + end +end + +-- nfqws1 : not available +-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag +-- arg : pos= . position marker list. 2 pos required, only 2 first pos used. example : "host,endhost" +-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero) +-- arg : seqovl_pattern= . override pattern +function tcpseg(ctx, desync) + if not desync.dis.tcp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if not desync.arg.pos then + error("tcpseg: no pos specified") + end + -- by default process only outgoing known payloads + local data = desync.reasm_data or desync.dis.payload + if #data>0 and direction_check(desync) and payload_check(desync) then + if replay_first(desync) then + if b_debug then DLOG("tcpseg: pos: "..desync.arg.pos) end + -- always returns 2 positions or nil or causes error + local pos = resolve_range(data, desync.l7payload, desync.arg.pos) + if pos then + -- check debug to save CPU + if b_debug then DLOG("tcpseg: resolved range: "..table.concat(zero_based_pos(pos)," ")) end + local part = string.sub(data,pos[1],pos[2]) + local seqovl=0 + if desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then + seqovl = tonumber(desync.arg.seqovl) + local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00" + part = pattern(pat,1,seqovl)..part + end + if b_debug then DLOG("tcpseg: sending "..(pos[1]-1).."-"..(pos[2]-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end + rawsend_payload_segmented(desync,part,pos[1]-1-seqovl) + else + DLOG("tcpseg: range cannot be resolved") + end + else + DLOG("tcpseg: not acting on further replay pieces") + end + end +end + +-- nfqws1 : "--dpi-desync=udplen" +-- standard args : direction, payload +-- arg : min=N . do not act on payloads smaller than N bytes +-- arg : max=N . do not act on payloads larger than N bytes +-- arg : increment=N . 2 by default. negative increment shrinks the packet, positive grows it. +-- arg : pattern= . used to fill extra bytes when length increases +-- arg : pattern_offset=N . offset in the pattern. 0 by default +function udplen(ctx, desync) + if not desync.dis.udp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if direction_check(desync) and payload_check(desync) then + local len = #desync.dis.payload + if (desync.arg.min and #desync.dis.payload < tonumber(desync.arg.min)) then + DLOG("udplen: payload size "..len.." is less than the minimum size "..desync.arg.min) + elseif (desync.arg.max and #desync.dis.payload > tonumber(desync.arg.max)) then + DLOG("udplen: payload size "..len.." is more than the maximum size "..desync.arg.max) + else + local inc = desync.arg.increment and tonumber(desync.arg.increment) or 2 + if inc>0 then + local pat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00" + local pat_offset = desync.arg.pattern_offset and (tonumber(desync.arg.pattern_offset)+1) or 1 + desync.dis.payload = desync.dis.payload .. pattern(pat, pat_offset, inc) + DLOG("udplen: "..len.." => "..#desync.dis.payload) + return VERDICT_MODIFY + elseif inc<0 then + if (len+inc)<1 then + DLOG("udplen: will not shrink to zero length") + else + desync.dis.payload = string.sub(desync.dis.payload,1,len+inc) + DLOG("udplen: "..len.." => "..#desync.dis.payload) + end + return VERDICT_MODIFY + end + end + end +end + +-- nfqws1 : "--dpi-desync=tamper" for dht proto +-- standard args : direction +-- arg : dn=N - message starts from "dN". 2 by default +function dht_dn(ctx, desync) + if not desync.dis.udp then + instance_cutoff(ctx) + return + end + direction_cutoff_opposite(ctx, desync) + if desync.l7payload=="dht" and direction_check(desync) then + local N = tonumber(desync.arg.dn) or 2 + -- remove "d1" from the start not breaking bencode + local prefix = "d"..tostring(N)..":"..string.rep("0",N).."1:x" + desync.dis.payload = prefix..string.sub(desync.dis.payload,2) + DLOG("dht_dn: tampered dht to start with '"..prefix.."' instead of 'd1:'") + return VERDICT_MODIFY + end +end diff --git a/lua/zapret-lib.lua b/lua/zapret-lib.lua new file mode 100644 index 0000000..09b34d4 --- /dev/null +++ b/lua/zapret-lib.lua @@ -0,0 +1,980 @@ +HEXDUMP_DLOG_MAX = HEXDUMP_DLOG_MAX or 32 +NOT3=bitnot(3) +NOT7=bitnot(7) +math.randomseed(os.time()) + +-- prepare standard rawsend options from desync +-- repeats - how many time send the packet +-- ifout - override outbound interface (if --bind_fix4, --bind-fix6 enabled) +-- fwmark - override fwmark. desync mark bit(s) will be set unconditionally +function rawsend_opts(desync) + return { + repeats = desync.arg.repeats, + ifout = desync.arg.ifout or desync.ifout, + fwmark = desync.arg.fwmark or desync.fwmark + } +end +-- only basic options. no repeats +function rawsend_opts_base(desync) + return { + ifout = desync.arg.ifout or desync.ifout, + fwmark = desync.arg.fwmark or desync.fwmark + } +end + +-- prepare standard reconstruct options from desync +-- badsum - make L4 checksum invalid +-- ip6_preserve_next - use next protocol fields from dissect, do not auto fill values. can be set from code only, not from args +-- ip6_last_proto - last ipv6 "next" protocol. used only by "reconstruct_ip6hdr". can be set from code only, not from args +function reconstruct_opts(desync) + return { + badsum = desync.arg.badsum + } +end + +-- combined desync opts +function desync_opts(desync) + return { + rawsend = rawsend_opts(desync), + reconstruct = reconstruct_opts(desync), + ipfrag = desync.arg, + ipid = desync.arg, + fooling = desync.arg + } +end + + +-- convert binary string to hex data +function string2hex(s) + local ss = "" + for i = 1, #s do + if i>1 then + ss = ss .. " " + end + ss = ss .. string.format("%02X", string.byte(s, i)) + end + return ss +end +function has_nonprintable(s) + return s:match("[^ -\\r\\n\\t]") +end +function make_readable(v) + if type(v)=="string" then + return string.gsub(v,"[^ -]","."); + else + return tostring(v) + end +end +-- return hex dump of a binary string if it has nonprintable characters or string itself otherwise +function str_or_hex(s) + if has_nonprintable(s) then + return string2hex(s) + else + return s + end +end +-- print to DLOG any variable. tables are expanded in the tree form, unprintables strings are hex dumped +function var_debug(v) + local function dbg(v,level) + if type(v)=="table" then + for key, value in pairs(v) do + DLOG(string.rep(" ",2*level).."."..key) + dbg(v[key],level+1) + end + elseif type(v)=="string" then + DLOG(string.rep(" ",2*level)..type(v).." "..str_or_hex(v)) + else + DLOG(string.rep(" ",2*level)..type(v).." "..make_readable(v)) + end + end + dbg(v,0) +end + +-- make hex dump +function hexdump(s,max) + local l = max<#s and max or #s + local ss = string.sub(s,1,l) + return string2hex(ss)..(#s>max and " ... " or " " )..make_readable(ss)..(#s>max and " ... " or "" ) +end +-- make hex dump limited by HEXDUMP_DLOG_MAX chars +function hexdump_dlog(s) + return hexdump(s,HEXDUMP_DLOG_MAX) +end + +-- make copy of an array recursively +function deepcopy(orig, copies) + copies = copies or {} + local orig_type = type(orig) + local copy + if orig_type == 'table' then + if copies[orig] then + copy = copies[orig] + else + copy = {} + copies[orig] = copy + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies) + end + setmetatable(copy, deepcopy(getmetatable(orig), copies)) + end + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +-- check if string 'v' in comma separated list 's' +function in_list(s, v) + if s then + for elem in string.gmatch(s, "[^,]+") do + if elem==v then + return true + end + end + end + return false +end + +-- blobs can be 0xHEX, field name in desync or global var +-- if name is nil - return def +function blob(desync, name, def) + if not name or #name==0 then + if def then + return def + else + error("empty blob name") + end + end + local blob + if string.sub(name,1,2)=="0x" then + blob = parse_hex(string.sub(name,3)) + if not blob then + error("invalid hex string : "..name) + end + else + blob = desync[name] + if not blob then + -- use global var if no field in dissect table + blob = _G[name] + if not blob then + error("blob '"..name.."' unavailable") + end + end + end + return blob +end + +-- repeat pattern as needed to extract part of it with any length +-- pat="12345" len=10 offset=4 => "4512345123" +function pattern(pat, offset, len) + if not pat or #pat==0 then + error("pattern: bad or empty pattern") + end + local off = (offset-1) % #pat + local pats = divint((len + #pat - 1), #pat) + (off==0 and 0 or 1) + return string.sub(string.rep(pat,pats),off+1,off+len) +end + +-- decrease by 1 all number values in the array +function zero_based_pos(a) + if not a then return nil end + local b={} + for i,v in ipairs(a) do + b[i] = type(a[i])=="number" and a[i] - 1 or a[i] + end + return b +end + +-- delete elements with number value 1 +function delete_pos_1(a) + local i=1 + while i<=#a do + if type(a[i])=="number" and a[i] == 1 then + table.remove(a,i) + else + i = i+1 + end + end + return a +end + +-- find pos of the next eol and pos of the next non-eol character after eol +function find_next_line(s, pos) + local p1, p2 + p1 = string.find(s,"[\r\n]",pos) + if p1 then + p2 = p1 + p1 = p1-1 + if string.sub(s,p2,p2)=='\r' then p2=p2+1 end + if string.sub(s,p2,p2)=='\n' then p2=p2+1 end + if p2>#s then p2=nil end + else + p1 = #s + end + return p1,p2 +end + +function http_dissect_header(header) + local p1,p2 + p1,p2 = string.find(header,":") + if p1 then + p2=string.find(header,"[^ \t]",p2+1) + return string.sub(header,1,p1-1), p2 and string.sub(header,p2) or "", p1-1, p2 or #header + end + return nil +end +-- make table with structured http header representation +function http_dissect_headers(http, pos) + local eol,pnext,header,value,idx,headers,pos_endheader,pos_startvalue + headers={} + while pos do + eol,pnext = find_next_line(http,pos) + header = string.sub(http,pos,eol) + if #header == 0 then break end + header,value,pos_endheader,pos_startvalue = http_dissect_header(header) + if header then + headers[string.lower(header)] = { header = header, value = value, pos_start = pos, pos_end = eol, pos_header_end = pos+pos_endheader-1, pos_value_start = pos+pos_startvalue-1 } + end + pos=pnext + end + return headers +end +-- make table with structured http request representation +function http_dissect_req(http) + if not http then return nil; end + local eol,pnext,req,hdrpos + local pos=1 + -- skip methodeol empty line(s) + while pos do + eol,pnext = find_next_line(http,pos) + req = string.sub(http,pos,eol) + pos=pnext + if #req>0 then break end + end + hdrpos = pos + if not req or #req==0 then return nil end + pos = string.find(req,"[ \t]") + if not pos then return nil end + local method = string.sub(req,1,pos-1); + pos = string.find(req,"[^ \t]",pos+1) + if not pos then return nil end + pnext = string.find(req,"[ \t]",pos+1) + if not pnext then pnext = #http + 1 end + local uri = string.sub(req,pos,pnext-1) + return { method = method, uri = uri, headers = http_dissect_headers(http,hdrpos) } +end + +-- convert comma separated list of tcp flags to tcp.th_flags bit field +function parse_tcp_flags(s) + local flags={FIN=TH_FIN, SYN=TH_SYN, RST=TH_RST, PSH=TH_PUSH, PUSH=TH_PUSH, ACK=TH_ACK, URG=TH_URG, ECE=TH_ECE, CWR=TH_CWR} + local f=0 + local s_upper = string.upper(s) + for flag in string.gmatch(s_upper, "[^,]+") do + if flags[flag] then + f = bitor(f,flags[flag]) + else + error("tcp flag '"..flag.."' is invalid") + end + end + return f +end + +-- find first tcp options of specified kind in dissect.tcp.options +function find_tcp_option(options, kind) + if options then + for i, opt in pairs(options) do + if opt.kind==kind then return i end + end + end + return nil +end + +-- find first ipv6 extension header of specified protocol in dissect.ip6.exthdr +function find_ip6_exthdr(exthdr, proto) + if exthdr then + for i, hdr in pairs(exthdr) do + if hdr.type==proto then return i end + end + end + return nil +end + +-- insert ipv6 extension header at specified index. fix next proto chain +function insert_ip6_exthdr(ip6, idx, header_type, data) + local prev + if not ip6.exthdr then ip6.exthdr={} end + if not idx then + -- insert to the end + idx = #ip6.exthdr+1 + elseif idx<0 or idx>(#ip6.exthdr+1) then + error("insert_ip6_exthdr: invalid index "..idx) + end + if idx==1 then + prev = ip6.ip6_nxt + ip6.ip6_nxt = header_type + else + prev = ip6.exthdr[idx-1].next + ip6.exthdr[idx-1].next = header_type + end + table.insert(ip6.exthdr, idx, {type = header_type, data = data, next = prev}) +end +-- delete ipv6 extension header at specified index. fix next proto chain +function del_ip6_exthdr(ip6, idx) + if idx<=0 or idx>#ip6.exthdr then + error("delete_ip6_exthdr: nonexistent index "..idx) + end + local nxt = ip6.exthdr[idx].next + if idx==1 then + ip6.ip6_nxt = nxt + else + ip6.exthdr[idx-1].next = nxt + end + table.remove(ip6.exthdr, idx) +end +-- fills next proto fields in ipv6 header and extension headers +function fix_ip6_next(ip6, last_proto) + if ip6.exthdr and #ip6.exthdr>0 then + for i=1,#ip6.exthdr do + if i==1 then + -- first header + ip6.ip6_nxt = ip6.exthdr[i].type + end + ip6.exthdr[i].next = i==#ip6.exthdr and (last_proto or IPPROTO_NONE) or ip6.exthdr[i+1].type + end + else + -- no headers + ip6.ip6_nxt = last_proto or IPPROTO_NONE + end +end + + +-- parse autottl : delta,min-max +function parse_autottl(s) + if s then + local delta,min,max = string.match(s,"([-+]?%d+),(%d+)-(%d+)") + min = tonumber(min) + max = tonumber(max) + delta = tonumber(delta) + if not delta or min>max then + error("parse_autottl: invalid value '"..s.."'") + end + return {delta=delta,min=min,max=max} + else + return nil + end +end + +-- calculate ttl value based on incoming_ttl and parsed attl definition (delta,min-max) +function autottl(incoming_ttl, attl) + local function hop_count_guess(incoming_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 + + local orig + + if incoming_ttl>223 then + orig=255 + elseif incoming_ttl<128 and incoming_ttl>96 then + orig=128 + elseif incoming_ttl<64 and incoming_ttl>32 then + orig=64 + else + return nil + end + + return orig-incoming_ttl + end + -- return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling + local function autottl_eval(hop_count, attl) + local d,fake + + d = hop_count + attl.delta + + if dattl.max then fake=attl.max + else fake=d + end + + if attl.delta<0 and fake>=hop_count or attl.delta>=0 and fake - set tcp flags in comma separated list +-- tcp_unflags_set= - unset tcp flags in comma separated list +-- tcp_ts_up - move timestamp tcp option to the top if it's present. this allows linux not to accept badack segments without badseq. this is very strange discovery but it works. + +-- fool - custom fooling function : fool_func(dis, fooling_options) +function apply_fooling(desync, dis, fooling_options) + local function prepare_bin(hex,def) + local bin = parse_hex(hex) + if not bin then error("apply_fooling: invalid hex string '"..hex.."'") end + return #bin>0 and bin or def + end + local function ttl_discover(arg_ttl,arg_autottl) + local ttl + if arg_autottl and desync.track then + if desync.track.incoming_ttl then + -- use lua_cache to store discovered autottl + if type(desync.track.lua_state.autottl_cache)~="table" then desync.track.lua_state.autottl_cache={} end + if type(desync.track.lua_state.autottl_cache[desync.func_instance])~="table" then desync.track.lua_state.autottl_cache[desync.func_instance]={} end + if not desync.track.lua_state.autottl_cache[desync.func_instance].autottl_found then + desync.track.lua_state.autottl_cache[desync.func_instance].autottl = autottl(desync.track.incoming_ttl,parse_autottl(arg_autottl)) + if desync.track.lua_state.autottl_cache[desync.func_instance].autottl then + desync.track.lua_state.autottl_cache[desync.func_instance].autottl_found = true + DLOG("apply_fooling: discovered autottl "..desync.track.lua_state.autottl_cache[desync.func_instance].autottl) + else + DLOG("apply_fooling: could not discover autottl") + end + elseif desync.track.lua_state.autottl_cache[desync.func_instance].autottl then + DLOG("apply_fooling: using cached autottl "..desync.track.lua_state.autottl_cache[desync.func_instance].autottl) + end + ttl=desync.track.lua_state.autottl_cache[desync.func_instance].autottl + else + DLOG("apply_fooling: cannot apply autottl because incoming ttl unknown") + end + end + if not ttl and tonumber(arg_ttl) then + ttl = tonumber(arg_ttl) + end + return ttl + end + local function move_ts_top() + local tsidx = find_tcp_option(dis.tcp.options, TCP_KIND_TS) + if tsidx and tsidx>1 then + table.insert(dis.tcp.options, 1, dis.tcp.options[tsidx]) + table.remove(dis.tcp.options, tsidx + 1) + end + end + -- take default fooling from desync.arg + if not fooling_options then fooling_options = desync.arg end + -- use current packet if dissect not given + if not dis then dis = desync.dis end + if dis.tcp then + if tonumber(fooling_options.tcp_seq) then + dis.tcp.th_seq = dis.tcp.th_seq + fooling_options.tcp_seq + end + if tonumber(fooling_options.tcp_ack) then + dis.tcp.th_ack = dis.tcp.th_ack + fooling_options.tcp_ack + end + if fooling_options.tcp_flags_unset then + dis.tcp.th_flags = bitand(dis.tcp.th_flags, bitnot(parse_tcp_flags(fooling_options.tcp_flags_unset))) + end + if fooling_options.tcp_flags_set then + dis.tcp.th_flags = bitor(dis.tcp.th_flags, parse_tcp_flags(fooling_options.tcp_flags_set)) + end + if tonumber(fooling_options.tcp_ts) then + local idx = find_tcp_option(dis.tcp.options,TCP_KIND_TS) + if idx and (dis.tcp.options[idx].data and #dis.tcp.options[idx].data or 0)==8 then + dis.tcp.options[idx].data = bu32(u32(dis.tcp.options[idx].data)+fooling_options.tcp_ts)..string.sub(dis.tcp.options[idx].data,5) + else + DLOG("apply_fooling: timestamp tcp option not present or invalid") + end + end + if fooling_options.tcp_md5 then + if find_tcp_option(dis.tcp.options,TCP_KIND_MD5) then + DLOG("apply_fooling: md5 option already present") + else + table.insert(dis.tcp.options,{kind=TCP_KIND_MD5, data=prepare_bin(fooling_options.tcp_md5,brandom(16))}) + end + end + if fooling_options.tcp_ts_up then + move_ts_top(dis.tcp.options) + end + end + if dis.ip6 then + local bin + if fooling_options.ip6_hopbyhop_x2 then + bin = prepare_bin(fooling_options.ip6_hopbyhop2_x2,"\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin) + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin) + elseif fooling_options.ip6_hopbyhop then + bin = prepare_bin(fooling_options.ip6_hopbyhop,"\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin) + end + -- for possible unfragmentable part + if fooling_options.ip6_destopt then + bin = prepare_bin(fooling_options.ip6_destopt,"\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_DSTOPTS,bin) + end + if fooling_options.ip6_routing then + bin = prepare_bin(fooling_options.ip6_routing,"\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_ROUTING,bin) + end + -- for possible fragmentable part + if fooling_options.ip6_destopt2 then + bin = prepare_bin(fooling_options.ip6_destopt2,"\x00\x00\x00\x00\x00\x00") + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_DSTOPTS,bin) + end + if fooling_options.ip6_ah then + -- by default truncated authentication header - only 6 bytes + bin = prepare_bin(fooling_options.ip6_ah,"\x00\x00"..brandom(4)) + insert_ip6_exthdr(dis.ip6,nil,IPPROTO_AH,bin) + end + end + if dis.ip then + local ttl = ttl_discover(fooling_options.ip_ttl,fooling_options.ip_autottl) + if ttl then dis.ip.ip_ttl = ttl end + end + if dis.ip6 then + local ttl = ttl_discover(fooling_options.ip6_ttl,fooling_options.ip6_autottl) + if ttl then dis.ip6.ip6_hlim = ttl end + end + + if fooling_options.fool and #fooling_options.fool>0 then + if type(_G[fooling_options.fool])=="function" then + DLOG("apply_fooling: calling '"..fooling_options.fool.."'") + _G[fooling_options.fool](dis, fooling_options) + else + error("apply_fooling: fool function '"..tostring(fooling_options.fool).."' does not exist") + end + end +end + + +-- assign dis.ip.ip_id value according to policy in ipid_options or desync.arg. apply def or "seq" policy if no ip_id options +-- ip_id=seq|rnd|zero|none +-- ip_id_conn - in 'seq' mode save current ip_id in track.lua_state to use it between packets +-- remember ip_id in desync +function apply_ip_id(desync, dis, ipid_options, def) + -- use current packet if dissect not given + if not dis then dis = desync.dis end + if dis.ip then -- ip_id is ipv4 only, ipv6 doesn't have it + -- take default ipid options from desync.arg + if not ipid_options then ipid_options = desync.arg end + local mode = ipid_options.ip_id or def or "seq" + if mode == "seq" then + if desync.track and ipid_options.ip_id_conn then + dis.ip.ip_id = desync.track.lua_state.ip_id or dis.ip.ip_id + desync.track.lua_state.ip_id = dis.ip.ip_id + 1 + else + dis.ip.ip_id = desync.ip_id or dis.ip.ip_id + desync.ip_id = dis.ip.ip_id + 1 + end + elseif mode == "zero" then + dis.ip.ip_id = 0 + elseif mode == "rnd" then + dis.ip.ip_id = math.random(1,0xFFFF) + end + end +end + + +-- return length of ipv4 or ipv6 header without options and extension headers. should be 20 for ipv4 and 40 for ipv6. +function l3_base_len(dis) + if dis.ip then + return IP_BASE_LEN + elseif dis.ip6 then + return IP6_BASE_LEN + else + return 0 + end +end +-- return length of ipv4 options or summary length of all ipv6 extension headers +-- ip6_exthdr_last_idx - count lengths for headers up to this index +function l3_extra_len(dis, ip6_exthdr_last_idx) + local l=0 + if dis.ip then + if dis.ip.options then + l = bitand(#dis.ip.options+3,NOT3) + end + elseif dis.ip6 and dis.ip6.exthdr then + local ct + if ip6_exthdr_last_idx and ip6_exthdr_last_idx<=#dis.ip6.exthdr then + ct = ip6_exthdr_last_idx + else + ct = #dis.ip6.exthdr + end + for i=1, ct do + if dis.ip6.exthdr[i].type == IPPROTO_AH then + -- length in 32-bit words + l = l + bitand(3+2+#dis.ip6.exthdr[i].data,NOT3) + else + -- length in 64-bit words + l = l + bitand(7+2+#dis.ip6.exthdr[i].data,NOT7) + end + end + end + return l +end +-- return length of ipv4/ipv6 header with options/extension headers +function l3_len(dis) + return l3_base_len(dis)+l3_extra_len(dis) +end +-- return length of tcp/udp headers without options. should be 20 for tcp and 8 for udp. +function l4_base_len(dis) + if dis.tcp then + return TCP_BASE_LEN + elseif dis.udp then + return UDP_BASE_LEN + else + return 0 + end +end +-- return length of tcp options or 0 if not tcp +function l4_extra_len(dis) + local l=0 + if dis.tcp and dis.tcp.options then + for i=1, #dis.tcp.options do + l = l + 1 + if dis.tcp.options[i].kind~=TCP_KIND_NOOP and dis.tcp.options[i].kind~=TCP_KIND_END then + l = l + 1 + if dis.tcp.options[i].data then l = l + #dis.tcp.options[i].data end + end + end + -- 4 byte aligned + l = bitand(3+l,NOT3) + end + return l +end +-- return length of tcp header with options or base length of udp header - 8 bytes +function l4_len(dis) + return l4_base_len(dis)+l4_extra_len(dis) +end +-- return summary extra length of ipv4/ipv6 and tcp headers. 0 if no options, no ext headers +function l3l4_extra_len(dis) + return l3_extra_len(dis)+l4_extra_len(dis) +end +-- return summary length of ipv4/ipv6 and tcp/udp headers +function l3l4_len(dis) + return l3_len(dis)+l4_len(dis) +end +-- return summary length of ipv4/ipv6 , tcp/udp headers and payload +function packet_len(dis) + return l3l4_len(dis) + #dis.payload +end + +function rawsend_dissect_ipfrag(dis, options) + if options and options.ipfrag and options.ipfrag.ipfrag then + local frag_func = options.ipfrag.ipfrag=="" and "ipfrag2" or options.ipfrag.ipfrag + if type(_G[frag_func]) ~= "function" then + error("rawsend_dissect_ipfrag: ipfrag function '"..tostring(frag_func).."' does not exist") + end + local fragments = _G[frag_func](dis, options.ipfrag) + + -- allow ipfrag function to do extheader magic with non-standard "next protocol" + -- NOTE : dis.ip6 must have valid next protocol fields !!!!! + local reconstruct_frag = options.reconstruct and deepcopy(options.reconstruct) or {} + reconstruct_frag.ip6_preserve_next = true + + if fragments then + if options.ipfrag.ipfrag_disorder then + for i=#fragments,1,-1 do + DLOG("sending ip fragment "..i) + -- C function + if not rawsend_dissect(fragments[i], options.rawsend, reconstruct_frag) then return false end + end + else + for i, d in pairs(fragments) do + DLOG("sending ip fragment "..i) + -- C function + if not rawsend_dissect(d, options.rawsend, reconstruct_frag) then return false end + end + end + return true + end + -- ipfrag failed. send unfragmented + end + -- C function + return rawsend_dissect(dis, options and options.rawsend, options and options.reconstruct) +end + +-- send dissect with tcp segmentation based on mss value. appply specified rawsend options. +function rawsend_dissect_segmented(desync, dis, mss, options) + local discopy = deepcopy(dis) + apply_ip_id(desync, discopy, options and options.ipid) + apply_fooling(desync, discopy, options and options.fooling) + + if dis.tcp then + local extra_len = l3l4_extra_len(dis) + if extra_len >= mss then return false end + local max_data = mss - extra_len + if #discopy.payload > max_data then + local pos=1 + local len + + while pos <= #dis.payload do + len = #dis.payload - pos + 1 + if len > max_data then len = max_data end + discopy.payload = string.sub(dis.payload,pos,pos+len-1) + if not rawsend_dissect_ipfrag(discopy, options) then + -- stop if failed + return false + end + discopy.tcp.th_seq = discopy.tcp.th_seq + len + pos = pos + len + end + return true + end + end + -- no reason to segment + return rawsend_dissect_ipfrag(discopy, options) +end + +-- send specified payload based on existing L3/L4 headers in the dissect. add seq to tcp.th_seq. +function rawsend_payload_segmented(desync, payload, seq, options) + options = options or desync_opts(desync) + local dis = deepcopy(desync.dis) + if payload then dis.payload = payload end + if dis.tcp and seq then + dis.tcp.th_seq = dis.tcp.th_seq + seq + end + return rawsend_dissect_segmented(desync, dis, desync.tcp_mss, options) +end + + +-- check if desync.outgoing comply with arg.dir or def if it's not present or "out" of they are not present both. dir can be "in","out","any" +function direction_check(desync, def) + local dir = desync.arg.dir or def or "out" + return desync.outgoing and desync.arg.dir~="in" or not desync.outgoing and dir~="out" +end +-- if dir "in" or "out" cutoff current desync function from opposite direction +function direction_cutoff_opposite(ctx, desync, def) + local dir = desync.arg.dir or def or "out" + if dir=="out" then + -- cutoff in + instance_cutoff(ctx, false) + elseif dir=="in" then + -- cutoff out + instance_cutoff(ctx, true) + end +end +-- check if desync payload type comply with payload type list in arg.payload +-- if arg.payload is not present - check if desync payload is not "empty" and not "unknown" (nfqws1 behavior without "--desync-any-protocol" option) +function payload_check(desync) + if desync.arg.payload and desync.arg.payload~="known" then + if not in_list(desync.arg.payload, "all") and not in_list(desync.arg.payload, desync.l7payload) then + DLOG("payload_check: payload '"..desync.l7payload.."' does not pass '"..desync.arg.payload.."' filter") + return false + end + else + if desync.l7payload=="empty" or desync.l7payload=="unknown" then + DLOG("payload_check: payload filter accepts only known protocols") + return false + end + end + return true +end + +-- return name of replay drop field in track.lua_state for the current desync function instance +function replay_drop_key(desync) + return desync.func_instance .. "_replay_drop" +end +-- set/unset replay drop flag in track.lua_state for the current desync function instance +function replay_drop_set(desync, v) + if desync.track then + if v == nil then v=true end + local rdk = replay_drop_key(desync) + if v then + if desync.replay then desync.track.lua_state[replay_drop_key] = true end + else + desync.track.lua_state[replay_drop_key] = nil + end + end +end +-- auto unset replay drop flag if desync is not replay or it's the last replay piece +-- return true if the caller should return VERDICT_DROP +function replay_drop(desync) + if desync.track then + local drop = desync.replay and desync.track.lua_state[replay_drop_key] + if not desync.replay or desync.replay_piece_last then + -- replay stopped or last piece of reasm + replay_drop_set(desync, false) + end + if drop then + DLOG("dropping replay packet because reasm was already sent") + return true + end + end + return false +end +-- true if desync is not replay or it's the first replay piece +function replay_first(desync) + return not desync.replay or desync.replay_piece==1 +end + +-- generate random host +-- template "google.com", len=16 : h82aj.google.com +-- template "google.com", len=11 : .google.com +-- template "google.com", len=10 : google.com +-- template "google.com", len=7 : gle.com +-- no template, len=6 : b8c54a +-- no template, len=7 : u9a.edu +-- no template, len=10 : jgha7c.com +function genhost(len, template) + if template and #template>0 then + if len <= #template then + return string.sub(template,#template-len+1) + elseif len==(#template+1) then + return "."..template + else + return brandom_az(1)..brandom_az09(len-#template-2).."."..template + end + else + if len>=7 then + local tlds = {"com","org","net","edu","gov","biz"} + local tld = tlds[math.random(#tlds)] + return brandom_az(1)..brandom_az09(len-#tld-1-1).."."..tld + else + return brandom_az(1)..brandom_az09(len-1) + end + end +end + + +-- arg : wsize=N . tcp window size +-- arg : scale=N . tcp option scale factor +-- return : true of changed anything +function wsize_rewrite(dis, arg) + local b = false + if arg.wsize then + local wsize = tonumber(arg.wsize) + DLOG("window size "..dis.tcp.th_win.." => "..wsize) + dis.tcp.th_win = tonumber(arg.wsize) + b = true + end + if arg.scale then + local scale = tonumber(arg.scale) + local i = find_tcp_option(dis.tcp.options, TCP_KIND_SCALE) + if i then + local oldscale = u8(dis.tcp.options[i].data) + if scale>oldscale then + DLOG("not increasing scale factor") + elseif scale "..scale) + dis.tcp.options[i].data = bu8(scale) + b = true + end + end + end + return b +end + +-- standard fragmentation to 2 ip fragments +-- function returns 2 dissects with fragments +-- option : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8 +-- option : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32 +-- option : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default. +-- option : ipfrag_disorder - send fragments from last to first +function ipfrag2(dis, ipfrag_options) + local function frag_idx(exthdr) + -- fragment header after hopbyhop, destopt, routing + -- allow second destopt header to be in fragmentable part + -- test case : --lua-desync=send:ipfrag:ipfrag_pos_tcp=40:ip6_hopbyhop:ip6_destopt:ip6_destopt2 + -- WINDOWS may not send second ipv6 fragment with next protocol 60 (destopt) + -- test case windows : --lua-desync=send:ipfrag:ipfrag_pos_tcp=40:ip6_hopbyhop:ip6_destopt:ip6_destopt2:ipfrag_next=255 + if exthdr then + local first_destopts + for i=1,#exthdr do + if exthdr[i].type==IPPROTO_DSTOPTS then + first_destopts = i + break + end + end + for i=#exthdr,1,-1 do + if exthdr[i].type==IPPROTO_HOPOPTS or exthdr[i].type==IPPROTO_ROUTING or (exthdr[i].type==IPPROTO_DSTOPTS and i==first_destopts) then + return i+1 + end + end + end + return 1 + end + + local pos + local dis1, dis2 + local l3 + + if dis.tcp then + pos = ipfrag_options.ipfrag_pos_tcp or 32 + elseif dis.udp then + pos = ipfrag_options.ipfrag_pos_udp or 8 + else + pos = ipfrag_options.ipfrag_pos or 32 + end + + DLOG("ipfrag2") + + if not pos then + error("ipfrag2: no frag position") + end + l3 = l3_len(dis) + if bitand(pos,7)~=0 then + error("ipfrag2: frag position must be multiple of 8") + end + if (pos+l3)>0xFFFF then + error("ipfrag2: too high frag offset") + end + local plen = l3 + l4_len(dis) + #dis.payload + if (pos+l3)>=plen then + DLOG("ipfrag2: ip frag pos exceeds packet length. ipfrag cancelled.") + return nil + end + + if dis.ip then + -- ipv4 frag is done by both lua and C part + -- lua code must correctly set ip_len, IP_MF and ip_off and provide full unfragmented payload + -- ip_len must be set to valid value as it would appear in the fragmented packet + -- ip_off must be set to fragment offset and IP_MF bit must be set if it's not the last fragment + -- C code constructs unfragmented packet then moves everything after ip header according to ip_off and ip_len + + -- ip_id must not be zero or fragment will be dropped + local ip_id = dis.ip.ip_id==0 and math.random(1,0xFFFF) or dis.ip.ip_id + dis1 = deepcopy(dis) + -- ip_len holds the whole packet length starting from the ip header. it includes ip, transport headers and payload + dis1.ip.ip_len = l3 + pos -- ip header + first part up to frag pos + dis1.ip.ip_off = IP_MF -- offset 0, IP_MF - more fragments + dis1.ip.ip_id = ip_id + dis2 = deepcopy(dis) + dis2.ip.ip_off = bitrshift(pos,3) -- offset = frag pos, IP_MF - not set + dis2.ip.ip_len = plen - pos -- unfragmented packet length - frag pos + dis2.ip.ip_id = ip_id + end + + if dis.ip6 then + -- ipv6 frag is done by both lua and C part + -- lua code must insert fragmentation extension header at any desirable position, fill fragment offset, more fragments flag and ident + -- lua must set up ip6_plen as it would appear in the fragmented packet + -- C code constructs unfragmented packet then moves fragmentable part as needed + + local idxfrag = frag_idx(dis.ip6.exthdr) + local l3extra = l3_extra_len(dis, idxfrag-1) + 8 -- all ext headers before frag + 8 bytes for frag header + local ident = math.random(1,0xFFFFFFFF) + + dis1 = deepcopy(dis) + insert_ip6_exthdr(dis1.ip6, idxfrag, IPPROTO_FRAGMENT, bu16(IP6F_MORE_FRAG)..bu32(ident)) + dis1.ip6.ip6_plen = l3extra + pos + dis2 = deepcopy(dis) + insert_ip6_exthdr(dis2.ip6, idxfrag, IPPROTO_FRAGMENT, bu16(pos)..bu32(ident)) + -- only next proto of the first fragment is considered by standard + -- fragments with non-zero offset can have different "next protocol" field + -- this can be used to evade protection systems + if ipfrag_options.ipfrag_next then + dis2.ip6.exthdr[idxfrag].next = tonumber(ipfrag_options.ipfrag_next) + end + dis2.ip6.ip6_plen = plen - IP6_BASE_LEN + 8 - pos -- packet len without frag + 8 byte frag header - ipv6 base header + end + + return {dis1,dis2} +end diff --git a/lua/zapret-tests.lua b/lua/zapret-tests.lua new file mode 100644 index 0000000..873afc3 --- /dev/null +++ b/lua/zapret-tests.lua @@ -0,0 +1,581 @@ +-- nfqws2 C functions tests +-- to run : --lua-init=@zapret-lib.lua --lua-init=@zapret-tests.lua --lua-init="test_all()" + +function test_assert(b) + assert(b, "test failed") +end + +function test_run(tests) + for k,f in pairs(tests) do + f() + end +end + + +function test_all() + test_run({test_crypto, test_bin, test_ipstr, test_dissect, test_csum, test_resolve, test_rawsend}) +end + + +function test_crypto() + test_run({test_random, test_aes, test_aes_gcm, test_hkdf, test_hash}) +end + +function test_random() + local rnds={} + for i=1,20 do + local rnd = bcryptorandom(math.random(10,20)) + print("random: "..string2hex(rnd)) + test_assert(not rnds[rnd]) -- should not be repeats + rnds[rnd] = true + end +end + +function test_hash() + local hashes={} + for i=1,5 do + local rnd = brandom(math.random(5,64)) + print("data: "..string2hex(rnd)) + for k,sha in pairs({"sha256","sha224"}) do + local hsh = hash(sha, rnd) + print(sha..": "..string2hex(hsh)) + local hsh2 = hash(sha, rnd) + test_assert(hsh==hsh2) + test_assert(not hashes[hsh]) + hashes[hsh] = true + end + end +end + +function test_hkdf() + local nblob = 2 + local okms = {} + for nsalt=1,nblob do + local salt = brandom(math.random(10,20)) + for nikm=1,nblob do + local ikm = brandom(math.random(5,10)) + for ninfo=1,nblob do + local info = brandom(math.random(5,10)) + local okm_prev + for k,sha in pairs({"sha256","sha224"}) do + for k,okml in pairs({8, 16, 50}) do + local okm_prev + local okm + print("* hkdf "..sha) + print("salt: "..string2hex(salt)) + print("ikm : "..string2hex(ikm)) + print("info: "..string2hex(info)) + print("okml: "..tostring(okml)) + okm = hkdf(sha, salt, ikm, info, okml) + test_assert(okm) + print("okm: "..string2hex(okm)) + if okms[okm] then + print("duplicate okm !") + end + okms[okm] = true + + test_assert(not okm_prev or okm_prev==string.sub(okm, 1, #okm_prev)) + okm_prev = okm + end + end + end + end + end +end + +function test_aes() + local clear_text="test "..brandom_az09(11) + local iv, key, encrypted, decrypted + + for key_size=16,32,8 do + local key = brandom(key_size) + + print() + print("* aes test key_size "..tostring(key_size)) + + print("clear text: "..clear_text); + + print("* encrypting") + encrypted = aes(true, key, clear_text) + print("encrypted: "..str_or_hex(encrypted)) + + print("* decrypting everything good") + decrypted = aes(false, key, encrypted) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted==clear_text) + + print("* decrypting bad payload with good key") + decrypted = aes(false, key, brandom(16)) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted~=clear_text) + + print("* decrypting good payload with bad key") + decrypted = aes(false, brandom(key_size), encrypted) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted~=clear_text) + + end +end + +function test_aes_gcm() + local authenticated_data = "authenticated message "..brandom_az09(math.random(10,50)) + local clear_text="test message "..brandom_az09(math.random(10,50)) + local iv, key, encrypted, atag, decrypted, atag2 + + for key_size=16,32,8 do + iv = brandom(12) + key = brandom(key_size) + + print() + print("* aes_gcm test key_size "..tostring(key_size)) + + print("clear text: "..clear_text); + print("authenticated data: "..authenticated_data); + + print("* encrypting") + encrypted, atag = aes_gcm(true, key, iv, clear_text, authenticated_data) + print("encrypted: "..str_or_hex(encrypted)) + print("auth tag: "..string2hex(atag)) + + print("* decrypting everything good") + decrypted, atag2 = aes_gcm(false, key, iv, encrypted, authenticated_data) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted==clear_text) + print("auth tag: "..string2hex(atag2)) + print( atag==atag2 and "TAG OK" or "TAG ERROR" ) + test_assert(atag==atag2) + + print("* decrypting bad payload with good key/iv and correct authentication data") + decrypted, atag2 = aes_gcm(false, key, iv, brandom(#encrypted), authenticated_data) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted~=clear_text) + print("auth tag: "..string2hex(atag2)) + print( atag==atag2 and "TAG OK" or "TAG ERROR" ) + test_assert(atag~=atag2) + + print("* decrypting good payload with good key/iv and incorrect authentication data") + decrypted, atag2 = aes_gcm(false, key, iv, encrypted, authenticated_data.."x") + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted==clear_text) + print("auth tag: "..string2hex(atag2)) + print( atag==atag2 and "TAG OK" or "TAG ERROR" ) + test_assert(atag~=atag2) + + print("* decrypting good payload with bad key, good iv and correct authentication data") + decrypted, atag2 = aes_gcm(false, brandom(key_size), iv, encrypted, authenticated_data) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted~=clear_text) + print("auth tag: "..string2hex(atag2)) + print( atag==atag2 and "TAG OK" or "TAG ERROR" ) + test_assert(atag~=atag2) + + print("* decrypting good payload with good key, bad iv and correct authentication data") + decrypted, atag2 = aes_gcm(false, key, brandom(12), encrypted, authenticated_data) + print("decrypted: "..str_or_hex(decrypted)) + print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" ) + test_assert(decrypted~=clear_text) + print("auth tag: "..string2hex(atag2)) + print( atag==atag2 and "TAG OK" or "TAG ERROR" ) + test_assert(atag~=atag2) + end +end + + + +function test_ub() + for k,f in pairs({{u8,bu8,0xFF,8}, {u16,bu16,0xFFFF,16}, {u24,bu24,0xFFFFFF,24}, {u32,bu32,0xFFFFFFFF,32}}) do + local v = math.random(0,f[3]) + local pos = math.random(1,20) + local s = brandom(pos-1)..f[2](v)..brandom(20) + local v2 = f[1](s,pos) + print("u"..tostring(f[4]).." pos="..tostring(pos).." "..tostring(v).." "..tostring(v2)) + test_assert(v==v2) + end +end + +function test_bit() + local v, v2, v3, v4, b1, b2, pow + + v = math.random(0,0xFFFFFFFFFFFF) + b1 = math.random(1,15) + + v2 = bitrshift(v, b1) + pow = 2^b1 + v3 = divint(v, pow) + print(string.format("rshift(0x%X,%u) = 0x%X 0x%X/%u = 0x%X", v,b1,v2, v,pow,v3)) + test_assert(v2==v3) + + v2 = bitlshift(v, b1) + pow = 2^b1 + v3 = v * pow + print(string.format("lshift(0x%X,%u) = 0x%X 0x%X*%u = 0x%X", v,b1,v2, v,pow,v3)) + test_assert(v2==v3) + + v2 = math.random(0,0xFFFFFFFFFFFF) + v3 = bitxor(v, v2) + v4 = bitor(v, v2) - bitand(v, v2) + print(string.format("xor(0x%X,0x%X) = %X or/and/minus = %X", v, v2, v3, v4)) + test_assert(v3==v4) + + b2 = b1 + math.random(1,31) + v2 = bitget(v, b1, b2) + pow = 2^(b2-b1+1) - 1 + v3 = bitand(bitrshift(v,b1), pow) + print(string.format("bitget(0x%X,%u,%u) = 0x%X bitand/bitrshift/pow = 0x%X", v, b1, b2, v2, v3)) + test_assert(v2==v3) + + v4 = math.random(0,pow) + v2 = bitset(v, b1, b2, v4) + v3 = bitor(bitlshift(v4, b1), bitand(v, bitnot(bitlshift(pow, b1)))) + print(string.format("bitset(0x%X,%u,%u,0x%X) = 0x%X bitand/bitnot/bitlshift/pow = 0x%X", v, b1, b2, v4, v2, v3)) + test_assert(v2==v3) +end + +function test_bin() + test_run({test_ub, test_bit}) +end + + +function test_ipstr() + local s_ip, ip, s_ip2 + + s_ip = string.format("%u.%u.%u.%u", math.random(0,255), math.random(0,255), math.random(0,255), math.random(0,255)); + ip = pton(s_ip) + s_ip2 = ntop(ip) + print("IP: "..s_ip) + print("IPBIN: "..string2hex(ip)) + print("IP2: "..s_ip2) + test_assert(s_ip==s_ip2) + + s_ip = string.format("%x:%x:%x:%x:%x:%x:%x:%x", math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF)); + ip = pton(s_ip) + s_ip2 = ntop(ip) + print("IP: "..s_ip) + print("IPBIN: "..string2hex(ip)) + print("IP2: "..s_ip2) + test_assert(s_ip==s_ip2) +end + + +function test_dissect() + local dis, raw1, raw2 + + for i=1,20 do + print("* dissect test "..tostring(i)) + + local ip_tcp = { + ip = { + ip_tos = math.random(0,255), + ip_id = math.random(0,0xFFFF), + ip_off = 0, + ip_ttl = math.random(0,255), + ip_p = IPPROTO_TCP, + ip_src = brandom(4), + ip_dst = brandom(4), + options = brandom(math.random(0,40)) + }, + tcp = { + th_sport = math.random(0,0xFFFF), + th_dport = math.random(0,0xFFFF), + th_seq = math.random(0,0xFFFFFFFF), + th_ack = math.random(0,0xFFFFFFFF), + th_x2 = math.random(0,0xF), + th_flags = math.random(0,0xFF), + th_win = math.random(0,0xFFFF), + th_urp = math.random(0,0xFFFF), + options = { + { kind = 1 }, + { kind = 0xE0, data = brandom(math.random(1,10)) }, + { kind = 1 }, + { kind = 0xE1, data = brandom(math.random(1,10)) }, + { kind = 0 } + } + }, + payload = brandom(math.random(0, 20)) + } + raw1 = reconstruct_dissect(ip_tcp) + print("IP+TCP : "..string2hex(raw1)) + dis1 = dissect(raw1); + raw2 = reconstruct_dissect(dis1) + dis2 = dissect(raw2); + print("IP+TCP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + + local ip6_udp = { + ip6 = { + ip6_flow = 0x60000000 + math.random(0,0xFFFFFFF), + ip6_hlim = math.random(1,0xFF), + ip6_src = brandom(16), + ip6_dst = brandom(16), + exthdr = { + { type = IPPROTO_HOPOPTS, data = brandom(6+8*math.random(0,2)) }, + { type = IPPROTO_AH, data = brandom(6+4*math.random(0,4)) } + } + }, + udp = { + uh_sport = math.random(0,0xFFFF), + uh_dport = math.random(0,0xFFFF) + }, + payload = brandom(math.random(0, 20)) + } + + raw1 = reconstruct_dissect(ip6_udp) + print("IP6+UDP : "..string2hex(raw1)) + dis1 = dissect(raw1); + raw2 = reconstruct_dissect(dis1) + dis2 = dissect(raw2); + print("IP6+UDP2: "..string2hex(raw2)) + print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" ) + test_assert(raw1==raw2) + end +end + +function test_csum() + local payload = brandom(math.random(10,20)) + local ip4b, ip6b, raw, tcpb, udpb, dis1, dis2 + local ip = { + ip_tos = math.random(0,255), + ip_id = math.random(0,0xFFFF), + ip_len = math.random(0,0xFFFF), + ip_off = 0, + ip_ttl = math.random(0,255), + ip_p = IPPROTO_TCP, + ip_src = brandom(4), + ip_dst = brandom(4), + options = brandom(4*math.random(0,10)) + } + ip4b = reconstruct_iphdr(ip) + raw = bu8(0x40 + 5 + #ip.options/4) .. + bu8(ip.ip_tos) .. + bu16(ip.ip_len) .. + bu16(ip.ip_id) .. + bu16(ip.ip_off) .. + bu8(ip.ip_ttl) .. + bu8(ip.ip_p) .. + bu16(0) .. + ip.ip_src .. ip.ip_dst .. + ip.options + raw = csum_ip4_fix(raw) + print( raw==ip4b and "IP4 RECONSTRUCT+CSUM OK" or "IP4 RECONSTRUCT+CSUM FAILED" ) + test_assert(raw==ip4b) + + + local tcp = { + th_sport = math.random(0,0xFFFF), + th_dport = math.random(0,0xFFFF), + th_seq = math.random(0,0xFFFFFFFF), + th_ack = math.random(0,0xFFFFFFFF), + th_x2 = math.random(0,0xF), + th_flags = math.random(0,0xFF), + th_win = math.random(0,0xFFFF), + th_urp = math.random(0,0xFFFF), + options = { + { kind = 1 }, + { kind = 0xE0, data = brandom(math.random(1,10)) }, + { kind = 1 }, + { kind = 0xE1, data = brandom(math.random(1,10)) }, + { kind = 0 } + } + } + tcpb = reconstruct_tcphdr(tcp) + raw = bu16(tcp.th_sport) .. + bu16(tcp.th_dport) .. + bu32(tcp.th_seq) .. + bu32(tcp.th_ack) .. + bu8(l4_len({tcp = tcp}) * 4 + tcp.th_x2) .. + bu8(tcp.th_flags) .. + bu16(tcp.th_win) .. + bu16(0) .. + bu16(tcp.th_urp) .. + bu8(tcp.options[1].kind).. + bu8(tcp.options[2].kind)..bu8(2 + #tcp.options[2].data)..tcp.options[2].data .. + bu8(tcp.options[3].kind).. + bu8(tcp.options[4].kind)..bu8(2 + #tcp.options[4].data)..tcp.options[4].data .. + bu8(tcp.options[5].kind) + raw = raw .. string.rep(bu8(TCP_KIND_NOOP), bitand(4-bitand(#raw,3),3)) + print( raw==tcpb and "TCP RECONSTRUCT OK" or "TCP RECONSTRUCT FAILED" ) + test_assert(raw==tcpb) + + raw = reconstruct_dissect({ip=ip, tcp=tcp, payload=payload}) + dis1 = dissect(raw) + tcpb = csum_tcp_fix(ip4b,tcpb,payload) + dis2 = dissect(ip4b..tcpb..payload) + print( dis1.tcp.th_sum==dis2.tcp.th_sum and "TCP+IP4 CSUM OK" or "TCP+IP4 CSUM FAILED" ) + test_assert(dis1.tcp.th_sum==dis2.tcp.th_sum) + + + local ip6 = { + ip6_flow = 0x60000000 + math.random(0,0xFFFFFFF), + ip6_hlim = math.random(1,0xFF), + ip6_src = brandom(16), + ip6_dst = brandom(16), + exthdr = { + { type = IPPROTO_HOPOPTS, data = brandom(6+8*math.random(0,2)) } + } + } + ip6.ip6_plen = packet_len({ip6=ip6,tcp=tcp,payload=payload}) - IP6_BASE_LEN + ip6b = reconstruct_ip6hdr(ip6, {ip6_last_proto=IPPROTO_TCP}) + raw = bu32(ip6.ip6_flow) .. + bu16(ip6.ip6_plen) .. + bu8(ip6.exthdr[1].type) .. + bu8(ip6.ip6_hlim) .. + ip6.ip6_src .. ip6.ip6_dst .. + bu8(IPPROTO_TCP) .. + bu8((#ip6.exthdr[1].data+2)/8 - 1) .. + ip6.exthdr[1].data + print( raw==ip6b and "IP6 RECONSTRUCT OK" or "IP6 RECONSTRUCT FAILED" ) + test_assert(raw==ip6b) + + raw = reconstruct_dissect({ip6=ip6, tcp=tcp, payload=payload}) + dis1 = dissect(raw) + tcpb = csum_tcp_fix(ip6b,tcpb,payload) + dis2 = dissect(ip6b..tcpb..payload) + print( dis1.tcp.th_sum==dis2.tcp.th_sum and "TCP+IP6 CSUM OK" or "TCP+IP6 CSUM FAILED" ) + test_assert(dis1.tcp.th_sum==dis2.tcp.th_sum) + + + ip.ip_p = IPPROTO_UDP + ip4b = reconstruct_iphdr(ip) + ip6.ip6_plen = packet_len({ip6=ip6,udp=udp,payload=payload}) - IP6_BASE_LEN + ip6b = reconstruct_ip6hdr(ip6, {ip6_last_proto=IPPROTO_UDP}) + + local udp = { + uh_sport = math.random(0,0xFFFF), + uh_dport = math.random(0,0xFFFF), + uh_ulen = UDP_BASE_LEN + #payload + } + + udpb = reconstruct_udphdr(udp) + raw = bu16(udp.uh_sport) .. + bu16(udp.uh_dport) .. + bu16(udp.uh_ulen) .. + bu16(0) + print( raw==udpb and "UDP RECONSTRUCT OK" or "UDP RECONSTRUCT FAILED" ) + test_assert(raw==udpb) + + raw = reconstruct_dissect({ip=ip, udp=udp, payload=payload}) + dis1 = dissect(raw) + udpb = csum_udp_fix(ip4b,udpb,payload) + dis2 = dissect(ip4b..udpb..payload) + print( dis1.udp.uh_sum==dis2.udp.uh_sum and "UDP+IP4 CSUM OK" or "UDP+IP4 CSUM FAILED" ) + test_assert(dis1.udp.uh_sum==dis2.udp.uh_sum) + + raw = reconstruct_dissect({ip6=ip6, udp=udp, payload=payload}) + dis1 = dissect(raw) + udpb = csum_udp_fix(ip6b,udpb,payload) + dis2 = dissect(ip6b..udpb..payload) + print( dis1.udp.uh_sum==dis2.udp.uh_sum and "UDP+IP6 CSUM OK" or "UDP+IP6 CSUM FAILED" ) + test_assert(dis1.udp.uh_sum==dis2.udp.uh_sum) +end + +function test_resolve() + local pos + + pos = zero_based_pos(resolve_multi_pos(fake_default_tls,"tls_client_hello","1,extlen,sniext,host,sld,midsld,endsld,endhost,-5")) + test_assert(pos) + print("resolve_multi_pos tls : "..table.concat(pos," ")) + pos = zero_based_pos(resolve_range(fake_default_tls,"tls_client_hello","host,endhost")) + test_assert(pos) + print("resolve_range tls : "..table.concat(pos," ")) + pos = resolve_pos(fake_default_tls,"tls_client_hello","midsld") + test_assert(pos) + print("resolve_pos tls : "..pos - 1) + pos = resolve_pos(fake_default_tls,"tls_client_hello","method") + test_assert(not pos) + print("resolve_pos tls non-existent : "..tostring(pos)) + + pos = zero_based_pos(resolve_multi_pos(fake_default_http,"http_req","method,host,sld,midsld,endsld,endhost,-5")) + test_assert(pos) + print("resolve_multi_pos http : "..table.concat(pos," ")) + pos = resolve_pos(fake_default_http,"http_req","sniext") + test_assert(not pos) + print("resolve_pos http non-existent : "..tostring(pos)) +end + +function test_rawsend() + local ip, ip6, udp, dis, ddis, raw_ip, raw_udp, raw + local payload = brandom(math.random(100,1200)) + + ip = { + ip_tos = 0, + ip_id = math.random(0,0xFFFF), + ip_off = 0, + ip_ttl = 1, + ip_p = IPPROTO_UDP, + ip_src = pton("192.168.1.1"), + ip_dst = pton("192.168.1.2") + } + udp = { + uh_sport = math.random(0,0xFFFF), + uh_dport = math.random(0,0xFFFF) + } + dis = {ip = ip, udp = udp, payload = payload} + print("send ipv4 udp") + test_assert(rawsend_dissect(dis, {repeats=3})) + + ddis = ipfrag2(dis, {ipfrag_pos_udp = 80}) + for k,d in pairs(ddis) do + print("send ipv4 udp frag "..k) + test_assert(rawsend_dissect(d)) + end + + raw_ip = reconstruct_iphdr(ip) + raw_udp = reconstruct_udphdr({uh_sport = udp.uh_sport, uh_dport = udp.uh_dport, uh_ulen = UDP_BASE_LEN + #payload}) + raw_udp = csum_udp_fix(raw_ip,raw_udp,payload) + raw = raw_ip .. raw_udp .. payload + print("send ipv4 udp using pure rawsend without dissect") + test_assert(rawsend(raw, {repeats=5})) + + ip6 = { + ip6_flow = 0x60000000, + ip6_hlim = 1, + ip6_src = pton("fdce:3124:164a:5318::1"), + ip6_dst = pton("fdce:3124:164a:5318::2") + } + dis = {ip6 = ip6, udp = udp, payload = payload} + print("send ipv6 udp") + test_assert(rawsend_dissect(dis, {repeats=3})) + + ddis = ipfrag2(dis, {ipfrag_pos_udp = 80}) + for k,d in pairs(ddis) do + print("send ipv6 udp frag "..k) + test_assert(rawsend_dissect(d)) + end + + ip6.exthdr={{ type = IPPROTO_HOPOPTS, data = "\x00\x00\x00\x00\x00\x00" }} + print("send ipv6 udp with hopbyhop ext header") + test_assert(rawsend_dissect(dis, {repeats=3})) + + ddis = ipfrag2(dis, {ipfrag_pos_udp = 80}) + for k,d in pairs(ddis) do + print("send ipv6 udp frag "..k.." with hopbyhop ext header") + test_assert(rawsend_dissect(d)) + end + + table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" }) + table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" }) + ip6.ip6_flow = 0x60001234; + ddis = ipfrag2(dis, {ipfrag_pos_udp = 80}) + for k,d in pairs(ddis) do + print("send ipv6 udp frag "..k.." with hopbyhop, destopt ext headers in unfragmentable part and another destopt ext header in fragmentable part") + test_assert(rawsend_dissect(d, {fwmark = 0x50EA})) + end + + fix_ip6_next(ip6) -- required to forge next proto in the second fragment + ip6.ip6_flow = 0x6000AE38; + ddis = ipfrag2(dis, {ipfrag_pos_udp = 80, ipfrag_next = IPPROTO_TCP}) + for k,d in pairs(ddis) do + print("send ipv6 udp frag "..k.." with hopbyhop, destopt ext headers in unfragmentable part and another destopt ext header in fragmentable part. forge next proto in fragment header of the second fragment to TCP") + -- reconstruct dissect using next proto fields in the dissect. do not auto fix next proto chain. + -- by default reconstruct fixes next proto chain + test_assert(rawsend_dissect(d, {fwmark = 0x409A, repeats=2}, {ip6_preserve_next = true})) + end +end diff --git a/lua/zapret-wgobfs.lua b/lua/zapret-wgobfs.lua new file mode 100644 index 0000000..61dd965 --- /dev/null +++ b/lua/zapret-wgobfs.lua @@ -0,0 +1,82 @@ +-- test case : nfqws2 --qnum 200 --debug --lua-init=@zapret-wgobfs.lua --in-range=a --out-range=a --lua-desync=wgobfs:secret=mycoolpassword +-- encrypt standard wireguard messages - initiation, response, cookie - and change udp packet size +-- do not encrypt data messages and keepalives +-- wgobfs adds maximum of 30+padmax bytes to udp size +-- reduce MTU of wireguard interface to avoid ip fragmentation ! +-- without knowing the secret encrypted packets should be crypto strong white noise with no signature +-- arg : secret - shared secret. any string. must be the same on both peers +-- arg : padmin - min random garbage bytes. 0 by default +-- arg : padmax - max random garbage bytes. 16 by default +function wgobfs(ctx, desync) + local padmin = desync.arg.padmin and tonumber(desync.arg.padmin) or 0 + local padmax = desync.arg.padmax and tonumber(desync.arg.padmax) or 16 + local function genkey() + -- cache key in lua_state of conntrack is present + if desync.track and desync.track.lua_state.wgobfs_key then + key = desync.track.lua_state.wgobfs_key + end + if not key then + key = hkdf("sha256", "wgobfs_salt", desync.arg.secret, nil, 16) + if desync.track then + desync.track.lua_state.wgobfs_key = key + end + end + return key + end + local function maybe_encrypted_payload(payload) + for k,plsize in pairs({2+12+16+148, 2+12+16+92, 2+12+16+64}) do + if #payload>=(plsize+padmin) and #payload<=(plsize+padmax) then + return true + end + end + return false + end + local function wg_payload_from_size(payload) + if #payload==148 then return "wireguard_initiation" + elseif #payload==92 then return "wireguard_response" + elseif #payload==64 then return "wireguard_cookie" + else return nil + end + end + + if not desync.dis.udp then + instance_cutoff(ctx) + return + end + if not desync.arg.secret or #desync.arg.secret==0 then + error("wgobfs requires secret") + end + if padmin>padmax then + error("wgobfs: padmin>padmax") + end + if desync.l7payload=="wireguard_initiation" or desync.l7payload=="wireguard_response" or desync.l7payload=="wireguard_cookie" and #desync.dis.payload<65506 then + DLOG("wgobfs: encrypting '"..desync.l7payload.."'. size "..#desync.dis.payload) + local key = genkey() + -- in aes-gcm every message require it's own crypto secure random iv + -- encryption more than one message with the same iv is considered catastrophic failure + -- iv must be sent with encrypted message + local iv = bcryptorandom(12) + local encrypted, atag = aes_gcm(true, key, iv, bu16(#desync.dis.payload)..desync.dis.payload..brandom(math.random(padmin,padmax)), nil) + desync.dis.payload = iv..atag..encrypted + return VERDICT_MODIFY + end + + if desync.l7payload=="unknown" and maybe_encrypted_payload(desync.dis.payload) then + local key = genkey() + local iv = string.sub(desync.dis.payload,1,12) + local atag = string.sub(desync.dis.payload,13,28) + local decrypted, atag2 = aes_gcm(false, key, iv, string.sub(desync.dis.payload,29)) + if atag==atag2 then + local plen = u16(decrypted) + if plen>(#decrypted-2) then + DLOG("wgobfs: bad decrypted payload data") + else + desync.dis.payload = string.sub(decrypted, 3, 3+plen-1) + if b_debug then DLOG("wgobfs: decrypted '"..(wg_payload_from_size(desync.dis.payload) or "unknown").."' message. size "..plen) end + return VERDICT_MODIFY + end + else + DLOG("wgobfs: decrypt auth tag mismatch") + end + end +end diff --git a/mdig/Makefile b/mdig/Makefile new file mode 100644 index 0000000..e4a011b --- /dev/null +++ b/mdig/Makefile @@ -0,0 +1,35 @@ +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 diff --git a/mdig/mdig.c b/mdig/mdig.c new file mode 100644 index 0000000..b2447f6 --- /dev/null +++ b/mdig/mdig.c @@ -0,0 +1,596 @@ +// 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 2 + +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 < 0x20 || (*dom & 0x80) || !(*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>0x7F) *dom = '?'; + if (*dom) *dom = 0; +} + +#define FAMILY4 1 +#define FAMILY6 2 +static struct +{ + char verbose; + char family; + int threads; + 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; + struct addrinfo *result; + + 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; + 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 < RESOLVER_EAGAIN_ATTEMPTS; 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) 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\n" + " --family=<4|6|46>\t\t; ipv4, ipv6, ipv4+ipv6\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" + ); + 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_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_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; + 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 = optarg ? atoi(optarg) : 0; + if (glob.threads <= 0 || glob.threads > 100) + { + fprintf(stderr, "thread number must be within 1..100\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; +} diff --git a/nfq2/BSDmakefile b/nfq2/BSDmakefile new file mode 100644 index 0000000..e29df74 --- /dev/null +++ b/nfq2/BSDmakefile @@ -0,0 +1,42 @@ +CC ?= cc +OPTIMIZE ?= -Os +CFLAGS += -std=gnu99 -s $(OPTIMIZE) -flto=auto -Wno-address-of-packed-member +LIBS = -lz -lm +SRC_FILES = *.c crypto/*.c + +LUA_JIT ?= 1 + +.if "${LUA_JIT}" == "1" + +LUAJIT_VER?=2.1 +LUA_VER?=5.1 +LUA_PKG:=luajit + +.else + +LUA_VER ?= 5.4 +LUA_VER_UNDOTTED!= echo $(LUA_VER) | sed 's/\.//g' + +OSNAME!=uname +.if ${OSNAME} == "OpenBSD" + LUA_PKG ?= lua$(LUA_VER_UNDOTTED) +.elif ${OSNAME} == "FreeBSD" + LUA_PKG ?= lua-$(LUA_VER) +.endif + +.endif + +LUA_LIB!= pkg-config --libs $(LUA_PKG) +LUA_CFLAGS!= pkg-config --cflags $(LUA_PKG) + +.if "${LUA_JIT}" == "1" +LUA_CFLAGS+=-DLUAJIT +.endif + +all: dvtws2 + +dvtws2: $(SRC_FILES) + $(CC) $(CFLAGS) $(LUA_CFLAGS) -o dvtws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LDFLAGS) + +clean: + rm -f dvtws2 diff --git a/nfq2/Makefile b/nfq2/Makefile new file mode 100644 index 0000000..2f90320 --- /dev/null +++ b/nfq2/Makefile @@ -0,0 +1,152 @@ +CC ?= cc +OPTIMIZE ?= -Os +CFLAGS += -std=gnu99 $(OPTIMIZE) -flto=auto +CFLAGS_SYSTEMD = -DUSE_SYSTEMD +CFLAGS_BSD = -Wno-address-of-packed-member +CFLAGS_CYGWIN = -Wno-address-of-packed-member -static +LDFLAGS_ANDROID = -llog +LIBS = +LIBS_LINUX = -lz -lnetfilter_queue -lnfnetlink -lmnl -lm +LIBS_SYSTEMD = -lsystemd +LIBS_BSD = -lz -lm +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 + + +LUA_JIT?=1 + +ifeq ($(LUA_JIT),1) + +LUAJIT_VER?=2.1 +LUAJIT_LUA_VER?=5.1 +LUA_PKG:=luajit + +$(info trying luajit $(LUAJIT_VER) lua $(LUAJIT_LUA_VER)) + +LUA_LIB_NAME= +ifeq ($(LUA_CFLAGS),) + LUA_CFLAGS := $(shell pkg-config --cflags $(LUA_PKG) 2>/dev/null) + ifeq ($(LUA_CFLAGS),) + LUA_CFLAGS := -I/usr/local/include/luajit-$(LUAJIT_VER) -I/usr/include/luajit-$(LUAJIT_VER) + endif +endif +ifeq ($(LUA_LIB),) + LUA_LIB := $(shell pkg-config --libs $(LUA_PKG) 2>/dev/null) + LUA_LIB_DIR := + + ifeq ($(LUA_LIB),) + ifneq ($(wildcard /usr/local/lib/libluajit-$(LUAJIT_LUA_VER).*),) + LUA_LIB_NAME:=luajit-$(LUAJIT_LUA_VER) + LUA_LIB_DIR:=/usr/local/lib + else ifneq ($(wildcard /usr/lib/libluajit-$(LUAJIT_LUA_VER).*),) + LUA_LIB_NAME:=luajit-$(LUAJIT_LUA_VER) + endif + ifeq ($(LUA_LIB_NAME),) + $(info could not find luajit lib name) + LUA_LIB:= + LUA_CFLAGS:= + else + ifneq ($(LUA_LIB_DIR),) + LUA_LIB = -L$(LUA_LIB_DIR) + endif + LUA_LIB += -l$(LUA_LIB_NAME) + endif + endif +endif + +LUA_CFL := -DLUAJIT + +else + +LUA_CFL := + +endif + + + +ifeq ($(LUA_LIB),) + +# no success with luajit + +LUA_VER?=5.4 +LUA_VER_UNDOTTED:=$(shell echo $(LUA_VER) | sed 's/\.//g') + +LUA_CFL := + +$(info trying lua $(LUA_VER)) + +OSNAME := $(shell uname) +ifeq ($(OSNAME),FreeBSD) + LUA_PKG:=lua-$(LUA_VER) +else + LUA_PKG:=lua$(LUA_VER_UNDOTTED) +endif + +ifeq ($(LUA_CFLAGS),) + LUA_CFLAGS := $(shell pkg-config --cflags $(LUA_PKG) 2>/dev/null) + ifeq ($(LUA_CFLAGS),) + LUA_CFLAGS := -I/usr/local/include/lua$(LUA_VER) -I/usr/local/include/lua-$(LUA_VER) -I/usr/include/lua$(LUA_VER) -I/usr/include/lua-$(LUA_VER) + endif +endif +ifeq ($(LUA_LIB),) + LUA_LIB := $(shell pkg-config --libs $(LUA_PKG) 2>/dev/null) + LUA_LIB_DIR := + ifeq ($(LUA_LIB),) + ifneq ($(wildcard /usr/local/lib/liblua-$(LUA_VER).*),) + LUA_LIB_NAME:=lua-$(LUA_VER) + LUA_LIB_DIR:=/usr/local/lib + else ifneq ($(wildcard /usr/local/lib/liblua$(LUA_VER).*),) + LUA_LIB_NAME:=lua$(LUA_VER) + LUA_LIB_DIR:=/usr/local/lib + else ifneq ($(wildcard /usr/local/lib/liblua.*),) + LUA_LIB_NAME:=lua + LUA_LIB_DIR:=/usr/local/lib + else ifneq ($(wildcard /usr/lib/liblua-$(LUA_VER).*),) + LUA_LIB_NAME:=lua-$(LUA_VER) + else ifneq ($(wildcard /usr/lib/liblua$(LUA_VER).*),) + LUA_LIB_NAME:=lua$(LUA_VER) + else ifneq ($(wildcard /usr/lib/liblua.*),) + LUA_LIB_NAME:=lua + endif + ifeq ($(LUA_LIB_NAME),) + $(error could not find lua lib name) + endif + ifneq ($(LUA_LIB_DIR),) + LUA_LIB = -L$(LUA_LIB_DIR) + endif + LUA_LIB += -l$(LUA_LIB_NAME) + endif +endif + +endif + + +LUA_CFL += $(LUA_CFLAGS) + + +all: nfqws2 + +nfqws2: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(LUA_CFL) -o nfqws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_LINUX) $(LDFLAGS) + +systemd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(LUA_CFL) $(CFLAGS_SYSTEMD) -o nfqws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_LINUX) $(LIBS_SYSTEMD) $(LDFLAGS) + +android: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(LUA_CFL) -o nfqws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_LINUX) $(LDFLAGS) $(LDFLAGS_ANDROID) + +bsd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(LUA_CFL) $(CFLAGS_BSD) -o dvtws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_BSD) $(LDFLAGS) + +cygwin64: + $(CC) -s $(CFLAGS) $(LUA_CFL) $(CFLAGS_CYGWIN) -o winws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_CYGWIN) $(LIBS_CYGWIN64) $(RES_CYGWIN64) $(LDFLAGS) +cygwin32: + $(CC) -s $(CFLAGS) $(LUA_CFL) $(CFLAGS_CYGWIN) -o winws2 $(SRC_FILES) $(LIBS) $(LUA_LIB) $(LIBS_CYGWIN) $(LIBS_CYGWIN32) $(RES_CYGWIN32) $(LDFLAGS) +cygwin: cygwin64 + +clean: + rm -f nfqws2 dvtws2 winws2.exe diff --git a/nfq2/checksum.c b/nfq2/checksum.c new file mode 100644 index 0000000..dcc3657 --- /dev/null +++ b/nfq2/checksum.c @@ -0,0 +1,159 @@ +#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); +} diff --git a/nfq2/checksum.h b/nfq2/checksum.h new file mode 100644 index 0000000..c33831e --- /dev/null +++ b/nfq2/checksum.h @@ -0,0 +1,27 @@ +#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); diff --git a/nfq2/conntrack.c b/nfq2/conntrack.c new file mode 100644 index 0000000..0e81583 --- /dev/null +++ b/nfq2/conntrack.c @@ -0,0 +1,413 @@ +#include "conntrack.h" +#include "darkmagic.h" +#include +#include + +#include "params.h" +#include "lua.h" + +#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); + luaL_unref(params.L, LUA_REGISTRYINDEX, track->lua_state); + luaL_unref(params.L, LUA_REGISTRYINDEX, track->lua_instance_cutoff); +} + +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->l7proto = L7_UNKNOWN; + t->scale_orig = t->scale_reply = SCALE_NONE; + time(&t->t_start); + rawpacket_queue_init(&t->delayed); + lua_newtable(params.L); + t->lua_state = luaL_ref(params.L, LUA_REGISTRYINDEX); + lua_newtable(params.L); + t->lua_instance_cutoff = luaL_ref(params.L, LUA_REGISTRYINDEX); +} +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; + uint16_t mss; + + if (bReverse) + { + t->pcounter_reply++; + t->pdcounter_reply += !!len_payload; + t->pbcounter_reply += len_payload; + } + + else + { + t->pcounter_orig++; + t->pdcounter_orig += !!len_payload; + t->pbcounter_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); + mss = ntohs(tcp_find_mss(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); + t->winsize_reply_calc = t->winsize_reply; + if (t->scale_reply != SCALE_NONE) t->winsize_reply_calc <<= t->scale_reply; + if (mss && !t->mss_reply) t->mss_reply = mss; + 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); + t->winsize_orig_calc = t->winsize_orig; + if (t->scale_orig != SCALE_NONE) t->winsize_orig_calc <<= t->scale_orig; + if (mss && !t->mss_reply) t->mss_orig = mss; + 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; + uint8_t proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : IPPROTO_NONE; + + 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: + ctr->track.ipproto = proto; + 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 orig=d%llu/n%llu/b%llu reply=d%llu/n%llu/b%lld ", + 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.pbcounter_orig, + (unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply, (unsigned long long)t->track.pbcounter_reply); + if (t->conn.l4proto == IPPROTO_TCP) + printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u mss_orig=%u mss_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.mss_orig, t->track.mss_reply, + 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 lua_in_cutoff=%u lua_out_cutoff=%u hostname=%s l7proto=%s\n", + t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_lua_in_cutoff, t->track.b_lua_out_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 (len < szcopy) szcopy = len; + memcpy(reasm->packet + 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; +} diff --git a/nfq2/conntrack.h b/nfq2/conntrack.h new file mode 100644 index 0000000..57144b6 --- /dev/null +++ b/nfq2/conntrack.h @@ -0,0 +1,136 @@ +#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 + uint8_t ipproto; + + 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) + uint64_t pbcounter_orig, pbcounter_reply; // transferred byte counter. includes retransmissions. it's not the same as relative seq. + 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 + uint32_t winsize_orig_calc, winsize_reply_calc; // calculated window size + uint16_t mss_orig, mss_reply; + + 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; + + bool b_cutoff; // mark for deletion + bool b_lua_in_cutoff,b_lua_out_cutoff; + + t_l7proto l7proto; + bool l7proto_discovered; + char *hostname; + bool hostname_is_ip; + bool hostname_discovered; + bool hostname_ah_check; // should perform autohostlist checks + + int lua_state; // registry index of associated LUA object + int lua_instance_cutoff; // registry index of per connection function instance cutoff table + + 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);} diff --git a/nfq2/crypto/aes-gcm.c b/nfq2/crypto/aes-gcm.c new file mode 100644 index 0000000..130cd60 --- /dev/null +++ b/nfq2/crypto/aes-gcm.c @@ -0,0 +1,15 @@ +#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; + + 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; +} diff --git a/nfq2/crypto/aes-gcm.h b/nfq2/crypto/aes-gcm.h new file mode 100644 index 0000000..d836001 --- /dev/null +++ b/nfq2/crypto/aes-gcm.h @@ -0,0 +1,6 @@ +#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); diff --git a/nfq2/crypto/aes.c b/nfq2/crypto/aes.c new file mode 100644 index 0000000..49cd313 --- /dev/null +++ b/nfq2/crypto/aes.c @@ -0,0 +1,483 @@ +/****************************************************************************** +* +* 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 */ diff --git a/nfq2/crypto/aes.h b/nfq2/crypto/aes.h new file mode 100644 index 0000000..06b1ef5 --- /dev/null +++ b/nfq2/crypto/aes.h @@ -0,0 +1,78 @@ +/****************************************************************************** +* +* 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 1 // 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 diff --git a/nfq2/crypto/gcm.c b/nfq2/crypto/gcm.c new file mode 100644 index 0000000..74842ce --- /dev/null +++ b/nfq2/crypto/gcm.c @@ -0,0 +1,512 @@ +/****************************************************************************** +* +* 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 != 0) 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... + */ + gcm_start(ctx, mode, iv, iv_len, add, add_len); + gcm_update(ctx, length, input, output); + gcm_finish(ctx, tag, tag_len); + return(0); +} + + +/****************************************************************************** + * + * 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 + /* + 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 + */ + gcm_crypt_and_tag(ctx, AES_DECRYPT, iv, iv_len, add, add_len, + input, output, length, check_tag, tag_len); + + // 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 != 0) { // 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)); +} diff --git a/nfq2/crypto/gcm.h b/nfq2/crypto/gcm.h new file mode 100644 index 0000000..42adad9 --- /dev/null +++ b/nfq2/crypto/gcm.h @@ -0,0 +1,183 @@ +/****************************************************************************** +* +* 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 unsigned int size_t;// use the right type for length declarations +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); diff --git a/nfq2/crypto/hkdf.c b/nfq2/crypto/hkdf.c new file mode 100644 index 0000000..266cb37 --- /dev/null +++ b/nfq2/crypto/hkdf.c @@ -0,0 +1,337 @@ +/**************************** 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]; + return hkdfExtract(whichSha, salt, salt_len, ikm, ikm_len, prk) || + 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); + } + else if (salt_len < 0) { + return shaBadParam; + } + 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; + + if (info == 0) { + info = (const unsigned char *)""; + info_len = 0; + } + else if (info_len < 0) { + return shaBadParam; + } + if (okm_len <= 0) return shaBadParam; + if (!okm) 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; + int ret = hmacReset(&context, whichSha, prk, prk_len) || + hmacInput(&context, T, Tlen) || + hmacInput(&context, info, info_len) || + hmacInput(&context, &c, 1) || + hmacResult(&context, T); + if (ret != shaSuccess) 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; + + ret = hmacResult(&context->hmacContext, prk) || + hkdfExpand(context->whichSha, prk, context->hashSize, info, + info_len, okm, okm_len); + context->Computed = 1; + return context->Corrupted = ret; +} + diff --git a/nfq2/crypto/hmac.c b/nfq2/crypto/hmac.c new file mode 100644 index 0000000..9e05325 --- /dev/null +++ b/nfq2/crypto/hmac.c @@ -0,0 +1,250 @@ +/**************************** 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; + return hmacReset(&context, whichSha, key, key_len) || + hmacInput(&context, message_array, length) || + 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; + int err = USHAReset(&tcontext, whichSha) || + USHAInput(&tcontext, key, key_len) || + USHAResult(&tcontext, tempkey); + if (err != shaSuccess) return err; + + 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 */ + ret = USHAReset(&context->shaContext, whichSha) || + /* and start with inner pad */ + 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.) */ + ret = + USHAResult(&context->shaContext, digest) || + + /* perform outer SHA */ + /* init context for 2nd pass */ + USHAReset(&context->shaContext, context->whichSha) || + + /* start with outer pad */ + USHAInput(&context->shaContext, context->k_opad, + context->blockSize) || + + /* then results of 1st hash */ + USHAInput(&context->shaContext, digest, context->hashSize) || + /* finish up 2nd pass */ + USHAResult(&context->shaContext, digest); + + context->Computed = 1; + return context->Corrupted = ret; +} diff --git a/nfq2/crypto/sha-private.h b/nfq2/crypto/sha-private.h new file mode 100644 index 0000000..4ceba0d --- /dev/null +++ b/nfq2/crypto/sha-private.h @@ -0,0 +1,25 @@ +/************************ 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)) diff --git a/nfq2/crypto/sha.h b/nfq2/crypto/sha.h new file mode 100644 index 0000000..8b3a63b --- /dev/null +++ b/nfq2/crypto/sha.h @@ -0,0 +1,278 @@ +/**************************** 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); diff --git a/nfq2/crypto/sha224-256.c b/nfq2/crypto/sha224-256.c new file mode 100644 index 0000000..2c9bc9c --- /dev/null +++ b/nfq2/crypto/sha224-256.c @@ -0,0 +1,581 @@ +/************************* 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 uint32_t addTemp; +#define SHA224_256AddLength(context, length) \ + (addTemp = (context)->Length_Low, (context)->Corrupted = \ + (((context)->Length_Low += (length)) < addTemp) && \ + (++(context)->Length_High == 0) ? shaInputTooLong : \ + (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; +} + diff --git a/nfq2/crypto/usha.c b/nfq2/crypto/usha.c new file mode 100644 index 0000000..861b4d0 --- /dev/null +++ b/nfq2/crypto/usha.c @@ -0,0 +1,191 @@ +/**************************** 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; + } +} diff --git a/nfq2/darkmagic.c b/nfq2/darkmagic.c new file mode 100644 index 0000000..3d3c141 --- /dev/null +++ b/nfq2/darkmagic.c @@ -0,0 +1,1737 @@ +#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, TCP_KIND_TS); + 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, TCP_KIND_SCALE); + 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, TCP_KIND_FASTOPEN); + 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(const struct tcphdr *tcp) +{ + uint8_t *t = tcp_find_option((struct tcphdr *)tcp, TCP_KIND_MSS); + return (t && t[1]==4) ? *(uint16_t*)(t+2) : 0; +} +bool tcp_has_sack(struct tcphdr *tcp) +{ + uint8_t *t = tcp_find_option(tcp, TCP_KIND_SACK_PERM); + return !!t; +} + +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; +} + +bool extract_dst(const uint8_t *data, size_t len, struct sockaddr* dst) +{ + if (proto_check_ipv4(data,len)) + { + struct sockaddr_in *in = (struct sockaddr_in *)dst; + in->sin_family = AF_INET; + in->sin_port = 0; + in->sin_addr = ((struct ip*)data)->ip_dst; + } + else if (proto_check_ipv6(data,len)) + { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)dst; + in6->sin6_family = AF_INET6; + in6->sin6_port = 0; + in6->sin6_flowinfo = 0; + in6->sin6_scope_id = 0; + in6->sin6_addr = ((struct ip6_hdr*)data)->ip6_dst; + } + else + return false; + return true; +} + +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 >= sizeof(struct ip) && (data[0] & 0xF0) == 0x40 && + len >= ((data[0] & 0x0F) << 2); +} +// move to transport protocol +void proto_skip_ipv4(const 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 >= sizeof(struct tcphdr) && len >= ((data[12] & 0xF0) >> 2); +} +void proto_skip_tcp(const 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 >= sizeof(struct udphdr) && len>=(data[4]<<8 | data[5]); +} +void proto_skip_udp(const uint8_t **data, size_t *len) +{ + *data += 8; + *len -= 8; +} + +bool proto_check_ipv6(const uint8_t *data, size_t len) +{ + return len >= sizeof(struct ip6_hdr) && (data[0] & 0xF0) == 0x60 && + (len - sizeof(struct ip6_hdr)) >= ntohs(((struct ip6_hdr*)data)->ip6_ctlun.ip6_un1.ip6_un1_plen); +} +// move to transport protocol +// proto_type = 0 => error +void proto_skip_ipv6(const uint8_t **data, size_t *len, uint8_t *proto_type, const 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 (last_header_type) *last_header_type = (*data)+6; + *data += sizeof(struct ip6_hdr); *len -= sizeof(struct ip6_hdr); // skip ipv6 base header + while (*len) // need at least one byte for NextHeader field + { + switch (HeaderType) + { + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + case IPPROTO_MH: // mobility header + case IPPROTO_HIP: // Host Identity Protocol Version v2 + case IPPROTO_SHIM6: + if (*len < 2) return; // error + hdrlen = 8 + ((*data)[1] << 3); + break; + case IPPROTO_FRAGMENT: // fragment. length fixed to 8, hdrlen field defined as reserved + hdrlen = 8; + break; + case IPPROTO_AH: + // 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 IPPROTO_NONE: // 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 +} + +bool proto_set_last_ip6_proto(struct ip6_hdr *ip6, size_t len, uint8_t proto) +{ + size_t hdrlen; + uint8_t HeaderType, *pproto, *data; + + if (lenip6_ctlun.ip6_un1.ip6_un1_nxt; + data = (uint8_t*)(ip6+1); + len -= sizeof(struct ip6_hdr); + while (len) // need at least one byte for NextHeader field + { + switch (*pproto) + { + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + case IPPROTO_MH: // mobility header + case IPPROTO_HIP: // Host Identity Protocol Version v2 + case IPPROTO_SHIM6: + if (len < 2) return false; // error + hdrlen = 8 + (data[1] << 3); + break; + case IPPROTO_FRAGMENT: // fragment. length fixed to 8, hdrlen field defined as reserved + hdrlen = 8; + break; + case IPPROTO_AH: + // special case. length in ah header is in 32-bit words minus 2 + if (len < 2) return false; // error + hdrlen = 8 + (data[1] << 2); + break; + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + *pproto = proto; + return true; + } + if (len < hdrlen) return false; // error + pproto = data; + len -= hdrlen; data += hdrlen; + } + *pproto = proto; + return true; +} + +uint8_t *proto_find_ip6_exthdr(struct ip6_hdr *ip6, size_t len, uint8_t proto) +{ + size_t hdrlen; + uint8_t HeaderType, last_proto, *data; + + if (lenip6_ctlun.ip6_un1.ip6_un1_nxt; + data = (uint8_t*)(ip6+1); + len -= sizeof(struct ip6_hdr); + while (len) // need at least one byte for NextHeader field + { + if (last_proto==proto) return data; // found + switch (last_proto) + { + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + case IPPROTO_MH: // mobility header + case IPPROTO_HIP: // Host Identity Protocol Version v2 + case IPPROTO_SHIM6: + if (len < 2) return false; // error + hdrlen = 8 + (data[1] << 3); + break; + case IPPROTO_FRAGMENT: // fragment. length fixed to 8, hdrlen field defined as reserved + hdrlen = 8; + break; + case IPPROTO_AH: + // special case. length in ah header is in 32-bit words minus 2 + if (len < 2) return false; // error + hdrlen = 8 + (data[1] << 2); + break; + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + // exthdr was not found + return NULL; + } + if (len < hdrlen) return false; // error + last_proto = *data; + len -= hdrlen; data += hdrlen; + } + // exthdr was not found + return NULL; +} + +void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis) +{ + const uint8_t *p; + + memset(dis,0,sizeof(*dis)); + + dis->data_pkt = data; + dis->len_pkt = len; + + if (proto_check_ipv4(data, len)) + { + dis->ip = (const struct ip *) data; + dis->proto = dis->ip->ip_p; + p = data; + proto_skip_ipv4(&data, &len); + dis->len_l3 = data-p; + } + else if (proto_check_ipv6(data, len)) + { + dis->ip6 = (const struct ip6_hdr *) data; + p = data; + proto_skip_ipv6(&data, &len, &dis->proto, NULL); + dis->len_l3 = data-p; + } + else + { + return; + } + + if (dis->proto==IPPROTO_TCP && proto_check_tcp(data, len)) + { + dis->tcp = (const struct tcphdr *) data; + dis->transport_len = len; + + p = data; + proto_skip_tcp(&data, &len); + dis->len_l4 = data-p; + + dis->data_payload = data; + dis->len_payload = len; + + } + else if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len)) + { + dis->udp = (const struct udphdr *) data; + dis->transport_len = len; + + p = data; + proto_skip_udp(&data, &len); + dis->len_l4 = data-p; + + 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); +} + + +uint8_t ttl46(const struct ip *ip, const struct ip6_hdr *ip6) +{ + return ip ? ip->ip_ttl : ip6 ? ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim : 0; +} + + +#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); + + if (!windivert_send(data,len,&wa)) + { + DLOG_ERR("windivert send error. win32 code %u\n",w_win32_error); + return false; + } + return true; +} + +#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_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; +} + +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); + +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, 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); + + 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) +{ + 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, 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; +} +static int wlan_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), wlan_info_attr_cb, wc->wlan+wc->count); + if (ret>=0 && *wc->wlan[wc->count].ifname && wc->wlan[wc->count].ifindex) + { + if (*wc->wlan[wc->count].ssid) + wc->count++; + else + { + // sometimes nl80211 does not return SSID but wireless ext does + int wext_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (wext_fd!=-1) + { + struct iwreq req; + snprintf(req.ifr_ifrn.ifrn_name,sizeof(req.ifr_ifrn.ifrn_name),"%s",wc->wlan[wc->count].ifname); + req.u.essid.pointer = wc->wlan[wc->count].ssid; + req.u.essid.length = sizeof(wc->wlan[wc->count].ssid); + req.u.essid.flags = 0; + if (ioctl(wext_fd, SIOCGIWESSID, &req)!=-1) + if (*wc->wlan[wc->count].ssid) + wc->count++; + close(wext_fd); + } + } + } + return ret; +} +static bool wlan_info(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* 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, wlan_info_cb, w); +} + +static bool wlan_init80211(struct mnl_socket** nl) +{ + if (!(*nl = mnl_socket_open(NETLINK_GENERIC))) + { + DLOG_PERROR("mnl_socket_open"); + return false; + } + if (mnl_socket_bind(*nl, 0, MNL_SOCKET_AUTOPID)) + { + DLOG_PERROR("mnl_socket_bind"); + return false; + } + return true; +} + +static void wlan_deinit80211(struct mnl_socket** nl) +{ + if (*nl) + { + mnl_socket_close(*nl); + *nl = NULL; + } +} + +static time_t wlan_info_last = 0; +static bool wlan_info_rate_limited(struct mnl_socket* nl, uint16_t wlan_family_id, struct wlan_interface_collection* w) +{ + bool bres = true; + time_t now = time(NULL); + + // do not purge too often to save resources + if (wlan_info_last != now) + { + bres = wlan_info(nl,wlan_family_id,w); + wlan_info_last = now; + } + return bres; +} + +static struct mnl_socket* nl_wifi = NULL; +static uint16_t id_nl80211; +struct wlan_interface_collection wlans = { .count = 0 }; + +void wlan_info_deinit(void) +{ + wlan_deinit80211(&nl_wifi); +} +bool wlan_info_init(void) +{ + wlan_info_deinit(); + + if (!wlan_init80211(&nl_wifi)) return false; + if (!(id_nl80211 = wlan_get_family_id(nl_wifi))) + { + wlan_info_deinit(); + return false; + } + return true; +} +bool wlan_info_get(void) +{ + return wlan_info(nl_wifi, id_nl80211, &wlans); +} +bool wlan_info_get_rate_limited(void) +{ + return wlan_info_rate_limited(nl_wifi, id_nl80211, &wlans); +} + +#endif + + +#ifdef HAS_FILTER_SSID +const char *wlan_ifname2ssid(const struct wlan_interface_collection *w, const char *ifname) +{ + int i; + if (ifname) + { + for (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 + + +void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, const struct ip *ip, const struct ip6_hdr *ip6hdr) +{ + // always fix csum for windivert. original can be partial or bad + // FreeBSD tend to pass ipv6 frames with wrong checksum (OBSERVED EARLIER, MAY BE FIXED NOW) + // Linux passes correct checksums +#ifndef __linux__ + if (!(verdict & VERDICT_NOCSUM) && (verdict & VERDICT_MASK)==VERDICT_PASS) + { + #ifdef __FreeBSD__ + if (ip6hdr) + #endif + { + DLOG("fixing tcp checksum\n"); + tcp_fix_checksum(tcphdr,transport_len,ip,ip6hdr); + } + } +#endif +} +void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, const struct ip *ip, const struct ip6_hdr *ip6hdr) +{ + // always fix csum for windivert. original can be partial or bad + // FreeBSD tend to pass ipv6 frames with wrong checksum (OBSERVED EARLIER, MAY BE FIXED NOW) + // Linux passes correct checksums +#ifndef __linux__ + if (!(verdict & VERDICT_NOCSUM) && (verdict & VERDICT_MASK)==VERDICT_PASS) + { + #ifdef __FreeBSD__ + if (ip6hdr) + #endif + DLOG("fixing udp checksum\n"); + udp_fix_checksum(udphdr,transport_len,ip,ip6hdr); + } +#endif +} + +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; +} diff --git a/nfq2/darkmagic.h b/nfq2/darkmagic.h new file mode 100644 index 0000000..7010a99 --- /dev/null +++ b/nfq2/darkmagic.h @@ -0,0 +1,198 @@ +#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 + +#define TCP_KIND_END 0 +#define TCP_KIND_NOOP 1 +#define TCP_KIND_MSS 2 +#define TCP_KIND_SCALE 3 +#define TCP_KIND_SACK_PERM 4 +#define TCP_KIND_SACK 5 +#define TCP_KIND_TS 8 +#define TCP_KIND_MD5 19 +#define TCP_KIND_AO 29 +#define TCP_KIND_FASTOPEN 34 + +#ifndef IPPROTO_MH +#define IPPROTO_MH 135 +#endif +#ifndef IPPROTO_HIP +#define IPPROTO_HIP 139 +#endif +#ifndef IPPROTO_SHIM6 +#define IPPROTO_SHIM6 140 +#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 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_MASK_VALID 7 + +#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) + +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); +bool extract_dst(const uint8_t *data, size_t len, struct sockaddr* 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(const 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); +bool rawsend_rep(int repeats, const struct sockaddr* dst, uint32_t fwmark, const char *ifout, const void *data, size_t len); + +#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(const uint8_t **data, size_t *len); +bool proto_check_ipv6(const uint8_t *data, size_t len); +void proto_skip_ipv6(const uint8_t **data, size_t *len, uint8_t *proto_type, const uint8_t **last_header_type); +bool proto_set_last_ip6_proto(struct ip6_hdr *ip6, size_t len, uint8_t proto); +uint8_t *proto_find_ip6_exthdr(struct ip6_hdr *ip6, size_t len, uint8_t proto); +bool proto_check_tcp(const uint8_t *data, size_t len); +void proto_skip_tcp(const uint8_t **data, size_t *len); +bool proto_check_udp(const uint8_t *data, size_t len); +void proto_skip_udp(const uint8_t **data, size_t *len); +struct dissect +{ + const uint8_t *data_pkt; + size_t len_pkt; + const struct ip *ip; + const struct ip6_hdr *ip6; + size_t len_l3; + uint8_t proto; + const struct tcphdr *tcp; + const struct udphdr *udp; + size_t len_l4; + size_t transport_len; + const uint8_t *data_payload; + size_t len_payload; +}; +void proto_dissect_l3l4(const 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); + +uint8_t ttl46(const struct ip *ip, const struct ip6_hdr *ip6); + +void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, const struct ip *ip, const struct ip6_hdr *ip6hdr); +void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, const struct ip *ip, const 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(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 diff --git a/nfq2/desync.c b/nfq2/desync.c new file mode 100644 index 0000000..0dda67b --- /dev/null +++ b/nfq2/desync.c @@ -0,0 +1,1943 @@ +#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" +#include "lua.h" + +#define PKTDATA_MAXDUMP 32 +#define IP_MAXDUMP 80 + +#define TCP_MAX_REASM 16384 +#define UDP_MAX_REASM 16384 + +typedef struct +{ + t_l7payload l7p; + t_l7proto l7; + bool(*check)(const uint8_t*, size_t); + bool l7match; +} t_protocol_probe; + +static void protocol_probe(t_protocol_probe *probe, int probe_count, const uint8_t *data_payload, size_t len_payload, t_ctrack *ctrack, t_l7proto *l7proto, t_l7payload *l7payload) +{ + for (int i = 0; i < probe_count; i++) + { + if ((!probe[i].l7match || *l7proto==probe[i].l7) && probe[i].check(data_payload, len_payload)) + { + *l7payload = probe[i].l7p; + if (*l7proto == L7_UNKNOWN) + { + *l7proto = probe[i].l7; + if (ctrack && ctrack->l7proto == L7_UNKNOWN) ctrack->l7proto = *l7proto; + } + DLOG("packet contains %s payload\n", l7payload_str(*l7payload)); + break; + } + } +} + + +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; + bool bServerHello = IsTLSHandshakeServerHello(tls, sz, true); + + 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) + { + if (bServerHello) + { + v = pntoh16(ext); + DLOG("TLS supported versions ext : %s\n", TLSVersionStr(v)); + } + else + { + 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 (!bServerHello) + { + 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); +} + + +static bool dp_match( + struct desync_profile *dp, + uint8_t l3proto, + const struct in_addr *ip, const struct in6_addr *ip6, uint16_t port, + 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 ((ip && !dp->filter_ipv4) || (ip6 && !dp->filter_ipv6)) + // L3 filter does not match + return false; + + if ((l3proto == IPPROTO_TCP && !port_filters_in_range(&dp->pf_tcp, port)) || (l3proto == IPPROTO_UDP && !port_filters_in_range(&dp->pf_udp, port))) + // L4 filter does not match + return false; + + if (!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, ip, ip6)) + // target ip does not match + return false; + + // autohostlist profile matching l3/l4/l7 filter always win if we have a hostname. no matter it matches or not. + if (dp->hostlist_auto && hostname) 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 in_addr *ip, const struct in6_addr *ip6, uint16_t port, + 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 s[40]; + ntopa46(ip, ip6, s, sizeof(s)); + DLOG("desync profile search for %s ip=%s port=%u l7proto=%s ssid='%s' hostname='%s'\n", proto_name(l3proto), s, port, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : ""); + } + if (bCheckDone) *bCheckDone = false; + LIST_FOREACH(dpl, head, next) + { + if (dp_match(&dpl->dp, l3proto, ip, ip6, port, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded)) + { + DLOG("desync profile %u matches\n", dpl->dp.n); + return &dpl->dp; + } + } + DLOG("desync profile not found\n"); + return NULL; +} + + +static void ctrack_stop_retrans_counter(t_ctrack *ctrack) +{ + if (ctrack && ctrack->hostname_ah_check) + ctrack->req_retrans_counter = RETRANS_COUNTER_STOP; +} + +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 %u) : %s : fail counter reset. website is working.\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %u : 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 %u) : %s : fail counter %d/%d\n", dp->n, hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %u : 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 %u) : 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 %u) : rechecking %s to avoid duplicates\n", dp->n, hostname); + bool bExcluded = false; + if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded) + { + DLOG("auto hostlist (profile %u) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %u : 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 %u) : NOT adding %s\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %u : 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) +{ + if (params.server) return; // no autohostlists in server mode + + 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 %u : 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 rawpacket_queue_csum_fix(struct rawpacket_tailhead *q, const struct dissect *dis, const struct sockaddr_storage* dst, uint32_t fwmark, uint32_t desync_fwmark, const char *ifin, const char *ifout) +{ + // this breaks const pointer to l4 header + if (dis->tcp) + verdict_tcp_csum_fix(VERDICT_PASS, (struct tcphdr *)dis->tcp, dis->transport_len, dis->ip, dis->ip6); + else if (dis->udp) + verdict_udp_csum_fix(VERDICT_PASS, (struct udphdr *)dis->udp, dis->transport_len, dis->ip, dis->ip6); + return rawpacket_queue(q, dst, fwmark, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload); +} + + +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, const struct dissect *dis, uint8_t *mod_pkt, size_t *len_mod_pkt) +{ +#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 && (params.server ? ctrack->pcounter_reply : ctrack->pcounter_orig) == 1 || dis->tcp && (tcp_syn_segment(dis->tcp) || tcp_synack_segment(dis->tcp))) + { + if (dis->len_pkt > *len_mod_pkt) + DLOG_ERR("linux postnat conntrack workaround cannot be applied\n"); + else + { + memcpy(mod_pkt, dis->data_pkt, dis->len_pkt); + DLOG("applying linux postnat conntrack workaround\n"); + // make ip protocol invalid and low TTL + if (dis->ip6) + { + ((struct ip6_hdr*)mod_pkt)->ip6_ctlun.ip6_un1.ip6_un1_nxt = 255; + ((struct ip6_hdr*)mod_pkt)->ip6_ctlun.ip6_un1.ip6_un1_hlim = 1; + } + if (dis->ip) + { + // this likely also makes ipv4 header checksum invalid + ((struct ip*)mod_pkt)->ip_p = 255; + ((struct ip*)mod_pkt)->ip_ttl = 1; + } + *len_mod_pkt = dis->len_pkt; + } + return VERDICT_MODIFY | VERDICT_NOCSUM; + } +#endif + return VERDICT_DROP; +} + + +static uint64_t pos_get(const t_ctrack *ctrack, char mode, bool bReply) +{ + if (ctrack) + { + switch (mode) + { + case 'n': return bReply ? ctrack->pcounter_reply : ctrack->pcounter_orig; + case 'd': return bReply ? ctrack->pdcounter_reply : ctrack->pdcounter_orig; + case 's': return bReply ? (ctrack->ack_last - ctrack->ack0) : (ctrack->seq_last - ctrack->seq0); + case 'b': return bReply ? ctrack->pbcounter_reply : ctrack->pbcounter_orig; + } + } + return 0; +} +static bool check_pos_from(const t_ctrack *ctrack, bool bReply, const struct packet_range *range) +{ + uint64_t pos; + if (range->from.mode == 'x') return false; + if (range->from.mode != 'a') + { + if (ctrack) + { + pos = pos_get(ctrack, range->from.mode, bReply); + return pos >= range->from.pos; + } + else + return false; + } + return true; +} +static bool check_pos_to(const t_ctrack *ctrack, bool bReply, const struct packet_range *range) +{ + uint64_t pos; + if (range->to.mode == 'x') return false; + if (range->to.mode != 'a') + { + if (ctrack) + { + pos = pos_get(ctrack, range->to.mode, bReply); + return (pos < range->to.pos) || !range->upper_cutoff && (pos == range->to.pos); + } + else + return false; + } + return true; +} +static bool check_pos_cutoff(const t_ctrack *ctrack, bool bReply, const struct packet_range *range) +{ + bool bto = check_pos_to(ctrack, bReply, range); + return ctrack ? !bto : (!bto || !check_pos_from(ctrack, bReply, range)); +} +static bool check_pos_range(const t_ctrack *ctrack, bool bReply, const struct packet_range *range) +{ + return check_pos_from(ctrack, bReply, range) && check_pos_to(ctrack, bReply, range); +} + + +static bool replay_queue(struct rawpacket_tailhead *q); + +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 void ipcache_update_ttl(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) +{ + // no need to cache ttl in server mode because first packet is incoming + if (ctrack && !params.server) + { + ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface); + if (!ipc) + { + DLOG_ERR("ipcache: out of memory\n"); + return; + } + if (ctrack->incoming_ttl) + { + if (ipc->ttl != ctrack->incoming_ttl) + { + DLOG("updated ttl cache\n"); + ipc->ttl = ctrack->incoming_ttl; + } + } + else if (ipc->ttl) + { + DLOG("got cached ttl %u\n", ipc->ttl); + ctrack->incoming_ttl = ipc->ttl; + } + } +} +static void ipcache_get_ttl(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface) +{ + // no need to cache ttl in server mode because first packet is incoming + if (ctrack && !ctrack->incoming_ttl && !params.server) + { + ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface); + if (!ipc) + DLOG_ERR("ipcache: out of memory\n"); + else if (ipc->ttl) + { + DLOG("got cached ttl %u\n", ipc->ttl); + ctrack->incoming_ttl = ipc->ttl; + } + } +} + + + +static bool desync_get_result(uint8_t *verdict) +{ + int rescount = lua_gettop(params.L); + if (rescount>1) + { + DLOG_ERR("desync function returned more than one result : %d\n", rescount); + goto err; + } + if (rescount) + { + if (!lua_isinteger(params.L, -1)) + { + DLOG_ERR("desync function returned non-int result\n"); + goto err; + } + lua_Integer lv = lua_tointeger(params.L, -1); + if (lv & ~VERDICT_MASK) + { + DLOG_ERR("desync function returned bad int result\n"); + goto err; + } + *verdict = (uint8_t)lv; + } + else + *verdict = VERDICT_PASS; // default result if function returns nothing + lua_pop(params.L, rescount); + return true; +err: + lua_pop(params.L, rescount); + return false; +} +static void desync_instance(const char *func, unsigned int dp_n, unsigned int func_n, char *instance, size_t inst_size) +{ + snprintf(instance, inst_size, "%s_%u_%u", func, dp_n, func_n); +} +static uint8_t desync( + struct desync_profile *dp, + uint32_t fwmark, + const char *ifin, + const char *ifout, + bool bIncoming, + t_ctrack *ctrack, + t_l7payload l7payload, + const struct dissect *dis, + uint8_t *mod_pkt, size_t *len_mod_pkt, + unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, const uint8_t *rdata_payload, size_t rlen_payload, + const uint8_t *data_decrypt, size_t len_decrypt) +{ + uint8_t verdict = VERDICT_PASS, verdict_func; + struct func_list *func; + int ref_arg = LUA_NOREF, status; + bool b, b_cutoff_all, b_unwanted_payload; + t_lua_desync_context ctx = { .dp = dp,.ctrack = ctrack }; + const char *sDirection = bIncoming ? "in" : "out"; + struct packet_range *range; + size_t l; + char instance[256]; + + if (ctrack) + { + // fast way not to do anything + if (bIncoming && ctrack->b_lua_in_cutoff) + { + DLOG("lua in cutoff\n"); + return verdict; + } + if (!bIncoming && ctrack->b_lua_out_cutoff) + { + DLOG("lua out cutoff\n"); + return verdict; + } + } + if (LIST_FIRST(&dp->lua_desync)) + { + unsigned int func_n; + + b_cutoff_all = b_unwanted_payload = true; + func_n = 1; + LIST_FOREACH(func, &dp->lua_desync, next) + { + ctx.func = func->func; + desync_instance(func->func, dp->n, func_n, instance, sizeof(instance)); + ctx.instance = instance; + + if (b_unwanted_payload) + { + range = bIncoming ? &func->range_in : &func->range_out; + b_unwanted_payload &= !l7_payload_match(l7payload, func->payload_type); + } + + if (b_cutoff_all) + { + if (lua_instance_cutoff_check(&ctx, bIncoming)) + DLOG("* lua '%s' : voluntary cutoff\n", instance); + else if (check_pos_cutoff(ctrack, bIncoming, range)) + { + DLOG("* lua '%s' : %s pos %c%u %c%u is beyond range %c%u%c%c%u (ctrack %s)\n", + instance, sDirection, + range->from.mode, pos_get(ctrack, range->from.mode, bIncoming), + range->to.mode, pos_get(ctrack, range->to.mode, bIncoming), + range->from.mode, range->from.pos, + range->upper_cutoff ? '<' : '-', + range->to.mode, range->to.pos, + ctrack ? "enabled" : "disabled"); + } + else + b_cutoff_all = false; + } + func_n++; + } + if (b_cutoff_all) + { + DLOG("all %s desync functions reached cutoff condition\n", sDirection); + if (ctrack) *(bIncoming ? &ctrack->b_lua_in_cutoff : &ctrack->b_lua_out_cutoff) = true; + } + else if (b_unwanted_payload) + DLOG("all %s desync functions do not want `%s` payload\n", sDirection, l7payload_str(l7payload)); + else + { + // create arg table that persists across multiple desync function calls + lua_createtable(params.L, 0, 12 + !!ctrack + !!dis->tcp + 3*!!replay_piece_count); + lua_pushf_dissect(dis); + lua_pushf_ctrack(ctrack); + lua_pushf_int("profile_n", dp->n); + lua_pushf_bool("outgoing", !bIncoming); + lua_pushf_str("ifin", (ifin && *ifin) ? ifin : NULL); + lua_pushf_str("ifout", (ifout && *ifout) ? ifout : NULL); + lua_pushf_int("fwmark", fwmark); + lua_pushf_bool("replay", !!replay_piece_count); + if (replay_piece_count) + { + lua_pushf_int("replay_piece", replay_piece+1); + lua_pushf_int("replay_piece_count", replay_piece_count); + lua_pushf_bool("replay_piece_last", (replay_piece+1)>=replay_piece_count); + } + lua_pushf_str("l7payload", l7payload_str(l7payload)); + lua_pushf_int("reasm_offset", reasm_offset); + lua_pushf_raw("reasm_data", rdata_payload, rlen_payload); + lua_pushf_raw("decrypt_data", data_decrypt, len_decrypt); + if (ctrack) lua_pushf_reg("instance_cutoff", ctrack->lua_instance_cutoff); + if (dis->tcp) + { + // recommended mss value for generated packets + if (ctrack && ctrack->mss_orig) + lua_pushf_int("tcp_mss", ctrack->mss_orig); + else + lua_pushf_global("tcp_mss", "DEFAULT_MSS"); + } + ref_arg = luaL_ref(params.L, LUA_REGISTRYINDEX); + + func_n = 1; + LIST_FOREACH(func, &dp->lua_desync, next) + { + ctx.func = func->func; + desync_instance(func->func, dp->n, func_n, instance, sizeof(instance)); + ctx.instance = instance; + + if (!lua_instance_cutoff_check(&ctx, bIncoming)) + { + range = bIncoming ? &func->range_in : &func->range_out; + if (check_pos_range(ctrack, bIncoming, range)) + { + DLOG("* lua '%s' : %s pos %c%u %c%u in range %c%u%c%c%u\n", + instance, sDirection, + range->from.mode, pos_get(ctrack, range->from.mode, bIncoming), + range->to.mode, pos_get(ctrack, range->to.mode, bIncoming), + range->from.mode, range->from.pos, + range->upper_cutoff ? '<' : '-', + range->to.mode, range->to.pos); + if (l7_payload_match(l7payload, func->payload_type)) + { + DLOG("* lua '%s' : payload_type '%s' satisfy filter\n", instance, l7payload_str(l7payload)); + DLOG("* lua '%s' : desync\n", instance); + lua_getglobal(params.L, func->func); + if (!lua_isfunction(params.L, -1)) + { + lua_pop(params.L, 1); + DLOG_ERR("desync function '%s' does not exist\n", func->func); + goto err; + } + lua_pushlightuserdata(params.L, &ctx); + lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref_arg); + lua_pushf_args(&func->args); + lua_pushf_str("func", func->func); + lua_pushf_int("func_n", func_n); + lua_pushf_str("func_instance", instance); + int initial_stack_top = lua_gettop(params.L); + status = lua_pcall(params.L, 2, LUA_MULTRET, 0); + if (status) + { + lua_dlog_error(); + goto err; + } + if (!desync_get_result(&verdict_func)) + goto err; + switch (verdict_func & VERDICT_MASK) + { + case VERDICT_MODIFY: + if (verdict == VERDICT_PASS) verdict = VERDICT_MODIFY; + break; + case VERDICT_DROP: + verdict = VERDICT_DROP; + } + + if (ctrack) + { + // lua cutoff + lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref_arg); + lua_getfield(params.L, -1, "track"); + if (lua_istable(params.L, -1)) + { + if (!ctrack->b_lua_in_cutoff) + { + lua_getfield(params.L, -1, "lua_in_cutoff"); + if (lua_toboolean(params.L, -1)) + { + ctrack->b_lua_in_cutoff = true; + DLOG("* lua in cutoff set\n"); + } + lua_pop(params.L, 1); + } + if (!ctrack->b_lua_out_cutoff) + { + lua_getfield(params.L, -1, "lua_out_cutoff"); + if (lua_toboolean(params.L, -1)) + { + ctrack->b_lua_out_cutoff = true; + DLOG("* lua out cutoff set\n"); + } + lua_pop(params.L, 1); + } + } + lua_pop(params.L, 2); + } + } + else + DLOG("* lua '%s' : payload_type '%s' does not satisfy filter\n", instance, l7payload_str(l7payload)); + } + else + DLOG("* lua '%s' : %s pos %c%u %c%u out of range %c%u%c%c%u\n", + instance, sDirection, + range->from.mode, pos_get(ctrack, range->from.mode, bIncoming), + range->to.mode, pos_get(ctrack, range->to.mode, bIncoming), + range->from.mode, range->from.pos, + range->upper_cutoff ? '<' : '-', + range->to.mode, range->to.pos); + } + func_n++; + } + } + + if (verdict == VERDICT_MODIFY) + { + // use same memory buffer to reduce memory copying + // packet size cannot grow + sockaddr_in46 sa; + + lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref_arg); + lua_getfield(params.L, -1, "dis"); + if (lua_type(params.L, -1) != LUA_TTABLE) + { + lua_pop(params.L, 2); + DLOG_ERR("dissect data is bad. VERDICT_MODIFY cancel.\n"); + goto err; + } + else + { + b = lua_reconstruct_dissect(-1, mod_pkt, len_mod_pkt, false, false); + lua_pop(params.L, 2); + if (!b) + { + DLOG_ERR("failed to reconstruct packet after VERDICT_MODIFY\n"); + // to reduce memory copying we used original packet buffer for reconstruction. + // it may have been modified. windows and BSD will send modified data despite of VERDICT_PASS. + // force same behavior on all OS + // it's LUA script error, they passed bad data + verdict = VERDICT_DROP; + goto ex; + } + DLOG("reconstructed packet due to VERDICT_MODIFY. size %zu => %zu\n", dis->len_pkt, *len_mod_pkt); + // no need to recalc sum after reconstruct + verdict |= VERDICT_NOCSUM; + } + } + } + else + DLOG("no lua functions in this profile\n"); +ex: + luaL_unref(params.L, LUA_REGISTRYINDEX, ref_arg); + return verdict; +err: + DLOG_ERR("desync ERROR. passing packet unmodified.\n"); + // do not do anything with the packet on error + verdict = VERDICT_PASS; + goto ex; +} + + + +static void setup_direction( + const struct dissect *dis, + bool bReverseFixed, + struct sockaddr_storage *src, + struct sockaddr_storage *dst, + const struct in_addr **sdip4, + const struct in6_addr **sdip6, + uint16_t *sdport) +{ + extract_endpoints(dis->ip, dis->ip6, dis->tcp, dis->udp, src, dst); + if (dis->ip6) + { + *sdip4 = NULL; + *sdip6 = bReverseFixed ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst; + } + else if (dis->ip) + { + *sdip6 = NULL; + *sdip4 = bReverseFixed ? &dis->ip->ip_src : &dis->ip->ip_dst; + } + else + { + // should never happen + *sdip6 = NULL; *sdip4 = NULL; *sdport = 0; + return; + } + *sdport = saport((struct sockaddr *)((bReverseFixed ^ params.server) ? src : dst)); + + if (params.debug) + { + char ip[40]; + ntopa46(*sdip4, *sdip6, ip, sizeof(ip)); + DLOG("%s mode desync profile/ipcache search target ip=%s port=%u\n", params.server ? "server" : "client", ip, *sdport); + } +} + +static uint8_t dpi_desync_tcp_packet_play(unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, const struct dissect *dis, uint8_t *mod_pkt, size_t *len_mod_pkt) +{ + 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, bReverseFixed = false; + struct sockaddr_storage src, dst; + const struct in_addr *sdip4; + const struct in6_addr *sdip6; + uint16_t sdport; + char host[256]; + const char *ifname = NULL, *ssid = NULL; + t_l7proto l7proto = L7_UNKNOWN; + t_l7payload l7payload = dis->len_payload ? L7P_UNKNOWN : L7P_EMPTY; + + uint32_t desync_fwmark = fwmark | params.desync_fwmark; + + if (replay_piece_count) + { + // 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; + bReverseFixed = bReverse ^ params.server; + setup_direction(dis, bReverseFixed, &src, &dst, &sdip4, &sdip6, &sdport); + + 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 + l7proto = ctrack_replay->l7proto; + dp = ctrack_replay->dp; + if (dp) + DLOG("using cached desync profile %u\n", dp->n); + else if (!ctrack_replay->dp_search_complete) + { + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, sdip4, sdip6, sdport, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, 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; + } + } + // in absence of conntrack guess direction by presence of interface names. won't work on BSD + bReverseFixed = ctrack ? (bReverse ^ params.server) : (bReverse = ifin && ifin && (!ifout || !*ifout)); + setup_direction(dis, bReverseFixed, &src, &dst, &sdip4, &sdip6, &sdport); + + 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 (ctrack) l7proto = ctrack->l7proto; + if (dp) + DLOG("using cached desync profile %u\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(sdip4, sdip6, 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, sdip4, sdip6, sdport, hostname, hostname_is_ip, l7proto, 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; + } + + HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); + + //ConntrackPoolDump(¶ms.conntrack); + + if (bReverseFixed) + { + if (ctrack && !ctrack->incoming_ttl) + { + ctrack->incoming_ttl = ttl46(dis->ip, dis->ip6); + DLOG("incoming TTL %u\n", ctrack->incoming_ttl); + } + ipcache_update_ttl(ctrack, sdip4, sdip6, ifin); + } + else + ipcache_get_ttl(ctrack, sdip4, sdip6, ifout); + + } // !replay + + const uint8_t *rdata_payload = dis->data_payload; + size_t rlen_payload = dis->len_payload; + + bool bCheckDone, bCheckResult, bCheckExcluded; + if (ctrack_replay) + { + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + else + bCheckDone = bCheckResult = bCheckExcluded = false; + + if (bReverse) + { + // protocol detection + if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload) + { + t_protocol_probe testers[] = { + {L7P_TLS_SERVER_HELLO,L7_TLS,IsTLSServerHelloPartial,false}, + {L7P_HTTP_REPLY,L7_HTTP,IsHttpReply,false}, + {L7P_XMPP_STREAM,L7_XMPP,IsXMPPStream,false}, + {L7P_XMPP_PROCEED,L7_XMPP,IsXMPPProceedTLS,false}, + {L7P_XMPP_FEATURES,L7_XMPP,IsXMPPFeatures,false} + }; + protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ctrack, &l7proto, &l7payload); + + if (l7payload==L7P_TLS_SERVER_HELLO) + TLSDebug(dis->data_payload, dis->len_payload); + } + + // 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 (!params.server && 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 %u : client %s : proto %s : incoming RST", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(l7proto)); + bFail = true; + } + else if (dis->len_payload && l7proto == L7_HTTP) + { + if (l7payload == L7P_HTTP_REPLY) + { + 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 %u : client %s : proto %s : redirect to another domain", ctrack->hostname, ctrack->dp->n, client_ip_port, l7proto_str(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, l7proto); + else + if (dis->len_payload) + auto_hostlist_reset_fail_counter(dp, ctrack->hostname, client_ip_port, l7proto); + if (dis->tcp->th_flags & TH_RST) + ctrack->hostname_ah_check = false; // do not react to further dup RSTs + } + } + // not reverse + else if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload) + { + struct blob_collection_head *fake; + uint8_t *p, *phost = NULL; + int i; + + bool bHaveHost = false, bHostIsIp = false; + + if (replay_piece_count) + { + 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"); + l7payload = L7P_HTTP_REQ; + if (l7proto == L7_UNKNOWN) + { + l7proto = L7_HTTP; + if (ctrack) ctrack->l7proto = l7proto; + } + + // we do not reassemble http + reasm_orig_cancel(ctrack); + + bHaveHost = HttpExtractHost(rdata_payload, rlen_payload, host, sizeof(host)); + if (!bHaveHost) + { + DLOG("not applying tampering to HTTP without Host:\n"); + goto pass; + } + 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"); + l7payload = L7P_TLS_CLIENT_HELLO; + if (l7proto == L7_UNKNOWN) + { + l7proto = L7_TLS; + if (ctrack) ctrack->l7proto = l7proto; + } + + if (bReqFull) TLSDebug(rdata_payload, rlen_payload); + + bHaveHost = TLSHelloExtractHost(rdata_payload, rlen_payload, host, sizeof(host), TLS_PARTIALS_ENABLE); + if (ctrack && !(params.reasm_payload_disable && l7_payload_match(l7payload, params.reasm_payload_disable))) + { + // 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)) + goto pass_reasm_cancel; + } + 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 (!ReasmIsEmpty(&ctrack->reasm_orig)) + { + if (rawpacket_queue_csum_fix(&ctrack->delayed, dis, &dst, fwmark, desync_fwmark, ifin, ifout)) + { + DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); + } + else + { + DLOG_ERR("rawpacket_queue failed !\n"); + goto pass_reasm_cancel; + } + if (ReasmIsFull(&ctrack->reasm_orig)) + { + replay_queue(&ctrack->delayed); + reasm_orig_fin(ctrack); + } + return VERDICT_DROP; + } + } + } + else + { + t_protocol_probe testers[] = { + {L7P_XMPP_STREAM,L7_XMPP,IsXMPPStream,false}, + {L7P_XMPP_STARTTLS,L7_XMPP,IsXMPPStartTLS,false} + }; + protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ctrack, &l7proto, &l7payload); + } + + 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) + { + bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto != L7_UNKNOWN; + ctrack_replay->l7proto_discovered = true; + } + else + bDiscoveredL7 = l7proto != L7_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"); + goto pass_reasm_cancel; + } + ctrack_replay->hostname_discovered = true; + if (!ipcache_put_hostname(sdip4, sdip6, host, bHostIsIp)) + goto pass_reasm_cancel; + + } + } + + if (bDiscoveredL7 || bDiscoveredHostname) + { + struct desync_profile *dp_prev = dp; + + // search for desync profile again. it may have changed. + dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, sdip4, sdip6, sdport, + ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, + ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, + 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) goto pass_reasm_cancel; + if (dp != dp_prev) + { + DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); + } + } + + if (bHaveHost && !PROFILE_HOSTLISTS_EMPTY(dp)) + { + if (!bCheckDone) + { + bCheckResult = HostlistCheck(dp, host, bHostIsIp, &bCheckExcluded, false); + bCheckDone = true; + if (ctrack_replay) + { + ctrack_replay->bCheckDone = bCheckDone; + ctrack_replay->bCheckResult = bCheckResult; + ctrack_replay->bCheckExcluded = bCheckExcluded; + } + } + 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); + } + } + } + } + + if (bCheckDone && !bCheckResult) + { + DLOG("not applying tampering because of previous negative hostlist check\n"); + goto pass_reasm_cancel; + } + 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 track_direction=%s fixed_direction=%s connection_proto=%s payload_type=%s\n", s1, s2, bReverse ? "in" : "out", bReverseFixed ? "in" : "out", l7proto_str(l7proto), l7payload_str(l7payload)); + } + verdict = desync(dp, fwmark, ifin, ifout, bReverseFixed, ctrack_replay, l7payload, dis, mod_pkt, len_mod_pkt, replay_piece, replay_piece_count, reasm_offset, rdata_payload, rlen_payload, NULL, 0); + +pass: + return (!bReverseFixed && (verdict & VERDICT_MASK) == VERDICT_DROP) ? ct_new_postnat_fix(ctrack, dis, mod_pkt, len_mod_pkt) : verdict; +pass_reasm_cancel: + reasm_orig_cancel(ctrack); + goto pass; +} + +// return : true - should continue, false - should stop with verdict +static void quic_reasm_cancel(t_ctrack *ctrack, const char *reason) +{ + reasm_orig_cancel(ctrack); + DLOG("%s\n", reason); +} + + +static uint8_t dpi_desync_udp_packet_play(unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, const struct dissect *dis, uint8_t *mod_pkt, size_t *len_mod_pkt) +{ + 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, bReverseFixed; + struct sockaddr_storage src, dst; + const struct in_addr *sdip4; + const struct in6_addr *sdip6; + uint16_t sdport; + char host[256]; + t_l7proto l7proto = L7_UNKNOWN; + t_l7payload l7payload = dis->len_payload ? L7P_UNKNOWN : L7P_EMPTY; + const char *ifname = NULL, *ssid = NULL; + + uint8_t defrag[UDP_MAX_REASM]; + uint8_t *data_decrypt = NULL; + size_t len_decrypt = 0; + + extract_endpoints(dis->ip, dis->ip6, NULL, dis->udp, &src, &dst); + sdport = saport((struct sockaddr *)&dst); + if (dis->ip6) + { + sdip4 = NULL; + sdip6 = params.server ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst; + } + else if (dis->ip) + { + sdip6 = NULL; + sdip4 = params.server ? &dis->ip->ip_src : &dis->ip->ip_dst; + } + else + return verdict; // should never happen + + if (replay_piece_count) + { + // 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; + bReverseFixed = bReverse ^ params.server; + setup_direction(dis, bReverseFixed, &src, &dst, &sdip4, &sdip6, &sdport); + + 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 + l7proto = ctrack_replay->l7proto; + dp = ctrack_replay->dp; + if (dp) + DLOG("using cached desync profile %u\n", dp->n); + else if (!ctrack_replay->dp_search_complete) + { + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, sdip4, sdip6, sdport, ctrack_replay->hostname, ctrack_replay->hostname_is_ip, 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; + } + } + // in absence of conntrack guess direction by presence of interface names. won't work on BSD + bReverseFixed = ctrack ? (bReverse ^ params.server) : (bReverse = ifin && ifin && (!ifout || !*ifout)); + setup_direction(dis, bReverseFixed, &src, &dst, &sdip4, &sdip6, &sdport); + + 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 (ctrack) l7proto = ctrack->l7proto; + if (dp) + DLOG("using cached desync profile %u\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(sdip4, sdip6, 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, sdip4, sdip6, sdport, hostname, hostname_is_ip, l7proto, 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; + } + + HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); + //ConntrackPoolDump(¶ms.conntrack); + + if (bReverseFixed) + { + if (ctrack && !ctrack->incoming_ttl) + { + ctrack->incoming_ttl = ttl46(dis->ip, dis->ip6); + DLOG("incoming TTL %u\n", ctrack->incoming_ttl); + } + ipcache_update_ttl(ctrack, sdip4, sdip6, ifin); + } + else + ipcache_get_ttl(ctrack, sdip4, sdip6, ifout); + } + + uint32_t desync_fwmark = fwmark | params.desync_fwmark; + + bool bCheckDone, bCheckResult, bCheckExcluded; + if (ctrack_replay) + { + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + else + bCheckDone = bCheckResult = bCheckExcluded = false; + + + if (dis->len_payload) + { + if (bReverse) + { + t_protocol_probe testers[] = { + {L7P_DNS_RESPONSE,L7_DNS,IsDNSResponse,false}, + {L7P_DHT,L7_DHT,IsDht,false}, + {L7P_WIREGUARD_INITIATION,L7_WIREGUARD,IsWireguardHandshakeInitiation,false}, + {L7P_WIREGUARD_RESPONSE,L7_WIREGUARD,IsWireguardHandshakeResponse,false}, + {L7P_WIREGUARD_COOKIE,L7_WIREGUARD,IsWireguardHandshakeCookie,false}, + {L7P_WIREGUARD_KEEPALIVE,L7_WIREGUARD,IsWireguardKeepalive,false}, + {L7P_WIREGUARD_DATA,L7_WIREGUARD,IsWireguardData,true} + }; + protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ctrack, &l7proto, &l7payload); + } + else + { + struct blob_collection_head *fake; + bool bHaveHost = false, bHostIsIp = false; + if (IsQUICInitial(dis->data_payload, dis->len_payload)) + { + DLOG("packet contains QUIC initial\n"); + l7payload = L7P_QUIC_INITIAL; + + l7proto = L7_QUIC; + // update ctrack l7proto here because reasm can happen + if (ctrack && ctrack->l7proto == L7_UNKNOWN) ctrack->l7proto = l7proto; + + uint8_t clean[UDP_MAX_REASM], *pclean; + size_t clean_len; + + if (replay_piece_count) + { + 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) + { + bool reasm_disable = params.reasm_payload_disable && l7_payload_match(l7payload, params.reasm_payload_disable); + if (ctrack && !reasm_disable && !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"); + goto pass_reasm_cancel; + } + } + 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 && !reasm_disable) + { + 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)) + goto pass_reasm_cancel; + } + if (!ReasmIsEmpty(&ctrack->reasm_orig)) + { + if (rawpacket_queue_csum_fix(&ctrack->delayed, dis, &dst, fwmark, desync_fwmark, ifin, ifout)) + { + DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); + } + else + { + DLOG_ERR("rawpacket_queue failed !\n"); + goto pass_reasm_cancel; + } + if (bReqFull) + { + replay_queue(&ctrack->delayed); + reasm_orig_fin(ctrack); + } + return ct_new_postnat_fix(ctrack, dis, mod_pkt, len_mod_pkt); + } + } + + if (bIsHello) + { + data_decrypt = defrag + hello_offset; + len_decrypt = hello_len; + bHaveHost = TLSHelloExtractHostFromHandshake(data_decrypt, len_decrypt, host, sizeof(host), TLS_PARTIALS_ENABLE); + } + else + { + quic_reasm_cancel(ctrack, "QUIC initial without ClientHello"); + } + } + else + { + DLOG("QUIC initial contains CRYPTO with partial fragment coverage\n"); + if (ctrack && !reasm_disable) + { + 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)) + goto pass_reasm_cancel; + } + if (rawpacket_queue_csum_fix(&ctrack->delayed, dis, &dst, fwmark, desync_fwmark, ifin, ifout)) + { + DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); + } + else + { + DLOG_ERR("rawpacket_queue failed !\n"); + goto pass_reasm_cancel; + } + return ct_new_postnat_fix(ctrack, dis, mod_pkt, len_mod_pkt); + } + quic_reasm_cancel(ctrack, "QUIC initial fragmented CRYPTO"); + } + } + else + { + // defrag failed + quic_reasm_cancel(ctrack, "QUIC initial defrag CRYPTO failed"); + } + } + else + { + // decrypt failed + quic_reasm_cancel(ctrack, "QUIC initial decryption failed"); + } + } + 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); + + t_protocol_probe testers[] = { + {L7P_DISCORD_IP_DISCOVERY,L7_DISCORD,IsDiscordIpDiscoveryRequest,false}, + {L7P_STUN_BINDING_REQ,L7_STUN,IsStunBindingRequest,false}, + {L7P_DNS_QUERY,L7_DNS,IsDNSQuery,false}, + {L7P_DHT,L7_DHT,IsDht,false}, + {L7P_WIREGUARD_INITIATION,L7_WIREGUARD,IsWireguardHandshakeInitiation,false}, + {L7P_WIREGUARD_RESPONSE,L7_WIREGUARD,IsWireguardHandshakeResponse,false}, + {L7P_WIREGUARD_COOKIE,L7_WIREGUARD,IsWireguardHandshakeCookie,false}, + {L7P_WIREGUARD_KEEPALIVE,L7_WIREGUARD,IsWireguardKeepalive,false}, + {L7P_WIREGUARD_DATA,L7_WIREGUARD,IsWireguardData,true} + }; + protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ctrack, &l7proto, &l7payload); + } + + if (bHaveHost) + { + bHostIsIp = strip_host_to_ip(host); + DLOG("hostname: %s\n", host); + } + + bool bDiscoveredL7; + if (ctrack_replay) + { + bDiscoveredL7 = !ctrack_replay->l7proto_discovered && l7proto != L7_UNKNOWN; + ctrack_replay->l7proto_discovered = true; + } + else + bDiscoveredL7 = l7proto != L7_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 pass; + } + if (!ipcache_put_hostname(sdip4, sdip6, host, bHostIsIp)) + goto pass; + } + } + + if (bDiscoveredL7 || bDiscoveredHostname) + { + struct desync_profile *dp_prev = dp; + + // search for desync profile again. it may have changed. + dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, sdip4, sdip6, sdport, + ctrack_replay ? ctrack_replay->hostname : bHaveHost ? host : NULL, + ctrack_replay ? ctrack_replay->hostname_is_ip : bHostIsIp, + 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) + goto pass_reasm_cancel; + if (dp != dp_prev) + { + DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); + } + } + 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); + bCheckDone = true; + if (ctrack_replay) + { + ctrack_replay->bCheckDone = bCheckDone; + ctrack_replay->bCheckResult = bCheckResult; + ctrack_replay->bCheckExcluded = bCheckExcluded; + } + } + 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); + } + } + } + } + + } + } + if (bCheckDone && !bCheckResult) + { + DLOG("not applying tampering because of negative hostlist check\n"); + goto pass_reasm_cancel; + } + 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 track_direction=%s fixed_direction=%s connection_proto=%s payload_type=%s\n", s1, s2, bReverse ? "in" : "out", bReverseFixed ? "in" : "out", l7proto_str(l7proto), l7payload_str(l7payload)); + } + verdict = desync(dp, fwmark, ifin, ifout, bReverseFixed, ctrack_replay, l7payload, dis, mod_pkt, len_mod_pkt, replay_piece, replay_piece_count, reasm_offset, NULL, 0, data_decrypt, len_decrypt); + +pass: + return (!bReverse && (verdict & VERDICT_MASK) == VERDICT_DROP) ? ct_new_postnat_fix(ctrack, dis, mod_pkt, len_mod_pkt) : verdict; +pass_reasm_cancel: + reasm_orig_cancel(ctrack); + goto pass; +} + + +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( + unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout, + const uint8_t *data_pkt, size_t len_pkt, + uint8_t *mod_pkt, size_t *len_mod_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_piece_count, &dis); + switch (dis.proto) + { + case IPPROTO_TCP: + if (dis.tcp) + { + verdict = dpi_desync_tcp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt); + // we fix csum before pushing to replay queue + if (!replay_piece_count) verdict_tcp_csum_fix(verdict, (struct tcphdr *)dis.tcp, dis.transport_len, dis.ip, dis.ip6); + } + break; + case IPPROTO_UDP: + if (dis.udp) + { + verdict = dpi_desync_udp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt); + // we fix csum before pushing to replay queue + if (!replay_piece_count) verdict_udp_csum_fix(verdict, (struct udphdr *)dis.udp, dis.transport_len, dis.ip, dis.ip6); + } + break; + } + } + return verdict; +} +uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, const uint8_t *data_pkt, size_t len_pkt, uint8_t *mod_pkt, size_t *len_mod_pkt) +{ + ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); + return dpi_desync_packet_play(0, 0, 0, fwmark, ifin, ifout, data_pkt, len_pkt, mod_pkt, len_mod_pkt); +} + + + +static bool replay_queue(struct rawpacket_tailhead *q) +{ + struct rawpacket *rp; + size_t offset; + unsigned int i, count; + bool b = true; + uint8_t mod[RECONSTRUCT_MAX_SIZE]; + size_t modlen; + + for (i = 0, offset = 0, count = rawpacket_queue_count(q); (rp = rawpacket_dequeue(q)); offset += rp->len_payload, rawpacket_free(rp), i++) + { + DLOG("REPLAYING delayed packet #%u offset %zu\n", i+1, offset); + modlen = sizeof(mod); + uint8_t verdict = dpi_desync_packet_play(i, count, offset, rp->fwmark_orig, rp->ifin, rp->ifout, rp->packet, rp->len, mod, &modlen); + switch (verdict & VERDICT_MASK) + { + case VERDICT_MODIFY: + DLOG("SENDING delayed packet #%u modified\n", i+1); + b &= rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,mod,modlen); + break; + case VERDICT_PASS: + DLOG("SENDING delayed packet #%u unmodified\n", i+1); + b &= rawsend_rp(rp); + break; + case VERDICT_DROP: + DLOG("DROPPING delayed packet #%u\n", i+1); + break; + } + } + return b; +} diff --git a/nfq2/desync.h b/nfq2/desync.h new file mode 100644 index 0000000..e9ff11f --- /dev/null +++ b/nfq2/desync.h @@ -0,0 +1,20 @@ +#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 + +uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, const uint8_t *data_pkt, size_t len_pkt, uint8_t *mod_pkt, size_t *len_mod_pkt); diff --git a/nfq2/gzip.c b/nfq2/gzip.c new file mode 100644 index 0000000..a3e4eb7 --- /dev/null +++ b/nfq2/gzip.c @@ -0,0 +1,79 @@ +#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; +} diff --git a/nfq2/gzip.h b/nfq2/gzip.h new file mode 100644 index 0000000..15e30d2 --- /dev/null +++ b/nfq2/gzip.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +int z_readfile(FILE *F,char **buf,size_t *size); +bool is_gzip(FILE* F); diff --git a/nfq2/helpers.c b/nfq2/helpers.c new file mode 100644 index 0000000..45d6c3e --- /dev/null +++ b/nfq2/helpers.c @@ -0,0 +1,746 @@ +#define _GNU_SOURCE + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include + +#define UNIQ_SORT \ +{ \ + int 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; \ +} + +int unique_size_t(size_t *pu, int ct) UNIQ_SORT +int unique_ssize_t(ssize_t *pu, int ct) UNIQ_SORT + +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, int ct) +{ + qsort(array,ct,sizeof(*array),cmp_size_t); +} +static int cmp_ssize_t(const void * a, const void * b) +{ + return *(ssize_t*)a < *(ssize_t*)b ? -1 : *(ssize_t*)a > *(ssize_t*)b; +} +void qsort_ssize_t(ssize_t *array, int ct) +{ + qsort(array,ct,sizeof(*array),cmp_ssize_t); +} + + +int str_index(const char **strs, int count, const char *str) +{ + for(int i=0;i= 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; +} + +static inline bool is_letter(char c) +{ + return (c>='a' && c<='z') || (c>='A' && c<='Z'); +} +static inline bool is_digit(char c) +{ + return c>='0' && c<='9'; +} +bool is_identifier(const char *p) +{ + if (*p!='_' && !is_letter(*p)) + return false; + for(++p;*p;p++) + if (!is_letter(*p) && !is_digit(*p) && *p!='_') + return false; + return true; +} + +bool load_file(const char *filename, off_t offset, void *buffer, size_t *buffer_size) +{ + FILE *F; + + F = fopen(filename, "rb"); + if (!F) return false; + + if (offset) + { + if (-1 == lseek(fileno(F), offset, SEEK_SET)) + { + fclose(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, off_t offset, void *buffer, size_t *buffer_size) +{ + bool b = load_file(filename, offset, 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 ntopa46(const struct in_addr *ip, const struct in6_addr *ip6,char *str, size_t len) +{ + if (!len) return; + *str = 0; + if (ip) inet_ntop(AF_INET, ip, str, len); + else if (ip6) inet_ntop(AF_INET6, ip6, str, len); + else snprintf(str, len, "UNKNOWN_FAMILY"); +} +void ntop46(const struct sockaddr *sa, char *str, size_t len) +{ + ntopa46(sa->sa_family==AF_INET ? &((struct sockaddr_in*)sa)->sin_addr : NULL, + sa->sa_family==AF_INET6 ? &((struct sockaddr_in6*)sa)->sin6_addr : NULL, + str, len); +} +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 ntohs(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port : + sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); +} + +bool sa_has_addr(const struct sockaddr *sa) +{ + switch(sa->sa_family) + { + case AF_INET: + return ((struct sockaddr_in*)sa)->sin_addr.s_addr!=INADDR_ANY; + case AF_INET6: + return memcmp(((struct sockaddr_in6*)sa)->sin6_addr.s6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); + default: + return false; + } +} + + +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); +} + + +uint16_t pntoh16(const uint8_t *p) +{ + return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; +} +void phton16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = v & 0xFF; +} +uint32_t pntoh24(const uint8_t *p) +{ + return ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; +} +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; +} +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]; +} +void phton32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v>>24); + p[1] = (uint8_t)(v>>16); + p[2] = (uint8_t)(v>>8); + p[3] = (uint8_t)v; +} +uint64_t pntoh64(const uint8_t *p) +{ + return ((uint64_t)p[0] << 56) | ((uint64_t)p[1] << 48) | ((uint64_t)p[2] << 40) | ((uint64_t)p[3] << 32) | ((uint64_t)p[4] << 24) | ((uint64_t)p[5] << 16) | ((uint64_t)p[6] << 8) | p[7]; +} +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; +} + + +#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); +} + +bool file_size(const char *filename, off_t *size) +{ + struct stat st; + if (stat(filename,&st)==-1) return false; + *size = st.st_size; + return true; +} +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; +} + +bool packet_pos_parse(const char *s, struct packet_pos *pos) +{ + if (*s!='n' && *s!='d' && *s!='s' && *s!='b' && *s!='x' && *s!='a') return false; + pos->mode=*s; + if (pos->mode=='x' || pos->mode=='a') + { + pos->pos=0; + return true; + } + return sscanf(s+1,"%u",&pos->pos)==1; +} +bool packet_range_parse(const char *s, struct packet_range *range) +{ + const char *p; + + range->upper_cutoff = false; + if (*s=='-' || *s=='<') + { + range->from = PACKET_POS_ALWAYS; + range->upper_cutoff = *s=='<'; + } + else + { + if (!packet_pos_parse(s,&range->from)) return false; + if (range->from.mode=='x') + { + range->to = range->from; + return true; + } + if (!(p = strchr(s,'-'))) + p = strchr(s,'<'); + if (p) + { + s = p; + range->upper_cutoff = *s=='<'; + } + else + { + if (range->from.mode=='a') + { + range->to = range->from; + return true; + } + return false; + } + } + s++; + if (*s) + { + return packet_pos_parse(s,&range->to); + } + else + { + range->to = PACKET_POS_ALWAYS; + return true; + } +} + +void fill_random_bytes(uint8_t *p,size_t sz) +{ + size_t k,sz16 = sz>>1; + for(k=0;k= 128) + memset(a->s6_addr,0xFF,16); + else + { + uint8_t n = plen >> 3; + memset(a->s6_addr,0xFF,n); + memset(a->s6_addr+n,0x00,16-n); + a->s6_addr[n] = (uint8_t)(0xFF00 >> (plen & 7)); + } +} +struct in6_addr ip6_mask[129]; +void mask_from_preflen6_prepare(void) +{ + for (int plen=0;plen<=128;plen++) mask_from_preflen6_make(plen, ip6_mask+plen); +} + +#if defined(__GNUC__) && !defined(__llvm__) +__attribute__((optimize ("no-strict-aliasing"))) +#endif +void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result) +{ + // int128 requires 16-bit alignment. in struct sockaddr_in6.sin6_addr is 8-byte aligned. + // it causes segfault on x64 arch with latest compiler. it can cause misalign slowdown on other archs + // use 64-bit AND + ((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]; +} + +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; +} + +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; +} diff --git a/nfq2/helpers.h b/nfq2/helpers.h new file mode 100644 index 0000000..ad159ff --- /dev/null +++ b/nfq2/helpers.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#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); +int unique_ssize_t(ssize_t *pu, int ct); +void qsort_size_t(size_t *array, int ct); +void qsort_ssize_t(ssize_t *array, int ct); + +int str_index(const char **strs, int count, const char *str); +void rtrim(char *s); +void replace_char(char *s, char from, char to); +char *strncasestr(const char *s,const char *find, size_t slen); +// [a-zA-z][a-zA-Z0-9]* +bool is_identifier(const char *p); + +bool load_file(const char *filename, off_t offset, void *buffer, size_t *buffer_size); +bool load_file_nonempty(const char *filename, off_t offset, 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 ntopa46(const struct in_addr *ip, const struct in6_addr *ip6,char *str, size_t len); +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 sa_has_addr(const struct sockaddr *sa); + +bool seq_within(uint32_t s, uint32_t s1, uint32_t s2); + +bool ipv6_addr_is_zero(const struct in6_addr *a); + +uint16_t pntoh16(const uint8_t *p); +void phton16(uint8_t *p, uint16_t v); +uint32_t pntoh24(const uint8_t *p); +void phton24(uint8_t *p, uint32_t v); +uint32_t pntoh32(const uint8_t *p); +void phton32(uint8_t *p, uint32_t v); +uint64_t pntoh64(const uint8_t *p); +void phton64(uint8_t *p, uint64_t v); + +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_size(const char *filename, off_t *size); +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); + +struct packet_pos +{ + char mode; // n - packets, d - data packets, s - relative sequence + unsigned int pos; +}; +struct packet_range +{ + struct packet_pos from, to; + bool upper_cutoff; // true - do not include upper limit, false - include upper limit +}; +#define PACKET_POS_NEVER (struct packet_pos){'x',0} +#define PACKET_POS_ALWAYS (struct packet_pos){'a',0} +#define PACKET_RANGE_NEVER (struct packet_range){PACKET_POS_NEVER,PACKET_POS_NEVER} +#define PACKET_RANGE_ALWAYS (struct packet_range){PACKET_POS_ALWAYS,PACKET_POS_ALWAYS} +bool packet_range_parse(const char *s, struct packet_range *range); + +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); +bool fill_crypto_random_bytes(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); + +bool parse_int16(const char *p, int16_t *v); + +static inline uint32_t mask_from_preflen(uint32_t preflen) +{ + return preflen ? preflen<32 ? ~((1 << (32-preflen)) - 1) : 0xFFFFFFFF : 0; +} +void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result); +extern struct in6_addr ip6_mask[129]; +void mask_from_preflen6_prepare(void); +static inline const struct in6_addr *mask_from_preflen6(uint8_t preflen) +{ + return ip6_mask+preflen; +} diff --git a/nfq2/hostlist.c b/nfq2/hostlist.c new file mode 100644 index 0000000..dcbd8fe --- /dev/null +++ b/nfq2/hostlist.c @@ -0,0 +1,340 @@ +#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 %u\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 %u include hostlist %s%s\n",dpl->dp.n, hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); + else + DLOG("profile %u 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 %u exclude hostlist %s%s\n",dpl->dp.n,hl_item->hfile->filename,hl_item->hfile->hostlist ? "" : " (empty)"); + else + DLOG("profile %u exclude fixed hostlist%s\n",dpl->dp.n,hl_item->hfile->hostlist ? "" : " (empty)"); + } + if (dpl->dp.hostlist_auto) + DLOG("profile %u auto hostlist %s%s\n",dpl->dp.n,dpl->dp.hostlist_auto->filename,dpl->dp.hostlist_auto->hostlist ? "" : " (empty)"); + } +} diff --git a/nfq2/hostlist.h b/nfq2/hostlist.h new file mode 100644 index 0000000..954d0cb --- /dev/null +++ b/nfq2/hostlist.h @@ -0,0 +1,17 @@ +#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) diff --git a/nfq2/ipset.c b/nfq2/ipset.c new file mode 100644 index 0000000..f9ba0fc --- /dev/null +++ b/nfq2/ipset.c @@ -0,0 +1,319 @@ +#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 %u\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 %u include ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); + else + DLOG("profile %u 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 %u exclude ipset %s (%s)\n",dpl->dp.n,ips_item->hfile->filename,dbg_ipset_fill(&ips_item->hfile->ipset)); + else + DLOG("profile %u exclude fixed ipset (%s)\n",dpl->dp.n,dbg_ipset_fill(&ips_item->hfile->ipset)); + } +} diff --git a/nfq2/ipset.h b/nfq2/ipset.h new file mode 100644 index 0000000..b711ac5 --- /dev/null +++ b/nfq2/ipset.h @@ -0,0 +1,14 @@ +#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) diff --git a/nfq2/kavl.h b/nfq2/kavl.h new file mode 100644 index 0000000..f3520fd --- /dev/null +++ b/nfq2/kavl.h @@ -0,0 +1,400 @@ +/* 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 diff --git a/nfq2/lua.c b/nfq2/lua.c new file mode 100644 index 0000000..5ab036f --- /dev/null +++ b/nfq2/lua.c @@ -0,0 +1,2626 @@ +#include + +#include "lua.h" +#include "params.h" +#include "helpers.h" + +static void lua_check_argc(lua_State *L, const char *where, int argc) +{ + int num_args = lua_gettop(L); + if (num_args != argc) + luaL_error(L, "%s expect exactly %d arguments, got %d", where, argc, num_args); +} +static void lua_check_argc_range(lua_State *L, const char *where, int argc_min, int argc_max) +{ + int num_args = lua_gettop(L); + if (num_args < argc_min || num_args > argc_max) + luaL_error(L, "%s expect from %d to %d arguments, got %d", where, argc_min, argc_max, num_args); +} + + +#if LUA_VERSION_NUM < 502 +int lua_absindex(lua_State *L, int idx) +{ + // convert relative index to absolute + return idx<0 ? lua_gettop(params.L) + idx + 1 : idx; +} +#endif + +static int luacall_DLOG(lua_State *L) +{ + lua_check_argc(L,"DLOG",1); + DLOG("LUA: %s\n",luaL_checkstring(L,1)); + return 0; +} +static int luacall_DLOG_ERR(lua_State *L) +{ + lua_check_argc(L,"DLOG_ERR",1); + DLOG_ERR("LUA: %s\n",luaL_checkstring(L,1)); + return 0; +} +static int luacall_DLOG_CONDUP(lua_State *L) +{ + lua_check_argc(L,"DLOG_CONDUP",1); + DLOG_CONDUP("LUA: %s\n",luaL_checkstring(L,1)); + return 0; +} + +static int luacall_bitlshift(lua_State *L) +{ + lua_check_argc(L,"bitlshift",2); + lua_pushinteger(L,luaL_checkinteger(L,1) << luaL_checkinteger(L,2)); + return 1; +} +static int luacall_bitrshift(lua_State *L) +{ + lua_check_argc(L,"bitrshift",2); + lua_pushinteger(L,((LUA_UNSIGNED)luaL_checkinteger(L,1)) >> luaL_checkinteger(L,2)); + return 1; +} +static int luacall_bitand(lua_State *L) +{ + lua_check_argc_range(L,"bitand",2,100); + int argc = lua_gettop(L); + lua_Integer v=luaL_checkinteger(L,1); + for(int i=2;i<=argc;i++) v&=luaL_checkinteger(L,i); + lua_pushinteger(L,v); + return 1; +} +static int luacall_bitor(lua_State *L) +{ + lua_check_argc_range(L,"bitor",2,100); + int argc = lua_gettop(L); + lua_Integer v=0; + for(int i=1;i<=argc;i++) v|=luaL_checkinteger(L,i); + lua_pushinteger(L,v); + return 1; +} +static int luacall_bitnot(lua_State *L) +{ + lua_check_argc(L,"bitnot",1); + lua_pushinteger(L,~luaL_checkinteger(L,1)); + return 1; +} +static int luacall_bitxor(lua_State *L) +{ + lua_check_argc_range(L,"bitxor",2,100); + int argc = lua_gettop(L); + lua_Integer v=0; + for(int i=1;i<=argc;i++) v^=luaL_checkinteger(L,i); + lua_pushinteger(L,v); + return 1; +} +static int luacall_bitget(lua_State *L) +{ + lua_check_argc(L,"bitget",3); + + LUA_UNSIGNED what = (LUA_UNSIGNED)luaL_checkinteger(L,1); + lua_Integer from = luaL_checkinteger(L,2); + lua_Integer to = luaL_checkinteger(L,3); + if (from>to || from>63 || to>63) + luaL_error(L, "bit range invalid"); + + what = (what >> from) & ~((lua_Integer)-1 << (to-from+1)); + + lua_pushinteger(L,what); + return 1; +} +static int luacall_bitset(lua_State *L) +{ + lua_check_argc(L,"bitset",4); + + LUA_UNSIGNED what = (LUA_UNSIGNED)luaL_checkinteger(L,1); + lua_Integer from = luaL_checkinteger(L,2); + lua_Integer to = luaL_checkinteger(L,3); + LUA_UNSIGNED set = (LUA_UNSIGNED)luaL_checkinteger(L,4); + if (from>to || from>63 || to>63) + luaL_error(L, "bit range invalid"); + + lua_Integer mask = ~((lua_Integer)-1 << (to-from+1)); + set = (set & mask) << from; + mask <<= from; + what = what & ~mask | set; + + lua_pushinteger(L,what); + return 1; +} + +static int luacall_u8(lua_State *L) +{ + lua_check_argc_range(L,"u8",1,2); + + int argc=lua_gettop(L); + size_t l; + lua_Integer offset; + const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; + if (offset<0 || (offset+1)>l) luaL_error(L, "out of range"); + + lua_pushinteger(L,p[offset]); + return 1; +} +static int luacall_u16(lua_State *L) +{ + lua_check_argc_range(L,"u16",1,2); + + int argc=lua_gettop(L); + size_t l; + lua_Integer offset; + const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; + if (offset<0 || (offset+2)>l) luaL_error(L, "out of range"); + + lua_pushinteger(L,pntoh16(p+offset)); + return 1; +} +static int luacall_u24(lua_State *L) +{ + lua_check_argc_range(L,"u24",1,2); + + int argc=lua_gettop(L); + size_t l; + lua_Integer offset; + const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; + if (offset<0 || (offset+3)>l) luaL_error(L, "out of range"); + + lua_pushinteger(L,pntoh24(p+offset)); + return 1; +} +static int luacall_u32(lua_State *L) +{ + lua_check_argc_range(L,"u32",1,2); + + int argc=lua_gettop(L); + size_t l; + lua_Integer offset; + const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; + if (offset<0 || (offset+4)>l) luaL_error(L, "out of range"); + + lua_pushinteger(L,pntoh32(p+offset)); + return 1; +} +static int luacall_bu8(lua_State *L) +{ + lua_check_argc(L,"bu8",1); + + lua_Integer i = luaL_checkinteger(L,1); + if (i & ~(uint64_t)0xFF) luaL_error(L, "out of range"); + uint8_t v=(uint8_t)i; + lua_pushlstring(L,(char*)&v,1); + return 1; +} +static int luacall_bu16(lua_State *L) +{ + lua_check_argc(L,"bu16",1); + + lua_Integer i = luaL_checkinteger(L,1); + if (i & ~(uint64_t)0xFFFF) luaL_error(L, "out of range"); + uint8_t v[2]; + phton16(v,(uint16_t)i); + lua_pushlstring(L,(char*)v,2); + return 1; +} +static int luacall_bu24(lua_State *L) +{ + lua_check_argc(L,"bu24",1); + + lua_Integer i = luaL_checkinteger(L,1); + if (i & ~(uint64_t)0xFFFFFF) luaL_error(L, "out of range"); + uint8_t v[3]; + phton24(v,(uint32_t)i); + lua_pushlstring(L,(char*)v,3); + return 1; +} +static int luacall_bu32(lua_State *L) +{ + lua_check_argc(L,"bu32",1); + + lua_Integer i = luaL_checkinteger(L,1); + if (i & ~(uint64_t)0xFFFFFFFF) luaL_error(L, "out of range"); + uint8_t v[4]; + phton32(v,(uint32_t)i); + lua_pushlstring(L,(char*)v,4); + return 1; +} + +static int luacall_divint(lua_State *L) +{ + lua_check_argc(L,"divint",2); + lua_Integer v1=luaL_checkinteger(L,1); + lua_Integer v2=luaL_checkinteger(L,2); + if (v2) + lua_pushinteger(L,v1/v2); + else + lua_pushnil(L); + return 1; +} + +static int luacall_brandom(lua_State *L) +{ + lua_check_argc(L,"brandom",1); + lua_Integer len = luaL_checkinteger(L,1); + + uint8_t *p = malloc(len); + if (!p) luaL_error(L, "out of memory"); + fill_random_bytes(p,len); + // in out of memory condition this will leave p unfreed + lua_pushlstring(L,(char*)p,len); + free(p); + return 1; +} +static int luacall_brandom_az(lua_State *L) +{ + lua_check_argc(L,"brandom_az",1); + lua_Integer len = luaL_checkinteger(L,1); + + uint8_t *p = malloc(len); + if (!p) luaL_error(L, "out of memory"); + fill_random_az(p,len); + // in out of memory condition this will leave p unfreed + lua_pushlstring(L,(char*)p,len); + free(p); + return 1; +} +static int luacall_brandom_az09(lua_State *L) +{ + lua_check_argc(L,"brandom_az09",1); + lua_Integer len = luaL_checkinteger(L,1); + + uint8_t *p = malloc(len); + if (!p) luaL_error(L, "out of memory"); + fill_random_az09(p,len); + // in out of memory condition this will leave p unfreed + lua_pushlstring(L,(char*)p,len); + free(p); + return 1; +} + +// hacky function. breaks immutable string behavior. +// if you change a string, it will change in all variables that hold the same string +static int luacall_memcpy(lua_State *L) +{ + // memcpy(to,to_offset,from,from_offset,size) + lua_check_argc_range(L,"memcpy",3,5); + + size_t lfrom,lto; + lua_Integer off_from,off_to,size; + int argc=lua_gettop(L); + const uint8_t *from = (uint8_t*)luaL_checklstring(L,3,&lfrom); + uint8_t *to = (uint8_t*)luaL_checklstring(L,1,<o); + off_from = argc>=4 ? luaL_checkinteger(L,4)-1 : 0; + off_to = luaL_checkinteger(L,2)-1; + if (off_from<0 || off_to<0 || off_from>lfrom || off_to>lto) + luaL_error(L, "out of range"); + size = argc>=5 ? luaL_checkinteger(L,5) : lfrom-off_from; + if (size<0 || (off_from+size)>lfrom || (off_to+size)>lto) + luaL_error(L, "out of range"); + memcpy(to+off_to,from+off_from,size); + return 0; +} + + +static int luacall_parse_hex(lua_State *L) +{ + lua_check_argc(L,"parse_hex",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t l; + const char *hex = lua_tolstring(L,1,&l); + if ((l&1)) goto err; + l>>=1; + uint8_t *p = malloc(l); + if (!p) goto err; + if (!parse_hex_str(hex,p,&l)) + { + free(p); + goto err; + } + // in out of memory condition this will leave p unfreed + lua_pushlstring(L,(char*)p,l); + free(p); +ex: + LUA_STACK_GUARD_RETURN(L,1) +err: + lua_pushnil(L); + goto ex; +} + + + +static SHAversion lua_hash_type(const char *s_hash_type) +{ + SHAversion sha_ver; + if (!strcmp(s_hash_type,"sha256")) + sha_ver = SHA256; + else if (!strcmp(s_hash_type,"sha224")) + sha_ver = SHA224; + else + luaL_error(params.L, "unsupported hash type %s", s_hash_type); + return sha_ver; +} + +static int luacall_bcryptorandom(lua_State *L) +{ + lua_check_argc(L,"bcryptorandom",1); + + LUA_STACK_GUARD_ENTER(L) + + lua_Integer len = luaL_checkinteger(L,1); + + uint8_t *p = malloc(len); + if (!p) luaL_error(L, "out of memory"); + + if (!fill_crypto_random_bytes(p,len)) + { + free(p); + // this is fatal. they expect us to give them crypto secure random blob + luaL_error(L, "could not read random data from /dev/random"); + } + + lua_pushlstring(L,(char*)p,len); + free(p); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_hash(lua_State *L) +{ + // hash(hash_type, data) returns hash + lua_check_argc(L,"hash",2); + + LUA_STACK_GUARD_ENTER(L) + + const char *s_hash_type = luaL_checkstring(L,1); + SHAversion sha_ver = lua_hash_type(s_hash_type); + + size_t data_len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,2,&data_len); + + unsigned char hash[USHAMaxHashSize]; + USHAContext tcontext; + if (USHAReset(&tcontext, sha_ver)!=shaSuccess || USHAInput(&tcontext, data, data_len)!=shaSuccess || USHAResult(&tcontext, hash)!=shaSuccess) + luaL_error(L, "hash failure"); + + lua_pushlstring(L,(char*)hash,USHAHashSize(sha_ver)); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_aes(lua_State *L) +{ + // aes_gcm(bEncrypt, key, in) returns out + lua_check_argc(L,"aes",3); + + LUA_STACK_GUARD_ENTER(L) + + bool bEncrypt = lua_toboolean(L,1); + size_t key_len; + const uint8_t *key = (uint8_t*)luaL_checklstring(L,2,&key_len); + if (key_len!=16 && key_len!=24 && key_len!=32) + luaL_error(L, "aes: wrong key length %u. should be 16,24,32.", (unsigned)key_len); + size_t input_len; + const uint8_t *input = (uint8_t*)luaL_checklstring(L,3,&input_len); + if (input_len!=16) + luaL_error(L, "aes: wrong data length %u. should be 16.", (unsigned)input_len); + + aes_init_keygen_tables(); + aes_context ctx; + uint8_t output[16]; + if (aes_setkey(&ctx, bEncrypt, key, key_len) || aes_cipher(&ctx, input, output)) + lua_pushnil(L); + else + lua_pushlstring(L,(const char*)output,sizeof(output)); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_aes_gcm(lua_State *L) +{ + // aes_gcm(bEncrypt, key, iv, in, [additional_data]) returns out, atag + lua_check_argc_range(L,"aes_gcm",4,5); + + LUA_STACK_GUARD_ENTER(L) + + int argc = lua_gettop(L); + bool bEncrypt = lua_toboolean(L,1); + size_t key_len; + const uint8_t *key = (uint8_t*)luaL_checklstring(L,2,&key_len); + if (key_len!=16 && key_len!=24 && key_len!=32) + luaL_error(L, "aes_gcm: wrong key length %u. should be 16,24,32.", (unsigned)key_len); + size_t iv_len; + const uint8_t *iv = (uint8_t*)luaL_checklstring(L,3,&iv_len); + if (iv_len!=12) + luaL_error(L, "aes_gcm: wrong iv length %u. should be 12.", (unsigned)iv_len); + size_t input_len; + const uint8_t *input = (uint8_t*)luaL_checklstring(L,4,&input_len); + size_t add_len=0; + const uint8_t *add = lua_isnoneornil(L,5) ? NULL : (uint8_t*)luaL_checklstring(L,5,&add_len); + + uint8_t atag[16]; + uint8_t *output = malloc(input_len); + if (!output) luaL_error(L, "out of memory"); + + gcm_initialize(); + if (aes_gcm_crypt(bEncrypt, output, input, input_len, key, key_len, iv, iv_len, add, add_len, atag, sizeof(atag))) + { + lua_pushnil(L); + lua_pushnil(L); + } + else + { + lua_pushlstring(L,(const char*)output,input_len); + lua_pushlstring(L,(const char*)atag,sizeof(atag)); + } + free(output); + + LUA_STACK_GUARD_RETURN(L,2) +} + +static int luacall_hkdf(lua_State *L) +{ + // hkdf(hash_type, salt, ikm, info, okm_len) returns okm + // hash_type - string "sha224" or "sha256" + lua_check_argc(L,"hkdf",5); + + LUA_STACK_GUARD_ENTER(L) + + const char *s_hash_type = luaL_checkstring(L,1); + SHAversion sha_ver = lua_hash_type(s_hash_type); + size_t salt_len=0; + const uint8_t *salt = lua_type(L,2) == LUA_TNIL ? NULL : (uint8_t*)luaL_checklstring(L,2,&salt_len); + size_t ikm_len=0; + const uint8_t *ikm = lua_type(L,3) == LUA_TNIL ? NULL : (uint8_t*)luaL_checklstring(L,3,&ikm_len); + size_t info_len=0; + const uint8_t *info = lua_type(L,4) == LUA_TNIL ? NULL : (uint8_t*)luaL_checklstring(L,4,&info_len); + size_t okm_len = (size_t)luaL_checkinteger(L,5); + + uint8_t *okm = malloc(okm_len); + if (!okm) luaL_error(L, "out of memory"); + + if (hkdf(sha_ver, salt, salt_len, ikm, ikm_len, info, info_len, okm, okm_len)) + lua_pushnil(L); + else + lua_pushlstring(L,(const char*)okm, okm_len); + + free(okm); + + LUA_STACK_GUARD_RETURN(L,1) +} + + + +static int luacall_instance_cutoff(lua_State *L) +{ + // out : func_name.profile_number[0] + // in : func_name.profile_number[1] + + lua_check_argc_range(L,"instance_cutoff",1,2); + + LUA_STACK_GUARD_ENTER(L) + + const t_lua_desync_context *ctx; + + if (!lua_islightuserdata(L,1)) + luaL_error(L, "instance_cutoff expect desync context in the first argument"); + ctx = lua_touserdata(L,1); + + int argc=lua_gettop(L); + bool bIn,bOut; + if (argc>=2) + { + luaL_checktype(L,2,LUA_TBOOLEAN); + bOut = lua_toboolean(L,2); + bIn = !bOut; + } + else + bIn = bOut = true; + + if (ctx->ctrack) + { + DLOG("instance cutoff for '%s' in=%u out=%u\n",ctx->instance,bIn,bOut); + lua_rawgeti(L,LUA_REGISTRYINDEX,ctx->ctrack->lua_instance_cutoff); + lua_getfield(L,-1,ctx->instance); + if (!lua_istable(L,-1)) + { + lua_pop(L,1); + lua_pushf_table(ctx->instance); + lua_getfield(L,-1,ctx->instance); + } + lua_rawgeti(L,-1,ctx->dp->n); + if (!lua_istable(L,-1)) + { + lua_pop(L,1); + lua_pushi_table(ctx->dp->n); + lua_rawgeti(L,-1,ctx->dp->n); + } + if (bOut) lua_pushi_bool(0,true); + if (bIn) lua_pushi_bool(1,true); + lua_pop(L,3); + } + else + DLOG("instance cutoff requested for '%s' in=%u out=%u but not possible without conntrack\n",ctx->instance,bIn,bOut); + + LUA_STACK_GUARD_RETURN(L,0) +} + +bool lua_instance_cutoff_check(const t_lua_desync_context *ctx, bool bIn) +{ + bool b=false; + + // out : func_name.profile_number[0] + // in : func_name.profile_number[1] + + if (ctx->ctrack) + { + lua_rawgeti(params.L,LUA_REGISTRYINDEX,ctx->ctrack->lua_instance_cutoff); + lua_getfield(params.L,-1,ctx->instance); + if (!lua_istable(params.L,-1)) + { + lua_pop(params.L,2); + return false; + } + lua_rawgeti(params.L,-1,ctx->dp->n); + if (!lua_istable(params.L,-1)) + { + lua_pop(params.L,3); + return false; + } + lua_rawgeti(params.L,-1,bIn); + b = lua_toboolean(params.L,-1); + lua_pop(params.L,4); + } + return b; +} + + +void lua_pushf_nil(const char *field) +{ + lua_pushstring(params.L, field); + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushi_nil(lua_Integer idx) +{ + lua_pushinteger(params.L, idx); + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_int(const char *field, lua_Integer v) +{ + lua_pushstring(params.L, field); + lua_pushinteger(params.L, v); + lua_rawset(params.L,-3); +} +void lua_pushi_int(lua_Integer idx, lua_Integer v) +{ + lua_pushinteger(params.L, idx); + lua_pushinteger(params.L, v); + lua_rawset(params.L,-3); +} +void lua_pushf_bool(const char *field, bool b) +{ + lua_pushstring(params.L, field); + lua_pushboolean(params.L, b); + lua_rawset(params.L,-3); +} +void lua_pushi_bool(lua_Integer idx, bool b) +{ + lua_pushinteger(params.L, idx); + lua_pushboolean(params.L, b); + lua_rawset(params.L,-3); +} +void lua_pushf_str(const char *field, const char *str) +{ + lua_pushstring(params.L, field); + lua_pushstring(params.L, str); // pushes nil if str==NULL + lua_rawset(params.L,-3); +} +void lua_pushi_str(lua_Integer idx, const char *str) +{ + lua_pushinteger(params.L, idx); + lua_pushstring(params.L, str); // pushes nil if str==NULL + lua_rawset(params.L,-3); +} +void lua_push_raw(const void *v, size_t l) +{ + if (v) + lua_pushlstring(params.L, (char*)v, l); + else + lua_pushnil(params.L); +} +void lua_pushf_raw(const char *field, const void *v, size_t l) +{ + lua_pushstring(params.L, field); + lua_push_raw(v,l); + lua_rawset(params.L,-3); +} +void lua_pushi_raw(lua_Integer idx, const void *v, size_t l) +{ + lua_pushinteger(params.L, idx); + lua_push_raw(v,l); + lua_rawset(params.L,-3); +} +void lua_pushf_reg(const char *field, int ref) +{ + lua_pushstring(params.L, field); + lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref); + lua_rawset(params.L, -3); +} +void lua_pushf_lud(const char *field, void *p) +{ + lua_pushstring(params.L, field); + lua_pushlightuserdata(params.L, p); + lua_rawset(params.L,-3); +} +void lua_pushf_table(const char *field) +{ + lua_pushstring(params.L, field); + lua_newtable(params.L); + lua_rawset(params.L,-3); +} +void lua_pushi_table(lua_Integer idx) +{ + lua_pushinteger(params.L, idx); + lua_newtable(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_global(const char *field, const char *global) +{ + lua_pushstring(params.L, field); + lua_getglobal(params.L, global); + lua_rawset(params.L,-3); +} + +void lua_pushf_tcphdr_options(const struct tcphdr *tcp, size_t len) +{ + lua_pushliteral(params.L,"options"); + lua_newtable(params.L); + + uint8_t *t = (uint8_t*)(tcp+1); + uint8_t *end = (uint8_t*)tcp + (tcp->th_off<<2); + uint8_t opt; + if ((end-(uint8_t*)tcp) < len) end=(uint8_t*)tcp + len; + lua_Integer idx=1; + while(t=end || t[1]<2 || (t+t[1])>end) break; + lua_pushinteger(params.L,idx); + lua_newtable(params.L); + lua_pushf_int("kind",opt); + lua_pushf_raw("data",t+2,t[1]-2); + t+=t[1]; + } + lua_rawset(params.L,-3); + if (opt==TCP_KIND_END) break; + idx++; + } + + lua_rawset(params.L,-3); +} + +void lua_pushf_tcphdr(const struct tcphdr *tcp, size_t len) +{ + lua_pushliteral(params.L, "tcp"); + if (tcp && len>=sizeof(struct tcphdr)) + { + lua_createtable(params.L, 0, 11); + lua_pushf_int("th_sport",ntohs(tcp->th_sport)); + lua_pushf_int("th_dport",ntohs(tcp->th_dport)); + lua_pushf_int("th_seq",ntohl(tcp->th_seq)); + lua_pushf_int("th_ack",ntohl(tcp->th_ack)); + lua_pushf_int("th_x2",tcp->th_x2); + lua_pushf_int("th_off",tcp->th_off); + lua_pushf_int("th_flags",tcp->th_flags); + lua_pushf_int("th_win",ntohs(tcp->th_win)); + lua_pushf_int("th_sum",ntohs(tcp->th_sum)); + lua_pushf_int("th_urp",ntohs(tcp->th_urp)); + lua_pushf_tcphdr_options(tcp,len); + } + else + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_udphdr(const struct udphdr *udp, size_t len) +{ + lua_pushliteral(params.L, "udp"); + if (udp && len>=sizeof(struct udphdr)) + { + lua_createtable(params.L, 0, 4); + lua_pushf_int("uh_sport",ntohs(udp->uh_sport)); + lua_pushf_int("uh_dport",ntohs(udp->uh_dport)); + lua_pushf_int("uh_ulen",ntohs(udp->uh_ulen)); + lua_pushf_int("uh_sum",ntohs(udp->uh_sum)); + } + else + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_iphdr(const struct ip *ip, size_t len) +{ + lua_pushliteral(params.L, "ip"); + if (ip && len>=sizeof(struct ip)) + { + uint16_t hl = ip->ip_hl<<2; + bool b_has_opt = hl>sizeof(struct tcphdr) && hl<=len; + lua_createtable(params.L, 0, 11+b_has_opt); + lua_pushf_int("ip_v",ip->ip_v); + lua_pushf_int("ip_hl",ip->ip_hl); + lua_pushf_int("ip_tos",ip->ip_tos); + lua_pushf_int("ip_len",ntohs(ip->ip_len)); + lua_pushf_int("ip_id",ntohs(ip->ip_id)); + lua_pushf_int("ip_off",ntohs(ip->ip_off)); + lua_pushf_int("ip_ttl",ip->ip_ttl); + lua_pushf_int("ip_p",ip->ip_p); + lua_pushf_int("ip_sum",ip->ip_sum); + lua_pushf_raw("ip_src",&ip->ip_src,sizeof(struct in_addr)); + lua_pushf_raw("ip_dst",&ip->ip_dst,sizeof(struct in_addr)); + if (b_has_opt) + lua_pushf_raw("options",(uint8_t*)(ip+1),hl-sizeof(struct tcphdr)); + } + else + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_ip6exthdr(const struct ip6_hdr *ip6, size_t len) +{ + // assume ipv6 packet structure was already checked for validity + size_t hdrlen; + uint8_t HeaderType, *data; + lua_Integer idx = 1; + + lua_pushliteral(params.L, "exthdr"); + lua_newtable(params.L); + if (len>=sizeof(struct ip6_hdr)) + { + HeaderType = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; + data=(uint8_t*)(ip6+1); + len-=sizeof(struct ip6_hdr); + while (len > 0) // need at least one byte for NextHeader field + { + switch (HeaderType) + { + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + case IPPROTO_MH: // mobility header + case IPPROTO_HIP: // Host Identity Protocol Version v2 + case IPPROTO_SHIM6: + if (len < 2) return; // error + hdrlen = 8 + (data[1] << 3); + break; + case IPPROTO_FRAGMENT: // fragment. length fixed to 8, hdrlen field defined as reserved + hdrlen = 8; + break; + case IPPROTO_AH: + // 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 IPPROTO_NONE: // no next header + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + goto end; + } + if (len < hdrlen) goto end; // error + + lua_pushinteger(params.L, idx++); + lua_createtable(params.L, 0, 3); + lua_pushf_int("type", HeaderType); + HeaderType = *data; + lua_pushf_int("next", HeaderType); + lua_pushf_raw("data",data+2,hdrlen-2); + lua_rawset(params.L,-3); + + // advance to the next header location + len -= hdrlen; + data += hdrlen; + } + } + +end: + lua_rawset(params.L,-3); +} +void lua_pushf_ip6hdr(const struct ip6_hdr *ip6, size_t len) +{ + lua_pushliteral(params.L, "ip6"); + if (ip6) + { + lua_createtable(params.L, 0, 7); + lua_pushf_int("ip6_flow",ntohl(ip6->ip6_ctlun.ip6_un1.ip6_un1_flow)); + lua_pushf_int("ip6_plen",ntohs(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen)); + lua_pushf_int("ip6_nxt",ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt); + lua_pushf_int("ip6_hlim",ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim); + lua_pushf_raw("ip6_src",&ip6->ip6_src,sizeof(struct in6_addr)); + lua_pushf_raw("ip6_dst",&ip6->ip6_dst,sizeof(struct in6_addr)); + lua_pushf_ip6exthdr(ip6,len); + } + else + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_push_dissect(const struct dissect *dis) +{ + if (dis) + { + lua_createtable(params.L, 0, 7); + lua_pushf_iphdr(dis->ip, dis->len_l3); + lua_pushf_ip6hdr(dis->ip6, dis->len_l3); + lua_pushf_tcphdr(dis->tcp, dis->len_l4); + lua_pushf_udphdr(dis->udp, dis->len_l4); + lua_pushf_int("l4proto",dis->proto); + lua_pushf_int("transport_len",dis->transport_len); + lua_pushf_raw("payload",dis->data_payload,dis->len_payload); + } + else + lua_pushnil(params.L); +} +void lua_pushf_dissect(const struct dissect *dis) +{ + lua_pushliteral(params.L, "dis"); + lua_push_dissect(dis); + lua_rawset(params.L,-3); +} + +void lua_pushf_ctrack(const t_ctrack *ctrack) +{ + lua_pushliteral(params.L, "track"); + if (ctrack) + { + lua_createtable(params.L, 0, 13 + (ctrack->ipproto == IPPROTO_TCP)); + + lua_pushf_int("pcounter_orig", ctrack->pcounter_orig); + lua_pushf_int("pdcounter_orig", ctrack->pdcounter_orig); + lua_pushf_int("pbcounter_orig", ctrack->pbcounter_orig); + lua_pushf_int("pcounter_reply", ctrack->pcounter_reply); + lua_pushf_int("pdcounter_reply", ctrack->pdcounter_reply); + lua_pushf_int("pbcounter_reply", ctrack->pbcounter_reply); + if (ctrack->incoming_ttl) + lua_pushf_int("incoming_ttl", ctrack->incoming_ttl); + else + lua_pushf_nil("incoming_ttl"); + lua_pushf_str("l7proto", l7proto_str(ctrack->l7proto)); + lua_pushf_str("hostname", ctrack->hostname); + lua_pushf_bool("hostname_is_ip", ctrack->hostname_is_ip); + lua_pushf_reg("lua_state", ctrack->lua_state); + lua_pushf_bool("lua_in_cutoff", ctrack->b_lua_in_cutoff); + lua_pushf_bool("lua_out_cutoff", ctrack->b_lua_out_cutoff); + + if (ctrack->ipproto == IPPROTO_TCP) + { + lua_pushliteral(params.L, "tcp"); + lua_createtable(params.L, 0, 14); + lua_pushf_int("seq0", ctrack->seq0); + lua_pushf_int("seq", ctrack->seq_last); + lua_pushf_int("ack0", ctrack->ack0); + lua_pushf_int("ack", ctrack->ack_last); + lua_pushf_int("pos_orig", ctrack->pos_orig - ctrack->seq0); + lua_pushf_int("winsize_orig", ctrack->winsize_orig); + lua_pushf_int("winsize_orig_calc", ctrack->winsize_orig_calc); + lua_pushf_int("scale_orig", ctrack->scale_orig); + lua_pushf_int("mss_orig", ctrack->mss_orig); + lua_pushf_int("pos_reply", ctrack->pos_reply - ctrack->ack0); + lua_pushf_int("winsize_reply", ctrack->winsize_reply); + lua_pushf_int("winsize_reply_calc", ctrack->winsize_reply_calc); + lua_pushf_int("scale_reply", ctrack->scale_reply); + lua_pushf_int("mss_reply", ctrack->mss_reply); + lua_rawset(params.L,-3); + } + } + else + lua_pushnil(params.L); + lua_rawset(params.L,-3); +} +void lua_pushf_args(const struct ptr_list_head *args) +{ + struct ptr_list *arg; + + lua_pushliteral(params.L,"arg"); + lua_newtable(params.L); + LIST_FOREACH(arg, args, next) + { + lua_pushf_str((char*)arg->ptr1,arg->ptr2 ? (char*)arg->ptr2 : ""); + } + lua_rawset(params.L,-3); +} + + + +static void lua_reconstruct_extract_options(lua_State *L, int idx, bool *badsum, bool *ip6_preserve_next, uint8_t *ip6_last_proto) +{ + if (lua_isnoneornil(L,idx)) + { + if (badsum) *badsum = false; + if (ip6_preserve_next) *ip6_preserve_next = false; + if (ip6_last_proto) *ip6_last_proto = IPPROTO_NONE; + } + else + { + luaL_checktype(L, idx, LUA_TTABLE); + if (badsum) + { + lua_getfield(L,idx,"badsum"); + *badsum = lua_type(L,-1)!=LUA_TNIL && (lua_type(L,-1)!=LUA_TBOOLEAN || lua_toboolean(L,-1)); + lua_pop(L,1); + } + if (ip6_preserve_next) + { + lua_getfield(L,idx,"ip6_preserve_next"); + *ip6_preserve_next = lua_type(L,-1)!=LUA_TNIL && (lua_type(L,-1)!=LUA_TBOOLEAN || lua_toboolean(L,-1)); + lua_pop(L,1); + } + if (ip6_last_proto) + { + lua_getfield(L,idx,"ip6_last_proto"); + *ip6_last_proto = lua_type(L,-1)==LUA_TNIL ? IPPROTO_NONE : (uint8_t)lua_tointeger(L,-1); + lua_pop(L,1); + } + } +} + + +static bool lua_reconstruct_ip6exthdr(int idx, struct ip6_hdr *ip6, size_t *len, uint8_t proto, bool preserve_next) +{ + // proto = last header type + if (*lenip6_ctlun.ip6_un1.ip6_un1_nxt; + uint8_t filled = sizeof(struct ip6_hdr); + lua_getfield(params.L,idx,"exthdr"); + if (lua_type(params.L,-1)==LUA_TTABLE) + { + lua_Integer idx=0; + uint8_t next, type, *p, *data = (uint8_t*)(ip6+1); + size_t l, left; + + left = *len - filled; + + for(;;) + { + lua_rawgeti(params.L,-1,++idx); + if (lua_type(params.L,-1)==LUA_TNIL) + { + lua_pop(params.L, 1); + break; + } + else + { + if (lua_type(params.L,-1)!=LUA_TTABLE) goto err2; + + lua_getfield(params.L,-1, "type"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err3; + type = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,-1, "next"); + next = lua_type(params.L,-1)==LUA_TNUMBER ? (uint8_t)lua_tointeger(params.L,-1) : IPPROTO_NONE; + lua_pop(params.L, 1); + + lua_getfield(params.L,-1, "data"); + if (lua_type(params.L,-1)!=LUA_TSTRING) goto err3; + p=(uint8_t*)lua_tolstring(params.L,-1,&l); + if (!l || (l+2)>left || ((type==IPPROTO_AH) ? (l<6 || ((l+2) & 3)) : ((l+2) & 7))) goto err3; + memcpy(data+2,p,l); + l+=2; + data[0] = next; // may be overwritten later + data[1] = (type==IPPROTO_AH) ? (l>>2)-2 : (l>>3)-1; + if (!preserve_next) *last_proto = type; + last_proto = data; // first byte of header holds type + left -= l; data += l; filled += l; + lua_pop(params.L, 2); + } + } + } + // set last header proto + if (!preserve_next) *last_proto = proto; + + *len = filled; + lua_pop(params.L, 1); + return true; +err2: + lua_pop(params.L, 2); + return false; +err3: + lua_pop(params.L, 3); + return false; +} +bool lua_reconstruct_ip6hdr(int idx, struct ip6_hdr *ip6, size_t *len, uint8_t last_proto, bool preserve_next) +{ + const char *p; + size_t l; + if (*lenip6_ctlun.ip6_un1.ip6_un1_flow = htonl(lua_type(params.L,-1)==LUA_TNUMBER ? (uint32_t)lua_tointeger(params.L,-1) : 0x60000000); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip6_plen"); + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons((uint16_t)lua_tointeger(params.L,-1)); + + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip6_nxt"); + ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip6_hlim"); + ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip6_src"); + if (lua_type(params.L,-1)!=LUA_TSTRING) goto err; + p = lua_tolstring(params.L,-1,&l); + if (l!=sizeof(struct in6_addr)) goto err; + ip6->ip6_src = *(struct in6_addr*)p; + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip6_dst"); + if (lua_type(params.L,-1)!=LUA_TSTRING) goto err; + p = lua_tolstring(params.L,-1,&l); + if (l!=sizeof(struct in6_addr)) goto err; + ip6->ip6_dst = *(struct in6_addr*)p; + lua_pop(params.L, 1); + return lua_reconstruct_ip6exthdr(idx, ip6, len, last_proto, preserve_next); +err: + lua_pop(params.L, 1); + return false; +} + +static int luacall_reconstruct_ip6hdr(lua_State *L) +{ + lua_check_argc_range(L,"reconstruct_ip6hdr",1,2); + + LUA_STACK_GUARD_ENTER(L) + + char data[512]; + size_t len=sizeof(data); + uint8_t last_proto; + bool preserve_next; + + lua_reconstruct_extract_options(L, 2, NULL, &preserve_next, &last_proto); + + if (!lua_reconstruct_ip6hdr(1,(struct ip6_hdr*)data, &len, last_proto, preserve_next)) + luaL_error(L, "invalid data for ip6hdr"); + lua_pushlstring(params.L,data,len); + + LUA_STACK_GUARD_RETURN(L,1) +} + +bool lua_reconstruct_iphdr(int idx, struct ip *ip, size_t *len) +{ + const char *p; + size_t l, lopt=0; + + if (*lenip_v = IPVERSION; + + lua_getfield(params.L,idx,"ip_tos"); + ip->ip_tos = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_len"); + ip->ip_len = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_id"); + ip->ip_id = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_off"); + ip->ip_off = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_ttl"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + ip->ip_ttl = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_p"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + ip->ip_p = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_src"); + if (lua_type(params.L,-1)!=LUA_TSTRING) goto err; + p = lua_tolstring(params.L,-1,&l); + if (l!=sizeof(struct in_addr)) goto err; + ip->ip_src = *(struct in_addr*)p; + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"ip_dst"); + if (lua_type(params.L,-1)!=LUA_TSTRING) goto err; + p = lua_tolstring(params.L,-1,&l); + if (l!=sizeof(struct in_addr)) goto err; + ip->ip_dst = *(struct in_addr*)p; + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"options"); + if (lua_type(params.L,-1)==LUA_TSTRING) + { + p = lua_tolstring(params.L,-1,&lopt); + if (lopt) + { + if (lopt>40 || ((sizeof(struct ip) + ((lopt+3)&~3)) > *len)) goto err; + memcpy(ip+1,p,lopt); + memset(((uint8_t*)ip) + sizeof(struct ip) + lopt, 0, (4-lopt&3)&3); + lopt = (lopt+3) & ~3; + } + } + lua_pop(params.L, 1); + + *len = sizeof(struct ip) + lopt; + ip->ip_hl = *len >> 2; + + ip4_fix_checksum(ip); + + return true; +err: + lua_pop(params.L, 1); + return false; +} +static int luacall_reconstruct_iphdr(lua_State *L) +{ + lua_check_argc(L,"reconstruct_iphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + char data[60]; + size_t l = sizeof(data); + if (!lua_reconstruct_iphdr(1,(struct ip*)&data,&l)) + luaL_error(L, "invalid data for iphdr"); + lua_pushlstring(params.L,data,l); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static bool lua_reconstruct_tcphdr_options(int idx, struct tcphdr *tcp, size_t *len) +{ + if (*len40) left=40; // max size of tcp options + + for (;;) + { + lua_rawgeti(params.L,-1,++idx); + if (lua_type(params.L,-1)==LUA_TNIL) + { + lua_pop(params.L, 1); + break; + } + else + { + // uses 'key' (at index -2) and 'value' (at index -1) + + if (!left || lua_type(params.L,-1)!=LUA_TTABLE) goto err2; + + lua_getfield(params.L,-1, "kind"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err3; + + kind = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + switch(kind) + { + case TCP_KIND_END: + *data = kind; data++; left--; filled++; + lua_pop(params.L, 1); + goto end; + case TCP_KIND_NOOP: + *data = kind; data++; left--; filled++; + break; + default: + lua_getfield(params.L,-1, "data"); + l = 0; + p = lua_type(params.L,-1)==LUA_TSTRING ? (uint8_t*)lua_tolstring(params.L,-1,&l) : NULL; + if ((2+l)>left) goto err3; + if (p) memcpy(data+2,p,l); + l+=2; + data[0] = kind; + data[1] = (uint8_t)l; + left -= l; + data += l; + filled += l; + lua_pop(params.L, 1); + } + lua_pop(params.L, 1); + } + } +end: + while(filled & 3) + { + if (!left) goto err1; + *data = TCP_KIND_NOOP; data++; left--; filled++; + } + } + + tcp->th_off = filled>>2; + *len = filled; + + lua_pop(params.L, 1); + return true; +err1: + lua_pop(params.L, 1); + return false; +err2: + lua_pop(params.L, 2); + return false; +err3: + lua_pop(params.L, 3); + return false; +} +bool lua_reconstruct_tcphdr(int idx, struct tcphdr *tcp, size_t *len) +{ + if (*lenth_sport = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_dport"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + tcp->th_dport = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_seq"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + tcp->th_seq = htonl((uint32_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_ack"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + tcp->th_ack = htonl((uint32_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_x2"); + tcp->th_x2 = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_flags"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + tcp->th_flags = (uint8_t)lua_tointeger(params.L,-1); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_win"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + tcp->th_win = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_sum"); + tcp->th_sum = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"th_urp"); + tcp->th_urp = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + tcp->th_off = 5; + + return lua_reconstruct_tcphdr_options(idx, tcp, len); +err: + lua_pop(params.L, 1); + return false; +} +static int luacall_reconstruct_tcphdr(lua_State *L) +{ + lua_check_argc(L,"reconstruct_tcphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + char data[60]; + size_t len=sizeof(data); + if (!lua_reconstruct_tcphdr(1,(struct tcphdr*)data,&len)) + luaL_error(L, "invalid data for tcphdr"); + lua_pushlstring(params.L,data,len); + + LUA_STACK_GUARD_RETURN(L,1) +} + +bool lua_reconstruct_udphdr(int idx, struct udphdr *udp) +{ + if (lua_type(params.L,-1)!=LUA_TTABLE) return false; + + lua_getfield(params.L,idx,"uh_sport"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + udp->uh_sport = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"uh_dport"); + if (lua_type(params.L,-1)!=LUA_TNUMBER) goto err; + udp->uh_dport = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"uh_ulen"); + udp->uh_ulen = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"uh_sum"); + udp->uh_sum = htons((uint16_t)lua_tointeger(params.L,-1)); + lua_pop(params.L, 1); + + return true; +err: + lua_pop(params.L, 1); + return false; +} +static int luacall_reconstruct_udphdr(lua_State *L) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_check_argc(L,"reconstruct_udphdr",1); + struct udphdr udp; + if (!lua_reconstruct_udphdr(1,&udp)) + luaL_error(L, "invalid data for udphdr"); + lua_pushlstring(params.L,(char*)&udp,sizeof(udp)); + + LUA_STACK_GUARD_RETURN(L,1) +} + +uint8_t lua_ip6_l4proto_from_dissect(int idx) +{ + int type; + + lua_getfield(params.L,idx,"tcp"); + type=lua_type(params.L,-1); + lua_pop(params.L,1); + if (type==LUA_TTABLE) return IPPROTO_TCP; + + lua_getfield(params.L,idx,"udp"); + type=lua_type(params.L,-1); + lua_pop(params.L,1); + return type==LUA_TTABLE ? IPPROTO_UDP : IPPROTO_NONE; +} + +bool lua_reconstruct_dissect(int idx, uint8_t *buf, size_t *len, bool badsum, bool ip6_preserve_next) +{ + uint8_t *data = buf; + size_t l,lpayload,l3,left = *len; + struct ip *ip=NULL; + struct ip6_hdr *ip6=NULL; + struct tcphdr *tcp=NULL; + struct udphdr *udp=NULL; + const char *p; + + idx = lua_absindex(params.L, idx); + + lua_getfield(params.L,idx,"ip"); + l = left; + if (lua_type(params.L,-1)==LUA_TTABLE) + { + ip = (struct ip*)data; + if (!lua_reconstruct_iphdr(-1, ip, &l)) + { + DLOG_ERR("reconstruct_dissect: bad ip\n"); + goto err; + } + ip4_fix_checksum(ip); + } + else + { + lua_pop(params.L, 1); + lua_getfield(params.L,idx,"ip6"); + if (lua_type(params.L,-1)!=LUA_TTABLE) goto err; + ip6 = (struct ip6_hdr*)data; + if (!lua_reconstruct_ip6hdr(-1, ip6, &l, lua_ip6_l4proto_from_dissect(idx), ip6_preserve_next)) + { + DLOG_ERR("reconstruct_dissect: bad ip6\n"); + goto err; + } + } + l3=l; + data+=l; left-=l; + lua_pop(params.L, 1); + + lua_getfield(params.L,idx,"tcp"); + l = left; + if (lua_type(params.L,-1)==LUA_TTABLE) + { + tcp = (struct tcphdr*)data; + if (!lua_reconstruct_tcphdr(-1, tcp, &l)) + { + DLOG_ERR("reconstruct_dissect: bad tcp\n"); + goto err; + } + } + else + { + lua_pop(params.L, 1); + lua_getfield(params.L,idx,"udp"); + l = sizeof(struct udphdr); + if (lua_type(params.L,-1)!=LUA_TTABLE || leftuh_ulen = htons((uint16_t)(lpayload+sizeof(struct udphdr))); + udp_fix_checksum(udp,l-l3,ip,ip6); + if (badsum) udp->uh_sum ^= 1 + (random() % 0xFFFF); + } + if (tcp) + { + tcp_fix_checksum(tcp,l-l3,ip,ip6); + if (badsum) tcp->th_sum ^= 1 + (random() % 0xFFFF); + } + + if (ip) + { + if (ntohs(ip->ip_off) & (IP_OFFMASK|IP_MF)) + { + // fragmentation. caller should set ip_len, ip_off and IP_MF correctly. C code moves and shrinks constructed ip payload + uint16_t iplen = ntohs(ip->ip_len); + uint16_t off = (ntohs(ip->ip_off) & IP_OFFMASK)<<3; + size_t frag_start = l3 + off; + if (iplenl) + { + DLOG_ERR("ipv4 frag : invalid ip_len\n"); + goto err; + } + if (frag_start>l) + { + DLOG_ERR("ipv4 frag : fragment offset is outside of the packet\n"); + goto err; + } + if (off) memmove(buf+l3,buf+l3+off,iplen-l3); + l = iplen; // shrink packet to iplen + } + else + ip->ip_len = htons((uint16_t)l); + ip4_fix_checksum(ip); + } + else if (ip6) + { + // data points to reconstructed packet's end + uint8_t *frag = proto_find_ip6_exthdr(ip6, l, IPPROTO_FRAGMENT); + if (frag) + { + uint16_t plen = ntohs(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen); // without ipv6 base header + uint16_t off = ntohs(((struct ip6_frag *)frag)->ip6f_offlg) & 0xFFF8; + uint8_t *endfrag = frag + 8; + size_t size_unfragmentable = endfrag - (uint8_t*)ip6 - sizeof(struct ip6_hdr); + + if (size_unfragmentable > plen) + { + DLOG_ERR("ipv6 frag : invalid ip6_plen\n"); + goto err; + } + size_t size_fragmentable = plen - size_unfragmentable; + if ((endfrag + off + size_fragmentable) > data) + { + DLOG_ERR("ipv6 frag : fragmentable part is outside of the packet\n"); + goto err; + } + if (off) memmove(endfrag, endfrag + off, size_fragmentable); + l = sizeof(struct ip6_hdr) + plen; + } + else + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons((uint16_t)(l-sizeof(struct ip6_hdr))); + } + + *len = l; + return true; +err: + lua_pop(params.L, 1); + return false; +} +static int luacall_reconstruct_dissect(lua_State *L) +{ + // reconstruct_dissect(data, reconstruct_opts) + lua_check_argc_range(L,"reconstruct_dissect",1,2); + + LUA_STACK_GUARD_ENTER(L) + + uint8_t buf[RECONSTRUCT_MAX_SIZE]; + size_t l = sizeof(buf); + + bool ip6_preserve_next, badsum; + lua_reconstruct_extract_options(params.L, 2, &badsum, &ip6_preserve_next, NULL); + + if (!lua_reconstruct_dissect(1, buf, &l, badsum, ip6_preserve_next)) + luaL_error(L, "invalid dissect data"); + lua_pushlstring(params.L,(char*)buf,l); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_dissect(lua_State *L) +{ + // dissect(packet_data) + lua_check_argc(L,"dissect",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)luaL_checklstring(L, 1, &len); + + struct dissect dis; + proto_dissect_l3l4(data, len, &dis); + + lua_push_dissect(&dis); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_csum_ip4_fix(lua_State *L) +{ + // csum_ip4_fix(ip_header) returns ip_header + lua_check_argc(L,"csum_ip4_fix",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t l; + const uint8_t *data = (const uint8_t*)luaL_checklstring(L, 1, &l); + if (l>60 || !proto_check_ipv4(data, l)) + luaL_error(L, "invalid ip header"); + + uint8_t data2[60]; + memcpy(data2, data, l); + ip4_fix_checksum((struct ip*)data2); + + lua_pushlstring(params.L,(char*)data2,l); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_csum_tcp_fix(lua_State *L) +{ + // csum_ip4_fix(ip_header, tcp_header, payload) returns tcp_header + lua_check_argc(L,"csum_tcp_fix",3); + + LUA_STACK_GUARD_ENTER(L) + + size_t l_ip; + const uint8_t *b_ip = (const uint8_t*)luaL_checklstring(L, 1, &l_ip); + const struct ip *ip=NULL; + const struct ip6_hdr *ip6=NULL; + + if (proto_check_ipv4(b_ip, l_ip)) + ip = (struct ip*)b_ip; + else if (proto_check_ipv6(b_ip, sizeof(struct ip6_hdr) + ntohs(((struct ip6_hdr*)b_ip)->ip6_ctlun.ip6_un1.ip6_un1_plen))) + ip6 = (struct ip6_hdr*)b_ip; + else + luaL_error(L, "invalid ip header"); + + size_t l_tcp; + const uint8_t *b_tcp = (const uint8_t*)luaL_checklstring(L, 2, &l_tcp); + if (!proto_check_tcp(b_tcp, l_tcp)) + luaL_error(L, "invalid tcp header"); + + size_t l_pl; + const uint8_t *b_pl = (const uint8_t*)luaL_checklstring(L, 3, &l_pl); + + size_t l_tpl = l_tcp + l_pl; + uint8_t *tpl = malloc(l_tpl); + if (!tpl) luaL_error(L, "out of memory"); + + memcpy(tpl, b_tcp, l_tcp); + memcpy(tpl+l_tcp, b_pl, l_pl); + struct tcphdr *tcp = (struct tcphdr*)tpl; + tcp_fix_checksum(tcp, l_tpl, ip, ip6); + + lua_pushlstring(L,(char*)tpl,l_tcp); + free(tpl); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_csum_udp_fix(lua_State *L) +{ + // csum_ip4_fix(ip_header, tcp_header, payload) returns tcp_header + lua_check_argc(L,"csum_udp_fix",3); + + LUA_STACK_GUARD_ENTER(L) + + size_t l_ip; + const uint8_t *b_ip = (const uint8_t*)luaL_checklstring(L, 1, &l_ip); + const struct ip *ip=NULL; + const struct ip6_hdr *ip6=NULL; + + if (proto_check_ipv4(b_ip, l_ip)) + ip = (struct ip*)b_ip; + else if (proto_check_ipv6(b_ip, sizeof(struct ip6_hdr) + ntohs(((struct ip6_hdr*)b_ip)->ip6_ctlun.ip6_un1.ip6_un1_plen))) + ip6 = (struct ip6_hdr*)b_ip; + else + luaL_error(L, "invalid ip header"); + + size_t l_udp; + const uint8_t *b_udp = (const uint8_t*)luaL_checklstring(L, 2, &l_udp); + if (!proto_check_udp(b_udp, ntohs(((struct udphdr*)b_udp)->uh_ulen))) + luaL_error(L, "invalid udp header"); + + size_t l_pl; + const uint8_t *b_pl = (const uint8_t*)luaL_checklstring(L, 3, &l_pl); + + size_t l_tpl = l_udp + l_pl; + uint8_t *tpl = malloc(l_tpl); + if (!tpl) luaL_error(L, "out of memory"); + + memcpy(tpl, b_udp, l_udp); + memcpy(tpl+l_udp, b_pl, l_pl); + struct udphdr *udp = (struct udphdr*)tpl; + udp_fix_checksum(udp, l_tpl, ip, ip6); + + lua_pushlstring(L,(char*)tpl,l_udp); + free(tpl); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_ntop(lua_State *L) +{ + size_t l; + const char *p; + char s[40]; + int af=0; + + lua_check_argc(L,"ntop",1); + + LUA_STACK_GUARD_ENTER(L) + + p=luaL_checklstring(L,1,&l); + switch(l) + { + case sizeof(struct in_addr): + af=AF_INET; + break; + case sizeof(struct in6_addr): + af=AF_INET6; + break; + default: + lua_pushnil(L); + return 1; + } + if (!inet_ntop(af,p,s,sizeof(s))) + luaL_error(L, "inet_ntop error"); + lua_pushstring(L,s); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_pton(lua_State *L) +{ + const char *p; + char s[sizeof(struct in6_addr)]; + + lua_check_argc(L,"pton",1); + + LUA_STACK_GUARD_ENTER(L) + + p=luaL_checkstring(L,1); + if (inet_pton(AF_INET,p,s)) + lua_pushlstring(L,s,sizeof(struct in_addr)); + else if (inet_pton(AF_INET6,p,s)) + lua_pushlstring(L,s,sizeof(struct in6_addr)); + else + lua_pushnil(L); + + LUA_STACK_GUARD_RETURN(L,1) +} + + +static void lua_rawsend_extract_options(lua_State *L, int idx, int *repeats, uint32_t *fwmark, const char **ifout) +{ + if (lua_isnoneornil(L,idx)) + { + if (repeats) *repeats = 1; + if (fwmark) *fwmark = params.desync_fwmark; + if (ifout) *ifout = NULL; + } + else + { + luaL_checktype(L, idx, LUA_TTABLE); + if (repeats) + { + lua_getfield(L,idx,"repeats"); + *repeats=(int)lua_tointeger(L,-1); + if (!*repeats) *repeats=1; + lua_pop(L,1); + } + if (fwmark) + { + lua_getfield(L,idx,"fwmark"); + *fwmark=(uint32_t)lua_tointeger(L,-1) | params.desync_fwmark; + lua_pop(L,1); + } + if (ifout) + { + lua_getfield(L,idx,"ifout"); + *ifout = lua_type(L,-1)==LUA_TSTRING ? lua_tostring(L,-1) : NULL; + lua_pop(L,1); + } + } +} + +static int luacall_rawsend(lua_State *L) +{ + // bool rawsend(raw_data, {repeats, fwmark, ifout}) + lua_check_argc_range(L,"rawsend",1,2); + + LUA_STACK_GUARD_ENTER(L) + + uint8_t *data; + const char *ifout; + size_t len; + int repeats; + uint32_t fwmark; + sockaddr_in46 sa; + bool b; + + data=(uint8_t*)luaL_checklstring(L,1,&len); + lua_rawsend_extract_options(L,2,&repeats,&fwmark,&ifout); + + if (!extract_dst(data, len, (struct sockaddr*)&sa)) + luaL_error(L, "bad ip4/ip6 header"); + DLOG("rawsend repeats=%d size=%zu ifout=%s fwmark=%08X\n", repeats,len,ifout ? ifout : "",fwmark); + b = rawsend_rep(repeats, (struct sockaddr*)&sa, fwmark, ifout, data, len); + lua_pushboolean(L, b); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_rawsend_dissect(lua_State *L) +{ + // rawsend(data, rawsend_opts, reconstruct_opts) + lua_check_argc_range(L,"rawsend_dissect",1,3); + + LUA_STACK_GUARD_ENTER(L) + + uint8_t buf[RECONSTRUCT_MAX_SIZE]; + size_t len=sizeof(buf); + const char *ifout; + int repeats; + uint32_t fwmark; + sockaddr_in46 sa; + bool b, badsum, ip6_preserve_next; + + luaL_checktype(L,1,LUA_TTABLE); + lua_rawsend_extract_options(L,2, &repeats, &fwmark, &ifout); + lua_reconstruct_extract_options(params.L, 3, &badsum, &ip6_preserve_next, NULL); + + if (!lua_reconstruct_dissect(1, buf, &len, badsum, ip6_preserve_next)) + luaL_error(L, "invalid dissect data"); + + if (!extract_dst(buf, len, (struct sockaddr*)&sa)) + luaL_error(L, "bad ip4/ip6 header"); + DLOG("rawsend_dissect repeats=%d size=%zu badsum=%u ifout=%s fwmark=%08X\n", repeats,len,badsum,ifout ? ifout : "",fwmark); + b = rawsend_rep(repeats, (struct sockaddr*)&sa, fwmark, ifout, buf, len); + lua_pushboolean(L, b); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_resolve_pos(lua_State *L) +{ + // resolve_pos(blob,l7payload_type,marker[,zero_based_pos]) + lua_check_argc_range(L,"resolve_pos",3,4); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + const char *sl7payload = luaL_checkstring(L,2); + const char *smarker = luaL_checkstring(L,3); + bool bZeroBased = argc>=4 && lua_toboolean(L,4); + + t_l7payload l7payload = l7payload_from_name(sl7payload); + if (l7payload==L7P_INVALID) + luaL_error(L, "bad payload type : '%s'", sl7payload); + + struct proto_pos marker; + if (!posmarker_parse(smarker,&marker)) + luaL_error(L, "bad marker : '%s'", smarker); + ssize_t pos=ResolvePos(data, len, l7payload, &marker); + + if (pos==POS_NOT_FOUND) + lua_pushnil(L); + else + lua_pushinteger(L,pos+!bZeroBased); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_resolve_multi_pos(lua_State *L) +{ + // resolve_multi_pos(blob,l7payload_type,marker_list[,zero_based_pos]) + lua_check_argc_range(L,"resolve_multi_pos",3,4); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + const char *sl7payload = luaL_checkstring(L,2); + const char *smarkers = luaL_checkstring(L,3); + bool bZeroBased = argc>=4 && lua_toboolean(L,4); + + t_l7payload l7payload = l7payload_from_name(sl7payload); + if (l7payload==L7P_INVALID) + luaL_error(L, "bad payload type : '%s'", sl7payload); + + struct proto_pos markers[128]; + ssize_t pos[sizeof(markers)/sizeof(*markers)]; + int i, ctpos, ctm = sizeof(markers)/sizeof(*markers); + if (!posmarker_list_parse(smarkers,markers,&ctm)) + luaL_error(L, "bad marker list"); + ResolveMultiPos(data, len, l7payload, markers, ctm, pos, &ctpos); + + lua_newtable(L); + for(i=0;i=4 && lua_toboolean(L,4); + bool bZeroBased = argc>=5 && lua_toboolean(L,5); + + t_l7payload l7payload = l7payload_from_name(sl7payload); + if (l7payload==L7P_INVALID) + luaL_error(L, "bad payload type : '%s'", sl7payload); + + struct proto_pos markers[2]; + ssize_t pos[sizeof(markers)/sizeof(*markers)]; + int ctm = sizeof(markers)/sizeof(*markers); + if (!posmarker_list_parse(smarkers,markers,&ctm)) + luaL_error(L, "bad marker list"); + if (ctm!=2) + luaL_error(L, "resolve_range require 2 markers"); + pos[0] = ResolvePos(data, len, l7payload, markers); + pos[1] = ResolvePos(data, len, l7payload, markers+1); + if (pos[0]==POS_NOT_FOUND && pos[1]==POS_NOT_FOUND || bStrict && (pos[0]==POS_NOT_FOUND || pos[1]==POS_NOT_FOUND)) + { + lua_pushnil(L); + return 1; + } + if (pos[0]==POS_NOT_FOUND) pos[0] = 0; + if (pos[1]==POS_NOT_FOUND) pos[1] = len-1; + if (pos[0]>pos[1]) + { + lua_pushnil(L); + return 1; + } + + lua_newtable(L); + lua_pushi_int(1,pos[0]+!bZeroBased); + lua_pushi_int(2,pos[1]+!bZeroBased); + + LUA_STACK_GUARD_RETURN(L,1) +} + +static int luacall_tls_record_is_tls_client_hello(lua_State *L) +{ + // (blob,partialOK) + lua_check_argc_range(L,"tls_record_is_tls_client_hello",1,2); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + bool bPartialOK = argc>=2 && lua_toboolean(L,2); + + lua_pushboolean(L,IsTLSClientHello(data,len,bPartialOK)); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_record_is_tls_server_hello(lua_State *L) +{ + // (blob,partialOK) + lua_check_argc_range(L,"tls_record_is_tls_server_hello",1,2); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + bool bPartialOK = argc>=2 && lua_toboolean(L,2); + + lua_pushboolean(L,IsTLSServerHello(data,len,bPartialOK)); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_is_tls_client_hello(lua_State *L) +{ + // (blob,partialOK) + lua_check_argc_range(L,"tls_handshake_is_tls_client_hello",1,2); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + bool bPartialOK = argc>=2 && lua_toboolean(L,2); + + lua_pushboolean(L,IsTLSHandshakeClientHello(data,len,bPartialOK)); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_is_tls_server_hello(lua_State *L) +{ + // (blob,partialOK) + lua_check_argc_range(L,"tls_handshake_is_tls_server_hello",1,2); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + bool bPartialOK = argc>=2 && lua_toboolean(L,2); + + lua_pushboolean(L,IsTLSHandshakeServerHello(data,len,bPartialOK)); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_record_find_ext(lua_State *L) +{ + // (blob,type,partialOK) + lua_check_argc_range(L,"tls_record_find_ext",2,3); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len, len_ext; + const uint8_t *ext, *data = (uint8_t*)luaL_checklstring(L,1,&len); + luaL_checktype(L,2,LUA_TNUMBER); + uint16_t type = (uint16_t)lua_tointeger(L,2); + bool bPartialOK = argc>=3 && lua_toboolean(L,3); + + bool b = TLSFindExt(data, len, type, &ext, &len_ext, bPartialOK); + lua_pushinteger(L,b ? ext-data+1 : 0); + lua_pushinteger(L,b ? len_ext : 0); + + LUA_STACK_GUARD_RETURN(L,2) +} +static int luacall_tls_handshake_find_ext(lua_State *L) +{ + // (blob,type,partialOK) + lua_check_argc_range(L,"tls_handshake_find_ext",2,3); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + size_t len, len_ext; + const uint8_t *ext, *data = (uint8_t*)luaL_checklstring(L,1,&len); + luaL_checktype(L,2,LUA_TNUMBER); + uint16_t type = (uint16_t)lua_tointeger(L,2); + bool bPartialOK = argc>=3 && lua_toboolean(L,3); + + bool b = TLSFindExtInHandshake(data, len, type, &ext, &len_ext, bPartialOK); + lua_pushinteger(L,b ? ext-data+1 : 0); + lua_pushinteger(L,b ? len_ext : 0); + + LUA_STACK_GUARD_RETURN(L,2) +} +static int luacall_tls_record_find_extlen(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_record_find_extlen",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len, offset; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + + bool b = TLSFindExtLen(data, len, &offset); + lua_pushinteger(L,b ? offset+1 : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_find_extlen(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_handshake_find_extlen",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len, offset; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + + bool b = TLSFindExtLenOffsetInHandshake(data, len, &offset); + lua_pushinteger(L,b ? offset+1 : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_record_len(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_record_len",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushinteger(L,IsTLSHello(data,len,0,true) ? TLSRecordLen(data) : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_record_data_len(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_record_data_len",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushinteger(L,IsTLSHello(data,len,0,true) ? TLSRecordDataLen(data) : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_record_is_full(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_record_is_full",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushboolean(L,IsTLSHello(data,len,0,true) && IsTLSRecordFull(data,len) ); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_len(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_handshake_len",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushinteger(L,IsTLSHandshakeHello(data,len,0,true) ? TLSHandshakeLen(data) : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_data_len(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_handshake_data_len",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushinteger(L,IsTLSHandshakeHello(data,len,0,true) ? TLSHandshakeDataLen(data) : 0); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_tls_handshake_is_full(lua_State *L) +{ + // (blob) + lua_check_argc(L,"tls_handshake_is_full",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); + lua_pushboolean(L,IsTLSHandshakeFull(data,len)); + + LUA_STACK_GUARD_RETURN(L,1) +} + + +static int luacall_tls_mod(lua_State *L) +{ + // (blob, modlist, payload) + lua_check_argc_range(L,"tls_mod",2,3); + + LUA_STACK_GUARD_ENTER(L) + + int argc=lua_gettop(L); + + size_t fake_tls_len; + bool bRes; + const uint8_t *fake_tls = (uint8_t*)luaL_checklstring(L,1,&fake_tls_len); + const char *modlist = luaL_checkstring(L,2); + + size_t payload_len = 0; + const uint8_t *payload = NULL; + if (argc>=3 && lua_type(L,3)!=LUA_TNIL) + payload = (uint8_t*)luaL_checklstring(L,3,&payload_len); + + struct fake_tls_mod mod; + if (!TLSMod_parse_list(modlist, &mod)) + luaL_error(L, "invalid tls mod list : '%s'", modlist); + + if (mod.mod) + { + size_t newlen = fake_tls_len, maxlen = fake_tls_len + sizeof(mod.sni) + 4; + uint8_t *newtls = malloc(maxlen); + if (!newtls) luaL_error(L, "out of memory"); + + memcpy(newtls, fake_tls, newlen); + bRes = TLSMod(&mod, payload, payload_len, newtls, &newlen, maxlen); + lua_pushlstring(L,(char*)newtls,newlen); + + free(newtls); + } + else + { + // no mod. push it back + lua_pushlstring(L,(char*)fake_tls,fake_tls_len); + bRes = true; + } + lua_pushboolean(L, bRes); + + LUA_STACK_GUARD_RETURN(L,2) +} + + +// ---------------------------------------- + + +void lua_shutdown() +{ + if (params.L) + { + DLOG("LUA SHUTDOWN\n"); + lua_close(params.L); + params.L=NULL; + } +} + +#if LUA_VERSION_NUM >= 504 +static void lua_warn(void *ud, const char *msg, int tocont) +{ + DLOG_CONDUP("LUA WARNING: %s\n",msg); +} +#endif +static void lua_perror(lua_State *L) +{ + if (lua_isstring(L, -1)) + { + const char *error_message = lua_tostring(L, -1); + DLOG_ERR("LUA ERROR: %s\n", error_message); + } + lua_pop(L, 1); +} +static int lua_panic (lua_State *L) +{ + lua_perror(L); + DLOG_ERR("LUA PANIC: THIS IS FATAL. DYING.\n"); + exit(100); + return 0; +} + +static bool lua_basic_init() +{ + lua_shutdown(); + if (!(params.L = luaL_newstate())) + { + DLOG_ERR("LUA INIT ERROR\n"); + return false; + } + unsigned int ver; +#if LUA_VERSION_NUM >= 504 + ver = (unsigned int)lua_version(params.L); +#elif LUA_VERSION_NUM >= 502 + ver = (unsigned int)*lua_version(params.L); +#else + ver = LUA_VERSION_NUM; +#endif +#ifdef LUAJIT_VERSION +#ifdef OPENRESTY_LUAJIT +#define LJSUBVER " OpenResty" +#else +#define LJSUBVER "" +#endif + DLOG_CONDUP("LUA v%u.%u %s%s\n",ver/100,ver%100, LUAJIT_VERSION, LJSUBVER); +#else + DLOG_CONDUP("LUA v%u.%u\n",ver/100,ver%100); +#endif +#if LUA_VERSION_NUM >= 504 + lua_setwarnf(params.L,lua_warn,NULL); +#endif + lua_atpanic(params.L,lua_panic); + luaL_openlibs(params.L); /* Load Lua libraries */ + return true; +} + +static bool lua_desync_functions_exist() +{ + struct desync_profile_list *dpl; + struct func_list *func; + + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + LIST_FOREACH(func, &dpl->dp.lua_desync, next) + { + lua_getglobal(params.L, func->func); + if (!lua_isfunction(params.L,-1)) + { + lua_pop(params.L,1); + DLOG_ERR("desync function '%s' does not exist\n",func->func); + return false; + } + lua_pop(params.L,1); + } + } + return true; +} + +static bool lua_init_scripts(void) +{ + struct str_list *str; + int status; + + LIST_FOREACH(str, ¶ms.lua_init_scripts, next) + { + if (params.debug) + { + if (str->str[0]=='@') + DLOG("LUA RUN FILE: %s\n",str->str+1); + else + { + char s[128]; + snprintf(s,sizeof(s),"%s",str->str); + DLOG("LUA RUN STR: %s\n",s); + } + } + if ((status = str->str[0]=='@' ? luaL_dofile(params.L, str->str+1) : luaL_dostring(params.L, str->str))) + { + lua_perror(params.L); + return false; + } + } + return true; +} + +static void lua_sec_harden(void) +{ + // remove unwanted functions. lua scripts are not intended to execute files + const struct + { + const char *global, *func; + } bad[] = { + {"os","execute"}, + {"io","popen"}, + {"package","loadlib"}, + {"debug", NULL} + }; + DLOG("LUA REMOVE:"); + for (int i=0;iname, blob->size); + lua_pushlstring(params.L, (char*)blob->data, blob->size); + lua_setglobal(params.L, blob->name); + blob_destroy(blob); + } +} + +static void lua_init_const(void) +{ + const struct + { + const char *name; + unsigned int v; + } cuint[] = { +#ifdef __linux__ + {"qnum",params.qnum}, +#elif defined(BSD) + {"divert_port",params.port}, +#endif + {"desync_fwmark",params.desync_fwmark}, + + {"VERDICT_PASS",VERDICT_PASS}, + {"VERDICT_MODIFY",VERDICT_MODIFY}, + {"VERDICT_DROP",VERDICT_DROP}, + + {"DEFAULT_MSS",DEFAULT_MSS}, + + {"IP_BASE_LEN",sizeof(struct ip)}, + {"IP6_BASE_LEN",sizeof(struct ip6_hdr)}, + {"TCP_BASE_LEN",sizeof(struct tcphdr)}, + {"UDP_BASE_LEN",sizeof(struct udphdr)}, + + {"TCP_KIND_END",TCP_KIND_END}, + {"TCP_KIND_NOOP",TCP_KIND_NOOP}, + {"TCP_KIND_MSS",TCP_KIND_MSS}, + {"TCP_KIND_SCALE",TCP_KIND_SCALE}, + {"TCP_KIND_SACK_PERM",TCP_KIND_SACK_PERM}, + {"TCP_KIND_SACK",TCP_KIND_SACK}, + {"TCP_KIND_TS",TCP_KIND_TS}, + {"TCP_KIND_MD5",TCP_KIND_MD5}, + {"TCP_KIND_AO",TCP_KIND_AO}, + {"TCP_KIND_FASTOPEN",TCP_KIND_FASTOPEN}, + + {"TH_FIN",TH_FIN}, + {"TH_SYN",TH_SYN}, + {"TH_RST",TH_RST}, + {"TH_PUSH",TH_PUSH}, + {"TH_ACK",TH_ACK}, + {"TH_FIN",TH_FIN}, + {"TH_URG",TH_URG}, + {"TH_ECE",0x40}, + {"TH_CWR",0x80}, + + {"IP_RF",IP_RF}, + {"IP_DF",IP_DF}, + {"IP_MF",IP_MF}, + {"IP_OFFMASK",IP_OFFMASK}, + {"IP_FLAGMASK",IP_RF|IP_DF|IP_MF}, + {"IPTOS_ECN_MASK",IPTOS_ECN_MASK}, + {"IPTOS_ECN_ECT1",IPTOS_ECN_ECT1}, + {"IPTOS_ECN_ECT0",IPTOS_ECN_ECT0}, + {"IPTOS_ECN_CE",IPTOS_ECN_CE}, + {"IP6F_MORE_FRAG",0x0001}, // in ip6.h it's defined depending of machine byte order + + {"IPPROTO_IP",IPPROTO_IP}, + {"IPPROTO_IPV6",IPPROTO_IPV6}, + {"IPPROTO_ICMP",IPPROTO_ICMP}, + {"IPPROTO_TCP",IPPROTO_TCP}, + {"IPPROTO_UDP",IPPROTO_UDP}, + {"IPPROTO_ICMPV6",IPPROTO_ICMPV6}, + {"IPPROTO_HOPOPTS",IPPROTO_HOPOPTS}, + {"IPPROTO_ROUTING",IPPROTO_ROUTING}, + {"IPPROTO_FRAGMENT",IPPROTO_FRAGMENT}, + {"IPPROTO_AH",IPPROTO_AH}, + {"IPPROTO_ESP",IPPROTO_ESP}, + {"IPPROTO_DSTOPTS",IPPROTO_DSTOPTS}, + {"IPPROTO_MH",IPPROTO_MH}, + {"IPPROTO_HIP",IPPROTO_HIP}, + {"IPPROTO_SHIM6",IPPROTO_SHIM6}, + {"IPPROTO_NONE",IPPROTO_NONE} + }; + DLOG("LUA NUMERIC:"); + for (int i=0;i= params.lua_gc) + { + int kb1 = lua_gc(params.L, LUA_GCCOUNT, 0); + lua_gc(params.L, LUA_GCCOLLECT, 0); + int kb2 = lua_gc(params.L, LUA_GCCOUNT, 0); + DLOG("\nLUA GARBAGE COLLECT: %dK => %dK\n",kb1,kb2); + gc_time = now; + } + } +} diff --git a/nfq2/lua.h b/nfq2/lua.h new file mode 100644 index 0000000..07b4a11 --- /dev/null +++ b/nfq2/lua.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#ifdef LUAJIT +#include "luajit.h" +#else +#include +#endif +#include +#include + +#include "pools.h" +#include "conntrack.h" +#include "darkmagic.h" + +#if LUA_VERSION_NUM < 503 +#define lua_isinteger lua_isnumber +#endif +#ifndef LUA_UNSIGNED +#define LUA_UNSIGNED uint64_t +#endif + +// pushing and not popping inside luacall cause memory leak +#define LUA_STACK_GUARD_ENTER(L) int _lsg=lua_gettop(L); +#define LUA_STACK_GUARD_LEAVE(L,N) if ((_lsg+N)!=lua_gettop(L)) luaL_error(L,"stack guard failure"); +#define LUA_STACK_GUARD_RETURN(L,N) LUA_STACK_GUARD_LEAVE(L,N); return N; + + +bool lua_init(void); +void lua_shutdown(void); +void lua_dlog_error(void); +void lua_do_gc(void); + +#if LUA_VERSION_NUM < 502 +int lua_absindex(lua_State *L, int idx); +#endif + +// push - create object and push to the stack +// pushf - create object and set it as a named field of a table already present on the stack +// pushi - create object and set it as a index field of a table already present on the stack +void lua_pushf_nil(const char *field); +void lua_pushi_nil(lua_Integer idx); +void lua_pushf_bool(const char *field, bool b); +void lua_pushi_bool(lua_Integer idx, bool b); +void lua_pushf_str(const char *field, const char *str); +void lua_pushi_str(lua_Integer idx, const char *str); +void lua_pushf_int(const char *field, lua_Integer v); +void lua_pushi_int(lua_Integer idx, lua_Integer v); +void lua_push_raw(const void *v, size_t l); +void lua_pushf_raw(const char *field, const void *v, size_t l); +void lua_pushi_raw(lua_Integer idx, const void *v, size_t l); +void lua_pushf_reg(const char *field, int ref); +void lua_pushf_lud(const char *field, void *p); +void lua_pushf_table(const char *field); +void lua_pushi_table(lua_Integer idx); + +void lua_pushf_tcphdr_options(const struct tcphdr *tcp, size_t len); +void lua_pushf_tcphdr(const struct tcphdr *tcp, size_t len); +void lua_pushf_udphdr(const struct udphdr *udp, size_t len); +void lua_pushf_iphdr(const struct ip *ip, size_t len); +void lua_pushf_ip6hdr(const struct ip6_hdr *ip6, size_t len); +void lua_push_dissect(const struct dissect *dis); +void lua_pushf_dissect(const struct dissect *dis); +void lua_pushf_ctrack(const t_ctrack *ctrack); +void lua_pushf_args(const struct ptr_list_head *args); +void lua_pushf_global(const char *field, const char *global); + +bool lua_reconstruct_ip6hdr(int idx, struct ip6_hdr *ip6, size_t *len, uint8_t last_proto, bool preserve_next); +bool lua_reconstruct_iphdr(int idx, struct ip *ip, size_t *len); +bool lua_reconstruct_tcphdr(int idx, struct tcphdr *tcp, size_t *len); +bool lua_reconstruct_udphdr(int idx, struct udphdr *udp); +bool lua_reconstruct_dissect(int idx, uint8_t *buf, size_t *len, bool badsum, bool ip6_preserve_next); + +typedef struct { + const char *func, *instance; + const struct desync_profile *dp; + const t_ctrack *ctrack; +} t_lua_desync_context; + +bool lua_instance_cutoff_check(const t_lua_desync_context *ctx, bool bIn); diff --git a/nfq2/nfqws.c b/nfq2/nfqws.c new file mode 100644 index 0000000..cd2a126 --- /dev/null +++ b/nfq2/nfqws.c @@ -0,0 +1,2558 @@ +#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 "lua.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 %u\n", dpl->dp.n); + HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); + } + 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, const uint8_t *data_pkt, size_t len_pkt, uint8_t *mod_pkt, size_t *len_mod_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, mod_pkt, len_mod_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]; + uint8_t mod[RECONSTRUCT_MAX_SIZE]; + size_t modlen; + + 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; + modlen = sizeof(mod); + // there's no space to grow packet in recv blob from nfqueue. it can contain multiple packets with no extra buffer length for modifications. + // to support increased sizes use separate mod buffer + // this is not a problem because only LUA code can trigger VERDICT_MODIFY (and postnat workaround too, once a connection if first packet is dropped) + // in case of VERIDCT_MODIFY packet is always reconstructed from dissect, so no difference where to save the data => no performance loss + uint8_t verdict = processPacketData(&mark, ifin, ifout, data, len, mod, &modlen); + switch (verdict & VERDICT_MASK) + { + case VERDICT_MODIFY: + DLOG("packet: id=%d pass modified. len %zu => %zu\n", id, len, modlen); + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)modlen, mod); + 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 nfq 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 nfq 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 + } + + 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[RECONSTRUCT_MAX_SIZE] __attribute__((aligned)); + struct nfq_handle *h = NULL; + struct nfq_q_handle *qh = NULL; + int res, 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; + + sec_harden(); + + 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 err; + + if (!lua_init()) + 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(); + + 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(); + lua_do_gc(); +#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) DLOG_ERR("nfq_handle_packet error %d\n", r); + } + 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); + + res=0; +ex: + nfq_deinit(&h, &qh); + lua_shutdown(); +#ifdef HAS_FILTER_SSID + wlan_info_deinit(); +#endif + return res; +err: + if (Fpid) fclose(Fpid); + res=1; + goto ex; +} + +#elif defined(BSD) + +static int dvt_main(void) +{ + uint8_t buf[RECONSTRUCT_MAX_SIZE] __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 (!lua_init()) + 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 modlen, len = rd; + const char *ifin, *ifout; + + ReloadCheck(); + lua_do_gc(); + + // in any BSD addr of incoming packet is set to the first addr of the interface. addr of outgoing packet is set to zero + bool bIncoming = sa_has_addr((struct sockaddr*)&sa_from); + ifin = bIncoming ? "unknown" : ""; + ifout = bIncoming ? "" : "unknown"; +#ifdef __FreeBSD__ + // FreeBSD passes ifname of incoming interface in 8 bytes after sin_addr + // it always sets family to AF_INET despite of ip version + char ifname[9]; + if (bIncoming && sa_from.ss_family==AF_INET) + { + const char *p = ((char*)&((struct sockaddr_in *)&sa_from)->sin_addr)+sizeof(struct in_addr); + if (*p) + { + memcpy(ifname, p, 8); + ifname[8] = 0; + ifin = ifname; + } + } +#endif + + DLOG("\npacket: id=%u len=%zu ifin=%s ifout=%s\n", id, len, ifin, ifout); + modlen = sizeof(buf); + verdict = processPacketData(&mark, NULL, NULL, buf, len, buf, &modlen); + switch (verdict & VERDICT_MASK) + { + case VERDICT_PASS: + case VERDICT_MODIFY: + if ((verdict & VERDICT_MASK) == VERDICT_PASS) + { + DLOG("packet: id=%u reinject unmodified\n", id); + modlen = len; + } + else + DLOG("packet: id=%u reinject modified len %zu => %zu\n", id, len, modlen); + wr = sendto(fd[i], buf, modlen, 0, (struct sockaddr*)&sa_from, socklen); + if (wr < 0) + DLOG_PERROR("reinject sendto"); + else if (wr != modlen) + 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]); + lua_shutdown(); + return res; +} + + +#elif defined (__CYGWIN__) + +static int win_main() +{ + size_t len, modlen; + unsigned int id; + uint8_t verdict; + bool bOutbound; + uint8_t packet[RECONSTRUCT_MAX_SIZE]; + uint32_t mark; + WINDIVERT_ADDRESS wa; + char ifname[IFNAMSIZ]; + int res=0; + + 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 (!lua_init()) + { + res=ERROR_INVALID_PARAMETER; goto ex; + } + + 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); + res=w_win32_error; goto ex; + } + + 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"); + goto ex; + } + usleep(500000); + } while (!logical_net_filter_match()); + DLOG_CONDUP("logical network now present\n"); + } + + if (!windivert_init(params.windivert_filter)) + { + res=w_win32_error; goto ex; + } + + 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"); + goto ex; + } + DLOG_ERR("windivert: recv failed. errno %d\n", errno); + res=w_win32_error; + goto ex; + } + + ReloadCheck(); + lua_do_gc(); + + *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 + modlen = sizeof(packet); + verdict = processPacketData(&mark, wa.Outbound ? "" : ifname, wa.Outbound ? ifname : "", packet, len, packet, &modlen); + } + switch (verdict & VERDICT_MASK) + { + case VERDICT_PASS: + DLOG("packet: id=%u reinject unmodified\n", id); + if (!windivert_send(packet, len, &wa)) + DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); + break; + case VERDICT_MODIFY: + DLOG("packet: id=%u reinject modified len %zu => %zu\n", id, len, modlen); + if (!windivert_send(packet, modlen, &wa)) + DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); + break; + default: + DLOG("packet: id=%u drop\n", id); + } + } + } +ex: + win_dark_deinit(); + lua_shutdown(); + return res; +} + +#endif // multiple OS divert handlers + + + + +static void exit_clean(int code) +{ + cleanup_params(¶ms); + exit(code); +} + + +static bool is_hexstring(const char *filename) +{ + return filename[0] == '0' && filename[1] == 'x'; +} +static bool parse_filespec(const char **filename, unsigned long long *ofs) +{ + *ofs = 0; + if (**filename == '+') + { + (*filename)++; + if (sscanf(*filename, "%llu", ofs) != 1) + { + DLOG("offset read error: %s\n", *filename); + return false; + } + while (**filename && **filename != '@') (*filename)++; + if (**filename == '@') (*filename)++; + } + else if (**filename == '@') + (*filename)++; + return true; +} + +static void load_file_or_exit(const char *filename, void *buf, size_t *size) +{ + unsigned long long ofs; + + // 0xaabbcc + // filename + // @filename + // +123@filename + + if (is_hexstring(filename)) + { + 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 + { + if (!parse_filespec(&filename, &ofs)) + exit_clean(1); + if (!load_file(filename, ofs, 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); + } +} + +static char* item_name(char **str) +{ + char *s,*p; + size_t l; + + l = (s = strchr(*str,':')) ? s-*str : strlen(*str); + if (!(p = malloc(l+1))) + { + DLOG_ERR("out of memory\n"); + return NULL; + } + memcpy(p,*str,l); + p[l]=0; + if (!is_identifier(p)) + { + DLOG_ERR("bad identifier '%s'\n",p); + free(p); + return NULL; + } + *str = s ? s+1 : *str+l; + return p; +} + +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; + char *name; + + if (!(name = item_name((char**)&filename))) + exit_clean(1); + + if (!is_hexstring(filename)) + { + const char *fn = filename; + unsigned long long ofs; + off_t fsize; + + if (!parse_filespec(&fn, &ofs)) + { + free(name); + exit_clean(1); + } + if (!file_size(fn,&fsize)) + { + free(name); + DLOG_ERR("cannot access file '%s'\n",fn); + exit_clean(1); + } + if (fsize) + { + if (ofs >= fsize) + { + free(name); + DLOG_ERR("offset %llu is beyond file size %llu\n", ofs, (uint64_t)fsize); + exit_clean(1); + } + max_size = fsize - ofs; + } + } + + if (blob_collection_search_name(blobs,name)) + { + DLOG_ERR("duplicate blob name '%s'\n",name); + free(name); + exit_clean(1); + } + if (!blob || (!(blob->data = malloc(max_size + size_reserve)))) + { + free(name); + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + blob->size = max_size; + blob->name = name; + load_file_or_exit(filename, blob->data, &blob->size); + + 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 char *name, const void *data, size_t sz, struct blob_collection_head *blobs, size_t size_reserve) +{ + if (blob_collection_search_name(blobs,name)) + { + DLOG_ERR("duplicate blob name '%s'\n",name); + exit_clean(1); + } + struct blob_item *blob = blob_collection_add(blobs); + if (!blob || (!(blob->data = malloc(sz + size_reserve))) || !(blob->name = strdup(name))) + { + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + blob->size = sz; + blob->size_buf = sz + size_reserve; + memcpy(blob->data, data, sz); + return blob; +} + + + + +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_l7_list(char *opt, uint64_t *l7) +{ + char *e, *p, c; + t_l7proto proto; + + for (p = opt, *l7 = 0; p; ) + { + if ((e = strchr(p, ','))) + { + c = *e; + *e = 0; + } + + if ((proto=l7proto_from_name(p))==L7_INVALID) + return false; + else if (proto==L7_ALL) + { + *l7 = 0; + break; + } + else + *l7 |= 1<ptr1 = strdup(opt); + if (p) + { + arg->ptr2 = strdup(p+1); + *p = c; + } + return arg->ptr1; +} + +struct func_list *parse_lua_call(char *opt, struct func_list_head *flist) +{ + char *name, *e, *p, c; + bool b; + struct func_list *f = NULL; + + if (!(name = item_name(&opt))) + return false; + + if (!is_identifier(name) || !(f=funclist_add_tail(flist,name))) + goto err; + + for (p = opt; p && *p; ) + { + if ((e = strchr(p, ':'))) + { + c = *e; + *e = 0; + } + + b = lua_call_param_add(p, &f->args); + if (e) *e++ = c; + if (!b) goto err; + + p = e; + } + free(name); + return f; +err: + free(name); + return NULL; + +} + +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_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_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 void BlobDebug() +{ + struct blob_item *blob; + LIST_FOREACH(blob, ¶ms.blobs, next) + { + DLOG("blob '%s' : size=%zu alloc=%zu\n",blob->name,blob->size,blob->size_buf); + } +} + +static void LuaDesyncDebug(struct desync_profile *dp) +{ + if (params.debug) + { + struct func_list *func; + struct ptr_list *arg; + int n,i; + LIST_FOREACH(func, &dp->lua_desync, next) + { + DLOG("profile %u lua %s(",dp->n,func->func); + n=0; + LIST_FOREACH(arg, &func->args, next) + { + if (n) DLOG(","); + DLOG(arg->ptr2 ? "%s=\"%s\"" : "%s=nil", (char*)arg->ptr1, (char*)arg->ptr2); + n++; + } + DLOG(" range_in=%c%u%c%c%u range_out=%c%u%c%c%u payload_type=", + func->range_in.from.mode,func->range_in.from.pos, + func->range_in.upper_cutoff ? '<' : '-', + func->range_in.to.mode,func->range_in.to.pos, + func->range_out.from.mode,func->range_out.from.pos, + func->range_out.upper_cutoff ? '<' : '-', + func->range_out.to.mode,func->range_out.to.pos); + if (func->payload_type) + { + for(i=0;ipayload_type & (1<=", 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_ALWAYS "(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, + bool bTcpEmpty, + const char *pf_tcp_src_out, const char *pf_tcp_dst_out, + const char *pf_tcp_src_in, const char *pf_tcp_dst_in, + const char *pf_udp_src_in, const char *pf_udp_dst_out, + const struct str_list_head *wf_raw_part, + bool bFilterOutLAN) +{ + struct str_list *wfpart; + bool bHaveTCP = *pf_tcp_src_in || *pf_tcp_dst_out; + + snprintf(wf, len, "%s", DIVERT_PROLOG); + if (IfIdx) + snprintf(wf + strlen(wf), len - strlen(wf), " and ifIdx=%u and subIfIdx=%u", IfIdx, SubIfIdx); + if (ipv4 ^ ipv6) + snprintf(wf + strlen(wf), len - strlen(wf), " and %s", ipv4 ? "ip" : "ipv6"); + if (bHaveTCP && !bTcpEmpty) + snprintf(wf + strlen(wf), len - strlen(wf), " and\n" DIVERT_TCP_NOT_EMPTY); + snprintf(wf + strlen(wf), len - strlen(wf), " and\n(\n false"); + + if (bHaveTCP) + { + if (dp_list_have_autohostlist(¶ms.desync_profiles)) + snprintf(wf + strlen(wf), len - strlen(wf), " or\n " DIVERT_HTTP_REDIRECT); + } + + if (!LIST_EMPTY(wf_raw_part)) + { + LIST_FOREACH(wfpart, wf_raw_part, next) + { + snprintf(wf + strlen(wf), len - strlen(wf), " or\n (\n%s\n )", wfpart->str); + } + } + + if (*pf_tcp_dst_out) + { + snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", pf_tcp_dst_out); + // always redirect opposite syn,fin,rst for conntrack + snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and " DIVERT_TCP_ALWAYS " and %s", pf_tcp_src_out); + } + if (*pf_tcp_src_in) + { + snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", pf_tcp_src_in); + // always redirect opposite syn,fin,rst for conntrack + snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and " DIVERT_TCP_ALWAYS " and %s", pf_tcp_dst_in); + } + if (*pf_udp_dst_out) snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", pf_udp_dst_out); + if (*pf_udp_src_in) snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", pf_udp_src_in); + + snprintf(wf + strlen(wf), len - strlen(wf), "\n)"); + + 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) +{ + char all_payloads[1024], all_protos[512]; + + *all_payloads=0; + for (t_l7payload pl=0 ; pl|$\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" + " --fwmark=\t\t\t\t\t; override fwmark for generated packets. default = 0x%08X (%u)\n" +#elif defined(SO_USER_COOKIE) + " --sockarg=\t\t\t\t\t; override sockarg (SO_USER_COOKIE) for generated packets. default = 0x%08X (%u)\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" + " --server=[0|1]\t\t\t\t\t\t; change multiple aspects of src/dst ip/port handling for incoming connections\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" + " --reasm-disable=[proto[,proto]]\t\t\t; disable reasm for these L7 payloads : tls_client_hello quic_initial . if no argument - disable all reasm.\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-in=[~]port1[-port2]\t\t\t\t; TCP in port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-udp-in=[~]port1[-port2]\t\t\t\t; UDP in port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-tcp-out=[~]port1[-port2]\t\t\t\t; TCP out port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-udp-out=[~]port1[-port2]\t\t\t\t; UDP out port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-tcp-empty=[0|1]\t\t\t\t\t; enable processing of empty tcp packets without flags SYN,RST,FIN (default : 0)\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 winws2 only if any of specified wifi SSIDs connected\n" + " --nlm-filter=net1[,net2,net3,...]\t\t\t; enable winws2 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 + "\nDESYNC ENGINE INIT:\n" + " --blob=:[+ofs]@|0xHEX\t\t; load blob to LUA var \n" + " --lua-init=@|\t\t\t; load LUA program from a file or string. if multiple parameters present order of execution is preserved.\n" + " --lua-gc=\t\t\t\t\t\t; forced garbage collection every N sec. default %u sec. triggers only when a packet arrives. 0 = disable.\n" + "\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=proto[,proto]\t\t\t\t; L6-L7 protocol filter : %s\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" + " --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 (global parameter)\n" + "\nLUA PACKET PASS MODE:\n" + " --payload=type[,type]\t\t\t\t\t; set payload types following LUA functions should process : %s\n" + " --out-range=[(n|a|d|s)](-|<)[(n|a|d|s)]\t; set outgoing packet range for following LUA functions. '-' - include end pos, '<' - not include. prefix meaning : n - packet number, d - data packet number, s - relative sequence, b - byte count, x - never, a - always\n" + " --in-range=[(n|a|d|s)](-|<)[(n|a|d|s)]\t; set incoming packet range for following LUA functions. '-' - include end pos, '<' - not include. prefix meaning : n - packet number, d - data packet number, s - relative sequence, b - byte count, x - never, a - always\n" + "\nLUA DESYNC ACTION:\n" + " --lua-desync=[:param1=val1[:param2=val2]]\t; call LUA function when packet received\n", +#if defined(__linux__) || defined(SO_USER_COOKIE) + DPI_DESYNC_FWMARK_DEFAULT,DPI_DESYNC_FWMARK_DEFAULT, +#endif + CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP, + IPCACHE_LIFETIME, + LUA_GC_INTERVAL, + all_protos, + HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, + all_payloads + ); + 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, 0, 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 + +static void check_dp(const struct desync_profile *dp) +{ +} + +static void ApplyDefaultBlobs(struct blob_collection_head *blobs) +{ + load_const_blob_to_collection("fake_default_tls",fake_tls_clienthello_default,sizeof(fake_tls_clienthello_default),blobs,BLOB_EXTRA_BYTES); + load_const_blob_to_collection("fake_default_http",fake_http_request_default,strlen(fake_http_request_default),blobs,0); + + uint8_t buf[620]; + memset(buf,0,sizeof(buf)); + buf[0]=0x40; + load_const_blob_to_collection("fake_default_quic",buf,620,blobs,0); +} + +#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, + IDX_BIND_FIX4, + IDX_BIND_FIX6, +#elif defined(BSD) + IDX_PORT, +#endif + IDX_DAEMON, + IDX_PIDFILE, +#ifndef __CYGWIN__ + IDX_USER, + IDX_UID, +#endif + IDX_CTRACK_TIMEOUTS, + IDX_CTRACK_DISABLE, + IDX_SERVER, + IDX_IPCACHE_LIFETIME, + IDX_IPCACHE_HOSTNAME, + IDX_REASM_DISABLE, +#ifdef __linux__ + IDX_FWMARK, +#elif defined(SO_USER_COOKIE) + IDX_SOCKARG, +#endif + + IDX_BLOB, + IDX_LUA_INIT, + IDX_LUA_GC, + + 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, + + IDX_PAYLOAD, + IDX_IN_RANGE, + IDX_OUT_RANGE, + IDX_LUA_DESYNC, + +#ifdef __CYGWIN__ + IDX_WF_IFACE, + IDX_WF_L3, + IDX_WF_TCP_IN, + IDX_WF_TCP_OUT, + IDX_WF_UDP_IN, + IDX_WF_UDP_OUT, + IDX_WF_TCP_EMPTY, + 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}, + [IDX_BIND_FIX4] = {"bind-fix4", no_argument, 0, 0}, + [IDX_BIND_FIX6] = {"bind-fix6", no_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_CTRACK_TIMEOUTS] = {"ctrack-timeouts", required_argument, 0, 0}, + [IDX_CTRACK_DISABLE] = {"ctrack-disable", optional_argument, 0, 0}, + [IDX_SERVER] = {"server", optional_argument, 0, 0}, + [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, + [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, + [IDX_REASM_DISABLE] = {"reasm-disable", optional_argument, 0, 0}, +#ifdef __linux__ + [IDX_FWMARK] = {"fwmark", required_argument, 0, 0}, +#elif defined(SO_USER_COOKIE) + [IDX_SOCKARG] = {"sockarg", required_argument, 0, 0}, +#endif + [IDX_BLOB] = {"blob", required_argument, 0, 0}, + [IDX_LUA_INIT] = {"lua-init", required_argument, 0, 0}, + [IDX_LUA_GC] = {"lua-gc", 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}, + + [IDX_PAYLOAD] = {"payload", required_argument, 0, 0}, + [IDX_IN_RANGE] = {"in-range", required_argument, 0, 0}, + [IDX_OUT_RANGE] = {"out-range", required_argument, 0, 0}, + [IDX_LUA_DESYNC] = {"lua-desync", required_argument, 0, 0}, + +#ifdef __CYGWIN__ + [IDX_WF_IFACE] = {"wf-iface", required_argument, 0, 0}, + [IDX_WF_L3] = {"wf-l3", required_argument, 0, 0}, + [IDX_WF_TCP_IN] = {"wf-tcp-in", required_argument, 0, 0}, + [IDX_WF_TCP_OUT] = {"wf-tcp-out", required_argument, 0, 0}, + [IDX_WF_UDP_IN] = {"wf-udp-in", required_argument, 0, 0}, + [IDX_WF_UDP_OUT] = {"wf-udp-out", required_argument, 0, 0}, + [IDX_WF_TCP_EMPTY] = {"wf-tcp-empty", optional_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) +{ + if (argc < 2) exithelp(); + + 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; + uint64_t payload_type=0; + struct packet_range range_in = PACKET_RANGE_NEVER, range_out = PACKET_RANGE_ALWAYS; +#ifdef __CYGWIN__ + char wf_save_file[256]; + bool wf_ipv4 = true, wf_ipv6 = true, wf_filter_lan = true, wf_tcp_empty = false; + unsigned int IfIdx = 0, SubIfIdx = 0; + unsigned int hash_wf_tcp_in = 0, hash_wf_udp_in = 0, hash_wf_tcp_out = 0, hash_wf_udp_out = 0, hash_wf_raw = 0, hash_wf_raw_part = 0, hash_ssid_filter = 0, hash_nlm_filter = 0; + *wf_save_file = 0; +#endif + + srandom(time(NULL)); + mask_from_preflen6_prepare(); + + PRINT_VER; + + memset(¶ms, 0, sizeof(params)); + + struct desync_profile_list *dpl; + struct desync_profile *dp; + unsigned 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; + params.lua_gc = LUA_GC_INTERVAL; + + LIST_INIT(¶ms.hostlists); + LIST_INIT(¶ms.ipsets); + LIST_INIT(¶ms.blobs); + LIST_INIT(¶ms.lua_init_scripts); + + ApplyDefaultBlobs(¶ms.blobs); + +#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 + +#ifdef __CYGWIN__ + params.windivert_filter = malloc(WINDIVERT_MAX); + if (!params.windivert_filter) + { + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + char **wdbufs[] = + {¶ms.wf_pf_tcp_src_in, ¶ms.wf_pf_tcp_dst_in, ¶ms.wf_pf_udp_src_in, ¶ms.wf_pf_udp_dst_in, + ¶ms.wf_pf_tcp_src_out, ¶ms.wf_pf_tcp_dst_out, ¶ms.wf_pf_udp_src_out, ¶ms.wf_pf_udp_dst_out}; + for (v=0 ; v<(sizeof(wdbufs)/sizeof(*wdbufs)) ; v++) + { + if (!(*wdbufs[v] = malloc(WINDIVERT_PORTFILTER_MAX))) + { + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + **wdbufs[v] = 0; + } +#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_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); + } + 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; + case IDX_BIND_FIX4: + params.bind_fix4 = true; + break; + case IDX_BIND_FIX6: + params.bind_fix6 = true; + 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_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_SERVER: + params.server = !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_REASM_DISABLE: + if (optarg) + { + if (!parse_l7p_list(optarg, ¶ms.reasm_payload_disable)) + { + DLOG_ERR("Invalid l7 protocol list : %s\n", optarg); + exit_clean(1); + } + } + else + params.reasm_payload_disable = 0xFFFFFFFFFFFFFFFF; + break; +#if defined(__linux__) + case IDX_FWMARK: +#elif defined(SO_USER_COOKIE) + case IDX_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_BLOB: + load_blob_to_collection(optarg, ¶ms.blobs, MAX_BLOB_SIZE, BLOB_EXTRA_BYTES); + break; + + case IDX_LUA_INIT: + if (!strlist_add_tail(¶ms.lua_init_scripts, optarg)) + { + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + break; + case IDX_LUA_GC: + params.lua_gc = atoi(optarg); + 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 = (uint8_t)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 = (uint8_t)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 = (uint8_t)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; + payload_type = 0; + range_in = PACKET_RANGE_NEVER; + range_out = PACKET_RANGE_ALWAYS; + 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; + + case IDX_PAYLOAD: + if (!parse_l7p_list(optarg, &payload_type)) + { + DLOG_ERR("Invalid payload filter : %s\n", optarg); + exit_clean(1); + } + break; + case IDX_OUT_RANGE: + if (!packet_range_parse(optarg, &range_out)) + { + DLOG_ERR("invalid packet range value : %s\n",optarg); + exit_clean(1); + } + break; + case IDX_IN_RANGE: + if (!packet_range_parse(optarg, &range_in)) + { + DLOG_ERR("invalid packet range value : %s\n",optarg); + exit_clean(1); + } + break; + + case IDX_LUA_DESYNC: + { + struct func_list *f; + if (!(f=parse_lua_call(optarg, &dp->lua_desync))) + { + DLOG_ERR("invalid lua function call : %s\n", optarg); + exit_clean(1); + } + f->payload_type = payload_type; + f->range_in = range_in; + f->range_out = range_out; + } + break; + +#ifdef __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_IN: + hash_wf_tcp_in = hash_jen(optarg, strlen(optarg)); + if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_in, WINDIVERT_PORTFILTER_MAX) || + !wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_in, WINDIVERT_PORTFILTER_MAX)) + { + DLOG_ERR("bad value for --wf-tcp-in\n"); + exit_clean(1); + } + break; + case IDX_WF_TCP_OUT: + hash_wf_tcp_out = hash_jen(optarg, strlen(optarg)); + if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_out, WINDIVERT_PORTFILTER_MAX) || + !wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_out, WINDIVERT_PORTFILTER_MAX)) + { + DLOG_ERR("bad value for --wf-tcp-out\n"); + exit_clean(1); + } + break; + case IDX_WF_UDP_IN: + hash_wf_udp_in = hash_jen(optarg, strlen(optarg)); + if (!wf_make_pf(optarg, "udp", "SrcPort", params.wf_pf_udp_src_in, WINDIVERT_PORTFILTER_MAX) || + !wf_make_pf(optarg, "udp", "DstPort", params.wf_pf_udp_dst_in, WINDIVERT_PORTFILTER_MAX)) + { + DLOG_ERR("bad value for --wf-udp-in\n"); + exit_clean(1); + } + break; + case IDX_WF_UDP_OUT: + hash_wf_udp_out = hash_jen(optarg, strlen(optarg)); + if (!wf_make_pf(optarg, "udp", "SrcPort", params.wf_pf_udp_src_out, WINDIVERT_PORTFILTER_MAX) || + !wf_make_pf(optarg, "udp", "DstPort", params.wf_pf_udp_dst_out, WINDIVERT_PORTFILTER_MAX)) + { + DLOG_ERR("bad value for --wf-udp-out\n"); + exit_clean(1); + } + break; + case IDX_WF_RAW: + hash_wf_raw = hash_jen(optarg, strlen(optarg)); + if (optarg[0] == '@') + { + size_t sz = WINDIVERT_MAX-1; + load_file_or_exit(optarg, params.windivert_filter, &sz); + params.windivert_filter[sz] = 0; + } + else + { + strncpy(params.windivert_filter, optarg, WINDIVERT_MAX); + params.windivert_filter[WINDIVERT_MAX - 1] = '\0'; + } + break; + case IDX_WF_TCP_EMPTY: + wf_tcp_empty = !optarg || atoi(optarg); + break; + case IDX_WF_RAW_PART: + hash_wf_raw_part ^= hash_jen(optarg, strlen(optarg)); + { + char *wfpart = malloc(WINDIVERT_MAX); + if (!wfpart) + { + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + if (optarg[0] == '@') + { + size_t sz = WINDIVERT_MAX - 1; + load_file_or_exit(optarg, wfpart, &sz); + wfpart[sz] = 0; + } + else + { + strncpy(wfpart, optarg, WINDIVERT_MAX); + wfpart[WINDIVERT_MAX - 1] = '\0'; + } + if (!strlist_add(¶ms.wf_raw_part, wfpart)) + { + free(wfpart); + DLOG_ERR("out of memory\n"); + exit_clean(1); + } + free(wfpart); + } + 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; + + if (params.server && dp->hostlist_auto) + { + DLOG_ERR("autohostlists not supported in server mode\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 + LuaDesyncDebug(dp); + } + + 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("\nblobs summary:\n"); + BlobDebug(); + DLOG("\n"); + +#ifdef __CYGWIN__ + if (!*params.windivert_filter) + { + if (!*params.wf_pf_tcp_src_in && !*params.wf_pf_udp_src_in && !*params.wf_pf_tcp_src_out && !*params.wf_pf_udp_src_out && LIST_EMPTY(¶ms.wf_raw_part)) + { + DLOG_ERR("windivert filter : must specify port or/and partial raw filter\n"); + exit_clean(1); + } + // exchange src/dst ports in server mode + bool b = params.server ? + wf_make_filter(params.windivert_filter, WINDIVERT_MAX, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, + wf_tcp_empty, + params.wf_pf_tcp_dst_out, params.wf_pf_tcp_src_out, + params.wf_pf_tcp_dst_in, params.wf_pf_tcp_src_in, + params.wf_pf_udp_dst_in, params.wf_pf_udp_src_out, + ¶ms.wf_raw_part, wf_filter_lan) : + wf_make_filter(params.windivert_filter, WINDIVERT_MAX, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, + wf_tcp_empty, + params.wf_pf_tcp_src_out, params.wf_pf_tcp_dst_out, + params.wf_pf_tcp_src_in, params.wf_pf_tcp_dst_in, + params.wf_pf_udp_src_in, params.wf_pf_udp_dst_out, + ¶ms.wf_raw_part, wf_filter_lan); + cleanup_windivert_portfilters(¶ms); + if (!b) + { + DLOG_ERR("windivert filter : could not make filter\n"); + exit_clean(1); + } + // free unneeded extra memory + char *p = realloc(params.windivert_filter, strlen(params.windivert_filter)+1); + if (p) params.windivert_filter=p; + } + DLOG("windivert filter size: %zu\nwindivert filter:\n%s\n", strlen(params.windivert_filter), params.windivert_filter); + if (*wf_save_file) + { + if (save_file(wf_save_file, params.windivert_filter, strlen(params.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\\winws2_arg_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u", hash_wf_tcp_in, hash_wf_udp_in, hash_wf_tcp_out, hash_wf_udp_out, 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 winws2 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); + } + 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(); +#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; +} diff --git a/nfq2/nfqws.h b/nfq2/nfqws.h new file mode 100644 index 0000000..745f695 --- /dev/null +++ b/nfq2/nfqws.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#ifdef __linux__ +#define HAS_FILTER_SSID 1 +#endif + +#ifdef __CYGWIN__ +extern bool bQuit; +#endif +int main(int argc, char *argv[]); diff --git a/nfq2/packet_queue.c b/nfq2/packet_queue.c new file mode 100644 index 0000000..950ec66 --- /dev/null +++ b/nfq2/packet_queue.c @@ -0,0 +1,71 @@ +#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_orig,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_orig = fwmark_orig; + 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); +} diff --git a/nfq2/packet_queue.h b/nfq2/packet_queue.h new file mode 100644 index 0000000..0db3d01 --- /dev/null +++ b/nfq2/packet_queue.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct rawpacket +{ + struct sockaddr_storage dst; + char ifin[IFNAMSIZ], ifout[IFNAMSIZ]; + uint32_t fwmark_orig; + 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_orig,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); diff --git a/nfq2/params.c b/nfq2/params.c new file mode 100644 index 0000000..055a37b --- /dev/null +++ b/nfq2/params.c @@ -0,0 +1,399 @@ +#include "params.h" + +#include +#include +#include +#ifdef __ANDROID__ +#include +#endif + +#include "pools.h" +#include "lua.h" + +#ifdef BSD +const char *progname = "dvtws2"; +#elif defined(__CYGWIN__) +const char *progname = "winws2"; +#elif defined(__linux__) +const char *progname = "nfqws2"; +#else +#error UNKNOWN_SYSTEM_TIME +#endif + +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 +}; + +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",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, 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); + LIST_INIT(&dp->lua_desync); + + 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; +} +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(¶ms.desync_profiles); + if (dpl) + { + while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; + LIST_INSERT_AFTER(dpl, entry, next); + } + else + LIST_INSERT_HEAD(¶ms.desync_profiles, 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); + port_filters_destroy(&dp->pf_udp); + funclist_destroy(&dp->lua_desync); +#ifdef HAS_FILTER_SSID + strlist_destroy(&dp->filter_ssid); +#endif + 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); + } +} +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; +} + +#if !defined( __OpenBSD__) && !defined(__ANDROID__) +void cleanup_args(struct params_s *params) +{ + wordfree(¶ms->wexp); +} +#endif + +#ifdef __CYGWIN__ +void cleanup_windivert_portfilters(struct params_s *params) +{ + char **wdbufs[] = + {¶ms->wf_pf_tcp_src_in, ¶ms->wf_pf_tcp_dst_in, ¶ms->wf_pf_udp_src_in, ¶ms->wf_pf_udp_dst_in, + ¶ms->wf_pf_tcp_src_out, ¶ms->wf_pf_tcp_dst_out, ¶ms->wf_pf_udp_src_out, ¶ms->wf_pf_udp_dst_out}; + for (int i=0 ; i<(sizeof(wdbufs)/sizeof(*wdbufs)) ; i++) + { + free(*wdbufs[i]); *wdbufs[i] = NULL; + } +} +#endif +void cleanup_params(struct params_s *params) +{ + lua_shutdown(); + +#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); + cleanup_windivert_portfilters(params); + free(params->windivert_filter); params->windivert_filter=NULL; +#else + free(params->user); params->user=NULL; +#endif +} diff --git a/nfq2/params.h b/nfq2/params.h new file mode 100644 index 0000000..35cc109 --- /dev/null +++ b/nfq2/params.h @@ -0,0 +1,184 @@ +#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 +#include +#if !defined( __OpenBSD__) && !defined(__ANDROID__) +#include +#endif + +#define TLS_PARTIALS_ENABLE true + +#define RAW_SNDBUF (64*1024) // in bytes + +#define Q_MAXLEN 1024 // in packets + +#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 MAX_GIDS 64 + +#define MAX_BLOB_SIZE (16*1024) +#define BLOB_EXTRA_BYTES 128 + +// this MSS is used for ipv6 in windows and linux +#define DEFAULT_MSS 1360 + +#define RECONSTRUCT_MAX_SIZE 16384 + +#define LUA_GC_INTERVAL 60 + +extern const char *tld[6]; +extern const char *fake_http_request_default; +extern const uint8_t fake_tls_clienthello_default[680]; + +enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG, LOG_TARGET_ANDROID }; + +struct desync_profile +{ + unsigned int n; // number of the profile + + bool filter_ipv4,filter_ipv6; + struct port_filters_head pf_tcp,pf_udp; + uint64_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 winws2 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; + + struct func_list_head lua_desync; +}; + +#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); +bool dp_list_have_autohostlist(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); + +#define WINDIVERT_MAX 65536 +#define WINDIVERT_PORTFILTER_MAX 4096 + +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 + bool 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; + + char *windivert_filter; + char *wf_pf_tcp_src_in, *wf_pf_tcp_dst_in, *wf_pf_udp_src_in, *wf_pf_udp_dst_in; + char *wf_pf_tcp_src_out, *wf_pf_tcp_dst_out, *wf_pf_udp_src_out, *wf_pf_udp_dst_out; +#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; + + // LUA var blobs + struct blob_collection_head blobs; + + unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp; + t_conntrack conntrack; + bool ctrack_disable, server; + +#ifdef HAS_FILTER_SSID + bool filter_ssid_present; +#endif + + bool cache_hostname; + unsigned int ipcache_lifetime; + ip_cache ipcache; + uint64_t reasm_payload_disable; + + struct str_list_head lua_init_scripts; + + int lua_gc; + lua_State *L; +}; + +extern struct params_s params; +extern const char *progname; +#if !defined( __OpenBSD__) && !defined(__ANDROID__) +void cleanup_args(struct params_s *params); +#endif +#ifdef __CYGWIN__ +void cleanup_windivert_portfilters(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); diff --git a/nfq2/pools.c b/nfq2/pools.c new file mode 100644 index 0000000..a9b382e --- /dev/null +++ b/nfq2/pools.c @@ -0,0 +1,956 @@ +#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) +{ + 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) + { + free(elem->str); + HASH_DEL(*pp, elem); + free(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); +} + + +static struct str_list *strlist_entry_alloc(const char *str) +{ + struct str_list *entry = malloc(sizeof(struct str_list)); + if (!entry) return false; + entry->str = strdup(str); + if (!entry->str) + { + free(entry); + return NULL; + } + return entry; +} + +bool strlist_add(struct str_list_head *head, const char *str) +{ + struct str_list *entry = strlist_entry_alloc(str); + if (!entry) return false; + LIST_INSERT_HEAD(head, entry, next); + return true; +} +bool strlist_add_tail(struct str_list_head *head, const char *str) +{ + struct str_list *entry = strlist_entry_alloc(str); + if (!entry) return false; + + // add to the tail + struct str_list *strn,*strl=LIST_FIRST(head); + if (strl) + { + while ((strn=LIST_NEXT(strl,next))) strl = strn; + LIST_INSERT_AFTER(strl, entry, next); + } + else + 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; +} + + +static struct ptr_list *ptrlist_entry_alloc() +{ + return (struct ptr_list*)calloc(1,sizeof(struct ptr_list)); +} + +struct ptr_list *ptrlist_add(struct ptr_list_head *head) +{ + struct ptr_list *entry = ptrlist_entry_alloc(); + if (!entry) return NULL; + LIST_INSERT_HEAD(head, entry, next); + return entry; +} +static void ptrlist_entry_destroy(struct ptr_list *entry) +{ + free(entry->ptr1); + free(entry->ptr2); + free(entry); +} +void ptrlist_destroy(struct ptr_list_head *head) +{ + struct ptr_list *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + ptrlist_entry_destroy(entry); + } +} + + +static struct func_list *funclist_entry_alloc(const char *func) +{ + struct func_list *entry = malloc(sizeof(struct func_list)); + if (!entry) return false; + entry->func = strdup(func); + if (!entry->func) + { + free(entry); + return NULL; + } + entry->payload_type = 0; + entry->range_in = entry->range_out = PACKET_RANGE_ALWAYS; + LIST_INIT(&entry->args); + return entry; +} + +struct func_list *funclist_add_tail(struct func_list_head *head, const char *func) +{ + struct func_list *entry = funclist_entry_alloc(func); + if (!entry) return NULL; + + // add to the tail + struct func_list *funcn,*funcl=LIST_FIRST(head); + if (funcl) + { + while ((funcn=LIST_NEXT(funcl,next))) funcl = funcn; + LIST_INSERT_AFTER(funcl, entry, next); + } + else + LIST_INSERT_HEAD(head, entry, next); + return entry; +} +static void funclist_entry_destroy(struct func_list *entry) +{ + free(entry->func); + ptrlist_destroy(&entry->args); + free(entry); +} +void funclist_destroy(struct func_list_head *head) +{ + struct func_list *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + funclist_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_destroy(struct blob_item *blob) +{ + if (blob) + { + free(blob->extra); + free(blob->name); + free(blob->data); + free(blob); + } +} +void blob_collection_destroy(struct blob_collection_head *head) +{ + struct blob_item *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + blob_destroy(entry); + } +} +bool blob_collection_empty(const struct blob_collection_head *head) +{ + return !LIST_FIRST(head); +} +struct blob_item *blob_collection_search_name(struct blob_collection_head *head, const char *name) +{ + struct blob_item *blob; + LIST_FOREACH(blob, head, next) + { + if (blob->name && !strcmp(blob->name,name)) + return blob; + } + return NULL; +} + + + +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->ttl = 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 : ttl %u hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->key.iface, ipc->data.ttl, 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 : ttl %u hostname=%s hostname_is_ip=%u now=last+%llu\n", s_ip, ipc->key.iface, ipc->data.ttl, 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; + } +} + diff --git a/nfq2/pools.h b/nfq2/pools.h new file mode 100644 index 0000000..eef397d --- /dev/null +++ b/nfq2/pools.h @@ -0,0 +1,232 @@ +#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 *str); +bool strlist_add_tail(struct str_list_head *head, const char *str); +void strlist_destroy(struct str_list_head *head); +bool strlist_search(const struct str_list_head *head, const char *str); + +struct ptr_list { + void *ptr1,*ptr2; + LIST_ENTRY(ptr_list) next; +}; +LIST_HEAD(ptr_list_head, ptr_list); + +struct ptr_list *ptrlist_add(struct ptr_list_head *head); +void ptrlist_destroy(struct ptr_list_head *head); + +struct func_list { + char *func; + uint64_t payload_type; + struct packet_range range_in, range_out; + struct ptr_list_head args; + LIST_ENTRY(func_list) next; +}; +LIST_HEAD(func_list_head, func_list); +struct func_list *funclist_add_tail(struct func_list_head *head, const char *func); +void funclist_destroy(struct func_list_head *head); + + +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 + char *name; // optional name for search + void *extra; // 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_destroy(struct blob_item *blob); +void blob_collection_destroy(struct blob_collection_head *head); +bool blob_collection_empty(const struct blob_collection_head *head); +struct blob_item *blob_collection_search_name(struct blob_collection_head *head, const char *name); + + +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 ttl; +} 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); diff --git a/nfq2/protocol.c b/nfq2/protocol.c new file mode 100644 index 0000000..60e9564 --- /dev/null +++ b/nfq2/protocol.c @@ -0,0 +1,1387 @@ +#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; +} + +static const char *l7proto_name[] = {"all","unknown","http","tls","quic","wireguard","dht","discord","stun","xmpp","dns"}; +const char *l7proto_str(t_l7proto l7) +{ + if (l7>=L7_LAST) return NULL; + return l7proto_name[l7]; +} +t_l7proto l7proto_from_name(const char *name) +{ + int idx = str_index(l7proto_name,sizeof(l7proto_name)/sizeof(*l7proto_name),name); + return idx<0 ? L7_INVALID : (t_l7proto)idx; +} +bool l7_proto_match(t_l7proto l7proto, uint64_t filter_l7) +{ + return !filter_l7 || (filter_l7 & (1<=L7P_LAST) return NULL; + return l7payload_name[l7]; +} +bool l7_payload_match(t_l7payload l7payload, uint64_t filter_l7p) +{ + return !filter_l7p || (filter_l7p & (1<=PM_LAST) return NULL; + return posmarker_names[posmarker]; +} +t_marker posmarker_from_name(const char *name) +{ + int idx = str_index(posmarker_names,sizeof(posmarker_names)/sizeof(*posmarker_names),name); + return idx<0 ? PM_INVALID : (t_marker)idx; +} +bool posmarker_parse(const char *s, struct proto_pos *m) +{ + if (parse_int16(s, &m->pos)) + { + m->marker = PM_ABS; + return true; + } + else + { + char c, sm[32]; + const char *p; + bool b; + + for (p = s; *p && *p != '+' && *p != '-'; p++); + if ((p-s)>=sizeof(sm)) return false; + memcpy(sm,s,p-s); + sm[p-s]=0; + m->marker = posmarker_from_name(sm); + if (m->marker==PM_INVALID) return false; + if (*p) + return parse_int16(p, &m->pos); + else + m->pos = 0; + } + return true; + +} +bool posmarker_list_parse(const char *s, struct proto_pos *m, int *mct) +{ + const char *p, *e, *smm; + int ct; + char sm[32]; + + if (!*s) + { + *mct = 0; + return true; + } + for (p=s, ct=0; ct<*mct; ct++) + { + if ((e=strchr(p,','))) + { + if ((e-p)>=sizeof(sm)) return false; + memcpy(sm,p,e-p); + sm[e-p]=0; + smm = sm; + } + else + smm = p; + if (!posmarker_parse(smm,m+ct)) return false; + if (!e) + { + *mct = ct+1; + return true; + } + p=e+1; + } + return false; +} + +static ssize_t CheckPos(size_t sz, ssize_t offset) +{ + return (offset>=0 && offsetmarker, sp->pos, data, sz); + case L7P_TLS_CLIENT_HELLO: + 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_l7payload l7payload, const struct proto_pos *marker, int marker_count, ssize_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; +} +ssize_t HttpPos(t_marker 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 (HttpFindHost((uint8_t **)&host,(uint8_t*)data,sz) && (host-data+7)>12)==((tlsver>>4)&0xF))) ? "GREASE" : "UNKNOWN"; + } +} + +size_t TLSHandshakeDataLen(const uint8_t *data) +{ + return pntoh24(data+1); // HandshakeProtocol length +} +size_t TLSHandshakeLen(const uint8_t *data) +{ + return TLSHandshakeDataLen(data)+4; +} +bool IsTLSHandshakeFull(const uint8_t *data, size_t len) +{ + return TLSHandshakeLen(data)<=len; +} +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 IsTLSHandshakeHello(const uint8_t *data, size_t len, uint8_t type, bool bPartialIsOK) +{ +// return len >= 1 && (type && data[0]==type || !type && (data[0]==0x01 || data[0]==0x02)) && (bPartialIsOK || IsTLSHandshakeFull(data,len)); + + return len >= 1 && (type && data[0]==type || !type && (data[0]==0x01 || data[0]==0x02)) && (bPartialIsOK || IsTLSHandshakeFull(data,len)); +} +bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) +{ + return IsTLSHandshakeHello(data,len,0x01,bPartialIsOK); +} +bool IsTLSHandshakeServerHello(const uint8_t *data, size_t len, bool bPartialIsOK) +{ + return IsTLSHandshakeHello(data,len,0x02,bPartialIsOK); +} +bool IsTLSClientHelloPartial(const uint8_t *data, size_t len) +{ + return IsTLSClientHello(data,len,true); +} +bool IsTLSServerHelloPartial(const uint8_t *data, size_t len) +{ + return IsTLSServerHello(data,len,true); +} + +bool IsTLSHello(const uint8_t *data, size_t len, uint8_t type, bool bPartialIsOK) +{ + return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && (bPartialIsOK || IsTLSRecordFull(data,len)) && IsTLSHandshakeHello(data+5,len-5,type,bPartialIsOK); +} +bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK) +{ + return IsTLSHello(data,len,0x01,bPartialIsOK); +} +bool IsTLSServerHello(const uint8_t *data, size_t len, bool bPartialIsOK) +{ + return IsTLSHello(data,len,0x02,bPartialIsOK); +} + + +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 + // + + // CLIENT + // u16 CipherSuitesLength + // + + // SERVER + // u16 CipherSuites + + // 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 += (data[0]==0x02 ? 0 : 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 + // + + // CLIENT + // u16 CipherSuitesLength + // + + // SERVER + // u16 CipherSuites + + // 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 (!IsTLSHello(data, len, 0, 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); +} + +ssize_t TLSPos(t_marker posmarker, int16_t pos, const uint8_t *data, size_t sz) +{ + size_t elen; + const uint8_t *ext, *p; + size_t offset,len; + + 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)) + return POS_NOT_FOUND; + offset = ext-data; + return HostPos(posmarker,pos,data,sz,offset,len); + } + } + return POS_NOT_FOUND; + case PM_EXT_LEN: + return TLSFindExtLen(data,sz,&offset) ? CheckPos(sz,offset+pos) : POS_NOT_FOUND; + default: + return AnyProtoPos(posmarker,pos,data,sz); + } +} + + + +bool TLSMod_parse_list(const char *modlist, struct fake_tls_mod *tls_mod) +{ + char opt[140], *val; + const char *p, *e; + size_t l; + + tls_mod->mod = 0; + *tls_mod->sni = 0; + for(p = modlist ; *p ;) + { + if (!(e = strchr(p, ','))) + e = p + strlen(p); + + l = e - p; + if (l >= sizeof(opt)) l=sizeof(opt)-1; + memcpy(opt, p, l); + opt[l] = 0; + + if ((val = strchr(opt, '='))) + *val++=0; + + if (!strcmp(opt, "rnd")) + tls_mod->mod |= FAKE_TLS_MOD_RND; + else if (!strcmp(opt, "rndsni")) + tls_mod->mod |= FAKE_TLS_MOD_RND_SNI; + else if (!strcmp(opt, "dupsid")) + tls_mod->mod |= FAKE_TLS_MOD_DUP_SID; + else if (!strcmp(opt, "padencap")) + tls_mod->mod |= FAKE_TLS_MOD_PADENCAP; + else if (!strcmp(opt, "sni")) + { + tls_mod->mod |= FAKE_TLS_MOD_SNI; + if (!val || !*val) return false; + strncpy(tls_mod->sni, val, sizeof(tls_mod->sni) - 1); + tls_mod->sni[sizeof(tls_mod->sni) - 1] = 0; + } + else if (strcmp(opt, "none")) + return false; + + p = e + !!*e; + } + + return true; +} + +// payload is related to received tls client hello +bool TLSMod(const struct fake_tls_mod *tls_mod, const uint8_t *payload, size_t payload_len, uint8_t *fake_tls, size_t *fake_tls_size, size_t fake_tls_buf_size) +{ + bool bRes = true; + const uint8_t *ext; + size_t extlen,slen,extlen_offset=0,padlen_offset=0; + + if (!IsTLSClientHello(fake_tls, *fake_tls_size, false) || (*fake_tls_size < (44 + fake_tls[43]))) // has session id ? + { + DLOG_ERR("cannot apply tls mod. tls structure invalid\n"); + return false; + } + if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI | FAKE_TLS_MOD_PADENCAP)) + { + if (!TLSFindExtLen(fake_tls, *fake_tls_size, &extlen_offset)) + { + DLOG_ERR("cannot apply tls mod.tls structure invalid\n"); + return false; + } + DLOG_ERR("tls extensions length offset : %zu\n", extlen_offset); + } + if (tls_mod->mod & (FAKE_TLS_MOD_RND_SNI | FAKE_TLS_MOD_SNI)) + { + if (!TLSFindExt(fake_tls, *fake_tls_size, 0, &ext, &extlen, false)) + { + DLOG_ERR("cannot apply tls mod. sni mod is set but tls does not have SNI\n"); + return false; + } + uint8_t *sniext = fake_tls + (ext - fake_tls); + if (!TLSAdvanceToHostInSNI(&ext, &extlen, &slen)) + { + DLOG_ERR("cannot apply tls mod. sni set but tls has invalid SNI structure\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("cannot apply sni tls mod. not enough space for new SNI\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 + extlen_offset, (uint16_t)(pntoh16(fake_tls + 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("change SNI : %s => %s size_delta=%zd\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("cannot apply rndsni tls mod. tls has zero sized SNI\n"); + bRes = false; + } + else + { + char *s1 = NULL, *s2 = NULL; + if (params.debug && (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("generated random SNI : %s -> %s\n",s1,s2); + } + free(s1); free(s2); + } + } + } + } + if (tls_mod->mod & FAKE_TLS_MOD_RND) + { + fill_random_bytes(fake_tls + 11, 32); // random + fill_random_bytes(fake_tls + 44, fake_tls[43]); // session id + DLOG("applied rnd tls mod\n"); + } + if (payload) + { + if (tls_mod->mod & FAKE_TLS_MOD_DUP_SID) + { + if (IsTLSClientHello(payload, payload_len, false)) + { + if (payload_len < 44) + { + DLOG("cannot apply dupsid tls mod. data payload is too short.\n"); + bRes = false; + } + else if (fake_tls[43] != payload[43]) + { + DLOG("cannot apply dupsid tls mod. fake and orig session id length mismatch : %u!=%u.\n", fake_tls[43], payload[43]); + bRes = false; + } + else if (payload_len < (44 + payload[43])) + { + DLOG("cannot apply dupsid tls mod. data payload is not valid.\n"); + bRes = false; + } + else + { + memcpy(fake_tls + 44, payload + 44, fake_tls[43]); // session id + DLOG("applied dupsid tls mod\n"); + } + } + else + { + DLOG_ERR("cannot apply dupsid tls mod. payload is not valid tls.\n"); + bRes = false; + } + } + 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("cannot apply padencap tls mod. tls padding ext is present but it's not at the end. padding ext offset %zu, padding ext size %zu, fake size %zu\n", ext - fake_tls, extlen, *fake_tls_size); + return false; + } + padlen_offset = ext - fake_tls - 2; + DLOG("tls padding ext is present, padding length offset %zu\n", padlen_offset); + } + else + { + if ((*fake_tls_size + 4) > fake_tls_buf_size) + { + DLOG_ERR("tls padding is absent and there's no space to add it\n"); + return false; + } + phton16(fake_tls + *fake_tls_size, 21); + *fake_tls_size += 2; + padlen_offset = *fake_tls_size; + phton16(fake_tls + *fake_tls_size, 0); + *fake_tls_size += 2; + phton16(fake_tls + extlen_offset, pntoh16(fake_tls + 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("tls padding is absent. added. padding length offset %zu\n", padlen_offset); + } + size_t sz_rec = pntoh16(fake_tls + 3) + payload_len; + size_t sz_handshake = pntoh24(fake_tls + 6) + payload_len; + size_t sz_ext = pntoh16(fake_tls + extlen_offset) + payload_len; + size_t sz_pad = pntoh16(fake_tls + padlen_offset) + payload_len; + if ((sz_rec & ~0xFFFF) || (sz_handshake & ~0xFFFFFF) || (sz_ext & ~0xFFFF) || (sz_pad & ~0xFFFF)) + { + DLOG("cannot apply padencap tls mod. length overflow.\n"); + bRes = false; + } + else + { + phton16(fake_tls + 3, (uint16_t)sz_rec); + phton24(fake_tls + 6, (uint32_t)sz_handshake); + phton16(fake_tls + extlen_offset, (uint16_t)sz_ext); + phton16(fake_tls + padlen_offset, (uint16_t)sz_pad); + DLOG("applied padencap tls mod. sizes increased by %zu bytes.\n", payload_len); + } + } + } + + return bRes; +} + + + + + + +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,true)) 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) { + 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 == 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; + size_t pn_offset; + pn_offset = 1 + 4 + 1 + data[5]; + if (pn_offset >= data_len) return false; + pn_offset += 1 + data[pn_offset]; + if ((pn_offset + tvb_get_size(data[pn_offset])) >= data_len) return false; + pn_offset += tvb_get_varint(data + pn_offset, &token_len); + pn_offset += token_len; + if ((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; + + gcm_initialize(); // initialize aes keygen tables + + 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); + + size_t cryptlen = payload_len - pkn_len - 16; + if (cryptlen > *clean_len) return false; + *clean_len = cryptlen; + const uint8_t *decrypt_begin = data + pn_offset + pkn_len; + + uint8_t atag[16],header[256]; + size_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,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 + if (zeropos < offset) + // make sure no uninitialized gaps exist in case of not full fragment coverage + memset(defrag_data+zeropos,0,offset-zeropos); + if ((offset+sz) > zeropos) + zeropos=offset+sz; + memcpy(defrag_data+offset,clean+pos,sz); + if ((offset+sz) > szmax) szmax = offset+sz; + + found=true; + pos+=sz; + + ranges[range].offset = offset; + ranges[range].len = sz; + range++; + } + } + if (found) + { + defrag[0] = 6; + defrag[1] = 0; // offset + // 2..9 - length 64 bit + // +10 - data start + phton64(defrag+2,szmax); + defrag[2] |= 0xC0; // 64 bit value + *defrag_len = (size_t)(szmax+10); + + qsort(ranges, range, sizeof(*ranges), cmp_range64); + + //for(i=0 ; i QUIC_MAX_CID_LENGTH) return false; + offset += 1 + data[offset]; + + // SCID + if (data[offset] > QUIC_MAX_CID_LENGTH) return false; + offset += 1 + data[offset]; + + // token length + offset += tvb_get_varint(data + offset, &sz); + offset += sz; + if (offset >= len) return false; + + // payload length + if ((offset + tvb_get_size(data[offset])) > len) return false; + tvb_get_varint(data + offset, &sz); + offset += sz; + if (offset > len) return false; + + // client hello cannot be too small. likely ACK + return sz>=96; +} + + + +bool IsXMPPStream(const uint8_t *data, size_t len) +{ + return len>=16 && !memcmp(data,"=36 && !memcmp(data,"=10 && !memcmp(data,"=9 && !memcmp(data,"=17 && !memcmp(data,"=12 && !(data[2] & 0xFE) && !memcmp(data+4,"\x00\x01\x00\x00\x00\x00",6) && pntoh16(data+10)<=3; +} +bool IsDNSResponse(const uint8_t *data, size_t len) +{ + // message is a response, type standard response, questions: 1, answers <= 100, authority <= 100, additional <= 100 + // RFC 1035 512 byte limit can be ignored by clients or servers + return len>=12 && (data[2] & 0xF8)==0x80 && pntoh16(data+4)==1 && pntoh16(data+6)<=100 && pntoh16(data+8)<=100 && pntoh16(data+10)<=100; +} +bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len) +{ + return len==148 && data[0]==1; +} +bool IsWireguardHandshakeResponse(const uint8_t *data, size_t len) +{ + return len==92 && data[0]==2; +} +bool IsWireguardHandshakeCookie(const uint8_t *data, size_t len) +{ + return len==64 && data[0]==3; +} +bool IsWireguardData(const uint8_t *data, size_t len) +{ + // 16 bytes wg header + min 20 bytes for ipv4 encrypted header + 16 byte auth tag + return len>=52 && data[0]==4; +} +bool IsWireguardKeepalive(const uint8_t *data, size_t len) +{ + return len==32 && data[0]==4; +} +bool IsDht(const uint8_t *data, size_t len) +{ + return len>=7 && data[0]=='d' && (data[1]=='1' || data[1]=='2') && data[2]==':' && 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 IsStunBindingRequest(const uint8_t *data, size_t len) +{ + return len>=20 && // header size + data[0]==0 && data[1]==1 && + (data[3]&0b11)==0 && // length must be a multiple of 4 + ntohl(*(uint32_t*)(&data[4]))==0x2112A442 && // magic cookie + ntohs(*(uint16_t*)(&data[2]))==len-20; +} diff --git a/nfq2/protocol.h b/nfq2/protocol.h new file mode 100644 index 0000000..bc8a18c --- /dev/null +++ b/nfq2/protocol.h @@ -0,0 +1,171 @@ +#pragma once + +#include +#include +#include +#include "crypto/sha.h" +#include "crypto/aes-gcm.h" +#include "helpers.h" + +typedef enum { + L7_ALL=0, + L7_UNKNOWN, + L7_HTTP, + L7_TLS, + L7_QUIC, + L7_WIREGUARD, + L7_DHT, + L7_DISCORD, + L7_STUN, + L7_XMPP, + L7_DNS, + L7_LAST, L7_INVALID=L7_LAST +} t_l7proto; +const char *l7proto_str(t_l7proto l7); +t_l7proto l7proto_from_name(const char *name); +bool l7_proto_match(t_l7proto l7proto, uint64_t filter_l7); + +typedef enum { + L7P_ALL=0, + L7P_UNKNOWN, + L7P_EMPTY, + L7P_HTTP_REQ, + L7P_HTTP_REPLY, + L7P_TLS_CLIENT_HELLO, + L7P_TLS_SERVER_HELLO, + L7P_QUIC_INITIAL, + L7P_WIREGUARD_INITIATION, + L7P_WIREGUARD_RESPONSE, + L7P_WIREGUARD_COOKIE, + L7P_WIREGUARD_KEEPALIVE, + L7P_WIREGUARD_DATA, + L7P_DHT, + L7P_DISCORD_IP_DISCOVERY, + L7P_STUN_BINDING_REQ, + L7P_XMPP_STREAM, + L7P_XMPP_STARTTLS, + L7P_XMPP_PROCEED, + L7P_XMPP_FEATURES, + L7P_DNS_QUERY, + L7P_DNS_RESPONSE, + L7P_LAST, L7P_INVALID=L7P_LAST +} t_l7payload; +t_l7payload l7payload_from_name(const char *name); +const char *l7payload_str(t_l7payload l7); +bool l7_payload_match(t_l7payload l7payload, uint64_t filter_l7p); + +typedef enum { + PM_ABS=0, + PM_HOST, + PM_HOST_END, + PM_HOST_SLD, + PM_HOST_MIDSLD, + PM_HOST_ENDSLD, + PM_HTTP_METHOD, + PM_EXT_LEN, + PM_SNI_EXT, + PM_LAST, PM_INVALID=PM_LAST +} t_marker; +struct proto_pos +{ + int16_t pos; + t_marker marker; +}; +#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) +const char *posmarker_name(t_marker posmarker); +t_marker posmarker_from_name(const char *name); +bool posmarker_parse(const char *s, struct proto_pos *m); +bool posmarker_list_parse(const char *s, struct proto_pos *m, int *mct); + +#define POS_NOT_FOUND ((ssize_t)-1) +ssize_t AnyProtoPos(t_marker posmarker, int16_t pos, const uint8_t *data, size_t sz); +ssize_t HttpPos(t_marker posmarker, int16_t pos, const uint8_t *data, size_t sz); +ssize_t TLSPos(t_marker posmarker, int16_t pos, const uint8_t *data, size_t sz); +ssize_t ResolvePos(const uint8_t *data, size_t sz, t_l7payload l7payload, const struct proto_pos *sp); +void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7payload l7payload, const struct proto_pos *marker, int marker_count, ssize_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 IsTLSHandshakeHello(const uint8_t *data, size_t len, uint8_t type, bool bPartialIsOK); +bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); +bool IsTLSHandshakeServerHello(const uint8_t *data, size_t len, bool bPartialIsOK); +bool IsTLSHello(const uint8_t *data, size_t len, uint8_t type, bool bPartialIsOK); +bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); +bool IsTLSServerHello(const uint8_t *data, size_t len, bool bPartialIsOK); +bool IsTLSClientHelloPartial(const uint8_t *data, size_t len); +bool IsTLSServerHelloPartial(const uint8_t *data, size_t len); +size_t TLSHandshakeLen(const uint8_t *data); +size_t TLSHandshakeDataLen(const uint8_t *data); +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); + +struct fake_tls_mod +{ + char sni[128]; + uint32_t mod; +}; +#define FAKE_TLS_MOD_RND 0x01 +#define FAKE_TLS_MOD_RND_SNI 0x02 +#define FAKE_TLS_MOD_SNI 0x04 +#define FAKE_TLS_MOD_DUP_SID 0x08 +#define FAKE_TLS_MOD_PADENCAP 0x10 + +bool TLSMod_parse_list(const char *modlist, struct fake_tls_mod *tls_mod); +bool TLSMod(const struct fake_tls_mod *tls_mod, const uint8_t *payload, size_t payload_len, uint8_t *fake_tls, size_t *fake_tls_size, size_t fake_tls_buf_size); + +bool IsXMPPStream(const uint8_t *data, size_t len); +bool IsXMPPStartTLS(const uint8_t *data, size_t len); +bool IsXMPPProceedTLS(const uint8_t *data, size_t len); +bool IsXMPPFeatures(const uint8_t *data, size_t len); + +bool IsDNSQuery(const uint8_t *data, size_t len); +bool IsDNSResponse(const uint8_t *data, size_t len); +bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len); +bool IsWireguardHandshakeResponse(const uint8_t *data, size_t len); +bool IsWireguardHandshakeCookie(const uint8_t *data, size_t len); +bool IsWireguardKeepalive(const uint8_t *data, size_t len); +bool IsWireguardData(const uint8_t *data, size_t len); +bool IsDht(const uint8_t *data, size_t len); +bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len); +bool IsStunBindingRequest(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); diff --git a/nfq2/sec.c b/nfq2/sec.c new file mode 100644 index 0000000..bf2f04f --- /dev/null +++ b/nfq2/sec.c @@ -0,0 +1,382 @@ +#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_osf_execve +SYS_osf_execve, +#endif +#ifdef SYS_uselib +SYS_uselib, +#endif +#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_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])) + +#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 + +#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); diff --git a/nfq2/uthash.h b/nfq2/uthash.h new file mode 100644 index 0000000..9a396b6 --- /dev/null +++ b/nfq2/uthash.h @@ -0,0 +1,1136 @@ +/* +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 */ diff --git a/nfq2/win.c b/nfq2/win.c new file mode 100644 index 0000000..1e2e10b --- /dev/null +++ b/nfq2/win.c @@ -0,0 +1,80 @@ +#ifdef __CYGWIN__ + +#include + +#include "win.h" +#include "nfqws.h" +#include "params.h" + +#define SERVICE_NAME "winws2" + +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 diff --git a/nfq2/win.h b/nfq2/win.h new file mode 100644 index 0000000..c319fa1 --- /dev/null +++ b/nfq2/win.h @@ -0,0 +1,9 @@ +#pragma once + +#ifdef __CYGWIN__ + +#include + +bool service_run(); + +#endif diff --git a/nfq2/windows/res/32/winicon.o b/nfq2/windows/res/32/winicon.o new file mode 100644 index 0000000..8b8eaf6 Binary files /dev/null and b/nfq2/windows/res/32/winicon.o differ diff --git a/nfq2/windows/res/32/winmanifest.o b/nfq2/windows/res/32/winmanifest.o new file mode 100644 index 0000000..0db470d Binary files /dev/null and b/nfq2/windows/res/32/winmanifest.o differ diff --git a/nfq2/windows/res/64/winicon.o b/nfq2/windows/res/64/winicon.o new file mode 100644 index 0000000..36180cb Binary files /dev/null and b/nfq2/windows/res/64/winicon.o differ diff --git a/nfq2/windows/res/64/winmanifest.o b/nfq2/windows/res/64/winmanifest.o new file mode 100644 index 0000000..0ca3b2f Binary files /dev/null and b/nfq2/windows/res/64/winmanifest.o differ diff --git a/nfq2/windows/windivert/libwindivert32.a b/nfq2/windows/windivert/libwindivert32.a new file mode 100644 index 0000000..7d15ae6 Binary files /dev/null and b/nfq2/windows/windivert/libwindivert32.a differ diff --git a/nfq2/windows/windivert/libwindivert64.a b/nfq2/windows/windivert/libwindivert64.a new file mode 100644 index 0000000..99f3d35 Binary files /dev/null and b/nfq2/windows/windivert/libwindivert64.a differ diff --git a/nfq2/windows/windivert/windivert.h b/nfq2/windows/windivert/windivert.h new file mode 100644 index 0000000..fc63adf --- /dev/null +++ b/nfq2/windows/windivert/windivert.h @@ -0,0 +1,630 @@ +/* + * 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 */