diff --git a/blockcheck2.d/custom/10-list.sh b/blockcheck2.d/custom/10-list.sh new file mode 100644 index 0000000..8531bdf --- /dev/null +++ b/blockcheck2.d/custom/10-list.sh @@ -0,0 +1,58 @@ +LIST_HTTP="${LIST_HTTP:-$TESTDIR/list_http.txt}" +LIST_HTTPS_TLS12="${LIST_HTTPS_TLS12:-$TESTDIR/list_https_tls12.txt}" +LIST_HTTPS_TLS13="${LIST_HTTPS_TLS13:-$TESTDIR/list_https_tls13.txt}" +LIST_QUIC="${LIST_QUIC:-$TESTDIR/list_quic.txt}" + +check_list() +{ + # $1 - test function + # $2 - domain + # $3 - file + + local line ok=0 + [ -f "$3" ] || { + echo "no strategy file '$3'" + return 1 + } + while IFS= read -r line; do + case "$line" in + ""|\#*) continue ;; + esac + line=$(echo "$line" | tr -d "\r\n") + eval pktws_curl_test_update "$1" "$2" $line && ok=1 + done < "$3" + + [ "$ok" = 1 ] +} + +pktws_check_http() +{ + # $1 - test function + # $2 - domain + + check_list "$1" "$2" "$LIST_HTTP" +} + +pktws_check_https_tls12() +{ + # $1 - test function + # $2 - domain + + check_list "$1" "$2" "$LIST_HTTPS_TLS12" +} + +pktws_check_https_tls13() +{ + # $1 - test function + # $2 - domain + + check_list "$1" "$2" "$LIST_HTTPS_TLS13" +} + +pktws_check_http3() +{ + # $1 - test function + # $2 - domain + + check_list "$1" "$2" "$LIST_QUIC" +} diff --git a/blockcheck2.d/custom/README.txt b/blockcheck2.d/custom/README.txt new file mode 100644 index 0000000..0dcbb5b --- /dev/null +++ b/blockcheck2.d/custom/README.txt @@ -0,0 +1,10 @@ +Простой тестер стратегий по списку из файла. +Скопируйте эту директорию под другим именем в blockcheck2.d, отредактируйте list файлы, впишите туда свои стратегии. +В диалоге blockcheck2.sh выберите тест с названием вашей директории. +Можно комментировать строки символом '#' в начале строки. +Альтернативный путь до файлов стратегий можно задать переменными LIST_HTTP, LIST_HTTPS_TLS12, LIST_HTTPS_TLS13, LIST_QUIC. + +This is simple strategy tester from a file. +Copy this folder, write your strategies into list files and select your test in blockcheck2 dialog. +Lines can be commented using the '#' symbol at the line start. +Strategy list files paths can be overriden in env variables : LIST_HTTP, LIST_HTTPS_TLS12, LIST_HTTPS_TLS13, LIST_QUIC. diff --git a/blockcheck2.d/custom/list_http.txt b/blockcheck2.d/custom/list_http.txt new file mode 100644 index 0000000..a9cc936 --- /dev/null +++ b/blockcheck2.d/custom/list_http.txt @@ -0,0 +1,4 @@ +# write nfqws2 parameters here +--payload=http_req --lua-desync=http_hostcase +--payload=http_req --lua-desync=http_methodeol +--payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_ts=-1000 diff --git a/blockcheck2.d/custom/list_https_tls12.txt b/blockcheck2.d/custom/list_https_tls12.txt new file mode 100644 index 0000000..5300d58 --- /dev/null +++ b/blockcheck2.d/custom/list_https_tls12.txt @@ -0,0 +1,3 @@ +# write nfqws2 parameters here +--payload tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_ts=-1000 +--payload=tls_client_hello --lua-desync=fake:blob=0x00000000:tcp_md5:repeats=1 --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,dupsid:repeats=1 --lua-desync=multisplit:pos=2 diff --git a/blockcheck2.d/custom/list_https_tls13.txt b/blockcheck2.d/custom/list_https_tls13.txt new file mode 100644 index 0000000..f88e5eb --- /dev/null +++ b/blockcheck2.d/custom/list_https_tls13.txt @@ -0,0 +1,4 @@ +# write nfqws2 parameters here +--payload tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_ts=-1000 +--payload tls_client_hello --lua-desync=tcpseg:pos=0,-1:seqovl=1 --lua-desync=drop +--payload tls_client_hello --lua-desync=luaexec:code="desync.pat=tls_mod(fake_default_tls,'rnd,rndsni,dupsid,padencap',desync.reasm_data)" --lua-desync=tcpseg:pos=0,-1:seqovl=#pat:seqovl_pattern=pat --lua-desync=drop diff --git a/blockcheck2.d/custom/list_quic.txt b/blockcheck2.d/custom/list_quic.txt new file mode 100644 index 0000000..ba7d128 --- /dev/null +++ b/blockcheck2.d/custom/list_quic.txt @@ -0,0 +1,3 @@ +# write nfqws2 parameters here +--payload quic_initial --lua-desync=fake:blob=fake_default_quic:repeats=11 +--payload quic_initial --lua-desync=send:ipfrag --lua-desync=drop diff --git a/blockcheck2.d/standard/10-http-basic.sh b/blockcheck2.d/standard/10-http-basic.sh new file mode 100644 index 0000000..68bf829 --- /dev/null +++ b/blockcheck2.d/standard/10-http-basic.sh @@ -0,0 +1,12 @@ +pktws_check_http() +{ + # $1 - test function + # $2 - domain + local s + + [ "$NOTEST_BASIC_HTTP" = 1 ] && { echo "SKIPPED"; return; } + + for s in 'http_hostcase' 'http_hostcase:spell=hoSt' 'http_domcase' 'http_methodeol'; do + pktws_curl_test_update $1 $2 --payload http_req --lua-desync=$s + done +} diff --git a/blockcheck2.d/standard/15-misc.sh b/blockcheck2.d/standard/15-misc.sh new file mode 100644 index 0000000..e2a1466 --- /dev/null +++ b/blockcheck2.d/standard/15-misc.sh @@ -0,0 +1,38 @@ +. "$TESTDIR/def.inc" + +pktws_check_http() +{ + # $1 - test function + # $2 - domain + + local PAYLOAD="--payload http_req" repeats ok + + for repeats in 1 20 100 260; do + # send starting bytes of original payload + pktws_curl_test_update "$1" "$2" $PAYLOAD --lua-desync=tcpseg:pos=0,method+2:ip_id=rnd:repeats=$repeats && ok=1 + pktws_curl_test_update "$1" "$2" $PAYLOAD --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=$repeats && ok=1 + [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break + done +} + +pktws_check_https_tls12() +{ + # $1 - test function + # $2 - domain + + local PAYLOAD="--payload tls_client_hello" repeats ok + + for repeats in 1 20 100 260; do + # send starting bytes of original payload + pktws_curl_test_update "$1" "$2" $PAYLOAD --lua-desync=tcpseg:pos=0,1:ip_id=rnd:repeats=$repeats && ok=1 + pktws_curl_test_update "$1" "$2" $PAYLOAD --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=$repeats && ok=1 + [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break + done +} + +pktws_check_https_tls13() +{ + # $1 - test function + # $2 - domain + pktws_check_https_tls12 "$1" "$2" +} diff --git a/blockcheck2.d/standard/20-multi.sh b/blockcheck2.d/standard/20-multi.sh new file mode 100644 index 0000000..01a9b1d --- /dev/null +++ b/blockcheck2.d/standard/20-multi.sh @@ -0,0 +1,66 @@ +pktws_simple_split_tests() +{ + # $1 - test function + # $2 - domain/uri + # $3 - splits + # $4 - PRE args for nfqws2 + local pos ok ok_any pre="$4" + local splitf splitfs="multisplit multidisorder" + + ok_any=0 + for splitf in multisplit multidisorder; do + eval need_$splitf=0 + ok=0 + for pos in $3; do + pktws_curl_test_update $1 $2 $pre $PAYLOAD --lua-desync=$splitf:pos=$pos && ok=1 + done + [ "$ok" = 1 -a "$SCANLEVEL" != force ] || eval need_$splitf=1 + [ "$ok" = 1 ] && ok_any=1 + done + [ "$ok_any" = 1 ] +} + + +pktws_check_http() +{ + # $1 - test function + # $2 - domain + local splits_http='method+2 midsld method+2,midsld' + local PAYLOAD="--payload http_req" + + [ "$NOTEST_MULTI_HTTP" = 1 ] && { echo "SKIPPED"; return; } + + pktws_simple_split_tests "$1" "$2" "$splits_http" +} + +pktws_check_https_tls() +{ + # $1 - test function + # $2 - domain + # $3 - PRE args for nfqws2 + local splits_tls='2 1 sniext+1 sniext+4 host+1 midsld 1,midsld 1,sniext+1,host+1,midsld-2,midsld,midsld+2,endhost-1' + local PAYLOAD="--payload tls_client_hello" + + [ "$NOTEST_MULTI_HTTPS" = 1 ] && { echo "SKIPPED"; return; } + + pktws_simple_split_tests "$1" "$2" "$splits_tls" "$3" +} + +pktws_check_https_tls12() +{ + # $1 - test function + # $2 - domain + pktws_check_https_tls "$1" "$2" && [ "$SCANLEVEL" != force ] && return + + # do not use 'need' values obtained with wssize + local need_multisplit_save=$need_multisplit need_multidisorder_save=$need_multidisorder + pktws_check_https_tls "$1" "$2" --lua-desync=wssize:wsize=1:scale=6 + need_multisplit=$need_multisplit_save; need_multidisorder=$need_multidisorder_save +} + +pktws_check_https_tls13() +{ + # $1 - test function + # $2 - domain + pktws_check_https_tls "$1" "$2" +} diff --git a/blockcheck2.d/standard/23-seqovl.sh b/blockcheck2.d/standard/23-seqovl.sh new file mode 100644 index 0000000..1022517 --- /dev/null +++ b/blockcheck2.d/standard/23-seqovl.sh @@ -0,0 +1,94 @@ +pktws_check_http() +{ + # $1 - test function + # $2 - domain + + [ "$NOTEST_SEQOVL_HTTP" = 1 ] && { echo "SKIPPED"; return; } + + local PAYLOAD="--payload http_req" + + local ok pat= split f f2 + + pat=${SEQOVL_PATTERN_HTTP:+seqovl_pat} + pat=${pat:-fake_default_http} + + pktws_curl_test_update $1 $2 $PAYLOAD --lua-desync=tcpseg:pos=0,-1:seqovl=1 --lua-desync=drop + pktws_curl_test_update $1 $2 ${SEQOVL_PATTERN_HTTP:+--blob=$pat:@"$SEQOVL_PATTERN_HTTP" }$PAYLOAD --lua-desync=tcpseg:pos=0,-1:seqovl=#$pat:seqovl_pattern=$pat --lua-desync=drop + + ok=0 + for split in method+2 method+2,midsld; do + pktws_curl_test_update $1 $2 $PAYLOAD --lua-desync=multisplit:pos=$split:seqovl=1 && ok=1 + pktws_curl_test_update $1 $2 ${SEQOVL_PATTERN_HTTP:+--blob=$pat:@"$SEQOVL_PATTERN_HTTP" }$PAYLOAD --lua-desync=multisplit:pos=$split:seqovl=#$pat:seqovl_pattern=$pat && ok=1 + [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break + done + for split in 'method+1 method+2' 'midsld-1 midsld' 'method+1 method+2,midsld'; do + f="$(extract_arg 1 $split)" + f2="$(extract_arg 2 $split)" + pktws_curl_test_update $1 $2 $PAYLOAD --lua-desync=multidisorder:pos=$f2:seqovl=$f + pktws_curl_test_update $1 $2 ${SEQOVL_PATTERN_HTTP:+--blob=$pat:@"$SEQOVL_PATTERN_HTTP" }$PAYLOAD --lua-desync=multidisorder:pos=$f2:seqovl=$f:seqovl_pattern=$pat + done +} + +pktws_seqovl_tests_tls() +{ + # $1 - test function + # $2 - domain/uri + # $3 - PRE args for nfqws2 + local ok ok_any + local testf=$1 domain="$2" pre="$3" + local pat rnd_mod padencap_mod split f f2 + local PAYLOAD="--payload tls_client_hello" + + pat=${SEQOVL_PATTERN_HTTPS:+seqovl_pat} + pat=${pat:-fake_default_tls} + rnd_mod="--lua-init=$pat=tls_mod($pat,'rnd')" + padencap_mod="--lua-desync=luaexec:code=desync.pat=tls_mod($pat,'rnd,dupsid,padencap',desync.reasm_data)" + + ok=0 + pktws_curl_test_update $testf $domain $pre $PAYLOAD --lua-desync=tcpseg:pos=0,-1:seqovl=1 --lua-desync=drop && ok=1 + pktws_curl_test_update $testf $domain ${SEQOVL_PATTERN_HTTPS:+--blob=$pat:@"$SEQOVL_PATTERN_HTTPS" }$rnd_mod $pre $PAYLOAD --lua-desync=tcpseg:pos=0,-1:seqovl=#$pat:seqovl_pattern=$pat --lua-desync=drop && ok=1 + pktws_curl_test_update $testf $domain ${SEQOVL_PATTERN_HTTPS:+--blob=$pat:@"$SEQOVL_PATTERN_HTTPS" }$pre $PAYLOAD $padencap_mod --lua-desync=tcpseg:pos=0,-1:seqovl=#pat:seqovl_pattern=pat --lua-desync=drop && ok=1 + ok_any=$ok + + ok=0 + for split in 10 10,sniext+1 10,sniext+4 10,midsld; do + pktws_curl_test_update $testf $domain $pre $PAYLOAD --lua-desync=multisplit:pos=$split:seqovl=1 && ok=1 + pktws_curl_test_update $testf $domain ${SEQOVL_PATTERN_HTTPS:+--blob=$pat:@"$SEQOVL_PATTERN_HTTPS" }$rnd_mod $pre $PAYLOAD --lua-desync=multisplit:pos=$split:seqovl=#$pat:seqovl_pattern=$pat && ok=1 + pktws_curl_test_update $testf $domain ${SEQOVL_PATTERN_HTTPS:+--blob=$pat:@"$SEQOVL_PATTERN_HTTPS" }$pre $PAYLOAD $padencap_mod --lua-desync=multisplit:pos=$split:seqovl=#pat:seqovl_pattern=pat && ok=1 + [ "$ok" = 1 -a "$SCANLEVEL" != force ] && break + done + for split in '1 2' 'sniext sniext+1' 'sniext+3 sniext+4' 'midsld-1 midsld' '1 2,midsld'; do + f="$(extract_arg 1 $split)" + f2="$(extract_arg 2 $split)" + pktws_curl_test_update $1 $2 $PAYLOAD --lua-desync=multidisorder:pos=$f2:seqovl=$f && ok=1 + pktws_curl_test_update $testf $domain ${SEQOVL_PATTERN_HTTPS:+--blob=$pat:@"$SEQOVL_PATTERN_HTTPS" }$rnd_mod $pre $PAYLOAD --lua-desync=multidisorder:pos=$f2:seqovl=$f:seqovl_pattern=$pat && ok=1 + done + [ "$ok" = 1 ] && ok_any=1 + [ "$ok_any" = 1 ] +} + +pktws_check_https_tls() +{ + # $1 - test function + # $2 - domain + # $3 - PRE args for nfqws2 + + [ "$NOTEST_SEQOVL_HTTPS" = 1 ] && { echo "SKIPPED"; return; } + + pktws_seqovl_tests_tls "$1" "$2" "$3" +} + +pktws_check_https_tls12() +{ + # $1 - test function + # $2 - domain + pktws_seqovl_tests_tls "$1" "$2" && [ "$SCANLEVEL" != force ] && return + pktws_seqovl_tests_tls "$1" "$2" --lua-desync=wssize:wsize=1:scale=6 +} + +pktws_check_https_tls13() +{ + # $1 - test function + # $2 - domain + pktws_seqovl_tests_tls "$1" "$2" +} diff --git a/blockcheck2.d/standard/25-fake.sh b/blockcheck2.d/standard/25-fake.sh new file mode 100644 index 0000000..6af2e13 --- /dev/null +++ b/blockcheck2.d/standard/25-fake.sh @@ -0,0 +1,149 @@ +. "$TESTDIR/def.inc" + +pktws_check_http() +{ + # $1 - test function + # $2 - domain + [ "$NOTEST_FAKE_HTTP" = 1 ] && { echo "SKIPPED"; return; } + + local testf=$1 domain="$2" + local ok ok_any ttls attls f ff fake fooling + local PAYLOAD="--payload=http_req" + + if [ -n "$FAKE_HTTP" ]; then + fake=fake_http + else + fake=fake_default_http + fi + + need_fake=0 + + ttls=$(seq -s ' ' $MIN_TTL $MAX_TTL) + attls=$(seq -s ' ' $MIN_AUTOTTL_DELTA $MAX_AUTOTTL_DELTA) + + ok_any=0 + ok=0 + for ttl in $ttls; do + # orig-ttl=1 with start/cutoff limiter drops empty ACK packet in response to SYN,ACK. it does not reach DPI or server. + # missing ACK is transmitted in the first data packet of TLS/HTTP proto + for ff in $fake 0x00000000; do + for f in '' "--payload=empty --out-range=s1/dev/null +} + +exitp() +{ + local A + + [ "$BATCH" = 1 ] || { + echo + echo press enter to continue + read A + } + exit $1 +} + +pf_is_avail() +{ + [ -c /dev/pf ] +} +pf_status() +{ + pfctl -qsi | sed -nre "s/^Status: ([^ ]+).*$/\1/p" +} +pf_is_enabled() +{ + [ "$(pf_status)" = Enabled ] +} +pf_save() +{ + PF_STATUS=0 + pf_is_enabled && PF_STATUS=1 + [ "$UNAME" = "OpenBSD" ] && pfctl -sr >"$PF_RULES_SAVE" +} +pf_restore() +{ + [ -n "$PF_STATUS" ] || return + case "$UNAME" in + OpenBSD) + if [ -f "$PF_RULES_SAVE" ]; then + pfctl -qf "$PF_RULES_SAVE" + else + echo | pfctl -qf - + fi + ;; + esac + if [ "$PF_STATUS" = 1 ]; then + pfctl -qe + else + pfctl -qd + fi +} +pf_clean() +{ + rm -f "$PF_RULES_SAVE" +} +opf_dvtws_anchor() +{ + # $1 - tcp/udp + # $2 - port + # $3 - ip list + local iplist family=inet + [ "$IPV" = 6 ] && family=inet6 + make_comma_list iplist "$3" + echo "set reassemble no" + [ "$1" = tcp ] && echo "pass in quick $family proto $1 from {$iplist} port $2 flags SA/SA divert-packet port $IPFW_DIVERT_PORT no state" + echo "pass in quick $family proto $1 from {$iplist} port $2 no state" + echo "pass out quick $family proto $1 to {$iplist} port $2 divert-packet port $IPFW_DIVERT_PORT no state" + echo "pass" +} +opf_prepare_dvtws() +{ + # $1 - tcp/udp + # $2 - port + # $3 - ip list + opf_dvtws_anchor $1 $2 "$3" | pfctl -qf - + pfctl -qe +} + +cleanup() +{ + case "$UNAME" in + OpenBSD) + pf_clean + ;; + esac +} + +IPT() +{ + $IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@" +} +IPT_DEL() +{ + $IPTABLES -C "$@" >/dev/null 2>/dev/null && $IPTABLES -D "$@" +} +IPT_ADD_DEL() +{ + on_off_function IPT IPT_DEL "$@" +} +IPFW_ADD() +{ + ipfw -qf add $IPFW_RULE_NUM "$@" +} +IPFW_DEL() +{ + ipfw -qf delete $IPFW_RULE_NUM 2>/dev/null +} +ipt6_has_raw() +{ + ip6tables -nL -t raw >/dev/null 2>/dev/null +} +ipt6_has_frag() +{ + ip6tables -A OUTPUT -m frag 2>/dev/null || return 1 + ip6tables -D OUTPUT -m frag 2>/dev/null +} +ipt_has_nfq() +{ + # cannot just check /proc/net/ip_tables_targets because of iptables-nft or modules not loaded yet + iptables -A OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null || return 1 + iptables -D OUTPUT -t mangle -p 255 -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null + return 0 +} +nft_has_nfq() +{ + local res=1 + nft delete table ${NFT_TABLE}_test 2>/dev/null + nft add table ${NFT_TABLE}_test 2>/dev/null && { + nft add chain ${NFT_TABLE}_test test + nft add rule ${NFT_TABLE}_test test queue num $QNUM bypass 2>/dev/null && res=0 + nft delete table ${NFT_TABLE}_test + } + return $res +} + +doh_resolve() +{ + # $1 - ip version 4/6 + # $2 - hostname + # $3 - doh server URL. use $DOH_SERVER if empty + "$MDIG" --family=$1 --dns-make-query=$2 | "$CURL" --max-time $CURL_MAX_TIME_DOH -s --data-binary @- -H "Content-Type: application/dns-message" "${3:-$DOH_SERVER}" | "$MDIG" --dns-parse-query +} +doh_find_working() +{ + local doh + + [ -n "$DOH_SERVER" ] && return 0 + echo "* searching working DoH server" + DOH_SERVER= + for doh in $DOH_SERVERS; do + echo -n "$doh : " + if doh_resolve 4 iana.org $doh >/dev/null 2>/dev/null; then + echo OK + DOH_SERVER="$doh" + return 0 + else + echo FAIL + fi + done + echo all DoH servers failed + return 1 +} + +mdig_vars() +{ + # $1 - ip version 4/6 + # $2 - hostname + + hostvar=$(echo $2 | sed -e 's/[\./?&#@%*$^:~=!()+-]/_/g') + cachevar=DNSCACHE_${hostvar}_$1 + countvar=${cachevar}_COUNT + eval count=\$${countvar} +} +mdig_cache() +{ + # $1 - ip version 4/6 + # $2 - hostname + local hostvar cachevar countvar count ip ips + mdig_vars "$@" + [ -n "$count" ] || { + # windows version of mdig outputs 0D0A line ending. remove 0D. + if [ "$SECURE_DNS" = 1 ]; then + ips="$(echo $2 | doh_resolve $1 $2 | tr -d '\r' | xargs)" + else + ips="$(echo $2 | "$MDIG" --family=$1 | tr -d '\r' | xargs)" + fi + [ -n "$ips" ] || return 1 + count=0 + for ip in $ips; do + eval ${cachevar}_$count=$ip + count=$(($count+1)) + done + eval $countvar=$count + } + return 0 +} +mdig_resolve() +{ + # $1 - ip version 4/6 + # $2 - hostname, possibly with uri : rutracker.org/xxx/xxxx + local hostvar cachevar countvar count ip n sdom + + split_by_separator "$2" / sdom + mdig_vars "$1" "$sdom" + if [ -n "$count" ]; then + n=$(random 0 $(($count-1))) + eval ip=\$${cachevar}_$n + echo $ip + return 0 + else + mdig_cache "$1" "$sdom" && mdig_resolve "$1" "$sdom" + fi +} +mdig_resolve_all() +{ + # $1 - ip version 4/6 + # $2 - hostname + + local hostvar cachevar countvar count ip ips n sdom + + split_by_separator "$2" / sdom + mdig_vars "$1" "$sdom" + if [ -n "$count" ]; then + n=0 + while [ "$n" -le $count ]; do + eval ip=\$${cachevar}_$n + if [ -n "$ips" ]; then + ips="$ips $ip" + else + ips="$ip" + fi + n=$(($n + 1)) + done + echo "$ips" + return 0 + else + mdig_cache "$1" "$sdom" && mdig_resolve_all "$1" "$sdom" + fi +} + +netcat_setup() +{ + [ -n "$NCAT" ] || { + if exists ncat; then + NCAT=ncat + elif exists nc; then + # busybox netcat does not support any required options + is_linked_to_busybox nc && return 1 + NCAT=nc + else + return 1 + fi + } + return 0 +} +netcat_test() +{ + # $1 - ip + # $2 - port + local cmd + netcat_setup && { + cmd="$NCAT -z -w 2 $1 $2" + echo $cmd + $cmd 2>&1 + } +} + +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="$NFQWS2" + PKTWSD=nfqws2 + linux_fwtype + [ "$FWTYPE" = iptables -o "$FWTYPE" = nftables ] || { + echo firewall type $FWTYPE not supported in $UNAME + exitp 5 + } + ;; + FreeBSD) + PKTWS="$DVTWS2" + PKTWSD=dvtws2 + FWTYPE=ipfw + [ -f /etc/platform ] && read SUBSYS /dev/null + ;; + *) + echo $UNAME not supported + exitp 5 + esac + echo $UNAME${SUBSYS:+/$SUBSYS} detected + echo -n 'kernel: ' + if [ -f "/proc/version" ]; then + cat /proc/version + else + uname -a + fi + [ -f /etc/os-release ] && { + . /etc/os-release + [ -n "$PRETTY_NAME" ] && echo "distro: $PRETTY_NAME" + [ -n "$OPENWRT_RELEASE" ] && echo "openwrt release: $OPENWRT_RELEASE" + [ -n "$OPENWRT_BOARD" ] && echo "openwrt board: $OPENWRT_BOARD" + [ -n "$OPENWRT_ARCH" ] && echo "openwrt arch: $OPENWRT_ARCH" + } + echo firewall type is $FWTYPE + echo CURL=$CURL + "$CURL" --version +} + +zp_already_running() +{ + case "$UNAME" in + CYGWIN) + win_process_exists $PKTWSD || win_process_exists winws || win_process_exists goodbyedpi + ;; + *) + process_exists $PKTWSD || process_exists tpws || process_exists nfqws + esac +} +check_already() +{ + echo \* checking already running DPI bypass processes + if zp_already_running; then + echo "!!! WARNING. some dpi bypass processes already running !!!" + echo "!!! WARNING. blockcheck requires all DPI bypass methods disabled !!!" + echo "!!! WARNING. pls stop all dpi bypass instances that may interfere with blockcheck !!!" + fi +} + +freebsd_module_loaded() +{ + # $1 - module name + kldstat -qm "${1}" +} +freebsd_modules_loaded() +{ + # $1,$2,$3, ... - module names + while [ -n "$1" ]; do + freebsd_module_loaded $1 || return 1 + shift + done + return 0 +} + +check_prerequisites() +{ + echo \* checking prerequisites + + [ "$SKIP_PKTWS" = 1 -o -x "$PKTWS" ] && [ -x "$MDIG" ] || { + local target + case $UNAME in + OpenBSD) + target="bsd" + echo $PKTWS or $MDIG is not available. \`gmake -C \"$ZAPRET_BASE\" bsd \` + ;; + *) + echo $PKTWS or $MDIG is not available. run \"$ZAPRET_BASE/install_bin.sh\" or \`make -C \"$ZAPRET_BASE\" $target\` + esac + exitp 6 + } + + local prog progs='curl' + [ "$SKIP_PKTWS" = 1 ] || { + case "$UNAME" in + Linux) + case "$FWTYPE" in + iptables) + ipt_has_nfq || { + echo NFQUEUE iptables or ip6tables target is missing. pls install modules. + exitp 6 + } + progs="$progs iptables ip6tables" + ;; + nftables) + nft_has_nfq || { + echo nftables queue support is not available. pls install modules. + exitp 6 + } + progs="$progs nft" + ;; + esac + ;; + FreeBSD) + freebsd_modules_loaded ipfw ipdivert || { + echo ipfw or ipdivert kernel module not loaded + exitp 6 + } + [ "$(sysctl -qn net.inet.ip.fw.enable)" = 0 -o "$(sysctl -qn net.inet6.ip6.fw.enable)" = 0 ] && { + echo ipfw is disabled. use : ipfw enable firewall + exitp 6 + } + pf_is_avail && { + pf_save + [ "$SUBSYS" = "pfSense" ] && { + # pfsense's ipfw may not work without these workarounds + sysctl net.inet.ip.pfil.outbound=ipfw,pf 2>/dev/null + sysctl net.inet.ip.pfil.inbound=ipfw,pf 2>/dev/null + sysctl net.inet6.ip6.pfil.outbound=ipfw,pf 2>/dev/null + sysctl net.inet6.ip6.pfil.inbound=ipfw,pf 2>/dev/null + pfctl -qd + pfctl -qe + pf_restore + } + } + progs="$progs ipfw" + ;; + OpenBSD) + pf_is_avail || { + echo pf is not available + exitp 6 + } + pf_save + progs="$progs pfctl" + ;; + esac + } + + for prog in $progs; do + exists $prog || { + echo $prog does not exist. please install + exitp 6 + } + done + + if exists nslookup; then + LOOKUP=nslookup + elif exists host; then + LOOKUP=host + else + echo nslookup or host does not exist. please install + exitp 6 + fi +} + + +curl_translate_code() +{ + # $1 - code + printf $1 + case $1 in + 0) printf ": ok" + ;; + 1) printf ": unsupported protocol" + ;; + 2) printf ": early initialization code failed" + ;; + 3) printf ": the URL was not properly formatted" + ;; + 4) printf ": feature not supported by libcurl" + ;; + 5) printf ": could not resolve proxy" + ;; + 6) printf ": could not resolve host" + ;; + 7) printf ": could not connect" + ;; + 8) printf ": invalid server reply" + ;; + 9) printf ": remote access denied" + ;; + 27) printf ": out of memory" + ;; + 28) printf ": operation timed out" + ;; + 35) printf ": SSL connect error" + ;; + esac +} +curl_supports_tls13() +{ + local r + "$CURL" --tlsv1.3 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + # return code 2 = init failed. likely bad command line options + [ $? = 2 ] && return 1 + # curl can have tlsv1.3 key present but ssl library without TLS 1.3 support + # this is online test because there's no other way to trigger library incompatibility case + "$CURL" --tlsv1.3 --max-time 1 -Is -o /dev/null https://iana.org 2>/dev/null + r=$? + [ $r != 4 -a $r != 35 ] +} + +curl_supports_tlsmax() +{ + # supported only in OpenSSL and LibreSSL + "$CURL" --version | grep -Fq -e OpenSSL -e LibreSSL -e BoringSSL -e GnuTLS -e quictls || return 1 + # supported since curl 7.54 + "$CURL" --tls-max 1.2 -Is -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + # return code 2 = init failed. likely bad command line options + [ $? != 2 ] +} + +curl_supports_connect_to() +{ + "$CURL" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 http://127.0.0.1:65535 2>/dev/null + [ "$?" != 2 ] +} + +curl_supports_http3() +{ + # if it has http3 : curl: (3) HTTP/3 requested for non-HTTPS URL + # otherwise : curl: (2) option --http3-only: is unknown + "$CURL" --connect-to 127.0.0.1:: -o /dev/null --max-time 1 --http3-only http://127.0.0.1:65535 2>/dev/null + [ "$?" != 2 ] +} + +hdrfile_http_code() +{ + # $1 - hdr file + sed -nre '1,1 s/^HTTP\/1\.[0,1] ([0-9]+) .*$/\1/p' "$1" +} +hdrfile_location() +{ + # $1 - hdr file + + # some DPIs return CRLF line ending + tr -d '\015' <"$1" | sed -nre 's/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ ]*([^ ]*)[ ]*$/\1/p' +} + +curl_with_subst_ip() +{ + # $1 - domain + # $2 - port + # $3 - ip + # $4+ - curl params + local ip="$3" + case "$ip" in + *:*) ip="[$ip]" ;; + esac + local connect_to="--connect-to $1::$ip${2:+:$2}" arg + shift ; shift ; shift; + [ "$CURL_VERBOSE" = 1 ] && arg="-v" + [ "$CURL_CMD" = 1 ] && echo $CURL ${arg:+$arg }$connect_to "$@" + ALL_PROXY="$ALL_PROXY" "$CURL" ${arg:+$arg }$connect_to "$@" +} +curl_with_dig() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - port + # $4+ - curl params + local dom=$2 port=$3 + local sdom suri ip + + split_by_separator "$dom" / sdom suri + ip=$(mdig_resolve $1 $sdom) + shift ; shift ; shift + if [ -n "$ip" ]; then + curl_with_subst_ip "$sdom" "$port" "$ip" "$@" + else + return 6 + fi +} +curl_probe() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - port + # $4 - subst ip + # $5+ - curl params + local ipv=$1 dom=$2 port=$3 subst=$4 + shift; shift; shift; shift + if [ -n "$subst" ]; then + curl_with_subst_ip $dom $port $subst "$@" + else + curl_with_dig $ipv $dom $port "$@" + fi +} +curl_test_http() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + # $4 - "detail" - detail info + + local code loc hdrt="${HDRTEMP}_${!:-$$}.txt" dom="$(tolower "$2")" + curl_probe $1 $2 $HTTP_PORT "$3" -SsD "$hdrt" -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT "http://$2" -o /dev/null 2>&1 || { + code=$? + rm -f "$hdrt" + return $code + } + if [ "$4" = "detail" ] ; then + head -n 1 "$hdrt" + grep "^[lL]ocation:" "$hdrt" + else + code=$(hdrfile_http_code "$hdrt") + [ "$code" = 301 -o "$code" = 302 -o "$code" = 307 -o "$code" = 308 ] && { + loc=$(hdrfile_location "$hdrt") + tolower "$loc" | grep -qE "^https?://.*$dom(/|$)" || + tolower "$loc" | grep -vqE '^https?://' || { + echo suspicious redirection $code to : $loc + rm -f "$hdrt" + return 254 + } + } + fi + rm -f "$hdrt" + [ "$code" = 400 ] && { + # this can often happen if the server receives fake packets it should not receive + echo http code $code. likely the server receives fakes. + return 254 + } + return 0 +} +curl_test_https_tls12() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + + # do not use tls 1.3 to make sure server certificate is not encrypted + curl_probe $1 $2 $HTTPS_PORT "$3" $HTTPS_HEAD -Ss -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.2 $TLSMAX12 "https://$2" -o /dev/null 2>&1 +} +curl_test_https_tls13() +{ + # $1 - ip version : 4/6 + # $2 - domain name + # $3 - subst ip + + # force TLS1.3 mode + curl_probe $1 $2 $HTTPS_PORT "$3" $HTTPS_HEAD -Ss -A "$USER_AGENT" --max-time $CURL_MAX_TIME $CURL_OPT --tlsv1.3 $TLSMAX13 "https://$2" -o /dev/null 2>&1 +} + +curl_test_http3() +{ + # $1 - ip version : 4/6 + # $2 - domain name + + # force QUIC only mode without tcp + curl_with_dig $1 $2 $QUIC_PORT $HTTPS_HEAD -Ss -A "$USER_AGENT" --max-time $CURL_MAX_TIME_QUIC --http3-only $CURL_OPT "https://$2" -o /dev/null 2>&1 +} + +ipt_aux_scheme() +{ + # $1 - 1 - add , 0 - del + # $2 - tcp/udp + # $3 - port + + # to avoid possible INVALID state drop + [ "$2" = tcp ] && IPT_ADD_DEL $1 INPUT -p $2 --sport $3 ! --syn -j ACCEPT + + local icmp_filter="-p icmp -m icmp --icmp-type" + [ "$IPV" = 6 ] && icmp_filter="-p icmpv6 -m icmp6 --icmpv6-type" + IPT_ADD_DEL $1 INPUT $icmp_filter time-exceeded -m connmark --mark $DESYNC_MARK/$DESYNC_MARK -j DROP + + # for strategies with incoming packets involved (autottl) + IPT_ADD_DEL $1 OUTPUT -p $2 --dport $3 -m conntrack --ctstate INVALID -j ACCEPT + if [ "$IPV" = 6 -a -n "$IP6_DEFRAG_DISABLE" ]; then + # the only way to reliable disable ipv6 defrag. works only in 4.16+ kernels + IPT_ADD_DEL $1 OUTPUT -t raw -p $2 -m frag -j CT --notrack + elif [ "$IPV" = 4 ]; then + # enable fragments + IPT_ADD_DEL $1 OUTPUT -f -j ACCEPT + fi + # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) + # raw table may not be present + IPT_ADD_DEL $1 OUTPUT -t raw -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j CT --notrack +} +ipt_scheme() +{ + # $1 - tcp/udp + # $2 - port + # $3 - ip list + + local ip + + $IPTABLES -t mangle -N blockcheck_output 2>/dev/null + $IPTABLES -t mangle -F blockcheck_output + IPT OUTPUT -t mangle -j blockcheck_output + + # prevent loop + $IPTABLES -t mangle -A blockcheck_output -m mark --mark $DESYNC_MARK/$DESYNC_MARK -j RETURN + $IPTABLES -t mangle -A blockcheck_output ! -p $1 -j RETURN + $IPTABLES -t mangle -A blockcheck_output -p $1 ! --dport $2 -j RETURN + + for ip in $3; do + $IPTABLES -t mangle -A blockcheck_output -d $ip -j CONNMARK --or-mark $DESYNC_MARK + $IPTABLES -t mangle -A blockcheck_output -d $ip -j NFQUEUE --queue-num $QNUM + done + + ipt_aux_scheme 1 $1 $2 +} +nft_scheme() +{ + # $1 - tcp/udp + # $2 - port + # $3 - ip list + + local iplist ipver=$IPV + [ "$IPV" = 6 ] || ipver= + make_comma_list iplist $3 + + nft add table inet $NFT_TABLE + nft "add chain inet $NFT_TABLE postnat { type filter hook postrouting priority 102; }" + nft "add rule inet $NFT_TABLE postnat meta nfproto ipv${IPV} $1 dport $2 mark and $DESYNC_MARK == 0 ip${ipver} daddr {$iplist} ct mark set ct mark or $DESYNC_MARK queue num $QNUM" + # for strategies with incoming packets involved (autottl) + nft "add chain inet $NFT_TABLE prenat { type filter hook prerouting priority -102; }" + # enable everything generated by nfqws (works only in OUTPUT, not in FORWARD) + nft "add chain inet $NFT_TABLE predefrag { type filter hook output priority -402; }" + nft "add rule inet $NFT_TABLE predefrag meta nfproto ipv${IPV} mark and $DESYNC_MARK !=0 notrack" + [ "$IPV" = 4 ] && { + nft "add rule inet $NFT_TABLE prenat icmp type time-exceeded ct mark and $DESYNC_MARK != 0 drop" + nft "add rule inet $NFT_TABLE prenat icmp type time-exceeded ct state invalid drop" + } + [ "$IPV" = 6 ] && { + nft "add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct mark and $DESYNC_MARK != 0 drop" + nft "add rule inet $NFT_TABLE prenat icmpv6 type time-exceeded ct state invalid drop" + } +} + +pktws_ipt_prepare() +{ + # $1 - tcp/udp + # $2 - port + # $3 - ip list + + local ip + + case "$FWTYPE" in + iptables) + ipt_scheme $1 $2 "$3" + ;; + nftables) + nft_scheme $1 $2 "$3" + ;; + ipfw) + # disable PF to avoid interferences + pf_is_avail && pfctl -qd + for ip in $3; do + IPFW_ADD divert $IPFW_DIVERT_PORT $1 from me to $ip $2 proto ip${IPV} out not diverted + done + ;; + opf) + opf_prepare_dvtws $1 $2 "$3" + ;; + windivert) + WF="--wf-l3=ipv${IPV} --wf-${1}=$2" + rm -f "$IPSET_FILE" + for ip in $3; do + echo $ip >>"$IPSET_FILE" + done + ;; + + esac +} +pktws_ipt_unprepare() +{ + # $1 - tcp/udp + # $2 - port + + case "$FWTYPE" in + iptables) + ipt_aux_scheme 0 $1 $2 + IPT_DEL OUTPUT -t mangle -j blockcheck_output + $IPTABLES -t mangle -F blockcheck_output 2>/dev/null + $IPTABLES -t mangle -X blockcheck_output 2>/dev/null + ;; + nftables) + nft delete table inet $NFT_TABLE 2>/dev/null + ;; + ipfw) + IPFW_DEL + pf_is_avail && pf_restore + ;; + opf) + pf_restore + ;; + windivert) + unset WF + rm -f "$IPSET_FILE" + ;; + esac +} + +pktws_ipt_prepare_tcp() +{ + # $1 - port + # $2 - ip list + + local ip iplist ipver + + pktws_ipt_prepare tcp $1 "$2" + + # for autottl mode + case "$FWTYPE" in + iptables) + $IPTABLES -N blockcheck_input -t mangle 2>/dev/null + $IPTABLES -F blockcheck_input -t mangle 2>/dev/null + IPT INPUT -t mangle -j blockcheck_input + $IPTABLES -t mangle -A blockcheck_input ! -p tcp -j RETURN + $IPTABLES -t mangle -A blockcheck_input -p tcp ! --sport $1 -j RETURN + $IPTABLES -t mangle -A blockcheck_input -p tcp ! --tcp-flags SYN,ACK SYN,ACK -j RETURN + for ip in $2; do + $IPTABLES -A blockcheck_input -t mangle -s $ip -j NFQUEUE --queue-num $QNUM + done + ;; + nftables) + ipver=$IPV + [ "$IPV" = 6 ] || ipver= + make_comma_list iplist $2 + nft "add rule inet $NFT_TABLE prenat meta nfproto ipv${IPV} tcp sport $1 tcp flags & (syn | ack) == (syn | ack) ip${ipver} saddr {$iplist} queue num $QNUM" + ;; + ipfw) + for ip in $2; do + IPFW_ADD divert $IPFW_DIVERT_PORT tcp from $ip $1 to me proto ip${IPV} tcpflags syn,ack in not diverted + done + ;; + esac +} +pktws_ipt_unprepare_tcp() +{ + # $1 - port + + pktws_ipt_unprepare tcp $1 + + case "$FWTYPE" in + iptables) + IPT_DEL INPUT -t mangle -j blockcheck_input + $IPTABLES -t mangle -F blockcheck_input 2>/dev/null + $IPTABLES -t mangle -X blockcheck_input 2>/dev/null + ;; + esac +} +pktws_ipt_prepare_udp() +{ + # $1 - port + # $2 - ip list + + pktws_ipt_prepare udp $1 "$2" +} +pktws_ipt_unprepare_udp() +{ + # $1 - port + + pktws_ipt_unprepare udp $1 +} + +pktws_start() +{ + case "$UNAME" in + Linux) + "$NFQWS2" --uid $WS_UID:$WS_GID --fwmark=$DESYNC_MARK --qnum=$QNUM --lua-init=@"$ZAPRET_BASE/lua/zapret-lib.lua" --lua-init=@"$ZAPRET_BASE/lua/zapret-antidpi.lua" "$@" >/dev/null & + ;; + FreeBSD|OpenBSD) + "$DVTWS2" --port=$IPFW_DIVERT_PORT --lua-init=@"$ZAPRET_BASE/lua/zapret-lib.lua" --lua-init=@"$ZAPRET_BASE/lua/zapret-antidpi.lua" "$@" >/dev/null & + ;; + CYGWIN) + "$WINWS2" $WF --ipset="$IPSET_FILE" --lua-init=@"$ZAPRET_BASE/lua/zapret-lib.lua" --lua-init=@"$ZAPRET_BASE/lua/zapret-antidpi.lua" "$@" >/dev/null & + ;; + esac + 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 p pids + + if [ "$PARALLEL" = 1 ]; then + rm -f "${PARALLEL_OUT}"* + for n in $(seq -s ' ' 1 $REPEATS); do + $1 "$IPV" $2 $3 "$4" >"${PARALLEL_OUT}_$n" & + pids="${pids:+$pids }$!" + done + n=1 + for p in $pids; do + [ $REPEATS -gt 1 ] && printf "[attempt $n] " + if wait $p; then + [ $REPEATS -gt 1 ] && echo 'AVAILABLE' + else + code=$? + cat "${PARALLEL_OUT}_$n" + fi + n=$(($n+1)) + done + rm -f "${PARALLEL_OUT}"* + else + while [ $n -lt $REPEATS ]; do + n=$(($n+1)) + [ $REPEATS -gt 1 ] && printf "[attempt $n] " + if $1 "$IPV" $2 $3 "$4" ; then + [ $REPEATS -gt 1 ] && echo 'AVAILABLE' + else + code=$? + [ "$SCANLEVEL" = quick ] && break + fi + done + fi + [ "$4" = detail ] || { + if [ $code = 254 ]; then + echo "UNAVAILABLE" + elif [ $code = 0 ]; then + echo '!!!!! AVAILABLE !!!!!' + else + echo "UNAVAILABLE code=$code" + fi + } + return $code +} +ws_curl_test() +{ + # $1 - ws start function + # $2 - test function + # $3 - domain + # $4,$5,$6, ... - ws params + local code ws_start=$1 testf=$2 dom=$3 + + [ "$SIMULATE" = 1 ] && { + n=$(random 0 99) + if [ "$n" -lt "$SIM_SUCCESS_RATE" ]; then + echo "SUCCESS" + return 0 + else + echo "FAILED" + return 7 + fi + } + shift + shift + shift + $ws_start "$@" + curl_test $testf $dom + code=$? + ws_kill + return $code +} +pktws_curl_test() +{ + # $1 - test function + # $2 - domain + # $3,$4,$5, ... - nfqws/dvtws params + local testf=$1 dom=$2 strategy code + + shift; shift; + echo - $testf ipv$IPV $dom : $PKTWSD ${WF:+$WF }${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+"$PKTWS_EXTRA_PRE_1" }${PKTWS_EXTRA_PRE_2:+"$PKTWS_EXTRA_PRE_2" }${PKTWS_EXTRA_PRE_3:+"$PKTWS_EXTRA_PRE_3" }${PKTWS_EXTRA_PRE_4:+"$PKTWS_EXTRA_PRE_4" }${PKTWS_EXTRA_PRE_5:+"$PKTWS_EXTRA_PRE_5" }${PKTWS_EXTRA_PRE_6:+"$PKTWS_EXTRA_PRE_6" }${PKTWS_EXTRA_PRE_7:+"$PKTWS_EXTRA_PRE_7" }${PKTWS_EXTRA_PRE_8:+"$PKTWS_EXTRA_PRE_8" }${PKTWS_EXTRA_PRE_9:+"$PKTWS_EXTRA_PRE_9" }$@${PKTWS_EXTRA_POST:+ $PKTWS_EXTRA_POST}${PKTWS_EXTRA_POST_1:+ "$PKTWS_EXTRA_POST_1"}${PKTWS_EXTRA_POST_2:+ "$PKTWS_EXTRA_POST_2"}${PKTWS_EXTRA_POST_3:+ "$PKTWS_EXTRA_POST_3"}${PKTWS_EXTRA_POST_4:+ "$PKTWS_EXTRA_POST_4"}${PKTWS_EXTRA_POST_5:+ "$PKTWS_EXTRA_POST_5"}${PKTWS_EXTRA_POST_6:+ "$PKTWS_EXTRA_POST_6"}${PKTWS_EXTRA_POST_7:+ "$PKTWS_EXTRA_POST_7"}${PKTWS_EXTRA_POST_8:+ "$PKTWS_EXTRA_POST_8"}${PKTWS_EXTRA_POST_9:+ "$PKTWS_EXTRA_POST_9"} + ws_curl_test pktws_start $testf $dom ${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+"$PKTWS_EXTRA_PRE_1" }${PKTWS_EXTRA_PRE_2:+"$PKTWS_EXTRA_PRE_2" }${PKTWS_EXTRA_PRE_3:+"$PKTWS_EXTRA_PRE_3" }${PKTWS_EXTRA_PRE_4:+"$PKTWS_EXTRA_PRE_4" }${PKTWS_EXTRA_PRE_5:+"$PKTWS_EXTRA_PRE_5" }${PKTWS_EXTRA_PRE_6:+"$PKTWS_EXTRA_PRE_6" }${PKTWS_EXTRA_PRE_7:+"$PKTWS_EXTRA_PRE_7" }${PKTWS_EXTRA_PRE_8:+"$PKTWS_EXTRA_PRE_8" }${PKTWS_EXTRA_PRE_9:+"$PKTWS_EXTRA_PRE_9" }"$@"${PKTWS_EXTRA_POST:+ $PKTWS_EXTRA_POST}${PKTWS_EXTRA_POST_1:+ "$PKTWS_EXTRA_POST_1"}${PKTWS_EXTRA_POST_2:+ "$PKTWS_EXTRA_POST_2"}${PKTWS_EXTRA_POST_3:+ "$PKTWS_EXTRA_POST_3"}${PKTWS_EXTRA_POST_4:+ "$PKTWS_EXTRA_POST_4"}${PKTWS_EXTRA_POST_5:+ "$PKTWS_EXTRA_POST_5"}${PKTWS_EXTRA_POST_6:+ "$PKTWS_EXTRA_POST_6"}${PKTWS_EXTRA_POST_7:+ "$PKTWS_EXTRA_POST_7"}${PKTWS_EXTRA_POST_8:+ "$PKTWS_EXTRA_POST_8"}${PKTWS_EXTRA_POST_9:+ "$PKTWS_EXTRA_POST_9"} + + code=$? + [ "$code" = 0 ] && { + strategy="$@" + strategy_append_extra_pktws + report_append "$dom" "$testf ipv${IPV}" "$PKTWSD ${WF:+$WF }$strategy" + } + return $code +} +strategy_append_extra_pktws() +{ + strategy="${strategy:+${PKTWS_EXTRA_PRE:+$PKTWS_EXTRA_PRE }${PKTWS_EXTRA_PRE_1:+"$PKTWS_EXTRA_PRE_1" }${PKTWS_EXTRA_PRE_2:+"$PKTWS_EXTRA_PRE_2" }${PKTWS_EXTRA_PRE_3:+"$PKTWS_EXTRA_PRE_3" }${PKTWS_EXTRA_PRE_4:+"$PKTWS_EXTRA_PRE_4" }${PKTWS_EXTRA_PRE_5:+"$PKTWS_EXTRA_PRE_5" }${PKTWS_EXTRA_PRE_6:+"$PKTWS_EXTRA_PRE_6" }${PKTWS_EXTRA_PRE_7:+"$PKTWS_EXTRA_PRE_7" }${PKTWS_EXTRA_PRE_8:+"$PKTWS_EXTRA_PRE_8" }${PKTWS_EXTRA_PRE_9:+"$PKTWS_EXTRA_PRE_9" }$strategy${PKTWS_EXTRA_POST:+ $PKTWS_EXTRA_POST}${PKTWS_EXTRA_1:+ "$PKTWS_EXTRA_POST_1"}${PKTWS_EXTRA_POST_2:+ "$PKTWS_EXTRA_POST_2"}${PKTWS_EXTRA_POST_3:+ "$PKTWS_EXTRA_POST_3"}${PKTWS_EXTRA_POST_4:+ "$PKTWS_EXTRA_POST_4"}${PKTWS_EXTRA_POST_5:+ "$PKTWS_EXTRA_POST_5"}${PKTWS_EXTRA_POST_6:+ "$PKTWS_EXTRA_POST_6"}${PKTWS_EXTRA_POST_7:+ "$PKTWS_EXTRA_POST_7"}${PKTWS_EXTRA_POST_8:+ "$PKTWS_EXTRA_POST_8"}${PKTWS_EXTRA_POST_9:+ "$PKTWS_EXTRA_POST_9"}}" +} + + +xxxws_curl_test_update() +{ + # $1 - xxx_curl_test function + # $2 - test function + # $3 - domain + # $4,$5,$6, ... - nfqws2/dvtws2 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 "$@" +} + +report_append() +{ + # $1 - domain + # $2 - test function + ipver + # $3 - value + local hashstr hash hashvar hashcountvar val ct + + # save resources if only one domain + [ "$DOMAINS_COUNT" -gt 1 ] && { + hashstr="$2 : $3" + hash="$(echo -n "$hashstr" | md5f)" + hashvar=RESHASH_${hash} + hashcountvar=${hashvar}_COUNTER + + NRESHASH=${NRESHASH:-0} + + eval val="\$$hashvar" + if [ -n "$val" ]; then + eval ct="\$$hashcountvar" + ct=$(($ct + 1)) + eval $hashcountvar="\$ct" + else + eval $hashvar=\"$hashstr\" + eval $hashcountvar=1 + eval RES_$NRESHASH="\$hash" + NRESHASH=$(($NRESHASH+1)) + fi + } + + NREPORT=${NREPORT:-0} + eval REPORT_${NREPORT}=\"$2 $1 : $3\" + NREPORT=$(($NREPORT+1)) +} +report_print() +{ + local n=0 s + NREPORT=${NREPORT:-0} + while [ $n -lt $NREPORT ]; do + eval s=\"\${REPORT_$n}\" + echo $s + n=$(($n+1)) + done +} +result_intersection_print() +{ + local n=0 hash hashvar hashcountvar ct val + while : ; do + eval hash=\"\$RES_$n\" + [ -n "$hash" ] || break + hashvar=RESHASH_${hash} + hashcountvar=${hashvar}_COUNTER + eval ct=\"\$$hashcountvar\" + [ "$ct" = "$DOMAINS_COUNT" ] && { + eval val=\"\$$hashvar\" + echo "$val" + } + n=$(($n + 1)) + done +} +report_strategy() +{ + # $1 - test function + # $2 - domain + # $3 - daemon + echo + if [ -n "$strategy" ]; then + # trim spaces at the end + strategy="$(echo "$strategy" | xargs)" + echo "!!!!! $1: working strategy found for ipv${IPV} $2 : $3 $strategy !!!!!" + echo + return 0 + else + echo "$1: $3 strategy for ipv${IPV} $2 not found" + echo + report_append "$2" "$1 ipv${IPV}" "$3 not working" + return 1 + fi +} + +test_runner() +{ + # $1 - function name + # $2+ - params + + local n script FUNC=$1 + + shift + + TESTDIR="$BLOCKCHECK2D/$TEST" + [ -d "$TESTDIR" ] && { + dir_is_not_empty "$TESTDIR" && { + for script in "$TESTDIR/"*.sh; do + [ -f "$script" ] || continue + unset -f $FUNC + . "$script" + echo + existf $FUNC && { + echo "* script : $TEST/$(basename "$script")" + $FUNC "$@" + } + 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 func + if [ "$2" = 0 ]; then + func=pktws_check_http + elif [ "$2" = 1 ]; then + func=pktws_check_https_tls12 + elif [ "$2" = 2 ]; then + func=pktws_check_https_tls13 + else + return 1 + fi + test_runner $func "$1" "$3" + strategy_append_extra_pktws + report_strategy $1 $3 $PKTWSD +} + +pktws_check_domain_http3_bypass() +{ + # $1 - test function + # $2 - domain + + local strategy + test_runner pktws_check_http3 "$@" + strategy_append_extra_pktws + report_strategy $1 $2 $PKTWSD +} + + +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 + + [ "$SIMULATE" = 1 ] && return 0 + + echo + echo \* $1 ipv$IPV $3 + + echo "- checking without DPI bypass" + curl_test $1 $3 && { + report_append "$3" "$1 ipv${IPV}" "working without bypass" + [ "$SCANLEVEL" = force ] || return 1 + } + code=$? + curl_has_reason_to_continue $code || { + report_append "$3" "$1 ipv${IPV}" "test aborted, no reason to continue. curl code $(curl_translate_code $code)" + return 1 + } + return 0 +} +check_domain_http_tcp() +{ + # $1 - test function + # $2 - port + # $3 - encrypted test : 0 = plain, 1 - encrypted with server reply risk, 2 - encrypted without server reply risk + # $4 - domain + + # in case was interrupted before + pktws_ipt_unprepare_tcp $2 + ws_kill + + check_domain_prolog $1 $2 $4 || return + + [ "$SKIP_IPBLOCK" = 1 ] || check_dpi_ip_block $1 $4 + + [ "$SKIP_PKTWS" = 1 ] || { + echo + echo preparing $PKTWSD redirection + pktws_ipt_prepare_tcp $2 "$(mdig_resolve_all $IPV $4)" + + 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 "$(mdig_resolve_all $IPV $3)" + + pktws_check_domain_http3_bypass $1 $3 + + echo clearing $PKTWSD redirection + pktws_ipt_unprepare_udp $2 + } +} + + +check_domain_http() +{ + # $1 - domain + check_domain_http_tcp curl_test_http $HTTP_PORT 0 $1 +} +check_domain_https_tls12() +{ + # $1 - domain + check_domain_http_tcp curl_test_https_tls12 $HTTPS_PORT 1 $1 +} +check_domain_https_tls13() +{ + # $1 - domain + check_domain_http_tcp curl_test_https_tls13 $HTTPS_PORT 2 $1 +} +check_domain_http3() +{ + # $1 - domain + check_domain_http_udp curl_test_http3 $QUIC_PORT $1 +} + +configure_ip_version() +{ + if [ "$IPV" = 6 ]; then + LOCALHOST=::1 + LOCALHOST_IPT=[${LOCALHOST}] + IPVV=6 + else + IPTABLES=iptables + LOCALHOST=127.0.0.1 + LOCALHOST_IPT=$LOCALHOST + IPVV= + fi + IPTABLES=ip${IPVV}tables +} +configure_curl_opt() +{ + # wolfssl : --tlsv1.x mandates exact ssl version, tls-max not supported + # openssl : --tlsv1.x means "version equal or greater", tls-max supported + TLSMAX12= + TLSMAX13= + curl_supports_tlsmax && { + TLSMAX12="--tls-max 1.2" + TLSMAX13="--tls-max 1.3" + } + TLS13= + curl_supports_tls13 && TLS13=1 + HTTP3= + curl_supports_http3 && HTTP3=1 + + HTTPS_HEAD=-I + [ "$CURL_HTTPS_GET" = 1 ] && HTTPS_HEAD= +} + +linux_ipv6_defrag_can_be_disabled() +{ + linux_min_version 4 16 +} + +configure_defrag() +{ + IP6_DEFRAG_DISABLE= + + [ "$IPVS" = 4 ] && return + + [ "$UNAME" = "Linux" ] && { + linux_ipv6_defrag_can_be_disabled || { + echo "WARNING ! ipv6 defrag can only be effectively disabled in linux kernel 4.16+" + echo "WARNING ! ipv6 ipfrag tests are disabled" + echo + return + } + } + + case "$FWTYPE" in + iptables) + if ipt6_has_raw ; then + if ipt6_has_frag; then + IP6_DEFRAG_DISABLE=1 + else + echo "WARNING ! ip6tables does not have '-m frag' module, ipv6 ipfrag tests are disabled" + echo + fi + else + echo "WARNING ! ip6tables raw table is not available, ipv6 ipfrag tests are disabled" + echo + fi + [ -n "$IP6_DEFRAG_DISABLE" ] && { + local ipexe="$(readlink -f $(whichq ip6tables))" + if contains "$ipexe" nft; then + echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6tables-nft is used. current ip6tables point to : $ipexe" + else + echo "WARNING ! ipv6 ipfrag tests may have no effect if ip6table_raw kernel module is not loaded with parameter : raw_before_defrag=1" + fi + echo + } + ;; + *) + IP6_DEFRAG_DISABLE=1 + ;; + esac +} + +ask_params() +{ + local d dirs more_dirs= + + 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 + } + + [ -n "$TEST" ] || { + dir_is_not_empty "$BLOCKCHECK2D" || { + echo "directory '$BLOCKCHECK2D' is absent or empty" + exitp 1 + } + TEST="$TEST_DEFAULT" + [ "$BATCH" = 1 ] || { + for d in "$BLOCKCHECK2D"/* ; do + more_dirs=${dirs:+1} + [ -d "$d" ] && dirs="${dirs:+$dirs }$(basename "$d")" + done + [ -n "$dirs" ] || { + echo "no subdirs found in '$BLOCKCHECK2D'" + exitp 1 + } + if [ -z "$more_dirs" ]; then + TEST="$dirs" + else + echo "select test :" + ask_list TEST "$dirs" "$TEST" + fi + } + } + [ -d "$BLOCKCHECK2D/$TEST" ] || { + echo "directory '$BLOCKCHECK2D/$TEST' does not exist" + exitp 1 + } + + local dom + [ -n "$DOMAINS" ] || { + DOMAINS="$DOMAINS_DEFAULT" + [ "$BATCH" = 1 ] || { + echo "specify domain(s) to test. multiple domains are space separated. URIs are supported (rutracker.org/forum/index.php)" + printf "domain(s) (default: $DOMAINS) : " + read dom + [ -n "$dom" ] && DOMAINS="$dom" + } + } + DOMAINS_COUNT="$(echo "$DOMAINS" | wc -w | trim)" + + local IPVS_def=4 + [ -n "$IPVS" ] || { + # yandex public dns + pingtest 6 2a02:6b8::feed:0ff && IPVS_def=46 + [ "$BATCH" = 1 ] || { + printf "ip protocol version(s) - 4, 6 or 46 for both (default: $IPVS_def) : " + read IPVS + } + [ -n "$IPVS" ] || IPVS=$IPVS_def + [ "$IPVS" = 4 -o "$IPVS" = 6 -o "$IPVS" = 46 ] || { + echo 'invalid ip version(s). should be 4, 6 or 46.' + exitp 1 + } + } + [ "$IPVS" = 46 ] && IPVS="4 6" + + configure_curl_opt + + [ -n "$ENABLE_HTTP" ] || { + ENABLE_HTTP=1 + [ "$BATCH" = 1 ] || { + echo + ask_yes_no_var ENABLE_HTTP "check http" + } + } + + [ -n "$ENABLE_HTTPS_TLS12" ] || { + ENABLE_HTTPS_TLS12=1 + [ "$BATCH" = 1 ] || { + echo + ask_yes_no_var ENABLE_HTTPS_TLS12 "check https tls 1.2" + } + } + + [ -n "$ENABLE_HTTPS_TLS13" ] || { + ENABLE_HTTPS_TLS13=0 + if [ -n "$TLS13" ]; then + [ "$BATCH" = 1 ] || { + echo + echo "TLS 1.3 uses encrypted ServerHello. DPI cannot check domain name in server response." + echo "This can allow more bypass strategies to work." + echo "What works for TLS 1.2 will also work for TLS 1.3 but not vice versa." + echo "Most sites nowadays support TLS 1.3 but not all. If you can't find a strategy for TLS 1.2 use this test." + echo "TLS 1.3 only strategy is better than nothing." + ask_yes_no_var ENABLE_HTTPS_TLS13 "check https tls 1.3" + } + else + echo + echo "installed curl version does not support TLS 1.3 . tests disabled." + fi + } + + [ -n "$ENABLE_HTTP3" ] || { + ENABLE_HTTP3=0 + if [ -n "$HTTP3" ]; then + ENABLE_HTTP3=1 + [ "$BATCH" = 1 ] || { + echo + echo "make sure target domain(s) support QUIC or result will be negative in any case" + ask_yes_no_var ENABLE_HTTP3 "check http3 QUIC" + } + else + echo + echo "installed curl version does not support http3 QUIC. tests disabled." + fi + } + + [ -n "$REPEATS" ] || { + [ "$BATCH" = 1 ] || { + echo + echo "sometimes ISPs use multiple DPIs or load balancing. bypass strategies may work unstable." + printf "how many times to repeat each test (default: 1) : " + read REPEATS + } + REPEATS=$((0+${REPEATS:-1})) + [ "$REPEATS" = 0 ] && { + echo invalid repeat count + exitp 1 + } + } + [ -z "$PARALLEL" -a $REPEATS -gt 1 ] && { + PARALLEL=0 + [ "$BATCH" = 1 ] || { + echo + echo "parallel scan can greatly increase speed but may also trigger DDoS protection and cause false result" + ask_yes_no_var PARALLEL "enable parallel scan" + } + } + PARALLEL=${PARALLEL:-0} + + [ -n "$SCANLEVEL" ] || { + SCANLEVEL=standard + [ "$BATCH" = 1 ] || { + echo + echo quick - in multi-attempt mode skip further attempts after first failure + echo standard - do investigation what works on your DPI + echo force - scan maximum despite of result + ask_list SCANLEVEL "quick standard force" "$SCANLEVEL" + } + } + + 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 + + local PING=ping ret + if [ "$1" = 6 ]; then + if exists ping6; then + PING=ping6 + else + PING="ping -6" + fi + else + if [ "$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 + OpenBSD) + $PING -c 1 -w 1 $2 >/dev/null + ;; + CYGWIN) + if starts_with "$(which ping)" /cygdrive; then + # cygwin does not have own ping by default. use windows PING. + $PING -n 1 -w 1000 $2 >/dev/null + else + ping_with_fix $2 '-c 1 -w 1' + fi + ;; + *) + ping_with_fix $2 '-c 1 -W 1' + ;; + esac +} +dnstest() +{ + # $1 - dns server. empty for system resolver + "$LOOKUP" iana.org $1 >/dev/null 2>/dev/null +} +find_working_public_dns() +{ + local dns + for dns in $DNSCHECK_DNS; do + pingtest 4 $dns && dnstest $dns && { + PUBDNS=$dns + return 0 + } + done + return 1 +} +lookup4() +{ + # $1 - domain + # $2 - DNS + case "$LOOKUP" in + nslookup) + if is_linked_to_busybox nslookup; then + nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^.*:[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' + else + nslookup $1 $2 2>/dev/null | sed -e '1,3d' -nre 's/^[^0-9]*(([0-9]{1,3}\.){3}[0-9]{1,3}).*$/\1/p' + fi + ;; + host) + host -t A $1 $2 | grep "has address" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' + ;; + esac +} +check_dns_spoof() +{ + # $1 - domain + # $2 - public DNS + + # windows version of mdig outputs 0D0A line ending. remove 0D. + echo $1 | "$MDIG" --family=4 | tr -d '\r' >"$DNSCHECK_DIG1" + lookup4 $1 $2 >"$DNSCHECK_DIG2" + # check whether system resolver returns anything other than public DNS + grep -qvFf "$DNSCHECK_DIG2" "$DNSCHECK_DIG1" +} +check_dns_cleanup() +{ + rm -f "$DNSCHECK_DIG1" "$DNSCHECK_DIG2" "$DNSCHECK_DIGS" 2>/dev/null +} +check_dns_() +{ + local C1 C2 dom + + DNS_IS_SPOOFED=0 + + [ "$SKIP_DNSCHECK" = 1 ] && return 0 + + echo \* checking DNS + + [ -f "$DNSCHECK_DIGS" ] && rm -f "$DNSCHECK_DIGS" + + dnstest || { + echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. + return 1 + } + echo system DNS is working + + if find_working_public_dns ; then + echo comparing system resolver to public DNS : $PUBDNS + for dom in $DNSCHECK_DOM; do + if check_dns_spoof $dom $PUBDNS ; then + echo $dom : MISMATCH + echo -- system resolver : + cat "$DNSCHECK_DIG1" + echo -- $PUBDNS : + cat "$DNSCHECK_DIG2" + check_dns_cleanup + echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! + echo -- DNS CHANGE OR DNSCRYPT MAY BE REQUIRED + DNS_IS_SPOOFED=1 + return 1 + else + echo $dom : OK + cat "$DNSCHECK_DIG1" >>"$DNSCHECK_DIGS" + fi + done + else + echo no working public DNS was found. looks like public DNS blocked. + for dom in $DNSCHECK_DOM; do echo $dom; done | "$MDIG" --threads=10 --family=4 >"$DNSCHECK_DIGS" + fi + + echo "checking resolved IP uniqueness for : $DNSCHECK_DOM" + echo "censor's DNS can return equal result for multiple blocked domains." + C1=$(wc -l <"$DNSCHECK_DIGS") + C2=$(sort -u "$DNSCHECK_DIGS" | wc -l) + [ "$C1" -eq 0 ] && + { + echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access. + check_dns_cleanup + return 1 + } + [ "$C1" = "$C2" ] || + { + echo system dns resolver has returned equal IPs for some domains checked above \($C1 total, $C2 unique\) + echo non-unique IPs : + sort "$DNSCHECK_DIGS" | uniq -d + echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!! + echo -- DNSCRYPT MAY BE REQUIRED + check_dns_cleanup + DNS_IS_SPOOFED=1 + return 1 + } + echo all resolved IPs are unique + echo -- DNS looks good + echo -- NOTE this check is Russia targeted. In your country other domains may be blocked. + check_dns_cleanup + return 0 +} + +check_dns() +{ + local r + check_dns_ + r=$? + [ "$DNS_IS_SPOOFED" = 1 ] && SECURE_DNS=${SECURE_DNS:-1} + [ "$SECURE_DNS" = 1 ] && { + doh_find_working || { + echo could not find working DoH server. exiting. + exitp 7 + } + } + return $r +} + +unprepare_all() +{ + # make sure we are not in a middle state that impacts connectivity + ws_kill + wait + [ -n "$IPV" ] && { + pktws_ipt_unprepare_tcp $HTTP_PORT + pktws_ipt_unprepare_tcp $HTTPS_PORT + pktws_ipt_unprepare_udp $QUIC_PORT + } + cleanup + rm -f "${HDRTEMP}"* "${PARALLEL_OUT}"* +} +sigint() +{ + echo + echo terminating... + unprepare_all + exitp 1 +} +sigint_cleanup() +{ + cleanup + exit 1 +} +sigsilent() +{ + # must not write anything here to stdout + unprepare_all + exit 1 +} + +fsleep_setup +fix_sbin_path +check_system +check_already +[ "$UNAME" != CYGWIN -a "$SKIP_PKTWS" != 1 ] && require_root +check_prerequisites +trap sigint_cleanup INT +check_dns +check_virt +ask_params +trap - INT + +PID= +NREPORT= +unset WF +trap sigint INT +trap sigsilent PIPE +trap sigsilent HUP +for dom in $DOMAINS; do + for IPV in $IPVS; do + configure_ip_version + [ "$ENABLE_HTTP" = 1 ] && { + [ "$SKIP_IPBLOCK" = 1 ] || check_domain_port_block $dom $HTTP_PORT + check_domain_http $dom + } + [ "$ENABLE_HTTPS_TLS12" = 1 -o "$ENABLE_HTTPS_TLS13" = 1 ] && [ "$SKIP_IPBLOCK" != 1 ] && check_domain_port_block $dom $HTTPS_PORT + [ "$ENABLE_HTTPS_TLS12" = 1 ] && check_domain_https_tls12 $dom + [ "$ENABLE_HTTPS_TLS13" = 1 ] && check_domain_https_tls13 $dom + [ "$ENABLE_HTTP3" = 1 ] && check_domain_http3 $dom + done +done +trap - HUP +trap - PIPE +trap - INT + +cleanup + +echo +echo \* SUMMARY +report_print +[ "$DOMAINS_COUNT" -gt 1 ] && { + echo + echo \* COMMON + result_intersection_print + echo + [ "$SCANLEVEL" = force ] || { + echo "blockcheck optimizes test sequence. To save time some strategies can be skipped if their test is considered useless." + echo "That's why COMMON intersection can miss strategies that would work for all domains." + echo "Use \"force\" scan level to test all strategies and generate trustable intersection." + echo "Current scan level was \"$SCANLEVEL\"". + } +} +echo +echo "Please note this SUMMARY does not guarantee a magic pill for you to copy/paste and be happy." +echo "Understanding how strategies work is very desirable." +echo "This knowledge allows to understand better which strategies to prefer and which to avoid if possible, how to combine strategies." +echo "Blockcheck does it's best to prioritize good strategies but it's not bullet-proof." +echo "It was designed not as magic pill maker but as a DPI bypass test tool." + +exitp 0 diff --git a/common/base.sh b/common/base.sh index 5ed6ec3..15b2354 100644 --- a/common/base.sh +++ b/common/base.sh @@ -105,6 +105,10 @@ split_by_separator() [ -n "$3" ] && eval $3="\$before" [ -n "$4" ] && eval $4="\$after" } +tolower() +{ + echo "$@" | tr 'A-Z' 'a-z' +} dir_is_not_empty() { diff --git a/common/dialog.sh b/common/dialog.sh new file mode 100644 index 0000000..addc016 --- /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..bc86a96 --- /dev/null +++ b/common/elevate.sh @@ -0,0 +1,28 @@ +require_root() +{ + local exe preserve_env + echo \* checking privileges + [ $(id -u) -ne "0" ] && { + echo root is required + exe="$EXEDIR/$(basename "$0")" + exists sudo && { + echo elevating with sudo + exec sudo -E sh "$exe" + } + exists su && { + echo elevating with su + case "$UNAME" in + Linux) + preserve_env="--preserve-environment" + ;; + FreeBSD|OpenBSD|Darwin) + preserve_env="-m" + ;; + esac + exec su $preserve_env root -c "sh \"$exe\"" + } + echo su or sudo not found + exitp 2 + } + HAVE_ROOT=1 +} 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/virt.sh b/common/virt.sh new file mode 100644 index 0000000..f929da9 --- /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 and placeholders to engage standard hostlists and autohostlist in ipset dir +# hostlist markers are replaced to empty string if MODE_FILTER does not satisfy +# appends ipset/zapret-hosts-auto.txt as normal list +NFQWS2_OPT=" +--filter-tcp=80 --payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=multisplit:pos=method+2 --new +--filter-tcp=443 --payload=tls_client_hello --lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld --new +--filter-udp=443 --payload=quic_initial --lua-desync=fake:blob=fake_default_quic:repeats=6 --new +" + +# none,ipset,hostlist,autohostlist +MODE_FILTER=none + +# donttouch,none,software,hardware +FLOWOFFLOAD=donttouch + +# openwrt: specify networks to be treated as WAN. default wans are interfaces with default route +#OPENWRT_WAN4="wan vpn" +#OPENWRT_WAN6="wan6 vpn6" + +# for routers based on desktop linux and macos. has no effect in openwrt. +# optionally CHOOSE WAN/WAN6 NETWORK INTERFACES +# or leave them commented if its not router +# it's possible to specify multiple interfaces like this : IFACE_WAN="eth0 eth1 eth2" +# if IFACE_WAN6 is not defined it take the value of IFACE_WAN +#IFACE_WAN=eth1 +#IFACE_WAN6="ipsec0 wireguard0 he_net" + +# should start/stop command of init scripts apply firewall rules ? +# not applicable to openwrt with firewall3+iptables +INIT_APPLY_FW=1 +# firewall apply hooks +#INIT_FW_PRE_UP_HOOK="/etc/firewall.zapret.hook.pre_up" +#INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up" +#INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down" +#INIT_FW_POST_DOWN_HOOK="/etc/firewall.zapret.hook.post_down" + +# do not work with ipv4 +#DISABLE_IPV4=1 +# do not work with ipv6 +DISABLE_IPV6=1 + +# drop icmp time exceeded messages for nfqws tampered connections +# in POSTNAT mode this can interfere with default mtr/traceroute in tcp or udp mode. use source port not redirected to nfqws +# set to 0 if you are not expecting connection breakage due to icmp in response to TCP SYN or UDP +FILTER_TTL_EXPIRED_ICMP=1 + +# select which init script will be used to get ip or host list +# possible values : get_user.sh get_antizapret.sh get_combined.sh get_reestr.sh get_hostlist.sh +# comment if not required +#GETLIST= diff --git a/docs/changes.txt b/docs/changes.txt index 13681ce..64dbb32 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -24,3 +24,13 @@ v0.1.5 * nfqws2: # and % arg substitution * zapret-antidpi: luaexec * zapret-pcap: simple packet capture to .cap file + +v0.2 + +* blockcheck2 +* nfqws2: several crash fixes +* nfqws2: bu8,bu16,bu24,bu32,swap16,swap32 functions now work with negative int +* nfqws2: getpid,gettid,uname,get_clock luacalls +* zapret-lib: bugfixes +* zapret-lib: remove ip6_hopbyhop_x2 fooling, separately add second hopbyhop header using ip6_hopbyhop2 +* zapret-pcap