commit 2aaa2f7cf3c33b282727784738059d73279bc60f Author: bol-van Date: Mon Oct 28 09:32:24 2024 +0300 Truncated history diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..243d139b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf +binaries/win64/readme.txt eol=crlf +*.cmd eol=crlf +*.bat eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7be93ddb --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +config +ip2net/ip2net +mdig/mdig +nfq/nfqws +tpws/tpws +binaries/my/ +binaries/win64/zapret-winws/autohostlist.txt +init.d/**/custom +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 00000000..68145e01 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +DIRS := nfq tpws ip2net mdig +DIRS_MAC := tpws ip2net mdig +TGT := binaries/my + +all: clean + @mkdir -p "$(TGT)"; \ + for dir in $(DIRS); do \ + find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ + $(MAKE) -C "$$dir" || exit; \ + for exe in "$$dir/"*; do \ + if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ + mv -f "$$exe" "${TGT}" ; \ + ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ + fi \ + done \ + done + +bsd: clean + @mkdir -p "$(TGT)"; \ + for dir in $(DIRS); do \ + find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ + $(MAKE) -C "$$dir" bsd || exit; \ + for exe in "$$dir/"*; do \ + if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ + mv -f "$$exe" "${TGT}" ; \ + ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ + fi \ + done \ + done + +mac: clean + @mkdir -p "$(TGT)"; \ + for dir in $(DIRS_MAC); do \ + find "$$dir" -type f \( -name "*.c" -o -name "*.h" -o -name "*akefile" \) -exec chmod -x {} \; ; \ + $(MAKE) -C "$$dir" mac || exit; \ + for exe in "$$dir/"*; do \ + if [ -f "$$exe" ] && [ -x "$$exe" ]; then \ + mv -f "$$exe" "${TGT}" ; \ + ln -fs "../${TGT}/$$(basename "$$exe")" "$$exe" ; \ + fi \ + done \ + done + +clean: + @[ -d "$(TGT)" ] && rm -rf "$(TGT)" ; \ + for dir in $(DIRS); do \ + $(MAKE) -C "$$dir" clean; \ + done diff --git a/binaries/aarch64/ip2net b/binaries/aarch64/ip2net new file mode 100755 index 00000000..969be273 Binary files /dev/null and b/binaries/aarch64/ip2net differ diff --git a/binaries/aarch64/mdig b/binaries/aarch64/mdig new file mode 100755 index 00000000..71146071 Binary files /dev/null and b/binaries/aarch64/mdig differ diff --git a/binaries/aarch64/nfqws b/binaries/aarch64/nfqws new file mode 100755 index 00000000..ec9fcb9b Binary files /dev/null and b/binaries/aarch64/nfqws differ diff --git a/binaries/aarch64/tpws b/binaries/aarch64/tpws new file mode 100755 index 00000000..247a6bcb Binary files /dev/null and b/binaries/aarch64/tpws differ diff --git a/binaries/arm/ip2net b/binaries/arm/ip2net new file mode 100755 index 00000000..7ea6ca95 Binary files /dev/null and b/binaries/arm/ip2net differ diff --git a/binaries/arm/mdig b/binaries/arm/mdig new file mode 100755 index 00000000..c862932b Binary files /dev/null and b/binaries/arm/mdig differ diff --git a/binaries/arm/nfqws b/binaries/arm/nfqws new file mode 100755 index 00000000..d107cee8 Binary files /dev/null and b/binaries/arm/nfqws differ diff --git a/binaries/arm/tpws b/binaries/arm/tpws new file mode 100755 index 00000000..06ffc331 Binary files /dev/null and b/binaries/arm/tpws differ diff --git a/binaries/freebsd-x64/dvtws b/binaries/freebsd-x64/dvtws new file mode 100755 index 00000000..e7a83638 Binary files /dev/null and b/binaries/freebsd-x64/dvtws differ diff --git a/binaries/freebsd-x64/ip2net b/binaries/freebsd-x64/ip2net new file mode 100755 index 00000000..af755026 Binary files /dev/null and b/binaries/freebsd-x64/ip2net differ diff --git a/binaries/freebsd-x64/mdig b/binaries/freebsd-x64/mdig new file mode 100755 index 00000000..0b67ab77 Binary files /dev/null and b/binaries/freebsd-x64/mdig differ diff --git a/binaries/freebsd-x64/tpws b/binaries/freebsd-x64/tpws new file mode 100755 index 00000000..deb67060 Binary files /dev/null and b/binaries/freebsd-x64/tpws differ diff --git a/binaries/mac64/ip2net b/binaries/mac64/ip2net new file mode 100755 index 00000000..c127414a Binary files /dev/null and b/binaries/mac64/ip2net differ diff --git a/binaries/mac64/mdig b/binaries/mac64/mdig new file mode 100755 index 00000000..d4664309 Binary files /dev/null and b/binaries/mac64/mdig differ diff --git a/binaries/mac64/tpws b/binaries/mac64/tpws new file mode 100755 index 00000000..43060602 Binary files /dev/null and b/binaries/mac64/tpws differ diff --git a/binaries/mips32r1-lsb/ip2net b/binaries/mips32r1-lsb/ip2net new file mode 100755 index 00000000..e18eb627 Binary files /dev/null and b/binaries/mips32r1-lsb/ip2net differ diff --git a/binaries/mips32r1-lsb/mdig b/binaries/mips32r1-lsb/mdig new file mode 100755 index 00000000..c7bebc94 Binary files /dev/null and b/binaries/mips32r1-lsb/mdig differ diff --git a/binaries/mips32r1-lsb/nfqws b/binaries/mips32r1-lsb/nfqws new file mode 100755 index 00000000..b9527a43 Binary files /dev/null and b/binaries/mips32r1-lsb/nfqws differ diff --git a/binaries/mips32r1-lsb/tpws b/binaries/mips32r1-lsb/tpws new file mode 100755 index 00000000..130820e7 Binary files /dev/null and b/binaries/mips32r1-lsb/tpws differ diff --git a/binaries/mips32r1-msb/ip2net b/binaries/mips32r1-msb/ip2net new file mode 100755 index 00000000..e3dccad3 Binary files /dev/null and b/binaries/mips32r1-msb/ip2net differ diff --git a/binaries/mips32r1-msb/mdig b/binaries/mips32r1-msb/mdig new file mode 100755 index 00000000..482096a3 Binary files /dev/null and b/binaries/mips32r1-msb/mdig differ diff --git a/binaries/mips32r1-msb/nfqws b/binaries/mips32r1-msb/nfqws new file mode 100755 index 00000000..045362c1 Binary files /dev/null and b/binaries/mips32r1-msb/nfqws differ diff --git a/binaries/mips32r1-msb/tpws b/binaries/mips32r1-msb/tpws new file mode 100755 index 00000000..b95c9dad Binary files /dev/null and b/binaries/mips32r1-msb/tpws differ diff --git a/binaries/mips64r2-msb/ip2net b/binaries/mips64r2-msb/ip2net new file mode 100755 index 00000000..73f494dd Binary files /dev/null and b/binaries/mips64r2-msb/ip2net differ diff --git a/binaries/mips64r2-msb/mdig b/binaries/mips64r2-msb/mdig new file mode 100755 index 00000000..05e95f32 Binary files /dev/null and b/binaries/mips64r2-msb/mdig differ diff --git a/binaries/mips64r2-msb/nfqws b/binaries/mips64r2-msb/nfqws new file mode 100755 index 00000000..ebb21225 Binary files /dev/null and b/binaries/mips64r2-msb/nfqws differ diff --git a/binaries/mips64r2-msb/tpws b/binaries/mips64r2-msb/tpws new file mode 100755 index 00000000..f1940cd0 Binary files /dev/null and b/binaries/mips64r2-msb/tpws differ diff --git a/binaries/ppc/ip2net b/binaries/ppc/ip2net new file mode 100755 index 00000000..eeaab7a0 Binary files /dev/null and b/binaries/ppc/ip2net differ diff --git a/binaries/ppc/mdig b/binaries/ppc/mdig new file mode 100755 index 00000000..8a122383 Binary files /dev/null and b/binaries/ppc/mdig differ diff --git a/binaries/ppc/nfqws b/binaries/ppc/nfqws new file mode 100755 index 00000000..51219f9b Binary files /dev/null and b/binaries/ppc/nfqws differ diff --git a/binaries/ppc/tpws b/binaries/ppc/tpws new file mode 100755 index 00000000..12a3fdba Binary files /dev/null and b/binaries/ppc/tpws differ diff --git a/binaries/win64/WinDivert.dll b/binaries/win64/WinDivert.dll new file mode 100644 index 00000000..50ca8743 Binary files /dev/null and b/binaries/win64/WinDivert.dll differ diff --git a/binaries/win64/WinDivert64.sys b/binaries/win64/WinDivert64.sys new file mode 100644 index 00000000..218ccaf4 Binary files /dev/null and b/binaries/win64/WinDivert64.sys differ diff --git a/binaries/win64/ip2net.exe b/binaries/win64/ip2net.exe new file mode 100644 index 00000000..5796e9ca Binary files /dev/null and b/binaries/win64/ip2net.exe differ diff --git a/binaries/win64/mdig.exe b/binaries/win64/mdig.exe new file mode 100644 index 00000000..80067370 Binary files /dev/null and b/binaries/win64/mdig.exe differ diff --git a/binaries/win64/readme.txt b/binaries/win64/readme.txt new file mode 100644 index 00000000..3923bc64 --- /dev/null +++ b/binaries/win64/readme.txt @@ -0,0 +1,9 @@ +Standalone version in zapret-winws folder !! +From this folder winws can be started only from cygwin shell. + +Cygwin refuses to start winws if a copy of cygwin1.dll is present ! + +How to get win7 and winws compatible version of cygwin : + +curl -O https://www.cygwin.com/setup-x86_64.exe +setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 diff --git a/binaries/win64/winws.exe b/binaries/win64/winws.exe new file mode 100644 index 00000000..2c992210 Binary files /dev/null and b/binaries/win64/winws.exe differ diff --git a/binaries/win64/zapret-winws/WinDivert.dll b/binaries/win64/zapret-winws/WinDivert.dll new file mode 100644 index 00000000..50ca8743 Binary files /dev/null and b/binaries/win64/zapret-winws/WinDivert.dll differ diff --git a/binaries/win64/zapret-winws/WinDivert64.sys b/binaries/win64/zapret-winws/WinDivert64.sys new file mode 100644 index 00000000..218ccaf4 Binary files /dev/null and b/binaries/win64/zapret-winws/WinDivert64.sys differ diff --git a/binaries/win64/zapret-winws/cygwin1.dll b/binaries/win64/zapret-winws/cygwin1.dll new file mode 100644 index 00000000..97785e97 Binary files /dev/null and b/binaries/win64/zapret-winws/cygwin1.dll differ diff --git a/binaries/win64/zapret-winws/list-youtube.txt b/binaries/win64/zapret-winws/list-youtube.txt new file mode 100644 index 00000000..e37b0162 --- /dev/null +++ b/binaries/win64/zapret-winws/list-youtube.txt @@ -0,0 +1,3 @@ +googlevideo.com +youtubei.googleapis.com +i.ytimg.com diff --git a/binaries/win64/zapret-winws/preset_russia.cmd b/binaries/win64/zapret-winws/preset_russia.cmd new file mode 100644 index 00000000..5dba0370 --- /dev/null +++ b/binaries/win64/zapret-winws/preset_russia.cmd @@ -0,0 +1,7 @@ +start "zapret: http,https,quic" /min "%~dp0winws.exe" ^ +--wf-tcp=80,443 --wf-udp=443 ^ +--filter-udp=443 --hostlist="%~dp0list-youtube.txt" --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic="%~dp0quic_initial_www_google_com.bin" --new ^ +--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=11 --new ^ +--filter-tcp=80 --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig --new ^ +--filter-tcp=443 --hostlist="%~dp0list-youtube.txt" --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig --dpi-desync-fake-tls="%~dp0tls_clienthello_www_google_com.bin" --new ^ +--dpi-desync=fake,disorder2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig diff --git a/binaries/win64/zapret-winws/preset_russia_autohostlist.cmd b/binaries/win64/zapret-winws/preset_russia_autohostlist.cmd new file mode 100644 index 00000000..b6d3b740 --- /dev/null +++ b/binaries/win64/zapret-winws/preset_russia_autohostlist.cmd @@ -0,0 +1,7 @@ +start "zapret: http,https,quic" /min "%~dp0winws.exe" ^ +--wf-tcp=80,443 --wf-udp=443 ^ +--filter-udp=443 --hostlist="%~dp0list-youtube.txt" --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic="%~dp0quic_initial_www_google_com.bin" --new ^ +--filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=11 --new ^ +--filter-tcp=80 --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig --hostlist-auto="%~dp0autohostlist.txt" --new ^ +--filter-tcp=443 --hostlist="%~dp0list-youtube.txt" --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig --dpi-desync-fake-tls="%~dp0tls_clienthello_www_google_com.bin" --new ^ +--dpi-desync=fake,disorder2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig --hostlist-auto="%~dp0autohostlist.txt" diff --git a/binaries/win64/zapret-winws/quic_initial_www_google_com.bin b/binaries/win64/zapret-winws/quic_initial_www_google_com.bin new file mode 100644 index 00000000..80a07cc8 Binary files /dev/null and b/binaries/win64/zapret-winws/quic_initial_www_google_com.bin differ diff --git a/binaries/win64/zapret-winws/service_create.cmd b/binaries/win64/zapret-winws/service_create.cmd new file mode 100644 index 00000000..88b95783 --- /dev/null +++ b/binaries/win64/zapret-winws/service_create.cmd @@ -0,0 +1,12 @@ +set ARGS=--wf-l3=ipv4,ipv6 --wf-tcp=80,443 --dpi-desync=fake,split --dpi-desync-ttl=7 --dpi-desync-fooling=md5sig +call :srvinst winws1 +rem set ARGS=--wf-l3=ipv4,ipv6 --wf-udp=443 --dpi-desync=fake +rem call :srvinst winws2 +goto :eof + +:srvinst +net stop %1 +sc delete %1 +sc create %1 binPath= "\"%~dp0winws.exe\" %ARGS%" DisplayName= "zapret DPI bypass : %1" start= auto +sc description %1 "zapret DPI bypass software" +sc start %1 diff --git a/binaries/win64/zapret-winws/service_del.cmd b/binaries/win64/zapret-winws/service_del.cmd new file mode 100644 index 00000000..0475692a --- /dev/null +++ b/binaries/win64/zapret-winws/service_del.cmd @@ -0,0 +1,7 @@ +call :srvdel winws1 +rem call :srvdel winws2 +goto :eof + +:srvdel +net stop %1 +sc delete %1 diff --git a/binaries/win64/zapret-winws/service_start.cmd b/binaries/win64/zapret-winws/service_start.cmd new file mode 100644 index 00000000..ec13738d --- /dev/null +++ b/binaries/win64/zapret-winws/service_start.cmd @@ -0,0 +1,2 @@ +sc start winws1 +rem sc start winws2 diff --git a/binaries/win64/zapret-winws/service_stop.cmd b/binaries/win64/zapret-winws/service_stop.cmd new file mode 100644 index 00000000..443af875 --- /dev/null +++ b/binaries/win64/zapret-winws/service_stop.cmd @@ -0,0 +1,2 @@ +net stop winws1 +rem net stop winws2 diff --git a/binaries/win64/zapret-winws/task_create.cmd b/binaries/win64/zapret-winws/task_create.cmd new file mode 100644 index 00000000..43038813 --- /dev/null +++ b/binaries/win64/zapret-winws/task_create.cmd @@ -0,0 +1,4 @@ +set WINWS1=--wf-l3=ipv4,ipv6 --wf-tcp=80,443 --dpi-desync=fake,split --dpi-desync-ttl=7 --dpi-desync-fooling=md5sig +schtasks /Create /F /TN winws1 /NP /RU "" /SC onstart /TR "\"%~dp0winws.exe\" %WINWS1%" +rem set WINWS2=--wf-l3=ipv4,ipv6 --wf-udp=443 --dpi-desync=fake +rem schtasks /Create /F /TN winws2 /NP /RU "" /SC onstart /TR "\"%~dp0winws.exe\" %WINWS2%" diff --git a/binaries/win64/zapret-winws/task_remove.cmd b/binaries/win64/zapret-winws/task_remove.cmd new file mode 100644 index 00000000..b6779049 --- /dev/null +++ b/binaries/win64/zapret-winws/task_remove.cmd @@ -0,0 +1,4 @@ +schtasks /End /TN winws1 +schtasks /Delete /TN winws1 /F +rem schtasks /End /TN winws2 +rem schtasks /Delete /TN winws2 /F diff --git a/binaries/win64/zapret-winws/task_start.cmd b/binaries/win64/zapret-winws/task_start.cmd new file mode 100644 index 00000000..bf151bee --- /dev/null +++ b/binaries/win64/zapret-winws/task_start.cmd @@ -0,0 +1,2 @@ +schtasks /Run /TN winws1 +rem schtasks /Run /TN winws2 diff --git a/binaries/win64/zapret-winws/task_stop.cmd b/binaries/win64/zapret-winws/task_stop.cmd new file mode 100644 index 00000000..a267cb97 --- /dev/null +++ b/binaries/win64/zapret-winws/task_stop.cmd @@ -0,0 +1,2 @@ +schtasks /End /TN winws1 +rem schtasks /End /TN winws2 diff --git a/binaries/win64/zapret-winws/tls_clienthello_www_google_com.bin b/binaries/win64/zapret-winws/tls_clienthello_www_google_com.bin new file mode 100644 index 00000000..c740462f Binary files /dev/null and b/binaries/win64/zapret-winws/tls_clienthello_www_google_com.bin differ diff --git a/binaries/win64/zapret-winws/winws.exe b/binaries/win64/zapret-winws/winws.exe new file mode 100644 index 00000000..2c992210 Binary files /dev/null and b/binaries/win64/zapret-winws/winws.exe differ diff --git a/binaries/x86/ip2net b/binaries/x86/ip2net new file mode 100755 index 00000000..8f9f31fb Binary files /dev/null and b/binaries/x86/ip2net differ diff --git a/binaries/x86/mdig b/binaries/x86/mdig new file mode 100755 index 00000000..0f1f801d Binary files /dev/null and b/binaries/x86/mdig differ diff --git a/binaries/x86/nfqws b/binaries/x86/nfqws new file mode 100755 index 00000000..4f295694 Binary files /dev/null and b/binaries/x86/nfqws differ diff --git a/binaries/x86/tpws b/binaries/x86/tpws new file mode 100755 index 00000000..34bfb983 Binary files /dev/null and b/binaries/x86/tpws differ diff --git a/binaries/x86_64/ip2net b/binaries/x86_64/ip2net new file mode 100755 index 00000000..77a953e1 Binary files /dev/null and b/binaries/x86_64/ip2net differ diff --git a/binaries/x86_64/mdig b/binaries/x86_64/mdig new file mode 100755 index 00000000..382e4db0 Binary files /dev/null and b/binaries/x86_64/mdig differ diff --git a/binaries/x86_64/nfqws b/binaries/x86_64/nfqws new file mode 100755 index 00000000..51452ef5 Binary files /dev/null and b/binaries/x86_64/nfqws differ diff --git a/binaries/x86_64/tpws b/binaries/x86_64/tpws new file mode 100755 index 00000000..015f3b9a Binary files /dev/null and b/binaries/x86_64/tpws differ diff --git a/binaries/x86_64/tpws_wsl.tgz b/binaries/x86_64/tpws_wsl.tgz new file mode 100644 index 00000000..fe32b7b2 Binary files /dev/null and b/binaries/x86_64/tpws_wsl.tgz differ diff --git a/blockcheck.sh b/blockcheck.sh new file mode 100755 index 00000000..eb913362 --- /dev/null +++ b/blockcheck.sh @@ -0,0 +1,1868 @@ +#!/bin/sh + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" + +CURL=${CURL:-curl} + +[ -f "$ZAPRET_CONFIG" ] || { + [ -f "$ZAPRET_CONFIG_DEFAULT" ] && { + ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" + [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" + cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" + } +} +[ -f "$ZAPRET_CONFIG" ] && . "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/dialog.sh" +. "$ZAPRET_BASE/common/elevate.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/virt.sh" + +QNUM=${QNUM:-59780} +SOCKS_PORT=${SOCKS_PORT:-1993} +TPWS_UID=${TPWS_UID:-1} +TPWS_GID=${TPWS_GID:-3003} +NFQWS=${NFQWS:-${ZAPRET_BASE}/nfq/nfqws} +DVTWS=${DVTWS:-${ZAPRET_BASE}/nfq/dvtws} +WINWS=${WINWS:-${ZAPRET_BASE}/nfq/winws} +TPWS=${TPWS:-${ZAPRET_BASE}/tpws/tpws} +MDIG=${MDIG:-${ZAPRET_BASE}/mdig/mdig} +DESYNC_MARK=0x10000000 +IPFW_RULE_NUM=${IPFW_RULE_NUM:-1} +IPFW_DIVERT_PORT=${IPFW_DIVERT_PORT:-59780} +DOMAINS=${DOMAINS:-rutracker.org} +CURL_MAX_TIME=${CURL_MAX_TIME:-2} +CURL_MAX_TIME_QUIC=${CURL_MAX_TIME_QUIC:-$CURL_MAX_TIME} +MIN_TTL=${MIN_TTL:-1} +MAX_TTL=${MAX_TTL:-12} +USER_AGENT=${USER_AGENT:-Mozilla} +HTTP_PORT=${HTTP_PORT:-80} +HTTPS_PORT=${HTTPS_PORT:-443} +QUIC_PORT=${QUIC_PORT:-443} +UNBLOCKED_DOM=${UNBLOCKED_DOM:-iana.org} +[ "$CURL_VERBOSE" = 1 ] && CURL_CMD=1 + +HDRTEMP=/tmp/zapret-hdr.txt + +NFT_TABLE=blockcheck + +DNSCHECK_DNS=${DNSCHECK_DNS:-8.8.8.8 1.1.1.1 77.88.8.1} +DNSCHECK_DOM=${DNSCHECK_DOM:-pornhub.com ntc.party rutracker.org www.torproject.org bbc.com} +DNSCHECK_DIG1=/tmp/dig1.txt +DNSCHECK_DIG2=/tmp/dig2.txt +DNSCHECK_DIGS=/tmp/digs.txt + +unset PF_STATUS +PF_RULES_SAVE=/tmp/pf-zapret-save.conf + +unset ALL_PROXY + +killwait() +{ + # $1 - signal (-9, -2, ...) + # $2 - pid + kill $1 $2 + # suppress job kill message + wait $2 2>/dev/null +} + +exitp() +{ + local A + + echo + echo press enter to continue + read A + exit $1 +} + +pf_is_avail() +{ + [ -c /dev/pf ] +} +pf_status() +{ + pfctl -qsi | sed -nre "s/^Status: ([^ ]+).*$/\1/p" +} +pf_is_enabled() +{ + [ "$(pf_status)" = Enabled ] +} +pf_save() +{ + PF_STATUS=0 + pf_is_enabled && PF_STATUS=1 + [ "$UNAME" = "OpenBSD" ] && pfctl -sr >"$PF_RULES_SAVE" +} +pf_restore() +{ + [ -n "$PF_STATUS" ] || return + case "$UNAME" in + OpenBSD) + if [ -f "$PF_RULES_SAVE" ]; then + pfctl -qf "$PF_RULES_SAVE" + else + echo | pfctl -qf - + fi + ;; + Darwin) + # it's not possible to save all rules in the right order. hard to reorder. if not ordered pf will refuse to load conf. + pfctl -qf /etc/pf.conf + ;; + esac + if [ "$PF_STATUS" = 1 ]; then + pfctl -qe + else + pfctl -qd + fi +} +pf_clean() +{ + rm -f "$PF_RULES_SAVE" +} +opf_dvtws_anchor() +{ + # $1 - tcp/udp + # $2 - port + local family=inet + [ "$IPV" = 6 ] && family=inet6 + echo "set reassemble no" + [ "$1" = tcp ] && echo "pass in quick $family proto $1 from port $2 flags SA/SA divert-packet port $IPFW_DIVERT_PORT no state" + echo "pass in quick $family proto $1 from port $2 no state" + echo "pass out quick $family proto $1 to port $2 divert-packet port $IPFW_DIVERT_PORT no state" + echo "pass" +} +opf_prepare_dvtws() +{ + # $1 - tcp/udp + # $2 - port + opf_dvtws_anchor $1 $2 | pfctl -qf - + pfctl -qe +} + +cleanup() +{ + case "$UNAME" in + OpenBSD) + pf_clean + ;; + esac +} + +IPT() +{ + $IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@" +} +IPT_DEL() +{ + $IPTABLES -C "$@" >/dev/null 2>/dev/null && $IPTABLES -D "$@" +} +IPT_ADD_DEL() +{ + on_off_function IPT IPT_DEL "$@" +} +IPFW_ADD() +{ + ipfw -qf add $IPFW_RULE_NUM "$@" +} +IPFW_DEL() +{ + ipfw -qf delete $IPFW_RULE_NUM 2>/dev/null +} +ipt6_has_raw() +{ + ip6tables -nL -t raw >/dev/null 2>/dev/null +} +ipt6_has_frag() +{ + ip6tables -A OUTPUT -m frag 2>/dev/null || return 1 + ip6tables -D OUTPUT -m frag 2>/dev/null +} +ipt_has_nfq() +{ + # cannot just check /proc/net/ip_tables_targets because of iptables-nft or modules not loaded yet + iptables -A OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null || return 1 + iptables -D OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null + return 0 +} +nft_has_nfq() +{ + local res=1 + nft delete table ${NFT_TABLE}_test 2>/dev/null + nft add table ${NFT_TABLE}_test 2>/dev/null && { + nft add chain ${NFT_TABLE}_test test + nft add rule ${NFT_TABLE}_test test queue num $QNUM bypass 2>/dev/null && res=0 + nft delete table ${NFT_TABLE}_test + } + return $res +} +mdig_vars() +{ + # $1 - ip version 4/6 + # $2 - hostname + + hostvar=$(echo $2 | sed -e 's/[\.-]/_/g') + cachevar=DNSCACHE_${hostvar}_$1 + countvar=${cachevar}_COUNT + eval count=\$${countvar} +} +mdig_cache() +{ + # $1 - ip version 4/6 + # $2 - hostname + local hostvar cachevar countvar count ip ips + mdig_vars "$@" + [ -n "$count" ] || { + # windows version of mdig outputs 0D0A line ending. remove 0D. + ips="$(echo $2 | "$MDIG" --family=$1 | tr -d '\r' | xargs)" + [ -n "$ips" ] || return 1 + count=0 + for ip in $ips; do + eval ${cachevar}_$count=$ip + count=$(($count+1)) + done + eval $countvar=$count + } + return 0 +} +mdig_resolve() +{ + # $1 - ip version 4/6 + # $2 - hostname + + local hostvar cachevar countvar count ip n + mdig_vars "$@" + if [ -n "$count" ]; then + n=$(random 0 $(($count-1))) + eval ip=\$${cachevar}_$n + echo $ip + return 0 + else + mdig_cache "$@" && mdig_resolve "$@" + fi +} +mdig_resolve_all() +{ + # $1 - ip version 4/6 + # $2 - hostname + + local hostvar cachevar countvar count ip ips n + mdig_vars "$@" + if [ -n "$count" ]; then + n=0 + while [ "$n" -le $count ]; do + eval ip=\$${cachevar}_$n + if [ -n "$ips" ]; then + ips="$ips $ip" + else + ips="$ip" + fi + n=$(($n + 1)) + done + echo "$ips" + return 0 + else + mdig_cache "$@" && mdig_resolve_all "$@" + fi +} + +netcat_setup() +{ + [ -n "$NCAT" ] || { + if exists ncat; then + NCAT=ncat + elif exists nc; then + # busybox netcat does not support any required options + is_linked_to_busybox nc && return 1 + NCAT=nc + else + return 1 + fi + } + return 0 + +} +netcat_test() +{ + # $1 - ip + # $2 - port + local cmd + netcat_setup && { + cmd="$NCAT -z -w 2 $1 $2" + echo $cmd + $cmd 2>&1 + } +} + +check_system() +{ + echo \* checking system + + UNAME=$(uname) + SUBSYS= + local s + + # can be passed FWTYPE=iptables to override default nftables preference + case "$UNAME" in + Linux) + PKTWS="$NFQWS" + PKTWSD=nfqws + linux_fwtype + [ "$FWTYPE" = iptables -o "$FWTYPE" = nftables ] || { + echo firewall type $FWTYPE not supported in $UNAME + exitp 5 + } + ;; + FreeBSD) + PKTWS="$DVTWS" + PKTWSD=dvtws + FWTYPE=ipfw + [ -f /etc/platform ] && read SUBSYS /dev/null + sysctl net.inet.ip.pfil.inbound=ipfw,pf 2>/dev/null + sysctl net.inet6.ip6.pfil.outbound=ipfw,pf 2>/dev/null + sysctl net.inet6.ip6.pfil.inbound=ipfw,pf 2>/dev/null + pfctl -qd + pfctl -qe + pf_restore + } + } + ;; + OpenBSD|Darwin) + progs="$progs pfctl" + pf_is_avail || { + echo pf is not available + exitp 6 + } + # no divert sockets in MacOS + [ "$UNAME" = "Darwin" ] && SKIP_PKTWS=1 + pf_save + ;; + CYGWIN) + SKIP_TPWS=1 + ;; + esac + + for prog in $progs; do + exists $prog || { + echo $prog does not exist. please install + exitp 6 + } + done + + if exists nslookup; then + LOOKUP=nslookup + elif exists host; then + LOOKUP=host + else + echo nslookup or host does not exist. please install + exitp 6 + fi +} + + +curl_translate_code() +{ + # $1 - code + printf $1 + case $1 in + 0) printf ": ok" + ;; + 1) printf ": unsupported protocol" + ;; + 2) printf ": early initialization code failed" + ;; + 3) printf ": the URL was not properly formatted" + ;; + 4) printf ": feature not supported by libcurl" + ;; + 5) printf ": could not resolve proxy" + ;; + 6) printf ": could not resolve host" + ;; + 7) printf ": could not connect" + ;; + 8) printf ": invalid server reply" + ;; + 9) printf ": remote access denied" + ;; + 27) printf ": out of memory" + ;; + 28) printf ": operation timed out" + ;; + 35) printf ": SSL connect error" + ;; + esac +} +curl_supports_tls13() +{ + local r + $CURL --tlsv1.3 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + # return code 2 = init failed. likely bad command line options + [ $? = 2 ] && return 1 + # curl can have tlsv1.3 key present but ssl library without TLS 1.3 support + # this is online test because there's no other way to trigger library incompatibility case + $CURL --tlsv1.3 --max-time $CURL_MAX_TIME -Is -o /dev/null https://w3.org 2>/dev/null + r=$? + [ $r != 4 -a $r != 35 ] +} + +curl_supports_tlsmax() +{ + # supported only in OpenSSL and LibreSSL + $CURL --version | grep -Fq -e OpenSSL -e LibreSSL -e BoringSSL -e GnuTLS -e quictls || return 1 + # supported since curl 7.54 + $CURL --tls-max 1.2 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + # return code 2 = init failed. likely bad command line options + [ $? != 2 ] +} + +curl_supports_connect_to() +{ + $CURL --connect-to 127.0.0.1:: -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + [ "$?" != 2 ] +} + +curl_supports_http3() +{ + # if it has http3 : curl: (3) HTTP/3 requested for non-HTTPS URL + # otherwise : curl: (2) option --http3-only: is unknown + $CURL --connect-to 127.0.0.1:: -o /dev/null --max-time 1 --http3-only http://127.0.0.1:65535 2>/dev/null + [ "$?" != 2 ] +} + +hdrfile_http_code() +{ + # $1 - hdr file + sed -nre '1,1 s/^HTTP\/1\.[0,1] ([0-9]+) .*$/\1/p' "$1" +} +hdrfile_location() +{ + # $1 - hdr file + + # some DPIs return CRLF line ending + tr -d '\015' <"$1" | sed -nre 's/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ ]*([^ ]*)[ ]*$/\1/p' +} + +curl_with_subst_ip() +{ + # $1 - domain + # $2 - port + # $3 - ip + # $4+ - curl params + local connect_to="--connect-to $1::[$3]${2:+:$2}" arg + shift ; shift ; shift + [ "$CURL_VERBOSE" = 1 ] && arg="-v" + [ "$CURL_CMD" = 1 ] && echo $CURL ${arg:+$arg }$connect_to "$@" + ALL_PROXY="$ALL_PROXY" $CURL ${arg:+$arg }$connect_to "$@" +} +curl_with_dig() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - port + # $4+ - curl params + local dom=$2 port=$3 + local ip=$(mdig_resolve $1 $dom) + shift ; shift ; shift + if [ -n "$ip" ]; then + curl_with_subst_ip $dom $port $ip "$@" + else + return 6 + fi +} +curl_probe() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - port + # $4 - subst ip + # $5+ - curl params + local ipv=$1 dom=$2 port=$3 subst=$4 + shift; shift; shift; shift + if [ -n "$subst" ]; then + curl_with_subst_ip $dom $port $subst "$@" + else + curl_with_dig $ipv $dom $port "$@" + fi +} +curl_test_http() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + # $4 - "detail" - detail info + + local code loc + curl_probe $1 $2 $HTTP_PORT "$3" -SsD "$HDRTEMP" -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT "http://$2" -o /dev/null 2>&1 || { + code=$? + rm -f "$HDRTEMP" + return $code + } + if [ "$4" = "detail" ] ; then + head -n 1 "$HDRTEMP" + grep "^[lL]ocation:" "$HDRTEMP" + else + code=$(hdrfile_http_code "$HDRTEMP") + [ "$code" = 301 -o "$code" = 302 -o "$code" = 307 -o "$code" = 308 ] && { + loc=$(hdrfile_location "$HDRTEMP") + echo "$loc" | grep -qE "^https?://.*$2(/|$)" || + echo "$loc" | grep -vqE '^https?://' || { + echo suspicious redirection $code to : $loc + rm -f "$HDRTEMP" + return 254 + } + } + fi + rm -f "$HDRTEMP" + [ "$code" = 400 ] && { + # this can often happen if the server receives fake packets it should not receive + echo http code $code. likely the server receives fakes. + return 254 + } + return 0 +} +curl_test_https_tls12() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + + # do not use tls 1.3 to make sure server certificate is not encrypted + curl_probe $1 $2 $HTTPS_PORT "$3" -ISs -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.2 $TLSMAX12 "https://$2" -o /dev/null 2>&1 +} +curl_test_https_tls13() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + + # force TLS1.3 mode + curl_probe $1 $2 $HTTPS_PORT "$3" -ISs -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.3 $TLSMAX13 "https://$2" -o /dev/null 2>&1 +} + +curl_test_http3() +{ + # $1 - ip version : 4/6 + # $2 - domain name + + # force QUIC only mode without tcp + curl_with_dig $1 $2 $QUIC_PORT -ISs -A "$USER_AGENT" --max-time $CURL_MAX_TIME_QUIC --http3-only $CURL_OPT "https://$2" -o /dev/null 2>&1 +} + +ipt_scheme() +{ + # $1 - 1 - add , 0 - del + # $2 - tcp/udp + # $3 - port + + IPT_ADD_DEL $1 OUTPUT -t mangle -p $2 --dport $3 -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK -j NFQUEUE --queue-num $QNUM + # to avoid possible INVALID state drop + [ "$2" = tcp ] && IPT_ADD_DEL $1 INPUT -p $2 --sport $3 ! --syn -j ACCEPT + # for strategies with incoming packets involved (autottl) + IPT_ADD_DEL $1 OUTPUT -p $2 --dport $3 -m conntrack --ctstate INVALID -j ACCEPT + if [ "$IPV" = 6 -a -n "$IP6_DEFRAG_DISABLE" ]; then + # the only way to reliable disable ipv6 defrag. works only in 4.16+ kernels + IPT_ADD_DEL $1 OUTPUT -t raw -p $2 -m frag -j CT --notrack + elif [ "$IPV" = 4 ]; then + # enable fragments + IPT_ADD_DEL $1 OUTPUT -f -j ACCEPT + fi + # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) + # raw table may not be present + IPT_ADD_DEL $1 OUTPUT -t raw -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CT --notrack +} +nft_scheme() +{ + # $1 - tcp/udp + # $2 - port + nft add table inet $NFT_TABLE + nft "add chain inet $NFT_TABLE postnat { type filter hook output priority 102; }" + nft "add rule inet $NFT_TABLE postnat meta nfproto ipv${IPV} $1 dport $2 mark and $DESYNC_MARK != $DESYNC_MARK queue num $QNUM" + # for strategies with incoming packets involved (autottl) + nft "add chain inet $NFT_TABLE prenat { type filter hook prerouting priority -102; }" + # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) + nft "add chain inet $NFT_TABLE predefrag { type filter hook output priority -402; }" + nft "add rule inet $NFT_TABLE predefrag meta nfproto ipv${IPV} mark and $DESYNC_MARK !=0 notrack" +} + +pktws_ipt_prepare() +{ + # $1 - tcp/udp + # $2 - port + case "$FWTYPE" in + iptables) + ipt_scheme 1 $1 $2 + ;; + nftables) + nft_scheme $1 $2 + ;; + ipfw) + # disable PF to avoid interferences + pf_is_avail && pfctl -qd + IPFW_ADD divert $IPFW_DIVERT_PORT $1 from me to any $2 proto ip${IPV} out not diverted not sockarg + ;; + opf) + opf_prepare_dvtws $1 $2 + ;; + windivert) + WF="--wf-l3=ipv${IPV} --wf-${1}=$2" + ;; + + esac +} +pktws_ipt_unprepare() +{ + # $1 - tcp/udp + # $2 - port + case "$FWTYPE" in + iptables) + ipt_scheme 0 $1 $2 + ;; + nftables) + nft delete table inet $NFT_TABLE 2>/dev/null + ;; + ipfw) + IPFW_DEL + pf_is_avail && pf_restore + ;; + opf) + pf_restore + ;; + windivert) + unset WF + ;; + esac +} + +pktws_ipt_prepare_tcp() +{ + # $1 - port + + pktws_ipt_prepare tcp $1 + + case "$FWTYPE" in + iptables) + # for autottl + IPT INPUT -t mangle -p tcp --sport $1 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:1 -j NFQUEUE --queue-num $QNUM + ;; + nftables) + # for autottl + nft "add rule inet $NFT_TABLE prenat meta nfproto ipv${IPV} tcp sport $1 ct original packets 1 queue num $QNUM" + ;; + ipfw) + # for autottl mode + IPFW_ADD divert $IPFW_DIVERT_PORT tcp from any $1 to me proto ip${IPV} tcpflags syn,ack in not diverted not sockarg + ;; + esac +} +pktws_ipt_unprepare_tcp() +{ + # $1 - port + + pktws_ipt_unprepare tcp $1 + + case "$FWTYPE" in + iptables) + IPT_DEL INPUT -t mangle -p tcp --sport $1 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:1 -j NFQUEUE --queue-num $QNUM + ;; + esac +} +pktws_ipt_prepare_udp() +{ + # $1 - port + + pktws_ipt_prepare udp $1 +} +pktws_ipt_unprepare_udp() +{ + # $1 - port + + pktws_ipt_unprepare udp $1 +} + +pktws_start() +{ + case "$UNAME" in + Linux) + "$NFQWS" --uid $TPWS_UID:$TPWS_GID --dpi-desync-fwmark=$DESYNC_MARK --qnum=$QNUM "$@" >/dev/null & + ;; + FreeBSD|OpenBSD) + "$DVTWS" --port=$IPFW_DIVERT_PORT "$@" >/dev/null & + ;; + CYGWIN) + "$WINWS" $WF "$@" >/dev/null & + ;; + esac + PID=$! + # give some time to initialize + minsleep +} +tpws_start() +{ + "$TPWS" --uid $TPWS_UID:$TPWS_GID --socks --bind-addr=127.0.0.1 --port=$SOCKS_PORT "$@" >/dev/null & + PID=$! + # give some time to initialize + minsleep +} +ws_kill() +{ + [ -z "$PID" ] || { + killwait -9 $PID 2>/dev/null + PID= + } +} + +check_domain_port_block() +{ + # $1 - domain + # $2 - port + local ip ips + echo + echo \* port block tests ipv$IPV $1:$2 + if netcat_setup; then + ips=$(mdig_resolve_all $IPV $1) + if [ -n "$ips" ]; then + for ip in $ips; do + if netcat_test $ip $2; then + echo $ip connects + else + echo $ip does not connect. netcat code $? + fi + done + else + echo "ipv${IPV} $1 does not resolve" + fi + else + echo suitable netcat not found. busybox nc is not supported. pls install nmap ncat or openbsd netcat. + fi +} + +curl_test() +{ + # $1 - test function + # $2 - domain + # $3 - subst ip + # $4 - param of test function + local code=0 n=0 + + while [ $n -lt $REPEATS ]; do + n=$(($n+1)) + [ $REPEATS -gt 1 ] && printf "[attempt $n] " + if $1 "$IPV" $2 $3 "$4" ; then + [ $REPEATS -gt 1 ] && echo 'AVAILABLE' + else + code=$? + [ "$SCANLEVEL" = quick ] && break + fi + done + [ "$4" = detail ] || { + if [ $code = 254 ]; then + echo "UNAVAILABLE" + elif [ $code = 0 ]; then + echo '!!!!! AVAILABLE !!!!!' + else + echo "UNAVAILABLE code=$code" + fi + } + return $code +} +ws_curl_test() +{ + # $1 - ws start function + # $2 - test function + # $3 - domain + # $4,$5,$6, ... - ws params + + local code ws_start=$1 testf=$2 dom=$3 + shift + shift + shift + $ws_start "$@" + curl_test $testf $dom + code=$? + ws_kill + return $code +} +tpws_curl_test() +{ + # $1 - test function + # $2 - domain + # $3,$4,$5, ... - tpws params + echo - checking tpws $3 $4 $5 $6 $7 $8 $9 $TPWS_EXTRA "$TPWS_EXTRA_1" "$TPWS_EXTRA_2" "$TPWS_EXTRA_3" "$TPWS_EXTRA_4" "$TPWS_EXTRA_5" "$TPWS_EXTRA_6" "$TPWS_EXTRA_7" "$TPWS_EXTRA_8" "$TPWS_EXTRA_9" + local ALL_PROXY="socks5://127.0.0.1:$SOCKS_PORT" + ws_curl_test tpws_start "$@" $TPWS_EXTRA "$TPWS_EXTRA_1" "$TPWS_EXTRA_2" "$TPWS_EXTRA_3" "$TPWS_EXTRA_4" "$TPWS_EXTRA_5" "$TPWS_EXTRA_6" "$TPWS_EXTRA_7" "$TPWS_EXTRA_8" "$TPWS_EXTRA_9" +} +pktws_curl_test() +{ + # $1 - test function + # $2 - domain + # $3,$4,$5, ... - nfqws/dvtws params + echo - checking $PKTWSD ${WF:+$WF }$3 $4 $5 $6 $7 $8 $9 $PKTWS_EXTRA "$PKTWS_EXTRA_1" "$PKTWS_EXTRA_2" "$PKTWS_EXTRA_3" "$PKTWS_EXTRA_4" "$PKTWS_EXTRA_5" "$PKTWS_EXTRA_6" "$PKTWS_EXTRA_7" "$PKTWS_EXTRA_8" "$PKTWS_EXTRA_9" + ws_curl_test pktws_start "$@" $PKTWS_EXTRA "$PKTWS_EXTRA_1" "$PKTWS_EXTRA_2" "$PKTWS_EXTRA_3" "$PKTWS_EXTRA_4" "$PKTWS_EXTRA_5" "$PKTWS_EXTRA_6" "$PKTWS_EXTRA_7" "$PKTWS_EXTRA_8" "$PKTWS_EXTRA_9" +} +xxxws_curl_test_update() +{ + # $1 - xxx_curl_test function + # $2 - test function + # $3 - domain + # $4,$5,$6, ... - nfqws/dvtws params + local code xxxf=$1 testf=$2 dom=$3 + shift + shift + shift + $xxxf $testf $dom "$@" + code=$? + [ $code = 0 ] && strategy="${strategy:-$@}" + return $code +} +pktws_curl_test_update() +{ + xxxws_curl_test_update pktws_curl_test "$@" +} +tpws_curl_test_update() +{ + xxxws_curl_test_update tpws_curl_test "$@" +} + +report_append() +{ + NREPORT=${NREPORT:-0} + eval REPORT_${NREPORT}=\"$@\" + NREPORT=$(($NREPORT+1)) +} +report_print() +{ + local n=0 s + NREPORT=${NREPORT:-0} + while [ $n -lt $NREPORT ]; do + eval s=\"\${REPORT_$n}\" + echo $s + n=$(($n+1)) + done +} +report_strategy() +{ + # $1 - test function + # $2 - domain + # $3 - daemon + echo + if [ -n "$strategy" ]; then + # trim spaces at the end + strategy="$(echo "$strategy" | xargs)" + echo "!!!!! $1: working strategy found for ipv${IPV} $2 : $3 $strategy !!!!!" + echo + report_append "ipv${IPV} $2 $1 : $3 ${WF:+$WF }$strategy" + return 0 + else + echo "$1: $3 strategy for ipv${IPV} $2 not found" + echo + report_append "ipv${IPV} $2 $1 : $3 not working" + return 1 + fi +} +test_has_split() +{ + contains "$1" split || contains "$1" disorder +} +test_has_fake() +{ + contains "$1" fake +} +warn_fool() +{ + case "$1" in + md5sig) echo 'WARNING ! although md5sig fooling worked it will not work on all sites. it typically works only on linux servers.' ;; + datanoack) echo 'WARNING ! although datanoack fooling worked it may break NAT and may only work with external IP. Additionally it may require nftables to work correctly.' ;; + esac +} +pktws_curl_test_update_vary() +{ + # $1 - test function + # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $3 - domain + # $4 - desync mode + # $5,$6,... - strategy + + local testf=$1 sec=$2 domain=$3 desync=$4 zerofake split fake + + shift; shift; shift; shift + + zerofake=http + [ "$sec" = 0 ] || zerofake=tls + zerofake="--dpi-desync-fake-$zerofake=0x00000000" + + for fake in '' $zerofake ; do + for split in '' '--dpi-desync-split-pos=1' ; do + pktws_curl_test_update $testf $domain --dpi-desync=$desync "$@" $fake $split && return 0 + # split-pos=1 is meaningful for DPIs searching for 16 03 in TLS. no reason to apply to http + [ "$sec" = 1 ] || break + test_has_split $desync || break + done + test_has_fake $desync || break + done + + return 1 +} + +pktws_check_domain_http_bypass_() +{ + # $1 - test function + # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $3 - domain + + local tests='fake' ret ok ttls s f e desync pos fooling frag sec="$2" delta hostcase + + [ "$sec" = 0 ] && { + for s in '--hostcase' '--hostspell=hoSt' '--hostnospace' '--domcase'; do + pktws_curl_test_update $1 $3 $s + done + } + + s="--dpi-desync=split2" + ok=0 + pktws_curl_test_update $1 $3 $s + ret=$? + [ "$ret" = 0 ] && { + [ "$SCANLEVEL" = quick ] && return + ok=1 + } + [ "$ret" != 0 -o "$SCANLEVEL" = force ] && { + if [ "$sec" = 0 ]; then + pktws_curl_test_update $1 $3 $s --hostcase && { + [ "$SCANLEVEL" = quick ] && return + ok=1 + } + for pos in method host; do + for hostcase in '' '--hostcase'; do + pktws_curl_test_update $1 $3 $s --dpi-desync-split-http-req=$pos $hostcase && { + [ "$SCANLEVEL" = quick ] && return + ok=1 + } + done + done + else + for pos in sni sniext; do + pktws_curl_test_update $1 $3 $s --dpi-desync-split-tls=$pos && { + [ "$SCANLEVEL" = quick ] && return + ok=1 + } + done + fi + for pos in 1 3 4 5 10 50; do + s="--dpi-desync=split2 --dpi-desync-split-pos=$pos" + if pktws_curl_test_update $1 $3 $s; then + [ "$SCANLEVEL" = quick ] && return + ok=1 + [ "$SCANLEVEL" = force ] || break + elif [ "$sec" = 0 ]; then + pktws_curl_test_update $1 $3 $s --hostcase && [ "$SCANLEVEL" = quick ] && return + fi + done + } + [ "$ok" = 1 -a "$SCANLEVEL" != force ] || tests="$tests split fake,split2 fake,split" + + pktws_curl_test_update $1 $3 --dpi-desync=disorder2 + ret=$? + [ "$ret" = 0 -a "$SCANLEVEL" = quick ] && return + [ "$ret" != 0 -o "$SCANLEVEL" = force ] && { + pktws_curl_test_update $1 $3 --dpi-desync=disorder2 --dpi-desync-split-pos=1 + ret=$? + [ "$ret" = 0 -a "$SCANLEVEL" = quick ] && return + } + [ "$ret" != 0 -o "$SCANLEVEL" = force ] && tests="$tests disorder fake,disorder2 fake,disorder" + + ttls=$(seq -s ' ' $MIN_TTL $MAX_TTL) + for e in '' '--wssize 1:6'; do + [ -n "$e" ] && { + pktws_curl_test_update $1 $3 $e && [ "$SCANLEVEL" = quick ] && return + for desync in split2 disorder2; do + pktws_curl_test_update_vary $1 $2 $3 $desync $e && [ "$SCANLEVEL" = quick ] && return + done + } + for desync in $tests; do + for ttl in $ttls; do + pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=$ttl $e && { + [ "$SCANLEVEL" = quick ] && return + break + } + done + f= + [ "$UNAME" = "OpenBSD" ] || f="badsum" + f="$f badseq datanoack md5sig" + [ "$IPV" = 6 ] && f="$f hopbyhop hopbyhop2" + for fooling in $f; do + pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fooling=$fooling $e && { + warn_fool $fooling + [ "$SCANLEVEL" = quick ] && return + } + done + done + [ "$IPV" = 6 ] && { + f="hopbyhop hopbyhop,split2 hopbyhop,disorder2 destopt destopt,split2 destopt,disorder2" + [ -n "$IP6_DEFRAG_DISABLE" ] && f="$f ipfrag1 ipfrag1,split2 ipfrag1,disorder2" + for desync in $f; do + pktws_curl_test_update_vary $1 $2 $3 $desync $e && [ "$SCANLEVEL" = quick ] && return + done + } + + for desync in split2 disorder2; do + s="--dpi-desync=$desync" + if [ "$sec" = 0 ]; then + for pos in method host; do + pktws_curl_test_update $1 $3 $s --dpi-desync-split-seqovl=1 --dpi-desync-split-http-req=$pos $e && [ "$SCANLEVEL" = quick ] && return + done + else + for pos in sni sniext; do + pktws_curl_test_update $1 $3 $s --dpi-desync-split-seqovl=1 --dpi-desync-split-tls=$pos $e && [ "$SCANLEVEL" = quick ] && return + done + fi + for pos in 2 3 4 5 10 50; do + pktws_curl_test_update $1 $3 $s --dpi-desync-split-seqovl=$(($pos - 1)) --dpi-desync-split-pos=$pos $e && [ "$SCANLEVEL" = quick ] && return + done + [ "$sec" != 0 -a $desync = split2 ] && { + pktws_curl_test_update $1 $3 $s --dpi-desync-split-seqovl=336 --dpi-desync-split-seqovl-pattern="$ZAPRET_BASE/files/fake/tls_clienthello_iana_org.bin" $e && [ "$SCANLEVEL" = quick ] && return + } + done + + for desync in $tests; do + ok=0 + for delta in 1 2 3 4 5; do + pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-ttl=1 --dpi-desync-autottl=$delta $e && ok=1 + done + [ "$ok" = 1 ] && + { + echo "WARNING ! although autottl worked it requires testing on multiple domains to find out reliable delta" + echo "WARNING ! if a reliable delta cannot be found it's a good idea not to use autottl" + [ "$SCANLEVEL" = quick ] && return + } + done + + s="http_iana_org.bin" + [ "$sec" = 0 ] || s="tls_clienthello_iana_org.bin" + for desync in syndata syndata,split2 syndata,disorder2 ; do + pktws_curl_test_update_vary $1 $2 $3 $desync $e && [ "$SCANLEVEL" = quick ] && return + pktws_curl_test_update_vary $1 $2 $3 $desync --dpi-desync-fake-syndata="$ZAPRET_BASE/files/fake/$s" $e && [ "$SCANLEVEL" = quick ] && return + done + + # do not do wssize test for http and TLS 1.3. it's useless + [ "$sec" = 1 ] || break + done +} +pktws_check_domain_http_bypass() +{ + # $1 - test function + # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $3 - domain + + local strategy + pktws_check_domain_http_bypass_ "$@" + strategy="${strategy:+$strategy $PKTWS_EXTRA $PKTWS_EXTRA_1 $PKTWS_EXTRA_2 $PKTWS_EXTRA_3 $PKTWS_EXTRA_4 $PKTWS_EXTRA_5 $PKTWS_EXTRA_6 $PKTWS_EXTRA_7 $PKTWS_EXTRA_8 $PKTWS_EXTRA_9}" + report_strategy $1 $3 $PKTWSD +} + +pktws_check_domain_http3_bypass_() +{ + # $1 - test function + # $2 - domain + + local f desync frag tests rep + + for rep in '' 2 5 10 20; do + pktws_curl_test_update $1 $2 --dpi-desync=fake ${rep:+--dpi-desync-repeats=$rep} && [ "$SCANLEVEL" != force ] && { + [ "$SCANLEVEL" = quick ] && return + break + } + done + + [ "$IPV" = 6 ] && { + f="hopbyhop destopt" + [ -n "$IP6_DEFRAG_DISABLE" ] && f="$f ipfrag1" + for desync in $f; do + pktws_curl_test_update $1 $2 --dpi-desync=$desync && [ "$SCANLEVEL" = quick ] && return + done + } + + # OpenBSD has checksum issues with fragmented packets + [ "$UNAME" != "OpenBSD" ] && [ "$IPV" = 4 -o -n "$IP6_DEFRAG_DISABLE" ] && { + for frag in 8 16 24 32 40 64; do + tests="ipfrag2" + [ "$IPV" = 6 ] && tests="$tests hopbyhop,ipfrag2 destopt,ipfrag2" + for desync in $tests; do + pktws_curl_test_update $1 $2 --dpi-desync=$desync --dpi-desync-ipfrag-pos-udp=$frag && [ "$SCANLEVEL" = quick ] && return + done + done + } + +} +pktws_check_domain_http3_bypass() +{ + # $1 - test function + # $2 - domain + + local strategy + pktws_check_domain_http3_bypass_ "$@" + strategy="${strategy:+$strategy $PKTWS_EXTRA $PKTWS_EXTRA_1 $PKTWS_EXTRA_2 $PKTWS_EXTRA_3 $PKTWS_EXTRA_4 $PKTWS_EXTRA_5 $PKTWS_EXTRA_6 $PKTWS_EXTRA_7 $PKTWS_EXTRA_8 $PKTWS_EXTRA_9}" + report_strategy $1 $2 $PKTWSD +} +warn_mss() +{ + [ -n "$1" ] && echo 'WARNING ! although mss worked it may not work on all sites and will likely cause significant slowdown. it may only be required for TLS1.2, not TLS1.3' + return 0 +} + +tpws_check_domain_http_bypass_() +{ + # $1 - test function + # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $3 - domain + + local s mss s2 s3 pos sec="$2" + if [ "$sec" = 0 ]; then + for s in '--hostcase' '--hostspell=hoSt' '--hostdot' '--hosttab' '--hostnospace' '--domcase' \ + '--hostpad=1024' '--hostpad=2048' '--hostpad=4096' '--hostpad=8192' '--hostpad=16384' ; do + tpws_curl_test_update $1 $3 $s && [ "$SCANLEVEL" = quick ] && return + done + for s2 in '' '--oob' '--disorder' '--oob --disorder'; do + for s in '--split-http-req=method' '--split-http-req=method --hostcase' '--split-http-req=host' '--split-http-req=host --hostcase' ; do + tpws_curl_test_update $1 $3 $s $s2 && [ "$SCANLEVEL" = quick ] && return + done + done + for s in '--methodspace' '--unixeol' '--methodeol'; do + tpws_curl_test_update $1 $3 $s && [ "$SCANLEVEL" = quick ] && return + done + else + for mss in '' 88; do + s3=${mss:+--mss=$mss} + for s2 in '' '--oob' '--disorder' '--oob --disorder'; do + for pos in sni sniext; do + s="--split-tls=$pos" + tpws_curl_test_update $1 $3 $s $s2 $s3 && warn_mss $s3 && [ "$SCANLEVEL" != force ] && { + [ "$SCANLEVEL" = quick ] && return + break + } + done + for pos in 1 2 3 4 5 10 50; do + s="--split-pos=$pos" + tpws_curl_test_update $1 $3 $s $s2 $s3 && warn_mss $s3 && [ "$SCANLEVEL" != force ] && { + [ "$SCANLEVEL" = quick ] && return + break + } + done + done + for s2 in '--tlsrec=sni' '--tlsrec=sni --split-tls=sni' '--tlsrec=sni --split-tls=sni --oob' \ + '--tlsrec=sni --split-tls=sni --disorder' '--tlsrec=sni --split-tls=sni --oob --disorder' \ + '--tlsrec=sni --split-pos=1' '--tlsrec=sni --split-pos=1 --oob' '--tlsrec=sni --split-pos=1 --disorder' \ + '--tlsrec=sni --split-pos=1 --oob --disorder'; do + tpws_curl_test_update $1 $3 $s2 $s3 && warn_mss $s3 && [ "$SCANLEVEL" != force ] && { + [ "$SCANLEVEL" = quick ] && return + break + } + done + # only linux supports mss + [ "$UNAME" = Linux -a "$sec" = 1 ] || break + done + fi +} +tpws_check_domain_http_bypass() +{ + # $1 - test function + # $2 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $3 - domain + + local strategy + tpws_check_domain_http_bypass_ "$@" + strategy="${strategy:+$strategy $TPWS_EXTRA $TPWS_EXTRA_1 $TPWS_EXTRA_2 $TPWS_EXTRA_3 $TPWS_EXTRA_4 $TPWS_EXTRA_5 $TPWS_EXTRA_6 $TPWS_EXTRA_7 $TPWS_EXTRA_8 $TPWS_EXTRA_9}" + report_strategy $1 $3 tpws +} + +check_dpi_ip_block() +{ + # $1 - test function + # $2 - domain + + local blocked_dom=$2 + local blocked_ip blocked_ips unblocked_ip + + echo + echo "- IP block tests (requires manual interpretation)" + + echo "> testing $UNBLOCKED_DOM on it's original ip" + if curl_test $1 $UNBLOCKED_DOM; then + unblocked_ip=$(mdig_resolve $IPV $UNBLOCKED_DOM) + [ -n "$unblocked_ip" ] || { + echo $UNBLOCKED_DOM does not resolve. tests not possible. + return 1 + } + + echo "> testing $blocked_dom on $unblocked_ip ($UNBLOCKED_DOM)" + curl_test $1 $blocked_dom $unblocked_ip detail + + blocked_ips=$(mdig_resolve_all $IPV $blocked_dom) + for blocked_ip in $blocked_ips; do + echo "> testing $UNBLOCKED_DOM on $blocked_ip ($blocked_dom)" + curl_test $1 $UNBLOCKED_DOM $blocked_ip detail + done + else + echo $UNBLOCKED_DOM is not available. skipping this test. + fi +} + +curl_has_reason_to_continue() +{ + # $1 - curl return code + for c in 1 2 3 4 6 27 ; do + [ $1 = $c ] && return 1 + done + return 0 +} + +check_domain_prolog() +{ + # $1 - test function + # $2 - port + # $3 - domain + + local code + + echo + echo \* $1 ipv$IPV $3 + + echo "- checking without DPI bypass" + curl_test $1 $3 && { + report_append "ipv${IPV} $3 $1 : working without bypass" + [ "$SCANLEVEL" = force ] || return 1 + } + code=$? + curl_has_reason_to_continue $code || { + report_append "ipv${IPV} $3 $1 : test aborted, no reason to continue. curl code $(curl_translate_code $code)" + return 1 + } + return 0 +} +check_domain_http_tcp() +{ + # $1 - test function + # $2 - port + # $3 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $4 - domain + + # in case was interrupted before + pktws_ipt_unprepare_tcp $2 + ws_kill + + check_domain_prolog $1 $2 $4 || return + + check_dpi_ip_block $1 $4 + + [ "$SKIP_TPWS" = 1 ] || { + echo + tpws_check_domain_http_bypass $1 $3 $4 + } + + [ "$SKIP_PKTWS" = 1 ] || { + echo + echo preparing $PKTWSD redirection + pktws_ipt_prepare_tcp $2 + + pktws_check_domain_http_bypass $1 $3 $4 + + echo clearing $PKTWSD redirection + pktws_ipt_unprepare_tcp $2 + } +} +check_domain_http_udp() +{ + # $1 - test function + # $2 - port + # $3 - domain + + # in case was interrupted before + pktws_ipt_unprepare_udp $2 + ws_kill + + check_domain_prolog $1 $2 $3 || return + + [ "$SKIP_PKTWS" = 1 ] || { + echo + echo preparing $PKTWSD redirection + pktws_ipt_prepare_udp $2 + + pktws_check_domain_http3_bypass $1 $3 + + echo clearing $PKTWSD redirection + pktws_ipt_unprepare_udp $2 + } +} + + +check_domain_http() +{ + # $1 - domain + check_domain_http_tcp curl_test_http 80 0 $1 +} +check_domain_https_tls12() +{ + # $1 - domain + check_domain_http_tcp curl_test_https_tls12 443 1 $1 +} +check_domain_https_tls13() +{ + # $1 - domain + check_domain_http_tcp curl_test_https_tls13 443 2 $1 +} +check_domain_http3() +{ + # $1 - domain + check_domain_http_udp curl_test_http3 443 $1 +} + +configure_ip_version() +{ + if [ "$IPV" = 6 ]; then + LOCALHOST=::1 + LOCALHOST_IPT=[${LOCALHOST}] + IPVV=6 + else + IPTABLES=iptables + LOCALHOST=127.0.0.1 + LOCALHOST_IPT=$LOCALHOST + IPVV= + fi + IPTABLES=ip${IPVV}tables +} +configure_curl_opt() +{ + # wolfssl : --tlsv1.x mandates exact ssl version, tls-max not supported + # openssl : --tlsv1.x means "version equal or greater", tls-max supported + TLSMAX12= + TLSMAX13= + curl_supports_tlsmax && { + TLSMAX12="--tls-max 1.2" + TLSMAX13="--tls-max 1.3" + } + TLS13= + curl_supports_tls13 && TLS13=1 + HTTP3= + curl_supports_http3 && HTTP3=1 +} + +linux_ipv6_defrag_can_be_disabled() +{ + linux_min_version 4 16 +} + +configure_defrag() +{ + IP6_DEFRAG_DISABLE= + + [ "$IPVS" = 4 ] && return + + [ "$UNAME" = "Linux" ] && { + linux_ipv6_defrag_can_be_disabled || { + echo "WARNING ! ipv6 defrag can only be effectively disabled in linux kernel 4.16+" + echo "WARNING ! ipv6 ipfrag tests are disabled" + echo + return + } + } + + case "$FWTYPE" in + iptables) + if ipt6_has_raw ; then + if ipt6_has_frag; then + IP6_DEFRAG_DISABLE=1 + else + echo "WARNING ! ip6tables does not have '-m frag' module, ipv6 ipfrag tests are disabled" + echo + fi + else + echo "WARNING ! ip6tables raw table is not available, ipv6 ipfrag tests are disabled" + echo + fi + [ -n "$IP6_DEFRAG_DISABLE" ] && { + local ipexe="$(readlink -f $(whichq ip6tables))" + if contains "$ipexe" nft; then + echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6tables-nft is used. current ip6tables point to : $ipexe" + else + echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6table_raw kernel module is not loaded with parameter : raw_before_defrag=1" + fi + echo + } + ;; + *) + IP6_DEFRAG_DISABLE=1 + ;; + esac +} + +ask_params() +{ + echo + echo NOTE ! this test should be run with zapret or any other bypass software disabled, without VPN + echo + + curl_supports_connect_to || { + echo "installed curl does not support --connect-to option. pls install at least curl 7.49" + echo "current curl version:" + $CURL --version + exitp 1 + } + + + echo "specify domain(s) to test. multiple domains are space separated." + printf "domain(s) (default: $DOMAINS) : " + local dom + read dom + [ -n "$dom" ] && DOMAINS="$dom" + + local IPVS_def=4 + # yandex public dns + pingtest 6 2a02:6b8::feed:0ff && IPVS_def=46 + printf "ip protocol version(s) - 4, 6 or 46 for both (default: $IPVS_def) : " + read IPVS + [ -n "$IPVS" ] || IPVS=$IPVS_def + [ "$IPVS" = 4 -o "$IPVS" = 6 -o "$IPVS" = 46 ] || { + echo 'invalid ip version(s). should be 4, 6 or 46.' + exitp 1 + } + [ "$IPVS" = 46 ] && IPVS="4 6" + + configure_curl_opt + + ENABLE_HTTP=1 + echo + ask_yes_no_var ENABLE_HTTP "check http" + + ENABLE_HTTPS_TLS12=1 + echo + ask_yes_no_var ENABLE_HTTPS_TLS12 "check https tls 1.2" + + ENABLE_HTTPS_TLS13=0 + echo + if [ -n "$TLS13" ]; then + echo "TLS 1.3 uses encrypted ServerHello. DPI cannot check domain name in server response." + echo "This can allow more bypass strategies to work." + echo "What works for TLS 1.2 will also work for TLS 1.3 but not vice versa." + echo "Most sites nowadays support TLS 1.3 but not all. If you can't find a strategy for TLS 1.2 use this test." + echo "TLS 1.3 only strategy is better than nothing." + ask_yes_no_var ENABLE_HTTPS_TLS13 "check https tls 1.3" + else + echo "installed curl version does not support TLS 1.3 . tests disabled." + fi + + ENABLE_HTTP3=0 + echo + if [ -n "$HTTP3" ]; then + echo "make sure target domain(s) support QUIC or result will be negative in any case" + ENABLE_HTTP3=1 + ask_yes_no_var ENABLE_HTTP3 "check http3 QUIC" + else + echo "installed curl version does not support http3 QUIC. tests disabled." + fi + + IGNORE_CA=0 + CURL_OPT= + [ $ENABLE_HTTPS_TLS13 = 1 -o $ENABLE_HTTPS_TLS12 = 1 ] && { + echo + echo "on limited systems like openwrt CA certificates might not be installed to preserve space" + echo "in such a case curl cannot verify server certificate and you should either install ca-bundle or disable verification" + echo "however disabling verification will break https check if ISP does MitM attack and substitutes server certificate" + ask_yes_no_var IGNORE_CA "do not verify server certificate" + [ "$IGNORE_CA" = 1 ] && CURL_OPT=-k + } + + echo + echo "sometimes ISPs use multiple DPIs or load balancing. bypass strategies may work unstable." + printf "how many times to repeat each test (default: 1) : " + read REPEATS + REPEATS=$((0+${REPEATS:-1})) + [ "$REPEATS" = 0 ] && { + echo invalid repeat count + exitp 1 + } + + echo + echo quick - scan as fast as possible to reveal any working strategy + echo standard - do investigation what works on your DPI + echo force - scan maximum despite of result + SCANLEVEL=${SCANLEVEL:-standard} + ask_list SCANLEVEL "quick standard force" "$SCANLEVEL" + # disable tpws checks by default in quick mode + [ "$SCANLEVEL" = quick -a -z "$SKIP_TPWS" ] && SKIP_TPWS=1 + + echo + + configure_defrag +} + + + +ping_with_fix() +{ + local ret + $PING $2 $1 >/dev/null 2>/dev/null + ret=$? + # can be because of unsupported -4 option + if [ "$ret" = 2 -o "$ret" = 64 ]; then + ping $2 $1 >/dev/null + else + return $ret + fi +} + +pingtest() +{ + # $1 - ip version : 4 or 6 + # $2 - domain or ip + + # ping command can vary a lot. some implementations have -4/-6 options. others don.t + # WARNING ! macos ping6 command does not have timeout option. ping6 will fail + + local PING=ping ret + if [ "$1" = 6 ]; then + if exists ping6; then + PING=ping6 + else + PING="ping -6" + fi + else + if [ "$UNAME" = Darwin -o "$UNAME" = FreeBSD -o "$UNAME" = OpenBSD ]; then + # ping by default pings ipv4, ping6 only pings ipv6 + # in FreeBSD -4/-6 options are supported, in others not + PING=ping + else + # this can be linux or cygwin + # in linux it's not possible for sure to figure out if it supports -4/-6. only try and check for result code=2 (invalid option) + PING="ping -4" + fi + fi + case "$UNAME" in + Darwin) + $PING -c 1 -t 1 $2 >/dev/null 2>/dev/null + # WARNING ! macos ping6 command does not have timeout option. ping6 will fail. but without timeout is not an option. + ;; + OpenBSD) + $PING -c 1 -w 1 $2 >/dev/null + ;; + CYGWIN) + if starts_with "$(which ping)" /cygdrive; then + # cygwin does not have own ping by default. use windows PING. + $PING -n 1 -w 1000 $2 >/dev/null + else + ping_with_fix $2 '-c 1 -w 1' + fi + ;; + *) + ping_with_fix $2 '-c 1 -W 1' + ;; + esac +} +dnstest() +{ + # $1 - dns server. empty for system resolver + "$LOOKUP" w3.org $1 >/dev/null 2>/dev/null +} +find_working_public_dns() +{ + local dns + for dns in $DNSCHECK_DNS; do + pingtest 4 $dns && dnstest $dns && { + PUBDNS=$dns + return 0 + } + done + return 1 +} +lookup4() +{ + # $1 - domain + # $2 - DNS + case "$LOOKUP" in + nslookup) + if is_linked_to_busybox nslookup; then + nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^.*:[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' + else + nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' + fi + ;; + host) + host -t A $1 $2 | grep "has address" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' + ;; + esac +} +check_dns_spoof() +{ + # $1 - domain + # $2 - public DNS + + # windows version of mdig outputs 0D0A line ending. remove 0D. + echo $1 | "$MDIG" --family=4 | tr -d '\r' >"$DNSCHECK_DIG1" + lookup4 $1 $2 >"$DNSCHECK_DIG2" + # check whether system resolver returns anything other than public DNS + grep -qvFf "$DNSCHECK_DIG2" "$DNSCHECK_DIG1" +} +check_dns_cleanup() +{ + rm -f "$DNSCHECK_DIG1" "$DNSCHECK_DIG2" "$DNSCHECK_DIGS" 2>/dev/null +} +check_dns() +{ + local C1 C2 dom + + echo \* checking DNS + + [ -f "$DNSCHECK_DIGS" ] && rm -f "$DNSCHECK_DIGS" + + dnstest || { + echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. + return 1 + } + echo system DNS is working + + if find_working_public_dns ; then + echo comparing system resolver to public DNS : $PUBDNS + for dom in $DNSCHECK_DOM; do + if check_dns_spoof $dom $PUBDNS ; then + echo $dom : MISMATCH + echo -- system resolver : + cat "$DNSCHECK_DIG1" + echo -- $PUBDNS : + cat "$DNSCHECK_DIG2" + check_dns_cleanup + echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! + echo -- DNS CHANGE OR DNSCRYPT MAY BE REQUIRED + return 1 + else + echo $dom : OK + cat "$DNSCHECK_DIG1" >>"$DNSCHECK_DIGS" + fi + done + else + echo no working public DNS was found. looks like public DNS blocked. + for dom in $DNSCHECK_DOM; do echo $dom; done | "$MDIG" --threads=10 --family=4 >"$DNSCHECK_DIGS" + fi + + echo checking resolved IP uniqueness for : $DNSCHECK_DOM + echo censor\'s DNS can return equal result for multiple blocked domains. + C1=$(wc -l <"$DNSCHECK_DIGS") + C2=$(sort -u "$DNSCHECK_DIGS" | wc -l) + [ "$C1" -eq 0 ] && + { + echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. + check_dns_cleanup + return 1 + } + [ "$C1" = "$C2" ] || + { + echo system dns resolver has returned equal IPs for some domains checked above \($C1 total, $C2 unique\) + echo non-unique IPs : + sort "$DNSCHECK_DIGS" | uniq -d + echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! + echo -- DNSCRYPT MAY BE REQUIRED + check_dns_cleanup + return 1 + } + echo all resolved IPs are unique + echo -- DNS looks good + echo -- NOTE this check is Russia targeted. In your country other domains may be blocked. + check_dns_cleanup + return 0 +} + + +unprepare_all() +{ + # make sure we are not in a middle state that impacts connectivity + rm -f "$HDRTEMP" + [ -n "$IPV" ] && { + pktws_ipt_unprepare_tcp 80 + pktws_ipt_unprepare_tcp 443 + pktws_ipt_unprepare_udp 443 + } + ws_kill + cleanup +} +sigint() +{ + echo + echo terminating... + unprepare_all + exitp 1 +} +sigint_cleanup() +{ + cleanup + exit 1 +} +sigsilent() +{ + # must not write anything here to stdout + unprepare_all + exit 1 +} + +fsleep_setup +fix_sbin_path +check_system +check_already +[ "$UNAME" = CYGWIN ] || require_root +check_prerequisites +trap sigint_cleanup INT +[ "$SKIP_DNSCHECK" = 1 ] || check_dns +check_virt +ask_params +trap - INT + +PID= +NREPORT= +unset WF +trap sigint INT +trap sigsilent PIPE +trap sigsilent HUP +for dom in $DOMAINS; do + for IPV in $IPVS; do + configure_ip_version + [ "$ENABLE_HTTP" = 1 ] && { + check_domain_port_block $dom $HTTP_PORT + check_domain_http $dom + } + [ "$ENABLE_HTTPS_TLS12" = 1 -o "$ENABLE_HTTPS_TLS13" = 1 ] && check_domain_port_block $dom $HTTPS_PORT + [ "$ENABLE_HTTPS_TLS12" = 1 ] && check_domain_https_tls12 $dom + [ "$ENABLE_HTTPS_TLS13" = 1 ] && check_domain_https_tls13 $dom + [ "$ENABLE_HTTP3" = 1 ] && check_domain_http3 $dom + done +done +trap - HUP +trap - PIPE +trap - INT + +cleanup + +echo +echo \* SUMMARY +report_print +echo +echo "Please note this SUMMARY does not guarantee a magic pill for you to copy/paste and be happy." +echo "Understanding how strategies work is very desirable." +echo "This knowledge allows to understand better which strategies to prefer and which to avoid if possible, how to combine strategies." +echo "Blockcheck does it's best to prioritize good strategies but it's not bullet-proof." +echo "It was designed not as magic pill maker but as a DPI bypass test tool." + +exitp 0 diff --git a/common/base.sh b/common/base.sh new file mode 100644 index 00000000..7c772ef8 --- /dev/null +++ b/common/base.sh @@ -0,0 +1,340 @@ +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 +} +find_str_in_list() +{ + [ -n "$1" ] && { + for v in $2; do + [ "$v" = "$1" ] && return 0 + done + } + return 1 +} +end_with_newline() +{ + local c="$(tail -c 1)" + [ "$c" = "" ] +} + +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() +{ + 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 +} + +setup_md5() +{ + [ -n "$MD5" ] && return + MD5=md5sum + exists $MD5 || MD5=md5 +} + +random() +{ + # $1 - min, $2 - max + local r rs + setup_md5 + 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" +} + +std_ports() +{ + HTTP_PORTS=${HTTP_PORTS:-80} + HTTPS_PORTS=${HTTPS_PORTS:-443} + QUIC_PORTS=${QUIC_PORTS:-443} + HTTP_PORTS_IPT=$(replace_char - : $HTTP_PORTS) + HTTPS_PORTS_IPT=$(replace_char - : $HTTPS_PORTS) + QUIC_PORTS_IPT=$(replace_char - : $QUIC_PORTS) +} diff --git a/common/custom.sh b/common/custom.sh new file mode 100644 index 00000000..40b65a74 --- /dev/null +++ b/common/custom.sh @@ -0,0 +1,25 @@ +custom_runner() +{ + # $1 - function name + # $2+ - params + + local n script FUNC=$1 + + shift + + [ -f "$CUSTOM_DIR/custom" ] && { + unset -f $FUNC + . "$CUSTOM_DIR/custom" + existf $FUNC && $FUNC "$@" + } + [ -d "$CUSTOM_DIR/custom.d" ] && { + n=$(ls "$CUSTOM_DIR/custom.d" | wc -c | xargs) + [ "$n" = 0 ] || { + for script in "$CUSTOM_DIR/custom.d/"*; do + unset -f $FUNC + . "$script" + existf $FUNC && $FUNC "$@" + done + } + } +} diff --git a/common/dialog.sh b/common/dialog.sh new file mode 100644 index 00000000..0cb3890c --- /dev/null +++ b/common/dialog.sh @@ -0,0 +1,58 @@ +read_yes_no() +{ + # $1 - default (Y/N) + local A + read A + [ -z "$A" ] || ([ "$A" != "Y" ] && [ "$A" != "y" ] && [ "$A" != "N" ] && [ "$A" != "n" ]) && A=$1 + [ "$A" = "Y" ] || [ "$A" = "y" ] || [ "$A" = "1" ] +} +ask_yes_no() +{ + # $1 - default (Y/N or 0/1) + # $2 - text + local DEFAULT=$1 + [ "$1" = "1" ] && DEFAULT=Y + [ "$1" = "0" ] && DEFAULT=N + [ -z "$DEFAULT" ] && DEFAULT=N + printf "$2 (default : $DEFAULT) (Y/N) ? " + read_yes_no $DEFAULT +} +ask_yes_no_var() +{ + # $1 - variable name for answer : 0/1 + # $2 - text + local DEFAULT + eval DEFAULT="\$$1" + if ask_yes_no "$DEFAULT" "$2"; then + eval $1=1 + else + eval $1=0 + fi +} +ask_list() +{ + # $1 - mode var + # $2 - space separated value list + # $3 - (optional) default value + local M_DEFAULT + eval M_DEFAULT="\$$1" + local M_ALL=$M_DEFAULT + local M="" + local m + + [ -n "$3" ] && { find_str_in_list "$M_DEFAULT" "$2" || M_DEFAULT="$3" ;} + + n=1 + for m in $2; do + echo $n : $m + n=$(($n+1)) + done + printf "your choice (default : $M_DEFAULT) : " + read m + [ -n "$m" ] && M=$(echo $2 | cut -d ' ' -f$m 2>/dev/null) + [ -z "$M" ] && M="$M_DEFAULT" + echo selected : $M + eval $1="\"$M\"" + + [ "$M" != "$M_OLD" ] +} diff --git a/common/elevate.sh b/common/elevate.sh new file mode 100644 index 00000000..65e8dc9b --- /dev/null +++ b/common/elevate.sh @@ -0,0 +1,13 @@ +require_root() +{ + local exe + echo \* checking privileges + [ $(id -u) -ne "0" ] && { + echo root is required + exe="$EXEDIR/$(basename "$0")" + exists sudo && exec sudo sh "$exe" + exists su && exec su root -c "sh \"$exe\"" + echo su or sudo not found + exitp 2 + } +} diff --git a/common/fwtype.sh b/common/fwtype.sh new file mode 100644 index 00000000..61390bb9 --- /dev/null +++ b/common/fwtype.sh @@ -0,0 +1,64 @@ +linux_ipt_avail() +{ + exists iptables && exists ip6tables +} +linux_maybe_iptables_fwtype() +{ + linux_ipt_avail && FWTYPE=iptables +} +linux_nft_avail() +{ + exists nft +} +linux_fwtype() +{ + [ -n "$FWTYPE" ] && return + + FWTYPE=unsupported + + linux_get_subsys + if [ "$SUBSYS" = openwrt ] ; then + # linux kernel is new enough if fw4 is there + if [ -x /sbin/fw4 ] && linux_nft_avail ; then + FWTYPE=nftables + else + linux_maybe_iptables_fwtype + fi + else + SUBSYS= + # generic linux + # flowtable is implemented since kernel 4.16 + if linux_nft_avail && linux_min_version 4 16; then + FWTYPE=nftables + else + linux_maybe_iptables_fwtype + fi + fi + + export FWTYPE +} + +get_fwtype() +{ + [ -n "$FWTYPE" ] && return + + local UNAME="$(uname)" + + case "$UNAME" in + Linux) + linux_fwtype + ;; + FreeBSD) + if exists ipfw ; then + FWTYPE=ipfw + else + FWTYPE=unsupported + fi + ;; + *) + FWTYPE=unsupported + ;; + esac + + export FWTYPE +} diff --git a/common/installer.sh b/common/installer.sh new file mode 100644 index 00000000..57d7cdf6 --- /dev/null +++ b/common/installer.sh @@ -0,0 +1,689 @@ +GET_LIST_PREFIX=/ipset/get_ + +SYSTEMD_DIR=/lib/systemd +[ -d "$SYSTEMD_DIR" ] || SYSTEMD_DIR=/usr/lib/systemd +[ -d "$SYSTEMD_DIR" ] && SYSTEMD_SYSTEM_DIR="$SYSTEMD_DIR/system" + +INIT_SCRIPT=/etc/init.d/zapret + + +exitp() +{ + echo + echo press enter to continue + read A + exit $1 +} + +parse_var_checked() +{ + # $1 - file name + # $2 - var name + local sed="sed -nre s/^[[:space:]]*$2=[\\\"|\']?([^\\\"|\']*)[\\\"|\']?/\1/p" + local v="$($sed <"$1" | tail -n 1)" + eval $2=\"$v\" +} +parse_vars_checked() +{ + # $1 - file name + # $2,$3,... - var names + local f="$1" + shift + while [ -n "$1" ]; do + parse_var_checked "$f" $1 + shift + done +} +edit_file() +{ + # $1 - file name + local ed="$EDITOR" + [ -n "$ed" ] || { + for e in mcedit nano vim vi; do + exists "$e" && { + ed="$e" + break + } + done + } + [ -n "$ed" ] && "$ed" "$1" +} +edit_vars() +{ + # $1,$2,... - var names + local n=1 var v tmp="/tmp/zvars" + rm -f "$tmp" + while [ 1=1 ]; do + eval var="\${$n}" + [ -n "$var" ] || break + eval v="\$$var" + echo $var=\"$v\" >>"$tmp" + n=$(($n+1)) + done + edit_file "$tmp" && parse_vars_checked "$tmp" "$@" + rm -f "$tmp" +} + +openrc_test() +{ + exists rc-update || return 1 + # some systems do not usse openrc-init but launch openrc from inittab + [ "$INIT" = "openrc-init" ] || grep -qE "sysinit.*openrc" /etc/inittab 2>/dev/null +} +check_system() +{ + # $1 - nonempty = do not fail on unknown rc system + + echo \* checking system + + SYSTEM= + SUBSYS= + SYSTEMCTL=$(whichq systemctl) + + get_fwtype + OPENWRT_FW3= + + local info + UNAME=$(uname) + if [ "$UNAME" = "Linux" ]; then + # do not use 'exe' because it requires root + local INIT="$(sed 's/\x0/\n/g' /proc/1/cmdline | head -n 1)" + [ -L "$INIT" ] && INIT=$(readlink "$INIT") + INIT="$(basename "$INIT")" + # some distros include systemctl without systemd + if [ -d "$SYSTEMD_DIR" ] && [ -x "$SYSTEMCTL" ] && [ "$INIT" = "systemd" ]; then + SYSTEM=systemd + elif [ -f "/etc/openwrt_release" ] && exists opkg && exists uci && [ "$INIT" = "procd" ] ; then + { + SYSTEM=openwrt + if openwrt_fw3 ; then + OPENWRT_FW3=1 + info="openwrt firewall uses fw3" + if is_ipt_flow_offload_avail; then + info="$info. hardware flow offloading requires iptables." + else + info="$info. flow offloading unavailable." + fi + elif openwrt_fw4; then + info="openwrt firewall uses fw4. flow offloading requires nftables." + fi + } + elif openrc_test; then + SYSTEM=openrc + else + echo system is not either systemd, openrc or openwrt based + echo easy installer can set up config settings but can\'t configure auto start + echo you have to do it manually. check readme.txt for manual setup info. + if [ -n "$1" ] || ask_yes_no N "do you want to continue"; then + SYSTEM=linux + else + exitp 5 + fi + fi + linux_get_subsys + elif [ "$UNAME" = "Darwin" ]; then + SYSTEM=macos + else + echo easy installer only supports Linux and MacOS. check readme.txt for supported systems and manual setup info. + exitp 5 + fi + echo system is based on $SYSTEM + [ -n "$info" ] && echo $info +} + +get_free_space_mb() +{ + df -m $PWD | awk '/[0-9]%/{print $(NF-2)}' +} +get_ram_kb() +{ + grep MemTotal /proc/meminfo | awk '{print $2}' +} +get_ram_mb() +{ + local R=$(get_ram_kb) + echo $(($R/1024)) +} + +crontab_del() +{ + exists crontab || return + + echo \* removing crontab entry + + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP 2>/dev/null + if grep -q "$GET_LIST_PREFIX" $CRONTMP; then + echo removing following entries from crontab : + grep "$GET_LIST_PREFIX" $CRONTMP + grep -v "$GET_LIST_PREFIX" $CRONTMP >$CRONTMP.2 + crontab $CRONTMP.2 + rm -f $CRONTMP.2 + fi + rm -f $CRONTMP +} +crontab_del_quiet() +{ + exists crontab || return + + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP 2>/dev/null + if grep -q "$GET_LIST_PREFIX" $CRONTMP; then + grep -v "$GET_LIST_PREFIX" $CRONTMP >$CRONTMP.2 + crontab $CRONTMP.2 + rm -f $CRONTMP.2 + fi + rm -f $CRONTMP +} +crontab_add() +{ + # $1 - hour min + # $2 - hour max + [ -x "$GET_LIST" ] && { + echo \* adding crontab entry + + if exists crontab; then + CRONTMP=/tmp/cron.tmp + crontab -l >$CRONTMP 2>/dev/null + if grep -q "$GET_LIST_PREFIX" $CRONTMP; then + echo some entries already exist in crontab. check if this is corrent : + grep "$GET_LIST_PREFIX" $CRONTMP + else + end_with_newline <"$CRONTMP" || echo >>"$CRONTMP" + echo "$(random 0 59) $(random $1 $2) */2 * * $GET_LIST" >>$CRONTMP + crontab $CRONTMP + fi + rm -f $CRONTMP + else + echo '!!! CRON IS ABSENT !!! LISTS AUTO UPDATE WILL NOT WORK !!!' + fi + } +} +cron_ensure_running() +{ + # if no crontabs present in /etc/cron openwrt init script does not launch crond. this is default + [ "$SYSTEM" = "openwrt" ] && { + /etc/init.d/cron enable + /etc/init.d/cron start + } +} + + +service_start_systemd() +{ + echo \* starting zapret service + + "$SYSTEMCTL" start zapret || { + echo could not start zapret service + exitp 30 + } +} +service_stop_systemd() +{ + echo \* stopping zapret service + + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" disable zapret + "$SYSTEMCTL" stop zapret +} +service_remove_systemd() +{ + echo \* removing zapret service + + rm -f "$SYSTEMD_SYSTEM_DIR/zapret.service" + "$SYSTEMCTL" daemon-reload +} +timer_remove_systemd() +{ + echo \* removing zapret-list-update timer + + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" disable zapret-list-update.timer + "$SYSTEMCTL" stop zapret-list-update.timer + rm -f "$SYSTEMD_SYSTEM_DIR/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR/zapret-list-update.timer" + "$SYSTEMCTL" daemon-reload +} + +install_sysv_init() +{ + # $1 - "0"=disable + echo \* installing init script + + [ -x "$INIT_SCRIPT" ] && { + "$INIT_SCRIPT" stop + "$INIT_SCRIPT" disable + } + ln -fs "$INIT_SCRIPT_SRC" "$INIT_SCRIPT" + [ "$1" != "0" ] && "$INIT_SCRIPT" enable +} +install_openrc_init() +{ + # $1 - "0"=disable + echo \* installing init script + + [ -x "$INIT_SCRIPT" ] && { + "$INIT_SCRIPT" stop + rc-update del zapret + } + ln -fs "$INIT_SCRIPT_SRC" "$INIT_SCRIPT" + [ "$1" != "0" ] && rc-update add zapret +} +service_remove_openrc() +{ + echo \* removing zapret service + + [ -x "$INIT_SCRIPT" ] && { + rc-update del zapret + "$INIT_SCRIPT" stop + } + rm -f "$INIT_SCRIPT" +} +service_start_sysv() +{ + [ -x "$INIT_SCRIPT" ] && { + echo \* starting zapret service + "$INIT_SCRIPT" start || { + echo could not start zapret service + exitp 30 + } + } +} +service_stop_sysv() +{ + [ -x "$INIT_SCRIPT" ] && { + echo \* stopping zapret service + "$INIT_SCRIPT" stop + } +} +service_remove_sysv() +{ + echo \* removing zapret service + + [ -x "$INIT_SCRIPT" ] && { + "$INIT_SCRIPT" disable + "$INIT_SCRIPT" stop + } + rm -f "$INIT_SCRIPT" +} + +check_kmod() +{ + [ -f "/lib/modules/$(uname -r)/$1.ko" ] +} +check_package_exists_openwrt() +{ + [ -n "$(opkg list $1)" ] +} +check_package_openwrt() +{ + [ -n "$(opkg list-installed $1)" ] && return 0 + local what="$(opkg whatprovides $1 | tail -n +2 | head -n 1)" + [ -n "$what" ] || return 1 + [ -n "$(opkg list-installed $what)" ] +} +check_packages_openwrt() +{ + for pkg in $@; do + check_package_openwrt $pkg || return + done +} + +install_openwrt_iface_hook() +{ + echo \* installing ifup hook + + ln -fs "$OPENWRT_IFACE_HOOK" /etc/hotplug.d/iface +} +remove_openwrt_iface_hook() +{ + echo \* removing ifup hook + + rm -f /etc/hotplug.d/iface/??-zapret +} +openwrt_fw_section_find() +{ + # $1 - fw include postfix + # echoes section number + + i=0 + while true + do + path=$(uci -q get firewall.@include[$i].path) + [ -n "$path" ] || break + [ "$path" = "$OPENWRT_FW_INCLUDE$1" ] && { + echo $i + return 0 + } + i=$(($i+1)) + done + return 1 +} +openwrt_fw_section_del() +{ + # $1 - fw include postfix + + local id="$(openwrt_fw_section_find $1)" + [ -n "$id" ] && { + uci delete firewall.@include[$id] && uci commit firewall + rm -f "$OPENWRT_FW_INCLUDE$1" + } +} +openwrt_fw_section_add() +{ + openwrt_fw_section_find || + { + uci add firewall include >/dev/null || return + echo -1 + } +} +openwrt_fw_section_configure() +{ + local id="$(openwrt_fw_section_add $1)" + [ -z "$id" ] || + ! uci set firewall.@include[$id].path="$OPENWRT_FW_INCLUDE" || + ! uci set firewall.@include[$id].reload="1" || + ! uci commit firewall && + { + echo could not add firewall include + exitp 50 + } +} +install_openwrt_firewall() +{ + echo \* installing firewall script $1 + + [ -n "MODE" ] || { + echo should specify MODE in $ZAPRET_CONFIG + exitp 7 + } + + echo "linking : $FW_SCRIPT_SRC => $OPENWRT_FW_INCLUDE" + ln -fs "$FW_SCRIPT_SRC" "$OPENWRT_FW_INCLUDE" + + openwrt_fw_section_configure $1 +} +restart_openwrt_firewall() +{ + echo \* restarting firewall + + local FW=fw4 + [ -n "$OPENWRT_FW3" ] && FW=fw3 + $FW -q restart || { + echo could not restart firewall $FW + exitp 30 + } +} +remove_openwrt_firewall() +{ + echo \* removing firewall script + + openwrt_fw_section_del + # from old zapret versions. now we use single include + openwrt_fw_section_del 6 +} + +clear_ipset() +{ + echo "* clearing ipset(s)" + + # free some RAM + "$IPSET_DIR/create_ipset.sh" clear +} + + +service_install_macos() +{ + echo \* installing zapret service + + ln -fs "$ZAPRET_BASE/init.d/macos/zapret.plist" /Library/LaunchDaemons +} +service_start_macos() +{ + echo \* starting zapret service + + "$INIT_SCRIPT_SRC" start +} +service_stop_macos() +{ + echo \* stopping zapret service + + "$INIT_SCRIPT_SRC" stop +} +service_remove_macos() +{ + echo \* removing zapret service + + rm -f /Library/LaunchDaemons/zapret.plist + zapret_stop_daemons +} + +remove_macos_firewall() +{ + echo \* removing zapret PF hooks + + pf_anchors_clear + pf_anchors_del + pf_anchor_root_del + pf_anchor_root_reload +} + +sedi() +{ + # MacOS doesnt support -i without parameter. busybox doesnt support -i with parameter. + # its not possible to put "sed -i ''" to a variable and then use it + if [ "$SYSTEM" = "macos" ]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +write_config_var() +{ + # $1 - mode var + local M + eval M="\$$1" + + if grep -q "^$1=\|^#$1=" "$ZAPRET_CONFIG"; then + # replace / => \/ + #M=${M//\//\\\/} + M=$(echo $M | sed 's/\//\\\//g') + if [ -n "$M" ]; then + if contains "$M" " "; then + sedi -Ee "s/^#?$1=.*$/$1=\"$M\"/" "$ZAPRET_CONFIG" + else + sedi -Ee "s/^#?$1=.*$/$1=$M/" "$ZAPRET_CONFIG" + fi + else + # write with comment at the beginning + sedi -Ee "s/^#?$1=.*$/#$1=/" "$ZAPRET_CONFIG" + fi + else + # var does not exist in config. add it + contains "$M" " " && M="\"$M\"" + if [ -n "$M" ]; then + echo "$1=$M" >>"$ZAPRET_CONFIG" + else + echo "#$1=$M" >>"$ZAPRET_CONFIG" + fi + fi +} + +check_prerequisites_linux() +{ + echo \* checking prerequisites + + local s cmd PKGS UTILS req="curl curl" + case "$FWTYPE" in + iptables) + req="$req iptables iptables ip6tables iptables ipset ipset" + ;; + nftables) + req="$req nft nftables" + ;; + esac + + PKGS=$(for s in $req; do echo $s; done | + while read cmd; do + read pkg + exists $cmd || echo $pkg + done | sort -u | xargs) + UTILS=$(for s in $req; do echo $s; done | + while read cmd; do + read pkg + echo $cmd + done | sort -u | xargs) + + if [ -z "$PKGS" ] ; then + echo required utilities exist : $UTILS + else + echo \* installing prerequisites + + echo packages required : $PKGS + + APTGET=$(whichq apt-get) + YUM=$(whichq yum) + PACMAN=$(whichq pacman) + ZYPPER=$(whichq zypper) + EOPKG=$(whichq eopkg) + APK=$(whichq apk) + if [ -x "$APTGET" ] ; then + "$APTGET" update + "$APTGET" install -y --no-install-recommends $PKGS dnsutils || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$YUM" ] ; then + "$YUM" -y install $PKGS || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$PACMAN" ] ; then + "$PACMAN" -Syy + "$PACMAN" --noconfirm -S $PKGS || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$ZYPPER" ] ; then + "$ZYPPER" --non-interactive install $PKGS || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$EOPKG" ] ; then + "$EOPKG" -y install $PKGS || { + echo could not install prerequisites + exitp 6 + } + elif [ -x "$APK" ] ; then + "$APK" update + # for alpine + [ "$FWTYPE" = iptables ] && [ -n "$($APK list ip6tables)" ] && PKGS="$PKGS ip6tables" + "$APK" add $PKGS || { + echo could not install prerequisites + exitp 6 + } + else + echo supported package manager not found + echo you must manually install : $UTILS + exitp 5 + fi + fi +} + +check_prerequisites_openwrt() +{ + echo \* checking prerequisites + + local PKGS="curl" UPD=0 + + case "$FWTYPE" in + iptables) + PKGS="$PKGS ipset iptables iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra" + [ "$DISABLE_IPV6" != "1" ] && PKGS="$PKGS ip6tables ip6tables-mod-nat ip6tables-extra" + ;; + nftables) + PKGS="$PKGS nftables kmod-nft-nat kmod-nft-offload kmod-nft-queue" + ;; + esac + + if check_packages_openwrt $PKGS ; then + echo everything is present + else + echo \* installing prerequisites + + opkg update + UPD=1 + opkg install $PKGS || { + echo could not install prerequisites + exitp 6 + } + fi + + is_linked_to_busybox gzip && { + echo + echo your system uses default busybox gzip. its several times slower than GNU gzip. + echo ip/host list scripts will run much faster with GNU gzip + echo installer can install GNU gzip but it requires about 100 Kb space + if ask_yes_no N "do you want to install GNU gzip"; then + [ "$UPD" = "0" ] && { + opkg update + UPD=1 + } + opkg install --force-overwrite gzip + fi + } + is_linked_to_busybox sort && { + echo + echo your system uses default busybox sort. its much slower and consumes much more RAM than GNU sort + echo ip/host list scripts will run much faster with GNU sort + echo installer can install GNU sort but it requires about 100 Kb space + if ask_yes_no N "do you want to install GNU sort"; then + [ "$UPD" = "0" ] && { + opkg update + UPD=1 + } + opkg install --force-overwrite coreutils-sort + fi + } + [ "$FSLEEP" = 0 ] && is_linked_to_busybox sleep && { + echo + echo no methods of sub-second sleep were found. + echo if you want to speed up blockcheck install coreutils-sleep. it requires about 40 Kb space + if ask_yes_no N "do you want to install COREUTILS sleep"; then + [ "$UPD" = "0" ] && { + opkg update + UPD=1 + } + opkg install --force-overwrite coreutils-sleep + fsleep_setup + fi + } +} + + + +select_ipv6() +{ + local T=N + + [ "$DISABLE_IPV6" != '1' ] && T=Y + local old6=$DISABLE_IPV6 + echo + if ask_yes_no $T "enable ipv6 support"; then + DISABLE_IPV6=0 + else + DISABLE_IPV6=1 + fi + [ "$old6" != "$DISABLE_IPV6" ] && write_config_var DISABLE_IPV6 +} +select_fwtype() +{ + echo + [ $(get_ram_mb) -le 400 ] && { + echo WARNING ! you are running a low RAM system + echo WARNING ! nft requires lots of RAM to load huge ip sets, much more than ipsets require + echo WARNING ! if you need large lists it may be necessary to fall back to iptables+ipset firewall + } + echo select firewall type : + ask_list FWTYPE "iptables nftables" "$FWTYPE" && write_config_var FWTYPE +} diff --git a/common/ipt.sh b/common/ipt.sh new file mode 100644 index 00000000..c134d82f --- /dev/null +++ b/common/ipt.sh @@ -0,0 +1,472 @@ +std_ports +readonly ipt_connbytes="-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes" + +ipt() +{ + iptables -C "$@" >/dev/null 2>/dev/null || iptables -I "$@" +} +ipta() +{ + iptables -C "$@" >/dev/null 2>/dev/null || iptables -A "$@" +} +ipt_del() +{ + iptables -C "$@" >/dev/null 2>/dev/null && iptables -D "$@" +} +ipt_add_del() +{ + on_off_function ipt ipt_del "$@" +} +ipta_add_del() +{ + on_off_function ipta ipt_del "$@" +} +ipt6() +{ + ip6tables -C "$@" >/dev/null 2>/dev/null || ip6tables -I "$@" +} +ipt6a() +{ + ip6tables -C "$@" >/dev/null 2>/dev/null || ip6tables -A "$@" +} +ipt6_del() +{ + ip6tables -C "$@" >/dev/null 2>/dev/null && ip6tables -D "$@" +} +ipt6_add_del() +{ + on_off_function ipt6 ipt6_del "$@" +} +ipt6a_add_del() +{ + on_off_function ipt6 ipt6a_del "$@" +} + +is_ipt_flow_offload_avail() +{ + # $1 = '' for ipv4, '6' for ipv6 + grep -q FLOWOFFLOAD 2>/dev/null /proc/net/ip$1_tables_targets +} + +filter_apply_port_target() +{ + # $1 - var name of iptables filter + local f + if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then + f="-p tcp -m multiport --dports $HTTP_PORTS_IPT,$HTTPS_PORTS_IPT" + elif [ "$MODE_HTTPS" = "1" ]; then + f="-p tcp -m multiport --dports $HTTPS_PORTS_IPT" + elif [ "$MODE_HTTP" = "1" ]; then + f="-p tcp -m multiport --dports $HTTP_PORTS_IPT" + else + echo WARNING !!! HTTP and HTTPS are both disabled + fi + eval $1="\"\$$1 $f\"" +} +filter_apply_port_target_quic() +{ + # $1 - var name of nftables filter + local f + f="-p udp -m multiport --dports $QUIC_PORTS_IPT" + eval $1="\"\$$1 $f\"" +} +filter_apply_ipset_target4() +{ + # $1 - var name of ipv4 iptables filter + if [ "$MODE_FILTER" = "ipset" ]; then + eval $1="\"\$$1 -m set --match-set zapret dst\"" + fi +} +filter_apply_ipset_target6() +{ + # $1 - var name of ipv6 iptables filter + if [ "$MODE_FILTER" = "ipset" ]; then + eval $1="\"\$$1 -m set --match-set zapret6 dst\"" + fi +} +filter_apply_ipset_target() +{ + # $1 - var name of ipv4 iptables filter + # $2 - var name of ipv6 iptables filter + filter_apply_ipset_target4 $1 + filter_apply_ipset_target6 $2 +} + +reverse_nfqws_rule_stream() +{ + sed -e 's/-o /-i /g' -e 's/--dport /--sport /g' -e 's/--dports /--sports /g' -e 's/ dst$/ src/' -e 's/ dst / src /g' -e 's/--connbytes-dir=original/--connbytes-dir=reply/g' -e "s/-m mark ! --mark $DESYNC_MARK\/$DESYNC_MARK//g" +} +reverse_nfqws_rule() +{ + echo "$@" | reverse_nfqws_rule_stream +} + +prepare_tpws_fw4() +{ + # otherwise linux kernel will treat 127.0.0.0/8 as "martian" ip and refuse routing to it + # NOTE : kernels <3.6 do not have this feature. consider upgrading or change DNAT to REDIRECT and do not bind to 127.0.0.0/8 + + [ "$DISABLE_IPV4" = "1" ] || { + iptables -N input_rule_zapret 2>/dev/null + ipt input_rule_zapret -d $TPWS_LOCALHOST4 -j RETURN + ipta input_rule_zapret -d 127.0.0.0/8 -j DROP + ipt INPUT ! -i lo -j input_rule_zapret + + prepare_route_localnet + } +} +unprepare_tpws_fw4() +{ + [ "$DISABLE_IPV4" = "1" ] || { + unprepare_route_localnet + + ipt_del INPUT ! -i lo -j input_rule_zapret + iptables -F input_rule_zapret 2>/dev/null + iptables -X input_rule_zapret 2>/dev/null + } +} +unprepare_tpws_fw() +{ + unprepare_tpws_fw4 +} + + +ipt_print_op() +{ + if [ "$1" = "1" ]; then + echo "Adding ip$4tables rule for $3 : $2" + else + echo "Deleting ip$4tables rule for $3 : $2" + fi +} + +_fw_tpws4() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - tpws port + # $4 - lan interface names space separated + # $5 - wan interface names space separated + [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { + local i rule + + [ "$1" = 1 ] && prepare_tpws_fw4 + + ipt_print_op $1 "$2" "tpws (port $3)" + + rule="$2 $IPSET_EXCLUDE dst -j DNAT --to $TPWS_LOCALHOST4:$3" + for i in $4 ; do + ipt_add_del $1 PREROUTING -t nat -i $i $rule + done + + rule="-m owner ! --uid-owner $WS_USER $rule" + if [ -n "$5" ]; then + for i in $5; do + ipt_add_del $1 OUTPUT -t nat -o $i $rule + done + else + ipt_add_del $1 OUTPUT -t nat $rule + fi + } +} +_fw_tpws6() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv6 + # $3 - tpws port + # $4 - lan interface names space separated + # $5 - wan interface names space separated + + [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { + local i rule DNAT6 + + ipt_print_op $1 "$2" "tpws (port $3)" 6 + + rule="$2 $IPSET_EXCLUDE6 dst" + for i in $4 ; do + _dnat6_target $i DNAT6 + [ -n "$DNAT6" -a "$DNAT6" != "-" ] && ipt6_add_del $1 PREROUTING -t nat -i $i $rule -j DNAT --to [$DNAT6]:$3 + done + + rule="-m owner ! --uid-owner $WS_USER $rule -j DNAT --to [::1]:$3" + if [ -n "$5" ]; then + for i in $5; do + ipt6_add_del $1 OUTPUT -t nat -o $i $rule + done + else + ipt6_add_del $1 OUTPUT -t nat $rule + fi + } +} +fw_tpws() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - iptable filter for ipv6 + # $4 - tpws port + fw_tpws4 $1 "$2" $4 + fw_tpws6 $1 "$3" $4 +} + + +_fw_nfqws_post4() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - queue number + # $4 - wan interface names space separated + [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { + local i + + ipt_print_op $1 "$2" "nfqws postrouting (qnum $3)" + + rule="$2 $IPSET_EXCLUDE dst -j NFQUEUE --queue-num $3 --queue-bypass" + if [ -n "$4" ] ; then + for i in $4; do + ipt_add_del $1 POSTROUTING -t mangle -o $i $rule + done + else + ipt_add_del $1 POSTROUTING -t mangle $rule + fi + } +} +_fw_nfqws_post6() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv6 + # $3 - queue number + # $4 - wan interface names space separated + [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { + local i + + ipt_print_op $1 "$2" "nfqws postrouting (qnum $3)" 6 + + rule="$2 $IPSET_EXCLUDE6 dst -j NFQUEUE --queue-num $3 --queue-bypass" + if [ -n "$4" ] ; then + for i in $4; do + ipt6_add_del $1 POSTROUTING -t mangle -o $i $rule + done + else + ipt6_add_del $1 POSTROUTING -t mangle $rule + fi + } +} +fw_nfqws_post() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - iptable filter for ipv6 + # $4 - queue number + fw_nfqws_post4 $1 "$2" $4 + fw_nfqws_post6 $1 "$3" $4 +} + +_fw_nfqws_pre4() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - queue number + # $4 - wan interface names space separated + [ "$DISABLE_IPV4" = "1" -o -z "$2" ] || { + local i + + ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)" + + rule="$2 $IPSET_EXCLUDE src -j NFQUEUE --queue-num $3 --queue-bypass" + if [ -n "$4" ] ; then + for i in $4; do + # iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there + ipt_add_del $1 INPUT -t mangle -i $i $rule + ipt_add_del $1 FORWARD -t mangle -i $i $rule + done + else + ipt_add_del $1 INPUT -t mangle $rule + ipt_add_del $1 FORWARD -t mangle $rule + fi + } +} +_fw_nfqws_pre6() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv6 + # $3 - queue number + # $4 - wan interface names space separated + [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || { + local i + + ipt_print_op $1 "$2" "nfqws input+forward (qnum $3)" 6 + + rule="$2 $IPSET_EXCLUDE6 src -j NFQUEUE --queue-num $3 --queue-bypass" + if [ -n "$4" ] ; then + for i in $4; do + # iptables PREROUTING chain is before NAT. not possible to have DNATed ip's there + ipt6_add_del $1 INPUT -t mangle -i $i $rule + ipt6_add_del $1 FORWARD -t mangle -i $i $rule + done + else + ipt6_add_del $1 INPUT -t mangle $rule + ipt6_add_del $1 FORWARD -t mangle $rule + fi + } +} +fw_nfqws_pre() +{ + # $1 - 1 - add, 0 - del + # $2 - iptable filter for ipv4 + # $3 - iptable filter for ipv6 + # $4 - queue number + fw_nfqws_pre4 $1 "$2" $4 + fw_nfqws_pre6 $1 "$3" $4 +} + + +produce_reverse_nfqws_rule() +{ + local rule="$1" + if contains "$rule" "$ipt_connbytes"; then + # autohostlist - need several incoming packets + # autottl - need only one incoming packet + [ "$MODE_FILTER" = autohostlist ] || rule=$(echo "$rule" | sed -re "s/$ipt_connbytes [0-9]+:[0-9]+/$ipt_connbytes 1:1/") + else + local n=1 + [ "$MODE_FILTER" = autohostlist ] && n=$(first_packets_for_mode) + rule="$ipt_connbytes 1:$n $rule" + fi + echo "$rule" | reverse_nfqws_rule_stream +} +fw_reverse_nfqws_rule4() +{ + fw_nfqws_pre4 $1 "$(produce_reverse_nfqws_rule "$2")" $3 +} +fw_reverse_nfqws_rule6() +{ + fw_nfqws_pre6 $1 "$(produce_reverse_nfqws_rule "$2")" $3 +} +fw_reverse_nfqws_rule() +{ + # ensure that modes relying on incoming traffic work + # $1 - 1 - add, 0 - del + # $2 - rule4 + # $3 - rule6 + # $4 - queue number + fw_reverse_nfqws_rule4 $1 "$2" $4 + fw_reverse_nfqws_rule6 $1 "$3" $4 +} + + +zapret_do_firewall_rules_ipt() +{ + local mode="${MODE_OVERRIDE:-$MODE}" + + local first_packet_only="$ipt_connbytes 1:$(first_packets_for_mode)" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + local n f4 f6 qn qns qn6 qns6 + + case "$mode" in + tpws) + if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then + echo both http and https are disabled. not applying redirection. + else + filter_apply_port_target f4 + f6=$f4 + filter_apply_ipset_target f4 f6 + fw_tpws $1 "$f4" "$f6" $TPPORT + fi + ;; + + nfqws) + # quite complex but we need to minimize nfqws processes to save RAM + get_nfqws_qnums qn qns qn6 qns6 + if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn" ] && [ "$qn" = "$qns" ]; then + filter_apply_port_target f4 + f4="$f4 $first_packet_only" + filter_apply_ipset_target4 f4 + fw_nfqws_post4 $1 "$f4 $desync" $qn + fw_reverse_nfqws_rule4 $1 "$f4" $qn + else + if [ -n "$qn" ]; then + f4="-p tcp -m multiport --dports $HTTP_PORTS_IPT" + [ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packet_only" + filter_apply_ipset_target4 f4 + fw_nfqws_post4 $1 "$f4 $desync" $qn + fw_reverse_nfqws_rule4 $1 "$f4" $qn + fi + if [ -n "$qns" ]; then + f4="-p tcp -m multiport --dports $HTTPS_PORTS_IPT $first_packet_only" + filter_apply_ipset_target4 f4 + fw_nfqws_post4 $1 "$f4 $desync" $qns + fw_reverse_nfqws_rule4 $1 "$f4" $qns + fi + fi + if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn6" ] && [ "$qn6" = "$qns6" ]; then + filter_apply_port_target f6 + f6="$f6 $first_packet_only" + filter_apply_ipset_target6 f6 + fw_nfqws_post6 $1 "$f6 $desync" $qn6 + fw_reverse_nfqws_rule6 $1 "$f6" $qn6 + else + if [ -n "$qn6" ]; then + f6="-p tcp -m multiport --dports $HTTP_PORTS_IPT" + [ "$MODE_HTTP_KEEPALIVE" = "1" ] || f6="$f6 $first_packet_only" + filter_apply_ipset_target6 f6 + fw_nfqws_post6 $1 "$f6 $desync" $qn6 + fw_reverse_nfqws_rule6 $1 "$f6" $qn6 + fi + if [ -n "$qns6" ]; then + f6="-p tcp -m multiport --dports $HTTPS_PORTS_IPT $first_packet_only" + filter_apply_ipset_target6 f6 + fw_nfqws_post6 $1 "$f6 $desync" $qns6 + fw_reverse_nfqws_rule6 $1 "$f6" $qns6 + fi + fi + + get_nfqws_qnums_quic qn qn6 + if [ -n "$qn" ]; then + f4= + filter_apply_port_target_quic f4 + f4="$f4 $first_packet_only" + filter_apply_ipset_target4 f4 + fw_nfqws_post4 $1 "$f4 $desync" $qn + fi + if [ -n "$qn6" ]; then + f6= + filter_apply_port_target_quic f6 + f6="$f6 $first_packet_only" + filter_apply_ipset_target6 f6 + fw_nfqws_post6 $1 "$f6 $desync" $qn6 + fi + ;; + custom) + custom_runner zapret_custom_firewall $1 + ;; + esac +} + +zapret_do_firewall_ipt() +{ + # $1 - 1 - add, 0 - del + + if [ "$1" = 1 ]; then + echo Applying iptables + else + echo Clearing iptables + fi + + local mode="${MODE_OVERRIDE:-$MODE}" + + [ "$mode" = "tpws-socks" ] && return 0 + + # always create ipsets. ip_exclude ipset is required + [ "$1" = 1 ] && create_ipset no-update + + zapret_do_firewall_rules_ipt "$@" + + if [ "$1" = 1 ] ; then + existf flow_offloading_exempt && flow_offloading_exempt + else + existf flow_offloading_unexempt && flow_offloading_unexempt + unprepare_tpws_fw + fi + + return 0 +} diff --git a/common/linux_fw.sh b/common/linux_fw.sh new file mode 100644 index 00000000..dbddc650 --- /dev/null +++ b/common/linux_fw.sh @@ -0,0 +1,53 @@ +set_conntrack_liberal_mode() +{ + [ -n "$SKIP_CONNTRACK_LIBERAL_MODE" ] || sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=$1 +} +zapret_do_firewall() +{ + linux_fwtype + + [ "$1" = 1 -a -n "$INIT_FW_PRE_UP_HOOK" ] && $INIT_FW_PRE_UP_HOOK + [ "$1" = 0 -a -n "$INIT_FW_PRE_DOWN_HOOK" ] && $INIT_FW_PRE_DOWN_HOOK + + case "$FWTYPE" in + iptables) + zapret_do_firewall_ipt "$@" + ;; + nftables) + zapret_do_firewall_nft "$@" + ;; + esac + + # russian DPI sends RST,ACK with wrong ACK. + # this is sometimes treated by conntrack as invalid and connbytes fw rules do not pass RST packet to nfqws. + # switch on liberal mode on zapret firewall start and switch off on zapret firewall stop + # this is only required for processing incoming bad RSTs. incoming rules are only applied in autohostlist mode + # calling this after firewall because conntrack module can be not loaded before applying conntrack firewall rules + [ "$MODE_FILTER" = "autohostlist" -a "$MODE" != tpws -a "$MODE" != tpws-socks ] && set_conntrack_liberal_mode $1 + + [ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK + [ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK + + return 0 +} +zapret_apply_firewall() +{ + zapret_do_firewall 1 "$@" +} +zapret_unapply_firewall() +{ + zapret_do_firewall 0 "$@" +} + +first_packets_for_mode() +{ + # autohostlist and autottl modes requires incoming traffic sample + # always use conntrack packet limiter or nfqws will deal with gigabytes + local n + if [ "$MODE_FILTER" = "autohostlist" ]; then + n=$((6+${AUTOHOSTLIST_RETRANS_THRESHOLD:-3})) + else + n=6 + fi + echo $n +} diff --git a/common/linux_iphelper.sh b/common/linux_iphelper.sh new file mode 100644 index 00000000..e22f91ac --- /dev/null +++ b/common/linux_iphelper.sh @@ -0,0 +1,127 @@ +# there's no route_localnet for ipv6 +# the best we can is to route to link local of the incoming interface +# OUTPUT - can DNAT to ::1 +# PREROUTING - can't DNAT to ::1. can DNAT to link local of -i interface or to any global addr +# not a good idea to expose tpws to the world (bind to ::) + + +get_ipv6_linklocal() +{ + # $1 - interface name. if empty - any interface + if exists ip ; then + local dev + [ -n "$1" ] && dev="dev $1" + ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope link.*$/\1/;t;d' | head -n 1 + else + ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Link.*$/\1/;t;d' | head -n 1 + fi +} +get_ipv6_global() +{ + # $1 - interface name. if empty - any interface + if exists ip ; then + local dev + [ -n "$1" ] && dev="dev $1" + ip addr show $dev | sed -e 's/^.*inet6 \([^ ]*\)\/[0-9]* scope global.*$/\1/;t;d' | head -n 1 + else + ifconfig $1 | sed -re 's/^.*inet6 addr: ([^ ]*)\/[0-9]* Scope:Global.*$/\1/;t;d' | head -n 1 + fi +} + +iface_is_up() +{ + # $1 - interface name + [ -f /sys/class/net/$1/operstate ] || return + local state + read state /dev/null +} +nft_list_table() +{ + nft -t list table inet $ZAPRET_NFT_TABLE +} + +nft_create_set() +{ + # $1 - set name + # $2 - params + nft create set inet $ZAPRET_NFT_TABLE $1 "{ $2 }" 2>/dev/null +} +nft_del_set() +{ + # $1 - set name + nft delete set inet $ZAPRET_NFT_TABLE $1 +} +nft_flush_set() +{ + # $1 - set name + nft flush set inet $ZAPRET_NFT_TABLE $1 +} +nft_set_exists() +{ + # $1 - set name + nft -t list set inet $ZAPRET_NFT_TABLE $1 2>/dev/null >/dev/null +} +nft_flush_chain() +{ + # $1 - chain name + nft flush chain inet $ZAPRET_NFT_TABLE $1 +} + +nft_del_all_chains_from_table() +{ + # $1 - table_name with or without family + + # delete all chains with possible references to each other + # cannot just delete all in the list because of references + # avoid infinite loops + local chains deleted=1 error=1 + while [ -n "$deleted" -a -n "$error" ]; do + chains=$(nft -t list table $1 2>/dev/null | sed -nre "s/^[ ]*chain ([^ ]+) \{/\1/p" | xargs) + [ -n "$chains" ] || break + deleted= + error= + for chain in $chains; do + if nft delete chain $1 $chain 2>/dev/null; then + deleted=1 + else + error=1 + fi + done + done +} + +nft_create_chains() +{ +cat << EOF | nft -f - + add chain inet $ZAPRET_NFT_TABLE dnat_output { type nat hook output priority -101; } + flush chain inet $ZAPRET_NFT_TABLE dnat_output + add chain inet $ZAPRET_NFT_TABLE dnat_pre { type nat hook prerouting priority -101; } + flush chain inet $ZAPRET_NFT_TABLE dnat_pre + add chain inet $ZAPRET_NFT_TABLE forward { type filter hook forward priority -1; } + flush chain inet $ZAPRET_NFT_TABLE forward + add chain inet $ZAPRET_NFT_TABLE input { type filter hook input priority -1; } + flush chain inet $ZAPRET_NFT_TABLE input + add chain inet $ZAPRET_NFT_TABLE flow_offload + flush chain inet $ZAPRET_NFT_TABLE flow_offload + add chain inet $ZAPRET_NFT_TABLE localnet_protect + flush chain inet $ZAPRET_NFT_TABLE localnet_protect + add rule inet $ZAPRET_NFT_TABLE localnet_protect ip daddr $TPWS_LOCALHOST4 return comment "route_localnet allow access to tpws" + add rule inet $ZAPRET_NFT_TABLE localnet_protect ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection" + add rule inet $ZAPRET_NFT_TABLE input iif != lo jump localnet_protect + add chain inet $ZAPRET_NFT_TABLE postrouting { type filter hook postrouting priority 99; } + flush chain inet $ZAPRET_NFT_TABLE postrouting + add chain inet $ZAPRET_NFT_TABLE postnat { type filter hook postrouting priority 101; } + flush chain inet $ZAPRET_NFT_TABLE postnat + add chain inet $ZAPRET_NFT_TABLE prerouting { type filter hook prerouting priority -99; } + flush chain inet $ZAPRET_NFT_TABLE prerouting + add chain inet $ZAPRET_NFT_TABLE prenat { type filter hook prerouting priority -101; } + flush chain inet $ZAPRET_NFT_TABLE prenat + add chain inet $ZAPRET_NFT_TABLE predefrag { type filter hook output priority -401; } + flush chain inet $ZAPRET_NFT_TABLE predefrag + add chain inet $ZAPRET_NFT_TABLE predefrag_nfqws + flush chain inet $ZAPRET_NFT_TABLE predefrag_nfqws + add rule inet $ZAPRET_NFT_TABLE predefrag mark and $DESYNC_MARK !=0 jump predefrag_nfqws comment "nfqws generated : avoid drop by INVALID conntrack state" + add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws mark and $DESYNC_MARK_POSTNAT !=0 notrack comment "postnat traffic" + add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws ip frag-off != 0 notrack comment "ipfrag" + add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws exthdr frag exists notrack comment "ipfrag" + add rule inet $ZAPRET_NFT_TABLE predefrag_nfqws tcp flags ! syn,rst,ack notrack comment "datanoack" + add set inet $ZAPRET_NFT_TABLE lanif { type ifname; } + add set inet $ZAPRET_NFT_TABLE wanif { type ifname; } + add set inet $ZAPRET_NFT_TABLE wanif6 { type ifname; } + add map inet $ZAPRET_NFT_TABLE link_local { type ifname : ipv6_addr; } +EOF + [ -n "$POSTNAT_ALL" ] && { + nft_flush_chain predefrag_nfqws + nft_add_rule predefrag_nfqws notrack comment \"do not track nfqws generated packets to avoid nat tampering and defragmentation\" + } +} +nft_del_chains() +{ + # do not delete all chains because of additional user hooks + # they must be inside zapret table to use nfsets + +cat << EOF | nft -f - 2>/dev/null + delete chain inet $ZAPRET_NFT_TABLE dnat_output + delete chain inet $ZAPRET_NFT_TABLE dnat_pre + delete chain inet $ZAPRET_NFT_TABLE forward + delete chain inet $ZAPRET_NFT_TABLE input + delete chain inet $ZAPRET_NFT_TABLE postrouting + delete chain inet $ZAPRET_NFT_TABLE postnat + delete chain inet $ZAPRET_NFT_TABLE prerouting + delete chain inet $ZAPRET_NFT_TABLE prenat + delete chain inet $ZAPRET_NFT_TABLE predefrag + delete chain inet $ZAPRET_NFT_TABLE predefrag_nfqws + delete chain inet $ZAPRET_NFT_TABLE flow_offload + delete chain inet $ZAPRET_NFT_TABLE localnet_protect +EOF +# unfortunately this approach breaks udp desync of the connection initiating packet (new, first one) +# delete chain inet $ZAPRET_NFT_TABLE predefrag +} +nft_del_flowtable() +{ + nft delete flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null +} +nft_create_or_update_flowtable() +{ + # $1 = flags ('offload' for hw offload) + # $2,$3,$4,... - interfaces + # can be called multiple times to add interfaces. interfaces can only be added , not removed + local flags=$1 devices makelist + shift + # warning ! nft versions at least up to 1.0.1 do not allow interface names starting with digit in flowtable and do not allow quoting + # warning ! openwrt fixes this in post-21.x snapshots with special nft patch + # warning ! in traditional linux distros nft is unpatched and will fail with quoted interface definitions if unfixed + [ -n "$flags" ] && flags="flags $flags;" + for makelist in make_quoted_comma_list make_comma_list; do + $makelist devices "$@" + [ -n "$devices" ] && devices="devices={$devices};" + nft add flowtable inet $ZAPRET_NFT_TABLE ft "{ hook ingress priority -1; $flags $devices }" && break + done +} +nft_flush_ifsets() +{ +cat << EOF | nft -f - 2>/dev/null + flush set inet $ZAPRET_NFT_TABLE lanif + flush set inet $ZAPRET_NFT_TABLE wanif + flush set inet $ZAPRET_NFT_TABLE wanif6 + flush map inet $ZAPRET_NFT_TABLE link_local +EOF +} +nft_flush_link_local() +{ + nft flush map inet $ZAPRET_NFT_TABLE link_local 2>/dev/null +} +nft_list_ifsets() +{ + nft list set inet $ZAPRET_NFT_TABLE lanif + nft list set inet $ZAPRET_NFT_TABLE wanif + nft list set inet $ZAPRET_NFT_TABLE wanif6 + nft list map inet $ZAPRET_NFT_TABLE link_local + nft list flowtable inet $ZAPRET_NFT_TABLE ft 2>/dev/null +} + +nft_create_firewall() +{ + nft_create_table + nft_del_flowtable + nft_flush_link_local + nft_create_chains +} +nft_del_firewall() +{ + nft_del_chains + nft_del_flowtable + nft_flush_link_local + # leave ifsets and ipsets because they may be used by custom rules +} + +nft_add_rule() +{ + # $1 - chain + # $2,$3,... - rule(s) + local chain="$1" + shift + nft add rule inet $ZAPRET_NFT_TABLE $chain "$@" +} +nft_add_set_element() +{ + # $1 - set or map name + # $2 - element + [ -z "$2" ] || nft add element inet $ZAPRET_NFT_TABLE $1 "{ $2 }" +} +nft_add_set_elements() +{ + # $1 - set or map name + # $2,$3,... - element(s) + local set="$1" elements + shift + make_comma_list elements "$@" + nft_add_set_element $set "$elements" +} +nft_reverse_nfqws_rule() +{ + echo "$@" | sed -e 's/oifname /iifname /g' -e 's/dport /sport /g' -e 's/daddr /saddr /g' -e 's/ct original /ct reply /g' -e "s/mark and $DESYNC_MARK == 0//g" +} +nft_clean_nfqws_rule() +{ + echo "$@" | sed -e "s/mark and $DESYNC_MARK == 0//g" -e "s/oifname @wanif6//g" -e "s/oifname @wanif//g" +} +nft_add_nfqws_flow_exempt_rule() +{ + # $1 - rule (must be all filters in one var) + nft_add_rule flow_offload $(nft_clean_nfqws_rule $1) return comment \"direct flow offloading exemption\" + # do not need this because of oifname @wanif/@wanif6 filter in forward chain + #nft_add_rule flow_offload $(nft_reverse_nfqws_rule $1) return comment \"reverse flow offloading exemption\" +} +nft_add_flow_offload_exemption() +{ + # "$1" - rule for ipv4 + # "$2" - rule for ipv6 + # "$3" - comment + [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || nft_add_rule flow_offload oifname @wanif $1 ip daddr != @nozapret return comment \"$3\" + [ "$DISABLE_IPV6" = "1" -o -z "$2" ] || nft_add_rule flow_offload oifname @wanif6 $2 ip6 daddr != @nozapret6 return comment \"$3\" +} + +nft_hw_offload_supported() +{ + # $1,$2,... - interface names + local devices res=1 + make_quoted_comma_list devices "$@" + [ -n "$devices" ] && devices="devices={$devices};" + nft add table ${ZAPRET_NFT_TABLE}_test && nft add flowtable ${ZAPRET_NFT_TABLE}_test ft "{ flags offload; $devices }" 2>/dev/null && res=0 + nft delete table ${ZAPRET_NFT_TABLE}_test 2>/dev/null + return $res +} + +nft_hw_offload_find_supported() +{ + # $1,$2,... - interface names + local supported_list + while [ -n "$1" ]; do + nft_hw_offload_supported "$1" && append_separator_list supported_list ' ' '' "$1" + shift + done + echo $supported_list +} + +nft_apply_flow_offloading() +{ + # ft can be absent + nft_add_rule flow_offload meta l4proto "{ tcp, udp }" flow add @ft 2>/dev/null && { + nft_add_rule flow_offload meta l4proto "{ tcp, udp }" counter comment \"if offload works here must not be too much traffic\" + # allow only outgoing packets to initiate flow offload + nft_add_rule forward oifname @wanif jump flow_offload + nft_add_rule forward oifname @wanif6 jump flow_offload + } +} + + + +nft_filter_apply_port_target() +{ + # $1 - var name of nftables filter + local f + if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then + f="tcp dport {$HTTP_PORTS,$HTTPS_PORTS}" + elif [ "$MODE_HTTPS" = "1" ]; then + f="tcp dport {$HTTPS_PORTS}" + elif [ "$MODE_HTTP" = "1" ]; then + f="tcp dport {$HTTP_PORTS}" + else + echo WARNING !!! HTTP and HTTPS are both disabled + fi + eval $1="\"\$$1 $f\"" +} +nft_filter_apply_port_target_quic() +{ + # $1 - var name of nftables filter + local f + f="udp dport {$QUIC_PORTS}" + eval $1="\"\$$1 $f\"" +} +nft_filter_apply_ipset_target4() +{ + # $1 - var name of ipv4 nftables filter + if [ "$MODE_FILTER" = "ipset" ]; then + eval $1="\"\$$1 ip daddr @zapret\"" + fi +} +nft_filter_apply_ipset_target6() +{ + # $1 - var name of ipv6 nftables filter + if [ "$MODE_FILTER" = "ipset" ]; then + eval $1="\"\$$1 ip6 daddr @zapret6\"" + fi +} +nft_filter_apply_ipset_target() +{ + # $1 - var name of ipv4 nftables filter + # $2 - var name of ipv6 nftables filter + nft_filter_apply_ipset_target4 $1 + nft_filter_apply_ipset_target6 $2 +} + + +nft_script_add_ifset_element() +{ + # $1 - set name + # $2 - space separated elements + local elements + [ -n "$2" ] && { + make_quoted_comma_list elements $2 + script="${script} +add element inet $ZAPRET_NFT_TABLE $1 { $elements }" + } +} +nft_fill_ifsets() +{ + # $1 - space separated lan interface names + # $2 - space separated wan interface names + # $3 - space separated wan6 interface names + # 4,5,6 is needed for pppoe+openwrt case. looks like it's not easily possible to resolve ethernet device behind a pppoe interface + # $4 - space separated lan physical interface names (optional) + # $5 - space separated wan physical interface names (optional) + # $6 - space separated wan6 physical interface names (optional) + + local script i j ALLDEVS devs + + # if large sets exist nft works very ineffectively + # looks like it analyzes the whole table blob to find required data pieces + # calling all in one shot helps not to waste cpu time many times + + script="flush set inet $ZAPRET_NFT_TABLE wanif +flush set inet $ZAPRET_NFT_TABLE wanif6 +flush set inet $ZAPRET_NFT_TABLE lanif" + + [ "$DISABLE_IPV4" = "1" ] || nft_script_add_ifset_element wanif "$2" + [ "$DISABLE_IPV6" = "1" ] || nft_script_add_ifset_element wanif6 "$3" + nft_script_add_ifset_element lanif "$1" + + echo "$script" | nft -f - + + case "$FLOWOFFLOAD" in + software) + ALLDEVS=$(unique $1 $2 $3) + # unbound flowtable may cause error in older nft version + nft_create_or_update_flowtable '' $ALLDEVS 2>/dev/null + ;; + hardware) + ALLDEVS=$(unique $1 $2 $3 $4 $5 $6) + # first create unbound flowtable. may cause error in older nft version + nft_create_or_update_flowtable 'offload' 2>/dev/null + # then add elements. some of them can cause error because unsupported + for i in $ALLDEVS; do + if nft_hw_offload_supported $i; then + nft_create_or_update_flowtable 'offload' $i + else + # bridge members must be added instead of the bridge itself + # some members may not support hw offload. example : lan1 lan2 lan3 support, wlan0 wlan1 - not + devs=$(resolve_lower_devices $i) + for j in $devs; do + # do not display error if addition failed + nft_create_or_update_flowtable 'offload' $j 2>/dev/null + done + fi + done + ;; + esac +} + +nft_only() +{ + linux_fwtype + + case "$FWTYPE" in + nftables) + "$@" + ;; + esac +} + + +nft_print_op() +{ + echo "Adding nftables ipv$3 rule for $2 : $1" +} +_nft_fw_tpws4() +{ + # $1 - filter ipv4 + # $2 - tpws port + # $3 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" + nft_print_op "$filter" "tpws (port $2)" 4 + nft_add_rule dnat_output skuid != $WS_USER ${3:+oifname @wanif }$filter ip daddr != @nozapret dnat ip to $TPWS_LOCALHOST4:$port + nft_add_rule dnat_pre iifname @lanif $filter ip daddr != @nozapret dnat ip to $TPWS_LOCALHOST4:$port + prepare_route_localnet + } +} +_nft_fw_tpws6() +{ + # $1 - filter ipv6 + # $2 - tpws port + # $3 - lan interface names space separated + # $4 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" DNAT6 i + nft_print_op "$filter" "tpws (port $port)" 6 + nft_add_rule dnat_output skuid != $WS_USER ${4:+oifname @wanif6 }$filter ip6 daddr != @nozapret6 dnat ip6 to [::1]:$port + [ -n "$3" ] && { + nft_add_rule dnat_pre $filter ip6 daddr != @nozapret6 dnat ip6 to iifname map @link_local:$port + for i in $3; do + _dnat6_target $i DNAT6 + # can be multiple tpws processes on different ports + [ -n "$DNAT6" -a "$DNAT6" != '-' ] && nft_add_set_element link_local "$i : $DNAT6" + done + } + } +} +nft_fw_tpws() +{ + # $1 - filter ipv4 + # $2 - filter ipv6 + # $3 - tpws port + + nft_fw_tpws4 "$1" $3 + nft_fw_tpws6 "$2" $3 +} +is_postnat() +{ + [ "$POSTNAT" != 0 -o "$POSTNAT_ALL" = 1 ] +} +get_postchain() +{ + if is_postnat ; then + echo -n postnat + else + echo -n postrouting + fi +} +get_prechain() +{ + if is_postnat ; then + echo -n prenat + else + echo -n prerouting + fi +} +_nft_fw_nfqws_post4() +{ + # $1 - filter ipv4 + # $2 - queue number + # $3 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" rule chain=$(get_postchain) setmark + nft_print_op "$filter" "nfqws postrouting (qnum $port)" 4 + rule="${3:+oifname @wanif }$filter ip daddr != @nozapret" + is_postnat && setmark="meta mark set meta mark or $DESYNC_MARK_POSTNAT" + nft_add_rule $chain $rule $setmark queue num $port bypass + nft_add_nfqws_flow_exempt_rule "$rule" + } +} +_nft_fw_nfqws_post6() +{ + # $1 - filter ipv6 + # $2 - queue number + # $3 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" rule chain=$(get_postchain) setmark + nft_print_op "$filter" "nfqws postrouting (qnum $port)" 6 + rule="${3:+oifname @wanif6 }$filter ip6 daddr != @nozapret6" + is_postnat && setmark="meta mark set meta mark or $DESYNC_MARK_POSTNAT" + nft_add_rule $chain $rule $setmark queue num $port bypass + nft_add_nfqws_flow_exempt_rule "$rule" + } +} +nft_fw_nfqws_post() +{ + # $1 - filter ipv4 + # $2 - filter ipv6 + # $3 - queue number + + nft_fw_nfqws_post4 "$1" $3 + nft_fw_nfqws_post6 "$2" $3 +} + +_nft_fw_nfqws_pre4() +{ + # $1 - filter ipv4 + # $2 - queue number + # $3 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV4" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" rule + nft_print_op "$filter" "nfqws prerouting (qnum $port)" 4 + rule="${3:+iifname @wanif }$filter ip saddr != @nozapret" + nft_add_rule $(get_prechain) $rule queue num $port bypass + } +} +_nft_fw_nfqws_pre6() +{ + # $1 - filter ipv6 + # $2 - queue number + # $3 - not-empty if wan interface filtering required + + [ "$DISABLE_IPV6" = "1" -o -z "$1" ] || { + local filter="$1" port="$2" rule + nft_print_op "$filter" "nfqws prerouting (qnum $port)" 6 + rule="${3:+iifname @wanif6 }$filter ip6 saddr != @nozapret6" + nft_add_rule $(get_prechain) $rule queue num $port bypass + } +} +nft_fw_nfqws_pre() +{ + # $1 - filter ipv4 + # $2 - filter ipv6 + # $3 - queue number + + nft_fw_nfqws_pre4 "$1" $3 + nft_fw_nfqws_pre6 "$2" $3 +} + +nft_fw_nfqws_both4() +{ + # $1 - filter ipv4 + # $2 - queue number + nft_fw_nfqws_post4 "$@" + nft_fw_nfqws_pre4 "$(nft_reverse_nfqws_rule $1)" $2 +} +nft_fw_nfqws_both6() +{ + # $1 - filter ipv6 + # $2 - queue number + nft_fw_nfqws_post6 "$@" + nft_fw_nfqws_pre6 "$(nft_reverse_nfqws_rule $1)" $2 +} +nft_fw_nfqws_both() +{ + # $1 - filter ipv4 + # $2 - filter ipv6 + # $3 - queue number + nft_fw_nfqws_both4 "$1" "$3" + nft_fw_nfqws_both6 "$2" "$3" +} + +zapret_reload_ifsets() +{ + nft_only nft_create_table ; nft_fill_ifsets_overload + return 0 +} +zapret_list_ifsets() +{ + nft_only nft_list_ifsets + return 0 +} +zapret_list_table() +{ + nft_only nft_list_table + return 0 +} + + + +nft_produce_reverse_nfqws_rule() +{ + local rule="$1" + if contains "$rule" "$nft_connbytes "; then + # autohostlist - need several incoming packets + # autottl - need only one incoming packet + [ "$MODE_FILTER" = autohostlist ] || rule=$(echo "$rule" | sed -re "s/$nft_connbytes [0-9]+-[0-9]+/$nft_connbytes 1/") + else + # old nft does not swallow 1-1 + local range=1 + [ "$MODE_FILTER" = autohostlist ] && range=$(first_packets_for_mode) + [ "$range" = 1 ] || range="1-$range" + rule="$nft_connbytes $range $rule" + fi + nft_reverse_nfqws_rule $rule +} +nft_fw_reverse_nfqws_rule4() +{ + nft_fw_nfqws_pre4 "$(nft_produce_reverse_nfqws_rule "$1")" $2 +} +nft_fw_reverse_nfqws_rule6() +{ + nft_fw_nfqws_pre6 "$(nft_produce_reverse_nfqws_rule "$1")" $2 +} +nft_fw_reverse_nfqws_rule() +{ + # ensure that modes relying on incoming traffic work + # $1 - rule4 + # $2 - rule6 + # $3 - queue number + nft_fw_reverse_nfqws_rule4 "$1" $3 + nft_fw_reverse_nfqws_rule6 "$2" $3 +} + +zapret_apply_firewall_rules_nft() +{ + local mode="${MODE_OVERRIDE:-$MODE}" + + local first_packets_only + local desync="mark and $DESYNC_MARK == 0" + local f4 f6 qn qns qn6 qns6 + + first_packets_only="$nft_connbytes 1-$(first_packets_for_mode)" + + case "$mode" in + tpws) + if [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ]; then + echo both http and https are disabled. not applying redirection. + else + nft_filter_apply_port_target f4 + f6=$f4 + nft_filter_apply_ipset_target f4 f6 + nft_fw_tpws "$f4" "$f6" $TPPORT + fi + ;; + nfqws) + local POSTNAT_SAVE=$POSTNAT + + POSTNAT=1 + # quite complex but we need to minimize nfqws processes to save RAM + get_nfqws_qnums qn qns qn6 qns6 + if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn" ] && [ "$qn" = "$qns" ]; then + nft_filter_apply_port_target f4 + f4="$f4 $first_packets_only" + nft_filter_apply_ipset_target4 f4 + nft_fw_nfqws_post4 "$f4 $desync" $qn + nft_fw_reverse_nfqws_rule4 "$f4" $qn + else + if [ -n "$qn" ]; then + f4="tcp dport {$HTTP_PORTS}" + [ "$MODE_HTTP_KEEPALIVE" = "1" ] || f4="$f4 $first_packets_only" + nft_filter_apply_ipset_target4 f4 + nft_fw_nfqws_post4 "$f4 $desync" $qn + nft_fw_reverse_nfqws_rule4 "$f4" $qn + fi + if [ -n "$qns" ]; then + f4="tcp dport {$HTTPS_PORTS} $first_packets_only" + nft_filter_apply_ipset_target4 f4 + nft_fw_nfqws_post4 "$f4 $desync" $qns + nft_fw_reverse_nfqws_rule4 "$f4" $qns + fi + fi + if [ "$MODE_HTTP_KEEPALIVE" != "1" ] && [ -n "$qn6" ] && [ "$qn6" = "$qns6" ]; then + nft_filter_apply_port_target f6 + f6="$f6 $first_packets_only" + nft_filter_apply_ipset_target6 f6 + nft_fw_nfqws_post6 "$f6 $desync" $qn6 + nft_fw_reverse_nfqws_rule6 "$f6" $qn6 + else + if [ -n "$qn6" ]; then + f6="tcp dport {$HTTP_PORTS}" + [ "$MODE_HTTP_KEEPALIVE" = "1" ] || f6="$f6 $first_packets_only" + nft_filter_apply_ipset_target6 f6 + nft_fw_nfqws_post6 "$f6 $desync" $qn6 + nft_fw_reverse_nfqws_rule6 "$f6" $qn6 + fi + if [ -n "$qns6" ]; then + f6="tcp dport {$HTTPS_PORTS} $first_packets_only" + nft_filter_apply_ipset_target6 f6 + nft_fw_nfqws_post6 "$f6 $desync" $qns6 + nft_fw_reverse_nfqws_rule6 "$f6" $qns6 + fi + fi + + get_nfqws_qnums_quic qn qn6 + if [ -n "$qn" ]; then + f4= + nft_filter_apply_port_target_quic f4 + f4="$f4 $first_packets_only" + nft_filter_apply_ipset_target4 f4 + nft_fw_nfqws_post4 "$f4 $desync" $qn + fi + if [ -n "$qn6" ]; then + f6= + nft_filter_apply_port_target_quic f6 + f6="$f6 $first_packets_only" + nft_filter_apply_ipset_target6 f6 + nft_fw_nfqws_post6 "$f6 $desync" $qn6 + fi + + POSTNAT=$POSTNAT_SAVE + ;; + custom) + custom_runner zapret_custom_firewall_nft + ;; + esac +} + +zapret_apply_firewall_nft() +{ + echo Applying nftables + + local mode="${MODE_OVERRIDE:-$MODE}" + + [ "$mode" = "tpws-socks" ] && return 0 + + create_ipset no-update + nft_create_firewall + nft_fill_ifsets_overload + + zapret_apply_firewall_rules_nft + + [ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && nft_apply_flow_offloading + + return 0 +} +zapret_unapply_firewall_nft() +{ + echo Clearing nftables + + unprepare_route_localnet + nft_del_firewall + return 0 +} +zapret_do_firewall_nft() +{ + # $1 - 1 - add, 0 - del + + if [ "$1" = 0 ] ; then + zapret_unapply_firewall_nft + else + zapret_apply_firewall_nft + fi + + return 0 +} diff --git a/common/pf.sh b/common/pf.sh new file mode 100644 index 00000000..4516a002 --- /dev/null +++ b/common/pf.sh @@ -0,0 +1,285 @@ +PF_MAIN="/etc/pf.conf" +PF_ANCHOR_DIR=/etc/pf.anchors +PF_ANCHOR_ZAPRET="$PF_ANCHOR_DIR/zapret" +PF_ANCHOR_ZAPRET_V4="$PF_ANCHOR_DIR/zapret-v4" +PF_ANCHOR_ZAPRET_V6="$PF_ANCHOR_DIR/zapret-v6" + +std_ports + +pf_anchor_root_reload() +{ + echo reloading PF root anchor + pfctl -qf "$PF_MAIN" +} + +pf_anchor_root() +{ + local patch + [ -f "$PF_MAIN" ] && { + grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" || { + echo patching rdr-anchor in $PF_MAIN + patch=1 + sed -i '' -e '/^rdr-anchor "com\.apple\/\*"$/i \ +rdr-anchor "zapret" +' $PF_MAIN + } + grep -q '^anchor "zapret"$' "$PF_MAIN" || { + echo patching anchor in $PF_MAIN + patch=1 + sed -i '' -e '/^anchor "com\.apple\/\*"$/i \ +anchor "zapret" +' $PF_MAIN + } + grep -q "^set limit table-entries" "$PF_MAIN" || { + echo patching table-entries limit + patch=1 + sed -i '' -e '/^scrub-anchor "com\.apple\/\*"$/i \ +set limit table-entries 5000000 +' $PF_MAIN + } + + grep -q '^anchor "zapret"$' "$PF_MAIN" && + grep -q '^rdr-anchor "zapret"$' "$PF_MAIN" && + grep -q '^set limit table-entries' "$PF_MAIN" && { + if [ -n "$patch" ]; then + echo successfully patched $PF_MAIN + pf_anchor_root_reload + else + echo successfully checked zapret anchors in $PF_MAIN + fi + return 0 + } + } + echo ---------------------------------- + echo Automatic $PF_MAIN patching failed. You must apply root anchors manually in your PF config. + echo rdr-anchor \"zapret\" + echo anchor \"zapret\" + echo ---------------------------------- + return 1 +} +pf_anchor_root_del() +{ + sed -i '' -e '/^anchor "zapret"$/d' -e '/^rdr-anchor "zapret"$/d' -e '/^set limit table-entries/d' "$PF_MAIN" +} + +pf_anchor_zapret() +{ + [ "$DISABLE_IPV4" = "1" ] || { + if [ -f "$ZIPLIST_EXCLUDE" ]; then + echo "table persist file \"$ZIPLIST_EXCLUDE\"" + else + echo "table persist" + fi + } + [ "$DISABLE_IPV6" = "1" ] || { + if [ -f "$ZIPLIST_EXCLUDE6" ]; then + echo "table persist file \"$ZIPLIST_EXCLUDE6\"" + else + echo "table persist" + fi + } + [ "$DISABLE_IPV4" = "1" ] || echo "rdr-anchor \"/zapret-v4\" inet to !" + [ "$DISABLE_IPV6" = "1" ] || echo "rdr-anchor \"/zapret-v6\" inet6 to !" + [ "$DISABLE_IPV4" = "1" ] || echo "anchor \"/zapret-v4\" inet to !" + [ "$DISABLE_IPV6" = "1" ] || echo "anchor \"/zapret-v6\" inet6 to !" +} +pf_anchor_zapret_tables() +{ + # $1 - variable to receive applied table names + # $2/$3 $4/$5 ... table_name/table_file + local tblv=$1 + local _tbl + + shift + [ "$MODE_FILTER" = "ipset" ] && + { + while [ -n "$1" ] && [ -n "$2" ] ; do + [ -f "$2" ] && { + echo "table <$1> file \"$2\"" + _tbl="$_tbl<$1> " + } + shift + shift + done + } + [ -n "$_tbl" ] || _tbl="any" + + eval $tblv="\"\$_tbl\"" +} +pf_nat_reorder_rules() +{ + # this is dirty hack to move rdr above route-to and remove route-to dups + sort -rfu +} +pf_anchor_port_target() +{ + if [ "$MODE_HTTP" = "1" ] && [ "$MODE_HTTPS" = "1" ]; then + echo "{$HTTP_PORTS_IPT,$HTTPS_PORTS_IPT}" + elif [ "$MODE_HTTPS" = "1" ]; then + echo "{$HTTPS_PORTS_IPT}" + elif [ "$MODE_HTTP" = "1" ]; then + echo "{$HTTP_PORTS_IPT}" + fi +} + +pf_anchor_zapret_v4_tpws() +{ + # $1 - tpws listen port + # $2 - rdr ports. defaults are used if empty + + local rule port + + if [ -n "$2" ]; then + port="{$2}" + else + port=$(pf_anchor_port_target) + fi + + for lan in $IFACE_LAN; do + for t in $tbl; do + echo "rdr on $lan inet proto tcp from any to $t port $port -> 127.0.0.1 port $1" + done + done + echo "rdr on lo0 inet proto tcp from !127.0.0.0/8 to any port $port -> 127.0.0.1 port $1" + for t in $tbl; do + rule="route-to (lo0 127.0.0.1) inet proto tcp from !127.0.0.0/8 to $t port $port user { >root }" + if [ -n "$IFACE_WAN" ] ; then + for wan in $IFACE_WAN; do + echo "pass out on $wan $rule" + done + else + echo "pass out $rule" + fi + done +} + +pf_anchor_zapret_v4() +{ + local tbl port + [ "$DISABLE_IPV4" = "1" ] || { + case "${MODE_OVERRIDE:-$MODE}" in + tpws) + [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ] && return + pf_anchor_zapret_tables tbl zapret-user "$ZIPLIST_USER" zapret "$ZIPLIST" + pf_anchor_zapret_v4_tpws $TPPORT + ;; + custom) + pf_anchor_zapret_tables tbl zapret-user "$ZIPLIST_USER" zapret "$ZIPLIST" + custom_runner zapret_custom_firewall_v4 | pf_nat_reorder_rules + ;; + esac + } +} +pf_anchor_zapret_v6_tpws() +{ + # $1 - tpws listen port + # $2 - rdr ports. defaults are used if empty + + local rule LL_LAN port + + if [ -n "$2" ]; then + port="{$2}" + else + port=$(pf_anchor_port_target) + fi + + # LAN link local is only for router + for lan in $IFACE_LAN; do + LL_LAN=$(get_ipv6_linklocal $lan) + [ -n "$LL_LAN" ] && { + for t in $tbl; do + echo "rdr on $lan inet6 proto tcp from any to $t port $port -> $LL_LAN port $1" + done + } + done + echo "rdr on lo0 inet6 proto tcp from !::1 to any port $port -> fe80::1 port $1" + for t in $tbl; do + rule="route-to (lo0 fe80::1) inet6 proto tcp from !::1 to $t port $port user { >root }" + if [ -n "${IFACE_WAN6:-$IFACE_WAN}" ] ; then + for wan in ${IFACE_WAN6:-$IFACE_WAN}; do + echo "pass out on $wan $rule" + done + else + echo "pass out $rule" + fi + done +} +pf_anchor_zapret_v6() +{ + local tbl port + + [ "$DISABLE_IPV6" = "1" ] || { + case "${MODE_OVERRIDE:-$MODE}" in + tpws) + [ ! "$MODE_HTTP" = "1" ] && [ ! "$MODE_HTTPS" = "1" ] && return + pf_anchor_zapret_tables tbl zapret6-user "$ZIPLIST_USER6" zapret6 "$ZIPLIST6" + pf_anchor_zapret_v6_tpws $TPPORT + ;; + custom) + pf_anchor_zapret_tables tbl zapret6-user "$ZIPLIST_USER6" zapret6 "$ZIPLIST6" + custom_runner zapret_custom_firewall_v6 | pf_nat_reorder_rules + ;; + esac + } +} + +pf_anchors_create() +{ + wait_lan_ll + pf_anchor_zapret >"$PF_ANCHOR_ZAPRET" + pf_anchor_zapret_v4 >"$PF_ANCHOR_ZAPRET_V4" + pf_anchor_zapret_v6 >"$PF_ANCHOR_ZAPRET_V6" +} +pf_anchors_del() +{ + rm -f "$PF_ANCHOR_ZAPRET" "$PF_ANCHOR_ZAPRET_V4" "$PF_ANCHOR_ZAPRET_V6" +} +pf_anchors_load() +{ + echo loading zapret anchor from "$PF_ANCHOR_ZAPRET" + pfctl -qa zapret -f "$PF_ANCHOR_ZAPRET" || { + echo error loading zapret anchor + return 1 + } + if [ "$DISABLE_IPV4" = "1" ]; then + echo clearing zapret-v4 anchor + pfctl -qa zapret-v4 -F all 2>/dev/null + else + echo loading zapret-v4 anchor from "$PF_ANCHOR_ZAPRET_V4" + pfctl -qa zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4" || { + echo error loading zapret-v4 anchor + return 1 + } + fi + if [ "$DISABLE_IPV6" = "1" ]; then + echo clearing zapret-v6 anchor + pfctl -qa zapret-v6 -F all 2>/dev/null + else + echo loading zapret-v6 anchor from "$PF_ANCHOR_ZAPRET_V6" + pfctl -qa zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6" || { + echo error loading zapret-v6 anchor + return 1 + } + fi + echo successfully loaded PF anchors + return 0 +} +pf_anchors_clear() +{ + echo clearing zapret anchors + pfctl -qa zapret-v4 -F all 2>/dev/null + pfctl -qa zapret-v6 -F all 2>/dev/null + pfctl -qa zapret -F all 2>/dev/null +} +pf_enable() +{ + echo enabling PF + pfctl -qe +} +pf_table_reload() +{ + echo reloading zapret tables + [ "$DISABLE_IPV4" = "1" ] || pfctl -qTl -a zapret-v4 -f "$PF_ANCHOR_ZAPRET_V4" + [ "$DISABLE_IPV6" = "1" ] || pfctl -qTl -a zapret-v6 -f "$PF_ANCHOR_ZAPRET_V6" + pfctl -qTl -a zapret -f "$PF_ANCHOR_ZAPRET" +} diff --git a/common/queue.sh b/common/queue.sh new file mode 100644 index 00000000..324129ff --- /dev/null +++ b/common/queue.sh @@ -0,0 +1,85 @@ +apply_unspecified_desync_modes() +{ + NFQWS_OPT_DESYNC_HTTP="${NFQWS_OPT_DESYNC_HTTP:-$NFQWS_OPT_DESYNC}" + NFQWS_OPT_DESYNC_HTTP_SUFFIX="${NFQWS_OPT_DESYNC_HTTP_SUFFIX:-$NFQWS_OPT_DESYNC_SUFFIX}" + NFQWS_OPT_DESYNC_HTTPS="${NFQWS_OPT_DESYNC_HTTPS:-$NFQWS_OPT_DESYNC}" + NFQWS_OPT_DESYNC_HTTPS_SUFFIX="${NFQWS_OPT_DESYNC_HTTPS_SUFFIX:-$NFQWS_OPT_DESYNC_SUFFIX}" + NFQWS_OPT_DESYNC_HTTP6="${NFQWS_OPT_DESYNC_HTTP6:-$NFQWS_OPT_DESYNC_HTTP}" + NFQWS_OPT_DESYNC_HTTP6_SUFFIX="${NFQWS_OPT_DESYNC_HTTP6_SUFFIX:-$NFQWS_OPT_DESYNC_HTTP_SUFFIX}" + NFQWS_OPT_DESYNC_HTTPS6="${NFQWS_OPT_DESYNC_HTTPS6:-$NFQWS_OPT_DESYNC_HTTPS}" + NFQWS_OPT_DESYNC_HTTPS6_SUFFIX="${NFQWS_OPT_DESYNC_HTTPS6_SUFFIX:-$NFQWS_OPT_DESYNC_HTTPS_SUFFIX}" + NFQWS_OPT_DESYNC_QUIC6="${NFQWS_OPT_DESYNC_QUIC6:-$NFQWS_OPT_DESYNC_QUIC}" + NFQWS_OPT_DESYNC_QUIC6_SUFFIX="${NFQWS_OPT_DESYNC_QUIC6_SUFFIX:-$NFQWS_OPT_DESYNC_QUIC_SUFFIX}" +} + +get_nfqws_qnums() +{ + # $1 - var name for ipv4 http + # $2 - var name for ipv4 https + # $3 - var name for ipv6 http + # $4 - var name for ipv6 https + local _qn _qns _qn6 _qns6 + + [ "$DISABLE_IPV4" = "1" ] || { + _qn=$QNUM + _qns=$_qn + [ "$NFQWS_OPT_DESYNC_HTTP $NFQWS_OPT_DESYNC_HTTP_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTPS $NFQWS_OPT_DESYNC_HTTPS_SUFFIX" ] || _qns=$(($QNUM+1)) + } + [ "$DISABLE_IPV6" = "1" ] || { + _qn6=$(($QNUM+2)) + _qns6=$(($QNUM+3)) + [ "$DISABLE_IPV4" = "1" ] || { + if [ "$NFQWS_OPT_DESYNC_HTTP6 $NFQWS_OPT_DESYNC_HTTP6_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTP $NFQWS_OPT_DESYNC_HTTP_SUFFIX" ]; then + _qn6=$_qn; + elif [ "$NFQWS_OPT_DESYNC_HTTP6 $NFQWS_OPT_DESYNC_HTTP6_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTPS $NFQWS_OPT_DESYNC_HTTPS_SUFFIX" ]; then + _qn6=$_qns; + fi + if [ "$NFQWS_OPT_DESYNC_HTTPS6 $NFQWS_OPT_DESYNC_HTTPS6_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTP $NFQWS_OPT_DESYNC_HTTP_SUFFIX" ]; then + _qns6=$_qn; + elif [ "$NFQWS_OPT_DESYNC_HTTPS6 $NFQWS_OPT_DESYNC_HTTPS6_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTPS $NFQWS_OPT_DESYNC_HTTPS_SUFFIX" ]; then + _qns6=$_qns; + fi + } + [ "$NFQWS_OPT_DESYNC_HTTPS6 $NFQWS_OPT_DESYNC_HTTPS6_SUFFIX" = "$NFQWS_OPT_DESYNC_HTTP6 $NFQWS_OPT_DESYNC_HTTP6_SUFFIX" ] && _qns6=$_qn6; + } + if [ "$MODE_HTTP" = 1 ]; then + eval $1=$_qn + eval $3=$_qn6 + else + eval $1= + eval $3= + fi + if [ "$MODE_HTTPS" = 1 ]; then + eval $2=$_qns + eval $4=$_qns6 + else + eval $2= + eval $4= + fi +} + +get_nfqws_qnums_quic() +{ + # $1 - var name for ipv4 quic + # $2 - var name for ipv6 quic + local _qn _qn6 + + [ "$DISABLE_IPV4" = "1" ] || { + _qn=$(($QNUM+10)) + } + [ "$DISABLE_IPV6" = "1" ] || { + _qn6=$(($QNUM+11)) + [ "$DISABLE_IPV4" = "1" ] || { + if [ "$NFQWS_OPT_DESYNC_QUIC $NFQWS_OPT_DESYNC_QUIC_SUFFIX" = "$NFQWS_OPT_DESYNC_QUIC6 $NFQWS_OPT_DESYNC_QUIC6_SUFFIX" ]; then + _qn6=$_qn; + fi + } + } + if [ "$MODE_QUIC" = 1 ]; then + eval $1=$_qn + eval $2=$_qn6 + else + eval $1= + eval $2= + fi +} diff --git a/common/virt.sh b/common/virt.sh new file mode 100644 index 00000000..6e566d68 --- /dev/null +++ b/common/virt.sh @@ -0,0 +1,39 @@ +get_virt() +{ + local vm s v UNAME + UNAME=$(uname) + case "$UNAME" in + Linux) + if exists systemd-detect-virt; then + vm=$(systemd-detect-virt --vm) + elif [ -f /sys/class/dmi/id/product_name ]; then + read s fe80::31c:29ff:dee2:1c4d port 988 +rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 +``` + +Then: +``` +/opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force +``` + +Its not clear how to do rdr-to outgoing traffic. I could not make route-to +scheme work. + + +### `pfsense` + +`pfsense` is based on FreeBSD. Binaries from `binaries/freebsd-x64` are +compiled in FreeBSD 11 and should work. Use `install_bin.sh`. pfsense uses pf +firewall which does not support divert. Fortunately ipfw and ipdivert modules +are present and can be kldload-ed. In older versions it's also necessary to +change firewall order using sysctl commands. In newer versions those sysctl +parameters are absent but the system behaves as required without them. +Sometimes pf may limit `dvtws` abilities. It scrubs ip fragments disabling `dvtws` +ipfrag2 desync mode. + +There's autostart script example in `init.d/pfsense`. It should be placed to +`/usr/local/etc/rc.d` and edited. Write your ipfw rules and daemon start +commands. Because git is absent the most convinient way to copy files is ssh. +curl is present by default. + +Copy zip with zapret files to `/opt` and unpack there as it's done in other +systems. In this case run `dvtws` as `/opt/zapret/nfq/dvtws`. Or just copy +`dvtws` to `/usr/local/sbin`. As you wish. `ipset` scripts are working, cron is +present. It's possible to renew lists. + +If you dont like poverty of default repos its possible to enable FreeBSD repo. +Change `no` to `yes` in `/usr/local/etc/pkg/repos/FreeBSD.conf` and `/usr/local/etc/pkg/repos/pfSense.conf`. +Then it becomes possible to install all the required software including git to download +zapret from github directly. + + +`/usr/local/etc/rc.d/zapret.sh` (chmod 755) +``` +#!/bin/sh + +kldload ipfw +kldload ipdivert + +# for older pfsense versions. newer do not have these sysctls +sysctl net.inet.ip.pfil.outbound=ipfw,pf +sysctl net.inet.ip.pfil.inbound=ipfw,pf +sysctl net.inet6.ip6.pfil.outbound=ipfw,pf +sysctl net.inet6.ip6.pfil.inbound=ipfw,pf + +ipfw delete 100 +ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg xmit em0 +pkill ^dvtws$ +dvtws --daemon --port 989 --dpi-desync=split2 + +# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state +pfctl -d ; pfctl -e +``` + +I could not make tpws work from ipfw. Looks like there's some conflict between +two firewalls. Only PF redirection works. PF does not allow to freely add and +delete rules. Only anchors can be reloaded. To make an anchor work it must be +referred from the main ruleset. But its managed by pfsense scripts. + +One possible solution would be to modify `/etc/inc/filter.inc` as follows: +``` + ................. + /* MOD */ + $natrules .= "# ZAPRET redirection\n"; + $natrules .= "rdr-anchor \"zapret\"\n"; + + $natrules .= "# TFTP proxy\n"; + $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; + ................. +``` + +Write the anchor code to `/etc/zapret.anchor`: +``` +rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 +rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 +``` +Replace `fe80::20c:29ff:5ae3:4821` with your link local address of the LAN +interface or remove the line if ipv6 is not needed. + +Autostart `/usr/local/etc/rc.d/zapret.sh`: +``` +pfctl -a zapret -f /etc/zapret.anchor +pkill ^tpws$ +tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-http-req=method --split-pos=2 +``` + +After reboot check that anchor is created and referred from the main ruleset: +``` +[root@pfSense /]# pfctl -s nat +no nat proto carp all +nat-anchor "natearly/*" all +nat-anchor "natrules/*" all +................... +no rdr proto carp all +rdr-anchor "zapret" all +rdr-anchor "tftp-proxy/*" all +rdr-anchor "miniupnpd" all +[root@pfSense /]# pfctl -s nat -a zapret +rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 +rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 +rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 +rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 +``` + +Also there's a way to add redirect in the pfsense UI and start `tpws` from cron using `@reboot` prefix. +This way avoids modification of pfsense code. + +## OpenBSD + +In OpenBSD default `tpws` bind is ipv6 only. To bind to ipv4 specify +`--bind-addr=0.0.0.0`. + +Use `--bind-addr=0.0.0.0 --bind-addr=::` to achieve the same default bind as in +others OSes. + +`tpws` for forwarded traffic only : + +`/etc/pf.conf`: +``` +pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 +pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 +``` + +Then: +``` +pfctl -f /etc/pf.conf +tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 +``` + +Its not clear how to do rdr-to outgoing traffic. I could not make route-to +scheme work. rdr-to support is done using /dev/pf, that's why transparent mode +requires root. + +`dvtws` for all traffic: + +`/etc/pf.conf`: +``` +pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 proto tcp from port {80,443} no state +pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 +``` + +Then: +``` +pfctl -f /etc/pf.conf +./dvtws --port=989 --dpi-desync=split2 +``` + +`dwtws` only for table zapret with the exception of table nozapret : + +`/etc/pf.conf`: +``` +set limit table-entries 2000000 +table file "/opt/zapret/ipset/zapret-ip.txt" +table file "/opt/zapret/ipset/zapret-ip-user.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude.txt" +pass out quick on em0 inet proto tcp to port {80,443} +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +table file "/opt/zapret/ipset/zapret-ip6.txt" +table file "/opt/zapret/ipset/zapret-ip-user6.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" +pass out quick on em0 inet6 proto tcp to port {80,443} +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state +``` + +Then: +``` +pfctl -f /etc/pf.conf +./dvtws --port=989 --dpi-desync=split2 +``` + +divert-packet automatically adds the reverse rule. By default also incoming +traffic will be passwed to `dvtws`. This is highly undesired because it is waste +of cpu resources and speed limiter. The trick with "no state" and "in" rules +allows to bypass auto reverse rule. + +`dvtws` in OpenBSD sends all fakes through a divert socket because raw sockets +have critical artificial limitations. Looks like pf automatically prevent +reinsertion of diverted frames. Loop problem does not exist. + +OpenBSD forcibly recomputes tcp checksum after divert. Thats why most likely +dpi-desync-fooling=badsum will not work. `dvtws` will warn if you specify this +parameter. + +`ipset` scripts do not reload PF by default. To enable reload specify command in +`/opt/zapret/config`: +``` +LISTS_RELOAD="pfctl -f /etc/pf.conf" +``` + +Newer `pfctl` versions can reload tables only: +``` +pfctl -Tl -f /etc/pf.conf +``` + +But OpenBSD 6.8 `pfctl` is old enough and does not support that. Newer FreeBSD do. + +Don't forget to disable gzip compression: +``` +GZIP_LISTS=0 +``` + +If some list files do not exist and have references in pf.conf it leads to +error. You need to exclude those tables from pf.conf and referencing them +rules. After configuration is done you can put `ipset` script: +``` + crontab -e +``` + +Then write the line: +``` +0 12 */2 * * /opt/zapret/ipset/get_config.sh +``` + +## MacOS + +Initially, the kernel of this OS was based on BSD. That's why it is still BSD +but a lot was modified by Apple. As usual a mass commercial project priorities +differ from their free counterparts. Apple guys do what they want. + +MacOS used to have ipfw but it was removed later and replaced by PF. It looks +like divert sockets are internally replaced with raw. Its possible to request a +divert socket but it behaves exactly as raw socket with all its BSD inherited + +apple specific bugs and feature. The fact is that divert-packet in +`/etc/pf.conf` does not work. pfctl binary does not contain the word `divert`. + +`dvtws` does compile but is useless. + +After some efforts `tpws` works. Apple has removed some important stuff from +their newer SDKs (DIOCNATLOOK) making them undocumented and unsupported. + +With important definitions copied from an older SDK it was possible to make +transparent mode working again. But this is not guaranteed to work in the +future versions. + +Another MacOS unique feature is root requirement while polling `/dev/pf`. + +By default tpws drops root. Its necessary to specify `--user=root` to stay with +root. + +In other aspects PF behaves very similar to FreeBSD and shares the same pf.conf +syntax. + +In MacOS redirection works both for passthrough and outgoing traffic. Outgoing +redirection requires route-to rule. Because tpws is forced to run as root to +avoid loop its necessary to exempt root from the redirection. That's why DPI +bypass will not work for local requests from root. + +If you do ipv6 routing you have to get rid of "secured" ipv6 address +assignment. + +"secured" addresses are designed to be permanent and not related to the MAC +address. + +And they really are. Except for link-locals. + +If you just reboot the system link-locals will not change. But next day they +will change. + +Not necessary to wait so long. Just change the system time to tomorrow and reboot. +Link-locals will change (at least they change in vmware guest). Looks like its a kernel bug. +Link locals should not change. Its useless and can be harmful. Cant use LL as a gateway. + +The easiest solution is to disable "secured" addresses. + +Outgoing connections prefer randomly generated temporary addressesas like in other systems. + +Put the string `net.inet6.send.opmode=0` to `/etc/sysctl.conf`. If not present +- create it. + +Then reboot the system. + +If you dont like this solution you can assign an additional static ipv6 address +from `fc00::/7` range with `/128` prefix to your LAN interface and use it as +the gateway address. + +`tpws` transparent mode only for outgoing connections. + +`/etc/pf.conf`: +``` +rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 +pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } +pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } +``` + +Then: +``` +pfctl -ef /etc/pf.conf +/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force +``` + +`tpws` transparent mode for both passthrough and outgoing connections. en1 - LAN. + +``` +ifconfig en1 | grep fe80 + inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 +``` + +`/etc/pf.conf`: +``` +rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 +rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 +pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } +pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } +``` + +Then: +``` +pfctl -ef /etc/pf.conf +/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force +``` + +Build from source : `make -C /opt/zapret mac` + +`ipset/*.sh` scripts work. + + +### MacOS easy install + +`install_easy.sh` supports MacOS + +Shipped precompiled binaries are built for 64-bit MacOS with +`-mmacosx-version-min=10.8` option. They should run on all supported MacOS +versions. If no - its easy to build your own. Running `make` automatically +installs developer tools. + +**WARNING**: +**Internet sharing is not supported!** + +Routing is supported but only manually configured through PF. If you enable +internet sharing tpws stops functioning. When you disable internet sharing you +may lose web site access. + +To fix: +``` +pfctl -f /etc/pf.conf +``` + +If you need internet sharing use `tpws` socks mode. + +`launchd` is used for autostart (`/Library/LaunchDaemons/zapret.plist`) + +Control script: `/opt/zapret/init.d/macos/zapret` + +The following commands fork with both tpws and firewall (if `INIT_APPLY_FW=1` in config) +``` +/opt/zapret/init.d/macos/zapret start +/opt/zapret/init.d/macos/zapret stop +/opt/zapret/init.d/macos/zapret restart +``` + +Work with `tpws` only: +``` +/opt/zapret/init.d/macos/zapret start-daemons +/opt/zapret/init.d/macos/zapret stop-daemons +/opt/zapret/init.d/macos/zapret restart-daemons +``` + +Work with PF only: +``` +/opt/zapret/init.d/macos/zapret start-fw +/opt/zapret/init.d/macos/zapret stop-fw +/opt/zapret/init.d/macos/zapret restart-fw +``` + +Reloading PF tables: +``` +/opt/zapret/init.d/macos/zapret reload-fw-tables +``` + +Installer configures `LISTS_RELOAD` in the config so `ipset *.sh` scripts +automatically reload PF tables. Installer creates cron job for `ipset +/get_config.sh`, as in OpenWRT. + +start-fw script automatically patches `/etc/pf.conf` inserting there `zapret` +anchors. Auto patching requires pf.conf with apple anchors preserved. If your +`pf.conf` is highly customized and patching fails you will see the warning. Do +not ignore it. + +In that case you need to manually insert "zapret" anchors to your `pf.conf` +(keeping the right rule type ordering): +``` +rdr-anchor "zapret" +anchor "zapret" +unistall_easy.sh unpatches pf.conf +``` +start-fw creates 3 anchor files in `/etc/pf.anchors` : +zapret,zapret-v4,zapret-v6. + +- Last 2 are referenced by anchor `zapret`. +- Tables `nozapret`,`nozapret6` belong to anchor `zapret`. +- Tables `zapret`,`zapret-user` belong to anchor `zapret-v4`. +- Tables `zapret6`,`apret6-user` belong to anchor `zapret-v6`. + +If an ip version is disabled then corresponding anchor is empty and is not +referenced from the anchor `zapret`. Tables are only created for existing list +files in the `ipset` directory. diff --git a/docs/bsd.txt b/docs/bsd.txt new file mode 100644 index 00000000..829dc5c8 --- /dev/null +++ b/docs/bsd.txt @@ -0,0 +1,476 @@ +Поддерживаемые версии +--------------------- + +FreeBSD 11.x+ , OpenBSD 6.x+, частично MacOS Sierra+ + +На более старых может собираться, может не собираться, может работать или не работать. +На FreeBSD 10 собирается и работает dvtws. С tpws есть проблемы из-за слишком старой версии компилятора clang. +Вероятно, будет работать, если обновить компилятор. +Возможна прикрутка к последним версиям pfsense без веб интерфейса в ручном режиме через консоль. + + +Особенности BSD систем +---------------------- + +В BSD нет nfqueue. Похожий механизм - divert sockets. +Из каталога "nfq" под BSD собирается dvtws вместо nfqws. +Он разделяет с nfqws большую часть кода и почти совпадает по параметрам командной строки. + +FreeBSD содержит 3 фаервола : IPFilter, ipfw и Packet Filter (PF). OpenBSD содержит только PF. + +Под FreeBSD tpws и dvtws собираются через "make", под OpenBSD - "make bsd", под MacOS - "make mac". +FreeBSD make распознает BSDmakefile , OpenBSD и MacOS - нет. Поэтому там используется отдельный target в Makefile. +Сборка всех исходников : make -C /opt/zapret + +divert сокет - внутренний тип сокета ядра BSD. Он не привязывается ни к какому сетевому адресу, не участвует +в обмене данными через сеть и идентифицируется по номеру порта 1..65535. Аналогия с номером очереди NFQUEUE. +На divert сокеты заворачивается трафик посредством правил ipfw или PF. +Если в фаерволе есть правило divert, но на divert порту никто не слушает, то пакеты дропаются. +Это поведение аналогично правилам NFQUEUE без параметра --queue-bypass. +На FreeBSD divert сокеты могут быть только ipv4, хотя на них принимаются и ipv4, и ipv6 фреймы. +На OpenBSD divert сокеты создаются отдельно для ipv4 и ipv6 и работают только с одной версией ip каждый. +На MacOS похоже, что divert сокеты из ядра вырезаны. См подробнее раздел про MacOS. +Отсылка в divert сокет работает аналогично отсылке через raw socket на linux. Передается полностью IP фрейм, начиная +с ip загловка . Эти особенности учитываются в dvtws. + +Скрипты ipset/*.sh при наличии ipfw работают с ipfw lookup tables. +Это прямой аналог ipset. lookup tables не разделены на v4 и v6. Они могут содержать v4 и v6 адреса и подсети одновременно. +Если ipfw отсутствует, то действие зависит от переменной LISTS_RELOAD в config. +Если она задана, то выполняется команда из LISTS_RELOAD. В противном случае не делается ничего. +Если LISTS_RELOAD=-, то заполнение таблиц отключается даже при наличии ipfw. + +PF может загружать ip таблицы из файла. Чтобы использовать эту возможность следует отключить сжатие gzip для листов +через параметр файла config "GZIP_LISTS=0". + +BSD не содержит системного вызова splice. tpws работает через переброску данных в user mode в оба конца. +Это медленнее, но не критически. +Управление асинхронными сокетами в tpws основано на linux-specific механизме epoll. +В BSD для его эмуляции используется epoll-shim - прослойка для эмуляции epoll на базе kqueue. + +mdig и ip2net полностью работоспособны в BSD. В них нет ничего системо-зависимого. + +FreeBSD +------- + +divert сокеты требуют специального модуля ядра ipdivert. +Поместите следующие строки в /boot/loader.conf (создать, если отсутствует) : +----------- +ipdivert_load="YES" +net.inet.ip.fw.default_to_accept=1 +----------- +В /etc/rc.conf : +----------- +firewall_enable="YES" +firewall_script="/etc/rc.firewall.my" +----------- +/etc/rc.firewall.my : +----------- +ipfw -q -f flush +----------- +В /etc/rc.firewall.my можно дописывать правила ipfw, чтобы они восстанавливались после перезагрузки. +Оттуда же можно запускать и демоны zapret, добавив в параметры "--daemon". Например так : +----------- +pkill ^dvtws$ +/opt/zapret/nfq/dvtws --port=989 --daemon --dpi-desync=split2 +----------- +Для перезапуска фаервола и демонов достаточно будет сделать : /etc/rc.d/ipfw restart + + +Краткая инструкция по запуску tpws в прозрачном режиме. +Предполагается, что интерфейс LAN называется em1, WAN - em0. + +Для всего трафика : +ipfw delete 100 +ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon +ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon +ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 +ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 +/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 + +Для трафика только на таблицу zapret, за исключением таблицы nozapret : +ipfw delete 100 +ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 +ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon +ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon +ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 +ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 +ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 +/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 + +Таблицы zapret, nozapret, ipban создаются скриптами из ipset по аналогии с Linux. +Обновление скриптов можно забить в cron под root : + crontab -e + Создать строчку "0 12 */2 * * /opt/zapret/ipset/get_config.sh" + +При использовании ipfw tpws не требует повышенных привилегий для реализации прозрачного режима. +Однако, без рута невозможен бинд на порты <1024 и смена UID/GID. Без смены UID будет рекурсия, +поэтому правила ipfw нужно создавать с учетом UID, под которым работает tpws. +Переадресация на порты >=1024 может создать угрозу перехвата трафика непривилегированным +процессом, если вдруг tpws не запущен. + + +Краткая инструкция по запуску dvtws. + +Для всего трафика : +ipfw delete 100 +ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 +# required for autottl mode only +ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 +/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2 + +Для трафика только на таблицу zapret, за исключением таблицы nozapret : +ipfw delete 100 +ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 +ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted not sockarg xmit em0 +# required for autottl mode only +ipfw add 100 divert 989 tcp from table\(zapret\) 80,443 to any tcpflags syn,ack in not diverted not sockarg recv em0 +/opt/zapret/nfq/dvtws --port=989 --dpi-desync=split2 + + +PF в FreeBSD: +Настройка аналогична OpenBSD, но есть важные нюансы. +1) В FreeBSD поддержка PF в tpws отключена по умолчанию. Чтобы ее включить, нужно использовать параметр --enable-pf. +2) Нельзя сделать ipv6 rdr на ::1. Нужно делать на link-local адрес входящего интерфейса. +Смотрите через ifconfig адрес fe80:... и добавляете в правило +3) Синтаксис pf.conf немного отличается. Более новая версия PF. +4) Лимит на количество элементов таблиц задается так : sysctl net.pf.request_maxcount=2000000 +5) divert-to сломан. Он работает, но не работает механизм предотвращения зацикливаний. +Кто-то уже написал патч, но в 14-RELEASE проблема все еще есть. +Следовательно, на данный момент работа dvtws через pf невозможна. + +/etc/pf.conf +----------- +rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::31c:29ff:dee2:1c4d port 988 +rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 +----------- +/opt/zapret/tpws/tpws --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force + +В PF непонятно как делать rdr-to с той же системы, где работает proxy. Вариант с route-to у меня не заработал. + + +pfsense +------- + +pfsense основано на FreeBSD. +pfsense использует фаервол pf, а он имеет проблемы с divert. +К счастью, модули ipfw и ipdivert присутствуют в поставке последних версий pfsense. +Их можно подгрузить через kldload. +В некоторых более старых версиях pfsense требуется изменить порядок фаерволов через sysctl, сделав ipfw первым. +В более новых эти параметры sysctl отсутствуют, но система работает как надо и без них. +В некоторых случаях фаервол pf может ограничивать возможности dvtws, в частности в области фрагментации ip. +Присутствуют по умолчанию правила scrub для реассемблинга фрагментов. +Бинарики из binaries/freebsd-x64 собраны под FreeBSD 11. Они должны работать и на последующих версиях FreeBSD, +включая pfsense. Можно пользоваться install_bin.sh. + +Пример скрипта автозапуска лежит в init.d/pfsense. Его следует поместить в /usr/local/etc/rc.d и отредактировать +на предмет правил ipfw и запуска демонов. Есть встроенный редактор edit как более приемлемая альтернатива vi. +Поскольку git отсутствует, копировать файлы удобнее всего через ssh. curl присутствует по умолчанию. +Можно скопировать zip с файлами zapret и распаковать в /opt, как это делается на других системах. +Тогда dvtws нужно запускать как /opt/zapret/nfq/dvtws. Либо скопировать только dvtws в /usr/local/sbin. +Как вам больше нравится. +ipset скрипты работают, крон есть. Можно сделать автообновление листов. + +Если вас напрягает бедность имеющегося репозитория, можно включить репозиторий от FreeBSD, который по умолчанию выключен. +Поменяйте no на yes в /usr/local/etc/pkg/repos/FreeBSD.conf +Можно установить весь привычный soft, включая git, чтобы напрямую скачивать zapret с github. + +/usr/local/etc/rc.d/zapret.sh (chmod 755) +----------- +#!/bin/sh + +kldload ipfw +kldload ipdivert + +# for older pfsense versions. newer do not have these sysctls +sysctl net.inet.ip.pfil.outbound=ipfw,pf +sysctl net.inet.ip.pfil.inbound=ipfw,pf +sysctl net.inet6.ip6.pfil.outbound=ipfw,pf +sysctl net.inet6.ip6.pfil.inbound=ipfw,pf + +ipfw delete 100 +ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 +pkill ^dvtws$ +dvtws --daemon --port 989 --dpi-desync=split2 + +# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state +pfctl -d ; pfctl -e +----------- + +Что касается tpws, то видимо имеется некоторый конфликт двух фаерволов, и правила fwd в ipfw не работают. +Работает перенаправление средствами pf как описано в разделе по FreeBSD. +В pf можно изменять правила только целыми блоками - якорями (anchors). Нельзя просто так добавить или удалить что-то. +Но чтобы какой-то anchor был обработан, на него должна быть ссылка из основного набора правил. +Его трогать нельзя, иначе порушится весь фаервол. +Поэтому придется править код скриптов pfsense. Поправьте /etc/inc/filter.inc следующим образом : +----------- + ................. + /* MOD */ + $natrules .= "# ZAPRET redirection\n"; + $natrules .= "rdr-anchor \"zapret\"\n"; + + $natrules .= "# TFTP proxy\n"; + $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; + ................. +----------- + +Напишите файл с содержимым anchor-а (например, /etc/zapret.anchor): +----------- +rdr pass on em1 inet proto tcp to port {80,443} -> 127.0.0.1 port 988 +rdr pass on em1 inet6 proto tcp to port {80,443} -> fe80::20c:29ff:5ae3:4821 port 988 +----------- +fe80::20c:29ff:5ae3:4821 замените на ваш link local адрес LAN интерфейса, либо уберите строчку, если ipv6 не нужен. + +Добавьте в автозапуск /usr/local/etc/rc.d/zapret.sh : +----------- +pfctl -a zapret -f /etc/zapret.anchor +pkill ^tpws$ +tpws --daemon --port=988 --enable-pf --bind-addr=127.0.0.1 --bind-iface6=em1 --bind-linklocal=force --split-http-req=method --split-pos=2 +----------- + +После перезагрузки проверьте, что правила создались : +----------- +[root@pfSense /]# pfctl -s nat +no nat proto carp all +nat-anchor "natearly/*" all +nat-anchor "natrules/*" all +................... +no rdr proto carp all +rdr-anchor "zapret" all +rdr-anchor "tftp-proxy/*" all +rdr-anchor "miniupnpd" all +[root@pfSense /]# pfctl -s nat -a zapret +rdr pass on em1 inet proto tcp from any to any port = http -> 127.0.0.1 port 988 +rdr pass on em1 inet proto tcp from any to any port = https -> 127.0.0.1 port 988 +rdr pass on em1 inet6 proto tcp from any to any port = http -> fe80::20c:29ff:5ae3:4821 port 988 +rdr pass on em1 inet6 proto tcp from any to any port = https -> fe80::20c:29ff:5ae3:4821 port 988 +----------- + +Так же есть более элегантный способ запуска tpws через @reboot в cron и правило перенаправления в UI. +Это позволит не редактировать код pfsense. + + +OpenBSD +------- + +В tpws бинд по умолчанию только на ipv6. для бинда на ipv4 указать "--bind-addr=0.0.0.0" +Используйте --bind-addr=0.0.0.0 --bind-addr=:: для достижения того же результата, как в других ОС по умолчанию. +(лучше все же так не делать, а сажать на определенные внутренние адреса или интерфейсы) + +tpws для проходящего трафика : + +/etc/pf.conf +------------ +pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 +pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 +------------ +pfctl -f /etc/pf.conf +tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 + +В PF непонятно как делать rdr-to с той же системы, где работает proxy. Вариант с route-to у меня не заработал. +Поддержка rdr-to реализована через /dev/pf, поэтому прозрачный режим требует root. + +dvtws для всего трафика : + +/etc/pf.conf +------------ +pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 proto tcp from port {80,443} no state +pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state +------------ +pfctl -f /etc/pf.conf +./dvtws --port=989 --dpi-desync=split2 + +dvtws для трафика только на таблицу zapret, за исключением таблицы nozapret : + +/etc/pf.conf +------------ +set limit table-entries 2000000 +table file "/opt/zapret/ipset/zapret-ip.txt" +table file "/opt/zapret/ipset/zapret-ip-user.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude.txt" +pass out quick on em0 inet proto tcp to port {80,443} +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +table file "/opt/zapret/ipset/zapret-ip6.txt" +table file "/opt/zapret/ipset/zapret-ip-user6.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" +pass out quick on em0 inet6 proto tcp to port {80,443} +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state +------------ +pfctl -f /etc/pf.conf +./dvtws --port=989 --dpi-desync=split2 + +divert-packet автоматически вносит обратное правило для перенаправления. +Трюк с no state и in правилом позволяет обойти эту проблему, чтобы напрасно не гнать массивный трафик через dvtws. + +В OpenBSD dvtws все фейки отсылает через divert socket, поскольку эта возможность через raw sockets заблокирована. +Видимо pf автоматически предотвращает повторный заворот diverted фреймов, поэтому проблемы зацикливания нет. + +OpenBSD принудительно пересчитывает tcp checksum после divert, поэтому скорее всего +dpi-desync-fooling=badsum у вас не заработает. При использовании этого параметра +dvtws предупредит о возможной проблеме. + +Скрипты из ipset не перезагружают таблицы в PF по умолчанию. +Чтобы они это делали, добавьте параметр в /opt/zapret/config : +LISTS_RELOAD="pfctl -f /etc/pf.conf" +Более новые версии pfctl понимают команду перезагрузить только таблицы : pfctl -Tl -f /etc/pf.conf +Но это не относится к OpenBSD 6.8. В новых FreeBSD есть. +Не забудьте выключить сжатие gzip : +GZIP_LISTS=0 +Если в вашей конфигурации какого-то файла листа нет, то его необходимо исключить из правил PF. +Если вдруг листа нет, и он задан в pf.conf, будет ошибка перезагрузки фаервола. +После настройки обновление листов можно поместить в cron : + crontab -e + дописать строчку : 0 12 */2 * * /opt/zapret/ipset/get_config.sh + + +MacOS +----- + +Иначально ядро этой ОС "darwin" основывалось на BSD, потому в ней много похожего на другие версии BSD. +Однако, как и в других массовых коммерческих проектах, приоритеты смещаются в сторону от оригинала. +Яблочники что хотят, то и творят. +Раньше был ipfw, потом его убрали, заменили на PF. +Есть сомнения, что divert сокеты в ядре остались. Попытка создать divert socket не выдает ошибок, +но полученный сокет ведет себя точно так же, как raw, со всеми его унаследованными косяками + еще яблочно специфическими. +В PF divert-packet не работает. Простой grep бинарика pfctl показывает, что там нет слова "divert", +а в других версиях BSD оно есть. dvtws собирается, но совершенно бесполезен. + +tpws удалось адаптировать, он работоспособен. Получение адреса назначения для прозрачного прокси в PF (DIOCNATLOOK) +убрали из заголовков в новых SDK, сделав фактически недокументированным. +В tpws перенесены некоторые определения из более старых версий яблочных SDK. С ними удалось завести прозрачный режим. +Однако, что будет в следующих версиях угадать сложно. Гарантий нет. +Еще одной особенностью PF в MacOS является проверка на рута в момент обращения к /dev/pf, чего нет в остальных BSD. +tpws по умолчанию сбрасывает рутовые привилегии. Необходимо явно указать параметр --user=root. +В остальном PF себя ведет похоже на FreeBSD. Синтаксис pf.conf тот же. + +На MacOS работает редирект как с проходящего трафика, так и с локальной системы через route-to. +Поскольку tpws вынужден работать под root, для исключения рекурсии приходится пускать исходящий от root трафик напрямую. +Отсюда имеем недостаток : обход DPI для рута работать не будет. + +Если вы пользуетесь MaсOS в качестве ipv6 роутера, то нужно будет решить вопрос с регулярно изменяемым link-local адресом. +С некоторых версий MacOS использует по умолчанию постоянные "secured" ipv6 адреса вместо генерируемых на базе MAC адреса. +Все замечательно, но есть одна проблема. Постоянными остаются только global scope адреса. +Link locals периодически меняются. Смена завязана на системное время. Перезагрузки адрес не меняют, +Но если перевести время на день вперед и перезагрузиться - link local станет другим. (по крайней мере в vmware это так) +Информации по вопросу крайне мало, но тянет на баг. Не должен меняться link local. Скрывать link local не имеет смысла, +а динамический link local нельзя использовать в качестве адреса шлюза. +Проще всего отказаться от "secured" адресов. +Поместите строчку "net.inet6.send.opmode=0" в /etc/sysctl.conf. Затем перезагрузите систему. +Все равно для исходящих соединений будут использоваться temporary адреса, как и в других системах. +Или вам идея не по вкусу, можно прописать дополнительный статический ipv6 из диапазона fc00::/7 - +выберите любой с длиной префикса 128. Это можно сделать в системных настройках, создав дополнительный адаптер на базе +того же сетевого интерфейса, отключить в нем ipv4 и вписать статический ipv6. Он добавится к автоматически настраеваемым. + +Настройка tpws на macos в прозрачном режиме только для исходящих запросов : + +/etc/pf.conf +------------ +rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 +pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } +pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } +------------ +pfctl -ef /etc/pf.conf +/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force + + +Настройка tpws на macos роутере в прозрачном режиме, где en1 - LAN. + +ifconfig en1 | grep fe80 + inet6 fe80::bbbb:bbbb:bbbb:bbbb%en1 prefixlen 64 scopeid 0x8 +/etc/pf.conf +------------ +rdr pass on en1 inet proto tcp from any to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on en1 inet6 proto tcp from any to any port {80,443} -> fe80::bbbb:bbbb:bbbb:bbbb port 988 +rdr pass on lo0 inet proto tcp from !127.0.0.0/8 to any port {80,443} -> 127.0.0.1 port 988 +rdr pass on lo0 inet6 proto tcp from !::1 to any port {80,443} -> fe80::1 port 988 +pass out route-to (lo0 127.0.0.1) inet proto tcp from any to any port {80,443} user { >root } +pass out route-to (lo0 fe80::1) inet6 proto tcp from any to any port {80,443} user { >root } +------------ +pfctl -ef /etc/pf.conf +/opt/zapret/tpws/tpws --user=root --port=988 --bind-addr=127.0.0.1 --bind-iface6=lo0 --bind-linklocal=force --bind-iface6=en1 --bind-linklocal=force + + +Сборка : make -C /opt/zapret mac + +Скрипты получения листов ipset/*.sh работают. +Если будете пользоваться ipset/get_combined.sh, нужно установить gnu grep через brew. +Имеющийся очень старый и безумно медленный с оцией -f. + + +MacOS простая установка +----------------------- + +В MacOS поддерживается install_easy.sh + +В комплекте идут бинарики, собраные под 64-bit с опцией -mmacosx-version-min=10.8. +Они должны работать на всех поддерживаемых версиях macos. +Если вдруг не работают - можно собрать свои. Developer tools ставятся автоматом при запуске make. + +!! Internet sharing средствами системы НЕ ПОДДЕРЖИВАЕТСЯ !! +Поддерживается только роутер, настроенный своими силами через PF. +Если вы вдруг включили шаринг, а потом выключили, то доступ к сайтам может пропасть совсем. +Лечение : pfctl -f /etc/pf.conf +Если вам нужен шаринг интернета, лучше отказаться от прозрачного режима и использовать socks. + +Для автостарта используется launchd (/Library/LaunchDaemons/zapret.plist) +Управляющий скрипт : /opt/zapret/init.d/macos/zapret +Следующие команды работают с tpws и фаерволом одновременно (если INIT_APPLY_FW=1 в config) +/opt/zapret/init.d/macos/zapret start +/opt/zapret/init.d/macos/zapret stop +/opt/zapret/init.d/macos/zapret restart +Работа только с tpws : +/opt/zapret/init.d/macos/zapret start-daemons +/opt/zapret/init.d/macos/zapret stop-daemons +/opt/zapret/init.d/macos/zapret restart-daemons +Работа только с PF : +/opt/zapret/init.d/macos/zapret start-fw +/opt/zapret/init.d/macos/zapret stop-fw +/opt/zapret/init.d/macos/zapret restart-fw +Перезагрузка всех IP таблиц из файлов : +/opt/zapret/init.d/macos/zapret reload-fw-tables + +Инсталятор настраивает LISTS_RELOAD в config, так что скрипты ipset/*.sh автоматически перезагружают IP таблицы в PF. +Автоматически создается cron job на ipset/get_config.sh, по аналогии с openwrt. + +При start-fw скрипт автоматически модицифирует /etc/pf.conf, вставляя туда anchors "zapret". +Модификация расчитана на pf.conf, в котором сохранены дефолтные anchors от apple. +Если у вас измененный pf.conf и модификация не удалась, об этом будет предупреждение. Не игнорируйте его. +В этом случае вам нужно вставить в свой pf.conf (в соответствии с порядком типов правил) : +rdr-anchor "zapret" +anchor "zapret" +При деинсталяции через uninstall_easy.sh модификации pf.conf убираются. + +start-fw создает 3 файла anchors в /etc/pf.anchors : zapret,zapret-v4,zapret-v6. +Последние 2 подключаются из anchor "zapret". +Таблицы nozapret,nozapret6 принадлежат anchor "zapret". +Таблицы zapret,zapret-user - в anchor "zapret-v4". +Таблицы zapret6,zapret6-user - в anchor "zapret-v6". +Если какая-то версия протокола отключена - соответствующий anchor пустой и не упоминается в anchor "zapret". +Таблицы и правила создаются только на те листы, которые фактически есть в директории ipset. + + +MacOS вариант custom +-------------------- + +Так же как и в других системах, поддерживаемых в простом инсталяторе, можно создавать свои custom скрипты. +Расположение : /opt/zapret/init.d/macos/custom + +zapret_custom_daemons() получает в $1 "0" или "1". "0" - stop, "1" - start +custom firewall отличается от linux варианта. +Вместо заполнения iptables вам нужно сгенерировать правила для zapret-v4 и zapret-v6 anchors и выдать их в stdout. +Это делается в функциях zapret_custom_firewall_v4() и zapret_custom_firewall_v6(). +Определения таблиц заполняются основным скриптом - вам это делать не нужно. +Можно ссылаться на таблицы zapret и zapret-user в v4, zapret6 и zapret6-user. + +Cм. пример в файле custom-tpws diff --git a/docs/bsdfw.txt b/docs/bsdfw.txt new file mode 100644 index 00000000..2575c690 --- /dev/null +++ b/docs/bsdfw.txt @@ -0,0 +1,97 @@ +WAN=em0 LAN=em1 + +FreeBSD IPFW : + +ipfw delete 100 +ipfw add 100 fwd 127.0.0.1,988 tcp from me to any 80,443 proto ip4 xmit em0 not uid daemon +ipfw add 100 fwd ::1,988 tcp from me to any 80,443 proto ip6 xmit em0 not uid daemon +ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 +ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 + +ipfw delete 100 +ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 +ipfw add 100 fwd 127.0.0.1,988 tcp from me to table\(zapret\) 80,443 proto ip4 xmit em0 not uid daemon +ipfw add 100 fwd ::1,988 tcp from me to table\(zapret\) 80,443 proto ip6 xmit em0 not uid daemon +ipfw add 100 allow tcp from any to table\(nozapret\) 80,443 recv em1 +ipfw add 100 fwd 127.0.0.1,988 tcp from any to any 80,443 proto ip4 recv em1 +ipfw add 100 fwd ::1,988 tcp from any to any 80,443 proto ip6 recv em1 + +/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 + + +ipfw delete 100 +ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted xmit em0 +; required for autottl mode +ipfw add 100 divert 989 tcp from any 80,443 to any tcpflags syn,ack in not diverted recv em0 +; udp +ipfw add 100 divert 989 udp from any to any 443 out not diverted xmit em0 + +ipfw delete 100 +ipfw add 100 allow tcp from me to table\(nozapret\) 80,443 +ipfw add 100 divert 989 tcp from any to table\(zapret\) 80,443 out not diverted xmit em0 + +/opt/zapret/nfq/dvtws --port=989 --debug --dpi-desync=split + + +sample ipfw NAT setup : + +WAN=em0 +LAN=em1 +ipfw -q flush +ipfw -q nat 1 config if $WAN unreg_only reset +ipfw -q add 10 allow ip from any to any via $LAN +ipfw -q add 20 allow ip from any to any via lo0 +ipfw -q add 300 nat 1 ip4 from any to any in recv $WAN +ipfw -q add 301 check-state +ipfw -q add 350 skipto 390 tcp from any to any out xmit $WAN setup keep-state +ipfw -q add 350 skipto 390 udp from any to any out xmit $WAN keep-state +ipfw -q add 360 allow all from any to me in recv $WAN +ipfw -q add 390 nat 1 ip4 from any to any out xmit $WAN +ipfw -q add 10000 allow ip from any to any + +Forwarding : +sysctl net.inet.ip.forwarding=1 +sysctl net.inet6.ip6.forwarding=1 + + +OpenBSD PF : + +; dont know how to rdr-to from local system. doesn't seem to work. only works for routed traffic. + +/etc/pf.conf +pass in quick on em1 inet proto tcp to port {80,443} rdr-to 127.0.0.1 port 988 +pass in quick on em1 inet6 proto tcp to port {80,443} rdr-to ::1 port 988 +pfctl -f /etc/pf.conf +/opt/zapret/tpws/tpws --port=988 --user=daemon --bind-addr=::1 --bind-addr=127.0.0.1 + +; dvtws works both for routed and local + +pass in quick on em0 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 proto tcp from port {80,443} no state +pass out quick on em0 proto tcp to port {80,443} divert-packet port 989 no state +pfctl -f /etc/pf.conf +./dvtws --port=989 --dpi-desync=split2 + +; dvtws with table limitations : to zapret,zapret6 but not to nozapret,nozapret6 +; reload tables : pfctl -f /etc/pf.conf +set limit table-entries 2000000 +table file "/opt/zapret/ipset/zapret-ip.txt" +table file "/opt/zapret/ipset/zapret-ip-user.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude.txt" +pass out quick on em0 inet proto tcp to port {80,443} +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet proto tcp from port {80,443} no state +pass out quick on em0 inet proto tcp to port {80,443} divert-packet port 989 no state +table file "/opt/zapret/ipset/zapret-ip6.txt" +table file "/opt/zapret/ipset/zapret-ip-user6.txt" +table file "/opt/zapret/ipset/zapret-ip-exclude6.txt" +pass out quick on em0 inet6 proto tcp to port {80,443} +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} flags SA/SA divert-packet port 989 no state +pass in quick on em0 inet6 proto tcp from port {80,443} no state +pass out quick on em0 inet6 proto tcp to port {80,443} divert-packet port 989 no state diff --git a/docs/changes.txt b/docs/changes.txt new file mode 100644 index 00000000..ec76971b --- /dev/null +++ b/docs/changes.txt @@ -0,0 +1,325 @@ +v1 + +Initial release + +v2 + +nfqws : command line options change. now using standard getopt. +nfqws : added options for window size changing and "Host:" case change +ISP support : tested on mns.ru and beeline (corbina) +init scripts : rewritten init scripts for simple choise of ISP +create_ipset : now using 'ipset restore', it works much faster +readme : updated. now using UTF-8 charset. + +v3 + +tpws : added transparent proxy (supports TPROXY and DNAT). + can help when ISP tracks whole HTTP session, not only the beginning +ipset : added zapret-hosts-user.txt which contain user defined host names to be resolved + and added to zapret ip list +ISP support : dom.ru support via TPROXY/DNAT +ISP support : successfully tested sknt.ru on 'domru' configuration + other configs will probably also work, but cannot test +compile : openwrt compile howto + +v4 + +tpws : added ability to insert extra space after http method : "GET /" => "GET /" +ISP support : TKT support + +v5 + +nfqws : ipv6 support in nfqws + +v6 + +ipset : added "get_antizapret.sh" + +v7 + +tpws : added ability to insert "." after Host: name + +v8 + +openwrt init : removed hotplug.d/firewall because of race conditions. now only use /etc/firewall.user + +v9 + +ipban : added ipban ipset. place domains banned by ip to zapret-hosts-user-ipban.txt + these IPs must be soxified for both http and https +ISP support : tiera support +ISP support : added DNS filtering to ubuntu and debian scripts + +v10 + +tpws : added split-pos option. split every message at specified position + +v11 + +ipset : scripts optimizations + +v12 + +nfqws : fix wrong tcp checksum calculation if packet length is odd and platform is big-endian + +v13 + +added binaries + +v14 + +change get_antizapret script to work with https://github.com/zapret-info/z-i/raw/master/dump.csv +filter out 192.168.*, 127.*, 10.* from blocked ips + +v15 + +added --hostspell option to nfqws and tpws +ISP support : beeline now catches "host" but other spellings still work +openwrt/LEDE : changed init script to work with procd +tpws, nfqws : minor cosmetic fixes + +v16 + +tpws: split-http-req=method : split inside method name, not after +ISP support : mns.ru changed split pos to 3 (got redirect page with HEAD req : curl -I ej.ru) + +v17 + +ISP support : athome moved from nfqws to tpws because of instability and http request hangs +tpws : added options unixeol,methodeol,hosttab + +v18 + +tpws,nfqws : added hostnospace option + +v19 + +tpws : added hostlist option + +v20 + +added ip2net. ip2net groups ips from iplist into subnets and reduces ipset size twice + +v21 + +added mdig. get_reestr.sh is *real* again + +v22 + +total review of init script logic +dropped support of older debian 7 and ubuntu 12/14 systems +install_bin.sh : auto binaries preparation +docs: readme review. some new topics added, others deleted +docs: VPN setup with policy based routing using wireguard +docs: wireguard modding guide + +v23 + +major init system rewrite +openwrt : separate firewall include /etc/firewall.zapret +install_easy.sh : easy setup on openwrt, debian, ubuntu, centos, fedora, opensuse + +v24 + +separate config from init scripts +gzip support in ipset/*.sh and tpws + +v25 + +init : move to native systemd units +use links to units, init scripts and firewall includes, no more copying + +v26 + +ipv6 support +tpws : advanced bind options + +v27 + +tpws : major connection code rewrite. originally it was derived from not top quality example , with many bugs and potential problems. +next generation connection code uses nonblocking sockets. now its in EXPERIMENTAL state. + +v28 + +tpws : added socks5 support +ipset : major RKN getlist rewrite. added antifilter.network support + +v29 + +nfqws : DPI desync attack +ip exclude system + +v30 + +nfqws : DPI desync attack modes : fake,rst + +v31 + +nfqws : DPI desync attack modes : disorder,disorder2,split,split2. +nfqws : DPI desync fooling mode : badseq. multiple modes supported + +v32 + +tpws : multiple binds +init scripts : run only one instance of tpws in any case + +v33 + +openwrt : flow offloading support +config : MODE refactoring + +v34 + +nfqws : dpi-desync 2 mode combos +nfqws : dpi-desync without parameter no more supported. previously it meant "fake" +nfqws : custom fake http request and tls client hello + +v35 + +limited FreeBSD and OpenBSD support + +v36 + +full FreeBSD and OpenBSD support + +v37 + +limited MacOS support + +v38 + +MacOS easy install + +v39 + +nfqws: conntrack, wssize + +v40 + +init scripts : IFACE_LAN, IFACE_WAN now accept multiple interfaces +init scripts : openwrt uses now OPENWRT_LAN parameter to override incoming interfaces for tpws + +v41 + +install_easy : openrc support + +v42 + +blockcheck.sh + +v43 + +nfqws: UDP desync with conntrack support (any-protocol only for now) + +v44 + +nfqws: ipfrag + +v45 + +nfqws: hop-by-hop ipv6 desync and fooling + +v46 + +big startup script refactoring to support nftables and new openwrt snapshot builds with firewall4 + +v47 + +nfqws: QUIC initial decryption +nfqws: udplen, fakeknown dpi desync modes + +v48 + +nfqws, tpws : multiple --hostlist and --hostlist-exclude support +launch system, ipset : no more list merging. all lists are passed separately to nfqws and tpws +nfqws : udplen fooling supports packet shrinking (negative increment value) + +v49 + +QUIC support integrated to the main system and setup + +v50 + +DHT protocol support. +DPI desync mode 'tamper' for DHT. +HEX string support in addition to binary files. + +v51 + +tpws --tlsrec attack. + +v52 + +autohostlist mode + +v53 + +nfqws: tcp session reassemble for TLS ClientHello + +v54 + +tpws: out of band send when splitting (--oob) +nfqws: autottl +nfqws: datanoack fooling +nftables: use POSTNAT path for tcp redirections to allow NAT-breaking strategies. use additional mark bit DESYNC_MARK_POSTNAT. + +v55 + +tpws: incompatible oob parameter change. it doesn't take oob byte anymore. instead it takes optional protocol filter - http or tls. + the same is done with disorder. oob byte can be specified in parameter --oob-data. +blockcheck: quick mode, strategy order optimizations, QUIC protocol support +nfqws: syndata desync mode + +v56 + +tpws: mss fooling +tpws: multi thread resolver. eliminates blocks related to hostname resolve. + +v57 + +tpws: --nosplice option +nfqws: postnat fixes +nfqws: --dpi-desync-start option +nfqws: packet delay for kyber TLS and QUIC +nfqws: --dpi-desync-retrans obsolete +nfqws: --qnum is mandatory, no more default queue 0 + +v58 + +winws + +v59 + +tpws: --split-tls +tpws: --tlsrec=sniext +nfqws: --dpi-desync-split-http-req, --dpi-desync-split-tls. multi segment TLS support for split. +blockcheck: mdig dns cache + +v60 + +blockcheck: port block test, partial ip block test +nfqws: seqovl split/disorder modes + +v61 + +C code cleanups +dvtws: do not use raw sockets. use divert. +nfqws,tpws: detect TLS 1.2 ClientHello from very old libraries with SSL 3.0 version in record layer +nfqws,tpws: debug log to file and syslog +tpws: --connect-bind-addr option +tpws: log local endpoint (including source port number) for remote leg + +v62: + +tpws: connection close logic rewrite. tcp user timeout parameters for local and remote leg. +nfqws: multi-strategy + +v63: + +tpws: multi-strategy + +v64: + +blockcheck: warn if dpi bypass software is already running +blockcheck: TPWS_EXTRA, NFQWS_EXTRA +init.d: multiple custom scripts diff --git a/docs/compile/build_howto_openwrt.txt b/docs/compile/build_howto_openwrt.txt new file mode 100644 index 00000000..46a65d80 --- /dev/null +++ b/docs/compile/build_howto_openwrt.txt @@ -0,0 +1,42 @@ +How to compile native programs for use in openwrt +------------------------------------------------- + +1) + + cd ~ + + + git clone git://git.openwrt.org/15.05/openwrt.git + + git clone git://git.openwrt.org/14.07/openwrt.git + + git clone git://git.openwrt.org/openwrt.git + + cd openwrt + +2) ./scripts/feeds update -a + ./scripts/feeds install -a + +3) #add zapret packages to build root + #copy package descriptions + copy compile/openwrt/* to ~/openwrt + #copy source code of tpws + copy tpws to ~/openwrt/package/zapret/tpws + #copy source code of nfq + copy nfq to ~/openwrt/package/zapret/nfq + #copy source code of ip2net + copy ip2net to ~/openwrt/package/zapret/ip2net + +4) make menuconfig + #select your target architecture + #select packages Network/Zapret/* as "M" + +5) make toolchain/compile + +6) make package/tpws/compile + make package/nfqws/compile + make package/ip2net/compile + make package/mdig/compile + +7) find bin -name tpws*.ipk + #take your tpws*.ipk , nfqws*.ipk , ip2net*.ipk, mdig*.ipk from there diff --git a/docs/compile/openwrt/package/zapret/ip2net/Makefile b/docs/compile/openwrt/package/zapret/ip2net/Makefile new file mode 100644 index 00000000..4564675c --- /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/ip2net + $(INSTALL_BIN) $(PKG_BUILD_DIR)/ip2net $(1)/opt/zapret/ip2net +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 00000000..abf7acd9 --- /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 00000000..55d55d26 --- /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/mdig + $(INSTALL_BIN) $(PKG_BUILD_DIR)/mdig $(1)/opt/zapret/mdig +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 00000000..14e5c149 --- /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/nfqws/Makefile b/docs/compile/openwrt/package/zapret/nfqws/Makefile new file mode 100644 index 00000000..48b562f0 --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws/Makefile @@ -0,0 +1,34 @@ +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=nfqws +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/nfqws + SECTION:=net + CATEGORY:=Network + TITLE:=nfqws + SUBMENU:=Zapret + DEPENDS:=+libnetfilter-queue +libcap +zlib +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./nfq/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) +endef + +define Package/nfqws/install + $(INSTALL_DIR) $(1)/opt/zapret/nfq + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/nfq +endef + +$(eval $(call BuildPackage,nfqws)) + + diff --git a/docs/compile/openwrt/package/zapret/nfqws/readme.txt b/docs/compile/openwrt/package/zapret/nfqws/readme.txt new file mode 100644 index 00000000..daf8b84a --- /dev/null +++ b/docs/compile/openwrt/package/zapret/nfqws/readme.txt @@ -0,0 +1 @@ +Copy "nfq" folder here ! diff --git a/docs/compile/openwrt/package/zapret/tpws/Makefile b/docs/compile/openwrt/package/zapret/tpws/Makefile new file mode 100644 index 00000000..3f8dfc7e --- /dev/null +++ b/docs/compile/openwrt/package/zapret/tpws/Makefile @@ -0,0 +1,33 @@ +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=tpws +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/tpws + SECTION:=net + CATEGORY:=Network + TITLE:=tpws + SUBMENU:=Zapret + DEPENDS:=+zlib +libcap +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./tpws/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) +endef + +define Package/tpws/install + $(INSTALL_DIR) $(1)/opt/zapret/tpws + $(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/tpws +endef + +$(eval $(call BuildPackage,tpws)) + diff --git a/docs/compile/openwrt/package/zapret/tpws/readme.txt b/docs/compile/openwrt/package/zapret/tpws/readme.txt new file mode 100644 index 00000000..18fa3edc --- /dev/null +++ b/docs/compile/openwrt/package/zapret/tpws/readme.txt @@ -0,0 +1 @@ +Copy "tpws" folder here ! diff --git a/docs/iptables.txt b/docs/iptables.txt new file mode 100644 index 00000000..4e319e0a --- /dev/null +++ b/docs/iptables.txt @@ -0,0 +1,63 @@ +For window size changing : + +iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass + +For dpi desync attack : + +iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I POSTROUTING -p tcp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I POSTROUTING -p udp --dport 443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass + +# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI +sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 +iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:12 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I PREROUTING -p tcp -m multiport --sports 80,443 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass + + +For TPROXY : + +sysctl -w net.ipv4.ip_forward=1 +iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE + +ip -f inet rule add fwmark 1 lookup 100 +ip -f inet route add local default dev lo table 100 +# prevent loop +iptables -t filter -I INPUT -p tcp --dport 988 -j REJECT +iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1 +iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 + +iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1 +iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 988 + +For DNAT : + +# run tpws as user "tpws". its required to avoid loops. +sysctl -w net.ipv4.conf.eth1.route_localnet=1 +iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 +iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 + + +Reset all iptable rules : + +iptables -F +iptables -X +iptables -t nat -F +iptables -t nat -X +iptables -t mangle -F +iptables -t mangle -X +iptables -t raw -F +iptables -t raw -X + +Reset iptable policies : + +iptables -P INPUT ACCEPT +iptables -P FORWARD ACCEPT +iptables -P OUTPUT ACCEPT +iptables -t mangle -P POSTROUTING ACCEPT +iptables -t mangle -P PREROUTING ACCEPT +iptables -t mangle -P INPUT ACCEPT +iptables -t mangle -P FORWARD ACCEPT +iptables -t mangle -P OUTPUT ACCEPT +iptables -t raw -P PREROUTING ACCEPT +iptables -t raw -P OUTPUT ACCEPT diff --git a/docs/manual_setup.txt b/docs/manual_setup.txt new file mode 100644 index 00000000..5dbdcc01 --- /dev/null +++ b/docs/manual_setup.txt @@ -0,0 +1,282 @@ +Пример ручной установки на debian-подобную систему +-------------------------------------------------- + +На debian основано большое количество дистрибутивов linux, включая ubuntu. +Здесь рассматриваются прежде всего Debian 8+ и Ubuntu 16+. +Но с большой вероятностью может сработать и на производных от них. +Главное условие - наличие systemd, apt и нескольких стандартных пакетов в репозитории. + +Установить пакеты : + apt-get update + apt-get install ipset curl dnsutils git + +Если хотите использовать nftables, то нужен пакет nftables, а ipset не обязателен. + +Скопировать директорию zapret в /opt или скачать через git : + cd /opt + git clone --depth 1 https://github.com/bol-van/zapret + +Запустить автоинсталятор бинариков. Он сам определит рабочую архитектуру и настроит все бинарики. + /opt/zapret/install_bin.sh +АЛЬТЕРНАТИВА : make -C /opt/zapret. Получите динамические бинарики под вашу ось. +Для сборки требуются dev пакеты : zlib1g-dev libcap-dev libnetfilter-queue-dev + +Создать конфиг по умолчанию : + cp /opt/zapret/config.default /opt/zapret/config + +Настроить параметры согласно разделу "Выбор параметров". + +Создать user листы по умолчанию : + cp /opt/zapret/ipset/zapret-hosts-user-exclude.txt.default /opt/zapret/ipset/zapret-hosts-user-exclude.txt + echo nonexistent.domain >/opt/zapret/ipset/zapret-hosts-user.txt + touch /opt/zapret/ipset/zapret-hosts-user-ipban.txt + +Создать ссылку на service unit в systemd : + ln -fs /opt/zapret/init.d/systemd/zapret.service /lib/systemd/system + +Удалить старые листы, если они были созданы ранее : + /opt/zapret/ipset/clear_lists.sh +По желанию прописать в /opt/zapret/ipset/zapret-hosts-user.txt свои домены. +Выполнить скрипт обновления листа : + /opt/zapret/ipset/get_config.sh +Настроить таймер systemd для обновления листа : + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.service /lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.timer /lib/systemd/system + +Принять изменения в systemd : + systemctl daemon-reload + +Включить автозапуск службы : + systemctl enable zapret + +Включить таймер обновления листа : + systemctl enable zapret-list-update.timer + +Запустить службу : + systemctl start zapret + +Шпаргалка по управлению службой и таймером : + +enable auto start : systemctl enable zapret +disable auto start : systemctl disable zapret +start : systemctl start zapret +stop : systemctl stop zapret +status, output messages : systemctl status zapret +timer info : systemctl list-timer +delete service : systemctl disable zapret ; rm /lib/systemd/system/zapret.service +delete timer : systemctl disable zapret-list-update.timer ; rm /lib/systemd/system/zapret-list-update.* + +Centos 7+, Fedora +----------------- + +Centos с 7 версии и более-менее новые федоры построены на systemd. +В качестве пакетного менеджера используется yum. + +Установить пакеты : + yum install -y curl ipset dnsutils git + +Далее все аналогично debian. + +OpenSUSE +-------- + +Новые OpenSUSE основаны на systemd и менеджере пакетов zypper. + +Установить пакеты : + zypper --non-interactive install curl ipset + +Далее все аналогично debian, кроме расположения systemd. +В opensuse он находится не в /lib/systemd, а в /usr/lib/systemd. +Правильные команды будут : + + ln -fs /opt/zapret/init.d/systemd/zapret.service /usr/lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.service /usr/lib/systemd/system + ln -fs /opt/zapret/init.d/systemd/zapret-list-update.timer /usr/lib/systemd/system + +Arch linux +---------- + +Построен на базе systemd. + +Установить пакеты : + pacman -Syy + pacman --noconfirm -S ipset curl + +Далее все аналогично debian. + +Gentoo +------ + +Эта система использует OpenRC - улучшенную версию sysvinit. +Установка пакетов производится командой : emerge +Пакеты собираются из исходников. + +Требуются все те же ipset, curl, git для скачивания с github. +git и curl по умолчанию могут присутствовать, ipset отсутствует. + + emerge ipset + +Настроить параметры согласно разделу "Выбор параметров". + +Запустить автоинсталятор бинариков. Он сам определит рабочую архитектуру и настроит все бинарики. + /opt/zapret/install_bin.sh +АЛЬТЕРНАТИВА : make -C /opt/zapret. Получите динамические бинарики под вашу ось. + +Удалить старые листы, если они были созданы ранее : + /opt/zapret/ipset/clear_lists.sh +По желанию прописать в /opt/zapret/ipset/zapret-hosts-user.txt свои домены. +Выполнить скрипт обновления листа : + /opt/zapret/ipset/get_config.sh +Зашедулить обновление листа : + crontab -e + Создать строчку "0 12 */2 * * /opt/zapret/ipset/get_config.sh" + +Подключить init скрипт : + + ln -fs /opt/zapret/init.d/openrc/zapret /etc/init.d + rc-update add zapret + +Запустить службу : + + rc-service zapret start + +Шпаргалка по управлению службой : + +enable auto start : rc-update add zapret +disable auto start : rc-update del zapret +start : rc-service zapret start +stop : rc-service zapret stop + + + +Ручная установка на openwrt/LEDE 15.xx-21.xx +-------------------------------------------- + +!!! Данная инструкция написана для систем, основанных на iptables+firewall3 +!!! В новых версиях openwrt переходит на nftables+firewall4, инструкция неприменима. Пользуйтесь install_easy.sh + +Установить дополнительные пакеты : +opkg update +opkg install iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt iptables-mod-conntrack-extra ipset curl +(ipv6) opkg install ip6tables-mod-nat +(опционально) opkg install gzip +(опционально) opkg install coreutils-sort + +ЭКОНОМИЯ МЕСТА : + +gzip от busybox в разы медленней полноценного варианта. gzip используется скриптами получения листов. +sort от busybox медленней полноценного варианта и жрет намного больше памяти. sort используется скриптами получения листов. +iptables-mod-nfqueue можно выкинуть, если не будем пользоваться nfqws +curl можно выкинуть, если для получения ip листа будет использоваться только get_user.sh + +Самая главная трудность - скомпилировать программы на C. Это можно сделать на linux x64 при помощи SDK, который +можно скачать с официального сайта openwrt или LEDE. Но процесс кросс компиляции - это всегда сложности. +Недостаточно запустить make как на традиционной linux системе. +Поэтому в binaries имеются готовые статические бинарики для всех самых распространенных архитектур. +Статическая сборка означает, что бинарик не зависит от типа libc (glibc, uclibc или musl) и наличия установленных so. +Его можно использовать сразу. Лишь бы подходил тип CPU. У ARM и MIPS есть несколько версий. +Скорее всего найдется рабочий вариант. Если нет - вам придется собирать самостоятельно. +Для всех поддерживаемых архитектур бинарики запакованы upx. На текущий момент все, кроме mips64. + +Скопировать директорию "zapret" в /opt на роутер. + +Если места достаточно, самый простой способ : + opkg update + opkg install git-http + mkdir /opt + cd /opt + git clone --depth 1 https://github.com/bol-van/zapret + +Если места немного : + opkg update + opkg install openssh-sftp-server unzip + ifconfig br-lan +Скачать на комп с github zip архив кнопкой "Clone or download"->Download ZIP +Скопировать средствами sftp zip архив на роутер в /tmp. + mkdir /opt + cd /opt + unzip /tmp/zapret-master.zip + mv zapret-master zapret + rm /tmp/zapret-master.zip + +Если места совсем мало : +На linux системе скачать и распаковать zapret. Оставить необходимый минимум файлов. +Запаковать в архив zapret.tar.gz. + nc -l -p 1111 1111 >zapret.tar.gz + +Не стоит работать с распакованной версией zapret на windows. Потеряются ссылки и chmod. + +Запустить автоинсталятор бинариков. Он сам определит рабочую архитектуру и настроит все бинарики. + /opt/zapret/install_bin.sh + +Создать ссылку на скрипт запуска : + ln -fs /opt/zapret/init.d/openwrt/zapret /etc/init.d +Создать ссылку на скрипт события поднятия интерфейса : + ln -fs /opt/zapret/init.d/openwrt/90-zapret /etc/hotplug.d/iface + +Создать конфиг по умолчанию : + cp /opt/zapret/config.default /opt/zapret/config + +Настроить параметры согласно разделу "Выбор параметров". + +Создать user листы по умолчанию : + cp /opt/zapret/ipset/zapret-hosts-user-exclude.txt.default /opt/zapret/ipset/zapret-hosts-user-exclude.txt + echo nonexistent.domain >/opt/zapret/ipset/zapret-hosts-user.txt + touch /opt/zapret/ipset/zapret-hosts-user-ipban.txt + +Удалить старые листы, если они были созданы ранее : + /opt/zapret/ipset/clear_lists.sh +По желанию прописать в /opt/zapret/ipset/zapret-hosts-user.txt свои домены. +Выполнить скрипт обновления листа : + /opt/zapret/ipset/get_config.sh +Зашедулить обновление листа : + crontab -e + Создать строчку "0 12 */2 * * /opt/zapret/ipset/get_config.sh" + +Включить автозапуск службы и запустить ее : + /etc/init.d/zapret enable + /etc/init.d/zapret start +ПРИМЕЧАНИЕ : на этапе старта системы интерфейсы еще не подняты. в некоторых случаях невозможно правильно +сформировать параметры запуска демонов, не зная имя физического интерфейса LAN. +Cкрипт из /etc/hotplug.d/iface перезапустит демоны по событию поднятия LAN. + +Создать ссылку на firewall include : + ln -fs /opt/zapret/init.d/openwrt/firewall.zapret /etc/firewall.zapret +Проверить была ли создана ранее запись о firewall include : + uci show firewall | grep firewall.zapret +Если firewall.zapret нет, значит добавить : + uci add firewall include + uci set firewall.@include[-1].path="/etc/firewall.zapret" + uci set firewall.@include[-1].reload="1" + uci commit firewall +Проверить не включен ли flow offload : + uci show firewall.@defaults[0] +Если flow_offloading=1 или flow_offloading_hw=1 , + uci set firewall.@defaults[0].flow_offloading=0 + uci set firewall.@defaults[0].flow_offloading_hw=0 + uci commit firewall +Перезапустить фаервол : + fw3 restart + +Посмотреть через iptables -nL, ip6tables -nL или через luci вкладку "firewall" появились ли нужные правила. + +ЭКОНОМИЯ МЕСТА : если его мало, то можно оставить в директории zapret лишь подкаталоги +ipset, common, файл config, init.d/openwrt. +Далее нужно создать подкаталоги с реально используемыми бинариками (ip2net, mdig, tpws, nfq) +и скопировать туда из binaries рабочие executables. + +ЕСЛИ ВСЕ ПЛОХО С МЕСТОМ : откажитесь от работы со списком РКН. используйте только get_user.sh + +ЕСЛИ СОВСЕМ ВСЕ УЖАСНО С МЕСТОМ : берете tpws и делаете все своими руками. поднятие iptables, автостарт бинарика. +С некоторых версий скрипты запуска zapret без ipset не работают (он требуется для ip exclude) + +СОВЕТ : Покупайте только роутеры с USB. В USB можно воткнуть флэшку и вынести на нее корневую файловую систему +или использовать ее в качестве оверлея. Не надо мучать себя, запихивая незапихиваемое в 8 мб встроенной флэшки. +Для комфортной работы с zapret нужен роутер с 16 Mb встроенной памяти или USB разъемом и 128+ Mb RAM. +На 64 Mb без swap будут проблемы с листами РКН. Если у вас только 64 Mb, и вы хотите листы РКН, подключите swap. +32 Mb для современных версий openwrt - конфигурация на грани живучести. Возможны хаотические падения процессов в oom. +Работа с листами РКН невозможна в принципе. + diff --git a/docs/nftables.txt b/docs/nftables.txt new file mode 100644 index 00000000..4acbb595 --- /dev/null +++ b/docs/nftables.txt @@ -0,0 +1,32 @@ +nftables test cheat sheet +simplified rules to test nfqws and tpws + + +For DNAT : + +# run tpws as user "tpws". its required to avoid loops. + +nft delete table inet ztest +nft create table inet ztest +nft add chain inet ztest pre "{type nat hook prerouting priority dstnat;}" +nft add rule inet ztest pre tcp dport "{80,443}" redirect to :988 +nft add chain inet ztest out "{type nat hook output priority -100;}" +nft add rule inet ztest out tcp dport "{80,443}" skuid != tpws redirect to :988 + + +For dpi desync attack : + +nft delete table inet ztest +nft create table inet ztest +nft add chain inet ztest post "{type filter hook postrouting priority mangle;}" +nft add rule inet ztest post tcp dport "{80,443}" ct original packets 1-12 queue num 200 bypass +nft add rule inet ztest post udp dport 443 ct original packets 1-4 queue num 200 bypass + +# auto hostlist with avoiding wrong ACK numbers in RST,ACK packets sent by russian DPI +sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 +nft add chain inet ztest pre "{type filter hook prerouting priority filter;}" +nft add rule inet ztest pre tcp sport "{80,443}" ct reply packets 1-4 queue num 200 bypass + + +show rules : nft list table inet ztest +delete table : nft delete table inet ztest diff --git a/docs/nftables_notes.txt b/docs/nftables_notes.txt new file mode 100644 index 00000000..75fa845f --- /dev/null +++ b/docs/nftables_notes.txt @@ -0,0 +1,120 @@ +nftables - это технология, пришедшая на замену iptables. +В ней собрали все, что относилось к различным iptables. А их немало. iptables, ip6tables, ebtables, arptables, ipset. +Весь код из разрозненных, но похожих компонент, собрали в одно целое с единым синтаксисом. +Добавили различные конструкции языка, позволяющие писать правила более лаконично, не повторяя одни и те же команды с небольшими различиями. +На nftables можно сделать почти все, что можно было сделать на iptables. Есть то, что можно сделать на nftables, но нельзя на iptables. +Есть и наоборот. + +К сожалению, не обошлось и без боли. + +Главная боль N1. Очень серьезная, актуальная для openwrt, и решения не видно. + +ipset-ы позволяли загонять пересекающиеся интервалы ip адресов или подсетей. +nftables sets это не позволяют. Любое пересечение вызывает ошибку. +Есть auto-merge, но это работает только в user mode в процессе nft, при условии, что весь блок адресов загоняется одной командой +и нет пересечений с уже имеющимся контентом в set. +Это не было бы критической проблемой, поскольку скрипты zapret и так загоняют ipset целиком. +Проблема в катастрофическом расходе памяти при операции загона больших интервальных листов, то есть с подсетями и диапазонами. +Чтобы загнать 100000 ipv4 записей, едва хватает 300 Mb памяти устройства. +При успехе операции в ядре список столько не занимает, но суть дела это не меняет. +Для традиционных linux систем это не проблема, но почти все роутеры загнутся. +Приемлемого решения не просматривается. +Сделать записи непересекающимися в листах - задача непростая. Потребуется переписать алгоритм auto-merge из nft, +но с пониженным расходом памяти. +Загонять записи по одному отдельными вызовами nft, игнорируя ошибки, займет вечность. +Загонять блоком отдельных команд, игнорируя ошибки, - nft такого не умеет. Похоже, при любой ошибке происходит откат всего скрипта. +К тому же при таком подходе будут неточности в итоговом результате. +Swap позволяет немного сгладить проблему, но лишь незначительно. +Скажем, если вдруг list загоняется без ошибок с 300 Mb памяти и с падением на 256 Mb, то swap спасает. +Если памяти становится 200 Mb, то swap уже не спасет. Все равно вызывается OOM killer, заодно убивая и другие процессы, кроме nft, +а это уже совсем плохо. Может быть убито что-то важное. + +Боль N2, не смертельная, но тоже не айс. + +Какие-то нерациональные алгоритмы разбора таблиц в nft. +Например, есть 1 большой set на 100000 элементов и 1 маленький на 2 элемента. +Чтобы просто пролистать мелкий set или добавить туда еще что-то nft будет мусолить несколько секунд. +Что он делает за это время ? Тащит из ядра огромный блоб, в котором все в куче, и разбирает его, чтобы выделить искомую мелочь ? +В какой-то мере удается это сгладить, обьединяя несколько команд в единый скрипт. + +Боль N3 + +Система nftables построена на виртуальной машине. Правила, которые вы пишите, переводятся в псевдокод VM. +Чтобы потом их показать , nft декомпилирует код и переводит в читаемый язык. +Это довольно сложно, и регулярно случаются баги, связанные с неверным отображением. + +Кроме этого, часто встречаются и баги парсера. +Например, все версии nft вплоть до 1.0.1 имеют баг, который не разрешает названия интерфейсов в кавычках в +определении flowtable. Без кавычек нельзя вставить интерфейсы , имя которых начинается с цифры. +OpenWRT решает эту проблему отдельным патчем в snapshot версии, но на традиционных системах и в openwrt 21.x- его нет. +Почему бы не наплевать на интерфейсы, начинающиеся с цифры ? Потому что для openwrt 6to4-6to4, 6in4-he-net - обычное явление. +На текущий момент этой проблемы в openwrt уже нет, если использовать актуальную версию. + +Но тем не менее, хоть nft и давно перешел отметку 1.0, всякая мелочь, особенно на не совсем стандартных правилах, +регулярно всплывает. Потому чем новее у вас будет версия nft, тем больше там выловлено проблем. +Здесь обновления важны, чтобы потом не мучаться из-за давно исправленного велосипеда. + +Боль N4 + +Невозможно , не копаясь в других таблицах и хуках, ничего не зная об их содержании, предотвратить DROP или REJECT. +Нельзя написать такое правило, которое что-то важное ACCEPTнет, игнорируя остальные хуки во всех таблицах. +Если у вас есть какой-то фаервол, и он что-то дропает, то как от этого отказаться, если надо временно что-то принять ? +Это особенность netfilter, он так работает, но в iptables есть лишь стандартные таблицы с их хуками, куда можно +вставить ACCEPT. Здесь хуков может быть сколько угодно в каких угодно таблицах. +Эта проблема частично ломает кайф от независимого управления таблицами. + + +Плюс N1, главный + +iptables хороши, когда ими пользуется кто-то один. Иначе это проходной двор. +Когда есть система управления фаерволом, то приходится как-то к ней прикручиваться, чтобы не нарушить ее работу +и управлять правилами синхронно. Нужно уметь внести и удалить отдельные правила когда это нужно, не трогая все остальное. +Некоторые системы управления фаерволом вообще не предполагают, чтобы кто-то еще лез в iptables, и это очень сильно портит жизнь. +У iptables есть предопределенный набор хуков netfilter с фиксированным приоритетом. +В nftables хуков можно создать неограниченное количество с выбранным приоритетом, управляя ими независимо в отдельных таблицах. +Система управления фаерволом может работать в одной таблице (fw4 в случае openwrt) и не трогать все остальное. +zapret может работать в другой таблице и не трогать систему управления фаерволом. Они друг другу не мешают. +Это снимает множество боли. +Но есть и исключение. nfset-ы - аналог ipset-ов - нельзя использовать из другой таблицы. Потому если вам нужен ipset, +создаваемый zapret скриптами, вам понадобится писать правила в той же таблице. Но нет никакой необходимости влезать в цепочки zapret. +Создаете свои цепочки и хуки и делаете в них что угодно. + +Плюс N2 + +Возможность выбора приоритета хука позволяет легко решить проблему хаотической и принудительной дефрагментацией L3 ipv6, +без танцев с загрузкой модулей ядра со специальными параметрами или перекомпиляцией nftables-nft. +Это же позволяет перехватить трафик после SNAT/MASQUERADE, что на iptables невозможно. +Атаки на проходящий трафик, ломающие NAT, крайне затруднены на iptables. + +Плюс N3 + +Наличие множеств (anonymous/named sets) позволяет не писать кучу однообразных правил там, где в iptables их пришлось бы написать. + +Плюс N4 + +Если у вас есть nftables, то там наверняка есть уже все или почти все. +Нет кучи разных модулей ядра и .so плагинов для iptables user-mode процесса. +Отдельные модули ядра есть, но их меньше, чем в iptables, и openwrt их делит на меньшее число пакетов, большинство из которых +и так ставятся по умолчанию. user-mode процесс nft и вовсе неделим. EXE-шник + lib. + +Плюс N5 + +Пишут, что nftables работают быстрее. Но это не точно и зависит от много чего. +В целом - чем меньше правил, тем выше скорость. Но в nftables правил можно писать меньше, значит скрость тоже может быть выше. +У разработчиков есть идея перевести backend nftables на BPF, а это наверняка будет существенно быстрее. + + +Выводы + +Без больших листов все почти прекрасно. Но большие ip листы убивают все. Не для домашних это роутеров. +А ipset-ы к nftables не прикрутить. +Зато есть возможность задействовать более продвинутые атаки, конфликтующие с NAT, которые на iptables могут быть невозможны. +Делать нечего. Openwrt отошел от iptables. С этим придется как-то жить. +Поэтому пришлось сделать для openwrt поддержку и iptables, и nftables (только с версии openwrt 21.xx, в более старых будут проблемы). +iptables можно задействовать на любой openwrt версии. +Если используется fw3, применяется старый механизм интеграции в fw3. +Если он не используется, то правилами iptables управляем как в традиционных linux системах - то есть с возможностью +запуска и остановки, а скрипт запуска вносит в том числе и правила iptables. + +На новых openwrt возможно снести nftables и firewall4 и установить firewall3 и iptables. +Если вам никак без больших ip листов на слабой системе, это может быть единственным спасением. diff --git a/docs/quick_start.txt b/docs/quick_start.txt new file mode 100644 index 00000000..4d0aee8b --- /dev/null +++ b/docs/quick_start.txt @@ -0,0 +1,141 @@ +Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню readme.txt. + +Предупреждение : не пишите в issue вопросы типа "как скопировать файл", "как скачать", "как запустить", ... +То есть все , что касается базовых навыков обращения с ОС linux. Эти вопросы буду закрывать сразу. +Если у вас подобные вопросы возникают, рекомендую не использовать данный софт или искать помощь где-то в другом месте. +То же самое могу сказать тем, кто хочет нажать 1 кнопку, чтобы все заработало, и совсем не хочет читать и изучать. +Увы, такое не подвезли и не подвезут. Ищите другие более простые методы обхода. Этот метод не для рядового пользователя. + +Обход DPI является хакерской методикой. Под этим словом понимается метод, которому сопротивляется окружающая среда, +которому автоматически не гарантирована работоспособность в любых условиях и на любых ресурсах, +требуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем, +и методика может начинать или переставать работать, может потребоваться повторный анализ ситуации. +Могут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать. +Могут и сломаться отдельные незаблокированные ресурсы. +Поэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию. +Не будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает. + +Будем считать, что у вас есть система на базе традиционного linux или openwrt. +Если у вас традиционный linux - задача обойти блокировки только на этой системе, если openwrt - обойти блокировки +для подключенных устройств. Это наиболее распространенный случай. + +1) Чтобы процедура установки сработала в штатном режиме на openwrt, нужно раcсчитывать на свободное место около 1-2 Mb +для установки самого zapret и необходимых дополнительных пакетов. +Если места мало и нет возможности его увеличить за счет extroot, возможно придется отказаться от варианта +простой установки и прикручивать в ручном режиме без имеющихся скриптов запуска, либо попробовать засунуть требуемые +zapret дополнительные пакеты в сжатый образ squashfs с помощью image builder и перешить этим вариантом роутер. +См docs/manual_setup.txt , docs/readme.txt . + +2) Скачайте zip архив проекта с github в /tmp, распакуйте его там, +либо клонируйте проект через : git clone --depth 1 https://github.com/bol-van/zapret + +3) Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret. +Гарантированно уберет zapret скрипт uninstall_easy.sh. + +4) Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. nat не подходит + +5) Выполните однократные действия по установке требуемых пакетов в ОС и настройке бинариков правильной архитектуры + +install_bin.sh +install_prereq.sh + +Вас могут спросить о типе фаервола (iptables/nftables) и использовании ipv6. Это нужно для установки +правильных пакетов в ОС, чтобы не устанавливать лишнее. + +6) Запустите blockcheck.sh. blockcheck.sh в начале проверяет DNS. Если выводятся сообщения о подмене адресов, то +первым делом нужно решить эту проблему, иначе ничего не будет работать. +Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо заменой DNS серверов +от провайдера на публичные (1.1.1.1, 8.8.8.8), либо в случае перехвата провайдером обращений +к сторонним серверам - через специальные средства шифрования DNS запросов, такие как dnscrypt, DoT, DoH. + +Еще один эффективный вариант - использовать ресолвер от yandex 77.88.8.88 на нестандартном порту 1253. +Многие провайдеры не анализируют обращения к DNS на нестандартных портах. + +Проверить работает ли этот вариант можно так : + +dig -p 53 @77.88.8.88 rutracker.org +dig -p 1253 @77.88.8.88 rutracker.org + +Если DNS действительно подменяется, и ответ на эти 2 команды разный, значит метод вероятно работает. + +В openwrt DNS на нестандартном порту можно прописать в /etc/config/dhcp таким способом : + +config dnsmasq + ............. + list server '77.88.8.88#1253' + +Если настройки IP и DNS получаются автоматически от провайдера, в /etc/config/network +найдите секцию интерфейса 'wan' и сделайте так : + +config interface 'wan' + ............. + option peerdns '0' + +/etc/init.d/network restart +/etc/init.d/dnsmasq restart + +Если это не подходит, можно перенаправлять обращения на udp и tcp порты 53 вашего DNS сервера на 77.88.8.88:1253 средствами +iptables/nftables. В /etc/resolv.conf нельзя прописать DNS на нестандартном порту. + +7) blockcheck позволяет выявить рабочую стратегию обхода блокировок +По результатам blockcheck нужно понять какой вариант будете использовать : nfqws или tpws +И запомнить найденные стратегии. + +Следует понимать, что blockcheck проверяет доступность только конкретного домена, который вы вводите в начале. +Вероятно, все остальные домены блокированы подобным образом, но не факт. +В большинстве случаев можно обьединить несколько стратегий в одну универсальную, но для этого необходимо понимать +"что там за буковки". Если вы в сетях слабо разбираетесь, это не для вас. В противном случае читайте readme.txt. +zapret не может пробить блокировку по IP адресу +Для проверки нескольких доменов вводите их через пробел. + +Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у вас может быть несколько маршрутов +с различной длиной по ХОПам, с DPI на разных хопах. Приходится преодолевать целый зоопарк DPI, +которые еще и включаются в работу хаотичным образом или образом, зависящим от направления (IP сервера). +blockcheck не всегда может выдать вам в итогах оптимальную стратегию, которую надо просто переписать в настройки. +В некоторых случаях надо реально думать что происходит, анализируя результат на разных стратегиях. +Если вы применяете большой TTL, чтобы достать до магистрала, то не лишним будет добавить дополнительный ограничитель +--dpi-desync-fooling, чтобы не сломать сайты на более коротких дистанциях. +md5sig наиболее совместим, но работает только на linux серверах. +badseq может работать только на https и не работать на http. +Чтобы выяснить какие дополнительные ограничители работают, смотрите результат теста аналогичных стратегий без TTL +с каждым из этих ограничителей. + +При использовании autottl следует протестировать как можно больше разных доменов. Эта техника +может на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах +она стабильна, на третьих полный хаос, и проще отказаться. + +Если используются методы нулевой фазы десинхронизации (--mss, --wssize, --dpi-desync=syndata) и режим фильтрации hostlist, +то все параметры, относящиеся к этим методам, следует помещать не в основные параметры (например, NFQWS_OPT_DESYNC), +а в suffix (NFQWS_OPT_DESYNC_SUFFIX). Чтобы не ошибиться, можно их продублировать и там, и там. +Иначе они могут не работать. + +8) Запустите install_easy.sh. +Выберите nfqws или tpws, затем согласитесь на редактирование параметров. +Откроется редактор, куда впишите найденные стратегии. +Для nfqws отдельно настраиваются стратегии на http и https для ipv4 и ipv6. +То есть по максимуму 4 разных варианта. +NFQWS_OPT_DESYNC - это общая установка, которая применяется, если какой-либо уточняющий параметр не задан +NFQWS_OPT_DESYNC_HTTP и NFQWS_OPT_DESYNC_HTTPS заменяют стратегию для http и https. +Если у вас включен ipv6, то они так же будут применены и к ipv6. Если для ipv6 нужна другая стратегия, +то можно задать уточняющие параметры NFQWS_OPT_DESYNC_HTTP6 и NFQWS_OPT_DESYNC_HTTPS6. +Если стратегии для ipv4 и ipv6 отличаются лишь ttl, то в целях экономии ресурсов роутера (меньше процессов nfqws) +следует отказаться от использования специфических для ipv6 установок. Вместо них использовать параметры +--dpi-desync-ttl и --dpi-desync-ttl6 в общих установках. Таким способом можно заставить один процесс nfqws +обрабатывать трафик на ipv4 и на ipv6 с разным ttl. + +Важным вопросом является вопрос о поддержке http keep alive. +Отвечайте N. Если вдруг на http сайтах будут хаотические сбои типа то загружается, то заглушка или сброс, +попробуйте включить поддержку keep alive. Но просто "на всякий случай" не включайте - это увеличит нагрузку на роутер. + +Если это не помогает, или хаотичное поведение наблюдается и на https, то еще раз прогоните blockcheck +с установленным числом попыток проверки не менее 5. Возможно, ваш провайдер использует балансировку нагрузки, +где на разных путях установлен разный DPI. + +9) На все остальные вопросы install_easy.sh отвечайте согласно выводимой аннонтации. + +10) Если ломаются отдельные незаблокированные ресурсы, следует вносить их в исключения, либо пользоваться ограничивающим +ipset или хост листом. Читайте основной талмуд readme.txt ради подробностей. + +Это минимальная инструкция, чтобы соориентироваться с чего начать. Однако, это - не панацея. +В некоторых случаях вы не обойдетесь без знаний и основного "талмуда". +Подробности и полное техническое описание расписаны в readme.txt diff --git a/docs/quick_start_windows.txt b/docs/quick_start_windows.txt new file mode 100644 index 00000000..d9e5a6d1 --- /dev/null +++ b/docs/quick_start_windows.txt @@ -0,0 +1,122 @@ +Специально для тех, кто хочет побыстрее начать, но не хочет слишком углубляться в простыню readme.txt. + +Как обычно, компьютерная грамотность ложится полностью на вас. +Вы должны уметь работать с консолью windows и иметь минимальные навыки обращения с командными файлами bat,cmd. +Если грамотность отсутствует и возникает куча "как" на базовых вещах - проходите мимо или ищите помощь в другом месте. + +Обход DPI является хакерской методикой. Под этим словом понимается метод, которому сопротивляется окружающая среда, +которому автоматически не гарантирована работоспособность в любых условиях и на любых ресурсах, +требуется настройка под специфические условия у вашего провайдера. Условия могут меняться со временем, +и методика может начинать или переставать работать, может потребоваться повторный анализ ситуации. +Могут обнаруживаться отдельные ресурсы, которые заблокированы иначе, и которые не работают или перестали работать. +Могут и сломаться отдельные незаблокированные ресурсы. +Поэтому очень желательно иметь знания в области сетей, чтобы иметь возможность проанализировать техническую ситуацию. +Не будет лишним иметь обходные каналы проксирования трафика на случай, если обход DPI не помогает. + +Будем считать, что у вас есть windows 7 или выше. Задача - обойти блокировки с самой системы. + +Есть решение самое простое, а есть "как положено". + +САМОЕ ПРОСТОЕ РЕШЕНИЕ + +Совсем ничего не могу, все очень сложно, дайте мне таблетку. + +Скачайте и распакуйте архив https://github.com/bol-van/zapret-win-bundle/archive/refs/heads/master.zip +Запустите zapret-winws/preset_russia.cmd от имени администратора. +Возможно, заведется сразу. Этот вариант похож на "2_any_country.cmd" из GoodbyeDPI. + +То же самое с ограничителем по автоматически создаваемому хост-листу preset_russia_autohostlist.cmd. +Что такое autohostlist - читайте readme.txt. Проще говоря, мы обходим только то, что долго и упорно не хочет открываться. +Сначала не будет, но надо пытаться много раз, и тогда сработает, а дальше будет всегда срабатывать. +Остальное не будет ломаться. Использовать только, если первый вариант тоже работает. + +Не помогла таблетка ? Это вовсе не значит, что ничего не получится. Но придется делать по-нормальному. + +РЕШЕНИЕ "КАК ПОЛОЖЕНО" + +1) Если у вас windows 7, обновляйте систему. Годами не обновляемая 7-ка может не запускать драйвер windivert. +Поддержка 32-битных x86 windows возможна, но в готовом виде отсутствует. +На windows 11 arm64 выполните arm64/install_arm64.cmd от имени администратора и перезагрузите компьютер. +Читайте docs/windows.txt + +Имейте в виду, что антивирусы могут плохо реагировать на windivert. +cygwin имеет внушительный список несовместимостей с антивирусами. Многие антивирусы его ломают. +https://www.cygwin.com/faq.html#faq.using.bloda +Если это имеет место , используйте исключения. Если это не помогает - отключайте антивирус совсем. + +2) Убедитесь, что у вас отключены все средства обхода блокировок, в том числе и сам zapret. + +3) Если вы работаете в виртуальной машине, необходимо использовать соединение с сетью в режиме bridge. nat не подходит + +4) Скачайте и распакуйте архив https://github.com/bol-van/zapret-win-bundle/archive/refs/heads/master.zip + +5) Запустите blockcheck\blockcheck.cmd. blockcheck в начале проверяет DNS. Если выводятся сообщения о подмене адресов, то +первым делом нужно решить эту проблему, иначе ничего не будет работать. +Решение проблемы DNS выходит за рамки проекта. Обычно она решается либо заменой DNS серверов +от провайдера на публичные (1.1.1.1, 8.8.8.8), либо в случае перехвата провайдером обращений +к сторонним серверам - через специальные средства шифрования DNS запросов, такие как dnscrypt, DoT, DoH. +В современных броузерах чаще всего DoH включен по умолчанию, но curl будет использовать обычный системный DNS. +Новые билды win10 и win11 поддерживают системные DoH из коробки. Они не настроены по умолчанию. + +Тут все разжевано как и где это включается : https://hackware.ru/?p=13707 + +6) blockcheck позволяет выявить рабочую стратегию обхода блокировок. +Лог скрипта будет сохранен в blockcheck\blockcheck.log. +Запомните найденные стратегии. + +Следует понимать, что blockcheck проверяет доступность только конкретного домена, который вы вводите в начале. +Вероятно, все остальные домены блокированы подобным образом, но не факт. +В большинстве случаев можно обьединить несколько стратегий в одну универсальную, но для этого необходимо понимать +"что там за буковки". Если вы в сетях слабо разбираетесь, это не для вас. В противном случае читайте readme.txt. +zapret не может пробить блокировку по IP адресу +Для проверки нескольких доменов вводите их через пробел. + +Сейчас блокираторы ставят на магистральных каналах. В сложных случаях у вас может быть несколько маршрутов +с различной длиной по ХОПам, с DPI на разных хопах. Приходится преодолевать целый зоопарк DPI, +которые еще и включаются в работу хаотичным образом или образом, зависящим от направления (IP сервера). +blockcheck не всегда может выдать вам в итогах оптимальную стратегию, которую надо просто переписать в настройки. +В некоторых случаях надо реально думать что происходит, анализируя результат на разных стратегиях. +Если вы применяете большой TTL, чтобы достать до магистрала, то не лишним будет добавить дополнительный ограничитель +--dpi-desync-fooling, чтобы не сломать сайты на более коротких дистанциях. +md5sig наиболее совместим, но работатет только на linux серверах. +badseq может работать только на https и не работать на http. +Чтобы выяснить какие дополнительные ограничители работают, смотрите результат теста аналогичных стратегий без TTL +с каждым из этих ограничителей. + +При использовании autottl следует протестировать как можно больше разных доменов. Эта техника +может на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах +она стабильна, на третьих полный хаос, и проще отказаться. + +Если используются методы нулевой фазы десинхронизации (--wssize, --dpi-desync=syndata) и фильтр hostlist, +то все параметры, относящиеся к этим методам, следует помещать в следующий профиль без хостлиста, +к которому перейдет управление, когда имя хоста еще неизвестно. +Используйте параметр --debug для отладки вашего сценария. + +7) Протестируйте найденные стратегии на winws. winws следует брать из zapret-winws. +Для этого откройте командную строку windows от имени администратора в директории zapret-winws. +Проще всего это сделать через _CMD_ADMIN.cmd. Он сам поднимет права и зайдет в нужную директорию. + +8) Обеспечьте удобную загрузку обхода блокировок. + +Есть 2 варианта. Ручной запуск через ярлык или автоматический при старте системы, вне контекста текущего пользователя. +Последний вариант разделяется на запуск через планировщик задач и через службы windows. + +Если хотите ручной запуск, скопируйте preset_russia.cmd в preset_my.cmd и адаптируйте его под ваши параметра запуска. +Потом можно создать ярлык на рабочем столе на preset_my.cmd. Не забудьте, что требуется запускать от имени администратора. + +Но лучше будет сделать неинтерактивный автоматический запуск вместе с системой. +В zapret-winws есть командные файлы task_*, предназначенные для управления задачами планировщика. +Там следует поменять содержимое переменной WINWS1 на свою стратегию. +Если вы не можете обьединить несколько стратегий для разных протоколов в одну, дублируйте код в каждом из cmd +для поддержки нескольких задач : winws1,winws2,winws3. +После создания задач запустите их. Проверьте, что обход встает после перезагрузки windows. + +Аналогично настраиваются и службы windows. Смотрите service_*.cmd + +9) Если ломаются отдельные незаблокированные ресурсы, используйте хост-листы. +Где они будут находиться - решайте сами. +Параметры управления хост-листами точно такие же, как в *nix. + +Это минимальная инструкция, чтобы соориентироваться с чего начать. Однако, это - не панацея. +В некоторых случаях вы не обойдетесь без знаний и основного "толмуда". +Подробности и полное техническое описание расписаны в readme.txt diff --git a/docs/readme.eng.md b/docs/readme.eng.md new file mode 100644 index 00000000..40f298ef --- /dev/null +++ b/docs/readme.eng.md @@ -0,0 +1,1272 @@ +## What is it for + +A stand-alone (without 3rd party servers) DPI circumvention tool. +May allow to bypass http(s) website blocking or speed shaping, resist signature tcp/udp protocol discovery. + +The project is mainly aimed at the Russian audience to fight russian regulator named "Roskomnadzor". +Some features of the project are russian reality specific (such as getting list of sites +blocked by Roskomnadzor), but most others are common. + +Mainly OpenWRT targeted but also supports traditional Linux, FreeBSD, OpenBSD, Windows, partially MacOS. + +Most features are also supported in Windows. + +## How it works + +In the simplest case you are dealing with passive DPI. Passive DPI can read passthrough traffic, +inject its own packets, but cannot drop packets. + +If the request is prohibited the passive DPI will inject its own RST packet and optionally http redirect packet. + +If fake packets from DPI are only sent to client, you can use iptables commands to drop them if you can write +correct filter rules. This requires manual in-deep traffic analysis and tuning for specific ISP. + +This is how we bypass the consequences of a ban trigger. + +If the passive DPI sends an RST packet also to the server, there is nothing you can do about it. +Your task is to prevent ban trigger from firing up. Iptables alone will not work. +This project is aimed at preventing the ban rather than eliminating its consequences. + +To do that send what DPI does not expect and what breaks its algorithm of recognizing requests and blocking them. + +Some DPIs cannot recognize the http request if it is divided into TCP segments. +For example, a request of the form `GET / HTTP / 1.1 \ r \ nHost: kinozal.tv ......` +we send in 2 parts: first go `GET`, then `/ HTTP / 1.1 \ r \ nHost: kinozal.tv .....`. + +Other DPIs stumble when the `Host:` header is written in another case: for example, `host:`. + +Sometimes work adding extra space after the method: `GET /` => `GET /` +or adding a dot at the end of the host name: `Host: kinozal.tv.` + +There is also more advanced magic for bypassing DPI at the packet level. + +## How to put this into practice in the linux system + +In short, the options can be classified according to the following scheme: + +1. Passive DPI not sending RST to the server. ISP tuned iptables commands can help. +This option is out of the scope of the project. If you do not allow ban trigger to fire, then you won’t have to +deal with its consequences. +2. Modification of the TCP connection at the stream level. Implemented through a proxy or transparent proxy. +3. Modification of TCP connection at the packet level. Implemented through the NFQUEUE handler and raw sockets. + +For options 2 and 3, tpws and nfqws programs are implemented, respectively. +You need to run them with the necessary parameters and redirect certain traffic with iptables or nftables. + +To redirect a TCP connection to a transparent proxy, the following commands are used: + +forwarded traffic : +`iptables -t nat -I PREROUTING -i -p tcp --dport 80 -j DNAT --to 127.0.0.127:988` + +outgoing traffic : +`iptables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988` + +DNAT on localhost works in the OUTPUT chain, but does not work in the PREROUTING chain without enabling the route_localnet parameter: + +`sysctl -w net.ipv4.conf..route_localnet=1` + +You can use `-j REDIRECT --to-port 988` instead of DNAT, but in this case the transparent proxy process +should listen on the ip address of the incoming interface or on all addresses. Listen all - not good +in terms of security. Listening one (local) is possible, but automated scripts will have to recognize it, +then dynamically enter it into the command. In any case, additional efforts are required. +Using route_localnet can also introduce some security risks. You make available from internal_interface everything +bound to `127.0.0.0/8`. Services are usually bound to `127.0.0.1`. Its possible to deny input to `127.0.0.1` from all interfaces except lo +or bind tpws to any other IP from `127.0.0.0/8` range, for example to `127.0.0.127`, and allow incomings only to that IP : + +``` +iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT +iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP +``` + +Owner filter is necessary to prevent recursive redirection of connections from tpws itself. +tpws must be started under OS user `tpws`. + +NFQUEUE redirection of the outgoing traffic and forwarded traffic going towards the external interface, +can be done with the following commands: + +`iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass` + +In order not to touch the traffic to unblocked addresses, you can take a list of blocked hosts, resolve it +into IP addresses and put them to ipset 'zapret', then add a filter to the command: + +`iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass` + +Some DPIs catch only the first http request, ignoring subsequent requests in a keep-alive session. +Then we can reduce CPU load, refusing to process unnecessary packets. + +`iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass` + +Mark filter does not allow nfqws-generated packets to enter the queue again. +Its necessary to use this filter when also using `connbytes 1:6`. Without it packet ordering can be changed breaking the whole idea. + +Some attacks require redirection of incoming packets : + +`iptables -t mangle -I PREROUTING -i -p tcp --sport 80 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:6 -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass` + +Incoming packets are filtered by incoming interface, source port and IP. This is opposite to the direct rule. + +Some techniques that break NAT are possible only with nftables. + + +## ip6tables + +ip6tables work almost exactly the same way as ipv4, but there are a number of important nuances. +In DNAT, you should take the address --to in square brackets. For example : + + `ip6tables -t nat -I OUTPUT -o -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988` + +The route_localnet parameter does not exist for ipv6. +DNAT to localhost (:: 1) is possible only in the OUTPUT chain. +In the PREROUTING DNAT chain, it is possible to any global address or to the link local address of the same interface +the packet came from. +NFQUEUE works without changes. + + +## nftables + +nftables are fine except one very big problem. +nft requires tons of RAM to load large nf sets (ip lists) with subnets/intervals. Most of the home routers can't afford that. +For example, even a 256 Mb system can't load a 100K ip list. nft process will OOM. +nf sets do not support overlapping intervals and that's why nft process applies very RAM consuming algorithm to merge intervals so they don't overlap. +There're equivalents to iptables for all other functions. Interface and protocol anonymous sets allow not to write multiple similar rules. +Flow offloading is built-in into new linux kernels and nft versions. + +nft version `1.0.2` or higher is recommended. But the higher is version the better. + +Some techniques can be fully used only with nftables. It's not possible to queue packets after NAT in iptables. +This limits techniques that break NAT. + + +## When it will not work + +* If DNS server returns false responses. ISP can return false IP addresses or not return anything +when blocked domains are queried. If this is the case change DNS to public ones, such as 8.8.8.8 or 1.1.1.1.Sometimes ISP hijacks queries to any DNS server. Dnscrypt or dns-over-tls help. +* If blocking is done by IP. +* If a connection passes through a filter capable of reconstructing a TCP connection, and which +follows all standards. For example, we are routed to squid. Connection goes through the full OS tcpip stack, fragmentation disappears immediately as a means of circumvention. Squid is correct, it will find everything as it should, it is useless to deceive him. BUT. Only small providers can afford using squid, since it is very resource intensive. Large companies usually use DPI, which is designed for much greater bandwidth. + +## nfqws + +This program is a packet modifier and a NFQUEUE queue handler. +For BSD systems there is dvtws. Its built from the same source and has almost the same parameters (see bsd.eng.md). +nfqws takes the following parameters: + +``` + --debug=0|1 + --qnum= + --daemon ; daemonize + --pidfile= ; write pid to file + --user= ; drop root privs + --uid=uid[:gid] ; drop root privs + --bind-fix4 ; apply outgoing interface selection fix for generated ipv4 packets + --bind-fix6 ; apply outgoing interface selection fix for generated ipv6 packets + --wsize=[:] ; set window size. 0 = do not modify. OBSOLETE ! + --wssize=[:] ; set window size for server. 0 = do not modify. default scale_factor = 0. + --wssize-cutoff=[n|d|s]N ; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N + --ctrack-timeouts=S:E:F[:U] ; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default 60:300:60:60 + --hostcase ; change Host: => host: + --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" + --hostnospace ; remove space after Host: and add it to User-Agent: to preserve packet size + --domcase ; mix domain case : Host: TeSt.cOm + --dpi-desync=[,][,] ; try to desync dpi state. modes : synack fake fakeknown rst rstack hopbyhop destopt ipfrag1 disorder disorder2 split split2 ipfrag2 udplen tamper + --dpi-desync-fwmark= ; override fwmark for desync packet. default = 0x40000000 (1073741824) + --dpi-desync-ttl= ; set ttl for desync packet + --dpi-desync-ttl6= ; set ipv6 hop limit for desync packet. by default ttl value is used. + --dpi-desync-autottl=[[:[-]]] ; auto ttl mode for both ipv4 and ipv6. default: 1:3-20 + --dpi-desync-autottl6=[[:[-]]] ; overrides --dpi-desync-autottl for ipv6 only + --dpi-desync-fooling=[,] ; can use multiple comma separated values. modes : none md5sig ts badseq badsum datanoack hopbyhop hopbyhop2 + --dpi-desync-repeats= ; send every desync packet N times + --dpi-desync-skip-nosni=0|1 ; 1(default)=do not act on ClientHello without SNI (ESNI ?) + --dpi-desync-split-pos=<1..9216> ; data payload split position + --dpi-desync-split-http-req=method|host ; split at specified logical part of plain http request + --dpi-desync-split-tls=sni|sniext ; split at specified logical part of TLS ClientHello + --dpi-desync-split-seqovl= ; use sequence overlap before first sent original split segment + --dpi-desync-split-seqovl-pattern=|0xHEX ; pattern for the fake part of overlap + --dpi-desync-ipfrag-pos-tcp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 8. + --dpi-desync-ipfrag-pos-udp=<8..9216> ; ip frag position starting from the transport header. multiple of 8, default 32. + --dpi-desync-badseq-increment= ; badseq fooling seq signed increment. default -10000 + --dpi-desync-badack-increment= ; badseq fooling ackseq signed increment. default -66000 + --dpi-desync-any-protocol=0|1 ; 0(default)=desync only http and tls 1=desync any nonempty data packet + --dpi-desync-fake-http=|0xHEX ; file containing fake http request + --dpi-desync-fake-tls=|0xHEX ; file containing fake TLS ClientHello (for https) + --dpi-desync-fake-unknown=|0xHEX ; file containing unknown protocol fake payload + --dpi-desync-fake-syndata=|0xHEX ; file containing SYN data payload + --dpi-desync-fake-quic=|0xHEX ; file containing fake QUIC Initial + --dpi-desync-fake-wireguard=|0xHEX ; file containing fake wireguard handshake initiation + --dpi-desync-fake-dht=|0xHEX ; file containing fake DHT (d1..e) + --dpi-desync-fake-unknown-udp=|0xHEX ; file containing unknown udp protocol fake payload + --dpi-desync-udplen-increment= ; increase or decrease udp packet length by N bytes (default 2). negative values decrease length. + --dpi-desync-udplen-pattern=|0xHEX ; udp tail fill pattern + --dpi-desync-start=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N + --dpi-desync-cutoff=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N + --hostlist= ; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed) + --hostlist-exclude= ; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed) + --hostlist-auto= ; detect DPI blocks and build hostlist automatically + --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) + --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) + --hostlist-auto-retrans-threshold= ; how many request retransmissions cause attempt to fail (default : 3) + --hostlist-auto-debug= ; debug auto hostlist positives + --new ; begin new strategy + --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. + --filter-tcp=[~]port1[-port2] ; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp. + --filter-udp=[~]port1[-port2] ; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. +``` + +The manipulation parameters can be combined in any way. + +WARNING. `--wsize` parameter is now not used anymore in scripts. TCP split can be achieved using DPI desync attack. + +### DPI desync attack + +After completion of the tcp 3-way handshake, the first data packet from the client goes. +It usually has `GET / ...` or TLS ClientHello. We drop this packet, replacing with something else. +It can be a fake version with another harmless but valid http or https request (`fake`), tcp reset packet (`rst`,`rstack`), +split into 2 segments original packet with fake segment in the middle (`split`). +`fakeknown` sends fake only in response to known application protocol. +In articles these attack have names **TCB desynchronization** and **TCB teardown**. +Fake packet must reach DPI, but do not reach the destination server. +The following means are available: set a low TTL, send a packet with bad checksum, +add tcp option **MD5 signature**. All of them have their own disadvantages : + +* md5sig does not work on all servers +* badsum doesn't work if your device is behind NAT which does not pass invalid packets. + The most common Linux NAT router configuration does not pass them. Most home routers are Linux based. + The default sysctl configuration `net.netfilter.nf_conntrack_checksum=1` causes contrack to verify tcp and udp checksums + and set INVALID state for packets with invalid checksum. + Typically, iptables rules include a rule for dropping packets with INVALID state in the FORWARD chain. + The combination of these factors does not allow badsum packets to pass through the router. + In openwrt mentioned sysctl is set to 0 from the box, in other routers its often left in the default "1" state. + For nfqws to work properly through the router set `net.netfilter.nf_conntrack_checksum=0` on the router. + System never verifies checksums of locally generated packets so nfqws will always work on the router itself. + If you are behind another NAT, such as a ISP, and it does not pass invalid packages, there is nothing you can do about it. + But usually ISPs pass badsum. + Some adapters/switches/drivers enable hardware filtering of rx badsum not allowing it to pass to the OS. + This behavior was observed on a Mediatek MT7621 based device. + Tried to modify mediatek ethernet driver with no luck, likely hardware enforced limitation. + However the device allowed to send badsum packets, problem only existed for passthrough traffic from clients. +* badseq packets will be dropped by server, but DPI also can ignore them. + default badseq increment is set to -10000 because some DPIs drop packets outside of the small tcp window. + But this also can cause troubles when `--dpi-desync-any-protocol` is enabled. + To be 100% sure fake packet cannot fit to server tcp window consider setting badseq increment to 0x80000000 +* TTL looks like the best option, but it requires special tuning for each ISP. If DPI is further than local ISP websites + you can cut access to them. Manual IP exclude list is required. Its possible to use md5sig with ttl. + This way you cant hurt anything, but good chances it will help to open local ISP websites. + If automatic solution cannot be found then use `zapret-hosts-user-exclude.txt`. + Some router stock firmwares fix outgoing TTL. Without switching this option off TTL fooling will not work. +* `hopbyhop` is ipv6 only. This fooling adds empty extension header `hop-by-hop options` or two headers in case of `hopbyhop2`. + Packets with two hop-by-hop headers violate RFC and discarded by all operating systems. + All OS accept packets with one hop-by-hop header. + Some ISPs/operators drop ipv6 packets with hop-by-hop options. Fakes will not be processed by the server either because + ISP drops them or because there are two same headers. + DPIs may still anaylize packets with one or two hop-by-hop headers. +* `datanoack` sends tcp fakes without ACK flag. Servers do not accept this but DPI may accept. + This mode may break NAT and may not work with iptables if masquerade is used, even from the router itself. + Works with nftables properly. Likely requires external IP address (some ISPs pass these packets through their NAT). +* `autottl` tries to automatically guess TTL value that allows DPI to receive fakes and does not allow them to reach the server. + This tech relies on well known TTL values used by OS : 64,128,255. nfqws takes first incoming packet (YES, you need to redirect it too), + guesses path length and decreases by `delta` value (default 1). If resulting value is outside the range (min,max - default 3,20) + then its normalized to min or max. If the path shorter than the value then autottl fails and falls back to the fixed value. + This can help if multiple DPIs exists on backbone channels, not just near the ISP. + Can fail if inbound and outbound paths are not symmetric. + + +`--dpi-desync-fooling` takes multiple comma separated values. + +For fake,rst,rstack modes original packet is sent after the fake. + +Disorder mode splits original packet and sends packets in the following order : +1. 2nd segment +2. fake 1st segment, data filled with zeroes +3. 1st segment +4. fake 1st segment, data filled with zeroes (2nd copy) + +Original packet is always dropped. `--dpi-desync-split-pos` sets split position (default 2). +If position is higher than packet length, pos=1 is used. +This sequence is designed to make reconstruction of critical message as difficult as possible. +Fake segments may not be required to bypass some DPIs, but can potentially help if more sophisticated reconstruction +algorithms are used. +Mode `disorder2` disables sending of fake segments. + +Split mode is very similar to disorder but without segment reordering : + +1. fake 1st segment, data filled with zeroes +2. 1st segment +3. fake 1st segment, data filled with zeroes (2nd copy) +4. 2nd segment + +Mode `split2` disables sending of fake segments. It can be used as a faster alternative to --wsize. + +In `disorder2` and 'split2` modes no fake packets are sent, so ttl and fooling options are not required. + +`seqovl` adds to the first sent original segment (1st for split, 2nd for disorder) seqovl bytes to the beginning and decreases +sequence number. +In `split2` mode this creates partially in-window packet. OS receives only in-window part. +In `disorder2` mode OS receives fake and real part of the second segment but does not pass received data to the socket until first +segment is received. First segment overwrites fake part of the second segment. Then OS passes original data to the socket. +All unix OS except Solaris preserve last received data. This is not the case for Windows servers and `disorder` with `seqovl` will not work. +Disorder requires `seqovl` to be less than `split_pos`. Either statically defined or automatically calculated. +Otherwise desync is not possible and will not happen. +Method allows to avoid separate fakes. Fakes and real data are mixed. + +`hopbyhop`, `destopt` and `ipfrag1` desync modes (they're not the same as `hopbyhop` fooling !) are ipv6 only. One `hop-by-hop`, +`destination options` or `fragment` header is added to all desynced packets. +Extra header increases packet size and can't be applied to the maximum size packets. +If it's not possible to send modified packet original one will be sent. +The idea here is that DPI sees 0 in the next header field of the main ipv6 header and does not +walk through the extension header chain until transport header is found. +`hopbyhop`, `destopt`, `ipfrag1` modes can be used with any second phase mode except `ipfrag1+ipfrag2`. +For example, `hopbyhop,split2` means split original tcp packet into 2 pieces and add hop-by-hop header to both. +With `hopbyhop,ipfrag2` header sequence will be : `ipv6,hop-by-hop,fragment,tcp/udp`. +`ipfrag1` mode may not always work without special preparations. See "IP Fragmentation" notices. + +There are DPIs that analyze responses from the server, particularly the certificate from the ServerHello +that contain domain name(s). The ClientHello delivery confirmation is an ACK packet from the server +with ACK sequence number corresponding to the length of the ClientHello+1. +In the disorder variant, a selective acknowledgement (SACK) usually arrives first, then a full ACK. +If, instead of ACK or SACK, there is an RST packet with minimal delay, DPI cuts you off at the request stage. +If the RST is after a full ACK after a delay of about ping to the server, then probably DPI acts +on the server response. The DPI may be satisfied with good ClientHello and stop monitoring the TCP session +without checking ServerHello. Then you were lucky. 'fake' option could work. +If it does not stop monitoring and persistently checks the ServerHello, --wssize parameter may help (see CONNTRACK). +Otherwise it is hardly possible to overcome this without the help of the server. +The best solution is to enable TLS 1.3 support on the server. TLS 1.3 sends the server certificate in encrypted form. +This is recommendation to all admins of blocked sites. Enable TLS 1.3. You will give more opportunities to overcome DPI. + +Hosts are extracted from plain http request Host: header and SNI of ClientHello TLS message. +Subdomains are applied automatically. gzip lists are supported. + +iptables for performing the attack on the first packet : + +`iptables -t mangle -I POSTROUTING -o -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass` + +This is good if DPI does not track all requests in http keep-alive session. +If it does, then pass all outgoing packets for http and only first data packet for https : + +``` +iptables -t mangle -I POSTROUTING -o -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I POSTROUTING -o -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +``` + +mark is needed to keep away generated packets from NFQUEUE. nfqws sets fwmark when it sends generated packets. +nfqws can internally filter marked packets. but when connbytes filter is used without mark filter +packet ordering can be changed breaking the whole idea of desync attack. + +### DPI desync combos + +dpi-desync parameter takes up to 3 comma separated arguments. +zero phase means tcp connection establishement (before sending data payload). Mode can be `synack`. +Hostlist filter is not applicable to the zero phase. +Next phases work on packets with data payload. +1st phase mode can be `fake`,`rst`,`rstack`, 2nd phase mode - `disorder`,`disorder2`,`split`,`split2`,`ipfrag2`. +Can be useful for ISPs with more than one DPI. + +### SYNACK mode + +In geneva docs it's called **TCP turnaround**. Attempt to make the DPI believe the roles of client and server are reversed. + +!!! This mode breaks NAT operation and can be used only if there's no NAT between the attacker's device and the DPI ! + +In linux it's required to remove standard firewall rule dropping INVALID packets in the OUTPUT chain, +for example : `-A OUTPUT -m state --state INVALID -j DROP` + +In openwrt it's possible to disable the rule for both FORWARD and OUTPUT chains in /etc/config/firewall : +``` +config zone + option name 'wan' + ......... + option masq_allow_invalid '1' +``` +Unfortunately there's no OUTPUT only switch. It's not desired to remove the rule from the FORWARD chain. +Add the following lines to `/etc/firewall.user` : + +``` +iptables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT +ip6tables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT +``` + +then `/etc/init.d/firewall restart` + +Otherwise raw sending SYN,ACK frame will cause error stopping the further processing. +If you realize you don't need the synack mode it's highly suggested to restore drop INVALID rule. + +### SYNDATA mode + +Normally SYNs come without data payload. If it's present it's ignored by all major OS if TCP fast open (TFO) is not involved, but may not be ignored by DPI. +Original connections with TFO are not touched because otherwise they would be definitely broken. +Without extra parameter payload is 16 zero bytes. + +### Virtual Machines + +Most of nfqws packet magic does not work from VMs powered by virtualbox and vmware when network is NATed. +Hypervisor forcibly changes ttl and does not forward fake packets. +Set up bridge networking. + +### CONNTRACK + +nfqws is equipped with minimalistic connection tracking system (conntrack) +It's used if some specific DPI circumvention methods are involved and helps to reassemble multi-packet requests. + +Conntrack can track connection phase : SYN,ESTABLISHED,FIN , packet counts in both directions , sequence numbers. + +It can be fed with unidirectional or bidirectional packets. + +A SYN or SYN,ACK packet creates an entry in the conntrack table. + +That's why iptables redirection must start with the first packet although can be cut later using connbytes filter. + +First seen UDP packet creates UDP stream. It defines the stream direction. Then all packets with the same +`src_ip,src_port,dst_ip,dst_port` are considered to belong to the same UDP stream. UDP stream exists till inactivity timeout. + +A connection is deleted from the table as soon as it's no more required to satisfy nfqws needs or when a timeout happens. + +There're 3 timeouts for each connection state. They can be changed in `--ctrack-timeouts` parameter. + +`--wssize` changes tcp window size for the server to force it to send split replies. +In order for this to affect all server operating systems, it is necessary to change the window size in each outgoing packet +before sending the message, the answer to which must be split (for example, TLS ClientHello). +That's why conntrack is required to know when to stop applying low window size. + +If you do not stop and set the low wssize all the time, the speed will drop catastrophically. +Linux can overcome this using connbytes filter but other OS may not include similar filter. + +In http(s) case wssize stops after the first http request or TLS ClientHello. + +If you deal with a non-http(s) protocol you need `--wssize-cutoff`. It sets the threshold where wssize stops. + +Threshold can be prefixed with 'n' (packet number starting from 1), 'd' (data packet number starting from 1), +'s' (relative sequence number - sent by client bytes + 1). + +If a http request or TLS ClientHello packet is detected wssize stops immediately ignoring wssize-cutoff option. + +If your protocol is prone to long inactivity, you should increase ESTABLISHED phase timeout using `--ctrack-timeouts`. + +Default timeout is low - only 5 mins. + +Don't forget that nfqws feeds with redirected packets. If you have limited redirection with connbytes +ESTABLISHED entries can remain in the table until dropped by timeout. + +To diagnose conntrack state send SIGUSR1 signal to nfqws : `killall -SIGUSR1 nfqws`. + +nfqws will dump current conntrack table to stdout. + +Typically, in a SYN packet, client sends TCP extension **scaling factor** in addition to window size. +scaling factor is the power of two by which the window size is multiplied : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... + +The wssize parameter specifies the scaling factor after a colon. + +Scaling factor can only decrease, increase is blocked to prevent the server from exceeding client's window size. + +To force a TLS server to fragment ServerHello message to avoid hostname detection on DPI use `--wssize=1:6` + +The main rule is to set scale_factor as much as possible so that after recovery the final window size +becomes the possible maximum. If you set `scale_factor` 64:0, it will be very slow. + +On the other hand, the server response must not be large enough for the DPI to find what it is looking for. + +`--wssize` is not applied in desync profiles with hostlist filter because it works since the connection initiation when it's not yet possible +to extract the host name. But it works with auto hostlist profiles. + +`--wssize` may slow down sites and/or increase response time. It's desired to use another methods if possible. + +`--dpi-desync-cutoff` allows you to set the threshold at which it stops applying dpi-desync. +Can be prefixed with 'n', 'd', 's' symbol the same way as `--wssize-cutoff`. +Useful with `--dpi-desync-any-protocol=1`. +If the connection falls out of the conntrack and --dpi-desync-cutoff is set, dpi desync will not be applied. + +Set conntrack timeouts appropriately. + +### Reassemble + +nfqws supports reassemble of TLS and QUIC ClientHello. +They can consist of multiple packets if kyber crypto is used (default starting from chromium 124). +Chromium randomizes TLS fingerprint. SNI can be in any packet or in-between. +Stateful DPIs usually reassemble all packets in the request then apply block decision. +If nfqws receives a partial ClientHello it begins reassemble session. Packets are delayed until it's finished. +Then they go through desync using fully reassembled message. +On any error reassemble is cancelled and all delayed packets are sent immediately without desync. + +There is special support for all tcp split options for multi segment TLS. Split position is treated +as message-oriented, not packet oriented. For example, if your client sends TLS ClientHello with size 2000 +and SNI is at 1700, desync mode is fake,split2, then fake is sent first, then original first segment +and the last splitted segment. 3 segments total. + + +### UDP support + +UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level. +Only desync modes `fake`,`hopbyhop`,`destopt`,`ipfrag1` and `ipfrag2` are applicable. +`fake`,`hopbyhop`,`destopt` can be used in combo with `ipfrag2`. +`fake` can be used in combo with `udplen` and `tamper`. + +`udplen` increases udp payload size by `--dpi-desync-udplen-increment` bytes. Padding is filled with zeroes by default but can be overriden with a pattern. +This option can resist DPIs that track outgoing UDP packet sizes. +Requires that application protocol does not depend on udp payload size. + +QUIC initial packets are recognized. Decryption and hostname extraction is supported so `--hostlist` parameter will work. +Wireguard handshake initiation and DHT packets are also recognized. +For other protocols desync use `--dpi-desync-any-protocol`. + +Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`. + +Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently. +By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`. + +### IP fragmentation + +Modern network can be very hostile to IP fragmentation. Fragmented packets are often not delivered or refragmented/reassembled on the way. +Frag position is set independently for tcp and udp. By default 24 and 8, must be multiple of 8. +Offset starts from the transport header. + +tcp fragments are almost always filtered. It's absolutely not suitable for arbitrary websites. +udp fragments have good chances to survive but not everywhere. It's good to assume success rate on QUIC between 50..75%. +Likely more with your VPS. Sometimes filtered by DDoS protection. + +There are important nuances when working with fragments in Linux. + +ipv4 : Linux allows to send ipv4 fragments but standard firewall rules in OUTPUT chain can cause raw send to fail. + +ipv6 : There's no way for an application to reliably send fragments without defragmentation by conntrack. +Sometimes it works, sometimes system defragments packets. +Looks like kernels <4.16 have no simple way to solve this problem. Unloading of `nf_conntrack` module +and its dependency `nf_defrag_ipv6` helps but this severely impacts functionality. +Kernels 4.16+ exclude from defragmentation untracked packets. +See `blockcheck.sh` code for example. + +Sometimes it's required to load `ip6table_raw` kernel module with parameter `raw_before_defrag=1`. +In openwrt module parameters are specified after module names separated by space in files located in `/etc/modules.d`. + +In traditional linux check whether `iptables-legacy` or `iptables-nft` is used. If legacy create the file +`/etc/modprobe.d/ip6table_raw.conf` with the following content : +``` +options ip6table_raw raw_before_defrag=1 +``` +In some linux distros its possible to change current ip6tables using this command: `update-alternatives --config ip6tables`. +If you want to stay with `nftables-nft` you need to patch and recompile your version. +In `nft.c` find : +``` + { + .name = "PREROUTING", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_LOCAL_OUT, + }, +``` +and replace -300 to -450. + +It must be done manually, `blockcheck.sh` cannot auto fix this for you. + +Or just move to `nftables`. You can create hooks with any priority there. + +Looks like there's no way to do ipfrag using iptables for forwarded traffic if NAT is present. +`MASQUERADE` is terminating target, after it `NFQUEUE` does not work. +nfqws sees packets with internal network source address. If fragmented NAT does not process them. +This results in attempt to send packets to internet with internal IP address. +You need to use nftables instead with hook priority 101 or higher. + +### multiple strategies + +`nfqws` can apply different strategies to different requests. It's done with multiple desync profiles. +Profiles are delimited by the `--new` parameter. First profile is created automatically and does not require `--new`. +Each profile has a filter. By default it's empty and profile matches any packet. +Filter can have hard parameters : ip version and tcp/udp port range. +Hard parameters are always identified unambiguously even on zero-phase when hostname is unknown yet. +Hostlist can also act as a filter. They can be combined with hard parameters. +When a packet comes profiles are matched from the first to the last until first filter condition match. +Hard filter is matched first. If it does not match verification goes to the next profile. +If a profile matches hard filter and has autohostlist it's selected immediately. +If a profile matches hard filter and has normal hostlist(s) and hostname is unknown yet verification goes to the next profile. +Otherwise profile hostlist(s) are checked for the hostname. If it matches profile is selected. +Otherwise verification goes to the next profile. + +It's possible that before getting hostname connection is served by one profile and after +hostname is revealed it's switched to another profile. +If you use 0-phase desync methods think carefully what can happen during strategy switch. +Use `--debug` logging to understand better what `nfqws` does. + +Profiles are numbered from 1 to N. There's last empty profile in the chain numbered 0. +It's used when no filter matched. + +IMPORTANT : multiple strategies exist only for the case when it's not possible to combine all to one strategy. +Copy-pasting blockcheck results of different websites to multiple strategies lead to the mess. +This way you may never unblock all resources and only confuse yourself. + +## tpws + +tpws is transparent proxy. + +``` + --debug=0|1|2|syslog|@ ; 1 and 2 means log to console and set debug level. for other targets use --debug-level. + --debug-level=0|1|2 ; specify debug level for syslog and @ + --bind-addr=|; for v6 link locals append %interface_name : fe80::1%br-lan + --bind-iface4= ; bind to the first ipv4 addr of interface + --bind-iface6= ; bind to the first ipv6 addr of interface + --bind-linklocal=no|unwanted|prefer|force + ; no : bind only to global ipv6 + ; unwanted (default) : prefer global address, then LL + ; prefer : prefer LL, then global + ; force : LL only + --bind-wait-ifup= ; wait for interface to appear and up + --bind-wait-ip= ; after ifup wait for ip address to appear up to N seconds + --bind-wait-ip-linklocal= ; accept only link locals first N seconds then any + --bind-wait-only ; wait for bind conditions satisfaction then exit. return code 0 if success. + --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name + --port= ; port number to listen on + --socks ; implement socks4/5 proxy instead of transparent proxy + --local-rcvbuf= ; SO_RCVBUF for local legs + --local-sndbuf= ; SO_SNDBUF for local legs + --remote-rcvbuf= ; SO_RCVBUF for remote legs + --remote-sndbuf= ; SO_SNDBUF for remote legs + --nosplice ; do not use splice to transfer data between sockets + --skip-nodelay ; do not set TCP_NODELAY for outgoing connections. incompatible with split. + --local-tcp-user-timeout= ; set tcp user timeout for local leg (default : 10, 0 = system default) + --remote-tcp-user-timeout= ; set tcp user timeout for remote leg (default : 20, 0 = system default) + --no-resolve ; disable socks5 remote dns + --resolver-threads= ; number of resolver worker threads + --maxconn= ; max number of local legs + --maxfiles= ; max file descriptors (setrlimit). min requirement is (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode. + ; its worth to make a reserve with 1.5 multiplier. by default maxfiles is (X*connections)*1.5+16 + --max-orphan-time= ; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds + + --new ; begin new strategy + --filter-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. + --filter-tcp=[~]port1[-port2] ; TCP port filter. ~ means negation + + --hostlist= ; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed) + --hostlist-exclude= ; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed) + --hostlist-auto= ; detect DPI blocks and build hostlist automatically + --hostlist-auto-fail-threshold= ; how many failed attempts cause hostname to be added to auto hostlist (default : 3) + --hostlist-auto-fail-time= ; all failed attemps must be within these seconds (default : 60) + --hostlist-auto-debug= ; debug auto hostlist positives + + --split-http-req=method|host ; split http request at specified logical position. + --split-tls=sni|sniext ; split at specified logical part of TLS ClientHello + --split-pos= ; split at specified pos. split-http-req takes precedence over split-pos for http reqs. + --split-any-protocol ; split not only http and https + --disorder[=http|tls] ; when splitting simulate sending second fragment first + --oob[=http|tls] ; when splitting send out of band byte. default is HEX 0x00. + --oob-data=|0xHEX ; override default 0x00 OOB byte. + --hostcase ; change Host: => host: + --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" + --hostdot ; add "." after Host: name + --hosttab ; add tab after Host: name + --hostnospace ; remove space after Host: + --hostpad= ; add dummy padding headers before Host: + --domcase ; mix domain case after Host: like this : TeSt.cOm + --methodspace ; add extra space after method + --methodeol ; add end-of-line before method + --unixeol ; replace 0D0A to 0A + --tlsrec=sni|sniext ; make 2 TLS records. split at specified logical part. don't split if SNI is not present. + --tlsrec-pos= ; make 2 TLS records. split at specified pos + --mss= ; set client MSS. forces server to split messages but significantly decreases speed ! + --mss-pf=[~]port1[-port2] ; MSS port filter. ~ means negation + --tamper-start=[n] ; start tampering only from specified outbound stream position. byte pos or block number ('n'). default is 0. + --tamper-cutoff=[n] ; do not tamper anymore after specified outbound stream position. byte pos or block number ('n'). default is unlimited. + --daemon ; daemonize + --pidfile= ; write pid to file + --user= ; drop root privs + --uid=uid[:gid] ; drop root privs +``` + +The manipulation parameters can be combined in any way. + +`split-http-req` takes precedence over split-pos for http reqs. + +split-pos works by default only on http and TLS ClientHello. use `--split-any-protocol` to act on any packet + +tpws can bind to multiple interfaces and IP addresses (up to 32). + +Port number is always the same. + +Parameters `--bind-iface*` and `--bind-addr` create new bind. + +Other parameters `--bind-*` are related to the last bind. + +link local ipv6 (`fe80::/8`) mode selection : + +``` +--bind-iface6 --bind-linklocal=no : first selects private address fc00::/7, then global address +--bind-iface6 --bind-linklocal=unwanted : first selects private address fc00::/7, then global address, then LL +--bind-iface6 --bind-linklocal=prefer : first selects LL, then private address fc00::/7, then global address +--bind-iface6 --bind-linklocal=force : select only LL +``` + +To bind to all ipv4 specify `--bind-addr "0.0.0.0"`, all ipv6 - `::`. + +`--bind-addr=""` - mean bind to all ipv4 and ipv6. + +If no binds are specified default bind to all ipv4 and ipv6 addresses is created. + +To bind to a specific link local address do : `--bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name` + +The `--bind-wait*` parameters can help in situations where you need to get IP from the interface, but it is not there yet, it is not raised +or not configured. + +In different systems, ifup events are caught in different ways and do not guarantee that the interface has already received an IP address of a certain type. + +In the general case, there is no single mechanism to hang oneself on an event of the type "link local address appeared on the X interface." + +To bind to a specific ip when its interface may not be configured yet do : `--bind-addr=192.168.5.3 --bind-wait-ip=20` + +It's possible to bind to any nonexistent address in transparent mode but in socks mode address must exist. + +In socks proxy mode no additional system privileges are required. Connections to local IPs of the system where tpws runs are prohibited. +tpws supports remote dns resolving (curl : `--socks5-hostname` firefox : `socks_remote_dns=true`) , but does it in blocking mode. + +tpws uses async sockets for all activities. Domain names are resolved in multi threaded pool. +Resolving does not freeze other connections. But if there're too many requests resolving delays may increase. +Number of resolver threads is choosen automatically proportinally to `--maxconn` and can be overriden using `--resolver-threads`. +To disable hostname resolve use `--no-resolve` option. + +`--disorder` is an additional flag to any split option. +It tries to simulate `--disorder2` option of `nfqws` using standard socket API without the need of additional privileges. +This works fine in Linux and MacOS but unexpectedly in FreeBSD and OpenBSD +(system sends second fragment then the whole packet instead of the first fragment). + +`--tlsrec` and `--tlsrec-pos` allow to split TLS ClientHello into 2 TLS records in one TCP segment. +`--tlsrec=sni` splits between 1st and 2nd chars of the hostname. No split occurs if SNI is not present. +`--tlsrec-pos` splits at specified position. If TLS data block size is too small pos=1 is applied. +`--tlsrec` can be combined with `--split-pos` and `--disorder`. +`--tlsrec` breaks significant number of sites. Crypto libraries on end servers usually accept fine modified ClientHello +but middleboxes such as CDNs and ddos guards - not always. +Use of `--tlsrec` without filters is discouraged. + +`--mss` sets TCP_MAXSEG socket option. Client sets this value in MSS TCP option in the SYN packet. +Server replies with it's own MSS in SYN,ACK packet. Usually servers lower their packet sizes but they still don't +fit to supplied MSS. The greater MSS client sets the bigger server's packets will be. +If it's enough to split TLS 1.2 ServerHello, it may fool DPI that checks certificate domain name. +This scheme may significantly lower speed. Hostlist filter is possible only in socks mode if client uses remote resolving (firefox `network.proxy.socks_remote_dns`). +`--mss` is not required for TLS1.3. If TLS1.3 is negotiable then MSS make things only worse. +Use only if nothing better is available. Works only in Linux, not BSD or MacOS. + +### multiple strategies + +`tpws` supports multiple strategies as well. They work mostly like with `nfqws` with minimal differences. +`filter-udp` is absent because `tpws` does not support udp. 0-phase desync methods (`--mss`) can work with hostlist in socks modes with remote hostname resolve. +This is the point where you have to plan profiles carefully. If you use `--mss` and hostlist filters, behaviour can be different depending on remote resolve feature enabled or not. +Use `--mss` both in hostlist profile and profile without hostlist. +Use `curl --socks5` and `curl --socks5-hostname` to issue two kinds of proxy queries. +See `--debug` output to test your setup. + +## Ways to get a list of blocked IP + +nftables can't work with ipsets. Native nf sets require lots of RAM to load large ip lists with subnets and intervals. +In case you're on a low RAM system and need large lists it may be required to fall back to iptables+ipset. + +1. Enter the blocked domains to `ipset/zapret-hosts-user.txt` and run `ipset/get_user.sh` +At the output, you get `ipset/zapret-ip-user.txt` with IP addresses. + +2. `ipset/get_reestr_*.sh`. Russian specific + +3. `ipset/get_antifilter_*.sh`. Russian specific + +4. `ipset/get_config.sh`. This script calls what is written into the GETLIST variable from the config file. + +If the variable is not defined, then only lists for ipsets nozapret/nozapret6 are resolved. + +So, if you're not russian, the only way for you is to manually add blocked domains. +Or write your own `ipset/get_iran_blocklist.sh` , if you know where to download this one. + +On routers, it is not recommended to call these scripts more than once in 2 days to minimize flash memory writes. + +`ipset/create_ipset.sh` executes forced ipset update. +With `no-update` parameter `create_ipset.sh` creates ipset but populate it only if it was actually created. + +It's useful when multiple subsequent calls are possible to avoid wasting of cpu time redoing the same job. + +Ipset loading is resource consuming. Its a good idea to call create_ipset without `no-update` parameter + +only once a several days. Use it with `no-update` option in other cases. + +ipset scripts automatically call ip2net utility. +ip2net helps to reduce ip list size by combining IPs to subnets. Also it cuts invalid IPs from the list. +Stored lists are already processed by ip2net. They are error free and ready for loading. + +`create_ipset.sh` supports loading ip lists from gzip files. First it looks for the filename with the ".gz" extension, +such as `zapret-ip.txt.gz`, if not found it falls back to the original name `zapret-ip.txt`. + +So your own get_iran_blockslist.sh can use "zz" function to produce gz. Study how other russian `get_XXX.sh` work. + +Gzipping helps saving a lot of precious flash space on embedded systems. + +User lists are not gzipped because they are not expected to be very large. + +You can add a list of domains to `ipset/zapret-hosts-user-ipban.txt`. Their ip addresses will be placed +in a separate ipset "ipban". It can be used to route connections to transparent proxy "redsocks" or VPN. + +IPV6: if ipv6 is enabled, then additional txt's are created with the same name, but with a "6" at the end before the extension. + +`zapret-ip.txt` => `zapret-ip6.txt` + +The ipsets zapret6 and ipban6 are created. + +IP EXCLUSION SYSTEM. All scripts resolve `zapret-hosts-user-exclude.txt` file, creating `zapret-ip-exclude.txt` and `zapret-ip-exclude6.txt`. + +They are the source for ipsets nozapret/nozapret6. All rules created by init scripts are created with these ipsets in mind. +The IPs placed in them are not involved in the process. +zapret-hosts-user-exclude.txt can contain domains, ipv4 and ipv6 addresses or subnets. + +FreeBSD. `ipset/*.sh` scripts also work in FreeBSD. Instead of ipset they create ipfw lookup tables with the same names as in Linux. +ipfw tables can store both ipv4 and ipv6 addresses and subnets. There's no 4 and 6 separation. + +LISTS_RELOAD config parameter defines a custom lists reloading command. +Its useful on BSD systems with PF. +LISTS_RELOAD=- disables reloading ip list backend. + +## Domain name filtering + +An alternative to ipset is to use tpws or nfqws with a list(s) of domains. +Both `tpws` and `nfqws` take any number of include (`--hostlist`) and exclude (`--hostlist-exclude`) domain lists. +All lists of the same type are combined internally leaving only 2 lists : include and exclude. + +Exclude list is checked first. Fooling is cancelled if domain belongs to exclude list. +If include list is present and domain does not belong to that list fooling is also cancelled. +Empty list means absent list. Otherwise fooling goes on. + +Launch system looks for 2 include lists : + +`ipset/zapret-hosts-users.txt.gz` or `ipset/zapret-hosts-users.txt` + +`ipset/zapret-hosts.txt.gz` or `ipset/zapret-hosts.txt` + +and 1 exclude list + +`ipset/zapret-hosts-users-exclude.txt.gz` or `ipset/zapret-hosts-users-exclude.txt` + +If `MODE_FILTER=hostlist` all present lists are passed to `nfqws` or `tpws`. +If all include lists are empty it works like no include lists exist at all. +If you need "all except" mode you dont have to delete zapret-hosts-users.txt. Just make it empty. + +Subdomains auto apply. For example, "ru" in the list affects "*.ru" . + +tpws and nfqws reread lists on HUP signal. + +When filtering by domain name, daemons should run without filtering by ipset. +When using large regulator lists estimate the amount of RAM on the router ! + +## **autohostlist** mode + +This mode analyzes both client requests and server replies. +If a host is not in any list and a situation similar to block occurs host is automatically added to the special list both in memory and file. +Use exclude hostlist to prevent autohostlist triggering. +If it did happen - delete the undesired record from the file and restart tpws/nfqws or send them SIGHUP to force lists reload. + +In case of nfqws it's required to redirect both incoming and outgoing traffic to the queue. +It's strongly recommended to use connbytes filter or nfqws will process gigabytes of incoming traffic. +For the same reason it's not recommended to use autohostlist mode in BSDs. BSDs do not support connbytes or similar mechanism. + +nfqws и tpws detect the folowing situations : +1) [nfqws] Multiple retransmissions of the first request inside a TCP session having host. +2) [nfqws,tpws] RST in response to the first request. +3) [nfqws,tpws] HTTP redirect in response to the first http request with 2nd level domain diferent from the original. +4) [tpws] Client closes connection after first request without having server reply (no reponse from server, timeout). + +To minimize false positives there's fail counter. If in specific time occurs more than specified number of fails +the host is added to the list. Then DPI bypass strategy start to apply immediately. + +For the user autohostlist mode looks like this. +When for the first time user visits a blocked website it sees block page, connection reset +or browser hangs until timeout, then display a error. +User presses multiple times F5 causing browser to retry attempts. +After some retries a website opens and next time works as expected. + +With autohostlist mode it's possible to use bypass strategies that break lots of sites. +If a site does not behave like blocked no fooling applies. +Otherwise it's nothing to lose. + +However false positives still can occur in case target website is behaving abnormally +(may be due to DDoS attack or server malfunction). If it happens bypass strategy +may start to break the website. This situation can only be controlled manually. +Remove undesired domain from the autohostlist file, restart nfqws/tpws or send them SIGHUP. +Use exclude hostlist to prevent further auto additions. + +It's possible to use one auto hostlist with multiple processes. All processes check for file modification time. +If a process modified autohostlist, all others will reread it automatically. +All processes must run with the same uid. + +If zapret scripts are used then autohostlist is `ipset/zapret-hosts-auto.txt` +and exlude list is `ipset/zapret-hosts-user-exclude.txt`. autohostlist mode +includes hostlist mode. You can use `ipset/zapret-hosts-user.txt`. + + +## Choosing parameters + +The file `/opt/zapret/config` is used by various components of the system and contains basic settings. +It needs to be viewed and edited if necessary. + +Which firewall type use on linux systems : `nftables` or `iptables`. +On traditional systems `nftables` is selected by default if `nft` is installed. +On openwrt by default `nftables` is selected on `firewall4` based systems. + +`FWTYPE=iptables` + +Main mode : + +``` +tpws - tpws transparent mode +tpws-socks - tpws socks mode + binds to localhost and LAN interface (if IFACE_LAN is specified or the system is OpenWRT). port 988 +nfqws - nfqws +filter - only fill ipset or load hostlist +custom - use custom script for running daemons and establishing firewall rules +``` + +`MODE=tpws` + +Enable http fooling : + +`MODE_HTTP=1` + +Apply fooling to keep alive http sessions. Only applicable to nfqws. Tpws always fool keepalives. +Not enabling this can save CPU time. + +`MODE_HTTP_KEEPALIVE=0` + +Enable https fooling : + +`MODE_HTTPS=1` + +Enable quic fooling : + +`MODE_QUIC=1` + +Host filtering mode : +``` +none - apply fooling to all hosts +ipset - limit fooling to hosts from ipset zapret/zapret6 +hostlist - limit fooling to hosts from hostlist +autohostlist - hostlist mode + blocks auto detection +``` + +`MODE_FILTER=none` + +Its possible to change manipulation options used by tpws : + +`TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3"` + +Additional low priority desync profile for `MODE_FILTER=hostlist`. +With multiple profile support 0-phase desync methods are no more applied with hostlist ! +To apply them additional profile is required without hostlist filter. + +`TPWS_OPT_SUFFIX="--mss=88"` + +nfqws options for DPI desync attack: + +``` +DESYNC_MARK=0x40000000 +DESYNC_MARK_POSTNAT=0x20000000 +NFQWS_OPT_DESYNC="--dpi-desync=fake --dpi-desync-ttl=0 --dpi-desync-fooling=badsum --dpi-desync-fwmark=$DESYNC_MARK" +``` + +Separate nfqws options for http and https and ip protocol versions 4,6: + +``` +NFQWS_OPT_DESYNC_HTTP="--dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum" +NFQWS_OPT_DESYNC_HTTPS="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum" +NFQWS_OPT_DESYNC_HTTP6="--dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none" +NFQWS_OPT_DESYNC_HTTPS6="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none" +``` + +If one of `NFQWS_OPT_DESYNC_HTTP`/`NFQWS_OPT_DESYNC_HTTPS` is not defined it takes value of NFQWS_OPT_DESYNC. +If one of `NFQWS_OPT_DESYNC_HTTP6`/`NFQWS_OPT_DESYNC_HTTPS6` is not defined it takes value from +`NFQWS_OPT_DESYNC_HTTP`/`NFQWS_OPT_DESYNC_HTTPS`. +It means if only `NFQWS_OPT_DESYNC` is defined all four take its value. + +If a variable is not defined, the value `NFQWS_OPT_DESYNC` is taken. + +Additional low priority desync profile for `MODE_FILTER=hostlist`. +With multiple profile support 0-phase desync methods are no more applied with hostlist ! +To apply them additional profile is required without hostlist filter. +``` +#NFQWS_OPT_DESYNC_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTP_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTPS_SUFFIX="--wssize 1:6" +#NFQWS_OPT_DESYNC_HTTP6_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTPS6_SUFFIX="--wssize 1:6" +``` + +Defaults are filled the same ways as with NFQWS_OPT_*. + +Separate QUIC options for ip protocol versions : + +``` +NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" +NFQWS_OPT_DESYNC_QUIC6="--dpi-desync=hopbyhop" +``` + +If `NFQWS_OPT_DESYNC_QUIC6` is not specified `NFQWS_OPT_DESYNC_QUIC` is taken. + + +flow offloading control (if supported) + +``` +donttouch : disable system flow offloading setting if selected mode is incompatible with it, dont touch it otherwise and dont configure selective flow offloading +none : always disable system flow offloading setting and dont configure selective flow offloading +software : always disable system flow offloading setting and configure selective software flow offloading +hardware : always disable system flow offloading setting and configure selective hardware flow offloading +``` + +`FLOWOFFLOAD=donttouch` + +The GETLIST parameter tells the install_easy.sh installer which script to call +to update the list of blocked ip or hosts. +Its called via `get_config.sh` from scheduled tasks (crontab or systemd timer). +Put here the name of the script that you will use to update the lists. +If not, then the parameter should be commented out. + +You can individually disable ipv4 or ipv6. If the parameter is commented out or not equal to "1", +use of the protocol is permitted. + +``` +#DISABLE_IPV4=1 +DISABLE_IPV6=1 +``` + +The number of threads for mdig multithreaded DNS resolver (1..100). +The more of them, the faster, but will your DNS server be offended by hammering ? + +`MDIG_THREADS=30` + +temp directory. Used by ipset/*.sh scripts for large lists processing. +/tmp by default. Can be reassigned if /tmp is tmpfs and RAM is low. +TMPDIR=/opt/zapret/tmp + +ipset and nfset options : + +``` +SET_MAXELEM=262144 +IPSET_OPT="hashsize 262144 maxelem 2097152 +``` + +Kernel automatically increases hashsize if ipset is too large for the current hashsize. +This procedure requires internal reallocation and may require additional memory. +On low RAM systems it can cause errors. +Do not use too high hashsize. This way you waste your RAM. And dont use too low hashsize to avoid reallocs. + +ip2net options. separate for ipv4 and ipv6. + +``` +IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" +IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" +``` + +autohostlist mode tuning. + +``` +AUTOHOSTLIST_RETRANS_THRESHOLD=3 +AUTOHOSTLIST_FAIL_THRESHOLD=2 +AUTOHOSTLIST_FAIL_TIME=60 +AUTOHOSTLIST_DEBUG=0 +``` + +Enable gzip compression for large lists. Used by ipset/*.sh scripts. + +`GZIP_LISTS=1` + +Command to reload ip/host lists after update. +Comment or leave empty for auto backend selection : ipset or ipfw if present. +On BSD systems with PF no auto reloading happens. You must provide your own command. +Newer FreeBSD versions support table only reloading : `pfctl -Tl -f /etc/pf.conf` +Set to "-" to disable reload. + +`LISTS_RELOAD="pfctl -f /etc/pf.conf"` + +In openwrt there's default network `lan`. Only traffic coming from this network is redirected to tpws by default. +To override this behaviour set the following variable : + +`OPENWRT_LAN="lan lan2 lan3"` + +In openwrt wan interfaces are those having default route. Separately for ipv4 and ipv6. +This can be redefined : +``` +OPENWRT_WAN4="wan4 vpn" +OPENWRT_WAN6="wan6 vpn6" +``` + +The `INIT_APPLY_FW=1` parameter enables the init script to independently apply iptables rules. +With other values or if the parameter is commented out, the rules will not be applied. +This is useful if you have a firewall management system, in the settings of which you should tie the rules. +Not applicable to `OpenWRT` if used with `firewall3+iptables`. + +The following settings are not relevant for openwrt : + +If your system works as a router, then you need to enter the names of the internal and external interfaces: +``` +IFACE_LAN=eth0 +IFACE_WAN=eth1 +IFACE_WAN6="henet ipsec0" +``` +Multiple interfaces are space separated. IF IFACE_WAN6 is omitted then IFACE_WAN value is taken. + +IMPORTANT: configuring routing, masquerade, etc. not a zapret task. +Only modes that intercept transit traffic are enabled. +It's possible to specify multiple interfaces like this : `IFACE_LAN="eth0 eth1 eth2"` + + +## Screwing to the firewall control system or your launch system + +If you use some kind of firewall management system, then it may conflict with an existing startup script. +When re-applying the rules, it could break the iptables settings from the zapret. +In this case, the rules for iptables should be screwed to your firewall separately from running tpws or nfqws. + +The following calls allow you to apply or remove iptables rules separately: + +``` + /opt/zapret/init.d/sysv/zapret start_fw + /opt/zapret/init.d/sysv/zapret stop_fw + /opt/zapret/init.d/sysv/zapret restart_fw +``` + +And you can start or stop the demons separately from the firewall: + +``` + /opt/zapret/init.d/sysv/zapret start_daemons + /opt/zapret/init.d/sysv/zapret stop_daemons + /opt/zapret/init.d/sysv/zapret restart_daemons +``` + +nftables nearly eliminate conflicts betweeen firewall control systems because they allow +separate tables and netfilter hooks. `zapret` nf table is used for zapret purposes. +If your system does not touch it everything will likely be OK. + +Some additional nftables-only calls exist : + +Lookup `lanif`, `wanif`, `wanif6` and `flow table` interface sets. +``` + /opt/zapret/init.d/sysv/zapret list_ifsets +``` + +Renew `lanif`, `wanif`, `wanif6` and `flow table` interface sets. +Taken from `IFACE_LAN`, `IFACE_WAN` config variables on traditional Linux systems. +Autoselected on `OpenWRT`. `lanif` can be extended using `OPENWRT_LAN` config variable. +``` + /opt/zapret/init.d/sysv/zapret reload_ifsets +``` + +Calls `nft -t list table inet zapret`. +``` + /opt/zapret/init.d/sysv/zapret list_table +``` + +It's also possible to hook with your script to any stage of zapret firewall processing. +The following settings are available in the zapret config file : + +``` +INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" +INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" +INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" +INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" +``` + +Hooks are extremely useful if you need nftables sets populated by zapret scripts. +nfsets can only belong to one table. You have to write rule there and synchorize them with zapret scripts. + +## Installation + +### Checking ISP + +Before running zapret you must discover working bypass strategy. +`blockcheck.sh` automates this process. It first checks DNS then tries many strategies finding the working ones. +Note that DNS check is mostly Russia targeted. It checks several pre-defined blocked in Russia domains and +verifies system DNS answers with public DNS answers. Because ISP can block public DNS or redirect any DNS queries +to their servers `blockcheck.sh` also checks that all returned answers are unique. Usually if DNS is blocked +ISP returns single ip for all blocked domains to redirect you to their "access denied" page. +`blockcheck.sh` works in Linux and FreeBSD. + +### desktop linux system + +Simple install works on most modern linux distributions with systemd or openrc, OpenWRT and MacOS. +Run `install_easy.sh` and answer its questions. + +### OpenWRT + +`install_easy.sh` works on openwrt but there're additional challenges. +They are mainly about possibly low flash free space. +Simple install will not work if it has no space to install itself and required packages from the repo. + +Another challenge would be to bring zapret to the router. You can download zip from github and use it. +Install openssh-sftp-server and unzip to openwrt and use sftp to transfer the file. +It's also not too hard to use 'nc' (netcat) for file transfer. + +The best way to start is to put zapret dir to `/tmp` and run `/tmp/zapret/install_easy.sh` from there. +After installation remove `/tmp/zapret` to free RAM. + +The absolute minimum for openwrt is 64/8 system, 64/16 is comfortable, 128/extroot is recommended. + +### Android + +Its not possible to use nfqws and tpws in transparent proxy mode without root privileges. +Without root tpws can run in --socks mode. + +Android has NFQUEUE and nfqws should work. + +There's no ipset support unless you run custom kernel. In common case task of bringing up ipset +on android is ranging from "not easy" to "almost impossible", unless you find working kernel +image for your device. + +Android does not use /etc/passwd, `tpws --user` won't work. There's replacement. +Use numeric uids in `--uid` option. +Its recommended to use gid 3003 (AID_INET), otherwise tpws will not have inet access. + +Example : `--uid 1:3003` + +In iptables use : `! --uid-owner 1` instead of `! --uid-owner tpws`. + +Nfqws should be executed with `--uid 1`. Otherwise on some devices or firmwares kernel may partially hang. Looks like processes with certain uids can be suspended. With buggy chineese cellular interface driver this can lead to device hang. + +Write your own shell script with iptables and tpws, run it using your root manager. +Autorun scripts are here : + +magisk : `/data/adb/service.d` + +supersu : `/system/su.d` + +How to run tpws on root-less android. +You can't write to `/system`, `/data`, can't run from sd card. +Selinux prevents running executables in `/data/local/tmp` from apps. +Use adb and adb shell. + +``` +mkdir /data/local/tmp/zapret +adb push tpws /data/local/tmp/zapret +chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws +chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws +``` + +Now its possible to run `/data/local/tmp/zapret/tpws` from any app such as tasker. + +### FreeBSD, OpenBSD, MacOS + +see docs/bsd.eng.md + +### Windows (WSL) + +see docs/windows.eng.md + +### Other devices + +Author's goal does not include easy supporting as much devices as possibles. +Please do not ask for easy supporting firmwares. It requires a lot of work and owning lots of devices. Its counterproductive. +As a devices owner its easier for you and should not be too hard if firmware is open. +Most closed stock firmwares are not designed for custom usage and sometimes actively prevent it. +In the latter case you have to hack into it and reverse engineer. Its not easy. +Binaries are universal. They can run on almost all firmwares. +You will need : + * root shell access. true sh shell, not microtik-like console + * startup hook + * r/w partition to store binaries and startup script with executable permission (+x) + * tpws can be run almost anywhere but nfqws require kernel support for NFQUEUE. Its missing in most firmwares. + * too old 2.6 kernels are unsupported and can cause errors. newer 2.6 kernels are OK. +If binaries crash with segfault (rare but happens on some kernels) try to unpack upx like this : upx -d tpws. + +First manually debug your scenario. Run iptables + daemon and check if its what you want. +Write your own script with iptables magic and run required daemon from there. Put it to startup. +Dont ask me how to do it. Its different for all firmwares and requires studying. +Find manual or reverse engineer yourself. +Check for race conditions. Firmware can clear or modify iptables after your startup script. +If this is the case then run another script in background and add some delay there. diff --git a/docs/readme.txt b/docs/readme.txt new file mode 100644 index 00000000..759f2353 --- /dev/null +++ b/docs/readme.txt @@ -0,0 +1,1937 @@ +zapret v.64 + +English +------- + +For english version refer to docs/readme.eng.txt + +Для чего это надо +----------------- + +Автономное, без задействования сторонних серверов, средство противодействия DPI. +Может помочь обойти блокировки или замедление сайтов http(s), сигнатурный анализ tcp и udp протоколов, +например с целью блокировки VPN. + +Проект нацелен прежде всего на маломощные embedded устройства - роутеры, работающие под openwrt. +Поддерживаются традиционные Linux системы, FreeBSD, OpenBSD, частично MacOS. +В некоторых случаях возможна самостоятельная прикрутка решения к различным прошивкам. + +Большая часть функционала работает на windows. + +Как побыстрее начать +-------------------- + +Читайте docs/quick_start.txt для linux и openwrt, docs/quick_start_windows.txt для windows. + + +Как это работает +---------------- + +В самом простейшем случае вы имеете дело с пассивным DPI. Пассивный DPI может читать трафик +из потока, может инжектить свои пакеты, но не может блокировать проходящие пакеты. +Если запрос "плохой", пассивный DPI инжектит пакет RST, опционально дополняя его пакетом http redirect. +Если фейк пакет инжектится только для клиента, в этом случае можно обойтись командами iptables +для дропа RST и/или редиректа на заглушку по определенным условиям, которые нужно подбирать +для каждого провайдера индивидуально. Так мы обходим последствия срабатывания триггера запрета. +Если пассивный DPI направляет пакет RST в том числе и серверу, то вы ничего с этим не сможете сделать. +Ваша задача - не допустить срабатывания триггера запрета. Одними iptables уже не обойдетесь. +Этот проект нацелен именно на предотвращение срабатывания запрета, а не ликвидацию его последствий. + +Активный DPI ставится в разрез провода и может дропать пакеты по любым критериям, +в том числе распознавать TCP потоки и блокировать любые пакеты, принадлежащие потоку. + +Как не допустить срабатывания триггера запрета ? Послать то, на что DPI не рассчитывает +и что ломает ему алгоритм распознавания запросов и их блокировки. + +Некоторые DPI не могут распознать http запрос, если он разделен на TCP сегменты. +Например, запрос вида "GET / HTTP/1.1\r\nHost: kinozal.tv......" +мы посылаем 2 частями : сначала идет "GET ", затем "/ HTTP/1.1\r\nHost: kinozal.tv.....". +Другие DPI спотыкаются, когда заголовок "Host:" пишется в другом регистре : например, "host:". +Кое-где работает добавление дополнительного пробела после метода : "GET /" => "GET /" +или добавление точки в конце имени хоста : "Host: kinozal.tv." + +Существует и более продвинутая магия, направленная на преодоление DPI на пакетном уровне. + +Подробнее про DPI : + https://habr.com/ru/post/335436 + https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf + + +Что сейчас происходит в России +------------------------------ + +Раньше, до внедрения повсеместных систем ТСПУ, использовался зоопарк различных DPI +у провайдеров. Какие-то были активными, какие-то пассивными. +Сейчас время простых iptables окончательно ушло. Везде активный DPI ТСПУ, но кое-где +могут оставаться невыключенными дополнительные старые DPI из зоопарка. +В этом случае приходится обходить сразу несколько DPI. +Все больше становится внереестровых блокировок, о которых вы узнаете только по факту +недоступности чего-либо, в списках этого нет. +Применяются блокировки некоторых диапазонов ip адресов (автономный обход невозможен) +и протоколов (VPN). +На некоторых диапазонах IP используется более строгий фильтр, распознающий попытки +обмана через сегментацию. Должно быть это связано с некоторыми сервисами, которые +пытаются таким образом обмануть DPI. + + +Как это реализовать на практике в системе linux +----------------------------------------------- + +Если кратко, то варианты можно классифицировать по следующей схеме : + +1) Пассивный DPI, не отправляющий RST серверу. Помогут индивидуально настраиваемые под провайдера команды iptables. +На rutracker в разделе "обход блокировок - другие способы" по этому вопросу существует отдельная тема. +В данном проекте не рассматривается. Если вы не допустите срабатывание триггера запрета, то и не придется +бороться с его последствиями. +2) Модификация TCP соединения на уровне потока. Реализуется через proxy или transparent proxy. +3) Модификация TCP соединения на уровне пакетов. Реализуется через обработчик очереди NFQUEUE и raw сокеты. + +Для вариантов 2 и 3 реализованы программы tpws и nfqws соответственно. +Чтобы они работали, необходимо их запустить с нужными параметрами и перенаправить на них определенный трафик +средствами iptables или nftables. + + +Для перенаправления tcp соединения на transparent proxy используются команды следующего вида : + +проходящий трафик : +iptables -t nat -I PREROUTING -i <внутренний_интерфейс> -p tcp --dport 80 -j DNAT --to 127.0.0.127:988 +исходящий трафик : +iptables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.127:988 + +DNAT на localhost работает в цепочке OUTPUT, но не работает в цепочке PREROUTING без включения параметра route_localnet : + +sysctl -w net.ipv4.conf.<внутренний_интерфейс>.route_localnet=1 + +Можно использовать "-j REDIRECT --to-port 988" вместо DNAT, однако в этом случае процесс transparent proxy +должен слушать на ip адресе входящего интерфейса или на всех адресах. Слушать на всех - не есть хорошо +с точки зрения безопасности. Слушать на одном (локальном) можно, но в случае автоматизированного +скрипта придется его узнавать, потом динамически вписывать в команду. В любом случае требуются дополнительные усилия. +Использование route_localnet тоже имеет потенциальные проблемы с безопасностью. Вы делаете доступным все, что висит +на 127.0.0.0/8 для локальной подсети <внутренний_интерфейс>. Службы обычно привязываются к 127.0.0.1, поэтому можно +средствами iptables запретить входящие на 127.0.0.1 не с интерфейса lo, либо повесить tpws на любой другой IP из +из 127.0.0.0/8, например на 127.0.0.127, и разрешить входящие не с lo только на этот IP. + +iptables -A INPUT ! -i lo -d 127.0.0.127 -j ACCEPT +iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP + +Фильтр по owner необходим для исключения рекурсивного перенаправления соединений от самого tpws. +tpws запускается под пользователем "tpws", для него задается исключающее правило. + +tpws может использоваться в режиме socks proxy. В этом случае iptables не нужны, а нужно прописать socks +в настройки программы (например, броузера), с которой будем обходить блокировки. +transparent proxy отличается от socks именно тем, что в варианте transparent настраивать клиентские программы не нужно. + + +Для перенаправления на очередь NFQUEUE исходящего и проходящего в сторону внешнего интерфейса трафика используются +команды следующего вида : + +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass + + +Чтобы не трогать трафик на незаблокированные адреса, можно взять список заблокированных хостов, заресолвить его +в IP адреса и загнать в ipset zapret, затем добавить фильтр в команду : + +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +DPI может ловить только первый http запрос, игнорируя последующие запросы в keep-alive сессии. +Тогда можем уменьшить нагрузку на проц, отказавшись от процессинга ненужных пакетов. + +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass + +Фильтр по mark нужен для отсечения от очереди пакетов, сгенерированных внутри nfqws. +Если применяется фильтр по connbytes 1:6, то обязательно добавлять в iptables и фильтр по mark. Иначе возможно +перепутывание порядка следования пакетов, что приведет к неработоспособности метода. + +Для некоторых атак на DPI требуется перенаправлять один или несколько входящих пакетов от соединения : + +iptables -t mangle -I PREROUTING -i <внешний_интерфейс> -p tcp --sport 80 -m connbytes --connbytes-dir=reply --connbytes-mode=packets --connbytes 1:6 -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass + +Получаемые пакеты будут фильтроваться по входящему интерфейсу, порту и IP источника, то есть наоборот прямому правилу. + +Некоторые техники, ломающие NAT, не всегда можно реализовать через iptables. Требуются nftables. + + +Если ваше устройство поддерживает аппаратное ускорение (flow offloading, hardware nat, hardware acceleration), то iptables могут не работать. +При включенном offloading пакет не проходит по обычному пути netfilter. +Необходимо или его отключить, или выборочно им управлять. + +В новых ядрах (и в более старых, openwrt портировал изменение на 4.14) присутствует software flow offloading (SFO). +Пакеты, проходящие через SFO, так же проходят мимо большей части механизмов iptables. +При включенном SFO работает DNAT/REDIRECT (tpws). Эти соединения исключаются из offloading. +Однако, остальные соединения идут через SFO, потому NFQUEUE будет срабатывать только до помещения +соединения в flowtable. Практически это означает, что nfqws будет работать на window size changing, +но не будут работать опции по модификации содержимого пакетов. +Offload включается через специальный target в iptables "FLOWOFFLOAD". Не обязательно пропускать весь трафик через offload. +Можно исключить из offload соединения, которые должны попасть на tpws или nfqws. +openwrt не предусматривает выборочного управления offload. +Поэтому скрипты zapret поддерживают свою систему выборочного управления offload в openwrt. + + +Особенности применения ip6tables +-------------------------------- + +ip6tables работают почти точно так же, как и ipv4, но есть ряд важных нюансов. +В DNAT следует брать адрес --to в квадратные скобки. Например : + + ip6tables -t nat -I OUTPUT -o <внешний_интерфейс> -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to [::1]:988 + +Параметра route_localnet не существует для ipv6. +DNAT на localhost (::1) возможен только в цепочке OUTPUT. +В цепочке PREROUTING DNAT возможен на любой global address или на link local address того же интерфейса, +откуда пришел пакет. +NFQUEUE работает без изменений. + + +Особенности применения nftables +------------------------------- + +Более подробно преимущества и недостатки nftables применительно к данной системе описаны в docs/nftables_notes.txt +Если коротко, то в nftables невозможно работать с большими ip листами на системах с малым количеством RAM. +Остальные рассматриваемые здесь функции могут быть перенесены на nftables. + +Рекомендуется версия nft 1.0.2 или выше. Но чем выше версия, тем лучше. В nft регулярно находят баги. + +Относительно старые версии ядра и/или утилиты nft могут вызывать ошибки. +В частности, на ubuntu 18.04 с ядром 4.15 будут проблемы. В 20.04 - работает. + +Некоторые техники можно полноценно использовать только с nftables. +Например, в iptables невозможно перенаправить пакеты на nfqws после NAT. +Следовательно, при использовании NAT невозможно произвести атаку, не совместимую с NAT. +В nftables этой проблемы не существует, потому что приоритеты хука выставляете вы сами, а не привязаны к фиксированным +значениям, соответствующим разным таблицам iptables. В iptables нет таблицы, способной перехватить пакеты после MASQUERDADE. + + +Когда это работать не будет +--------------------------- + +* Если подменяется DNS. С этой проблемой легко справиться. +* Если блокировка осуществляется по IP. +* Если соединение проходит через фильтр, способный реконструировать TCP соединение, и который +следует всем стандартам. Например, нас заворачивают на squid. Соединение идет через полноценный стек tcpip +операционной системы, фрагментация отпадает сразу как средство обхода. Squid правильный, он все найдет +как надо, обманывать его бесполезно. +НО. Заворачивать на squid могут позволить себе лишь небольшие провайдеры, поскольку это очень ресурсоемко. +Большие компании обычно используют DPI, который рассчитан на гораздо большую пропускную способность. +Может применяться комбинированный подход, когда на DPI заворачивают только IP из "плохого" списка, +и дальше уже DPI решает пропускать или нет. Так можно снизить нагрузку на DPI в десятки, если не сотни раз, +а следовательно не покупать очень дорогие решения, обойдясь чем-то существенно более дешевым. +Мелкие провайдеры могут покупать услугу фильтрации у вышестоящих, чтобы самим не морочиться, и +они уже будут применять DPI. + + +nfqws +----- + +Эта программа - модификатор пакетов и обработчик очереди NFQUEUE. +Для BSD систем существует адаптированный вариант - dvtws, собираемый из тех же исходников (см. bsd.txt). + + --debug=0|1 ; 1=выводить отладочные сообщения + --daemon ; демонизировать прогу + --pidfile= ; сохранить PID в файл + --user= ; менять uid процесса + --uid=uid[:gid] ; менять uid процесса + --qnum=N ; номер очереди N + --bind-fix4 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv4 пакетов + --bind-fix6 ; пытаться решить проблему неверного выбора исходящего интерфейса для сгенерированных ipv6 пакетов + --wsize=[:] ; менять tcp window size на указанный размер в SYN,ACK. если не задан scale_factor, то он не меняется (устарело !) + --wssize=[:] ; менять tcp window size на указанный размер в исходящих пакетах. scale_factor по умолчанию 0. (см. conntrack !) + --wssize-cutoff=[n|d|s]N ; изменять server window size в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N + --ctrack-timeouts=S:E:F[:U] ; таймауты внутреннего conntrack в состояниях SYN, ESTABLISHED, FIN, таймаут udp. по умолчанию 60:300:60:60 + --hostcase ; менять регистр заголовка "Host:" по умолчанию на "host:". + --hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета + --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase + --domcase ; домен после Host: сделать таким : TeSt.cOm + --dpi-desync=[,][, ; бит fwmark для пометки десинхронизирующих пакетов, чтобы они повторно не падали в очередь. default = 0x40000000 + --dpi-desync-ttl= ; установить ttl для десинхронизирующих пакетов + --dpi-desync-ttl6= ; установить ipv6 hop limit для десинхронизирующих пакетов. если не указано, используется значение ttl + --dpi-desync-autottl=[[:[-]]] ; режим auto ttl для ipv4 и ipv6. по умолчанию: 1:3-20. delta=0 отключает функцию. + --dpi-desync-autottl6=[[:[-]]] ; переопределение предыдущего параметра для ipv6 + --dpi-desync-fooling= ; дополнительные методики как сделать, чтобы фейковый пакет не дошел до сервера. none md5sig badseq badsum datanoack hopbyhop hopbyhop2 + --dpi-desync-repeats= ; посылать каждый генерируемый в nfqws пакет N раз (не влияет на остальные пакеты) + --dpi-desync-skip-nosni=0| 1 ; 1(default)=не применять dpi desync для запросов без hostname в SNI, в частности для ESNI + --dpi-desync-split-pos=<1..1500> ; (только для split*, disorder*) разбивать пакет на указанной позиции + --dpi-desync-split-http-req=method|host ; разбивка http request на указанном логическом месте + --dpi-desync-split-tls=sni|sniext ; разбивка tls client hello на указанном логическом месте + --dpi-desync-split-seqovl= ; использовать sequence overlap перед первым отсылаемым оригинальным tcp сегментом + --dpi-desync-split-seqovl-pattern=|0xHEX ; чем заполнять фейковую часть overlap + --dpi-desync-badseq-increment= ; инкремент sequence number для badseq. по умолчанию -10000 + --dpi-desync-badack-increment= ; инкремент ack sequence number для badseq. по умолчанию -66000 + --dpi-desync-any-protocol=0|1 ; 0(default)=работать только по http request и tls clienthello 1=по всем непустым пакетам данных + --dpi-desync-fake-http=|0xHEX ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному www.iana.org + --dpi-desync-fake-tls=|0xHEX ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному + --dpi-desync-fake-unknown=|0xHEX ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт + --dpi-desync-fake-syndata=|0xHEX ; файл, содержащий фейковый пейлоад пакета SYN для режима десинхронизации syndata + --dpi-desync-fake-quic=|0xHEX ; файл, содержащий фейковый QUIC Initial + --dpi-desync-fake-dht=|0xHEX ; файл, содержащий фейковый пейлоад DHT протокола для dpi-desync=fake, на замену стандартным нулям 64 байт + --dpi-desync-fake-unknown-udp=|0xHEX ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт + --dpi-desync-udplen-increment= ; насколько увеличивать длину udp пейлоада в режиме udplen + --dpi-desync-udplen-pattern=|0xHEX ; чем добивать udp пакет в режиме udplen. по умолчанию - нули + --dpi-desync-start=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру больше или равно N + --dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N + --hostlist= ; применять дурение только к хостам из листа. может быть множество листов, они объединяются. пустой обший лист = его отсутствие + --hostlist-exclude= ; не применять дурение к хостам из листа. может быть множество листов, они объединяются + --hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) + --hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) + --hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) + --hostlist-auto-retrans-threshold= ; сколько ретрансмиссий запроса считать блокировкой (по умолчанию: 3) + --hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. + --new ; начало новой стратегии + --filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии + --filter-tcp=[~]port1[-port2] ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. установка фильтра tcp и неустановка фильтра udp запрещает udp. + --filter-udp=[~]port1[-port2] ; фильтр портов udp для текущей стратегии. ~ означает инверсию. установка фильтра udp и неустановка фильтра tcp запрещает udp. + +Параметры манипуляции могут сочетаться в любых комбинациях. + +ЗАМЕЧАНИЕ. Параметр --wsize считается устаревшим и более не поддерживается в скриптах. +Функции сплита выполняются в рамках атаки десинхронизации. Это быстрее и избавляет от целого ряда недостатков wsize. + +--debug позволяет выводить подробный лог действий на консоль, в syslog или в файл. +Может быть важен порядок следования опций. --debug лучше всего указывать в самом начале. +Опции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа --debug еще дело не дошло, +то сообщения не будут выведены в файл или syslog. +При логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. +Так что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. +Но имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root. +В начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл, +и у процесса не будет прав на создание файла в его директории, лог больше не будет вестись. +Вместо удаления лучше использовать truncate. +В шелле это можно сделать через команду ": >filename" + +АТАКА ДЕСИНХРОНИЗАЦИИ DPI +Суть ее в следующем. После выполнения tcp 3-way handshake идет первый пакет с данными от клиента. +Там обычно "GET / ..." или TLS ClientHello. Мы дропаем этот пакет, заменяя чем-то другим. +Это может быть поддельная версия с безобидным, но валидным запросом http или https (вариант fake), +пакет сброса соединения (варианты rst, rstack), разбитый на части оригинальный пакет с перепутанным +порядком следования сегментов + обрамление первого сегмента фейками (disorder), +то же самое без перепутывания порядка сегментов (split). +fakeknown отличается от fake тем, что применяется только к распознанному протоколу. +В литературе такие атаки еще называют TCB desynchronization и TCB teardown. +Надо, чтобы фейковые пакеты дошли до DPI, но не дошли до сервера. +На вооружении есть следующие возможности : установить низкий TTL, посылать пакет с инвалидной чексуммой, +добавлять tcp option "MD5 signature", испортить sequence numbers. Все они не лишены недостатков. + +* md5sig работает не на всех серверах. Пакеты с md5 обычно отбрасывают только linux. +* badsum не сработает, если ваше устройство за NAT, который не пропускает пакеты с инвалидной суммой. + Наиболее распространенная настройка NAT роутера в Linux их не пропускает. На Linux построено большинство + домашних роутеров. Непропускание обеспечивается так : настройка ядра sysctl по умолчанию + net.netfilter.nf_conntrack_checksum=1 заставляет conntrack проверять tcp и udp чексуммы входящих пакетов + и выставлять state INVALID для пакетов с инвалидной суммой. + Обычно в правилах iptables вставляется правило для дропа пакетов с состоянием INVALID в цепочке FORWARD. + Совместное сочетание этих факторов приводит к непрохождению badsum через такой роутер. + В openwrt из коробки net.netfilter.nf_conntrack_checksum=0, в других роутерах часто нет, + и не всегда это можно изменить. Чтобы nfqws мог работать через роутер, нужно на нем выставить указанное + значение sysctl в 0. nfqws на самом роутере будет работать и без этой настройки, потому что + чексумма локально созданных пакетов не проверяется никогда. + Если роутер за другим NAT, например провайдерским, и он не пропускает invalid packets + вы ничего не сможете с этим сделать. Но обычно провайдеры все же пропускают badsum. + На некоторых адаптерах/свитчах/драйверах принудительно включен rx-checksum offload, badsum пакеты отсекаются + еще до получения в ОС. В этом случае если что-то и можно сделать, то только модифицировать драйвер, + что представляется задачей крайне нетривиальной. Установлено, что так себя ведут некоторые роутеры на базе mediatek. + badsum пакеты уходят с клиентской ОС, но роутером не видятся в br-lan через tcpdump. + При этом если nfqws выполняется на самом роутере, обход может работать. badsum нормально уходят с внешнего интерфейса. +* Пакеты с badseq будут наверняка отброшены принимающим узлом, но так же и DPI, если он ориентируется + на sequence numbers. По умолчанию смещение seq выбирается -10000. Практика показала, что некоторые DPI + не пропускают seq вне определенного окна. Однако, такое небольшое смещение может вызвать проблемы + при существенной потоковой передаче и потере пакетов. Если вы используете --dpi-desync-any-protocol, + может понадобится установить badseq increment 0x80000000. Это обеспечит надежную гарантию, + что поддельный пакет не вклинится в tcp window на сервере. Так же было замечено, что badseq ломает логику + некоторых DPI при анализе http, вызывая зависание соединения. Причем на тех же DPI TLS с badseq работает нормально. +* TTL казалось бы - лучший вариант, но он требует индивидуальной настройки под каждого провайдера. + Если DPI находится дальше локальных сайтов провайдера, то вы можете отрезать себе доступ к ним. + Ситуация усугубляется наличием ТСПУ на магистралах, что вынуждает делать TTL достаточно высоким, увеличивая + риск пробоя фейка до сервера. + Необходим ip exclude list, заполняемый вручную. Вместе с ttl можно применять md5sig. Это ничего не испортит, + зато дает неплохой шанс работы сайтов, до которых "плохой" пакет дойдет по TTL. + Если не удается найти автоматическое решение, воспользуйтесь файлом zapret-hosts-user-exclude.txt. + Некоторые стоковые прошивки роутеров фиксируют исходящий TTL, без отключения этой опции через них работать не будет. + КАКИМ СТОИТ ВЫБИРАТЬ TTL : найдите минимальное значение, при котором обход еще работает. + Это и будет номер хопа вашего DPI. +* hopbyhop относится только к ipv6. Добавляется ipv6 extenstion header "hop-by-hop options". + В варианте hopbyhop2 добавляются 2 хедера, что является нарушением стандарта и гарантированно отбрасывается + стеком протоколов во всех ОС. Один хедер hop-by-hop принимается всеми ОС, однако на некоторых каналах/провайдерах + такие пакеты могут фильтроваться и не доходить. Расчет идет на то, что DPI проанализирует пакет с hop-by-hop, + но он либо не дойдет до адресата всилу фильтров провайдера, либо будет отброшен сервером, потому что хедера два. +* datanoack высылает фейки со снятым tcp флагом ACK. Сервера такое не принимают, а DPI может принять. + Эта техника может ломать NAT и не всегда работает с iptables, если используется masquerade, даже с локальной + системы (почти всегда на роутерах ipv4). На системах c iptables без masquerade и на nftables работает без + ограничений. Экспериментально выяснено, что многие провайдерские NAT не отбрасывают эти пакеты, потому + работает даже с внутренним провайдерским IP. Но linux NAT оно не пройдет, так что за домашним роутером эта + техника не сработает, но может сработать с него. +* autottl. Суть режима в автоматическом определении TTL, чтобы он почти наверняка прошел DPI и немного не дошел до + сервера. Берутся базовые значения TTL 64,128,255, смотрится входящий пакет + (да, требуется направить первый входящий пакет на nfqws !). + Вычисляется длина пути, отнимается delta (1 по умолчанию). Если TTL вне диапазона (min,max - 3,20 по умолчанию), + то берутся значения min,max, чтобы вписаться в диапазон. Если при этом полученный TTL больше длины пути, + то автоматизм не сработал и берутся фиксированные значения TTL для атаки. + Техника позволяет решить вопрос, когда вся сеть перегорожена шлагбаумами (DPI, ТСПУ) везде где только можно, + включая магистралов. Но потенциально может давать сбои. + Например, при асимметрии входящего и исходящего канала до конкретного сервера. + На каких-то провайдерах эта техника будет работать неплохо, на других доставит больше проблем, чем пользы. + Где-то может потребоваться тюнинг параметров. Лучше использовать с дополнительным ограничителем. + +Режимы дурения могут сочетаться в любых комбинациях. --dpi-desync-fooling берет множество значений через запятую. + +Для режимов fake, rst, rstack после фейка отправляем оригинальный пакет. + +Режим disorder делит оригинальный пакет на 2 части и отправляет следующую комбинацию в указанном порядке : +1. 2-я часть пакета +2. поддельная 1-я часть пакета, поле данных заполнено нулями +3. 1-я часть пакета +4. поддельная 1-я часть пакета, поле данных заполнено нулями. отсылка 2-й раз. +Оригинальный пакет дропается всегда. Параметр --dpi-desync-split-pos позволяет указать байтовую позицию, на которой +происходит разбивка. По умолчанию - 2. Если позиция больше длины пакета, позиция выбирается 1. +Этой последовательностью для DPI максимально усложняется задача реконструкции начального сообщения, +по которому принимается решение о блокировке. Некоторым DPI хватит и tcp сегментов в неправильном порядке, +поддельные части сделаны для дополнительной надежности и более сложных алгоритмов реконструкции. +Режим disorder2 отключает отправку поддельных частей. + +Режим split очень похож на disorder, только нет изменения порядка следования сегментов : +1. поддельная 1-я часть пакета, поле данных заполнено нулями +2. 1-я часть пакета +3. поддельная 1-я часть пакета, поле данных заполнено нулями. отсылка 2-й раз. +4. 2-я часть пакета +Режим split2 отключает отправку поддельных частей. +Он может быть использован как более быстрая альтернатива --wsize. + +disorder2 и split2 не предполагают отсылку фейк пакетов, поэтому опции ttl и fooling неактуальны. + +seqovl добавляет в начало первой отсылаемой части оригинального пакета (1 часть для split и 2 часть для disorder) +seqovl байт со смещенным в минус sequence number на величину seqovl. +В случае split2 расчет идет на то, что предыдущий отсыл, если он был, уже попал в сокет серверного приложения, +поэтому новая пришедшая часть лишь частично находится в пределах текущего окна (in-window). +Спереди фейковая часть отбрасывается, а оставшаяся часть содержит оригинал и начинается с начала window, +поэтому попадает в сокет. +Серверное приложение получает все, что реально отсылает клиент, отбрасывая фейковую out-of-window часть. +Но DPI не может этого понять, поэтому у него происходит sequence десинхронизация. + +Для disorder2 overlap идет на 2-ю часть пакета. Обязательно, чтобы seqovl был меньше split_pos, иначе +все отосланное будет передано в сокет сразу же, включая фейк, ломая протокол прикладного уровня. +При соблюдении этого условия 2-я часть пакета является полностью in-window, +поэтому серверная ОС принимает ее целиком, включая фейк. Но поскольку начальная часть данных из 1 пакета +еще не принята, то фейк и реальные данные остаются в памяти ядра, не отправляясь в серверное приложение. +Как только приходит 1-я часть пакета, она переписывает фейковую часть в памяти ядра. +Ядро получает данные из 1 и 2 части, поэтому далее идет отправка в сокет приложения. +Таково поведение всех unix ОС, кроме solaris - оставлять последние принятые данные. +Windows оставляет старые данные, поэтому disorder с seqovl будет приводить к зависаниям соединения +при работе с Windows серверами. Solaris практически мертв, windows серверов очень немного. +Можно использовать листы при необходимости. +Метод позволяет обойтись без fooling и TTL. Фейки перемешаны с реальным данными. +split/disorder вместо split2/disorder2 по-прежнему добавляют дополнительные отдельные фейки. + +Режимы десинхронизации hopbyhop, destopt и ipfrag1 (не путать с fooling !) относятся только к ipv6 и заключается +в добавлении хедера "hop-by-hop options", "destination options" или "fragment" во все пакеты, попадающие под десинхронизацию. +Здесь надо обязательно понимать, что добавление хедера увеличивает размер пакета, потому не может быть применено +к пакетам максимального размера. Это имеет место при передаче больших сообщений. +В случае невозможности отослать пакет дурение будет отменено, пакет будет выслан в оригинале. +Расчет идет на то, что DPI увидит 0 в поле next header основного заголовка ipv6 и не будет скакать по +extension хедерам в поисках транспортного хедера. Таким образом не поймет, что это tcp или udp, и пропустит пакет +без анализа. Возможно, какие-то DPI на это купятся. +Может сочетаться с любыми режимами 2-й фазы, кроме варианта "ipfrag1+ipfrag2". +Например, "hopbyhop,split2" означает разбить tcp пакет на 2 сегмента, в каждый из них добавить hop-by-hop. +При "hopbyhop,ipfrag2" последовательность хедеров будет : ipv6,hop-by-hop,fragment,tcp/udp. +Режим "ipfrag1" может срабатывать не всегда без специальной подготовки. См. раздел "IP фрагментация". + +Есть DPI, которые анализируют ответы от сервера, в частности сертификат из ServerHello, где прописаны домены. +Подтверждением доставки ClientHello является ACK пакет от сервера с номером ACK sequence, соответствующим длине ClientHello+1. +В варианте disorder обычно приходит сперва частичное подтверждение (SACK), потом полный ACK. +Если вместо ACK или SACK идет RST пакет с минимальной задержкой, то DPI вас отсекает еще на этапе вашего запроса. +Если RST идет после полного ACK спустя задержку, равную примерно пингу до сервера, +тогда вероятно DPI реагирует на ответ сервера. +DPI может отстать от потока, если ClientHello его удовлетворил и не проверять ServerHello. +Тогда вам повезло. Вариант fake может сработать. +Если же он не отстает и упорно проверяет ServerHello, то можно попробовать заставить сервер высылать ServerHello частями +через параметр --wssize (см. conntrack). +Если и это не помогает, то сделать с этим что-либо вряд ли возможно без помощи со стороны сервера. +Лучшее решение - включить на сервере поддержку TLS 1.3. В нем сертификат сервера передается в зашифрованном виде. +Это рекомендация ко всем админам блокируемых сайтов. Включайте TLS 1.3. Так вы дадите больше возможностей преодолеть DPI. + +Хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. +Субдомены учитываются автоматически. Поддерживаются листы gzip. + +iptables для задействования атаки на первый пакет данных : + +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp -m multiport --dports 80,443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass + +Этот вариант применяем, когда DPI не следит за всеми запросами http внутри keep-alive сессии. +Если следит, направляем только первый пакет от https и все пакеты от http : + +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass +iptables -t mangle -I POSTROUTING -o <внешний_интерфейс> -p tcp --dport 80 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass + +mark нужен, чтобы сгенерированный поддельный пакет не попал опять к нам на обработку. nfqws выставляет fwmark при его отсылке. +хотя nfqws способен самостоятельно различать помеченные пакеты, фильтр в iptables по mark нужен при использовании connbytes, +чтобы не допустить изменения порядка следования пакетов. Процессинг очереди - процесс отложенный. +Если ядро имеет пакеты на отсылку вне очереди - оно их отправляет незамедлительно. +Изменение правильного порядка следования пакетов при десинхронизации ломает всю идею. +При отсутствии ограничения на connbytes, атака будет работать и без фильтра по mark. +Но лучше его все же оставить для увеличения скорости. + +Почему --connbytes 1:6 : +1 - для работы методов десинхронизации 0-й фазы и wssize +2 - иногда данные идут в 3-м пакете 3-way handshake +3 - стандартная ситуация приема одного пакета запроса +4-6 - на случай ретрансмиссии или запроса длиной в несколько пакетов (TLSClientHello с kyber, например) + +КОМБИНИРОВАНИЕ МЕТОДОВ ДЕСИНХРОНИЗАЦИИ +В параметре dpi-desync можно указать до 3 режимов через запятую. +0 фаза предполагает работу на этапе установления соединения. Может быть synack или syndata. +На 0 фазу не действует фильтр по hostlist. +Последующие режимы отрабатывают на пакетах с данными. +Режим 1-й фазы может быть fake,rst,rstack. Режим 2-й фазы может быть disorder,disorder2,split,split2,ipfrag2. +Может быть полезно, когда у провайдера стоит не один DPI. + +РЕЖИМ SYNACK +В документации по geneva это называется "TCB turnaround". Попытка ввести DPI в заблуждение относительно +ролей клиента и сервера. +!!! Поскольку режим нарушает работу NAT, техника может сработать только если между атакующим устройством +и DPI нет NAT. Атака не сработает через NAT роутер, но может сработать с него. +Для реализации атаки в linux обязательно требуется отключить стандартное правило firewall, +дропающее инвалидные пакеты в цепочке OUTPUT. Например : -A OUTPUT -m state --state INVALID -j DROP +В openwrt можно отключить drop INVALID в OUTPUT и FORWARD через опцию в /etc/config/firewall : + +config zone + option name 'wan' + ......... + option masq_allow_invalid '1' + +К сожалению, отключить только в OUTPUT таким образом нельзя. Но можно сделать иначе. Вписать в /etc/firewall.user : + +iptables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT +ip6tables -D zone_wan_output -m comment --comment '!fw3' -j zone_wan_dest_ACCEPT + +Лучше делать так, потому что отсутствие дропа INVALID в FORWARD может привести к нежелательным утечкам пакетов из LAN. +Если не принять эти меры, отсылка SYN,ACK сегмента вызовет ошибку и операция будет прервана. +Остальные режимы тоже не сработают. Если поймете, что вам synack не нужен, обязательно верните правило дропа INVALID. + +РЕЖИМ SYNDATA +Тут все просто. Добавляются данные в пакет SYN. Все ОС их игнорируют, если не используется TCP fast open (TFO), +а DPI может воспринять, не разобравшись есть там TFO или нет. +Оригинальные соединения с TFO не трогаются, поскольку это их точно сломает. +Без уточняющего параметра добавляются 16 нулевых байтов. + +ВИРТУАЛЬНЫЕ МАШИНЫ +Изнутри VM от virtualbox и vmware в режиме NAT не работают многие техники пакетной магии nfqws. +Принудительно заменяется ttl, не проходят фейк пакеты. Необходимо настроить сеть в режиме bridge. + +CONNTRACK +nfqws оснащен ограниченной реализацией слежения за состоянием tcp соединений (conntrack). +Он включается для реализации некоторых методов противодействия DPI. +conntrack способен следить за фазой соединения : SYN,ESTABLISHED,FIN, количеством пакетов в каждую сторону, +sequence numbers. conntrack способен "кормиться" пакетами в обе или только в одну сторону. +Соединение попадает в таблицу при обнаружении пакетов с выставленными флагами SYN или SYN,ACK. +Поэтому если необходим conntrack, в правилах перенаправления iptables соединение должно идти на nfqws с самого первого +пакета, хотя затем может обрываться по фильтру connbytes. +Для UDP инициатором попадания в таблицу является первый UDP пакет. Он же и определяет направление потока. +Считается, что первый UDP пакет исходит от клиента к серверу. Далее все пакеты с совпадающими +src_ip,src_port,dst_ip,dst_port считаются принадлежащими этому потоку до истечения времени неактивности. +conntrack - простенький, он не писался с учетом всевозможных атак на соединение, он не проверяет +пакеты на валидность sequence numbers или чексумму. Его задача - лишь обслуживание нужд nfqws, он обычно +кормится только исходящим трафиком, потому нечувствителен к подменам со стороны внешней сети. +Соединение удаляется из таблицы, как только отпадает нужда в слежении за ним или по таймауту неактивности. +Существуют отдельные таймауты на каждую фазу соединения. Они могут быть изменены параметром --ctrack-timeouts. + +--wssize позволяет изменить с клиента размер tcp window для сервера, чтобы он послал следующие ответы разбитыми на части. +Чтобы это подействовало на все серверные ОС, необходимо менять window size в каждом исходящем с клиента пакете до отсылки сообщения, +ответ на который должен быть разбит (например, TLS ClientHello). Именно поэтому и необходим conntrack, чтобы +знать когда надо остановиться. Если не остановиться и все время устанавливать низкий wssize, скорость упадет катастрофически. +В linux это может быть купировано через connbytes, но в BSD системах такой возможности нет. +В случае http(s) останавливаемся сразу после отсылки первого http запроса или TLS ClientHello. +Если вы имеете дело с не http(s), то вам потребуется параметр --wssize-cutoff. Он устанавливает предел, с которого действие +wssize прекращается. Префикс d перед номером означает учитывать только пакеты с data payload, префикс s - relative sequence number, +проще говоря количество переданных клиентом байтов + 1. +Если проскочит пакет с http request или TLS ClientHello, действие wssize прекращается сразу же, не дожидаясь wssize-cutoff. +Если ваш протокол склонен к долгому бездействию, следует увеличить таймаут фазы ESTABLISHED через параметр --ctrack-timeouts. +Таймаут по умолчанию низкий - всего 5 минут. +Не забывайте, что nfqws кормится приходящими на него пакетами. Если вы ограничили поступление пакетов через connbytes, +то в таблице могут остаться повисшие соединения в фазе ESTABLISHED, которые отвалятся только по таймауту. +Для диагностики состояния conntrack пошлите сигнал SIGUSR1 процессу nfqws : killall -SIGUSR1 nfqws. +Текущая таблица будет выведена nfqws в stdout. + +Обычно в SYN пакете клиент отсылает кроме window size еще и TCP extension "scaling factor". +scaling factor представляет из себя степень двойки, на которую умножается window size : 0=>1, 1=>2, 2=>4, ..., 8=>256, ... +В параметре wssize scaling factor указывается через двоеточие. +Scaling factor может только снижаться, увеличение заблокировано, чтобы не допустить превышение размера окна со стороны сервера. +Для принуждения сервера к фрагментации ServerHello, чтобы избежать просекание имени сервера из сертификата сервера на DPI, +лучше всего использовать --wssize=1:6 . Основное правило - делать scale_factor как можно больше, чтобы после восстановления +window size итоговый размер окна стал максимально возможным. Если вы сделаете 64:0, будет очень медленно. +С другой стороны нельзя допустить, чтобы ответ сервера стал достаточно большим, чтобы DPI нашел там искомое. + +--wssize не работает в профилях с хостлистами, поскольку он действует с самого начала соединения, когда еще нельзя +принять решение о попадании в лист. Однако, профиль с auto hostlist может содержать --wssize. +--wssize может замедлять скорость и/или увеличивать время ответа сайтов, поэтому если есть другие работающие способы +обхода DPI, лучше применять их. + +--dpi-desync-cutoff позволяет задать предел, при достижении которого прекращается применение dpi-desync. +Доступны префиксы n,d,s по аналогии с --wssize-cutoff. +Полезно совместно с --dpi-desync-any-protocol=1. +На склонных к бездействию соединениях следует изменить таймауты conntrack. +Если соединение выпало из conntrack и задана опция --dpi-desync-cutoff, dpi desync применяться не будет. + +РЕАССЕМБЛИНГ +nfqws поддерживает реассемблинг некоторых видов запросов. +На текущий момент это TLS и QUIC ClientHello. Они бывает длинными, если в chrome включить пост-квантовую +криптографию tls-kyber, и занимают как правило 2 или 3 пакета. kyber включен по умолчанию, начиная с chromium 124. +chrome рандомизирует фингерпринт TLS. SNI может оказаться как в начале, так и в конце, то есть +попасть любой пакет. stateful DPI обычно реассемблирует запрос целиком, и только потом +принимает решение о блокировке. +В случае получения TLS или QUIC пакета с частичным ClientHello начинается процесс сборки, а пакеты +задерживаются и не отсылаются до ее окончания. По окончании сборки пакеты проходит через десинхронизацию +на основании полностью собранного ClientHello. +При любой ошибке в процессе сборки задержанные пакеты немедленно отсылаются в сеть, а десинхронизация отменяется. + +Есть специальная поддержка всех вариантов tcp сплита для многосегментного TLS. +Если указать позицию сплита больше длины первого пакета или использовать --dpi-desync-split-tls, +то разбивка происходит не обязательно первого пакета, а того, на который пришлась итоговая позиция. +Если, допустим, клиент послал TLS ClientHello длиной 2000, а SNI начинается с 1700, +и заданы опции fake,split2, то перед первым пакетом идет fake, затем первый пакет в оригинале, +а последний пакет разбивается на 2 сегмента. В итоге имеем фейк в начале и 3 реальных сегмента. + +ПОДДЕРЖКА UDP +Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip. +Для UDP действуют только режимы десинхронизации fake,hopbyhop,destopt,ipfrag1,ipfrag2,udplen,tamper. +Возможно сочетание fake,hopbyhop,destopt с ipfrag2, fake,fakeknown с udplen и tamper. +udplen увеличивает размер udp пакета на указанное в --dpi-desync-udplen-increment количество байтов. +Паддинг заполняется нулями по умолчанию, но можно задать свой паттерн. +Предназначено для обмана DPI, ориентирующегося на размеры пакетов. +Может сработать, если пользовательский протокол не привязан жестко к размеру udp пейлоада. +Режим tamper означает модификацию пакетов известных протоколов особенным для протокола образом. +На текущий момент работает только с DHT. +Поддерживается определение пакетов QUIC Initial с расшифровкой содержимого и имени хоста, то есть параметр +--hostlist будет работать. +Определяются пакеты wireguard handshake initiation и DHT (начинается с 'd1', кончается 'e'). +Для десинхронизации других протоколов обязательно указывать --dpi-desync-any-protocol. +Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp +можно изменить 4-м параметром в --ctrack-timeouts. +Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов. +По умолчанию fake наполнение - 64 нуля. Можно указать файл в --dpi-desync-fake-unknown-udp. + +IP ФРАГМЕНТАЦИЯ +Современная сеть практически не пропускает фрагментированные tcp на уровне ip. +На udp с этим дело получше, поскольку некоторые udp протоколы могут опираться на этот механизм (IKE старых версий). +Однако, кое-где бывает, что режут и фрагментированный udp. +Роутеры на базе linux могут самопроизвольно собирать или перефрагментировать пакеты. +Позиция фрагментации задается отдельно для tcp и udp. По умолчанию 24 и 8 соответственно, должна быть кратна 8. +Смещение считается с транспортного заголовка. + +Существует ряд моментов вокруг работы с фрагментами на Linux, без понимания которых может ничего не получиться. + +ipv4 : Linux дает отсылать ipv4 фрагменты, но стандартные настройки iptables в цепочке OUTPUT могут вызывать ошибки отправки. + +ipv6 : Нет способа для приложения гарантированно отослать фрагменты без дефрагментации в conntrack. +На разных системах получается по-разному. Где-то нормально уходят, где-то пакеты дефрагментируются. +Для ядер <4.16 похоже, что нет иного способа решить эту проблему, кроме как выгрузить модуль nf_conntrack, +который подтягивает зависимость nf_defrag_ipv6. Он то как раз и выполняет дефрагментацию. +Для ядер 4.16+ ситуация чуть лучше. Из дефрагментации исключаются пакеты в состоянии NOTRACK. +Чтобы не загромождать описание, смотрите пример решения этой проблемы в blockcheck.sh. + +Иногда требуется подгружать модуль ip6table_raw с параметром raw_before_defrag=1. +В openwrt параметры модулей указываются через пробел после их названий в файлах /etc/modules.d. +В традиционных системах посмотрите используется ли iptables-legacy или iptables-nft. Если legacy, то нужно создать файл +/etc/modprobe.d/ip6table_raw.conf с содержимым : +options ip6table_raw raw_before_defrag=1 +В некоторых традиционных дистрибутивах можно изменить текущий ip6tables через : update-alternatives --config ip6tables +Если вы хотите оставаться на iptables-nft, вам придется пересобрать патченную версию. Патч совсем небольшой. +В nft.c найдите фрагмент : + { + .name = "PREROUTING", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_LOCAL_OUT, + }, +и замените везде -300 на -450. + +Это нужно сделать вручную, никакой автоматики в blockcheck.sh нет. + +Либо можно раз и навсегда избавиться от этой проблемы, используя nftables. Там можно создать netfilter hook +с любым приоритетом. Используйте приоритет -401 и ниже. + +При использовании iptables и NAT, похоже, что нет способа прицепить обработчик очереди после NAT. +Пакет попадает в nfqws с source адресом внутренней сети, затем фрагментируется и уже не обрабатывается NAT. +Так и уходит во внешюю сеть с src ip 192.168.x.x. Следовательно, метод не срабатывает. +Видимо единственный рабочий метод - отказаться от iptables и использовать nftables. +Хук должен быть с приоритетом 101 или выше. + +МНОЖЕСТВЕННЫЕ СТРАТЕГИИ +nfqws способен по-разному реагировать на различные запросы и применять разные стратегии дурения. +Это реализовано посредством поддержки множества профилей дурения. +Профили разделяются в командной строке параметром --new. Первый профиль создается автоматически. +Для него не нужно --new. Каждый профиль имеет фильтр. По умолчанию он пуст, то есть профиль удовлетворяет +любым условиям. +Фильтр может содержать жесткие параметры : версия ip протокола или порты tcp/udp. +Они всегда однозначно идентифицируются даже на нулевой фазе десинхронизации, когда еще хост неизвестен. +В качестве фильтра могут выступать и хост-листы. Они могут сочетаться с жесткими параметрами. +При поступлении запроса идет проверка профилей в порядке от первого до последнего до +достижения первого совпадения с фильтром. +Жесткие параметры фильтра сверяются первыми. При несовпадении идет сразу же переход к следующему профилю. +Если какой-то профиль удовлетворяет жесткому фильтру и содержит авто-хостлист, он выбирается сразу. +Если профиль удовлетворяет жесткому фильтру, для него задан хостлист, и у нас еще нет имени хоста, +идет переход к следующему профилю. В противном случае идет проверка по хостлистам этого профиля. +Если имя хоста удовлетворяет листам, выбирается этот профиль. Иначе идет переход к следующему. +Может так случиться, что до получения имени хоста соединение идет по одному профилю, а при получении +хоста профиль меняется на лету. Поэтому если у вас есть параметры дурения нулевой фазы, тщательно +продумывайте что может произойти при переключении стратегии. Смотрите debug log, чтобы лучше +понять что делает nfqws. +Нумерация профилей идет с 1 до N. Последним в цепочке создается пустой профиль с номером 0. +Он используется, когда никакие условия фильтров не совпали. + +ВАЖНО : множественные стратегии создавались только для случаев, когда невозможно обьединить +имеющиеся стратегии для разных ресурсов. Копирование стратегий из blockcheck для разных сайтов +во множество профилей без понимания как они работают приведет к нагромождению параметров, которые все равно +не покроют все возможные заблокированные ресурсы. Вы только увязните в этой каше. + + +tpws +----- + +tpws - это transparent proxy. + --debug=0|1|2|syslog|@ ; 0,1,2 = логирование на косоль : 0=тихо, 1(default)=подробно, 2=отладка. + --debug-level=0|1|2 ; указать уровень логирования для syslog и @ + --daemon ; демонизировать прогу + --pidfile= ; сохранить PID в файл + --user= ; менять uid процесса + --uid=uid[:gid] ; менять uid процесса + --bind-addr ; на каком адресе слушать. может быть ipv4 или ipv6 адрес + ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan + --bind-linklocal=no|unwanted|prefer|force + ; no : биндаться только на global ipv6 + ; unwanted (default) : предпочтительно global, если нет - LL + ; prefer : предпочтительно LL, если нет - global + ; force : биндаться только на LL + --bind-iface4= ; слушать на первом ipv4 интерфейса iface + --bind-iface6= ; слушать на первом ipv6 интерфейса iface + --bind-wait-ifup= ; ждать до N секунд появления и поднятия интерфейса + --bind-wait-ip= ; ждать до N секунд получения IP адреса (если задан --bind-wait-ifup - время идет после поднятия интерфейса) + --bind-wait-ip-linklocal= + ; имеет смысл только при задании --bind-wait-ip + ; --bind-linklocal=unwanted : согласиться на LL после N секунд + ; --bind-linklocal=prefer : согласиться на global address после N секунд + --bind-wait-only ; подождать все бинды и выйти. результат 0 в случае успеха, иначе не 0. + --connect-bind-addr ; с какого адреса подключаться во внешнюю сеть. может быть ipv4 или ipv6 адрес + ; если указан ipv6 link local, то требуется указать с какого он интерфейса : fe80::1%br-lan + ; опция может повторяться для v4 и v6 адресов + ; опция не отменяет правил маршрутизации ! выбор интерфейса определяется лишь правилами маршрутизации, кроме случая v6 link local. + --socks ; вместо прозрачного прокси реализовать socks4/5 proxy + --no-resolve ; запретить ресолвинг имен через socks5 + --resolve-threads ; количество потоков ресолвера + --port= ; на каком порту слушать + --maxconn= ; максимальное количество соединений от клиентов к прокси + --maxfiles= ; макс количество файловых дескрипторов (setrlimit). мин требование (X*connections+16), где X=6 в tcp proxy mode, X=4 в режиме тамперинга. + ; стоит сделать запас с коэффициентом как минимум 1.5. по умолчанию maxfiles (X*connections)*1.5+16 + --max-orphan-time=; если вы запускаете через tpws торрент-клиент с множеством раздач, он пытается установить очень много исходящих соединений, + ; большая часть из которых отваливается по таймауту (юзера сидят за NAT, firewall, ...) + ; установление соединения в linux может длиться очень долго. локальный конец отвалился, перед этим послав блок данных, + ; tpws ждет подключения удаленного конца, чтобы отослать ему этот блок, и зависает надолго. + ; настройка позволяет сбрасывать такие подключения через N секунд, теряя блок данных. по умолчанию 5 сек. 0 означает отключить функцию + ; эта функция не действует на успешно подключенные ранее соединения + + --local-rcvbuf= ; SO_RCVBUF для соединений client-proxy + --local-sndbuf= ; SO_SNDBUF для соединений client-proxy + --remote-rcvbuf= ; SO_RCVBUF для соединений proxy-target + --remote-sndbuf= ; SO_SNDBUF для соединений proxy-target + --nosplice ; не использовать splice на linux системах + --skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split. + --local-tcp-user-timeout= ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение) + --remote-tcp-user-timeout= ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение) + + --split-http-req=method|host ; способ разделения http запросов на сегменты : около метода (GET,POST) или около заголовка Host + --split-pos= ; делить все посылы на сегменты в указанной позиции. единственная опция, работающая на не-http. при указании split-http-req он имеет преимущество на http. + --split-any-protocol ; применять split-pos к любым пакетам. по умолчанию - только к http и TLS ClientHello + --disorder[=http|tls] ; путем манипуляций с сокетом вынуждает отправлять первым второй сегмент разделенного запроса + --oob[=http|tls] ; отправить байт out-of-band data (OOB) в конце первой части сплита + --oob-data=|0xHEX ; переопределить байт OOB. по умолчанию 0x00. + --hostcase ; менять регистр заголовка "Host:". по умолчанию на "host:". + --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase + --hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." + --hosttab ; добавление табуляции после имени хоста : "Host: kinozal.tv\t" + --hostnospace ; убрать пробел после "Host:" + --hostpad= ; добавить паддинг-хедеров общей длиной перед Host: + --domcase ; домен после Host: сделать таким : TeSt.cOm + --methodspace ; добавить пробел после метода : "GET /" => "GET /" + --methodeol ; добавить перевод строки перед методом : "GET /" => "\r\nGET /" + --unixeol ; конвертировать 0D0A в 0A и использовать везде 0A + --tlsrec=sni|sniext ; разбивка TLS ClientHello на 2 TLS records. режем между 1 и 2 символами hostname в SNI или между байтами длины SNI extension. Если SNI нет - отмена. + --tlsrec-pos= ; разбивка TLS ClientHello на 2 TLS records. режем на указанной позиции, если длина слишком мелкая - на позиции 1. + --mss= ; установить MSS для клиента. может заставить сервер разбивать ответы, но существенно снижает скорость + --mss-pf=[~]port1[-port2] ; применять MSS только к портам назначения, подпадающим под фильтр. ~ означает инверсию + --tamper-start=[n] ; начинать дурение только с указанной байтовой позиции или номера блока исходяшего потока (считается позиция начала принятого блока) + --tamper-cutoff=[n] ; закончить дурение на указанной байтовой позиции или номере блока исходящего потока (считается позиция начала принятого блока) + --hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются. + ; в файле должен быть хост на каждой строке. + ; список читается 1 раз при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. + ; по сигналу HUP список будет перечитан при следующем принятом соединении + ; список может быть запакован в gzip. формат автоматически распознается и разжимается + ; списков может быть множество, они объединяются. пустой общий лист = его отсутствие + ; хосты извлекаются из Host: хедера обычных http запросов и из SNI в TLS ClientHello. + --hostlist-exclude= ; не применять дурение к доменам из листа. может быть множество листов, они объединяются + --hostlist-auto= ; обнаруживать автоматически блокировки и заполнять автоматический hostlist (требует перенаправления входящего трафика) + --hostlist-auto-fail-threshold= ; сколько раз нужно обнаружить ситуацию, похожую на блокировку, чтобы добавить хост в лист (по умолчанию: 3) + --hostlist-auto-fail-time= ; все эти ситуации должны быть в пределах указанного количества секунд (по умолчанию: 60) + --hostlist-auto-debug= ; лог положительных решений по autohostlist. позволяет разобраться почему там появляются хосты. + --new ; начало новой стратегии + --filter-l3=ipv4|ipv6 ; фильтр версии ip для текущей стратегии + --filter-tcp=[~]port1[-port2] ; фильтр портов tcp для текущей стратегии. ~ означает инверсию. + + +--debug позволяет выводить подробный лог действий на консоль, в syslog или в файл. +Может быть важен порядок следования опций. --debug лучше всего указывать в самом начале. +Опции анализируются последовательно. Если ошибка будет при проверке опции, а до анализа --debug еще дело не дошло, +то сообщения не будут выведены в файл или syslog. +--debug=0|1|2 позволяют сразу в одном параметре включить логирование на консоль и указать уровень. +Сохранено для совместимости с более старыми версиями. Для выбора уровня в режиме syslog или file используйте +отдельный параметр --debug-level. Если в этих режимах --debug не указывать уровень через --debug-level, то +автоматически назначается уровень 1. +При логировании в файл процесс не держит файл открытым. Ради каждой записи файл открывается и потом закрывается. +Так что файл можно удалить в любой момент, и он будет создан заново при первом же сообщении в лог. +Но имейте в виду, что если вы запускаете процесс под root, то будет сменен UID на не-root. +В начале на лог файл меняется owner, иначе запись будет невозможна. Если вы потом удалите файл, +и у процесса не будет прав на создание файла в его директории, лог больше не будет вестись. +Вместо удаления лучше использовать truncate. +В шелле это можно сделать через команду ": >filename" + +Параметры манипуляции могут сочетаться в любых комбинациях. + +В случае http запроса split-http-req имеет преимущество над split-pos. +split-pos по умолчанию работает только на http и TLS ClientHello. +Чтобы он работал на любых пакетах, укажите --split-any-protocol. + +На прикладном уровне в общем случае нет гарантированного средства заставить ядро выплюнуть +блок данных, порезанным в определенном месте. ОС держит буфер отсылки (SNDBUF) у каждого сокета. +Если у сокета включена опция TCP_NODELAY и буфер пуст, то каждый send приводит к отсылке +отдельного ip пакета или группы пакетов, если блок не вмещается в один ip пакет. +Однако, если в момент send уже имеется неотосланный буфер, то ОС присоединит данные к нему, +никакой отсылки отдельным пакетом не будет. Но в этом случае и так нет никакой гарантии, +что какой-то блок сообщения пойдет в начале пакета, на что собственно и заточены DPI. +Разбиение будет производится согласно MSS, который зависит от MTU исходящего интерфейса. +Таким образом DPI, смотрящие в начало поля данных TCP пакета, будут поломаны в любом случае. +Протокол http относится к запрос-ответным протоколам. Новое сообщение посылается только тогда, +когда сервер получил запрос и полностью вернул ответ. Значит запрос фактически был не только отослан, +но и принят другой стороной, а следовательно буфер отсылки пуст, и следующие 2 send приведут +к отсылке сегментов данных разными ip пакетами. +Резюме : tpws гарантирует сплит только за счет раздельных вызовов send, что на практике +вполне достаточно для протоколов http(s). + +tpws может биндаться на множество интерфейсов и IP адресов (до 32 шт). +Порт всегда только один. +Параметры --bind-iface* и --bind-addr создают новый бинд. +Остальные параметры --bind-* относятся к последнему бинду. +Для бинда на все ipv4 укажите --bind-addr "0.0.0.0", на все ipv6 - "::". --bind-addr="" - биндаемся на все ipv4 и ipv6. +Выбор режима использования link local ipv6 адресов (fe80::/8) : +--bind-iface6 --bind-linklocal=no : сначала приватный адрес fc00::/7, затем глобальный адрес +--bind-iface6 --bind-linklocal=unwanted : сначала приватный адрес fc00::/7, затем глобальный адрес, затем link local. +--bind-iface6 --bind-linklocal=prefer : сначала link local, затем приватный адрес fc00::/7, затем глобальный адрес. +--bind-iface6 --bind-linklocal=force : только link local +Если не указано ни одного бинда, то создается бинд по умолчанию на все адреса всех интерфейсов. +Для бинда на конкретный link-local address делаем так : --bind-iface6=fe80::aaaa:bbbb:cccc:dddd%iface-name +Параметры --bind-wait* могут помочь в ситуациях, когда нужно взять IP с интерфейса, но его еще нет, он не поднят +или не сконфигурирован. +В разных системах события ifup ловятся по-разному и не гарантируют, что интерфейс уже получил IP адрес определенного типа. +В общем случае не существует единого механизма повеситься на событие типа "на интерфейсе X появился link local address". +Для бинда на известный ip, когда еще интерфейс не сконфигурирован, нужно делать так : --bind-addr=192.168.5.3 --bind-wait-ip=20 +В режиме transparent бинд возможен на любой несуществующий адрес, в режиме socks - только на существующий. + +Параметры rcvbuf и sndbuf позволяют установить setsockopt SO_RCVBUF SO_SNDBUF для локального и удаленного соединения. + +Если не указан ни один из параметров модификации содержимого, tpws работает в режиме "tcp proxy mode". +Он отличается тем, что в оба конца применяется splice для переброски данных из одного сокета в другой +без копирования в память процесса. Практически - это то же самое, но может быть чуть побыстрее. +TCP проксирование может быть полезно для обхода блокировок, когда DPI спотыкается на экзотических +хедерах IP или TCP. Вы вряд ли сможете поправить хедеры, исходящие от айфончиков и гаджетиков, +но на linux сможете влиять на них в какой-то степени через sysctl. +Когда соединение проходит через tpws, фактически прокси-сервер сам устанавливает подключение к удаленному +узлу от своего имени, и на это распространяются настройки системы, на которой работает прокси. +tpws можно использовать на мобильном устройстве, раздающем интернет на тарифе сотового оператора, +где раздача запрещена, в socks режиме даже без рута. Соединения от tpws неотличимы от соединений +с самого раздающего устройства. Отличить можно только по содержанию (типа обновлений windows). +Заодно можно и обойти блокировки. 2 зайца одним выстрелом. +Более подробную информацию по вопросу обхода ограничений операторов гуглите на 4pda.ru. + +Режим "--socks" не требует повышенных привилегий (кроме бинда на привилегированные порты 1..1023). +Поддерживаются версии socks 4 и 5 без авторизации. Версия протокола распознается автоматически. +Подключения к IP того же устройства, на котором работает tpws, включая localhost, запрещены. +socks5 позволяет удаленно ресолвить хосты (curl : --socks5-hostname firefox : socks_remote_dns=true). +tpws поддерживает эту возможность асинхронно, не блокируя процессинг других соединений, используя +многопоточный пул ресолверов. Количество потоков определяется автоматически в зависимости от "--maxconn", +но можно задать и вручную через параметр "--resolver-threads". +Запрос к socks выставляется на паузу, пока домен не будет преобразован в ip адрес в одном из потоков +ресолвера. Ожидание может быть более длинным, если все потоки заняты. +Если задан параметр "--no-resolve", то подключения по именам хостов запрещаются, а пул ресолверов не создается. +Тем самым экономятся ресурсы. + +Параметр --hostpad= добавляет паддинг-хедеров перед Host: на указанное количество байтов. +Если размер слишком большой, то идет разбивка на разные хедеры по 2K. +Общий буфер приема http запроса - 64K, больший паддинг не поддерживается, да и http сервера +такое уже не принимают. +Полезно против DPI, выполняющих реассемблинг TCP с ограниченным буфером. +Если техника работает, то после некоторого количества bytes http запрос начнет проходить до сайта. +Если при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции --split-… +Если все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения. + +--disorder - это попытка симулировать режим disorder2 nfqws, используя особенности ОС по реализации stream сокетов. +Однако, в отличие от nfqws, здесь не требуются повышенные привилегии. +Реализовано это следующим образом. У сокета есть возможность выставить TTL. Все пакеты будут отправляться с ним. +Перед отправкой первого сегмента ставим TTL=1. Пакет будет дропнут на первом же роутере, он не дойдет ни до DPI, ни до сервера. +Затем возвращаем TTL в значение по умолчанию. ОС отсылает второй сегмент, и он уже доходит до сервера. +Сервер возвращает SACK, потому что не получил первый кусок, и ОС его отправляет повторно, но здесь уже мы ничего не делаем. +Этот режим работает как ожидается на Linux и MacOS. Однако, на FreeBSD и OpenBSD он работает не так хорошо. +Ядро этих ОС отсылает ретрансмиссию в виде полного пакета. Потому выходит, что до сервера идет сначала второй кусок, +а потом полный запрос без сплита. На него может отреагировать DPI штатным образом. +--disorder является дополнительным флагом к любому сплиту. Сам по себе он не делает ничего. + +--tlsrec и --tlsrec-pos позволяют внутри одного tcp сегмента разрезать TLS ClientHello на 2 TLS records. +--tlsrec=sni режет между 1 и 2 символами hostname в SNI, делая невозможным бинарный поиск паттерна без анализа +структуры данных. В случае отсутствия SNI разбиение отменяется. +--tlsrec-pos режет на указанной позиции. Если длина блока данных TLS меньше указанной позиции, режем на позиции 1. +Параметр сочетается с --split-pos. В этом случае происходит сначала разделение на уровне TLS record layer, потом на уровне TCP. +Самая изощрённая атака --tlsrec, --split-pos и --disorder вместе. +--tlsrec ломает значительное количество сайтов. Криптобиблиотеки (openssl, ...) на оконечных http серверах +без проблем принимают разделенные tls сегменты, но мидлбоксы - не всегда. К мидлбоксам можно отнести CDN +или системы ddos-защиты. Поэтому применение --tlsrec без ограничителей вряд ли целесообразно. +В РФ --tlsrec обычно не работает с TLS 1.2, потому что цензор парсит сертификат сервера из ServerHello. +Работает только с TLS 1.3, поскольку там эта информация шифруется. +Впрочем, сейчас сайтов, не поддерживающих TLS 1.3, осталось немного. + +--mss устанавливает опцию сокета TCP_MAXSEG. Клиент выдает это значение в tcp опциях SYN пакета. +Сервер в ответ в SYN,ACK выдает свой MSS. На практике сервера обычно снижают размеры отсылаемых ими пакетов, но они +все равно не вписываются в низкий MSS, указанный клиентом. Обычно чем больше указал клиент, тем больше +шлет сервер. На TLS 1.2 если сервер разбил заброс так, чтобы домен из сертификата не попал в первый пакет, +это может обмануть DPI, секущий ответ сервера. +Схема может значительно снизить скорость и сработать не на всех сайтах. +С фильтром по hostlist совместимо только в режиме socks при включенном удаленном ресолвинге хостов. +(firefox network.proxy.socks_remote_dns). Это единственный вариант, когда tpws может узнать имя хоста +еще на этапе установления соединения. +Применяя данную опцию к сайтам TLS1.3, если броузер тоже поддерживает TLS1.3, то вы делаете только хуже. +Но нет способа автоматически узнать когда надо применять, когда нет, поскольку MSS идет только в +3-way handshake еще до обмена данными, а версию TLS можно узнать только по ответу сервера, который +может привести к реакции DPI. +Использовать только когда нет ничего лучше или для отдельных ресурсов. +Для http использовать смысла нет, поэтому заводите отдельный desync profile с фильтром по порту 443. +Работает только на linux, не работает на BSD и MacOS. + +--skip-nodelay может быть полезен, чтобы привести MTU к MTU системы, на которой работает tpws. +Это может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения +подозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз. + +--local-tcp-user-timeout и --remote-tcp-user-timeout устанавливают значение таймаута в секундах +для соединений клиент-прокси и прокси-сервер. Этот таймаут соответствует опции сокета linux +TCP_USER_TIMEOUT. Под таймаутом подразумевается время, в течение которого буферизированные данные +не переданы или на переданные данные не получено подтверждение (ACK) от другой стороны. +Этот таймаут никак не касается времени отсутствия какой-либо передачи через сокет лишь потому, +что данных для передачи нет. Полезно для сокращения время закрытия подвисших соединений. +Поддерживается только на Linux и MacOS. + +МНОЖЕСТВЕННЫЕ СТРАТЕГИИ +Работают аналогично nfqws, кроме некоторых моментов. +Нет параметра --filter-udp, поскольку tpws udp не поддерживает. +Методы нулевой фазы (--mss) могут работать по хостлисту в одном единственном случае : +если используется режим socks и удаленный ресолвинг хостов через прокси. +То есть работоспособность вашей настройки в одном и том же режиме может зависеть от того, +применяет ли клиент удаленный ресолвинг. Это может быть неочевидно. +В одной программе работает, в другой - нет. +Если вы используете профиль с хостлистом , и вам нужен mss, укажите mss в профиле с хостлистом, +создайте еще один профиль без хостлиста, если его еще нет, и в нем еще раз укажите mss. +Тогда при любом раскладе будет выполняться mss. +Используйте `curl --socks5` и `curl --socks5-hostname` для проверки вашей стратегии. +Смотрите вывод --debug, чтобы убедиться в правильности настроек. + +Способы получения списка заблокированных IP +------------------------------------------- + +!!! nftables не могут работать с ipset-ами. Собственный аналогичный механизм требует огромного количество RAM +!!! для загрузки больших листов. Например, для загона 100K записей в nfset не хватает даже 256 Mb. +!!! Если вам нужны большие листы на домашних роутерах, откатывайтесь на iptables+ipset. + +1) Внесите заблокированные домены в ipset/zapret-hosts-user.txt и запустите ipset/get_user.sh +На выходе получите ipset/zapret-ip-user.txt с IP адресами. + +Cкрипты с названием get_reestr_* оперируют дампом реестра заблокированных сайтов : + +2) ipset/get_reestr_resolve.sh получает список доменов от rublacklist и дальше их ресолвит в ip адреса +в файл ipset/zapret-ip.txt.gz. В этом списке есть готовые IP адреса, но судя во всему они там в точности в том виде, +что вносит в реестр РосКомПозор. Адреса могут меняться, позор не успевает их обновлять, а провайдеры редко +банят по IP : вместо этого они банят http запросы с "нехорошим" заголовком "Host:" вне зависимости +от IP адреса. Поэтому скрипт ресолвит все сам, хотя это и занимает много времени. +Используется мультипоточный ресолвер mdig (собственная разработка). + +3) ipset/get_reestr_preresolved.sh. то же самое, что и 2), только берется уже заресолвленый список +со стороннего ресурса. + +4) ipset/get_reestr_preresolved_smart.sh. то же самое, что и 3), с добавлением всего диапазона некоторых +автономных систем (прыгающие IP адреса из cloudflare, facebook, ...) и некоторых поддоменов блокируемых сайтов + +Cкрипты с названием get_antifilter_* оперируют списками адресов и масок подсетей с сайтов antifilter.network и antifilter.download : + +5) ipset/get_antifilter_ip.sh. получает лист https://antifilter.download/list/ip.lst. + +6) ipset/get_antifilter_ipsmart.sh. получает лист https://antifilter.network/download/ipsmart.lst. +умная суммаризация отдельных адресов из ip.lst по маскам от /32 до /22 + +7) ipset/get_antifilter_ipsum.sh. получает лист https://antifilter.download/list/ipsum.lst. +суммаризация отдельных адресов из ip.lst по маске /24 + +8) ipset/get_antifilter_ipresolve.sh. получает лист https://antifilter.download/list/ipresolve.lst. +пре-ресолвленный список, аналогичный получаемый при помощи get_reestr_resolve. только ipv4. + +9) ipset/get_antifilter_allyouneed.sh. получает лист https://antifilter.download/list/allyouneed.lst. +Суммарный список префиксов, созданный из ipsum.lst и subnet.lst. + +Все варианты рассмотренных скриптов автоматически создают и заполняют ipset. +Варианты 2-9 дополнительно вызывают вариант 1. + +10) ipset/get_config.sh. этот скрипт вызывает то, что прописано в переменной GETLIST из файла config +Если переменная не определена, то ресолвятся лишь листы для ipset nozapret/nozapret6. + +Листы РКН все время изменяются. Возникают новые тенденции. Требования к RAM могут меняться. +Поэтому необходима нечастая, но все же регулярная ревизия что же вообще у вас происходит на роутере. +Или вы можете узнать о проблеме лишь когда у вас начнет постоянно пропадать wifi, и вам придется +его перезагружать каждые 2 часа (метод кувалды). + +Самые щадящие варианты по RAM - get_antifilter_allyouneed.sh, get_antifilter_ipsum.sh. + +Листы zapret-ip.txt и zapret-ipban.txt сохраняются в сжатом виде в файлы .gz. +Это позволяет снизить их размер во много раз и сэкономить место на роутере. +Отключить сжатие листов можно параметром конфига GZIP_LISTS=0. + +На роутерах не рекомендуется вызывать эти скрипты чаще раза за 2 суток, поскольку сохранение идет +либо во внутреннюю флэш память роутера, либо в случае extroot - на флэшку. +В обоих случаях слишком частая запись может убить флэшку, но если это произойдет с внутренней +флэш памятью, то вы просто убьете роутер. + +Принудительное обновление ipset выполняет скрипт ipset/create_ipset.sh. +Если передан параметр "no-update", скрипт не обновляет ipset, а только создает его при его отсутствии и заполняет. +Это полезно, когда могут случиться несколько последовательных вызовов скрипта. Нет смысла несколько раз перезаполнять +ipset, это длительная операция на больших листах. Листы можно обновлять раз в несколько суток, и только тогда +вызывать create_ipset без параметра "no-update". Во всех остальных случаях стоит применять "no-update". + +Список РКН уже достиг внушительных размеров в сотни тысяч IP адресов. Поэтому для оптимизации ipset +применяется утилита ip2net. Она берет список отдельных IP адресов и пытается интеллектуально создать из него подсети для сокращения +количества адресов. ip2net отсекает неправильные записи в листах, гарантируя отсутствие ошибок при их загрузке. +ip2net написан на языке C, поскольку операция ресурсоемкая. Иные способы роутер может не потянуть. + +Можно внести список доменов в ipset/zapret-hosts-user-ipban.txt. Их ip адреса будут помещены +в отдельный ipset "ipban". Он может использоваться для принудительного завертывания всех +соединений на прозрачный proxy "redsocks" или на VPN. + +IPV6 : если включен ipv6, то дополнительно создаются листы с таким же именем, но с "6" на конце перед расширением. +zapret-ip.txt => zapret-ip6.txt +Создаются ipset-ы zapret6 и ipban6. +Листы с antifilter не содержат список ipv6 адресов. + +СИСТЕМА ИСКЛЮЧЕНИЯ IP. Все скрипты ресолвят файл zapret-hosts-user-exclude.txt, создавая zapret-ip-exclude.txt и zapret-ip-exclude6.txt. +Они загоняются в ipset-ы nozapret и nozapret6. Все правила, создаваемые init скриптами, создаются с учетом этих ipset. +Помещенные в них IP не участвуют в процессе. +zapret-hosts-user-exclude.txt может содержать домены, ipv4 и ipv6 адреса или подсети. + +FreeBSD. Скрипты ipset/*.sh работают так же на FreeBSD. Вместо ipset они создают lookup таблицы ipfw с аналогичными именами. +ipfw таблицы в отличие от ipset могут содержать как ipv4, так и ipv6 адреса и подсети в одной таблице, поэтому разделения нет. + +Параметр конфига LISTS_RELOAD задает произвольную команду для перезагрузки листов. +Это особенно полезно на BSD системах с PF. +LISTS_RELOAD=- отключает перезагрузку листов. + + +ip2net +------ + +Утилита ip2net предназначена для преобразования ipv4 или ipv6 списка ip в список подсетей +с целью сокращения размера списка. Входные данные берутся из stdin, выходные выдаются в stdout. + + -4 ; лист - ipv4 (по умолчанию) + -6 ; лист - ipv6 + --prefix-length=min[-max] ; диапазон рассматриваемых длин префиксов. например : 22-30 (ipv4), 56-64 (ipv6) + --v4-threshold=mul/div ; ipv4 : включать подсети, в которых заполнено по крайней мере mul/div адресов. например : 3/4 + --v6-threshold=N ; ipv6 : минимальное количество ip для создания подсети + +В списке могут присутствовать записи вида ip/prefix и ip1-ip2. Такие записи выкидываются в stdout без изменений. +Они принимаются командой ipset. ipset умеет для листов hash:net из ip1-ip2 делать оптимальное покрытие ip/prefix. +ipfw из FreeBSD понимает ip/prefix, но не понимает ip1-ip2. +ip2net фильтрует входные данные, выкидывая неправильные IP адреса. + +Выбирается подсеть, в которой присутствует указанный минимум адресов. +Для ipv4 минимум задается как процент от размера подсети (mul/div. например, 3/4), для ipv6 минимум задается напрямую. + +Размер подсети выбирается следующим алгоритмом : +Сначала в указанном диапазоне длин префиксов ищутся подсети, в которых количество адресов - максимально. +Если таких сетей найдено несколько, берется наименьшая сеть (префикс больше). +Например, заданы параметры v6_threshold=2 prefix_length=32-64, имеются следующие ipv6 : +1234:5678:aaaa::5 +1234:5678:aaaa::6 +1234:5678:aaac::5 +Результат будет : +1234:5678:aaa8::/45 +Эти адреса так же входят в подсеть /32. Однако, нет смысла проходиться ковровой бомбардировкой, +когда те же самые адреса вполне влезают в /45 и их ровно столько же. +Если изменить v6_threshold=4, то результат будет : +1234:5678:aaaa::5 +1234:5678:aaaa::6 +1234:5678:aaac::5 +То есть ip не объединятся в подсеть, потому что их слишком мало. +Если изменить prefix_length=56-64, результат будет : +1234:5678:aaaa::/64 +1234:5678:aaac::5 + +Требуемое процессорное время для вычислений сильно зависит от ширины диапазона длин префиксов, размера искомых подсетей и длины листа. +Если ip2net думает слишком долго, не используйте слишком большие подсети и уменьшите диапазон длин префиксов. +Учтите, что арифметика mul/div - целочисленная. При превышении разрядной сетки 32 bit результат непредсказуем. +Не надо делать такое : 5000000/10000000. 1/2 - гораздо лучше. + + +Фильтрация по именам доменов +---------------------------- + +Альтернативой ipset является использование tpws или nfqws со списком доменов. +Оба демона принимают неограниченное количество листов include (--hostlist) и exclude (--hostlist-exclude). +Все листы одного типа объединяются, и таким образом остаются только 2 листа. +Прежде всего проверяется exclude list. При вхождении в него происходит отказ от дурения. +Далее при наличии include list проверяется домен на вхождение в него. При невхождении в список отказ от дурения. +Пустой список приравнивается к его отсутствию. +В иных случаях происходит дурение. +Нет ни одного списка - дурение всегда. +Есть только exclude список - дурение всех, кроме. +Есть только include список - дурение только их. +Есть оба - дурение только include, кроме exclude. + +В системе запуска это обыграно следующим образом. +Присутствуют 2 include списка : +ipset/zapret-hosts-users.txt.gz или ipset/zapret-hosts-users.txt +ipset/zapret-hosts.txt.gz или ipset/zapret-hosts.txt +и 1 exclude список +ipset/zapret-hosts-users-exclude.txt.gz или ipset/zapret-hosts-users-exclude.txt + +При режиме фильтрации MODE_FILTER=hostlist система запуска передает nfqws или tpws все листы, файлы которых присутствуют. +Если вдруг листы include присутствуют, но все они пустые, то работа аналогична отсутствию include листа. +Файл есть, но не смотря на это дурится все, кроме exclude. +Если вам нужен именно такой режим - не обязательно удалять zapret-hosts-users.txt. Достаточно сделать его пустым. + +Поддомены учитываются автоматически. Например, строчка "ru" вносит в список "*.ru". Строчка "*.ru" в списке не сработает. + +Список доменов РКН может быть получен скриптами ipset/get_reestr_hostlist.sh или ipset/get_antizapret_domains.sh +- кладется в ipset/zapret-hosts.txt.gz. + +Чтобы обновить списки, перезапускать nfqws или tpws не нужно. Обновляете файлы, затем даете сигнал HUP. +По HUP листы будут перечитаны. Если вдруг какого-то листа не окажется, процесс завершится с ошибкой. +Скрипты получения листов из ipset сами выдают HUP в конце. + +При фильтрации по именам доменов демон должен запускаться без фильтрации по ipset. +tpws и nfqws решают нужно ли применять дурение в зависимости от хоста, полученного из протокола прикладного уровня (http, tls, quic). +При использовании больших списков, в том числе списка РКН, оцените объем RAM на роутере ! +Если после запуска демона RAM под завязку или случаются oom, значит нужно отказаться от таких больших списков. + + +Режим фильтрации autohostlist +----------------------------- + +Этот режим позволяет проанализировать как запросы со стороны клиента, так и ответы от сервера. +Если хост еще не находится ни в каких листах и обнаруживается ситуация, похожая на блокировку, +происходит автоматическое добавление хоста в список autohostlist как в памяти, так и в файле. +nfqws или tpws сами ведут этот файл. +Чтобы какой-то хост не смог попась в autohostlist используйте hostlist-exclude. +Если он все-же туда попал - удалите запись из файла вручную. Процессы автоматически перечитают файл. +tpws/nfqws сами назначают владельцем файла юзера, под которым они работают после сброса привилегий, +чтобы иметь возможность обновлять лист. + +В случае nfqws данный режим требует перенаправления в том числе и входящего трафика. +Крайне рекомендовано использовать ограничитель connbytes, чтобы nfqws не обрабатывал гигабайты. +По этой же причине не рекомендуется использование режима на BSD системах. Там нет фильтра connbytes. + +На linux системах при использовании nfqws и фильтра connbytes может понадобится : +sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1 +Было замечено, что некоторые DPI в России возвращают RST с неверным ACK. Это принимается tcp/ip стеком +linux, но через раз приобретает статус INVALID в conntrack. Поэтому правила с connbytes срабатывают +через раз, не пересылая RST пакет nfqws. + +Как вообще могут вести себя DPI, получив "плохой запрос" и приняв решение о блокировке : + +1) Зависание : просто отмораживается, блокируя прохождение пакетов по TCP каналу. +2) RST : отправляет RST клиенту и/или серверу +3) Редирект : (только для http) отправляет редирект на сайт-заглушку +4) Подмена сертификата : (только для https) полный перехват TLS сеанса с попыткой всунуть что-то +свое клиенту. Применяется нечасто, поскольку броузеры на такое ругаются. + +nfqws и tpws могут сечь варианты 1-3, 4 они не распознают. +Всилу специфики работы с отдельными пакетами или с TCP каналом tpws и nfqws распознают эти ситуации +по-разному. +Что считается ситуацией, похожей на блокировку : +1) [nfqws] Несколько ретрансмиссий первого запроса в TCP сеансе, в котором имеется host. +2) [nfqws,tpws] RST, пришедший в ответ на первый запрос с хостом. +3) [nfqws,tpws] HTTP редирект, пришедший в ответ на первый запрос с хостом, на глобальный адрес +с доменом 2 уровня, не совпадающим с доменом 2 уровня оригинального запроса. +4) [tpws] закрытие соединения клиентом после отправки первого запроса с хостом, если не было на него +ответа со стороны сервера. Это обычно случается по таймауту, когда нет ответа (случай "зависание"). + +Чтобы снизить вероятность ложных срабатываний, имеется счетчик ситуаций, похожих на блокировку. +Если за определенное время произойдет более определенного их количества, хост считается заблокированным +и заносится в autohostlist. По нему сразу же начинает работать стратегия по обходу блокировки. +Если в процессе счета вебсайт отвечает без признаков блокировки, счетчик сбрасывается. +Вероятно, это был временный сбой сайта. + +На практике работа с данным режимом выглядит так. +Первый раз пользователь заходит на сайт и получает заглушку, сброс соединения или броузер подвисает, +вываливаясь по таймауту с сообщением о невозможности загрузить страницу. +Надо долбить F5, принуждая броузер повторять попытки. После некоторой попытки сайт +начинает работать, и дальше он будет работать всегда. + +С этим режимом можно использовать техники обхода, ломающие значительное количество сайтов. +Если сайт не ведет себя как заблокированный, значит обход применен не будет. +В противном случае терять все равно нечего. +Однако, могут быть временные сбои сервера, приводящие к ситуации, аналогичной блокировке. +Могут происходит ложные срабатывания. Если такое произошло, стратегия может начать ломать +незаблокированный сайт. Эту ситуацию, увы, придется вам контролировать вручную. +Заносите такие домены в ipset/zapret-hosts-user-exclude.txt, чтобы избежать повторения. +Чтобы впоследствии разобраться почему домен был занесен в лист, можно включить autohostlist debug log. +Он полезен тем, что работает без постоянного просмотра вывода nfqws в режиме debug. +В лог заносятся только основные события, ведущие к занесению хоста в лист. +По логу можно понять как избежать ложных срабатываний и подходит ли вообще вам этот режим. + +Можно использовать один autohostlist с множеством процессов. Все процессы проверяют время модификации файла. +Если файл был изменен в другом процессе, то происходит перечитывание всех include листов, включая autohostlist. +Все процессы должны работать под одним uid, чтобы были права доступа на файл. + +Скрипты zapret ведут autohostlist в ipset/zapret-hosts-auto.txt. +install_easy.sh при апгрейде zapret сохраняет этот файл. +Режим autohostlist включает в себя режим hostlist. +Можно вести ipset/zapret-hosts-user.txt, ipset/zapret-hosts-user-exclude.txt. + + +Проверка провайдера +------------------- + +Перед настройкой нужно провести исследование какую бяку устроил вам ваш провайдер. + +Нужно выяснить не подменяет ли он DNS и какой метод обхода DPI работает. +В этом вам поможет скрипт blockcheck.sh. + +Если DNS подменяется, но провайдер не перехватывает обращения к сторонним DNS, поменяйте DNS на публичный. +Например : 8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1, 9.9.9.9 +Если DNS подменяется и провайдер перехватывает обращения к сторонним DNS, настройте dnscrypt. +Еще один эффективный вариант - использовать ресолвер от yandex 77.88.8.88 на нестандартном порту 1253. +Многие провайдеры не анализируют обращения к DNS на нестандартных портах. + +Следует прогнать blockcheck по нескольким заблокированным сайтам и выявить общий характер блокировок. +Разные сайты могут быть заблокированы по-разному, нужно искать такую технику, которая работает на большинстве. +Чтобы записать вывод blockcheck.sh в файл, выполните : ./blockcheck.sh | tee /tmp/blockcheck.txt + +Проанализируйте какие методы дурения DPI работают, в соответствии с ними настройте /opt/zapret/config. + +Имейте в виду, что у провайдеров может быть несколько DPI или запросы могут идти через разные каналы +по методу балансировки нагрузки. Балансировка может означать, что на разных ветках разные DPI или +они находятся на разных хопах. Такая ситуация может выражаться в нестабильности работы обхода. +Дернули несколько раз curl. То работает, то connection reset или редирект. blockcheck.sh выдает +странноватые результаты. То split работает на 2-м. хопе, то на 4-м. Достоверность результата вызывает сомнения. +В этом случае задайте несколько повторов одного и того же теста. Тест будет считаться успешным только, +если все попытки пройдут успешно. + +При использовании autottl следует протестировать как можно больше разных доменов. Эта техника +может на одних провайдерах работать стабильно, на других потребуется выяснить при каких параметрах +она стабильна, на третьих полный хаос, и проще отказаться. + +Blockcheck имеет 3 уровня сканирования. +Цель режима quick - максимально быстро найти хоть что-то работающее. +standard дает возможность провести исследование как и на что реагирует DPI в плане методов обхода. +force дает максимум проверок даже в случаях, когда ресурс работает без обхода или с более простыми стратегиями. + +СКАН ПОРТОВ +Если в системе присутствует совместимый netcat (ncat от nmap или openbsd ncat. в openwrt по умолчанию нет.), +то выполняется сканирование портов http или https всех IP адресов домена. +Если ни один IP не отвечает, то результат очевиден. Можно останавливать сканирование. +Автоматически оно не остановится, потому что netcat-ы недостаточно подробно информируют о причинах ошибки. +Если доступна только часть IP, то можно ожидать хаотичных сбоев, т.к. подключение идет к случайному адресу +из списка. + +ПРОВЕРКА НА ЧАСТИЧНЫЙ IP block +Под частичным блоком подразумевается ситуация, когда коннект на порты есть, но по определенному транспортному +или прикладному протоколу всегда идет реакция DPI вне зависимости от запрашиваемого домена. +Эта проверка так же не выдаст автоматического вердикта/решения, потому что может быть очень много вариаций. +Вместо этого анализ происходящего возложен на самого пользователя или тех, кто будет читать лог. +Суть этой проверки в попытке дернуть неблокированный IP с блокированным доменом и наоборот, анализируя +при этом реакцию DPI. Реакция DPI обычно проявляется в виде таймаута (зависание запроса), connection reset +или http redirect на заглушку. Любой другой вариант скорее всего говорит об отсутствии реакции DPI. +В частности, любые http коды, кроме редиректа, ведущего именно на заглушку, а не куда-то еще. +На TLS - ошибки handshake без задержек. +Ошибка сертификата может говорить как о реакции DPI с MiTM атакой (подмена сертификата), так и +о том, что принимающий сервер неблокированного домена все равно принимает ваш TLS handshake с чужим доменом, +пытаясь при этом выдать сертификат без запрошенного домена. Требуется дополнительный анализ. +Если на заблокированный домен есть реакция на всех IP адресах, значит есть блокировка по домену. +Если на неблокированный домен есть реакция на IP адресах блокированного домена, значит имеет место блок по IP. +Соответственно, если есть и то, и другое, значит есть и блок по IP, и блок по домену. +Неблокированный домен первым делом проверяется на доступность на оригинальном адресе. +При недоступности тест отменяется, поскольку он будет неинформативен. + +Если выяснено, что есть частичный блок по IP на DPI, то скорее всего все остальные тесты будут провалены +вне зависимости от стратегий обхода. Но бывают и некоторые исключения. Например, пробитие через ipv6 +option headers. Или сделать так, чтобы он не мог распознать протокол прикладного уровня. +Дальнейшие тесты могут быть не лишены смысла. + +ПРИМЕРЫ БЛОКИРОВКИ ТОЛЬКО ПО ДОМЕНУ БЕЗ БЛОКА ПО IP + +> testing iana.org on it's original ip +!!!!! AVAILABLE !!!!! +> testing rutracker.org on 192.0.43.8 (iana.org) +curl: (28) Operation timed out after 1002 milliseconds with 0 bytes received +> testing iana.org on 172.67.182.196 (rutracker.org) +HTTP/1.1 409 Conflict +> testing iana.org on 104.21.32.39 (rutracker.org) +HTTP/1.1 409 Conflict + +> testing iana.org on it's original ip +!!!!! AVAILABLE !!!!! +> testing rutracker.org on 192.0.43.8 (iana.org) +curl: (28) Connection timed out after 1001 milliseconds +> testing iana.org on 172.67.182.196 (rutracker.org) +curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure +> testing iana.org on 104.21.32.39 (rutracker.org) +curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure + +> testing iana.org on it's original ip +!!!!! AVAILABLE !!!!! +> testing rutracker.org on 192.0.43.8 (iana.org) +HTTP/1.1 307 Temporary Redirect +Location: https://www.gblnet.net/blocked.php +> testing iana.org on 172.67.182.196 (rutracker.org) +HTTP/1.1 409 Conflict +> testing iana.org on 104.21.32.39 (rutracker.org) +HTTP/1.1 409 Conflict + +> testing iana.org on it's original ip +!!!!! AVAILABLE !!!!! +> testing rutracker.org on 192.0.43.8 (iana.org) +curl: (35) Recv failure: Connection reset by peer +> testing iana.org on 172.67.182.196 (rutracker.org) +curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure +> testing iana.org on 104.21.32.39 (rutracker.org) +curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure + + +ПРИМЕР ПОЛНОГО IP БЛОКА ИЛИ БЛОКА TCP ПОРТА ПРИ ОТСУТСТВИИ БЛОКА ПО ДОМЕНУ + +* port block tests ipv4 startmail.com:80 +ncat -z -w 1 145.131.90.136 80 +145.131.90.136 does not connect. netcat code 1 +ncat -z -w 1 145.131.90.152 80 +145.131.90.152 does not connect. netcat code 1 + +* curl_test_http ipv4 startmail.com +- checking without DPI bypass +curl: (28) Connection timed out after 2002 milliseconds +UNAVAILABLE code=28 + +- IP block tests (requires manual interpretation) +> testing iana.org on it's original ip +!!!!! AVAILABLE !!!!! +> testing startmail.com on 192.0.43.8 (iana.org) +HTTP/1.1 302 Found +Location: https://www.iana.org/ +> testing iana.org on 145.131.90.136 (startmail.com) +curl: (28) Connection timed out after 2002 milliseconds +> testing iana.org on 145.131.90.152 (startmail.com) +curl: (28) Connection timed out after 2002 milliseconds + + +Выбор параметров +---------------- + +Файл /opt/zapret/config используется различными компонентами системы и содержит основные настройки. +Его нужно просмотреть и при необходимости отредактировать. + +На linux системах можно выбрать использовать iptables или nftables. +По умолчанию на традиционных linux выбирается nftables, если установлен nft. +На openwrt по умолчанию выбирается nftables на новых версиях с firewall4. + +FWTYPE=iptables + +Основной режим : +tpws - tpws в режиме transparent +tpws-socks - tpws в режиме socks + вешается на localhost и LAN интерфейс (если задан IFACE_LAN или если система - OpenWRT). порт 988 +nfqws - nfqws +filter - только заполнить ipset или загрузить hostlist +custom - нужно самому запрограммировать запуск демонов в init скрипте и правила iptables + +MODE=tpws + +Применять ли дурение к HTTP : + +MODE_HTTP=1 + +Применять ли дурение к последовательным http запросам в одном tcp соединении (http keeaplive). +Относится только к nfqws. Выключение данной функции способно сэкономить загрузку процессора. +tpws всегда работает с http keepalive + +MODE_HTTP_KEEPALIVE=0 + +Применять ли дурение к HTTPS : + +MODE_HTTPS=1 + +Применять ли дурение к QUIC : + +MODE_QUIC=0 + +Режим фильтрации хостов : +none - применять дурение ко всем хостам +ipset - ограничить дурение ipset-ом zapret/zapret6 +hostlist - ограничить дурение списком хостов из файла +autohostlist - режим hostlist + распознавание блокировок и ведения автоматического листа + +MODE_FILTER=none + +Опции tpws : + +TPWS_OPT="--hostspell=HOST --split-http-req=method --split-pos=3" + +Дополнительный низкоприоритетный профиль десинхронизации для режимов с MODE_FILTER=hostlist. +После реализации поддержки множественных профилей режимы нулевой фазы десинхронизации больше не применяются с хостлистом ! +Для их применения требуется дополнительный профиль без хостлист фильтра. + +#TPWS_OPT_SUFFIX="--mss 88" + +Опции nfqws для атаки десинхронизации DPI : + +DESYNC_MARK=0x40000000 +DESYNC_MARK_POSTNAT=0x20000000 +NFQWS_OPT_DESYNC="--dpi-desync=fake --dpi-desync-ttl=0 --dpi-desync-fooling=badsum" + +Задание раздельных опций nfqws для http и https и для версий ip протоколов 4,6 : + +NFQWS_OPT_DESYNC_HTTP="--dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum" +NFQWS_OPT_DESYNC_HTTPS="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=0 --dpi-desync-fooling=badsum" +NFQWS_OPT_DESYNC_HTTP6="--dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none" +NFQWS_OPT_DESYNC_HTTPS6="--wssize=1:6 --dpi-desync=split --dpi-desync-ttl=5 --dpi-desync-fooling=none" + +Если какая-то из переменных NFQWS_OPT_DESYNC_HTTP/NFQWS_OPT_DESYNC_HTTPS не определена, +берется значение NFQWS_OPT_DESYNC. +Если какая-то из переменных NFQWS_OPT_DESYNC_HTTP6/NFQWS_OPT_DESYNC_HTTPS6 не определена, +берется значение NFQWS_OPT_DESYNC_HTTP/NFQWS_OPT_DESYNC_HTTPS. + +Дополнительный низкоприоритетный профиль десинхронизации для режимов с MODE_FILTER=hostlist. +После реализации поддержки множественных профилей режимы нулевой фазы десинхронизации больше не применяются с хостлистом ! +Для их применения требуется дополнительный профиль без хостлист фильтра. +#NFQWS_OPT_DESYNC_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTP_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTPS_SUFFIX="--wssize 1:6" +#NFQWS_OPT_DESYNC_HTTP6_SUFFIX="--dpi-desync=syndata" +#NFQWS_OPT_DESYNC_HTTPS6_SUFFIX="--wssize 1:6" + +Значения по умолчанию заполняются аналогично NFQWS_OPT_*. + +Опции дурения для QUIC : +NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" +NFQWS_OPT_DESYNC_QUIC6="--dpi-desync=hopbyhop" +Если NFQWS_OPT_DESYNC_QUIC6 не задано, то берется NFQWS_OPT_DESYNC_QUIC. + +Настройка системы управления выборочным traffic offload (только если поддерживается) +donttouch : выборочное управление отключено, используется системная настройка, простой инсталлятор выключает системную настройку, если она не совместима с выбранным режимом +none : выборочное управление отключено, простой инсталлятор выключает системную настройку +software : выборочное управление включено в режиме software, простой инсталлятор выключает системную настройку +hardware : выборочное управление включено в режиме hardware, простой инсталлятор выключает системную настройку + +FLOWOFFLOAD=donttouch + +Параметр GETLIST указывает инсталлятору install_easy.sh какой скрипт дергать +для обновления списка заблокированных ip или хостов. +Он же вызывается через get_config.sh из запланированных заданий (crontab или systemd timer). +Поместите сюда название скрипта, который будете использовать для обновления листов. +Если не нужно, то параметр следует закомментировать. + +Можно индивидуально отключить ipv4 или ipv6. Если параметр закомментирован или не равен "1", +использование протокола разрешено. +#DISABLE_IPV4=1 +DISABLE_IPV6=1 + +Количество потоков для многопоточного DNS ресолвера mdig (1..100). +Чем их больше, тем быстрее, но не обидится ли на долбежку ваш DNS сервер ? +MDIG_THREADS=30 + +Место для хранения временных файлов. При скачивании огромных реестров в /tmp места может не хватить. +Если файловая система на нормальном носителе (не встроенная память роутера), то можно +указать место на флэшке или диске. +TMPDIR=/opt/zapret/tmp + +Опции для создания ipset-ов и nfset-ов + +SET_MAXELEM=262144 +IPSET_OPT="hashsize 262144 maxelem 2097152" + +Хук, позволяющий внести ip адреса динамически. $1 = имя таблицы +Адреса выводятся в stdout. В случае nfset автоматически решается проблема возможного пересечения интервалов. +IPSET_HOOK="/etc/zapret.ipset.hook" + +ПРО РУГАНЬ в dmesg по поводу нехватки памяти. +Может так случиться, что памяти в системе достаточно, но при попытке заполнить огромный ipset +ядро начинает громко ругаться, ipset заполняется не полностью. +Вероятная причина в том, что превышается hashsize, заданный при создании ipset (create_ipset.sh). +Происходит переаллокация списка, не находится непрерывных фрагментов памяти нужной длины. +Это лечится увеличением hashsize. Но чем больше hashsize, тем больше занимает ipset в памяти. +Задавать слишком большой hashsize для недостаточно больших списков нецелесообразно. + +Опции для вызова ip2net. Отдельно для листов ipv4 и ipv6. +IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4" +IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5" + +Настройка режима autohostlist. +AUTOHOSTLIST_RETRANS_THRESHOLD=3 +AUTOHOSTLIST_FAIL_THRESHOLD=2 +AUTOHOSTLIST_FAIL_TIME=60 +AUTOHOSTLIST_DEBUG=0 + +Включить или выключить сжатие больших листов в скриптах ipset/*.sh. По умолчанию включено. +GZIP_LISTS=1 + +Команда для перезагрузки ip таблиц фаервола. +Если не указано или пустое, выбирается автоматически ipset или ipfw при их наличии. +На BSD системах с PF нет автоматической загрузки. Там нужно указать команду явно : pfctl -f /etc/pf.conf +На более новых pfctl (есть в новых FreeBSD, нет в OpenBSD 6.8) можно дать команду загрузки только таблиц : pfctl -Tl -f /etc/pf.conf +"-" означает отключение загрузки листов даже при наличии поддерживаемого backend. +#LISTS_RELOAD="pfctl -f /etc/pf.conf" +#LISTS_RELOAD=- + +В openwrt существует сеть по умолчанию 'lan'. Только трафик с этой сети будет перенаправлен на tpws. +Но возможно задать другие сети или список сетей : +OPENWRT_LAN="lan lan2 lan3" + +В openwrt в качестве wan берутся интерфейсы, имеющие default route. Отдельно для ipv4 и ipv6. +Это можно переопределить : +OPENWRT_WAN4="wan4 vpn" +OPENWRT_WAN6="wan6 vpn6" + +Параметр INIT_APPLY_FW=1 разрешает init скрипту самостоятельно применять правила iptables. +При иных значениях или если параметр закомментирован, правила применены не будут. +Это полезно, если у вас есть система управления фаерволом, в настройки которой и следует прикрутить правила. +На openwrt неприменимо при использовании firewall3+iptables. + +Следующие настройки не актуальны для openwrt : + +Если ваша система работает как роутер, то нужно вписать названия внутренних и внешних интерфейсов : +IFACE_LAN=eth0 +IFACE_WAN=eth1 +IFACE_WAN6="henet ipsec0" +Несколько интерфейсов могут быть вписаны через пробел. +Если IFACE_WAN6 не задан, то берется значение IFACE_WAN. + +ВАЖНО : настройка маршрутизации, маскарада и т.д. не входит в задачу zapret. +Включаются только режимы, обеспечивающие перехват транзитного трафика. +Возможно определить несколько интерфейсов следующим образом : IFACE_LAN="eth0 eth1 eth2" + +Прикручивание к системе управления фаерволом или своей системе запуска +---------------------------------------------------------------------- + +Если вы используете какую-то систему управления фаерволом, то она может вступать в конфликт +с имеющимся скриптом запуска. При повторном применении правил она могла бы поломать настройки iptables от zapret. +В этом случае правила для iptables должны быть прикручены к вашему фаерволу отдельно от запуска tpws или nfqws. + +Следующие вызовы позволяют применить или убрать правила iptables отдельно : + + /opt/zapret/init.d/sysv/zapret start_fw + /opt/zapret/init.d/sysv/zapret stop_fw + /opt/zapret/init.d/sysv/zapret restart_fw + +А так можно запустить или остановить демоны отдельно от фаервола : + + /opt/zapret/init.d/sysv/zapret start_daemons + /opt/zapret/init.d/sysv/zapret stop_daemons + /opt/zapret/init.d/sysv/zapret restart_daemons + +nftables сводят практически на нет конфликты между разными системами управления, поскольку позволяют +использовать независимые таблицы и хуки. Используется отдельная nf-таблица "zapret". +Если ваша система ее не будет трогать, скорее всего все будет нормально. + +Для nftables предусмотрено несколько дополнительных вызовов : + +Посмотреть set-ы интерфейсов, относящихся к lan, wan и wan6. По ним идет завертывание трафика. +А так же таблицу flow table с именами интерфейсов ingress hook. + /opt/zapret/init.d/sysv/zapret list_ifsets + +Обновить set-ы интерфейсов, относящихся к lan, wan и wan6. +Для традиционных linux список интерфейсов берется из переменных конфига IFACE_LAN, IFACE_WAN. +Для openwrt определяется автоматически. Множество lanif может быть расширено параметром OPENWRT_LAN. +Все интерфейсы lan и wan так же добавляются в ingress hook от flow table. + /opt/zapret/init.d/sysv/zapret reload_ifsets + +Просмотр таблицы без содержимого set-ов. Вызывает nft -t list table inet zapret + /opt/zapret/init.d/sysv/zapret list_table + +Так же возможно прицепиться своим скриптом к любой стадии применения и снятия фаервола со стороны zapret скриптов : + +INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" +INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" +INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" +INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" + +Эти настройки доступны в config. +Может быть полезно, если вам нужно использовать nftables set-ы, например ipban/ipban6. +nfset-ы принадлежат только одной таблице, следовательно вам придется писать правила для таблицы zapret, +а значит нужно синхронизироваться с применением/снятием правил со стороны zapret скриптов. + + +Вариант custom +-------------- + +custom код вынесен в отдельные shell includes. +Поддерживается старый вариант в +/opt/zapret/init.d/sysv/custom +/opt/zapret/init.d/openwrt/custom +/opt/zapret/init.d/macos/custom +Он считается устаревшим. Актуальный вариант - помещать отдельные скрипты там же, но в директорию "custom.d". +Она будет просканирована стандартным образом, т.е. в алфавитном порядке, и каждый скрипт будет применен. +Рядом имеется "custom.d.examples". Это готовые скрипты, который можно копировать в "custom.d". +Особо стоит отметить "10-inherit-*". Они наследуют стандартные режимы nfqws/tpws/tpws-socks. +Полезно, чтобы не писать код заново. Достаточно лишь скопировать соответствующий файл. + +Для linux пишется код в функции +zapret_custom_daemons +zapret_custom_firewall +zapret_custom_firewall_nft + +Для macos +zapret_custom_daemons +zapret_custom_firewall_v4 +zapret_custom_firewall_v6 + +zapret_custom_daemons поднимает демоны nfqws/tpws в нужном вам количестве и с нужными вам параметрами. +Особо обратите внимание на номер демона в функциях "run_daemon" и "do_daemon". +Они должны быть уникальными во всех скриптах. При накладке будет ошибка. +Так же следует избегать пересечения номеров портов tpws и очередей nfqws. +При пересечении какой-то из демонов не запустится. +Чтобы как-то нивелировать эту проблему, в examples используется переменная DNUM. +На ее базе считается диапазон номеров очередей (5 шт), которые использует этот скрипт. +При таком подходе достаточно, чтобы DNUM был везде уникален. +Поскольку номера очереди и портов имеют нумерацию до 65536, можно использовать DNUM до 13106. +Однако, следует оставить номера очереди 200-299 для стандартных режимов и не использовать их. + +custom скрипты могут использовать переменные из config. Можно помещать в config свои переменные +и использовать их в скриптах. +Можно использовать функции-хелперы. Они являются частью общего пространства функций shell. +Полезные функции можно взять из примеров скриптов. Так же смотрите "common/*.sh". +Используя хелпер функции, вы избавитесь от необходимости учитывать все возможные случаи +типа наличия/отсутствия ipv6, является ли система роутером, имена интерфейсов, ... +Хелперы это учитывают, вам нужно сосредоточиться лишь на фильтрах {ip,nf}tables и +параметрах демонов. + +Код для openwrt и sysv немного отличается. В sysv нужно обрабатывать и запуск, и остановку демонов. +Запуск это или остановка передается в параметре $1 (0 или 1). +В openwrt за остановку отвечает procd. + +Для фаервола в linux кастом пишется отдельно для iptables и nftables. Все очень похоже, но отличается +написание фильтров и названия процедур хелперов. Если вам не нужны iptables или nftables - +можете не писать соответствующую функцию. + +В macos firewall-функции ничего сами никуда не заносят. Их задача - лишь выдать текст в stdout, +содержащий правила для pf-якоря. Остальное сделает обертка. + + +Простая установка +----------------- + +install_easy.sh автоматизирует ручные варианты процедур установки (см manual_setup.txt). +Он поддерживает OpenWRT, linux системы на базе systemd или openrc и MacOS. + +Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". + +Если система запуска поддерживается, но используется не поддерживаемый инсталлятором менеджер пакетов +или названия пакетов не соответствуют прописанным в инсталлятор, пакеты нужно установить вручную. +Всегда требуется curl. ipset - только для режима iptables, для nftables - не нужен. + +Для совсем обрезанных дистрибутивов (alpine) требуется отдельно установить iptables и ip6tables, либо nftables. + +В комплекте идут статические бинарники для большинства архитектур. Какой-то из них подойдет +с вероятностью 99%. Но если у вас экзотическая система, инсталлятор попробует собрать бинарники сам +через make. Для этого нужны gcc, make и необходимые -dev пакеты. Можно форсировать режим +компиляции следующим вызовом : + + install_easy.sh make + +Под openwrt все уже сразу готово для использования системы в качестве роутера. +Имена интерфейсов WAN и LAN известны из настроек системы. +Под другими системами роутер вы настраиваете самостоятельно. инсталлятор в это не вмешивается. +инсталлятор в зависимости от выбранного режима может спросить LAN и WAN интерфейсы. +Нужно понимать, что заворот проходящего трафика на tpws в прозрачном режиме происходит до выполнения маршрутизации, +следовательно возможна фильтрация по LAN и невозможна по WAN. +Решение о завороте на tpws локального исходящего трафика принимается после выполнения маршрутизации, +следовательно ситуация обратная : LAN не имеет смысла, фильтрация по WAN возможна. +Заворот на nfqws происходит всегда после маршрутизации, поэтому к нему применима только фильтрация по WAN. +Возможность прохождения трафика в том или ином направлении настраивается вами в процессе конфигурации роутера. + +Деинсталляция выполняется через uninstall_easy.sh + + +Простая установка на openwrt +---------------------------- + +Работает только если у вас на роутере достаточно места. + +Копируем zapret на роутер в /tmp. + +Запускаем установщик : + sh /tmp/zapret/install_easy.sh +Он скопирует в /opt/zapret только необходимый минимум файлов. + +После успешной установки можно удалить zapret из tmp для освобождения RAM : + rm -r /tmp/zapret + +Для более гибкой настройки перед запуском инсталлятора следует выполнить раздел "Выбор параметров". + +Система простой инсталяции заточена на любое умышленное или неумышленное изменение прав доступа на файлы. +Устойчива к репаку под windows. После копирования в /opt права будут принудительно восстановлены. + +Android +------- + +Без рута забудьте про nfqws и tpws в режиме transparent proxy. tpws будет работать только в режиме --socks. + +Ядра Android имеют поддержку NFQUEUE. nfqws работает. + +В стоковых ядрах нет поддержки ipset. В общем случае сложность задачи по поднятию ipset варьируется от +"не просто" до "почти невозможно". Если только вы не найдете готовое собранное ядро под ваш девайс. + +tpws будет работать в любом случае, он не требует чего-либо особенного. +В android нет /etc/passwd, потому опция --user не будет работать. Вместо нее можно +пользоваться числовыми user id и опцией --uid. +Рекомендую использовать gid 3003 (AID_INET). Иначе можете получить permission denied на создание сокета. +Например : --uid 1:3003 +В iptables укажите : "! --uid-owner 1" вместо "! --uid-owner tpws". +Напишите шелл скрипт с iptables и tpws, запускайте его средствами вашего рут менеджера. +Скрипты автозапуска лежат тут : +magisk : /data/adb/service.d +supersu : /system/su.d + +nfqws может иметь такой глюк. При запуске с uid по умолчанию (0x7FFFFFFF) при условии работы на сотовом интерфейсе +и отключенном кабеле внешнего питания система может частично виснуть. Перестает работать тач и кнопки, +но анимация на экране может продолжаться. Если экран был погашен, то включить его кнопкой power невозможно. +Это, видимо, связано с переводом в suspend процессов с определенным UID. UID соответствует приложению или +системному сервису. По UID android определяет политику power saving. +Так же возможно, что глюк связан с кривым драйвером сотового интерфейса от китайцев, поскольку при использовании +wifi такого не наблюдается. suspend обработчика nfqueue на обычном linux не вызывает подобных фатальных последствий. +Изменение UID на низкий (--uid 1 подойдет) позволяет решить эту проблему. +Глюк был замечен на android 8.1 на девайсе, основанном на платформе mediatek. + +Ответ на вопрос куда поместить tpws на android без рута, чтобы потом его запускать из приложений. +Файл заливаем через adb shell в /data/local/tmp/, лучше всего в субфолдер. +mkdir /data/local/tmp/zapret +adb push tpws /data/local/tmp/zapret +chmod 755 /data/local/tmp/zapret /data/local/tmp/zapret/tpws +chcon u:object_r:system_file:s0 /data/local/tmp/zapret/tpws + +Как найти стратегию обхода сотового оператора : проще всего раздать инет на комп с linux. +Можно записать live image linux на флэшку и загрузиться с нее или запустить виртуалку с linux +и пробросить в нее usb устройство от режима модема с телефона. +На компе с linux прогнать стандартную процедуру blockcheck. При переносе правил на телефон уменьшить TTL на 1, +если правила с TTL присутствуют в стратегии. +Можно развернуть rootfs какого-нибудь дистрибутива linux прямо на телефоне, имея рута. +Это лучше всего делать с компа через adb shell. +Если компа нет, то это единственный вариант, хотя и неудобный. +Подойдет что-то легковесное, например, alpine или даже openwrt. +Если это не эмулятор android, то универсальная архитектура - arm (любой вариант). +Если вы точно знаете, что ОС у вас 64-разрядная, то лучше вместо arm - aarch64. + +mount --bind /dev /data/linux/dev +mount --bind /proc /data/linux/proc +mount --bind /sys /data/linux/sys +chroot /data/linux + +Первым делом вам нужно будет один раз настроить DNS. Сам он не заведется. + +echo nameserver 1.1.1.1 >/etc/resolv.conf + +Далее нужно средствами пакетного менеджера установить iptables-legacy. Обязательно НЕ iptables-nft, +который как правило присутствует по умолчанию. В ядре android нет nftables. +ls -la $(which iptables) +Линк должен указывать на legacy вариант. +Если нет, значит устанавливайте нужные пакеты вашего дистрибутива, и убеждайтесь в правильности ссылок. +iptables -S +Так можно проверить, что ваш iptables увидел то, что туда насовал android. iptables-nft выдаст ошибку. +Далее качаем zapret в /opt/zapret. Обычные действия с install_prereq.sh, install_bin.sh, blockcheck.sh. + +Учтите, что стратегии обхода сотового оператора и домашнего wifi вероятно будут разные. +Выделить сотового оператора легко через параметр iptables -o <имя интерфейса>. Имя может быть, например, ccmni0. +Его легко увидеть через ifconfig. +Wifi сеть - обычно wlan0. + +Переключать blockcheck между оператором и wifi можно вместе со всем инетом - включив или выключив wifi. +Если найдете стратегию для wifi и впишите ее в автостарт, то при подключении к другому wifi +она может не сработать или вовсе что-то поломать, потому подумайте стоит ли. +Может быть лучше сделать скрипты типа "запустить обход домашнего wifi", "снять обход домашнего wifi", +и пользоваться ими по необходимости из терминала. +Но домашний wifi лучше все-же обходить на роутере. + + +Мобильные модемы и роутеры huawei +--------------------------------- + +Устройства типа E3372, E8372, E5770 разделяют общую идеологию построения системы. +Имеются 2 вычислительных ядра. Одно ядро выполняет vxworks, другое - linux. +На 4pda имеются модифицированные прошивки с telnet и adb. Их и нужно использовать. + +Дальнейшие утверждения проверены на E8372. На других может быть аналогично или похоже. +Присутствуют дополнительные аппаратные блоки для offload-а сетевых функций. +Не весь трафик идет через linux. Исходящий трафик с самого модема проходит +цепочку OUTPUT нормально, на FORWARD =>wan часть пакетов выпадает из tcpdump. + +tpws работает обычным образом. + +nfqueue поломан. можно собрать фиксящий модуль https://github.com/im-0/unfuck-nfqueue-on-e3372h, +используя исходники с huawei open source. Исходники содержат тулчейн и полусобирающееся, +неактуальное ядро. Конфиг можно взять с рабочего модема из /proc/config.gz. +С помощью этих исходников умельцы могут собрать модуль unfuck_nfqueue.ko. +После его применения NFQUEUE и nfqws для arm работают нормально. + +Чтобы избежать проблемы с offload-ом при использовании nfqws, следует комбинировать tpws в режиме tcp proxy и nfqws. +Правила NFQUEUE пишутся для цепочки OUTPUT. +connbytes придется опускать, поскольку модуля в ядре нет. Но это не смертельно. + +Скрипт автозапуска - /system/etc/autorun.sh. Создайте свой скрипт настройки zapret, +запускайте из конца autorun.sh через "&". Скрипт должен в начале делать sleep 5, чтобы дождаться +поднятия сети и iptables от huawei. + +ПРЕДУПРЕЖДЕНИЕ. +На этом модеме происходят хаотические сбросы соединений tcp по непонятным причинам. +Выглядит это так, если запускать curl с самого модема : + curl www.ru + curl: (7) Failed to connect to www.ru port 80: Host is unreachable +Возникает ошибка сокета EHOSTUNREACH (errno -113). То же самое видно в tpws. +В броузере не подгружаются части веб страниц, картинки, стили. +В tcpdump на внешнем интерфейсе eth_x виден только единственный и безответный SYN пакет, без сообщений ICMP. +ОС каким-то образом узнает о невозможности установить TCP соединение и выдает ошибку. +Если выполнять подключение с клиента, то SYN пропадают, соединение не устанавливается. +ОС клиента проводит ретрансмиссию, и с какого-то раза подключение удается. +Поэтому без tcp проксирования в этой ситуации сайты тупят, но загружаются, а с проксированием +подключение выполняется, но вскоре сбрасывается без каких-либо данных, и броузеры не пытаются установить +его заново. Поэтому качество броузинга с tpws может быть хуже, но дело не в tpws. +Частота сбросов заметно возрастает, если запущен торент клиент, имеется много tcp соединений. +Однако, причина не в переполнении таблицы conntrack. Увеличение лимитов и очистка conntrack не помогают. +Предположительно эта особенность связана с обработкой пакетов сброса соединения в hardware offload. +Точного ответа на вопрос у меня нет. Если вы знаете - поделитесь, пожалуйста. +Чтобы не ухудшать качество броузинга, можно фильтровать заворот на tpws по ip фильтру. +Поддержка ipset отсутствует. Значит, все, что можно сделать - создать индивидуальные правила +на небольшое количество хостов. + +Некоторые наброски скриптов присутствуют в files/huawei. Не готовое решение ! Смотрите, изучайте, приспосабливайте. +Здесь можно скачать готовые полезные статические бинарники для arm, включая curl : https://github.com/bol-van/bins + + +FreeBSD, OpenBSD, MacOS +----------------------- + +Описано в docs/bsd.txt + + +Windows +------- + +Описано в docs/windows.txt + + +Другие прошивки +--------------- + +Для статических бинариков не имеет значения на чем они запущены : PC, android, приставка, роутер, любой другой девайс. +Подойдет любая прошивка, дистрибутив linux. Статические бинарники запустятся на всем. +Им нужно только ядро с необходимыми опциями сборки или модулями. +Но кроме бинариков в проекте используются еще и скрипты, в которых задействуются некоторые +стандартные программы. + +Основные причины почему нельзя просто так взять и установить эту систему на что угодно : + * отсутствие доступа к девайсу через shell + * отсутствие рута + * отсутствие раздела r/w для записи и энергонезависимого хранения файлов + * отсутствие возможности поставить что-то в автозапуск + * отсутствие cron + * неотключаемый flow offload или другая проприетарщина в netfilter + * недостаток модулей ядра или опций его сборки + * недостаток модулей iptables (/usr/lib/iptables/lib*.so) + * недостаток стандартных программ (типа ipset, curl) или их кастрированность (облегченная замена) + * кастрированный или нестандартный шелл sh + +Если в вашей прошивке есть все необходимое, то вы можете адаптировать zapret под ваш девайс в той или иной степени. +Может быть у вас не получится поднять все части системы, однако вы можете хотя бы попытаться +поднять tpws и завернуть на него через -j REDIRECT весь трафик на порт 80. +Если вам есть куда записать tpws, есть возможность выполнять команды при старте, то как минимум +это вы сделать сможете. Скорее всего поддержка REDIRECT в ядре есть. Она точно есть на любом роутере, +на других устройствах под вопросом. NFQUEUE, ipset на большинстве прошивок отсутствуют из-за ненужности. + +Пересобрать ядро или модули для него будет скорее всего достаточно трудно. +Для этого вам необходимо будет по крайней мере получить исходники вашей прошивки. +User mode компоненты могут быть привнесены относительно безболезненно, если есть место куда их записать. +Специально для девайсов, имеющих область r/w, существует проект entware. +Некоторые прошивки даже имеют возможность его облегченной установки через веб интерфейс. +entware содержит репозиторий user-mode компонент, которые устанавливаются в /opt. +С их помощью можно компенсировать недостаток ПО основной прошивки, за исключением ядра. + +Можно попытаться использовать sysv init script таким образом, как это описано в разделе +"Прикручивание к системе управления фаерволом или своей системе запуска". +В случае ругани на отсутствие каких-то базовых программ, их следует восполнить посредством entware. +Перед запуском скрипта путь к дополнительным программам должен быть помещен в PATH. + +Подробное описание настроек для других прошивок выходит за рамки данного проекта. + +Openwrt является одной из немногих относительно полноценных linux систем для embedded devices. +Она характеризуется следующими вещами, которые и послужили основой выбора именно этой прошивки : + * полный root доступ к девайсу через shell. на заводских прошивках чаще всего отсутствует, на многих альтернативных есть + * корень r/w. это практически уникальная особенность openwrt. заводские и большинство альтернативных прошивок + построены на базе squashfs root (r/o), а конфигурация хранится в специально отформатированной области + встроенной памяти, называемой nvram. не имеющие r/w корня системы сильно кастрированы. они не имеют + возможности доустановки ПО из репозитория без специальных вывертов и заточены в основном + на чуть более продвинутого, чем обычно, пользователя и управление имеющимся функционалом через веб интерфейс, + но функционал фиксированно ограничен. альтернативные прошивки как правило могут монтировать r/w раздел + в какую-то область файловой системы, заводские обычно могут монтировать лишь флэшки, подключенные к USB, + и не факт, что есть поддержка unix файловых системы. может быть поддержка только fat и ntfs. + * возможность выноса корневой файловой системы на внешний носитель (extroot) или создания на нем оверлея (overlay) + * наличие менеджера пакетов opkg и репозитория софта + * flow offload предсказуемо, стандартно и выборочно управляем, а так же отключаем + * в репозитории есть все модули ядра, их можно доустановить через opkg. ядро пересобирать не нужно. + * в репозитории есть все модули iptables, их можно доустановить через opkg + * в репозитории есть огромное количество стандартных программ и дополнительного софта + * наличие SDK, позволяющего собрать недостающее + + +Обход блокировки через сторонний хост +------------------------------------- + +Если не работает автономный обход, приходится перенаправлять трафик через сторонний хост. +Предлагается использовать прозрачный редирект через socks5 посредством iptables+redsocks, либо iptables+iproute+vpn. +Настройка варианта с redsocks на openwrt описана в redsocks.txt. +Настройка варианта с iproute+wireguard - в wireguard_iproute_openwrt.txt. + + +Почему стоит вложиться в покупку VPS +------------------------------------ + +VPS - это виртуальный сервер. Существует огромное множество датацентров, предлагающих данную услугу. +На VPS могут выполняться какие угодно задачи. От простого веб сайта до навороченной системы собственной разработки. +Можно использовать VPS и для поднятия собственного vpn или прокси. +Сама широта возможных способов применения, распространенность услуги сводят к минимуму возможности +регуляторов по бану сервисов такого типа. Да, если введут белые списки, то решение загнется, но это будет уже другая +реальность, в которой придется изобретать иные решения. +Пока этого не сделали, никто не будет банить хостинги просто потому, что они предоставляют хостинг услуги. +Вы как индивидуум скорее всего никому не нужны. Подумайте чем вы отличаетесь от известного VPN провайдера. +VPN провайдер предоставляет _простую_ и _доступную_ услугу по обходу блокировок для масс. +Этот факт делает его первоочередной целью блокировки. РКН направит уведомление, после отказа сотрудничать +заблокирует VPN. Предоплаченная сумма пропадет. +У регуляторов нет и никогда не будет ресурсов для тотальной проверки каждого сервера в сети. +Возможен китайский расклад, при котором DPI выявляет vpn протоколы и динамически банит IP серверов, +предоставляющих нелицензированный VPN. Но имея знания, голову, вы всегда можете обфусцировать +vpn трафик или применить другие типы VPN, более устойчивые к анализу на DPI или просто менее широкоизвестные, +а следовательно с меньшей вероятностью обнаруживаемые регулятором. +У вас есть свобода делать на вашем VPS все что вы захотите, адаптируясь к новым условиям. +Да, это потребует знаний. Вам выбирать учиться и держать ситуацию под контролем, когда вам ничего запретить +не могут, или покориться системе. + +VPS можно прибрести в множестве мест. Существуют специализированные на поиске предложений VPS порталы. +Например, вот этот : https://vps.today/ +Для персонального VPN сервера обычно достаточно самой минимальной конфигурации, но с безлимитным трафиком или +с большим лимитом по трафику (терабайты). Важен и тип VPS. Openvz подойдет для openvpn, но +вы не поднимете на нем wireguard, ipsec, то есть все, что требует kernel mode. +Для kernel mode требуется тип виртуализации, предполагающий запуск полноценного экземпляра ОС linux +вместе с ядром. Подойдут kvm, xen, hyper-v, vmware. + +По цене можно найти предложения, которые будут дешевле готовой VPN услуги, но при этом вы сам хозяин в своей лавке +и не рискуете попасть под бан регулятора, разве что "заодно" под ковровую бомбардировку с баном миллионов IP. +Кроме того, если вам совсем все кажется сложным, прочитанное вызывает ступор, и вы точно знаете, что ничего +из описанного сделать не сможете, то вы сможете хотя бы использовать динамическое перенаправление портов ssh +для получения шифрованного socks proxy и прописать его в броузер. Знания linux не нужны совсем. +Это вариант наименее напряжный для чайников, хотя и не самый удобный в использовании. diff --git a/docs/redsocks.txt b/docs/redsocks.txt new file mode 100644 index 00000000..c3bc9900 --- /dev/null +++ b/docs/redsocks.txt @@ -0,0 +1,196 @@ +Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. +Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего +понимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно. +Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. + + +Прозрачный выборочный заворот tcp соединений на роутере через socks + +Tor поддерживает "из коробки" режим transparent proxy. Это можно использовать в теории, но практически - только на роутерах с 128 мб памяти и выше. И тор еще и тормозной. +Другой вариант напрашивается, если у вас есть доступ к какой-нибудь unix системе с SSH, где сайты не блокируются. Например, у вас есть VPS вне России. +Понятийно требуются следующие шаги : +1) Выделять IP, на которые надо проксировать трафик. У нас уже имеется ipset "zapret", технология создания которого отработана. +2) Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks. +3) Установить transparent соксификатор. Redsocks прекрасно подошел на эту роль. +4) Завернуть через iptables или nftables трафик с порта назначения 443 и на ip адреса из ipset/nfset 'zapret' на соксификатор +Тоже самое сделать с ipset/nfset 'ipban' для всех tcp портов. +Буду рассматривать систему на базе openwrt, где уже установлена система обхода dpi "zapret". +Если вам не нужны функции обхода DPI, можно выбрать режим MODE=filter. + + +* Сделать так, чтобы все время при загрузке системы на некотором порту возникал socks + +Т.к. дефолтный dropbear клиент не поддерживает создание socks, то для начала придется заменить dropbear ssh client на openssh : пакеты openssh-client и openssh-client-utils. +Устанавливать их нужно с опцией opkg --force-overwrite, поскольку они перепишут ssh клиент от dropbear. +После установки пакетов расслабим неоправданно жестокие права : chmod 755 /etc/ssh. +Следует создать пользователя, под которым будем крутить ssh client. Допустим, это будет 'proxy'. +Сначала установить пакет shadow-useradd. +------------------ +useradd -d /home/proxy proxy +mkdir -p /home/proxy +chown proxy:proxy /home/proxy +------------------ +Openssh ловит разные глюки, если у него нет доступа к /dev/tty. +Добавим в /etc/rc.local строчку : "chmod 666 /dev/tty" +Сгенерируем для него ключ RSA для доступа к ssh серверу. +------------------ +su proxy +cd +mkdir -m 700 .ssh +cd .ssh +ssh-keygen +ls +exit +------------------ +Должны получиться файлы id_rsa и id_rsa.pub. +Строчку из id_rsa.pub следует добавить на ssh сервер в файл $HOME/.ssh/authorized_keys. +Более подробно о доступе к ssh через авторизацию по ключам : https://beget.com/ru/articles/ssh_by_key +Предположим, ваш ssh сервер - vps.mydomain.com, пользователь называется 'proxy'. +Проверить подключение можно так : ssh -N -D 1098 -l proxy vps.mydomain.com. +Сделайте это под пользователем "proxy", поскольку при первом подключении ssh спросит о правильности hostkey. +Соединение может отвалиться в любой момент, поэтому нужно зациклить запуск ssh. +Для этого лучший вариант - использовать procd - упрощенная замена systemd на openwrt версий BB и выше. +--- /etc/init.d/socks_vps --- +#!/bin/sh /etc/rc.common +START=50 +STOP=50 +USE_PROCD=1 +USERNAME=proxy +COMMAND="ssh -N -D 1098 -l proxy vps.mydomain.com" +start_service() { + procd_open_instance + procd_set_param user $USERNAME + procd_set_param respawn 10 10 0 + procd_set_param command $COMMAND + procd_close_instance +} +----------------------------- +Этому файлу нужно дать права : chmod +x /etc/init.d/socks_vps +Запуск : /etc/init.d/socks_vps start +Останов : /etc/init.d/socks_vps stop +Включить автозагрузку : /etc/init.d/socks_vps enable +Проверка : curl -4 --socks5 127.0.0.1:1098 https://rutracker.org + + +* Организовать прозрачную соксификацию + +Установить пакет redsocks. +Конфиг : +-- /etc/redsocks.conf : --- +base { + log_debug = off; + log_info = on; + log = "syslog:local7"; + daemon = on; + user = nobody; + group = nogroup; + redirector = iptables; +} +redsocks { + local_ip = 127.0.0.127; + local_port = 1099; + ip = 127.0.0.1; + port = 1098; + type = socks5; +} +--------------------------- + +После чего перезапускаем : /etc/init.d/redsocks restart +Смотрим появился ли листенер : netstat -tnlp | grep 1099 + +В zapret для перенаправления DNAT на интерфейс lo используется 127.0.0.127. +Ко всем остальным адресам из 127.0.0.0/8 DNAT может быть заблокирован. Читайте readme.txt про route_localnet. + +* Завертывание соединений через iptables + +!! Версии OpenWRT до 21.02 включительно используют iptables + fw3. Более новые перешили на nftables по умолчанию. +!! В новых OpenWRT можно снести firewall4 и nftables, заменив их на firewall3 + iptables +!! Инструкция относится только к openwrt, где используется iptables. + +Будем завертывать любые tcp соединения на ip из ipset "ipban" и https на ip из ipset "zapret", за исключением ip из ipset "nozapret". + +--- /etc/firewall.user ----- +SOXIFIER_PORT=1099 + +. /opt/zapret/init.d/openwrt/functions + +create_ipset no-update + +network_find_wan4_all wan_iface +for ext_iface in $wan_iface; do + network_get_device ext_device $ext_iface + ipt OUTPUT -t nat -o $ext_device -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT + ipt OUTPUT -t nat -o $ext_device -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret dst -j REDIRECT --to-port $SOXIFIER_PORT +done + +prepare_route_localnet + +ipt prerouting_lan_rule -t nat -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT +ipt prerouting_lan_rule -t nat -p tcp -m set --match-set ipban dst -m set ! --match-set nozapret -j DNAT --to $TPWS_LOCALHOST4:$SOXIFIER_PORT +---------------------------- + +Внести параметр "reload" в указанное место : +--- /etc/config/firewall --- +config include + option path '/etc/firewall.user' + option reload '1' +---------------------------- + +Перезапуск firewall : /etc/init.d/firewall restart + + +* Завертывание соединений через nftables + +!! Только для версий OpenWRT старше 21.02 + +nftables не могут использовать ipset. Вместо ipset существует аналог - nfset. +nfset является частью таблицы nftable и принадлежит только к ней. Адресация nfset из другой nftable невозможна. +Скрипты ipset/* в случае nftables используют nfset-ы в таблице zapret. +Чтобы использовать эти nfset-ы в своих правилах, необходимо синхронизироваться с их созданием и вносить свои цепочки в nftable "zapret". +Для этого существуют хуки - скрипты, вызываемые из zapret на определенных стадиях инициализации фаервола. + +Раскоментируейте в /opt/zapret/config строчку +INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" + +Создайте файл /etc/firewall.zapret.hook.post_up и присвойте ему chmod 755. + +--- /etc/firewall.zapret.hook.post_up --- +#!/bin/sh + +SOXIFIER_PORT=1099 + +. /opt/zapret/init.d/openwrt/functions + +cat << EOF | nft -f - 2>/dev/null + delete chain inet $ZAPRET_NFT_TABLE my_output + delete chain inet $ZAPRET_NFT_TABLE my_prerouting +EOF + +prepare_route_localnet + +cat << EOF | nft -f - + add chain inet $ZAPRET_NFT_TABLE my_output { type nat hook output priority -102; } + flush chain inet $ZAPRET_NFT_TABLE my_output + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT + + add chain inet $ZAPRET_NFT_TABLE my_prerouting { type nat hook prerouting priority -102; } + flush chain inet $ZAPRET_NFT_TABLE my_prerouting + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif meta l4proto tcp ip daddr @ipban ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret dnat to $TPWS_LOCALHOST4:$SOXIFIER_PORT +EOF +---------------------------- + +Перезапуск firewall : /etc/init.d/zapret restart_fw + + +* Проверка + +Все, теперь можно проверять : +/etc/init.d/redsocks stop +curl -4 https://rutracker.org +# должно обломаться с надписью "Connection refused". если не обламывается - значит ip адрес rutracker.org не в ipset, +# либо не сработали правила фаервола. например, из-за не установленных модулей ipt +/etc/init.d/redsocks start +curl -4 https://rutracker.org +# должно выдать страницу diff --git a/docs/windows.eng.md b/docs/windows.eng.md new file mode 100644 index 00000000..e624becd --- /dev/null +++ b/docs/windows.eng.md @@ -0,0 +1,161 @@ +### tpws + +Using `WSL` (Windows subsystem for Linux) it's possible to run `tpws` in socks mode under rather new builds of +windows 10 and windows server. +Its not required to install any linux distributions as suggested in most articles. +tpws is static binary. It doesn't need a distribution. + +Install `WSL` : `dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all` + +Copy `binaries/x86_64/tpws_wsl.tgz` to the target system. +Run : `wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz` + +Run tpws : `wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 ` + +Configure socks as `127.0.0.1:1080` in a browser or another program. + +Cleanup : `wsl --unregister tpws` + +Tested in windows 10 build 19041 (20.04). + +`--oob` , `--mss` and `--disorder` do not work. +RST detection in autohostlist scheme may not work. +WSL may glitch with splice. `--nosplice` may be required. + + +### winws + +`winws` is `nfqws` version for windows. It's based on `windivert`. Most functions are working. +Large ip filters (ipsets) are not possible. Forwarded traffic and connection sharing are not supported. +Administrator rights are required. + +Working with packet filter consists of two parts + +1. In-kernel packet selection and passing selected packets to a packet filter in user mode. +In *nix it's done by `iptables`, `nftables`, `pf`, `ipfw`. +2. User mode packet filter processes packets and does DPI bypass magic. + +Windows does not have part 1. No `iptables` exist. That's why 3rd party packet redirector is used. +It's called `windivert`. It works starting from `windows 7`. Kernel driver is signed but it may require to disable secure boot +or update windows 7. Read below for windows 7 windivert signing info. + +Task of `iptables` is done inside `winws` through `windivert` filters. `Windivert` has it's own [filter language](https://reqrypt.org/windivert-doc.html#filter_language). +`winws` can automate filter construction using simple ip version and port filter. Raw filters are also supported. + +``` + --wf-iface=[:] ; numeric network interface and subinterface indexes + --wf-l3=ipv4|ipv6 ; L3 protocol filter. multiple comma separated values allowed. + --wf-tcp=[~]port1[-port2] ; TCP port filter. ~ means negation. multiple comma separated values allowed. + --wf-udp=[~]port1[-port2] ; UDP port filter. ~ means negation. multiple comma separated values allowed. + --wf-raw=|@ ; raw windivert filter string or filename + --wf-save= ; save windivert filter string to a file and exit + --ssid-filter=ssid1[,ssid2,ssid3,...] ; enable winws only if any of specified wifi SSIDs connected + --nlm-filter=net1[,net2,net3,...] ; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted. + --nlm-list[=all] ; list Network List Manager (NLM) networks. connected only or all. +``` + +`--wf-l3`, `--wf-tcp`, `--wf-udp` can take multiple comma separated arguments. + +Interface indexes can be discovered using this command : `netsh int ip show int` + +If you can't find index this way use `winws --debug` to see index there. Subinterface index is almost always 0 and you can omit it. + +Multiple `winws` processes are allowed. However, it's discouraged to intersect their filters. + +`--ssid-filter` allows to enable `winws` only if specified wifi networks are connected. `winws` auto detects SSID appearance and disappearance. +SSID names must be written in the same case as the system sees them. This option does not analyze routing and does not detect where traffic actually goes. +If multiple connections are available, the only thing that triggers `winws` operation is wifi connection presence. That's why it's a good idea to add also `--wf-iface` filter to not break ethernet, for example. + +`--nlm-filter` is like `--ssid-filter` but works with names or GUIDs from Network List Manager. NLM names are those you see in Control Panel "Network and Sharing Center". +NLM networks are adapter independent. Usually MAC address of the default router is used to distinugish networks. NLM works with any type of adapters : ethernet, wifi, vpn and others. +That's why NLM is more universal than `ssid-filter`. + +`Cygwin` shell does not run binaries if their directory has it's own copy of `cygwin1.dll`. +That's why exists separate standalone version in `binaries/win64/zapret-tpws`. +`Cygwin` is required for `blockcheck.sh` support but `winws` itself can be run standalone without cygwin. + +How to get `windows 7` and `winws` compatible `cygwin` : +``` +curl -O https://www.cygwin.com/setup-x86_64.exe +setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 +``` +You must choose to install `curl`. To compile from sources install `gcc-core`,`make`,`zlib-devel`. + +`winws` requires `cygwin1.dll`, `windivert.dll`, `windivert64.sys`. You can take them from `binaries/win64/zapret-winws`. + +It's possible to build x86 32-bit version but this version is not shipped. You have to build it yourself. +32-bit `windivert` can be downloaded from it's developer github. Required version is 2.2.2. +There's no `arm64` signed `windivert` driver and no `cygwin`. +But it's possible to use unsigned driver version in test mode and user mode components with x64 emulation. +x64 emulation requires `windows 11` and not supported in `windows 10`. + +### windows 7 windivert signing + +Requirements for windows driver signing have changed in 2021. +Official free updates of windows 7 ended in 2020. +After 2020 for the years paid updates were available (ESU). +One of the updates from ESU enables signatures used in windivert 2.2.2-A. +There are several options : + +1. Take `windivert64.sys` and `windivert.dll` version `2.2.0-C` or `2.2.0-D` from [here](https://reqrypt.org/download). +Replace these 2 files in every location they are present. +In `zapret-win-bundle` they are in `zapret-winws` и `blockcheck/zapret/nfq` folders. +However this option still requires 10+ year old patch that enables SHA256 signatures. + +2. [Hack ESU](https://hackandpwn.com/windows-7-esu-patching) + +3. Use `UpdatePack7R2` from simplix : https://blog.simplix.info +If you are in Russia or Belarus temporary change region in Control Panel. + +### blockcheck + +`blockcheck.sh` is written in posix shell and uses some standard posix utilites. +Windows does not have them. To execute `blockcheck.sh` use `cygwin` command prompt run as administrator. +It's not possible to use `WSL`. It's not the same as `cygwin`. +First run once `install_bin.sh` then `blockcheck.sh`. + +Backslashes in windows paths shoud be doubled. Or use cygwin path notation. +``` +cd "C:\\Users\\vasya" +cd "C:/Users/vasya" +cd "/cygdrive/c/Users/vasya" +``` +`Cygwin` is required only for `blockcheck.sh`. Standalone `winws` can be run without it. + + +### auto start + +To start `winws` with windows use windows task scheduler. There are `task_*.cmd` batch files in `binaries/win64/zapret-winws`. +They create, remove, start and stop scheduled task `winws1`. They must be run as administrator. + +Edit `task_create.cmd` and write your `winws` parameters to `%WINWS1%` variable. If you need multiple `winws` instances +clone the code in all cmd files to support multiple tasks `winws1,winws2,winws3,...`. + +Tasks can also be controlled from GUI `taskschd.msc`. + +Also you can use windows services the same way with `service_*.cmd`. + + +### zapret-win-bundle + +To make your life easier there's ready to use [bundle](https://github.com/bol-van/zapret-win-bundle) with `cygwin`,`blockcheck` and `winws`. + +* `/zapret-winws` - standalone version of `winws` for everyday use. does not require any other folders. +* `/zapret-winws/_CMD_ADMIN.cmd` - open `cmd` as administrator in the current folder +* `/blockcheck/blockcheck.cmd` - run `blockcheck` with logging to `blockcheck/blockcheck.log` +* `/cygwin/cygwin.cmd` - run `cygwin` shell as current user +* `/cygwin/cygwin-admin.cmd` - run `cygwin` shell as administrator + +There're aliases in cygwin shell for `winws`,`blockcheck`,`ip2net`,`mdig`. No need to mess with paths. +It's possible to send signals to `winws` using standard unix utilites : `pidof,kill,killall,pgrep,pkill`. +`Cygwin` shares common process list per `cygwin1.dll` copy. If you run a `winws` from `zapret-winws` +you won't be able to `kill` it because this folder contain its own copy of `cygwin1.dll`. + +It's possible to use `cygwin` shell to make `winws` debug log. Use `tee` command like this : + +``` +winws --debug --wf-tcp=80,443 | tee winws.log +unix2dos winws.log +``` + +`winws.log` will be in `cygwin/home/`. `unix2dos` helps with `windows 7` notepad. It's not necessary in `Windows 10` and later. diff --git a/docs/windows.txt b/docs/windows.txt new file mode 100644 index 00000000..b71d7ff1 --- /dev/null +++ b/docs/windows.txt @@ -0,0 +1,222 @@ +tpws +---- + +Запуск tpws возможен только в Linux варианте под WSL. +Нативного варианта под Windows нет, поскольку он использует epoll, которого под windows не существует. + +tpws в режиме socks можно запускать под более-менее современными билдами windows 10 и windows server +с установленным WSL. Совсем не обязательно устанавливать дистрибутив убунту, как вам напишут почти в каждой +статье про WSL, которую вы найдете в сети. tpws - статический бинарик, ему дистрибутив не нужен. + +Установить WSL : dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all +Скопировать на целевую систему binaries/x86_64/tpws_wsl.tgz. +Выполнить : wsl --import tpws "%USERPROFILE%\tpws" tpws_wsl.tgz +Запустить : wsl -d tpws --exec /tpws --uid=1 --no-resolve --socks --bind-addr=127.0.0.1 --port=1080 <параметры_дурения> +Прописать socks 127.0.0.1:1080 в броузер или другую программу. + +Удаление : wsl --unregister tpws + +Проверено на windows 10 build 19041 (20.04). + +Не работают функции --oob и --mss из-за ограничений реализации WSL. +--disorder не работает из-за особенностей tcp/ip стека windows. +Может не срабатывать детект RST в autohostlist. +WSL может глючить со splice, приводя к зацикливанию процесса. Может потребоваться --nosplice. +Не поддерживается tcp user timeout. +Чтобы избавиться от исообщений об ошибке добавляйте "--local-tcp-user-timeout=0 --remote-tcp-user-timeout=0". +Эти сообщения только информативные, на работу они не влияют. + + +winws +----- + +Это вариант пакетного фильтра nfqws для Windows, построенный на базе windivert. +Все функции работоспособны, однако функционал ipset отсутствует. Фильтры по большому количеству IP адресов невозможны. +Работа с проходящим трафиком, например в случае "расшаривания" соединения, не проверялась и не гарантируется. +Для работы с windivert требуются права администратора. +Специфические для unix параметры, такие как --uid, --user и тд, исключены. Все остальные параметры аналогичны nfqws и dvtws. + +Работа с пакетным фильтром основана на двух действиях. +Первое - выделение перенаправляемого трафика в режиме ядра и передача его пакетному фильтру в user mode. +Второе - собственно обработка перенаправленных пакетов в пакетном фильтре. + +В windows отсутствуют встроенные средства для перенаправления трафика, такие как iptables, nftables, pf или ipfw. +Поэтому используется сторонний драйвер ядра windivert. Он работает, начиная с windows 7. На системах с включенным +secure boot могут быть проблемы из-за подписи драйвера. В этом случае отключите secureboot или включите режим testsigning. +На windows 7 вероятно будут проблемы с загрузкой windivert. Читайте ниже соответствующий раздел. + +Задача iptables в winws решается внутренними средствами через фильтры windivert. +У windivert существует собственный язык фильтров, похожий на язык фильтров wireshark. +Документация по фильтрам windivert : https://reqrypt.org/windivert-doc.html#filter_language +Чтобы не писать сложные фильтры вручную, предусмотрены различные упрощенные варианты автоматического построения фильтров. + + --wf-iface=[.] ; числовые индексы интерфейса и суб-интерфейса + --wf-l3=ipv4|ipv6 ; фильтр L3 протоколов. по умолчанию включены ipv4 и ipv6. + --wf-tcp=[~]port1[-port2] ; фильтр портов для tcp. ~ означает отрицание + --wf-udp=[~]port1[-port2] ; фильтр портов для udp. ~ означает отрицание + --wf-raw=|@ ; задать напрямую фильтр windivert из параметра или из файла. имени файла предшествует символ @. + --wf-save= ; сохранить сконструированный фильтр windivert в файл для последующей правки вручную + --ssid-filter=ssid1[,ssid2,ssid3,...] ; включать winws только когда подключена любая из указанных wifi сетей + --nlm-filter=net1[,net2,net3,...] ; включать winws только когда подключена любая из указанных сетей NLM + --nlm-list[=all] ; вывести список сетей NLM. по умолчанию только подключенных, all - всех. +Параметры --wf-l3, --wf-tcp, --wf-udp могут брать несколько значений через запятую. + +Номера интерфейсов можно узнать так : netsh int ip show int. +Некоторых типы соединений там не увидеть. В этом случае запускайте winws с параметром --debug и смотрите IfIdx там. +SubInterface используется windivert, но практически всегда 0, его можно не указывать. Вероятно он нужен в редких случаях. + +Конструктор фильтров автоматически включает входящие tcp пакеты с tcp synack и tcp rst для корректной работы функций +autottl и autohostlist. При включении autohostlist так же перенаправляются пакеты данных с http redirect с кодами 302 и 307. +Всегда добавляется фильтр на исключение не-интернет адресов ipv4 и ipv6. +Для сложных нестандартных сценариев могут потребоваться свои фильтры. Логично будет начать со стандартного шаблона, +сохраненного через --wf-save. Нужно править файл и подсовывать его в параметре --wf-raw. Максимальный размер фильтра - 8 Kb. + +Можно запускать несколько процессов winws с разными стратегиями. Однако, не следует делать пересекающиеся фильтры. + +В --ssid-filter можно через запятую задать неограниченное количество имен wifi сетей (SSID). Если задана хотя бы одна сеть, +то winws включается только, если подключен указанный SSID. Если SSID исчезает, winws отключается. Если SSID появляется снова, +winws включается. Это нужно, чтобы можно было применять раздельное дурение к каждой отдельной wifi сети. +Названия сетей должны быть написаны в том регистре, в котором их видит система. Сравнение идет с учетом регистра ! +При этом нет никаких проверок куда реально идет трафик. Если одновременно подключен, допустим, ethernet, +и трафик идет туда, то дурение включается и выключается просто по факту наличия wifi сети, на которую трафик может и не идти. +И это может сломать дурение на ethernet. Поэтому полезно так же будет добавить фильтр --wf-iface на индекс интерфейса wifi адаптера, +чтобы не трогать другой трафик. + +--nlm-filter аналогичен --ssid-filter, но работает с именами или GUIDами сетей Network List Manager (NLM). +Это те сети, которые вы видите в панели управления в разделе "Центр управления сетями и общим доступом". +Под сетью подразумевается не конкретный адаптер, а именно сетевое окружение конкретного подключения. +Обычно проверяется mac адрес шлюза. К сети можно подключиться через любой адаптер, и она останется той же самой. +Если подключиться, допустим, к разными роутерам по кабелю, то будут разные сети. +А если к одному роутеру через 2 разных сетевых карточки на том же компе - будет одна сеть. +NLM абстрагирует типы сетевых адаптеров. Он работает как с wifi, так и с ethernet и любыми другими. +Поэтому это более универсальный метод, чем ssid фильтр. +Однако, есть и неприятная сторона. В windows 7 вы легко могли ткнуть на иконку сети и выбрать тип : private или public. +Там же вы могли посмотреть список сетей и обьединить их. Чтобы, допустим, вы могли подключаться по кабелю и wifi +к одному роутеру, и система эти подключения воспринимала как одну сеть. +В следующих версиях windows они эти возможности сильно порезали. Похоже нет встроенных средств полноценно управлять +network locations в win10/11. Кое-что есть в powershell. +Можно поковыряться напрямую в реестре здесь : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList +Нужно менять ProfileGUID в Signatures\Unmanaged. Имена можно поменять в Profiles. +Есть кое-какие сторонние утилиты. Кое-что находится, позволяющее посмотреть и удалить network profiles, но не обьединить. +Факт, что в ms они это сильно испортили. Движок network list все тот же, и он способен на все то, что было в win7. +Можно не бороться с этой проблемой, а просто указывать через запятую те названия сетей или GUIDы, которые выбрала система. +Или если у вас только wifi, то использовать --ssid-filter. Там хотя бы есть гарантия, что SSID соответствуют реальности, +а система их не назвала как-то по-своему. + +Если в путях присутствуют национальные символы, то при вызове winws из cmd или bat кодировку нужно использовать OEM. +Для русского языка это 866. Пути с пробелами нужно брать в кавычки. + +Существует неочевидный момент, каcаемый запуска winws из cygwin шелла. Если в директории, где находится nfqws, находится +копия cygwin1.dll, winws не запустится. Поэтому в binaries/win64 существует директория zapret-winws, содержащая полный +комплект для запуска без cygwin. Его вы и берете для повседневного использования. +Если нужен запуск под cygwin, то следует запускать из binaries/win64. Это нужно для работы blockcheck. +Из cygwin шелла можно посылать winws сигналы через kill точно так же, как в *nix. + +Как получить совместимый с windows 7 и winws cygwin : +curl -O https://www.cygwin.com/setup-x86_64.exe +setup-x86_64.exe --allow-unsupported-windows --no-verify --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 +Следует выбрать установку curl. + +Для сборки из исходников требуется gcc-core,make,zlib-devel. +Собирать из директории nfq командой "make cygwin". +winws требует cygwin1.dll, windivert.dll, windivert64.sys. Их можно взять из binaries/win64/zapret-winws. +Версию для 32-битных x86 windows собрать можно, но такие системы уже уходят в прошлое, поэтому если надо - собирайте сами. +32-битный windivert можно взять с сайта разработчика. Требуется версия 2.2.2. + +Для arm64 windows нет подписанного драйвера windivert и нет cygwin. +Однако, эмуляция x64 windows 11 позволяет использовать все, кроме WinDivert64.sys без изменений. +Но при этом надо заменить WinDivert64.sys на неподписанную arm64 версию и установить режим testsigning. + +Windows 7 и windivert +--------------------- + +Требования к подписи драйверов windows изменились в 2021 году. +Официальные бесплатные обновления windows 7 закончились в 2020. +После этого несколько лет продолжали идти платные обновления по программе ESU. +Именно в этих ESU обновлениях находится обновление ядра windows 7, позволяющиее загрузить драйвер +windivert 2.2.2-A, который идет в поставке zapret. +Поэтому варианты следующие : + +1) Взять windivert64.sys и windivert.dll версии 2.2.0-C или 2.2.0-D отсюда : https://reqrypt.org/download +и заменить эти 2 файла. +В zapret-win-bundle есть отдельных 2 места, где находится winws : zapret-winws и blockcheck/zapret/nfq. +Надо менять в обоих местах. +Этот вариант проверен и должен работать. Тем не менее патч 10 летней давности, который включает SHA256 +сигнатуры, все еще необходим. + +2) Взломать ESU : +https://hackandpwn.com/windows-7-esu-patching/ +http://www.bifido.net/tweaks-and-scripts/8-extended-security-updates-installer.html +и обновить систему + +3) Использовать UpdatePack7R2 от simplix : https://blog.simplix.info +Но с этим паком есть проблема. Автор из Украины, он очень обиделся на русских. +Если в панели управления стоит регион RU или BY, появляется неприятный диалог. +Чтобы эту проблему обойти, можно поставить временно любой другой регион, потом вернуть. +Так же нет никаких гарантий, что автор не насовал туда какой-то зловредный код. +Использовать на свой страх и риск. +Более безопасный вариант - скачать последнюю нормальную довоенную версию : 22.2.10 +https://nnmclub.to/forum/viewtopic.php?t=1530323 +Ее достаточно, чтобы windivert 2.2.2-A заработал на windows 7. + +blockcheck +---------- + +blockcheck.sh написан на posix shell и требует некоторых стандартных утилит posix. В windows, естественно, этого нет. +Потому просто так запустить blockcheck.sh невозможно. +Для этого требуется скачать и установить cygwin так , как описано в предыдущем разделе. +Следует запустить от имени администратора cygwin shell через cygwin.bat. +В нем нужно пройти в директорию с zapret. +Обратные слэши путей windows нужно удваивать, менять на прямые слэши, либо использовать отображение на unix path. +Корректный вариант 1 : cd "C:\\Users\\vasya" +Корректный вариант 2 : cd "C:/Users/vasya" +Корректный вариант 3 : cd "/cygdrive/c/Users/vasya" +Далее все как в *nix : 1 раз ./install_bin.sh , затем ./blockcheck.sh. +WSL использовать нельзя, это не то же самое. + +cygwin для обычной работы winws не нужен. Разве что вы хотите посылать winws SIGHUP для перечитки листов без перезапуска. + +автозапуск winws +---------------- + +Для запуска winws вместе с windows есть 2 варианта. Планировщик задач или службы windows. + +Можно создавать задачи и управлять ими через консольную программу schtasks. +В директории binaries/win64/winws подготовлены файлы task_*.cmd . +В них реализовано создание, удаление, старт и стоп одной копии процесса winws с параметрами из переменной %WINWS1%. +Исправьте параметры на нужную вам стратегию. Если для разных фильтров применяется разная стратегия, размножьте код +для задач winws1,winws2,winws3,... + +Аналогично настраивается вариант запуска через службы windows. Смотрите service_*.cmd. + +Все батники требуется запускать от имени администратора. + +Управлять задачами можно так же из графической программы управления планировщиком taskschd.msc + +zapret-win-bundle +----------------- + +Можно не возиться с cygwin, а взять готовый пакет, включающий в себя cygwin и blockcheck : https://github.com/bol-van/zapret-win-bundle +Там сделан максимум удобств для сосредоточения на самом zapret, исключая возню с установкой cygwin, +заходами в директории, запусками под администратором и прочими сугубо техническими моментами, в которых могут быть +ошибки и непонимания, а новичок без базиса знаний может и вовсе запутаться. + +/zapret-winws - здесь все, что нужно для запуска winws в повседневном рабочем режиме. остальное не нужно. +/zapret-winws/_CMD_ADMIN.cmd - получить командную строку cmd в этой директории от имени администратора для тестирования winws +с параметрами, вводимыми вручную +/blockcheck/blockcheck.cmd - достаточно кликнуть по нему, чтобы пошел blockcheck с записью лога в blockcheck/blockcheck.log +/cygwin/cygwin.cmd - запуск среды cygwin bash под текущим пользователем +/cygwin/cygwin-admin.cmd - запуск среды cygwin bash под администратором + +В среде cygwin уже настроены alias-ы на winws,blockcheck,ip2net,mdig. С путями возиться не нужно ! +Из cygwin можно не только тестировать winws, но и посылать сигналы. +Доступны команды pidof,kill,killall,pgrep,pkill. +Но важно понимать, что таким образом не выйдет посылать сигналы winws, запущенному из zapret-winws, +поскольку там свой cygwin1.dll, и они не разделяют общее пространство процессов unix. +zapret-winws - это отдельный комплект для повседневного использования, не требующий что-то еще, но и не связанный со средой cygwin. + +Среду cygwin можно использовать для записи в файл дебаг-лога winws. Для этого пользуйтесь командой tee. +winws --debug --wf-tcp=80,443 | tee winws.log +winws.log будет в cygwin/home/<имя_пользователя> +Если у вас windows 7, то блокнот не поймет переводы строк в стиле unix. Воспользуйтесь командой +unix2dos winws.log diff --git a/docs/wireguard/010-wg-mod.patch b/docs/wireguard/010-wg-mod.patch new file mode 100644 index 00000000..1577da68 --- /dev/null +++ b/docs/wireguard/010-wg-mod.patch @@ -0,0 +1,133 @@ +Index: WireGuard-0.0.20190123/src/cookie.c +=================================================================== +--- WireGuard-0.0.20190123.orig/src/cookie.c ++++ WireGuard-0.0.20190123/src/cookie.c +@@ -193,6 +193,8 @@ void wg_cookie_message_create(struct mes + xchacha20poly1305_encrypt(dst->encrypted_cookie, cookie, COOKIE_LEN, + macs->mac1, COOKIE_LEN, dst->nonce, + checker->cookie_encryption_key); ++ // MOD : randomize trash ++ dst->header.trash = gen_trash(); + } + + void wg_cookie_message_consume(struct message_handshake_cookie *src, +Index: WireGuard-0.0.20190123/src/messages.h +=================================================================== +--- WireGuard-0.0.20190123.orig/src/messages.h ++++ WireGuard-0.0.20190123/src/messages.h +@@ -53,23 +53,41 @@ enum limits { + MAX_QUEUED_PACKETS = 1024 /* TODO: replace this with DQL */ + }; + ++/* + enum message_type { +- MESSAGE_INVALID = 0, +- MESSAGE_HANDSHAKE_INITIATION = 1, +- MESSAGE_HANDSHAKE_RESPONSE = 2, +- MESSAGE_HANDSHAKE_COOKIE = 3, +- MESSAGE_DATA = 4 ++ MESSAGE_INVALID = 0, ++ MESSAGE_HANDSHAKE_INITIATION = 1, ++ MESSAGE_HANDSHAKE_RESPONSE = 2, ++ MESSAGE_HANDSHAKE_COOKIE = 3, ++ MESSAGE_DATA = 4 + }; ++*/ ++ ++// MOD : message type ++enum message_type { ++ MESSAGE_INVALID = 0xE319CCD0, ++ MESSAGE_HANDSHAKE_INITIATION = 0x48ADE198, ++ MESSAGE_HANDSHAKE_RESPONSE = 0xFCA6A8F3, ++ MESSAGE_HANDSHAKE_COOKIE = 0x64A3BB18, ++ MESSAGE_DATA = 0x391820AA ++}; ++ ++// MOD : generate fast trash without true RNG ++__le32 gen_trash(void); + + struct message_header { +- /* The actual layout of this that we want is: +- * u8 type +- * u8 reserved_zero[3] +- * +- * But it turns out that by encoding this as little endian, +- * we achieve the same thing, and it makes checking faster. +- */ +- __le32 type; ++ /* The actual layout of this that we want is: ++ * u8 type ++ * u8 reserved_zero[3] ++ * ++ * But it turns out that by encoding this as little endian, ++ * we achieve the same thing, and it makes checking faster. ++ */ ++ ++ // MOD : trash field to change message size and add 4 byte offset to all fields ++ __le32 trash; ++ ++ __le32 type; + }; + + struct message_macs { +Index: WireGuard-0.0.20190123/src/noise.c +=================================================================== +--- WireGuard-0.0.20190123.orig/src/noise.c ++++ WireGuard-0.0.20190123/src/noise.c +@@ -17,6 +17,24 @@ + #include + #include + ++ ++// MOD : trash generator ++__le32 gtrash = 0; ++__le32 gen_trash(void) ++{ ++ if (gtrash) ++ gtrash = gtrash*1103515243 + 12345; ++ else ++ // first value is true random ++ get_random_bytes_wait(>rash, sizeof(gtrash)); ++ return gtrash; ++} ++ + /* This implements Noise_IKpsk2: + * + * <- s +@@ -515,6 +533,10 @@ wg_noise_handshake_create_initiation(str + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_INITIATION; ++ ++ // MOD : randomize trash ++ dst->header.trash = gen_trash(); ++ + ret = true; + + out: +@@ -655,6 +677,10 @@ bool wg_noise_handshake_create_response( + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_RESPONSE; ++ ++ // MOD : randomize trash ++ dst->header.trash = gen_trash(); ++ + ret = true; + + out: +Index: WireGuard-0.0.20190123/src/send.c +=================================================================== +--- WireGuard-0.0.20190123.orig/src/send.c ++++ WireGuard-0.0.20190123/src/send.c +@@ -200,6 +200,10 @@ static bool encrypt_packet(struct sk_buf + header->header.type = cpu_to_le32(MESSAGE_DATA); + header->key_idx = keypair->remote_index; + header->counter = cpu_to_le64(PACKET_CB(skb)->nonce); ++ ++ // MOD : randomize trash ++ header->header.trash = gen_trash(); ++ + pskb_put(skb, trailer, trailer_len); + + /* Now we can encrypt the scattergather segments */ diff --git a/docs/wireguard/wireguard-mod.txt b/docs/wireguard/wireguard-mod.txt new file mode 100644 index 00000000..878aa447 --- /dev/null +++ b/docs/wireguard/wireguard-mod.txt @@ -0,0 +1,250 @@ +!!! Эта инструкция написана еще до включения wireguard в ядро linux. +!!! Процесс сборки для in-tree модулей отличается. +!!! Цель данного чтива - дать идею для программистов как можно исправить исходники wireguard +!!! для преодоления DPI. Автор не преследует цели поддерживать готовые патчи для актуальных версий. +!!! Вместо патчинга гораздо проще использовать навесное решение ipobfs. + +Посвящено возможной блокировке в РФ VPN протоколов через DPI. +Предпосылками являются последние законодательные акты и во всю сочащиеся "секретные" записки. +В РФ разрабатываются и готовятся к применению более продвинутые решения по блокировке трафика. +Вполне вероятно будут резать стандартные VPN протоколы. Нам надо быть к этому готовыми. + +Один из возможных и перспективных путей решения данного вопроса - кустомная модификация +исходников VPN с целью незначительного изменения протокола, ломающего стандартные модули обнаружения в DPI. +Это относительно сложно, доступно только для гиков. +Никто не будет разрабатывать специальные модули обнаружения в DPI, если только кто-то не сделает простое и +удобное решение для всех, и его станут широко применять. Но это маловероятно, и даже если и так, +то всегда можно модифицировать протокол чуток по другому. Делать моды для DPI несравненно дольше +и дороже, чем клепать на коленке изменения протокола для wireguard. + + +ЗАМЕЧЕНИЕ : альтернативой модификации конечного софта для VPN является использование "навесных" +обфускаторов. см : https://github.com/bol-van/ipobfs + + +Рассмотрю что нам надо пропатчить в wireguard. Модифицированный wireguard проверен на виртуалках +с десктопным linux, он работает, сообщения в wireshark действительно не вписываются в стандартный +протокол и не опознаются. + +Wireguard протокол очень простой. Все сообщения описаны в messages.h +Поставим себе целью сделать 2 простые модификации : +1) Добавим в начало всех сообщений немного мусора, чтобы изменить размер сообщений и смещения полей +2) Изменим коды типов сообщений +Этого может быть вполне достаточно для обмана DPI + +--messages.h-------------------------- +/* +enum message_type { + MESSAGE_INVALID = 0, + MESSAGE_HANDSHAKE_INITIATION = 1, + MESSAGE_HANDSHAKE_RESPONSE = 2, + MESSAGE_HANDSHAKE_COOKIE = 3, + MESSAGE_DATA = 4 +}; +*/ + +// MOD : message type +enum message_type { + MESSAGE_INVALID = 0xE319CCD0, + MESSAGE_HANDSHAKE_INITIATION = 0x48ADE198, + MESSAGE_HANDSHAKE_RESPONSE = 0xFCA6A8F3, + MESSAGE_HANDSHAKE_COOKIE = 0x64A3BB18, + MESSAGE_DATA = 0x391820AA +}; + +// MOD : generate fast trash without true RNG +__le32 gen_trash(void); + +struct message_header { + /* The actual layout of this that we want is: + * u8 type + * u8 reserved_zero[3] + * + * But it turns out that by encoding this as little endian, + * we achieve the same thing, and it makes checking faster. + */ + + // MOD : trash field to change message size and add 4 byte offset to all fields + __le32 trash; + + __le32 type; +}; +-------------------------------------- + +Напишем функцию для генерации trash. Функция должна быть быстрая, важно не замедлить скорость. +Мы не расчитываем, что нас будут специально ловить, иначе бы пришлось делать полноценный обфускатор. +Задача лишь сломать стандартный модуль обнаружения протокола wireguard. Потому истинная рандомность +trash не важна. +Но все же немного "трэша" не повредит. Гонки между тредами так же пофигистичны. Это же трэш. + +--noise.c----------------------------- +// MOD : trash generator +__le32 gtrash = 0; +__le32 gen_trash(void) +{ + if (gtrash) + gtrash = gtrash*1103515243 + 12345; + else + // first value is true random + get_random_bytes_wait(>rash, sizeof(gtrash)); + return gtrash; +} +-------------------------------------- + +Теперь осталось найти все места, где создаются сообщения и внести туда заполнение поля trash. +Сообщений всего 4. Их можно найти по присваиванию полю type одного из значений enum message_type. + +2 места в noise.c в функциях wg_noise_handshake_create_initiation и wg_noise_handshake_create_response, +1 место в cookie.c в функции wg_cookie_message_create +Дописываем в конец инициализации структуры сообщения : + +-------------------------------------- + // MOD : randomize trash + dst->header.trash = gen_trash(); +-------------------------------------- + +и 1 место в send.c в функции encrypt_packet + +-------------------------------------- + // MOD : randomize trash + header->header.trash = gen_trash(); +-------------------------------------- + + +Вот и весь патчинг. Полный patch (версия wireguard 0.0.20190123) лежит в 010-wg-mod.patch. +Патчинг кода - самое простое. Для десктопного linux дальше все просто. +Пересобираем через make, устанавливаем через make install, перегружаем +модуль wireguard, перезапускаем интерфейсы, и все готово. + +Настоящий геморой начнется когда вы это попытаетесь засунуть на роутер под openwrt. +Одна из больших проблем linux - отсутствие совместимости драйверов на уровне бинариков. +Поэтому собирать необходимо в точности под вашу версию ядра и в точности под его .config. +Вам придется либо полностью самостоятельно собирать всю прошивку, либо найти SDK в точности +от вашей версии прошивки для вашей архитектуры и собрать модуль с помощью этого SDK. +Последний вариант более легкий. +Для сборки вам понадобится система на linux x86_64. Ее можно установить в виртуалке. +Теоретически можно пользоваться WSL из win10, но на практике там очень медленное I/O, +по крайней мере на старых версиях win10. Безумно медленное. Будете собирать вечность. +Может в новых win10 что-то и улучшили, но я бы сразу расчитывал на полноценный linux. + +Находим здесь вашу версию : https://downloads.openwrt.org/ +Скачиваем файл openwrt-sdk-*.tar.xz или lede-sdk-*.tar.xz +Например : https://downloads.openwrt.org/releases/18.06.2/targets/ar71xx/generic/openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64.tar.xz +Если ваша версия непонятна или стара, то проще будет найти последнюю прошивку и перешить роутер. +Распаковываем SDK. Следующими командами можно собрать оригинальный вариант wireguard : + +# scripts/feeds update -a +# scripts/feeds install -a +# make defconfig +# make -j 4 package/wireguard/compile + +Сборка будет довольно долгой. Ведь придется подтащить ядро, собрать его, собрать зависимости. +"-j 4" означает использовать 4 потока. Впишите вместо 4 количество доступных cpu cores. + +Получим следующие файлы : + +openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/bin/targets/ar71xx/generic/packages/kmod-wireguard_4.9.152+0.0.20190123-1_mips_24kc.ipk +openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/bin/packages/mips_24kc/base/wireguard-tools_0.0.20190123-1_mips_24kc.ipk + +Но это будет оригинальный wireguard. Нам нужен патченый. +Установим quilt и mc для нормального редактора вместо vim : + +# sudo apt-get update +# sudo apt-get install quilt mc + +# make package/wireguard/clean +# make package/wireguard/prepare V=s QUILT=1 + + +Сорцы приготовлены для сборки в : + openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src + +# cd build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src +# quilt push -a +# quilt new 010-wg-mod.patch +# export EDITOR=mcedit + +Далее будет открываться редактор mcedit, в который нужно вносить изменения в каждый файл : + +# quilt edit messages.h +# quilt edit cookie.c +# quilt edit noise.c +# quilt edit send.c +# quilt diff +# quilt refresh + +Получили файл патча в : +openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/patches/010-wg-mod.patch + +Выходим в корень SDK. + +# make package/wireguard/compile V=99 + +Если не было ошибок, то получили измененные ipk. +Патч можно зафиксировать в описании пакета : + +# make package/wireguard/update + +Получим : +openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/feeds/base/package/network/services/wireguard/patches/010-wg-mod.patch +При последующей очистке и пересборке он будет автоматом применяться. + + +АЛЬТЕРНАТИВА : можно не возиться с quilt. +сделайте +# make package/wireguard/clean +# make package/wireguard/prepare +и напрямую модифицируйте или копируйте файлы в + openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/build_dir/target-mips_24kc_musl/linux-ar71xx_generic/WireGuard-0.0.20190123/src +затем +# make package/wireguard/compile + +Если нужно поменять версию wireguard, то идите в +openwrt-sdk-18.06.2-ar71xx-generic_gcc-7.3.0_musl.Linux-x86_64/feeds/base/package/network/services/wireguard/Makefile +поменяйте там версию в PKG_VERSION на последнюю из : https://git.zx2c4.com/WireGuard +скачайте tar.xz с этой версией , вычислите его sha256sum, впишите в PKG_HASH + +1 раз где-нибудь пропатчите файлы последней версии wireguard в текстовом редакторе, скопируйте в build_dir, +сделайте версию для openwrt. эти же файлы скопируйте на ваш сервер с десктопным linux, сделайте там make / make install + +Но имейте в виду, что build_dir - локация для временных файлов. +make clean оттуда все снесет, включая ваши модификации. Модифицированные файлы лучше сохранить отдельно, +чтобы потом было легко скопировать обратно. + +Полученные ipk копируем на роутер в /tmp, устанавливаем через +# cd /tmp +# rm -r /tmp/opkg-lists +# opkg install *.ipk +Если требует зависимостей, то +# opkg update +# opkg install .... <зависимости> +# rm -r /tmp/opkg-lists +# opkg install *.ipk + +В /tmp/opkg-lists opkg хранит кэш списка пакетов. Если попытаться установить файл ipk, и такой же пакет +найдется в репозитории, opkg будет устанавливать из репозитория. А нам это не надо. + +# rmmod wireguard +# kmodloader +# dmesg | tail +должны увидеть что-то вроде : +[8985.415490] wireguard: WireGuard 0.0.20190123 loaded. See www.wireguard.com for information. +[8985.424178] wireguard: Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. +значит модуль загрузился + +Могут понадобиться ключи opkg --force-reinstall, --force-depends. +--force-depends поможет при несоответствии hash версии ядра. То есть версия x.x.x та же самая, но hash конфигурации разный. +При несоответствии x.x.x вы что-то делаете не так, работать это не будет. +Например : 4.14.56-1-b1186491495127cc6ff81d29c00a91fc, 4.14.56-1-3f8a21a63974cfb7ee67e41f2d4b805d +Это свидетельствует о несоответствии .config ядра при сборке прошивки и в SDK. +Если несоответствие легкое, то может все прокатить, но при более серьезной разнице в .config модуль может не загрузиться +или вызвать стабильные или хаотические падения ядра и перезагрузки (включая вариант беcконечной перезагрузки - bootloop). +Так что перед --force-depends убедитесь, что знаете как лечится такая ситуация, и не стоит это делать при отсутствии физического +доступа к девайсу. + +Когда поднимите линк, и вдруг ничего не будет работать, то посмотрите в wireshark udp пакеты +на порт endpoint. Они не должны начинаться с 0,1,2,3,4. В первых 4 байтах должен быть рандом, +в следующих 4 байтах - значения из измененного enum message_type. Если пакет все еще начинается с 0..4, +значит модуль wireguard оригинальный, что-то не собралось, не скопировалось, не перезапустилось. +В противном случае должен подняться линк, пинги ходить. Значит вы победили, поздравляю. +Регулятору будет намного сложнее поймать ваш VPN. diff --git a/docs/wireguard/wireguard_iproute_openwrt.txt b/docs/wireguard/wireguard_iproute_openwrt.txt new file mode 100644 index 00000000..6d36f91f --- /dev/null +++ b/docs/wireguard/wireguard_iproute_openwrt.txt @@ -0,0 +1,645 @@ +Данный мануал пишется не как копипастная инструкция, а как помощь уже соображающему. +Если вы не знаете основ сетей, linux, openwrt, а пытаетесь что-то скопипастить отсюда без малейшего +понимания смысла, то маловероятно, что у вас что-то заработает. Не тратье свое время напрасно. +Цель - донести принципы как это настраивается вообще, а не указать какую буковку где вписать. + + +Есть возможность поднять свой VPN сервер ? Не хотим использовать redsocks ? +Хотим завертывать на VPN только часть трафика ? +Например, из ipset zapret только порт tcp:443, из ipban - весь трафик, не только tcp ? +Да, с VPN такое возможно. +Опишу понятийно как настраивается policy based routing в openwrt на примере wireguard. +Вместо wireguard можно использовать openvpn или любой другой. Но wireguard прекрасен сразу несколькими вещами. +Главная из которых - в разы большая скорость, даже немного превышающая ipsec. +Ведь openvpn основан на tun, а tun - всегда в разы медленнее решения в kernel mode, +и если для PC оно может быть не так актуально, для soho роутеров - более чем. +Wireguard может дать 50 mbps там, где openvpn еле тащит 10. +Но есть и дополнительное требование. Wireguard работает в ядре, значит ядро должно +быть под вашим контролем. vps на базе openvz не подойдет. Нужен xen, kvm, +любой другой вариант, где загружается ваше собственное ядро, а не используется +общее, разделяемое на множество vps. + +Понятийно необходимо выполнить следующие шаги : +1) Поднять vpn сервер. +2) Настроить vpn клиент. Результат этого шага - получение поднятого интерфейса vpn. +Будь то wireguard, openvpn или любой другой тип vpn. +3) Создать такую схему маршрутизации, при которой пакеты, помечаемые особым mark, +попадают на vpn, а остальные идут обычным способом. +4) Создать правила, выставляющие mark для всего трафика, который необходимо рулить на vpn. +Критерии могут быть любые, ограниченные лишь возможностями iptables и вашим воображением. + +Будем считать наш vpn сервер находится на ip 91.15.68.202. +Вешать его будем на udp порт 12345. На этот же порт будем вешать и клиентов. +Сервер работает под debian 9 или выше. Клиент работает под openwrt. +Для vpn отведем подсеть 192.168.254.0/24. + +--- Если нет своего сервера --- + +Но есть конфиг от VPN провайдера или от друга "Васи", который захотел с вами поделиться. +Тогда вам не надо настраивать сервер, задача упрощается. Делается невозможным вариант настройки +без masquerade (см ниже). +Из конфига вытаскиваете приватный ключ своего пира и публичный ключ сервера, ip/host/port сервера, +используете их в настройках openwrt вместо сгенеренных самостоятельно. + +--- Поднятие сервера --- + +Wireguard был включен в ядро linux с версии 5.6. +Если у вас ядро >=5.6, то достаточно установить пакет wireguard-tools. Он содержит user-mode компоненты wireguard. +Посмотрите, возможно в вашем дистрибутиве ядро по умолчанию более старое, но в репозитории +имеются бэкпорты новых версий. Лучше будет обновить ядро из репозитория. + +В репозитории может быть пакет wireguard-dkms. Это автоматизированное средство сборки +wireguard с исходников, в том числе модуль ядра. Можно пользоваться им. + +Иначе вам придется собрать wireguard самому. Ядро должно быть не ниже 3.10. +На сервере должны быть установлены заголовки ядра (linux-headers-...) и компилятор gcc. + +# git clone --depth 1 https://git.zx2c4.com/wireguard-linux-compat +# cd wireguard-linux-compat/src +# make +# strip --strip-debug wireguard.ko +# sudo make install + +wireguard основан на понятии криптороутинга. Каждый пир (сервер - тоже пир) +имеет пару открытый/закрытый ключ. Закрытый ключ остается у пира, +открытый прописывается у его партнера. Каждый пир авторизует другого +по знанию приватного ключа, соответствующего прописанному у него публичному ключу. +Протокол построен таким образом, что на все неправильные udp пакеты не следует ответа. +Не знаешь приватный ключ ? Не смог послать правильный запрос ? Долбись сколько влезет, +я тебе ничего не отвечу. Это защищает от активного пробинга со стороны DPI и просто +экономит ресурсы. +Значит первым делом нужно создать 2 пары ключей : для сервера и для клиента. +wg genkey генерит приватный ключ, wg pubkey получает из него публичный ключ. + +# wg genkey +oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= +# echo oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0= | wg pubkey +bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= +# wg genkey +OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= +# echo OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= | wg pubkey +EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg= + +Пишем конфиг +--/etc/wireguard/wgvps.conf------------------- +[Interface] +PrivateKey = OKXX0TSlyjJmGt3/yHlHxi0AqjJ0vh+Msne3qEHk0VM= +ListenPort = 12345 + +[Peer] +#Endpoint = +PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= +AllowedIPs = 192.168.254.3 +PersistentKeepalive=20 +---------------------------------------------- + +Wireguard - минималистичный vpn. В нем нет никаких средств для автоконфигурации ip. +Все придется прописывать руками. +В wgvps.conf должны быть перечислены все пиры с их публичными ключами, +а так же прописаны допустимые для них ip адреса. +Назначим нашему клиенту 192.168.254.3. Сервер будет иметь ip 192.168.254.1. +Endpoint должен быть прописан хотя бы на одном пире. +Если endpoint настроен для пира, то wireguard будет периодически пытаться к нему подключиться. +В схеме клиент/сервер у сервера можно не прописывать endpoint-ы пиров, что позволит +менять ip и быть за nat. Endpoint пира настраивается динамически после успешной фазы +проверки ключа. + +Включаем маршрутизцию : +# echo net.ipv4.ip_forward = 1 >>/etc/sysctl.conf +# sysctl -p + +Интерфейс конфигурится стандартно для дебианоподобных систем : + +--/etc/network/interfaces.d/wgvps------------- +auto wgvps +iface wgvps inet static + address 192.168.254.1 + netmask 255.255.255.0 + pre-up ip link add $IFACE type wireguard + pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf + post-up iptables -t nat -A POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE + post-up iptables -A FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + post-down iptables -D FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu + post-down iptables -t nat -D POSTROUTING -o eth0 -s 192.168.254.0/24 -j MASQUERADE + post-down ip link del $IFACE +---------------------------------------------- + +Поднятие через ifup wgvps, опускание через ifdown wgvps. +При поднятии интерфейса заодно настраивается nat. eth0 здесь означает интерфейс vpn сервера с инетовским ip адресом. +Если у вас какая-то система управления фаерволом, то надо настройку nat прикручивать туда. +Пример написан для простейшего случая, когда никаких ограничений нет, таблицы iptables пустые. +Чтобы посмотреть текущие настройки wireguard, запустите 'wg' без параметров. + + +--- Поднятие клиента --- + +# opkg update +# opkg install wireguard-tools + +Добавляем записи в конфиги. + +--/etc/config/network-------------------------- +config interface 'wgvps' + option proto 'wireguard' + option auto '1' + option private_key 'oAUkmhoREtFQ5D5yZmeHEgYaSWCcLYlKe2jBP7EAGV0=' + option listen_port '12345' + option metric '9' + option mtu '1420' + +config wireguard_wgvps + option public_key 'EELdA2XzjcKxtriOCPBXMOgxlkgpbRdIyjtc3aIpkxg=' + list allowed_ips '0.0.0.0/0' + option endpoint_host '91.15.68.202' + option endpoint_port '12345' + option route_allowed_ips '0' + option persistent_keepalive '20' + +config interface 'wgvps_ip' + option proto 'static' + option ifname '@wgvps' + list ipaddr '192.168.254.3/24' + +config route + option interface 'wgvps' + option target '0.0.0.0/0' + option table '100' + +config rule + option mark '0x800/0x800' + option priority '100' + option lookup '100' +------------------------------------------------ + +--/etc/config/firewall-------------------------- +config zone + option name 'tunvps' + option output 'ACCEPT' + option input 'REJECT' + option masq '1' + option mtu_fix '1' + option forward 'REJECT' + option network 'wgvps wgvps_ip' + +config forwarding + option dest 'tunvps' + option src 'lan' + +config rule + option name 'Allow-ICMP-tunvps' + option src 'tunvps' + option proto 'icmp' + option target 'ACCEPT' + +config rule + option target 'ACCEPT' + option src 'wan' + option proto 'udp' + option family 'ipv4' + option src_port '12345' + option src_ip '91.15.68.202' + option name 'WG-VPS' +------------------------------------------------ + +Что тут было сделано : +*) Настроен интерфейс wireguard. Указан собственный приватный ключ. +*) Настроен пир-партнер с указанием его публичнго ключа и endpoint (ip:port нашего сервера) + такая настройка заставит периодически долбиться на сервер по указанному ip + route_allowed_ip '0' запрещает автоматическое создание маршрута + allowed_ips '0.0.0.0/0' разрешает пакеты с любым адресом источника. + ведь мы собираемся подключаться к любым ip в инете + persistent_keepalive '20' помогает исключить дропание mapping на nat-е, если мы сидим за ним, + да и вообще полезная вещь, чтобы не было подвисших пиров +*) Статическая конфигурация ip интерфейса wgvps. +*) Маршрут default route на wgvps в отдельной таблице маршрутизации с номером 100. Аналог команды ip route add .. table 100 +*) Правило использовать таблицу 100 при выставлении в mark бита 0x800. Аналог команды ip rule. +*) Отдельная зона фаервола для VPN - 'tunvps'. В принципе ее можно не создавать, можете приписать интерфейс к зоне wan. + Но в случае с отдельной зоной можно настроить особые правила на подключения с vpn сервера в сторону клиента. +*) Разрешение форвардинга между локалкой за роутером и wgvps. +*) Разрешение принимать icmp от vpn сервера, включая пинги. ICMP жизненно важны для правильного функционирования ip сети ! +*) И желательно проткнуть дырку в фаерволе, чтобы принимать пакеты wireguard со стороны инетовского ip vpn сервера. + Конечно, оно скорее всего заработает и так, потому что первый пакет пойдет от клиента к серверу и тем самым создаст + запись в conntrack. Все дальнейшие пакеты в обе стороны подпадут под состояние ESTABLISHED и будут пропущены. + Запись будет поддерживаться за счет периодических запросов keep alive. Но если вы вдруг уберете keep alive или + выставите таймаут, превышающий udp таймаут в conntrack, то могут начаться ошибки, висы и переподключения. + Если же в фаерволе проткнута дырка, то пакеты от сервера не будут заблокированы ни при каких обстоятельствах. + +# /etc/init.d/firewall restart +# ifup wgvps +# ifconfig wgvps +# ping 192.168.254.1 + +Если все хорошо, должны ходить пинги. +С сервера не помешает : +# ping 192.168.254.3 + + +--- Подготовка zapret --- + +Выполните install_easy.sh. Он настроит режим обхода DPI. Если обход DPI не нужен - выберите MODE=filter. +Так же инсталятор заресолвит домены из ipset/zapret-hosts-user-ipban.txt и внесет крон-джоб для периодического обновления ip. + +Если вы используете в своих правилах ipset zapret, то он ресолвится и обновляется только, если выбран режим фильтрации обхода DPI по ipset. +По сути он вам нужен исключительно, если обход DPI не помогает. Например, удается как-то пробить http, но не удается пробить https. +И при этом вы хотите, чтобы на VPN направлялись только ip из скачанного ip листа, в добавок к заресолвленному ipset/zapret-hosts-user.txt. +Именно этот случай и рассмотрен в данном примере. Если это не так, то убирайте правила с портом 443 из нижеприведенных правил iptables/nftables. +Если не хотите ограничиваться листом, и хотите направлять все на порт 443, то уберите фильтры из правил iptables/nftables, +связанные с ipset/nfset "zapret". + +Фильтрация по именам доменов (MODE_FILTER=hostlist) невозможна средствами iptables/nftables. Она производится исключительно в tpws и nfqws +по результатам анализа протокола прикладного уровня, иногда достаточно сложного, связанного с дешифровкой пакета (QUIC). +Скачиваются листы с именами доменов, не ip адресами. ipset/zapret-hosts-user.txt не ресолвится, а используется как hostlist. +Потому вам нельзя расчитывать на ipset zapret. +Тем не менее при выборе этого режима фильтрации , либо вовсе при ее отсутствии (MODE_FILTER=none), ipset/zapret-hosts-user-ipban.txt +все равно ресолвится. Вы всегда можете расчитывать на ipset/nfset "ipban", "nozapret". + +"nozapret" - это ipset/nfset, связанный с системой исключения ip. Сюда загоняется все из ipset/zapret-hosts-user-exclude.txt после ресолвинга. +Его учет крайне желателен, чтобы вдруг из скачанного листа не просочились записи, например, 192.168.0.0/16 и не заставили лезть туда через VPN. +Хотя скрипты получения листов и пытаются отсечь IP локалок, но так будет намного надежнее. + +--- Маркировка трафика --- + +Завернем на vpn все из ipset zapret на tcp:443 и все из ipban. +OUTPUT относится к исходящим с роутера пакетам, PREROUTING - ко всем остальным. +Если с самого роутера ничего заруливать не надо, можно опустить часть, отвечающую за OUTPUT. + +--/etc/firewall.user---------------------------- +. /opt/zapret/init.d/openwrt/functions + +create_ipset no-update + +network_find_wan4_all wan_iface +for ext_iface in $wan_iface; do + network_get_device DEVICE $ext_iface + ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 + ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 +done + +network_get_device DEVICE lan +ipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 +ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 +------------------------------------------------ + +# /etc/init.d/firewall restart + +--- Маркировка трафика nftables --- + +В новых openwrt по умолчанию установлен nftables, iptables отсутствует. +Есть вариант снести nftables + fw4 и заменить их на iptables + fw3. +Веб интерфейс luci понимает прозрачно и fw3, и fw4. Однако, при установке iptables и fw3 новые пакеты +будут устанавливаться без сжатия squashfs. Убедитесь, что у вас достаточно места. +Либо сразу настраивайте образ через image builder. + +Фаервол fw4 работает в одноименной nftable - "inet fw4". "inet" означает, что таблица принимает и ipv4, и ipv6. +Поскольку для маркировки трафика используется nfset, принадлежащий таблице zapret, цепочки необходимо помещать в ту же таблицу. +Для синхронизации лучше всего использовать хук +INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" +Параметр нужно раскоментировать в /opt/zapret/config. Далее надо создать указанный файл и дать ему chmod 755. + +--/etc/firewall.zapret.hook.post_up---------------------------- +#!/bin/sh + +ZAPRET_NFT_TABLE=zapret + +cat << EOF | nft -f - 2>/dev/null + delete chain inet $ZAPRET_NFT_TABLE my_output + delete chain inet $ZAPRET_NFT_TABLE my_prerouting +EOF + +cat << EOF | nft -f - + add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } + flush chain inet $ZAPRET_NFT_TABLE my_output + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 + + add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } + flush chain inet $ZAPRET_NFT_TABLE my_prerouting + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 +EOF +------------------------------------------------ + +# /etc/init.d/zapret restart_fw + +Проверка правил : +# /etc/init.d/zapret list_table +или +# nft -t list table inet zapret + +Должны быть цепочки my_prerouting и my_output. + +Проверка заполнения nfsets : +# nft list set inet zapret zapret +# nft list set inet zapret ipban +# nft list set inet zapret nozapret + +Проверка заполнения множеств lanif, wanif, wanif6, link_local : +# /etc/init.d/zapret list_ifsets + +Должны присутствовать имена интерфейсов во множествах lanif, wanif. +wanif6 заполняется только при включении ipv6. +link_local нужен только для tpws при включении ipv6. + +--- По поводу двойного NAT --- + +В описанной конфигурации nat выполняется дважды : на роутере-клиенте происходит замена адреса источника из LAN +на 192.168.254.3 и на сервере замена 192.168.254.3 на внешний адрес сервера в инете. +Зачем так делать ? Исключительно для простоты настройки. Или на случай, если сервер wireguard не находится под вашим контролем. +Делать для вас нижеописанные настройки никто не будет с вероятностью, близкой к 100%. +Если сервер wireguard - ваш, и вы готовы чуток еще поднапрячься и не хотите двойного nat, +то можете вписать в /etc/config/firewall "masq '0'", на сервер дописать маршрут до вашей подсети lan. +Чтобы не делать это для каждого клиента, можно отвести под всех клиентов диапазон 192.168.0.0-192.168.127.255 +и прописать его одним маршрутом. + +--/etc/network/interfaces.d/wgvps------------- + post-up ip route add dev $IFACE 192.168.0.0/17 + post-down ip route del dev $IFACE 192.168.0.0/17 +---------------------------------------------- + +Так же необходимо указать wireguard дополнительные разрешенные ip для peer : + +--/etc/wireguard/wgvps.conf------------------- +[Peer] +PublicKey = bCdDaPYSTBZVO1HTmKD+Tztuf3PbOWGDWfz7Lb1E6C4= +AllowedIPs = 192.168.254.3, 192.168.2.0/24 +---------------------------------------------- + +Всем клиентам придется назначать различные диапазоны адресов в lan и индивидуально прописывать AllowedIPs +для каждого peer. + +# ifdown wgvps ; ifup wgvps + +На клиенте разрешим форвард icmp, чтобы работал пинг и корректно определялось mtu. + +--/etc/config/firewall-------------------------- +config rule + option name 'Allow-ICMP-tunvps' + option src 'tunvps' + option dest 'lan' + option proto 'icmp' + option target 'ACCEPT' +------------------------------------------------ + +Существуют еще два неочевидных нюанса. + +Первый из них касается пакетов с самого роутера (цепочка OUTPUT). +Адрес источника выбирается по особому алгоритму, если программа явно его не задала, еще до этапа iptables. +Он берется с интерфейса, куда бы пошел пакет при нормальном раскладе. +Обратная маршрутизация с VPN станет невозможной, да и wireguard такие пакеты порежет, поскольку они не вписываются в AllowedIPs. +Никаким мистическим образом автоматом source address не поменяется. +В прошлом варианте настройки проблема решалось через маскарад. Сейчас же маскарада нет. +Потому все же придется его делать в случае, когда пакет изначально направился бы через wan, +а мы его завертываем на VPN. Помечаем такие пакеты марком 0x1000. +Если вам не актуальны исходящие с самого роутера, то можно ничего не менять. + +Другой нюанс связан с обработкой проброшенных на vps портов, соединения по которым приходят как входящие с интерфейса wgvps. +Представьте себе, что вы пробросили порт 2222. Кто-то подключается с адреса 1.2.3.4. Вам приходит пакет SYN 1.2.3.4:51723=>192.168.2.2:2222. +По правилам маршрутизации он пойдет в локалку. 192.168.2.2 его обработает, ответит пакетом ACK 192.168.2.2:2222=>1.2.3.4:51723. +Этот пакет придет на роутер. И куда он дальше пойдет ? Если он не занесен в ipban, то согласно правилам машрутизации +он пойдет по WAN интерфейсу, а не по исходному wgvps. +Чтобы решить эту проблему, необходимо воспользоваться CONNMARK. Существуют 2 отдельных марка : fwmark и connmark. +connmark относится к соединению, fwmark - к пакету. Трэкингом соединений занимается conntrack. +Посмотреть его таблицу можно командой "conntrack -L". Там же найдете connmark : mark=xxxx. +Как только видим приходящий с wgvps пакет с новым соединением, отмечаем его connmark как 0x800/0x800. +При этом fwmark не меняется, иначе бы пакет тут же бы завернулся обратно на wgvps согласно ip rule. +Если к нам приходит пакет с какого-то другого интерфейса, то восстанавливаем его connmark в fwmark по маске 0x800. +И теперь он подпадает под правило ip rule, заворачиваясь на wgvps, что и требовалось. + +Альтернативное решение - использовать на VPSке для проброса портов не только DNAT, но и SNAT/MASQUERADE. Тогда source address +будет заменен на 192.168.254.1. Он по таблице маршрутизации пойдет на wgvps. Но в этом случае клиентские программы, +на которые осуществляется проброс портов, не будут видеть реальный IP подключенца. + +--/etc/firewall.user---------------------------- +. /opt/zapret/init.d/openwrt/functions + +create_ipset no-update + +network_find_wan4_all wan_iface +for ext_iface in $wan_iface; do + network_get_device DEVICE $ext_iface + ipt OUTPUT -t mangle -o $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 + ipt OUTPUT -t mangle -o $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 + ipt OUTPUT -t mangle -o $DEVICE -j MARK --set-mark 0x1000/0x1000 +done + +network_get_device DEVICE lan +ipt PREROUTING -t mangle -i $DEVICE -p tcp --dport 443 -m set --match-set zapret dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 +ipt PREROUTING -t mangle -i $DEVICE -m set --match-set ipban dst -m set ! --match-set nozapret dst -j MARK --set-mark 0x800/0x800 + +# do masquerade for OUTPUT to ensure correct outgoing address +ipt postrouting_tunvps_rule -t nat -m mark --mark 0x1000/0x1000 -j MASQUERADE + +# incoming from wgvps +network_get_device DEVICE wgvps +ipt PREROUTING -t mangle ! -i $DEVICE -j CONNMARK --restore-mark --nfmask 0x800 --ctmask 0x800 +ipt PREROUTING -t mangle -i $DEVICE -m conntrack --ctstate NEW -j CONNMARK --set-mark 0x800/0x800 +------------------------------------------------ + +# /etc/init.d/firewall restart + +Вариант nftables : + +--/etc/firewall.zapret.hook.post_up---------------------------- +#!/bin/sh + +ZAPRET_NFT_TABLE=zapret +DEVICE=wgvps + +cat << EOF | nft -f - 2>/dev/null + delete chain inet $ZAPRET_NFT_TABLE my_output + delete chain inet $ZAPRET_NFT_TABLE my_prerouting + delete chain inet $ZAPRET_NFT_TABLE my_nat +EOF + +cat << EOF | nft -f - + add chain inet $ZAPRET_NFT_TABLE my_output { type route hook output priority mangle; } + flush chain inet $ZAPRET_NFT_TABLE my_output + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_output oifname @wanif meta mark set mark or 0x1000 + + add chain inet $ZAPRET_NFT_TABLE my_prerouting { type filter hook prerouting priority mangle; } + flush chain inet $ZAPRET_NFT_TABLE my_prerouting + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname $DEVICE ct state new ct mark set ct mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname != $DEVICE meta mark set ct mark and 0x800 + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif ip daddr @ipban ip daddr != @nozapret meta mark set mark or 0x800 + add rule inet $ZAPRET_NFT_TABLE my_prerouting iifname @lanif tcp dport 443 ip daddr @zapret ip daddr != @nozapret meta mark set mark or 0x800 + + add chain inet $ZAPRET_NFT_TABLE my_nat { type nat hook postrouting priority 100 ; } + flush chain inet $ZAPRET_NFT_TABLE my_nat + add rule inet $ZAPRET_NFT_TABLE my_nat oifname $DEVICE mark and 0x1000 == 0x1000 masquerade +EOF +------------------------------------------------ + +# /etc/init.d/zapret restart_fw + +К сожалению, здесь возможности nftables немного хромают. Полноценного эквивалента CONNMARK --restore-mark --nfmask +не существует. Оригинал iptables предполагал копирование одного бита 0x800 из connmark в mark. +Лучшее, что можно сделать в nftables, это копирование одного бита с занулением всех остальных. +Сложные выражения типа "meta mark set mark and ~0x800 or (ct mark and 0x800)" nft не понимает. +Об этом же говорит попытка перевода через iptables-translate. + +Сейчас уже можно с vpn сервера пингануть ip адрес внутри локалки клиента. Пинги должны ходить. + +Отсутствие двойного NAT значительно облегчает проброс портов с внешнего IP vpn сервера в локалку какого-либо клиента. +Для этого надо выполнить 2 действия : добавить разрешение в фаервол на клиенте и сделать dnat на сервере. +Пример форварда портов 5001 и 5201 на 192.168.2.2 : + +--/etc/config/firewall-------------------------- +config rule + option target 'ACCEPT' + option src 'tunvps' + option dest 'lan' + option proto 'tcp udp' + option dest_port '5001 5201' + option dest_ip '192.168.2.2' + option name 'IPERF' +------------------------------------------------ + +# /etc/init.d/firewall restart +# /etc/init.d/zapret restart_fw + +--/etc/network/interfaces.d/wgvps------------- + post-up iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 + post-up iptables -t nat -A PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 + post-down iptables -t nat -D PREROUTING -i eth0 -p tcp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 + post-down iptables -t nat -D PREROUTING -i eth0 -p udp -m multiport --dports 5001,5201 -j DNAT --to-destination 192.168.2.2 +---------------------------------------------- + +# ifdown wgvps ; ifup wgvps + +Пример приведен для iperf и iperf3, чтобы показать как пробрасывать несколько портов tcp+udp с минимальным количеством команд. +Проброс tcp и udp порта так же необходим для полноценной работы bittorrent клиента, чтобы работали входящие. + +--- Как мне отправлять на vpn весь трафик с bittorrent ? --- + +Можно поступить так : посмотрите порт в настройках torrent клиента, убедитесь, что не поставлено "случайный порт", +добавьте на роутер правило маркировки по порту источника. +Но мне предпочтительно иное решение. На windows есть замечательная возможность +прописать правило установки поля качества обслуживания в заголовках ip пакетов в зависимости от процесса-источника. +Для windows 7/2008R2 необходимо будет установить ключик реестра и перезагрузить комп : +# reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\QoS /v "Do not use NLA" /t REG_SZ /d "1" +Редактировать политику можно в : gpedit.msc -> Computer Configuration -> Windows Settings -> Policy-based QoS +На win 10 ключик реестра больше не работает, правила qos в gpedit применяются только для профиля домена. +Необходимо пользоваться командой powershell New-NetQosPolicy. Гуглите хелп по ней. Пример : +# powershell New-NetQosPolicy -Name "torrent" -AppPathNameMatchCondition "qbittorrent.exe" -DSCPAction 1 +Однозначно требуется проверка в wireshark или netmon успешности установки поля dscp. Если там по-прежнему 0x00, +значит что-то не сработало. 0x04 означает DSCP=1 (dscp находится в старших 6 битах). + +На роутере в фаер прописываем правило : + +--/etc/config/firewall-------------------------- +config rule + option target 'MARK' + option src 'lan' + option proto 'all' + option extra '-m dscp --dscp 1' + option name 'route-dscp-1' + option set_mark '0x0800/0x0800' +------------------------------------------------ + +# /etc/init.d/firewall restart + +Теперь все с полем dscp "1" идет на vpn. Клиент сам решает какой трафик ему нужно забрасывать +на vpn, перенастраивать роутер не нужно. +На linux клиенте проще всего будет выставлять dscp в iptables по номеру порта источника : + +--/etc/rc.local--------------------------------- +iptables -A OUTPUT -t mangle -p tcp --sport 23444 -j DSCP --set-dscp 1 +iptables -A OUTPUT -t mangle -p udp --sport 23444 -j DSCP --set-dscp 1 +------------------------------------------------ + +можно привязываться к pid процесса, но тогда нужно перенастраивать iptables при каждом перезапуске +торент клиента, это требует рута, и все становится очень неудобно. + + +--- Автоматизация проброса портов через miniupnd --- + +Да, его тоже можно использовать на vps. Только как всегда есть нюансы. + +miniupnpd поддерживает 3 протокола IGD : upnp,nat-pmp и pcp. +upnp и pcp работают через мультикаст, который не пройдет через wgvps. +nat-pmp работает через посылку специальных сообщений на udp:5351 на default gateway. +Обычно их обслуживает miniupnpd на роутере. При создании lease miniupnpd добавляет +правила для проброса портов в цепочку iptables MINIUPNPD, при потери lease - убирает. + +udp:5351 можно перенаправить на vpn сервер через DNAT, чтобы их обрабатывал miniupnpd там. +Но вы должны иметь однозначный критерий перенаправления. +Если вы решили завернуть на vpn все, то проблем нет. Пробрасываем udp:5351 безусловно. +Если у вас идет перенаправление только с торрент, то необходимо к условию перенаправления +добавить условия, выделяющие torrent трафик из прочего. Или по dscp, или по sport. +Чтобы запросы от остальных программ обрабатывались miniupnpd на роутере. +Если какая-то программа создаст lease не там, где нужно, то входящий трафик до нее не дойдет. + +На роутере стоит запретить протокол upnp, чтобы торрент клиент не удовлетворился запросом, +обслуженным по upnp на роутере, и пытался использовать nat-pmp. + +--/etc/config/upnp-------------------------- +config upnpd 'config' + ..... + option enable_upnp '0' +------------------------------------------------ + +/etc/init.d/miniupnpd restart + +Делаем проброс порта на роутере. +Для простоты изложения будем считать, что на vpn у нас завернут весь трафик. +Если это не так, то следует добавить фильтр в "config redirect". +Заодно выделяем диапазон портов для торрент клиентов. +Порт в торент клиенте следует прописать какой-то из этого диапазона. + +------------------------------------------------ +config redirect + option enabled '1' + option target 'DNAT' + option src 'lan' + option dest 'tunvps' + option proto 'udp' + option src_dport '5351' + option dest_ip '192.168.254.1' + option dest_port '5351' + option name 'NAT-PMP' + option reflection '0' +config rule + option enabled '1' + option target 'ACCEPT' + option src 'tunvps' + option dest 'lan' + option name 'tunvps-torrent' + option dest_port '28000-28009' +------------------------------------------------ + +/etc/init.d/firewall reload + + +На сервере : + +apt install miniupnpd + +--- /etc/miniupnpd/miniupnpd.conf -------- +enable_natpmp=yes +enable_upnp=no +lease_file=/var/log/upnp.leases +system_uptime=yes +clean_ruleset_threshold=10 +clean_ruleset_interval=600 +force_igd_desc_v1=no +listening_ip=192.168.254.1/16 +ext_ifname=eth0 +------------------------------------------ + +systemctl restart miniupnpd + +listening_ip прописан именно таким образом, чтобы обозначить диапазон разрешенных IP. +С других IP он не будет обрабатывать запросы на редирект. +В ext_ifname впишите название inet интерфейса на сервере. + +Запускаем торрент клиент. Попутно смотрим в tcpdump весь путь udp:5351 до сервера и обратно. +Смотрим syslog сервера на ругань от miniupnpd. +Если все ок, то можем проверить редиректы : iptables -t nat -nL MINIUPNPD +С какого-нибудь другого хоста (не vpn сервер, не ваше подключение) можно попробовать telnet-нуться на проброшенный порт. +Должно установиться соединение. Или качайте торент и смотрите в пирах флаг "I" (incoming). +Если "I" есть и по ним идет закачка, значит все в порядке. + +ОСОБЕННОСТЬ НОВЫХ DEBIAN : по умолчанию используются iptables-nft. miniupnpd работает с iptables-legacy. +ЛЕЧЕНИЕ : update-alternatives --set iptables /usr/sbin/iptables-legacy diff --git a/files/fake/dht_find_node.bin b/files/fake/dht_find_node.bin new file mode 100644 index 00000000..ba9117de Binary files /dev/null and b/files/fake/dht_find_node.bin differ diff --git a/files/fake/dht_get_peers.bin b/files/fake/dht_get_peers.bin new file mode 100644 index 00000000..916bc10e Binary files /dev/null and b/files/fake/dht_get_peers.bin differ diff --git a/files/fake/dtls_clienthello_w3_org.bin b/files/fake/dtls_clienthello_w3_org.bin new file mode 100644 index 00000000..8d90faf5 Binary files /dev/null and b/files/fake/dtls_clienthello_w3_org.bin differ diff --git a/files/fake/http_iana_org.bin b/files/fake/http_iana_org.bin new file mode 100644 index 00000000..ce1d4207 Binary files /dev/null and b/files/fake/http_iana_org.bin differ diff --git a/files/fake/quic_initial_facebook_com.bin b/files/fake/quic_initial_facebook_com.bin new file mode 100644 index 00000000..5d21a592 Binary files /dev/null and b/files/fake/quic_initial_facebook_com.bin differ diff --git a/files/fake/quic_initial_facebook_com_quiche.bin b/files/fake/quic_initial_facebook_com_quiche.bin new file mode 100644 index 00000000..84dceabb Binary files /dev/null and b/files/fake/quic_initial_facebook_com_quiche.bin differ diff --git a/files/fake/quic_initial_rr2---sn-gvnuxaxjvh-o8ge_googlevideo_com.bin b/files/fake/quic_initial_rr2---sn-gvnuxaxjvh-o8ge_googlevideo_com.bin new file mode 100644 index 00000000..d65c0e6c Binary files /dev/null and b/files/fake/quic_initial_rr2---sn-gvnuxaxjvh-o8ge_googlevideo_com.bin differ diff --git a/files/fake/quic_initial_rutracker_org.bin b/files/fake/quic_initial_rutracker_org.bin new file mode 100644 index 00000000..25b3edcd Binary files /dev/null and b/files/fake/quic_initial_rutracker_org.bin differ diff --git a/files/fake/quic_initial_rutracker_org_kyber_1.bin b/files/fake/quic_initial_rutracker_org_kyber_1.bin new file mode 100644 index 00000000..9a16d987 Binary files /dev/null and b/files/fake/quic_initial_rutracker_org_kyber_1.bin differ diff --git a/files/fake/quic_initial_rutracker_org_kyber_2.bin b/files/fake/quic_initial_rutracker_org_kyber_2.bin new file mode 100644 index 00000000..f3a721b7 Binary files /dev/null and b/files/fake/quic_initial_rutracker_org_kyber_2.bin differ diff --git a/files/fake/quic_initial_vk_com.bin b/files/fake/quic_initial_vk_com.bin new file mode 100644 index 00000000..ea0c77cd Binary files /dev/null and b/files/fake/quic_initial_vk_com.bin differ diff --git a/files/fake/quic_initial_www_google_com.bin b/files/fake/quic_initial_www_google_com.bin new file mode 100644 index 00000000..80a07cc8 Binary files /dev/null and b/files/fake/quic_initial_www_google_com.bin differ diff --git a/files/fake/quic_short_header.bin b/files/fake/quic_short_header.bin new file mode 100644 index 00000000..7562c0a8 Binary files /dev/null and b/files/fake/quic_short_header.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 00000000..d9e0f4a8 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 00000000..e641d731 Binary files /dev/null and b/files/fake/tls_clienthello_iana_org.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 00000000..9ccc5fc4 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 00000000..59571bb1 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 00000000..ec908c2b 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 00000000..92c639e3 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 00000000..c740462f 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 00000000..80558634 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 00000000..c4597de7 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 00000000..06d74050 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 00000000..65f57c2e 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 00000000..a64a5a93 Binary files /dev/null and b/files/fake/zero_512.bin differ diff --git a/files/huawei/E8372/run-zapret-hostlist b/files/huawei/E8372/run-zapret-hostlist new file mode 100755 index 00000000..7f37d580 Binary files /dev/null and b/files/huawei/E8372/run-zapret-hostlist differ diff --git a/files/huawei/E8372/run-zapret-ip b/files/huawei/E8372/run-zapret-ip new file mode 100755 index 00000000..803e9844 Binary files /dev/null and b/files/huawei/E8372/run-zapret-ip differ diff --git a/files/huawei/E8372/unfuck_nfqueue.ko b/files/huawei/E8372/unfuck_nfqueue.ko new file mode 100644 index 00000000..c24ce5eb Binary files /dev/null and b/files/huawei/E8372/unfuck_nfqueue.ko differ diff --git a/files/huawei/E8372/unzapret b/files/huawei/E8372/unzapret new file mode 100755 index 00000000..f040dfc5 Binary files /dev/null and b/files/huawei/E8372/unzapret differ diff --git a/files/huawei/E8372/unzapret-ip b/files/huawei/E8372/unzapret-ip new file mode 100755 index 00000000..ccb74258 Binary files /dev/null and b/files/huawei/E8372/unzapret-ip differ diff --git a/files/huawei/E8372/zapret b/files/huawei/E8372/zapret new file mode 100755 index 00000000..f19eed35 Binary files /dev/null and b/files/huawei/E8372/zapret differ diff --git a/files/huawei/E8372/zapret-ip b/files/huawei/E8372/zapret-ip new file mode 100755 index 00000000..9e70fac9 Binary files /dev/null and b/files/huawei/E8372/zapret-ip differ diff --git a/init.d/macos/custom.d.examples/10-inherit-tpws b/init.d/macos/custom.d.examples/10-inherit-tpws new file mode 100644 index 00000000..a4c08c53 --- /dev/null +++ b/init.d/macos/custom.d.examples/10-inherit-tpws @@ -0,0 +1,18 @@ +# this custom script applies tpws mode as it would be with MODE=tpws + +OVERRIDE=tpws + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_daemons $1 +} +zapret_custom_firewall_v4() +{ + MODE_OVERRIDE=$OVERRIDE pf_anchor_zapret_v4 +} +zapret_custom_firewall_v6() +{ + MODE_OVERRIDE=$OVERRIDE pf_anchor_zapret_v6 +} diff --git a/init.d/macos/custom.d.examples/10-inherit-tpws-socks b/init.d/macos/custom.d.examples/10-inherit-tpws-socks new file mode 100644 index 00000000..bdcda12e --- /dev/null +++ b/init.d/macos/custom.d.examples/10-inherit-tpws-socks @@ -0,0 +1,18 @@ +# this custom script applies tpws-socks mode as it would be with MODE=tpws-socks + +OVERRIDE=tpws-socks + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_daemons $1 +} +zapret_custom_firewall_v4() +{ + MODE_OVERRIDE=$OVERRIDE pf_anchor_zapret_v4 +} +zapret_custom_firewall_v6() +{ + MODE_OVERRIDE=$OVERRIDE pf_anchor_zapret_v6 +} diff --git a/init.d/macos/custom.d.examples/50-extra-tpws b/init.d/macos/custom.d.examples/50-extra-tpws new file mode 100644 index 00000000..dfe6d100 --- /dev/null +++ b/init.d/macos/custom.d.examples/50-extra-tpws @@ -0,0 +1,30 @@ +# this script is an example describing how to run tpws on a custom port + +DNUM=100 +TPPORT_MY=${TPPORT_MY:-987} +TPWS_OPT_MY=${TPWS_OPT_MY:-987} +TPWS_OPT_SUFFIX_MY="${TPWS_OPT_SUFFIX_MY:-}" +DPORTS_MY=${DPORTS_MY:-20443,20444,30000-30009} + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + local opt="--user=root --port=$TPPORT_MY" + tpws_apply_binds opt + opt="$opt $TPWS_OPT_MY" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX_MY" + do_daemon $1 $DNUM "$TPWS" "$opt" +} + +# custom firewall functions echo rules for zapret-v4 and zapret-v6 anchors +# they come after automated table definitions. so you can use ... + +zapret_custom_firewall_v4() +{ + pf_anchor_zapret_v4_tpws $TPPORT_MY $(replace_char - : $DPORTS_MY) +} +zapret_custom_firewall_v6() +{ + pf_anchor_zapret_v6_tpws $TPPORT_MY $(replace_char - : $DPORTS_MY) +} diff --git a/init.d/macos/custom.d/.keep b/init.d/macos/custom.d/.keep new file mode 100644 index 00000000..e69de29b diff --git a/init.d/macos/functions b/init.d/macos/functions new file mode 100644 index 00000000..d004dc2b --- /dev/null +++ b/init.d/macos/functions @@ -0,0 +1,211 @@ +# init script functions library for macos + +ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/pf.sh" +. "$ZAPRET_BASE/common/list.sh" +. "$ZAPRET_BASE/common/custom.sh" +CUSTOM_DIR="$ZAPRET_RW/init.d/macos" + +IPSET_DIR=$ZAPRET_BASE/ipset +. "$IPSET_DIR/def.sh" + +PIDDIR=/var/run +[ -n "$TPPORT" ] || TPPORT=988 +[ -n "$WS_USER" ] || WS_USER=daemon +TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" +TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" +[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" + +CUSTOM_SCRIPT="$ZAPRET_BASE/init.d/macos/custom" +[ -f "$CUSTOM_SCRIPT" ] && . "$CUSTOM_SCRIPT" + +run_daemon() +{ + # $1 - daemon number : 1,2,3,... + # $2 - daemon + # $3 - daemon args + # use $PIDDIR/$DAEMONBASE$1.pid as pidfile + local DAEMONBASE="$(basename "$2")" + local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" + local ARGS="--daemon --pidfile=$PIDFILE $3" + [ -f "$PIDFILE" ] && pgrep -qF "$PIDFILE" && { + echo Already running $1: $2 + return 0 + } + echo "Starting daemon $1: $2 $ARGS" + "$2" $ARGS +} +stop_daemon() +{ + # $1 - daemon number : 1,2,3,... + # $2 - daemon + # use $PIDDIR/$DAEMONBASE$1.pid as pidfile + + local PID + local DAEMONBASE="$(basename "$2")" + local PIDFILE="$PIDDIR/$DAEMONBASE$1.pid" + [ -f "$PIDFILE" ] && read PID <"$PIDFILE" + [ -n "$PID" ] && { + echo "Stopping daemon $1: $2 (PID=$PID)" + kill $PID + rm -f "$PIDFILE" + } + return 0 +} +do_daemon() +{ + # $1 - 1 - run, 0 - stop + on_off_function run_daemon stop_daemon "$@" +} + +tpws_apply_binds() +{ + local o + [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" + [ "$DISABLE_IPV6" = "1" ] || { + for i in lo0 $IFACE_LAN; do + o="$o --bind-iface6=$i --bind-linklocal=force $TPWS_WAIT" + done + } + eval $1="\"\$$1 $o\"" +} +tpws_apply_socks_binds() +{ + local o + + [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" + + for lan in $IFACE_LAN; do + [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" + done + eval $1="\"\$$1 $o\"" +} + +wait_interface_ll() +{ + echo waiting for an ipv6 link local address on $1 ... + "$TPWS" --bind-wait-only --bind-iface6=$1 --bind-linklocal=force $TPWS_WAIT +} +wait_lan_ll() +{ + [ "$DISABLE_IPV6" != "1" ] && { + for lan in $IFACE_LAN; do + wait_interface_ll $lan >&2 || { + echo "wait interface failed on $lan" + return 1 + } + done + } + return 0 +} +get_ipv6_linklocal() +{ + ifconfig $1 | sed -nEe 's/^.*inet6 (fe80:[a-f0-9:]+).*/\1/p' +} + + +zapret_do_firewall() +{ + # $1 - 1 - add, 0 - del + + [ "$1" = 1 -a -n "$INIT_FW_PRE_UP_HOOK" ] && $INIT_FW_PRE_UP_HOOK + [ "$1" = 0 -a -n "$INIT_FW_PRE_DOWN_HOOK" ] && $INIT_FW_PRE_DOWN_HOOK + + case "${MODE_OVERRIDE:-$MODE}" in + tpws|filter|custom) + if [ "$1" = "1" ] ; then + pf_anchor_root || return 1 + pf_anchors_create + pf_anchors_load || return 1 + pf_enable + else + pf_anchors_clear + fi + ;; + esac + + [ "$1" = 1 -a -n "$INIT_FW_POST_UP_HOOK" ] && $INIT_FW_POST_UP_HOOK + [ "$1" = 0 -a -n "$INIT_FW_POST_DOWN_HOOK" ] && $INIT_FW_POST_DOWN_HOOK + + return 0 +} +zapret_apply_firewall() +{ + zapret_do_firewall 1 "$@" +} +zapret_unapply_firewall() +{ + zapret_do_firewall 0 "$@" +} +zapret_restart_firewall() +{ + zapret_unapply_firewall "$@" + zapret_apply_firewall "$@" +} + + + +zapret_do_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt + + case "${MODE_OVERRIDE:-$MODE}" in + tpws) + [ "$1" = "1" ] && [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && { + echo "both ipv4 and ipv6 are disabled. nothing to do" + return 0 + } + # MacOS requires root. kernel hardcoded requirement for /dev/pf ioctls + opt="--user=root --port=$TPPORT" + tpws_apply_binds opt + opt="$opt $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + do_daemon $1 1 "$TPWS" "$opt" + ;; + tpws-socks) + [ "$1" = "1" ] && [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && { + echo "both ipv4 and ipv6 are disabled. nothing to do" + return 0 + } + opt="--socks --user=$WS_USER --port=$TPPORT" + tpws_apply_socks_binds opt + opt="$opt $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + do_daemon $1 1 "$TPWS" "$opt" + ;; + filter) + ;; + custom) + custom_runner zapret_custom_daemons $1 + ;; + *) + echo "unsupported MODE=$MODE" + return 1 + ;; + esac + + return 0 +} +zapret_run_daemons() +{ + zapret_do_daemons 1 "$@" +} +zapret_stop_daemons() +{ + zapret_do_daemons 0 "$@" +} +zapret_restart_daemons() +{ + zapret_stop_daemons "$@" + zapret_run_daemons "$@" +} diff --git a/init.d/macos/zapret b/init.d/macos/zapret new file mode 100755 index 00000000..17f7897b --- /dev/null +++ b/init.d/macos/zapret @@ -0,0 +1,51 @@ +#!/bin/sh + +EXEDIR="$(dirname "$0")" +ZAPRET_BASE="$EXEDIR/../.." +ZAPRET_BASE="$(cd "$ZAPRET_BASE"; pwd)" + +. "$EXEDIR/functions" + +case "$1" in + start) + zapret_run_daemons + [ "$INIT_APPLY_FW" != "1" ] || zapret_apply_firewall + ;; + stop) + [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall + zapret_stop_daemons + ;; + restart) + "$0" stop + "$0" start + ;; + + start-fw|start_fw) + zapret_apply_firewall + ;; + stop-fw|stop_fw) + zapret_unapply_firewall + ;; + restart-fw|stop_fw) + zapret_restart_firewall + ;; + reload-fw-tables|reload_fw_tables) + pf_table_reload + ;; + + start-daemons|start_daemons) + zapret_run_daemons + ;; + stop-daemons|stop_daemons) + zapret_stop_daemons + ;; + restart-daemons|restart_daemons) + zapret_restart_daemons + ;; + + *) + N="$SCRIPT/$NAME" + echo "Usage: $N {start|stop|start-fw|stop-fw|restart-fw|reload-fw-tables|start-daemons|stop-daemons|restart-daemons}" >&2 + exit 1 + ;; +esac diff --git a/init.d/macos/zapret.plist b/init.d/macos/zapret.plist new file mode 100644 index 00000000..747d69bc --- /dev/null +++ b/init.d/macos/zapret.plist @@ -0,0 +1,17 @@ + + + + + Label + zapret + LaunchOnlyOnce + + ProgramArguments + + /opt/zapret/init.d/macos/zapret + start + + RunAtLoad + + + diff --git a/init.d/openrc/zapret b/init.d/openrc/zapret new file mode 100755 index 00000000..3a1ca586 --- /dev/null +++ b/init.d/openrc/zapret @@ -0,0 +1,69 @@ +#!/sbin/openrc-run + +# zapret openrc to sysv adapter +# on some systems (alpine) for unknown reason non-openrc-run scripts are not started from /etc/init.d + +EXEDIR=$(dirname "$RC_SERVICE") +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE="$EXEDIR/../.." +ZAPRET_INIT="$ZAPRET_BASE/init.d/sysv/zapret" + +extra_commands="start_fw stop_fw restart_fw start_daemons stop_daemons restart_daemons reload_ifsets list_ifsets list_table" +description="extra commands :" +description_stop_fw="Stop zapret firewall" +description_start_fw="Start zapret firewall" +description_restart_fw="Restart zapret firewall" +description_reload_ifsets="Reload interface lists (nftables only)" +description_list_ifsets="Display interface lists (nftables only)" +description_list_table="Display zapret nftable (nftables only)" +description_stop_daemons="Stop zapret daemons only" +description_start_daemons="Start zapret daemons only" +description_restart_daemons="Restart zapret firewall only" + +depend() { + rc-service -e networking && need networking +} +start() +{ + "$ZAPRET_INIT" start +} +stop() +{ + "$ZAPRET_INIT" stop +} +start_fw() +{ + "$ZAPRET_INIT" start_fw +} +stop_fw() +{ + "$ZAPRET_INIT" stop_fw +} +restart_fw() +{ + "$ZAPRET_INIT" restart_fw +} +start_daemons() +{ + "$ZAPRET_INIT" start_daemons +} +stop_daemons() +{ + "$ZAPRET_INIT" stop_daemons +} +restart_daemons() +{ + "$ZAPRET_INIT" restart_daemons +} +reload_ifsets() +{ + "$ZAPRET_INIT" reload_ifsets +} +list_ifsets() +{ + "$ZAPRET_INIT" list_ifsets +} +list_table() +{ + "$ZAPRET_INIT" list_table +} diff --git a/init.d/openwrt/90-zapret b/init.d/openwrt/90-zapret new file mode 100644 index 00000000..8cb05f56 --- /dev/null +++ b/init.d/openwrt/90-zapret @@ -0,0 +1,63 @@ +#!/bin/sh + +ZAPRET=/etc/init.d/zapret + +check_lan() +{ + IS_LAN= + [ -n "$OPENWRT_LAN" ] || OPENWRT_LAN=lan + for lan in $OPENWRT_LAN; do + [ "$INTERFACE" = "$lan" ] && { + IS_LAN=1 + break + } + done +} +check_need_to_reload_tpws6() +{ + # tpws6 dnat target nft map can only be reloaded within firewall apply procedure + # interface ifsets (wanif, wanif6, lanif) can be reloaded independently + check_lan + RELOAD_TPWS6= + [ "$ACTION" = "ifup" -a "$DISABLE_IPV6" != 1 -a -n "$IS_LAN" ] && [ "$MODE" = "tpws" -o "$MODE" = "custom" ] && RELOAD_TPWS6=1 +} + + +[ -n "$INTERFACE" ] && [ "$ACTION" = ifup -o "$ACTION" = ifdown ] && [ -x "$ZAPRET" ] && "$ZAPRET" enabled && { + SCRIPT=$(readlink "$ZAPRET") + if [ -n "$SCRIPT" ]; then + EXEDIR=$(dirname "$SCRIPT") + ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") + else + ZAPRET_BASE=/opt/zapret + fi + ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} + ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} + . "$ZAPRET_CONFIG" + + check_need_to_reload_tpws6 + [ -n "$RELOAD_TPWS6" ] && { + logger -t zapret restarting daemons due to $ACTION of $INTERFACE to update tpws6 dnat target + "$ZAPRET" restart_daemons + } + . "$ZAPRET_BASE/common/base.sh" + . "$ZAPRET_BASE/common/fwtype.sh" + linux_fwtype + case "$FWTYPE" in + nftables) + if [ -n "$RELOAD_TPWS6" ] ; then + logger -t zapret reloading nftables due to $ACTION of $INTERFACE to update tpws6 dnat target + "$ZAPRET" restart_fw + else + logger -t zapret reloading nftables ifsets due to $ACTION of $INTERFACE + "$ZAPRET" reload_ifsets + fi + ;; + iptables) + openwrt_fw3 || { + logger -t zapret reloading iptables due to $ACTION of $INTERFACE + "$ZAPRET" restart_fw + } + ;; + esac +} diff --git a/init.d/openwrt/custom.d.examples/10-inherit-nfqws b/init.d/openwrt/custom.d.examples/10-inherit-nfqws new file mode 100644 index 00000000..b1564024 --- /dev/null +++ b/init.d/openwrt/custom.d.examples/10-inherit-nfqws @@ -0,0 +1,22 @@ +# this custom script applies nfqws mode as it would be with MODE=nfqws + +OVERRIDE=nfqws + +zapret_custom_daemons() +{ + # stop logic is managed by procd + + MODE_OVERRIDE=$OVERRIDE start_daemons_procd +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/openwrt/custom.d.examples/10-inherit-tpws b/init.d/openwrt/custom.d.examples/10-inherit-tpws new file mode 100644 index 00000000..ae2bdf93 --- /dev/null +++ b/init.d/openwrt/custom.d.examples/10-inherit-tpws @@ -0,0 +1,22 @@ +# this custom script applies tpws mode as it would be with MODE=tpws + +OVERRIDE=tpws + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE start_daemons_procd +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/openwrt/custom.d.examples/10-inherit-tpws-socks b/init.d/openwrt/custom.d.examples/10-inherit-tpws-socks new file mode 100644 index 00000000..8336b729 --- /dev/null +++ b/init.d/openwrt/custom.d.examples/10-inherit-tpws-socks @@ -0,0 +1,22 @@ +# this custom script applies tpws-socks mode as it would be with MODE=tpws-socks + +OVERRIDE=tpws-socks + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE start_daemons_procd +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/openwrt/custom.d.examples/50-dht4all b/init.d/openwrt/custom.d.examples/50-dht4all new file mode 100644 index 00000000..31266586 --- /dev/null +++ b/init.d/openwrt/custom.d.examples/50-dht4all @@ -0,0 +1,39 @@ +# this custom script runs desync to DHT packets with udp payload length 101..399 , without ipset/hostlist filtering +# need to add to config : NFQWS_OPT_DESYNC_DHT="--dpi-desync=fake --dpi-desync-ttl=5" + +DNUM=101 +QNUM2=$(($DNUM * 5)) + +zapret_custom_daemons() +{ + # stop logic is managed by procd + + local opt="--qnum=$QNUM2 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_DHT" + run_daemon $DNUM $NFQWS "$opt" +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f uf4 uf6 + local first_packet_only="$ipt_connbytes 1:1" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + f='-p udp -m length --length 109:407 -m u32 --u32' + uf4='0>>22&0x3C@8>>16=0x6431' + uf6='48>>16=0x6431' + fw_nfqws_post $1 "$f $uf4 $desync $first_packet_only" "$f $uf6 $desync $first_packet_only" $QNUM2 + +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packet_only="$nft_connbytes 1" + local desync="mark and $DESYNC_MARK == 0" + + f="meta length 109-407 meta l4proto udp @th,64,16 0x6431" + nft_fw_nfqws_post "$f $desync $first_packet_only" "$f $desync $first_packet_only" $QNUM2 +} + diff --git a/init.d/openwrt/custom.d.examples/50-discord b/init.d/openwrt/custom.d.examples/50-discord new file mode 100644 index 00000000..92d1400f --- /dev/null +++ b/init.d/openwrt/custom.d.examples/50-discord @@ -0,0 +1,69 @@ +# this custom script in addition to MODE=nfqws runs desync of some udp packets to discord subnets +# idea taken from community. not tested and not optimized by author. + +# can override in config : +NFQWS_OPT_DESYNC_DISCORD="${NFQWS_OPT_DESYNC_DISCORD:---dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-any-protocol}" +DISCORD_PORTS=${DISCORD_PORTS:-50000-65535} +DISCORD_SUBNETS="${DISCORD_SUBNETS:-5.200.14.249 18.165.140.0/25 23.227.38.74 34.0.48.0/24 34.0.49.64/26 34.0.50.0/25 34.0.51.0/24 34.0.52.0/22 34.0.56.0/23 34.0.59.0/24 34.0.60.0/24 34.0.62.128/25 34.0.63.228 34.0.64.0/23 34.0.66.130 34.0.82.140 34.0.129.128/25 34.0.130.0/24 34.0.131.130 34.0.132.139 34.0.133.75 34.0.134.0/24 34.0.135.251 34.0.136.51 34.0.137.0/24 34.0.139.0/24 34.0.140.0/23 34.0.142.0/25 34.0.144.0/23 34.0.146.0/24 34.0.148.25 34.0.149.101 34.0.151.0/25 34.0.153.0/24 34.0.155.0/24 34.0.156.101 34.0.157.0/25 34.0.158.247 34.0.159.188 34.0.192.0/25 34.0.193.0/24 34.0.194.0/24 34.0.195.172 34.0.196.200/29 34.0.197.81 34.0.198.25 34.0.199.0/24 34.0.200.0/24 34.0.201.81 34.0.202.34 34.0.203.0/24 34.0.204.0/23 34.0.206.0/25 34.0.207.0/25 34.0.208.195 34.0.209.0/24 34.0.210.20 34.0.211.0/26 34.0.212.0/24 34.0.213.64/26 34.0.215.128/25 34.0.216.238 34.0.217.0/24 34.0.218.83 34.0.220.103 34.0.221.0/24 34.0.222.193 34.0.223.68 34.0.227.0/24 34.0.240.0/21 34.0.248.0/23 34.0.250.0/24 34.0.251.0/25 34.1.216.0/24 34.1.221.166 35.207.64.0/23 35.207.67.116 35.207.71.0/24 35.207.72.32 35.207.73.0/24 35.207.74.0/24 35.207.75.128/25 35.207.76.128/26 35.207.77.0/24 35.207.78.129 35.207.79.0/24 35.207.80.76 35.207.81.248/30 35.207.82.0/23 35.207.84.0/24 35.207.85.160 35.207.86.41 35.207.87.184 35.207.89.188 35.207.91.146 35.207.92.230 35.207.95.0/24 35.207.97.174 35.207.99.134 35.207.100.64/26 35.207.101.130 35.207.103.64/26 35.207.104.0/24 35.207.106.128/26 35.207.107.19 35.207.108.192/27 35.207.109.185 35.207.110.0/24 35.207.111.174 35.207.114.16 35.207.115.163 35.207.116.51 35.207.117.0/24 35.207.121.204 35.207.122.0/25 35.207.124.145 35.207.125.116 35.207.126.30 35.207.129.0/24 35.207.131.128/27 35.207.132.247 35.207.135.147 35.207.136.69 35.207.137.0/24 35.207.139.0/24 35.207.140.241 35.207.141.119 35.207.142.0/24 35.207.143.96/27 35.207.144.0/25 35.207.145.0/24 35.207.146.89 35.207.147.0/24 35.207.149.0/24 35.207.150.0/24 35.207.151.61 35.207.153.117 35.207.154.0/24 35.207.155.128/25 35.207.156.254 35.207.157.7 35.207.158.192 35.207.160.160 35.207.162.239 35.207.163.0/24 35.207.164.0/25 35.207.165.147 35.207.166.0/25 35.207.167.0/24 35.207.168.116 35.207.170.0/23 35.207.172.0/24 35.207.174.55 35.207.176.128/25 35.207.178.0/24 35.207.180.152 35.207.181.76 35.207.182.125 35.207.184.101 35.207.185.192 35.207.186.128/25 35.207.187.228 35.207.188.0/24 35.207.189.0/25 35.207.190.194 35.207.191.64/26 35.207.193.165 35.207.195.75 35.207.196.0/24 35.207.198.0/23 35.207.201.186 35.207.202.169 35.207.205.211 35.207.207.4 35.207.209.0/25 35.207.210.191 35.207.211.253 35.207.213.97 35.207.214.0/24 35.207.220.147 35.207.221.58 35.207.222.105 35.207.224.151 35.207.225.210 35.207.227.0/24 35.207.229.212 35.207.232.26 35.207.234.182 35.207.238.0/24 35.207.240.0/24 35.207.245.0/24 35.207.249.0/24 35.207.250.212 35.207.251.0/27 35.212.4.134 35.212.12.148 35.212.88.11 35.212.102.50 35.212.111.0/26 35.212.117.247 35.212.120.122 35.213.0.0/24 35.213.2.8 35.213.4.185 35.213.6.118 35.213.7.128/25 35.213.8.168 35.213.10.0/24 35.213.11.21 35.213.12.224/27 35.213.13.19 35.213.14.217 35.213.16.67 35.213.17.235 35.213.23.166 35.213.25.164 35.213.26.62 35.213.27.252 35.213.32.0/24 35.213.33.74 35.213.34.204 35.213.37.81 35.213.38.186 35.213.39.253 35.213.42.0/24 35.213.43.79 35.213.45.0/24 35.213.46.136 35.213.49.17 35.213.50.0/24 35.213.51.213 35.213.52.0/25 35.213.53.0/24 35.213.54.0/24 35.213.56.0/25 35.213.59.0/24 35.213.61.58 35.213.65.0/24 35.213.67.0/24 35.213.68.192/26 35.213.70.151 35.213.72.128/25 35.213.73.245 35.213.74.131 35.213.78.0/24 35.213.79.137 35.213.80.0/25 35.213.83.128/25 35.213.84.245 35.213.85.0/24 35.213.88.145 35.213.89.80/28 35.213.90.0/24 35.213.91.195 35.213.92.0/24 35.213.93.254 35.213.94.78 35.213.95.145 35.213.96.87 35.213.98.0/24 35.213.99.126 35.213.101.214 35.213.102.0/24 35.213.105.0/24 35.213.106.128/25 35.213.107.158 35.213.109.0/24 35.213.110.40 35.213.111.0/25 35.213.115.0/25 35.213.120.0/24 35.213.122.0/24 35.213.124.89 35.213.125.40 35.213.126.185 35.213.127.0/24 35.213.128.0/22 35.213.132.0/23 35.213.134.140 35.213.135.0/24 35.213.136.0/23 35.213.138.128/25 35.213.139.0/24 35.213.140.0/25 35.213.141.164 35.213.142.128/25 35.213.143.0/24 35.213.144.0/22 35.213.148.0/23 35.213.150.0/24 35.213.152.0/23 35.213.154.137 35.213.155.134 35.213.156.144 35.213.157.0/24 35.213.158.64/26 35.213.160.90 35.213.161.253 35.213.162.0/25 35.213.163.0/24 35.213.164.0/23 35.213.166.106 35.213.167.160/27 35.213.168.0/24 35.213.169.179 35.213.170.0/24 35.213.171.201 35.213.172.159 35.213.173.0/24 35.213.174.128/25 35.213.175.128/26 35.213.176.0/24 35.213.177.0/25 35.213.179.139 35.213.180.0/24 35.213.181.0/25 35.213.182.0/23 35.213.184.0/23 35.213.186.70 35.213.187.0/24 35.213.188.128/25 35.213.190.158 35.213.191.0/24 35.213.192.240/31 35.213.193.74 35.213.194.0/25 35.213.195.178 35.213.196.38 35.213.197.68 35.213.198.0/23 35.213.200.0/23 35.213.202.0/25 35.213.203.195 35.213.204.32/27 35.213.205.170 35.213.207.128/25 35.213.208.85 35.213.210.0/24 35.213.211.176/29 35.213.212.0/24 35.213.213.225 35.213.214.0/25 35.213.215.255 35.213.217.0/24 35.213.218.248 35.213.219.0/25 35.213.220.211 35.213.221.0/24 35.213.222.215 35.213.223.0/24 35.213.225.0/24 35.213.227.227 35.213.229.17 35.213.230.89 35.213.231.0/24 35.213.233.0/24 35.213.234.134 35.213.236.0/24 35.213.237.212 35.213.238.0/24 35.213.240.212 35.213.241.0/24 35.213.242.10 35.213.243.219 35.213.244.146 35.213.245.119 35.213.246.0/23 35.213.249.79 35.213.250.0/24 35.213.251.74 35.213.252.0/24 35.213.253.155 35.213.254.89 35.214.128.248 35.214.129.220 35.214.130.217 35.214.131.144 35.214.132.189 35.214.133.0/24 35.214.134.163 35.214.137.0/24 35.214.138.0/25 35.214.140.0/24 35.214.142.0/24 35.214.143.41 35.214.144.26 35.214.145.200 35.214.146.9 35.214.147.135 35.214.148.89 35.214.149.110 35.214.151.128/25 35.214.152.0/24 35.214.156.115 35.214.158.181 35.214.159.128/25 35.214.160.128/25 35.214.161.217 35.214.162.0/24 35.214.163.28 35.214.165.102 35.214.167.77 35.214.169.0/24 35.214.170.2 35.214.171.0/25 35.214.172.128/25 35.214.173.0/24 35.214.175.0/24 35.214.177.183 35.214.179.46 35.214.180.0/23 35.214.184.179 35.214.185.28 35.214.186.3 35.214.187.0/24 35.214.191.0/24 35.214.192.128/25 35.214.193.0/24 35.214.194.128/25 35.214.195.0/25 35.214.196.64/26 35.214.197.0/24 35.214.198.7 35.214.199.224 35.214.201.0/25 35.214.203.155 35.214.204.0/23 35.214.207.0/24 35.214.208.128/25 35.214.209.64 35.214.210.0/24 35.214.211.3 35.214.212.64/26 35.214.213.0/25 35.214.214.0/24 35.214.215.64/26 35.214.216.0/23 35.214.218.140 35.214.219.0/24 35.214.220.149 35.214.221.0/24 35.214.222.149 35.214.223.0/24 35.214.224.71 35.214.225.0/24 35.214.226.0/23 35.214.228.0/23 35.214.231.187 35.214.233.8 35.214.235.38 35.214.237.0/24 35.214.238.0/25 35.214.239.0/24 35.214.240.87 35.214.241.0/24 35.214.243.21 35.214.244.0/24 35.214.245.16/28 35.214.246.106 35.214.248.119 35.214.249.154 35.214.250.0/24 35.214.251.128/25 35.214.252.187 35.214.253.0/24 35.214.255.154 35.215.72.85 35.215.73.65 35.215.83.0 35.215.108.111 35.215.115.120 35.215.126.35 35.215.127.34 35.215.128.0/21 35.215.136.0/26 35.215.137.0/24 35.215.138.0/23 35.215.140.0/24 35.215.141.64/27 35.215.142.0/24 35.215.143.83 35.215.144.128/25 35.215.145.0/24 35.215.146.0/24 35.215.147.86 35.215.148.0/23 35.215.150.0/26 35.215.151.0/24 35.215.152.0/24 35.215.153.128/25 35.215.154.240/28 35.215.155.20 35.215.156.0/24 35.215.158.0/23 35.215.160.192/26 35.215.161.0/24 35.215.163.0/24 35.215.164.0/24 35.215.165.236 35.215.166.128/25 35.215.167.128/25 35.215.168.0/24 35.215.169.12 35.215.170.0/23 35.215.172.0/22 35.215.176.0/24 35.215.177.72 35.215.178.0/24 35.215.179.161 35.215.180.0/22 35.215.184.253 35.215.185.64/26 35.215.186.0/25 35.215.187.0/24 35.215.188.0/23 35.215.190.0/24 35.215.191.61 35.215.192.0/23 35.215.194.192/28 35.215.195.0/24 35.215.196.0/25 35.215.197.0/25 35.215.198.230 35.215.199.204 35.215.200.0/23 35.215.202.0/24 35.215.203.0/25 35.215.204.128/25 35.215.205.0/25 35.215.206.0/23 35.215.208.0/24 35.215.209.0/25 35.215.210.0/23 35.215.212.0/22 35.215.216.0/22 35.215.221.0/24 35.215.222.128/25 35.215.223.126 35.215.224.0/23 35.215.226.0/24 35.215.227.0/25 35.215.228.0/24 35.215.229.64 35.215.230.89 35.215.231.0/24 35.215.232.0/24 35.215.233.0/25 35.215.234.37 35.215.235.0/24 35.215.238.0/25 35.215.239.119 35.215.240.0/24 35.215.241.128/25 35.215.242.0/25 35.215.243.0/24 35.215.244.0/23 35.215.246.222 35.215.247.0/24 35.215.248.0/22 35.215.252.0/24 35.215.253.118 35.215.254.0/23 35.217.0.0/24 35.217.1.64/26 35.217.2.5 35.217.3.0/24 35.217.4.72 35.217.5.0/25 35.217.6.0/24 35.217.8.0/25 35.217.9.0/24 35.217.11.186 35.217.12.0/24 35.217.14.192/26 35.217.15.65 35.217.16.75 35.217.17.128/25 35.217.18.0/24 35.217.19.183 35.217.20.0/24 35.217.21.128/25 35.217.22.128/25 35.217.23.128/25 35.217.24.0/24 35.217.25.81 35.217.26.0/24 35.217.27.128/25 35.217.28.128/25 35.217.29.0/24 35.217.30.0/25 35.217.31.0/25 35.217.32.128/25 35.217.33.0/24 35.217.35.128/25 35.217.36.0/23 35.217.38.179 35.217.39.186 35.217.40.176 35.217.41.204 35.217.43.0/24 35.217.45.248 35.217.46.0/24 35.217.47.128/25 35.217.48.195 35.217.49.160/27 35.217.50.0/25 35.217.51.0/24 35.217.52.117 35.217.53.128/25 35.217.54.0/25 35.217.55.96/27 35.217.56.6 35.217.57.184 35.217.58.0/24 35.217.59.64/26 35.217.60.0/24 35.217.61.128/25 35.217.62.0/24 35.217.63.128/25 35.219.225.149 35.219.226.57 35.219.227.0/24 35.219.228.37 35.219.229.128/25 35.219.230.0/23 35.219.235.0/24 35.219.236.198 35.219.238.115 35.219.239.0/24 35.219.241.0/24 35.219.242.221 35.219.243.191 35.219.244.1 35.219.245.0/24 35.219.246.159 35.219.247.0/26 35.219.248.0/24 35.219.249.126 35.219.251.186 35.219.252.0/23 35.219.254.0/24 64.233.161.207 64.233.162.207 64.233.163.207 64.233.164.207 64.233.165.207 66.22.196.0/26 66.22.197.0/24 66.22.198.0/26 66.22.199.0/24 66.22.200.0/26 66.22.202.0/26 66.22.204.0/24 66.22.206.0/24 66.22.208.0/25 66.22.210.0/26 66.22.212.0/24 66.22.214.0/24 66.22.216.0/23 66.22.220.0/25 66.22.221.0/24 66.22.222.0/23 66.22.224.0/25 66.22.225.0/26 66.22.226.0/25 66.22.227.0/25 66.22.228.0/22 66.22.233.0/24 66.22.234.0/24 66.22.236.0/23 66.22.238.0/24 66.22.240.0/22 66.22.244.0/23 66.22.248.0/24 74.125.131.207 74.125.205.207 104.17.51.93 104.17.117.93 104.18.4.161 104.18.5.161 104.18.8.105 104.18.9.105 104.18.30.128 104.18.31.128 104.21.2.204 104.21.25.51 104.21.40.151 104.21.59.128 104.21.72.221 104.21.82.160 108.177.14.207 138.128.140.240/28 142.250.150.207 142.251.1.207 162.159.128.232/30 162.159.129.232/30 162.159.130.232/30 162.159.133.232/30 162.159.134.232/30 162.159.135.232/30 162.159.136.232/30 162.159.137.232/30 162.159.138.232/30 172.65.202.19 172.66.41.34 172.66.42.222 172.67.152.224/28 172.67.155.163 172.67.159.89 172.67.177.131 172.67.222.182 173.194.73.207 173.194.220.207 173.194.221.207 173.194.222.207 188.114.96.2 188.114.97.2 188.114.98.224 188.114.99.224 204.11.56.48 209.85.233.207}" + +DNUM=105 +QNUM_DISCORD=$(($DNUM * 5)) +DISCORD_SET_NAME=discord + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt="--qnum=$QNUM_DISCORD $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_DISCORD" + run_daemon $DNUM $NFQWS "$opt" +} + +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f + local first_packets_only="$ipt_connbytes 1:1" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + local DISCORD_PORTS_IPT=$(replace_char - : $DISCORD_PORTS) + local dest_set="-m set --match-set $DISCORD_SET_NAME dst" + local subnet + + local DISABLE_IPV6=1 + + [ "$1" = 1 ] && { + ipset create $DISCORD_SET_NAME hash:net hashsize 8192 maxelem 4096 2>/dev/null + ipset flush $DISCORD_SET_NAME + for subnet in $DISCORD_SUBNETS; do + echo add $DISCORD_SET_NAME $subnet + done | ipset -! restore + } + + f="-p udp -m multiport --dports $DISCORD_PORTS_IPT" + fw_nfqws_post $1 "$f $desync $first_packets_only $dest_set" "" $QNUM_DISCORD + + [ "$1" = 1 ] || { + ipset destroy $DISCORD_SET_NAME + } +} + +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packets_only="$nft_connbytes 1" + local desync="mark and $DESYNC_MARK == 0" + local dest_set="ip daddr @$DISCORD_SET_NAME" + local subnets + + local DISABLE_IPV6=1 + + make_comma_list subnets $DISCORD_SUBNETS + nft_create_set $DISCORD_SET_NAME "type ipv4_addr; size 4096; auto-merge; flags interval;" + nft_flush_set $DISCORD_SET_NAME + nft_add_set_element $DISCORD_SET_NAME "$subnets" + + f="udp dport {$DISCORD_PORTS}" + nft_fw_nfqws_post "$f $desync $first_packets_only $dest_set" "" $QNUM_DISCORD +} diff --git a/init.d/openwrt/custom.d.examples/50-quic4all b/init.d/openwrt/custom.d.examples/50-quic4all new file mode 100644 index 00000000..7445344c --- /dev/null +++ b/init.d/openwrt/custom.d.examples/50-quic4all @@ -0,0 +1,37 @@ +# this custom script runs desync to all QUIC initial packets, without ipset/hostlist filtering +# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" +# NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received + +DNUM=102 +QNUM2=$(($DNUM * 5)) + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt="--qnum=$QNUM2 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC" + run_daemon $DNUM $NFQWS "$opt" +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f + local first_packets_only="$ipt_connbytes 1:3" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + f="-p udp -m multiport --dports $QUIC_PORTS_IPT" + fw_nfqws_post $1 "$f $desync $first_packets_only" "$f $desync $first_packets_only" $QNUM2 + +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packets_only="$nft_connbytes 1-3" + local desync="mark and $DESYNC_MARK == 0" + + f="udp dport {$QUIC_PORTS}" + nft_fw_nfqws_post "$f $desync $first_packets_only" "$f $desync $first_packets_only" $QNUM2 +} diff --git a/init.d/openwrt/custom.d.examples/50-tpws4http-nfqws4https b/init.d/openwrt/custom.d.examples/50-tpws4http-nfqws4https new file mode 100644 index 00000000..df18da8c --- /dev/null +++ b/init.d/openwrt/custom.d.examples/50-tpws4http-nfqws4https @@ -0,0 +1,71 @@ +# this custom script demonstrates how to apply tpws to http and nfqws to https +# it preserves config settings : MODE_HTTP, MODE_HTTPS, MODE_FILTER, TPWS_OPT, NFQWS_OPT_DESYNC, NFQWS_OPT_DESYNC_HTTPS + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt + + [ "$MODE_HTTP" = "1" ] && { + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + run_tpws 1 "$opt" + } + + [ "$MODE_HTTPS" = "1" ] && { + opt="--qnum=$QNUM $NFQWS_OPT_DESYNC_HTTPS" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS_SUFFIX" + run_daemon 2 $NFQWS "$opt" + } +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f4 f6 + local first_packet_only="$ipt_connbytes 1:$(first_packets_for_mode)" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + [ "$MODE_HTTP" = "1" ] && { + f4="-p tcp -m multiport --dports $HTTP_PORTS_IPT" + f6=$f4 + filter_apply_ipset_target f4 f6 + fw_tpws $1 "$f4" "$f6" $TPPORT + } + + [ "$MODE_HTTPS" = "1" ] && { + f4="-p tcp -m multiport --dports $HTTPS_PORTS_IPT $first_packet_only" + f6=$f4 + filter_apply_ipset_target f4 f6 + fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM + # for modes that require incoming traffic + fw_reverse_nfqws_rule $1 "$f4" "$f6" $QNUM + } +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f4 f6 + local first_packet_only="$nft_connbytes 1-$(first_packets_for_mode)" + local desync="mark and $DESYNC_MARK == 0" + + [ "$MODE_HTTP" = "1" ] && { + f4="tcp dport {$HTTP_PORTS}" + f6=$f4 + nft_filter_apply_ipset_target f4 f6 + nft_fw_tpws "$f4" "$f6" $TPPORT + } + + [ "$MODE_HTTPS" = "1" ] && { + f4="tcp dport {$HTTPS_PORTS} $first_packet_only" + f6=$f4 + nft_filter_apply_ipset_target f4 f6 + nft_fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM + # for modes that require incoming traffic + nft_fw_reverse_nfqws_rule "$f4" "$f6" $QNUM + } +} diff --git a/init.d/openwrt/custom.d/.keep b/init.d/openwrt/custom.d/.keep new file mode 100644 index 00000000..e69de29b diff --git a/init.d/openwrt/firewall.zapret b/init.d/openwrt/firewall.zapret new file mode 100644 index 00000000..a09d74df --- /dev/null +++ b/init.d/openwrt/firewall.zapret @@ -0,0 +1,11 @@ +SCRIPT=$(readlink /etc/init.d/zapret) +if [ -n "$SCRIPT" ]; then + EXEDIR=$(dirname "$SCRIPT") + ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") +else + ZAPRET_BASE=/opt/zapret +fi + +. "$ZAPRET_BASE/init.d/openwrt/functions" + +zapret_apply_firewall diff --git a/init.d/openwrt/functions b/init.d/openwrt/functions new file mode 100644 index 00000000..3c1e8d06 --- /dev/null +++ b/init.d/openwrt/functions @@ -0,0 +1,282 @@ +. /lib/functions/network.sh + +ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/queue.sh" +. "$ZAPRET_BASE/common/linux_iphelper.sh" +. "$ZAPRET_BASE/common/ipt.sh" +. "$ZAPRET_BASE/common/nft.sh" +. "$ZAPRET_BASE/common/linux_fw.sh" +. "$ZAPRET_BASE/common/list.sh" +. "$ZAPRET_BASE/common/custom.sh" +CUSTOM_DIR="$ZAPRET_RW/init.d/openwrt" + +[ -n "$QNUM" ] || QNUM=200 +[ -n "$TPPORT" ] || TPPORT=988 +[ -n "$WS_USER" ] || WS_USER=daemon +[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000 +[ -n "$DESYNC_MARK_POSTNAT" ] || DESYNC_MARK_POSTNAT=0x20000000 +[ -n "$OPENWRT_LAN" ] || OPENWRT_LAN=lan + +TPWS_LOCALHOST4=127.0.0.127 + +# max wait time for the link local ipv6 on the LAN interface +LINKLOCAL_WAIT_SEC=5 + +IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh" + +IPSET_EXCLUDE="-m set ! --match-set nozapret" +IPSET_EXCLUDE6="-m set ! --match-set nozapret6" + +apply_unspecified_desync_modes + + +# can be multiple ipv6 outgoing interfaces +# uplink from isp, tunnelbroker, vpn, ... +# want them all. who knows what's the real one that blocks sites +# dont want any manual configuration - want to do it automatically +# standard network_find_wan[6] return only the first +# we use low level function from network.sh to avoid this limitation +# it can change theoretically and stop working + +network_find_wan4_all() +{ + if [ -n "$OPENWRT_WAN4" ]; then + eval $1="\$OPENWRT_WAN4" + else + __network_ifstatus "$1" "" "[@.route[@.target='0.0.0.0' && !@.table]].interface" "" 10 2>/dev/null && return + network_find_wan $1 + fi +} +network_find_wan_all() +{ + network_find_wan4_all "$@" +} +network_find_wan6_all() +{ + if [ -n "$OPENWRT_WAN6" ]; then + eval $1="\$OPENWRT_WAN6" + else + __network_ifstatus "$1" "" "[@.route[@.target='::' && !@.table]].interface" "" 10 2>/dev/null && return + network_find_wan6 $1 + fi +} +network_find_wanX_devices() +{ + # $1 - ip version: 4 or 6 + # $2 - variable to put result to + local ifaces + network_find_wan${1}_all ifaces + call_for_multiple_items network_get_device $2 "$ifaces" +} + + +dnat6_target() +{ + # $1 - lan network name + # $2 - var to store target ip6 + + network_is_up $1 || { + [ -n "$2" ] && eval $2='' + return + } + + local DEVICE + network_get_device DEVICE $1 + + _dnat6_target $DEVICE $2 +} + +set_route_localnet() +{ + # $1 - 1 = enable, 0 = disable + + local DLAN + call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" + _set_route_localnet $1 $DLAN +} + + +fw_nfqws_prepost_x() +{ + # $1 - 1 - add, 0 - del + # $2 - filter + # $3 - queue number + # $4 - 4/6 + # $5 - post/pre + + local ifaces DWAN + network_find_wan${4}_all ifaces + call_for_multiple_items network_get_device DWAN "$ifaces" + + [ -n "$DWAN" ] && _fw_nfqws_${5}${4} $1 "$2" $3 "$(unique $DWAN)" +} +fw_nfqws_post4() +{ + fw_nfqws_prepost_x $1 "$2" $3 4 post +} +fw_nfqws_post6() +{ + fw_nfqws_prepost_x $1 "$2" $3 6 post +} +fw_nfqws_pre4() +{ + fw_nfqws_prepost_x $1 "$2" $3 4 pre +} +fw_nfqws_pre6() +{ + fw_nfqws_prepost_x $1 "$2" $3 6 pre +} +fw_tpws_x() +{ + # $1 - 1 - add, 0 - del + # $2 - filter + # $3 - tpws port + # $4 - ip version : 4 or 6 + + local ifaces DLAN DWAN + + call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" + + network_find_wan${4}_all ifaces + call_for_multiple_items network_get_device DWAN "$ifaces" + + [ -n "$DWAN" ] && _fw_tpws${4} $1 "$2" $3 "$DLAN" "$(unique $DWAN)" +} +fw_tpws4() +{ + fw_tpws_x $1 "$2" $3 4 +} +fw_tpws6() +{ + fw_tpws_x $1 "$2" $3 6 +} + + +create_ipset() +{ + echo "Creating ip list table (firewall type $FWTYPE)" + "$IPSET_CR" "$@" +} + +list_nfqws_rules() +{ + # $1 = '' for ipv4, '6' for ipv6 + ip$1tables -S POSTROUTING -t mangle | \ + grep -E "NFQUEUE --queue-num $QNUM --queue-bypass|NFQUEUE --queue-num $(($QNUM+1)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+2)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+3)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+10)) --queue-bypass|NFQUEUE --queue-num $(($QNUM+11)) --queue-bypass" | \ + sed -re 's/^-A POSTROUTING (.*) -j NFQUEUE.*$/\1/' -e "s/-m mark ! --mark $DESYNC_MARK\/$DESYNC_MARK//" +} +apply_flow_offloading_enable_rule() +{ + # $1 = '' for ipv4, '6' for ipv6 + local i off='-j FLOWOFFLOAD' + [ "$FLOWOFFLOAD" = "hardware" ] && off="$off --hw" + i="forwarding_rule_zapret -m comment --comment zapret_traffic_offloading_enable -m conntrack --ctstate RELATED,ESTABLISHED $off" + echo enabling ipv${1:-4} flow offloading : $i + ip$1tables -A $i +} +apply_flow_offloading_exempt_rule() +{ + # $1 = '' for ipv4, '6' for ipv6 + local i v + v=$1 + shift + i="forwarding_rule_zapret $@ -m comment --comment zapret_traffic_offloading_exemption -j RETURN" + echo applying ipv${v:-4} flow offloading exemption : $i + ip${v}tables -A $i +} +flow_offloading_unexempt_v() +{ + # $1 = '' for ipv4, '6' for ipv6 + local DWAN + network_find_wanX_devices ${1:-4} DWAN + for i in $DWAN; do ipt$1_del FORWARD -o $i -j forwarding_rule_zapret ; done + ip$1tables -F forwarding_rule_zapret 2>/dev/null + ip$1tables -X forwarding_rule_zapret 2>/dev/null +} +flow_offloading_exempt_v() +{ + # $1 = '' for ipv4, '6' for ipv6 + is_ipt_flow_offload_avail $1 || return 0 + + flow_offloading_unexempt_v $1 + + [ "$FLOWOFFLOAD" = 'software' -o "$FLOWOFFLOAD" = 'hardware' ] && { + ip$1tables -N forwarding_rule_zapret + + # remove outgoing interface + list_nfqws_rules $1 | sed -re 's/-o +[^ ]+//g' | + while read rule; do + apply_flow_offloading_exempt_rule "$1" $rule + done + + apply_flow_offloading_enable_rule $1 + + # only outgoing to WAN packets trigger flow offloading + local DWAN + network_find_wanX_devices ${1:-4} DWAN + for i in $DWAN; do ipt$1 FORWARD -o $i -j forwarding_rule_zapret; done + } + return 0 +} +flow_offloading_exempt() +{ + [ "$DISABLE_IPV4" = "1" ] || flow_offloading_exempt_v + [ "$DISABLE_IPV6" = "1" ] || flow_offloading_exempt_v 6 +} +flow_offloading_unexempt() +{ + [ "$DISABLE_IPV4" = "1" ] || flow_offloading_unexempt_v + [ "$DISABLE_IPV6" = "1" ] || flow_offloading_unexempt_v 6 +} + + + +nft_fill_ifsets_overload() +{ + local ifaces DLAN DWAN DWAN6 PDLAN PDWAN PDWAN6 + + call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" + call_for_multiple_items network_get_physdev PDLAN "$OPENWRT_LAN" + + network_find_wan4_all ifaces + call_for_multiple_items network_get_device DWAN "$ifaces" + call_for_multiple_items network_get_physdev PDWAN "$ifaces" + + network_find_wan6_all ifaces + call_for_multiple_items network_get_device DWAN6 "$ifaces" + call_for_multiple_items network_get_physdev PDWAN6 "$ifaces" + + nft_fill_ifsets "$DLAN" "$DWAN" "$DWAN6" "$PDLAN" "$PDWAN" "$PDWAN6" +} + +nft_fw_tpws4() +{ + _nft_fw_tpws4 "$1" $2 always_apply_wan_filter +} +nft_fw_tpws6() +{ + local DLAN + call_for_multiple_items network_get_device DLAN "$OPENWRT_LAN" + _nft_fw_tpws6 "$1" $2 "$DLAN" always_apply_wan_filter +} +nft_fw_nfqws_post4() +{ + _nft_fw_nfqws_post4 "$1" $2 always_apply_wan_filter +} +nft_fw_nfqws_post6() +{ + _nft_fw_nfqws_post6 "$1" $2 always_apply_wan_filter +} +nft_fw_nfqws_pre4() +{ + _nft_fw_nfqws_pre4 "$1" $2 always_apply_wan_filter +} +nft_fw_nfqws_pre6() +{ + _nft_fw_nfqws_pre6 "$1" $2 always_apply_wan_filter +} diff --git a/init.d/openwrt/zapret b/init.d/openwrt/zapret new file mode 100755 index 00000000..c62760de --- /dev/null +++ b/init.d/openwrt/zapret @@ -0,0 +1,240 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 +# after network +START=21 + +my_extra_command() { + local cmd="$1" + local help="$2" + + local extra="$(printf "%-16s%s" "${cmd}" "${help}")" + EXTRA_HELP="${EXTRA_HELP} ${extra} +" + EXTRA_COMMANDS="${EXTRA_COMMANDS} ${cmd}" +} +my_extra_command stop_fw "Stop zapret firewall (noop in iptables+fw3 case)" +my_extra_command start_fw "Start zapret firewall (noop in iptables+fw3 case)" +my_extra_command restart_fw "Restart zapret firewall (noop in iptables+fw3 case)" +my_extra_command reload_ifsets "Reload interface lists (nftables only)" +my_extra_command list_ifsets "Display interface lists (nftables only)" +my_extra_command list_table "Display zapret nftable (nftables only)" +my_extra_command stop_daemons "Stop zapret daemons only (=stop in iptables+fw3 case)" +my_extra_command start_daemons "Start zapret daemons only (=start in iptables+fw3 case)" +my_extra_command restart_daemons "Restart zapret firewall only (=restart in iptables+fw3 case)" + +SCRIPT=$(readlink /etc/init.d/zapret) +if [ -n "$SCRIPT" ]; then + EXEDIR=$(dirname "$SCRIPT") + ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") +else + ZAPRET_BASE=/opt/zapret +fi + +. "$ZAPRET_BASE/init.d/openwrt/functions" + + +# !!!!! in old openwrt 21.x- with iptables firewall rules are configured separately +# !!!!! in new openwrt >21.x with nftables firewall is configured here + +PIDDIR=/var/run + +[ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws" +NFQWS_OPT_BASE="--user=$WS_USER --dpi-desync-fwmark=$DESYNC_MARK" + +[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" +TPWS_OPT_BASE="--user=$WS_USER" +TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4" +TPWS_OPT_BASE6="--bind-addr=::1" +TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" +TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" +TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3" + +run_daemon() +{ + # $1 - daemon string id or number. can use 1,2,3,... + # $2 - daemon + # $3 - daemon args + # use $PIDDIR/$DAEMONBASE$1.pid as pidfile + local DAEMONBASE="$(basename "$2")" + echo "Starting daemon $1: $2 $3" + procd_open_instance + procd_set_param command $2 $3 + procd_set_param pidfile $PIDDIR/$DAEMONBASE$1.pid + procd_close_instance +} + +run_tpws() +{ + [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 + + local OPT="$TPWS_OPT_BASE" + local DEVICE + + [ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4" + [ "$DISABLE_IPV6" = "1" ] || { + OPT="$OPT $TPWS_OPT_BASE6" + for lan in $OPENWRT_LAN; do + network_get_device DEVICE $lan + [ -n "$DEVICE" ] && OPT="$OPT --bind-iface6=$DEVICE $TPWS_OPT_BASE6_PRE" + done + } + run_daemon $1 "$TPWS" "$OPT $2" +} +run_tpws_socks() +{ + [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 + + local opt="$TPWS_OPT_BASE --socks" + + tpws_apply_socks_binds opt + run_daemon $1 "$TPWS" "$opt $2" +} + +stop_tpws() +{ + stop_daemon $1 "$TPWS" +} + + +tpws_apply_socks_binds() +{ + local o + + [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" + + for lan in $OPENWRT_LAN; do + network_get_device DEVICE $lan + [ -n "$DEVICE" ] || continue + [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$DEVICE $TPWS_WAIT" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$DEVICE --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" + done + eval $1="\"\$$1 $o\"" +} + + +start_daemons_procd() +{ + local opt qn qns qn6 qns6 + + case "${MODE_OVERRIDE:-$MODE}" in + tpws) + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + run_tpws 1 "$opt" + ;; + tpws-socks) + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + run_tpws_socks 1 "$opt" + ;; + nfqws) + # quite complex but we need to minimize nfqws processes to save RAM + get_nfqws_qnums qn qns qn6 qns6 + [ -z "$qn" ] || { + opt="--qnum=$qn $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_HTTP" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTP_SUFFIX" + run_daemon 1 "$NFQWS" "$opt" + } + [ -z "$qns" ] || [ "$qns" = "$qn" ] || { + opt="--qnum=$qns $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_HTTPS" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS_SUFFIX" + run_daemon 2 "$NFQWS" "$opt" + } + [ -z "$qn6" ] || [ "$qn6" = "$qn" ] || [ "$qn6" = "$qns" ] || { + opt="--qnum=$qn6 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_HTTP6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTP6_SUFFIX" + run_daemon 3 "$NFQWS" "$opt" + } + [ -z "$qns6" ] || [ "$qns6" = "$qn" ] || [ "$qns6" = "$qns" ] || [ "$qns6" = "$qn6" ] || { + opt="--qnum=$qns6 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_HTTPS6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS6_SUFFIX" + run_daemon 4 "$NFQWS" "$opt" + } + get_nfqws_qnums_quic qn qn6 + [ -z "$qn" ] || { + opt="--qnum=$qn $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_QUIC_SUFFIX" + run_daemon 10 "$NFQWS" "$opt" + } + [ -z "$qn6" ] || [ "$qn6" = "$qn" ] || { + opt="--qnum=$qn6 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_QUIC6_SUFFIX" + run_daemon 11 "$NFQWS" "$opt" + } + ;; + custom) + custom_runner zapret_custom_daemons $1 + ;; + esac + + return 0 +} +start_daemons() +{ + rc_procd start_daemons_procd "$@" +} +stop_daemons() +{ + local svc="$(basename ${basescript:-$initscript})" + procd_running "$svc" "$1" && procd_kill "$svc" "$1" +} +restart_daemons() +{ + stop_daemons + start_daemons +} + +start_fw() +{ + zapret_apply_firewall +} +stop_fw() +{ + zapret_unapply_firewall +} +restart_fw() +{ + stop_fw + start_fw +} +reload_ifsets() +{ + zapret_reload_ifsets +} +list_ifsets() +{ + zapret_list_ifsets +} +list_table() +{ + zapret_list_table +} + +start_service() +{ + start_daemons_procd + [ "$INIT_APPLY_FW" != "1" ] || { + linux_fwtype + openwrt_fw3_integration || start_fw + } +} + +stop_service() +{ + # this procedure is called from stop() + # stop() already stop daemons + [ "$INIT_APPLY_FW" != "1" ] || { + linux_fwtype + openwrt_fw3_integration || stop_fw + } +} diff --git a/init.d/pfsense/zapret.sh b/init.d/pfsense/zapret.sh new file mode 100755 index 00000000..9c434ac7 --- /dev/null +++ b/init.d/pfsense/zapret.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# this file should be placed to /usr/local/etc/rc.d and chmod 755 + +# prepare system + +kldload ipfw +kldload ipdivert + +# for older pfsense versions. newer do not have these sysctls +sysctl net.inet.ip.pfil.outbound=ipfw,pf +sysctl net.inet.ip.pfil.inbound=ipfw,pf +sysctl net.inet6.ip6.pfil.outbound=ipfw,pf +sysctl net.inet6.ip6.pfil.inbound=ipfw,pf + +# required for newer pfsense versions (2.6.0 tested) to return ipfw to functional state +pfctl -d ; pfctl -e + +# add ipfw rules and start daemon + +ipfw delete 100 +ipfw add 100 divert 989 tcp from any to any 80,443 out not diverted not sockarg +pkill ^dvtws$ +dvtws --daemon --port 989 --dpi-desync=split2 diff --git a/init.d/runit/zapret/finish b/init.d/runit/zapret/finish new file mode 100755 index 00000000..2781ad36 --- /dev/null +++ b/init.d/runit/zapret/finish @@ -0,0 +1,2 @@ +#!/bin/sh +/opt/zapret/init.d/sysv/zapret stop diff --git a/init.d/runit/zapret/run b/init.d/runit/zapret/run new file mode 100755 index 00000000..f216e934 --- /dev/null +++ b/init.d/runit/zapret/run @@ -0,0 +1,3 @@ +#!/bin/sh +/opt/zapret/init.d/sysv/zapret start +exec chpst -b zapret sleep infinity diff --git a/init.d/s6/zapret/down b/init.d/s6/zapret/down new file mode 100644 index 00000000..aaab9114 --- /dev/null +++ b/init.d/s6/zapret/down @@ -0,0 +1,2 @@ +#!/bin/execlineb -P +exec /opt/zapret/init.d/sysv/zapret stop diff --git a/init.d/s6/zapret/type b/init.d/s6/zapret/type new file mode 100644 index 00000000..bdd22a18 --- /dev/null +++ b/init.d/s6/zapret/type @@ -0,0 +1 @@ +oneshot diff --git a/init.d/s6/zapret/up b/init.d/s6/zapret/up new file mode 100644 index 00000000..42a12102 --- /dev/null +++ b/init.d/s6/zapret/up @@ -0,0 +1,2 @@ +#!/bin/execlineb -P +exec /opt/zapret/init.d/sysv/zapret start diff --git a/init.d/systemd/zapret-list-update.service b/init.d/systemd/zapret-list-update.service new file mode 100644 index 00000000..eeee1b06 --- /dev/null +++ b/init.d/systemd/zapret-list-update.service @@ -0,0 +1,13 @@ +[Unit] +Description=zapret ip/host list update + +[Service] +Restart=no +IgnoreSIGPIPE=no +KillMode=control-group +GuessMainPID=no +RemainAfterExit=no +ExecStart=/opt/zapret/ipset/get_config.sh + +[Install] +WantedBy=multi-user.target diff --git a/init.d/systemd/zapret-list-update.timer b/init.d/systemd/zapret-list-update.timer new file mode 100644 index 00000000..29379bde --- /dev/null +++ b/init.d/systemd/zapret-list-update.timer @@ -0,0 +1,11 @@ +[Unit] +Description=zapret ip/host list update timer + +[Timer] +OnCalendar=*-*-2,4,6,8,10,12,14,16,18,20,22,24,26,28,30 00:00:00 +RandomizedDelaySec=86400 +Persistent=true +Unit=zapret-list-update.service + +[Install] +WantedBy=timers.target diff --git a/init.d/systemd/zapret.service b/init.d/systemd/zapret.service new file mode 100644 index 00000000..9d3bf415 --- /dev/null +++ b/init.d/systemd/zapret.service @@ -0,0 +1,17 @@ +[Unit] +After=network-online.target +Wants=network-online.target + +[Service] +Type=forking +Restart=no +TimeoutSec=30sec +IgnoreSIGPIPE=no +KillMode=none +GuessMainPID=no +RemainAfterExit=no +ExecStart=/opt/zapret/init.d/sysv/zapret start +ExecStop=/opt/zapret/init.d/sysv/zapret stop + +[Install] +WantedBy=multi-user.target diff --git a/init.d/sysv/custom.d.examples/10-inherit-nfqws b/init.d/sysv/custom.d.examples/10-inherit-nfqws new file mode 100644 index 00000000..60029690 --- /dev/null +++ b/init.d/sysv/custom.d.examples/10-inherit-nfqws @@ -0,0 +1,22 @@ +# this custom script applies nfqws mode as it would be with MODE=nfqws + +OVERRIDE=nfqws + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_daemons $1 +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/sysv/custom.d.examples/10-inherit-tpws b/init.d/sysv/custom.d.examples/10-inherit-tpws new file mode 100644 index 00000000..c1b183e3 --- /dev/null +++ b/init.d/sysv/custom.d.examples/10-inherit-tpws @@ -0,0 +1,22 @@ +# this custom script applies tpws mode as it would be with MODE=tpws + +OVERRIDE=tpws + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_daemons $1 +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/sysv/custom.d.examples/10-inherit-tpws-socks b/init.d/sysv/custom.d.examples/10-inherit-tpws-socks new file mode 100644 index 00000000..7fcb0e26 --- /dev/null +++ b/init.d/sysv/custom.d.examples/10-inherit-tpws-socks @@ -0,0 +1,22 @@ +# this custom script applies tpws-socks mode as it would be with MODE=tpws-socks + +OVERRIDE=tpws-socks + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_daemons $1 +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + MODE_OVERRIDE=$OVERRIDE zapret_do_firewall_rules_ipt $1 +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + MODE_OVERRIDE=$OVERRIDE zapret_apply_firewall_rules_nft +} diff --git a/init.d/sysv/custom.d.examples/50-dht4all b/init.d/sysv/custom.d.examples/50-dht4all new file mode 100644 index 00000000..735b2c51 --- /dev/null +++ b/init.d/sysv/custom.d.examples/50-dht4all @@ -0,0 +1,39 @@ +# this custom script runs desync to DHT packets with udp payload length 101..399 , without ipset/hostlist filtering +# need to add to config : NFQWS_OPT_DESYNC_DHT="--dpi-desync=fake --dpi-desync-ttl=5" + +DNUM=101 +QNUM2=$(($DNUM * 5)) + +zapret_custom_daemons() +{ + # stop logic is managed by procd + + local opt="--qnum=$QNUM2 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_DHT" + do_nfqws $1 $DNUM "$opt" +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f uf4 uf6 + local first_packet_only="$ipt_connbytes 1:1" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + f='-p udp -m length --length 109:407 -m u32 --u32' + uf4='0>>22&0x3C@8>>16=0x6431' + uf6='48>>16=0x6431' + fw_nfqws_post $1 "$f $uf4 $desync $first_packet_only" "$f $uf6 $desync $first_packet_only" $QNUM2 + +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packet_only="$nft_connbytes 1" + local desync="mark and $DESYNC_MARK == 0" + + f="meta length 109-407 meta l4proto udp @th,64,16 0x6431" + nft_fw_nfqws_post "$f $desync $first_packet_only" "$f $desync $first_packet_only" $QNUM2 +} + diff --git a/init.d/sysv/custom.d.examples/50-discord b/init.d/sysv/custom.d.examples/50-discord new file mode 100644 index 00000000..487b4cd5 --- /dev/null +++ b/init.d/sysv/custom.d.examples/50-discord @@ -0,0 +1,69 @@ +# this custom script in addition to MODE=nfqws runs desync of some udp packets to discord subnets +# idea taken from community. not tested and not optimized by author. + +# can override in config : +NFQWS_OPT_DESYNC_DISCORD="${NFQWS_OPT_DESYNC_DISCORD:---dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-any-protocol}" +DISCORD_PORTS=${DISCORD_PORTS:-50000-65535} +DISCORD_SUBNETS="${DISCORD_SUBNETS:-5.200.14.249 18.165.140.0/25 23.227.38.74 34.0.48.0/24 34.0.49.64/26 34.0.50.0/25 34.0.51.0/24 34.0.52.0/22 34.0.56.0/23 34.0.59.0/24 34.0.60.0/24 34.0.62.128/25 34.0.63.228 34.0.64.0/23 34.0.66.130 34.0.82.140 34.0.129.128/25 34.0.130.0/24 34.0.131.130 34.0.132.139 34.0.133.75 34.0.134.0/24 34.0.135.251 34.0.136.51 34.0.137.0/24 34.0.139.0/24 34.0.140.0/23 34.0.142.0/25 34.0.144.0/23 34.0.146.0/24 34.0.148.25 34.0.149.101 34.0.151.0/25 34.0.153.0/24 34.0.155.0/24 34.0.156.101 34.0.157.0/25 34.0.158.247 34.0.159.188 34.0.192.0/25 34.0.193.0/24 34.0.194.0/24 34.0.195.172 34.0.196.200/29 34.0.197.81 34.0.198.25 34.0.199.0/24 34.0.200.0/24 34.0.201.81 34.0.202.34 34.0.203.0/24 34.0.204.0/23 34.0.206.0/25 34.0.207.0/25 34.0.208.195 34.0.209.0/24 34.0.210.20 34.0.211.0/26 34.0.212.0/24 34.0.213.64/26 34.0.215.128/25 34.0.216.238 34.0.217.0/24 34.0.218.83 34.0.220.103 34.0.221.0/24 34.0.222.193 34.0.223.68 34.0.227.0/24 34.0.240.0/21 34.0.248.0/23 34.0.250.0/24 34.0.251.0/25 34.1.216.0/24 34.1.221.166 35.207.64.0/23 35.207.67.116 35.207.71.0/24 35.207.72.32 35.207.73.0/24 35.207.74.0/24 35.207.75.128/25 35.207.76.128/26 35.207.77.0/24 35.207.78.129 35.207.79.0/24 35.207.80.76 35.207.81.248/30 35.207.82.0/23 35.207.84.0/24 35.207.85.160 35.207.86.41 35.207.87.184 35.207.89.188 35.207.91.146 35.207.92.230 35.207.95.0/24 35.207.97.174 35.207.99.134 35.207.100.64/26 35.207.101.130 35.207.103.64/26 35.207.104.0/24 35.207.106.128/26 35.207.107.19 35.207.108.192/27 35.207.109.185 35.207.110.0/24 35.207.111.174 35.207.114.16 35.207.115.163 35.207.116.51 35.207.117.0/24 35.207.121.204 35.207.122.0/25 35.207.124.145 35.207.125.116 35.207.126.30 35.207.129.0/24 35.207.131.128/27 35.207.132.247 35.207.135.147 35.207.136.69 35.207.137.0/24 35.207.139.0/24 35.207.140.241 35.207.141.119 35.207.142.0/24 35.207.143.96/27 35.207.144.0/25 35.207.145.0/24 35.207.146.89 35.207.147.0/24 35.207.149.0/24 35.207.150.0/24 35.207.151.61 35.207.153.117 35.207.154.0/24 35.207.155.128/25 35.207.156.254 35.207.157.7 35.207.158.192 35.207.160.160 35.207.162.239 35.207.163.0/24 35.207.164.0/25 35.207.165.147 35.207.166.0/25 35.207.167.0/24 35.207.168.116 35.207.170.0/23 35.207.172.0/24 35.207.174.55 35.207.176.128/25 35.207.178.0/24 35.207.180.152 35.207.181.76 35.207.182.125 35.207.184.101 35.207.185.192 35.207.186.128/25 35.207.187.228 35.207.188.0/24 35.207.189.0/25 35.207.190.194 35.207.191.64/26 35.207.193.165 35.207.195.75 35.207.196.0/24 35.207.198.0/23 35.207.201.186 35.207.202.169 35.207.205.211 35.207.207.4 35.207.209.0/25 35.207.210.191 35.207.211.253 35.207.213.97 35.207.214.0/24 35.207.220.147 35.207.221.58 35.207.222.105 35.207.224.151 35.207.225.210 35.207.227.0/24 35.207.229.212 35.207.232.26 35.207.234.182 35.207.238.0/24 35.207.240.0/24 35.207.245.0/24 35.207.249.0/24 35.207.250.212 35.207.251.0/27 35.212.4.134 35.212.12.148 35.212.88.11 35.212.102.50 35.212.111.0/26 35.212.117.247 35.212.120.122 35.213.0.0/24 35.213.2.8 35.213.4.185 35.213.6.118 35.213.7.128/25 35.213.8.168 35.213.10.0/24 35.213.11.21 35.213.12.224/27 35.213.13.19 35.213.14.217 35.213.16.67 35.213.17.235 35.213.23.166 35.213.25.164 35.213.26.62 35.213.27.252 35.213.32.0/24 35.213.33.74 35.213.34.204 35.213.37.81 35.213.38.186 35.213.39.253 35.213.42.0/24 35.213.43.79 35.213.45.0/24 35.213.46.136 35.213.49.17 35.213.50.0/24 35.213.51.213 35.213.52.0/25 35.213.53.0/24 35.213.54.0/24 35.213.56.0/25 35.213.59.0/24 35.213.61.58 35.213.65.0/24 35.213.67.0/24 35.213.68.192/26 35.213.70.151 35.213.72.128/25 35.213.73.245 35.213.74.131 35.213.78.0/24 35.213.79.137 35.213.80.0/25 35.213.83.128/25 35.213.84.245 35.213.85.0/24 35.213.88.145 35.213.89.80/28 35.213.90.0/24 35.213.91.195 35.213.92.0/24 35.213.93.254 35.213.94.78 35.213.95.145 35.213.96.87 35.213.98.0/24 35.213.99.126 35.213.101.214 35.213.102.0/24 35.213.105.0/24 35.213.106.128/25 35.213.107.158 35.213.109.0/24 35.213.110.40 35.213.111.0/25 35.213.115.0/25 35.213.120.0/24 35.213.122.0/24 35.213.124.89 35.213.125.40 35.213.126.185 35.213.127.0/24 35.213.128.0/22 35.213.132.0/23 35.213.134.140 35.213.135.0/24 35.213.136.0/23 35.213.138.128/25 35.213.139.0/24 35.213.140.0/25 35.213.141.164 35.213.142.128/25 35.213.143.0/24 35.213.144.0/22 35.213.148.0/23 35.213.150.0/24 35.213.152.0/23 35.213.154.137 35.213.155.134 35.213.156.144 35.213.157.0/24 35.213.158.64/26 35.213.160.90 35.213.161.253 35.213.162.0/25 35.213.163.0/24 35.213.164.0/23 35.213.166.106 35.213.167.160/27 35.213.168.0/24 35.213.169.179 35.213.170.0/24 35.213.171.201 35.213.172.159 35.213.173.0/24 35.213.174.128/25 35.213.175.128/26 35.213.176.0/24 35.213.177.0/25 35.213.179.139 35.213.180.0/24 35.213.181.0/25 35.213.182.0/23 35.213.184.0/23 35.213.186.70 35.213.187.0/24 35.213.188.128/25 35.213.190.158 35.213.191.0/24 35.213.192.240/31 35.213.193.74 35.213.194.0/25 35.213.195.178 35.213.196.38 35.213.197.68 35.213.198.0/23 35.213.200.0/23 35.213.202.0/25 35.213.203.195 35.213.204.32/27 35.213.205.170 35.213.207.128/25 35.213.208.85 35.213.210.0/24 35.213.211.176/29 35.213.212.0/24 35.213.213.225 35.213.214.0/25 35.213.215.255 35.213.217.0/24 35.213.218.248 35.213.219.0/25 35.213.220.211 35.213.221.0/24 35.213.222.215 35.213.223.0/24 35.213.225.0/24 35.213.227.227 35.213.229.17 35.213.230.89 35.213.231.0/24 35.213.233.0/24 35.213.234.134 35.213.236.0/24 35.213.237.212 35.213.238.0/24 35.213.240.212 35.213.241.0/24 35.213.242.10 35.213.243.219 35.213.244.146 35.213.245.119 35.213.246.0/23 35.213.249.79 35.213.250.0/24 35.213.251.74 35.213.252.0/24 35.213.253.155 35.213.254.89 35.214.128.248 35.214.129.220 35.214.130.217 35.214.131.144 35.214.132.189 35.214.133.0/24 35.214.134.163 35.214.137.0/24 35.214.138.0/25 35.214.140.0/24 35.214.142.0/24 35.214.143.41 35.214.144.26 35.214.145.200 35.214.146.9 35.214.147.135 35.214.148.89 35.214.149.110 35.214.151.128/25 35.214.152.0/24 35.214.156.115 35.214.158.181 35.214.159.128/25 35.214.160.128/25 35.214.161.217 35.214.162.0/24 35.214.163.28 35.214.165.102 35.214.167.77 35.214.169.0/24 35.214.170.2 35.214.171.0/25 35.214.172.128/25 35.214.173.0/24 35.214.175.0/24 35.214.177.183 35.214.179.46 35.214.180.0/23 35.214.184.179 35.214.185.28 35.214.186.3 35.214.187.0/24 35.214.191.0/24 35.214.192.128/25 35.214.193.0/24 35.214.194.128/25 35.214.195.0/25 35.214.196.64/26 35.214.197.0/24 35.214.198.7 35.214.199.224 35.214.201.0/25 35.214.203.155 35.214.204.0/23 35.214.207.0/24 35.214.208.128/25 35.214.209.64 35.214.210.0/24 35.214.211.3 35.214.212.64/26 35.214.213.0/25 35.214.214.0/24 35.214.215.64/26 35.214.216.0/23 35.214.218.140 35.214.219.0/24 35.214.220.149 35.214.221.0/24 35.214.222.149 35.214.223.0/24 35.214.224.71 35.214.225.0/24 35.214.226.0/23 35.214.228.0/23 35.214.231.187 35.214.233.8 35.214.235.38 35.214.237.0/24 35.214.238.0/25 35.214.239.0/24 35.214.240.87 35.214.241.0/24 35.214.243.21 35.214.244.0/24 35.214.245.16/28 35.214.246.106 35.214.248.119 35.214.249.154 35.214.250.0/24 35.214.251.128/25 35.214.252.187 35.214.253.0/24 35.214.255.154 35.215.72.85 35.215.73.65 35.215.83.0 35.215.108.111 35.215.115.120 35.215.126.35 35.215.127.34 35.215.128.0/21 35.215.136.0/26 35.215.137.0/24 35.215.138.0/23 35.215.140.0/24 35.215.141.64/27 35.215.142.0/24 35.215.143.83 35.215.144.128/25 35.215.145.0/24 35.215.146.0/24 35.215.147.86 35.215.148.0/23 35.215.150.0/26 35.215.151.0/24 35.215.152.0/24 35.215.153.128/25 35.215.154.240/28 35.215.155.20 35.215.156.0/24 35.215.158.0/23 35.215.160.192/26 35.215.161.0/24 35.215.163.0/24 35.215.164.0/24 35.215.165.236 35.215.166.128/25 35.215.167.128/25 35.215.168.0/24 35.215.169.12 35.215.170.0/23 35.215.172.0/22 35.215.176.0/24 35.215.177.72 35.215.178.0/24 35.215.179.161 35.215.180.0/22 35.215.184.253 35.215.185.64/26 35.215.186.0/25 35.215.187.0/24 35.215.188.0/23 35.215.190.0/24 35.215.191.61 35.215.192.0/23 35.215.194.192/28 35.215.195.0/24 35.215.196.0/25 35.215.197.0/25 35.215.198.230 35.215.199.204 35.215.200.0/23 35.215.202.0/24 35.215.203.0/25 35.215.204.128/25 35.215.205.0/25 35.215.206.0/23 35.215.208.0/24 35.215.209.0/25 35.215.210.0/23 35.215.212.0/22 35.215.216.0/22 35.215.221.0/24 35.215.222.128/25 35.215.223.126 35.215.224.0/23 35.215.226.0/24 35.215.227.0/25 35.215.228.0/24 35.215.229.64 35.215.230.89 35.215.231.0/24 35.215.232.0/24 35.215.233.0/25 35.215.234.37 35.215.235.0/24 35.215.238.0/25 35.215.239.119 35.215.240.0/24 35.215.241.128/25 35.215.242.0/25 35.215.243.0/24 35.215.244.0/23 35.215.246.222 35.215.247.0/24 35.215.248.0/22 35.215.252.0/24 35.215.253.118 35.215.254.0/23 35.217.0.0/24 35.217.1.64/26 35.217.2.5 35.217.3.0/24 35.217.4.72 35.217.5.0/25 35.217.6.0/24 35.217.8.0/25 35.217.9.0/24 35.217.11.186 35.217.12.0/24 35.217.14.192/26 35.217.15.65 35.217.16.75 35.217.17.128/25 35.217.18.0/24 35.217.19.183 35.217.20.0/24 35.217.21.128/25 35.217.22.128/25 35.217.23.128/25 35.217.24.0/24 35.217.25.81 35.217.26.0/24 35.217.27.128/25 35.217.28.128/25 35.217.29.0/24 35.217.30.0/25 35.217.31.0/25 35.217.32.128/25 35.217.33.0/24 35.217.35.128/25 35.217.36.0/23 35.217.38.179 35.217.39.186 35.217.40.176 35.217.41.204 35.217.43.0/24 35.217.45.248 35.217.46.0/24 35.217.47.128/25 35.217.48.195 35.217.49.160/27 35.217.50.0/25 35.217.51.0/24 35.217.52.117 35.217.53.128/25 35.217.54.0/25 35.217.55.96/27 35.217.56.6 35.217.57.184 35.217.58.0/24 35.217.59.64/26 35.217.60.0/24 35.217.61.128/25 35.217.62.0/24 35.217.63.128/25 35.219.225.149 35.219.226.57 35.219.227.0/24 35.219.228.37 35.219.229.128/25 35.219.230.0/23 35.219.235.0/24 35.219.236.198 35.219.238.115 35.219.239.0/24 35.219.241.0/24 35.219.242.221 35.219.243.191 35.219.244.1 35.219.245.0/24 35.219.246.159 35.219.247.0/26 35.219.248.0/24 35.219.249.126 35.219.251.186 35.219.252.0/23 35.219.254.0/24 64.233.161.207 64.233.162.207 64.233.163.207 64.233.164.207 64.233.165.207 66.22.196.0/26 66.22.197.0/24 66.22.198.0/26 66.22.199.0/24 66.22.200.0/26 66.22.202.0/26 66.22.204.0/24 66.22.206.0/24 66.22.208.0/25 66.22.210.0/26 66.22.212.0/24 66.22.214.0/24 66.22.216.0/23 66.22.220.0/25 66.22.221.0/24 66.22.222.0/23 66.22.224.0/25 66.22.225.0/26 66.22.226.0/25 66.22.227.0/25 66.22.228.0/22 66.22.233.0/24 66.22.234.0/24 66.22.236.0/23 66.22.238.0/24 66.22.240.0/22 66.22.244.0/23 66.22.248.0/24 74.125.131.207 74.125.205.207 104.17.51.93 104.17.117.93 104.18.4.161 104.18.5.161 104.18.8.105 104.18.9.105 104.18.30.128 104.18.31.128 104.21.2.204 104.21.25.51 104.21.40.151 104.21.59.128 104.21.72.221 104.21.82.160 108.177.14.207 138.128.140.240/28 142.250.150.207 142.251.1.207 162.159.128.232/30 162.159.129.232/30 162.159.130.232/30 162.159.133.232/30 162.159.134.232/30 162.159.135.232/30 162.159.136.232/30 162.159.137.232/30 162.159.138.232/30 172.65.202.19 172.66.41.34 172.66.42.222 172.67.152.224/28 172.67.155.163 172.67.159.89 172.67.177.131 172.67.222.182 173.194.73.207 173.194.220.207 173.194.221.207 173.194.222.207 188.114.96.2 188.114.97.2 188.114.98.224 188.114.99.224 204.11.56.48 209.85.233.207}" + +DNUM=105 +QNUM_DISCORD=$(($DNUM * 5)) +DISCORD_SET_NAME=discord + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt="--qnum=$QNUM_DISCORD $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_DISCORD" + do_nfqws $1 $DNUM "$opt" +} + +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f + local first_packets_only="$ipt_connbytes 1:1" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + local DISCORD_PORTS_IPT=$(replace_char - : $DISCORD_PORTS) + local dest_set="-m set --match-set $DISCORD_SET_NAME dst" + local subnet + + local DISABLE_IPV6=1 + + [ "$1" = 1 ] && { + ipset create $DISCORD_SET_NAME hash:net hashsize 8192 maxelem 4096 2>/dev/null + ipset flush $DISCORD_SET_NAME + for subnet in $DISCORD_SUBNETS; do + echo add $DISCORD_SET_NAME $subnet + done | ipset -! restore + } + + f="-p udp -m multiport --dports $DISCORD_PORTS_IPT" + fw_nfqws_post $1 "$f $desync $first_packets_only $dest_set" "" $QNUM_DISCORD + + [ "$1" = 1 ] || { + ipset destroy $DISCORD_SET_NAME + } +} + +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packets_only="$nft_connbytes 1" + local desync="mark and $DESYNC_MARK == 0" + local dest_set="ip daddr @$DISCORD_SET_NAME" + local subnets + + local DISABLE_IPV6=1 + + make_comma_list subnets $DISCORD_SUBNETS + nft_create_set $DISCORD_SET_NAME "type ipv4_addr; size 4096; auto-merge; flags interval;" + nft_flush_set $DISCORD_SET_NAME + nft_add_set_element $DISCORD_SET_NAME "$subnets" + + f="udp dport {$DISCORD_PORTS}" + nft_fw_nfqws_post "$f $desync $first_packets_only $dest_set" "" $QNUM_DISCORD +} diff --git a/init.d/sysv/custom.d.examples/50-quic4all b/init.d/sysv/custom.d.examples/50-quic4all new file mode 100644 index 00000000..5f4b5932 --- /dev/null +++ b/init.d/sysv/custom.d.examples/50-quic4all @@ -0,0 +1,37 @@ +# this custom script runs desync to all QUIC initial packets, without ipset/hostlist filtering +# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake" +# NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received + +DNUM=102 +QNUM2=$(($DNUM * 5)) + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt="--qnum=$QNUM2 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC" + do_nfqws $1 $DNUM "$opt" +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f + local first_packets_only="$ipt_connbytes 1:3" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + f="-p udp -m multiport --dports $QUIC_PORTS_IPT" + fw_nfqws_post $1 "$f $desync $first_packets_only" "$f $desync $first_packets_only" $QNUM2 + +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f + local first_packets_only="$nft_connbytes 1-3" + local desync="mark and $DESYNC_MARK == 0" + + f="udp dport {$QUIC_PORTS}" + nft_fw_nfqws_post "$f $desync $first_packets_only" "$f $desync $first_packets_only" $QNUM2 +} diff --git a/init.d/sysv/custom.d.examples/50-tpws4http-nfqws4https b/init.d/sysv/custom.d.examples/50-tpws4http-nfqws4https new file mode 100644 index 00000000..95042c06 --- /dev/null +++ b/init.d/sysv/custom.d.examples/50-tpws4http-nfqws4https @@ -0,0 +1,71 @@ +# this custom script demonstrates how to apply tpws to http and nfqws to https +# it preserves config settings : MODE_HTTP, MODE_HTTPS, MODE_FILTER, TPWS_OPT, NFQWS_OPT_DESYNC, NFQWS_OPT_DESYNC_HTTPS + +zapret_custom_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt + + [ "$MODE_HTTP" = "1" ] && { + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + do_tpws $1 1 "$opt" + } + + [ "$MODE_HTTPS" = "1" ] && { + opt="--qnum=$QNUM $NFQWS_OPT_DESYNC_HTTPS" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS_SUFFIX" + do_nfqws $1 2 "$opt" + } +} +zapret_custom_firewall() +{ + # $1 - 1 - run, 0 - stop + + local f4 f6 + local first_packet_only="$ipt_connbytes 1:$(first_packets_for_mode)" + local desync="-m mark ! --mark $DESYNC_MARK/$DESYNC_MARK" + + [ "$MODE_HTTP" = "1" ] && { + f4="-p tcp -m multiport --dports $HTTP_PORTS_IPT" + f6=$f4 + filter_apply_ipset_target f4 f6 + fw_tpws $1 "$f4" "$f6" $TPPORT + } + + [ "$MODE_HTTPS" = "1" ] && { + f4="-p tcp -m multiport --dports $HTTPS_PORTS_IPT $first_packet_only" + f6=$f4 + filter_apply_ipset_target f4 f6 + fw_nfqws_post $1 "$f4 $desync" "$f6 $desync" $QNUM + # for modes that require incoming traffic + fw_reverse_nfqws_rule $1 "$f4" "$f6" $QNUM + } +} +zapret_custom_firewall_nft() +{ + # stop logic is not required + + local f4 f6 + local first_packet_only="$nft_connbytes 1-$(first_packets_for_mode)" + local desync="mark and $DESYNC_MARK == 0" + + [ "$MODE_HTTP" = "1" ] && { + f4="tcp dport {$HTTP_PORTS}" + f6=$f4 + nft_filter_apply_ipset_target f4 f6 + nft_fw_tpws "$f4" "$f6" $TPPORT + } + + [ "$MODE_HTTPS" = "1" ] && { + f4="tcp dport {$HTTPS_PORTS} $first_packet_only" + f6=$f4 + nft_filter_apply_ipset_target f4 f6 + nft_fw_nfqws_post "$f4 $desync" "$f6 $desync" $QNUM + # for modes that require incoming traffic + nft_fw_reverse_nfqws_rule "$f4" "$f6" $QNUM + } +} diff --git a/init.d/sysv/custom.d/.keep b/init.d/sysv/custom.d/.keep new file mode 100644 index 00000000..e69de29b diff --git a/init.d/sysv/functions b/init.d/sysv/functions new file mode 100644 index 00000000..9caa5699 --- /dev/null +++ b/init.d/sysv/functions @@ -0,0 +1,357 @@ +# init script functions library for desktop linux systems + +ZAPRET_BASE=${ZAPRET_BASE:-/opt/zapret} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/queue.sh" +. "$ZAPRET_BASE/common/linux_iphelper.sh" +. "$ZAPRET_BASE/common/ipt.sh" +. "$ZAPRET_BASE/common/nft.sh" +. "$ZAPRET_BASE/common/linux_fw.sh" +. "$ZAPRET_BASE/common/list.sh" +. "$ZAPRET_BASE/common/custom.sh" +CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" + + +user_exists() +{ + id -u $1 >/dev/null 2>/dev/null +} +useradd_compat() +{ + # $1 - username + # skip for readonly systems + [ -w "/etc" ] && { + if exists useradd ; then + useradd --no-create-home --system --shell /bin/false $1 + elif is_linked_to_busybox adduser ; then + # some systems may miss nogroup group in /etc/group + # adduser fails if it's absent and no group is specified + addgroup nogroup 2>/dev/null + # busybox has special adduser syntax + adduser -S -H -D $1 + elif exists adduser; then + adduser --no-create-home --system --disabled-login $1 + fi + } + user_exists $1 +} +prepare_user() +{ + # $WS_USER is required to prevent redirection of the traffic originating from TPWS itself + # otherwise infinite loop will occur + # also its good idea not to run tpws as root + user_exists $WS_USER || { + # fallback to daemon if we cant add WS_USER + useradd_compat $WS_USER || { + for user in daemon nobody; do + user_exists $user && { + WS_USER=$user + return 0 + } + done + return 1 + } + } +} + +# this complex user selection allows to survive in any locked/readonly/minimalistic environment +[ -n "$WS_USER" ] || WS_USER=tpws +if prepare_user; then + USEROPT="--user=$WS_USER" +else + WS_USER=1 + USEROPT="--uid $WS_USER:$WS_USER" +fi + +PIDDIR=/var/run +IPSET_CR="$ZAPRET_BASE/ipset/create_ipset.sh" + +[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000 +[ -n "$DESYNC_MARK_POSTNAT" ] || DESYNC_MARK_POSTNAT=0x20000000 + +[ -n "$QNUM" ] || QNUM=200 +[ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws" +NFQWS_OPT_BASE="$USEROPT --dpi-desync-fwmark=$DESYNC_MARK" +apply_unspecified_desync_modes + +[ -n "$TPPORT" ] || TPPORT=988 +[ -n "$TPWS" ] || TPWS="$ZAPRET_BASE/tpws/tpws" +TPWS_LOCALHOST4=127.0.0.127 + +TPWS_OPT_BASE="$USEROPT" +TPWS_OPT_BASE4="--bind-addr=$TPWS_LOCALHOST4" +TPWS_OPT_BASE6="--bind-addr=::1" +TPWS_WAIT="--bind-wait-ifup=30 --bind-wait-ip=30" +TPWS_WAIT_SOCKS6="$TPWS_WAIT --bind-wait-ip-linklocal=30" +# first wait for lan to ifup, then wait for bind-wait-ip-linklocal seconds for link local address and bind-wait-ip for any ipv6 as the worst case +TPWS_OPT_BASE6_PRE="--bind-linklocal=prefer $TPWS_WAIT --bind-wait-ip-linklocal=3" + +# max wait time for the link local ipv6 on the LAN interface +LINKLOCAL_WAIT_SEC=5 + +IPSET_EXCLUDE="-m set ! --match-set nozapret" +IPSET_EXCLUDE6="-m set ! --match-set nozapret6" + + +dnat6_target() +{ + _dnat6_target "$@" +} +set_route_localnet() +{ + _set_route_localnet $1 "$IFACE_LAN" +} + +fw_nfqws_post4() +{ + _fw_nfqws_post4 $1 "$2" $3 "$IFACE_WAN" +} +fw_nfqws_post6() +{ + _fw_nfqws_post6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" +} +fw_nfqws_pre4() +{ + _fw_nfqws_pre4 $1 "$2" $3 "$IFACE_WAN" +} +fw_nfqws_pre6() +{ + _fw_nfqws_pre6 $1 "$2" $3 "${IFACE_WAN6:-$IFACE_WAN}" +} +fw_tpws4() +{ + _fw_tpws4 $1 "$2" $3 "$IFACE_LAN" "$IFACE_WAN" +} +fw_tpws6() +{ + _fw_tpws6 $1 "$2" $3 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" +} +nft_fw_tpws4() +{ + _nft_fw_tpws4 "$1" $2 "$IFACE_WAN" +} +nft_fw_tpws6() +{ + _nft_fw_tpws6 "$1" $2 "$IFACE_LAN" "${IFACE_WAN6:-$IFACE_WAN}" +} +nft_fw_nfqws_post4() +{ + _nft_fw_nfqws_post4 "$1" $2 "$IFACE_WAN" +} +nft_fw_nfqws_post6() +{ + _nft_fw_nfqws_post6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" +} +nft_fw_nfqws_pre4() +{ + _nft_fw_nfqws_pre4 "$1" $2 "$IFACE_WAN" +} +nft_fw_nfqws_pre6() +{ + _nft_fw_nfqws_pre6 "$1" $2 "${IFACE_WAN6:-$IFACE_WAN}" +} +nft_fill_ifsets_overload() +{ + nft_fill_ifsets "$IFACE_LAN" "$IFACE_WAN" "${IFACE_WAN6:-$IFACE_WAN}" +} + + +run_daemon() +{ + # $1 - daemon number : 1,2,3,... + # $2 - daemon + # $3 - daemon args + # use $PIDDIR/$DAEMONBASE$1.pid as pidfile + + local DAEMONBASE="$(basename "$2")" + local PIDFILE=$PIDDIR/$DAEMONBASE$1.pid + echo "Starting daemon $1: $2 $3" + if exists start-stop-daemon ; then + start-stop-daemon -S -p "$PIDFILE" -m -b -x "$2" -- $3 + else + if [ -f "$PIDFILE" ] && pgrep -F "$PIDFILE" "$DAEMONBASE" >/dev/null; then + echo already running + else + "$2" $3 >/dev/null 2>/dev/null & + PID=$! + if [ -n "$PID" ]; then + echo $PID >$PIDFILE + else + echo could not start daemon $1 : $2 $3 + false + fi + fi + fi +} +stop_daemon() +{ + # $1 - daemon number : 1,2,3,... + # $2 - daemon + # use $PIDDIR/$DAEMONBASE$1.pid as pidfile + local DAEMONBASE="$(basename "$2")" + local PIDFILE=$PIDDIR/$DAEMONBASE$1.pid + echo "Stopping daemon $1: $2" + if exists start-stop-daemon ; then + start-stop-daemon -K -p "$PIDFILE" -x "$2" + else + if [ -f "$PIDFILE" ]; then + read PID <"$PIDFILE" + kill $PID + rm -f "$PIDFILE" + else + echo no pidfile : $PIDFILE + fi + fi +} +do_daemon() +{ + # $1 - 1 - run, 0 - stop + on_off_function run_daemon stop_daemon "$@" +} + + +do_tpws() +{ + # $1 : 1 - run, 0 - stop + # $2 : daemon number + # $3 : daemon args + + [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 + + local OPT="$TPWS_OPT_BASE" + + [ "$DISABLE_IPV4" = "1" ] || OPT="$OPT $TPWS_OPT_BASE4" + [ "$DISABLE_IPV6" = "1" ] || { + OPT="$OPT $TPWS_OPT_BASE6" + for lan in $IFACE_LAN; do + OPT="$OPT --bind-iface6=$lan $TPWS_OPT_BASE6_PRE" + done + } + + do_daemon $1 $2 "$TPWS" "$OPT $3" +} +do_tpws_socks() +{ + # $1 : 1 - run, 0 - stop + # $2 : daemon number + # $3 : daemon args + + [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && return 0 + + local opt="$TPWS_OPT_BASE --socks" + + tpws_apply_socks_binds opt + + do_daemon $1 $2 "$TPWS" "$opt $3" +} + +do_nfqws() +{ + # $1 : 1 - run, 0 - stop + # $2 : daemon number + # $3 : daemon args + + do_daemon $1 $2 "$NFQWS" "$NFQWS_OPT_BASE $3" +} + +tpws_apply_socks_binds() +{ + local o + + [ "$DISABLE_IPV4" = "1" ] || o="--bind-addr=127.0.0.1" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-addr=::1" + + for lan in $IFACE_LAN; do + [ "$DISABLE_IPV4" = "1" ] || o="$o --bind-iface4=$lan $TPWS_WAIT" + [ "$DISABLE_IPV6" = "1" ] || o="$o --bind-iface6=$lan --bind-linklocal=unwanted $TPWS_WAIT_SOCKS6" + done + eval $1="\"\$$1 $o\"" +} + + +create_ipset() +{ + echo "Creating ip list table (firewall type $FWTYPE)" + "$IPSET_CR" "$@" +} + + +zapret_do_daemons() +{ + # $1 - 1 - run, 0 - stop + + local opt qn qns qn6 qns6 + + case "${MODE_OVERRIDE:-$MODE}" in + tpws) + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + do_tpws $1 1 "$opt" + ;; + tpws-socks) + opt="--port=$TPPORT $TPWS_OPT" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$TPWS_OPT_SUFFIX" + do_tpws_socks $1 1 "$opt" + ;; + nfqws) + get_nfqws_qnums qn qns qn6 qns6 + [ -z "$qn" ] || { + opt="--qnum=$qn $NFQWS_OPT_DESYNC_HTTP" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTP_SUFFIX" + do_nfqws $1 1 "$opt" + } + [ -z "$qns" ] || [ "$qns" = "$qn" ] || { + opt="--qnum=$qns $NFQWS_OPT_DESYNC_HTTPS" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS_SUFFIX" + do_nfqws $1 2 "$opt" + } + [ -z "$qn6" ] || [ "$qn6" = "$qn" ] || [ "$qn6" = "$qns" ] || { + opt="--qnum=$qn6 $NFQWS_OPT_DESYNC_HTTP6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTP6_SUFFIX" + do_nfqws $1 3 "$opt" + } + [ -z "$qns6" ] || [ "$qns6" = "$qn" ] || [ "$qns6" = "$qns" ] || [ "$qns6" = "$qn6" ] || { + opt="--qnum=$qns6 $NFQWS_OPT_DESYNC_HTTPS6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_HTTPS6_SUFFIX" + do_nfqws $1 4 "$opt" + } + get_nfqws_qnums_quic qn qn6 + [ -z "$qn" ] || { + opt="--qnum=$qn $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_QUIC_SUFFIX" + do_nfqws $1 10 "$opt" + } + [ -z "$qn6" ] || [ "$qn6" = "$qn" ] || { + opt="--qnum=$qn6 $NFQWS_OPT_BASE $NFQWS_OPT_DESYNC_QUIC6" + filter_apply_hostlist_target opt + filter_apply_suffix opt "$NFQWS_OPT_DESYNC_QUIC6_SUFFIX" + do_nfqws $1 11 "$opt" + } + ;; + custom) + custom_runner zapret_custom_daemons $1 + ;; + esac + + return 0 +} +zapret_run_daemons() +{ + zapret_do_daemons 1 "$@" +} +zapret_stop_daemons() +{ + zapret_do_daemons 0 "$@" +} + diff --git a/init.d/sysv/zapret b/init.d/sysv/zapret new file mode 100755 index 00000000..9e247a47 --- /dev/null +++ b/init.d/sysv/zapret @@ -0,0 +1,83 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: zapret +# Required-Start: $local_fs $network +# Required-Stop: $local_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") +ZAPRET_BASE=$(readlink -f "$EXEDIR/../..") +. "$EXEDIR/functions" + +NAME=zapret +DESC=anti-zapret + +do_start() +{ + zapret_run_daemons + [ "$INIT_APPLY_FW" != "1" ] || { zapret_apply_firewall; } +} +do_stop() +{ + zapret_stop_daemons + [ "$INIT_APPLY_FW" != "1" ] || zapret_unapply_firewall +} + +case "$1" in + start) + do_start + ;; + + stop) + do_stop + ;; + + restart) + do_stop + do_start + ;; + + start-fw|start_fw) + zapret_apply_firewall + ;; + stop-fw|stop_fw) + zapret_unapply_firewall + ;; + + restart-fw|restart_fw) + zapret_unapply_firewall + zapret_apply_firewall + ;; + + start-daemons|start_daemons) + zapret_run_daemons + ;; + stop-daemons|stop_daemons) + zapret_stop_daemons + ;; + restart-daemons|restart_daemons) + zapret_stop_daemons + zapret_run_daemons + ;; + + reload-ifsets|reload_ifsets) + zapret_reload_ifsets + ;; + list-ifsets|list_ifsets) + zapret_list_ifsets + ;; + list-table|list_table) + zapret_list_table + ;; + + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|start-fw|stop-fw|restart-fw|start-daemons|stop-daemons|restart-daemons|reload-ifsets|list-ifsets|list-table}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/install_bin.sh b/install_bin.sh new file mode 100755 index 00000000..c4f1f3ee --- /dev/null +++ b/install_bin.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +BINS=binaries +BINDIR="$EXEDIR/$BINS" + +ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} +. "$ZAPRET_BASE/common/base.sh" + +check_dir() +{ + local dir="$BINDIR/$1" + local exe="$dir/ip2net" + local out + if [ -f "$exe" ]; then + if [ -x "$exe" ]; then + # 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 + out=$(echo 0.0.0.0 | bash -c "\"$exe"\" 2>/dev/null) + elif exists zsh; then + out=$(echo 0.0.0.0 | zsh -c "\"$exe\"" 2>/dev/null) + else + # 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 + out=$(echo 0.0.0.0 | find "$dir" -maxdepth 1 -name ip2net -exec {} \; 2>/dev/null) + fi + [ -n "$out" ] + 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) +unset PKTWS +case $UNAME in + Linux) + ARCHLIST="my x86_64 x86 aarch64 arm mips64r2-msb mips32r1-lsb mips32r1-msb ppc" + PKTWS=nfqws + ;; + Darwin) + ARCHLIST="my mac64" + ;; + FreeBSD) + ARCHLIST="my freebsd-x64" + PKTWS=dvtws + ;; + CYGWIN*) + UNAME=CYGWIN + ARCHLIST="win64" + PKTWS=winws + ;; + *) + ARCHLIST="my" +esac + +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 + for arch in $ARCHLIST + do + [ -d "$BINDIR/$arch" ] || continue + if check_dir $arch; then + echo $arch is OK + echo installing binaries ... + ccp $arch/ip2net ip2net + ccp $arch/mdig mdig + [ -n "$PKTWS" ] && ccp $arch/$PKTWS nfq + [ "$UNAME" = CYGWIN ] || ccp $arch/tpws tpws + exit 0 + else + echo $arch is NOT OK + fi + done + echo no compatible binaries found +fi + +exit 1 diff --git a/install_easy.sh b/install_easy.sh new file mode 100755 index 00000000..f2ae1c12 --- /dev/null +++ b/install_easy.sh @@ -0,0 +1,911 @@ +#!/bin/sh + +# automated script for easy installing zapret + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" +IPSET_DIR="$ZAPRET_BASE/ipset" + +[ -f "$ZAPRET_CONFIG" ] || { + ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" + [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" + cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" +} +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/elevate.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/dialog.sh" +. "$ZAPRET_BASE/common/ipt.sh" +. "$ZAPRET_BASE/common/installer.sh" +. "$ZAPRET_BASE/common/virt.sh" + +# install target +ZAPRET_TARGET=${ZAPRET_TARGET:-/opt/zapret} + +GET_LIST="$IPSET_DIR/get_config.sh" + +[ -n "$TPPORT" ] || TPPORT=988 + +check_readonly_system() +{ + local RO + echo \* checking readonly system + case $SYSTEM in + systemd) + [ -w "$SYSTEMD_SYSTEM_DIR" ] || RO=1 + ;; + openrc) + [ -w "$(dirname "$INIT_SCRIPT")" ] || RO=1 + ;; + esac + [ -z "$RO" ] || { + echo '!!! READONLY SYSTEM DETECTED !!!' + echo '!!! WILL NOT BE ABLE TO CONFIGURE STARTUP !!!' + echo '!!! MANUAL STARTUP CONFIGURATION IS REQUIRED !!!' + ask_yes_no N "do you want to continue" || exitp 5 + } +} + +check_bins() +{ + echo \* checking executables + + fix_perms_bin_test "$EXEDIR" + local arch="$(get_bin_arch)" + local make_target + [ "$FORCE_BUILD" = "1" ] && { + echo forced build mode + if [ "$arch" = "my" ]; then + echo already compiled + else + arch="" + fi + } + if [ -n "$arch" ] ; then + echo found architecture "\"$arch\"" + elif [ -f "$EXEDIR/Makefile" ] && exists make; then + echo trying to compile + [ "$SYSTEM" = "macos" ] && make_target=mac + make -C "$EXEDIR" $make_target || { + echo could not compile + make -C "$EXEDIR" clean + exitp 8 + } + echo compiled + else + echo build tools not found + exitp 8 + fi +} + +call_install_bin() +{ + sh "$EXEDIR/install_bin.sh" $1 +} +get_bin_arch() +{ + call_install_bin getarch +} + +install_binaries() +{ + echo \* installing binaries + + call_install_bin || { + echo compatible binaries not found + exitp 8 + } +} + +select_mode_mode() +{ + local edited v vars MODES="tpws tpws-socks nfqws filter custom" + [ "$SYSTEM" = "macos" ] && MODES="tpws tpws-socks filter custom" + echo + echo select MODE : + ask_list MODE "$MODES" tpws && write_config_var MODE + + case $MODE in + tpws|tpws-socks) + vars="TPWS_OPT TPWS_OPT_SUFFIX" + ;; + nfqws) + vars="NFQWS_OPT_DESYNC NFQWS_OPT_DESYNC_SUFFIX NFQWS_OPT_DESYNC_HTTP NFQWS_OPT_DESYNC_HTTP_SUFFIX NFQWS_OPT_DESYNC_HTTPS NFQWS_OPT_DESYNC_HTTPS_SUFFIX NFQWS_OPT_DESYNC_HTTP6 NFQWS_OPT_DESYNC_HTTP6_SUFFIX NFQWS_OPT_DESYNC_HTTPS6 NFQWS_OPT_DESYNC_HTTPS6_SUFFIX NFQWS_OPT_DESYNC_QUIC NFQWS_OPT_DESYNC_QUIC_SUFFIX NFQWS_OPT_DESYNC_QUIC6 NFQWS_OPT_DESYNC_QUIC6_SUFFIX" + ;; + esac + [ -n "$vars" ] && { + echo + while [ 1=1 ]; do + for var in $vars; do + eval v="\$$var" + echo $var=\"$v\" + done + ask_yes_no N "do you want to edit the options" || { + [ -n "$edited" ] && { + for var in $vars; do + write_config_var $var + done + } + break + } + edit_vars $vars + edited=1 + echo ..edited.. + done + } + [ "$MODE" = custom ] && { + echo + echo "current custom scripts :" + [ -f "$CUSTOM_DIR/custom" ] && echo "legacy custom script $CUSTOM_DIR/custom" + echo "$CUSTOM_DIR/custom.d :" + [ -d "$CUSTOM_DIR/custom.d" ] && ls "$CUSTOM_DIR/custom.d" + echo "Make sure this is ok" + echo + } +} +select_mode_http() +{ + [ "$MODE" != "filter" ] && [ "$MODE" != "tpws-socks" ] && { + echo + ask_yes_no_var MODE_HTTP "enable http support" + write_config_var MODE_HTTP + } +} +select_mode_keepalive() +{ + [ "$MODE" = "nfqws" ] && [ "$MODE_HTTP" = "1" ] && { + echo + echo enable keep alive support only if DPI checks every outgoing packet for http signature + echo dont enable otherwise because it consumes more cpu resources + ask_yes_no_var MODE_HTTP_KEEPALIVE "enable http keep alive support" + write_config_var MODE_HTTP_KEEPALIVE + } +} +select_mode_https() +{ + [ "$MODE" != "filter" ] && [ "$MODE" != "tpws-socks" ] && { + echo + ask_yes_no_var MODE_HTTPS "enable https support" + write_config_var MODE_HTTPS + } +} +select_mode_quic() +{ + [ "$SUBSYS" = "keenetic" ] && { + echo + echo "WARNING ! Keenetic is not officially supported by zapret." + echo "WARNING ! This firmware requires additional manual iptables setup to support udp desync properly." + echo "WARNING ! Keenetic uses proprietary ndmmark to limit MASQUERADE." + echo "WARNING ! Desynced packets may go outside without MASQUERADE with LAN source ip." + echo "WARNING ! To fix this you need to add additional MASQUERADE rule to iptables nat table." + echo "WARNING ! Installer WILL NOT fix it for you automatically." + echo "WARNING ! If you cannot understand what it is all about - do not enable QUIC." + } + [ "$MODE" != "filter" ] && [ "$MODE" != "tpws-socks" ] && [ "$MODE" != "tpws" ] && { + echo + ask_yes_no_var MODE_QUIC "enable quic support" + write_config_var MODE_QUIC + } +} +select_mode_filter() +{ + local filter="none ipset hostlist autohostlist" + [ "$MODE" = "tpws-socks" ] && filter="none hostlist autohostlist" + echo + echo select filtering : + ask_list MODE_FILTER "$filter" none && write_config_var MODE_FILTER +} +select_mode() +{ + select_mode_mode + select_mode_iface + select_mode_http + select_mode_keepalive + select_mode_https + select_mode_quic + select_mode_filter +} + +select_getlist() +{ + if [ "$MODE_FILTER" = "ipset" -o "$MODE_FILTER" = "hostlist" ]; then + local D=N + [ -n "$GETLIST" ] && D=Y + echo + if ask_yes_no $D "do you want to auto download ip/host list"; then + if [ "$MODE_FILTER" = "hostlist" ] ; then + GETLISTS="get_antizapret_domains.sh get_reestr_resolvable_domains.sh get_reestr_hostlist.sh" + GETLIST_DEF="get_antizapret_domains.sh" + else + GETLISTS="get_user.sh get_antifilter_ip.sh get_antifilter_ipsmart.sh get_antifilter_ipsum.sh get_antifilter_ipresolve.sh get_antifilter_allyouneed.sh get_reestr_resolve.sh get_reestr_preresolved.sh get_reestr_preresolved_smart.sh" + GETLIST_DEF="get_antifilter_allyouneed.sh" + fi + ask_list GETLIST "$GETLISTS" "$GETLIST_DEF" && write_config_var GETLIST + return + fi + fi + GETLIST="" + write_config_var GETLIST +} + +ask_config() +{ + select_mode + select_getlist +} + +ask_config_offload() +{ + [ "$FWTYPE" = nftables ] || is_ipt_flow_offload_avail && { + echo + echo flow offloading can greatly increase speed on slow devices and high speed links \(usually 150+ mbits\) + if [ "$SYSTEM" = openwrt ]; then + echo unfortuantely its not compatible with most nfqws options. nfqws traffic must be exempted from flow offloading. + echo donttouch = disable system flow offloading setting if nfqws mode was selected, dont touch it otherwise and dont configure selective flow offloading + echo none = always disable system flow offloading setting and dont configure selective flow offloading + echo software = always disable system flow offloading setting and configure selective software flow offloading + echo hardware = always disable system flow offloading setting and configure selective hardware flow offloading + else + echo offloading is applicable only to forwarded traffic. it has no effect on outgoing traffic + echo hardware flow offloading is available only on specific supporting hardware. most likely will not work on a generic system + fi + echo offloading breaks traffic shaper + echo select flow offloading : + local options="none software hardware" + local default="none" + [ "$SYSTEM" = openwrt ] && { + options="donttouch none software hardware" + default="donttouch" + } + ask_list FLOWOFFLOAD "$options" $default && write_config_var FLOWOFFLOAD + } +} + +ask_config_tmpdir() +{ + # ask tmpdir change for low ram systems with enough free disk space + [ -n "$GETLIST" ] && [ $(get_free_space_mb "$EXEDIR/tmp") -ge 128 ] && [ $(get_ram_mb) -le 400 ] && { + echo + echo /tmp in openwrt is tmpfs. on low RAM systems there may be not enough RAM to store downloaded files + echo default tmpfs has size of 50% RAM + echo "RAM : $(get_ram_mb) Mb" + echo "DISK : $(get_free_space_mb) Mb" + echo select temp file location + [ -z "$TMPDIR" ] && TMPDIR=/tmp + ask_list TMPDIR "/tmp $EXEDIR/tmp" && { + [ "$TMPDIR" = "/tmp" ] && TMPDIR= + write_config_var TMPDIR + } + } +} + +nft_flow_offload() +{ + [ "$UNAME" = Linux -a "$FWTYPE" = nftables -a "$MODE" != "tpws-socks" ] && [ "$FLOWOFFLOAD" = software -o "$FLOWOFFLOAD" = hardware ] +} + +ask_iface() +{ + # $1 - var to ask + # $2 - additional name for empty string synonim + + local ifs i0 def new + eval def="\$$1" + + [ -n "$2" ] && i0="$2 " + case $SYSTEM in + macos) + ifs="$(ifconfig -l)" + ;; + *) + ifs="$(ls /sys/class/net)" + ;; + esac + [ -z "$def" ] && eval $1="$2" + ask_list $1 "$i0$ifs" && { + eval new="\$$1" + [ "$new" = "$2" ] && eval $1="" + write_config_var $1 + } +} +ask_iface_lan() +{ + echo LAN interface : + local opt + nft_flow_offload || opt=NONE + ask_iface IFACE_LAN $opt +} +ask_iface_wan() +{ + echo WAN interface : + local opt + nft_flow_offload || opt=ANY + ask_iface IFACE_WAN $opt +} + +select_mode_iface() +{ + # openwrt has its own interface management scheme + # filter just creates ip tables, no daemons involved + # nfqws sits in POSTROUTING chain and unable to filter by incoming interface + # tpws redirection works in PREROUTING chain + # in tpws-socks mode IFACE_LAN specifies additional bind interface for the socks listener + # it's not possible to instruct tpws to route outgoing connection to an interface (OS routing table decides) + # custom mode can also benefit from interface names (depends on custom script code) + + if [ "$SYSTEM" = "openwrt" ] || [ "$MODE" = "filter" ]; then return; fi + + case "$MODE" in + tpws-socks) + echo "select LAN interface to allow socks access from your LAN. select NONE for localhost only." + echo "expect socks on tcp port $TPPORT" + ask_iface_lan + ;; + tpws) + echo "select LAN interface to operate in router mode. select NONE for local outgoing traffic only." + if [ "$SYSTEM" = "macos" ]; then + echo "WARNING ! OS feature \"internet sharing\" is not supported." + echo "Only manually configured PF router is supported." + else + echo "WARNING ! This installer will not configure routing, NAT, ... for you. Its your responsibility." + fi + ask_iface_lan + ;; + custom) + echo "select LAN interface for your custom script (how it works depends on your code)" + ask_iface_lan + ;; + *) + nft_flow_offload && { + echo "select LAN interface for nftables flow offloading" + ask_iface_lan + } + ;; + esac + + case "$MODE" in + tpws) + echo "select WAN interface for $MODE operations. select ANY to operate on any interface." + [ -n "$IFACE_LAN" ] && echo "WAN filtering works only for local outgoing traffic !" + ask_iface_wan + ;; + nfqws) + echo "select WAN interface for $MODE operations. select ANY to operate on any interface." + ask_iface_wan + ;; + custom) + echo "select WAN interface for your custom script (how it works depends on your code)" + ask_iface_wan + ;; + *) + nft_flow_offload && { + echo "select WAN interface for nftables flow offloading" + ask_iface_wan + } + ;; + esac +} + +default_files() +{ + # $1 - ro location + # $2 - rw location (can be equal to $1) + [ -d "$2/ipset" ] || mkdir -p "$2/ipset" + [ -f "$2/ipset/zapret-hosts-user-exclude.txt" ] || cp "$1/ipset/zapret-hosts-user-exclude.txt.default" "$2/ipset/zapret-hosts-user-exclude.txt" + [ -f "$2/ipset/zapret-hosts-user.txt" ] || echo nonexistent.domain >> "$2/ipset/zapret-hosts-user.txt" + [ -f "$2/ipset/zapret-hosts-user-ipban.txt" ] || touch "$2/ipset/zapret-hosts-user-ipban.txt" + for dir in openwrt sysv macos; do + [ -d "$1/init.d/$dir" ] && { + [ -d "$2/init.d/$dir" ] || mkdir -p "$2/init.d/$dir" + [ -d "$2/init.d/$dir/custom.d" ] || mkdir -p "$2/init.d/$dir/custom.d" + } + done +} +copy_all() +{ + local dir + + cp -R "$1" "$2" + [ -d "$2/tmp" ] || mkdir "$2/tmp" +} +copy_openwrt() +{ + local ARCH="$(get_bin_arch)" + local BINDIR="$1/binaries/$ARCH" + local file + + [ -d "$2" ] || mkdir -p "$2" + + mkdir "$2/tpws" "$2/nfq" "$2/ip2net" "$2/mdig" "$2/binaries" "$2/binaries/$ARCH" "$2/init.d" "$2/tmp" "$2/files" + cp -R "$1/files/fake" "$2/files" + cp -R "$1/common" "$1/ipset" "$2" + cp -R "$1/init.d/openwrt" "$2/init.d" + cp "$1/config" "$1/config.default" "$1/install_easy.sh" "$1/uninstall_easy.sh" "$1/install_bin.sh" "$1/install_prereq.sh" "$1/blockcheck.sh" "$2" + cp "$BINDIR/tpws" "$BINDIR/nfqws" "$BINDIR/ip2net" "$BINDIR/mdig" "$2/binaries/$ARCH" +} + +fix_perms_bin_test() +{ + [ -d "$1" ] || return + find "$1/binaries" -name ip2net ! -perm -111 -exec chmod +x {} \; +} +fix_perms() +{ + [ -d "$1" ] || return + find "$1" -type d -exec chmod 755 {} \; + find "$1" -type f -exec chmod 644 {} \; + local chow + case "$UNAME" in + Linux) + chow=root:root + ;; + *) + chow=root:wheel + esac + chown -R $chow "$1" + find "$1/binaries" '(' -name tpws -o -name dvtws -o -name nfqws -o -name ip2net -o -name mdig ')' -exec chmod 755 {} \; + for f in \ +install_bin.sh \ +blockcheck.sh \ +install_easy.sh \ +install_prereq.sh \ +files/huawei/E8372/zapret-ip \ +files/huawei/E8372/unzapret-ip \ +files/huawei/E8372/run-zapret-hostlist \ +files/huawei/E8372/unzapret \ +files/huawei/E8372/zapret \ +files/huawei/E8372/run-zapret-ip \ +ipset/get_exclude.sh \ +ipset/clear_lists.sh \ +ipset/get_antifilter_ipresolve.sh \ +ipset/get_reestr_resolvable_domains.sh \ +ipset/get_config.sh \ +ipset/get_reestr_preresolved.sh \ +ipset/get_user.sh \ +ipset/get_antifilter_allyouneed.sh \ +ipset/get_reestr_resolve.sh \ +ipset/create_ipset.sh \ +ipset/get_reestr_hostlist.sh \ +ipset/get_ipban.sh \ +ipset/get_antifilter_ipsum.sh \ +ipset/get_antifilter_ipsmart.sh \ +ipset/get_antizapret_domains.sh \ +ipset/get_reestr_preresolved_smart.sh \ +ipset/get_antifilter_ip.sh \ +init.d/pfsense/zapret.sh \ +init.d/macos/zapret \ +init.d/runit/zapret/run \ +init.d/runit/zapret/finish \ +init.d/openrc/zapret \ +init.d/sysv/zapret \ +init.d/openwrt/zapret \ +uninstall_easy.sh \ + ; do chmod 755 "$1/$f" 2>/dev/null ; done +} + + +_backup_settings() +{ + local i=0 + for f in "$@"; do + # safety check + [ -z "$f" -o "$f" = "/" ] && continue + + [ -f "$ZAPRET_TARGET/$f" ] && cp -f "$ZAPRET_TARGET/$f" "/tmp/zapret-bkp-$i" + [ -d "$ZAPRET_TARGET/$f" ] && cp -rf "$ZAPRET_TARGET/$f" "/tmp/zapret-bkp-$i" + i=$(($i+1)) + done +} +_restore_settings() +{ + local i=0 + for f in "$@"; do + # safety check + [ -z "$f" -o "$f" = "/" ] && continue + + [ -f "/tmp/zapret-bkp-$i" ] && mv -f "/tmp/zapret-bkp-$i" "$ZAPRET_TARGET/$f" || rm -f "/tmp/zapret-bkp-$i" + [ -d "/tmp/zapret-bkp-$i" ] && { + [ -d "$ZAPRET_TARGET/$f" ] && rm -r "$ZAPRET_TARGET/$f" + mv -f "/tmp/zapret-bkp-$i" "$ZAPRET_TARGET/$f" || rm -r "/tmp/zapret-bkp-$i" + } + i=$(($i+1)) + done +} +backup_restore_settings() +{ + # $1 - 1 - backup, 0 - restore + local mode=$1 + on_off_function _backup_settings _restore_settings $mode "config" "init.d/sysv/custom" "init.d/sysv/custom.d" "init.d/openwrt/custom" "init.d/openwrt/custom.d" "init.d/macos/custom" "init.d/macos/custom.d" "ipset/zapret-hosts-user.txt" "ipset/zapret-hosts-user-exclude.txt" "ipset/zapret-hosts-user-ipban.txt" "ipset/zapret-hosts-auto.txt" +} + +check_location() +{ + # $1 - copy function + + echo \* checking location + + # use inodes in case something is linked + if [ -d "$ZAPRET_TARGET" ] && [ $(get_dir_inode "$EXEDIR") = $(get_dir_inode "$ZAPRET_TARGET") ]; then + default_files "$ZAPRET_TARGET" "$ZAPRET_RW" + else + echo + echo easy install is supported only from default location : $ZAPRET_TARGET + echo currently its run from $EXEDIR + if ask_yes_no N "do you want the installer to copy it for you"; then + local keep=N + if [ -d "$ZAPRET_TARGET" ]; then + echo + echo installer found existing $ZAPRET_TARGET + echo directory needs to be replaced. config and custom scripts can be kept or replaced with clean version + if ask_yes_no N "do you want to delete all files there and copy this version"; then + echo + if [ $(get_dir_inode "$ZAPRET_BASE") = $(get_dir_inode "$ZAPRET_RW") ]; then + ask_yes_no Y "keep config, custom scripts and user lists" && keep=Y + [ "$keep" = "Y" ] && backup_restore_settings 1 + fi + rm -r "$ZAPRET_TARGET" + else + echo refused to overwrite $ZAPRET_TARGET. exiting + exitp 3 + fi + fi + local B="$(dirname "$ZAPRET_TARGET")" + [ -d "$B" ] || mkdir -p "$B" + $1 "$EXEDIR" "$ZAPRET_TARGET" + fix_perms "$ZAPRET_TARGET" + [ "$keep" = "Y" ] && backup_restore_settings 0 + echo relaunching itself from $ZAPRET_TARGET + exec "$ZAPRET_TARGET/$(basename "$0")" + else + echo copying aborted. exiting + exitp 3 + fi + fi + echo running from $EXEDIR +} + + +service_install_systemd() +{ + echo \* installing zapret service + + if [ -w "$SYSTEMD_SYSTEM_DIR" ] ; then + rm -f "$INIT_SCRIPT" + ln -fs "$EXEDIR/init.d/systemd/zapret.service" "$SYSTEMD_SYSTEM_DIR" + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" enable zapret || { + echo could not enable systemd service + exitp 20 + } + else + echo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!' + fi +} + +timer_install_systemd() +{ + echo \* installing zapret-list-update timer + + if [ -w "$SYSTEMD_SYSTEM_DIR" ] ; then + "$SYSTEMCTL" disable zapret-list-update.timer + "$SYSTEMCTL" stop zapret-list-update.timer + ln -fs "$EXEDIR/init.d/systemd/zapret-list-update.service" "$SYSTEMD_SYSTEM_DIR" + ln -fs "$EXEDIR/init.d/systemd/zapret-list-update.timer" "$SYSTEMD_SYSTEM_DIR" + "$SYSTEMCTL" daemon-reload + "$SYSTEMCTL" enable zapret-list-update.timer || { + echo could not enable zapret-list-update.timer + exitp 20 + } + "$SYSTEMCTL" start zapret-list-update.timer || { + echo could not start zapret-list-update.timer + exitp 30 + } + else + echo '!!! READONLY SYSTEM DETECTED !!! CANNOT INSTALL SYSTEMD UNITS !!!' + fi +} + +download_list() +{ + [ -x "$GET_LIST" ] && { + echo \* downloading blocked ip/host list + + # can be txt or txt.gz + "$IPSET_DIR/clear_lists.sh" + "$GET_LIST" + } +} + + +dnstest() +{ + # $1 - dns server. empty for system resolver + nslookup w3.org $1 >/dev/null 2>/dev/null +} +check_dns() +{ + echo \* checking DNS + + dnstest || { + echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. + return 1 + } + echo system DNS is working + return 0 +} + + +install_systemd() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" + CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" + + check_bins + require_root + check_readonly_system + check_location copy_all + check_dns + check_virt + service_stop_systemd + select_fwtype + check_prerequisites_linux + install_binaries + select_ipv6 + ask_config_offload + ask_config + service_install_systemd + download_list + # in case its left from old version of zapret + crontab_del_quiet + # now we use systemd timers + timer_install_systemd + service_start_systemd +} + +_install_sysv() +{ + # $1 - install init script + + CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" + + check_bins + require_root + check_readonly_system + check_location copy_all + check_dns + check_virt + service_stop_sysv + select_fwtype + check_prerequisites_linux + install_binaries + select_ipv6 + ask_config_offload + ask_config + $1 + download_list + crontab_del_quiet + # desktop system. more likely up at daytime + crontab_add 10 22 + service_start_sysv +} + +install_sysv() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" + _install_sysv install_sysv_init +} + +install_openrc() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/openrc/zapret" + _install_sysv install_openrc_init +} + + +install_linux() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" + CUSTOM_DIR="$ZAPRET_RW/init.d/sysv" + + check_bins + require_root + check_location copy_all + check_dns + check_virt + select_fwtype + check_prerequisites_linux + install_binaries + select_ipv6 + ask_config_offload + ask_config + download_list + crontab_del_quiet + # desktop system. more likely up at daytime + crontab_add 10 22 + + echo + echo '!!! WARNING. YOUR SETUP IS INCOMPLETE !!!' + echo you must manually add to auto start : $INIT_SCRIPT_SRC start + echo make sure it\'s executed after your custom/firewall iptables configuration + echo "if your system uses sysv init : ln -fs $INIT_SCRIPT_SRC /etc/init.d/zapret ; chkconfig zapret on" +} + + +deoffload_openwrt_firewall() +{ + echo \* checking flow offloading + + [ "$FWTYPE" = "nftables" ] || is_ipt_flow_offload_avail || { + echo unavailable + return + } + + local fo=$(uci -q get firewall.@defaults[0].flow_offloading) + + if [ "$fo" = "1" ] ; then + local mod=0 + printf "system wide flow offloading detected. " + case $FLOWOFFLOAD in + donttouch) + if [ "$MODE" = "nfqws" ]; then + echo its incompatible with nfqws tcp data tampering. disabling + uci set firewall.@defaults[0].flow_offloading=0 + mod=1 + else + if [ "$MODE" = "custom" ] ; then + echo custom mode selected !!! only you can decide whether flow offloading is compatible + else + echo its compatible with selected options. not disabling + fi + fi + ;; + *) + echo zapret will disable system wide offloading setting and add selective rules if required + uci set firewall.@defaults[0].flow_offloading=0 + mod=1 + esac + [ "$mod" = "1" ] && uci commit firewall + else + echo system wide software flow offloading disabled. ok + fi + +} + + + +install_openwrt() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/openwrt/zapret" + CUSTOM_DIR="$ZAPRET_RW/init.d/openwrt" + FW_SCRIPT_SRC="$EXEDIR/init.d/openwrt/firewall.zapret" + OPENWRT_FW_INCLUDE=/etc/firewall.zapret + OPENWRT_IFACE_HOOK="$EXEDIR/init.d/openwrt/90-zapret" + + check_bins + require_root + check_location copy_openwrt + install_binaries + check_dns + check_virt + + local FWTYPE_OLD=$FWTYPE + + echo \* stopping current firewall rules/daemons + "$INIT_SCRIPT_SRC" stop_fw + "$INIT_SCRIPT_SRC" stop_daemons + + select_fwtype + select_ipv6 + check_prerequisites_openwrt + ask_config + ask_config_tmpdir + ask_config_offload + # stop and reinstall sysv init + install_sysv_init + [ "$FWTYPE_OLD" != "$FWTYPE" -a "$FWTYPE_OLD" = iptables -a -n "$OPENWRT_FW3" ] && remove_openwrt_firewall + # free some RAM + clear_ipset + download_list + crontab_del_quiet + # router system : works 24/7. night is the best time + crontab_add 0 6 + cron_ensure_running + install_openwrt_iface_hook + # in case of nftables or iptables without fw3 sysv init script also controls firewall + [ -n "$OPENWRT_FW3" -a "$FWTYPE" = iptables ] && install_openwrt_firewall + service_start_sysv + deoffload_openwrt_firewall + restart_openwrt_firewall +} + + + +remove_pf_zapret_hooks() +{ + echo \* removing zapret PF hooks + + pf_anchors_clear +} + +macos_fw_reload_trigger_clear() +{ + case "$MODE" in + tpws|tpws-socks|custom) + LISTS_RELOAD= + write_config_var LISTS_RELOAD + ;; + esac +} +macos_fw_reload_trigger_set() +{ + case "$MODE" in + tpws|custom) + LISTS_RELOAD="$INIT_SCRIPT_SRC reload-fw-tables" + write_config_var LISTS_RELOAD + ;; + esac +} + +install_macos() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/macos/zapret" + CUSTOM_DIR="$ZAPRET_RW/init.d/macos" + + # compile before root + check_bins + require_root + check_location copy_all + service_stop_macos + remove_pf_zapret_hooks + install_binaries + check_dns + select_ipv6 + ask_config + service_install_macos + macos_fw_reload_trigger_clear + # gzip lists are incompatible with PF + GZIP_LISTS=0 write_config_var GZIP_LISTS + download_list + macos_fw_reload_trigger_set + crontab_del_quiet + # desktop system. more likely up at daytime + crontab_add 10 22 + service_start_macos +} + + +# build binaries, do not use precompiled +[ "$1" = "make" ] && FORCE_BUILD=1 + +umask 0022 +fix_sbin_path +fsleep_setup +check_system + +[ "$SYSTEM" = "macos" ] && . "$EXEDIR/init.d/macos/functions" + +case $SYSTEM in + systemd) + install_systemd + ;; + openrc) + install_openrc + ;; + linux) + install_linux + ;; + openwrt) + install_openwrt + ;; + macos) + install_macos + ;; +esac + + +exitp 0 diff --git a/install_prereq.sh b/install_prereq.sh new file mode 100755 index 00000000..be938cce --- /dev/null +++ b/install_prereq.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# install prerequisites + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" + +[ -f "$ZAPRET_CONFIG" ] || { + ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" + [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" + cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" +} + +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/elevate.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/dialog.sh" +. "$ZAPRET_BASE/common/installer.sh" +. "$ZAPRET_BASE/common/ipt.sh" + +umask 0022 +fix_sbin_path +fsleep_setup +check_system accept_unknown_rc +[ $UNAME = "Linux" ] || { + echo no prerequisites required for $UNAME + exitp 0 +} +require_root + +case $UNAME in + Linux) + select_fwtype + case $SYSTEM in + openwrt) + select_ipv6 + check_prerequisites_openwrt + ;; + *) + check_prerequisites_linux + ;; + esac + ;; +esac + +exitp 0 diff --git a/ip2net/Makefile b/ip2net/Makefile new file mode 100644 index 00000000..97a53d76 --- /dev/null +++ b/ip2net/Makefile @@ -0,0 +1,28 @@ +CC ?= gcc +CFLAGS += -std=gnu99 -O3 +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 $@ $(SRC_FILES) $(LDFLAGS) $(LIBS) + +bsd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o ip2net $(SRC_FILES) $(LDFLAGS) $(LIBS) + +mac: $(SRC_FILES) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2neta $(SRC_FILES) $(LDFLAGS) -target arm64-apple-macos10.8 $(LIBS) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o ip2netx $(SRC_FILES) $(LDFLAGS) -target x86_64-apple-macos10.8 $(LIBS) + 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) $(LDFLAGS) $(LIBS_WIN) + +clean: + rm -f ip2net *.o diff --git a/ip2net/ip2net.c b/ip2net/ip2net.c new file mode 100644 index 00000000..793ee721 --- /dev/null +++ b/ip2net/ip2net.c @@ -0,0 +1,495 @@ +// 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 = '\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); +} + +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; + + const struct option long_options[] = { + { "help",no_argument,0,0 },// optidx=0 + { "h",no_argument,0,0 },// optidx=1 + { "4",no_argument,0,0 },// optidx=2 + { "6",no_argument,0,0 },// optidx=3 + { "prefix-length",required_argument,0,0 },// optidx=4 + { "v4-threshold",required_argument,0,0 },// optidx=5 + { "v6-threshold",required_argument,0,0 },// optidx=6 + { NULL,0,NULL,0 } + }; + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp(); + switch (option_index) + { + case 0: + case 1: + exithelp(); + break; + case 2: + params.ipv6 = false; + break; + case 3: + params.ipv6 = true; + break; + case 4: + 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 6: + 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 00000000..2ee11850 --- /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 00000000..f537ab75 --- /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/ipset/antifilter.helper b/ipset/antifilter.helper new file mode 100644 index 00000000..05082093 --- /dev/null +++ b/ipset/antifilter.helper @@ -0,0 +1,19 @@ +get_antifilter() +{ + # $1 - list url + # $2 - target file + local ZIPLISTTMP="$TMPDIR/zapret-ip.txt" + + [ "$DISABLE_IPV4" != "1" ] && { + curl --fail --max-time 150 --connect-timeout 20 --max-filesize 41943040 -k -L "$1" | cut_local >"$ZIPLISTTMP" && + { + dlsize=$(LANG=C wc -c "$ZIPLISTTMP" | xargs | cut -f 1 -d ' ') + if [ $dlsize -lt 102400 ]; then + echo list file is too small. can be bad. + exit 2 + fi + ip2net4 <"$ZIPLISTTMP" | zz "$2" + rm -f "$ZIPLISTTMP" + } + } +} diff --git a/ipset/clear_lists.sh b/ipset/clear_lists.sh new file mode 100755 index 00000000..80c15319 --- /dev/null +++ b/ipset/clear_lists.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +rm -f "$ZIPLIST"* "$ZIPLIST6"* "$ZIPLIST_USER" "$ZIPLIST_USER6" "$ZIPLIST_IPBAN"* "$ZIPLIST_IPBAN6"* "$ZIPLIST_USER_IPBAN" "$ZIPLIST_USER_IPBAN6" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6" "$ZHOSTLIST"* diff --git a/ipset/create_ipset.sh b/ipset/create_ipset.sh new file mode 100755 index 00000000..a88137df --- /dev/null +++ b/ipset/create_ipset.sh @@ -0,0 +1,308 @@ +#!/bin/sh + +# create ipset or ipfw table from resolved ip's +# $1=no-update - do not update ipset, only create if its absent +# $1=clear - clear ipset + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" + +. "$EXEDIR/def.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/nft.sh" + +IPSET_CMD="$TMPDIR/ipset_cmd.txt" +IPSET_SAVERAM_CHUNK_SIZE=20000 +IPSET_SAVERAM_MIN_FILESIZE=131072 + +NFSET_TEMP="$TMPDIR/nfset_temp.txt" +NFSET_SAVERAM_MIN_FILESIZE=16384 +NFSET_SAVERAM_CHUNK_SIZE=1000 + +IPSET_HOOK_TEMP="$TMPDIR/ipset_hook.txt" + +while [ -n "$1" ]; do + [ "$1" = "no-update" ] && NO_UPDATE=1 + [ "$1" = "clear" ] && DO_CLEAR=1 + shift +done + + +file_extract_lines() +{ + # $1 - filename + # $2 - from line (starting with 0) + # $3 - line count + # awk "{ err=1 } NR < $(($2+1)) { next } { print; err=0 } NR == $(($2+$3)) { exit err } END {exit err}" "$1" + $AWK "NR < $(($2+1)) { next } { print } NR == $(($2+$3)) { exit }" "$1" +} +ipset_restore_chunked() +{ + # $1 - filename + # $2 - chunk size + local pos lines + [ -f "$1" ] || return + lines=$(wc -l <"$1") + pos=$lines + while [ "$pos" -gt "0" ]; do + pos=$((pos-$2)) + [ "$pos" -lt "0" ] && pos=0 + file_extract_lines "$1" $pos $2 | ipset -! restore + sed -i "$(($pos+1)),$ d" "$1" + done +} + + +ipset_get_script() +{ + # $1 - ipset name + sed -nEe "s/^.+$/add $1 &/p" +} +ipset_get_script_from_file() +{ + # $1 - filename + # $2 - ipset name + zzcat "$1" | sort -u | ipset_get_script $2 +} +ipset_restore() +{ + # $1 - ipset name + # $2 - filename + + zzexist "$2" || return + local fsize=$(zzsize "$2") + local svram=0 + # do not saveram small files. file can also be gzipped + [ "$SAVERAM" = "1" ] && [ "$fsize" -ge "$IPSET_SAVERAM_MIN_FILESIZE" ] && svram=1 + + local T="Adding to ipset $1 " + [ "$svram" = "1" ] && T="$T (saveram)" + T="$T : $f" + echo $T + + if [ "$svram" = "1" ]; then + ipset_get_script_from_file "$2" "$1" >"$IPSET_CMD" + ipset_restore_chunked "$IPSET_CMD" $IPSET_SAVERAM_CHUNK_SIZE + rm -f "$IPSET_CMD" + else + ipset_get_script_from_file "$2" "$1" | ipset -! restore + fi +} +create_ipset() +{ + if [ "$1" -eq "6" ]; then + FAMILY=inet6 + else + FAMILY=inet + fi + ipset create $2 $3 $4 family $FAMILY 2>/dev/null || { + [ "$NO_UPDATE" = "1" ] && return 0 + } + ipset flush $2 + [ "$DO_CLEAR" = "1" ] || { + for f in "$5" "$6" ; do + ipset_restore "$2" "$f" + done + [ -n "$IPSET_HOOK" ] && $IPSET_HOOK $2 | ipset_get_script $2 | ipset -! restore + } + return 0 +} + +nfset_get_script_multi() +{ + # $1 - set name + # $2,$3,... - filenames + + # all in one shot. this allows to merge overlapping ranges + # good but eats lots of RAM + + local set=$1 nonempty N=1 f + + shift + # first we need to make sure at least one element exists or nft will fail + while : + do + eval f=\$$N + [ -n "$f" ] || break + nonempty=$(zzexist "$f" && zzcat "$f" 2>/dev/null | head -n 1) + [ -n "$nonempty" ] && break + N=$(($N+1)) + done + + [ -n "$nonempty" ] && { + echo "add element inet $ZAPRET_NFT_TABLE $set {" + while [ -n "$1" ]; do + zzexist "$1" && zzcat "$1" | sed -nEe "s/^.+$/&,/p" + shift + done + echo "}" + } +} +nfset_restore() +{ + # $1 - set name + # $2,$3,... - filenames + + echo "Adding to nfset $1 : $2 $3 $4 $5" + local hookfile + [ -n "$IPSET_HOOK" ] && { + $IPSET_HOOK $1 >"$IPSET_HOOK_TEMP" + [ -s "$IPSET_HOOK_TEMP" ] && hookfile=$IPSET_HOOK_TEMP + } + nfset_get_script_multi "$@" $hookfile | nft -f - + rm -f "$IPSET_HOOK_TEMP" +} +create_nfset() +{ + # $1 - family + # $2 - set name + # $3 - maxelem + # $4,$5 - list files + + local policy + [ $SAVERAM = "1" ] && policy="policy memory;" + nft_create_set $2 "type ipv${1}_addr; size $3; flags interval; auto-merge; $policy" || { + [ "$NO_UPDATE" = "1" ] && return 0 + nft flush set inet $ZAPRET_NFT_TABLE $2 + } + [ "$DO_CLEAR" = "1" ] || { + nfset_restore $2 $4 $5 + } + return 0 +} + +add_ipfw_table() +{ + # $1 - table name + sed -nEe "s/^.+$/table $1 add &/p" | ipfw -q /dev/stdin +} +populate_ipfw_table() +{ + # $1 - table name + # $2 - ip list file + zzexist "$2" || return + zzcat "$2" | sort -u | add_ipfw_table $1 +} +create_ipfw_table() +{ + # $1 - table name + # $2 - table options + # $3,$4, ... - ip list files. can be v4,v6 or mixed + + local name=$1 + ipfw table "$name" create $2 2>/dev/null || { + [ "$NO_UPDATE" = "1" ] && return 0 + } + ipfw -q table $1 flush + shift + shift + [ "$DO_CLEAR" = "1" ] || { + while [ -n "$1" ]; do + echo "Adding to ipfw table $name : $1" + populate_ipfw_table $name "$1" + shift + done + [ -n "$IPSET_HOOK" ] && $IPSET_HOOK $name | add_ipfw_table $name + } + return 0 +} + +print_reloading_backend() +{ + # $1 - backend name + local s="reloading $1 backend" + if [ "$NO_UPDATE" = 1 ]; then + s="$s (no-update)" + elif [ "$DO_CLEAR" = 1 ]; then + s="$s (clear)" + else + s="$s (forced-update)" + fi + echo $s +} + + +oom_adjust_high +get_fwtype + +if [ -n "$LISTS_RELOAD" ] ; then + if [ "$LISTS_RELOAD" = "-" ] ; then + echo not reloading ip list backend + true + else + echo executing custom ip list reload command : $LISTS_RELOAD + $LISTS_RELOAD + [ -n "$IPSET_HOOK" ] && $IPSET_HOOK + fi +else + case "$FWTYPE" in + iptables) + # ipset seem to buffer the whole script to memory + # on low RAM system this can cause oom errors + # in SAVERAM mode we feed script lines in portions starting from the end, while truncating source file to free /tmp space + # only /tmp is considered tmpfs. other locations mean tmpdir was redirected to a disk + SAVERAM=0 + [ "$TMPDIR" = "/tmp" ] && { + RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}') + [ "$RAMSIZE" -lt "110000" ] && SAVERAM=1 + } + print_reloading_backend ipset + [ "$DISABLE_IPV4" != "1" ] && { + create_ipset 4 $ZIPSET hash:net "$IPSET_OPT" "$ZIPLIST" "$ZIPLIST_USER" + create_ipset 4 $ZIPSET_IPBAN hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" + create_ipset 4 $ZIPSET_EXCLUDE hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" + } + [ "$DISABLE_IPV6" != "1" ] && { + create_ipset 6 $ZIPSET6 hash:net "$IPSET_OPT" "$ZIPLIST6" "$ZIPLIST_USER6" + create_ipset 6 $ZIPSET_IPBAN6 hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" + create_ipset 6 $ZIPSET_EXCLUDE6 hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6" + } + true + ;; + nftables) + nft_create_table && { + SAVERAM=0 + RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}') + [ "$RAMSIZE" -lt "420000" ] && SAVERAM=1 + print_reloading_backend "nftables set" + [ "$DISABLE_IPV4" != "1" ] && { + create_nfset 4 $ZIPSET $SET_MAXELEM "$ZIPLIST" "$ZIPLIST_USER" + create_nfset 4 $ZIPSET_IPBAN $SET_MAXELEM "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" + create_nfset 4 $ZIPSET_EXCLUDE $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE" + } + [ "$DISABLE_IPV6" != "1" ] && { + create_nfset 6 $ZIPSET6 $SET_MAXELEM "$ZIPLIST6" "$ZIPLIST_USER6" + create_nfset 6 $ZIPSET_IPBAN6 $SET_MAXELEM "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" + create_nfset 6 $ZIPSET_EXCLUDE6 $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE6" + } + true + } + ;; + ipfw) + print_reloading_backend "ipfw table" + if [ "$DISABLE_IPV4" != "1" ] && [ "$DISABLE_IPV6" != "1" ]; then + create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" "$ZIPLIST6" "$ZIPLIST_USER6" + create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" + create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6" + elif [ "$DISABLE_IPV4" != "1" ]; then + create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" + create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" + create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" + elif [ "$DISABLE_IPV6" != "1" ]; then + create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST6" "$ZIPLIST_USER6" + create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6" + create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6" + else + create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" + create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" + create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" + fi + true + ;; + *) + echo no supported ip list backend found + true + ;; + esac + +fi diff --git a/ipset/def.sh b/ipset/def.sh new file mode 100644 index 00000000..174cc35b --- /dev/null +++ b/ipset/def.sh @@ -0,0 +1,270 @@ +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE=${ZAPRET_BASE:-"$(cd "$EXEDIR/.."; pwd)"} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +IPSET_RW_DIR="$ZAPRET_RW/ipset" + +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" + +[ -z "$TMPDIR" ] && TMPDIR=/tmp +[ -z "$GZIP_LISTS" ] && GZIP_LISTS=1 + +[ -z "$SET_MAXELEM" ] && SET_MAXELEM=262144 +[ -z "$IPSET_OPT" ] && IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM" +[ -z "$SET_MAXELEM_EXCLUDE" ] && SET_MAXELEM_EXCLUDE=65536 +[ -z "$IPSET_OPT_EXCLUDE" ] && IPSET_OPT_EXCLUDE="hashsize 1024 maxelem $SET_MAXELEM_EXCLUDE" + +[ -z "$IPFW_TABLE_OPT" ] && IPFW_TABLE_OPT="algo addr:radix" +[ -z "$IPFW_TABLE_OPT_EXCLUDE" ] && IPFW_TABLE_OPT_EXCLUDE="algo addr:radix" + +ZIPSET=zapret +ZIPSET6=zapret6 +ZIPSET_EXCLUDE=nozapret +ZIPSET_EXCLUDE6=nozapret6 +ZIPLIST="$IPSET_RW_DIR/zapret-ip.txt" +ZIPLIST6="$IPSET_RW_DIR/zapret-ip6.txt" +ZIPLIST_EXCLUDE="$IPSET_RW_DIR/zapret-ip-exclude.txt" +ZIPLIST_EXCLUDE6="$IPSET_RW_DIR/zapret-ip-exclude6.txt" +ZIPLIST_USER="$IPSET_RW_DIR/zapret-ip-user.txt" +ZIPLIST_USER6="$IPSET_RW_DIR/zapret-ip-user6.txt" +ZUSERLIST="$IPSET_RW_DIR/zapret-hosts-user.txt" +ZHOSTLIST="$IPSET_RW_DIR/zapret-hosts.txt" + +ZIPSET_IPBAN=ipban +ZIPSET_IPBAN6=ipban6 +ZIPLIST_IPBAN="$IPSET_RW_DIR/zapret-ip-ipban.txt" +ZIPLIST_IPBAN6="$IPSET_RW_DIR/zapret-ip-ipban6.txt" +ZIPLIST_USER_IPBAN="$IPSET_RW_DIR/zapret-ip-user-ipban.txt" +ZIPLIST_USER_IPBAN6="$IPSET_RW_DIR/zapret-ip-user-ipban6.txt" +ZUSERLIST_IPBAN="$IPSET_RW_DIR/zapret-hosts-user-ipban.txt" +ZUSERLIST_EXCLUDE="$IPSET_RW_DIR/zapret-hosts-user-exclude.txt" + + +[ -n "$IP2NET" ] || IP2NET="$ZAPRET_BASE/ip2net/ip2net" +[ -n "$MDIG" ] || MDIG="$ZAPRET_BASE/mdig/mdig" +[ -z "$MDIG_THREADS" ] && MDIG_THREADS=30 + + + +# BSD grep is damn slow with -f option. prefer GNU grep (ggrep) if present +# MacoS in cron does not include /usr/local/bin to PATH +if [ -x /usr/local/bin/ggrep ] ; then + GREP=/usr/local/bin/ggrep +elif [ -x /usr/local/bin/grep ] ; then + GREP=/usr/local/bin/grep +elif exists ggrep; then + GREP=$(whichq ggrep) +else + GREP=$(whichq grep) +fi + +# GNU awk is faster +if exists gawk; then + AWK=gawk +else + AWK=awk +fi + +grep_supports_b() +{ + # \b does not work with BSD grep + $GREP --version 2>&1 | $GREP -qE "BusyBox|GNU" +} +get_ip_regex() +{ + REG_IPV4='((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12][0-9]|3[012]))?' + REG_IPV6='[0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}|:)+(\/([0-9][0-9]?|1[01][0-9]|12[0-8]))?' + # good but too slow + # REG_IPV6='([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,7}:(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}(/[0-9]+)?|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}(/[0-9]+)?|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})(/[0-9]+)?|:((:[0-9a-fA-F]{1,4}){1,7}|:)(/([0-9][0-9]?|1[01][0-9]|12[0-8]))?' +# grep_supports_b && { +# REG_IPV4="\b$REG_IPV4\b" +# REG_IPV6="\b$REG_IPV6\b" +# } +} + +ip2net4() +{ + if [ -x "$IP2NET" ]; then + "$IP2NET" -4 $IP2NET_OPT4 + else + sort -u + fi +} +ip2net6() +{ + if [ -x "$IP2NET" ]; then + "$IP2NET" -6 $IP2NET_OPT6 + else + sort -u + fi +} + +zzexist() +{ + [ -f "$1.gz" ] || [ -f "$1" ] +} +zztest() +{ + gzip -t "$1" 2>/dev/null +} +zzcat() +{ + if [ -f "$1.gz" ]; then + gunzip -c "$1.gz" + elif [ -f "$1" ]; then + if zztest "$1"; then + gunzip -c "$1" + else + cat "$1" + fi + fi +} +zz() +{ + if [ "$GZIP_LISTS" = "1" ]; then + gzip -c >"$1.gz" + rm -f "$1" + else + cat >"$1" + rm -f "$1.gz" + fi +} +zzsize() +{ + local f="$1" + [ -f "$1.gz" ] && f="$1.gz" + if [ -f "$f" ]; then + wc -c <"$f" | xargs + else + printf 0 + fi +} + +digger() +{ + # $1 - family (4|6) + # $2 - s=enable mdig stats + if [ -x "$MDIG" ]; then + local cmd + [ "$2" = "s" ] && cmd=--stats=1000 + "$MDIG" --family=$1 --threads=$MDIG_THREADS $cmd + else + local A=A + [ "$1" = "6" ] && A=AAAA + dig $A +short +time=8 +tries=2 -f - | $GREP -E '^[^;].*[^\.]$' + fi +} +filedigger() +{ + # $1 - hostlist + # $2 - family (4|6) + >&2 echo digging $(wc -l <"$1" | xargs) ipv$2 domains : "$1" + zzcat "$1" | digger $2 s +} +flush_dns_cache() +{ + echo clearing all known DNS caches + + if exists killall; then + killall -HUP dnsmasq 2>/dev/null + # MacOS + killall -HUP mDNSResponder 2>/dev/null + elif exists pkill; then + pkill -HUP ^dnsmasq$ + else + echo no mass killer available ! cant flush dnsmasq + fi + + if exists rndc; then + rndc flush + fi + + if exists systemd-resolve; then + systemd-resolve --flush-caches + fi + +} +dnstest() +{ + local ip="$(echo w3.org | digger 46)" + [ -n "$ip" ] +} +dnstest_with_cache_clear() +{ + flush_dns_cache + if dnstest ; then + echo DNS is working + return 0 + else + echo "! DNS is not working" + return 1 + fi +} + + +cut_local() +{ + $GREP -vE '^192\.168\.|^127\.|^10\.' +} +cut_local6() +{ + $GREP -vE '^::|^fc..:|^fd..:|^fe8.:|^fe9.:|^fea.:|^feb.:|^FC..:|^FD..:|^FE8.:|^FE9.:|^FEA.:|^FEB.:' +} + +oom_adjust_high() +{ + [ -f /proc/$$/oom_score_adj ] && { + echo setting high oom kill priority + echo -n 100 >/proc/$$/oom_score_adj + } +} + +getexclude() +{ + oom_adjust_high + dnstest_with_cache_clear || return + [ -f "$ZUSERLIST_EXCLUDE" ] && { + [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST_EXCLUDE" 4 | sort -u > "$ZIPLIST_EXCLUDE" + [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST_EXCLUDE" 6 | sort -u > "$ZIPLIST_EXCLUDE6" + } + return 0 +} + +_get_ipban() +{ + [ -f "$ZUSERLIST_IPBAN" ] && { + [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST_IPBAN" 4 | cut_local | sort -u > "$ZIPLIST_USER_IPBAN" + [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST_IPBAN" 6 | cut_local6 | sort -u > "$ZIPLIST_USER_IPBAN6" + } +} +getuser() +{ + getexclude || return + [ -f "$ZUSERLIST" ] && { + [ "$DISABLE_IPV4" != "1" ] && filedigger "$ZUSERLIST" 4 | cut_local | sort -u > "$ZIPLIST_USER" + [ "$DISABLE_IPV6" != "1" ] && filedigger "$ZUSERLIST" 6 | cut_local6 | sort -u > "$ZIPLIST_USER6" + } + _get_ipban + return 0 +} +getipban() +{ + getexclude || return + _get_ipban + return 0 +} + +hup_zapret_daemons() +{ + echo forcing zapret daemons to reload their hostlist + if exists killall; then + killall -HUP tpws nfqws dvtws 2>/dev/null + elif exists pkill; then + pkill -HUP ^tpws$ ^nfqws$ ^dvtws$ + else + echo no mass killer available ! cant HUP zapret daemons + fi +} + diff --git a/ipset/get_antifilter_allyouneed.sh b/ipset/get_antifilter_allyouneed.sh new file mode 100755 index 00000000..a5b3d225 --- /dev/null +++ b/ipset/get_antifilter_allyouneed.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser && { + . "$IPSET_DIR/antifilter.helper" + get_antifilter https://antifilter.download/list/allyouneed.lst "$ZIPLIST" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ip.sh b/ipset/get_antifilter_ip.sh new file mode 100755 index 00000000..e2cd085c --- /dev/null +++ b/ipset/get_antifilter_ip.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser && { + . "$IPSET_DIR/antifilter.helper" + get_antifilter https://antifilter.download/list/ip.lst "$ZIPLIST" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ipresolve.sh b/ipset/get_antifilter_ipresolve.sh new file mode 100755 index 00000000..de08e286 --- /dev/null +++ b/ipset/get_antifilter_ipresolve.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser && { + . "$IPSET_DIR/antifilter.helper" + get_antifilter https://antifilter.download/list/ipresolve.lst "$ZIPLIST" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ipsmart.sh b/ipset/get_antifilter_ipsmart.sh new file mode 100755 index 00000000..9f0d671d --- /dev/null +++ b/ipset/get_antifilter_ipsmart.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser && { + . "$IPSET_DIR/antifilter.helper" + get_antifilter https://antifilter.network/download/ipsmart.lst "$ZIPLIST" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_antifilter_ipsum.sh b/ipset/get_antifilter_ipsum.sh new file mode 100755 index 00000000..ccf1c8fc --- /dev/null +++ b/ipset/get_antifilter_ipsum.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser && { + . "$IPSET_DIR/antifilter.helper" + get_antifilter https://antifilter.download/list/ipsum.lst "$ZIPLIST" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_antizapret_domains.sh b/ipset/get_antizapret_domains.sh new file mode 100755 index 00000000..93848acf --- /dev/null +++ b/ipset/get_antizapret_domains.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +# useful in case ipban set is used in custom scripts +FAIL= +getipban || FAIL=1 +"$IPSET_DIR/create_ipset.sh" +[ -n "$FAIL" ] && exit + +ZURL=https://antizapret.prostovpn.org:8443/domains-export.txt +ZDOM="$TMPDIR/zapret.txt" + + +curl -H "Accept-Encoding: gzip" -k --fail --max-time 600 --connect-timeout 5 --retry 3 --max-filesize 251658240 "$ZURL" | gunzip - >"$ZDOM" || +{ + echo domain list download failed + exit 2 +} + +dlsize=$(LANG=C wc -c "$ZDOM" | xargs | cut -f 1 -d ' ') +if test $dlsize -lt 102400; then + echo list file is too small. can be bad. + exit 2 +fi + +sort -u "$ZDOM" | zz "$ZHOSTLIST" + +rm -f "$ZDOM" + +hup_zapret_daemons + +exit 0 diff --git a/ipset/get_config.sh b/ipset/get_config.sh new file mode 100755 index 00000000..f751f182 --- /dev/null +++ b/ipset/get_config.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# run script specified in config + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/../config" + +[ -z "$GETLIST" ] && GETLIST=get_ipban.sh +[ -x "$IPSET_DIR/$GETLIST" ] && exec "$IPSET_DIR/$GETLIST" diff --git a/ipset/get_exclude.sh b/ipset/get_exclude.sh new file mode 100755 index 00000000..adaf8d63 --- /dev/null +++ b/ipset/get_exclude.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# resolve user host list + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getexclude + +"$IPSET_DIR/create_ipset.sh" + +[ "$MODE_FILTER" = hostlist ] && hup_zapret_daemons diff --git a/ipset/get_ipban.sh b/ipset/get_ipban.sh new file mode 100755 index 00000000..2bda9810 --- /dev/null +++ b/ipset/get_ipban.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# resolve only ipban user host list + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getipban + +"$IPSET_DIR/create_ipset.sh" + +[ "$MODE_FILTER" = hostlist ] && hup_zapret_daemons diff --git a/ipset/get_reestr_hostlist.sh b/ipset/get_reestr_hostlist.sh new file mode 100755 index 00000000..66912688 --- /dev/null +++ b/ipset/get_reestr_hostlist.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +ZREESTR="$TMPDIR/zapret.txt" +IPB="$TMPDIR/ipb.txt" +ZURL_REESTR=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +dl_checked() +{ + # $1 - url + # $2 - file + # $3 - minsize + # $4 - maxsize + # $5 - maxtime + curl -k --fail --max-time $5 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$2" "$1" || + { + echo list download failed : $1 + return 2 + } + dlsize=$(LANG=C wc -c "$2" | xargs | cut -f 1 -d ' ') + if test $dlsize -lt $3; then + echo list is too small : $dlsize bytes. can be bad. + return 2 + fi + return 0 +} + +reestr_list() +{ + LANG=C cut -s -f2 -d';' "$ZREESTR" | LANG=C nice -n 5 sed -Ee 's/^\*\.(.+)$/\1/' -ne 's/^[a-z0-9A-Z._-]+$/&/p' | $AWK '{ print tolower($0) }' +} +reestr_extract_ip() +{ + LANG=C nice -n 5 $AWK -F ';' '($1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}/) && (($2 == "" && $3 == "") || ($1 == $2)) {gsub(/ \| /, RS); print $1}' "$ZREESTR" | LANG=C $AWK '{split($1, a, /\|/); for (i in a) {print a[i]}}' +} + +ipban_fin() +{ + getipban + "$IPSET_DIR/create_ipset.sh" +} + +dl_checked "$ZURL_REESTR" "$ZREESTR" 204800 251658240 600 || { + ipban_fin + exit 2 +} + +reestr_list | sort -u | zz "$ZHOSTLIST" + +reestr_extract_ip <"$ZREESTR" >"$IPB" + +rm -f "$ZREESTR" +[ "$DISABLE_IPV4" != "1" ] && $AWK '/^([0-9]{1,3}\.){3}[0-9]{1,3}($|(\/[0-9]{2}$))/' "$IPB" | cut_local | ip2net4 | zz "$ZIPLIST_IPBAN" +[ "$DISABLE_IPV6" != "1" ] && $AWK '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}($|(\/[0-9]{2,3}$))/' "$IPB" | cut_local6 | ip2net6 | zz "$ZIPLIST_IPBAN6" +rm -f "$IPB" + +hup_zapret_daemons + +ipban_fin + +exit 0 diff --git a/ipset/get_reestr_preresolved.sh b/ipset/get_reestr_preresolved.sh new file mode 100755 index 00000000..6e530e71 --- /dev/null +++ b/ipset/get_reestr_preresolved.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +TMPLIST="$TMPDIR/list.txt" + +BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" +URL4="$BASEURL/reestr_resolved4.txt" +URL6="$BASEURL/reestr_resolved6.txt" +IPB4="$BASEURL/reestr_ipban4.txt" +IPB6="$BASEURL/reestr_ipban6.txt" + +dl() +{ + # $1 - url + # $2 - file + # $3 - minsize + # $4 - maxsize + curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || + { + echo list download failed : $1 + exit 2 + } + dlsize=$(LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') + if test $dlsize -lt $3; then + echo list is too small : $dlsize bytes. can be bad. + exit 2 + fi + zzcat "$TMPLIST" | zz "$2" + rm -f "$TMPLIST" +} + +getuser && { + [ "$DISABLE_IPV4" != "1" ] && { + dl "$URL4" "$ZIPLIST" 32768 4194304 + dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 + } + [ "$DISABLE_IPV6" != "1" ] && { + dl "$URL6" "$ZIPLIST6" 8192 4194304 + dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 + } +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_reestr_preresolved_smart.sh b/ipset/get_reestr_preresolved_smart.sh new file mode 100755 index 00000000..d31c0b3d --- /dev/null +++ b/ipset/get_reestr_preresolved_smart.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +TMPLIST="$TMPDIR/list.txt" + +BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" +URL4="$BASEURL/reestr_smart4.txt" +URL6="$BASEURL/reestr_smart6.txt" +IPB4="$BASEURL/reestr_ipban4.txt" +IPB6="$BASEURL/reestr_ipban6.txt" + +dl() +{ + # $1 - url + # $2 - file + # $3 - minsize + # $4 - maxsize + curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || + { + echo list download failed : $1 + exit 2 + } + dlsize=$(LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') + if test $dlsize -lt $3; then + echo list is too small : $dlsize bytes. can be bad. + exit 2 + fi + zzcat "$TMPLIST" | zz "$2" + rm -f "$TMPLIST" +} + +getuser && { + [ "$DISABLE_IPV4" != "1" ] && { + dl "$URL4" "$ZIPLIST" 32768 4194304 + dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 + } + [ "$DISABLE_IPV6" != "1" ] && { + dl "$URL6" "$ZIPLIST6" 8192 4194304 + dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 + } +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_reestr_resolvable_domains.sh b/ipset/get_reestr_resolvable_domains.sh new file mode 100755 index 00000000..d2defdc0 --- /dev/null +++ b/ipset/get_reestr_resolvable_domains.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +TMPLIST="$TMPDIR/list_nethub.txt" + +BASEURL="https://raw.githubusercontent.com/bol-van/rulist/main" +URL="$BASEURL/reestr_hostname_resolvable.txt" +IPB4="$BASEURL/reestr_ipban4.txt" +IPB6="$BASEURL/reestr_ipban6.txt" + +dl() +{ + # $1 - url + # $2 - file + # $3 - minsize + # $4 - maxsize + curl -H "Accept-Encoding: gzip" -k --fail --max-time 120 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$TMPLIST" "$1" || + { + echo list download failed : $1 + exit 2 + } + dlsize=$(LANG=C wc -c "$TMPLIST" | xargs | cut -f 1 -d ' ') + if test $dlsize -lt $3; then + echo list is too small : $dlsize bytes. can be bad. + exit 2 + fi + zzcat "$TMPLIST" | zz "$2" + rm -f "$TMPLIST" +} + +dl "$URL" "$ZHOSTLIST" 65536 67108864 + +hup_zapret_daemons + +[ "$DISABLE_IPV4" != "1" ] && dl "$IPB4" "$ZIPLIST_IPBAN" 8192 1048576 +[ "$DISABLE_IPV6" != "1" ] && dl "$IPB6" "$ZIPLIST_IPBAN6" 128 1048576 + +getipban +"$IPSET_DIR/create_ipset.sh" + +exit 0 diff --git a/ipset/get_reestr_resolve.sh b/ipset/get_reestr_resolve.sh new file mode 100755 index 00000000..924a0731 --- /dev/null +++ b/ipset/get_reestr_resolve.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +ZREESTR="$TMPDIR/zapret.txt" +ZDIG="$TMPDIR/zapret-dig.txt" +IPB="$TMPDIR/ipb.txt" +ZIPLISTTMP="$TMPDIR/zapret-ip.txt" +#ZURL=https://reestr.rublacklist.net/api/current +ZURL_REESTR=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +dl_checked() +{ + # $1 - url + # $2 - file + # $3 - minsize + # $4 - maxsize + # $5 - maxtime + curl -k --fail --max-time $5 --connect-timeout 10 --retry 4 --max-filesize $4 -o "$2" "$1" || + { + echo list download failed : $1 + return 2 + } + dlsize=$(LANG=C wc -c "$2" | xargs | cut -f 1 -d ' ') + if test $dlsize -lt $3; then + echo list is too small : $dlsize bytes. can be bad. + return 2 + fi + return 0 +} + +reestr_list() +{ + LANG=C cut -s -f2 -d';' "$ZREESTR" | LANG=C nice -n 5 sed -Ee 's/^\*\.(.+)$/\1/' -ne 's/^[a-z0-9A-Z._-]+$/&/p' +} +reestr_extract_ip() +{ + LANG=C nice -n 5 $AWK -F ';' '($1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}/) && (($2 == "" && $3 == "") || ($1 == $2)) {gsub(/ \| /, RS); print $1}' "$ZREESTR" | LANG=C $AWK '{split($1, a, /\|/); for (i in a) {print a[i]}}' +} + +getuser && { + # both disabled + [ "$DISABLE_IPV4" = "1" ] && [ "$DISABLE_IPV6" = "1" ] && exit 0 + + dl_checked "$ZURL_REESTR" "$ZREESTR" 204800 251658240 600 || exit 2 + + echo preparing ipban list .. + + reestr_extract_ip <"$ZREESTR" >"$IPB" + [ "$DISABLE_IPV4" != "1" ] && $AWK '/^([0-9]{1,3}\.){3}[0-9]{1,3}($|(\/[0-9]{2}$))/' "$IPB" | cut_local | ip2net4 | zz "$ZIPLIST_IPBAN" + [ "$DISABLE_IPV6" != "1" ] && $AWK '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}($|(\/[0-9]{2,3}$))/' "$IPB" | cut_local6 | ip2net6 | zz "$ZIPLIST_IPBAN6" + rm -f "$IPB" + + echo preparing dig list .. + reestr_list | sort -u >"$ZDIG" + + rm -f "$ZREESTR" + + echo digging started. this can take long ... + + [ "$DISABLE_IPV4" != "1" ] && { + filedigger "$ZDIG" 4 | cut_local >"$ZIPLISTTMP" || { + rm -f "$ZDIG" + exit 1 + } + ip2net4 <"$ZIPLISTTMP" | zz "$ZIPLIST" + rm -f "$ZIPLISTTMP" + } + [ "$DISABLE_IPV6" != "1" ] && { + filedigger "$ZDIG" 6 | cut_local6 >"$ZIPLISTTMP" || { + rm -f "$ZDIG" + exit 1 + } + ip2net6 <"$ZIPLISTTMP" | zz "$ZIPLIST6" + rm -f "$ZIPLISTTMP" + } + rm -f "$ZDIG" +} + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/get_user.sh b/ipset/get_user.sh new file mode 100755 index 00000000..2d989813 --- /dev/null +++ b/ipset/get_user.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# resolve user host list + +IPSET_DIR="$(dirname "$0")" +IPSET_DIR="$(cd "$IPSET_DIR"; pwd)" + +. "$IPSET_DIR/def.sh" + +getuser + +"$IPSET_DIR/create_ipset.sh" diff --git a/ipset/zapret-hosts-user-exclude.txt.default b/ipset/zapret-hosts-user-exclude.txt.default new file mode 100644 index 00000000..999ccdd5 --- /dev/null +++ b/ipset/zapret-hosts-user-exclude.txt.default @@ -0,0 +1,6 @@ +10.0.0.0/8 +172.16.0.0/12 +192.168.0.0/16 +169.254.0.0/16 +fc00::/7 +fe80::/10 diff --git a/mdig/Makefile b/mdig/Makefile new file mode 100644 index 00000000..58bc4b48 --- /dev/null +++ b/mdig/Makefile @@ -0,0 +1,28 @@ +CC ?= gcc +CFLAGS += -std=gnu99 -O3 +CFLAGS_BSD = -Wno-address-of-packed-member +CFLAGS_WIN = -static +LIBS = -lpthread +LIBS_WIN = -lws2_32 +SRC_FILES = *.c + +all: mdig + +mdig: $(SRC_FILES) + $(CC) -s $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS) + +bsd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o mdig $(SRC_FILES) $(LDFLAGS) $(LIBS) + +mac: $(SRC_FILES) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdiga $(SRC_FILES) $(LDFLAGS) -target arm64-apple-macos10.8 $(LIBS_BSD) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o mdigx $(SRC_FILES) $(LDFLAGS) -target x86_64-apple-macos10.8 $(LIBS_BSD) + 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) $(LDFLAGS) $(LIBS_WIN) + +clean: + rm -f mdig *.o diff --git a/mdig/mdig.c b/mdig/mdig.c new file mode 100644 index 00000000..047e7b61 --- /dev/null +++ b/mdig/mdig.c @@ -0,0 +1,435 @@ +// 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 +#include +#ifdef _WIN32 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#include +#include +#include +#else +#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 = '\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; +} + +static void exithelp(void) +{ + printf( + " --threads=\n" + " --family=<4|6|46>\t; ipv4, ipv6, ipv4+ipv6\n" + " --verbose\t\t; print query progress to stderr\n" + " --stats=N\t\t; print resolve stats to stderr every N domains\n" + " --log-resolved=\t; log successfully resolved domains to a file\n" + " --log-failed=\t; log failed domains to a file\n" + ); + exit(1); +} +int main(int argc, char **argv) +{ + int r, v, option_index = 0; + char fn1[256],fn2[256]; + + static const struct option long_options[] = { + {"help",no_argument,0,0}, // optidx=0 + {"threads",required_argument,0,0}, // optidx=1 + {"family",required_argument,0,0}, // optidx=2 + {"verbose",no_argument,0,0}, // optidx=3 + {"stats",required_argument,0,0}, // optidx=4 + {"log-resolved",required_argument,0,0}, // optidx=5 + {"log-failed",required_argument,0,0}, // optidx=6 + {NULL,0,NULL,0} + }; + + memset(&glob, 0, sizeof(glob)); + *fn1 = *fn2 = 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 0: /* help */ + exithelp(); + break; + case 1: /* 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 2: /* 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 3: /* verbose */ + glob.verbose = '\1'; + break; + case 4: /* stats */ + glob.stats_every = optarg ? atoi(optarg) : 0; + break; + case 5: /* log-resolved */ + strncpy(fn1,optarg,sizeof(fn1)); + fn1[sizeof(fn1)-1] = 0; + break; + case 6: /* log-failed */ + strncpy(fn2,optarg,sizeof(fn2)); + fn2[sizeof(fn2)-1] = 0; + break; + } + } + +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData)) + { + fprintf(stderr,"WSAStartup failed\n"); + return 4; + } +#endif + + 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/nfq/BSDmakefile b/nfq/BSDmakefile new file mode 100644 index 00000000..17173407 --- /dev/null +++ b/nfq/BSDmakefile @@ -0,0 +1,12 @@ +CC ?= cc +CFLAGS += -std=gnu99 -s -O3 -Wno-address-of-packed-member +LIBS = -lz +SRC_FILES = *.c crypto/*.c + +all: dvtws + +dvtws: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS) + +clean: + rm -f dvtws diff --git a/nfq/Makefile b/nfq/Makefile new file mode 100644 index 00000000..783f4374 --- /dev/null +++ b/nfq/Makefile @@ -0,0 +1,30 @@ +CC ?= gcc +CFLAGS += -std=gnu99 -O3 +CFLAGS_BSD = -Wno-address-of-packed-member +CFLAGS_MAC = -mmacosx-version-min=10.8 +CFLAGS_CYGWIN = -Wno-address-of-packed-member -static +LIBS_LINUX = -lnetfilter_queue -lnfnetlink -lz +LIBS_BSD = -lz +LIBS_CYGWIN = -lz -Lwindivert -lwindivert -lwlanapi -lole32 -loleaut32 -luuid +SRC_FILES = *.c crypto/*.c + +all: nfqws + +nfqws: $(SRC_FILES) + $(CC) -s $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS_LINUX) + +bsd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -o dvtws $(SRC_FILES) $(LDFLAGS) $(LIBS_BSD) + +mac: $(SRC_FILES) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsa $(SRC_FILES) $(LDFLAGS) -target arm64-apple-macos10.8 $(LIBS_BSD) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -o dvtwsx $(SRC_FILES) $(LDFLAGS) -target x86_64-apple-macos10.8 $(LIBS_BSD) + strip dvtwsa dvtwsx + lipo -create -output dvtws dvtwsx dvtwsa + rm -f dvtwsx dvtwsa + +cygwin: + $(CC) -s $(CFLAGS) $(CFLAGS_CYGWIN) -o winws $(SRC_FILES) $(LDFLAGS) $(LIBS_CYGWIN) winmanifest.o winicon.o + +clean: + rm -f nfqws dvtws winws.exe diff --git a/nfq/checksum.c b/nfq/checksum.c new file mode 100644 index 00000000..dcc36579 --- /dev/null +++ b/nfq/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/nfq/checksum.h b/nfq/checksum.h new file mode 100644 index 00000000..c33831eb --- /dev/null +++ b/nfq/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/nfq/conntrack.c b/nfq/conntrack.c new file mode 100644 index 00000000..62a79dbc --- /dev/null +++ b/nfq/conntrack.c @@ -0,0 +1,405 @@ +#include "conntrack.h" +#include "darkmagic.h" +#include +#include + +#undef uthash_nonfatal_oom +#define uthash_nonfatal_oom(elt) ut_oom_recover(elt) + +static bool oom = false; +static void ut_oom_recover(void *elem) +{ + oom = true; +} + +static const char *connstate_s[]={"SYN","ESTABLISHED","FIN"}; + +static void connswap(const t_conn *c, t_conn *c2) +{ + memset(c2,0,sizeof(*c2)); + c2->l3proto = c->l3proto; + c2->l4proto = c->l4proto; + c2->src = c->dst; + c2->dst = c->src; + c2->sport = c->dport; + c2->dport = c->sport; +} + +void ConntrackClearHostname(t_ctrack *track) +{ + if (track->hostname) + { + free(track->hostname); + track->hostname = NULL; + } +} +static void ConntrackClearTrack(t_ctrack *track) +{ + ConntrackClearHostname(track); + ReasmClear(&track->reasm_orig); + rawpacket_queue_destroy(&track->delayed); +} + +static void ConntrackFreeElem(t_conntrack_pool *elem) +{ + ConntrackClearTrack(&elem->track); + free(elem); +} + +static void ConntrackPoolDestroyPool(t_conntrack_pool **pp) +{ + t_conntrack_pool *elem, *tmp; + HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); } +} +void ConntrackPoolDestroy(t_conntrack *p) +{ + ConntrackPoolDestroyPool(&p->pool); +} + +void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp) +{ + p->timeout_syn = timeout_syn; + p->timeout_established = timeout_established; + p->timeout_fin = timeout_fin; + p->timeout_udp= timeout_udp; + p->t_purge_interval = purge_interval; + time(&p->t_last_purge); + p->pool = NULL; +} + +void ConntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) +{ + memset(c,0,sizeof(*c)); + if (ip) + { + c->l3proto = IPPROTO_IP; + c->dst.ip = bReverse ? ip->ip_src : ip->ip_dst; + c->src.ip = bReverse ? ip->ip_dst : ip->ip_src; + } + else if (ip6) + { + c->l3proto = IPPROTO_IPV6; + c->dst.ip6 = bReverse ? ip6->ip6_src : ip6->ip6_dst; + c->src.ip6 = bReverse ? ip6->ip6_dst : ip6->ip6_src; + } + else + c->l3proto = -1; + extract_ports(tcphdr, udphdr, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport); +} + + +static t_conntrack_pool *ConntrackPoolSearch(t_conntrack_pool *p, const t_conn *c) +{ + t_conntrack_pool *t; + HASH_FIND(hh, p, c, sizeof(*c), t); + return t; +} + +static void ConntrackInitTrack(t_ctrack *t) +{ + memset(t,0,sizeof(*t)); + t->scale_orig = t->scale_reply = SCALE_NONE; + time(&t->t_start); + rawpacket_queue_init(&t->delayed); +} +static void ConntrackReInitTrack(t_ctrack *t) +{ + ConntrackClearTrack(t); + ConntrackInitTrack(t); +} + +static t_conntrack_pool *ConntrackNew(t_conntrack_pool **pp, const t_conn *c) +{ + t_conntrack_pool *ctnew; + if (!(ctnew = malloc(sizeof(*ctnew)))) return NULL; + ctnew->conn = *c; + oom = false; + HASH_ADD(hh, *pp, conn, sizeof(*c), ctnew); + if (oom) { free(ctnew); return NULL; } + ConntrackInitTrack(&ctnew->track); + return ctnew; +} + +// non-tcp packets are passed with tcphdr=NULL but len_payload filled +static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct tcphdr *tcphdr, uint32_t len_payload) +{ + uint8_t scale; + + if (bReverse) + { + t->pcounter_reply++; + t->pdcounter_reply+=!!len_payload; + + } + else + { + t->pcounter_orig++; + t->pdcounter_orig+=!!len_payload; + } + + if (tcphdr) + { + if (tcp_syn_segment(tcphdr)) + { + if (t->state!=SYN) ConntrackReInitTrack(t); // erase current entry + t->seq0 = ntohl(tcphdr->th_seq); + } + else if (tcp_synack_segment(tcphdr)) + { + if (t->state!=SYN) ConntrackReInitTrack(t); // erase current entry + if (!t->seq0) t->seq0 = ntohl(tcphdr->th_ack)-1; + t->ack0 = ntohl(tcphdr->th_seq); + } + else if (tcphdr->th_flags & (TH_FIN|TH_RST)) + { + t->state = FIN; + } + else + { + if (t->state==SYN) + { + t->state=ESTABLISHED; + if (!bReverse && !t->ack0) t->ack0 = ntohl(tcphdr->th_ack)-1; + } + } + scale = tcp_find_scale_factor(tcphdr); + if (bReverse) + { + t->pos_orig = t->seq_last = ntohl(tcphdr->th_ack); + t->ack_last = ntohl(tcphdr->th_seq); + t->pos_reply = t->ack_last + len_payload; + t->winsize_reply = ntohs(tcphdr->th_win); + if (scale!=SCALE_NONE) t->scale_reply = scale; + + } + else + { + t->seq_last = ntohl(tcphdr->th_seq); + t->pos_orig = t->seq_last + len_payload; + t->pos_reply = t->ack_last = ntohl(tcphdr->th_ack); + t->winsize_orig = ntohs(tcphdr->th_win); + if (scale!=SCALE_NONE) t->scale_orig = scale; + } + } + else + { + if (bReverse) + { + t->ack_last=t->pos_reply; + t->pos_reply+=len_payload; + } + else + { + t->seq_last=t->pos_orig; + t->pos_orig+=len_payload; + } + } + + time(&t->t_last); +} + +static bool ConntrackPoolDoubleSearchPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse) +{ + t_conn conn,connswp; + t_conntrack_pool *ctr; + + ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); + if ((ctr=ConntrackPoolSearch(*pp,&conn))) + { + if (bReverse) *bReverse = false; + if (ctrack) *ctrack = &ctr->track; + return true; + } + else + { + connswap(&conn,&connswp); + if ((ctr=ConntrackPoolSearch(*pp,&connswp))) + { + if (bReverse) *bReverse = true; + if (ctrack) *ctrack = &ctr->track; + return true; + } + } + return false; +} +bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse) +{ + return ConntrackPoolDoubleSearchPool(&p->pool, ip, ip6, tcphdr, udphdr, ctrack, bReverse); +} + +static bool ConntrackPoolFeedPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse) +{ + t_conn conn, connswp; + t_conntrack_pool *ctr; + bool b_rev; + + ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); + if ((ctr=ConntrackPoolSearch(*pp,&conn))) + { + ConntrackFeedPacket(&ctr->track, (b_rev=false), tcphdr, len_payload); + goto ok; + } + else + { + connswap(&conn,&connswp); + if ((ctr=ConntrackPoolSearch(*pp,&connswp))) + { + ConntrackFeedPacket(&ctr->track, (b_rev=true), tcphdr, len_payload); + goto ok; + } + } + b_rev = tcphdr && tcp_synack_segment(tcphdr); + if ((tcphdr && tcp_syn_segment(tcphdr)) || b_rev || udphdr) + { + if ((ctr=ConntrackNew(pp, b_rev ? &connswp : &conn))) + { + ConntrackFeedPacket(&ctr->track, b_rev, tcphdr, len_payload); + goto ok; + } + } + return false; +ok: + if (ctrack) *ctrack = &ctr->track; + if (bReverse) *bReverse = b_rev; + return true; +} +bool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse) +{ + return ConntrackPoolFeedPool(&p->pool,ip,ip6,tcphdr,udphdr,len_payload,ctrack,bReverse); +} + +static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) +{ + t_conn conn, connswp; + t_conntrack_pool *t; + ConntrackExtractConn(&conn,false,ip,ip6,tcphdr,udphdr); + if (!(t=ConntrackPoolSearch(*pp,&conn))) + { + connswap(&conn,&connswp); + t=ConntrackPoolSearch(*pp,&connswp); + } + if (!t) return false; + HASH_DEL(*pp, t); ConntrackFreeElem(t); + return true; +} +bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr) +{ + return ConntrackPoolDropPool(&p->pool,ip,ip6,tcphdr,udphdr); +} + +void ConntrackPoolPurge(t_conntrack *p) +{ + time_t tidle, tnow = time(NULL); + t_conntrack_pool *t, *tmp; + + if ((tnow - p->t_last_purge)>=p->t_purge_interval) + { + HASH_ITER(hh, p->pool , t, tmp) { + tidle = tnow - t->track.t_last; + if ( t->track.b_cutoff || + (t->conn.l4proto==IPPROTO_TCP && ( + (t->track.state==SYN && tidle>=p->timeout_syn) || + (t->track.state==ESTABLISHED && tidle>=p->timeout_established) || + (t->track.state==FIN && tidle>=p->timeout_fin)) + ) || (t->conn.l4proto==IPPROTO_UDP && tidle>=p->timeout_udp) + ) + { + HASH_DEL(p->pool, t); ConntrackFreeElem(t); + } + } + p->t_last_purge = tnow; + } +} + +static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsize) +{ + if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0; +} + +static const char *ConntrackProtoName(t_l7proto proto) +{ + switch(proto) + { + case HTTP: return "HTTP"; + case TLS: return "TLS"; + case QUIC: return "QUIC"; + case WIREGUARD: return "WIREGUARD"; + case DHT: return "DHT"; + default: return "UNKNOWN"; + } +} +void ConntrackPoolDump(const t_conntrack *p) +{ + t_conntrack_pool *t, *tmp; + char sa1[40],sa2[40]; + time_t tnow = time(NULL); + HASH_ITER(hh, p->pool, t, tmp) { + taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1)); + taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2)); + printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu ", + proto_name(t->conn.l4proto), + sa1, t->conn.sport, sa2, t->conn.dport, + t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-", + (unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last), + (unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig, + (unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply); + if (t->conn.l4proto==IPPROTO_TCP) + printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d", + t->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0, + t->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0, + t->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig, + t->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply); + else + printf("rseq=%u pos_orig=%u rack=%u pos_reply=%u", + t->track.seq_last, t->track.pos_orig, + t->track.ack_last, t->track.pos_reply); + printf(" req_retrans=%u cutoff=%u wss_cutoff=%u d_cutoff=%u hostname=%s l7proto=%s\n", + t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.hostname, ConntrackProtoName(t->track.l7proto)); + }; +} + + +void ReasmClear(t_reassemble *reasm) +{ + if (reasm->packet) + { + free(reasm->packet); + reasm->packet = NULL; + } + reasm->size = reasm->size_present = 0; +} +bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start) +{ + reasm->packet = malloc(size_requested); + if (!reasm->packet) return false; + reasm->size = size_requested; + reasm->size_present = 0; + reasm->seq = seq_start; + return true; +} +bool ReasmResize(t_reassemble *reasm, size_t new_size) +{ + uint8_t *p = realloc(reasm->packet, new_size); + if (!p) return false; + reasm->packet = p; + reasm->size = new_size; + if (reasm->size_present > new_size) reasm->size_present = new_size; + return true; +} +bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len) +{ + if (reasm->seq!=seq) return false; // fail session if out of sequence + + size_t szcopy; + szcopy = reasm->size - reasm->size_present; + if (lenpacket + reasm->size_present, payload, szcopy); + reasm->size_present += szcopy; + reasm->seq += (uint32_t)szcopy; + + return true; +} +bool ReasmHasSpace(t_reassemble *reasm, size_t len) +{ + return (reasm->size_present+len)<=reasm->size; +} diff --git a/nfq/conntrack.h b/nfq/conntrack.h new file mode 100644 index 00000000..e37a616e --- /dev/null +++ b/nfq/conntrack.h @@ -0,0 +1,125 @@ +#pragma once + + +// this conntrack is not bullet-proof +// its designed to satisfy dpi desync needs only + +#include "packet_queue.h" + +#include +#include +#include +#include +#include +#include + +#define __FAVOR_BSD +#include +#include +#include +#include + + +//#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 enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto; +typedef struct +{ + struct desync_profile *dp; // desync profile cache + bool dp_search_complete; + bool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache + + // common state + time_t t_start, t_last; + uint64_t pcounter_orig, pcounter_reply; // packet counter + uint64_t pdcounter_orig, pdcounter_reply; // data packet counter (with payload) + uint32_t pos_orig, pos_reply; // TCP: seq_last+payload, ack_last+payload UDP: sum of all seen payload lenghts including current + uint32_t seq_last, ack_last; // TCP: last seen seq and ack UDP: sum of all seen payload lenghts NOT including current + + // tcp only state, not used in udp + t_connstate state; + uint32_t seq0, ack0; // starting seq and ack + uint16_t winsize_orig, winsize_reply; // last seen window size + uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none + + uint8_t req_retrans_counter; // number of request retransmissions + bool req_seq_present,req_seq_finalized,req_seq_abandoned; + uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions) + + uint8_t autottl; + + bool b_cutoff; // mark for deletion + bool b_wssize_cutoff, b_desync_cutoff; + + t_l7proto l7proto; + char *hostname; + bool hostname_ah_check; // should perform autohostlist checks + + t_reassemble reasm_orig; + struct rawpacket_tailhead delayed; +} t_ctrack; + +typedef struct +{ + t_ctrack track; + UT_hash_handle hh; // makes this structure hashable + t_conn conn; // key +} t_conntrack_pool; +typedef struct +{ + // inactivity time to purge an entry in each connection state + uint32_t timeout_syn,timeout_established,timeout_fin,timeout_udp; + time_t t_purge_interval, t_last_purge; + t_conntrack_pool *pool; +} t_conntrack; + +void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp); +void ConntrackPoolDestroy(t_conntrack *p); +bool ConntrackPoolFeed(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, size_t len_payload, t_ctrack **ctrack, bool *bReverse); +// do not create, do not update. only find existing +bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr, t_ctrack **ctrack, bool *bReverse); +bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); +void CaonntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr); +void ConntrackPoolDump(const t_conntrack *p); +void ConntrackPoolPurge(t_conntrack *p); +void ConntrackClearHostname(t_ctrack *track); + +bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start); +bool ReasmResize(t_reassemble *reasm, size_t new_size); +void ReasmClear(t_reassemble *reasm); +// false means reassemble session has failed and we should ReasmClear() it +bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len); +// check if it has enough space to buffer 'len' bytes +bool ReasmHasSpace(t_reassemble *reasm, size_t len); +inline static bool ReasmIsEmpty(t_reassemble *reasm) {return !reasm->size;} +inline static bool ReasmIsFull(t_reassemble *reasm) {return !ReasmIsEmpty(reasm) && (reasm->size==reasm->size_present);} diff --git a/nfq/crypto/aes-gcm.c b/nfq/crypto/aes-gcm.c new file mode 100644 index 00000000..1d0a0462 --- /dev/null +++ b/nfq/crypto/aes-gcm.c @@ -0,0 +1,13 @@ +#include "aes-gcm.h" + +int aes_gcm_crypt(int mode, uint8_t *output, const uint8_t *input, size_t input_length, const uint8_t *key, const size_t key_len, const uint8_t *iv, const size_t iv_len, const uint8_t *adata, size_t adata_len, uint8_t *atag, size_t atag_len) +{ + int ret = 0; + gcm_context ctx; + + gcm_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/nfq/crypto/aes-gcm.h b/nfq/crypto/aes-gcm.h new file mode 100644 index 00000000..d8360013 --- /dev/null +++ b/nfq/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/nfq/crypto/aes.c b/nfq/crypto/aes.c new file mode 100644 index 00000000..1ce55ef4 --- /dev/null +++ b/nfq/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 == 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 == 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/nfq/crypto/aes.h b/nfq/crypto/aes.h new file mode 100644 index 00000000..b04724d0 --- /dev/null +++ b/nfq/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 0 // whether AES decryption is supported +/******************************************************************************/ + +#include + +#define AES_ENCRYPT 1 // specify whether we're encrypting +#define AES_DECRYPT 0 // or decrypting + +#if defined(_MSC_VER) +#include +typedef UINT32 uint32_t; +#else +#include +#endif + +typedef unsigned char uchar; // add some convienent shorter types +typedef unsigned int uint; + + +/****************************************************************************** + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use + ******************************************************************************/ +void aes_init_keygen_tables(void); + + +/****************************************************************************** + * AES_CONTEXT : cipher context / holds inter-call data + ******************************************************************************/ +typedef struct { + int mode; // 1 for Encryption, 0 for Decryption + int rounds; // keysize-based rounds count + uint32_t *rk; // pointer to current round key + uint32_t buf[68]; // key expansion buffer +} aes_context; + + +/****************************************************************************** + * AES_SETKEY : called to expand the key for encryption or decryption + ******************************************************************************/ +int aes_setkey(aes_context *ctx, // pointer to context + int mode, // 1 or 0 for Encrypt/Decrypt + const uchar *key, // AES input key + uint keysize); // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) + // returns 0 for success + +/****************************************************************************** + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data + ******************************************************************************/ +int aes_cipher(aes_context *ctx, // pointer to context + const uchar input[16], // 128-bit block to en/decipher + uchar output[16]); // 128-bit output result block + // returns 0 for success diff --git a/nfq/crypto/gcm.c b/nfq/crypto/gcm.c new file mode 100644 index 00000000..92a6e8fb --- /dev/null +++ b/nfq/crypto/gcm.c @@ -0,0 +1,511 @@ +/****************************************************************************** +* +* 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, // GCM_ENCRYPT or GCM_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: GCM_ENCRYPT or GCM_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/nfq/crypto/gcm.h b/nfq/crypto/gcm.h new file mode 100644 index 00000000..42adad99 --- /dev/null +++ b/nfq/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/nfq/crypto/hkdf.c b/nfq/crypto/hkdf.c new file mode 100644 index 00000000..266cb37d --- /dev/null +++ b/nfq/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/nfq/crypto/hmac.c b/nfq/crypto/hmac.c new file mode 100644 index 00000000..9e053255 --- /dev/null +++ b/nfq/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/nfq/crypto/sha-private.h b/nfq/crypto/sha-private.h new file mode 100644 index 00000000..4ceba0d6 --- /dev/null +++ b/nfq/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/nfq/crypto/sha.h b/nfq/crypto/sha.h new file mode 100644 index 00000000..8b3a63b2 --- /dev/null +++ b/nfq/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/nfq/crypto/sha224-256.c b/nfq/crypto/sha224-256.c new file mode 100644 index 00000000..2c9bc9c1 --- /dev/null +++ b/nfq/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/nfq/crypto/usha.c b/nfq/crypto/usha.c new file mode 100644 index 00000000..861b4d0d --- /dev/null +++ b/nfq/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/nfq/darkmagic.c b/nfq/darkmagic.c new file mode 100644 index 00000000..40eeeda7 --- /dev/null +++ b/nfq/darkmagic.c @@ -0,0 +1,1879 @@ +#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 +#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); +} + +uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind) +{ + uint8_t *t = (uint8_t*)(tcp+1); + uint8_t *end = (uint8_t*)tcp + (tcp->th_off<<2); + while(t=end || t[1]<2 || (t+t[1])>end) + return NULL; + if (*t==kind) + return t; + t+=t[1]; + break; + } + } + return NULL; +} +uint32_t *tcp_find_timestamps(struct tcphdr *tcp) +{ + uint8_t *t = tcp_find_option(tcp,8); + return (t && t[1]==10) ? (uint32_t*)(t+2) : NULL; +} +uint8_t tcp_find_scale_factor(const struct tcphdr *tcp) +{ + uint8_t *scale = tcp_find_option((struct tcphdr*)tcp,3); // tcp option 3 - scale factor + if (scale && scale[1]==3) return scale[2]; + return SCALE_NONE; +} +bool tcp_has_fastopen(const struct tcphdr *tcp) +{ + uint8_t *opt; + // new style RFC7413 + opt = tcp_find_option((struct tcphdr*)tcp, 34); + if (opt) return true; + // old style RFC6994 + opt = tcp_find_option((struct tcphdr*)tcp, 254); + return opt && opt[1]>=4 && opt[2]==0xF9 && opt[3]==0x89; +} + +// n prefix (nsport, nwsize) means network byte order +static void fill_tcphdr( + struct tcphdr *tcp, uint32_t fooling, uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nsport, uint16_t ndport, + uint16_t nwsize, uint8_t scale_factor, + uint32_t *timestamps, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + uint16_t data_len) +{ + char *tcpopt = (char*)(tcp+1); + uint8_t t=0; + + memset(tcp,0,sizeof(*tcp)); + tcp->th_sport = nsport; + tcp->th_dport = ndport; + if (fooling & FOOL_BADSEQ) + { + tcp->th_seq = net32_add(nseq,badseq_increment); + tcp->th_ack = net32_add(nack_seq,badseq_ack_increment); + } + else + { + tcp->th_seq = nseq; + tcp->th_ack = nack_seq; + } + tcp->th_off = 5; + if ((fooling & FOOL_DATANOACK) && !(tcp_flags & (TH_SYN|TH_RST)) && data_len) + tcp_flags &= ~TH_ACK; + *((uint8_t*)tcp+13)= tcp_flags; + tcp->th_win = nwsize; + if (fooling & FOOL_MD5SIG) + { + tcpopt[0] = 19; // kind + tcpopt[1] = 18; // len + *(uint32_t*)(tcpopt+2)=random(); + *(uint32_t*)(tcpopt+6)=random(); + *(uint32_t*)(tcpopt+10)=random(); + *(uint32_t*)(tcpopt+14)=random(); + t=18; + } + if (timestamps || (fooling & FOOL_TS)) + { + tcpopt[t] = 8; // kind + tcpopt[t+1] = 10; // len + // forge only TSecr if orig timestamp is present + *(uint32_t*)(tcpopt+t+2) = timestamps ? timestamps[0] : -1; + *(uint32_t*)(tcpopt+t+6) = (timestamps && !(fooling & FOOL_TS)) ? timestamps[1] : -1; + t+=10; + } + if (scale_factor!=SCALE_NONE) + { + tcpopt[t++]=3; + tcpopt[t++]=3; + tcpopt[t++]=scale_factor; + } + while (t&3) tcpopt[t++]=1; // noop + tcp->th_off += t>>2; + tcp->th_sum = 0; +} +static uint16_t tcpopt_len(uint32_t fooling, const uint32_t *timestamps, uint8_t scale_factor) +{ + uint16_t t=0; + if (fooling & FOOL_MD5SIG) t=18; + if ((fooling & FOOL_TS) || timestamps) t+=10; + if (scale_factor!=SCALE_NONE) t+=3; + return (t+3)&~3; +} + +// n prefix (nsport, nwsize) means network byte order +static void fill_udphdr(struct udphdr *udp, uint16_t nsport, uint16_t ndport, uint16_t len_payload) +{ + udp->uh_sport = nsport; + udp->uh_dport = ndport; + udp->uh_ulen = htons(len_payload+sizeof(struct udphdr)); + udp->uh_sum = 0; +} + +static void fill_iphdr(struct ip *ip, const struct in_addr *src, const struct in_addr *dst, uint16_t pktlen, uint8_t proto, uint8_t ttl, uint8_t tos) +{ + ip->ip_tos = tos; + ip->ip_sum = 0; + ip->ip_off = 0; + ip->ip_v = 4; + ip->ip_hl = 5; + ip->ip_len = htons(pktlen); + ip->ip_id = 0; + ip->ip_ttl = ttl; + ip->ip_p = proto; + ip->ip_src = *src; + ip->ip_dst = *dst; +} +static void fill_ip6hdr(struct ip6_hdr *ip6, const struct in6_addr *src, const struct in6_addr *dst, uint16_t payloadlen, uint8_t proto, uint8_t ttl, uint32_t flow_label) +{ + ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(ntohl(flow_label) & 0x0FFFFFFF | 0x60000000); + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen); + ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = proto; + ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; + ip6->ip6_src = *src; + ip6->ip6_dst = *dst; +} + +bool prepare_tcp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint8_t tos, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + uint16_t tcpoptlen = tcpopt_len(fooling,timestamps,scale_factor); + uint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; + uint16_t pktlen = sizeof(struct ip) + ip_payload_len; + if (pktlen>*buflen) return false; + + struct ip *ip = (struct ip*)buf; + struct tcphdr *tcp = (struct tcphdr*)(ip+1); + uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; + + fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_TCP, ttl, tos); + fill_tcphdr(tcp,fooling,tcp_flags,nseq,nack_seq,src->sin_port,dst->sin_port,nwsize,scale_factor,timestamps,badseq_increment,badseq_ack_increment,len); + + memcpy(payload,data,len); + tcp4_fix_checksum(tcp,ip_payload_len,&ip->ip_src,&ip->ip_dst); + if (fooling & FOOL_BADSUM) tcp->th_sum^=htons(0xBEAF); + + *buflen = pktlen; + return true; +} + +bool prepare_tcp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint32_t flow_label, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + uint16_t tcpoptlen = tcpopt_len(fooling,timestamps,scale_factor); + uint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len; + uint16_t ip_payload_len = transport_payload_len + + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + + 16*!!(fooling & FOOL_HOPBYHOP2) + + 8*!!(fooling & FOOL_DESTOPT) + + 8*!!(fooling & FOOL_IPFRAG1); + uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; + if (pktlen>*buflen) return false; + + struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; + struct tcphdr *tcp = (struct tcphdr*)(ip6+1); + uint8_t proto = IPPROTO_TCP, *nexttype = NULL; + + if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) + { + struct ip6_hbh *hbh = (struct ip6_hbh*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+8); + memset(hbh,0,8); + // extra HOPBYHOP header. standard violation + if (fooling & FOOL_HOPBYHOP2) + { + hbh = (struct ip6_hbh*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+8); + memset(hbh,0,8); + } + hbh->ip6h_nxt = IPPROTO_TCP; + nexttype = &hbh->ip6h_nxt; + proto = IPPROTO_HOPOPTS; + } + if (fooling & FOOL_DESTOPT) + { + struct ip6_dest *dest = (struct ip6_dest*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+8); + memset(dest,0,8); + dest->ip6d_nxt = IPPROTO_TCP; + if (nexttype) + *nexttype = IPPROTO_DSTOPTS; + else + proto = IPPROTO_DSTOPTS; + nexttype = &dest->ip6d_nxt; + } + if (fooling & FOOL_IPFRAG1) + { + struct ip6_frag *frag = (struct ip6_frag*)tcp; + tcp = (struct tcphdr*)((uint8_t*)tcp+sizeof(struct ip6_frag)); + frag->ip6f_nxt = IPPROTO_TCP; + frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); + frag->ip6f_reserved = 0; + frag->ip6f_offlg = 0; + if (nexttype) + *nexttype = IPPROTO_FRAGMENT; + else + proto = IPPROTO_FRAGMENT; + } + + uint8_t *payload = (uint8_t*)(tcp+1)+tcpoptlen; + + fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); + fill_tcphdr(tcp,fooling,tcp_flags,nseq,nack_seq,src->sin6_port,dst->sin6_port,nwsize,scale_factor,timestamps,badseq_increment,badseq_ack_increment,len); + + memcpy(payload,data,len); + tcp6_fix_checksum(tcp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); + if (fooling & FOOL_BADSUM) tcp->th_sum^=htons(0xBEAF); + + *buflen = pktlen; + return true; +} + +bool prepare_tcp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint8_t tos, uint32_t flow_label, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? + prepare_tcp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,tcp_flags,nseq,nack_seq,nwsize,scale_factor,timestamps,ttl,tos,fooling,badseq_increment,badseq_ack_increment,data,len,buf,buflen) : + (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? + prepare_tcp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,tcp_flags,nseq,nack_seq,nwsize,scale_factor,timestamps,ttl,flow_label,fooling,badseq_increment,badseq_ack_increment,data,len,buf,buflen) : + false; +} + + +// padlen<0 means payload shrinking +bool prepare_udp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t ttl, + uint8_t tos, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte + if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range + if (padlen<0) + { + len+=padlen; + padlen=0; + } + uint16_t datalen = (uint16_t)(len + padlen); + uint16_t ip_payload_len = sizeof(struct udphdr) + datalen; + uint16_t pktlen = sizeof(struct ip) + ip_payload_len; + if (pktlen>*buflen) return false; + + struct ip *ip = (struct ip*)buf; + struct udphdr *udp = (struct udphdr*)(ip+1); + uint8_t *payload = (uint8_t*)(udp+1); + + + fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_UDP, ttl, tos); + fill_udphdr(udp, src->sin_port, dst->sin_port, datalen); + + memcpy(payload,data,len); + if (padding) + fill_pattern(payload+len,padlen,padding,padding_size); + else + memset(payload+len,0,padlen); + udp4_fix_checksum(udp,ip_payload_len,&ip->ip_src,&ip->ip_dst); + if (fooling & FOOL_BADSUM) udp->uh_sum^=htons(0xBEAF); + + *buflen = pktlen; + return true; +} +bool prepare_udp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t ttl, + uint32_t flow_label, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + if ((len+padlen)<=0) padlen=-(int)len+1; // do not allow payload to be less that 1 byte + if ((len+padlen)>0xFFFF) padlen=0xFFFF-len; // do not allow payload size to exceed u16 range + if (padlen<0) + { + len+=padlen; + padlen=0; + } + uint16_t datalen = (uint16_t)(len + padlen); + uint16_t transport_payload_len = sizeof(struct udphdr) + datalen; + uint16_t ip_payload_len = transport_payload_len + + 8*!!((fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2))==FOOL_HOPBYHOP) + + 16*!!(fooling & FOOL_HOPBYHOP2) + + 8*!!(fooling & FOOL_DESTOPT) + + 8*!!(fooling & FOOL_IPFRAG1); + uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len; + if (pktlen>*buflen) return false; + + struct ip6_hdr *ip6 = (struct ip6_hdr*)buf; + struct udphdr *udp = (struct udphdr*)(ip6+1); + uint8_t proto = IPPROTO_UDP, *nexttype = NULL; + + if (fooling & (FOOL_HOPBYHOP|FOOL_HOPBYHOP2)) + { + struct ip6_hbh *hbh = (struct ip6_hbh*)udp; + udp = (struct udphdr*)((uint8_t*)udp+8); + memset(hbh,0,8); + // extra HOPBYHOP header. standard violation + if (fooling & FOOL_HOPBYHOP2) + { + hbh = (struct ip6_hbh*)udp; + udp = (struct udphdr*)((uint8_t*)udp+8); + memset(hbh,0,8); + } + hbh->ip6h_nxt = IPPROTO_UDP; + nexttype = &hbh->ip6h_nxt; + proto = IPPROTO_HOPOPTS; + } + if (fooling & FOOL_DESTOPT) + { + struct ip6_dest *dest = (struct ip6_dest*)udp; + udp = (struct udphdr*)((uint8_t*)udp+8); + memset(dest,0,8); + dest->ip6d_nxt = IPPROTO_UDP; + if (nexttype) + *nexttype = IPPROTO_DSTOPTS; + else + proto = IPPROTO_DSTOPTS; + nexttype = &dest->ip6d_nxt; + } + if (fooling & FOOL_IPFRAG1) + { + struct ip6_frag *frag = (struct ip6_frag*)udp; + udp = (struct udphdr*)((uint8_t*)udp+sizeof(struct ip6_frag)); + frag->ip6f_nxt = IPPROTO_UDP; + frag->ip6f_ident = htonl(1+random()%0xFFFFFFFF); + frag->ip6f_reserved = 0; + frag->ip6f_offlg = 0; + if (nexttype) + *nexttype = IPPROTO_FRAGMENT; + else + proto = IPPROTO_FRAGMENT; + } + + uint8_t *payload = (uint8_t*)(udp+1); + + fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl, flow_label); + fill_udphdr(udp, src->sin6_port, dst->sin6_port, datalen); + + memcpy(payload,data,len); + if (padding) + fill_pattern(payload+len,padlen,padding,padding_size); + else + memset(payload+len,0,padlen); + udp6_fix_checksum(udp,transport_payload_len,&ip6->ip6_src,&ip6->ip6_dst); + if (fooling & FOOL_BADSUM) udp->uh_sum^=htons(0xBEAF); + + *buflen = pktlen; + return true; +} +bool prepare_udp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t ttl, + uint8_t tos, uint32_t flow_label, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen) +{ + return (src->sa_family==AF_INET && dst->sa_family==AF_INET) ? + prepare_udp_segment4((struct sockaddr_in *)src,(struct sockaddr_in *)dst,ttl,tos,fooling,padding,padding_size,padlen,data,len,buf,buflen) : + (src->sa_family==AF_INET6 && dst->sa_family==AF_INET6) ? + prepare_udp_segment6((struct sockaddr_in6 *)src,(struct sockaddr_in6 *)dst,ttl,flow_label,fooling,padding,padding_size,padlen,data,len,buf,buflen) : + false; +} + +bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen) +{ + if ((len_pkt+8)<=*buflen && len_pkt>=sizeof(struct ip6_hdr)) + { + struct ip6_hdr *ip6 = (struct ip6_hdr *)buf; + struct ip6_ext *hdr = (struct ip6_ext*)(ip6+1); + *ip6 = *(struct ip6_hdr*)data_pkt; + memset(hdr,0,8); + memcpy((uint8_t*)hdr+8, data_pkt+sizeof(struct ip6_hdr), len_pkt-sizeof(struct ip6_hdr)); + hdr->ip6e_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; + ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = type; + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = net16_add(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen, 8); + *buflen = len_pkt + 8; + return true; + } + return false; +} + +// split ipv4 packet into 2 fragments at data payload position frag_pos +bool ip_frag4( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size) +{ + uint16_t hdrlen, payload_len; + // frag_pos must be 8-byte aligned + if (frag_pos & 7 || pkt_size < sizeof(struct ip)) return false; + payload_len = htons(((struct ip *)pkt)->ip_len); + hdrlen = ((struct ip *)pkt)->ip_hl<<2; + if (payload_len>pkt_size || hdrlen>pkt_size || hdrlen>payload_len) return false; + payload_len -= hdrlen; + if (frag_pos>=payload_len || *pkt1_size<(hdrlen+frag_pos) || *pkt2_size<(hdrlen+payload_len-frag_pos)) return false; + + memcpy(pkt1, pkt, hdrlen+frag_pos); + ((struct ip*)pkt1)->ip_off = htons(IP_MF); + ((struct ip*)pkt1)->ip_len = htons(hdrlen+frag_pos); + if (ident!=(uint32_t)-1) ((struct ip*)pkt1)->ip_id = (uint16_t)ident; + *pkt1_size=hdrlen+frag_pos; + ip4_fix_checksum((struct ip *)pkt1); + + memcpy(pkt2, pkt, hdrlen); + memcpy(pkt2+hdrlen, pkt+hdrlen+frag_pos, payload_len-frag_pos); + ((struct ip*)pkt2)->ip_off = htons((uint16_t)frag_pos>>3 & IP_OFFMASK); + ((struct ip*)pkt2)->ip_len = htons(hdrlen+payload_len-frag_pos); + if (ident!=(uint32_t)-1) ((struct ip*)pkt2)->ip_id = (uint16_t)ident; + *pkt2_size=hdrlen+payload_len-frag_pos; + ip4_fix_checksum((struct ip *)pkt2); + + return true; +} +bool ip_frag6( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size) +{ + size_t payload_len, unfragmentable; + uint8_t *last_header_type; + uint8_t proto; + struct ip6_frag *frag; + const uint8_t *payload; + + if (frag_pos & 7 || pkt_size < sizeof(struct ip6_hdr)) return false; + payload_len = sizeof(struct ip6_hdr) + htons(((struct ip6_hdr*)pkt)->ip6_ctlun.ip6_un1.ip6_un1_plen); + if (pkt_size < payload_len) return false; + + payload = pkt; + proto_skip_ipv6((uint8_t**)&payload, &payload_len, &proto, &last_header_type); + unfragmentable = payload - pkt; + + //printf("pkt_size=%zu FRAG_POS=%zu payload_len=%zu unfragmentable=%zu dh=%zu\n",pkt_size,frag_pos,payload_len,unfragmentable,last_header_type - pkt); + + if (frag_pos>=payload_len || + *pkt1_size<(unfragmentable + sizeof(struct ip6_frag) + frag_pos) || + *pkt2_size<(unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos)) + { + return false; + } + + memcpy(pkt1, pkt, unfragmentable); + ((struct ip6_hdr*)pkt1)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + frag_pos); + pkt1[last_header_type - pkt] = IPPROTO_FRAGMENT; + frag = (struct ip6_frag*)(pkt1 + unfragmentable); + frag->ip6f_nxt = proto; + frag->ip6f_reserved = 0; + frag->ip6f_offlg = IP6F_MORE_FRAG; + frag->ip6f_ident = ident; + memcpy(frag+1, pkt + unfragmentable, frag_pos); + *pkt1_size = unfragmentable + sizeof(struct ip6_frag) + frag_pos; + + memcpy(pkt2, pkt, sizeof(struct ip6_hdr)); + ((struct ip6_hdr*)pkt2)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + payload_len - frag_pos); + pkt2[last_header_type - pkt] = IPPROTO_FRAGMENT; + frag = (struct ip6_frag*)(pkt2 + unfragmentable); + frag->ip6f_nxt = proto; + frag->ip6f_reserved = 0; + frag->ip6f_offlg = htons(frag_pos); + frag->ip6f_ident = ident; + memcpy(frag+1, pkt + unfragmentable + frag_pos, payload_len - frag_pos); + *pkt2_size = unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos; + + return true; +} +bool ip_frag( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size) +{ + if (proto_check_ipv4(pkt,pkt_size)) + return ip_frag4(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); + else if (proto_check_ipv6(pkt,pkt_size)) + return ip_frag6(pkt,pkt_size,frag_pos,ident,pkt1,pkt1_size,pkt2,pkt2_size); + else + return false; +} + +void rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl) +{ + if (ip) ip->ip_ttl = ttl; + if (ip6) ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl; +} + + +void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport) +{ + if (sport) *sport = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0); + if (dport) *dport = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0); + if (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : -1; +} + +void extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst) +{ + if (ip) + { + struct sockaddr_in *si; + + if (dst) + { + si = (struct sockaddr_in*)dst; + si->sin_family = AF_INET; + si->sin_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; + si->sin_addr = ip->ip_dst; + } + + if (src) + { + si = (struct sockaddr_in*)src; + si->sin_family = AF_INET; + si->sin_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; + si->sin_addr = ip->ip_src; + } + } + else if (ip6hdr) + { + struct sockaddr_in6 *si; + + if (dst) + { + si = (struct sockaddr_in6*)dst; + si->sin6_family = AF_INET6; + si->sin6_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0; + si->sin6_addr = ip6hdr->ip6_dst; + si->sin6_flowinfo = 0; + si->sin6_scope_id = 0; + } + + if (src) + { + si = (struct sockaddr_in6*)src; + si->sin6_family = AF_INET6; + si->sin6_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0; + si->sin6_addr = ip6hdr->ip6_src; + si->sin6_flowinfo = 0; + si->sin6_scope_id = 0; + } + } +} + +const char *proto_name(uint8_t proto) +{ + switch(proto) + { + case IPPROTO_TCP: + return "tcp"; + case IPPROTO_UDP: + return "udp"; + case IPPROTO_ICMP: + return "icmp"; + case IPPROTO_ICMPV6: + return "icmp6"; + case IPPROTO_IGMP: + return "igmp"; + case IPPROTO_ESP: + return "esp"; + case IPPROTO_AH: + return "ah"; + case IPPROTO_IPV6: + return "6in4"; + case IPPROTO_IPIP: + return "4in4"; +#ifdef IPPROTO_GRE + case IPPROTO_GRE: + return "gre"; +#endif +#ifdef IPPROTO_SCTP + case IPPROTO_SCTP: + return "sctp"; +#endif + default: + return NULL; + } +} +static void str_proto_name(char *s, size_t s_len, uint8_t proto) +{ + const char *name = proto_name(proto); + if (name) + snprintf(s,s_len,"%s",name); + else + snprintf(s,s_len,"%u",proto); +} +uint16_t family_from_proto(uint8_t l3proto) +{ + switch(l3proto) + { + case IPPROTO_IP: return AF_INET; + case IPPROTO_IPV6: return AF_INET6; + default: return -1; + } +} + +static void str_srcdst_ip(char *s, size_t s_len, const void *saddr,const void *daddr) +{ + char s_ip[16],d_ip[16]; + *s_ip=*d_ip=0; + inet_ntop(AF_INET, saddr, s_ip, sizeof(s_ip)); + inet_ntop(AF_INET, daddr, d_ip, sizeof(d_ip)); + snprintf(s,s_len,"%s => %s",s_ip,d_ip); +} +void str_ip(char *s, size_t s_len, const struct ip *ip) +{ + char ss[35],s_proto[16]; + str_srcdst_ip(ss,sizeof(ss),&ip->ip_src,&ip->ip_dst); + str_proto_name(s_proto,sizeof(s_proto),ip->ip_p); + snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip->ip_ttl); +} +void print_ip(const struct ip *ip) +{ + char s[66]; + str_ip(s,sizeof(s),ip); + printf("%s",s); +} +void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr) +{ + char s_ip[40],d_ip[40]; + *s_ip=*d_ip=0; + inet_ntop(AF_INET6, saddr, s_ip, sizeof(s_ip)); + inet_ntop(AF_INET6, daddr, d_ip, sizeof(d_ip)); + snprintf(s,s_len,"%s => %s",s_ip,d_ip); +} +void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto) +{ + char ss[83],s_proto[16]; + str_srcdst_ip6(ss,sizeof(ss),&ip6hdr->ip6_src,&ip6hdr->ip6_dst); + str_proto_name(s_proto,sizeof(s_proto),proto); + snprintf(s,s_len,"%s proto=%s ttl=%u",ss,s_proto,ip6hdr->ip6_hlim); +} +void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto) +{ + char s[128]; + str_ip6hdr(s,sizeof(s),ip6hdr,proto); + printf("%s",s); +} +void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr) +{ + char flags[7],*f=flags; + if (tcphdr->th_flags & TH_SYN) *f++='S'; + if (tcphdr->th_flags & TH_ACK) *f++='A'; + if (tcphdr->th_flags & TH_RST) *f++='R'; + if (tcphdr->th_flags & TH_FIN) *f++='F'; + if (tcphdr->th_flags & TH_PUSH) *f++='P'; + if (tcphdr->th_flags & TH_URG) *f++='U'; + *f=0; + snprintf(s,s_len,"sport=%u dport=%u flags=%s seq=%u ack_seq=%u",htons(tcphdr->th_sport),htons(tcphdr->th_dport),flags,htonl(tcphdr->th_seq),htonl(tcphdr->th_ack)); +} +void print_tcphdr(const struct tcphdr *tcphdr) +{ + char s[80]; + str_tcphdr(s,sizeof(s),tcphdr); + printf("%s",s); +} +void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr) +{ + snprintf(s,s_len,"sport=%u dport=%u",htons(udphdr->uh_sport),htons(udphdr->uh_dport)); +} +void print_udphdr(const struct udphdr *udphdr) +{ + char s[30]; + str_udphdr(s,sizeof(s),udphdr); + printf("%s",s); +} + + + + +bool proto_check_ipv4(const uint8_t *data, size_t len) +{ + return len >= 20 && (data[0] & 0xF0) == 0x40 && + len >= ((data[0] & 0x0F) << 2); +} +// move to transport protocol +void proto_skip_ipv4(uint8_t **data, size_t *len) +{ + size_t l; + + l = (**data & 0x0F) << 2; + *data += l; + *len -= l; +} +bool proto_check_tcp(const uint8_t *data, size_t len) +{ + return len >= 20 && len >= ((data[12] & 0xF0) >> 2); +} +void proto_skip_tcp(uint8_t **data, size_t *len) +{ + size_t l; + l = ((*data)[12] & 0xF0) >> 2; + *data += l; + *len -= l; +} +bool proto_check_udp(const uint8_t *data, size_t len) +{ + return len >= 8 && len>=(data[4]<<8 | data[5]); +} +void proto_skip_udp(uint8_t **data, size_t *len) +{ + *data += 8; + *len -= 8; +} + +bool proto_check_ipv6(const uint8_t *data, size_t len) +{ + return len >= 40 && (data[0] & 0xF0) == 0x60 && + (len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length +} +// move to transport protocol +// proto_type = 0 => error +void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type) +{ + size_t hdrlen; + uint8_t HeaderType; + + if (proto_type) *proto_type = 0; // put error in advance + + HeaderType = (*data)[6]; // NextHeader field + if (last_header_type) *last_header_type = (*data)+6; + *data += 40; *len -= 40; // skip ipv6 base header + while (*len > 0) // need at least one byte for NextHeader field + { + switch (HeaderType) + { + case 0: // Hop-by-Hop Options + case 43: // routing + case 51: // authentication + case 60: // Destination Options + case 135: // mobility + case 139: // Host Identity Protocol Version v2 + case 140: // Shim6 + if (*len < 2) return; // error + hdrlen = 8 + ((*data)[1] << 3); + break; + case 44: // fragment. length fixed to 8, hdrlen field defined as reserved + hdrlen = 8; + break; + case 59: // no next header + return; // error + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + if (proto_type) *proto_type = HeaderType; + return; + } + if (*len < hdrlen) return; // error + HeaderType = **data; + if (last_header_type) *last_header_type = *data; + // advance to the next header location + *len -= hdrlen; + *data += hdrlen; + } + // we have garbage +} + +void proto_dissect_l3l4( + uint8_t *data, size_t len, + struct ip **ip, struct ip6_hdr **ip6, + uint8_t *proto, + struct tcphdr **tcp, + struct udphdr **udp, + size_t *transport_len, + uint8_t **data_payload, size_t *len_payload) +{ + *ip = NULL; + *ip6 = NULL; + *proto = 0; + *tcp = NULL; + *transport_len = 0; + *udp = NULL; + *data_payload = NULL; + *len_payload = 0; + + if (proto_check_ipv4(data, len)) + { + *ip = (struct ip *) data; + *proto = (*ip)->ip_p; + proto_skip_ipv4(&data, &len); + } + else if (proto_check_ipv6(data, len)) + { + *ip6 = (struct ip6_hdr *) data; + proto_skip_ipv6(&data, &len, proto, NULL); + } + else + { + return; + } + + if (*proto==IPPROTO_TCP && proto_check_tcp(data, len)) + { + *tcp = (struct tcphdr *) data; + *transport_len = len; + + proto_skip_tcp(&data, &len); + + *data_payload = data; + *len_payload = len; + + } + else if (*proto==IPPROTO_UDP && proto_check_udp(data, len)) + { + *udp = (struct udphdr *) data; + *transport_len = len; + + proto_skip_udp(&data, &len); + + *data_payload = data; + *len_payload = len; + } +} + + +bool tcp_synack_segment(const struct tcphdr *tcphdr) +{ + /* check for set bits in TCP hdr */ + return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == (TH_ACK|TH_SYN)); +} +bool tcp_syn_segment(const struct tcphdr *tcphdr) +{ + /* check for set bits in TCP hdr */ + return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_SYN); +} +bool tcp_ack_segment(const struct tcphdr *tcphdr) +{ + /* check for set bits in TCP hdr */ + return ((tcphdr->th_flags & (TH_URG|TH_ACK|TH_PUSH|TH_RST|TH_SYN|TH_FIN)) == TH_ACK); +} + +void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor) +{ + uint8_t *scale,scale_factor_old; + + if (scale_factor!=SCALE_NONE) + { + scale = tcp_find_option(tcp,3); // tcp option 3 - scale factor + if (scale && scale[1]==3) // length should be 3 + { + scale_factor_old=scale[2]; + // do not allow increasing scale factor + if (scale_factor>=scale_factor_old) + DLOG("Scale factor %u unchanged\n", scale_factor_old); + else + { + scale[2]=scale_factor; + DLOG("Scale factor change %u => %u\n", scale_factor_old, scale_factor); + } + } + } +} +// scale_factor=SCALE_NONE - do not change +void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor) +{ + uint16_t winsize_old; + + winsize_old = htons(tcp->th_win); // << scale_factor; + tcp->th_win = htons(winsize); + DLOG("Window size change %u => %u\n", winsize_old, winsize); + + tcp_rewrite_wscale(tcp, scale_factor); +} + + +#ifdef __CYGWIN__ + +static HANDLE w_filter = NULL; +static OVERLAPPED ovl = { .hEvent = NULL }; +static const struct str_list_head *wlan_filter_ssid = NULL, *nlm_filter_net = NULL; +static DWORD logical_net_filter_tick=0; +uint32_t w_win32_error=0; +INetworkListManager* pNetworkListManager=NULL; + +static void guid2str(const GUID *guid, char *str) +{ + snprintf(str,37, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); +} +static bool str2guid(const char* str, GUID *guid) +{ + unsigned int u[11],k; + + if (36 != strlen(str) || 11 != sscanf(str, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", u+0, u+1, u+2, u+3, u+4, u+5, u+6, u+7, u+8, u+9, u+10)) + return false; + guid->Data1 = u[0]; + if ((u[1] & 0xFFFF0000) || (u[2] & 0xFFFF0000)) return false; + guid->Data2 = (USHORT)u[1]; + guid->Data3 = (USHORT)u[2]; + for (k = 0; k < 8; k++) + { + if (u[k+3] & 0xFFFFFF00) return false; + guid->Data4[k] = (UCHAR)u[k+3]; + } + return true; +} + +static const char *sNetworkCards="SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; +// get adapter name from guid string +static bool AdapterID2Name(const GUID *guid,char *name,DWORD name_len) +{ + char sguid[39],sidx[32],val[256]; + HKEY hkNetworkCards,hkCard; + DWORD dwIndex,dwLen; + bool bRet = false; + WCHAR namew[128]; + DWORD namew_len; + + if (name_len<2) return false; + + if ((w_win32_error = RegOpenKeyExA(HKEY_LOCAL_MACHINE,sNetworkCards,0,KEY_ENUMERATE_SUB_KEYS,&hkNetworkCards)) == ERROR_SUCCESS) + { + guid2str(guid, sguid+1); + sguid[0]='{'; + sguid[37]='}'; + sguid[38]='\0'; + + for (dwIndex=0;;dwIndex++) + { + dwLen=sizeof(sidx)-1; + w_win32_error = RegEnumKeyExA(hkNetworkCards,dwIndex,sidx,&dwLen,NULL,NULL,NULL,NULL); + if (w_win32_error == ERROR_SUCCESS) + { + sidx[dwLen]='\0'; + + if ((w_win32_error = RegOpenKeyExA(hkNetworkCards,sidx,0,KEY_QUERY_VALUE,&hkCard)) == ERROR_SUCCESS) + { + dwLen=sizeof(val)-1; + if ((w_win32_error = RegQueryValueExA(hkCard,"ServiceName",NULL,NULL,val,&dwLen)) == ERROR_SUCCESS) + { + val[dwLen]='\0'; + if (!strcmp(val,sguid)) + { + namew_len = sizeof(namew)-sizeof(WCHAR); + if ((w_win32_error = RegQueryValueExW(hkCard,L"Description",NULL,NULL,(LPBYTE)namew,&namew_len)) == ERROR_SUCCESS) + { + namew[namew_len/sizeof(WCHAR)]=L'\0'; + if (WideCharToMultiByte(CP_UTF8, 0, namew, -1, name, name_len, NULL, NULL)) + bRet = true; + } + } + } + RegCloseKey(hkCard); + } + if (bRet) break; + } + else + break; + } + RegCloseKey(hkNetworkCards); + } + + return bRet; +} + +bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter) +{ + win_dark_deinit(); + if (LIST_EMPTY(ssid_filter)) ssid_filter=NULL; + if (LIST_EMPTY(nlm_filter)) nlm_filter=NULL; + if (nlm_filter) + { + if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) + { + if (FAILED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) + { + CoUninitialize(); + return false; + } + } + else + return false; + } + nlm_filter_net = nlm_filter; + wlan_filter_ssid = ssid_filter; + return true; +} +bool win_dark_deinit(void) +{ + if (pNetworkListManager) + { + pNetworkListManager->lpVtbl->Release(pNetworkListManager); + pNetworkListManager = NULL; + } + if (nlm_filter_net) CoUninitialize(); + wlan_filter_ssid = nlm_filter_net = NULL; +} + + +bool nlm_list(bool bAll) +{ + bool bRet = true; + + if (SUCCEEDED(w_win32_error = CoInitialize(NULL))) + { + INetworkListManager* pNetworkListManager; + if (SUCCEEDED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID*)&pNetworkListManager))) + { + IEnumNetworks* pEnumNetworks; + if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_ALL, &pEnumNetworks))) + { + INetwork *pNet; + INetworkConnection *pConn; + IEnumNetworkConnections *pEnumConnections; + VARIANT_BOOL bIsConnected, bIsConnectedInet; + NLM_NETWORK_CATEGORY category; + GUID idNet, idAdapter; + BSTR bstrName; + char Name[128],Name2[128]; + int connected; + for (connected = 1; connected >= !bAll; connected--) + { + for (;;) + { + if (FAILED(w_win32_error = pEnumNetworks->lpVtbl->Next(pEnumNetworks, 1, &pNet, NULL))) + { + bRet = false; + break; + } + if (w_win32_error != S_OK) break; + if (SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnected(pNet, &bIsConnected)) && + SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnectedToInternet(pNet, &bIsConnectedInet)) && + SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && + SUCCEEDED(w_win32_error = pNet->lpVtbl->GetCategory(pNet, &category)) && + SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) + { + if (!!bIsConnected == connected) + { + if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) + { + printf("Name : %s", Name); + if (bIsConnected) printf(" (connected)"); + if (bIsConnectedInet) printf(" (inet)"); + printf(" (%s)\n", + category==NLM_NETWORK_CATEGORY_PUBLIC ? "public" : + category==NLM_NETWORK_CATEGORY_PRIVATE ? "private" : + category==NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED ? "domain" : + "unknown"); + guid2str(&idNet, Name); + printf("NetID : %s\n", Name); + if (connected && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkConnections(pNet, &pEnumConnections))) + { + while ((w_win32_error = pEnumConnections->lpVtbl->Next(pEnumConnections, 1, &pConn, NULL))==S_OK) + { + if (SUCCEEDED(w_win32_error = pConn->lpVtbl->GetAdapterId(pConn,&idAdapter))) + { + guid2str(&idAdapter, Name); + if (AdapterID2Name(&idAdapter,Name2,sizeof(Name2))) + printf("Adapter : %s (%s)\n", Name2, Name); + else + printf("Adapter : %s\n", Name); + } + pConn->lpVtbl->Release(pConn); + } + pEnumConnections->lpVtbl->Release(pEnumConnections); + } + printf("\n"); + } + else + { + w_win32_error = HRESULT_FROM_WIN32(GetLastError()); + bRet = false; + } + } + SysFreeString(bstrName); + } + else + bRet = false; + pNet->lpVtbl->Release(pNet); + if (!bRet) break; + } + if (!bRet) break; + pEnumNetworks->lpVtbl->Reset(pEnumNetworks); + } + pEnumNetworks->lpVtbl->Release(pEnumNetworks); + } + else + bRet = false; + pNetworkListManager->lpVtbl->Release(pNetworkListManager); + } + else + bRet = false; + } + else + bRet = false; + + CoUninitialize(); + return bRet; +} + +static bool nlm_filter_match(const struct str_list_head *nlm_list) +{ + // no filter given. always matches. + if (!nlm_list || LIST_EMPTY(nlm_list)) + { + w_win32_error = 0; + return true; + } + + bool bRet = true, bMatch = false; + IEnumNetworks* pEnum; + + if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_CONNECTED, &pEnum))) + { + INetwork* pNet; + GUID idNet,g; + BSTR bstrName; + char Name[128]; + struct str_list *nlm; + for (;;) + { + if (FAILED(w_win32_error = pEnum->lpVtbl->Next(pEnum, 1, &pNet, NULL))) + { + bRet = false; + break; + } + if (w_win32_error != S_OK) break; + if (SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) && + SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName))) + { + if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL)) + { + LIST_FOREACH(nlm, nlm_list, next) + { + bMatch = !strcmp(Name,nlm->str) || str2guid(nlm->str,&g) && !memcmp(&idNet,&g,sizeof(GUID)); + if (bMatch) break; + } + } + else + { + w_win32_error = HRESULT_FROM_WIN32(GetLastError()); + bRet = false; + } + SysFreeString(bstrName); + } + else + bRet = false; + pNet->lpVtbl->Release(pNet); + if (!bRet || bMatch) break; + } + pEnum->lpVtbl->Release(pEnum); + } + else + bRet = false; + return bRet && bMatch; +} + +static bool wlan_filter_match(const struct str_list_head *ssid_list) +{ + DWORD dwCurVersion; + HANDLE hClient = NULL; + PWLAN_INTERFACE_INFO_LIST pIfList = NULL; + PWLAN_INTERFACE_INFO pIfInfo; + PWLAN_CONNECTION_ATTRIBUTES pConnectInfo; + DWORD connectInfoSize, k; + bool bRes; + struct str_list *ssid; + size_t len; + + // no filter given. always matches. + if (!ssid_list || LIST_EMPTY(ssid_list)) + { + w_win32_error = 0; + return true; + } + + w_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient); + if (w_win32_error != ERROR_SUCCESS) goto fail; + w_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList); + if (w_win32_error != ERROR_SUCCESS) goto fail; + for (k = 0; k < pIfList->dwNumberOfItems; k++) + { + pIfInfo = pIfList->InterfaceInfo + k; + if (pIfInfo->isState == wlan_interface_state_connected) + { + w_win32_error = WlanQueryInterface(hClient, + &pIfInfo->InterfaceGuid, + wlan_intf_opcode_current_connection, + NULL, + &connectInfoSize, + (PVOID *)&pConnectInfo, + NULL); + if (w_win32_error != ERROR_SUCCESS) goto fail; + +// printf("%s\n", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID); + + LIST_FOREACH(ssid, ssid_list, next) + { + len = strlen(ssid->str); + if (len==pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str,pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID,len)) + { + WlanFreeMemory(pConnectInfo); + goto found; + } + } + + WlanFreeMemory(pConnectInfo); + } + } + w_win32_error = 0; +fail: + bRes = false; +ex: + if (pIfList) WlanFreeMemory(pIfList); + if (hClient) WlanCloseHandle(hClient, 0); + return bRes; +found: + w_win32_error = 0; + bRes = true; + goto ex; +} + +bool logical_net_filter_match(void) +{ + return wlan_filter_match(wlan_filter_ssid) && nlm_filter_match(nlm_filter_net); +} + +static bool logical_net_filter_match_rate_limited(void) +{ + DWORD dwTick = GetTickCount() / 1000; + if (logical_net_filter_tick == dwTick) return true; + logical_net_filter_tick = dwTick; + return logical_net_filter_match(); +} + +static HANDLE windivert_init_filter(const char *filter, UINT64 flags) +{ + LPSTR errormessage = NULL; + HANDLE h, hMutex; + const char *mutex_name = "Global\\winws_windivert_mutex"; + + // windivert driver start in windivert.dll has race conditions + hMutex = CreateMutexA(NULL,TRUE,mutex_name); + if (hMutex && GetLastError()==ERROR_ALREADY_EXISTS) + WaitForSingleObject(hMutex,INFINITE); + h = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags); + w_win32_error = GetLastError(); + + if (hMutex) + { + ReleaseMutex(hMutex); + CloseHandle(hMutex); + SetLastError(w_win32_error); + } + + if (h != INVALID_HANDLE_VALUE) return h; + + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, w_win32_error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPSTR)&errormessage, 0, NULL); + DLOG_ERR("windivert: error opening filter: %s", errormessage); + LocalFree(errormessage); + if (w_win32_error == ERROR_INVALID_IMAGE_HASH) + DLOG_ERR("windivert: try to disable secure boot and install OS patches\n"); + + return NULL; +} +void rawsend_cleanup(void) +{ + if (w_filter) + { + CancelIoEx(w_filter,&ovl); + WinDivertClose(w_filter); + w_filter=NULL; + } + if (ovl.hEvent) + { + CloseHandle(ovl.hEvent); + ovl.hEvent=NULL; + } +} +bool windivert_init(const char *filter) +{ + rawsend_cleanup(); + w_filter = windivert_init_filter(filter, 0); + if (w_filter) + { + ovl.hEvent = CreateEventW(NULL,FALSE,FALSE,NULL); + if (!ovl.hEvent) + { + w_win32_error = GetLastError(); + rawsend_cleanup(); + return false; + } + return true; + } + return false; +} + +static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) +{ + UINT recv_len; + DWORD err; + DWORD rd; + char c; + + if (bQuit) + { + errno=EINTR; + return false; + } + if (!logical_net_filter_match_rate_limited()) + { + errno=ENODEV; + return false; + } + usleep(0); + if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl)) + { + *len = recv_len; + return true; + } + for(;;) + { + w_win32_error = GetLastError(); + switch(w_win32_error) + { + case ERROR_IO_PENDING: + // make signals working + while (WaitForSingleObject(ovl.hEvent,50)==WAIT_TIMEOUT) + { + if (bQuit) + { + errno=EINTR; + return false; + } + if (!logical_net_filter_match_rate_limited()) + { + errno=ENODEV; + return false; + } + usleep(0); + } + if (!GetOverlappedResult(hFilter,&ovl,&rd,TRUE)) + continue; + *len = rd; + return true; + case ERROR_INSUFFICIENT_BUFFER: + errno = ENOBUFS; + break; + case ERROR_NO_DATA: + errno = ESHUTDOWN; + break; + default: + errno = EIO; + } + break; + } + return false; +} +bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa) +{ + return windivert_recv_filter(w_filter,packet,len,wa); +} + +static bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) +{ + bool b = WinDivertSend(hFilter,packet,(UINT)len,NULL,wa); + w_win32_error = GetLastError(); + return b; +} +bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa) +{ + return windivert_send_filter(w_filter,packet,len,wa); +} + +bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) +{ + WINDIVERT_ADDRESS wa; + + memset(&wa,0,sizeof(wa)); + // pseudo interface id IfIdx.SubIfIdx + if (sscanf(ifout,"%u.%u",&wa.Network.IfIdx,&wa.Network.SubIfIdx)!=2) + { + errno = EINVAL; + return false; + } + wa.Outbound=1; + wa.IPChecksum=1; + wa.TCPChecksum=1; + wa.UDPChecksum=1; + wa.IPv6 = (dst->sa_family==AF_INET6); + + return windivert_send(data,len,&wa); +} + +#else // *nix + +static int rawsend_sock4=-1, rawsend_sock6=-1; +static bool b_bind_fix4=false, b_bind_fix6=false; +static void rawsend_clean_sock(int *sock) +{ + if (sock && *sock!=-1) + { + close(*sock); + *sock=-1; + } +} +void rawsend_cleanup(void) +{ + rawsend_clean_sock(&rawsend_sock4); + rawsend_clean_sock(&rawsend_sock6); +} +static int *rawsend_family_sock(sa_family_t family) +{ + switch(family) + { + case AF_INET: return &rawsend_sock4; + case AF_INET6: return &rawsend_sock6; + default: return NULL; + } +} + +#ifdef BSD +int socket_divert(sa_family_t family) +{ + int fd; + +#ifdef __FreeBSD__ + // freebsd14+ way + // don't want to use ifdefs with os version to make binaries compatible with all versions + fd = socket(PF_DIVERT, SOCK_RAW, 0); + if (fd==-1 && (errno==EPROTONOSUPPORT || errno==EAFNOSUPPORT || errno==EPFNOSUPPORT)) +#endif + // freebsd13- or openbsd way + fd = socket(family, SOCK_RAW, IPPROTO_DIVERT); + return fd; +} +static int rawsend_socket_divert(sa_family_t family) +{ + // HACK HACK HACK HACK HACK HACK HACK HACK + // FreeBSD doesnt allow IP_HDRINCL for IPV6 + // OpenBSD doesnt allow rawsending tcp frames + // we either have to go to the link layer (its hard, possible problems arise, compat testing, ...) or use some HACKING + // from my point of view disabling direct ability to send ip frames is not security. its SHIT + + int fd = socket_divert(family); + if (fd!=-1 && !set_socket_buffers(fd,4096,RAW_SNDBUF)) + { + close(fd); + return -1; + } + return fd; +} +static int rawsend_sendto_divert(sa_family_t family, int sock, const void *buf, size_t len) +{ + struct sockaddr_storage sa; + socklen_t slen; + +#ifdef __FreeBSD__ + // since FreeBSD 14 it requires hardcoded ipv4 values, although can also send ipv6 frames + family = AF_INET; + slen = sizeof(struct sockaddr_in); +#else + // OpenBSD requires correct family and size + switch(family) + { + case AF_INET: + slen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + slen = sizeof(struct sockaddr_in6); + break; + default: + return -1; + } +#endif + memset(&sa,0,slen); + sa.ss_family = family; + return sendto(sock, buf, len, 0, (struct sockaddr*)&sa, slen); +} +#endif + +static int rawsend_socket_raw(int domain, int proto) +{ + int fd = socket(domain, SOCK_RAW, proto); + if (fd!=-1) + { + #ifdef __linux__ + int s=RAW_SNDBUF/2; + int r=2048; + #else + int s=RAW_SNDBUF; + int r=4096; + #endif + if (!set_socket_buffers(fd,r,s)) + { + close(fd); + return -1; + } + } + return fd; +} + +static bool set_socket_fwmark(int sock, uint32_t fwmark) +{ +#ifdef BSD +#ifdef SO_USER_COOKIE + if (setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &fwmark, sizeof(fwmark)) == -1) + { + DLOG_PERROR("rawsend: setsockopt(SO_USER_COOKIE)"); + return false; + } +#endif +#elif defined(__linux__) + if (setsockopt(sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1) + { + DLOG_PERROR("rawsend: setsockopt(SO_MARK)"); + return false; + } + +#endif + return true; +} + +static int rawsend_socket(sa_family_t family) +{ + int *sock = rawsend_family_sock(family); + if (!sock) return -1; + + if (*sock==-1) + { + int yes=1,pri=6; + //printf("rawsend_socket: family %d",family); + +#ifdef __FreeBSD__ + // IPPROTO_RAW with ipv6 in FreeBSD always returns EACCES on sendto. + // must use IPPROTO_TCP for ipv6. IPPROTO_RAW works for ipv4 + // divert sockets are always v4 but accept both v4 and v6 + *sock = rawsend_socket_divert(AF_INET); +#elif defined(__OpenBSD__) || defined (__APPLE__) + // OpenBSD does not allow sending TCP frames through raw sockets + // I dont know about macos. They have dropped ipfw in recent versions and their PF does not support divert-packet + *sock = rawsend_socket_divert(family); +#else + *sock = rawsend_socket_raw(family, IPPROTO_RAW); +#endif + if (*sock==-1) + { + DLOG_PERROR("rawsend: socket()"); + return -1; + } +#ifdef __linux__ + if (setsockopt(*sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1) + { + DLOG_PERROR("rawsend: setsockopt(SO_PRIORITY)"); + goto exiterr; + } + if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_NODEFRAG, &yes, sizeof(yes)) == -1) + { + DLOG_PERROR("rawsend: setsockopt(IP_NODEFRAG)"); + goto exiterr; + } + if (family==AF_INET && setsockopt(*sock, IPPROTO_IP, IP_FREEBIND, &yes, sizeof(yes)) == -1) + { + DLOG_PERROR("rawsend: setsockopt(IP_FREEBIND)"); + goto exiterr; + } + if (family==AF_INET6 && setsockopt(*sock, SOL_IPV6, IPV6_FREEBIND, &yes, sizeof(yes)) == -1) + { + //DLOG_PERROR("rawsend: setsockopt(IPV6_FREEBIND)"); + // dont error because it's supported only from kernel 4.15 + } +#endif + } + return *sock; +exiterr: + rawsend_clean_sock(sock); + return -1; +} +bool rawsend_preinit(bool bind_fix4, bool bind_fix6) +{ + b_bind_fix4 = bind_fix4; + b_bind_fix6 = bind_fix6; + // allow ipv6 disabled systems + return rawsend_socket(AF_INET)!=-1 && (rawsend_socket(AF_INET6)!=-1 || errno==EAFNOSUPPORT); +} +bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) +{ + ssize_t bytes; + int sock=rawsend_socket(dst->sa_family); + if (sock==-1) return false; + if (!set_socket_fwmark(sock,fwmark)) return false; + + int salen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + struct sockaddr_storage dst2; + memcpy(&dst2,dst,salen); + if (dst->sa_family==AF_INET6) + ((struct sockaddr_in6 *)&dst2)->sin6_port = 0; // or will be EINVAL in linux + +#if defined(BSD) + bytes = rawsend_sendto_divert(dst->sa_family,sock,data,len); + if (bytes==-1) + { + DLOG_PERROR("rawsend: sendto_divert"); + return false; + } + return true; + +#else + +#ifdef __linux__ + struct sockaddr_storage sa_src; + switch(dst->sa_family) + { + case AF_INET: + if (!b_bind_fix4) goto nofix; + extract_endpoints(data,NULL,NULL,NULL, &sa_src, NULL); + break; + case AF_INET6: + if (!b_bind_fix6) goto nofix; + extract_endpoints(NULL,data,NULL,NULL, &sa_src, NULL); + break; + default: + return false; // should not happen + } + //printf("family %u dev %s bind : ", dst->sa_family, ifout); print_sockaddr((struct sockaddr *)&sa_src); printf("\n"); + if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifout, ifout ? strlen(ifout)+1 : 0) == -1) + { + DLOG_PERROR("rawsend: setsockopt(SO_BINDTODEVICE)"); + return false; + } + if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) + { + DLOG_PERROR("rawsend: bind (ignoring)"); + // do not fail. this can happen regardless of IP_FREEBIND + // rebind to any address + memset(&sa_src,0,sizeof(sa_src)); + sa_src.ss_family = dst->sa_family; + if (bind(sock, (const struct sockaddr*)&sa_src, dst->sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) + { + DLOG_PERROR("rawsend: bind to any"); + return false; + } + } +nofix: +#endif + + // normal raw socket sendto + bytes = sendto(sock, data, len, 0, (struct sockaddr*)&dst2, salen); + if (bytes==-1) + { + DLOG_PERROR("rawsend: sendto"); + return false; + } + return true; +#endif +} + +#endif // not CYGWIN + +bool rawsend_rp(const struct rawpacket *rp) +{ + return rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,rp->packet,rp->len); +} +bool rawsend_queue(struct rawpacket_tailhead *q) +{ + struct rawpacket *rp; + bool b; + for (b=true; (rp=rawpacket_dequeue(q)) ; rawpacket_free(rp)) + b &= rawsend_rp(rp); + return b; +} + + +// return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling +// ttl = TTL of incoming packet +uint8_t autottl_guess(uint8_t ttl, const autottl *attl) +{ + uint8_t orig, path, fake; + + // 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 + if (ttl>223) + orig=255; + else if (ttl<128 && ttl>96) + orig=128; + else if (ttl<64 && ttl>32) + orig=64; + else + return 0; + + path = orig - ttl; + + fake = path > attl->delta ? path - attl->delta : attl->min; + if (fakemin) fake=attl->min; + else if (fake>attl->max) fake=attl->max; + + if (fake>=path) return 0; + + return fake; +} + +void do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6) +{ + uint16_t nport; + + if (ip && target4) + { + nport = target4->sin_port; + if (bOutbound) + ip->ip_dst = target4->sin_addr; + else + ip->ip_src = target4->sin_addr; + ip4_fix_checksum(ip); + } + else if (ip6 && target6) + { + nport = target6->sin6_port; + if (bOutbound) + ip6->ip6_dst = target6->sin6_addr; + else + ip6->ip6_src = target6->sin6_addr; + } + else + return; + if (nport) + { + if (tcphdr) + { + if (bOutbound) + tcphdr->th_dport = nport; + else + tcphdr->th_sport = nport; + } + if (udphdr) + { + if (bOutbound) + udphdr->uh_dport = nport; + else + udphdr->uh_sport = nport; + } + } +} + + +void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) +{ + if (!(verdict & VERDICT_NOCSUM)) + { + // always fix csum for windivert. original can be partial or bad + #ifndef __CYGWIN__ + #ifdef __FreeBSD__ + // FreeBSD tend to pass ipv6 frames with wrong checksum + if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) + #else + // if original packet was tampered earlier it needs checksum fixed + if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) + #endif + #endif + tcp_fix_checksum(tcphdr,transport_len,ip,ip6hdr); + } +} +void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr) +{ + if (!(verdict & VERDICT_NOCSUM)) + { + // always fix csum for windivert. original can be partial or bad + #ifndef __CYGWIN__ + #ifdef __FreeBSD__ + // FreeBSD tend to pass ipv6 frames with wrong checksum + if ((verdict & VERDICT_MASK)==VERDICT_MODIFY || ip6hdr) + #else + // if original packet was tampered earlier it needs checksum fixed + if ((verdict & VERDICT_MASK)==VERDICT_MODIFY) + #endif + #endif + udp_fix_checksum(udphdr,transport_len,ip,ip6hdr); + } +} diff --git a/nfq/darkmagic.h b/nfq/darkmagic.h new file mode 100644 index 00000000..90f67014 --- /dev/null +++ b/nfq/darkmagic.h @@ -0,0 +1,243 @@ +#pragma once + +#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__ +#include "windivert/windivert.h" +#endif + +#ifndef IPPROTO_DIVERT +#define IPPROTO_DIVERT 258 +#endif + +#ifndef AF_DIVERT +#define AF_DIVERT 44 /* divert(4) */ +#endif +#ifndef PF_DIVERT +#define PF_DIVERT AF_DIVERT +#endif + +// returns netorder value +uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment); +uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment); + +#define FOOL_NONE 0x00 +#define FOOL_MD5SIG 0x01 +#define FOOL_BADSUM 0x02 +#define FOOL_TS 0x04 +#define FOOL_BADSEQ 0x08 +#define FOOL_HOPBYHOP 0x10 +#define FOOL_HOPBYHOP2 0x20 +#define FOOL_DESTOPT 0x40 +#define FOOL_IPFRAG1 0x80 +#define FOOL_DATANOACK 0x100 + +#define SCALE_NONE ((uint8_t)-1) + +#define VERDICT_PASS 0 +#define VERDICT_MODIFY 1 +#define VERDICT_DROP 2 +#define VERDICT_MASK 3 +#define VERDICT_NOCSUM 4 + +#define IP4_TOS(ip_header) (ip_header ? ip_header->ip_tos : 0) +#define IP6_FLOW(ip6_header) (ip6_header ? ip6_header->ip6_ctlun.ip6_un1.ip6_un1_flow : 0) + +// seq and wsize have network byte order +bool prepare_tcp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint8_t tos, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); +bool prepare_tcp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint32_t flow_label, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); +bool prepare_tcp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t tcp_flags, + uint32_t nseq, uint32_t nack_seq, + uint16_t nwsize, + uint8_t scale_factor, + uint32_t *timestamps, + uint8_t ttl, + uint8_t tos, uint32_t flow_label, + uint32_t fooling, + uint32_t badseq_increment, + uint32_t badseq_ack_increment, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); + + +bool prepare_udp_segment4( + const struct sockaddr_in *src, const struct sockaddr_in *dst, + uint8_t ttl, + uint8_t tos, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); +bool prepare_udp_segment6( + const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst, + uint8_t ttl, + uint32_t flow_label, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); +bool prepare_udp_segment( + const struct sockaddr *src, const struct sockaddr *dst, + uint8_t ttl, + uint8_t tos, uint32_t flow_label, + uint32_t fooling, + const uint8_t *padding, size_t padding_size, + int padlen, + const void *data, uint16_t len, + uint8_t *buf, size_t *buflen); + +bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen); + +// ipv4: ident==-1 - copy ip_id from original ipv4 packet +bool ip_frag4( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size); +bool ip_frag6( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size); +bool ip_frag( + const uint8_t *pkt, size_t pkt_size, + size_t frag_pos, uint32_t ident, + uint8_t *pkt1, size_t *pkt1_size, + uint8_t *pkt2, size_t *pkt2_size); + +void rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl); + +void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport); +void extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const struct tcphdr *tcphdr,const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst); +uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind); +uint32_t *tcp_find_timestamps(struct tcphdr *tcp); +uint8_t tcp_find_scale_factor(const struct tcphdr *tcp); +bool tcp_has_fastopen(const struct tcphdr *tcp); + +#ifdef __CYGWIN__ +extern uint32_t w_win32_error; + +bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter); +bool win_dark_deinit(void); +bool logical_net_filter_match(void); +bool nlm_list(bool bAll); +bool windivert_init(const char *filter); +bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa); +bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa); +#else +// should pre-do it if dropping privileges. otherwise its not necessary +bool rawsend_preinit(bool bind_fix4, bool bind_fix6); +#endif + +// auto creates internal socket and uses it for subsequent calls +bool rawsend(const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len); +bool rawsend_rp(const struct rawpacket *rp); +// return trues if all packets were send successfully +bool rawsend_queue(struct rawpacket_tailhead *q); +// cleans up socket autocreated by rawsend +void rawsend_cleanup(void); + +#ifdef BSD +int socket_divert(sa_family_t family); +#endif + +const char *proto_name(uint8_t proto); +uint16_t family_from_proto(uint8_t l3proto); +void print_ip(const struct ip *ip); +void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto); +void print_tcphdr(const struct tcphdr *tcphdr); +void print_udphdr(const struct udphdr *udphdr); +void str_ip(char *s, size_t s_len, const struct ip *ip); +void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto); +void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr); +void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr); +void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr); + +bool proto_check_ipv4(const uint8_t *data, size_t len); +void proto_skip_ipv4(uint8_t **data, size_t *len); +bool proto_check_ipv6(const uint8_t *data, size_t len); +void proto_skip_ipv6(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type); +bool proto_check_tcp(const uint8_t *data, size_t len); +void proto_skip_tcp(uint8_t **data, size_t *len); +bool proto_check_udp(const uint8_t *data, size_t len); +void proto_skip_udp(uint8_t **data, size_t *len); +void proto_dissect_l3l4( + uint8_t *data, size_t len, + struct ip **ip, struct ip6_hdr **ip6, + uint8_t *proto, + struct tcphdr **tcp, + struct udphdr **udp, + size_t *transport_len, + uint8_t **data_payload, size_t *len_payload); + +bool tcp_synack_segment(const struct tcphdr *tcphdr); +bool tcp_syn_segment(const struct tcphdr *tcphdr); +bool tcp_ack_segment(const struct tcphdr *tcphdr); +// scale_factor=SCALE_NONE - do not change +void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor); +void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor); + +typedef struct +{ + uint8_t delta, min, max; +} autottl; +#define AUTOTTL_DEFAULT_DELTA 1 +#define AUTOTTL_DEFAULT_MIN 3 +#define AUTOTTL_DEFAULT_MAX 20 +#define AUTOTTL_ENABLED(a) (!!(a).delta) +#define AUTOTTL_SET_DEFAULT(a) {(a).delta=AUTOTTL_DEFAULT_DELTA; (a).min=AUTOTTL_DEFAULT_MIN; (a).max=AUTOTTL_DEFAULT_MAX;} + +uint8_t autottl_guess(uint8_t ttl, const autottl *attl); +void do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6); + +void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); +void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr); diff --git a/nfq/desync.c b/nfq/desync.c new file mode 100644 index 00000000..e1181e67 --- /dev/null +++ b/nfq/desync.c @@ -0,0 +1,1964 @@ +#define _GNU_SOURCE + +#include "desync.h" +#include "protocol.h" +#include "params.h" +#include "helpers.h" +#include "hostlist.h" +#include "conntrack.h" + +#include + + +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"; + +// random : +11 size 32 +// random : +44 size 32 +// sni : gatech.edu +125 size 11 +const uint8_t fake_tls_clienthello_default[648] = { +0x16,0x03,0x01,0x02,0x83,0x01,0x00,0x02,0x7f,0x03,0x03,0x98,0xfb,0x69,0x1d,0x31, +0x66,0xc4,0xd8,0x07,0x25,0x2b,0x74,0x47,0x01,0x44,0x09,0x08,0xcf,0x13,0x67,0xe0, +0x46,0x19,0x1f,0xcb,0xee,0xe6,0x8e,0x33,0xb9,0x91,0xa0,0x20,0xf2,0xed,0x56,0x73, +0xa4,0x0a,0xce,0xa6,0xad,0xd2,0xfd,0x71,0xb8,0xb9,0xfd,0x06,0x0e,0xdd,0xf0,0x57, +0x37,0x7d,0x96,0xb5,0x80,0x6e,0x54,0xe2,0x15,0xce,0x5f,0xff,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,0x14,0x00,0x00,0x00,0x0f,0x00,0x0d,0x00,0x00,0x0a,0x67,0x61,0x74, +0x65,0x63,0x68,0x2e,0x65,0x64,0x75,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,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,0x33,0x00,0x6b,0x00,0x69,0x00,0x1d,0x00,0x20,0x72,0xe5,0xce,0x58,0x31, +0x3c,0x08,0xaa,0x2f,0xa8,0x40,0xe7,0x7a,0xdf,0x46,0x5b,0x63,0x62,0xc7,0xfa,0x49, +0x18,0xac,0xa1,0x00,0x7c,0x42,0xc5,0x02,0x94,0x5c,0x44,0x00,0x17,0x00,0x41,0x04, +0x8f,0x3e,0x5f,0xd4,0x7f,0x37,0x47,0xd3,0x33,0x70,0x38,0x7f,0x11,0x35,0xc1,0x55, +0x8a,0x6c,0xc7,0x5a,0xd4,0xf7,0x31,0xbb,0x9e,0xee,0xd1,0x8f,0x74,0xdd,0x9b,0xbb, +0x91,0xa1,0x72,0xda,0xeb,0xf6,0xc6,0x82,0x84,0xfe,0xb7,0xfd,0x7b,0xe1,0x9f,0xd2, +0xb9,0x3e,0x83,0xa6,0x9c,0xac,0x81,0xe2,0x00,0xd5,0x19,0x55,0x91,0xa7,0x0c,0x29, +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,0x1c,0x00,0x02,0x40,0x01,0xfe,0x0d,0x01,0x19,0x00, +0x00,0x01,0x00,0x01,0xfe,0x00,0x20,0xae,0x8b,0x30,0x3c,0xf0,0xa9,0x0d,0xa1,0x69, +0x95,0xb8,0xe2,0xed,0x08,0x6d,0x48,0xdf,0xf7,0x5b,0x9d,0x66,0xef,0x15,0x97,0xbc, +0x2c,0x99,0x91,0x12,0x7a,0x35,0xd0,0x00,0xef,0xb1,0x8d,0xff,0x61,0x57,0x52,0xef, +0xd6,0xea,0xbf,0xf3,0x6d,0x78,0x14,0x38,0xff,0xeb,0x58,0xe8,0x9d,0x59,0x4b,0xd5, +0x9f,0x59,0x12,0xf9,0x03,0x9a,0x20,0x37,0x85,0x77,0xb1,0x4c,0xd8,0xef,0xa6,0xc8, +0x54,0x8d,0x07,0x27,0x95,0xce,0xd5,0x37,0x4d,0x69,0x18,0xd4,0xfd,0x5e,0xdf,0x64, +0xcc,0x10,0x2f,0x7f,0x0e,0xc9,0xfd,0xd4,0xd0,0x18,0x61,0x1b,0x57,0x8f,0x41,0x7f, +0x6f,0x4f,0x5c,0xad,0x04,0xc6,0x5e,0x74,0x54,0x87,0xba,0x28,0xe6,0x11,0x0b,0x9d, +0x3f,0x0b,0x6d,0xf4,0x2d,0xfc,0x31,0x4e,0xfd,0x49,0xe7,0x15,0x96,0xaf,0xee,0x9a, +0x48,0x1b,0xae,0x5e,0x7c,0x20,0xbe,0xb4,0xec,0x68,0xb6,0x74,0x22,0xa0,0xec,0xff, +0x19,0x96,0xe4,0x10,0x8f,0x3c,0x91,0x88,0xa1,0xcc,0x78,0xef,0x4e,0x0e,0xe3,0xb6, +0x57,0x8c,0x33,0xef,0xaa,0xb0,0x1d,0x45,0x1c,0x02,0x4c,0xe2,0x80,0x30,0xe8,0x48, +0x7a,0x09,0x71,0x94,0x7c,0xb6,0x75,0x81,0x1c,0xae,0xe3,0x3f,0xde,0xea,0x2b,0x45, +0xcc,0xe3,0x64,0x09,0xf7,0x60,0x26,0x0c,0x7d,0xad,0x55,0x65,0xb6,0xf5,0x85,0x04, +0x64,0x2f,0x97,0xd0,0x6a,0x06,0x36,0xcd,0x25,0xda,0x51,0xab,0xd6,0xf7,0x5e,0xeb, +0xd4,0x03,0x39,0xa4,0xc4,0x2a,0x9c,0x17,0xe8,0xb0,0x9f,0xc0,0xd3,0x8c,0x76,0xdd, +0xa1,0x0b,0x76,0x9f,0x23,0xfa,0xed,0xfb,0xd7,0x78,0x0f,0x00,0xf7,0x45,0x03,0x04, +0x84,0x66,0x6b,0xec,0xc7,0xed,0xbc,0xe4 +}; + +static const char * tld[]={"com","org","net","edu","gov","biz"}; +void randomize_default_tls_payload(uint8_t *p) +{ + fill_random_bytes(p+11,32); + fill_random_bytes(p+44,32); + fill_random_az(p+125,1); + fill_random_az09(p+126,5); + memcpy(p+132,tld[random()%(sizeof(tld)/sizeof(*tld))],3); +} + +#define PKTDATA_MAXDUMP 32 +#define IP_MAXDUMP 80 + +static uint8_t zeropkt[DPI_DESYNC_MAX_FAKE_LEN]; + +void desync_init(void) +{ + memset(zeropkt, 0, sizeof(zeropkt)); +} + +bool desync_valid_zero_stage(enum dpi_desync_mode mode) +{ + return mode==DESYNC_SYNACK || mode==DESYNC_SYNDATA; +} +bool desync_valid_first_stage(enum dpi_desync_mode mode) +{ + return mode==DESYNC_FAKE || mode==DESYNC_FAKE_KNOWN || mode==DESYNC_RST || mode==DESYNC_RSTACK || mode==DESYNC_HOPBYHOP || mode==DESYNC_DESTOPT || mode==DESYNC_IPFRAG1; +} +bool desync_only_first_stage(enum dpi_desync_mode mode) +{ + return false; +} +bool desync_valid_second_stage(enum dpi_desync_mode mode) +{ + return mode==DESYNC_NONE || mode==DESYNC_DISORDER || mode==DESYNC_DISORDER2 || mode==DESYNC_SPLIT || mode==DESYNC_SPLIT2 || mode==DESYNC_IPFRAG2 || mode==DESYNC_UDPLEN || mode==DESYNC_TAMPER; +} +bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode) +{ + return mode==DESYNC_NONE || mode==DESYNC_DISORDER || mode==DESYNC_DISORDER2 || mode==DESYNC_SPLIT || mode==DESYNC_SPLIT2 || mode==DESYNC_IPFRAG2; +} +bool desync_valid_second_stage_udp(enum dpi_desync_mode mode) +{ + return mode==DESYNC_NONE || mode==DESYNC_UDPLEN || mode==DESYNC_TAMPER || mode==DESYNC_IPFRAG2; +} +enum dpi_desync_mode desync_mode_from_string(const char *s) +{ + if (!s) + return DESYNC_NONE; + else if (!strcmp(s,"fake")) + return DESYNC_FAKE; + else if (!strcmp(s,"fakeknown")) + return DESYNC_FAKE_KNOWN; + else if (!strcmp(s,"rst")) + return DESYNC_RST; + else if (!strcmp(s,"rstack")) + return DESYNC_RSTACK; + else if (!strcmp(s,"synack")) + return DESYNC_SYNACK; + else if (!strcmp(s,"syndata")) + return DESYNC_SYNDATA; + else if (!strcmp(s,"disorder")) + return DESYNC_DISORDER; + else if (!strcmp(s,"disorder2")) + return DESYNC_DISORDER2; + else if (!strcmp(s,"split")) + return DESYNC_SPLIT; + else if (!strcmp(s,"split2")) + return DESYNC_SPLIT2; + else if (!strcmp(s,"ipfrag2")) + return DESYNC_IPFRAG2; + else if (!strcmp(s,"hopbyhop")) + return DESYNC_HOPBYHOP; + else if (!strcmp(s,"destopt")) + return DESYNC_DESTOPT; + else if (!strcmp(s,"ipfrag1")) + return DESYNC_IPFRAG1; + else if (!strcmp(s,"udplen")) + return DESYNC_UDPLEN; + else if (!strcmp(s,"tamper")) + return DESYNC_TAMPER; + return DESYNC_INVALID; +} + +static bool dp_match_l3l4(struct desync_profile *dp, bool ipv6, uint16_t tcp_port, uint16_t udp_port) +{ + return \ + ((!ipv6 && dp->filter_ipv4) || (ipv6 && dp->filter_ipv6)) && + (!tcp_port || pf_in_range(tcp_port,&dp->pf_tcp)) && + (!udp_port || pf_in_range(udp_port,&dp->pf_udp)); +} +static bool dp_match( + struct desync_profile *dp, bool ipv6, uint16_t tcp_port, uint16_t udp_port, const char *hostname, + bool *bCheckDone, bool *bCheckResult, bool *bExcluded) +{ + if (bCheckDone) *bCheckDone = false; + if (dp_match_l3l4(dp,ipv6,tcp_port,udp_port)) + { + // autohostlist profile matching l3/l4 filter always win + if (*dp->hostlist_auto_filename) return true; + + if (dp->hostlist || dp->hostlist_exclude) + { + // without known hostname first profile matching l3/l4 filter and without hostlist filter wins + if (hostname) + { + if (bCheckDone) *bCheckDone = true; + bool b; + b = HostlistCheck(dp, hostname, bExcluded); + if (bCheckResult) *bCheckResult = b; + return b; + } + } + else + // profile without hostlist filter wins + return true; + } + return false; +} +static struct desync_profile *dp_find( + struct desync_profile_list_head *head, bool ipv6, uint16_t tcp_port, uint16_t udp_port, const char *hostname, + bool *bCheckDone, bool *bCheckResult, bool *bExcluded) +{ + struct desync_profile_list *dpl; + DLOG("desync profile search for hostname='%s' ipv6=%u tcp_port=%u udp_port=%u\n", hostname ? hostname : "", ipv6, tcp_port, udp_port); + if (bCheckDone) *bCheckDone = false; + LIST_FOREACH(dpl, head, next) + { + if (dp_match(&dpl->dp,ipv6,tcp_port,udp_port,hostname,bCheckDone,bCheckResult,bExcluded)) + { + DLOG("desync profile %d matches\n",dpl->dp.n); + return &dpl->dp; + } + } + DLOG("desync profile not found\n"); + return NULL; +} + +// auto creates internal socket and uses it for subsequent calls +static bool rawsend_rep(int repeats, const struct sockaddr* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len) +{ + for (int i=0;ipcounter_orig; + case 'd': return ctrack->pdcounter_orig; + case 's': return ctrack->seq_last - ctrack->seq0; + default: return 0; + } +} +static bool cutoff_test(const t_ctrack *ctrack, uint64_t cutoff, char mode) +{ + return cutoff && cutoff_get_limit(ctrack, mode)>=cutoff; +} +static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto) +{ + if (ctrack && ctrack->dp) + { + if (proto==IPPROTO_TCP) + ctrack->b_wssize_cutoff |= cutoff_test(ctrack, ctrack->dp->wssize_cutoff, ctrack->dp->wssize_cutoff_mode); + ctrack->b_desync_cutoff |= cutoff_test(ctrack, ctrack->dp->desync_cutoff, ctrack->dp->desync_cutoff_mode); + + // in MULTI STRATEGY concept conntrack entry holds desync profile + // we do not want to remove conntrack entries ASAP anymore + + /* + // we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached + // do not drop udp entry because it will be recreated when next packet arrives + if (proto==IPPROTO_TCP) + ctrack->b_cutoff |= \ + (!ctrack->dp->wssize || ctrack->b_wssize_cutoff) && + (!ctrack->dp->desync_cutoff || ctrack->b_desync_cutoff) && + (!ctrack->hostname_ah_check || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP) && + ReasmIsEmpty(&ctrack->reasm_orig); + */ + } +} +static void wssize_cutoff(t_ctrack *ctrack) +{ + if (ctrack) + { + ctrack->b_wssize_cutoff = true; + maybe_cutoff(ctrack, IPPROTO_TCP); + } +} +static void forced_wssize_cutoff(t_ctrack *ctrack) +{ + if (ctrack && ctrack->dp && ctrack->dp->wssize && !ctrack->b_wssize_cutoff) + { + DLOG("forced wssize-cutoff\n"); + wssize_cutoff(ctrack); + } +} + +static void ctrack_stop_retrans_counter(t_ctrack *ctrack) +{ + if (ctrack && ctrack->hostname_ah_check) + { + ctrack->req_retrans_counter = RETRANS_COUNTER_STOP; + maybe_cutoff(ctrack, IPPROTO_TCP); + } +} + +static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname) +{ + if (hostname) + { + hostfail_pool *fail_counter; + + fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); + if (fail_counter) + { + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); + DLOG("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter reset. website is working.", hostname, dp->n); + } + } +} + +// return true if retrans trigger fires +static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold) +{ + 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); + 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) +{ + 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) + { + fprintf(stderr, "HostFailPoolAdd: out of memory\n"); + return; + } + } + fail_counter->counter++; + DLOG("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n, hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter %d/%d", hostname, dp->n, fail_counter->counter, dp->hostlist_auto_fail_threshold); + if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) + { + DLOG("auto hostlist (profile %d) : fail threshold reached. about to add %s to auto hostlist\n", dp->n, hostname); + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); + + DLOG("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); + bool bExcluded=false; + if (!HostlistCheck(dp, hostname, &bExcluded) && !bExcluded) + { + DLOG("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto_filename); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : adding to %s", hostname, dp->n, dp->hostlist_auto_filename); + if (!StrPoolAddStr(&dp->hostlist, hostname)) + { + fprintf(stderr, "StrPoolAddStr out of memory\n"); + return; + } + if (!append_to_list_file(dp->hostlist_auto_filename, hostname)) + { + DLOG_PERROR("write to auto hostlist:"); + return; + } + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + } + else + { + DLOG("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : NOT adding, duplicate detected", hostname, dp->n); + } + } +} + +static void process_retrans_fail(t_ctrack *ctrack, uint8_t proto) +{ + if (ctrack && ctrack->dp && ctrack->hostname && auto_hostlist_retrans(ctrack, proto, ctrack->dp->hostlist_auto_retrans_threshold)) + { + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : tcp retrans threshold reached", ctrack->hostname, ctrack->dp->n); + auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } +} + +static bool send_delayed(t_ctrack *ctrack) +{ + if (!rawpacket_queue_empty(&ctrack->delayed)) + { + DLOG("SENDING %u delayed packets\n", rawpacket_queue_count(&ctrack->delayed)); + return rawsend_queue(&ctrack->delayed); + } + return true; +} + + +static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload) +{ + ReasmClear(reasm); + if (sz<=szMax) + { + uint32_t seq = (proto==IPPROTO_TCP) ? ctrack->seq_last : 0; + if (ReasmInit(reasm,sz,seq)) + { + ReasmFeed(reasm,seq,data_payload,len_payload); + DLOG("starting reassemble. now we have %zu/%zu\n",reasm->size_present,reasm->size); + return true; + } + else + DLOG("reassemble init failed. out of memory\n"); + } + else + DLOG("unexpected large payload for reassemble: size=%zu\n",sz); + return false; +} +static bool reasm_orig_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload) +{ + return reasm_start(ctrack,&ctrack->reasm_orig,proto,sz,szMax,data_payload,len_payload); +} +static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, const uint8_t *data_payload, size_t len_payload) +{ + if (ctrack && !ReasmIsEmpty(reasm)) + { + uint32_t seq = (proto==IPPROTO_TCP) ? ctrack->seq_last : (uint32_t)reasm->size_present; + if (ReasmFeed(reasm, seq, data_payload, len_payload)) + { + DLOG("reassemble : feeding data payload size=%zu. now we have %zu/%zu\n", len_payload,reasm->size_present,reasm->size); + return true; + } + else + { + ReasmClear(reasm); + DLOG("reassemble session failed\n"); + send_delayed(ctrack); + } + } + return false; +} +static bool reasm_orig_feed(t_ctrack *ctrack, uint8_t proto, const uint8_t *data_payload, size_t len_payload) +{ + return reasm_feed(ctrack, &ctrack->reasm_orig, proto, data_payload, len_payload); +} +static void reasm_orig_stop(t_ctrack *ctrack, const char *dlog_msg) +{ + if (ctrack) + { + if (!ReasmIsEmpty(&ctrack->reasm_orig)) + { + DLOG("%s",dlog_msg); + ReasmClear(&ctrack->reasm_orig); + } + send_delayed(ctrack); + } +} +static void reasm_orig_cancel(t_ctrack *ctrack) +{ + reasm_orig_stop(ctrack, "reassemble session cancelled\n"); +} +static void reasm_orig_fin(t_ctrack *ctrack) +{ + reasm_orig_stop(ctrack, "reassemble session finished\n"); +} + + +static uint8_t ct_new_postnat_fix(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, uint8_t proto, struct udphdr *udp, struct tcphdr *tcp, size_t *len_pkt) +{ +#ifdef __linux__ + // if used in postnat chain, dropping initial packet will cause conntrack connection teardown + // so we need to workaround this. + // we can't use low ttl because TCP/IP stack listens to ttl expired ICMPs and notify socket + // we also can't use fooling because DPI would accept fooled packets + if (ctrack && ctrack->pcounter_orig==1) + { + DLOG("applying linux postnat conntrack workaround\n"); + if (proto==IPPROTO_UDP && udp && len_pkt) + { + // make malformed udp packet with zero length and invalid checksum + udp->uh_ulen = 0; // invalid length. must be >=8 + udp_fix_checksum(udp,sizeof(struct udphdr),ip,ip6); + udp->uh_sum ^= htons(0xBEAF); + // truncate packet + *len_pkt = (uint8_t*)udp - (ip ? (uint8_t*)ip : (uint8_t*)ip6) + sizeof(struct udphdr); + if (ip) + { + ip->ip_len = htons((uint16_t)*len_pkt); + ip4_fix_checksum(ip); + } + else if (ip6) + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = (uint16_t)htons(sizeof(struct udphdr)); + } + else if (proto==IPPROTO_TCP && tcp) + { + // only SYN here is expected + // make flags invalid and also corrupt checksum + tcp->th_flags = 0; + } + if (ip) ip->ip_sum ^= htons(0xBEAF); + return VERDICT_MODIFY | VERDICT_NOCSUM; + } +#endif + return VERDICT_DROP; +} + +static uint8_t ct_new_postnat_fix_tcp(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr) +{ + return ct_new_postnat_fix(ctrack,ip,ip6,IPPROTO_TCP,NULL,tcphdr,NULL); +} +static uint8_t ct_new_postnat_fix_udp(const t_ctrack *ctrack, struct ip *ip, struct ip6_hdr *ip6, struct udphdr *udphdr, size_t *len_pkt) +{ + return ct_new_postnat_fix(ctrack,ip,ip6,IPPROTO_UDP,udphdr,NULL,len_pkt); +} + + +static bool check_desync_interval(const struct desync_profile *dp, const t_ctrack *ctrack) +{ + if (dp) + { + if (dp->desync_start) + { + if (ctrack) + { + if (!cutoff_test(ctrack, dp->desync_start, dp->desync_start_mode)) + { + DLOG("desync-start not reached (mode %c): %llu/%u . not desyncing\n", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->desync_start_mode), dp->desync_start); + return false; + } + DLOG("desync-start reached (mode %c): %llu/%u\n", dp->desync_start_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->desync_start_mode), dp->desync_start); + } + else + { + DLOG("not desyncing. desync-start is set but conntrack entry is missing\n"); + return false; + } + } + if (dp->desync_cutoff) + { + if (ctrack) + { + if (ctrack->b_desync_cutoff) + { + DLOG("desync-cutoff reached (mode %c): %llu/%u . not desyncing\n", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->desync_cutoff_mode), dp->desync_cutoff); + return false; + } + DLOG("desync-cutoff not reached (mode %c): %llu/%u\n", dp->desync_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->desync_cutoff_mode), dp->desync_cutoff); + } + else + { + DLOG("not desyncing. desync-cutoff is set but conntrack entry is missing\n"); + return false; + } + } + } + return true; +} +static bool process_desync_interval(const struct desync_profile *dp, t_ctrack *ctrack) +{ + if (check_desync_interval(dp, ctrack)) + return true; + else + { + reasm_orig_cancel(ctrack); + return false; + } +} + +static bool replay_queue(struct rawpacket_tailhead *q); + +static size_t pos_normalize(size_t split_pos, size_t reasm_offset, size_t len_payload) +{ + size_t rsplit_pos = split_pos; + // normalize split pos to current packet + split_pos=(split_pos>reasm_offset && (split_pos-reasm_offset) %zu\n",rsplit_pos,split_pos); + else + DLOG("split pos %zu is outside of this packet %zu-%zu\n",rsplit_pos,reasm_offset,reasm_offset+len_payload); + } + } + return split_pos; +} + + +static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt, struct ip *ip, struct ip6_hdr *ip6hdr, struct tcphdr *tcphdr, size_t transport_len, uint8_t *data_payload, size_t len_payload) +{ + uint8_t verdict=VERDICT_PASS; + + // additional safety check + if (!!ip == !!ip6hdr) return verdict; + + struct desync_profile *dp = NULL; + + t_ctrack *ctrack=NULL, *ctrack_replay=NULL; + bool bReverse=false; + + struct sockaddr_storage src, dst; + uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN+100], pkt2[DPI_DESYNC_MAX_FAKE_LEN+100]; + size_t pkt1_len, pkt2_len; + uint8_t ttl_orig,ttl_fake,flags_orig,scale_factor; + uint32_t *timestamps; + t_l7proto l7proto = UNKNOWN; + + ttl_orig = ip ? ip->ip_ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; + uint32_t desync_fwmark = fwmark | params.desync_fwmark; + + if (replay) + { + // in replay mode conntrack_replay is not NULL and ctrack is NULL + + //ConntrackPoolDump(¶ms.conntrack); + if (!ConntrackPoolDoubleSearch(¶ms.conntrack, ip, ip6hdr, tcphdr, NULL, &ctrack_replay, &bReverse) || bReverse) + return verdict; + + dp = ctrack_replay->dp; + if (dp) + DLOG("using cached desync profile %d\n",dp->n); + else if (!ctrack_replay->dp_search_complete) + { + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack_replay->hostname, 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 + + ConntrackPoolPurge(¶ms.conntrack); + if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse)) + { + dp = ctrack->dp; + ctrack_replay = ctrack; + } + if (dp) + DLOG("using cached desync profile %d\n",dp->n); + else if (!ctrack || !ctrack->dp_search_complete) + { + dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack ? ctrack->hostname : NULL, NULL, NULL, NULL); + if (ctrack) + { + ctrack->dp = dp; + ctrack->dp_search_complete = true; + } + } + if (!dp) + { + DLOG("matching desync profile not found\n"); + return verdict; + } + maybe_cutoff(ctrack, IPPROTO_TCP); + + HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); + + //ConntrackPoolDump(¶ms.conntrack); + + if (dp->wsize && tcp_synack_segment(tcphdr)) + { + tcp_rewrite_winsize(tcphdr, dp->wsize, dp->wscale); + verdict=VERDICT_MODIFY; + } + + if (bReverse) + { + if (ctrack && !ctrack->autottl && ctrack->pcounter_reply==1) + { + autottl *attl = ip ? &dp->desync_autottl : &dp->desync_autottl6; + if (AUTOTTL_ENABLED(*attl)) + { + ctrack->autottl = autottl_guess(ttl_orig, attl); + if (ctrack->autottl) + DLOG("autottl: guessed %u\n",ctrack->autottl); + else + DLOG("autottl: could not guess\n"); + } + } + + // process reply packets for auto hostlist mode + // by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked + // we only process first-sequence replies. do not react to subsequent redirects or RSTs + if (ctrack && ctrack->hostname && ctrack->hostname_ah_check && (ctrack->ack_last-ctrack->ack0)==1) + { + bool bFail=false; + if (tcphdr->th_flags & TH_RST) + { + DLOG("incoming RST detected for hostname %s\n", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : incoming RST", ctrack->hostname, ctrack->dp->n); + bFail = true; + } + else if (len_payload && ctrack->l7proto==HTTP) + { + if (IsHttpReply(data_payload,len_payload)) + { + DLOG("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); + bFail = HttpReplyLooksLikeDPIRedirect(data_payload, len_payload, ctrack->hostname); + if (bFail) + { + DLOG("redirect to another domain detected. possibly DPI redirect.\n"); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : redirect to another domain", ctrack->hostname, ctrack->dp->n); + } + 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); + else + if (len_payload) + auto_hostlist_reset_fail_counter(dp, ctrack->hostname); + if (tcphdr->th_flags & TH_RST) + ConntrackClearHostname(ctrack); // do not react to further dup RSTs + } + + return verdict; // nothing to do. do not waste cpu + } + + if (dp->wssize) + { + if (ctrack) + { + if (ctrack->b_wssize_cutoff) + { + DLOG("wssize-cutoff reached (mode %c): %llu/%u . not changing wssize.\n", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->wssize_cutoff_mode), dp->wssize_cutoff); + } + else + { + if (dp->wssize_cutoff) DLOG("wssize-cutoff not reached (mode %c): %llu/%u\n", dp->wssize_cutoff_mode, (unsigned long long)cutoff_get_limit(ctrack,dp->wssize_cutoff_mode), dp->wssize_cutoff); + tcp_rewrite_winsize(tcphdr, dp->wssize, dp->wsscale); + verdict=VERDICT_MODIFY; + } + } + else + { + DLOG("not changing wssize. wssize is set but conntrack entry is missing\n"); + } + } + } // !replay + + ttl_fake = (ctrack_replay && ctrack_replay->autottl) ? ctrack_replay->autottl : (ip6hdr ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); + flags_orig = *((uint8_t*)tcphdr+13); + scale_factor = tcp_find_scale_factor(tcphdr); + timestamps = tcp_find_timestamps(tcphdr); + extract_endpoints(ip, ip6hdr, tcphdr, NULL, &src, &dst); + + if (!replay) + { + if (tcp_syn_segment(tcphdr)) + { + switch (dp->desync_mode0) + { + case DESYNC_SYNACK: + pkt1_len = sizeof(pkt1); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_SYN|TH_ACK, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_fake,IP4_TOS(ip),IP6_FLOW(ip6hdr), + dp->desync_fooling_mode,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + NULL, 0, pkt1, &pkt1_len)) + { + return verdict; + } + DLOG("sending fake SYNACK\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + break; + case DESYNC_SYNDATA: + // make sure we are not breaking TCP fast open + if (tcp_has_fastopen(tcphdr)) + { + DLOG("received SYN with TCP fast open option. syndata desync is not applied.\n"); + break; + } + if (len_payload) + { + DLOG("received SYN with data payload. syndata desync is not applied.\n"); + break; + } + pkt1_len = sizeof(pkt1); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + 0,0,0, dp->fake_syndata,dp->fake_syndata_size, pkt1,&pkt1_len)) + { + return verdict; + } + DLOG("sending SYN with fake data : "); + hexdump_limited_dlog(dp->fake_syndata,dp->fake_syndata_size,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + verdict = ct_new_postnat_fix_tcp(ctrack, ip, ip6hdr, tcphdr); + break; + default: + break; + } + // can do nothing else with SYN packet + return verdict; + } + + // start and cutoff limiters + if (!process_desync_interval(dp, ctrack)) return verdict; + } // !replay + + if (!(tcphdr->th_flags & TH_SYN) && len_payload) + { + const uint8_t *fake; + size_t fake_size; + char host[256]; + bool bHaveHost=false; + uint8_t *p, *phost; + const uint8_t *rdata_payload = data_payload; + size_t rlen_payload = len_payload; + size_t split_pos; + + if (replay) + { + rdata_payload = ctrack_replay->reasm_orig.packet; + rlen_payload = ctrack_replay->reasm_orig.size_present; + } + else if (reasm_orig_feed(ctrack,IPPROTO_TCP,data_payload,len_payload)) + { + rdata_payload = ctrack->reasm_orig.packet; + rlen_payload = ctrack->reasm_orig.size_present; + } + + process_retrans_fail(ctrack, IPPROTO_TCP); + + if (IsHttp(rdata_payload,rlen_payload)) + { + DLOG("packet contains HTTP request\n"); + l7proto = HTTP; + if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + + // we do not reassemble http + reasm_orig_cancel(ctrack); + forced_wssize_cutoff(ctrack); + + bHaveHost=HttpExtractHost(rdata_payload,rlen_payload,host,sizeof(host)); + if (!bHaveHost) + { + DLOG("not applying tampering to HTTP without Host:\n"); + return verdict; + } + if (ctrack) + { + // we do not reassemble http + if (!ctrack->req_seq_present) + { + ctrack->req_seq_start=ctrack->seq_last; + ctrack->req_seq_end=ctrack->pos_orig-1; + ctrack->req_seq_present=ctrack->req_seq_finalized=true; + DLOG("req retrans : tcp seq interval %u-%u\n",ctrack->req_seq_start,ctrack->req_seq_end); + } + } + } + else if (IsTLSClientHello(rdata_payload,rlen_payload,TLS_PARTIALS_ENABLE)) + { + bool bReqFull = IsTLSRecordFull(rdata_payload,rlen_payload); + DLOG(bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n"); + l7proto = TLS; + + bHaveHost=TLSHelloExtractHost(rdata_payload,rlen_payload,host,sizeof(host),TLS_PARTIALS_ENABLE); + + if (ctrack) + { + if (!ctrack->l7proto) ctrack->l7proto = l7proto; + // do not reasm retransmissions + if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig) && !ctrack->req_seq_abandoned && + !(ctrack->req_seq_finalized && seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))) + { + // do not reconstruct unexpected large payload (they are feeding garbage ?) + if (!reasm_orig_start(ctrack,IPPROTO_TCP,TLSRecordLen(data_payload),16384,data_payload,len_payload)) + { + reasm_orig_cancel(ctrack); + return verdict; + } + + } + 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 (bReqFull || ReasmIsEmpty(&ctrack->reasm_orig)) forced_wssize_cutoff(ctrack); + + if (!ReasmIsEmpty(&ctrack->reasm_orig)) + { + verdict_tcp_csum_fix(verdict, tcphdr, transport_len, ip, ip6hdr); + if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifout, data_pkt, *len_pkt, len_payload)) + { + DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); + } + else + { + fprintf(stderr, "rawpacket_queue failed !'\n"); + reasm_orig_cancel(ctrack); + return verdict; + } + if (ReasmIsFull(&ctrack->reasm_orig)) + { + replay_queue(&ctrack->delayed); + reasm_orig_fin(ctrack); + } + return VERDICT_DROP; + } + } + + if (dp->desync_skip_nosni && !bHaveHost) + { + DLOG("not applying tampering to TLS ClientHello without hostname in the SNI\n"); + reasm_orig_cancel(ctrack); + return verdict; + } + } + + 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) + { + bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; + DLOG("hostname: %s\n",host); + if (ctrack_replay) + { + if (!ctrack_replay->hostname) + { + ctrack_replay->hostname=strdup(host); + if (!ctrack_replay->hostname) + { + DLOG_ERR("hostname dup : out of memory"); + reasm_orig_cancel(ctrack); + return verdict; + } + DLOG("we have hostname now. searching desync profile again.\n"); + struct desync_profile *dp_prev = dp; + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack_replay->hostname, &ctrack_replay->bCheckDone, &ctrack_replay->bCheckResult, &ctrack_replay->bCheckExcluded); + ctrack_replay->dp_search_complete = true; + if (!dp) + { + reasm_orig_cancel(ctrack); + return verdict; + } + if (dp!=dp_prev) + { + DLOG("desync profile changed by revealed hostname !\n"); + // re-evaluate start/cutoff limiters + if (!replay) + { + maybe_cutoff(ctrack, IPPROTO_TCP); + if (!process_desync_interval(dp, ctrack)) + { + reasm_orig_cancel(ctrack); + return verdict; + } + } + } + } + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + if (dp->hostlist || dp->hostlist_exclude) + { + if (!bCheckDone) + bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); + if (bCheckResult) + ctrack_stop_retrans_counter(ctrack_replay); + else + { + if (ctrack_replay) + { + ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; + if (!ctrack_replay->hostname_ah_check) + ctrack_stop_retrans_counter(ctrack_replay); + } + DLOG("not applying tampering to this request\n"); + reasm_orig_cancel(ctrack); + return verdict; + } + } + } + + // desync profile may have changed after hostname was revealed + switch(l7proto) + { + case HTTP: + fake = dp->fake_http; + fake_size = dp->fake_http_size; + split_pos = HttpPos(dp->desync_split_http_req, dp->desync_split_pos, rdata_payload, rlen_payload); + break; + case TLS: + fake = dp->fake_tls; + fake_size = dp->fake_tls_size; + split_pos = TLSPos(dp->desync_split_tls, dp->desync_split_pos, rdata_payload, rlen_payload, 0); + break; + default: + fake = dp->fake_unknown; + fake_size = dp->fake_unknown_size; + split_pos=dp->desync_split_pos; + break; + } + + // we do not need reasm buffer anymore + reasm_orig_cancel(ctrack); + rdata_payload=NULL; + + if (l7proto==UNKNOWN) + { + if (!dp->desync_any_proto) return verdict; + DLOG("applying tampering to unknown protocol\n"); + } + + ttl_fake = (ctrack_replay && ctrack_replay->autottl) ? ctrack_replay->autottl : (ip6hdr ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); + + if ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase) && (phost = (uint8_t*)memmem(data_payload, len_payload, "\r\nHost: ", 8))) + { + if (dp->hostcase) + { + DLOG("modifying Host: => %c%c%c%c:\n", dp->hostspell[0], dp->hostspell[1], dp->hostspell[2], dp->hostspell[3]); + memcpy(phost + 2, dp->hostspell, 4); + verdict=VERDICT_MODIFY; + } + if (dp->domcase) + { + DLOG("mixing domain case\n"); + for (p = phost+7; p < (data_payload + len_payload) && *p != '\r' && *p != '\n'; p++) + *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); + verdict=VERDICT_MODIFY; + } + uint8_t *pua; + if (dp->hostnospace && + (pua = (uint8_t*)memmem(data_payload, len_payload, "\r\nUser-Agent: ", 14)) && + (pua = (uint8_t*)memmem(pua + 1, len_payload - (pua - data_payload) - 1, "\r\n", 2))) + { + DLOG("removing space after Host: and adding it to User-Agent:\n"); + if (pua > phost) + { + memmove(phost + 7, phost + 8, pua - phost - 8); + phost[pua - phost - 1] = ' '; + } + else + { + memmove(pua + 1, pua, phost - pua + 7); + *pua = ' '; + } + verdict=VERDICT_MODIFY; + } + } + + if (dp->desync_mode==DESYNC_NONE) return verdict; + + if (params.debug) + { + char s1[48],s2[48]; + ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); + ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); + DLOG("dpi desync src=%s dst=%s\n",s1,s2); + } + + if (!split_pos || split_pos>rlen_payload) split_pos=1; + split_pos=pos_normalize(split_pos,reasm_offset,len_payload); + + enum dpi_desync_mode desync_mode = dp->desync_mode; + uint32_t fooling_orig = FOOL_NONE; + bool b; + pkt1_len = sizeof(pkt1); + b = false; + switch(desync_mode) + { + case DESYNC_FAKE_KNOWN: + if (reasm_offset) + { + desync_mode = dp->desync_mode2; + break; + } + if (l7proto==UNKNOWN) + { + DLOG("not applying fake because of unknown protocol\n"); + desync_mode = dp->desync_mode2; + break; + } + case DESYNC_FAKE: + if (reasm_offset) break; + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_fake,IP4_TOS(ip),IP6_FLOW(ip6hdr), + dp->desync_fooling_mode,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + fake, fake_size, pkt1, &pkt1_len)) + { + return verdict; + } + DLOG("sending fake request : "); + hexdump_limited_dlog(fake,fake_size,PKTDATA_MAXDUMP); DLOG("\n"); + b = true; + break; + case DESYNC_RST: + case DESYNC_RSTACK: + if (reasm_offset) break; + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, TH_RST | (desync_mode==DESYNC_RSTACK ? TH_ACK:0), tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_fake,IP4_TOS(ip),IP6_FLOW(ip6hdr), + dp->desync_fooling_mode,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + NULL, 0, pkt1, &pkt1_len)) + { + return verdict; + } + DLOG("sending fake RST/RSTACK\n"); + b = true; + break; + case DESYNC_HOPBYHOP: + case DESYNC_DESTOPT: + case DESYNC_IPFRAG1: + fooling_orig = (desync_mode==DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (desync_mode==DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; + desync_mode = dp->desync_mode2; + if (ip6hdr && (desync_mode==DESYNC_NONE || !desync_valid_second_stage_tcp(desync_mode) || + (!split_pos && (desync_mode==DESYNC_SPLIT || desync_mode==DESYNC_SPLIT2 || desync_mode==DESYNC_DISORDER || desync_mode==DESYNC_DISORDER2)))) + { + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,0,0, + data_payload, len_payload, pkt1, &pkt1_len)) + { + return verdict; + } + DLOG("resending original packet with extension header\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + // this mode is final, no other options available + return VERDICT_DROP; + } + default: + pkt1_len=0; + break; + } + + if (b) + { + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + if (dp->desync_mode2==DESYNC_NONE || !desync_valid_second_stage_tcp(dp->desync_mode2)) + { + DLOG("reinjecting original packet. len=%zu len_payload=%zu\n", *len_pkt, len_payload); + verdict_tcp_csum_fix(verdict, tcphdr, transport_len, ip, ip6hdr); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , data_pkt, *len_pkt)) + return verdict; + return VERDICT_DROP; + } + desync_mode = dp->desync_mode2; + } + + pkt1_len = sizeof(pkt1); + switch(desync_mode) + { + case DESYNC_DISORDER: + case DESYNC_DISORDER2: + if (split_pos) + { + uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN+100], *seg; + size_t seg_len; + + if (dp->desync_seqovl>=split_pos) + { + DLOG("seqovl>=split_pos. desync is not possible.\n"); + return verdict; + } + + if (split_posdesync_seqovl) + { + seg_len = len_payload-split_pos+dp->desync_seqovl; + if (seg_len>sizeof(fakeseg)) + { + DLOG("seqovl is too large\n"); + return verdict; + } + fill_pattern(fakeseg,dp->desync_seqovl,dp->seqovl_pattern,sizeof(dp->seqovl_pattern)); + memcpy(fakeseg+dp->desync_seqovl,data_payload+split_pos,len_payload-split_pos); + seg = fakeseg; + } + else + { + seg = data_payload+split_pos; + seg_len = len_payload-split_pos; + } + + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, net32_add(net32_add(tcphdr->th_seq,split_pos),-dp->desync_seqovl), tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + seg, seg_len, pkt1, &pkt1_len)) + return verdict; + DLOG("sending 2nd out-of-order tcp segment %zu-%zu len=%zu seqovl=%u : ",split_pos,len_payload-1, len_payload-split_pos, dp->desync_seqovl); + hexdump_limited_dlog(seg,seg_len,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + } + + + if (desync_mode==DESYNC_DISORDER) + { + seg_len = sizeof(fakeseg); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_fake,IP4_TOS(ip),IP6_FLOW(ip6hdr), + dp->desync_fooling_mode,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + zeropkt, split_pos, fakeseg, &seg_len)) + return verdict; + DLOG("sending fake(1) 1st out-of-order tcp segment 0-%zu len=%zu : ",split_pos-1, split_pos); + hexdump_limited_dlog(zeropkt,split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , fakeseg, seg_len)) + return verdict; + } + + pkt1_len = sizeof(pkt1); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + data_payload, split_pos, pkt1, &pkt1_len)) + return verdict; + DLOG("sending 1st out-of-order tcp segment 0-%zu len=%zu : ",split_pos-1, split_pos); + hexdump_limited_dlog(data_payload,split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + + if (desync_mode==DESYNC_DISORDER) + { + DLOG("sending fake(2) 1st out-of-order tcp segment 0-%zu len=%zu : ",split_pos-1, split_pos); + hexdump_limited_dlog(zeropkt,split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , fakeseg, seg_len)) + return verdict; + } + + return VERDICT_DROP; + } + break; + case DESYNC_SPLIT: + case DESYNC_SPLIT2: + if (split_pos) + { + uint8_t fakeseg[DPI_DESYNC_MAX_FAKE_LEN+100],ovlseg[DPI_DESYNC_MAX_FAKE_LEN+100], *seg; + size_t fakeseg_len,seg_len; + + if (desync_mode==DESYNC_SPLIT) + { + fakeseg_len = sizeof(fakeseg); + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, tcphdr->th_seq, tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_fake,IP4_TOS(ip),IP6_FLOW(ip6hdr), + dp->desync_fooling_mode,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + zeropkt, split_pos, fakeseg, &fakeseg_len)) + return verdict; + DLOG("sending fake(1) 1st tcp segment 0-%zu len=%zu : ",split_pos-1, split_pos); + hexdump_limited_dlog(zeropkt,split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , fakeseg, fakeseg_len)) + return verdict; + } + + if (dp->desync_seqovl) + { + seg_len = split_pos+dp->desync_seqovl; + if (seg_len>sizeof(ovlseg)) + { + DLOG("seqovl is too large"); + return verdict; + } + fill_pattern(ovlseg,dp->desync_seqovl,dp->seqovl_pattern,sizeof(dp->seqovl_pattern)); + memcpy(ovlseg+dp->desync_seqovl,data_payload,split_pos); + seg = ovlseg; + } + else + { + seg = data_payload; + seg_len = split_pos; + } + + if (!prepare_tcp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, flags_orig, net32_add(tcphdr->th_seq,-dp->desync_seqovl), tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + seg, seg_len, pkt1, &pkt1_len)) + return verdict; + DLOG("sending 1st tcp segment 0-%zu len=%zu seqovl=%u : ",split_pos-1, split_pos, dp->desync_seqovl); + hexdump_limited_dlog(seg,seg_len,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + + if (desync_mode==DESYNC_SPLIT) + { + DLOG("sending fake(2) 1st tcp segment 0-%zu len=%zu : ",split_pos-1, split_pos); + hexdump_limited_dlog(zeropkt,split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , fakeseg, fakeseg_len)) + return verdict; + } + if (split_posth_seq,split_pos), tcphdr->th_ack, tcphdr->th_win, scale_factor, timestamps, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,dp->desync_badseq_increment,dp->desync_badseq_ack_increment, + data_payload+split_pos, len_payload-split_pos, pkt1, &pkt1_len)) + return verdict; + DLOG("sending 2nd tcp segment %zu-%zu len=%zu : ",split_pos,len_payload-1, len_payload-split_pos); + hexdump_limited_dlog(data_payload+split_pos,len_payload-split_pos,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + } + + return VERDICT_DROP; + } + break; + case DESYNC_IPFRAG2: + if (!reasm_offset) + { + verdict_tcp_csum_fix(verdict, tcphdr, transport_len, ip, ip6hdr); + + uint8_t pkt3[DPI_DESYNC_MAX_FAKE_LEN+100], *pkt_orig; + size_t pkt_orig_len; + + size_t ipfrag_pos = (dp->desync_ipfrag_pos_tcp && dp->desync_ipfrag_pos_tcpdesync_ipfrag_pos_tcp : 24; + uint32_t ident = ip ? ip->ip_id ? ip->ip_id : htons(1+random()%0xFFFF) : htonl(1+random()%0xFFFFFFFF); + + pkt1_len = sizeof(pkt1); + pkt2_len = sizeof(pkt2); + + if (ip6hdr && (fooling_orig==FOOL_HOPBYHOP || fooling_orig==FOOL_DESTOPT)) + { + pkt_orig_len = sizeof(pkt3); + if (!ip6_insert_simple_hdr(fooling_orig==FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, data_pkt, *len_pkt, pkt3, &pkt_orig_len)) + return verdict; + pkt_orig = pkt3; + } + else + { + pkt_orig = data_pkt; + pkt_orig_len = *len_pkt; + } + + if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) + return verdict; + + DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos-1, ipfrag_pos); + hexdump_limited_dlog(pkt1,pkt1_len,IP_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + + DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, transport_len-1, transport_len-ipfrag_pos); + hexdump_limited_dlog(pkt2,pkt2_len,IP_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt2, pkt2_len)) + return verdict; + + return VERDICT_DROP; + } + default: + break; + } + + } + + return verdict; +} + +// return : true - should continue, false - should stop with verdict +static bool quic_reasm_cancel(t_ctrack *ctrack, const char *reason) +{ + reasm_orig_cancel(ctrack); + if (ctrack && ctrack->dp && ctrack->dp->desync_any_proto) + { + DLOG("%s. applying tampering because desync_any_proto is set\n",reason); + return true; + } + else + { + DLOG("%s. not applying tampering because desync_any_proto is not set\n",reason); + return false; + } +} + +static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt, struct ip *ip, struct ip6_hdr *ip6hdr, struct udphdr *udphdr, size_t transport_len, uint8_t *data_payload, size_t len_payload) +{ + uint8_t verdict=VERDICT_PASS; + + // additional safety check + if (!!ip == !!ip6hdr) return verdict; + + // no need to desync middle packets in reasm session + if (reasm_offset) return verdict; + + struct desync_profile *dp = NULL; + + t_ctrack *ctrack=NULL, *ctrack_replay=NULL; + bool bReverse=false; + + struct sockaddr_storage src, dst; + uint8_t pkt1[DPI_DESYNC_MAX_FAKE_LEN+100], pkt2[DPI_DESYNC_MAX_FAKE_LEN+100]; + size_t pkt1_len, pkt2_len; + uint8_t ttl_orig,ttl_fake; + t_l7proto l7proto = UNKNOWN; + + if (replay) + { + // in replay mode conntrack_replay is not NULL and ctrack is NULL + + //ConntrackPoolDump(¶ms.conntrack); + if (!ConntrackPoolDoubleSearch(¶ms.conntrack, ip, ip6hdr, NULL, udphdr, &ctrack_replay, &bReverse) || bReverse) + return verdict; + + dp = ctrack_replay->dp; + if (dp) + DLOG("using cached desync profile %d\n",dp->n); + else if (!ctrack_replay->dp_search_complete) + { + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack_replay->hostname, 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 + + ConntrackPoolPurge(¶ms.conntrack); + if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse)) + { + dp = ctrack->dp; + ctrack_replay = ctrack; + } + if (dp) + DLOG("using cached desync profile %d\n",dp->n); + else if (!ctrack || !ctrack->dp_search_complete) + { + dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack ? ctrack->hostname : NULL, NULL, NULL, NULL); + if (ctrack) + { + ctrack->dp = dp; + ctrack->dp_search_complete = true; + } + } + if (!dp) + { + DLOG("matching desync profile not found\n"); + return verdict; + } + maybe_cutoff(ctrack, IPPROTO_UDP); + + HostFailPoolPurgeRateLimited(&dp->hostlist_auto_fail_counters); + //ConntrackPoolDump(¶ms.conntrack); + } + + if (bReverse) return verdict; // nothing to do. do not waste cpu + + // start and cutoff limiters + if (!replay && !process_desync_interval(dp, ctrack)) return verdict; + + uint32_t desync_fwmark = fwmark | params.desync_fwmark; + ttl_orig = ip ? ip->ip_ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; + ttl_fake = ip6hdr ? dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig : dp->desync_ttl ? dp->desync_ttl : ttl_orig; + extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst); + + if (len_payload) + { + const uint8_t *fake; + size_t fake_size; + bool b; + char host[256]; + bool bHaveHost=false; + + if (IsQUICInitial(data_payload,len_payload)) + { + DLOG("packet contains QUIC initial\n"); + l7proto = QUIC; + if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + + uint8_t clean[16384], *pclean; + size_t clean_len; + + if (replay) + { + clean_len = ctrack_replay->reasm_orig.size_present; + pclean = ctrack_replay->reasm_orig.packet; + } + else + { + clean_len = sizeof(clean); + pclean = QUICDecryptInitial(data_payload,len_payload,clean,&clean_len) ? clean : NULL; + } + if (pclean) + { + if (ctrack && !ReasmIsEmpty(&ctrack->reasm_orig)) + { + if (ReasmHasSpace(&ctrack->reasm_orig, clean_len)) + { + reasm_orig_feed(ctrack,IPPROTO_UDP,clean,clean_len); + pclean = ctrack->reasm_orig.packet; + clean_len = ctrack->reasm_orig.size_present; + } + else + { + DLOG("QUIC reasm is too long. cancelling.\n"); + reasm_orig_cancel(ctrack); + return verdict; // cannot be first packet + } + } + + uint8_t defrag[16384]; + size_t hello_offset, hello_len, defrag_len = sizeof(defrag); + if (QUICDefragCrypto(pclean,clean_len,defrag,&defrag_len)) + { + 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 (ctrack) + { + if (bIsHello && !bReqFull && ReasmIsEmpty(&ctrack->reasm_orig)) + { + // preallocate max buffer to avoid reallocs that cause memory copy + if (!reasm_orig_start(ctrack,IPPROTO_UDP,16384,16384,clean,clean_len)) + { + reasm_orig_cancel(ctrack); + return verdict; + } + } + if (!ReasmIsEmpty(&ctrack->reasm_orig)) + { + verdict_udp_csum_fix(verdict, udphdr, transport_len, ip, ip6hdr); + if (rawpacket_queue(&ctrack->delayed, &dst, desync_fwmark, ifout, data_pkt, *len_pkt, len_payload)) + { + DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ctrack->delayed)); + } + else + { + fprintf(stderr, "rawpacket_queue failed !'\n"); + reasm_orig_cancel(ctrack); + return verdict; + } + if (bReqFull) + { + replay_queue(&ctrack->delayed); + reasm_orig_fin(ctrack); + } + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + } + } + + if (bIsHello) + { + bHaveHost = TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, sizeof(host), TLS_PARTIALS_ENABLE); + if (!bHaveHost && dp->desync_skip_nosni) + { + reasm_orig_cancel(ctrack); + DLOG("not applying tampering to QUIC ClientHello without hostname in the SNI\n"); + return verdict; + } + } + else + { + if (!quic_reasm_cancel(ctrack,"QUIC initial without ClientHello")) return verdict; + } + } + else + { + // defrag failed + if (!quic_reasm_cancel(ctrack,"QUIC initial defrag CRYPTO failed")) return verdict; + } + } + else + { + // decrypt failed + if (!quic_reasm_cancel(ctrack,"QUIC initial decryption failed")) return verdict; + } + } + else // not QUIC initial + { + // received payload without host. it means we are out of the request retransmission phase. stop counter + ctrack_stop_retrans_counter(ctrack); + + reasm_orig_cancel(ctrack); + + if (IsWireguardHandshakeInitiation(data_payload,len_payload)) + { + DLOG("packet contains wireguard handshake initiation\n"); + l7proto = WIREGUARD; + if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + } + else if (IsDhtD1(data_payload,len_payload)) + { + DLOG("packet contains DHT d1...e\n"); + l7proto = DHT; + if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + } + else + { + if (!dp->desync_any_proto) return verdict; + DLOG("applying tampering to unknown protocol\n"); + } + } + + if (bHaveHost) + { + bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; + DLOG("hostname: %s\n",host); + if (ctrack_replay) + { + if (!ctrack_replay->hostname) + { + ctrack_replay->hostname=strdup(host); + if (!ctrack_replay->hostname) + { + DLOG_ERR("hostname dup : out of memory"); + return verdict; + } + DLOG("we have hostname now. searching desync profile again.\n"); + struct desync_profile *dp_prev = dp; + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack_replay->hostname, &ctrack_replay->bCheckDone, &ctrack_replay->bCheckResult, &ctrack_replay->bCheckExcluded); + ctrack_replay->dp_search_complete = true; + if (!dp) return verdict; + if (dp!=dp_prev) + { + DLOG("desync profile changed by reavealed hostname !\n"); + // re-evaluate start/cutoff limiters + if (!replay) + { + maybe_cutoff(ctrack, IPPROTO_UDP); + if (!process_desync_interval(dp, ctrack)) return verdict; + } + } + } + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + if (dp->hostlist || dp->hostlist_exclude) + { + bool bCheckExcluded; + if (!bCheckDone) + bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); + if (!bCheckResult) + { + if (ctrack_replay) + { + ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; + if (ctrack_replay->hostname_ah_check) + { + // first request is not retrans + if (ctrack_replay->hostname) + process_retrans_fail(ctrack_replay, IPPROTO_UDP); + else + ctrack_replay->hostname=strdup(host); + } + } + DLOG("not applying tampering to this request\n"); + return verdict; + } + } + } + + // desync profile may have changed after hostname was revealed + switch(l7proto) + { + case QUIC: + fake = dp->fake_quic; + fake_size = dp->fake_quic_size; + break; + case WIREGUARD: + fake = dp->fake_wg; + fake_size = dp->fake_wg_size; + break; + case DHT: + fake = dp->fake_dht; + fake_size = dp->fake_dht_size; + break; + default: + fake = dp->fake_unknown_udp; + fake_size = dp->fake_unknown_udp_size; + break; + } + ttl_fake = ip6hdr ? dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig : dp->desync_ttl ? dp->desync_ttl : ttl_orig; + + enum dpi_desync_mode desync_mode = dp->desync_mode; + uint32_t fooling_orig = FOOL_NONE; + + if (params.debug) + { + char s1[48],s2[48]; + ntop46_port((struct sockaddr *)&src, s1, sizeof(s1)); + ntop46_port((struct sockaddr *)&dst, s2, sizeof(s2)); + DLOG("dpi desync src=%s dst=%s\n",s1,s2); + } + + pkt1_len = sizeof(pkt1); + b = false; + switch(desync_mode) + { + case DESYNC_FAKE_KNOWN: + if (l7proto==UNKNOWN) + { + DLOG("not applying fake because of unknown protocol\n"); + desync_mode = dp->desync_mode2; + break; + } + case DESYNC_FAKE: + if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, ttl_fake, IP4_TOS(ip),IP6_FLOW(ip6hdr), dp->desync_fooling_mode, NULL, 0, 0, fake, fake_size, pkt1, &pkt1_len)) + return verdict; + DLOG("sending fake request : "); + hexdump_limited_dlog(fake,fake_size,PKTDATA_MAXDUMP); DLOG("\n"); + if (!rawsend_rep(dp->desync_repeats,(struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + b = true; + break; + case DESYNC_HOPBYHOP: + case DESYNC_DESTOPT: + case DESYNC_IPFRAG1: + fooling_orig = (desync_mode==DESYNC_HOPBYHOP) ? FOOL_HOPBYHOP : (desync_mode==DESYNC_DESTOPT) ? FOOL_DESTOPT : FOOL_IPFRAG1; + if (ip6hdr && (dp->desync_mode2==DESYNC_NONE || !desync_valid_second_stage_udp(dp->desync_mode2))) + { + if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, + ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), + fooling_orig,NULL,0,0, + data_payload, len_payload, pkt1, &pkt1_len)) + { + return verdict; + } + DLOG("resending original packet with extension header\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + // this mode is final, no other options available + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + } + desync_mode = dp->desync_mode2; + break; + default: + pkt1_len=0; + break; + } + + if (b) + { + if (dp->desync_mode2==DESYNC_NONE || !desync_valid_second_stage_udp(dp->desync_mode2)) + { + DLOG("reinjecting original packet. len=%zu len_payload=%zu\n", *len_pkt, len_payload); + verdict_udp_csum_fix(verdict, udphdr, transport_len, ip, ip6hdr); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , data_pkt, *len_pkt)) + return verdict; + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + } + desync_mode = dp->desync_mode2; + } + + switch(desync_mode) + { + case DESYNC_UDPLEN: + pkt1_len = sizeof(pkt1); + if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), fooling_orig, dp->udplen_pattern, sizeof(dp->udplen_pattern), dp->udplen_increment, data_payload, len_payload, pkt1, &pkt1_len)) + { + DLOG("could not construct packet with modified length. too large ?\n"); + return verdict; + } + DLOG("resending original packet with increased by %d length\n", dp->udplen_increment); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + case DESYNC_TAMPER: + if (IsDhtD1(data_payload,len_payload)) + { + size_t szbuf,szcopy; + memcpy(pkt2,"d2:001:x",8); + pkt2_len=8; + szbuf=sizeof(pkt2)-pkt2_len; + szcopy=len_payload-1; + if (szcopy>szbuf) + { + DLOG("packet is too long to tamper"); + return verdict; + } + memcpy(pkt2+pkt2_len,data_payload+1,szcopy); + pkt2_len+=szcopy; + pkt1_len = sizeof(pkt1); + if (!prepare_udp_segment((struct sockaddr *)&src, (struct sockaddr *)&dst, ttl_orig,IP4_TOS(ip),IP6_FLOW(ip6hdr), fooling_orig, NULL, 0 , 0, pkt2, pkt2_len, pkt1, &pkt1_len)) + { + DLOG("could not construct packet with modified length. too large ?\n"); + return verdict; + } + DLOG("resending tampered DHT\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + } + else + { + DLOG("payload is not tamperable\n"); + return verdict; + } + case DESYNC_IPFRAG2: + { + verdict_udp_csum_fix(verdict, udphdr, transport_len, ip, ip6hdr); + + uint8_t pkt3[DPI_DESYNC_MAX_FAKE_LEN+100], *pkt_orig; + size_t pkt_orig_len; + + size_t ipfrag_pos = (dp->desync_ipfrag_pos_udp && dp->desync_ipfrag_pos_udpdesync_ipfrag_pos_udp : sizeof(struct udphdr); + // freebsd do not set ip.id + uint32_t ident = ip ? ip->ip_id ? ip->ip_id : htons(1+random()%0xFFFF) : htonl(1+random()%0xFFFFFFFF); + + pkt1_len = sizeof(pkt1); + pkt2_len = sizeof(pkt2); + + if (ip6hdr && (fooling_orig==FOOL_HOPBYHOP || fooling_orig==FOOL_DESTOPT)) + { + pkt_orig_len = sizeof(pkt3); + if (!ip6_insert_simple_hdr(fooling_orig==FOOL_HOPBYHOP ? IPPROTO_HOPOPTS : IPPROTO_DSTOPTS, data_pkt, *len_pkt, pkt3, &pkt_orig_len)) + return verdict; + pkt_orig = pkt3; + } + else + { + pkt_orig = data_pkt; + pkt_orig_len = *len_pkt; + } + + if (!ip_frag(pkt_orig, pkt_orig_len, ipfrag_pos, ident, pkt1, &pkt1_len, pkt2, &pkt2_len)) + return verdict; + + DLOG("sending 1st ip fragment 0-%zu ip_payload_len=%zu : ", ipfrag_pos-1, ipfrag_pos); + hexdump_limited_dlog(pkt1,pkt1_len,IP_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt1, pkt1_len)) + return verdict; + + DLOG("sending 2nd ip fragment %zu-%zu ip_payload_len=%zu : ", ipfrag_pos, transport_len-1, transport_len-ipfrag_pos); + hexdump_limited_dlog(pkt2,pkt2_len,IP_MAXDUMP); DLOG("\n"); + if (!rawsend((struct sockaddr *)&dst, desync_fwmark, ifout , pkt2, pkt2_len)) + return verdict; + + return ct_new_postnat_fix_udp(ctrack, ip, ip6hdr, udphdr, len_pkt); + } + default: + break; + } + + } + + return verdict; +} + + +static void packet_debug(bool replay, uint8_t proto, const struct ip *ip, const struct ip6_hdr *ip6hdr, const struct tcphdr *tcphdr, const struct udphdr *udphdr, const uint8_t *data_payload, size_t len_payload) +{ + if (params.debug) + { + if (replay) DLOG("REPLAY "); + if (ip) + { + char s[66]; + str_ip(s,sizeof(s),ip); + DLOG("IP4: %s",s); + } + else if (ip6hdr) + { + char s[128]; + str_ip6hdr(s,sizeof(s),ip6hdr, proto); + DLOG("IP6: %s",s); + } + if (tcphdr) + { + char s[80]; + str_tcphdr(s,sizeof(s),tcphdr); + DLOG(" %s\n",s); + if (len_payload) { DLOG("TCP: "); hexdump_limited_dlog(data_payload, len_payload, 32); DLOG("\n"); } + + } + else if (udphdr) + { + char s[30]; + str_udphdr(s,sizeof(s),udphdr); + DLOG(" %s\n",s); + if (len_payload) { DLOG("UDP: "); hexdump_limited_dlog(data_payload, len_payload, 32); DLOG("\n"); } + } + else + DLOG("\n"); + } +} + + +static uint8_t dpi_desync_packet_play(bool replay, size_t reasm_offset, uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) +{ + struct ip *ip; + struct ip6_hdr *ip6hdr; + struct tcphdr *tcphdr; + struct udphdr *udphdr; + size_t transport_len; + uint8_t *data_payload,proto; + size_t len_payload; + uint8_t verdict = VERDICT_PASS; + + proto_dissect_l3l4(data_pkt,*len_pkt,&ip,&ip6hdr,&proto,&tcphdr,&udphdr,&transport_len,&data_payload,&len_payload); + if (!!ip != !!ip6hdr) + { + packet_debug(replay, proto, ip, ip6hdr, tcphdr, udphdr, data_payload, len_payload); + switch(proto) + { + case IPPROTO_TCP: + if (tcphdr) + { + verdict = dpi_desync_tcp_packet_play(replay, reasm_offset, fwmark, ifout, data_pkt, len_pkt, ip, ip6hdr, tcphdr, transport_len, data_payload, len_payload); + verdict_tcp_csum_fix(verdict, tcphdr, transport_len, ip, ip6hdr); + } + break; + case IPPROTO_UDP: + if (udphdr) + { + verdict = dpi_desync_udp_packet_play(replay, reasm_offset, fwmark, ifout, data_pkt, len_pkt, ip, ip6hdr, udphdr, transport_len, data_payload, len_payload); + verdict_udp_csum_fix(verdict, udphdr, transport_len, ip, ip6hdr); + } + break; + } + } + return verdict; +} +uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) +{ + return dpi_desync_packet_play(false, 0, fwmark, ifout, data_pkt, len_pkt); +} + + + +static bool replay_queue(struct rawpacket_tailhead *q) +{ + struct rawpacket *rp; + size_t offset; + unsigned int i; + bool b = true; + for (i=1,offset=0 ; (rp=rawpacket_dequeue(q)) ; offset+=rp->len_payload, rawpacket_free(rp), i++) + { + DLOG("REPLAYING delayed packet #%u offset %zu\n",i,offset); + uint8_t verdict = dpi_desync_packet_play(true, offset, rp->fwmark, rp->ifout, rp->packet, &rp->len); + switch(verdict & VERDICT_MASK) + { + case VERDICT_MODIFY: + DLOG("SENDING delayed packet #%u modified\n", i); + b &= rawsend_rp(rp); + break; + case VERDICT_PASS: + DLOG("SENDING delayed packet #%u unmodified\n", i); + b &= rawsend_rp(rp); + break; + case VERDICT_DROP: + DLOG("DROPPING delayed packet #%u\n", i); + break; + } + } + return b; +} diff --git a/nfq/desync.h b/nfq/desync.h new file mode 100644 index 00000000..4aa42aac --- /dev/null +++ b/nfq/desync.h @@ -0,0 +1,56 @@ +#pragma once + +#include "darkmagic.h" + +#include +#include + +#define __FAVOR_BSD +#include +#include +#include +#include + +#ifdef __linux__ +#define DPI_DESYNC_FWMARK_DEFAULT 0x40000000 +#else +#define DPI_DESYNC_FWMARK_DEFAULT 512 +#endif + +#define DPI_DESYNC_MAX_FAKE_LEN 9216 + +enum dpi_desync_mode { + DESYNC_NONE=0, + DESYNC_INVALID, + DESYNC_FAKE, + DESYNC_FAKE_KNOWN, + DESYNC_RST, + DESYNC_RSTACK, + DESYNC_SYNACK, + DESYNC_SYNDATA, + DESYNC_DISORDER, + DESYNC_DISORDER2, + DESYNC_SPLIT, + DESYNC_SPLIT2, + DESYNC_IPFRAG2, + DESYNC_HOPBYHOP, + DESYNC_DESTOPT, + DESYNC_IPFRAG1, + DESYNC_UDPLEN, + DESYNC_TAMPER +}; + +extern const char *fake_http_request_default; +extern const uint8_t fake_tls_clienthello_default[648]; +void randomize_default_tls_payload(uint8_t *p); + +enum dpi_desync_mode desync_mode_from_string(const char *s); +bool desync_valid_zero_stage(enum dpi_desync_mode mode); +bool desync_valid_first_stage(enum dpi_desync_mode mode); +bool desync_only_first_stage(enum dpi_desync_mode mode); +bool desync_valid_second_stage(enum dpi_desync_mode mode); +bool desync_valid_second_stage_tcp(enum dpi_desync_mode mode); +bool desync_valid_second_stage_udp(enum dpi_desync_mode mode); + +void desync_init(void); +uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt); diff --git a/nfq/gzip.c b/nfq/gzip.c new file mode 100644 index 00000000..cb46670a --- /dev/null +++ b/nfq/gzip.c @@ -0,0 +1,82 @@ +#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); + if (*buf) + { + 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/nfq/gzip.h b/nfq/gzip.h new file mode 100644 index 00000000..15e30d2e --- /dev/null +++ b/nfq/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/nfq/helpers.c b/nfq/helpers.c new file mode 100644 index 00000000..01d0558e --- /dev/null +++ b/nfq/helpers.c @@ -0,0 +1,387 @@ +#define _GNU_SOURCE + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include + +#include "params.h" + +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(" ..."); +} + +char *strncasestr(const char *s, const char *find, size_t slen) +{ + char c, sc; + size_t len; + + if ((c = *find++) != '\0') + { + len = strlen(find); + do + { + do + { + if (slen-- < 1 || (sc = *s++) == '\0') return NULL; + } while (toupper(c) != toupper(sc)); + if (len > slen) return NULL; + } while (strncasecmp(s, find, len) != 0); + s--; + } + return (char *)s; +} + +bool load_file(const char *filename, void *buffer, size_t *buffer_size) +{ + FILE *F; + + F = fopen(filename, "rb"); + if (!F) return false; + + *buffer_size = fread(buffer, 1, *buffer_size, F); + if (ferror(F)) + { + fclose(F); + return false; + } + + fclose(F); + return true; +} +bool load_file_nonempty(const char *filename, void *buffer, size_t *buffer_size) +{ + bool b = load_file(filename, buffer, buffer_size); + return b && *buffer_size; +} +bool save_file(const char *filename, const void *buffer, size_t buffer_size) +{ + FILE *F; + + F = fopen(filename, "wb"); + if (!F) return false; + + fwrite(buffer, 1, buffer_size, F); + if (ferror(F)) + { + fclose(F); + return false; + } + + fclose(F); + return true; +} +bool append_to_list_file(const char *filename, const char *s) +{ + FILE *F = fopen(filename,"at"); + if (!F) return false; + bool bOK = fprintf(F,"%s\n",s)>0; + fclose(F); + return bOK; +} + + +void ntop46(const struct sockaddr *sa, char *str, size_t len) +{ + if (!len) return; + *str = 0; + switch (sa->sa_family) + { + case AF_INET: + inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); + break; + case AF_INET6: + inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); + break; + default: + snprintf(str, len, "UNKNOWN_FAMILY_%d", sa->sa_family); + } +} +void ntop46_port(const struct sockaddr *sa, char *str, size_t len) +{ + char ip[40]; + ntop46(sa, ip, sizeof(ip)); + switch (sa->sa_family) + { + case AF_INET: + snprintf(str, len, "%s:%u", ip, ntohs(((struct sockaddr_in*)sa)->sin_port)); + break; + case AF_INET6: + snprintf(str, len, "[%s]:%u", ip, ntohs(((struct sockaddr_in6*)sa)->sin6_port)); + break; + default: + snprintf(str, len, "%s", ip); + } +} +void print_sockaddr(const struct sockaddr *sa) +{ + char ip_port[48]; + + ntop46_port(sa, ip_port, sizeof(ip_port)); + printf("%s", ip_port); +} + +bool pton4_port(const char *s, struct sockaddr_in *sa) +{ + char ip[16],*p; + size_t l; + unsigned int u; + + p = strchr(s,':'); + if (!p) return false; + l = p-s; + if (l<7 || l>15) return false; + memcpy(ip,s,l); + ip[l]=0; + p++; + + sa->sin_family = AF_INET; + if (inet_pton(AF_INET,ip,&sa->sin_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; + sa->sin_port = htons((uint16_t)u); + + return true; +} +bool pton6_port(const char *s, struct sockaddr_in6 *sa) +{ + char ip[40],*p; + size_t l; + unsigned int u; + + if (*s++!='[') return false; + p = strchr(s,']'); + if (!p || p[1]!=':') return false; + l = p-s; + if (l<2 || l>39) return false; + p+=2; + memcpy(ip,s,l); + ip[l]=0; + + sa->sin6_family = AF_INET6; + if (inet_pton(AF_INET6,ip,&sa->sin6_addr)!=1 || sscanf(p,"%u",&u)!=1 || !u || u>0xFFFF) return false; + sa->sin6_port = htons((uint16_t)u); + sa->sin6_flowinfo = 0; + sa->sin6_scope_id = 0; + + return true; +} + + +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)"); + close(fd); + return false; + } + if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0) + { + DLOG_PERROR("setsockopt (SO_SNDBUF)"); + close(fd); + return false; + } + dbgprint_socket_buffers(fd); + return true; +} + +uint64_t pntoh64(const void *p) +{ + return (uint64_t)*((const uint8_t *)(p)+0) << 56 | + (uint64_t)*((const uint8_t *)(p)+1) << 48 | + (uint64_t)*((const uint8_t *)(p)+2) << 40 | + (uint64_t)*((const uint8_t *)(p)+3) << 32 | + (uint64_t)*((const uint8_t *)(p)+4) << 24 | + (uint64_t)*((const uint8_t *)(p)+5) << 16 | + (uint64_t)*((const uint8_t *)(p)+6) << 8 | + (uint64_t)*((const uint8_t *)(p)+7) << 0; +} +void phton64(uint8_t *p, uint64_t v) +{ + p[0] = (uint8_t)(v >> 56); + p[1] = (uint8_t)(v >> 48); + p[2] = (uint8_t)(v >> 40); + p[3] = (uint8_t)(v >> 32); + p[4] = (uint8_t)(v >> 24); + p[5] = (uint8_t)(v >> 16); + p[6] = (uint8_t)(v >> 8); + p[7] = (uint8_t)(v >> 0); +} + +bool seq_within(uint32_t s, uint32_t s1, uint32_t s2) +{ + return (s2>=s1 && s>=s1 && s<=s2) || (s2=s1)); +} + +bool ipv6_addr_is_zero(const struct in6_addr *a) +{ + return !memcmp(a,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",16); +} + + +#define INVALID_HEX_DIGIT ((uint8_t)-1) +static inline uint8_t parse_hex_digit(char c) +{ + return (c>='0' && c<='9') ? c-'0' : (c>='a' && c<='f') ? c-'a'+0xA : (c>='A' && c<='F') ? c-'A'+0xA : INVALID_HEX_DIGIT; +} +static inline bool parse_hex_byte(const char *s, uint8_t *pbyte) +{ + uint8_t u,l; + u = parse_hex_digit(s[0]); + l = parse_hex_digit(s[1]); + if (u==INVALID_HEX_DIGIT || l==INVALID_HEX_DIGIT) + { + *pbyte=0; + return false; + } + else + { + *pbyte=(u<<4) | l; + return true; + } +} +bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size) +{ + uint8_t *pe = pbuf+*size; + *size=0; + while(pbufpatsize ? patsize : bufsize; + memcpy(buf,pattern,size); + buf += size; + bufsize -= size; + } +} + +int fprint_localtime(FILE *F) +{ + struct tm t; + time_t now; + + time(&now); + localtime_r(&now,&t); + return fprintf(F, "%02d.%02d.%04d %02d:%02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); +} + +time_t file_mod_time(const char *filename) +{ + struct stat st; + return stat(filename,&st)==-1 ? 0 : st.st_mtime; +} + +bool 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=='~') + { + pf->neg=true; + s++; + } + else + pf->neg=false; + if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2) + { + if (v1>65535 || v2>65535 || v1>v2) return false; + pf->from=(uint16_t)v1; + pf->to=(uint16_t)v2; + } + else if (sscanf(s,"%u%c",&v1,&c)==1) + { + if (v1>65535) return false; + pf->to=pf->from=(uint16_t)v1; + } + else + return false; + // deny all case + if (!pf->from && !pf->to) pf->neg=true; + return true; +} +bool pf_is_empty(const port_filter *pf) +{ + return !pf->neg && !pf->from && !pf->to; +} + +void fill_random_bytes(uint8_t *p,size_t sz) +{ + size_t k,sz16 = sz>>1; + for(k=0;k +#include +#include +#include +#include +#include +#include +#include + +void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); +char *strncasestr(const char *s,const char *find, size_t slen); +bool load_file(const char *filename,void *buffer,size_t *buffer_size); +bool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size); +bool save_file(const char *filename, const void *buffer, size_t buffer_size); +bool append_to_list_file(const char *filename, const char *s); + +void print_sockaddr(const struct sockaddr *sa); +void ntop46(const struct sockaddr *sa, char *str, size_t len); +void ntop46_port(const struct sockaddr *sa, char *str, size_t len); +bool pton4_port(const char *s, struct sockaddr_in *sa); +bool pton6_port(const char *s, struct sockaddr_in6 *sa); + +bool seq_within(uint32_t s, uint32_t s1, uint32_t s2); + +void dbgprint_socket_buffers(int fd); +bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); + +uint64_t pntoh64(const void *p); +void phton64(uint8_t *p, uint64_t v); + +bool ipv6_addr_is_zero(const struct in6_addr *a); + +static inline uint16_t pntoh16(const uint8_t *p) { + return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; +} +static inline void phton16(uint8_t *p, uint16_t v) { + p[0] = (uint8_t)(v >> 8); + p[1] = v & 0xFF; +} +static inline uint32_t pntoh32(const uint8_t *p) { + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} + +bool parse_hex_str(const char *s, uint8_t *pbuf, size_t *size); +void fill_pattern(uint8_t *buf,size_t bufsize,const void *pattern,size_t patsize); + +int fprint_localtime(FILE *F); + +time_t file_mod_time(const char *filename); + +typedef struct +{ + uint16_t from,to; + bool neg; +} port_filter; +bool pf_in_range(uint16_t port, const port_filter *pf); +bool pf_parse(const char *s, port_filter *pf); +bool pf_is_empty(const port_filter *pf); + +void fill_random_bytes(uint8_t *p,size_t sz); +void fill_random_az(uint8_t *p,size_t sz); +void fill_random_az09(uint8_t *p,size_t sz); + +bool cd_to_exe_dir(const char *argv0); diff --git a/nfq/hostlist.c b/nfq/hostlist.c new file mode 100644 index 00000000..03ed441b --- /dev/null +++ b/nfq/hostlist.c @@ -0,0 +1,205 @@ +#include +#include "hostlist.h" +#include "gzip.h" +#include "helpers.h" + +// inplace tolower() and add to pool +static bool addpool(strpool **hostlist, char **s, const char *end) +{ + char *p; + + // advance until eol lowering all chars + for (p = *s; pstr)) return false; + } + return true; +} + +bool NonEmptyHostlist(strpool **hostlist) +{ + // add impossible hostname if the list is empty + return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4); +} + + +bool SearchHostList(strpool *hostlist, const char *host) +{ + if (hostlist) + { + const char *p = host; + bool bInHostList; + while (p) + { + bInHostList = StrPoolCheckStr(hostlist, p); + DLOG("Hostlist check for %s : %s\n", p, bInHostList ? "positive" : "negative"); + if (bInHostList) return true; + p = strchr(p, '.'); + if (p) p++; + } + } + return false; +} + +// return : true = apply fooling, false = do not apply +static bool HostlistCheck_(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded) +{ + if (excluded) *excluded = false; + if (hostlist_exclude) + { + DLOG("Checking exclude hostlist\n"); + if (SearchHostList(hostlist_exclude, host)) + { + if (excluded) *excluded = true; + return false; + } + } + if (hostlist) + { + DLOG("Checking include hostlist\n"); + return SearchHostList(hostlist, host); + } + return true; +} + +static bool LoadIncludeHostListsForProfile(struct desync_profile *dp) +{ + if (!LoadHostLists(&dp->hostlist, &dp->hostlist_files)) + return false; + if (*dp->hostlist_auto_filename) + { + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + NonEmptyHostlist(&dp->hostlist); + } + return true; +} + +// return : true = apply fooling, false = do not apply +bool HostlistCheck(struct desync_profile *dp, const char *host, bool *excluded) +{ + DLOG("* Hostlist check for profile %d\n",dp->n); + if (*dp->hostlist_auto_filename) + { + time_t t = file_mod_time(dp->hostlist_auto_filename); + if (t!=dp->hostlist_auto_mod_time) + { + DLOG_CONDUP("Autohostlist '%s' from profile %d was modified. Reloading include hostlists for this profile.\n",dp->hostlist_auto_filename, dp->n); + if (!LoadIncludeHostListsForProfile(dp)) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + dp->hostlist_auto_mod_time = t; + NonEmptyHostlist(&dp->hostlist); + } + } + return HostlistCheck_(dp->hostlist, dp->hostlist_exclude, host, excluded); +} + +bool LoadIncludeHostLists() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadIncludeHostListsForProfile(&dpl->dp)) + return false; + return true; +} +bool LoadExcludeHostLists() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadHostLists(&dpl->dp.hostlist_exclude, &dpl->dp.hostlist_exclude_files)) + return false; + return true; +} diff --git a/nfq/hostlist.h b/nfq/hostlist.h new file mode 100644 index 00000000..0bdb94ab --- /dev/null +++ b/nfq/hostlist.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "pools.h" +#include "params.h" + +bool AppendHostList(strpool **hostlist, char *filename); +bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list); +bool LoadIncludeHostLists(); +bool LoadExcludeHostLists(); +bool NonEmptyHostlist(strpool **hostlist); +bool SearchHostList(strpool *hostlist, const char *host); +// return : true = apply fooling, false = do not apply +bool HostlistCheck(struct desync_profile *dp,const char *host, bool *excluded); \ No newline at end of file diff --git a/nfq/nfqws.c b/nfq/nfqws.c new file mode 100644 index 00000000..7fea2b3c --- /dev/null +++ b/nfq/nfqws.c @@ -0,0 +1,1831 @@ +#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 "gzip.h" +#include "pools.h" + +#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 __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 + +struct params_s params; +#ifdef __CYGWIN__ +bool bQuit=false; +#endif + +static bool bHup = false; +static void onhup(int sig) +{ + printf("HUP received !\n"); + printf("Will reload hostlist on next request (if any)\n"); + bHup = true; +} +// should be called in normal execution +static void dohup(void) +{ + if (bHup) + { + if (!LoadIncludeHostLists() || !LoadExcludeHostLists()) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + bHup = false; + } +} + +static void onusr1(int sig) +{ + printf("\nCONNTRACK DUMP\n"); + ConntrackPoolDump(¶ms.conntrack); + printf("\n"); +} +static void onusr2(int sig) +{ + printf("\nHOSTFAIL POOL DUMP\n"); + + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + printf("\nDESYNC PROFILE %d\n",dpl->dp.n); + HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); + } + + printf("\n"); +} + +static void pre_desync(void) +{ + signal(SIGHUP, onhup); + signal(SIGUSR1, onusr1); + signal(SIGUSR2, onusr2); + + desync_init(); +} + + +static uint8_t processPacketData(uint32_t *mark, const char *ifout, uint8_t *data_pkt, size_t *len_pkt) +{ +#ifdef __linux__ + if (*mark & params.desync_fwmark) + { + DLOG("ignoring generated packet\n"); + return VERDICT_PASS; + } +#endif + return dpi_desync_packet(*mark, ifout, data_pkt, len_pkt); +} + + +#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; + char ifout[IFNAMSIZ+1]; + + 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); + + *ifout=0; + if (params.bind_fix4 || params.bind_fix6) + { + char ifin[IFNAMSIZ+1]; + uint32_t ifidx_in; + + ifidx = nfq_get_outdev(nfa); + if (ifidx) if_indextoname(ifidx,ifout); + *ifin=0; + ifidx_in = nfq_get_indev(nfa); + if (ifidx_in) if_indextoname(ifidx_in,ifin); + + DLOG("packet: id=%d len=%d mark=%08X ifin=%s(%u) ifout=%s(%u)\n", id, ilen, mark, ifin, ifidx_in, ifout, ifidx); + } + else + // save some syscalls + DLOG("packet: id=%d len=%d mark=%08X\n", id, ilen, mark); + if (ilen >= 0) + { + len = ilen; + uint8_t verdict = processPacketData(&mark, ifout, data, &len); + switch(verdict & VERDICT_MASK) + { + case VERDICT_MODIFY: + DLOG("packet: id=%d pass modified. len=%zu\n", id, len); + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)len, data); + case VERDICT_DROP: + DLOG("packet: id=%d drop\n", id); + return nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL); + } + } + DLOG("packet: id=%d pass unmodified\n", id); + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); +} +static int nfq_main(void) +{ + struct nfq_handle *h = NULL; + struct nfq_q_handle *qh = NULL; + int fd,rv; + uint8_t buf[16384] __attribute__((aligned)); + + DLOG_CONDUP("opening library handle\n"); + h = nfq_open(); + if (!h) { + DLOG_PERROR("nfq_open()"); + goto exiterr; + } + + DLOG_CONDUP("unbinding existing nf_queue handler for AF_INET (if any)\n"); + if (nfq_unbind_pf(h, AF_INET) < 0) { + DLOG_PERROR("nfq_unbind_pf()"); + goto exiterr; + } + + DLOG_CONDUP("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); + if (nfq_bind_pf(h, AF_INET) < 0) { + DLOG_PERROR("nfq_bind_pf()"); + goto exiterr; + } + + DLOG_CONDUP("binding this socket to queue '%u'\n", params.qnum); + qh = nfq_create_queue(h, params.qnum, &nfq_cb, ¶ms); + if (!qh) { + DLOG_PERROR("nfq_create_queue()"); + goto exiterr; + } + + DLOG_CONDUP("setting copy_packet mode\n"); + if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { + DLOG_PERROR("can't set packet_copy mode"); + goto exiterr; + } + if (nfq_set_queue_maxlen(qh, Q_MAXLEN) < 0) { + DLOG_PERROR("can't set queue maxlen"); + goto exiterr; + } + // accept packets if they cant be handled + if (nfq_set_queue_flags(qh, NFQA_CFG_F_FAIL_OPEN , NFQA_CFG_F_FAIL_OPEN)) + { + DLOG_ERR("can't set queue flags. its OK on linux <3.6\n"); + // dot not fail. not supported on old linuxes <3.6 + } + + DLOG_CONDUP("initializing raw sockets bind-fix4=%u bind-fix6=%u\n",params.bind_fix4,params.bind_fix6); + if (!rawsend_preinit(params.bind_fix4,params.bind_fix6)) + goto exiterr; + +#ifndef __CYGWIN__ + sec_harden(); + + if (params.droproot && !droproot(params.uid, params.gid)) + goto exiterr; + + print_id(); +#endif + + pre_desync(); + + fd = nfq_fd(h); + + // increase socket buffer size. on slow systems reloading hostlist can take a while. + // if too many unhandled packets are received its possible to get "no buffer space available" error + if (!set_socket_buffers(fd,Q_RCVBUF/2,Q_SNDBUF/2)) + goto exiterr; + do + { + while ((rv = recv(fd, buf, sizeof(buf), 0)) > 0) + { + dohup(); + int r = nfq_handle_packet(h, (char *)buf, rv); + if (r) DLOG_ERR("nfq_handle_packet error %d\n", r); + } + DLOG_ERR("recv: errno %d\n",errno); + DLOG_PERROR("recv"); + // do not fail on ENOBUFS + } while(errno==ENOBUFS); + + DLOG_CONDUP("unbinding from queue %u\n", params.qnum); + nfq_destroy_queue(qh); + +#ifdef INSANE + /* normally, applications SHOULD NOT issue this command, since + * it detaches other programs/sockets from AF_INET, too ! */ + DLOG_CONDUP("unbinding from AF_INET\n"); + nfq_unbind_pf(h, AF_INET); +#endif + + DLOG_CONDUP("closing library handle\n"); + nfq_close(h); + return 0; + +exiterr: + if (qh) nfq_destroy_queue(qh); + if (h) nfq_close(h); + return 1; +} + +#elif defined(BSD) + +static int dvt_main(void) +{ + uint8_t buf[16384] __attribute__((aligned)); + struct sockaddr_storage sa_from; + int fd[2] = {-1,-1}; // 4,6 + int i,r,res=1,fdct=1,fdmax; + unsigned int id=0; + socklen_t socklen; + ssize_t rd,wr; + fd_set fdset; + + { + 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; + } + if (!set_socket_buffers(fd[0],Q_RCVBUF,Q_SNDBUF)) + 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++; + if (!set_socket_buffers(fd[1],Q_RCVBUF,Q_SNDBUF)) + goto exiterr; + } +#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.gid)) + goto exiterr; + print_id(); + + pre_desync(); + + for(;;) + { + FD_ZERO(&fdset); + for(i=0;i0) + { + uint32_t mark=0; + uint8_t verdict; + size_t len = rd; + + DLOG("packet: id=%u len=%zu\n", id, len); + verdict = processPacketData(&mark, NULL, buf, &len); + switch (verdict & VERDICT_MASK) + { + case VERDICT_PASS: + case VERDICT_MODIFY: + if ((verdict & VERDICT_MASK)==VERDICT_PASS) + DLOG("packet: id=%u reinject unmodified\n", id); + else + DLOG("packet: id=%u reinject modified len=%zu\n", id, len); + wr = sendto(fd[i], buf, len, 0, (struct sockaddr*)&sa_from, socklen); + if (wr<0) + DLOG_PERROR("reinject sendto"); + else if (wr!=len) + DLOG_ERR("reinject sendto: not all data was reinjected. received %zu, sent %zd\n", len, wr); + break; + default: + DLOG("packet: id=%u drop\n", id); + } + id++; + } + else + { + DLOG("unexpected zero size recvfrom\n"); + } + } + } + } + + res=0; +exiterr: + if (fd[0]!=-1) close(fd[0]); + if (fd[1]!=-1) close(fd[1]); + return res; +} + + +#elif defined (__CYGWIN__) + +static int win_main(const char *windivert_filter) +{ + size_t len; + unsigned int id; + uint8_t verdict; + bool bOutbound; + uint8_t packet[16384]; + uint32_t mark; + WINDIVERT_ADDRESS wa; + char ifout[22]; + + pre_desync(); + + if (!win_dark_init(¶ms.ssid_filter, ¶ms.nlm_filter)) + { + DLOG_ERR("win_dark_init failed. win32 error %u (0x%08X)\n", w_win32_error, w_win32_error); + return w_win32_error; + } + + for(;;) + { + if (!logical_net_filter_match()) + { + DLOG_CONDUP("logical network is not present. waiting it to appear.\n"); + fflush(stdout); + do + { + if (bQuit) + { + DLOG("QUIT requested\n"); + win_dark_deinit(); + return 0; + } + usleep(500000); + } + while (!logical_net_filter_match()); + DLOG_CONDUP("logical network now present\n"); + fflush(stdout); + } + + if (!windivert_init(windivert_filter)) + { + win_dark_deinit(); + return w_win32_error; + } + + DLOG_CONDUP("windivert initialized. capture is started.\n"); + + // cygwin auto flush fails when piping + fflush(stdout); + fflush(stderr); + + for (id=0;;id++) + { + len = sizeof(packet); + if (!windivert_recv(packet, &len, &wa)) + { + if (errno==ENOBUFS) + { + DLOG("windivert: ignoring too large packet\n"); + continue; // too large packet + } + else if (errno==ENODEV) + { + DLOG_CONDUP("logical network disappeared. deinitializing windivert.\n"); + rawsend_cleanup(); + break; + } + else if (errno==EINTR) + { + DLOG("QUIT requested\n"); + win_dark_deinit(); + return 0; + } + DLOG_ERR("windivert: recv failed. errno %d\n", errno); + win_dark_deinit(); + return w_win32_error; + } + *ifout=0; + if (wa.Outbound) snprintf(ifout,sizeof(ifout),"%u.%u", wa.Network.IfIdx, wa.Network.SubIfIdx); + DLOG("packet: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\n", id, len, wa.Outbound ? "outbound" : "inbound", wa.IPv6, wa.IPChecksum, wa.TCPChecksum, wa.UDPChecksum, wa.Network.IfIdx, wa.Network.SubIfIdx); + if (wa.Impostor) + { + DLOG("windivert: passing impostor packet\n"); + verdict = VERDICT_PASS; + } + else if (wa.Loopback) + { + DLOG("windivert: passing loopback packet\n"); + verdict = VERDICT_PASS; + } + else + { + mark=0; + // pseudo interface id IfIdx.SubIfIdx + verdict = processPacketData(&mark, ifout, packet, &len); + } + switch (verdict & VERDICT_MASK) + { + case VERDICT_PASS: + case VERDICT_MODIFY: + if ((verdict & VERDICT_MASK)==VERDICT_PASS) + DLOG("packet: id=%u reinject unmodified\n", id); + else + DLOG("packet: id=%u reinject modified len=%zu\n", id, len); + if (!windivert_send(packet, len, &wa)) + DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); + break; + default: + DLOG("packet: id=%u drop\n", id); + } + + // cygwin auto flush fails when piping + fflush(stdout); + fflush(stderr); + } + } + win_dark_deinit(); + return 0; +} + +#endif // multiple OS divert handlers + + + +static bool parse_ws_scale_factor(char *s, uint16_t *wsize, uint8_t *wscale) +{ + int v; + char *p; + + if ((p = strchr(s,':'))) *p++=0; + v = atoi(s); + if (v < 0 || v>65535) + { + DLOG_ERR("bad wsize\n"); + return false; + } + *wsize=(uint16_t)v; + if (p && *p) + { + v = atoi(p); + if (v < 0 || v>255) + { + DLOG_ERR("bad wscale\n"); + return false; + } + *wscale = (uint8_t)v; + } + return true; +} + + + +static void cleanup_params(void) +{ + ConntrackPoolDestroy(¶ms.conntrack); + + dp_list_destroy(¶ms.desync_profiles); + +#ifdef __CYGWIN__ + strlist_destroy(¶ms.ssid_filter); + strlist_destroy(¶ms.nlm_filter); +#endif +} +static void exit_clean(int code) +{ + cleanup_params(); + exit(code); +} + +static bool parse_cutoff(const char *opt, unsigned int *value, char *mode) +{ + *mode = (*opt=='n' || *opt=='d' || *opt=='s') ? *opt++ : 'n'; + return sscanf(opt, "%u", value)>0; +} +static bool parse_badseq_increment(const char *opt, uint32_t *value) +{ + if (((opt[0]=='0' && opt[1]=='x') || (opt[0]=='-' && opt[1]=='0' && opt[2]=='x')) && sscanf(opt+2+(opt[0]=='-'), "%X", (int32_t*)value)>0) + { + if (opt[0]=='-') *value = -*value; + return true; + } + else + { + return sscanf(opt, "%d", (int32_t*)value)>0; + } +} +static void load_file_or_exit(const char *filename, void *buf, size_t *size) +{ + if (filename[0]=='0' && filename[1]=='x') + { + if (!parse_hex_str(filename+2,buf,size) || !*size) + { + DLOG_ERR("invalid hex string: %s\n",filename+2); + exit_clean(1); + } + DLOG("read %zu bytes from hex string\n",*size); + } + else + { + if (!load_file_nonempty(filename,buf,size)) + { + DLOG_ERR("could not read %s\n",filename); + exit_clean(1); + } + DLOG("read %zu bytes from %s\n",*size,filename); + } +} + +bool parse_autottl(const char *s, autottl *t) +{ + unsigned int delta,min,max; + AUTOTTL_SET_DEFAULT(*t); + if (s) + { + max = t->max; + switch (sscanf(s,"%u:%u-%u",&delta,&min,&max)) + { + case 3: + if ((delta && !max) || max>255) return false; + t->max=(uint8_t)max; + case 2: + if ((delta && !min) || min>255 || min>max) return false; + t->min=(uint8_t)min; + case 1: + if (delta>255) return false; + t->delta=(uint8_t)delta; + break; + default: + return false; + } + } + return true; +} + +static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) +{ + char *e,*p,c; + + for (p=opt,*ipv4=*ipv6=false ; p ; ) + { + if ((e = strchr(p,','))) + { + c=*e; + *e=0; + } + + if (!strcmp(p,"ipv4")) + *ipv4 = true; + else if (!strcmp(p,"ipv6")) + *ipv6 = true; + else return false; + + if (e) + { + *e++=c; + } + p = e; + } + return true; +} +#ifdef __CYGWIN__ +static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) +{ + char *e,*p,c,s1[64]; + port_filter pf; + int n; + + if (len<3) return false; + + for (n=0,p=opt,*buf='(',buf[1]=0 ; p ; n++) + { + if ((e = strchr(p,','))) + { + c=*e; + *e=0; + } + if (!pf_parse(p,&pf)) return false; + + if (pf.from==pf.to) + snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from); + else + snprintf(s1, sizeof(s1), "(%s.%s %s %u %s %s.%s %s %u)", l4, portname, pf.neg ? "<" : ">=", pf.from, pf.neg ? "or" : "and" , l4, portname, pf.neg ? ">" : "<=", pf.to); + if (n) strncat(buf," or ",len-strlen(buf)-1); + strncat(buf, s1, len-strlen(buf)-1); + + if (e) + { + *e++=c; + } + p = e; + } + strncat(buf, ")", len-strlen(buf)-1); + return true; +} + +#define DIVERT_NO_LOCALNETSv4_DST "(" \ + "(ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and " \ + "(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and " \ + "(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and " \ + "(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and " \ + "(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255))" +#define DIVERT_NO_LOCALNETSv4_SRC "(" \ + "(ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and " \ + "(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and " \ + "(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and " \ + "(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and " \ + "(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255))" + +#define DIVERT_NO_LOCALNETSv6_DST "(" \ + "(ipv6.DstAddr > ::1) and " \ + "(ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and " \ + "(ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and " \ + "(ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and " \ + "(ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))" +#define DIVERT_NO_LOCALNETSv6_SRC "(" \ + "(ipv6.SrcAddr > ::1) and " \ + "(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and " \ + "(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and " \ + "(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and " \ + "(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0))" + +#define DIVERT_NO_LOCALNETS_SRC "(" DIVERT_NO_LOCALNETSv4_SRC " or " DIVERT_NO_LOCALNETSv6_SRC ")" +#define DIVERT_NO_LOCALNETS_DST "(" DIVERT_NO_LOCALNETSv4_DST " or " DIVERT_NO_LOCALNETSv6_DST ")" + +#define DIVERT_TCP_INBOUNDS "(tcp.Ack and tcp.Syn or tcp.Rst or tcp.Fin)" + +// HTTP/1.? 30(2|7) +#define DIVERT_HTTP_REDIRECT "tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)" + +#define DIVERT_PROLOG "!impostor and !loopback" + +static bool wf_make_filter( + char *wf, size_t len, + unsigned int IfIdx,unsigned int SubIfIdx, + bool ipv4, bool ipv6, + const char *pf_tcp_src, const char *pf_tcp_dst, + const char *pf_udp_src, const char *pf_udp_dst) +{ + char pf_dst_buf[512],iface[64]; + const char *pf_dst; + const char *f_tcpin = *pf_tcp_src ? dp_list_have_autohostlist(¶ms.desync_profiles) ? "(" DIVERT_TCP_INBOUNDS " or (" DIVERT_HTTP_REDIRECT "))" : DIVERT_TCP_INBOUNDS : ""; + + snprintf(iface,sizeof(iface)," ifIdx=%u and subIfIdx=%u and",IfIdx,SubIfIdx); + + if (!*pf_tcp_src && !*pf_udp_src) return false; + if (*pf_tcp_src && *pf_udp_src) + { + snprintf(pf_dst_buf,sizeof(pf_dst_buf),"(%s or %s)",pf_tcp_dst,pf_udp_dst); + pf_dst = pf_dst_buf; + } + else + pf_dst = *pf_tcp_dst ? pf_tcp_dst : pf_udp_dst; + snprintf(wf,len, + DIVERT_PROLOG " and%s%s\n ((outbound and %s%s)\n or\n (inbound and tcp%s%s%s%s%s%s%s))", + IfIdx ? iface : "", + ipv4 ? ipv6 ? "" : " ip and" : " ipv6 and", + pf_dst, + ipv4 ? ipv6 ? " and " DIVERT_NO_LOCALNETS_DST : " and " DIVERT_NO_LOCALNETSv4_DST : " and " DIVERT_NO_LOCALNETSv6_DST, + *pf_tcp_src ? "" : " and false", + *f_tcpin ? " and " : "", + *f_tcpin ? f_tcpin : "", + *pf_tcp_src ? " and " : "", + *pf_tcp_src ? pf_tcp_src : "", + *pf_tcp_src ? " and " : "", + *pf_tcp_src ? ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_SRC : DIVERT_NO_LOCALNETSv4_SRC : DIVERT_NO_LOCALNETSv6_SRC : "" + ); + + return true; +} + +static unsigned int hash_jen(const void *data,unsigned int len) +{ + unsigned int hash; + HASH_JEN(data,len,hash); + return hash; +} + +#endif + + +static void exithelp(void) +{ + printf( + " --debug=0|1|syslog|@\n" +#ifdef __linux__ + " --qnum=\n" +#elif defined(BSD) + " --port=\t\t\t\t\t; divert port\n" +#endif + " --daemon\t\t\t\t\t; daemonize\n" + " --pidfile=\t\t\t\t; write pid to file\n" +#ifndef __CYGWIN__ + " --user=\t\t\t\t; drop root privs\n" + " --uid=uid[:gid]\t\t\t\t; drop root privs\n" +#endif +#ifdef __linux__ + " --bind-fix4\t\t\t\t\t; apply outgoing interface selection fix for generated ipv4 packets\n" + " --bind-fix6\t\t\t\t\t; apply outgoing interface selection fix for generated ipv6 packets\n" +#endif + " --ctrack-timeouts=S:E:F[:U]\t\t\t; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default %u:%u:%u:%u\n" +#ifdef __CYGWIN__ + "\nWINDIVERT FILTER:\n" + " --wf-iface=[.]\t\t\t; numeric network interface and subinterface indexes\n" + " --wf-l3=ipv4|ipv6\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" + " --wf-tcp=[~]port1[-port2]\t\t\t; TCP port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-udp=[~]port1[-port2]\t\t\t; UDP port filter. ~ means negation. multiple comma separated values allowed.\n" + " --wf-raw=|@\t\t\t; raw windivert filter string or filename\n" + " --wf-save=\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; enable winws only if any of specified wifi SSIDs connected\n" + " --nlm-filter=net1[,net2,net3,...]\t\t; enable winws only if any of specified NLM network is connected. names and GUIDs are accepted.\n" + " --nlm-list[=all]\t\t\t\t; list Network List Manager (NLM) networks. connected only or all.\n" +#endif + "\nMULTI-STRATEGY:\n" + " --new\t\t\t\t\t\t; begin new strategy\n" + " --filter-l3=ipv4|ipv6\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.\n" + " --filter-udp=[~]port1[-port2]\t\t\t; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp.\n" + "\nHOSTLIST FILTER:\n" + " --hostlist=\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-exclude=\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-auto=\t\t\t; detect DPI blocks and build hostlist automatically\n" + " --hostlist-auto-fail-threshold=\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" + " --hostlist-auto-fail-time=\t\t; all failed attemps must be within these seconds (default : %d)\n" + " --hostlist-auto-retrans-threshold=\t; how many request retransmissions cause attempt to fail (default : %d)\n" + " --hostlist-auto-debug=\t\t; debug auto hostlist positives\n" + "\nTAMPER:\n" + " --wsize=[:]\t\t; set window size. 0 = do not modify. OBSOLETE !\n" + " --wssize=[:]\t; set window size for server. 0 = do not modify. default scale_factor = 0.\n" + " --wssize-cutoff=[n|d|s]N\t\t\t; apply server wsize only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" + " --hostcase\t\t\t\t\t; change Host: => host:\n" + " --hostspell\t\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" + " --hostnospace\t\t\t\t\t; remove space after Host: and add it to User-Agent: to preserve packet size\n" + " --domcase\t\t\t\t\t; mix domain case : Host: TeSt.cOm\n" + " --dpi-desync=[,][,]\t; try to desync dpi state. modes : synack syndata fake fakeknown rst rstack hopbyhop destopt ipfrag1 disorder disorder2 split split2 ipfrag2 udplen tamper\n" +#ifdef __linux__ + " --dpi-desync-fwmark=\t\t; override fwmark for desync packet. default = 0x%08X (%u)\n" +#elif defined(SO_USER_COOKIE) + " --dpi-desync-sockarg=\t\t; override sockarg (SO_USER_COOKIE) for desync packet. default = 0x%08X (%u)\n" +#endif + " --dpi-desync-ttl=\t\t\t\t; set ttl for desync packet\n" + " --dpi-desync-ttl6=\t\t\t; set ipv6 hop limit for desync packet. by default ttl value is used.\n" + " --dpi-desync-autottl=[[:[-]]]\t; auto ttl mode for both ipv4 and ipv6. default: %u:%u-%u\n" + " --dpi-desync-autottl6=[[:[-]]] ; overrides --dpi-desync-autottl for ipv6 only\n" + " --dpi-desync-fooling=[,]\t\t; can use multiple comma separated values. modes : none md5sig ts badseq badsum datanoack hopbyhop hopbyhop2\n" + " --dpi-desync-repeats=\t\t\t; send every desync packet N times\n" + " --dpi-desync-skip-nosni=0|1\t\t\t; 1(default)=do not act on ClientHello without SNI (ESNI ?)\n" + " --dpi-desync-split-pos=<1..%u>\t\t; data payload split position\n" + " --dpi-desync-split-http-req=method|host\t; split at specified logical part of plain http request\n" + " --dpi-desync-split-tls=sni|sniext\t\t; split at specified logical part of TLS ClientHello\n" + " --dpi-desync-split-seqovl=\t\t; use sequence overlap before first sent original split segment\n" + " --dpi-desync-split-seqovl-pattern=|0xHEX ; pattern for the fake part of overlap\n" + " --dpi-desync-ipfrag-pos-tcp=<8..%u>\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" + " --dpi-desync-ipfrag-pos-udp=<8..%u>\t\t; ip frag position starting from the transport header. multiple of 8, default %u.\n" + " --dpi-desync-badseq-increment=\t; badseq fooling seq signed increment. default %d\n" + " --dpi-desync-badack-increment=\t; badseq fooling ackseq signed increment. default %d\n" + " --dpi-desync-any-protocol=0|1\t\t\t; 0(default)=desync only http and tls 1=desync any nonempty data packet\n" + " --dpi-desync-fake-http=|0xHEX\t; file containing fake http request\n" + " --dpi-desync-fake-tls=|0xHEX\t\t; file containing fake TLS ClientHello (for https)\n" + " --dpi-desync-fake-unknown=|0xHEX\t; file containing unknown protocol fake payload\n" + " --dpi-desync-fake-syndata=|0xHEX\t; file containing SYN data payload\n" + " --dpi-desync-fake-quic=|0xHEX\t; file containing fake QUIC Initial\n" + " --dpi-desync-fake-wireguard=|0xHEX\t; file containing fake wireguard handshake initiation\n" + " --dpi-desync-fake-dht=|0xHEX\t\t; file containing DHT protocol fake payload (d1...e)\n" + " --dpi-desync-fake-unknown-udp=|0xHEX\t; file containing unknown udp protocol fake payload\n" + " --dpi-desync-udplen-increment=\t\t; increase or decrease udp packet length by N bytes (default %u). negative values decrease length.\n" + " --dpi-desync-udplen-pattern=|0xHEX\t; udp tail fill pattern\n" + " --dpi-desync-start=[n|d|s]N\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) greater or equal than N\n" + " --dpi-desync-cutoff=[n|d|s]N\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n", + CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP, + HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, +#if defined(__linux__) || defined(SO_USER_COOKIE) + DPI_DESYNC_FWMARK_DEFAULT,DPI_DESYNC_FWMARK_DEFAULT, +#endif + AUTOTTL_DEFAULT_DELTA,AUTOTTL_DEFAULT_MIN,AUTOTTL_DEFAULT_MAX, + DPI_DESYNC_MAX_FAKE_LEN, + DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT, + DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT, + BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT, + UDPLEN_INCREMENT_DEFAULT + ); + exit(1); +} +static void exithelp_clean(void) +{ + cleanup_params(); + exithelp(); +} + +bool parse_httpreqpos(const char *s, enum httpreqpos *pos) +{ + if (!strcmp(s, "method")) + *pos = httpreqpos_method; + else if (!strcmp(s, "host")) + *pos = httpreqpos_host; + else + return false; + return true; +} +bool parse_tlspos(const char *s, enum tlspos *pos) +{ + if (!strcmp(s, "sni")) + *pos = tlspos_sni; + else if (!strcmp(s, "sniext")) + *pos = tlspos_sniext; + else + return false; + return true; +} + +int main(int argc, char **argv) +{ +#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 daemon = false; + char pidfile[256]; +#ifdef __CYGWIN__ + char windivert_filter[8192], wf_pf_tcp_src[256], wf_pf_tcp_dst[256], wf_pf_udp_src[256], wf_pf_udp_dst[256], wf_save_file[256]; + bool wf_ipv4=true, wf_ipv6=true; + unsigned int IfIdx=0, SubIfIdx=0; + unsigned int hash_wf_tcp=0,hash_wf_udp=0,hash_wf_raw=0,hash_ssid_filter=0,hash_nlm_filter=0; + *windivert_filter = *wf_pf_tcp_src = *wf_pf_tcp_dst = *wf_pf_udp_src = *wf_pf_udp_dst = *wf_save_file = 0; +#endif + + srandom(time(NULL)); + + memset(¶ms, 0, sizeof(params)); + *pidfile = 0; + + struct desync_profile_list *dpl; + struct desync_profile *dp; + int desync_profile_count=0; + if (!(dpl = dp_list_add(¶ms.desync_profiles))) + { + DLOG_ERR("desync_profile_add: out of memory\n"); + exit_clean(1); + } + dp = &dpl->dp; + dp->n = ++desync_profile_count; + +#ifdef __linux__ + params.qnum = -1; +#elif defined(BSD) + params.port = 0; +#endif + params.desync_fwmark = DPI_DESYNC_FWMARK_DEFAULT; + params.ctrack_t_syn = CTRACK_T_SYN; + params.ctrack_t_est = CTRACK_T_EST; + params.ctrack_t_fin = CTRACK_T_FIN; + params.ctrack_t_udp = CTRACK_T_UDP; + +#ifdef __CYGWIN__ + LIST_INIT(¶ms.ssid_filter); + LIST_INIT(¶ms.nlm_filter); +#else + if (can_drop_root()) // are we root ? + { + params.uid = params.gid = 0x7FFFFFFF; // default uid:gid + params.droproot = true; + } +#endif + + const struct option long_options[] = { + {"debug",optional_argument,0,0}, // optidx=0 +#ifdef __linux__ + {"qnum",required_argument,0,0}, // optidx=1 +#elif defined(BSD) + {"port",required_argument,0,0}, // optidx=1 +#else + {"disabled_argument_1",no_argument,0,0},// optidx=1 +#endif + {"daemon",no_argument,0,0}, // optidx=2 + {"pidfile",required_argument,0,0}, // optidx=3 +#ifndef __CYGWIN__ + {"user",required_argument,0,0 }, // optidx=4 + {"uid",required_argument,0,0 }, // optidx=5 +#else + {"disabled_argument_2",no_argument,0,0}, // optidx=4 + {"disabled_argument_3",no_argument,0,0}, // optidx=5 +#endif + {"wsize",required_argument,0,0}, // optidx=6 + {"wssize",required_argument,0,0}, // optidx=7 + {"wssize-cutoff",required_argument,0,0},// optidx=8 + {"ctrack-timeouts",required_argument,0,0},// optidx=9 + {"hostcase",no_argument,0,0}, // optidx=10 + {"hostspell",required_argument,0,0}, // optidx=11 + {"hostnospace",no_argument,0,0}, // optidx=12 + {"domcase",no_argument,0,0 }, // optidx=13 + {"dpi-desync",required_argument,0,0}, // optidx=14 +#ifdef __linux__ + {"dpi-desync-fwmark",required_argument,0,0}, // optidx=15 +#elif defined(SO_USER_COOKIE) + {"dpi-desync-sockarg",required_argument,0,0}, // optidx=15 +#else + {"disabled_argument_4",no_argument,0,0}, // optidx=15 +#endif + {"dpi-desync-ttl",required_argument,0,0}, // optidx=16 + {"dpi-desync-ttl6",required_argument,0,0}, // optidx=17 + {"dpi-desync-autottl",optional_argument,0,0}, // optidx=18 + {"dpi-desync-autottl6",optional_argument,0,0}, // optidx=19 + {"dpi-desync-fooling",required_argument,0,0}, // optidx=20 + {"dpi-desync-repeats",required_argument,0,0}, // optidx=21 + {"dpi-desync-skip-nosni",optional_argument,0,0},// optidx=22 + {"dpi-desync-split-pos",required_argument,0,0},// optidx=23 + {"dpi-desync-split-http-req",required_argument,0,0 },// optidx=24 + {"dpi-desync-split-tls",required_argument,0,0 },// optidx=25 + {"dpi-desync-split-seqovl",required_argument,0,0 },// optidx=26 + {"dpi-desync-split-seqovl-pattern",required_argument,0,0 },// optidx=27 + {"dpi-desync-ipfrag-pos-tcp",required_argument,0,0},// optidx=28 + {"dpi-desync-ipfrag-pos-udp",required_argument,0,0},// optidx=29 + {"dpi-desync-badseq-increment",required_argument,0,0},// optidx=30 + {"dpi-desync-badack-increment",required_argument,0,0},// optidx=31 + {"dpi-desync-any-protocol",optional_argument,0,0},// optidx=32 + {"dpi-desync-fake-http",required_argument,0,0},// optidx=33 + {"dpi-desync-fake-tls",required_argument,0,0},// optidx=34 + {"dpi-desync-fake-unknown",required_argument,0,0},// optidx=35 + {"dpi-desync-fake-syndata",required_argument,0,0},// optidx=36 + {"dpi-desync-fake-quic",required_argument,0,0},// optidx=37 + {"dpi-desync-fake-wireguard",required_argument,0,0},// optidx=38 + {"dpi-desync-fake-dht",required_argument,0,0},// optidx=39 + {"dpi-desync-fake-unknown-udp",required_argument,0,0},// optidx=40 + {"dpi-desync-udplen-increment",required_argument,0,0},// optidx=41 + {"dpi-desync-udplen-pattern",required_argument,0,0},// optidx=42 + {"dpi-desync-cutoff",required_argument,0,0},// optidx=43 + {"dpi-desync-start",required_argument,0,0},// optidx=43 + {"hostlist",required_argument,0,0}, // optidx=44 + {"hostlist-exclude",required_argument,0,0}, // optidx=45 + {"hostlist-auto",required_argument,0,0}, // optidx=46 + {"hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=48 + {"hostlist-auto-fail-time",required_argument,0,0}, // optidx=49 + {"hostlist-auto-retrans-threshold",required_argument,0,0}, // optidx=50 + {"hostlist-auto-debug",required_argument,0,0}, // optidx=51 + {"new",no_argument,0,0}, // optidx=52 + {"filter-l3",required_argument,0,0}, // optidx=53 + {"filter-tcp",required_argument,0,0}, // optidx=54 + {"filter-udp",required_argument,0,0}, // optidx=55 +#ifdef __linux__ + {"bind-fix4",no_argument,0,0}, // optidx=56 + {"bind-fix6",no_argument,0,0}, // optidx=57 +#elif defined(__CYGWIN__) + {"wf-iface",required_argument,0,0}, // optidx=56 + {"wf-l3",required_argument,0,0}, // optidx=57 + {"wf-tcp",required_argument,0,0}, // optidx=58 + {"wf-udp",required_argument,0,0}, // optidx=59 + {"wf-raw",required_argument,0,0}, // optidx=60 + {"wf-save",required_argument,0,0}, // optidx=61 + {"ssid-filter",required_argument,0,0}, // optidx=62 + {"nlm-filter",required_argument,0,0}, // optidx=63 + {"nlm-list",optional_argument,0,0}, // optidx=64 +#endif + {NULL,0,NULL,0} + }; + if (argc < 2) exithelp(); + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp(); + switch (option_index) + { + case 0: /* 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); + } +#ifndef __CYGWIN__ + if (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); +#endif + 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); + } + else + { + params.debug = !!atoi(optarg); + params.debug_target = LOG_TARGET_CONSOLE; + } + } + else + { + params.debug = true; + params.debug_target = LOG_TARGET_CONSOLE; + } + break; +#ifndef __CYGWIN__ + case 1: /* qnum or port */ +#ifdef __linux__ + params.qnum = atoi(optarg); + if (params.qnum < 0 || params.qnum>65535) + { + DLOG_ERR("bad qnum\n"); + exit_clean(1); + } +#elif defined(BSD) + { + int i = atoi(optarg); + if (i <= 0 || i > 65535) + { + DLOG_ERR("bad port number\n"); + exit_clean(1); + } + params.port = (uint16_t)i; + } +#endif + break; +#endif + case 2: /* daemon */ + daemon = true; + break; + case 3: /* pidfile */ + strncpy(pidfile, optarg, sizeof(pidfile)); + pidfile[sizeof(pidfile) - 1] = '\0'; + break; +#ifndef __CYGWIN__ + case 4: /* user */ + { + struct passwd *pwd = getpwnam(optarg); + if (!pwd) + { + DLOG_ERR("non-existent username supplied\n"); + exit_clean(1); + } + params.uid = pwd->pw_uid; + params.gid = pwd->pw_gid; + params.droproot = true; + break; + } + case 5: /* uid */ + params.gid = 0x7FFFFFFF; // default gid. drop gid=0 + params.droproot = true; + if (sscanf(optarg, "%u:%u", ¶ms.uid, ¶ms.gid)<1) + { + DLOG_ERR("--uid should be : uid[:gid]\n"); + exit_clean(1); + } + break; +#endif + case 6: /* wsize */ + if (!parse_ws_scale_factor(optarg,&dp->wsize,&dp->wscale)) + exit_clean(1); + break; + case 7: /* wssize */ + if (!parse_ws_scale_factor(optarg,&dp->wssize,&dp->wsscale)) + exit_clean(1); + break; + case 8: /* wssize-cutoff */ + if (!parse_cutoff(optarg, &dp->wssize_cutoff, &dp->wssize_cutoff_mode)) + { + DLOG_ERR("invalid wssize-cutoff value\n"); + exit_clean(1); + } + break; + case 9: /* 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 10: /* hostcase */ + dp->hostcase = true; + break; + case 11: /* hostspell */ + if (strlen(optarg) != 4) + { + DLOG_ERR("hostspell must be exactly 4 chars long\n"); + exit_clean(1); + } + dp->hostcase = true; + memcpy(dp->hostspell, optarg, 4); + break; + case 12: /* hostnospace */ + dp->hostnospace = true; + break; + case 13: /* domcase */ + dp->domcase = true; + break; + case 14: /* dpi-desync */ + { + char *mode=optarg,*mode2,*mode3; + mode2 = mode ? strchr(mode,',') : NULL; + if (mode2) *mode2++=0; + mode3 = mode2 ? strchr(mode2,',') : NULL; + if (mode3) *mode3++=0; + + dp->desync_mode0 = desync_mode_from_string(mode); + if (desync_valid_zero_stage(dp->desync_mode0)) + { + mode = mode2; + mode2 = mode3; + mode3 = NULL; + } + else + { + dp->desync_mode0 = DESYNC_NONE; + } + dp->desync_mode = desync_mode_from_string(mode); + dp->desync_mode2 = desync_mode_from_string(mode2); + if (dp->desync_mode0==DESYNC_INVALID || dp->desync_mode==DESYNC_INVALID || dp->desync_mode2==DESYNC_INVALID) + { + DLOG_ERR("invalid dpi-desync mode\n"); + exit_clean(1); + } + if (mode3) + { + DLOG_ERR("invalid desync combo : %s+%s+%s\n",mode,mode2,mode3); + exit_clean(1); + } + if (dp->desync_mode2 && (desync_only_first_stage(dp->desync_mode) || !(desync_valid_first_stage(dp->desync_mode) && desync_valid_second_stage(dp->desync_mode2)))) + { + DLOG_ERR("invalid desync combo : %s+%s\n", mode,mode2); + exit_clean(1); + } + #if defined(__OpenBSD__) + if (dp->desync_mode==DESYNC_IPFRAG2 || dp->desync_mode2==DESYNC_IPFRAG2) + { + DLOG_ERR("OpenBSD has checksum issues with fragmented packets. ipfrag disabled.\n"); + exit_clean(1); + } + #endif + } + break; +#ifndef __CYGWIN__ + case 15: /* dpi-desync-fwmark/dpi-desync-sockarg */ +#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); + } +#else + DLOG_ERR("fmwark/sockarg not supported in this OS\n"); + exit_clean(1); +#endif + break; +#endif + case 16: /* dpi-desync-ttl */ + dp->desync_ttl = (uint8_t)atoi(optarg); + break; + case 17: /* dpi-desync-ttl6 */ + dp->desync_ttl6 = (uint8_t)atoi(optarg); + break; + case 18: /* dpi-desync-autottl */ + if (!parse_autottl(optarg, &dp->desync_autottl)) + { + DLOG_ERR("dpi-desync-autottl value error\n"); + exit_clean(1); + } + break; + case 19: /* dpi-desync-autottl6 */ + if (!parse_autottl(optarg, &dp->desync_autottl6)) + { + DLOG_ERR("dpi-desync-autottl6 value error\n"); + exit_clean(1); + } + break; + case 20: /* dpi-desync-fooling */ + { + char *e,*p = optarg; + while (p) + { + e = strchr(p,','); + if (e) *e++=0; + if (!strcmp(p,"md5sig")) + dp->desync_fooling_mode |= FOOL_MD5SIG; + else if (!strcmp(p,"ts")) + dp->desync_fooling_mode |= FOOL_TS; + else if (!strcmp(p,"badsum")) + { + #ifdef __OpenBSD__ + DLOG_CONDUP("\nWARNING !!! OpenBSD may forcibly recompute tcp/udp checksums !!! In this case badsum fooling will not work.\nYou should check tcp checksum correctness in tcpdump manually before using badsum.\n\n"); + #endif + dp->desync_fooling_mode |= FOOL_BADSUM; + } + else if (!strcmp(p,"badseq")) + dp->desync_fooling_mode |= FOOL_BADSEQ; + else if (!strcmp(p,"datanoack")) + dp->desync_fooling_mode |= FOOL_DATANOACK; + else if (!strcmp(p,"hopbyhop")) + dp->desync_fooling_mode |= FOOL_HOPBYHOP; + else if (!strcmp(p,"hopbyhop2")) + dp->desync_fooling_mode |= FOOL_HOPBYHOP2; + else if (strcmp(p,"none")) + { + DLOG_ERR("dpi-desync-fooling allowed values : none,md5sig,ts,badseq,badsum,datanoack,hopbyhop,hopbyhop2\n"); + exit_clean(1); + } + p = e; + } + } + break; + case 21: /* dpi-desync-repeats */ + if (sscanf(optarg,"%u",&dp->desync_repeats)<1 || !dp->desync_repeats || dp->desync_repeats>20) + { + DLOG_ERR("dpi-desync-repeats must be within 1..20\n"); + exit_clean(1); + } + break; + case 22: /* dpi-desync-skip-nosni */ + dp->desync_skip_nosni = !optarg || atoi(optarg); + break; + case 23: /* dpi-desync-split-pos */ + if (sscanf(optarg,"%u",&dp->desync_split_pos)<1 || dp->desync_split_pos<1) + { + DLOG_ERR("dpi-desync-split-pos is not valid\n"); + exit_clean(1); + } + break; + case 24: /* dpi-desync-split-http-req */ + if (!parse_httpreqpos(optarg, &dp->desync_split_http_req)) + { + DLOG_ERR("Invalid argument for dpi-desync-split-http-req\n"); + exit_clean(1); + } + break; + case 25: /* dpi-desync-split-tls */ + if (!parse_tlspos(optarg, &dp->desync_split_tls)) + { + DLOG_ERR("Invalid argument for dpi-desync-split-tls\n"); + exit_clean(1); + } + break; + case 26: /* dpi-desync-split-seqovl */ + if (sscanf(optarg,"%u",&dp->desync_seqovl)<1) + { + DLOG_ERR("dpi-desync-split-seqovl is not valid\n"); + exit_clean(1); + } + break; + case 27: /* dpi-desync-split-seqovl-pattern */ + { + char buf[sizeof(dp->seqovl_pattern)]; + size_t sz=sizeof(buf); + load_file_or_exit(optarg,buf,&sz); + fill_pattern(dp->seqovl_pattern,sizeof(dp->seqovl_pattern),buf,sz); + } + break; + case 28: /* dpi-desync-ipfrag-pos-tcp */ + if (sscanf(optarg,"%u",&dp->desync_ipfrag_pos_tcp)<1 || dp->desync_ipfrag_pos_tcp<1 || dp->desync_ipfrag_pos_tcp>DPI_DESYNC_MAX_FAKE_LEN) + { + DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be within 1..%u range\n",DPI_DESYNC_MAX_FAKE_LEN); + exit_clean(1); + } + if (dp->desync_ipfrag_pos_tcp & 7) + { + DLOG_ERR("dpi-desync-ipfrag-pos-tcp must be multiple of 8\n"); + exit_clean(1); + } + break; + case 29: /* dpi-desync-ipfrag-pos-udp */ + if (sscanf(optarg,"%u",&dp->desync_ipfrag_pos_udp)<1 || dp->desync_ipfrag_pos_udp<1 || dp->desync_ipfrag_pos_udp>DPI_DESYNC_MAX_FAKE_LEN) + { + DLOG_ERR("dpi-desync-ipfrag-pos-udp must be within 1..%u range\n",DPI_DESYNC_MAX_FAKE_LEN); + exit_clean(1); + } + if (dp->desync_ipfrag_pos_udp & 7) + { + DLOG_ERR("dpi-desync-ipfrag-pos-udp must be multiple of 8\n"); + exit_clean(1); + } + break; + case 30: /* dpi-desync-badseq-increments */ + if (!parse_badseq_increment(optarg,&dp->desync_badseq_increment)) + { + DLOG_ERR("dpi-desync-badseq-increment should be signed decimal or signed 0xHEX\n"); + exit_clean(1); + } + break; + case 31: /* dpi-desync-badack-increment */ + if (!parse_badseq_increment(optarg,&dp->desync_badseq_ack_increment)) + { + DLOG_ERR("dpi-desync-badack-increment should be signed decimal or signed 0xHEX\n"); + exit_clean(1); + } + break; + case 32: /* dpi-desync-any-protocol */ + dp->desync_any_proto = !optarg || atoi(optarg); + break; + case 33: /* dpi-desync-fake-http */ + dp->fake_http_size = sizeof(dp->fake_http); + load_file_or_exit(optarg,dp->fake_http,&dp->fake_http_size); + break; + case 34: /* dpi-desync-fake-tls */ + dp->fake_tls_size = sizeof(dp->fake_tls); + load_file_or_exit(optarg,dp->fake_tls,&dp->fake_tls_size); + break; + case 35: /* dpi-desync-fake-unknown */ + dp->fake_unknown_size = sizeof(dp->fake_unknown); + load_file_or_exit(optarg,dp->fake_unknown,&dp->fake_unknown_size); + break; + case 36: /* dpi-desync-fake-syndata */ + dp->fake_syndata_size = sizeof(dp->fake_syndata); + load_file_or_exit(optarg,dp->fake_syndata,&dp->fake_syndata_size); + break; + case 37: /* dpi-desync-fake-quic */ + dp->fake_quic_size = sizeof(dp->fake_quic); + load_file_or_exit(optarg,dp->fake_quic,&dp->fake_quic_size); + break; + case 38: /* dpi-desync-fake-wireguard */ + dp->fake_wg_size = sizeof(dp->fake_wg); + load_file_or_exit(optarg,dp->fake_wg,&dp->fake_wg_size); + break; + case 39: /* dpi-desync-fake-dht */ + dp->fake_dht_size = sizeof(dp->fake_dht); + load_file_or_exit(optarg,dp->fake_dht,&dp->fake_dht_size); + break; + case 40: /* dpi-desync-fake-unknown-udp */ + dp->fake_unknown_udp_size = sizeof(dp->fake_unknown_udp); + load_file_or_exit(optarg,dp->fake_unknown_udp,&dp->fake_unknown_udp_size); + break; + case 41: /* dpi-desync-udplen-increment */ + if (sscanf(optarg,"%d",&dp->udplen_increment)<1 || dp->udplen_increment>0x7FFF || dp->udplen_increment<-0x8000) + { + DLOG_ERR("dpi-desync-udplen-increment must be integer within -32768..32767 range\n"); + exit_clean(1); + } + break; + case 42: /* dpi-desync-udplen-pattern */ + { + char buf[sizeof(dp->udplen_pattern)]; + size_t sz=sizeof(buf); + load_file_or_exit(optarg,buf,&sz); + fill_pattern(dp->udplen_pattern,sizeof(dp->udplen_pattern),buf,sz); + } + break; + case 43: /* desync-cutoff */ + if (!parse_cutoff(optarg, &dp->desync_cutoff, &dp->desync_cutoff_mode)) + { + DLOG_ERR("invalid desync-cutoff value\n"); + exit_clean(1); + } + break; + case 44: /* desync-start */ + if (!parse_cutoff(optarg, &dp->desync_start, &dp->desync_start_mode)) + { + DLOG_ERR("invalid desync-start value\n"); + exit_clean(1); + } + break; + case 45: /* hostlist */ + if (!strlist_add(&dp->hostlist_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + break; + case 46: /* hostlist-exclude */ + if (!strlist_add(&dp->hostlist_exclude_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + break; + case 47: /* hostlist-auto */ + if (*dp->hostlist_auto_filename) + { + DLOG_ERR("only one auto hostlist per profile is supported\n"); + exit_clean(1); + } + { + FILE *F = fopen(optarg,"at"); + 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); + } +#ifndef __CYGWIN__ + if (params.droproot && chown(optarg, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg); +#endif + } + if (!strlist_add(&dp->hostlist_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + strncpy(dp->hostlist_auto_filename, optarg, sizeof(dp->hostlist_auto_filename)); + dp->hostlist_auto_filename[sizeof(dp->hostlist_auto_filename) - 1] = '\0'; + break; + case 48: /* 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 49: /* 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 50: /* 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 51: /* hostlist-auto-debug */ + { + FILE *F = fopen(optarg,"a+t"); + if (!F) + { + DLOG_ERR("cannot create %s\n", optarg); + exit_clean(1); + } + fclose(F); +#ifndef __CYGWIN__ + if (params.droproot && chown(optarg, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", optarg); +#endif + strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); + params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; + } + break; + + case 52: /* new */ + 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; + break; + case 53: /* 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 54: /* filter-tcp */ + if (!pf_parse(optarg,&dp->pf_tcp)) + { + DLOG_ERR("Invalid port filter : %s\n",optarg); + exit_clean(1); + } + // deny udp if not set + if (pf_is_empty(&dp->pf_udp)) dp->pf_udp.neg=true; + break; + case 55: /* filter-udp */ + if (!pf_parse(optarg,&dp->pf_udp)) + { + DLOG_ERR("Invalid port filter : %s\n",optarg); + exit_clean(1); + } + // deny tcp if not set + if (pf_is_empty(&dp->pf_tcp)) dp->pf_tcp.neg=true; + break; + +#ifdef __linux__ + case 56: /* bind-fix4 */ + params.bind_fix4 = true; + break; + case 57: /* bind-fix6 */ + params.bind_fix6 = true; + break; +#elif defined(__CYGWIN__) + case 56: /* wf-iface */ + if (!sscanf(optarg,"%u.%u",&IfIdx,&SubIfIdx)) + { + DLOG_ERR("bad value for --wf-iface\n"); + exit_clean(1); + } + break; + case 57: /* wf-l3 */ + if (!wf_make_l3(optarg,&wf_ipv4,&wf_ipv6)) + { + DLOG_ERR("bad value for --wf-l3\n"); + exit_clean(1); + } + break; + case 58: /* wf-tcp */ + hash_wf_tcp=hash_jen(optarg,strlen(optarg)); + if (!wf_make_pf(optarg,"tcp","SrcPort",wf_pf_tcp_src,sizeof(wf_pf_tcp_src)) || + !wf_make_pf(optarg,"tcp","DstPort",wf_pf_tcp_dst,sizeof(wf_pf_tcp_dst))) + { + DLOG_ERR("bad value for --wf-tcp\n"); + exit_clean(1); + } + break; + case 59: /* wf-udp */ + hash_wf_udp=hash_jen(optarg,strlen(optarg)); + if (!wf_make_pf(optarg,"udp","SrcPort",wf_pf_udp_src,sizeof(wf_pf_udp_src)) || + !wf_make_pf(optarg,"udp","DstPort",wf_pf_udp_dst,sizeof(wf_pf_udp_dst))) + { + DLOG_ERR("bad value for --wf-udp\n"); + exit_clean(1); + } + break; + case 60: /* wf-raw */ + hash_wf_raw=hash_jen(optarg,strlen(optarg)); + if (optarg[0]=='@') + { + size_t sz = sizeof(windivert_filter)-1; + load_file_or_exit(optarg+1,windivert_filter,&sz); + windivert_filter[sz] = 0; + } + else + { + strncpy(windivert_filter, optarg, sizeof(windivert_filter)); + windivert_filter[sizeof(windivert_filter) - 1] = '\0'; + } + break; + case 61: /* wf-save */ + strncpy(wf_save_file, optarg, sizeof(wf_save_file)); + wf_save_file[sizeof(wf_save_file) - 1] = '\0'; + break; + case 62: /* ssid-filter */ + hash_ssid_filter=hash_jen(optarg,strlen(optarg)); + { + char *e,*p = optarg; + while (p) + { + e = strchr(p,','); + if (e) *e++=0; + if (*p && !strlist_add(¶ms.ssid_filter, p)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + p = e; + + } + } + break; + case 63: /* nlm-filter */ + hash_nlm_filter=hash_jen(optarg,strlen(optarg)); + { + char *e,*p = optarg; + while (p) + { + e = strchr(p,','); + if (e) *e++=0; + if (*p && !strlist_add(¶ms.nlm_filter, p)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + p = e; + + } + } + break; + case 64: /* 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 + } + } + +#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); + } +#elif defined(__CYGWIN__) + if (!*windivert_filter) + { + if (!*wf_pf_tcp_src && !*wf_pf_udp_src) + { + DLOG_ERR("windivert filter : must specify port filter\n"); + exit_clean(1); + } + if (!wf_make_filter(windivert_filter, sizeof(windivert_filter), IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, wf_pf_tcp_src, wf_pf_tcp_dst, wf_pf_udp_src, wf_pf_udp_dst)) + { + DLOG_ERR("windivert filter : could not make filter\n"); + exit_clean(1); + } + } + DLOG("windivert filter size: %zu\nwindivert filter:\n%s\n",strlen(windivert_filter),windivert_filter); + if (*wf_save_file) + { + if (save_file(wf_save_file,windivert_filter,strlen(windivert_filter))) + { + DLOG_ERR("windivert filter: raw filter saved to %s\n", wf_save_file); + exit_clean(0); + } + else + { + DLOG_ERR("windivert filter: could not save raw filter to %s\n", wf_save_file); + exit_clean(1); + } + } + HANDLE hMutexArg; + { + char mutex_name[128]; + snprintf(mutex_name,sizeof(mutex_name),"Global\\winws_arg_%u_%u_%u_%u_%u_%u_%u_%u_%u",hash_wf_tcp,hash_wf_udp,hash_wf_raw,hash_ssid_filter,hash_nlm_filter,IfIdx,SubIfIdx,wf_ipv4,wf_ipv6); + + hMutexArg = CreateMutexA(NULL,TRUE,mutex_name); + if (hMutexArg && GetLastError()==ERROR_ALREADY_EXISTS) + { + CloseHandle(hMutexArg); hMutexArg = NULL; + DLOG_ERR("A copy of winws is already running with the same filter\n"); + goto exiterr; + } + + } +#endif + + 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); + + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + dp = &dpl->dp; + // not specified - use desync_ttl value instead + if (dp->desync_ttl6 == 0xFF) dp->desync_ttl6=dp->desync_ttl; + if (!AUTOTTL_ENABLED(dp->desync_autottl6)) dp->desync_autottl6 = dp->desync_autottl; + if (AUTOTTL_ENABLED(dp->desync_autottl)) + DLOG("[profile %d] autottl ipv4 %u:%u-%u\n",v,dp->desync_autottl.delta,dp->desync_autottl.min,dp->desync_autottl.max); + if (AUTOTTL_ENABLED(dp->desync_autottl6)) + DLOG("[profile %d] autottl ipv6 %u:%u-%u\n",v,dp->desync_autottl6.delta,dp->desync_autottl6.min,dp->desync_autottl6.max); + if (dp->desync_split_tls==tlspos_none && dp->desync_split_pos) dp->desync_split_tls=tlspos_pos; + if (dp->desync_split_http_req==httpreqpos_none && dp->desync_split_pos) dp->desync_split_http_req=httpreqpos_pos; + } + + if (!LoadIncludeHostLists()) + { + DLOG_ERR("Include hostlists load failed\n"); + exit_clean(1); + } + if (!LoadExcludeHostLists()) + { + DLOG_ERR("Exclude hostlists load failed\n"); + exit_clean(1); + } + + if (daemon) daemonize(); + + if (*pidfile && !writepid(pidfile)) + { + DLOG_ERR("could not write pidfile\n"); + goto exiterr; + } + + 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); + +#ifdef __linux__ + result = nfq_main(); +#elif defined(BSD) + result = dvt_main(); +#elif defined(__CYGWIN__) + result = win_main(windivert_filter); +#else + #error unsupported OS +#endif +ex: + rawsend_cleanup(); + cleanup_params(); +#ifdef __CYGWIN__ + if (hMutexArg) + { + ReleaseMutex(hMutexArg); + CloseHandle(hMutexArg); + } +#endif + return result; +exiterr: + result = 1; + goto ex; +} diff --git a/nfq/nfqws.h b/nfq/nfqws.h new file mode 100644 index 00000000..86aa8829 --- /dev/null +++ b/nfq/nfqws.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#ifdef __CYGWIN__ +extern bool bQuit; +#endif +int main(int argc, char *argv[]); diff --git a/nfq/packet_queue.c b/nfq/packet_queue.c new file mode 100644 index 00000000..ff5ee73b --- /dev/null +++ b/nfq/packet_queue.c @@ -0,0 +1,68 @@ +#include +#include + +#include "packet_queue.h" + +void rawpacket_queue_init(struct rawpacket_tailhead *q) +{ + TAILQ_INIT(q); +} +void rawpacket_free(struct rawpacket *rp) +{ + if (rp) free(rp->packet); + free(rp); +} +struct rawpacket *rawpacket_dequeue(struct rawpacket_tailhead *q) +{ + struct rawpacket *rp; + rp = TAILQ_FIRST(q); + if (rp) TAILQ_REMOVE(q, rp, next); + return rp; +} +void rawpacket_queue_destroy(struct rawpacket_tailhead *q) +{ + struct rawpacket *rp; + while((rp = rawpacket_dequeue(q))) rawpacket_free(rp); +} + +struct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *ifout,const void *data,size_t len,size_t len_payload) +{ + struct rawpacket *rp = malloc(sizeof(struct rawpacket)); + if (!rp) return NULL; + + rp->packet = malloc(len); + if (!rp->packet) + { + free(rp); + return NULL; + } + + rp->dst = *dst; + rp->fwmark = fwmark; + if (ifout) + { + strncpy(rp->ifout,ifout,sizeof(rp->ifout)); + rp->ifout[sizeof(rp->ifout)-1]=0; + } + else + rp->ifout[0]=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/nfq/packet_queue.h b/nfq/packet_queue.h new file mode 100644 index 00000000..fb57798e --- /dev/null +++ b/nfq/packet_queue.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct rawpacket +{ + struct sockaddr_storage dst; + char ifout[IFNAMSIZ+1]; + uint32_t fwmark; + size_t len, len_payload; + uint8_t *packet; + TAILQ_ENTRY(rawpacket) next; +}; +TAILQ_HEAD(rawpacket_tailhead, rawpacket); + +void rawpacket_queue_init(struct rawpacket_tailhead *q); +void rawpacket_queue_destroy(struct rawpacket_tailhead *q); +bool rawpacket_queue_empty(const struct rawpacket_tailhead *q); +unsigned int rawpacket_queue_count(const struct rawpacket_tailhead *q); +struct rawpacket *rawpacket_queue(struct rawpacket_tailhead *q,const struct sockaddr_storage* dst,uint32_t fwmark,const char *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/nfq/params.c b/nfq/params.c new file mode 100644 index 00000000..e6ad407c --- /dev/null +++ b/nfq/params.c @@ -0,0 +1,232 @@ +#include "params.h" + +#include +#include +#include + +#include "pools.h" +#include "desync.h" + +#ifdef BSD +const char *progname = "dvtws"; +#elif defined(__CYGWIN__) +const char *progname = "winws"; +#elif defined(__linux__) +const char *progname = "nfqws"; +#else +#error UNKNOWN_SYSTEM_TIME +#endif + + +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; +} + +static char syslog_buf[1024]; +static size_t syslog_buf_sz=0; +static void syslog_buffered(int priority, const char *format, va_list args) +{ + if (vsnprintf(syslog_buf+syslog_buf_sz,sizeof(syslog_buf)-syslog_buf_sz,format,args)>0) + { + syslog_buf_sz=strlen(syslog_buf); + // log when buffer is full or buffer ends with \n + if (syslog_buf_sz>=(sizeof(syslog_buf)-1) || (syslog_buf_sz && syslog_buf[syslog_buf_sz-1]=='\n')) + { + syslog(priority,"%s",syslog_buf); + syslog_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); + } + 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 + syslog_buffered(syslog_priority,format,args); + r = 1; + break; + 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; +} + + +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; + + LIST_INIT(&entry->dp.hostlist_files); + LIST_INIT(&entry->dp.hostlist_exclude_files); + memcpy(entry->dp.hostspell, "host", 4); // default hostspell + entry->dp.desync_skip_nosni = true; + entry->dp.desync_split_pos = 2; + entry->dp.desync_ipfrag_pos_udp = IPFRAG_UDP_DEFAULT; + entry->dp.desync_ipfrag_pos_tcp = IPFRAG_TCP_DEFAULT; + entry->dp.desync_repeats = 1; + entry->dp.fake_tls_size = sizeof(fake_tls_clienthello_default); + memcpy(entry->dp.fake_tls,fake_tls_clienthello_default,entry->dp.fake_tls_size); + randomize_default_tls_payload(entry->dp.fake_tls); + entry->dp.fake_http_size = strlen(fake_http_request_default); + memcpy(entry->dp.fake_http,fake_http_request_default,entry->dp.fake_http_size); + entry->dp.fake_quic_size = 620; // must be 601+ for TSPU hack + entry->dp.fake_quic[0] = 0x40; // russian TSPU QUIC short header fake + entry->dp.fake_wg_size = 64; + entry->dp.fake_dht_size = 64; + entry->dp.fake_unknown_size = 256; + entry->dp.fake_syndata_size = 16; + entry->dp.fake_unknown_udp_size = 64; + entry->dp.wscale=-1; // default - dont change scale factor (client) + entry->dp.desync_ttl6 = 0xFF; // unused + entry->dp.desync_badseq_increment = BADSEQ_INCREMENT_DEFAULT; + entry->dp.desync_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT; + entry->dp.wssize_cutoff_mode = entry->dp.desync_start_mode = entry->dp.desync_cutoff_mode = 'n'; // packet number by default + entry->dp.udplen_increment = UDPLEN_INCREMENT_DEFAULT; + entry->dp.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; + entry->dp.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; + entry->dp.hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT; + entry->dp.filter_ipv4 = entry->dp.filter_ipv6 = true; + + // 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_entry_destroy(struct desync_profile_list *entry) +{ + strlist_destroy(&entry->dp.hostlist_files); + strlist_destroy(&entry->dp.hostlist_exclude_files); + StrPoolDestroy(&entry->dp.hostlist_exclude); + StrPoolDestroy(&entry->dp.hostlist); + HostFailPoolDestroy(&entry->dp.hostlist_auto_fail_counters); + 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_filename) + return true; + return false; +} + diff --git a/nfq/params.h b/nfq/params.h new file mode 100644 index 00000000..b9dd3953 --- /dev/null +++ b/nfq/params.h @@ -0,0 +1,123 @@ +#pragma once + +#include "pools.h" +#include "conntrack.h" +#include "desync.h" +#include "protocol.h" +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TLS_PARTIALS_ENABLE true + +#define Q_RCVBUF (128*1024) // in bytes +#define Q_SNDBUF (64*1024) // in bytes +#define RAW_SNDBUF (64*1024) // in bytes + +#define Q_MAXLEN 1024 // in packets + +#define BADSEQ_INCREMENT_DEFAULT -10000 +#define BADSEQ_ACK_INCREMENT_DEFAULT -66000 + +#define IPFRAG_UDP_DEFAULT 8 +#define IPFRAG_TCP_DEFAULT 32 + +#define UDPLEN_INCREMENT_DEFAULT 2 + +#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 +#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 +#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3 + +enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG }; + +struct desync_profile +{ + int n; // number of the profile + + uint16_t wsize,wssize; + uint8_t wscale,wsscale; + char wssize_cutoff_mode; // n - packets, d - data packets, s - relative sequence + unsigned int wssize_cutoff; + + bool hostcase, hostnospace, domcase; + char hostspell[4]; + enum dpi_desync_mode desync_mode0,desync_mode,desync_mode2; + bool desync_retrans,desync_skip_nosni,desync_any_proto; + unsigned int desync_repeats,desync_split_pos,desync_seqovl,desync_ipfrag_pos_tcp,desync_ipfrag_pos_udp; + enum httpreqpos desync_split_http_req; + enum tlspos desync_split_tls; + char desync_start_mode, desync_cutoff_mode; // n - packets, d - data packets, s - relative sequence + unsigned int desync_start, desync_cutoff; + uint8_t desync_ttl, desync_ttl6; + autottl desync_autottl, desync_autottl6; + uint32_t desync_fooling_mode; + uint32_t desync_badseq_increment, desync_badseq_ack_increment; + uint8_t fake_http[1460],fake_tls[1460],fake_unknown[1460],fake_syndata[1460],seqovl_pattern[1460]; + uint8_t fake_unknown_udp[1472],udplen_pattern[1472],fake_quic[1472],fake_wg[1472],fake_dht[1472]; + size_t fake_http_size,fake_tls_size,fake_quic_size,fake_wg_size,fake_dht_size,fake_unknown_size,fake_syndata_size,fake_unknown_udp_size; + int udplen_increment; + + bool filter_ipv4,filter_ipv6; + port_filter pf_tcp,pf_udp; + strpool *hostlist, *hostlist_exclude; + struct str_list_head hostlist_files, hostlist_exclude_files; + char hostlist_auto_filename[PATH_MAX]; + int hostlist_auto_fail_threshold, hostlist_auto_fail_time, hostlist_auto_retrans_threshold; + time_t hostlist_auto_mod_time; + hostfail_pool *hostlist_auto_fail_counters; +}; + +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_list_destroy(struct desync_profile_list_head *head); +bool dp_list_have_autohostlist(struct desync_profile_list_head *head); + +struct params_s +{ + enum log_target debug_target; + char debug_logfile[PATH_MAX]; + bool debug; + +#ifdef __linux__ + int qnum; +#elif defined(BSD) + uint16_t port; // divert port +#endif + char bind_fix4,bind_fix6; + uint32_t desync_fwmark; // unused in BSD + + struct desync_profile_list_head desync_profiles; + +#ifdef __CYGWIN__ + struct str_list_head ssid_filter,nlm_filter; +#else + bool droproot; + uid_t uid; + gid_t gid; +#endif + + char hostlist_auto_debuglog[PATH_MAX]; + + unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp; + t_conntrack conntrack; +}; + +extern struct params_s params; +extern const char *progname; + +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, ...); diff --git a/nfq/pools.c b/nfq/pools.c new file mode 100644 index 00000000..785b04d3 --- /dev/null +++ b/nfq/pools.c @@ -0,0 +1,153 @@ +#define _GNU_SOURCE +#include "pools.h" +#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, strlen(elem->str), elem); \ + if (oom) \ + { \ + free(elem->str); \ + free(elem); \ + return false; \ + } + + +#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 StrPoolAddStrLen(strpool **pp, const char *s, size_t slen) +{ + ADD_STR_POOL(strpool, pp, s, slen) + return true; +} +// for zero terminated strings +bool StrPoolAddStr(strpool **pp, const char *s) +{ + return StrPoolAddStrLen(pp, s, strlen(s)); +} + +bool StrPoolCheckStr(strpool *p, const char *s) +{ + strpool *elem; + HASH_FIND_STR(p, s, elem); + return elem != NULL; +} + +void StrPoolDestroy(strpool **pp) +{ + DESTROY_STR_POOL(strpool, 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); +} + + +bool strlist_add(struct str_list_head *head, const char *filename) +{ + struct str_list *entry = malloc(sizeof(struct str_list)); + if (!entry) return false; + entry->str = strdup(filename); + if (!entry->str) + { + free(entry); + return false; + } + LIST_INSERT_HEAD(head, entry, next); + return true; +} +static void strlist_entry_destroy(struct str_list *entry) +{ + if (entry->str) 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); + } +} diff --git a/nfq/pools.h b/nfq/pools.h new file mode 100644 index 00000000..154d5419 --- /dev/null +++ b/nfq/pools.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +//#define HASH_BLOOM 20 +#define HASH_NONFATAL_OOM 1 +#define HASH_FUNCTION HASH_BER +#include "uthash.h" + +typedef struct strpool { + char *str; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} strpool; + +void StrPoolDestroy(strpool **pp); +bool StrPoolAddStr(strpool **pp,const char *s); +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen); +bool StrPoolCheckStr(strpool *p,const char *s); + +struct str_list { + char *str; + LIST_ENTRY(str_list) next; +}; +LIST_HEAD(str_list_head, str_list); + +typedef struct hostfail_pool { + char *str; /* key */ + int counter; /* value */ + time_t expire; /* when to expire record (unixtime) */ + UT_hash_handle hh; /* makes this structure hashable */ +} hostfail_pool; + +void HostFailPoolDestroy(hostfail_pool **pp); +hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); +hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); +void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); +void HostFailPoolPurge(hostfail_pool **pp); +void HostFailPoolPurgeRateLimited(hostfail_pool **pp); +void HostFailPoolDump(hostfail_pool *p); + +bool strlist_add(struct str_list_head *head, const char *filename); +void strlist_destroy(struct str_list_head *head); diff --git a/nfq/protocol.c b/nfq/protocol.c new file mode 100644 index 00000000..b83607e2 --- /dev/null +++ b/nfq/protocol.c @@ -0,0 +1,771 @@ +#define _GNU_SOURCE + +#include "protocol.h" +#include "helpers.h" +#include +#include +#include +#include + +const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL }; +const char *HttpMethod(const uint8_t *data, size_t len) +{ + const char **method; + size_t method_len; + for (method = http_methods; *method; method++) + { + method_len = strlen(*method); + if (method_len <= len && !memcmp(data, *method, method_len)) + return *method; + } + return NULL; +} +bool IsHttp(const uint8_t *data, size_t len) +{ + return !!HttpMethod(data,len); +} + +static bool IsHostAt(const uint8_t *p) +{ + return \ + p[0]=='\n' && + (p[1]=='H' || p[1]=='h') && + (p[2]=='o' || p[2]=='O') && + (p[3]=='s' || p[3]=='S') && + (p[4]=='t' || p[4]=='T') && + p[5]==':'; +} +static uint8_t *FindHostIn(uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} +static const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} +// pHost points to "Host: ..." +bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs) +{ + if (!*pHost) + { + *pHost = FindHostIn(buf, bs); + if (*pHost) (*pHost)++; + } + return !!*pHost; +} +bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs) +{ + if (!*pHost) + { + *pHost = FindHostInConst(buf, bs); + if (*pHost) (*pHost)++; + } + return !!*pHost; +} + +bool IsHttpReply(const uint8_t *data, size_t len) +{ + // HTTP/1.x 200\r\n + return len>14 && !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); +} +const char *HttpFind2ndLevelDomain(const char *host) +{ + const char *p=NULL; + if (*host) + { + for (p = host + strlen(host)-1; p>host && *p!='.'; p--); + if (*p=='.') for (p--; p>host && *p!='.'; p--); + if (*p=='.') p++; + } + return p; +} +// 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 = HttpFind2ndLevelDomain(host); + const char *drhost = HttpFind2ndLevelDomain(redirect_host); + + return strcasecmp(dhost, drhost)!=0; +} +size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz) +{ + const uint8_t *method, *host=NULL; + int i; + + switch(tpos_type) + { + case httpreqpos_method: + // recognize some tpws pre-applied hacks + method=http; + if (sz<10) break; + if (*method=='\n' || *method=='\r') method++; + if (*method=='\n' || *method=='\r') method++; + for (i=0;i<7;i++) if (*method>='A' && *method<='Z') method++; + if (i<3 || *method!=' ') break; + return method-http-1; + case httpreqpos_host: + if (HttpFindHostConst(&host,http,sz) && (host-http+7)= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); +} + +size_t TLSHandshakeLen(const uint8_t *data) +{ + return data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length +} +bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len) +{ + return len>=4 && data[0]==0x01 && TLSHandshakeLen(data)>0; +} +bool IsTLSHandshakeFull(const uint8_t *data, size_t len) +{ + return (4+TLSHandshakeLen(data))<=len; +} + + +// bPartialIsOK=true - accept partial packets not containing the whole TLS message +bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) +{ + // +0 + // u8 HandshakeType: ClientHello + // u24 Length + // u16 Version + // c[32] random + // u8 SessionIDLength + // + // u16 CipherSuitesLength + // + // u8 CompressionMethodsLength + // + // u16 ExtensionsLength + + size_t l; + + if (!bPartialIsOK && !IsTLSHandshakeFull(data,len)) return false; + + l = 1 + 3 + 2 + 32; + // SessionIDLength + if (len < (l + 1)) return false; + l += data[l] + 1; + // CipherSuitesLength + if (len < (l + 2)) return false; + l += pntoh16(data + l) + 2; + // CompressionMethodsLength + if (len < (l + 1)) return false; + l += data[l] + 1; + // ExtensionsLength + if (len < (l + 2)) return false; + + data += l; len -= l; + l = pntoh16(data); + data += 2; len -= 2; + + if (bPartialIsOK) + { + if (len < l) l = len; + } + else + { + if (len < l) return false; + } + + while (l >= 4) + { + uint16_t etype = pntoh16(data); + size_t elen = pntoh16(data + 2); + data += 4; l -= 4; + if (l < elen) break; + if (etype == type) + { + if (ext && len_ext) + { + *ext = data; + *len_ext = elen; + } + return true; + } + data += elen; l -= elen; + } + + return false; +} +bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) +{ + // +0 + // u8 ContentType: Handshake + // u16 Version: TLS1.0 + // u16 Length + size_t reclen; + if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; + reclen=TLSRecordLen(data); + if (reclen= len_host) slen = len_host - 1; + for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); + host[slen] = 0; + } + return true; +} +bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) +{ + const uint8_t *ext; + size_t elen; + + if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; + return TLSExtractHostFromExt(ext, elen, host, len_host); +} +bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) +{ + const uint8_t *ext; + size_t elen; + + if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; + return TLSExtractHostFromExt(ext, elen, host, len_host); +} +size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type) +{ + size_t elen; + const uint8_t *ext; + switch(tpos_type) + { + case tlspos_sni: + case tlspos_sniext: + if (TLSFindExt(tls,sz,0,&ext,&elen,false)) + return (tpos_type==tlspos_sni) ? ext-tls+6 : ext-tls+1; + // fall through + case tlspos_pos: + return tpos_pos> 6) + { + case 0: /* 0b00 => 1 byte length (6 bits Usable) */ + if (value) *value = *tvb & 0x3F; + return 1; + case 1: /* 0b01 => 2 bytes length (14 bits Usable) */ + if (value) *value = pntoh16(tvb) & 0x3FFF; + return 2; + case 2: /* 0b10 => 4 bytes length (30 bits Usable) */ + if (value) *value = pntoh32(tvb) & 0x3FFFFFFF; + return 4; + case 3: /* 0b11 => 8 bytes length (62 bits Usable) */ + if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF; + return 8; + } + // impossible case + if (*value) *value = 0; + return 0; +} +static uint8_t tvb_get_size(uint8_t tvb) +{ + return 1 << (tvb >> 6); +} + +bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len) +{ + size_t offset = 1; + uint64_t coff, clen; + if (len < 3 || *data != 6) return false; + if ((offset+tvb_get_size(data[offset])) >= len) return false; + offset += tvb_get_varint(data + offset, &coff); + // offset must be 0 if it's a full segment, not just a chunk + if (coff || (offset+tvb_get_size(data[offset])) >= len) return false; + offset += tvb_get_varint(data + offset, &clen); + if ((offset + clen) > len || !IsTLSHandshakeClientHello(data+offset,clen)) return false; + if (hello_offset) *hello_offset = offset; + if (hello_len) *hello_len = (size_t)clen; + return true; +} + +/* Returns the QUIC draft version or 0 if not applicable. */ +uint8_t QUICDraftVersion(uint32_t version) +{ + /* IETF Draft versions */ + if ((version >> 8) == 0xff0000) { + return (uint8_t)version; + } + /* Facebook mvfst, based on draft -22. */ + if (version == 0xfaceb001) { + return 22; + } + /* Facebook mvfst, based on draft -27. */ + if (version == 0xfaceb002 || version == 0xfaceb00e) { + return 27; + } + /* GQUIC Q050, T050 and T051: they are not really based on any drafts, + * but we must return a sensible value */ + if (version == 0x51303530 || + version == 0x54303530 || + version == 0x54303531) { + return 27; + } + /* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + "Versions that follow the pattern 0x?a?a?a?a are reserved for use in + forcing version negotiation to be exercised" + It is tricky to return a correct draft version: such number is primarily + used to select a proper salt (which depends on the version itself), but + we don't have a real version here! Let's hope that we need to handle + only latest drafts... */ + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + return 29; + } + /* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the + final draft version */ + if (version == 0x00000001) { + return 34; + } + /* QUIC Version 2 */ + /* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */ + if (version == 0x709A50C4) { + 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; + + aes_init_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); +} + +bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len) +{ + // 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; + + 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 ((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; + 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; + } + } + 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); + } + return found; +} + +bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello) +{ + if (bIsCryptoHello) *bIsCryptoHello=false; + if (bDecryptOK) *bDecryptOK=false; + + uint8_t clean[1500]; + size_t clean_len = sizeof(clean); + if (!QUICDecryptInitial(data,data_len,clean,&clean_len)) return false; + + if (bDecryptOK) *bDecryptOK=true; + + uint8_t defrag[1500]; + size_t defrag_len = sizeof(defrag); + if (!QUICDefragCrypto(clean,clean_len,defrag,&defrag_len)) return false; + + size_t hello_offset, hello_len; + if (!IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len)) return false; + if (bIsCryptoHello) *bIsCryptoHello=true; + + return TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, len_host, true); +} + +bool IsQUICInitial(const uint8_t *data, size_t len) +{ + // too small packets are not likely to be initials with client hello + // long header, fixed bit + if (len < 256 || (data[0] & 0xC0)!=0xC0) return false; + + uint32_t ver = QUICExtractVersion(data,len); + if (QUICDraftVersion(ver) < 11) return false; + + // quic v1 : initial packets are 00b + // quic v2 : initial packets are 01b + if ((data[0] & 0x30) != (is_quic_v2(ver) ? 0x10 : 0x00)) return false; + + uint64_t offset=5, sz; + + // DCID. must be present + if (!data[offset] || data[offset] > 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 IsWireguardHandshakeInitiation(const uint8_t *data, size_t len) +{ + return len==148 && data[0]==1 && data[1]==0 && data[2]==0 && data[3]==0; +} +bool IsDhtD1(const uint8_t *data, size_t len) +{ + return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e'; +} diff --git a/nfq/protocol.h b/nfq/protocol.h new file mode 100644 index 00000000..2264f81b --- /dev/null +++ b/nfq/protocol.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include "crypto/sha.h" +#include "crypto/aes-gcm.h" +#include "helpers.h" + +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); +enum httpreqpos { httpreqpos_none = 0, httpreqpos_method, httpreqpos_host, httpreqpos_pos }; +size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz); + +uint16_t TLSRecordDataLen(const uint8_t *data); +size_t TLSRecordLen(const uint8_t *data); +bool IsTLSRecordFull(const uint8_t *data, size_t len); +bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); +size_t TLSHandshakeLen(const uint8_t *data); +bool IsTLSHandshakeClientHello(const uint8_t *data, size_t len); +bool IsTLSHandshakeFull(const uint8_t *data, size_t len); +bool 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); +enum tlspos { tlspos_none = 0, tlspos_sni, tlspos_sniext, tlspos_pos }; +size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type); + +bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len); +bool IsDhtD1(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); +bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,size_t *defrag_len); +bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello); diff --git a/nfq/sec.c b/nfq/sec.c new file mode 100644 index 00000000..b6f8e66f --- /dev/null +++ b/nfq/sec.c @@ -0,0 +1,391 @@ +#define _GNU_SOURCE + +#include +#include +#include "sec.h" +#include +#include +#include + +#include "params.h" + +#ifdef __linux__ + +#include +#include +#include +#include +// __X32_SYSCALL_BIT defined in linux/unistd.h +#include +#include +#include + +/************ SECCOMP ************/ + +// block most of the undesired syscalls to harden against code execution +static long blocked_syscalls[] = { +#ifdef SYS_execv +SYS_execv, +#endif +SYS_execve, +#ifdef SYS_execveat +SYS_execveat, +#endif +#ifdef SYS_exec_with_loader +SYS_exec_with_loader, +#endif +#ifdef SYS_clone +SYS_clone, +#endif +#ifdef SYS_clone2 +SYS_clone2, +#endif +#ifdef SYS_clone3 +SYS_clone3, +#endif +#ifdef SYS_osf_execve +SYS_osf_execve, +#endif +#ifdef SYS_fork +SYS_fork, +#endif +#ifdef SYS_vfork +SYS_vfork, +#endif +#ifdef SYS_uselib +SYS_uselib, +#endif +#ifdef SYS_unlink +SYS_unlink, +#endif +SYS_unlinkat, +#ifdef SYS_chmod +SYS_chmod, +#endif +SYS_fchmod,SYS_fchmodat, +#ifdef SYS_chown +SYS_chown, +#endif +#ifdef SYS_chown32 +SYS_chown32, +#endif +SYS_fchown, +#ifdef SYS_fchown32 +SYS_fchown32, +#endif +#ifdef SYS_lchown +SYS_lchown, +#endif +#ifdef SYS_lchown32 +SYS_lchown32, +#endif +SYS_fchownat, +#ifdef SYS_symlink +SYS_symlink, +#endif +SYS_symlinkat, +#ifdef SYS_link +SYS_link, +#endif +SYS_linkat, +#ifdef SYS_pkey_mprotect +SYS_pkey_mprotect, +#endif +SYS_mprotect, +SYS_truncate, +#ifdef SYS_truncate64 +SYS_truncate64, +#endif +SYS_ftruncate, +#ifdef SYS_ftruncate64 +SYS_ftruncate64, +#endif +#ifdef SYS_mknod +SYS_mknod, +#endif +SYS_mknodat, +#ifdef SYS_mkdir +SYS_mkdir, +#endif +SYS_mkdirat, +#ifdef SYS_rmdir +SYS_rmdir, +#endif +#ifdef SYS_rename +SYS_rename, +#endif +#ifdef SYS_renameat2 +SYS_renameat2, +#endif +#ifdef SYS_renameat +SYS_renameat, +#endif +#ifdef SYS_readdir +SYS_readdir, +#endif +#ifdef SYS_getdents +SYS_getdents, +#endif +#ifdef SYS_getdents64 +SYS_getdents64, +#endif +#ifdef SYS_process_vm_readv +SYS_process_vm_readv, +#endif +#ifdef SYS_process_vm_writev +SYS_process_vm_writev, +#endif +#ifdef SYS_process_madvise +SYS_process_madvise, +#endif +#ifdef SYS_tkill +SYS_tkill, +#endif +#ifdef SYS_tgkill +SYS_tgkill, +#endif +SYS_kill, SYS_ptrace +}; +#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) + +static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) +{ + filter->code = code; + filter->jt = jt; + filter->jf = jf; + filter->k = k; +} +// deny all blocked syscalls +static bool set_seccomp(void) +{ +#ifdef __X32_SYSCALL_BIT + #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) +#else + #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) +#endif + struct sock_filter sockf[SECCOMP_PROG_SIZE]; + struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; + int i,idx=0; + + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); +#ifdef __X32_SYSCALL_BIT + // x86 only + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail +#else + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); +#endif + +/* + // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr +*/ + for(i=0 ; i= 0; +} + +bool sec_harden(void) +{ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + { + DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); + return 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"); + return false; + } +#endif + return true; +} + + + + +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, gid_t gid); +void print_id(void); +#endif + +void daemonize(void); +bool writepid(const char *filename); diff --git a/nfq/uthash.h b/nfq/uthash.h new file mode 100644 index 00000000..9a396b61 --- /dev/null +++ b/nfq/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/nfq/win.c b/nfq/win.c new file mode 100644 index 00000000..e135324f --- /dev/null +++ b/nfq/win.c @@ -0,0 +1,80 @@ +#ifdef __CYGWIN__ + +#include + +#include "win.h" +#include "nfqws.h" + +#define SERVICE_NAME "winws" + +static SERVICE_STATUS ServiceStatus; +static SERVICE_STATUS_HANDLE hStatus = NULL; +static int service_argc = 0; +static char **service_argv = NULL; + +void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))); + +bool service_run(int argc, char *argv[]) +{ + SERVICE_TABLE_ENTRY ServiceTable[] = { + {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main}, + {NULL, NULL} + }; + + service_argc = argc; + service_argv = argv; + + return StartServiceCtrlDispatcherA(ServiceTable); +} + +static void service_set_status(DWORD state) +{ + ServiceStatus.dwCurrentState = state; + SetServiceStatus(hStatus, &ServiceStatus); +} + +// Control handler function +void service_controlhandler(DWORD request) +{ + switch (request) + { + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + bQuit = true; + ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + break; + } + SetServiceStatus(hStatus, &ServiceStatus); +} + +void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +{ + ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ServiceStatus.dwCurrentState = SERVICE_RUNNING; + ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + ServiceStatus.dwWin32ExitCode = 0; + ServiceStatus.dwServiceSpecificExitCode = 0; + ServiceStatus.dwCheckPoint = 1; + ServiceStatus.dwWaitHint = 0; + + hStatus = RegisterServiceCtrlHandlerA( + SERVICE_NAME, + (LPHANDLER_FUNCTION)service_controlhandler); + if (hStatus == (SERVICE_STATUS_HANDLE)0) + { + // Registering Control Handler failed + return; + } + + SetServiceStatus(hStatus, &ServiceStatus); + + // Calling main with saved argc & argv + ServiceStatus.dwWin32ExitCode = (DWORD)main(service_argc, service_argv); + + ServiceStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(hStatus, &ServiceStatus); + return; +} + + +#endif diff --git a/nfq/win.h b/nfq/win.h new file mode 100644 index 00000000..13a09801 --- /dev/null +++ b/nfq/win.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef __CYGWIN__ + +#include + +bool service_run(); + +#endif + diff --git a/nfq/windivert/libwindivert.a b/nfq/windivert/libwindivert.a new file mode 100644 index 00000000..99f3d351 Binary files /dev/null and b/nfq/windivert/libwindivert.a differ diff --git a/nfq/windivert/windivert.h b/nfq/windivert/windivert.h new file mode 100644 index 00000000..fc63adf2 --- /dev/null +++ b/nfq/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 */ diff --git a/nfq/winicon.o b/nfq/winicon.o new file mode 100644 index 00000000..36180cb4 Binary files /dev/null and b/nfq/winicon.o differ diff --git a/nfq/winmanifest.o b/nfq/winmanifest.o new file mode 100644 index 00000000..0ca3b2f7 Binary files /dev/null and b/nfq/winmanifest.o differ diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/tpws/BSDmakefile b/tpws/BSDmakefile new file mode 100644 index 00000000..568f67b5 --- /dev/null +++ b/tpws/BSDmakefile @@ -0,0 +1,12 @@ +CC ?= cc +CFLAGS += -std=gnu99 -s -O3 +LIBS = -lz -lpthread +SRC_FILES = *.c + +all: tpws + +tpws: $(SRC_FILES) + $(CC) $(CFLAGS) -Iepoll-shim/include -o $@ $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS) + +clean: + rm -f tpws *.o diff --git a/tpws/Makefile b/tpws/Makefile new file mode 100644 index 00000000..52b72f73 --- /dev/null +++ b/tpws/Makefile @@ -0,0 +1,23 @@ +CC ?= gcc +CFLAGS += -std=gnu99 -O3 +CFLAGS_BSD = -Wno-address-of-packed-member +LIBS = -lz -lpthread +SRC_FILES = *.c + +all: tpws + +tpws: $(SRC_FILES) + $(CC) -s $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS) + +bsd: $(SRC_FILES) + $(CC) -s $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS) + +mac: $(SRC_FILES) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsa -target arm64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS) + $(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsx -target x86_64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS) + strip tpwsa tpwsx + lipo -create -output tpws tpwsx tpwsa + rm -f tpwsx tpwsa + +clean: + rm -f tpws *.o diff --git a/tpws/epoll-shim/include/sys/epoll.h b/tpws/epoll-shim/include/sys/epoll.h new file mode 100644 index 00000000..f96c0f10 --- /dev/null +++ b/tpws/epoll-shim/include/sys/epoll.h @@ -0,0 +1,80 @@ +#ifndef SHIM_SYS_EPOLL_H +#define SHIM_SYS_EPOLL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#if defined(__NetBSD__) +#include +#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__APPLE__) +#include +#endif + +#define EPOLL_CLOEXEC O_CLOEXEC +#define EPOLL_NONBLOCK O_NONBLOCK + +enum EPOLL_EVENTS { __EPOLL_DUMMY }; +#define EPOLLIN 0x001 +#define EPOLLPRI 0x002 +#define EPOLLOUT 0x004 +#define EPOLLRDNORM 0x040 +#define EPOLLNVAL 0x020 +#define EPOLLRDBAND 0x080 +#define EPOLLWRNORM 0x100 +#define EPOLLWRBAND 0x200 +#define EPOLLMSG 0x400 +#define EPOLLERR 0x008 +#define EPOLLHUP 0x010 +#define EPOLLRDHUP 0x2000 +#define EPOLLEXCLUSIVE (1U<<28) +#define EPOLLWAKEUP (1U<<29) +#define EPOLLONESHOT (1U<<30) +#define EPOLLET (1U<<31) + +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_DEL 2 +#define EPOLL_CTL_MOD 3 + +typedef union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; +} epoll_data_t; + +struct epoll_event { + uint32_t events; + epoll_data_t data; +} +#ifdef __x86_64__ +__attribute__ ((__packed__)) +#endif +; + + +int epoll_create(int); +int epoll_create1(int); +int epoll_ctl(int, int, int, struct epoll_event *); +int epoll_wait(int, struct epoll_event *, int, int); +int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *); + + +#ifndef SHIM_SYS_SHIM_HELPERS +#define SHIM_SYS_SHIM_HELPERS +#include /* IWYU pragma: keep */ + +extern int epoll_shim_close(int); +#define close epoll_shim_close +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* sys/epoll.h */ diff --git a/tpws/epoll-shim/src/epoll.c b/tpws/epoll-shim/src/epoll.c new file mode 100644 index 00000000..7b11653f --- /dev/null +++ b/tpws/epoll-shim/src/epoll.c @@ -0,0 +1,305 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "epoll_shim_ctx.h" + +#ifdef __NetBSD__ +#define ppoll pollts +#endif + +// TODO(jan): Remove this once the definition is exposed in in +// all supported FreeBSD versions. +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + +static errno_t +epollfd_close(FDContextMapNode *node) +{ + return epollfd_ctx_terminate(&node->ctx.epollfd); +} + +static FDContextVTable const epollfd_vtable = { + .read_fun = fd_context_default_read, + .write_fun = fd_context_default_write, + .close_fun = epollfd_close, +}; + +static FDContextMapNode * +epoll_create_impl(errno_t *ec) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); + if (!node) { + return NULL; + } + + node->flags = 0; + + if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/ + node->fd)) != 0) { + goto fail; + } + + node->vtable = &epollfd_vtable; + return node; + +fail: + epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); + (void)fd_context_map_node_destroy(node); + return NULL; +} + +static int +epoll_create_common(void) +{ + FDContextMapNode *node; + errno_t ec; + + node = epoll_create_impl(&ec); + if (!node) { + errno = ec; + return -1; + } + + return node->fd; +} + +int +epoll_create(int size) +{ + if (size <= 0) { + errno = EINVAL; + return -1; + } + + return epoll_create_common(); +} + +int +epoll_create1(int flags) +{ + if (flags & ~EPOLL_CLOEXEC) { + errno = EINVAL; + return -1; + } + + return epoll_create_common(); +} + +static errno_t +epoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev) +{ + if (!ev && op != EPOLL_CTL_DEL) { + return EFAULT; + } + + FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &epollfd_vtable) { + struct stat sb; + return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; + } + + return epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev); +} + +int +epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) +{ + errno_t ec = epoll_ctl_impl(fd, op, fd2, ev); + if (ec != 0) { + errno = ec; + return -1; + } + + return 0; +} + +static bool +is_no_wait_deadline(struct timespec const *deadline) +{ + return (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0); +} + +static errno_t +epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs) +{ + errno_t ec; + + for (;;) { + if ((ec = epollfd_ctx_wait(epollfd, /**/ + ev, cnt, actual_cnt)) != 0) { + return ec; + } + + if (*actual_cnt || is_no_wait_deadline(deadline)) { + return 0; + } + + struct timespec timeout; + + if (deadline) { + struct timespec current_time; + + if (clock_gettime(CLOCK_MONOTONIC, /**/ + ¤t_time) < 0) { + return errno; + } + + timespecsub(deadline, ¤t_time, &timeout); + if (timeout.tv_sec < 0 || + is_no_wait_deadline(&timeout)) { + return 0; + } + } + + (void)pthread_mutex_lock(&epollfd->mutex); + + nfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size); + + size_t size; + if (__builtin_mul_overflow(nfds, sizeof(struct pollfd), + &size)) { + ec = ENOMEM; + (void)pthread_mutex_unlock(&epollfd->mutex); + return ec; + } + + struct pollfd *pfds = malloc(size); + if (!pfds) { + ec = errno; + (void)pthread_mutex_unlock(&epollfd->mutex); + return ec; + } + + epollfd_ctx_fill_pollfds(epollfd, pfds); + + (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); + ++epollfd->nr_polling_threads; + (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); + + (void)pthread_mutex_unlock(&epollfd->mutex); + + /* + * This surfaced a race condition when + * registering/unregistering poll-only fds. The tests should + * still succeed if this is enabled. + */ +#if 0 + usleep(500000); +#endif + + int n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs); + if (n < 0) { + ec = errno; + } + + free(pfds); + + (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); + --epollfd->nr_polling_threads; + if (epollfd->nr_polling_threads == 0) { + (void)pthread_cond_signal( + &epollfd->nr_polling_threads_cond); + } + (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); + + if (n < 0) { + return ec; + } + } +} + +static errno_t +timeout_to_deadline(struct timespec *deadline, int to) +{ + assert(to >= 0); + + if (to == 0) { + *deadline = (struct timespec){0, 0}; + } else if (to > 0) { + if (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) { + return errno; + } + + if (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1, + &deadline->tv_sec)) { + return EINVAL; + } + deadline->tv_sec -= 1; + + deadline->tv_nsec += (to % 1000) * 1000000L; + if (deadline->tv_nsec >= 1000000000) { + deadline->tv_nsec -= 1000000000; + deadline->tv_sec += 1; + } + } + + return 0; +} + +static errno_t +epoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to, + sigset_t const *sigs, int *actual_cnt) +{ + if (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) { + return EINVAL; + } + + FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node || node->vtable != &epollfd_vtable) { + struct stat sb; + return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; + } + + struct timespec deadline; + errno_t ec; + if (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) { + return ec; + } + + return epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt, + actual_cnt, (to >= 0) ? &deadline : NULL, sigs); +} + +int +epoll_pwait(int fd, struct epoll_event *ev, int cnt, int to, + sigset_t const *sigs) +{ + int actual_cnt; + + errno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt); + if (ec != 0) { + errno = ec; + return -1; + } + + return actual_cnt; +} + +int +epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) +{ + return epoll_pwait(fd, ev, cnt, to, NULL); +} diff --git a/tpws/epoll-shim/src/epoll_shim_ctx.c b/tpws/epoll-shim/src/epoll_shim_ctx.c new file mode 100644 index 00000000..ac89f5fc --- /dev/null +++ b/tpws/epoll-shim/src/epoll_shim_ctx.c @@ -0,0 +1,281 @@ +#include "epoll_shim_ctx.h" + +#include + +#include +#include +#include +#include + +static void +fd_context_map_node_init(FDContextMapNode *node, int kq) +{ + node->fd = kq; + node->vtable = NULL; +} + +static FDContextMapNode * +fd_context_map_node_create(int kq, errno_t *ec) +{ + FDContextMapNode *node; + + node = malloc(sizeof(FDContextMapNode)); + if (!node) { + *ec = errno; + return NULL; + } + + fd_context_map_node_init(node, kq); + return node; +} + +static errno_t +fd_context_map_node_terminate(FDContextMapNode *node, bool close_fd) +{ + errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0; + + if (close_fd && close(node->fd) < 0) { + ec = ec ? ec : errno; + } + + return ec; +} + +errno_t +fd_context_map_node_destroy(FDContextMapNode *node) +{ + errno_t ec = fd_context_map_node_terminate(node, true); + free(node); + return ec; +} + +/**/ + +errno_t +fd_context_default_read(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred) +{ + (void)node; + (void)buf; + (void)nbytes; + (void)bytes_transferred; + + return EINVAL; +} + +errno_t +fd_context_default_write(FDContextMapNode *node, /**/ + void const *buf, size_t nbytes, size_t *bytes_transferred) +{ + (void)node; + (void)buf; + (void)nbytes; + (void)bytes_transferred; + + return EINVAL; +} + +/**/ + +static int +fd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2) +{ + return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); +} + +RB_PROTOTYPE_STATIC(fd_context_map_, fd_context_map_node_, entry, + fd_context_map_node_cmp); +RB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry, + fd_context_map_node_cmp); + +EpollShimCtx epoll_shim_ctx = { + .fd_context_map = RB_INITIALIZER(&fd_context_map), + .mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +static FDContextMapNode * +epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq, + errno_t *ec) +{ + FDContextMapNode *node; + + { + FDContextMapNode find; + find.fd = kq; + + node = RB_FIND(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, &find); + } + + if (node) { + /* + * If we get here, someone must have already closed the old fd + * with a normal 'close()' call, i.e. not with our + * 'epoll_shim_close()' wrapper. The fd inside the node + * refers now to the new kq we are currently creating. We + * must not close it, but we must clean up the old context + * object! + */ + (void)fd_context_map_node_terminate(node, false); + fd_context_map_node_init(node, kq); + } else { + node = fd_context_map_node_create(kq, ec); + if (!node) { + return NULL; + } + + void *colliding_node = RB_INSERT(fd_context_map_, + &epoll_shim_ctx->fd_context_map, node); + (void)colliding_node; + assert(colliding_node == NULL); + } + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec) +{ + FDContextMapNode *node; + + int kq = kqueue(); + if (kq < 0) { + *ec = errno; + return NULL; + } + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + if (!node) { + close(kq); + } + + return node; +} + +static FDContextMapNode * +epoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + FDContextMapNode find; + find.fd = fd; + + node = RB_FIND(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, &find); + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + return node; +} + +FDContextMapNode * +epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd) +{ + FDContextMapNode *node; + + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd); + if (node) { + RB_REMOVE(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, node); + } + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); + + return node; +} + +void +epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, + FDContextMapNode *node) +{ + (void)pthread_mutex_lock(&epoll_shim_ctx->mutex); + RB_REMOVE(fd_context_map_, /**/ + &epoll_shim_ctx->fd_context_map, node); + (void)pthread_mutex_unlock(&epoll_shim_ctx->mutex); +} + +/**/ + +int +epoll_shim_close(int fd) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd); + if (!node) { + return close(fd); + } + + errno_t ec = fd_context_map_node_destroy(node); + if (ec != 0) { + errno = ec; + return -1; + } + + return 0; +} + +ssize_t +epoll_shim_read(int fd, void *buf, size_t nbytes) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node) { + return read(fd, buf, nbytes); + } + + if (nbytes > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + size_t bytes_transferred; + errno_t ec = node->vtable->read_fun(node, /**/ + buf, nbytes, &bytes_transferred); + if (ec != 0) { + errno = ec; + return -1; + } + + return (ssize_t)bytes_transferred; +} + +ssize_t +epoll_shim_write(int fd, void const *buf, size_t nbytes) +{ + FDContextMapNode *node; + + node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); + if (!node) { + return write(fd, buf, nbytes); + } + + if (nbytes > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + size_t bytes_transferred; + errno_t ec = node->vtable->write_fun(node, /**/ + buf, nbytes, &bytes_transferred); + if (ec != 0) { + errno = ec; + return -1; + } + + return (ssize_t)bytes_transferred; +} diff --git a/tpws/epoll-shim/src/epoll_shim_ctx.h b/tpws/epoll-shim/src/epoll_shim_ctx.h new file mode 100644 index 00000000..01ae19a7 --- /dev/null +++ b/tpws/epoll-shim/src/epoll_shim_ctx.h @@ -0,0 +1,76 @@ +#ifndef EPOLL_SHIM_CTX_H_ +#define EPOLL_SHIM_CTX_H_ + +#include "fix.h" + +#include + +#include + +#include "epollfd_ctx.h" +#include "eventfd_ctx.h" +#include "signalfd_ctx.h" +#include "timerfd_ctx.h" + +struct fd_context_map_node_; +typedef struct fd_context_map_node_ FDContextMapNode; + +typedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred); +typedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/ + const void *buf, size_t nbytes, size_t *bytes_transferred); +typedef errno_t (*fd_context_close_fun)(FDContextMapNode *node); + +typedef struct { + fd_context_read_fun read_fun; + fd_context_write_fun write_fun; + fd_context_close_fun close_fun; +} FDContextVTable; + +errno_t fd_context_default_read(FDContextMapNode *node, /**/ + void *buf, size_t nbytes, size_t *bytes_transferred); +errno_t fd_context_default_write(FDContextMapNode *node, /**/ + void const *buf, size_t nbytes, size_t *bytes_transferred); + +struct fd_context_map_node_ { + RB_ENTRY(fd_context_map_node_) entry; + int fd; + int flags; + union { + EpollFDCtx epollfd; + EventFDCtx eventfd; + TimerFDCtx timerfd; + SignalFDCtx signalfd; + } ctx; + FDContextVTable const *vtable; +}; + +errno_t fd_context_map_node_destroy(FDContextMapNode *node); + +/**/ + +typedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap; + +typedef struct { + FDContextMap fd_context_map; + pthread_mutex_t mutex; +} EpollShimCtx; + +extern EpollShimCtx epoll_shim_ctx; + +FDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, + errno_t *ec); +FDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, + int fd); +FDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, + int fd); +void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx, + FDContextMapNode *node); + +/**/ + +int epoll_shim_close(int fd); +ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes); +ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes); + +#endif diff --git a/tpws/epoll-shim/src/epollfd_ctx.c b/tpws/epoll-shim/src/epollfd_ctx.c new file mode 100644 index 00000000..baf3dc28 --- /dev/null +++ b/tpws/epoll-shim/src/epollfd_ctx.c @@ -0,0 +1,1386 @@ +#include "epollfd_ctx.h" + +#include + +#if defined(__FreeBSD__) +#include +#endif +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) +/* For TAILQ_FOREACH_SAFE. */ +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +static RegisteredFDsNode * +registered_fds_node_create(int fd) +{ + RegisteredFDsNode *node; + + node = malloc(sizeof(*node)); + if (!node) { + return NULL; + } + + *node = (RegisteredFDsNode){.fd = fd, .self_pipe = {-1, -1}}; + + return node; +} + +static void +registered_fds_node_destroy(RegisteredFDsNode *node) +{ + if (node->self_pipe[0] >= 0 && node->self_pipe[1] >= 0) { + (void)close(node->self_pipe[0]); + (void)close(node->self_pipe[1]); + } + + free(node); +} + +typedef struct { + int evfilt_read; + int evfilt_write; + int evfilt_except; +} NeededFilters; + +static NeededFilters +get_needed_filters(RegisteredFDsNode *fd2_node) +{ + NeededFilters needed_filters; + + needed_filters.evfilt_except = 0; + + if (fd2_node->node_type == NODE_TYPE_FIFO) { + if (fd2_node->node_data.fifo.readable && + fd2_node->node_data.fifo.writable) { + needed_filters.evfilt_read = !!( + fd2_node->events & EPOLLIN); + needed_filters.evfilt_write = !!( + fd2_node->events & EPOLLOUT); + + if (fd2_node->events == 0) { + needed_filters.evfilt_read = + fd2_node->eof_state ? 1 : EV_CLEAR; + } + + } else if (fd2_node->node_data.fifo.readable) { + needed_filters.evfilt_read = !!( + fd2_node->events & EPOLLIN); + needed_filters.evfilt_write = 0; + + if (needed_filters.evfilt_read == 0) { + needed_filters.evfilt_read = + fd2_node->eof_state ? 1 : EV_CLEAR; + } + } else if (fd2_node->node_data.fifo.writable) { + needed_filters.evfilt_read = 0; + needed_filters.evfilt_write = !!( + fd2_node->events & EPOLLOUT); + + if (needed_filters.evfilt_write == 0) { + needed_filters.evfilt_write = + fd2_node->eof_state ? 1 : EV_CLEAR; + } + } else { + __builtin_unreachable(); + } + + goto out; + } + + if (fd2_node->node_type == NODE_TYPE_KQUEUE) { + needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); + needed_filters.evfilt_write = 0; + + assert(fd2_node->eof_state == 0); + + if (needed_filters.evfilt_read == 0) { + needed_filters.evfilt_read = EV_CLEAR; + } + + goto out; + } + + if (fd2_node->node_type == NODE_TYPE_SOCKET) { + needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); + + if (needed_filters.evfilt_read == 0 && + (fd2_node->events & EPOLLRDHUP)) { + needed_filters.evfilt_read = (fd2_node->eof_state & + EOF_STATE_READ_EOF) + ? 1 + : EV_CLEAR; + } + +#ifdef EVFILT_EXCEPT + needed_filters.evfilt_except = !!(fd2_node->events & EPOLLPRI); +#else + if (needed_filters.evfilt_read == 0 && + (fd2_node->events & EPOLLPRI)) { + needed_filters.evfilt_read = fd2_node->pollpri_active + ? 1 + : EV_CLEAR; + } +#endif + + needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); + + /* Let's use EVFILT_READ to drive the POLLHUP. */ + if (fd2_node->eof_state == + (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { + if (needed_filters.evfilt_read != 1 && + needed_filters.evfilt_write != 1) { + needed_filters.evfilt_read = 1; + } + + if (needed_filters.evfilt_read) { + needed_filters.evfilt_write = 0; + } else { + needed_filters.evfilt_read = 0; + } + } + + /* We need something to detect POLLHUP. */ + if (fd2_node->eof_state == 0 && + needed_filters.evfilt_read == 0 && + needed_filters.evfilt_write == 0) { + needed_filters.evfilt_read = EV_CLEAR; + } + + if (fd2_node->eof_state == EOF_STATE_READ_EOF) { + if (needed_filters.evfilt_write == 0) { + needed_filters.evfilt_write = EV_CLEAR; + } + } + + if (fd2_node->eof_state == EOF_STATE_WRITE_EOF) { + if (needed_filters.evfilt_read == 0) { + needed_filters.evfilt_read = EV_CLEAR; + } + } + + goto out; + } + + needed_filters.evfilt_read = !!(fd2_node->events & EPOLLIN); + needed_filters.evfilt_write = !!(fd2_node->events & EPOLLOUT); + + if (fd2_node->events == 0) { + needed_filters.evfilt_read = fd2_node->eof_state ? 1 + : EV_CLEAR; + } + +out: + if (fd2_node->is_edge_triggered) { + if (needed_filters.evfilt_read) { + needed_filters.evfilt_read = EV_CLEAR; + } + if (needed_filters.evfilt_write) { + needed_filters.evfilt_write = EV_CLEAR; + } + if (needed_filters.evfilt_except) { + needed_filters.evfilt_except = EV_CLEAR; + } + } + + assert(needed_filters.evfilt_read || needed_filters.evfilt_write); + assert(needed_filters.evfilt_read == 0 || + needed_filters.evfilt_read == 1 || + needed_filters.evfilt_read == EV_CLEAR); + assert(needed_filters.evfilt_write == 0 || + needed_filters.evfilt_write == 1 || + needed_filters.evfilt_write == EV_CLEAR); + assert(needed_filters.evfilt_except == 0 || + needed_filters.evfilt_except == 1 || + needed_filters.evfilt_except == EV_CLEAR); + + return needed_filters; +} + +static void +registered_fds_node_update_flags_from_epoll_event(RegisteredFDsNode *fd2_node, + struct epoll_event *ev) +{ + fd2_node->events = ev->events & + (EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLOUT); + fd2_node->data = ev->data; + fd2_node->is_edge_triggered = ev->events & EPOLLET; + fd2_node->is_oneshot = ev->events & EPOLLONESHOT; + + if (fd2_node->is_oneshot) { + fd2_node->is_edge_triggered = true; + } +} + +static errno_t +registered_fds_node_add_self_trigger(RegisteredFDsNode *fd2_node, + EpollFDCtx *epollfd) +{ + struct kevent kevs[1]; + +#ifdef EVFILT_USER + EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ + EV_ADD | EV_CLEAR, 0, 0, fd2_node); +#else + if (fd2_node->self_pipe[0] < 0 && fd2_node->self_pipe[1] < 0) { + if (pipe2(fd2_node->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { + errno_t ec = errno; + fd2_node->self_pipe[0] = fd2_node->self_pipe[1] = -1; + return ec; + } + + assert(fd2_node->self_pipe[0] >= 0); + assert(fd2_node->self_pipe[1] >= 0); + } + + EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ + EV_ADD | EV_CLEAR, 0, 0, fd2_node); +#endif + + if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { + return errno; + } + + return 0; +} + +static void +registered_fds_node_trigger_self(RegisteredFDsNode *fd2_node, + EpollFDCtx *epollfd) +{ +#ifdef EVFILT_USER + struct kevent kevs[1]; + EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ + 0, NOTE_TRIGGER, 0, fd2_node); + (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); +#else + (void)epollfd; + assert(fd2_node->self_pipe[1] >= 0); + + char c = 0; + (void)write(fd2_node->self_pipe[1], &c, 1); +#endif +} + +static void +registered_fds_node_feed_event(RegisteredFDsNode *fd2_node, + EpollFDCtx *epollfd, struct kevent const *kev) +{ + int revents = 0; + + if (fd2_node->node_type == NODE_TYPE_POLL) { + assert(fd2_node->revents == 0); + +#ifdef EVFILT_USER + assert(kev->filter == EVFILT_USER); +#else + char c[32]; + while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { + } +#endif + + struct pollfd pfd = { + .fd = fd2_node->fd, + .events = (short)fd2_node->events, + }; + + revents = poll(&pfd, 1, 0) < 0 ? EPOLLERR : pfd.revents; + + fd2_node->revents = revents & POLLNVAL ? 0 : (uint32_t)revents; + assert(!(fd2_node->revents & + ~(uint32_t)(POLLIN | POLLOUT | POLLERR | POLLHUP))); + return; + } + + if (fd2_node->node_type == NODE_TYPE_FIFO && +#ifdef EVFILT_USER + kev->filter == EVFILT_USER +#else + (fd2_node->self_pipe[0] >= 0 && + kev->ident == (uintptr_t)fd2_node->self_pipe[0]) +#endif + ) { + assert(fd2_node->revents == 0); + + assert(!fd2_node->has_evfilt_read); + assert(!fd2_node->has_evfilt_write); + assert(!fd2_node->has_evfilt_except); + + NeededFilters needed_filters = get_needed_filters(fd2_node); + assert(needed_filters.evfilt_write); + + struct kevent nkev[1]; + EV_SET(&nkev[0], fd2_node->fd, EVFILT_WRITE, + EV_ADD | (needed_filters.evfilt_write & EV_CLEAR) | + EV_RECEIPT, + 0, 0, fd2_node); + + if (kevent(epollfd->kq, nkev, 1, nkev, 1, NULL) != 1 || + nkev[0].data != 0) { + revents = EPOLLERR | EPOLLOUT; + + if (!fd2_node->is_edge_triggered) { + registered_fds_node_trigger_self(fd2_node, + epollfd); + } + + goto out; + } else { + fd2_node->has_evfilt_write = true; + return; + } + } + +#ifdef EVFILT_EXCEPT + assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE || + kev->filter == EVFILT_EXCEPT); +#else + assert(kev->filter == EVFILT_READ || kev->filter == EVFILT_WRITE); +#endif + assert((int)kev->ident == fd2_node->fd); + + if (kev->filter == EVFILT_READ) { + revents |= EPOLLIN; +#ifndef EVFILT_EXCEPT + if (fd2_node->events & EPOLLPRI) { + struct pollfd pfd = { + .fd = fd2_node->fd, + .events = POLLPRI, + }; + + if ((poll(&pfd, 1, 0) == 1) && + (pfd.revents & POLLPRI)) { + revents |= EPOLLPRI; + fd2_node->pollpri_active = true; + } else { + fd2_node->pollpri_active = false; + } + } +#endif + } else if (kev->filter == EVFILT_WRITE) { + revents |= EPOLLOUT; + } +#ifdef EVFILT_EXCEPT + else if (kev->filter == EVFILT_EXCEPT) { + assert((kev->fflags & NOTE_OOB) != 0); + + revents |= EPOLLPRI; + goto out; + } +#endif + + if (fd2_node->node_type == NODE_TYPE_SOCKET) { + if (kev->filter == EVFILT_READ) { + if (kev->flags & EV_EOF) { + fd2_node->eof_state |= EOF_STATE_READ_EOF; + } else { + fd2_node->eof_state &= ~EOF_STATE_READ_EOF; + } + } else if (kev->filter == EVFILT_WRITE) { + if (kev->flags & EV_EOF) { + fd2_node->eof_state |= EOF_STATE_WRITE_EOF; + } else { + fd2_node->eof_state &= ~EOF_STATE_WRITE_EOF; + } + } + } else { + if (kev->filter == EVFILT_READ) { + if (kev->flags & EV_EOF) { + fd2_node->eof_state = EOF_STATE_READ_EOF | + EOF_STATE_WRITE_EOF; + } else { + fd2_node->eof_state = 0; + } + } else if (kev->filter == EVFILT_WRITE) { + if (kev->flags & EV_EOF) { + fd2_node->eof_state = EOF_STATE_READ_EOF | + EOF_STATE_WRITE_EOF; + } else { + fd2_node->eof_state = 0; + } + } + } + + if (kev->flags & EV_ERROR) { + revents |= EPOLLERR; + } + + if (kev->flags & EV_EOF) { + if (kev->fflags) { + revents |= EPOLLERR; + } + } + + if (fd2_node->eof_state) { + int epoll_event; + + if (fd2_node->node_type == NODE_TYPE_FIFO) { + if (kev->filter == EVFILT_READ) { + epoll_event = EPOLLHUP; + if (kev->data == 0) { + revents &= ~EPOLLIN; + } + } else if (kev->filter == EVFILT_WRITE) { + if (fd2_node->has_evfilt_read) { + assert( + fd2_node->node_data.fifo.readable); + assert( + fd2_node->node_data.fifo.writable); + + /* + * Any non-zero revents must have come + * from the EVFILT_READ filter. It + * could either be "POLLIN", + * "POLLIN | POLLHUP" or "POLLHUP", so + * we know if there is data to read. + * But we also know that the FIFO is + * done, so set POLLHUP because it + * would be set anyway. + * + * If revents is zero, not setting it + * will simply ignore this EVFILT_WRITE + * and wait for the next EVFILT_READ + * (which will be EOF). + */ + + if (fd2_node->revents != 0) { + fd2_node->revents |= POLLHUP; + } + return; + } + + epoll_event = EPOLLERR; + if (kev->data < PIPE_BUF) { + revents &= ~EPOLLOUT; + } + } else { + __builtin_unreachable(); + } + } else if (fd2_node->node_type == NODE_TYPE_SOCKET) { + epoll_event = 0; + + if (fd2_node->eof_state & EOF_STATE_READ_EOF) { + epoll_event |= EPOLLIN | EPOLLRDHUP; + } + + if (fd2_node->eof_state & EOF_STATE_WRITE_EOF) { + epoll_event |= EPOLLOUT; + } + + if (fd2_node->eof_state == + (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF)) { + epoll_event |= EPOLLHUP; + } + } else { + epoll_event = EPOLLHUP; + } + + revents |= epoll_event; + } + +out: + fd2_node->revents |= (uint32_t)revents; + fd2_node->revents &= (fd2_node->events | EPOLLHUP | EPOLLERR); + + if (fd2_node->revents && (uintptr_t)fd2_node->fd == kev->ident) { + if (kev->filter == EVFILT_READ) { + fd2_node->got_evfilt_read = true; + } else if (kev->filter == EVFILT_WRITE) { + fd2_node->got_evfilt_write = true; + } +#ifdef EVFILT_EXCEPT + else if (kev->filter == EVFILT_EXCEPT) { + fd2_node->got_evfilt_except = true; + } +#endif + } +} + +static void +registered_fds_node_register_for_completion(int *kq, + RegisteredFDsNode *fd2_node) +{ + struct kevent kev[3]; + int n = 0; + + if (fd2_node->has_evfilt_read && !fd2_node->got_evfilt_read) { + EV_SET(&kev[n++], fd2_node->fd, EVFILT_READ, + EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); + } + if (fd2_node->has_evfilt_write && !fd2_node->got_evfilt_write) { + EV_SET(&kev[n++], fd2_node->fd, EVFILT_WRITE, + EV_ADD | EV_ONESHOT | EV_RECEIPT, 0, 0, fd2_node); + } + if (fd2_node->has_evfilt_except && !fd2_node->got_evfilt_except) { +#ifdef EVFILT_EXCEPT + EV_SET(&kev[n++], fd2_node->fd, EVFILT_EXCEPT, + EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_OOB, 0, fd2_node); +#else + assert(0); +#endif + } + + if (n == 0) { + return; + } + + if (*kq < 0) { + *kq = kqueue(); + } + + if (*kq >= 0) { + (void)kevent(*kq, kev, n, kev, n, NULL); + } +} + +static void +registered_fds_node_complete(int kq) +{ + if (kq < 0) { + return; + } + + struct kevent kevs[32]; + int n; + + while ((n = kevent(kq, /**/ + NULL, 0, kevs, 32, &(struct timespec){0, 0})) > 0) { + for (int i = 0; i < n; ++i) { + RegisteredFDsNode *fd2_node = + (RegisteredFDsNode *)kevs[i].udata; + + registered_fds_node_feed_event(fd2_node, NULL, + &kevs[i]); + } + } + + (void)close(kq); +} + +static int +fd_cmp(RegisteredFDsNode *e1, RegisteredFDsNode *e2) +{ + return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd); +} + +RB_PROTOTYPE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); +RB_GENERATE_STATIC(registered_fds_set_, registered_fds_node_, entry, fd_cmp); + +errno_t +epollfd_ctx_init(EpollFDCtx *epollfd, int kq) +{ + errno_t ec; + + *epollfd = (EpollFDCtx){ + .kq = kq, + .registered_fds = RB_INITIALIZER(®istered_fds), + .self_pipe = {-1, -1}, + }; + + TAILQ_INIT(&epollfd->poll_fds); + + if ((ec = pthread_mutex_init(&epollfd->mutex, NULL)) != 0) { + return ec; + } + + if ((ec = pthread_mutex_init(&epollfd->nr_polling_threads_mutex, + NULL)) != 0) { + pthread_mutex_destroy(&epollfd->mutex); + return ec; + } + + if ((ec = pthread_cond_init(&epollfd->nr_polling_threads_cond, + NULL)) != 0) { + pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); + pthread_mutex_destroy(&epollfd->mutex); + return ec; + } + + return 0; +} + +errno_t +epollfd_ctx_terminate(EpollFDCtx *epollfd) +{ + errno_t ec = 0; + errno_t ec_local; + + ec_local = pthread_cond_destroy(&epollfd->nr_polling_threads_cond); + ec = ec ? ec : ec_local; + ec_local = pthread_mutex_destroy(&epollfd->nr_polling_threads_mutex); + ec = ec ? ec : ec_local; + ec_local = pthread_mutex_destroy(&epollfd->mutex); + ec = ec ? ec : ec_local; + + RegisteredFDsNode *np; + RegisteredFDsNode *np_temp; + RB_FOREACH_SAFE(np, registered_fds_set_, &epollfd->registered_fds, + np_temp) + { + RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, np); + registered_fds_node_destroy(np); + } + + free(epollfd->kevs); + free(epollfd->pfds); + if (epollfd->self_pipe[0] >= 0 && epollfd->self_pipe[1] >= 0) { + (void)close(epollfd->self_pipe[0]); + (void)close(epollfd->self_pipe[1]); + } + + return ec; +} + +static errno_t +epollfd_ctx_make_kevs_space(EpollFDCtx *epollfd, size_t cnt) +{ + assert(cnt > 0); + + if (cnt <= epollfd->kevs_length) { + return 0; + } + + size_t size; + if (__builtin_mul_overflow(cnt, sizeof(struct kevent), &size)) { + return ENOMEM; + } + + struct kevent *new_kevs = realloc(epollfd->kevs, size); + if (!new_kevs) { + return errno; + } + + epollfd->kevs = new_kevs; + epollfd->kevs_length = cnt; + + return 0; +} + +static errno_t +epollfd_ctx_make_pfds_space(EpollFDCtx *epollfd) +{ + size_t cnt = 1 + epollfd->poll_fds_size; + + if (cnt <= epollfd->pfds_length) { + return 0; + } + + size_t size; + if (__builtin_mul_overflow(cnt, sizeof(struct pollfd), &size)) { + return ENOMEM; + } + + struct pollfd *new_pfds = realloc(epollfd->pfds, size); + if (!new_pfds) { + return errno; + } + + epollfd->pfds = new_pfds; + epollfd->pfds_length = cnt; + + return 0; +} + +static errno_t +epollfd_ctx__add_self_trigger(EpollFDCtx *epollfd) +{ + struct kevent kevs[1]; + +#ifdef EVFILT_USER + EV_SET(&kevs[0], 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0); +#else + if (epollfd->self_pipe[0] < 0 && epollfd->self_pipe[1] < 0) { + if (pipe2(epollfd->self_pipe, O_NONBLOCK | O_CLOEXEC) < 0) { + errno_t ec = errno; + epollfd->self_pipe[0] = epollfd->self_pipe[1] = -1; + return ec; + } + + assert(epollfd->self_pipe[0] >= 0); + assert(epollfd->self_pipe[1] >= 0); + } + + EV_SET(&kevs[0], epollfd->self_pipe[0], EVFILT_READ, /**/ + EV_ADD | EV_CLEAR, 0, 0, 0); +#endif + + if (kevent(epollfd->kq, kevs, 1, NULL, 0, NULL) < 0) { + return errno; + } + + return 0; +} + +static void +epollfd_ctx__trigger_self(EpollFDCtx *epollfd) +{ +#ifdef EVFILT_USER + struct kevent kevs[1]; + EV_SET(&kevs[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); + (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); +#else + assert(epollfd->self_pipe[0] >= 0); + assert(epollfd->self_pipe[1] >= 0); + + char c = 0; + (void)write(epollfd->self_pipe[1], &c, 1); +#endif +} + +static void +epollfd_ctx__trigger_repoll(EpollFDCtx *epollfd) +{ + (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); + unsigned long nr_polling_threads = epollfd->nr_polling_threads; + (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); + + if (nr_polling_threads == 0) { + return; + } + + epollfd_ctx__trigger_self(epollfd); + + (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); + while (epollfd->nr_polling_threads != 0) { + pthread_cond_wait(&epollfd->nr_polling_threads_cond, + &epollfd->nr_polling_threads_mutex); + } + (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); + +#ifndef EVFILT_USER + char c[32]; + while (read(epollfd->self_pipe[0], c, sizeof(c)) >= 0) { + } +#endif +} + +static void +epollfd_ctx__remove_node_from_kq(EpollFDCtx *epollfd, + RegisteredFDsNode *fd2_node) +{ + if (fd2_node->is_on_pollfd_list) { + TAILQ_REMOVE(&epollfd->poll_fds, fd2_node, pollfd_list_entry); + fd2_node->is_on_pollfd_list = false; + assert(epollfd->poll_fds_size != 0); + --epollfd->poll_fds_size; + + epollfd_ctx__trigger_repoll(epollfd); + } + + if (fd2_node->self_pipe[0] >= 0) { + struct kevent kevs[1]; + EV_SET(&kevs[0], fd2_node->self_pipe[0], EVFILT_READ, /**/ + EV_DELETE, 0, 0, 0); + (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); + + char c[32]; + while (read(fd2_node->self_pipe[0], c, sizeof(c)) >= 0) { + } + } + + if (fd2_node->node_type == NODE_TYPE_POLL) { +#ifdef EVFILT_USER + struct kevent kevs[1]; + EV_SET(&kevs[0], (uintptr_t)fd2_node, EVFILT_USER, /**/ + EV_DELETE, 0, 0, 0); + (void)kevent(epollfd->kq, kevs, 1, NULL, 0, NULL); +#endif + } else { + struct kevent kevs[3]; + int fd2 = fd2_node->fd; + + EV_SET(&kevs[0], fd2, EVFILT_READ, /**/ + EV_DELETE | EV_RECEIPT, 0, 0, 0); + EV_SET(&kevs[1], fd2, EVFILT_WRITE, /**/ + EV_DELETE | EV_RECEIPT, 0, 0, 0); +#ifdef EVFILT_USER + EV_SET(&kevs[2], (uintptr_t)fd2_node, EVFILT_USER, /**/ + EV_DELETE | EV_RECEIPT, 0, 0, 0); +#endif + (void)kevent(epollfd->kq, kevs, 3, kevs, 3, NULL); + + fd2_node->has_evfilt_read = false; + fd2_node->has_evfilt_write = false; + fd2_node->has_evfilt_except = false; + } +} + +static errno_t +epollfd_ctx__register_events(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) +{ + errno_t ec = 0; + + /* Only sockets support EPOLLRDHUP and EPOLLPRI. */ + if (fd2_node->node_type != NODE_TYPE_SOCKET) { + fd2_node->events &= ~(uint32_t)EPOLLRDHUP; + fd2_node->events &= ~(uint32_t)EPOLLPRI; + } + + int const fd2 = fd2_node->fd; + struct kevent kev[4] = { + {.data = 0}, + {.data = 0}, + {.data = 0}, + {.data = 0}, + }; + + assert(fd2 >= 0); + + int evfilt_read_index = -1; + int evfilt_write_index = -1; + + if (fd2_node->node_type != NODE_TYPE_POLL) { + if (fd2_node->is_registered) { + epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); + } + + int n = 0; + + assert(!fd2_node->has_evfilt_read); + assert(!fd2_node->has_evfilt_write); + assert(!fd2_node->has_evfilt_except); + + NeededFilters needed_filters = get_needed_filters(fd2_node); + + if (needed_filters.evfilt_read) { + fd2_node->has_evfilt_read = true; + evfilt_read_index = n; + EV_SET(&kev[n++], fd2, EVFILT_READ, + EV_ADD | (needed_filters.evfilt_read & EV_CLEAR), + 0, 0, fd2_node); + } + if (needed_filters.evfilt_write) { + fd2_node->has_evfilt_write = true; + evfilt_write_index = n; + EV_SET(&kev[n++], fd2, EVFILT_WRITE, + EV_ADD | (needed_filters.evfilt_write & EV_CLEAR), + 0, 0, fd2_node); + } + + assert(n != 0); + + if (needed_filters.evfilt_except) { +#ifdef EVFILT_EXCEPT + fd2_node->has_evfilt_except = true; + EV_SET(&kev[n++], fd2, EVFILT_EXCEPT, + EV_ADD | (needed_filters.evfilt_except & EV_CLEAR), + NOTE_OOB, 0, fd2_node); +#else + assert(0); +#endif + } + + for (int i = 0; i < n; ++i) { + kev[i].flags |= EV_RECEIPT; + } + + int ret = kevent(epollfd->kq, kev, n, kev, n, NULL); + if (ret < 0) { + ec = errno; + goto out; + } + + assert(ret == n); + + for (int i = 0; i < n; ++i) { + assert((kev[i].flags & EV_ERROR) != 0); + } + } + + /* Check for fds that only support poll. */ + if (((fd2_node->node_type == NODE_TYPE_OTHER && + kev[0].data == ENODEV) || + fd2_node->node_type == NODE_TYPE_POLL)) { + + assert((fd2_node->events & /**/ + ~(uint32_t)(EPOLLIN | EPOLLOUT)) == 0); + assert(fd2_node->is_registered || + fd2_node->node_type == NODE_TYPE_OTHER); + + fd2_node->has_evfilt_read = false; + fd2_node->has_evfilt_write = false; + fd2_node->has_evfilt_except = false; + + fd2_node->node_type = NODE_TYPE_POLL; + + if ((ec = registered_fds_node_add_self_trigger(fd2_node, + epollfd)) != 0) { + goto out; + } + + if (!fd2_node->is_on_pollfd_list) { + if ((ec = /**/ + epollfd_ctx__add_self_trigger(epollfd)) != 0) { + goto out; + } + + TAILQ_INSERT_TAIL(&epollfd->poll_fds, fd2_node, + pollfd_list_entry); + fd2_node->is_on_pollfd_list = true; + ++epollfd->poll_fds_size; + } + + /* This is outside the above if because poll ".events" might + * have changed which needs a retriggering. */ + epollfd_ctx__trigger_repoll(epollfd); + + goto out; + } + + for (int i = 0; i < 4; ++i) { + if (kev[i].data != 0) { + if ((kev[i].data == EPIPE +#ifdef __NetBSD__ + || kev[i].data == EBADF +#endif + ) && + i == evfilt_write_index && + fd2_node->node_type == NODE_TYPE_FIFO) { + + fd2_node->eof_state = EOF_STATE_READ_EOF | + EOF_STATE_WRITE_EOF; + fd2_node->has_evfilt_write = false; + + if (evfilt_read_index < 0) { + if ((ec = registered_fds_node_add_self_trigger( + fd2_node, epollfd)) != 0) { + goto out; + } + + registered_fds_node_trigger_self( + fd2_node, epollfd); + } + } else { + ec = (int)kev[i].data; + goto out; + } + } + } + + ec = 0; + +out: + return ec; +} + +static void +epollfd_ctx_remove_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node) +{ + epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); + + RB_REMOVE(registered_fds_set_, &epollfd->registered_fds, fd2_node); + assert(epollfd->registered_fds_size > 0); + --epollfd->registered_fds_size; + + registered_fds_node_destroy(fd2_node); +} + +#if defined(__FreeBSD__) +static void +modify_fifo_rights_from_capabilities(RegisteredFDsNode *fd2_node) +{ + assert(fd2_node->node_data.fifo.readable); + assert(fd2_node->node_data.fifo.writable); + + cap_rights_t rights; + memset(&rights, 0, sizeof(rights)); + + if (cap_rights_get(fd2_node->fd, &rights) == 0) { + cap_rights_t test_rights; + + cap_rights_init(&test_rights, CAP_READ); + bool has_read_rights = cap_rights_contains(&rights, + &test_rights); + + cap_rights_init(&test_rights, CAP_WRITE); + bool has_write_rights = cap_rights_contains(&rights, + &test_rights); + + if (has_read_rights != has_write_rights) { + fd2_node->node_data.fifo.readable = has_read_rights; + fd2_node->node_data.fifo.writable = has_write_rights; + } + } +} +#endif + +static errno_t +epollfd_ctx_add_node(EpollFDCtx *epollfd, int fd2, struct epoll_event *ev, + struct stat const *statbuf) +{ + RegisteredFDsNode *fd2_node = registered_fds_node_create(fd2); + if (!fd2_node) { + return ENOMEM; + } + + if (S_ISFIFO(statbuf->st_mode)) { + int tmp; + + if (ioctl(fd2_node->fd, FIONREAD, &tmp) < 0 && + errno == ENOTTY) { +#ifdef __FreeBSD__ + /* + * On FreeBSD we need to distinguish between kqueues + * and native eventfds. + */ + if (ioctl(fd2_node->fd, FIONBIO, &tmp) < 0 && + errno == ENOTTY) { + fd2_node->node_type = NODE_TYPE_KQUEUE; + } else { + fd2_node->node_type = NODE_TYPE_OTHER; + } +#else + fd2_node->node_type = NODE_TYPE_KQUEUE; +#endif + } else { + fd2_node->node_type = NODE_TYPE_FIFO; + + int fl = fcntl(fd2, F_GETFL, 0); + if (fl < 0) { + errno_t ec = errno; + registered_fds_node_destroy(fd2_node); + return ec; + } + + fl &= O_ACCMODE; + + if (fl == O_RDWR) { + fd2_node->node_data.fifo.readable = true; + fd2_node->node_data.fifo.writable = true; +#if defined(__FreeBSD__) + modify_fifo_rights_from_capabilities(fd2_node); +#endif + } else if (fl == O_WRONLY) { + fd2_node->node_data.fifo.writable = true; + } else if (fl == O_RDONLY) { + fd2_node->node_data.fifo.readable = true; + } else { + registered_fds_node_destroy(fd2_node); + return EINVAL; + } + } + } else if (S_ISSOCK(statbuf->st_mode)) { + fd2_node->node_type = NODE_TYPE_SOCKET; + } else { + /* May also be NODE_TYPE_POLL, + will be checked when registering. */ + fd2_node->node_type = NODE_TYPE_OTHER; + } + + registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); + + void *colliding_node = RB_INSERT(registered_fds_set_, + &epollfd->registered_fds, fd2_node); + (void)colliding_node; + assert(colliding_node == NULL); + ++epollfd->registered_fds_size; + + errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); + if (ec != 0) { + epollfd_ctx_remove_node(epollfd, fd2_node); + return ec; + } + + fd2_node->is_registered = true; + + return 0; +} + +static errno_t +epollfd_ctx_modify_node(EpollFDCtx *epollfd, RegisteredFDsNode *fd2_node, + struct epoll_event *ev) +{ + registered_fds_node_update_flags_from_epoll_event(fd2_node, ev); + + assert(fd2_node->is_registered); + + errno_t ec = epollfd_ctx__register_events(epollfd, fd2_node); + if (ec != 0) { + epollfd_ctx_remove_node(epollfd, fd2_node); + return ec; + } + + return 0; +} + +static errno_t +epollfd_ctx_ctl_impl(EpollFDCtx *epollfd, int op, int fd2, + struct epoll_event *ev) +{ + assert(op == EPOLL_CTL_DEL || ev != NULL); + + if (epollfd->kq == fd2) { + return EINVAL; + } + + if (op != EPOLL_CTL_DEL && + ((ev->events & + ~(uint32_t)(EPOLLIN | EPOLLOUT | EPOLLRDHUP | /**/ + EPOLLPRI | /* unsupported by FreeBSD's kqueue! */ + EPOLLHUP | EPOLLERR | /**/ + EPOLLET | EPOLLONESHOT)))) { + return EINVAL; + } + + RegisteredFDsNode *fd2_node; + { + RegisteredFDsNode find; + find.fd = fd2; + + fd2_node = RB_FIND(registered_fds_set_, /**/ + &epollfd->registered_fds, &find); + } + + struct stat statbuf; + if (fstat(fd2, &statbuf) < 0) { + errno_t ec = errno; + + /* If the fstat fails for any reason we must clear + * internal state to avoid EEXIST errors in future + * calls to epoll_ctl. */ + if (fd2_node) { + epollfd_ctx_remove_node(epollfd, fd2_node); + } + + return ec; + } + + errno_t ec; + + if (op == EPOLL_CTL_ADD) { + ec = fd2_node + ? EEXIST + : epollfd_ctx_add_node(epollfd, fd2, ev, &statbuf); + } else if (op == EPOLL_CTL_DEL) { + ec = !fd2_node + ? ENOENT + : (epollfd_ctx_remove_node(epollfd, fd2_node), 0); + } else if (op == EPOLL_CTL_MOD) { + ec = !fd2_node + ? ENOENT + : epollfd_ctx_modify_node(epollfd, fd2_node, ev); + } else { + ec = EINVAL; + } + + return ec; +} + +void +epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds) +{ + pfds[0] = (struct pollfd){.fd = epollfd->kq, .events = POLLIN}; + + RegisteredFDsNode *poll_node; + size_t i = 1; + TAILQ_FOREACH(poll_node, &epollfd->poll_fds, pollfd_list_entry) + { + pfds[i++] = (struct pollfd){ + .fd = poll_node->fd, + .events = poll_node->node_type == NODE_TYPE_POLL + ? (short)poll_node->events + : POLLPRI, + }; + } +} + +errno_t +epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, struct epoll_event *ev) +{ + errno_t ec; + + (void)pthread_mutex_lock(&epollfd->mutex); + ec = epollfd_ctx_ctl_impl(epollfd, op, fd2, ev); + (void)pthread_mutex_unlock(&epollfd->mutex); + + return ec; +} + +static errno_t +epollfd_ctx_wait_impl(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt) +{ + errno_t ec; + + assert(cnt >= 1); + + ec = epollfd_ctx_make_pfds_space(epollfd); + if (ec != 0) { + return ec; + } + + epollfd_ctx_fill_pollfds(epollfd, epollfd->pfds); + + int n = poll(epollfd->pfds, (nfds_t)(1 + epollfd->poll_fds_size), 0); + if (n < 0) { + return errno; + } + if (n == 0) { + *actual_cnt = 0; + return 0; + } + + { + RegisteredFDsNode *poll_node, *tmp_poll_node; + size_t i = 1; + TAILQ_FOREACH_SAFE(poll_node, &epollfd->poll_fds, + pollfd_list_entry, tmp_poll_node) + { + struct pollfd *pfd = &epollfd->pfds[i++]; + + if (pfd->revents & POLLNVAL) { + epollfd_ctx_remove_node(epollfd, poll_node); + } else if (pfd->revents) { + registered_fds_node_trigger_self(poll_node, + epollfd); + } + } + } + +again:; + + /* + * Each registered fd can produce a maximum of 3 kevents. If + * the provided space in 'ev' is large enough to hold results + * for all registered fds, provide enough space for the kevent + * call as well. Add some wiggle room for the 'poll only fd' + * notification mechanism. + */ + if ((size_t)cnt >= epollfd->registered_fds_size) { + if (__builtin_add_overflow(cnt, 1, &cnt)) { + return ENOMEM; + } + if (__builtin_mul_overflow(cnt, 3, &cnt)) { + return ENOMEM; + } + } + + ec = epollfd_ctx_make_kevs_space(epollfd, (size_t)cnt); + if (ec != 0) { + return ec; + } + + struct kevent *kevs = epollfd->kevs; + assert(kevs != NULL); + + n = kevent(epollfd->kq, NULL, 0, kevs, cnt, &(struct timespec){0, 0}); + if (n < 0) { + return errno; + } + + int j = 0; + + for (int i = 0; i < n; ++i) { + RegisteredFDsNode *fd2_node = + (RegisteredFDsNode *)kevs[i].udata; + + if (!fd2_node) { +#ifdef EVFILT_USER + assert(kevs[i].filter == EVFILT_USER); +#else + assert(kevs[i].filter == EVFILT_READ); +#endif + assert(kevs[i].udata == 0); + continue; + } + + uint32_t old_revents = fd2_node->revents; + NeededFilters old_needed_filters = get_needed_filters( + fd2_node); + + registered_fds_node_feed_event(fd2_node, epollfd, &kevs[i]); + + if (fd2_node->node_type != NODE_TYPE_POLL && + !(fd2_node->is_edge_triggered && + fd2_node->eof_state == + (EOF_STATE_READ_EOF | EOF_STATE_WRITE_EOF) && + fd2_node->node_type != NODE_TYPE_FIFO)) { + + NeededFilters needed_filters = get_needed_filters( + fd2_node); + + if (old_needed_filters.evfilt_read != + needed_filters.evfilt_read || + old_needed_filters.evfilt_write != + needed_filters.evfilt_write) { + + if (epollfd_ctx__register_events(epollfd, + fd2_node) != 0) { + epollfd_ctx__remove_node_from_kq( + epollfd, fd2_node); + } + } + } + + if (fd2_node->revents && !old_revents) { + ev[j++].data.ptr = fd2_node; + } + } + + { + int completion_kq = -1; + + for (int i = 0; i < j; ++i) { + RegisteredFDsNode *fd2_node = + (RegisteredFDsNode *)ev[i].data.ptr; + + if (n == cnt || fd2_node->is_edge_triggered) { + registered_fds_node_register_for_completion( + &completion_kq, fd2_node); + } + } + + registered_fds_node_complete(completion_kq); + } + + for (int i = 0; i < j; ++i) { + RegisteredFDsNode *fd2_node = + (RegisteredFDsNode *)ev[i].data.ptr; + + ev[i].events = fd2_node->revents; + ev[i].data = fd2_node->data; + + fd2_node->revents = 0; + fd2_node->got_evfilt_read = false; + fd2_node->got_evfilt_write = false; + fd2_node->got_evfilt_except = false; + + if (fd2_node->is_oneshot) { + epollfd_ctx__remove_node_from_kq(epollfd, fd2_node); + } + } + + if (n && j == 0) { + goto again; + } + + *actual_cnt = j; + return 0; +} + +errno_t +epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt) +{ + errno_t ec; + + (void)pthread_mutex_lock(&epollfd->mutex); + ec = epollfd_ctx_wait_impl(epollfd, ev, cnt, actual_cnt); + (void)pthread_mutex_unlock(&epollfd->mutex); + + return ec; +} diff --git a/tpws/epoll-shim/src/epollfd_ctx.h b/tpws/epoll-shim/src/epollfd_ctx.h new file mode 100644 index 00000000..1af7195a --- /dev/null +++ b/tpws/epoll-shim/src/epollfd_ctx.h @@ -0,0 +1,108 @@ +#ifndef EPOLLFD_CTX_H_ +#define EPOLLFD_CTX_H_ + +#include "fix.h" + +#define SHIM_SYS_SHIM_HELPERS +#include + +#include +#include + +#include +#include +#include + +#include +#include + +struct registered_fds_node_; +typedef struct registered_fds_node_ RegisteredFDsNode; + +typedef enum { + EOF_STATE_READ_EOF = 0x01, + EOF_STATE_WRITE_EOF = 0x02, +} EOFState; + +typedef enum { + NODE_TYPE_FIFO = 1, + NODE_TYPE_SOCKET = 2, + NODE_TYPE_KQUEUE = 3, + NODE_TYPE_OTHER = 4, + NODE_TYPE_POLL = 5, +} NodeType; + +struct registered_fds_node_ { + RB_ENTRY(registered_fds_node_) entry; + TAILQ_ENTRY(registered_fds_node_) pollfd_list_entry; + + int fd; + epoll_data_t data; + + bool is_registered; + + bool has_evfilt_read; + bool has_evfilt_write; + bool has_evfilt_except; + + bool got_evfilt_read; + bool got_evfilt_write; + bool got_evfilt_except; + + NodeType node_type; + union { + struct { + bool readable; + bool writable; + } fifo; + } node_data; + int eof_state; + bool pollpri_active; + + uint16_t events; + uint32_t revents; + + bool is_edge_triggered; + bool is_oneshot; + + bool is_on_pollfd_list; + int self_pipe[2]; +}; + +typedef TAILQ_HEAD(pollfds_list_, registered_fds_node_) PollFDList; +typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet; + +typedef struct { + int kq; // non owning + pthread_mutex_t mutex; + + PollFDList poll_fds; + size_t poll_fds_size; + + RegisteredFDsSet registered_fds; + size_t registered_fds_size; + + struct kevent *kevs; + size_t kevs_length; + + struct pollfd *pfds; + size_t pfds_length; + + pthread_mutex_t nr_polling_threads_mutex; + pthread_cond_t nr_polling_threads_cond; + unsigned long nr_polling_threads; + + int self_pipe[2]; +} EpollFDCtx; + +errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq); +errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd); + +void epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds); + +errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2, + struct epoll_event *ev); +errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, + int *actual_cnt); + +#endif diff --git a/tpws/epoll-shim/src/eventfd_ctx.h b/tpws/epoll-shim/src/eventfd_ctx.h new file mode 100644 index 00000000..3e5bb557 --- /dev/null +++ b/tpws/epoll-shim/src/eventfd_ctx.h @@ -0,0 +1,31 @@ +#ifndef EVENTFD_CTX_H_ +#define EVENTFD_CTX_H_ + +#include "fix.h" + +#include +#include +#include + +#include + +#define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0) + +typedef struct { + int kq_; // non owning + int flags_; + pthread_mutex_t mutex_; + + bool is_signalled_; + int self_pipe_[2]; // only used if EVFILT_USER is not available + uint_least64_t counter_; +} EventFDCtx; + +errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter, + int flags); +errno_t eventfd_ctx_terminate(EventFDCtx *eventfd); + +errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value); +errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value); + +#endif diff --git a/tpws/epoll-shim/src/fix.c b/tpws/epoll-shim/src/fix.c new file mode 100644 index 00000000..6fbd3f59 --- /dev/null +++ b/tpws/epoll-shim/src/fix.c @@ -0,0 +1,19 @@ +#include "fix.h" + +#ifdef __APPLE__ + +#include + +int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask) +{ + // macos does not implement ppoll + // this is a hacky ppoll shim. only for tpws which does not require sigmask + if (sigmask) + { + errno = EINVAL; + return -1; + } + return poll(fds,nfds,tmo_p ? tmo_p->tv_sec*1000 + tmo_p->tv_nsec/1000000 : -1); +} + +#endif diff --git a/tpws/epoll-shim/src/fix.h b/tpws/epoll-shim/src/fix.h new file mode 100644 index 00000000..ebefc14b --- /dev/null +++ b/tpws/epoll-shim/src/fix.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef _ERRNO_T_DEFINED +#define _ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifdef __APPLE__ + +#include +#include +#include + +struct itimerspec { + struct timespec it_interval; + struct timespec it_value; +}; +int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask); + +#endif diff --git a/tpws/epoll-shim/src/signalfd_ctx.h b/tpws/epoll-shim/src/signalfd_ctx.h new file mode 100644 index 00000000..8623f630 --- /dev/null +++ b/tpws/epoll-shim/src/signalfd_ctx.h @@ -0,0 +1,19 @@ +#ifndef SIGNALFD_CTX_H_ +#define SIGNALFD_CTX_H_ + +#include "fix.h" + +#include +#include +#include + +typedef struct { + int kq; // non owning +} SignalFDCtx; + +errno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs); +errno_t signalfd_ctx_terminate(SignalFDCtx *signalfd); + +errno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident); + +#endif diff --git a/tpws/epoll-shim/src/timerfd_ctx.h b/tpws/epoll-shim/src/timerfd_ctx.h new file mode 100644 index 00000000..8b415075 --- /dev/null +++ b/tpws/epoll-shim/src/timerfd_ctx.h @@ -0,0 +1,38 @@ +#ifndef TIMERFD_CTX_H_ +#define TIMERFD_CTX_H_ + +#include "fix.h" + +#include +#include +#include +#include + +#include +#include + +typedef struct { + int kq; // non owning + int flags; + pthread_mutex_t mutex; + + int clockid; + /* + * Next expiration time, absolute (clock given by clockid). + * If it_interval is != 0, it is a periodic timer. + * If it_value is == 0, the timer is disarmed. + */ + struct itimerspec current_itimerspec; + uint64_t nr_expirations; +} TimerFDCtx; + +errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid); +errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd); + +errno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags, + struct itimerspec const *new, struct itimerspec *old); +errno_t timerfd_ctx_gettime(TimerFDCtx *timerfd, struct itimerspec *cur); + +errno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value); + +#endif diff --git a/tpws/gzip.c b/tpws/gzip.c new file mode 100644 index 00000000..cb46670a --- /dev/null +++ b/tpws/gzip.c @@ -0,0 +1,82 @@ +#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); + if (*buf) + { + 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/tpws/gzip.h b/tpws/gzip.h new file mode 100644 index 00000000..15e30d2e --- /dev/null +++ b/tpws/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/tpws/helpers.c b/tpws/helpers.c new file mode 100644 index 00000000..ce208f7f --- /dev/null +++ b/tpws/helpers.c @@ -0,0 +1,289 @@ +#define _GNU_SOURCE + +#include "helpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *strncasestr(const char *s,const char *find, size_t slen) +{ + char c, sc; + size_t len; + + if ((c = *find++) != '\0') + { + len = strlen(find); + do + { + do + { + if (slen-- < 1 || (sc = *s++) == '\0') return NULL; + } while (toupper(c) != toupper(sc)); + if (len > slen) return NULL; + } while (strncasecmp(s, find, len) != 0); + s--; + } + return (char *)s; +} + +bool 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 ntop46(const struct sockaddr *sa, char *str, size_t len) +{ + if (!len) return; + *str=0; + switch (sa->sa_family) + { + case AF_INET: + inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len); + break; + case AF_INET6: + inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len); + break; + default: + snprintf(str,len,"UNKNOWN_FAMILY_%d",sa->sa_family); + } +} +void ntop46_port(const struct sockaddr *sa, char *str, size_t len) +{ + char ip[40]; + ntop46(sa,ip,sizeof(ip)); + switch (sa->sa_family) + { + case AF_INET: + snprintf(str,len,"%s:%u",ip,ntohs(((struct sockaddr_in*)sa)->sin_port)); + break; + case AF_INET6: + snprintf(str,len,"[%s]:%u",ip,ntohs(((struct sockaddr_in6*)sa)->sin6_port)); + break; + default: + snprintf(str,len,"%s",ip); + } +} +void print_sockaddr(const struct sockaddr *sa) +{ + char ip_port[48]; + + ntop46_port(sa,ip_port,sizeof(ip_port)); + printf("%s",ip_port); +} + + +// -1 = error, 0 = not local, 1 = local +bool check_local_ip(const struct sockaddr *saddr) +{ + struct ifaddrs *addrs,*a; + + if (is_localnet(saddr)) + return true; + + if (getifaddrs(&addrs)<0) return false; + a = addrs; + + bool bres=false; + while (a) + { + if (a->ifa_addr && sacmp(a->ifa_addr,saddr)) + { + bres=true; + break; + } + a = a->ifa_next; + } + + freeifaddrs(addrs); + return bres; +} +void print_addrinfo(const struct addrinfo *ai) +{ + char str[64]; + while (ai) + { + switch (ai->ai_family) + { + case AF_INET: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str))) + printf("%s\n", str); + break; + case AF_INET6: + if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str))) + printf( "%s\n", str); + break; + } + ai = ai->ai_next; + } +} + + + +bool saismapped(const struct sockaddr_in6 *sa) +{ + // ::ffff:1.2.3.4 + return !memcmp(sa->sin6_addr.s6_addr,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff",12); +} +bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2) +{ + return saismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr+12,&sa1->sin_addr.s_addr,4); +} +bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2) +{ + return (sa1->sa_family==AF_INET && sa2->sa_family==AF_INET && !memcmp(&((struct sockaddr_in*)sa1)->sin_addr,&((struct sockaddr_in*)sa2)->sin_addr,sizeof(struct in_addr))) || + (sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET6 && !memcmp(&((struct sockaddr_in6*)sa1)->sin6_addr,&((struct sockaddr_in6*)sa2)->sin6_addr,sizeof(struct in6_addr))) || + (sa1->sa_family==AF_INET && sa2->sa_family==AF_INET6 && samappedcmp((struct sockaddr_in*)sa1,(struct sockaddr_in6*)sa2)) || + (sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET && samappedcmp((struct sockaddr_in*)sa2,(struct sockaddr_in6*)sa1)); +} +uint16_t saport(const struct sockaddr *sa) +{ + return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port : + sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); +} +bool saconvmapped(struct sockaddr_storage *a) +{ + if ((a->ss_family == AF_INET6) && saismapped((struct sockaddr_in6*)a)) + { + uint32_t ip4 = IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr); + uint16_t port = ((struct sockaddr_in6*)a)->sin6_port; + a->ss_family = AF_INET; + ((struct sockaddr_in*)a)->sin_addr.s_addr = ip4; + ((struct sockaddr_in*)a)->sin_port = port; + return true; + } + return false; +} + +void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa) +{ + switch(sa->sa_family) + { + case AF_INET: + memcpy(sa_dest,sa,sizeof(struct sockaddr_in)); + break; + case AF_INET6: + memcpy(sa_dest,sa,sizeof(struct sockaddr_in6)); + break; + default: + sa_dest->ss_family = 0; + } +} + +bool is_localnet(const struct sockaddr *a) +{ + // match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0 + return (a->sa_family==AF_INET && (IN_LOOPBACK(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)) || + INADDR_ANY == ntohl((((struct sockaddr_in *)a)->sin_addr.s_addr)))) || + (a->sa_family==AF_INET6 && (IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)a)->sin6_addr) || + IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)a)->sin6_addr) || + (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)a)->sin6_addr) && (IN_LOOPBACK(ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))) || + INADDR_ANY == ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr)))))); +} +bool is_linklocal(const struct sockaddr_in6 *a) +{ + // fe80::/10 + return a->sin6_addr.s6_addr[0]==0xFE && (a->sin6_addr.s6_addr[1] & 0xC0)==0x80; +} +bool is_private6(const struct sockaddr_in6* a) +{ + // fc00::/7 + return (a->sin6_addr.s6_addr[0] & 0xFE) == 0xFC; +} + + + +bool set_keepalive(int fd) +{ + int yes=1; + return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int))!=-1; +} +bool set_ttl(int fd, int ttl) +{ + return setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))!=-1; +} +bool set_hl(int fd, int hl) +{ + return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl))!=-1; +} +bool set_ttl_hl(int fd, int ttl) +{ + bool b1,b2; + // try to set both but one may fail if family is wrong + b1=set_ttl(fd, ttl); + b2=set_hl(fd, ttl); + return b1 || b2; +} +int get_so_error(int fd) +{ + // getsockopt(SO_ERROR) clears error + int errn; + socklen_t optlen = sizeof(errn); + if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) + errn=errno; + return errn; +} + +int fprint_localtime(FILE *F) +{ + struct tm t; + time_t now; + + time(&now); + localtime_r(&now,&t); + return fprintf(F, "%02d.%02d.%04d %02d:%02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); +} + +time_t file_mod_time(const char *filename) +{ + struct stat st; + return stat(filename,&st)==-1 ? 0 : st.st_mtime; +} + +bool 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=='~') + { + 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; +} diff --git a/tpws/helpers.h b/tpws/helpers.h new file mode 100644 index 00000000..17ace359 --- /dev/null +++ b/tpws/helpers.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +char *strncasestr(const char *s,const char *find, size_t slen); + +bool append_to_list_file(const char *filename, const char *s); + +void ntop46(const struct sockaddr *sa, char *str, size_t len); +void ntop46_port(const struct sockaddr *sa, char *str, size_t len); +void print_sockaddr(const struct sockaddr *sa); +void print_addrinfo(const struct addrinfo *ai); +bool check_local_ip(const struct sockaddr *saddr); + +bool saismapped(const struct sockaddr_in6 *sa); +bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2); +bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2); +uint16_t saport(const struct sockaddr *sa); +// true = was converted +bool saconvmapped(struct sockaddr_storage *a); + +void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa); + +bool is_localnet(const struct sockaddr *a); +bool is_linklocal(const struct sockaddr_in6* a); +bool is_private6(const struct sockaddr_in6* a); + +bool set_keepalive(int fd); +bool set_ttl(int fd, int ttl); +bool set_hl(int fd, int hl); +bool set_ttl_hl(int fd, int ttl); +int get_so_error(int fd); + +// alignment-safe functions +static inline uint16_t pntoh16(const uint8_t *p) { + return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; +} +static inline void phton16(uint8_t *p, uint16_t v) { + p[0] = (uint8_t)(v>>8); + p[1] = (uint8_t)v; +} + +int fprint_localtime(FILE *F); + +time_t file_mod_time(const char *filename); + +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); + +#ifndef IN_LOOPBACK +#define IN_LOOPBACK(a) ((((uint32_t) (a)) & 0xff000000) == 0x7f000000) +#endif + +#ifdef __GNUC__ +#define IN6_EXTRACT_MAP4(a) \ + (__extension__ \ + ({ const struct in6_addr *__a = (const struct in6_addr *) (a); \ + (((const uint32_t *) (__a))[3]); })) +#else +#define IN6_EXTRACT_MAP4(a) (((const uint32_t *) (a))[3]) +#endif diff --git a/tpws/hostlist.c b/tpws/hostlist.c new file mode 100644 index 00000000..7a8b8b43 --- /dev/null +++ b/tpws/hostlist.c @@ -0,0 +1,206 @@ +#include +#include "hostlist.h" +#include "gzip.h" +#include "params.h" +#include "helpers.h" + +// inplace tolower() and add to pool +static bool addpool(strpool **hostlist, char **s, const char *end) +{ + char *p; + + // advance until eol lowering all chars + for (p = *s; pstr)) return false; + } + return true; +} + +bool NonEmptyHostlist(strpool **hostlist) +{ + // add impossible hostname if the list is empty + return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4); +} + + +bool SearchHostList(strpool *hostlist, const char *host) +{ + if (hostlist) + { + const char *p = host; + bool bInHostList; + while (p) + { + bInHostList = StrPoolCheckStr(hostlist, p); + VPRINT("Hostlist check for %s : %s\n", p, bInHostList ? "positive" : "negative"); + if (bInHostList) return true; + p = strchr(p, '.'); + if (p) p++; + } + } + return false; +} + +// return : true = apply fooling, false = do not apply +static bool HostlistCheck_(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded) +{ + if (excluded) *excluded = false; + if (hostlist_exclude) + { + VPRINT("Checking exclude hostlist\n"); + if (SearchHostList(hostlist_exclude, host)) + { + if (excluded) *excluded = true; + return false; + } + } + if (hostlist) + { + VPRINT("Checking include hostlist\n"); + return SearchHostList(hostlist, host); + } + return true; +} + +static bool LoadIncludeHostListsForProfile(struct desync_profile *dp) +{ + if (!LoadHostLists(&dp->hostlist, &dp->hostlist_files)) + return false; + if (*dp->hostlist_auto_filename) + { + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + NonEmptyHostlist(&dp->hostlist); + } + return true; +} + +// return : true = apply fooling, false = do not apply +bool HostlistCheck(struct desync_profile *dp, const char *host, bool *excluded) +{ + VPRINT("* Hostlist check for profile %d\n",dp->n); + if (*dp->hostlist_auto_filename) + { + time_t t = file_mod_time(dp->hostlist_auto_filename); + if (t!=dp->hostlist_auto_mod_time) + { + DLOG_CONDUP("Autohostlist '%s' from profile %d was modified. Reloading include hostlists for this profile.\n",dp->hostlist_auto_filename, dp->n); + if (!LoadIncludeHostListsForProfile(dp)) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + dp->hostlist_auto_mod_time = t; + NonEmptyHostlist(&dp->hostlist); + } + } + return HostlistCheck_(dp->hostlist, dp->hostlist_exclude, host, excluded); +} + +bool LoadIncludeHostLists() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadIncludeHostListsForProfile(&dpl->dp)) + return false; + return true; +} +bool LoadExcludeHostLists() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadHostLists(&dpl->dp.hostlist_exclude, &dpl->dp.hostlist_exclude_files)) + return false; + return true; +} diff --git a/tpws/hostlist.h b/tpws/hostlist.h new file mode 100644 index 00000000..0bdb94ab --- /dev/null +++ b/tpws/hostlist.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "pools.h" +#include "params.h" + +bool AppendHostList(strpool **hostlist, char *filename); +bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list); +bool LoadIncludeHostLists(); +bool LoadExcludeHostLists(); +bool NonEmptyHostlist(strpool **hostlist); +bool SearchHostList(strpool *hostlist, const char *host); +// return : true = apply fooling, false = do not apply +bool HostlistCheck(struct desync_profile *dp,const char *host, bool *excluded); \ No newline at end of file diff --git a/tpws/macos/net/pfvar.h b/tpws/macos/net/pfvar.h new file mode 100644 index 00000000..e6f6b6e4 --- /dev/null +++ b/tpws/macos/net/pfvar.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +// taken from an older apple SDK +// some fields are different from BSDs + +#define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) + +enum { PF_INOUT, PF_IN, PF_OUT, PF_FWD }; + +struct pf_addr { + union { + struct in_addr v4; + struct in6_addr v6; + u_int8_t addr8[16]; + u_int16_t addr16[8]; + u_int32_t addr32[4]; + } pfa; /* 128-bit address */ +#define v4 pfa.v4 +#define v6 pfa.v6 +#define addr8 pfa.addr8 +#define addr16 pfa.addr16 +#define addr32 pfa.addr32 +}; + +union pf_state_xport { + u_int16_t port; + u_int16_t call_id; + u_int32_t spi; +}; + +struct pfioc_natlook { + struct pf_addr saddr; + struct pf_addr daddr; + struct pf_addr rsaddr; + struct pf_addr rdaddr; + union pf_state_xport sxport; + union pf_state_xport dxport; + union pf_state_xport rsxport; + union pf_state_xport rdxport; + sa_family_t af; + u_int8_t proto; + u_int8_t proto_variant; + u_int8_t direction; +}; diff --git a/tpws/macos/sys/tree.h b/tpws/macos/sys/tree.h new file mode 100644 index 00000000..697fddfa --- /dev/null +++ b/tpws/macos/sys/tree.h @@ -0,0 +1,803 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD: releng/12.2/sys/sys/tree.h 326256 2017-11-27 15:01:59Z pfg $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +#include + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ + RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \ + RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \ + RB_PROTOTYPE_INSERT(name, type, attr); \ + RB_PROTOTYPE_REMOVE(name, type, attr); \ + RB_PROTOTYPE_FIND(name, type, attr); \ + RB_PROTOTYPE_NFIND(name, type, attr); \ + RB_PROTOTYPE_NEXT(name, type, attr); \ + RB_PROTOTYPE_PREV(name, type, attr); \ + RB_PROTOTYPE_MINMAX(name, type, attr); +#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \ + attr void name##_RB_INSERT_COLOR(struct name *, struct type *) +#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \ + attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *) +#define RB_PROTOTYPE_REMOVE(name, type, attr) \ + attr struct type *name##_RB_REMOVE(struct name *, struct type *) +#define RB_PROTOTYPE_INSERT(name, type, attr) \ + attr struct type *name##_RB_INSERT(struct name *, struct type *) +#define RB_PROTOTYPE_FIND(name, type, attr) \ + attr struct type *name##_RB_FIND(struct name *, struct type *) +#define RB_PROTOTYPE_NFIND(name, type, attr) \ + attr struct type *name##_RB_NFIND(struct name *, struct type *) +#define RB_PROTOTYPE_NEXT(name, type, attr) \ + attr struct type *name##_RB_NEXT(struct type *) +#define RB_PROTOTYPE_PREV(name, type, attr) \ + attr struct type *name##_RB_PREV(struct type *) +#define RB_PROTOTYPE_MINMAX(name, type, attr) \ + attr struct type *name##_RB_MINMAX(struct name *, int) + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ + RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ + RB_GENERATE_INSERT(name, type, field, cmp, attr) \ + RB_GENERATE_REMOVE(name, type, field, attr) \ + RB_GENERATE_FIND(name, type, field, cmp, attr) \ + RB_GENERATE_NFIND(name, type, field, cmp, attr) \ + RB_GENERATE_NEXT(name, type, field, attr) \ + RB_GENERATE_PREV(name, type, field, attr) \ + RB_GENERATE_MINMAX(name, type, field, attr) + +#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) != NULL && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)) \ + != NULL) \ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)) \ + != NULL) \ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} + +#define RB_GENERATE_REMOVE(name, type, field, attr) \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field)) != NULL) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field)) != NULL); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + +#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} + +#define RB_GENERATE_FIND(name, type, field, cmp, attr) \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} + +#define RB_GENERATE_NFIND(name, type, field, cmp, attr) \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} + +#define RB_GENERATE_NEXT(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_PREV(name, type, field, attr) \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} + +#define RB_GENERATE_MINMAX(name, type, field, attr) \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#endif /* _SYS_TREE_H_ */ diff --git a/tpws/params.c b/tpws/params.c new file mode 100644 index 00000000..d31aac0b --- /dev/null +++ b/tpws/params.c @@ -0,0 +1,193 @@ +#include "params.h" +#include +#include +#include + +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; +} + +static char syslog_buf[1024]; +static size_t syslog_buf_sz=0; +static void syslog_buffered(int priority, const char *format, va_list args) +{ + if (vsnprintf(syslog_buf+syslog_buf_sz,sizeof(syslog_buf)-syslog_buf_sz,format,args)>0) + { + syslog_buf_sz=strlen(syslog_buf); + // log when buffer is full or buffer ends with \n + if (syslog_buf_sz>=(sizeof(syslog_buf)-1) || (syslog_buf_sz && syslog_buf[syslog_buf_sz-1]=='\n')) + { + syslog(priority,"%s",syslog_buf); + syslog_buf_sz = 0; + } + } +} + +static int DLOG_VA(const char *format, int syslog_priority, bool condup, int level, va_list args) +{ + int r=0; + va_list args2; + + if (condup && !(params.debug>=level && params.debug_target==LOG_TARGET_CONSOLE)) + { + va_copy(args2,args); + DLOG_CON(format,syslog_priority,args2); + } + if (params.debug>=level) + { + switch(params.debug_target) + { + case LOG_TARGET_CONSOLE: + r = DLOG_CON(format,syslog_priority,args); + break; + case LOG_TARGET_FILE: + r = DLOG_FILENAME(params.debug_logfile,format,args); + break; + case LOG_TARGET_SYSLOG: + // skip newlines + syslog_buffered(syslog_priority,format,args); + r = 1; + break; + default: + break; + } + } + return r; +} + +int DLOG(const char *format, int level, ...) +{ + int r; + va_list args; + va_start(args, level); + r = DLOG_VA(format, LOG_DEBUG, false, level, args); + va_end(args); + return r; +} +int DLOG_CONDUP(const char *format, ...) +{ + int r; + va_list args; + va_start(args, format); + r = DLOG_VA(format, LOG_DEBUG, true, 1, args); + va_end(args); + return r; +} +int DLOG_ERR(const char *format, ...) +{ + int r; + va_list args; + va_start(args, format); + r = DLOG_VA(format, LOG_ERR, true, 1, args); + va_end(args); + return r; +} +int DLOG_PERROR(const char *s) +{ + return DLOG_ERR("%s: %s\n", s, strerror(errno)); +} + + +int LOG_APPEND(const char *filename, const char *format, va_list args) +{ + int r; + FILE *F = fopen(filename,"at"); + if (F) + { + fprint_localtime(F); + fprintf(F, " : "); + r = vfprintf(F, format, args); + fprintf(F, "\n"); + fclose(F); + } + else + r=-1; + return r; +} + +int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) +{ + if (*params.hostlist_auto_debuglog) + { + int r; + va_list args; + + va_start(args, format); + r = LOG_APPEND(params.hostlist_auto_debuglog, format, args); + va_end(args); + return r; + } + else + return 0; +} + + +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; + + LIST_INIT(&entry->dp.hostlist_files); + LIST_INIT(&entry->dp.hostlist_exclude_files); + entry->dp.filter_ipv4 = entry->dp.filter_ipv6 = true; + + memcpy(entry->dp.hostspell, "host", 4); // default hostspell + entry->dp.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; + entry->dp.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; + + // 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_entry_destroy(struct desync_profile_list *entry) +{ + strlist_destroy(&entry->dp.hostlist_files); + strlist_destroy(&entry->dp.hostlist_exclude_files); + StrPoolDestroy(&entry->dp.hostlist_exclude); + StrPoolDestroy(&entry->dp.hostlist); + HostFailPoolDestroy(&entry->dp.hostlist_auto_fail_counters); + 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_filename) + return true; + return false; +} diff --git a/tpws/params.h b/tpws/params.h new file mode 100644 index 00000000..3764fc75 --- /dev/null +++ b/tpws/params.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "tpws.h" +#include "pools.h" +#include "helpers.h" +#include "protocol.h" + +#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 +#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 + +enum bindll { unwanted=0, no, prefer, force }; + +#define MAX_BINDS 32 +struct bind_s +{ + char bindaddr[64],bindiface[IF_NAMESIZE]; + bool bind_if6; + enum bindll bindll; + int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll; +}; + +enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG }; + +struct desync_profile +{ + int n; // number of the profile + + bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase; + int hostpad; + char hostspell[4]; + enum httpreqpos split_http_req; + enum tlspos tlsrec; + int tlsrec_pos; + enum tlspos split_tls; + bool split_any_protocol; + int split_pos; + bool disorder, disorder_http, disorder_tls; + bool oob, oob_http, oob_tls; + uint8_t oob_byte; + + int mss; + + bool tamper_start_n,tamper_cutoff_n; + unsigned int tamper_start,tamper_cutoff; + + bool filter_ipv4,filter_ipv6; + port_filter pf_tcp; + + strpool *hostlist, *hostlist_exclude; + struct str_list_head hostlist_files, hostlist_exclude_files; + char hostlist_auto_filename[PATH_MAX]; + int hostlist_auto_fail_threshold, hostlist_auto_fail_time; + time_t hostlist_auto_mod_time; + hostfail_pool *hostlist_auto_fail_counters; +}; + +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_list_destroy(struct desync_profile_list_head *head); +bool dp_list_have_autohostlist(struct desync_profile_list_head *head); + +struct params_s +{ + int debug; + enum log_target debug_target; + char debug_logfile[PATH_MAX]; + + struct bind_s binds[MAX_BINDS]; + int binds_last; + bool bind_wait_only; + uint16_t port; + struct sockaddr_in connect_bind4; + struct sockaddr_in6 connect_bind6; + char connect_bind6_ifname[IF_NAMESIZE]; + + uint8_t proxy_type; + bool no_resolve; + bool skip_nodelay; + bool droproot; + uid_t uid; + gid_t gid; + bool daemon; + char pidfile[256]; + int maxconn,resolver_threads,maxfiles,max_orphan_time; + int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; +#if defined(__linux__) || defined(__APPLE__) + int tcp_user_timeout_local,tcp_user_timeout_remote; +#endif + +#if defined(BSD) + bool pf_enable; +#endif +#ifdef SPLICE_PRESENT + bool nosplice; +#endif + + int ttl_default; + char hostlist_auto_debuglog[PATH_MAX]; + + bool tamper; // any tamper option is set + bool tamper_lim; // tamper-start or tamper-cutoff set in any profile + struct desync_profile_list_head desync_profiles; +}; + +extern struct params_s params; + +int DLOG(const char *format, int level, ...); +int DLOG_CONDUP(const char *format, ...); +int DLOG_ERR(const char *format, ...); +int DLOG_PERROR(const char *s); +int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); + +#define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__) +#define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__) diff --git a/tpws/pools.c b/tpws/pools.c new file mode 100644 index 00000000..785b04d3 --- /dev/null +++ b/tpws/pools.c @@ -0,0 +1,153 @@ +#define _GNU_SOURCE +#include "pools.h" +#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, strlen(elem->str), elem); \ + if (oom) \ + { \ + free(elem->str); \ + free(elem); \ + return false; \ + } + + +#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 StrPoolAddStrLen(strpool **pp, const char *s, size_t slen) +{ + ADD_STR_POOL(strpool, pp, s, slen) + return true; +} +// for zero terminated strings +bool StrPoolAddStr(strpool **pp, const char *s) +{ + return StrPoolAddStrLen(pp, s, strlen(s)); +} + +bool StrPoolCheckStr(strpool *p, const char *s) +{ + strpool *elem; + HASH_FIND_STR(p, s, elem); + return elem != NULL; +} + +void StrPoolDestroy(strpool **pp) +{ + DESTROY_STR_POOL(strpool, 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); +} + + +bool strlist_add(struct str_list_head *head, const char *filename) +{ + struct str_list *entry = malloc(sizeof(struct str_list)); + if (!entry) return false; + entry->str = strdup(filename); + if (!entry->str) + { + free(entry); + return false; + } + LIST_INSERT_HEAD(head, entry, next); + return true; +} +static void strlist_entry_destroy(struct str_list *entry) +{ + if (entry->str) 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); + } +} diff --git a/tpws/pools.h b/tpws/pools.h new file mode 100644 index 00000000..ab589681 --- /dev/null +++ b/tpws/pools.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +//#define HASH_BLOOM 20 +#define HASH_NONFATAL_OOM 1 +#define HASH_FUNCTION HASH_BER +#include "uthash.h" + +typedef struct strpool { + char *str; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} strpool; + +void StrPoolDestroy(strpool **pp); +bool StrPoolAddStr(strpool **pp,const char *s); +bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen); +bool StrPoolCheckStr(strpool *p,const char *s); + +struct str_list { + char *str; + LIST_ENTRY(str_list) next; +}; +LIST_HEAD(str_list_head, str_list); + + +typedef struct hostfail_pool { + char *str; /* key */ + int counter; /* value */ + time_t expire; /* when to expire record (unixtime) */ + UT_hash_handle hh; /* makes this structure hashable */ +} hostfail_pool; + +void HostFailPoolDestroy(hostfail_pool **pp); +hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time); +hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s); +void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem); +void HostFailPoolPurge(hostfail_pool **pp); +void HostFailPoolPurgeRateLimited(hostfail_pool **pp); +void HostFailPoolDump(hostfail_pool *p); + +bool strlist_add(struct str_list_head *head, const char *filename); +void strlist_destroy(struct str_list_head *head); diff --git a/tpws/protocol.c b/tpws/protocol.c new file mode 100644 index 00000000..190277d9 --- /dev/null +++ b/tpws/protocol.c @@ -0,0 +1,347 @@ +#define _GNU_SOURCE + +#include "protocol.h" +#include "helpers.h" +#include +#include +#include +#include + + +const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL }; +const char *HttpMethod(const uint8_t *data, size_t len) +{ + const char **method; + size_t method_len; + for (method = http_methods; *method; method++) + { + method_len = strlen(*method); + if (method_len <= len && !memcmp(data, *method, method_len)) + return *method; + } + return NULL; +} +bool IsHttp(const uint8_t *data, size_t len) +{ + return !!HttpMethod(data,len); +} + +static bool IsHostAt(const uint8_t *p) +{ + return \ + p[0]=='\n' && + (p[1]=='H' || p[1]=='h') && + (p[2]=='o' || p[2]=='O') && + (p[3]=='s' || p[3]=='S') && + (p[4]=='t' || p[4]=='T') && + p[5]==':'; +} +static uint8_t *FindHostIn(uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} +static const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} +// pHost points to "Host: ..." +bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs) +{ + if (!*pHost) + { + *pHost = FindHostIn(buf, bs); + if (*pHost) (*pHost)++; + } + return !!*pHost; +} +bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs) +{ + if (!*pHost) + { + *pHost = FindHostInConst(buf, bs); + if (*pHost) (*pHost)++; + } + return !!*pHost; +} +bool IsHttpReply(const uint8_t *data, size_t len) +{ + // HTTP/1.x 200\r\n + return len>14 && !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); +} +const char *HttpFind2ndLevelDomain(const char *host) +{ + const char *p=NULL; + if (*host) + { + for (p = host + strlen(host)-1; p>host && *p!='.'; p--); + if (*p=='.') for (p--; p>host && *p!='.'; p--); + if (*p=='.') p++; + } + return p; +} +// 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 = HttpFind2ndLevelDomain(host); + const char *drhost = HttpFind2ndLevelDomain(redirect_host); + + return strcasecmp(dhost, drhost)!=0; +} +size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz) +{ + const uint8_t *method, *host=NULL; + int i; + + switch(tpos_type) + { + case httpreqpos_method: + // recognize some tpws pre-applied hacks + method=http; + if (sz<10) break; + if (*method=='\n' || *method=='\r') method++; + if (*method=='\n' || *method=='\r') method++; + for (i=0;i<7;i++) if (*method>='A' && *method<='Z') method++; + if (i<3 || *method!=' ') break; + return method-http-1; + case httpreqpos_host: + if (HttpFindHostConst(&host,http,sz) && (host-http+7)= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len); +} + +// bPartialIsOK=true - accept partial packets not containing the whole TLS message +bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) +{ + // +0 + // u8 HandshakeType: ClientHello + // u24 Length + // u16 Version + // c[32] random + // u8 SessionIDLength + // + // u16 CipherSuitesLength + // + // u8 CompressionMethodsLength + // + // u16 ExtensionsLength + + size_t l, ll; + + l = 1 + 3 + 2 + 32; + // SessionIDLength + if (len < (l + 1)) return false; + if (!bPartialIsOK) + { + ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length + if (len < (ll + 4)) return false; + } + l += data[l] + 1; + // CipherSuitesLength + if (len < (l + 2)) return false; + l += pntoh16(data + l) + 2; + // CompressionMethodsLength + if (len < (l + 1)) return false; + l += data[l] + 1; + // ExtensionsLength + if (len < (l + 2)) return false; + + data += l; len -= l; + l = pntoh16(data); + data += 2; len -= 2; + + if (bPartialIsOK) + { + if (len < l) l = len; + } + else + { + if (len < l) return false; + } + + while (l >= 4) + { + uint16_t etype = pntoh16(data); + size_t elen = pntoh16(data + 2); + data += 4; l -= 4; + if (l < elen) break; + if (etype == type) + { + if (ext && len_ext) + { + *ext = data; + *len_ext = elen; + } + return true; + } + data += elen; l -= elen; + } + + return false; +} +bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK) +{ + // +0 + // u8 ContentType: Handshake + // u16 Version: TLS1.0 + // u16 Length + size_t reclen; + if (!IsTLSClientHello(data, len, bPartialIsOK)) return false; + reclen=TLSRecordLen(data); + if (reclen= len_host) slen = len_host - 1; + for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]); + host[slen] = 0; + } + return true; +} +bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) +{ + const uint8_t *ext; + size_t elen; + + if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false; + return TLSExtractHostFromExt(ext, elen, host, len_host); +} +bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK) +{ + const uint8_t *ext; + size_t elen; + + if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false; + return TLSExtractHostFromExt(ext, elen, host, len_host); +} +size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type) +{ + size_t elen; + const uint8_t *ext; + switch(tpos_type) + { + case tlspos_sni: + case tlspos_sniext: + if (TLSFindExt(tls,sz,0,&ext,&elen,false)) + return (tpos_type==tlspos_sni) ? ext-tls+6 : ext-tls+1; + // fall through + case tlspos_pos: + return tpos_pos +#include +#include + +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); +enum httpreqpos { httpreqpos_none = 0, httpreqpos_method, httpreqpos_host, httpreqpos_pos }; +size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz); + +uint16_t TLSRecordDataLen(const uint8_t *data); +size_t TLSRecordLen(const uint8_t *data); +bool IsTLSRecordFull(const uint8_t *data, size_t len); +bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK); +bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); +bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); +bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); +bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); +enum tlspos { tlspos_none = 0, tlspos_sni, tlspos_sniext, tlspos_pos }; +size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type); diff --git a/tpws/redirect.c b/tpws/redirect.c new file mode 100644 index 00000000..ecfc8b21 --- /dev/null +++ b/tpws/redirect.c @@ -0,0 +1,230 @@ +#include "redirect.h" +#include +#include +#include +#include +#include +#include +#include + +#include "params.h" +#include "helpers.h" + +#ifdef __linux__ + #include + #ifndef IP6T_SO_ORIGINAL_DST + #define IP6T_SO_ORIGINAL_DST 80 + #endif +#endif + + +#if defined(BSD) + +#include +#include + +static int redirector_fd=-1; + +void redir_close(void) +{ + if (redirector_fd!=-1) + { + close(redirector_fd); + redirector_fd = -1; + DBGPRINT("closed redirector\n"); + } +} +static bool redir_open_private(const char *fname, int flags) +{ + redir_close(); + redirector_fd = open(fname, flags); + if (redirector_fd < 0) + { + DLOG_PERROR("redir_openv_private"); + return false; + } + DBGPRINT("opened redirector %s\n",fname); + return true; +} +bool redir_init(void) +{ + return params.pf_enable ? redir_open_private("/dev/pf", O_RDONLY) : true; +} + +static bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst) +{ + struct pfioc_natlook nl; + struct sockaddr_storage asa2; + + if (redirector_fd==-1) return false; + + if (params.debug>=2) + { + char s[48],s2[48]; + *s=0; ntop46_port(accept_sa, s, sizeof(s)); + *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); + DBGPRINT("destination_from_pf %s %s\n",s,s2); + } + + saconvmapped(orig_dst); + if (accept_sa->sa_family==AF_INET6 && orig_dst->ss_family==AF_INET) + { + memcpy(&asa2,accept_sa,sizeof(struct sockaddr_in6)); + saconvmapped(&asa2); + accept_sa = (struct sockaddr*)&asa2; + } + + if (params.debug>=2) + { + char s[48],s2[48]; + *s=0; ntop46_port(accept_sa, s, sizeof(s)); + *s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2)); + DBGPRINT("destination_from_pf (saconvmapped) %s %s\n",s,s2); + } + + if (accept_sa->sa_family!=orig_dst->ss_family) + { + DBGPRINT("accept_sa and orig_dst sa_family mismatch : %d %d\n", accept_sa->sa_family, orig_dst->ss_family); + return false; + } + + memset(&nl, 0, sizeof(nl)); + nl.proto = IPPROTO_TCP; + nl.direction = PF_OUT; + nl.af = orig_dst->ss_family; + switch(orig_dst->ss_family) + { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in *)orig_dst; + nl.daddr.v4.s_addr = sin->sin_addr.s_addr; + nl.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr; +#ifdef __APPLE__ + nl.sxport.port = ((struct sockaddr_in*)accept_sa)->sin_port; + nl.dxport.port = sin->sin_port; +#else + nl.sport = ((struct sockaddr_in*)accept_sa)->sin_port; + nl.dport = sin->sin_port; +#endif + } + break; + case AF_INET6: + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)orig_dst; + nl.daddr.v6 = sin6->sin6_addr; + nl.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr; +#ifdef __APPLE__ + nl.sxport.port = ((struct sockaddr_in6*)accept_sa)->sin6_port; + nl.dxport.port = sin6->sin6_port; +#else + nl.sport = ((struct sockaddr_in6*)accept_sa)->sin6_port; + nl.dport = sin6->sin6_port; +#endif + } + break; + default: + DBGPRINT("destination_from_pf : unexpected address family %d\n",orig_dst->ss_family); + return false; + } + + if (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0) + { + DLOG_PERROR("ioctl(DIOCNATLOOK) failed"); + return false; + } + DBGPRINT("destination_from_pf : got orig dest addr from pf\n"); + + switch(nl.af) + { + case AF_INET: + orig_dst->ss_family = nl.af; +#ifdef __APPLE__ + ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdxport.port; +#else + ((struct sockaddr_in*)orig_dst)->sin_port = nl.rdport; +#endif + ((struct sockaddr_in*)orig_dst)->sin_addr = nl.rdaddr.v4; + break; + case AF_INET6: + orig_dst->ss_family = nl.af; +#ifdef __APPLE__ + ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdxport.port; +#else + ((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdport; +#endif + ((struct sockaddr_in6*)orig_dst)->sin6_addr = nl.rdaddr.v6; + break; + default: + DBGPRINT("destination_from_pf : DIOCNATLOOK returned unexpected address family %d\n",nl.af); + return false; + } + + return true; +} + + +#else + +bool redir_init(void) {return true;} +void redir_close(void) {}; + +#endif + + + +//Store the original destination address in orig_dst +bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst) +{ + char orig_dst_str[INET6_ADDRSTRLEN]; + socklen_t addrlen = sizeof(*orig_dst); + int r; + + memset(orig_dst, 0, addrlen); + + //For UDP transparent proxying: + //Set IP_RECVORIGDSTADDR socket option for getting the original + //destination of a datagram + +#ifdef __linux__ + // DNAT + r=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + r = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + { + DBGPRINT("both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n"); +#endif + // TPROXY : socket is bound to original destination + r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen); + if (r<0) + { + DLOG_PERROR("getsockname"); + return false; + } + if (orig_dst->ss_family==AF_INET6) + ((struct sockaddr_in6*)orig_dst)->sin6_scope_id=0; // or MacOS will not connect() +#ifdef BSD + if (params.pf_enable && !destination_from_pf(accept_sa, orig_dst)) + DBGPRINT("pf filter destination_from_pf failed\n"); +#endif +#ifdef __linux__ + } +#endif + if (saconvmapped(orig_dst)) + DBGPRINT("Original destination : converted ipv6 mapped address to ipv4\n"); + + if (params.debug) + { + if (orig_dst->ss_family == AF_INET) + { + inet_ntop(AF_INET, &(((struct sockaddr_in*) orig_dst)->sin_addr), orig_dst_str, INET_ADDRSTRLEN); + VPRINT("Original destination for socket fd=%d : %s:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port)); + } + else if (orig_dst->ss_family == AF_INET6) + { + inet_ntop(AF_INET6,&(((struct sockaddr_in6*) orig_dst)->sin6_addr), orig_dst_str, INET6_ADDRSTRLEN); + VPRINT("Original destination for socket fd=%d : [%s]:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port)); + } + } + return true; +} diff --git a/tpws/redirect.h b/tpws/redirect.h new file mode 100644 index 00000000..ee46267c --- /dev/null +++ b/tpws/redirect.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst); +bool redir_init(void); +void redir_close(void); diff --git a/tpws/resolver.c b/tpws/resolver.c new file mode 100644 index 00000000..b9c204f2 --- /dev/null +++ b/tpws/resolver.c @@ -0,0 +1,264 @@ +#define _GNU_SOURCE + +#include "resolver.h" +#include "params.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIG_BREAK SIGUSR1 + +#ifdef __APPLE__ + static const char *sem_name="/tpws_resolver"; +#endif + +TAILQ_HEAD(resolve_tailhead, resolve_item); + +typedef struct +{ + int fd_signal_pipe; + sem_t *sem; +#ifndef __APPLE__ + sem_t _sem; +#endif + struct resolve_tailhead resolve_list; + pthread_mutex_t resolve_list_lock; + int threads; + pthread_t *thread; + bool bInit, bStop; +} t_resolver; +static t_resolver resolver = { .bInit = false }; + +#define rlist_lock pthread_mutex_lock(&resolver.resolve_list_lock) +#define rlist_unlock pthread_mutex_unlock(&resolver.resolve_list_lock) + +static void resolver_clear_list(void) +{ + struct resolve_item *ri; + + for (;;) + { + ri = TAILQ_FIRST(&resolver.resolve_list); + if (!ri) break; + TAILQ_REMOVE(&resolver.resolve_list, ri, next); + free(ri); + } +} + +int resolver_thread_count(void) +{ + return resolver.bInit ? resolver.threads : 0; +} + +static void *resolver_thread(void *arg) +{ + int r; + sigset_t signal_mask; + + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SIG_BREAK); + + //printf("resolver_thread %d start\n",syscall(SYS_gettid)); + for(;;) + { + if (resolver.bStop) break; + r = sem_wait(resolver.sem); + if (resolver.bStop) break; + if (r) + { + if (errno!=EINTR) + { + DLOG_PERROR("sem_wait (resolver_thread)"); + break; // fatal err + } + } + else + { + struct resolve_item *ri; + ssize_t wr; + + rlist_lock; + ri = TAILQ_FIRST(&resolver.resolve_list); + if (ri) TAILQ_REMOVE(&resolver.resolve_list, ri, next); + rlist_unlock; + + if (ri) + { + struct addrinfo *ai,hints; + char sport[6]; + + //printf("THREAD %d GOT JOB %s\n", syscall(SYS_gettid), ri->dom); + snprintf(sport,sizeof(sport),"%u",ri->port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + // unfortunately getaddrinfo cannot be interrupted with a signal. we cannot cancel a query + ri->ga_res = getaddrinfo(ri->dom,sport,&hints,&ai); + if (!ri->ga_res) + { + memcpy(&ri->ss, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + } + //printf("THREAD %d END JOB %s FIRST=%p\n", syscall(SYS_gettid), ri->dom, TAILQ_FIRST(&resolver.resolve_list)); + + // never interrupt this + pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); + wr = write(resolver.fd_signal_pipe,&ri,sizeof(void*)); + if (wr<0) + { + free(ri); + DLOG_PERROR("write resolve_pipe"); + } + else if (wr!=sizeof(void*)) + { + // partial pointer write is FATAL. in any case it will cause pointer corruption and coredump + free(ri); + DLOG_ERR("write resolve_pipe : not full write\n"); + exit(1000); + } + pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); + } + } + } + //printf("resolver_thread %d exit\n",syscall(SYS_gettid)); + return NULL; +} + +static void sigbreak(int sig) +{ +} + +void resolver_deinit(void) +{ + if (resolver.bInit) + { + resolver.bStop = true; + + // wait all threads to terminate + for (int t = 0; t < resolver.threads; t++) + pthread_kill(resolver.thread[t], SIGUSR1); + for (int t = 0; t < resolver.threads; t++) + { + pthread_kill(resolver.thread[t], SIGUSR1); + pthread_join(resolver.thread[t], NULL); + } + + pthread_mutex_destroy(&resolver.resolve_list_lock); + free(resolver.thread); + + #ifdef __APPLE__ + sem_close(resolver.sem); + #else + sem_destroy(resolver.sem); + #endif + + resolver_clear_list(); + + memset(&resolver,0,sizeof(resolver)); + } +} + +bool resolver_init(int threads, int fd_signal_pipe) +{ + int t; + struct sigaction action; + + if (threads<1 || resolver.bInit) return false; + + memset(&resolver,0,sizeof(resolver)); + resolver.bInit = true; + +#ifdef __APPLE__ + // MacOS does not support unnamed semaphores + + char sn[64]; + snprintf(sn,sizeof(sn),"%s_%d",sem_name,getpid()); + resolver.sem = sem_open(sn,O_CREAT,0600,0); + if (resolver.sem==SEM_FAILED) + { + DLOG_PERROR("sem_open"); + goto ex; + } + // unlink immediately to remove tails + sem_unlink(sn); +#else + if (sem_init(&resolver._sem,0,0)==-1) + { + DLOG_PERROR("sem_init"); + goto ex; + } + resolver.sem = &resolver._sem; +#endif + + if (pthread_mutex_init(&resolver.resolve_list_lock, NULL)) goto ex; + + resolver.fd_signal_pipe = fd_signal_pipe; + TAILQ_INIT(&resolver.resolve_list); + + // start as many threads as we can up to specified number + resolver.thread = malloc(sizeof(pthread_t)*threads); + if (!resolver.thread) goto ex; + + memset(&action,0,sizeof(action)); + action.sa_handler = sigbreak; + sigaction(SIG_BREAK, &action, NULL); + + + pthread_attr_t attr; + if (pthread_attr_init(&attr)) goto ex; + // set minimum thread stack size + + if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>20480 ? PTHREAD_STACK_MIN : 20480)) + { + pthread_attr_destroy(&attr); + goto ex; + } + + for(t=0, resolver.threads=threads ; tdom,dom,sizeof(ri->dom)); + ri->dom[sizeof(ri->dom)-1] = 0; + ri->port = port; + ri->ptr = ptr; + + rlist_lock; + TAILQ_INSERT_TAIL(&resolver.resolve_list, ri, next); + rlist_unlock; + if (sem_post(resolver.sem)<0) + { + DLOG_PERROR("resolver_queue sem_post"); + free(ri); + return NULL; + } + return ri; +} diff --git a/tpws/resolver.h b/tpws/resolver.h new file mode 100644 index 00000000..31517173 --- /dev/null +++ b/tpws/resolver.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct resolve_item +{ + char dom[256]; // request dom + struct sockaddr_storage ss; // resolve result + int ga_res; // getaddrinfo result code + uint16_t port; // request port + void *ptr; + TAILQ_ENTRY(resolve_item) next; +}; + +struct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr); +void resolver_deinit(void); +bool resolver_init(int threads, int fd_signal_pipe); +int resolver_thread_count(void); diff --git a/tpws/sec.c b/tpws/sec.c new file mode 100644 index 00000000..873c8752 --- /dev/null +++ b/tpws/sec.c @@ -0,0 +1,360 @@ +#define _GNU_SOURCE + +#include +#include +#include "sec.h" +#include +#include +#include + +#ifdef __linux__ + +#include +#include +#include +#include +// __X32_SYSCALL_BIT defined in linux/unistd.h +#include +#include +#include + +/************ SECCOMP ************/ + +// block most of the undesired syscalls to harden against code execution +static long blocked_syscalls[] = { +#ifdef SYS_execv +SYS_execv, +#endif +SYS_execve, +#ifdef SYS_execveat +SYS_execveat, +#endif +#ifdef SYS_exec_with_loader +SYS_exec_with_loader, +#endif +#ifdef SYS_osf_execve +SYS_osf_execve, +#endif +#ifdef SYS_uselib +SYS_uselib, +#endif +#ifdef SYS_unlink +SYS_unlink, +#endif +SYS_unlinkat, +#ifdef SYS_chmod +SYS_chmod, +#endif +SYS_fchmod,SYS_fchmodat, +#ifdef SYS_chown +SYS_chown, +#endif +#ifdef SYS_chown32 +SYS_chown32, +#endif +SYS_fchown, +#ifdef SYS_fchown32 +SYS_fchown32, +#endif +#ifdef SYS_lchown +SYS_lchown, +#endif +#ifdef SYS_lchown32 +SYS_lchown32, +#endif +SYS_fchownat, +#ifdef SYS_symlink +SYS_symlink, +#endif +SYS_symlinkat, +#ifdef SYS_link +SYS_link, +#endif +SYS_linkat, +SYS_truncate, +#ifdef SYS_truncate64 +SYS_truncate64, +#endif +SYS_ftruncate, +#ifdef SYS_ftruncate64 +SYS_ftruncate64, +#endif +#ifdef SYS_mknod +SYS_mknod, +#endif +SYS_mknodat, +#ifdef SYS_mkdir +SYS_mkdir, +#endif +SYS_mkdirat, +#ifdef SYS_rmdir +SYS_rmdir, +#endif +#ifdef SYS_rename +SYS_rename, +#endif +#ifdef SYS_renameat2 +SYS_renameat2, +#endif +#ifdef SYS_renameat +SYS_renameat, +#endif +#ifdef SYS_readdir +SYS_readdir, +#endif +#ifdef SYS_getdents +SYS_getdents, +#endif +#ifdef SYS_getdents64 +SYS_getdents64, +#endif +#ifdef SYS_process_vm_readv +SYS_process_vm_readv, +#endif +#ifdef SYS_process_vm_writev +SYS_process_vm_writev, +#endif +#ifdef SYS_process_madvise +SYS_process_madvise, +#endif +SYS_kill, SYS_ptrace +}; +#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls)) + +static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) +{ + filter->code = code; + filter->jt = jt; + filter->jf = jf; + filter->k = k; +} +// deny all blocked syscalls +static bool set_seccomp(void) +{ +#ifdef __X32_SYSCALL_BIT + #define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT) +#else + #define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT) +#endif + struct sock_filter sockf[SECCOMP_PROG_SIZE]; + struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf }; + int i,idx=0; + + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); +#ifdef __X32_SYSCALL_BIT + // x86 only + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail +#else + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); +#endif + +/* + // ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd + set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad + set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr +*/ + for(i=0 ; i= 0; +} + +bool sec_harden(void) +{ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + { + DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)"); + return 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"); + return false; + } +#endif + return true; +} + + + + +bool checkpcap(uint64_t caps) +{ + if (!caps) return true; // no special caps reqd + + struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; + struct __user_cap_data_struct cd[2]; + uint32_t c0 = (uint32_t)caps; + uint32_t c1 = (uint32_t)(caps>>32); + + return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1; +} +bool setpcap(uint64_t caps) +{ + struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()}; + struct __user_cap_data_struct cd[2]; + + cd[0].effective = cd[0].permitted = (uint32_t)caps; + cd[0].inheritable = 0; + cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32); + cd[1].inheritable = 0; + + return !capset(&ch,cd); +} +int getmaxcap(void) +{ + int maxcap = CAP_LAST_CAP; + FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); + if (F) + { + int n = fscanf(F, "%d", &maxcap); + fclose(F); + } + return maxcap; + +} +bool dropcaps(void) +{ + uint64_t caps = 0; + int maxcap = getmaxcap(); + + if (setpcap(caps|(1< +#include + +#ifdef __linux__ + +#include +#include +#include + +bool checkpcap(uint64_t caps); +bool setpcap(uint64_t caps); +int getmaxcap(void); +bool dropcaps(void); + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) +#define syscall_arg(x) (offsetof(struct seccomp_data, args[x])) + +#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 + +bool sec_harden(void); +bool can_drop_root(); +bool droproot(uid_t uid, gid_t gid); +void print_id(void); +void daemonize(void); +bool writepid(const char *filename); diff --git a/tpws/socks.h b/tpws/socks.h new file mode 100644 index 00000000..5c9feb06 --- /dev/null +++ b/tpws/socks.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include + +#pragma pack(push,1) + +#define S4_CMD_CONNECT 1 +#define S4_CMD_BIND 2 +typedef struct +{ + uint8_t ver,cmd; + uint16_t port; + uint32_t ip; +} s4_req; +#define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4) +#define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT) + +#define S4_REP_OK 90 +#define S4_REP_FAILED 91 +typedef struct +{ + uint8_t zero,rep; + uint16_t port; + uint32_t ip; +} s4_rep; + + + +#define S5_AUTH_NONE 0 +#define S5_AUTH_GSSAPI 1 +#define S5_AUTH_USERPASS 2 +#define S5_AUTH_UNACCEPTABLE 0xFF +typedef struct +{ + uint8_t ver,nmethods,methods[255]; +} s5_handshake; +#define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods)) +typedef struct +{ + uint8_t ver,method; +} s5_handshake_ack; + +#define S5_CMD_CONNECT 1 +#define S5_CMD_BIND 2 +#define S5_CMD_UDP_ASSOC 3 +#define S5_ATYP_IP4 1 +#define S5_ATYP_DOM 3 +#define S5_ATYP_IP6 4 +typedef struct +{ + uint8_t ver,cmd,rsv,atyp; + union { + struct { + struct in_addr addr; + uint16_t port; + } d4; + struct { + struct in6_addr addr; + uint16_t port; + } d6; + struct { + uint8_t len; + char domport[255+2]; // max hostname + binary port + } dd; + }; +} s5_req; +#define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5) +#define S5_IP46_VALID(r,l) ((r->atyp==S5_ATYP_IP4 && l>=(4+sizeof(r->d4))) || (r->atyp==S5_ATYP_IP6 && l>=(4+sizeof(r->d6)))) +#define S5_REQ_CONNECT_VALID(r,l) (S5_REQ_HEADER_VALID(r,l) && r->cmd==S5_CMD_CONNECT && (S5_IP46_VALID(r,l) || (r->atyp==S5_ATYP_DOM && l>=5 && l>=(5+r->dd.len)))) +#define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0) + +#define S5_REP_OK 0 +#define S5_REP_GENERAL_FAILURE 1 +#define S5_REP_NOT_ALLOWED_BY_RULESET 2 +#define S5_REP_NETWORK_UNREACHABLE 3 +#define S5_REP_HOST_UNREACHABLE 4 +#define S5_REP_CONN_REFUSED 5 +#define S5_REP_TTL_EXPIRED 6 +#define S5_REP_COMMAND_NOT_SUPPORTED 7 +#define S5_REP_ADDR_TYPE_NOT_SUPPORTED 8 +typedef struct +{ + uint8_t ver,rep,rsv,atyp; + union { + struct { + struct in_addr addr; + uint16_t port; + } d4; + }; +} s5_rep; + +#pragma pack(pop) diff --git a/tpws/tamper.c b/tpws/tamper.c new file mode 100644 index 00000000..8fc33ca3 --- /dev/null +++ b/tpws/tamper.c @@ -0,0 +1,461 @@ +#define _GNU_SOURCE + +#include "tamper.h" +#include "hostlist.h" +#include "protocol.h" +#include "helpers.h" +#include +#include + +static bool dp_match_l3l4(struct desync_profile *dp, bool ipv6, uint16_t tcp_port) +{ + return \ + ((!ipv6 && dp->filter_ipv4) || (ipv6 && dp->filter_ipv6)) && + (!tcp_port || pf_in_range(tcp_port,&dp->pf_tcp)); +} +static bool dp_match(struct desync_profile *dp, bool ipv6, uint16_t tcp_port, const char *hostname) +{ + if (dp_match_l3l4(dp,ipv6,tcp_port)) + { + // autohostlist profile matching l3/l4 filter always win + if (*dp->hostlist_auto_filename) return true; + + if (dp->hostlist || dp->hostlist_exclude) + { + // without known hostname first profile matching l3/l4 filter and without hostlist filter wins + if (hostname) + return HostlistCheck(dp, hostname, NULL); + } + else + // profile without hostlist filter wins + return true; + } + return false; +} +static struct desync_profile *dp_find(struct desync_profile_list_head *head, bool ipv6, uint16_t tcp_port, const char *hostname) +{ + struct desync_profile_list *dpl; + VPRINT("desync profile search for hostname='%s' ipv6=%u tcp_port=%u\n", hostname ? hostname : "", ipv6, tcp_port); + LIST_FOREACH(dpl, head, next) + { + if (dp_match(&dpl->dp,ipv6,tcp_port,hostname)) + { + VPRINT("desync profile %d matches\n",dpl->dp.n); + return &dpl->dp; + } + } + VPRINT("desync profile not found\n"); + return NULL; +} +void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) +{ + ctrack->dp = dp_find(¶ms.desync_profiles, dest->sa_family==AF_INET6, saport(dest), ctrack->hostname); +} + + + +// segment buffer has at least 5 extra bytes to extend data block +void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags) +{ + uint8_t *p, *pp, *pHost = NULL; + size_t method_len = 0, pos; + size_t tpos, spos; + const char *method; + bool bHaveHost = false; + char *pc, Host[256]; + t_l7proto l7proto; + + DBGPRINT("tamper_out\n"); + + if (params.debug) + { + char ip_port[48]; + ntop46_port(dest,ip_port,sizeof(ip_port)); + VPRINT("tampering tcp segment with size %zu to %s\n", *size, ip_port); + if (ctrack->dp) VPRINT("using cached desync profile %d\n",ctrack->dp->n); + if (ctrack->hostname) VPRINT("connection hostname: %s\n", ctrack->hostname); + } + + if (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6) + { + DLOG_ERR("tamper_out dest family unknown\n"); + return; + } + + *split_pos=0; + *split_flags=0; + + if ((method = HttpMethod(segment,*size))) + { + method_len = strlen(method)-2; + VPRINT("Data block looks like http request start : %s\n", method); + l7proto=HTTP; + if (HttpFindHost(&pHost,segment,*size)) + { + p = pHost + 5; + while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++; + pp = p; + while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++; + memcpy(Host, p, pp - p); + Host[pp - p] = '\0'; + bHaveHost = true; + for(pc = Host; *pc; pc++) *pc=tolower(*pc); + } + } + else if (IsTLSClientHello(segment,*size,false)) + { + VPRINT("Data block contains TLS ClientHello\n"); + l7proto=TLS; + bHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false); + } + else + { + VPRINT("Data block contains unknown payload\n"); + l7proto = UNKNOWN; + } + + if (ctrack->l7proto==UNKNOWN) ctrack->l7proto=l7proto; + + if (bHaveHost) + { + VPRINT("request hostname: %s\n", Host); + if (!ctrack->hostname) + { + if (!(ctrack->hostname=strdup(Host))) + { + DLOG_ERR("strdup hostname : out of memory\n"); + return; + } + + struct desync_profile *dp_prev = ctrack->dp; + apply_desync_profile(ctrack, dest); + if (ctrack->dp!=dp_prev) + VPRINT("desync profile changed by revealed hostname !\n"); + else if (*ctrack->dp->hostlist_auto_filename) + { + bool bHostExcluded; + if (!HostlistCheck(ctrack->dp, Host, &bHostExcluded)) + { + ctrack->b_ah_check = !bHostExcluded; + VPRINT("Not acting on this request\n"); + return; + } + } + } + } + + if (!ctrack->dp) return; + + switch(l7proto) + { + case HTTP: + if (ctrack->dp->unixeol) + { + p = pp = segment; + while ((p = memmem(p, segment + *size - p, "\r\n", 2))) + { + *p = '\n'; p++; + memmove(p, p + 1, segment + *size - p - 1); + (*size)--; + if (pp == (p - 1)) + { + // probably end of http headers + VPRINT("Found double EOL at pos %td. Stop replacing.\n", pp - segment); + break; + } + pp = p; + } + pHost = NULL; // invalidate + } + if (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size) + { + VPRINT("Adding EOL before method\n"); + if (ctrack->dp->unixeol) + { + memmove(segment + 1, segment, *size); + (*size)++;; + segment[0] = '\n'; + } + else + { + memmove(segment + 2, segment, *size); + *size += 2; + segment[0] = '\r'; + segment[1] = '\n'; + } + pHost = NULL; // invalidate + } + if (ctrack->dp->methodspace && *sizedp->hostdot || ctrack->dp->hosttab) && *sizedp->hostdot ? "dot" : "tab", pos); + memmove(p + 1, p, *size - pos); + *p = ctrack->dp->hostdot ? '.' : '\t'; // insert dot or tab + (*size)++; // block will grow by 1 byte + } + } + if (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size)) + { + p = pHost + 5; + pos = p - segment; + VPRINT("Mixing domain case at pos %zu\n",pos); + for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++) + *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); + } + if (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ') + { + p = pHost + 6; + pos = p - segment; + VPRINT("Removing space before host name at pos %zu\n", pos); + memmove(p - 1, p, *size - pos); + (*size)--; // block will shrink by 1 byte + } + if (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size)) + { + VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %td\n", ctrack->dp->hostspell[0], ctrack->dp->hostspell[1], ctrack->dp->hostspell[2], ctrack->dp->hostspell[3], pHost - segment); + memcpy(pHost, ctrack->dp->hostspell, 4); + } + if (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size)) + { + // add : XXXXX: dp->unixeol ? 8 : 9; + size_t hostpad = ctrack->dp->hostpaddp->hostpad; + + if ((hsize+*size)>segment_buffer_size) + VPRINT("could not add host padding : buffer too small\n"); + else + { + if ((hostpad+*size)>segment_buffer_size) + { + hostpad=segment_buffer_size-*size; + VPRINT("host padding reduced to %zu bytes : buffer too small\n", hostpad); + } + else + VPRINT("host padding with %zu bytes\n", hostpad); + + p = pHost; + pos = p - segment; + memmove(p + hostpad, p, *size - pos); + (*size) += hostpad; + while(hostpad) + { + #define MAX_HDR_SIZE 2048 + size_t padsize = hostpad > hsize ? hostpad-hsize : 0; + if (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE; + // if next header would be too small then add extra padding to the current one + if ((hostpad-padsize-hsize)dp->unixeol) + *p++='\n'; + else + { + *p++='\r'; + *p++='\n'; + } + hostpad-=hsize+padsize; + } + pHost = NULL; // invalidate + } + } + *split_pos = HttpPos(ctrack->dp->split_http_req, ctrack->dp->split_pos, segment, *size); + if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB; + break; + + case TLS: + spos = TLSPos(ctrack->dp->split_tls, ctrack->dp->split_pos, segment, *size, 0); + if ((5+*size)<=segment_buffer_size) + { + tpos = TLSPos(ctrack->dp->tlsrec, ctrack->dp->tlsrec_pos+5, segment, *size, 0); + if (tpos>5) + { + // construct 2 TLS records from one + uint16_t l = pntoh16(segment+3); // length + if (l>=2) + { + // length is checked in IsTLSClientHello and cannot exceed buffer size + if ((tpos-5)>=l) tpos=5+1; + VPRINT("making 2 TLS records at pos %zu\n",tpos); + memmove(segment+tpos+5,segment+tpos,*size-tpos); + segment[tpos] = segment[0]; + segment[tpos+1] = segment[1]; + segment[tpos+2] = segment[2]; + phton16(segment+tpos+3,l-(tpos-5)); + phton16(segment+3,tpos-5); + *size += 5; + // split pos present and it is not before tlsrec split. increase split pos by tlsrec header size (5 bytes) + if (spos && spos>=tpos) spos+=5; + } + } + } + + if (spos && spos < *size) + *split_pos = spos; + + if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB; + + break; + + default: + if (ctrack->dp->split_any_protocol && ctrack->dp->split_pos < *size) + *split_pos = ctrack->dp->split_pos; + } + + if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB; +} + +static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname) +{ + if (hostname) + { + hostfail_pool *fail_counter; + + fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); + if (fail_counter) + { + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); + VPRINT("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter reset. website is working.", hostname, dp->n); + } + } +} + +static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname) +{ + hostfail_pool *fail_counter; + + fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); + if (!fail_counter) + { + fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); + if (!fail_counter) + { + DLOG_ERR("HostFailPoolAdd: out of memory\n"); + return; + } + } + fail_counter->counter++; + VPRINT("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter %d/%d", hostname, dp->n, fail_counter->counter, dp->hostlist_auto_fail_threshold); + if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) + { + VPRINT("auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\n", dp->n , hostname); + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); + + VPRINT("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); + bool bExcluded=false; + if (!HostlistCheck(dp, hostname, &bExcluded) && !bExcluded) + { + VPRINT("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto_filename); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : adding to %s", hostname, dp->n, dp->hostlist_auto_filename); + if (!StrPoolAddStr(&dp->hostlist, hostname)) + { + 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; + } + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + } + else + { + VPRINT("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : NOT adding, duplicate detected", hostname, dp->n); + } + } +} + +void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size) +{ + bool bFail=false; + + DBGPRINT("tamper_in hostname=%s\n", ctrack->hostname); + + if (ctrack->dp && ctrack->b_ah_check) + { + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); + + if (ctrack->l7proto==HTTP && ctrack->hostname) + { + if (IsHttpReply(segment,*size)) + { + VPRINT("incoming HTTP reply detected for hostname %s\n", ctrack->hostname); + bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname); + if (bFail) + { + VPRINT("redirect to another domain detected. possibly DPI redirect.\n"); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : redirect to another domain", ctrack->hostname, ctrack->dp->n); + } + else + VPRINT("local or in-domain redirect detected. it's not a DPI redirect.\n"); + } + else + { + // received not http reply. do not monitor this connection anymore + VPRINT("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); + } + if (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } + if (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname); + } + ctrack->bTamperInCutoff = true; +} + +void rst_in(t_ctrack *ctrack) +{ + DBGPRINT("rst_in hostname=%s\n", ctrack->hostname); + + if (ctrack->dp && ctrack->b_ah_check) + { + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); + + if (!ctrack->bTamperInCutoff && ctrack->hostname) + { + VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : incoming RST", ctrack->hostname, ctrack->dp->n); + auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } + } +} +void hup_out(t_ctrack *ctrack) +{ + DBGPRINT("hup_out hostname=%s\n", ctrack->hostname); + + if (ctrack->dp && ctrack->b_ah_check) + { + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); + + if (!ctrack->bTamperInCutoff && ctrack->hostname) + { + // local leg dropped connection after first request. probably due to timeout. + VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client closed connection without server reply", ctrack->hostname, ctrack->dp->n); + auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } + } +} diff --git a/tpws/tamper.h b/tpws/tamper.h new file mode 100644 index 00000000..ccc5c6fe --- /dev/null +++ b/tpws/tamper.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include "params.h" + +#define SPLIT_FLAG_DISORDER 0x01 +#define SPLIT_FLAG_OOB 0x02 + +typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto; +typedef struct +{ + // common state + t_l7proto l7proto; + bool bFirstReplyChecked; + bool bTamperInCutoff; + bool b_ah_check; + char *hostname; + struct desync_profile *dp; // desync profile cache +} t_ctrack; + +void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest); + +void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags); +void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size); +// connection reset by remote leg +void rst_in(t_ctrack *ctrack); +// local leg closed connection (timeout waiting response ?) +void hup_out(t_ctrack *ctrack); diff --git a/tpws/tpws.c b/tpws/tpws.c new file mode 100644 index 00000000..3e494850 --- /dev/null +++ b/tpws/tpws.c @@ -0,0 +1,1450 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpws.h" + +#ifdef BSD + #include +#endif + +#include "tpws_conn.h" +#include "hostlist.h" +#include "params.h" +#include "sec.h" +#include "redirect.h" +#include "helpers.h" +#include "gzip.h" +#include "pools.h" + +struct params_s params; + +bool bHup = false; +static void onhup(int sig) +{ + printf("HUP received !\n"); + printf("Will reload hostlist on next request (if any)\n"); + bHup = true; +} +// should be called in normal execution +void dohup(void) +{ + if (bHup) + { + if (!LoadIncludeHostLists() || !LoadExcludeHostLists()) + { + // what will we do without hostlist ?? sure, gonna die + exit(1); + } + bHup = false; + } +} + +static void onusr2(int sig) +{ + printf("\nHOSTFAIL POOL DUMP\n"); + + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + printf("\nDESYNC PROFILE %d\n",dpl->dp.n); + HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); + } + + printf("\n"); +} + + +static int8_t block_sigpipe(void) +{ + sigset_t sigset; + memset(&sigset, 0, sizeof(sigset)); + + //Get the old sigset, add SIGPIPE and update sigset + if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) { + DLOG_PERROR("sigprocmask (get)"); + return -1; + } + + if (sigaddset(&sigset, SIGPIPE) == -1) { + DLOG_PERROR("sigaddset"); + return -1; + } + + if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) { + DLOG_PERROR("sigprocmask (set)"); + return -1; + } + + return 0; +} + + +static bool is_interface_online(const char *ifname) +{ + struct ifreq ifr; + int sock; + + if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1) + return false; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = 0; + ioctl(sock, SIOCGIFFLAGS, &ifr); + close(sock); + return !!(ifr.ifr_flags & IFF_UP); +} +static int get_default_ttl(void) +{ + int sock,ttl=0; + socklen_t optlen=sizeof(ttl); + + if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))!=-1) + { + getsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, &optlen); + close(sock); + } + return ttl; +} + + +static void exithelp(void) +{ + printf( + " --bind-addr=|\t; for v6 link locals append %%interface_name\n" + " --bind-iface4=\t\t; bind to the first ipv4 addr of interface\n" + " --bind-iface6=\t\t; bind to the first ipv6 addr of interface\n" + " --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\n" + " --bind-wait-ifup=\t\t\t; wait for interface to appear and up\n" + " --bind-wait-ip=\t\t\t; after ifup wait for ip address to appear up to N seconds\n" + " --bind-wait-ip-linklocal=\t\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n" + " --bind-wait-only\t\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n" + " * multiple binds are supported. each bind-addr, bind-iface* start new bind\n" + " --connect-bind-addr=| ; address for outbound connections. for v6 link locals append %%interface_name\n" + " --port=\t\t\t\t; only one port number for all binds is supported\n" + " --socks\t\t\t\t; implement socks4/5 proxy instead of transparent proxy\n" + " --no-resolve\t\t\t\t; disable socks5 remote dns ability\n" + " --resolver-threads=\t\t; number of resolver worker threads\n" + " --local-rcvbuf=\n" + " --local-sndbuf=\n" + " --remote-rcvbuf=\n" + " --remote-sndbuf=\n" +#ifdef SPLICE_PRESENT + " --nosplice\t\t\t\t; do not use splice to transfer data between sockets\n" +#endif + " --skip-nodelay\t\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n" +#if defined(__linux__) || defined(__APPLE__) + " --local-tcp-user-timeout=\t; set tcp user timeout for local leg (default : %d, 0 = system default)\n" + " --remote-tcp-user-timeout=\t; set tcp user timeout for remote leg (default : %d, 0 = system default)\n" +#endif + " --maxconn=\n" +#ifdef SPLICE_PRESENT + " --maxfiles=\t\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n" +#else + " --maxfiles=\t\t; should be at least (connections*2+16)\n" +#endif + " --max-orphan-time=\t\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n" + " --daemon\t\t\t\t; daemonize\n" + " --pidfile=\t\t\t; write pid to file\n" + " --user=\t\t\t; drop root privs\n" + " --uid=uid[:gid]\t\t\t; drop root privs\n" +#if defined(__FreeBSD__) + " --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n" +#endif + " --debug=0|1|2|syslog|@\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" + " --debug-level=0|1|2\t\t\t; specify debug level\n" + "\nMULTI-STRATEGY:\n" + " --new\t\t\t\t\t; begin new strategy\n" + " --filter-l3=ipv4|ipv6\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" + " --filter-tcp=[~]port1[-port2]\t\t; TCP port filter. ~ means negation\n" + "\nHOSTLIST FILTER:\n" + " --hostlist=\t\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" + " --hostlist-exclude=\t\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" + " --hostlist-auto=\t\t; detect DPI blocks and build hostlist automatically\n" + " --hostlist-auto-fail-threshold=\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" + " --hostlist-auto-fail-time=\t; all failed attemps must be within these seconds (default : %d)\n" + " --hostlist-auto-debug=\t; debug auto hostlist positives\n" + "\nTAMPER:\n" + " --split-http-req=method|host\t\t; split at specified logical part of plain http request\n" + " --split-tls=sni|sniext\t\t\t; split at specified logical part of TLS ClientHello\n" + " --split-pos=\t\t; split at specified pos. split-http-req or split-tls take precedence for http.\n" + " --split-any-protocol\t\t\t; split not only http and https\n" +#if defined(BSD) && !defined(__APPLE__) + " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n" +#else + " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first\n" +#endif + " --oob[=http|tls]\t\t\t; when splitting send out of band byte. default is HEX 0x00.\n" + " --oob-data=|0xHEX\t\t; override default 0x00 OOB byte.\n" + " --hostcase\t\t\t\t; change Host: => host:\n" + " --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" + " --hostdot\t\t\t\t; add \".\" after Host: name\n" + " --hosttab\t\t\t\t; add tab after Host: name\n" + " --hostnospace\t\t\t\t; remove space after Host:\n" + " --hostpad=\t\t\t; add dummy padding headers before Host:\n" + " --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n" + " --methodspace\t\t\t\t; add extra space after method\n" + " --methodeol\t\t\t\t; add end-of-line before method\n" + " --unixeol\t\t\t\t; replace 0D0A to 0A\n" + " --tlsrec=sni|sniext\t\t\t; make 2 TLS records. split at specified logical part. don't split if SNI is not present\n" + " --tlsrec-pos=\t\t\t; make 2 TLS records. split at specified pos\n" +#ifdef __linux__ + " --mss=\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n" +#endif + " --tamper-start=[n]\t\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\n" + " --tamper-cutoff=[n]\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n", +#if defined(__linux__) || defined(__APPLE__) + DEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE, +#endif + HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT + ); + exit(1); +} +static void cleanup_params(void) +{ + dp_list_destroy(¶ms.desync_profiles); +} +static void exithelp_clean(void) +{ + cleanup_params(); + exithelp(); +} +static void exit_clean(int code) +{ + cleanup_params(); + exit(code); +} +static void nextbind_clean(void) +{ + params.binds_last++; + if (params.binds_last>=MAX_BINDS) + { + DLOG_ERR("maximum of %d binds are supported\n",MAX_BINDS); + exit_clean(1); + } +} +static void checkbind_clean(void) +{ + if (params.binds_last<0) + { + DLOG_ERR("start new bind with --bind-addr,--bind-iface*\n"); + exit_clean(1); + } +} + + +void save_default_ttl(void) +{ + if (!params.ttl_default) + { + params.ttl_default = get_default_ttl(); + if (!params.ttl_default) + { + DLOG_ERR("could not get default ttl\n"); + exit_clean(1); + } + } +} + +bool parse_httpreqpos(const char *s, enum httpreqpos *pos) +{ + if (!strcmp(s, "method")) + *pos = httpreqpos_method; + else if (!strcmp(s, "host")) + *pos = httpreqpos_host; + else + return false; + return true; +} +bool parse_tlspos(const char *s, enum tlspos *pos) +{ + if (!strcmp(s, "sni")) + *pos = tlspos_sni; + else if (!strcmp(s, "sniext")) + *pos = tlspos_sniext; + else + return false; + return true; +} + +static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) +{ + char *e,*p,c; + + for (p=opt,*ipv4=*ipv6=false ; p ; ) + { + if ((e = strchr(p,','))) + { + c=*e; + *e=0; + } + + if (!strcmp(p,"ipv4")) + *ipv4 = true; + else if (!strcmp(p,"ipv6")) + *ipv6 = true; + else return false; + + if (e) + { + *e++=c; + } + p = e; + } + return true; +} + +void parse_params(int argc, char *argv[]) +{ + int option_index = 0; + int v, i; + + memset(¶ms, 0, sizeof(params)); + params.maxconn = DEFAULT_MAX_CONN; + params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; + params.binds_last = -1; +#if defined(__linux__) || defined(__APPLE__) + params.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL; + params.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE; +#endif + +#if defined(__OpenBSD__) || defined(__APPLE__) + params.pf_enable = true; // OpenBSD and MacOS have no other choice +#endif + if (can_drop_root()) + { + params.uid = params.gid = 0x7FFFFFFF; // default uid:gid + params.droproot = true; + } + + struct desync_profile_list *dpl; + struct desync_profile *dp; + int desync_profile_count=0; + if (!(dpl = dp_list_add(¶ms.desync_profiles))) + { + DLOG_ERR("desync_profile_add: out of memory\n"); + exit_clean(1); + } + dp = &dpl->dp; + dp->n = ++desync_profile_count; + + const struct option long_options[] = { + { "help",no_argument,0,0 },// optidx=0 + { "h",no_argument,0,0 },// optidx=1 + { "bind-addr",required_argument,0,0 },// optidx=2 + { "bind-iface4",required_argument,0,0 },// optidx=3 + { "bind-iface6",required_argument,0,0 },// optidx=4 + { "bind-linklocal",required_argument,0,0 },// optidx=5 + { "bind-wait-ifup",required_argument,0,0 },// optidx=6 + { "bind-wait-ip",required_argument,0,0 },// optidx=7 + { "bind-wait-ip-linklocal",required_argument,0,0 },// optidx=8 + { "bind-wait-only",no_argument,0,0 },// optidx=9 + { "port",required_argument,0,0 },// optidx=10 + { "daemon",no_argument,0,0 },// optidx=11 + { "user",required_argument,0,0 },// optidx=12 + { "uid",required_argument,0,0 },// optidx=13 + { "maxconn",required_argument,0,0 },// optidx=14 + { "maxfiles",required_argument,0,0 },// optidx=15 + { "max-orphan-time",required_argument,0,0 },// optidx=16 + { "hostcase",no_argument,0,0 },// optidx=17 + { "hostspell",required_argument,0,0 },// optidx=18 + { "hostdot",no_argument,0,0 },// optidx=19 + { "hostnospace",no_argument,0,0 },// optidx=20 + { "hostpad",required_argument,0,0 },// optidx=21 + { "domcase",no_argument,0,0 },// optidx=22 + { "split-http-req",required_argument,0,0 },// optidx=23 + { "split-tls",required_argument,0,0 },// optidx=24 + { "split-pos",required_argument,0,0 },// optidx=25 + { "split-any-protocol",optional_argument,0,0},// optidx=26 + { "disorder",optional_argument,0,0 },// optidx=27 + { "oob",optional_argument,0,0 },// optidx=28 + { "oob-data",required_argument,0,0 },// optidx=29 + { "methodspace",no_argument,0,0 },// optidx=30 + { "methodeol",no_argument,0,0 },// optidx=31 + { "hosttab",no_argument,0,0 },// optidx=32 + { "unixeol",no_argument,0,0 },// optidx=33 + { "tlsrec",required_argument,0,0 },// optidx=34 + { "tlsrec-pos",required_argument,0,0 },// optidx=35 + { "hostlist",required_argument,0,0 },// optidx=36 + { "hostlist-exclude",required_argument,0,0 },// optidx=37 + { "hostlist-auto",required_argument,0,0}, // optidx=38 + { "hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=39 + { "hostlist-auto-fail-time",required_argument,0,0}, // optidx=40 + { "hostlist-auto-debug",required_argument,0,0}, // optidx=41 + { "pidfile",required_argument,0,0 },// optidx=42 + { "debug",optional_argument,0,0 },// optidx=43 + { "debug-level",required_argument,0,0 },// optidx=44 + { "local-rcvbuf",required_argument,0,0 },// optidx=45 + { "local-sndbuf",required_argument,0,0 },// optidx=46 + { "remote-rcvbuf",required_argument,0,0 },// optidx=47 + { "remote-sndbuf",required_argument,0,0 },// optidx=48 + { "socks",no_argument,0,0 },// optidx=40 + { "no-resolve",no_argument,0,0 },// optidx=50 + { "resolver-threads",required_argument,0,0 },// optidx=51 + { "skip-nodelay",no_argument,0,0 },// optidx=52 + { "tamper-start",required_argument,0,0 },// optidx=53 + { "tamper-cutoff",required_argument,0,0 },// optidx=54 + { "connect-bind-addr",required_argument,0,0 },// optidx=55 + + { "new",no_argument,0,0 }, // optidx=56 + { "filter-l3",required_argument,0,0 }, // optidx=57 + { "filter-tcp",required_argument,0,0 }, // optidx=58 + +#if defined(__FreeBSD__) + { "enable-pf",no_argument,0,0 },// optidx=59 +#elif defined(__APPLE__) + { "local-tcp-user-timeout",required_argument,0,0 },// optidx=59 + { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=60 +#elif defined(__linux__) + { "local-tcp-user-timeout",required_argument,0,0 },// optidx=59 + { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=60 + { "mss",required_argument,0,0 },// optidx=61 +#ifdef SPLICE_PRESENT + { "nosplice",no_argument,0,0 },// optidx=62 +#endif +#endif + { "hostlist-auto-retrans-threshold",optional_argument,0,0}, // ignored. for nfqws command line compatibility + { NULL,0,NULL,0 } + }; + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp_clean(); + switch (option_index) + { + case 0: + case 1: + exithelp_clean(); + break; + case 2: /* bind-addr */ + nextbind_clean(); + { + char *p = strchr(optarg,'%'); + if (p) + { + *p=0; + strncpy(params.binds[params.binds_last].bindiface, p+1, sizeof(params.binds[params.binds_last].bindiface)); + } + strncpy(params.binds[params.binds_last].bindaddr, optarg, sizeof(params.binds[params.binds_last].bindaddr)); + } + params.binds[params.binds_last].bindaddr[sizeof(params.binds[params.binds_last].bindaddr) - 1] = 0; + break; + case 3: /* bind-iface4 */ + nextbind_clean(); + params.binds[params.binds_last].bind_if6=false; + strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); + params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; + break; + case 4: /* bind-iface6 */ + nextbind_clean(); + params.binds[params.binds_last].bind_if6=true; + strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface)); + params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0; + break; + case 5: /* bind-linklocal */ + checkbind_clean(); + params.binds[params.binds_last].bindll = true; + if (!strcmp(optarg, "no")) + params.binds[params.binds_last].bindll=no; + else if (!strcmp(optarg, "prefer")) + params.binds[params.binds_last].bindll=prefer; + else if (!strcmp(optarg, "force")) + params.binds[params.binds_last].bindll=force; + else if (!strcmp(optarg, "unwanted")) + params.binds[params.binds_last].bindll=unwanted; + else + { + DLOG_ERR("invalid parameter in bind-linklocal : %s\n",optarg); + exit_clean(1); + } + break; + case 6: /* bind-wait-ifup */ + checkbind_clean(); + params.binds[params.binds_last].bind_wait_ifup = atoi(optarg); + break; + case 7: /* bind-wait-ip */ + checkbind_clean(); + params.binds[params.binds_last].bind_wait_ip = atoi(optarg); + break; + case 8: /* bind-wait-ip-linklocal */ + checkbind_clean(); + params.binds[params.binds_last].bind_wait_ip_ll = atoi(optarg); + break; + case 9: /* bind-wait-only */ + params.bind_wait_only = true; + break; + case 10: /* port */ + i = atoi(optarg); + if (i <= 0 || i > 65535) + { + DLOG_ERR("bad port number\n"); + exit_clean(1); + } + params.port = (uint16_t)i; + break; + case 11: /* daemon */ + params.daemon = true; + break; + case 12: /* user */ + { + struct passwd *pwd = getpwnam(optarg); + if (!pwd) + { + DLOG_ERR("non-existent username supplied\n"); + exit_clean(1); + } + params.uid = pwd->pw_uid; + params.gid = pwd->pw_gid; + params.droproot = true; + break; + } + case 13: /* uid */ + params.gid=0x7FFFFFFF; // default git. drop gid=0 + params.droproot = true; + if (sscanf(optarg,"%u:%u",¶ms.uid,¶ms.gid)<1) + { + DLOG_ERR("--uid should be : uid[:gid]\n"); + exit_clean(1); + } + break; + case 14: /* maxconn */ + params.maxconn = atoi(optarg); + if (params.maxconn <= 0 || params.maxconn > 10000) + { + DLOG_ERR("bad maxconn\n"); + exit_clean(1); + } + break; + case 15: /* maxfiles */ + params.maxfiles = atoi(optarg); + if (params.maxfiles < 0) + { + DLOG_ERR("bad maxfiles\n"); + exit_clean(1); + } + break; + case 16: /* max-orphan-time */ + params.max_orphan_time = atoi(optarg); + if (params.max_orphan_time < 0) + { + DLOG_ERR("bad max_orphan_time\n"); + exit_clean(1); + } + break; + case 17: /* hostcase */ + dp->hostcase = true; + params.tamper = true; + break; + case 18: /* hostspell */ + if (strlen(optarg) != 4) + { + DLOG_ERR("hostspell must be exactly 4 chars long\n"); + exit_clean(1); + } + dp->hostcase = true; + memcpy(dp->hostspell, optarg, 4); + params.tamper = true; + break; + case 19: /* hostdot */ + dp->hostdot = true; + params.tamper = true; + break; + case 20: /* hostnospace */ + dp->hostnospace = true; + params.tamper = true; + break; + case 21: /* hostpad */ + dp->hostpad = atoi(optarg); + params.tamper = true; + break; + case 22: /* domcase */ + dp->domcase = true; + params.tamper = true; + break; + case 23: /* split-http-req */ + if (!parse_httpreqpos(optarg, &dp->split_http_req)) + { + DLOG_ERR("Invalid argument for split-http-req\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 24: /* split-tls */ + if (!parse_tlspos(optarg, &dp->split_tls)) + { + DLOG_ERR("Invalid argument for split-tls\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 25: /* split-pos */ + i = atoi(optarg); + if (i>0) + dp->split_pos = i; + else + { + DLOG_ERR("Invalid argument for split-pos\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 26: /* split-any-protocol */ + dp->split_any_protocol = true; + break; + case 27: /* disorder */ + if (optarg) + { + if (!strcmp(optarg,"http")) dp->disorder_http=true; + else if (!strcmp(optarg,"tls")) dp->disorder_tls=true; + else + { + DLOG_ERR("Invalid argument for disorder\n"); + exit_clean(1); + } + } + else + dp->disorder = true; + save_default_ttl(); + break; + case 28: /* oob */ + if (optarg) + { + if (!strcmp(optarg,"http")) dp->oob_http=true; + else if (!strcmp(optarg,"tls")) dp->oob_tls=true; + else + { + DLOG_ERR("Invalid argument for oob\n"); + exit_clean(1); + } + } + else + dp->oob = true; + break; + case 29: /* oob-data */ + { + size_t l = strlen(optarg); + unsigned int bt; + if (l==1) dp->oob_byte = (uint8_t)*optarg; + else if (l!=4 || sscanf(optarg,"0x%02X",&bt)!=1) + { + DLOG_ERR("Invalid argument for oob-data\n"); + exit_clean(1); + } + else dp->oob_byte = (uint8_t)bt; + } + break; + case 30: /* methodspace */ + dp->methodspace = true; + params.tamper = true; + break; + case 31: /* methodeol */ + dp->methodeol = true; + params.tamper = true; + break; + case 32: /* hosttab */ + dp->hosttab = true; + params.tamper = true; + break; + case 33: /* unixeol */ + dp->unixeol = true; + params.tamper = true; + break; + case 34: /* tlsrec */ + if (!parse_tlspos(optarg, &dp->tlsrec)) + { + DLOG_ERR("Invalid argument for tlsrec\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 35: /* tlsrec-pos */ + if ((dp->tlsrec_pos = atoi(optarg))>0) + dp->tlsrec = tlspos_pos; + else + { + DLOG_ERR("Invalid argument for tlsrec-pos\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 36: /* hostlist */ + if (!strlist_add(&dp->hostlist_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 37: /* hostlist-exclude */ + if (!strlist_add(&dp->hostlist_exclude_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + params.tamper = true; + break; + case 38: /* hostlist-auto */ + if (*dp->hostlist_auto_filename) + { + DLOG_ERR("only one auto hostlist per profile is supported\n"); + exit_clean(1); + } + { + FILE *F = fopen(optarg,"a+t"); + 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 (params.droproot && chown(optarg, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg); + } + if (!strlist_add(&dp->hostlist_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + strncpy(dp->hostlist_auto_filename, optarg, sizeof(dp->hostlist_auto_filename)); + dp->hostlist_auto_filename[sizeof(dp->hostlist_auto_filename) - 1] = '\0'; + params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice. + break; + case 39: /* 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 40: /* 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 41: /* hostlist-auto-debug */ + { + FILE *F = fopen(optarg,"a+t"); + if (!F) + { + DLOG_ERR("cannot create %s\n", optarg); + exit_clean(1); + } + fclose(F); + if (params.droproot && chown(optarg, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", optarg); + strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); + params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; + } + break; + case 42: /* pidfile */ + strncpy(params.pidfile,optarg,sizeof(params.pidfile)); + params.pidfile[sizeof(params.pidfile)-1]='\0'; + break; + case 43: /* debug */ + if (optarg) + { + if (*optarg=='@') + { + strncpy(params.debug_logfile,optarg+1,sizeof(params.debug_logfile)); + params.debug_logfile[sizeof(params.debug_logfile)-1] = 0; + FILE *F = fopen(params.debug_logfile,"wt"); + if (!F) + { + fprintf(stderr, "cannot create %s\n", params.debug_logfile); + exit_clean(1); + } + if (params.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.debug) params.debug = 1; + params.debug_target = LOG_TARGET_FILE; + } + else if (!strcmp(optarg,"syslog")) + { + if (!params.debug) params.debug = 1; + params.debug_target = LOG_TARGET_SYSLOG; + openlog("tpws",LOG_PID,LOG_USER); + } + else + { + params.debug = atoi(optarg); + params.debug_target = LOG_TARGET_CONSOLE; + } + } + else + { + params.debug = 1; + params.debug_target = LOG_TARGET_CONSOLE; + } + break; + case 44: /* debug-level */ + params.debug = atoi(optarg); + break; + case 45: /* local-rcvbuf */ +#ifdef __linux__ + params.local_rcvbuf = atoi(optarg)/2; +#else + params.local_rcvbuf = atoi(optarg); +#endif + break; + case 46: /* local-sndbuf */ +#ifdef __linux__ + params.local_sndbuf = atoi(optarg)/2; +#else + params.local_sndbuf = atoi(optarg); +#endif + break; + case 47: /* remote-rcvbuf */ +#ifdef __linux__ + params.remote_rcvbuf = atoi(optarg)/2; +#else + params.remote_rcvbuf = atoi(optarg); +#endif + break; + case 48: /* remote-sndbuf */ +#ifdef __linux__ + params.remote_sndbuf = atoi(optarg)/2; +#else + params.remote_sndbuf = atoi(optarg); +#endif + break; + case 49: /* socks */ + params.proxy_type = CONN_TYPE_SOCKS; + break; + case 50: /* no-resolve */ + params.no_resolve = true; + break; + case 51: /* resolver-threads */ + params.resolver_threads = atoi(optarg); + if (params.resolver_threads<1 || params.resolver_threads>300) + { + DLOG_ERR("resolver-threads must be within 1..300\n"); + exit_clean(1); + } + break; + case 52: /* skip-nodelay */ + params.skip_nodelay = true; + break; + case 53: /* tamper-start */ + { + const char *p=optarg; + if (*p=='n') + { + dp->tamper_start_n=true; + p++; + } + else + dp->tamper_start_n=false; + dp->tamper_start = atoi(p); + } + params.tamper_lim = true; + break; + case 54: /* tamper-cutoff */ + { + const char *p=optarg; + if (*p=='n') + { + dp->tamper_cutoff_n=true; + p++; + } + else + dp->tamper_cutoff_n=false; + dp->tamper_cutoff = atoi(p); + } + params.tamper_lim = true; + break; + case 55: /* connect-bind-addr */ + { + char *p = strchr(optarg,'%'); + if (p) *p++=0; + if (inet_pton(AF_INET, optarg, ¶ms.connect_bind4.sin_addr)) + { + params.connect_bind4.sin_family = AF_INET; + } + else if (inet_pton(AF_INET6, optarg, ¶ms.connect_bind6.sin6_addr)) + { + params.connect_bind6.sin6_family = AF_INET6; + if (p && *p) + { + // copy interface name for delayed resolution + strncpy(params.connect_bind6_ifname,p,sizeof(params.connect_bind6_ifname)); + params.connect_bind6_ifname[sizeof(params.connect_bind6_ifname)-1]=0; + } + + } + else + { + DLOG_ERR("bad bind addr : %s\n", optarg); + exit_clean(1); + } + } + break; + + + case 56: /* new */ + 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; + break; + case 57: /* 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 58: /* filter-tcp */ + if (!pf_parse(optarg,&dp->pf_tcp)) + { + DLOG_ERR("Invalid port filter : %s\n",optarg); + exit_clean(1); + } + break; + +#if defined(__FreeBSD__) + case 59: /* enable-pf */ + params.pf_enable = true; + break; +#elif defined(__linux__) || defined(__APPLE__) + case 59: /* local-tcp-user-timeout */ + params.tcp_user_timeout_local = atoi(optarg); + if (params.tcp_user_timeout_local<0 || params.tcp_user_timeout_local>86400) + { + DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); + exit_clean(1); + } + break; + case 60: /* remote-tcp-user-timeout */ + params.tcp_user_timeout_remote = atoi(optarg); + if (params.tcp_user_timeout_remote<0 || params.tcp_user_timeout_remote>86400) + { + DLOG_ERR("Invalid argument for tcp user timeout. must be 0..86400\n"); + exit_clean(1); + } + break; +#endif + +#if defined(__linux__) + case 61: /* mss */ + // this option does not work in any BSD and MacOS. OS may accept but it changes nothing + dp->mss = atoi(optarg); + if (dp->mss<88 || dp->mss>32767) + { + DLOG_ERR("Invalid value for MSS. Linux accepts MSS 88-32767.\n"); + exit_clean(1); + } + break; +#ifdef SPLICE_PRESENT + case 62: /* nosplice */ + params.nosplice = true; + break; +#endif +#endif + } + } + if (!params.bind_wait_only && !params.port) + { + DLOG_ERR("Need port number\n"); + exit_clean(1); + } + if (params.binds_last<=0) + { + params.binds_last=0; // default bind to all + } + if (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50; + + VPRINT("adding low-priority default empty desync profile\n"); + // add default empty profile + if (!(dpl = dp_list_add(¶ms.desync_profiles))) + { + DLOG_ERR("desync_profile_add: out of memory\n"); + exit_clean(1); + } + + DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count); + + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + dp = &dpl->dp; + if (dp->split_tls==tlspos_none && dp->split_pos) dp->split_tls=tlspos_pos; + if (dp->split_http_req==httpreqpos_none && dp->split_pos) dp->split_http_req=httpreqpos_pos; + if (*dp->hostlist_auto_filename) dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + if (params.skip_nodelay && (dp->split_tls || dp->split_http_req || dp->split_pos)) + { + DLOG_ERR("Cannot split with --skip-nodelay\n"); + exit_clean(1); + } + } + + if (!LoadIncludeHostLists()) + { + DLOG_ERR("Include hostlist load failed\n"); + exit_clean(1); + } + if (!LoadExcludeHostLists()) + { + DLOG_ERR("Exclude hostlist load failed\n"); + exit_clean(1); + } +} + + +static bool find_listen_addr(struct sockaddr_storage *salisten, const char *bindiface, bool bind_if6, enum bindll bindll, int *if_index) +{ + struct ifaddrs *addrs,*a; + bool found=false; + + if (getifaddrs(&addrs)<0) + return false; + + // for ipv6 preference order + // bind-linklocal-1 : link-local,any + // bind-linklocal=0 : private,global,link-local + for(int pass=0;pass<3;pass++) + { + a = addrs; + while (a) + { + if (a->ifa_addr) + { + if (a->ifa_addr->sa_family==AF_INET && + *bindiface && !bind_if6 && !strcmp(a->ifa_name, bindiface)) + { + salisten->ss_family = AF_INET; + memcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr)); + found=true; + goto ex; + } + // ipv6 links locals are fe80::/10 + else if (a->ifa_addr->sa_family==AF_INET6 + && + ((!*bindiface && (bindll==prefer || bindll==force)) || + (*bindiface && bind_if6 && !strcmp(a->ifa_name, bindiface))) + && + ((bindll==force && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || + (bindll==prefer && ((pass==0 && is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || pass==2)) || + (bindll==no && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)))) || + (bindll==unwanted && ((pass==0 && is_private6((struct sockaddr_in6*)a->ifa_addr)) || (pass==1 && !is_linklocal((struct sockaddr_in6*)a->ifa_addr)) || pass==2))) + ) + { + salisten->ss_family = AF_INET6; + memcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr)); + if (if_index) *if_index = if_nametoindex(a->ifa_name); + found=true; + goto ex; + } + } + a = a->ifa_next; + } + } +ex: + freeifaddrs(addrs); + return found; +} + +static bool read_system_maxfiles(rlim_t *maxfile) +{ +#ifdef __linux__ + FILE *F; + int n; + uintmax_t um; + if (!(F=fopen("/proc/sys/fs/file-max","r"))) + return false; + n=fscanf(F,"%ju",&um); + fclose(F); + if (!n) return false; + *maxfile = (rlim_t)um; + return true; +#elif defined(BSD) + int maxfiles,mib[2]={CTL_KERN, KERN_MAXFILES}; + size_t len = sizeof(maxfiles); + if (sysctl(mib,2,&maxfiles,&len,NULL,0)==-1) + return false; + *maxfile = (rlim_t)maxfiles; + return true; +#else + return false; +#endif +} +static bool write_system_maxfiles(rlim_t maxfile) +{ +#ifdef __linux__ + FILE *F; + int n; + if (!(F=fopen("/proc/sys/fs/file-max","w"))) + return false; + n=fprintf(F,"%ju",(uintmax_t)maxfile); + fclose(F); + return !!n; +#elif defined(BSD) + int maxfiles=(int)maxfile,mib[2]={CTL_KERN, KERN_MAXFILES}; + if (sysctl(mib,2,NULL,0,&maxfiles,sizeof(maxfiles))==-1) + return false; + return true; +#else + return false; +#endif +} + +static bool set_ulimit(void) +{ + rlim_t fdmax,fdmin_system,cur_lim=0; + int n; + + if (!params.maxfiles) + { + // 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket), 2 fds (2 socket) for nosplice + // additional 1/2 for unpaired remote legs sending buffers + // 16 for listen_fd, epoll, hostlist, ... +#ifdef SPLICE_PRESENT + fdmax = (params.nosplice ? 2 : (params.tamper && !params.tamper_lim ? 4 : 6)) * params.maxconn; +#else + fdmax = 2 * params.maxconn; +#endif + fdmax += fdmax/2 + 16; + } + else + fdmax = params.maxfiles; + fdmin_system = fdmax + 4096; + DBGPRINT("set_ulimit : fdmax=%ju fdmin_system=%ju\n",(uintmax_t)fdmax,(uintmax_t)fdmin_system); + + if (!read_system_maxfiles(&cur_lim)) + return false; + DBGPRINT("set_ulimit : current system file-max=%ju\n",(uintmax_t)cur_lim); + if (cur_lim 0) + { + int sec=0; + if (!is_interface_online(params.binds[i].bindiface)) + { + DLOG_CONDUP("waiting for ifup of %s for up to %d second(s)...\n",params.binds[i].bindiface,params.binds[i].bind_wait_ifup); + do + { + sleep(1); + sec++; + } + while (!is_interface_online(params.binds[i].bindiface) && sec=params.binds[i].bind_wait_ifup) + { + DLOG_CONDUP("wait timed out\n"); + goto exiterr; + } + } + } + if (!(if_index = if_nametoindex(params.binds[i].bindiface)) && params.binds[i].bind_wait_ip<=0) + { + DLOG_CONDUP("bad iface %s\n",params.binds[i].bindiface); + goto exiterr; + } + } + list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip; + if (*params.binds[i].bindaddr) + { + if (inet_pton(AF_INET, params.binds[i].bindaddr, &((struct sockaddr_in*)(&list[i].salisten))->sin_addr)) + { + list[i].salisten.ss_family = AF_INET; + } + else if (inet_pton(AF_INET6, params.binds[i].bindaddr, &((struct sockaddr_in6*)(&list[i].salisten))->sin6_addr)) + { + list[i].salisten.ss_family = AF_INET6; + list[i].ipv6_only = 1; + } + else + { + DLOG_CONDUP("bad bind addr : %s\n", params.binds[i].bindaddr); + goto exiterr; + } + } + else + { + if (*params.binds[i].bindiface || params.binds[i].bindll) + { + bool found; + enum bindll bindll_1; + int sec=0; + + if (params.binds[i].bind_wait_ip > 0) + { + DLOG_CONDUP("waiting for ip on %s for up to %d second(s)...\n", *params.binds[i].bindiface ? params.binds[i].bindiface : "", params.binds[i].bind_wait_ip); + if (params.binds[i].bind_wait_ip_ll>0) + { + if (params.binds[i].bindll==prefer) + DLOG_CONDUP("during the first %d second(s) accepting only link locals...\n", params.binds[i].bind_wait_ip_ll); + else if (params.binds[i].bindll==unwanted) + DLOG_CONDUP("during the first %d second(s) accepting only ipv6 globals...\n", params.binds[i].bind_wait_ip_ll); + } + } + + for(;;) + { + // allow, no, prefer, force + bindll_1 = (params.binds[i].bindll==prefer && sec=params.binds[i].bind_wait_ip) + break; + + sleep(1); + sec++; + } + + if (!found) + { + DLOG_CONDUP("suitable ip address not found\n"); + goto exiterr; + } + list[i].bind_wait_ip_left = params.binds[i].bind_wait_ip - sec; + list[i].ipv6_only=1; + } + else + { + list[i].salisten.ss_family = AF_INET6; + // leave sin6_addr zero + } + } + if (list[i].salisten.ss_family == AF_INET6) + { + list[i].salisten_len = sizeof(struct sockaddr_in6); + ((struct sockaddr_in6*)(&list[i].salisten))->sin6_port = htons(params.port); + if (is_linklocal((struct sockaddr_in6*)(&list[i].salisten))) + ((struct sockaddr_in6*)(&list[i].salisten))->sin6_scope_id = if_index; + } + else + { + list[i].salisten_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in*)(&list[i].salisten))->sin_port = htons(params.port); + } + } + + if (params.bind_wait_only) + { + DLOG_CONDUP("bind wait condition satisfied\n"); + exit_v = 0; + goto exiterr; + } + + if (params.proxy_type==CONN_TYPE_TRANSPARENT && !redir_init()) + { + DLOG_ERR("could not initialize redirector !!!\n"); + goto exiterr; + } + + for(i=0;i<=params.binds_last;i++) + { + if (params.debug) + { + ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); + VPRINT("Binding %d to %s\n",i,ip_port); + } + + if ((listen_fd[i] = socket(list[i].salisten.ss_family, SOCK_STREAM, 0)) == -1) { + DLOG_PERROR("socket"); + goto exiterr; + } + +#ifndef __OpenBSD__ +// in OpenBSD always IPV6_ONLY for wildcard sockets + if ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1) + { + DLOG_PERROR("setsockopt (IPV6_ONLY)"); + goto exiterr; + } +#endif + + if (setsockopt(listen_fd[i], SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) + { + DLOG_PERROR("setsockopt (SO_REUSEADDR)"); + goto exiterr; + } + + //Mark that this socket can be used for transparent proxying + //This allows the socket to accept connections for non-local IPs + if (params.proxy_type==CONN_TYPE_TRANSPARENT) + { + #ifdef __linux__ + if (setsockopt(listen_fd[i], SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1) + { + DLOG_PERROR("setsockopt (IP_TRANSPARENT)"); + goto exiterr; + } + #elif defined(BSD) && defined(SO_BINDANY) + if (setsockopt(listen_fd[i], SOL_SOCKET, SO_BINDANY, &yes, sizeof(yes)) == -1) + { + DLOG_PERROR("setsockopt (SO_BINDANY)"); + goto exiterr; + } + #endif + } + + if (!set_socket_buffers(listen_fd[i], params.local_rcvbuf, params.local_sndbuf)) + goto exiterr; + if (!params.local_rcvbuf) + { + // HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ? + int v; + socklen_t sz=sizeof(int); + if (!getsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,&sz)) + { + v/=2; + setsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,sizeof(int)); + } + } + bool bBindBug=false; + for(;;) + { + if (bind(listen_fd[i], (struct sockaddr *)&list[i].salisten, list[i].salisten_len) == -1) + { + // in linux strange behaviour was observed + // just after ifup and address assignment there's short window when bind() can't bind to addresses got from getifaddrs() + // it does not happen to transparent sockets because they can bind to any non-existend ip + // also only ipv6 seem to be buggy this way + if (errno==EADDRNOTAVAIL && params.proxy_type!=CONN_TYPE_TRANSPARENT && list[i].bind_wait_ip_left) + { + if (!bBindBug) + { + ntop46_port((struct sockaddr *)&list[i].salisten, ip_port, sizeof(ip_port)); + DLOG_CONDUP("address %s is not available. will retry for %d sec\n",ip_port,list[i].bind_wait_ip_left); + bBindBug=true; + } + sleep(1); + list[i].bind_wait_ip_left--; + continue; + } + DLOG_PERROR("bind"); + goto exiterr; + } + break; + } + if (listen(listen_fd[i], BACKLOG) == -1) + { + DLOG_PERROR("listen"); + goto exiterr; + } + } + + set_ulimit(); + sec_harden(); + + if (params.droproot && !droproot(params.uid,params.gid)) + goto exiterr; + print_id(); + //splice() causes the process to receive the SIGPIPE-signal if one part (for + //example a socket) is closed during splice(). I would rather have splice() + //fail and return -1, so blocking SIGPIPE. + if (block_sigpipe() == -1) { + DLOG_ERR("Could not block SIGPIPE signal\n"); + goto exiterr; + } + + DLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); + if (!params.tamper) DLOG_CONDUP("TCP proxy mode (no tampering)\n"); + + signal(SIGHUP, onhup); + signal(SIGUSR2, onusr2); + + retval = event_loop(listen_fd,params.binds_last+1); + exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + DLOG_CONDUP("Exiting\n"); + +exiterr: + redir_close(); + for(i=0;i<=params.binds_last;i++) if (listen_fd[i]!=-1) close(listen_fd[i]); + cleanup_params(); + return exit_v; +} diff --git a/tpws/tpws.h b/tpws/tpws.h new file mode 100644 index 00000000..fa4eb307 --- /dev/null +++ b/tpws/tpws.h @@ -0,0 +1,9 @@ +#pragma once + +#ifdef __linux__ + #define SPLICE_PRESENT +#endif + +#include + +void dohup(void); diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c new file mode 100644 index 00000000..3597ae84 --- /dev/null +++ b/tpws/tpws_conn.c @@ -0,0 +1,1690 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpws.h" +#include "tpws_conn.h" +#include "redirect.h" +#include "tamper.h" +#include "socks.h" +#include "helpers.h" +#include "hostlist.h" + + +// keep separate legs counter. counting every time thousands of legs can consume cpu +static int legs_local, legs_remote; +/* +static void count_legs(struct tailhead *conn_list) +{ + tproxy_conn_t *conn = NULL; + + legs_local = legs_remote = 0; + TAILQ_FOREACH(conn, conn_list, conn_ptrs) + conn->remote ? legs_remote++ : legs_local++; + +} +*/ +static void print_legs(void) +{ + VPRINT("Legs : local:%d remote:%d\n", legs_local, legs_remote); +} + + +static bool socks5_send_rep(int fd,uint8_t rep) +{ + s5_rep s5rep; + memset(&s5rep,0,sizeof(s5rep)); + s5rep.ver = 5; + s5rep.rep = rep; + s5rep.atyp = S5_ATYP_IP4; + return send(fd,&s5rep,sizeof(s5rep),MSG_DONTWAIT)==sizeof(s5rep); +} +static bool socks5_send_rep_errno(int fd,int errn) +{ + uint8_t rep; + switch(errn) + { + case 0: + rep=S5_REP_OK; break; + case ECONNREFUSED: + rep=S5_REP_CONN_REFUSED; break; + case ENETUNREACH: + rep=S5_REP_NETWORK_UNREACHABLE; break; + case ETIMEDOUT: + case EHOSTUNREACH: + rep=S5_REP_HOST_UNREACHABLE; break; + default: + rep=S5_REP_GENERAL_FAILURE; + } + return socks5_send_rep(fd,rep); +} +static bool socks4_send_rep(int fd, uint8_t rep) +{ + s4_rep s4rep; + memset(&s4rep, 0, sizeof(s4rep)); + s4rep.rep = rep; + return send(fd, &s4rep, sizeof(s4rep), MSG_DONTWAIT) == sizeof(s4rep); +} +static bool socks4_send_rep_errno(int fd, int errn) +{ + return socks4_send_rep(fd, errn ? S4_REP_FAILED : S4_REP_OK); +} +static bool socks_send_rep(uint8_t ver, int fd, uint8_t rep5) +{ + return ver==5 ? socks5_send_rep(fd, rep5) : socks4_send_rep(fd, rep5 ? S4_REP_FAILED : S4_REP_OK); +} +static bool socks_send_rep_errno(uint8_t ver, int fd, int errn) +{ + return ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn); +} + + +ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl) +{ + ssize_t wr; + + if (ttl) + { + DBGPRINT("send_with_ttl %d fd=%d\n",ttl,fd); + if (!set_ttl_hl(fd, ttl)) + //DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); + DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); + } + wr = send(fd, buf, len, flags); + if (ttl) + { + int e=errno; + if (!set_ttl_hl(fd, params.ttl_default)) + DLOG_ERR("could not set ttl %d to fd=%d\n",params.ttl_default,fd); + errno=e; + } + return wr; +} + + +static bool send_buffer_create(send_buffer_t *sb, const void *data, size_t len, size_t extra_bytes, int flags, int ttl) +{ + if (sb->data) + { + DLOG_ERR("FATAL : send_buffer_create but buffer is not empty\n"); + exit(1); + } + sb->data = malloc(len + extra_bytes); + if (!sb->data) + { + DBGPRINT("send_buffer_create failed\n"); + return false; + } + if (data) memcpy(sb->data,data,len); + sb->len = len; + sb->pos = 0; + sb->ttl = ttl; + sb->flags = flags; + return true; +} +static bool send_buffer_realloc(send_buffer_t *sb, size_t extra_bytes) +{ + if (sb->data) + { + uint8_t *p = (uint8_t*)realloc(sb->data, sb->len + extra_bytes); + if (p) + { + sb->data = p; + DBGPRINT("reallocated send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); + return true; + } + else + { + DBGPRINT("failed to realloc send_buffer from %zd to %zd\n", sb->len, sb->len + extra_bytes); + } + } + return false; +} + +static void send_buffer_free(send_buffer_t *sb) +{ + if (sb->data) + { + free(sb->data); + sb->data = NULL; + } +} +static void send_buffers_free(send_buffer_t *sb_array, int count) +{ + for (int i=0;iwr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); +} +static bool send_buffer_present(send_buffer_t *sb) +{ + return !!sb->data; +} +static bool send_buffers_present(send_buffer_t *sb_array, int count) +{ + for(int i=0;idata + sb->pos, sb->len - sb->pos, sb->flags, sb->ttl); + DBGPRINT("send_buffer_send len=%zu pos=%zu wr=%zd err=%d\n",sb->len,sb->pos,wr,errno); + if (wr>0) + { + sb->pos += wr; + if (sb->pos >= sb->len) + { + send_buffer_free(sb); + } + } + else if (wr<0 && errno==EAGAIN) wr=0; + + return wr; +} +static ssize_t send_buffers_send(send_buffer_t *sb_array, int count, int fd, size_t *real_wr) +{ + ssize_t wr,twr=0; + + for (int i=0;iconn_type==CONN_TYPE_SOCKS && conn->socks_state!=S_TCP); +} + +static bool conn_partner_alive(tproxy_conn_t *conn) +{ + return conn->partner && conn->partner->state!=CONN_CLOSED; +} +static bool conn_buffers_present(tproxy_conn_t *conn) +{ + return send_buffers_present(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0])); +} +static ssize_t conn_buffers_send(tproxy_conn_t *conn) +{ + size_t wr,real_twr; + wr = send_buffers_send(conn->wr_buf,sizeof(conn->wr_buf)/sizeof(conn->wr_buf[0]), conn->fd, &real_twr); + conn->twr += real_twr; + return wr; +} +static bool conn_has_unsent(tproxy_conn_t *conn) +{ + return conn->wr_unsent || conn_buffers_present(conn); +} +static int conn_bytes_unread(tproxy_conn_t *conn) +{ + int numbytes=-1; + ioctl(conn->fd, FIONREAD, &numbytes); + return numbytes; +} +static bool conn_has_unsent_pair(tproxy_conn_t *conn) +{ + return conn_has_unsent(conn) || (conn_partner_alive(conn) && conn_has_unsent(conn->partner)); +} + +static bool conn_shutdown(tproxy_conn_t *conn) +{ + conn->bShutdown = true; + if (shutdown(conn->fd,SHUT_WR)<0) + { + DLOG_PERROR("shutdown"); + return false; + } + return true; +} + +static ssize_t send_or_buffer(send_buffer_t *sb, int fd, const void *buf, size_t len, int flags, int ttl) +{ + ssize_t wr=0; + if (len) + { + wr = send_with_ttl(fd, buf, len, flags, ttl); + if (wr<0 && errno==EAGAIN) wr=0; + if (wr>=0 && wr=2) + { + int v; + socklen_t sz; + sz=sizeof(int); + if (!getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&v,&sz)) + DBGPRINT("fd=%d SO_RCVBUF=%d\n",fd,v); + sz=sizeof(int); + if (!getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&v,&sz)) + DBGPRINT("fd=%d SO_SNDBUF=%d\n",fd,v); + } +} + +bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) +{ + DBGPRINT("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n",fd,rcvbuf,sndbuf); + if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0) + { + DLOG_PERROR("setsockopt (SO_RCVBUF)"); + close(fd); + return false; + } + if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0) + { + DLOG_PERROR("setsockopt (SO_SNDBUF)"); + close(fd); + return false; + } + dbgprint_socket_buffers(fd); + return true; +} + +static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err) +{ + // if proxy mode acknowledge connection request + // conn = remote. conn->partner = local + if (!conn->remote || !conn_partner_alive(conn)) return false; + bool bres = true; + switch(conn->partner->conn_type) + { + case CONN_TYPE_SOCKS: + if (conn->partner->socks_state==S_WAIT_CONNECTION) + { + conn->partner->socks_state=S_TCP; + bres = socks_send_rep_errno(conn->partner->socks_ver,conn->partner->fd,sock_err); + DBGPRINT("socks connection acknowledgement. bres=%d remote_errn=%d remote_fd=%d local_fd=%d\n",bres,sock_err,conn->fd,conn->partner->fd); + } + break; + } + return bres; +} + +#if defined(__linux__) || defined(__APPLE__) + +static void set_user_timeout(int fd, int timeout) +{ +#ifdef __linux__ + if (timeout>0) + { + int msec = 1000*timeout; + if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &msec, sizeof(int)) <0) + DLOG_PERROR("setsockopt (TCP_USER_TIMEOUT)"); + } +#elif defined(__APPLE__) + if (timeout>0 && setsockopt(fd, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &timeout, sizeof(int)) <0) + DLOG_PERROR("setsockopt (TCP_RXT_CONNDROPTIME)"); +#endif +} + +#else + +#define set_user_timeout(fd,timeout) + +#endif + + +//Createas a socket and initiates the connection to the host specified by +//remote_addr. +//Returns -1 if something fails, >0 on success (socket fd). +static int connect_remote(const struct sockaddr *remote_addr, int mss) +{ + int remote_fd = 0, yes = 1, no = 0; + + + if((remote_fd = socket(remote_addr->sa_family, SOCK_STREAM, 0)) < 0) + { + DLOG_PERROR("socket (connect_remote)"); + return -1; + } + // Use NONBLOCK to avoid slow connects affecting the performance of other connections + // separate fcntl call to comply with macos + if (fcntl(remote_fd, F_SETFL, O_NONBLOCK)<0) + { + DLOG_PERROR("socket set O_NONBLOCK (connect_remote)"); + close(remote_fd); + return -1; + } + if (setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) + { + DLOG_PERROR("setsockopt (SO_REUSEADDR, connect_remote)"); + close(remote_fd); + return -1; + } + if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf)) + return -1; + if (!set_keepalive(remote_fd)) + { + DLOG_PERROR("set_keepalive"); + close(remote_fd); + return -1; + } + if (setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, params.skip_nodelay ? &no : &yes, sizeof(int)) <0) + { + DLOG_PERROR("setsockopt (TCP_NODELAY, connect_remote)"); + close(remote_fd); + return -1; + } +#ifdef __linux__ + if (mss) + { + VPRINT("Setting MSS %d\n", mss); + if (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(int)) <0) + { + DLOG_PERROR("setsockopt (TCP_MAXSEG, connect_remote)"); + close(remote_fd); + return -1; + } + } +#endif + + // if no bind address specified - address family will be 0 in params_connect_bindX + if(remote_addr->sa_family == params.connect_bind4.sin_family) + { + if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind4, sizeof(struct sockaddr_in)) == -1) + { + DLOG_PERROR("bind on connect"); + close(remote_fd); + return -1; + } + } + else if(remote_addr->sa_family == params.connect_bind6.sin6_family) + { + if (*params.connect_bind6_ifname && !params.connect_bind6.sin6_scope_id) + { + params.connect_bind6.sin6_scope_id=if_nametoindex(params.connect_bind6_ifname); + if (!params.connect_bind6.sin6_scope_id) + { + DLOG_ERR("interface name not found : %s\n", params.connect_bind6_ifname); + close(remote_fd); + return -1; + } + } + + if (bind(remote_fd, (struct sockaddr *)¶ms.connect_bind6, sizeof(struct sockaddr_in6)) == -1) + { + DLOG_PERROR("bind on connect"); + close(remote_fd); + return -1; + } + } + + set_user_timeout(remote_fd, params.tcp_user_timeout_remote); + + if (connect(remote_fd, remote_addr, remote_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0) + { + if(errno != EINPROGRESS) + { + DLOG_PERROR("connect (connect_remote)"); + close(remote_fd); + return -1; + } + } + DBGPRINT("Connecting remote fd=%d\n",remote_fd); + + return remote_fd; +} + + +//Free resources occupied by this connection +static void free_conn(tproxy_conn_t *conn) +{ + if (!conn) return; + if (conn->fd) close(conn->fd); + if (conn->splice_pipe[0]) + { + close(conn->splice_pipe[0]); + close(conn->splice_pipe[1]); + } + conn_free_buffers(conn); + if (conn->partner) conn->partner->partner=NULL; + if (conn->track.hostname) free(conn->track.hostname); + if (conn->socks_ri) conn->socks_ri->ptr = NULL; // detach conn + free(conn); +} +static tproxy_conn_t *new_conn(int fd, bool remote) +{ + tproxy_conn_t *conn; + + //Create connection object and fill in information + if((conn = (tproxy_conn_t*) calloc(1, sizeof(tproxy_conn_t))) == NULL) + { + DLOG_ERR("Could not allocate memory for connection\n"); + return NULL; + } + + conn->state = CONN_UNAVAILABLE; + conn->fd = fd; + conn->remote = remote; + +#ifdef SPLICE_PRESENT + // if dont tamper - both legs are spliced, create 2 pipes + // otherwise create pipe only in local leg + if (!params.nosplice && ( !remote || !params.tamper || params.tamper_lim ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) + { + DLOG_ERR("Could not create the splice pipe\n"); + free_conn(conn); + return NULL; + } +#endif + + return conn; +} + +static bool epoll_set(tproxy_conn_t *conn, uint32_t events) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = (void*) conn; + DBGPRINT("epoll_set fd=%d events=%08X\n",conn->fd,events); + if(epoll_ctl(conn->efd, EPOLL_CTL_MOD, conn->fd, &ev)==-1 && + epoll_ctl(conn->efd, EPOLL_CTL_ADD, conn->fd, &ev)==-1) + { + DLOG_PERROR("epoll_ctl (add/mod)"); + return false; + } + return true; +} +static bool epoll_del(tproxy_conn_t *conn) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + + DBGPRINT("epoll_del fd=%d\n",conn->fd); + if(epoll_ctl(conn->efd, EPOLL_CTL_DEL, conn->fd, &ev)==-1) + { + DLOG_PERROR("epoll_ctl (del)"); + return false; + } + return true; +} + +static bool epoll_update_flow(tproxy_conn_t *conn) +{ + if (conn->bFlowInPrev==conn->bFlowIn && conn->bFlowOutPrev==conn->bFlowOut && conn->bPrevRdhup==(conn->state==CONN_RDHUP)) + return true; // unchanged, no need to syscall + DBGPRINT("SET FLOW fd=%d to in=%d out=%d state_rdhup=%d\n",conn->fd,conn->bFlowIn,conn->bFlowOut,conn->state==CONN_RDHUP); + uint32_t evtmask = (conn->state==CONN_RDHUP ? 0 : EPOLLRDHUP)|(conn->bFlowIn?EPOLLIN:0)|(conn->bFlowOut?EPOLLOUT:0); + if (!epoll_set(conn, evtmask)) + return false; + conn->bFlowInPrev = conn->bFlowIn; + conn->bFlowOutPrev = conn->bFlowOut; + conn->bPrevRdhup = (conn->state==CONN_RDHUP); + return true; +} +static bool epoll_set_flow(tproxy_conn_t *conn, bool bFlowIn, bool bFlowOut) +{ + conn->bFlowIn = bFlowIn; + conn->bFlowOut = bFlowOut; + return epoll_update_flow(conn); +} + +//Acquires information, initiates a connect and initialises a new connection +//object. Return NULL if anything fails, pointer to object otherwise +static tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,int local_fd, const struct sockaddr *accept_sa, uint16_t listen_port, conn_type_t proxy_type) +{ + struct sockaddr_storage orig_dst; + tproxy_conn_t *conn; + int remote_fd=0; + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + if(!get_dest_addr(local_fd, accept_sa, &orig_dst)) + { + DLOG_ERR("Could not get destination address\n"); + close(local_fd); + return NULL; + } + if (check_local_ip((struct sockaddr*)&orig_dst) && saport((struct sockaddr*)&orig_dst)==listen_port) + { + VPRINT("Dropping connection to local address to the same port to avoid loop\n"); + close(local_fd); + return NULL; + } + } + + // socket buffers inherited from listen_fd + dbgprint_socket_buffers(local_fd); + + if(!set_keepalive(local_fd)) + { + DLOG_PERROR("set_keepalive"); + close(local_fd); + return 0; + } + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + if ((remote_fd = connect_remote((struct sockaddr *)&orig_dst, 0)) < 0) + { + DLOG_ERR("Failed to connect\n"); + close(local_fd); + return NULL; + } + } + + if(!(conn = new_conn(local_fd, false))) + { + if (remote_fd) close(remote_fd); + close(local_fd); + return NULL; + } + conn->conn_type = proxy_type; // only local connection has proxy_type. remote is always in tcp mode + conn->state = CONN_AVAILABLE; // accepted connection is immediately available + conn->efd = efd; + + if (proxy_type==CONN_TYPE_TRANSPARENT) + { + sacopy(&conn->dest, (struct sockaddr *)&orig_dst); + + if(!(conn->partner = new_conn(remote_fd, true))) + { + free_conn(conn); + close(remote_fd); + return NULL; + } + conn->partner->partner = conn; + conn->partner->efd = efd; + + //remote_fd is connecting. Non-blocking connects are signaled as done by + //socket being marked as ready for writing + if (!epoll_set(conn->partner, EPOLLOUT)) + { + free_conn(conn->partner); + free_conn(conn); + return NULL; + } + } + + //Transparent proxy mode : + // Local socket can be closed while waiting for connection attempt. I need + // to detect this when waiting for connect() to complete. However, I dont + // want to get EPOLLIN-events, as I dont want to receive any data before + // remote connection is established + //Proxy mode : I need to service proxy protocol + // remote connection not started until proxy handshake is complete + + if (!epoll_set(conn, proxy_type==CONN_TYPE_TRANSPARENT ? EPOLLRDHUP : (EPOLLIN|EPOLLRDHUP))) + { + free_conn(conn->partner); + free_conn(conn); + return NULL; + } + + TAILQ_INSERT_HEAD(conn_list, conn, conn_ptrs); + legs_local++; + if (conn->partner) + { + TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); + legs_remote++; + } + + if (proxy_type==CONN_TYPE_TRANSPARENT) + apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); + + return conn; +} + +//Checks if a connection attempt was successful or not +//Returns true if successfull, false if not +static bool check_connection_attempt(tproxy_conn_t *conn, int efd) +{ + int errn = 0; + socklen_t optlen = sizeof(errn); + + if (conn->state!=CONN_UNAVAILABLE || !conn->remote) + { + // locals are connected since accept + // remote need to be checked only once + return true; + } + + // check the connection was sucessfull. it means its not in in SO_ERROR state + if(getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1) + { + DLOG_PERROR("getsockopt (SO_ERROR)"); + return false; + } + if (!errn) + { + if (params.debug>=1) + { + struct sockaddr_storage sa; + socklen_t salen=sizeof(sa); + char ip_port[48]; + + if (getsockname(conn->fd,(struct sockaddr *)&sa,&salen)) + *ip_port=0; + else + ntop46_port((struct sockaddr*)&sa,ip_port,sizeof(ip_port)); + VPRINT("Socket fd=%d (remote) connected from : %s\n", conn->fd, ip_port); + } + if (!epoll_set_flow(conn, true, false) || (conn_partner_alive(conn) && !epoll_set_flow(conn->partner, true, false))) + { + return false; + } + conn->state = CONN_AVAILABLE; + } + proxy_remote_conn_ack(conn,get_so_error(conn->fd)); + return !errn; +} + + + + +static bool epoll_set_flow_pair(tproxy_conn_t *conn) +{ + bool bHasUnsent = conn_has_unsent(conn); + bool bHasUnsentPartner = conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false; + + DBGPRINT("epoll_set_flow_pair fd=%d remote=%d partner_fd=%d bHasUnsent=%d bHasUnsentPartner=%d state_rdhup=%d\n", + conn->fd , conn->remote, conn_partner_alive(conn) ? conn->partner->fd : 0, bHasUnsent, bHasUnsentPartner, conn->state==CONN_RDHUP); + if (!epoll_set_flow(conn, !bHasUnsentPartner && (conn->state != CONN_RDHUP), bHasUnsent)) + return false; + if (conn_partner_alive(conn)) + { + if (!epoll_set_flow(conn->partner, !bHasUnsent && (conn->partner->state != CONN_RDHUP), bHasUnsentPartner)) + return false; + } + return true; +} + +static bool handle_unsent(tproxy_conn_t *conn) +{ + ssize_t wr; + + DBGPRINT("+handle_unsent, fd=%d has_unsent=%d has_unsent_partner=%d\n",conn->fd,conn_has_unsent(conn),conn_partner_alive(conn) ? conn_has_unsent(conn->partner) : false); + +#ifdef SPLICE_PRESENT + if (!params.nosplice && conn->wr_unsent) + { + wr = splice(conn->splice_pipe[0], NULL, conn->fd, NULL, conn->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice unsent=%zd wr=%zd err=%d\n",conn->wr_unsent,wr,errno); + if (wr<0) + { + if (errno==EAGAIN) wr=0; + else return false; + } + conn->twr += wr; + conn->wr_unsent -= wr; + } +#endif + if (!conn->wr_unsent && conn_buffers_present(conn)) + { + wr=conn_buffers_send(conn); + DBGPRINT("conn_buffers_send wr=%zd\n",wr); + if (wr<0) return false; + } + if (!conn_has_unsent(conn) && conn_partner_alive(conn) && conn->partner->state==CONN_RDHUP) + { + if (!conn->bShutdown) + { + DBGPRINT("fd=%d no more has unsent. partner in RDHUP state. executing delayed shutdown.\n", conn->fd); + if (!conn_shutdown(conn)) + { + DBGPRINT("emergency connection close due to failed shutdown\n"); + return false; + } + } + if (conn->state==CONN_RDHUP && !conn_has_unsent(conn->partner)) + { + DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); + return false; + } + } + + return epoll_set_flow_pair(conn); +} + + +static bool proxy_mode_connect_remote(tproxy_conn_t *conn, struct tailhead *conn_list) +{ + int remote_fd; + + if (params.debug>=1) + { + char ip_port[48]; + ntop46_port((struct sockaddr *)&conn->dest,ip_port,sizeof(ip_port)); + VPRINT("socks target for fd=%d is : %s\n", conn->fd, ip_port); + } + if (check_local_ip((struct sockaddr *)&conn->dest)) + { + VPRINT("Dropping connection to local address for security reasons\n"); + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET); + return false; + } + + apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); + + if ((remote_fd = connect_remote((struct sockaddr *)&conn->dest, conn->track.dp ? conn->track.dp->mss : 0)) < 0) + { + DLOG_ERR("socks failed to connect (1) errno=%d\n", errno); + socks_send_rep_errno(conn->socks_ver, conn->fd, errno); + return false; + } + if (!(conn->partner = new_conn(remote_fd, true))) + { + close(remote_fd); + DLOG_ERR("socks out-of-memory (1)\n"); + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); + return false; + } + conn->partner->partner = conn; + conn->partner->efd = conn->efd; + if (!epoll_set(conn->partner, EPOLLOUT)) + { + DLOG_ERR("socks epoll_set error %d\n", errno); + free_conn(conn->partner); + conn->partner = NULL; + socks_send_rep(conn->socks_ver, conn->fd, S5_REP_GENERAL_FAILURE); + return false; + } + TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); + legs_remote++; + print_legs(); + DBGPRINT("S_WAIT_CONNECTION\n"); + conn->socks_state = S_WAIT_CONNECTION; + return true; +} + +static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) +{ + // To simplify things I dont care about buffering. If message splits, I just hang up + // in proxy mode messages are short. they can be split only intentionally. all normal programs send them in one packet + + ssize_t rd,wr; + char buf[sizeof(s5_req)]; // s5_req - the largest possible req + + // receive proxy control message + rd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); + DBGPRINT("handle_proxy_mode rd=%zd\n",rd); + if (rd<1) return false; // hangup + switch(conn->conn_type) + { + case CONN_TYPE_SOCKS: + switch(conn->socks_state) + { + case S_WAIT_HANDSHAKE: + DBGPRINT("S_WAIT_HANDSHAKE\n"); + if (buf[0] != 5 && buf[0] != 4) return false; // unknown socks version + conn->socks_ver = buf[0]; + DBGPRINT("socks version %u\n", conn->socks_ver); + if (conn->socks_ver==5) + { + s5_handshake *m = (s5_handshake*)buf; + s5_handshake_ack ack; + uint8_t k; + + ack.ver=5; + if (!S5_REQ_HANDHSHAKE_VALID(m,rd)) + { + DBGPRINT("socks5 proxy handshake invalid\n"); + return false; + } + for (k=0;knmethods;k++) if (m->methods[k]==S5_AUTH_NONE) break; + if (k>=m->nmethods) + { + DBGPRINT("socks5 client wants authentication but we dont support\n"); + ack.method=S5_AUTH_UNACCEPTABLE; + wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); + return false; + } + DBGPRINT("socks5 recv valid handshake\n"); + ack.method=S5_AUTH_NONE; + wr=send(conn->fd,&ack,sizeof(ack),MSG_DONTWAIT); + if (wr!=sizeof(ack)) + { + DBGPRINT("socks5 handshake ack send error. wr=%zd errno=%d\n",wr,errno); + return false; + } + DBGPRINT("socks5 send handshake ack OK\n"); + conn->socks_state=S_WAIT_REQUEST; + return true; + } + else + { + // socks4 does not have separate handshake phase. it starts with connect request + // ipv6 and domain resolving are not supported + s4_req *m = (s4_req*)buf; + if (!S4_REQ_HEADER_VALID(m, rd)) + { + DBGPRINT("socks4 request invalid\n"); + return false; + } + if (m->cmd!=S4_CMD_CONNECT) + { + // BIND is not supported + DBGPRINT("socks4 unsupported command %02X\n", m->cmd); + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + if (!S4_REQ_CONNECT_VALID(m, rd)) + { + DBGPRINT("socks4 connect request invalid\n"); + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + if (!m->port) + { + DBGPRINT("socks4 zero port\n"); + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + if (m->ip==htonl(1)) // special ip 0.0.0.1 + { + VPRINT("socks4a protocol not supported\n"); + socks4_send_rep(conn->fd, S4_REP_FAILED); + return false; + } + conn->dest.ss_family = AF_INET; + ((struct sockaddr_in*)&conn->dest)->sin_port = m->port; + ((struct sockaddr_in*)&conn->dest)->sin_addr.s_addr = m->ip; + return proxy_mode_connect_remote(conn, conn_list); + } + break; + case S_WAIT_REQUEST: + DBGPRINT("S_WAIT_REQUEST\n"); + { + s5_req *m = (s5_req*)buf; + + if (!S5_REQ_HEADER_VALID(m,rd)) + { + DBGPRINT("socks5 request invalid\n"); + return false; + } + if (m->cmd!=S5_CMD_CONNECT) + { + // BIND and UDP are not supported + DBGPRINT("socks5 unsupported command %02X\n", m->cmd); + socks5_send_rep(conn->fd,S5_REP_COMMAND_NOT_SUPPORTED); + return false; + } + if (!S5_REQ_CONNECT_VALID(m,rd)) + { + DBGPRINT("socks5 connect request invalid\n"); + return false; + } + DBGPRINT("socks5 recv valid connect request\n"); + switch(m->atyp) + { + case S5_ATYP_IP4: + conn->dest.ss_family = AF_INET; + ((struct sockaddr_in*)&conn->dest)->sin_port = m->d4.port; + ((struct sockaddr_in*)&conn->dest)->sin_addr = m->d4.addr; + break; + case S5_ATYP_IP6: + conn->dest.ss_family = AF_INET6; + ((struct sockaddr_in6*)&conn->dest)->sin6_port = m->d6.port; + ((struct sockaddr_in6*)&conn->dest)->sin6_addr = m->d6.addr; + ((struct sockaddr_in6*)&conn->dest)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)&conn->dest)->sin6_scope_id = 0; + break; + case S5_ATYP_DOM: + { + uint16_t port; + + if (params.no_resolve) + { + VPRINT("socks5 hostname resolving disabled\n"); + socks5_send_rep(conn->fd,S5_REP_NOT_ALLOWED_BY_RULESET); + return false; + } + port=S5_PORT_FROM_DD(m,rd); + if (!port) + { + VPRINT("socks5 no port is given\n"); + socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); + return false; + } + m->dd.domport[m->dd.len] = 0; + DBGPRINT("socks5 queue resolve hostname '%s' port '%u'\n",m->dd.domport,port); + conn->socks_ri = resolver_queue(m->dd.domport,port,conn); + if (!conn->socks_ri) + { + VPRINT("socks5 could not queue resolve item\n"); + socks5_send_rep(conn->fd,S5_REP_GENERAL_FAILURE); + return false; + } + conn->socks_state=S_WAIT_RESOLVE; + DBGPRINT("S_WAIT_RESOLVE\n"); + return true; + } + break; + default: + return false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp + + } + return proxy_mode_connect_remote(conn,conn_list); + } + break; + case S_WAIT_RESOLVE: + DBGPRINT("socks received message while in S_WAIT_RESOLVE. hanging up\n"); + break; + case S_WAIT_CONNECTION: + DBGPRINT("socks received message while in S_WAIT_CONNECTION. hanging up\n"); + break; + default: + DBGPRINT("socks received message while in an unexpected connection state\n"); + break; + } + break; + } + return false; +} + +static bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list) +{ + tproxy_conn_t *conn = (tproxy_conn_t *)ri->ptr; + + if (conn && (conn->state != CONN_CLOSED)) + { + if (conn->socks_state==S_WAIT_RESOLVE) + { + DBGPRINT("resolve_complete %s. getaddrinfo result %d\n", ri->dom, ri->ga_res); + if (ri->ga_res) + { + socks5_send_rep(conn->fd,S5_REP_HOST_UNREACHABLE); + return false;; + } + else + { + if (!conn->track.hostname) + { + DBGPRINT("resolve_complete put hostname : %s\n", ri->dom); + conn->track.hostname = strdup(ri->dom); + } + sacopy(&conn->dest, (struct sockaddr *)&ri->ss); + return proxy_mode_connect_remote(conn,conn_list); + } + } + else + DLOG_ERR("resolve_complete: conn in wrong socks_state !!! (%s)\n", ri->dom); + } + else + DBGPRINT("resolve_complete: orphaned resolve for %s\n", ri->dom); + + return true; +} + + +static bool in_tamper_out_range(tproxy_conn_t *conn) +{ + if (!conn->track.dp) return true; + bool in_range = \ + ((conn->track.dp->tamper_start_n ? (conn->tnrd+1) : conn->trd) >= conn->track.dp->tamper_start && + (!conn->track.dp->tamper_cutoff || (conn->track.dp->tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < conn->track.dp->tamper_cutoff)); + DBGPRINT("tamper_out range check. stream pos %" PRIu64 "(n%" PRIu64 "). tamper range %s%u-%s%u (%s)\n", + conn->trd, conn->tnrd+1, + conn->track.dp ? conn->track.dp->tamper_start_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_start : 0, + conn->track.dp ? conn->track.dp->tamper_cutoff_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_cutoff : 0, + in_range ? "IN RANGE" : "OUT OF RANGE"); + return in_range; + +} + +static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *split_pos, uint8_t *split_flags) +{ + *split_pos=0; + if (params.tamper) + { + if (conn->remote) + { + if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff) + tamper_in(&conn->partner->track,segment,segment_buffer_size,segment_size); + } + else + { + if (in_tamper_out_range(conn)) + tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,split_pos,split_flags); + } + } +} + +// buffer must have at least one extra byte for OOB +static ssize_t send_or_buffer_oob(send_buffer_t *sb, int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte) +{ + ssize_t wr; + if (oob) + { + VPRINT("Sending OOB byte %02X\n", oob_byte); + uint8_t oob_save; + oob_save = buf[len]; + buf[len] = oob_byte; + wr = send_or_buffer(sb, fd, buf, len+1, MSG_OOB, ttl); + buf[len] = oob_save; + } + else + wr = send_or_buffer(sb, fd, buf, len, 0, ttl); + return wr; +} + + +#define RD_BLOCK_SIZE 65536 +#define MAX_WASTE (1024*1024) +static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt) +{ + int numbytes; + ssize_t rd = 0, wr = 0; + size_t bs; + + + DBGPRINT("+handle_epoll\n"); + + if (!conn_in_tcp_mode(conn)) + { + if (!(evt & EPOLLIN)) + return true; // nothing to read + return handle_proxy_mode(conn,conn_list); + } + + if (!handle_unsent(conn)) + return false; // error + if (!conn_partner_alive(conn) && !conn_has_unsent(conn)) + return false; // when no partner, we only waste read and send unsent + + if (!(evt & EPOLLIN)) + return true; // nothing to read + + if (!conn_partner_alive(conn)) + { + // throw it to a black hole + uint8_t waste[65070]; + uint64_t trd=0; + + while((rd=recv(conn->fd, waste, sizeof(waste), MSG_DONTWAIT))>0 && trdtrd+=rd; + } + DBGPRINT("wasted recv=%zd all_rd=%" PRIu64 " err=%d\n",rd,trd,errno); + return true; + } + + // do not receive new until old is sent + if (conn_has_unsent(conn->partner)) + return true; + + bool oom=false; + + numbytes=conn_bytes_unread(conn); + DBGPRINT("numbytes=%d\n",numbytes); + if (numbytes>0) + { + DBGPRINT("%s leg fd=%d stream pos : %" PRIu64 "(n%" PRIu64 ")/%" PRIu64 "\n", conn->remote ? "remote" : "local", conn->fd, conn->trd,conn->tnrd+1,conn->twr); +#ifdef SPLICE_PRESENT + if (!params.nosplice && (!params.tamper || (conn->remote && conn->partner->track.bTamperInCutoff) || (!conn->remote && !in_tamper_out_range(conn)))) + { + // incoming data from remote leg we splice without touching + // pipe is in the local leg, so its in conn->partner->splice_pipe + // if we dont tamper - splice both legs + + rd = splice(conn->fd, NULL, conn->partner->splice_pipe[1], NULL, SPLICE_LEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice fd=%d remote=%d len=%d rd=%zd err=%d\n",conn->fd,conn->remote,SPLICE_LEN,rd,errno); + if (rd<0 && errno==EAGAIN) rd=0; + if (rd>0) + { + conn->tnrd++; + conn->trd += rd; + conn->partner->wr_unsent += rd; + wr = splice(conn->partner->splice_pipe[0], NULL, conn->partner->fd, NULL, conn->partner->wr_unsent, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + DBGPRINT("splice fd=%d remote=%d wr=%zd err=%d\n",conn->partner->fd,conn->partner->remote,wr,errno); + if (wr<0 && errno==EAGAIN) wr=0; + if (wr>0) + { + conn->partner->wr_unsent -= wr; + conn->partner->twr += wr; + } + } + } + else +#endif + { + // incoming data from local leg + uint8_t buf[RD_BLOCK_SIZE + 5]; + + rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); + DBGPRINT("recv fd=%d rd=%zd err=%d\n",conn->fd, rd,errno); + if (rd<0 && errno==EAGAIN) rd=0; + if (rd>0) + { + size_t split_pos; + uint8_t split_flags; + + bs = rd; + + // tamper needs to know stream position of the block start + tamper(conn, buf, sizeof(buf), &bs, &split_pos, &split_flags); + // increase after tamper + conn->tnrd++; + conn->trd+=rd; + + if (split_pos && bspartner->wr_buf, conn->partner->fd, buf, split_pos, !!(split_flags & SPLIT_FLAG_DISORDER), !!(split_flags & SPLIT_FLAG_OOB), conn->track.dp ? conn->track.dp->oob_byte : 0); + DBGPRINT("send_or_buffer(1) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); + if (wr >= 0) + { + conn->partner->twr += wr; + wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos, 0, 0); + DBGPRINT("send_or_buffer(2) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); + if (wr>0) conn->partner->twr += wr; + } + } + else + { + wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs, 0, 0); + DBGPRINT("send_or_buffer(3) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); + if (wr>0) conn->partner->twr += wr; + } + if (wr<0 && errno==ENOMEM) oom=true; + } + } + + if (!epoll_set_flow_pair(conn)) + return false; + } + + DBGPRINT("-handle_epoll rd=%zd wr=%zd\n",rd,wr); + if (oom) DBGPRINT("handle_epoll: OUT_OF_MEMORY\n"); + + // do not fail if partner fails. + // if partner fails there will be another epoll event with EPOLLHUP or EPOLLERR + return rd>=0 && !oom; +} + +static bool remove_closed_connections(int efd, struct tailhead *close_list) +{ + tproxy_conn_t *conn = NULL; + bool bRemoved = false; + + while ((conn = TAILQ_FIRST(close_list))) + { + TAILQ_REMOVE(close_list, conn, conn_ptrs); + + epoll_del(conn); + VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) closed, connection removed. total_read=%" PRIu64 " total_write=%" PRIu64 " event_count=%u\n", + conn->fd, conn->partner ? conn->partner->fd : 0, conn->remote, conn->trd, conn->twr, conn->event_count); + if (conn->remote) legs_remote--; else legs_local--; + free_conn(conn); + bRemoved = true; + } + return bRemoved; +} + +// move to close list connection and its partner +static void close_tcp_conn(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + if (conn->state != CONN_CLOSED) + { + conn->state = CONN_CLOSED; + TAILQ_REMOVE(conn_list, conn, conn_ptrs); + TAILQ_INSERT_TAIL(close_list, conn, conn_ptrs); + } +} + + +static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) +{ + if (conn_partner_alive(conn)) + { + int numbytes=conn_bytes_unread(conn); + DBGPRINT("read_all_and_buffer(%d) numbytes=%d\n",buffer_number,numbytes); + if (numbytes>0) + { + if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 5, 0, 0)) + { + ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT); + if (rd>0) + { + conn->trd+=rd; + conn->partner->wr_buf[buffer_number].len = rd; + + conn->partner->bFlowOut = true; + + size_t split_pos; + uint8_t split_flags; + + tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+5, &conn->partner->wr_buf[buffer_number].len, &split_pos, &split_flags); + + if (epoll_update_flow(conn->partner)) + return true; + + } + send_buffer_free(conn->partner->wr_buf+buffer_number); + } + } + } + return false; +} + + +static bool conn_timed_out(tproxy_conn_t *conn) +{ + if (conn->orphan_since && conn->state==CONN_UNAVAILABLE) + { + time_t timediff = time(NULL) - conn->orphan_since; + return timediff>=params.max_orphan_time; + } + else + return false; +} +static void conn_close_timed_out(struct tailhead *conn_list, struct tailhead *close_list) +{ + tproxy_conn_t *c,*cnext = NULL; + + DBGPRINT("conn_close_timed_out\n"); + + c = TAILQ_FIRST(conn_list); + while(c) + { + cnext = TAILQ_NEXT(c,conn_ptrs); + if (conn_timed_out(c)) + { + DBGPRINT("closing timed out connection: fd=%d remote=%d\n",c->fd,c->remote); + close_tcp_conn(conn_list,close_list,c); + } + c = cnext; + } +} + +static void conn_close_both(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + if (conn_partner_alive(conn)) close_tcp_conn(conn_list,close_list,conn->partner); + close_tcp_conn(conn_list,close_list,conn); +} +static void conn_close_with_partner_check(struct tailhead *conn_list, struct tailhead *close_list, tproxy_conn_t *conn) +{ + close_tcp_conn(conn_list,close_list,conn); + if (conn_partner_alive(conn)) + { + if (!conn_has_unsent(conn->partner)) + close_tcp_conn(conn_list,close_list,conn->partner); + else if (conn->partner->remote && conn->partner->state==CONN_UNAVAILABLE && params.max_orphan_time) + // time out only remote legs that are not connected yet + conn->partner->orphan_since = time(NULL); + } +} + + +static bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list, int fd) +{ + ssize_t rd; + struct resolve_item *ri; + bool b; + + rd = read(fd,&ri,sizeof(void*)); + if (rd<0) + { + DLOG_PERROR("resolve_pipe read"); + return false; + } + else if (rd!=sizeof(void*)) + { + // partial pointer read is FATAL. in any case it will cause pointer corruption and coredump + DLOG_ERR("resolve_pipe not full read %zu\n",rd); + exit(1000); + } + b = resolve_complete(ri, conn_list); + *conn = (tproxy_conn_t *)ri->ptr; + if (*conn) (*conn)->socks_ri = NULL; + free(ri); + return b; +} + +int event_loop(const int *listen_fd, size_t listen_fd_ct) +{ + int retval = 0, num_events = 0; + int tmp_fd = 0; //Used to temporarily hold the accepted file descriptor + tproxy_conn_t *conn = NULL; + int efd=0, i; + struct epoll_event ev, events[MAX_EPOLL_EVENTS]; + struct tailhead conn_list, close_list; + time_t tm,last_timeout_check=0; + tproxy_conn_t *listen_conn = NULL; + size_t sct; + struct sockaddr_storage accept_sa; + socklen_t accept_salen; + int resolve_pipe[2]; + + if (!listen_fd_ct) return -1; + + resolve_pipe[0]=resolve_pipe[1]=0; + + legs_local = legs_remote = 0; + //Initialize queue (remember that TAILQ_HEAD just defines the struct) + TAILQ_INIT(&conn_list); + TAILQ_INIT(&close_list); + + if ((efd = epoll_create(1)) == -1) { + DLOG_PERROR("epoll_create"); + return -1; + } + + if (!(listen_conn=calloc(listen_fd_ct,sizeof(*listen_conn)))) + { + DLOG_PERROR("calloc listen_conn"); + return -1; + } + + //Start monitoring listen sockets + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN; + for(sct=0;sctevent_count++; + if (conn->listener) + { + DBGPRINT("\nEVENT mask %08X fd=%d accept\n",events[i].events,conn->fd); + + accept_salen = sizeof(accept_sa); + //Accept new connection +#if defined (__APPLE__) + // macos does not have accept4() + tmp_fd = accept(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen); +#else + tmp_fd = accept4(conn->fd, (struct sockaddr*)&accept_sa, &accept_salen, SOCK_NONBLOCK); +#endif + if (tmp_fd < 0) + { + DLOG_PERROR("Failed to accept connection"); + } + else if (legs_local >= params.maxconn) // each connection has 2 legs - local and remote + { + close(tmp_fd); + VPRINT("Too many local legs : %d\n", legs_local); + } +#if defined (__APPLE__) + // separate fcntl call to comply with macos + else if (fcntl(tmp_fd, F_SETFL, O_NONBLOCK) < 0) + { + DLOG_PERROR("socket set O_NONBLOCK (accept)"); + close(tmp_fd); + } +#endif + else if (!(conn=add_tcp_connection(efd, &conn_list, tmp_fd, (struct sockaddr*)&accept_sa, params.port, params.proxy_type))) + { + // add_tcp_connection closes fd in case of failure + VPRINT("Failed to add connection\n"); + } + else + { + print_legs(); + + if (params.debug>=1) + { + struct sockaddr_storage sa; + socklen_t salen=sizeof(sa); + char ip_port[48]; + + if (getpeername(conn->fd,(struct sockaddr *)&sa,&salen)) + *ip_port=0; + else + ntop46_port((struct sockaddr*)&sa,ip_port,sizeof(ip_port)); + + VPRINT("Socket fd=%d (local) connected from %s\n", conn->fd, ip_port); + } + set_user_timeout(conn->fd, params.tcp_user_timeout_local); + } + } + else + { + DBGPRINT("\nEVENT mask %08X fd=%d remote=%d fd_partner=%d\n",events[i].events,conn->fd,conn->remote,conn_partner_alive(conn) ? conn->partner->fd : 0); + + if (conn->state != CONN_CLOSED) + { + if (events[i].events & (EPOLLHUP|EPOLLERR)) + { + int errn = get_so_error(conn->fd); + const char *se; + switch (events[i].events & (EPOLLHUP|EPOLLERR)) + { + case EPOLLERR: se="EPOLLERR"; break; + case EPOLLHUP: se="EPOLLHUP"; break; + case EPOLLHUP|EPOLLERR: se="EPOLLERR EPOLLHUP"; break; + default: se=NULL; + } + VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) %s so_error=%d (%s)\n",conn->fd,conn->partner ? conn->partner->fd : 0,conn->remote,se,errn,strerror(errn)); + proxy_remote_conn_ack(conn,errn); + read_all_and_buffer(conn,3); + if (errn==ECONNRESET && conn_partner_alive(conn)) + { + if (conn->remote && params.tamper) rst_in(&conn->partner->track); + + struct linger lin; + lin.l_onoff=1; + lin.l_linger=0; + DBGPRINT("setting LINGER=0 to partner to force mirrored RST close\n"); + if (setsockopt(conn->partner->fd,SOL_SOCKET,SO_LINGER,&lin,sizeof(lin))<0) + DLOG_PERROR("setsockopt (SO_LINGER)"); + } + conn_close_with_partner_check(&conn_list,&close_list,conn); + continue; + } + if (events[i].events & EPOLLOUT) + { + if (!check_connection_attempt(conn, efd)) + { + VPRINT("Connection attempt failed for fd=%d\n", conn->fd); + conn_close_both(&conn_list,&close_list,conn); + continue; + } + } + if (events[i].events & EPOLLRDHUP) + { + DBGPRINT("EPOLLRDHUP\n"); + read_all_and_buffer(conn,2); + if (!conn->remote && params.tamper) hup_out(&conn->track); + + conn->state = CONN_RDHUP; // only writes. do not receive RDHUP anymore + if (conn_has_unsent(conn)) + { + DBGPRINT("conn fd=%d has unsent\n", conn->fd); + epoll_set_flow(conn,false,true); + } + else + { + + DBGPRINT("conn fd=%d has no unsent\n", conn->fd); + conn->bFlowIn = false; + epoll_update_flow(conn); + if (conn_partner_alive(conn)) + { + if (conn_has_unsent(conn->partner)) + DBGPRINT("partner has unset. partner shutdown delayed.\n"); + else + { + DBGPRINT("partner has no unsent. shutting down partner.\n"); + if (!conn_shutdown(conn->partner)) + { + DBGPRINT("emergency connection close due to failed shutdown\n"); + conn_close_with_partner_check(&conn_list,&close_list,conn); + } + if (conn->partner->state==CONN_RDHUP) + { + DBGPRINT("both partners are in RDHUP state and have no unsent. closing.\n"); + conn_close_with_partner_check(&conn_list,&close_list,conn); + } + } + } + else + { + DBGPRINT("partner is absent or not alive. closing.\n"); + close_tcp_conn(&conn_list,&close_list,conn); + } + } + continue; + } + + if (events[i].events & (EPOLLIN|EPOLLOUT)) + { + const char *se; + switch (events[i].events & (EPOLLIN|EPOLLOUT)) + { + case EPOLLIN: se="EPOLLIN"; break; + case EPOLLOUT: se="EPOLLOUT"; break; + case EPOLLIN|EPOLLOUT: se="EPOLLIN EPOLLOUT"; break; + default: se=NULL; + } + if (se) DBGPRINT("%s\n",se); + // will not receive this until successful check_connection_attempt() + if (!handle_epoll(conn, &conn_list, events[i].events)) + { + DBGPRINT("handle_epoll false\n"); + conn_close_with_partner_check(&conn_list,&close_list,conn); + continue; + } + if ((conn->state == CONN_RDHUP) && conn_partner_alive(conn) && !conn->partner->bShutdown && !conn_has_unsent(conn)) + { + DBGPRINT("conn fd=%d has no unsent. shutting down partner.\n", conn->fd); + if (!conn_shutdown(conn->partner)) + { + DBGPRINT("emergency connection close due to failed shutdown\n"); + conn_close_with_partner_check(&conn_list,&close_list,conn); + continue; + } + } + + } + } + + } + } + tm = time(NULL); + if (last_timeout_check!=tm) + { + // limit whole list lookups to once per second + last_timeout_check=tm; + conn_close_timed_out(&conn_list,&close_list); + } + if (remove_closed_connections(efd, &close_list)) + { + // at least one leg was removed. recount legs + print_legs(); + } + + fflush(stderr); fflush(stdout); // for console messages + } + +ex: + if (efd) close(efd); + if (listen_conn) free(listen_conn); + resolver_deinit(); + if (resolve_pipe[0]) close(resolve_pipe[0]); + if (resolve_pipe[1]) close(resolve_pipe[1]); + return retval; +} diff --git a/tpws/tpws_conn.h b/tpws/tpws_conn.h new file mode 100644 index 00000000..c52cbc8c --- /dev/null +++ b/tpws/tpws_conn.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include +#include "tamper.h" +#include "params.h" +#include "resolver.h" + +#define BACKLOG 10 +#define MAX_EPOLL_EVENTS 64 +#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT +#define SPLICE_LEN 65536 +#define DEFAULT_MAX_CONN 512 +#define DEFAULT_MAX_ORPHAN_TIME 5 +#define DEFAULT_TCP_USER_TIMEOUT_LOCAL 10 +#define DEFAULT_TCP_USER_TIMEOUT_REMOTE 20 + +int event_loop(const int *listen_fd, size_t listen_fd_ct); + +//Three different states of a connection +enum{ + CONN_UNAVAILABLE=0, // connecting + CONN_AVAILABLE, // operational + CONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked + CONN_CLOSED // will be deleted soon +}; +typedef uint8_t conn_state_t; + +// data in a send_buffer can be sent in several stages +// pos indicates size of already sent data +// when pos==len its time to free buffer +struct send_buffer +{ + uint8_t *data; + size_t len,pos; + int ttl, flags; +}; +typedef struct send_buffer send_buffer_t; + +enum{ + CONN_TYPE_TRANSPARENT=0, + CONN_TYPE_SOCKS +}; +typedef uint8_t conn_type_t; + +struct tproxy_conn +{ + bool listener; // true - listening socket. false = connecion socket + bool remote; // false - accepted, true - connected + int efd; // epoll fd + int fd; + int splice_pipe[2]; + conn_state_t state; + conn_type_t conn_type; + struct sockaddr_storage dest; + + struct tproxy_conn *partner; // other leg + time_t orphan_since; + + // socks5 state machine + enum { + S_WAIT_HANDSHAKE=0, + S_WAIT_REQUEST, + S_WAIT_RESOLVE, + S_WAIT_CONNECTION, + S_TCP + } socks_state; + uint8_t socks_ver; + struct resolve_item *socks_ri; + + // these value are used in flow control. we do not use ET (edge triggered) polling + // if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time + bool bFlowIn,bFlowOut, bShutdown, bFlowInPrev,bFlowOutPrev, bPrevRdhup; + + // total read,write + uint64_t trd,twr, tnrd; + // number of epoll_wait events + unsigned int event_count; + + // connection is either spliced or send/recv + // spliced connection have pipe buffering but also can have send_buffer's + // pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1 + // send/recv connection do not have pipe and wr_unsent is meaningless, always 0 + ssize_t wr_unsent; // unsent bytes in the pipe + // buffer 0 : send before split_pos + // buffer 1 : send after split_pos + // buffer 2 : after RDHUP read all and buffer to the partner + // buffer 3 : after HUP read all and buffer to the partner + // (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL) + // all buffers are sent strictly from 0 to countof(wr_buf)-1 + // buffer cannot be sent if there is unsent data in a lower buffer + struct send_buffer wr_buf[4]; + + t_ctrack track; + + //Create the struct which contains ptrs to next/prev element + TAILQ_ENTRY(tproxy_conn) conn_ptrs; +}; +typedef struct tproxy_conn tproxy_conn_t; + +//Define the struct tailhead (code in sys/queue.h is quite intuitive) +//Use tail queue for efficient delete +TAILQ_HEAD(tailhead, tproxy_conn); + + +bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); diff --git a/tpws/uthash.h b/tpws/uthash.h new file mode 100644 index 00000000..9a396b61 --- /dev/null +++ b/tpws/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/uninstall_easy.sh b/uninstall_easy.sh new file mode 100755 index 00000000..781c8d5d --- /dev/null +++ b/uninstall_easy.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +# automated script for easy uninstalling zapret + +EXEDIR="$(dirname "$0")" +EXEDIR="$(cd "$EXEDIR"; pwd)" +ZAPRET_BASE=${ZAPRET_BASE:-"$EXEDIR"} +ZAPRET_RW=${ZAPRET_RW:-"$ZAPRET_BASE"} +ZAPRET_CONFIG=${ZAPRET_CONFIG:-"$ZAPRET_RW/config"} +ZAPRET_CONFIG_DEFAULT="$ZAPRET_BASE/config.default" +IPSET_DIR="$ZAPRET_BASE/ipset" + +[ -f "$ZAPRET_CONFIG" ] || { + ZAPRET_CONFIG_DIR="$(dirname "$ZAPRET_CONFIG")" + [ -d "$ZAPRET_CONFIG_DIR" ] || mkdir -p "$ZAPRET_CONFIG_DIR" + cp "$ZAPRET_CONFIG_DEFAULT" "$ZAPRET_CONFIG" +} + +. "$ZAPRET_CONFIG" +. "$ZAPRET_BASE/common/base.sh" +. "$ZAPRET_BASE/common/elevate.sh" +. "$ZAPRET_BASE/common/fwtype.sh" +. "$ZAPRET_BASE/common/dialog.sh" +. "$ZAPRET_BASE/common/ipt.sh" +. "$ZAPRET_BASE/common/nft.sh" +. "$ZAPRET_BASE/common/pf.sh" +. "$ZAPRET_BASE/common/installer.sh" + +remove_systemd() +{ + clear_ipset + service_stop_systemd + service_remove_systemd + timer_remove_systemd + nft_del_table + crontab_del +} + +remove_openrc() +{ + clear_ipset + service_remove_openrc + nft_del_table + crontab_del +} + +remove_linux() +{ + INIT_SCRIPT_SRC="$EXEDIR/init.d/sysv/zapret" + + clear_ipset + + echo \* executing sysv init stop + "$INIT_SCRIPT_SRC" stop + + nft_del_table + crontab_del + + echo + echo '!!! WARNING. YOUR UNINSTALL IS INCOMPLETE !!!' + echo 'you must manually remove zapret auto start from your system' +} + +remove_openwrt() +{ + OPENWRT_FW_INCLUDE=/etc/firewall.zapret + + clear_ipset + service_remove_sysv + remove_openwrt_firewall + remove_openwrt_iface_hook + nft_del_table + restart_openwrt_firewall + crontab_del +} + +remove_macos() +{ + remove_macos_firewall + service_remove_macos + crontab_del +} + + +fix_sbin_path +check_system +require_root + +[ "$SYSTEM" = "macos" ] && . "$EXEDIR/init.d/macos/functions" + +case $SYSTEM in + systemd) + remove_systemd + ;; + openrc) + remove_openrc + ;; + linux) + remove_linux + ;; + openwrt) + remove_openwrt + ;; + macos) + remove_macos + ;; +esac + + +exitp 0