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 0000000..243d139 --- /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 0000000..7be93dd --- /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 0000000..68145e0 --- /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 0000000..969be27 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 0000000..7114607 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 0000000..ec9fcb9 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 0000000..247a6bc 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 0000000..7ea6ca9 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 0000000..c862932 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 0000000..d107cee 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 0000000..06ffc33 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 0000000..e7a8363 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 0000000..af75502 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 0000000..0b67ab7 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 0000000..deb6706 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 0000000..c127414 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 0000000..d466430 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 0000000..4306060 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 0000000..e18eb62 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 0000000..c7bebc9 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 0000000..b9527a4 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 0000000..130820e 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 0000000..e3dccad 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 0000000..482096a 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 0000000..045362c 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 0000000..b95c9da 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 0000000..73f494d 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 0000000..05e95f3 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 0000000..ebb2122 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 0000000..f1940cd 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 0000000..eeaab7a 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 0000000..8a12238 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 0000000..51219f9 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 0000000..12a3fdb 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 0000000..50ca874 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 0000000..218ccaf 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 0000000..5796e9c 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 0000000..8006737 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 0000000..3923bc6 --- /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 0000000..2c99221 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 0000000..50ca874 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 0000000..218ccaf 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 0000000..97785e9 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 0000000..e37b016 --- /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 0000000..5dba037 --- /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 0000000..b6d3b74 --- /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 0000000..80a07cc 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 0000000..88b9578 --- /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 0000000..0475692 --- /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 0000000..ec13738 --- /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 0000000..443af87 --- /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 0000000..4303881 --- /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 0000000..b677904 --- /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 0000000..bf151be --- /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 0000000..a267cb9 --- /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 0000000..c740462 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 0000000..2c99221 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 0000000..8f9f31f 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 0000000..0f1f801 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 0000000..4f29569 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 0000000..34bfb98 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 0000000..77a953e 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 0000000..382e4db 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 0000000..51452ef 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 0000000..015f3b9 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 0000000..fe32b7b 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 0000000..eb91336 --- /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 0000000..7c772ef --- /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 0000000..40b65a7 --- /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 0000000..0cb3890 --- /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 0000000..65e8dc9 --- /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 0000000..61390bb --- /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 0000000..57d7cdf --- /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 0000000..c134d82 --- /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 0000000..dbddc65 --- /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 0000000..e22f91a --- /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 0000000..4516a00 --- /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 0000000..324129f --- /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 0000000..6e566d6 --- /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 0000000..829dc5c --- /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 0000000..2575c69 --- /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 0000000..ec76971 --- /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 0000000..46a65d8 --- /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 0000000..4564675 --- /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 0000000..abf7acd --- /dev/null +++ b/docs/compile/openwrt/package/zapret/ip2net/readme.txt @@ -0,0 +1 @@ +Copy "ip2net" folder here ! diff --git a/docs/compile/openwrt/package/zapret/mdig/Makefile b/docs/compile/openwrt/package/zapret/mdig/Makefile new file mode 100644 index 0000000..55d55d2 --- /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 0000000..14e5c14 --- /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 0000000..48b562f --- /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 0000000..daf8b84 --- /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 0000000..3f8dfc7 --- /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 0000000..18fa3ed --- /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 0000000..4e319e0 --- /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 0000000..5dbdcc0 --- /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 0000000..4acbb59 --- /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 0000000..75fa845 --- /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 0000000..4d0aee8 --- /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 0000000..d9e5a6d --- /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 0000000..40f298e --- /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 0000000..759f235 --- /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 0000000..c3bc990 --- /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 0000000..e624bec --- /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 0000000..b71d7ff --- /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 0000000..1577da6 --- /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 0000000..878aa44 --- /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 0000000..6d36f91 --- /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 0000000..ba9117d 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 0000000..916bc10 --- /dev/null +++ b/files/fake/dht_get_peers.bin @@ -0,0 +1,2 @@ +d1:ad2:id20:.+NA-¢ÔutÚÛE–wΑiJ·9:info_hash20:; +ÂÅÙ侧¾“äÙOÁ£2IÂe1:q9:get_peers1:t2:äâ1:v4:LT1:y1:qe \ No newline at end of file diff --git a/files/fake/dtls_clienthello_w3_org.bin b/files/fake/dtls_clienthello_w3_org.bin new file mode 100644 index 0000000..8d90faf 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 0000000..ce1d420 --- /dev/null +++ b/files/fake/http_iana_org.bin @@ -0,0 +1,9 @@ +GET / HTTP/1.1 +Host: www.iana.org +Connection: keep-alive +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4300.0 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Accept-Encoding: gzip, deflate +Accept-Language: en-US,en;q=0.9,ru;q=0.8 + diff --git a/files/fake/quic_initial_facebook_com.bin b/files/fake/quic_initial_facebook_com.bin new file mode 100644 index 0000000..5d21a59 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 0000000..84dceab 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 0000000..d65c0e6 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 0000000..25b3edc 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 0000000..9a16d98 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 0000000..f3a721b 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 0000000..ea0c77c 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 0000000..80a07cc 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 0000000..7562c0a 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 0000000..d9e0f4a Binary files /dev/null and b/files/fake/tls_clienthello_gosuslugi_ru.bin differ diff --git a/files/fake/tls_clienthello_iana_org.bin b/files/fake/tls_clienthello_iana_org.bin new file mode 100644 index 0000000..e641d73 Binary files /dev/null and b/files/fake/tls_clienthello_iana_org.bin differ diff --git a/files/fake/tls_clienthello_rutracker_org_kyber.bin b/files/fake/tls_clienthello_rutracker_org_kyber.bin new file mode 100644 index 0000000..9ccc5fc Binary files /dev/null and b/files/fake/tls_clienthello_rutracker_org_kyber.bin differ diff --git a/files/fake/tls_clienthello_sberbank_ru.bin b/files/fake/tls_clienthello_sberbank_ru.bin new file mode 100644 index 0000000..59571bb Binary files /dev/null and b/files/fake/tls_clienthello_sberbank_ru.bin differ diff --git a/files/fake/tls_clienthello_vk_com.bin b/files/fake/tls_clienthello_vk_com.bin new file mode 100644 index 0000000..ec908c2 Binary files /dev/null and b/files/fake/tls_clienthello_vk_com.bin differ diff --git a/files/fake/tls_clienthello_vk_com_kyber.bin b/files/fake/tls_clienthello_vk_com_kyber.bin new file mode 100644 index 0000000..92c639e Binary files /dev/null and b/files/fake/tls_clienthello_vk_com_kyber.bin differ diff --git a/files/fake/tls_clienthello_www_google_com.bin b/files/fake/tls_clienthello_www_google_com.bin new file mode 100644 index 0000000..c740462 Binary files /dev/null and b/files/fake/tls_clienthello_www_google_com.bin differ diff --git a/files/fake/wireguard_initiation.bin b/files/fake/wireguard_initiation.bin new file mode 100644 index 0000000..8055863 Binary files /dev/null and b/files/fake/wireguard_initiation.bin differ diff --git a/files/fake/wireguard_response.bin b/files/fake/wireguard_response.bin new file mode 100644 index 0000000..c4597de Binary files /dev/null and b/files/fake/wireguard_response.bin differ diff --git a/files/fake/zero_1024.bin b/files/fake/zero_1024.bin new file mode 100644 index 0000000..06d7405 Binary files /dev/null and b/files/fake/zero_1024.bin differ diff --git a/files/fake/zero_256.bin b/files/fake/zero_256.bin new file mode 100644 index 0000000..65f57c2 Binary files /dev/null and b/files/fake/zero_256.bin differ diff --git a/files/fake/zero_512.bin b/files/fake/zero_512.bin new file mode 100644 index 0000000..a64a5a9 Binary files /dev/null and b/files/fake/zero_512.bin differ diff --git a/files/huawei/E8372/run-zapret-hostlist b/files/huawei/E8372/run-zapret-hostlist new file mode 100755 index 0000000..7f37d58 --- /dev/null +++ b/files/huawei/E8372/run-zapret-hostlist @@ -0,0 +1,35 @@ +#!/system/bin/busybox sh + +# download hostlist from http(s) (need curl, its absent by default), +# feed it to zapret. save flash write cycles + +u="https://your.host.com/censorship/hoslist.txt" + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +d=/data/censorship +[ -d $d ] || mkdir $d +f=$d/hostlist.txt +t=/hostlist.txt + +curl -k --fail --max-time 10 -o "$t" "$u" && { + if [ -s "$t" ]; then + m1=$(md5sum "$t" | cut -d ' ' -f 1) + m2=$(md5sum "$f" | cut -d ' ' -f 1) + echo $m1 $m2 + if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then + echo updating hostlist + cp -f "$t" "$f" + else + echo hostlist was not changed. keeping old copy + fi + else + echo downloaded hostlist is empty. disabling zapret + rm "$f" + fi +} + +rm -f "$t" +"$EXEDIR/unzapret" +[ -s "$f" ] && exec "$EXEDIR/zapret" "--hostlist=$f" diff --git a/files/huawei/E8372/run-zapret-ip b/files/huawei/E8372/run-zapret-ip new file mode 100755 index 0000000..803e984 --- /dev/null +++ b/files/huawei/E8372/run-zapret-ip @@ -0,0 +1,39 @@ +#!/system/bin/busybox sh + +# download hostlist from http(s) (need curl, its absent by default), +# resolve to ip list, feed to zapret-ip. save flash write cycles + +u="https://your.host.com/censorship/hoslist.txt" + +SCRIPT=$(readlink -f "$0") +EXEDIR=$(dirname "$SCRIPT") + +d=/data/censorship +[ -d $d ] || mkdir $d +f=$d/hostlist.txt +t=/hostlist.txt +i=/iplist.txt + +curl -k --fail --max-time 10 -o "$t" "$u" && { + if [ -s "$t" ]; then + m1=$(md5sum "$t" | cut -d ' ' -f 1) + m2=$(md5sum "$f" | cut -d ' ' -f 1) + echo $m1 $m2 + if [ -z "$m2" ] || [ "$m1" != "$m2" ]; then + echo updating hostlist + cp -f "$t" "$f" + else + echo hostlist was not changed. keeping old copy + fi + else + echo downloaded hostlist is empty. disabling zapret + rm "$f" + fi +} + +rm -f "$t" +"$EXEDIR/unzapret-ip" +[ -s "$f" ] && { + mdig --threads=10 --family=4 <"$f" >"$i" + [ -s "$i" ] && exec "$EXEDIR/zapret-ip" "$i" +} diff --git a/files/huawei/E8372/unfuck_nfqueue.ko b/files/huawei/E8372/unfuck_nfqueue.ko new file mode 100644 index 0000000..c24ce5e 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 0000000..f040dfc --- /dev/null +++ b/files/huawei/E8372/unzapret @@ -0,0 +1,9 @@ +#!/system/bin/busybox sh + +rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" +iptables -C $rule 2>/dev/null && iptables -D $rule +killall tpws + +rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" +iptables -C $rule 2>/dev/null && iptables -D $rule +killall nfqws diff --git a/files/huawei/E8372/unzapret-ip b/files/huawei/E8372/unzapret-ip new file mode 100755 index 0000000..ccb7425 --- /dev/null +++ b/files/huawei/E8372/unzapret-ip @@ -0,0 +1,11 @@ +#!/system/bin/busybox sh + +rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" +iptables -C $rule 2>/dev/null && iptables -D $rule +iptables -F tpws -t nat +iptables -X tpws -t nat +killall tpws + +rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" +iptables -C $rule 2>/dev/null && iptables -D $rule +killall nfqws diff --git a/files/huawei/E8372/zapret b/files/huawei/E8372/zapret new file mode 100755 index 0000000..f19eed3 --- /dev/null +++ b/files/huawei/E8372/zapret @@ -0,0 +1,15 @@ +#!/system/bin/busybox sh + +# $1 - additional parameters for nfqws + +insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null + +rule="PREROUTING -t nat -i br0 ! -d 192.168.0.0/16 -p tcp -m multiport --dports 80,443 -j REDIRECT --to-port 1" +iptables -C $rule 2>/dev/null || iptables -I $rule + +tpws --uid 1:3003 --port=1 --daemon + +rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" +iptables -C $rule 2>/dev/null || iptables -I $rule + +nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon $1 diff --git a/files/huawei/E8372/zapret-ip b/files/huawei/E8372/zapret-ip new file mode 100755 index 0000000..9e70fac --- /dev/null +++ b/files/huawei/E8372/zapret-ip @@ -0,0 +1,34 @@ +#!/system/bin/busybox sh + +# $1 - ip list file. create individual rules for tpws redirection. ipset is not available + +[ -z "$1" ] && { + echo need iplist file as parameter + exit 1 +} + +insmod /online/modules/unfuck_nfqueue.ko 2>/dev/null + +tpws --maxconn=1024 --uid 1:3003 --port=1 --daemon + + +REDIR="-j REDIRECT --to-port 1" + +iptables -F tpws -t nat +iptables -X tpws -t nat +iptables -N tpws -t nat +iptables -A tpws -t nat -d 192.168.0.0/16 -j RETURN + +while read ip; do + echo redirecting $ip + iptables -A tpws -t nat -d $ip -p tcp $REDIR +done <"$1" + + +rule="PREROUTING -t nat -i br0 -p tcp -m multiport --dports 80,443 -j tpws" +iptables -C $rule 2>/dev/null || iptables -I $rule + +nfqws --uid 2 --qnum=200 --dpi-desync=disorder --dpi-desync-ttl=8 --dpi-desync-fooling=md5sig --daemon + +rule="OUTPUT -t mangle -o wan0 -p tcp -m multiport --dports 80,443 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num 200 --queue-bypass" +iptables -C $rule 2>/dev/null || iptables -I $rule 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 0000000..a4c08c5 --- /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 0000000..bdcda12 --- /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 0000000..dfe6d10 --- /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 0000000..e69de29 diff --git a/init.d/macos/functions b/init.d/macos/functions new file mode 100644 index 0000000..d004dc2 --- /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 0000000..17f7897 --- /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 0000000..747d69b --- /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 0000000..3a1ca58 --- /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 0000000..8cb05f5 --- /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 0000000..b156402 --- /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 0000000..ae2bdf9 --- /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 0000000..8336b72 --- /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 0000000..3126658 --- /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 0000000..92d1400 --- /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 0000000..7445344 --- /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 0000000..df18da8 --- /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 0000000..e69de29 diff --git a/init.d/openwrt/firewall.zapret b/init.d/openwrt/firewall.zapret new file mode 100644 index 0000000..a09d74d --- /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 0000000..3c1e8d0 --- /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 0000000..c62760d --- /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 0000000..9c434ac --- /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 0000000..2781ad3 --- /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 0000000..f216e93 --- /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 0000000..aaab911 --- /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 0000000..bdd22a1 --- /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 0000000..42a1210 --- /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 0000000..eeee1b0 --- /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 0000000..29379bd --- /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 0000000..9d3bf41 --- /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 0000000..6002969 --- /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 0000000..c1b183e --- /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 0000000..7fcb0e2 --- /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 0000000..735b2c5 --- /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 0000000..487b4cd --- /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 0000000..5f4b593 --- /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 0000000..95042c0 --- /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 0000000..e69de29 diff --git a/init.d/sysv/functions b/init.d/sysv/functions new file mode 100644 index 0000000..9caa569 --- /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 0000000..9e247a4 --- /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 0000000..c4f1f3e --- /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 0000000..f2ae1c1 --- /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 0000000..be938cc --- /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 0000000..97a53d7 --- /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 0000000..793ee72 --- /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 0000000..2ee1185 --- /dev/null +++ b/ip2net/qsort.c @@ -0,0 +1,250 @@ +/* Copyright (C) 1991-2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Written by Douglas C. Schmidt (schmidt@ics.uci.edu). + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* If you consider tuning this algorithm, you should consult first: + Engineering a sort function; Jon Bentley and M. Douglas McIlroy; + Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */ + +//#include +#include +#include +//#include +#include "qsort.h" + +/* Byte-wise swap two items of size SIZE. */ +#define SWAP(a, b, size) \ + do \ + { \ + size_t __size = (size); \ + char *__a = (a), *__b = (b); \ + do \ + { \ + char __tmp = *__a; \ + *__a++ = *__b; \ + *__b++ = __tmp; \ + } while (--__size > 0); \ + } while (0) + +/* Discontinue quicksort algorithm when partition gets below this size. + This particular magic number was chosen to work best on a Sun 4/260. */ +#define MAX_THRESH 4 + +/* Stack node declarations used to store unfulfilled partition obligations. */ +typedef struct + { + char *lo; + char *hi; + } stack_node; + +/* The next 4 #defines implement a very fast in-line stack abstraction. */ +/* The stack needs log (total_elements) entries (we could even subtract + log(MAX_THRESH)). Since total_elements has type size_t, we get as + upper bound for log (total_elements): + bits per byte (CHAR_BIT) * sizeof(size_t). */ +#define STACK_SIZE (CHAR_BIT * sizeof(size_t)) +#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top)) +#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi))) +#define STACK_NOT_EMPTY (stack < top) + + +/* Order size using quicksort. This implementation incorporates + four optimizations discussed in Sedgewick: + + 1. Non-recursive, using an explicit stack of pointer that store the + next array partition to sort. To save time, this maximum amount + of space required to store an array of SIZE_MAX is allocated on the + stack. Assuming a 32-bit (64 bit) integer for size_t, this needs + only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes). + Pretty cheap, actually. + + 2. Chose the pivot element using a median-of-three decision tree. + This reduces the probability of selecting a bad pivot value and + eliminates certain extraneous comparisons. + + 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving + insertion sort to order the MAX_THRESH items within each partition. + This is a big win, since insertion sort is faster for small, mostly + sorted array segments. + + 4. The larger of the two sub-partitions is always pushed onto the + stack first, with the algorithm then concentrating on the + smaller partition. This *guarantees* no more than log (total_elems) + stack size is needed (actually O(1) in this case)! */ + +void +gnu_quicksort (void *const pbase, size_t total_elems, size_t size, + __gnu_compar_d_fn_t cmp, void *arg) +{ + char *base_ptr = (char *) pbase; + + const size_t max_thresh = MAX_THRESH * size; + + if (total_elems == 0) + /* Avoid lossage with unsigned arithmetic below. */ + return; + + if (total_elems > MAX_THRESH) + { + char *lo = base_ptr; + char *hi = &lo[size * (total_elems - 1)]; + stack_node stack[STACK_SIZE]; + stack_node *top = stack; + + PUSH (NULL, NULL); + + while (STACK_NOT_EMPTY) + { + char *left_ptr; + char *right_ptr; + + /* Select median value from among LO, MID, and HI. Rearrange + LO and HI so the three values are sorted. This lowers the + probability of picking a pathological pivot value and + skips a comparison for both the LEFT_PTR and RIGHT_PTR in + the while loops. */ + + char *mid = lo + size * ((hi - lo) / size >> 1); + + if ((*cmp) ((void *) mid, (void *) lo, arg) < 0) + SWAP (mid, lo, size); + if ((*cmp) ((void *) hi, (void *) mid, arg) < 0) + SWAP (mid, hi, size); + else + goto jump_over; + if ((*cmp) ((void *) mid, (void *) lo, arg) < 0) + SWAP (mid, lo, size); + jump_over:; + + left_ptr = lo + size; + right_ptr = hi - size; + + /* Here's the famous ``collapse the walls'' section of quicksort. + Gotta like those tight inner loops! They are the main reason + that this algorithm runs much faster than others. */ + do + { + while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0) + left_ptr += size; + + while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0) + right_ptr -= size; + + if (left_ptr < right_ptr) + { + SWAP (left_ptr, right_ptr, size); + if (mid == left_ptr) + mid = right_ptr; + else if (mid == right_ptr) + mid = left_ptr; + left_ptr += size; + right_ptr -= size; + } + else if (left_ptr == right_ptr) + { + left_ptr += size; + right_ptr -= size; + break; + } + } + while (left_ptr <= right_ptr); + + /* Set up pointers for next iteration. First determine whether + left and right partitions are below the threshold size. If so, + ignore one or both. Otherwise, push the larger partition's + bounds on the stack and continue sorting the smaller one. */ + + if ((size_t) (right_ptr - lo) <= max_thresh) + { + if ((size_t) (hi - left_ptr) <= max_thresh) + /* Ignore both small partitions. */ + POP (lo, hi); + else + /* Ignore small left partition. */ + lo = left_ptr; + } + else if ((size_t) (hi - left_ptr) <= max_thresh) + /* Ignore small right partition. */ + hi = right_ptr; + else if ((right_ptr - lo) > (hi - left_ptr)) + { + /* Push larger left partition indices. */ + PUSH (lo, right_ptr); + lo = left_ptr; + } + else + { + /* Push larger right partition indices. */ + PUSH (left_ptr, hi); + hi = right_ptr; + } + } + } + + /* Once the BASE_PTR array is partially sorted by quicksort the rest + is completely sorted using insertion sort, since this is efficient + for partitions below MAX_THRESH size. BASE_PTR points to the beginning + of the array to sort, and END_PTR points at the very last element in + the array (*not* one beyond it!). */ + +#define min(x, y) ((x) < (y) ? (x) : (y)) + + { + char *const end_ptr = &base_ptr[size * (total_elems - 1)]; + char *tmp_ptr = base_ptr; + char *thresh = min(end_ptr, base_ptr + max_thresh); + char *run_ptr; + + /* Find smallest element in first threshold and place it at the + array's beginning. This is the smallest array element, + and the operation speeds up insertion sort's inner loop. */ + + for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size) + if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0) + tmp_ptr = run_ptr; + + if (tmp_ptr != base_ptr) + SWAP (tmp_ptr, base_ptr, size); + + /* Insertion sort, running from left-hand-side up to right-hand-side. */ + + run_ptr = base_ptr + size; + while ((run_ptr += size) <= end_ptr) + { + tmp_ptr = run_ptr - size; + while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0) + tmp_ptr -= size; + + tmp_ptr += size; + if (tmp_ptr != run_ptr) + { + char *trav; + + trav = run_ptr + size; + while (--trav >= run_ptr) + { + char c = *trav; + char *hi, *lo; + + for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo) + *hi = *lo; + *hi = c; + } + } + } + } +} diff --git a/ip2net/qsort.h b/ip2net/qsort.h new file mode 100644 index 0000000..f537ab7 --- /dev/null +++ b/ip2net/qsort.h @@ -0,0 +1,6 @@ +#pragma once + +// GNU qsort is 2x faster than musl + +typedef int (*__gnu_compar_d_fn_t) (const void *, const void *, void *); +void gnu_quicksort (void *const pbase, size_t total_elems, size_t size, __gnu_compar_d_fn_t cmp, void *arg); diff --git a/ipset/antifilter.helper b/ipset/antifilter.helper new file mode 100644 index 0000000..0508209 --- /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 0000000..80c1531 --- /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 0000000..a88137d --- /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 0000000..174cc35 --- /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 0000000..a5b3d22 --- /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 0000000..e2cd085 --- /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 0000000..de08e28 --- /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 0000000..9f0d671 --- /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 0000000..ccf1c8f --- /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 0000000..93848ac --- /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 0000000..f751f18 --- /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 0000000..adaf8d6 --- /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 0000000..2bda981 --- /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 0000000..6691268 --- /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 0000000..6e530e7 --- /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 0000000..d31c0b3 --- /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 0000000..d2defdc --- /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 0000000..924a073 --- /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 0000000..2d98981 --- /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 0000000..999ccdd --- /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 0000000..58bc4b4 --- /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 0000000..047e7b6 --- /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 0000000..1717340 --- /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 0000000..783f437 --- /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 0000000..dcc3657 --- /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 0000000..c33831e --- /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 0000000..62a79db --- /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 0000000..e37a616 --- /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 0000000..1d0a046 --- /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 0000000..d836001 --- /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 0000000..1ce55ef --- /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 0000000..b04724d --- /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 0000000..92a6e8f --- /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 0000000..42adad9 --- /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 0000000..266cb37 --- /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 0000000..9e05325 --- /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 0000000..4ceba0d --- /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 0000000..8b3a63b --- /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 0000000..2c9bc9c --- /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 0000000..861b4d0 --- /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 0000000..40eeeda --- /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 0000000..90f6701 --- /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 0000000..e1181e6 --- /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 0000000..4aa42aa --- /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 0000000..cb46670 --- /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 0000000..15e30d2 --- /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 0000000..01d0558 --- /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 0000000..03ed441 --- /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 0000000..0bdb94a --- /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 0000000..7fea2b3 --- /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 0000000..86aa882 --- /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 0000000..ff5ee73 --- /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 0000000..fb57798 --- /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 0000000..e6ad407 --- /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 0000000..b9dd395 --- /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 0000000..785b04d --- /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 0000000..154d541 --- /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 0000000..b83607e --- /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 0000000..2264f81 --- /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 0000000..b6f8e66 --- /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 0000000..9a396b6 --- /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 0000000..e135324 --- /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 0000000..13a0980 --- /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 0000000..99f3d35 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 0000000..fc63adf --- /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 0000000..36180cb 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 0000000..0ca3b2f Binary files /dev/null and b/nfq/winmanifest.o differ diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000..e69de29 diff --git a/tpws/BSDmakefile b/tpws/BSDmakefile new file mode 100644 index 0000000..568f67b --- /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 0000000..52b72f7 --- /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 0000000..f96c0f1 --- /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 0000000..7b11653 --- /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 0000000..ac89f5f --- /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 0000000..01ae19a --- /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 0000000..baf3dc2 --- /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 0000000..1af7195 --- /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 0000000..3e5bb55 --- /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 0000000..6fbd3f5 --- /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 0000000..ebefc14 --- /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 0000000..8623f63 --- /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 0000000..8b41507 --- /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 0000000..cb46670 --- /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 0000000..15e30d2 --- /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 0000000..ce208f7 --- /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 0000000..17ace35 --- /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 0000000..7a8b8b4 --- /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 0000000..0bdb94a --- /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 0000000..e6f6b6e --- /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 0000000..697fddf --- /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 0000000..d31aac0 --- /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 0000000..3764fc7 --- /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 0000000..785b04d --- /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 0000000..ab58968 --- /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 0000000..190277d --- /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 0000000..ecfc8b2 --- /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 0000000..ee46267 --- /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 0000000..b9c204f --- /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 0000000..3151717 --- /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 0000000..873c875 --- /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 0000000..5c9feb0 --- /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 0000000..8fc33ca --- /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 0000000..ccc5c6f --- /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 0000000..3e49485 --- /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 0000000..fa4eb30 --- /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 0000000..3597ae8 --- /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 0000000..c52cbc8 --- /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 0000000..9a396b6 --- /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 0000000..781c8d5 --- /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