diff --git a/probe.sh b/probe.sh index 688c831..00eeefa 100644 --- a/probe.sh +++ b/probe.sh @@ -1,9 +1,9 @@ #!/data/data/com.termux/files/usr/bin/bash # ══════════════════════════════════════════════════════ -# WHITELIST PROBE v1.0 +# WHITELIST PROBE v1.1 # Определяет доступные методы обхода whitelist # Платформа: Termux (Android) / Linux -# Зависимости: curl, nc (ncat), openssl, nslookup +# Зависимости: curl, nc (ncat), openssl, dig/nslookup # ══════════════════════════════════════════════════════ set -o pipefail @@ -13,10 +13,9 @@ 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=3 # секунд на каждый тест +TIMEOUT=5 # секунд на каждый тест RESULT_DIR="./results" TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') -RESULT_FILE="${RESULT_DIR}/probe_${TIMESTAMP}.txt" JSON_FILE="${RESULT_DIR}/probe_${TIMESTAMP}.json" # ── Цвета ───────────────────────────────────────────── @@ -26,9 +25,10 @@ if [ -t 1 ]; then 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='' N='' + G='' R='' Y='' C='' B='' W='' N='' fi # ── Счётчики ────────────────────────────────────────── @@ -36,19 +36,24 @@ 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++)); } -skip() { echo -e " ${Y}—${N} $1"; RESULTS["$2"]="skip"; ((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 с таймаутом +# Ping с таймаутом (2 попытки, успех если хотя бы одна прошла) do_ping() { local host=$1 local result - result=$(ping -c 1 -W $TIMEOUT "$host" 2>/dev/null | grep -oP 'time=\K[0-9.]+') + 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 @@ -63,35 +68,55 @@ do_tcp() { return $? } -# UDP test (отправить пакет, ждать ответ) -do_udp() { +# UDP — отправка WireGuard handshake initiation (тип 1) +# WG отвечает cookie reply на невалидный handshake, что позволяет +# подтвердить прохождение UDP-пакетов +do_udp_wg() { local host=$1 port=$2 - echo -n "test" | timeout $TIMEOUT nc -u -w $TIMEOUT "$host" "$port" 2>/dev/null - # UDP "успех" если nc не вернул ошибку (не гарантирует доставку) + # 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 +# HTTPS test — один запрос, возвращает "код HTTP/версия" do_https() { local host=$1 - local code - code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) "https://$host" 2>/dev/null) + 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" + echo "$code HTTP/$ver" return 0 fi return 1 } -# DNS resolve +# DNS resolve — предпочитает dig, фолбэк на nslookup do_resolve() { local domain=$1 server=$2 local result - if [ -n "$server" ]; then - result=$(nslookup "$domain" "$server" 2>/dev/null | grep -A1 "Name:" | grep "Address:" | head -1 | awk '{print $2}') + + 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 - result=$(nslookup "$domain" 2>/dev/null | grep -A1 "Name:" | grep "Address:" | head -1 | awk '{print $2}') + 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 @@ -106,16 +131,6 @@ do_tls_sni() { return $? } -# HTTP/2 check -do_h2() { - local host=$1 - local proto - proto=$(curl -s -o /dev/null -w '%{http_version}' --connect-timeout $TIMEOUT --max-time $((TIMEOUT+2)) "https://$host" 2>/dev/null) - echo "$proto" - [ "$proto" = "2" ] && return 0 - return 1 -} - # ══════════════════════════════════════════════════════ # УСТАНОВКА ЗАВИСИМОСТЕЙ # ══════════════════════════════════════════════════════ @@ -124,9 +139,13 @@ install_deps() { command -v curl >/dev/null || missing+=(curl) command -v nc >/dev/null || missing+=(nmap) command -v openssl >/dev/null || missing+=(openssl-tool) - command -v nslookup >/dev/null || { command -v host >/dev/null || missing+=(dnsutils); } 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 @@ -177,6 +196,7 @@ test_provider() { test_icmp() { hdr "ICMP (Ping)" + info "2 попытки на хост, таймаут ${TIMEOUT}с" local -A targets=( ["VPS Ирландия"]="$VPS_IP" @@ -212,7 +232,6 @@ test_icmp() { test_dns() { hdr "DNS RESOLUTION" - # Системный DNS local r r=$(do_resolve "yandex.ru") if [ $? -eq 0 ]; then @@ -221,7 +240,6 @@ test_dns() { fail "System DNS yandex.ru" "dns_system" fi - # Через разные DNS-серверы local -A dns_servers=( ["Yandex 77.88.8.8"]="77.88.8.8" ["Yandex 77.88.8.1"]="77.88.8.1" @@ -243,15 +261,17 @@ test_dns() { fi done - # DNSTT домен echo "" r=$(do_resolve "$DNSTT_DOMAIN") if [ $? -eq 0 ]; then ok "DNSTT domain $DNSTT_DOMAIN → $r" "dns_dnstt" else - # Попробуем NS запись local ns_result - ns_result=$(nslookup -type=NS "$DNSTT_DOMAIN" 2>/dev/null | grep -i "nameserver" | head -1) + 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 @@ -270,8 +290,8 @@ test_dns() { test_dns_tunnel() { hdr "DNS TUNNEL VIABILITY" - # DoH (DNS over HTTPS) — через Яндекс 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) @@ -318,7 +338,9 @@ test_tcp_our_servers() { 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" @@ -327,7 +349,18 @@ test_tcp_our_servers() { ["Moscow:22 (SSH)"]="$MOSCOW_IP:22" ) - local order=("VPS:443 (VLESS)" "VPS:38246 (SS)" "VPS:8443 (Chisel)" "VPS:22 (SSH)" "VPS:53 (DNS)" "Moscow:2053 (relay)" "Moscow:443 (HTTPS)" "Moscow:22 (SSH)") + 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]}" @@ -344,35 +377,51 @@ test_tcp_our_servers() { test_udp() { hdr "UDP" - local -A targets=( - ["VPS:53 (DNSTT)"]="$VPS_IP:53" - ["VPS:443 (QUIC)"]="$VPS_IP:443" - ["VPS:43742 (WG)"]="$VPS_IP:43742" - ["VPS:47426 (AWG)"]="$VPS_IP:47426" - ["VPS:48253 (AWG2)"]="$VPS_IP:48253" + # 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)") - local order=("VPS:53 (DNSTT)" "VPS:443 (QUIC)" "VPS:43742 (WG)" "VPS:47426 (AWG)" "VPS:48253 (AWG2)") - - info "UDP-тесты ненадёжны без ответа сервера" - for name in "${order[@]}"; do - local hp="${targets[$name]}" - local host="${hp%%:*}" - local port="${hp##*:}" - # Для DNS порта — отправляем реальный DNS запрос - if [ "$port" = "53" ]; then - local dns_r - dns_r=$(do_resolve "yandex.ru" "$host") - if [ $? -eq 0 ]; then - ok "$(printf '%-25s (DNS ответил: %s)' "$name" "$dns_r")" "udp_$name" - else - fail "$(printf '%-25s' "$name")" "udp_$name" - fi + 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 - if do_udp "$host" "$port"; then - skip "$(printf '%-25s (отправлено, ответ неизвестен)' "$name")" "udp_$name" + # 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' "$name")" "udp_$name" + fail "$(printf '%-25s (UDP заблокирован)' "$name")" "udp_$name" fi fi done @@ -413,45 +462,60 @@ test_https() { 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---" + "---" "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 + if [[ "$name" == "---" ]]; then echo "" continue fi local target="${sites[$name]}" - local code - code=$(do_https "$target") + local result + result=$(do_https "$target") if [ $? -eq 0 ]; then - local h2 - h2=$(do_h2 "$target") - local proto_tag="" - [ "$h2" = "2" ] && proto_tag=" HTTP/2" || proto_tag=" HTTP/$h2" - ok "$(printf '%-28s %s%s' "$name" "$code" "$proto_tag")" "https_$name" + 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" "$VPS_IP") + 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' "$sni")" "sni_$sni" + fail "$(printf 'SNI=%-20s → DPI фильтрует!' "$sni")" "sni_$sni" + FILTERED_SNIS+=("$sni") fi done @@ -461,9 +525,17 @@ test_sni() { if do_tls_sni "$MOSCOW_IP" 2053 "$sni"; then ok "$(printf 'SNI=%-20s → TLS OK' "$sni")" "sni_moscow_$sni" else - fail "$(printf 'SNI=%-20s' "$sni")" "sni_moscow_$sni" + 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() { @@ -471,15 +543,23 @@ test_cdn() { # 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 @@ -510,18 +590,20 @@ test_cdn() { test_advanced() { hdr "ADVANCED" - # WebSocket upgrade test через доступные хосты - info "WebSocket upgrade" + # 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 \ + 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://yandex.ru" 2>/dev/null) + "https://$VPS_IP" 2>/dev/null) if [ "$ws_code" = "101" ]; then - ok "WebSocket yandex.ru → 101 Switching" "ws_yandex" + 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 yandex.ru → $ws_code (не поддерживает)" "ws_yandex" + fail "WebSocket VPS → нет ответа (DPI или сервер)" "ws_vps" fi # QUIC / HTTP3 @@ -539,13 +621,15 @@ test_advanced() { # ECH support echo "" info "ECH (Encrypted Client Hello)" - local ech - ech=$(echo | timeout $TIMEOUT openssl s_client -connect cloudflare.com:443 \ - -ech grease 2>&1 | grep -i "ech") - if [ -n "$ech" ]; then - ok "ECH → поддерживается" "ech" + 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" + skip "ECH → openssl не поддерживает ECH" "ech" fi } @@ -556,80 +640,106 @@ recommendations() { hdr "РЕКОМЕНДАЦИИ" echo "" - local rec=() local rec_detail=() + local warnings=() - # S-тир: CDN tunnel - if [ "${RESULTS[https_cloudflare.com]}" = "ok" ] || [ "${RESULTS[cdn_cf_104.16.0.1]}" = "ok" ]; then - rec+=("S") - rec_detail+=("${G}S-тир${N} │ VLESS + WebSocket + Cloudflare CDN │ CF доступен, быстро и надёжно") + # Собираем предупреждения + 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+=("S") rec_detail+=("${G}S-тир${N} │ CDN-туннель через jsDelivr │ CDN доступен") fi - # S-тир: Direct VLESS + # S-тир: Direct VLESS + Reality if [ "${RESULTS[tcp_VPS:443 (VLESS)]}" = "ok" ]; then - rec+=("S") - rec_detail+=("${G}S-тир${N} │ VLESS + XHTTP + Reality (прямой) │ TCP к VPS:443 открыт") + 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+=("A") rec_detail+=("${Y}A-тир${N} │ VLESS через Moscow relay │ TCP к Москве:2053 открыт") fi - # A-тир: Hysteria2 + # 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+=("A") - rec_detail+=("${Y}A-тир${N} │ Hysteria2 + Salamander (UDP) │ UDP к VPS работает") + 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+=("B") 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+=("B") rec_detail+=("${C}B-тир${N} │ DNSTT (DNS-туннель) │ DNS работает, ~50-100 Кбит/с") fi - # B-тир: DoH tunnel - if [ "${RESULTS[doh_yandex]}" = "ok" ]; then - rec+=("B") - rec_detail+=("${C}B-тир${N} │ DoH tunnel (dns.yandex.net) │ DNS-over-HTTPS через Яндекс") - fi - # C-тир: ICMP tunnel - local icmp_to_vps="${RESULTS[icmp_VPS Ирландия]}" - local icmp_to_moscow="${RESULTS[icmp_Москва relay]}" - if [ "$icmp_to_vps" = "ok" ] || [ "$icmp_to_moscow" = "ok" ]; then - rec+=("C") - rec_detail+=("${R}C-тир${N} │ ICMP tunnel (ptunnel-ng) │ Ping к серверу работает, очень медленно") + if [ "${RESULTS[icmp_VPS Ирландия]}" = "ok" ] || [ "${RESULTS[icmp_Москва relay]}" = "ok" ]; then + rec_detail+=("${R}C-тир${N} │ ICMP tunnel (ptunnel-ng) │ Ping к серверу, очень медленно") fi - # Shadowsocks - if [ "${RESULTS[tcp_VPS:38246 (SS)]}" = "ok" ]; then - rec+=("A") - rec_detail+=("${Y}A-тир${N} │ Shadowsocks │ TCP к VPS:38246 открыт") - fi - - # Chisel - if [ "${RESULTS[tcp_VPS:8443 (Chisel)]}" = "ok" ]; then - rec+=("B") - rec_detail+=("${C}B-тир${N} │ Chisel (HTTP tunnel) │ TCP к VPS:8443 открыт") - fi - - # WireGuard / AmneziaWG - if [ "${RESULTS[udp_VPS:43742 (WG)]}" = "ok" ] || [ "${RESULTS[udp_VPS:47426 (AWG)]}" = "ok" ]; then - rec+=("B") - rec_detail+=("${C}B-тир${N} │ AmneziaWG / WireGuard │ UDP к VPS работает") + # Вывод предупреждений + if [ ${#warnings[@]} -gt 0 ]; then + for w in "${warnings[@]}"; do + echo -e " ${Y}$w${N}" + done + echo "" fi if [ ${#rec_detail[@]} -eq 0 ]; then @@ -648,27 +758,40 @@ recommendations() { } # ══════════════════════════════════════════════════════ -# СОХРАНЕНИЕ РЕЗУЛЬТАТОВ +# СОХРАНЕНИЕ РЕЗУЛЬТАТОВ (безопасный JSON) # ══════════════════════════════════════════════════════ save_results() { mkdir -p "$RESULT_DIR" - # JSON - echo "{" > "$JSON_FILE" - echo " \"timestamp\": \"$TIMESTAMP\"," >> "$JSON_FILE" - echo " \"results\": {" >> "$JSON_FILE" - local first=true - for key in $(echo "${!RESULTS[@]}" | tr ' ' '\n' | sort); do - if [ "$first" = true ]; then - first=false - else - echo "," >> "$JSON_FILE" - fi - printf ' "%s": "%s"' "$key" "${RESULTS[$key]}" >> "$JSON_FILE" - done - echo "" >> "$JSON_FILE" - echo " }" >> "$JSON_FILE" - echo "}" >> "$JSON_FILE" + # Экранирование для 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" } @@ -678,7 +801,7 @@ save_results() { # ══════════════════════════════════════════════════════ main() { echo -e "${B}══════════════════════════════════════════${N}" - echo -e "${B} WHITELIST PROBE v1.0 — $(date '+%Y-%m-%d %H:%M')${N}" + echo -e "${B} WHITELIST PROBE v1.1 — $(date '+%Y-%m-%d %H:%M')${N}" echo -e "${B}══════════════════════════════════════════${N}" install_deps @@ -690,6 +813,7 @@ main() { test_tcp_our_servers test_udp test_https + test_icmp_crosscheck test_sni test_cdn test_advanced @@ -698,7 +822,7 @@ main() { echo "" echo -e "${B}══════════════════════════════════════════${N}" - echo -e " Тестов: $TOTAL ${G}✓ $PASS${N} ${R}✗ $FAIL${N}" + echo -e " Тестов: $TOTAL ${G}✓ $PASS${N} ${R}✗ $FAIL${N} ${Y}— $SKIP${N}" echo -e "${B}══════════════════════════════════════════${N}" }