Files
probe/probe.sh

830 lines
31 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/data/data/com.termux/files/usr/bin/bash
# ══════════════════════════════════════════════════════
# WHITELIST PROBE v1.1
# Определяет доступные методы обхода whitelist
# Платформа: Termux (Android) / Linux
# Зависимости: curl, nc (ncat), openssl, dig/nslookup
# ══════════════════════════════════════════════════════
set -o pipefail
# ── Конфигурация ──────────────────────────────────────
VPS_IP="194.87.245.138" # Ирландия (exit node)
MOSCOW_IP="94.141.253.12" # Москва (relay)
DNSTT_DOMAIN="t.chatsquad.ru"
DNSTT_NS="ns-t.chatsquad.ru"
TIMEOUT=5 # секунд на каждый тест
RESULT_DIR="./results"
TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S')
JSON_FILE="${RESULT_DIR}/probe_${TIMESTAMP}.json"
# ── Цвета ─────────────────────────────────────────────
if [ -t 1 ]; then
G='\033[0;32m' # green
R='\033[0;31m' # red
Y='\033[0;33m' # yellow
C='\033[0;36m' # cyan
B='\033[1m' # bold
W='\033[0;37m' # white/dim
N='\033[0m' # reset
else
G='' R='' Y='' C='' B='' W='' N=''
fi
# ── Счётчики ──────────────────────────────────────────
declare -A RESULTS
TOTAL=0
PASS=0
FAIL=0
SKIP=0
# Список SNI, по которым обнаружена фильтрация
declare -a FILTERED_SNIS=()
# ── Утилиты ───────────────────────────────────────────
ok() { echo -e " ${G}${N} $1"; RESULTS["$2"]="ok"; ((PASS++)); ((TOTAL++)); }
fail() { echo -e " ${R}${N} $1"; RESULTS["$2"]="fail"; ((FAIL++)); ((TOTAL++)); }
warn() { echo -e " ${Y}${N} $1"; RESULTS["$2"]="warn"; ((TOTAL++)); }
skip() { echo -e " ${Y}${N} $1"; RESULTS["$2"]="skip"; ((SKIP++)); ((TOTAL++)); }
info() { echo -e " ${C}${N} $1"; }
hdr() { echo -e "\n${B}[$1]${N}"; }
# Ping с таймаутом (2 попытки, успех если хотя бы одна прошла)
do_ping() {
local host=$1
local result
result=$(ping -c 2 -W $TIMEOUT "$host" 2>/dev/null | grep -oP 'time=\K[0-9.]+' | tail -1)
if [ -n "$result" ]; then
echo "${result}ms"
return 0
fi
return 1
}
# TCP connect
do_tcp() {
local host=$1 port=$2
timeout $TIMEOUT bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null
return $?
}
# UDP — отправка WireGuard handshake initiation (тип 1)
# WG отвечает cookie reply на невалидный handshake, что позволяет
# подтвердить прохождение UDP-пакетов
do_udp_wg() {
local host=$1 port=$2
# WG handshake initiation: type=1(LE32), sender=0x12345678, rest zeros (148 байт)
# Генерируем бинарный пакет через printf напрямую в pipe (без переменных — bash не хранит \x00)
{
printf '\x01\x00\x00\x00\x78\x56\x34\x12'
dd if=/dev/zero bs=1 count=140 2>/dev/null
} | timeout $TIMEOUT nc -u -w 2 "$host" "$port" 2>/dev/null | head -c 1 | grep -qP '.' 2>/dev/null
return $?
}
# HTTPS test — один запрос, возвращает "код HTTP/версия"
do_https() {
local host=$1
local raw
raw=$(curl -s -o /dev/null -w '%{http_code} %{http_version}' \
--connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) \
"https://$host" 2>/dev/null)
local code="${raw%% *}"
local ver="${raw##* }"
if [ "$code" -ge 100 ] 2>/dev/null && [ "$code" -lt 600 ] 2>/dev/null; then
echo "$code HTTP/$ver"
return 0
fi
return 1
}
# DNS resolve — предпочитает dig, фолбэк на nslookup
do_resolve() {
local domain=$1 server=$2
local result
if command -v dig >/dev/null 2>&1; then
if [ -n "$server" ]; then
result=$(dig +short +time=$TIMEOUT +tries=1 "$domain" A "@$server" 2>/dev/null | grep -oP '^[0-9.]+' | head -1)
else
result=$(dig +short +time=$TIMEOUT +tries=1 "$domain" A 2>/dev/null | grep -oP '^[0-9.]+' | head -1)
fi
else
if [ -n "$server" ]; then
result=$(nslookup "$domain" "$server" 2>/dev/null | awk '/^Address:/{a=$2} /Name:/{found=1} found && /Address:/{print $2; exit}')
else
result=$(nslookup "$domain" 2>/dev/null | awk '/^Address:/{a=$2} /Name:/{found=1} found && /Address:/{print $2; exit}')
fi
fi
if [ -n "$result" ]; then
echo "$result"
return 0
fi
return 1
}
# TLS handshake с кастомным SNI
do_tls_sni() {
local ip=$1 port=$2 sni=$3
echo | timeout $TIMEOUT openssl s_client -connect "$ip:$port" -servername "$sni" 2>/dev/null | grep -q "BEGIN CERTIFICATE"
return $?
}
# ══════════════════════════════════════════════════════
# УСТАНОВКА ЗАВИСИМОСТЕЙ
# ══════════════════════════════════════════════════════
install_deps() {
local missing=()
command -v curl >/dev/null || missing+=(curl)
command -v nc >/dev/null || missing+=(nmap)
command -v openssl >/dev/null || missing+=(openssl-tool)
command -v ping >/dev/null || missing+=(inetutils)
# dig предпочтителен, но nslookup тоже подойдёт
if ! command -v dig >/dev/null 2>&1; then
command -v nslookup >/dev/null || missing+=(dnsutils)
fi
if [ ${#missing[@]} -gt 0 ]; then
echo -e "${Y}Установка: ${missing[*]}${N}"
if command -v pkg >/dev/null; then
pkg install -y "${missing[@]}" 2>/dev/null
elif command -v apt-get >/dev/null; then
sudo apt-get install -y "${missing[@]}" 2>/dev/null
fi
fi
}
# ══════════════════════════════════════════════════════
# ТЕСТЫ
# ══════════════════════════════════════════════════════
test_provider() {
hdr "ПРОВАЙДЕР"
local ext_ip isp asn
ext_ip=$(curl -s --connect-timeout $TIMEOUT ifconfig.me 2>/dev/null)
if [ -z "$ext_ip" ]; then
ext_ip=$(curl -s --connect-timeout $TIMEOUT api.ipify.org 2>/dev/null)
fi
if [ -z "$ext_ip" ]; then
ext_ip=$(curl -s --connect-timeout $TIMEOUT ident.me 2>/dev/null)
fi
if [ -n "$ext_ip" ]; then
info "Внешний IP: ${B}$ext_ip${N}"
RESULTS["ext_ip"]="$ext_ip"
local ipinfo
ipinfo=$(curl -s --connect-timeout $TIMEOUT "http://ip-api.com/json/$ext_ip?fields=isp,as,country" 2>/dev/null)
if [ -n "$ipinfo" ]; then
isp=$(echo "$ipinfo" | grep -oP '"isp"\s*:\s*"\K[^"]+')
asn=$(echo "$ipinfo" | grep -oP '"as"\s*:\s*"\K[^"]+')
local country
country=$(echo "$ipinfo" | grep -oP '"country"\s*:\s*"\K[^"]+')
info "ISP: ${B}$isp${N}"
info "ASN: ${B}$asn${N}"
info "Страна: ${B}$country${N}"
RESULTS["isp"]="$isp"
RESULTS["asn"]="$asn"
fi
else
fail "Не удалось определить внешний IP" "ext_ip"
fi
}
test_icmp() {
hdr "ICMP (Ping)"
info "2 попытки на хост, таймаут ${TIMEOUT}с"
local -A targets=(
["VPS Ирландия"]="$VPS_IP"
["Москва relay"]="$MOSCOW_IP"
["yandex.ru"]="yandex.ru"
["ya.ru"]="ya.ru"
["vk.com"]="vk.com"
["mail.ru"]="mail.ru"
["ok.ru"]="ok.ru"
["sber.ru"]="sber.ru"
["gosuslugi.ru"]="gosuslugi.ru"
["mos.ru"]="mos.ru"
["Google 8.8.8.8"]="8.8.8.8"
["Cloudflare 1.1.1.1"]="1.1.1.1"
["Yandex DNS 77.88.8.8"]="77.88.8.8"
["Telegram"]="149.154.167.99"
)
local order=("yandex.ru" "ya.ru" "vk.com" "mail.ru" "ok.ru" "sber.ru" "gosuslugi.ru" "mos.ru" "Yandex DNS 77.88.8.8" "VPS Ирландия" "Москва relay" "Google 8.8.8.8" "Cloudflare 1.1.1.1" "Telegram")
for name in "${order[@]}"; do
local target="${targets[$name]}"
local t
t=$(do_ping "$target")
if [ $? -eq 0 ]; then
ok "$(printf '%-22s %s' "$name" "$t")" "icmp_$name"
else
fail "$(printf '%-22s' "$name")" "icmp_$name"
fi
done
}
test_dns() {
hdr "DNS RESOLUTION"
local r
r=$(do_resolve "yandex.ru")
if [ $? -eq 0 ]; then
ok "System DNS yandex.ru → $r" "dns_system"
else
fail "System DNS yandex.ru" "dns_system"
fi
local -A dns_servers=(
["Yandex 77.88.8.8"]="77.88.8.8"
["Yandex 77.88.8.1"]="77.88.8.1"
["Google 8.8.8.8"]="8.8.8.8"
["Google 8.8.4.4"]="8.8.4.4"
["Cloudflare 1.1.1.1"]="1.1.1.1"
["Quad9 9.9.9.9"]="9.9.9.9"
)
local dns_order=("Yandex 77.88.8.8" "Yandex 77.88.8.1" "Google 8.8.8.8" "Google 8.8.4.4" "Cloudflare 1.1.1.1" "Quad9 9.9.9.9")
for name in "${dns_order[@]}"; do
local server="${dns_servers[$name]}"
r=$(do_resolve "yandex.ru" "$server")
if [ $? -eq 0 ]; then
ok "$(printf '%-22s yandex.ru → %s' "$name" "$r")" "dns_$name"
else
fail "$(printf '%-22s' "$name")" "dns_$name"
fi
done
echo ""
r=$(do_resolve "$DNSTT_DOMAIN")
if [ $? -eq 0 ]; then
ok "DNSTT domain $DNSTT_DOMAIN$r" "dns_dnstt"
else
local ns_result
if command -v dig >/dev/null 2>&1; then
ns_result=$(dig +short +time=$TIMEOUT NS "$DNSTT_DOMAIN" 2>/dev/null)
else
ns_result=$(nslookup -type=NS "$DNSTT_DOMAIN" 2>/dev/null | grep -i "nameserver" | head -1)
fi
if [ -n "$ns_result" ]; then
ok "DNSTT domain (NS) $DNSTT_DOMAIN → delegated" "dns_dnstt"
else
fail "DNSTT domain $DNSTT_DOMAIN" "dns_dnstt"
fi
fi
r=$(do_resolve "$DNSTT_NS")
if [ $? -eq 0 ]; then
ok "DNSTT NS $DNSTT_NS$r" "dns_dnstt_ns"
else
fail "DNSTT NS $DNSTT_NS" "dns_dnstt_ns"
fi
}
test_dns_tunnel() {
hdr "DNS TUNNEL VIABILITY"
local r
# DoH Яндекс
r=$(curl -s --connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) \
-H 'Accept: application/dns-json' \
"https://dns.yandex.net/dns-query?name=example.com&type=A" 2>/dev/null)
if echo "$r" | grep -q "Answer\|Status"; then
ok "DoH Яндекс dns.yandex.net" "doh_yandex"
else
fail "DoH Яндекс dns.yandex.net" "doh_yandex"
fi
# DoH Google
r=$(curl -s --connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) \
"https://dns.google/resolve?name=example.com&type=A" 2>/dev/null)
if echo "$r" | grep -q "Answer\|Status"; then
ok "DoH Google dns.google" "doh_google"
else
fail "DoH Google dns.google" "doh_google"
fi
# DoH Cloudflare
r=$(curl -s --connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) \
-H 'Accept: application/dns-json' \
"https://cloudflare-dns.com/dns-query?name=example.com&type=A" 2>/dev/null)
if echo "$r" | grep -q "Answer\|Status"; then
ok "DoH Cloudflare cloudflare-dns.com" "doh_cf"
else
fail "DoH Cloudflare cloudflare-dns.com" "doh_cf"
fi
# DoT (DNS over TLS) — порт 853
echo ""
for name_server in "Yandex:77.88.8.8" "Google:8.8.8.8" "CF:1.1.1.1"; do
local label="${name_server%%:*}"
local ip="${name_server##*:}"
if do_tcp "$ip" 853; then
ok "DoT $label $ip:853" "dot_$label"
else
fail "DoT $label $ip:853" "dot_$label"
fi
done
}
test_tcp_our_servers() {
hdr "TCP — НАШИ СЕРВЕРЫ"
local -A ports=(
["VPS:443 (VLESS)"]="$VPS_IP:443"
["VPS:553 (OVPNCloak)"]="$VPS_IP:553"
["VPS:38246 (SS)"]="$VPS_IP:38246"
["VPS:43781 (SOCKS5)"]="$VPS_IP:43781"
["VPS:8443 (Chisel)"]="$VPS_IP:8443"
["VPS:22 (SSH)"]="$VPS_IP:22"
["VPS:53 (DNS)"]="$VPS_IP:53"
["Moscow:2053 (relay)"]="$MOSCOW_IP:2053"
["Moscow:443 (HTTPS)"]="$MOSCOW_IP:443"
["Moscow:22 (SSH)"]="$MOSCOW_IP:22"
)
local order=(
"VPS:443 (VLESS)"
"VPS:553 (OVPNCloak)"
"VPS:38246 (SS)"
"VPS:43781 (SOCKS5)"
"VPS:8443 (Chisel)"
"VPS:22 (SSH)"
"VPS:53 (DNS)"
"Moscow:2053 (relay)"
"Moscow:443 (HTTPS)"
"Moscow:22 (SSH)"
)
for name in "${order[@]}"; do
local hp="${ports[$name]}"
local host="${hp%%:*}"
local port="${hp##*:}"
if do_tcp "$host" "$port"; then
ok "$(printf '%-25s' "$name")" "tcp_$name"
else
fail "$(printf '%-25s' "$name")" "tcp_$name"
fi
done
}
test_udp() {
hdr "UDP"
# DNS (порт 53) — реальный DNS-запрос
info "DNS-запрос к VPS:53"
local dns_r
dns_r=$(do_resolve "yandex.ru" "$VPS_IP")
if [ $? -eq 0 ]; then
ok "$(printf '%-25s (DNS ответил: %s)' "VPS:53 (DNSTT)" "$dns_r")" "udp_VPS:53 (DNSTT)"
else
fail "$(printf '%-25s' "VPS:53 (DNSTT)")" "udp_VPS:53 (DNSTT)"
fi
# QUIC (порт 443) — через curl --http3 если поддерживается
info "QUIC к VPS:443"
local quic_ver
quic_ver=$(curl -s -o /dev/null -w '%{http_version}' \
--connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) \
--http3-only "https://$VPS_IP" 2>/dev/null)
if [ "$quic_ver" = "3" ]; then
ok "$(printf '%-25s HTTP/3 работает' "VPS:443 (QUIC)")" "udp_VPS:443 (QUIC)"
else
# Фолбэк: отправляем UDP-пакет, проверяем что nc не получил ICMP unreachable
echo -n "test" | timeout 2 nc -u -w 1 "$VPS_IP" 443 >/dev/null 2>&1
skip "$(printf '%-25s (curl без HTTP/3, UDP отправлен)' "VPS:443 (QUIC)")" "udp_VPS:443 (QUIC)"
fi
# WireGuard / AmneziaWG — отправляем WG handshake init
info "WireGuard handshake init"
local -A wg_ports=(
["VPS:43742 (WG)"]="43742"
["VPS:47426 (AWG)"]="47426"
["VPS:48253 (AWG2)"]="48253"
)
local wg_order=("VPS:43742 (WG)" "VPS:47426 (AWG)" "VPS:48253 (AWG2)")
for name in "${wg_order[@]}"; do
local port="${wg_ports[$name]}"
if do_udp_wg "$VPS_IP" "$port"; then
ok "$(printf '%-25s (WG ответил)' "$name")" "udp_$name"
else
# nc не получил ICMP port unreachable — пакет, вероятно, дошёл
echo -n "test" | timeout 2 nc -u -w 1 "$VPS_IP" "$port" >/dev/null 2>&1
local nc_rc=$?
if [ $nc_rc -eq 0 ]; then
skip "$(printf '%-25s (отправлено, нет ответа)' "$name")" "udp_$name"
else
fail "$(printf '%-25s (UDP заблокирован)' "$name")" "udp_$name"
fi
fi
done
}
test_https() {
hdr "HTTPS / TLS"
local -A sites=(
# Российские
["yandex.ru"]="yandex.ru"
["ya.ru"]="ya.ru"
["vk.com"]="vk.com"
["mail.ru"]="mail.ru"
["ok.ru"]="ok.ru"
["sber.ru"]="sber.ru"
["gosuslugi.ru"]="gosuslugi.ru"
["mos.ru"]="mos.ru"
["rutube.ru"]="rutube.ru"
# Зарубежные
["google.com"]="google.com"
["cloudflare.com"]="cloudflare.com"
["microsoft.com"]="microsoft.com"
["apple.com"]="apple.com"
["github.com"]="github.com"
["telegram.org"]="telegram.org"
# CDN
["cdn.jsdelivr.net"]="cdn.jsdelivr.net"
["cdnjs.cloudflare.com"]="cdnjs.cloudflare.com"
["unpkg.com"]="unpkg.com"
# Облака
["cloud.yandex.ru"]="cloud.yandex.ru"
["functions.yandexcloud.net"]="functions.yandexcloud.net"
["cloud.vk.com"]="cloud.vk.com"
# DoH
["dns.yandex.net"]="dns.yandex.net"
)
local order=(
"yandex.ru" "ya.ru" "vk.com" "mail.ru" "ok.ru" "sber.ru" "gosuslugi.ru" "mos.ru" "rutube.ru"
"---"
"google.com" "cloudflare.com" "microsoft.com" "apple.com" "github.com" "telegram.org"
"---"
"cdn.jsdelivr.net" "cdnjs.cloudflare.com" "unpkg.com"
"---"
"cloud.yandex.ru" "functions.yandexcloud.net" "cloud.vk.com" "dns.yandex.net"
)
for name in "${order[@]}"; do
if [[ "$name" == "---" ]]; then
echo ""
continue
fi
local target="${sites[$name]}"
local result
result=$(do_https "$target")
if [ $? -eq 0 ]; then
ok "$(printf '%-28s %s' "$name" "$result")" "https_$name"
else
fail "$(printf '%-28s' "$name")" "https_$name"
fi
done
}
# Кросс-валидация: ICMP ✗ но HTTPS ✓ → предупредить
test_icmp_crosscheck() {
hdr "ICMP КРОСС-ВАЛИДАЦИЯ"
info "Сайты с ICMP ✗ но HTTPS ✓ (блокируют ping, но работают)"
local found=0
local icmp_sites=("yandex.ru" "ya.ru" "vk.com" "mail.ru" "ok.ru" "sber.ru" "gosuslugi.ru" "mos.ru")
for name in "${icmp_sites[@]}"; do
if [ "${RESULTS[icmp_$name]}" = "fail" ] && [ "${RESULTS[https_$name]}" = "ok" ]; then
warn "$(printf '%-22s ICMP блокирован сервером, HTTPS работает' "$name")" "crosscheck_$name"
found=1
fi
done
if [ "$found" -eq 0 ]; then
info "Расхождений не найдено"
fi
}
test_sni() {
hdr "SNI FILTERING"
info "TLS handshake к VPS ($VPS_IP:443) с разными SNI"
local snis=("yandex.ru" "ya.ru" "vk.com" "mail.ru" "google.com" "cloudflare.com" "telegram.org" "$VPS_IP")
for sni in "${snis[@]}"; do
if do_tls_sni "$VPS_IP" 443 "$sni"; then
ok "$(printf 'SNI=%-20s → TLS OK' "$sni")" "sni_$sni"
else
fail "$(printf 'SNI=%-20s → DPI фильтрует!' "$sni")" "sni_$sni"
FILTERED_SNIS+=("$sni")
fi
done
echo ""
info "TLS handshake к Москве ($MOSCOW_IP:2053)"
for sni in "yandex.ru" "ya.ru"; do
if do_tls_sni "$MOSCOW_IP" 2053 "$sni"; then
ok "$(printf 'SNI=%-20s → TLS OK' "$sni")" "sni_moscow_$sni"
else
fail "$(printf 'SNI=%-20s → DPI фильтрует!' "$sni")" "sni_moscow_$sni"
FILTERED_SNIS+=("$sni")
fi
done
if [ ${#FILTERED_SNIS[@]} -gt 0 ]; then
echo ""
info "${Y}⚠ Обнаружена SNI-фильтрация: ${FILTERED_SNIS[*]}${N}"
info "DPI провайдера блокирует TLS к чужим IP с этими SNI"
info "Для Reality/VLESS избегайте этих доменов как маскировочных"
fi
}
test_cdn() {
hdr "CDN REACHABILITY"
# Cloudflare IP ranges
local cf_ips=("104.16.0.1" "104.17.0.1" "172.64.0.1" "172.67.0.1")
local cf_ok=0
local cf_total=${#cf_ips[@]}
info "Cloudflare IP ranges"
for ip in "${cf_ips[@]}"; do
if do_tcp "$ip" 443; then
ok "$(printf '%-22s TCP:443' "$ip")" "cdn_cf_$ip"
((cf_ok++))
else
fail "$(printf '%-22s' "$ip")" "cdn_cf_$ip"
fi
done
if [ "$cf_ok" -gt 0 ] && [ "$cf_ok" -lt "$cf_total" ]; then
info "${Y}⚠ Частичная блокировка Cloudflare: $cf_ok/$cf_total диапазонов доступны${N}"
RESULTS["cf_partial_block"]="true"
fi
echo ""
info "Yandex CDN"
local ycdn
ycdn=$(do_resolve "yastatic.net")
if [ $? -eq 0 ]; then
if do_tcp "$ycdn" 443; then
ok "yastatic.net $ycdn TCP:443" "cdn_yandex"
else
fail "yastatic.net $ycdn" "cdn_yandex"
fi
else
fail "yastatic.net (не резолвится)" "cdn_yandex"
fi
local vkcdn
vkcdn=$(do_resolve "st.vk.com")
if [ $? -eq 0 ]; then
if do_tcp "$vkcdn" 443; then
ok "st.vk.com $vkcdn TCP:443" "cdn_vk"
else
fail "st.vk.com $vkcdn" "cdn_vk"
fi
else
fail "st.vk.com (не резолвится)" "cdn_vk"
fi
}
test_advanced() {
hdr "ADVANCED"
# WebSocket upgrade — тестируем к VPS, а не к Яндексу
info "WebSocket upgrade к VPS ($VPS_IP:443)"
local ws_code
ws_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout $TIMEOUT -k \
-H "Upgrade: websocket" -H "Connection: Upgrade" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" \
"https://$VPS_IP" 2>/dev/null)
if [ "$ws_code" = "101" ]; then
ok "WebSocket VPS → 101 Switching Protocols" "ws_vps"
elif [ "$ws_code" -ge 100 ] 2>/dev/null && [ "$ws_code" -lt 600 ] 2>/dev/null; then
warn "WebSocket VPS → $ws_code (сервер не поддерживает WS upgrade)" "ws_vps"
else
fail "WebSocket VPS → нет ответа (DPI или сервер)" "ws_vps"
fi
# QUIC / HTTP3
echo ""
info "QUIC (HTTP/3)"
local quic
quic=$(curl -s -o /dev/null -w '%{http_version}' --connect-timeout $TIMEOUT \
--http3-only "https://yandex.ru" 2>/dev/null)
if [ "$quic" = "3" ]; then
ok "HTTP/3 yandex.ru → работает" "quic_yandex"
else
skip "HTTP/3 yandex.ru → curl без HTTP/3 или заблокирован" "quic_yandex"
fi
# ECH support
echo ""
info "ECH (Encrypted Client Hello)"
local ech_out
ech_out=$(echo | timeout $TIMEOUT openssl s_client -connect cloudflare.com:443 \
-ech grease 2>&1)
if echo "$ech_out" | grep -qi "ech.*accepted\|ECH.*status.*success"; then
ok "ECH → принят сервером" "ech"
elif echo "$ech_out" | grep -qi "ech"; then
warn "ECH → openssl знает ECH, но GREASE (не настоящий ECH)" "ech"
else
skip "ECH → openssl не поддерживает ECH" "ech"
fi
}
# ══════════════════════════════════════════════════════
# РЕКОМЕНДАЦИИ
# ══════════════════════════════════════════════════════
recommendations() {
hdr "РЕКОМЕНДАЦИИ"
echo ""
local rec_detail=()
local warnings=()
# Собираем предупреждения
if [ "${RESULTS[cf_partial_block]}" = "true" ]; then
warnings+=("⚠ Cloudflare частично заблокирован — CDN-туннели могут быть нестабильны")
fi
if [ ${#FILTERED_SNIS[@]} -gt 0 ]; then
warnings+=("⚠ SNI-фильтрация: ${FILTERED_SNIS[*]} — Reality должен использовать безопасные SNI")
fi
# Определяем безопасные SNI для Reality
local safe_sni_note=""
if [ ${#FILTERED_SNIS[@]} -gt 0 ]; then
local safe_snis=()
for sni in "yandex.ru" "ya.ru" "mail.ru" "google.com" "cloudflare.com"; do
local filtered=0
for f in "${FILTERED_SNIS[@]}"; do
[ "$sni" = "$f" ] && filtered=1 && break
done
[ "$filtered" -eq 0 ] && [ "${RESULTS[sni_$sni]}" = "ok" ] && safe_snis+=("$sni")
done
if [ ${#safe_snis[@]} -gt 0 ]; then
safe_sni_note=" (безопасные SNI: ${safe_snis[*]})"
fi
fi
# S-тир: CDN tunnel через Cloudflare
if [ "${RESULTS[https_cloudflare.com]}" = "ok" ] || [ "${RESULTS[cdn_cf_104.16.0.1]}" = "ok" ]; then
local cf_note="CF доступен, быстро и надёжно"
[ "${RESULTS[cf_partial_block]}" = "true" ] && cf_note="CF частично доступен, возможна нестабильность"
rec_detail+=("${G}S-тир${N} │ VLESS + WebSocket + Cloudflare CDN │ $cf_note")
fi
# S-тир: CDN через jsDelivr
if [ "${RESULTS[https_cdn.jsdelivr.net]}" = "ok" ]; then
rec_detail+=("${G}S-тир${N} │ CDN-туннель через jsDelivr │ CDN доступен")
fi
# S-тир: Direct VLESS + Reality
if [ "${RESULTS[tcp_VPS:443 (VLESS)]}" = "ok" ]; then
rec_detail+=("${G}S-тир${N} │ VLESS + XHTTP + Reality (прямой) │ TCP к VPS:443 открыт${safe_sni_note}")
fi
# A-тир: Moscow relay
if [ "${RESULTS[tcp_Moscow:2053 (relay)]}" = "ok" ]; then
rec_detail+=("${Y}A-тир${N} │ VLESS через Moscow relay │ TCP к Москве:2053 открыт")
fi
# A-тир: Shadowsocks
if [ "${RESULTS[tcp_VPS:38246 (SS)]}" = "ok" ]; then
rec_detail+=("${Y}A-тир${N} │ Shadowsocks │ TCP к VPS:38246 открыт")
fi
# A-тир: OpenVPN + Cloak
if [ "${RESULTS[tcp_VPS:553 (OVPNCloak)]}" = "ok" ]; then
rec_detail+=("${Y}A-тир${N} │ OpenVPN + Cloak │ TCP к VPS:553 открыт")
fi
# B-тир: SOCKS5 proxy (без обфускации, легко детектится DPI)
if [ "${RESULTS[tcp_VPS:43781 (SOCKS5)]}" = "ok" ]; then
rec_detail+=("${C}B-тир${N} │ SOCKS5 proxy (без обфускации) │ TCP к VPS:43781 открыт")
fi
# A-тир: Hysteria2 / QUIC
if [ "${RESULTS[udp_VPS:443 (QUIC)]}" = "ok" ]; then
rec_detail+=("${Y}A-тир${N} │ Hysteria2 + Salamander (UDP) │ UDP QUIC к VPS работает")
fi
# B-тир: WireGuard / AmneziaWG
if [ "${RESULTS[udp_VPS:43742 (WG)]}" = "ok" ] || [ "${RESULTS[udp_VPS:47426 (AWG)]}" = "ok" ] || [ "${RESULTS[udp_VPS:48253 (AWG2)]}" = "ok" ]; then
rec_detail+=("${C}B-тир${N} │ AmneziaWG / WireGuard │ UDP к VPS работает")
fi
# B-тир: Chisel
if [ "${RESULTS[tcp_VPS:8443 (Chisel)]}" = "ok" ]; then
rec_detail+=("${C}B-тир${N} │ Chisel (HTTP tunnel) │ TCP к VPS:8443 открыт")
fi
# B-тир: Yandex Cloud Functions
if [ "${RESULTS[https_functions.yandexcloud.net]}" = "ok" ]; then
rec_detail+=("${C}B-тир${N} │ Serverless proxy (YC Functions) │ Yandex Cloud доступен")
fi
# B-тир: DNSTT
if [ "${RESULTS[dns_dnstt]}" = "ok" ] || [ "${RESULTS[dns_system]}" = "ok" ]; then
rec_detail+=("${C}B-тир${N} │ DNSTT (DNS-туннель) │ DNS работает, ~50-100 Кбит/с")
fi
# C-тир: ICMP tunnel
if [ "${RESULTS[icmp_VPS Ирландия]}" = "ok" ] || [ "${RESULTS[icmp_Москва relay]}" = "ok" ]; then
rec_detail+=("${R}C-тир${N} │ ICMP tunnel (ptunnel-ng) │ Ping к серверу, очень медленно")
fi
# Вывод предупреждений
if [ ${#warnings[@]} -gt 0 ]; then
for w in "${warnings[@]}"; do
echo -e " ${Y}$w${N}"
done
echo ""
fi
if [ ${#rec_detail[@]} -eq 0 ]; then
echo -e " ${R}Ни один метод не определён как рабочий${N}"
echo -e " ${Y}Варианты:${N}"
echo " • Спутниковый интернет (Starlink)"
echo " • Физический доступ к незаблокированной сети"
echo " • Mesh-сети"
else
local i=0
for detail in "${rec_detail[@]}"; do
((i++))
echo -e " $i. $detail"
done
fi
}
# ══════════════════════════════════════════════════════
# СОХРАНЕНИЕ РЕЗУЛЬТАТОВ (безопасный JSON)
# ══════════════════════════════════════════════════════
save_results() {
mkdir -p "$RESULT_DIR"
# Экранирование для JSON
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\t'/\\t}"
echo -n "$s"
}
{
echo "{"
echo " \"version\": \"1.1\","
echo " \"timestamp\": \"$TIMESTAMP\","
echo " \"filtered_snis\": [$(printf '"%s",' "${FILTERED_SNIS[@]}" | sed 's/,$//' )],"
echo " \"results\": {"
local first=true
for key in $(echo "${!RESULTS[@]}" | tr ' ' '\n' | sort); do
if [ "$first" = true ]; then
first=false
else
echo ","
fi
printf ' "%s": "%s"' "$(json_escape "$key")" "$(json_escape "${RESULTS[$key]}")"
done
echo ""
echo " }"
echo "}"
} > "$JSON_FILE"
info "Результаты: $JSON_FILE"
}
# ══════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════
main() {
echo -e "${B}══════════════════════════════════════════${N}"
echo -e "${B} WHITELIST PROBE v1.1 — $(date '+%Y-%m-%d %H:%M')${N}"
echo -e "${B}══════════════════════════════════════════${N}"
install_deps
test_provider
test_icmp
test_dns
test_dns_tunnel
test_tcp_our_servers
test_udp
test_https
test_icmp_crosscheck
test_sni
test_cdn
test_advanced
recommendations
save_results
echo ""
echo -e "${B}══════════════════════════════════════════${N}"
echo -e " Тестов: $TOTAL ${G}$PASS${N} ${R}$FAIL${N} ${Y}$SKIP${N}"
echo -e "${B}══════════════════════════════════════════${N}"
}
main "$@"