Автообход: полный подбор идёт всегда, стратегии снова запускаются, равные сравниваются в фоне

- Полный подбор после быстрого не проверял ни одной стратегии: перебор шёл только когда
  что-то не открывалось, а после быстрого открывалось всё. Теперь полный идёт всегда.
- Бэкенд из rpcd работал с umask 077, и списки zapret, которые он переписывал, получали
  права 600: nfqws после сброса прав их не читал, и каждая стратегия «не запустилась», а
  общий обход после перезапуска молча терял списки. umask 022, права чинятся при
  перезапуске zapret и перед подбором.
- Счёт стратегий — общий («открылось N из 54»); при равном выше та, что открыла больше
  адресов неоткрывшихся сервисов.
- Равные лучшие сравниваются уже после подбора, в фоне, по узлам CDN из списка
  dpi-detector (Runnin4ik): обход к этому моменту работает, на странице — «Лучшая
  стратегия определится позже». Стратегия меняется, только если другая набрала строго
  больше и сервисов открывается не меньше.
- На роутерах меньше 256 МБ пробы идут по четыре, а не по восемь.
- Запасная версия tgws — 0.2.9.
This commit is contained in:
xyzmean committed 2026-09-23 23:45:48 +03:00
1 parent 61bff13932
commit e22b573785
1 file changed
+231 -30
+231 -30
View File
@@ -10,6 +10,13 @@ echo -e "\n${MAGENTA}Устанавливаем Zapret Manager для LuCI${NC}"
# Пока идёт автообход, переустанавливать нельзя: ниже стирается каталог задач, а с # Пока идёт автообход, переустанавливать нельзя: ниже стирается каталог задач, а с
# ним журнал идущего подбора, и страница показывает пустой журнал до самого конца. Тестер при # ним журнал идущего подбора, и страница показывает пустой журнал до самого конца. Тестер при
# этом меняет стратегии zapret, и обрывать его посередине значит оставить роутер на случайной. # этом меняет стратегии zapret, и обрывать его посередине значит оставить роутер на случайной.
# Сравнение равных стратегий в фоне — не повод отказывать: оно снимается, а стратегию меняет только
# в самом конце.
if [ -f /tmp/zapret-manager-luci/redbtn_deep.pid ]; then
kill "$(cat /tmp/zapret-manager-luci/redbtn_deep.pid 2>/dev/null)" 2>/dev/null
pkill -f "qnum=8397" 2>/dev/null
nft delete table inet zm_rb_ztest >/dev/null 2>&1
fi
for _zm_job in redbtn steer; do for _zm_job in redbtn steer; do
if [ -f /tmp/zapret-manager-luci/$_zm_job.pid ] && kill -0 "$(cat /tmp/zapret-manager-luci/$_zm_job.pid 2>/dev/null)" 2>/dev/null; then if [ -f /tmp/zapret-manager-luci/$_zm_job.pid ] && kill -0 "$(cat /tmp/zapret-manager-luci/$_zm_job.pid 2>/dev/null)" 2>/dev/null; then
echo -e "${YELLOW}Идёт автообход или операция Steer — дождитесь окончания и запустите установку снова${NC}" echo -e "${YELLOW}Идёт автообход или операция Steer — дождитесь окончания и запустите установку снова${NC}"
@@ -62,6 +69,12 @@ mkdir -p /opt/zapret-manager-luci
chmod 0755 /opt/zapret-manager-luci chmod 0755 /opt/zapret-manager-luci
cat > '/opt/zapret-manager-luci/backend.sh' << 'ZM_INSTALLER_EOF' cat > '/opt/zapret-manager-luci/backend.sh' << 'ZM_INSTALLER_EOF'
# rpcd запускает бэкенд с umask 077, и всё, что он пишет — списки zapret, файлы fake при
# установке, — получалось с правами 600. nfqws сбрасывает права до пользователя daemon и такие
# файлы уже не читает: стратегия с --hostlist не запускается, а обход после перезапуска
# молча перестаёт действовать.
umask 022
CONF="/etc/config/zapret" CONF="/etc/config/zapret"
ZM_VERSION="1.38" ZM_VERSION="1.38"
ZM_SCRIPT_URL="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/main/ZapretManager_LuCI.sh" ZM_SCRIPT_URL="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/main/ZapretManager_LuCI.sh"
@@ -164,6 +177,8 @@ log_tail() {
} }
zapret_restart() { zapret_restart() {
# Файлы, записанные прежними версиями с правами 600 (см. umask выше), nfqws не прочтёт.
chmod -R a+rX /opt/zapret/ipset /opt/zapret/files 2>/dev/null
[ -x /opt/zapret/sync_config.sh ] && /opt/zapret/sync_config.sh >/dev/null 2>&1 [ -x /opt/zapret/sync_config.sh ] && /opt/zapret/sync_config.sh >/dev/null 2>&1
/etc/init.d/zapret restart >/dev/null 2>&1 /etc/init.d/zapret restart >/dev/null 2>&1
} }
@@ -1568,7 +1583,7 @@ nfqws_opt_set() {
TG_MTPROTO_VER="0.10" TG_MTPROTO_VER="0.10"
TGWS_VERSION="0.2.8" TGWS_VERSION="0.2.9"
TGWS_BASE_URL="https://gitlab.com/xyzmean/brb/-/raw/main/dist" TGWS_BASE_URL="https://gitlab.com/xyzmean/brb/-/raw/main/dist"
TGWS_VERSION_URL="https://gitlab.com/xyzmean/brb/-/raw/main/VERSION" TGWS_VERSION_URL="https://gitlab.com/xyzmean/brb/-/raw/main/VERSION"
TG_GO_VER="1.4.1" TG_GO_VER="1.4.1"
@@ -6069,7 +6084,10 @@ RB_PROBE_DOH=""
# «без обхода» не требует останавливать zapret всей сети. # «без обхода» не требует останавливать zapret всей сети.
RB_LANE_DIRECT="21000-21199" RB_LANE_DIRECT="21000-21199"
RB_SYS_DNS="" RB_SYS_DNS=""
# Проб разом: каждый curl — три-четыре мегабайта памяти, и на роутере со 128 МБ, где рядом
# живут Steer, мост Telegram и dnsmasq со списком ИИ-доменов, восемь разом доводили до OOM.
RB_PARALLEL=8 RB_PARALLEL=8
[ "$(awk '/^MemTotal/ {print $2}' /proc/meminfo 2>/dev/null)" -lt 262144 ] 2>/dev/null && RB_PARALLEL=4
RB_BAR=60 RB_BAR=60
mkdir -p "$RB_RUN" 2>/dev/null mkdir -p "$RB_RUN" 2>/dev/null
@@ -6329,13 +6347,15 @@ _rb_zt_stop_nfqws() {
_rb_zt_down() { _rb_zt_stop_nfqws; nft delete table inet "$RB_ZT_TABLE" >/dev/null 2>&1; } _rb_zt_down() { _rb_zt_stop_nfqws; nft delete table inet "$RB_ZT_TABLE" >/dev/null 2>&1; }
# Проверить набор «имя|ссылка» с портов изоляции. Печатает «удачи всего». # Проверить набор «имя|ссылка» с портов изоляции. Печатает «удачи всего удачи_сервисов»: цели с
# именем «svc:…» — адреса сервисов, которые сейчас не открываются. В общий счёт они не входят и
# считаются отдельно: из стратегий с равным общим счётом лучше та, что открыла их больше.
_rb_zt_check() { # ФАЙЛ_ЦЕЛЕЙ _rb_zt_check() { # ФАЙЛ_ЦЕЛЕЙ
# Ждём ТОЛЬКО свои пробы: в этой же оболочке фоном живёт nfqws проверяемой стратегии, и # Ждём ТОЛЬКО свои пробы: в этой же оболочке фоном живёт nfqws проверяемой стратегии, и
# голый `wait` ждал бы и его. # голый `wait` ждал бы и его.
local okf="$RB_RUN/zt.ok" run=0 e total pids="" local okf="$RB_RUN/zt.ok" run=0 e total svc pids=""
: > "$okf" : > "$okf"
total=$(grep -c '|' "$1") total=$(grep -v '^svc:' "$1" | grep -c '|')
while IFS= read -r e; do while IFS= read -r e; do
[ -n "$e" ] || continue [ -n "$e" ] || continue
_rb_stopped && break _rb_stopped && break
@@ -6343,7 +6363,58 @@ _rb_zt_check() { # ФАЙЛ_ЦЕЛЕЙ
curl -sL --local-port "$RB_ZT_LO-$RB_ZT_HI" ${RB_PROBE_DOH:+--doh-url "$RB_PROBE_DOH"} \ curl -sL --local-port "$RB_ZT_LO-$RB_ZT_HI" ${RB_PROBE_DOH:+--doh-url "$RB_PROBE_DOH"} \
--connect-timeout 4 --max-time 6 --speed-time 3 --speed-limit 1 --range 0-65535 \ --connect-timeout 4 --max-time 6 --speed-time 3 --speed-limit 1 --range 0-65535 \
-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) curl/8.0" -o /dev/null "${e#*|}" >/dev/null 2>&1 && -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) curl/8.0" -o /dev/null "${e#*|}" >/dev/null 2>&1 &&
echo 1 >> "$okf" echo "${e%%|*}" >> "$okf"
) &
pids="$pids $!"
run=$((run + 1))
if [ "$run" -ge "$RB_PARALLEL" ]; then wait $pids 2>/dev/null; pids=""; run=0; fi
done < "$1"
[ -n "$pids" ] && wait $pids 2>/dev/null
svc=$(grep -c '^svc:' "$okf")
echo "$(grep -vc '^svc:' "$okf") $total $svc"
rm -f "$okf"
}
# Углублённая проверка равных лучших — по узлам CDN из списка dpi-detector (Runnin4ik), как в
# brb-deep. Короткие запросы основного прохода не различают стратегии, набравшие одинаково, а
# российский DPI рвёт поток к CDN и хостингам на 16-20 КБ, когда страница уже «открылась». Здесь
# с каждого узла берётся диапазон в 64 КБ, и узел, отдавший меньше 20 КБ, считается оборванным.
# В списке есть узлы, которые столько не отдают и без DPI, поэтому числа сравниваются только
# между стратегиями на одном и том же наборе. Узел без имени — идём по адресу, сертификат не
# проверяется: важен объём дошедшего, а не подлинность узла.
RB_DEEP_URLS="https://raw.githubusercontent.com/Runnin4ik/dpi-detector/main/tcp16.json https://cdn.jsdelivr.net/gh/Runnin4ik/dpi-detector@main/tcp16.json"
RB_DEEP_MIN=20480
# Больше равных не проверяем: минута на стратегию, а дюжина равных — это уже четверть часа.
RB_DEEP_MAX=6
# Цели: «id|адрес|порт», каждый третий узел списка — так в выборку попадают все провайдеры, а не
# только первые в файле (Hetzner и Cloudflare).
_rb_zt_deep_targets() { # ФАЙЛ
local u src="$RB_RUN/zt.tcp16.json"
for u in $RB_DEEP_URLS; do
curl -fsSL ${RB_PROBE_DOH:+--doh-url "$RB_PROBE_DOH"} --connect-timeout 6 --max-time 20 -o "$src" "$u" 2>/dev/null && [ -s "$src" ] && break
rm -f "$src"
done
[ -s "$src" ] || return 1
sed -n 's/.*"id":[[:space:]]*"\([^"]*\)".*"ip":[[:space:]]*"\([^"]*\)".*"port":[[:space:]]*\([0-9]*\).*/\1|\2|\3/p' "$src" |
awk 'NR % 3 == 1' > "$1"
rm -f "$src"
[ -s "$1" ]
}
_rb_zt_deep() { # ФАЙЛ_УЗЛОВ -> «необорванных всего»
local okf="$RB_RUN/zt.dok" run=0 id ip port sc total pids=""
: > "$okf"
total=$(grep -c '|' "$1")
while IFS='|' read -r id ip port; do
[ -n "$ip" ] || continue
_rb_stopped && break
sc=https; [ "$port" = 80 ] && sc=http
(
n=$(curl -sk --local-port "$RB_ZT_LO-$RB_ZT_HI" --connect-timeout 4 --max-time 12 --range 0-65535 \
-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) curl/8.0" -o /dev/null -w '%{size_download}' \
"$sc://$ip:$port/" 2>/dev/null)
[ "${n:-0}" -ge "$RB_DEEP_MIN" ] 2>/dev/null && echo "$id" >> "$okf"
) & ) &
pids="$pids $!" pids="$pids $!"
run=$((run + 1)) run=$((run + 1))
@@ -6354,11 +6425,17 @@ _rb_zt_check() { # ФАЙЛ_ЦЕЛЕЙ
rm -f "$okf" rm -f "$okf"
} }
# Блок стратегии по имени из кандидатов последнего прогона.
_rb_zt_block() { # ИМЯ ФАЙЛ [КАНДИДАТЫ]
awk -v n="#$1" '/^#/ { p = ($0 == n) } p' "${3:-$RB_RUN/zt.cand}" > "$2"
[ -s "$2" ]
}
# Одна стратегия: поднять обработчик с её ключами, промерить, снять. -1 — не поднялась. # Одна стратегия: поднять обработчик с её ключами, промерить, снять. -1 — не поднялась.
_rb_zt_one() { # ФАЙЛ_БЛОКА ФАЙЛ_ЦЕЛЕЙ _rb_zt_one() { # ФАЙЛ_БЛОКА ФАЙЛ_ЦЕЛЕЙ [ПРОВЕРКА]
# Аргументы — сразу в переменные: ниже `set --` собирает ключи стратегии в позиционные # Аргументы — сразу в переменные: ниже `set --` собирает ключи стратегии в позиционные
# параметры и затирает $1 и $2. # параметры и затирает $1 и $2.
local blk="$1" tgt="$2" l opts="$RB_RUN/zt.opts" pid local blk="$1" tgt="$2" chk="${3:-_rb_zt_check}" l opts="$RB_RUN/zt.opts" pid
grep -v '^[[:space:]]*#' "$blk" | grep -v '^[[:space:]]*$' > "$opts" grep -v '^[[:space:]]*#' "$blk" | grep -v '^[[:space:]]*$' > "$opts"
[ -s "$opts" ] || { echo "-1 0"; return; } [ -s "$opts" ] || { echo "-1 0"; return; }
set -- set --
@@ -6371,16 +6448,18 @@ _rb_zt_one() { # ФАЙЛ_БЛОКА ФАЙЛ_ЦЕЛЕЙ
echo "$pid" > "$RB_ZT_PID" echo "$pid" > "$RB_ZT_PID"
sleep 1 sleep 1
kill -0 "$pid" 2>/dev/null || { rm -f "$RB_ZT_PID"; echo "-1 0"; return; } kill -0 "$pid" 2>/dev/null || { rm -f "$RB_ZT_PID"; echo "-1 0"; return; }
_rb_zt_check "$tgt" "$chk" "$tgt"
kill "$pid" 2>/dev/null kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null wait "$pid" 2>/dev/null
rm -f "$RB_ZT_PID" rm -f "$RB_ZT_PID"
} }
# Прогон режима менеджера (v | v_flowseal | youtube). Пишет результаты в формате тестера ZM # Прогон режима менеджера (v | v_flowseal | youtube). Пишет результаты в формате тестера ZM
# («имя → удачи/всего», первой строкой контроль) и печатает путь к файлу. # («имя → удачи/всего», первой строкой контроль) и печатает путь к файлу. С файлом целей
_rb_ztest() { # РЕЖИМ # сервисов («svc:хост|ссылка») у строки есть хвост « · сервисы N»: сколько адресов
local mode="$1" cand="$RB_RUN/zt.cand" urls="$RB_RUN/zt.urls" res="$RB_RUN/zt.res.$1" lines total cur=0 start next r ok tot name # неоткрывшихся сервисов стратегия открыла. Сортировка — по общему счёту, хвост решает при равном.
_rb_ztest() { # РЕЖИМ [ЦЕЛИ_СЕРВИСОВ]
local mode="$1" svcf="${2:-}" cand="$RB_RUN/zt.cand" urls="$RB_RUN/zt.urls" res="$RB_RUN/zt.res.$1" lines total cur=0 start next r ok tot sv name
[ -x "$RB_ZT_NFQWS" ] || return 1 [ -x "$RB_ZT_NFQWS" ] || return 1
# Остатки прошлого подбора, убитого без уборки (kill -9, перезагрузка панели): чужой nfqws # Остатки прошлого подбора, убитого без уборки (kill -9, перезагрузка панели): чужой nfqws
# на нашей очереди не дал бы запуститься ни одной стратегии. # на нашей очереди не дал бы запуститься ни одной стратегии.
@@ -6388,11 +6467,13 @@ _rb_ztest() { # РЕЖИМ
pkill -f "qnum=$RB_ZT_QUEUE" 2>/dev/null pkill -f "qnum=$RB_ZT_QUEUE" 2>/dev/null
_add_gp_domains _add_gp_domains
_refresh_exclude_file _refresh_exclude_file
chmod -R a+rX /opt/zapret/ipset /opt/zapret/files 2>/dev/null
echo " Собираем стратегии" >&2 echo " Собираем стратегии" >&2
_test_build_candidates "$mode" "$cand" _test_build_candidates "$mode" "$cand"
[ -s "$cand" ] || return 1 [ -s "$cand" ] || return 1
echo " Собираем адреса для проверки" >&2 echo " Собираем адреса для проверки" >&2
if [ "$mode" = youtube ]; then _test_yt_urls > "$urls"; else _test_prepare_urls "$urls"; fi if [ "$mode" = youtube ]; then _test_yt_urls > "$urls"; else _test_prepare_urls "$urls"; fi
[ -n "$svcf" ] && [ -s "$svcf" ] && cat "$svcf" >> "$urls"
total=$(grep -c '^#' "$cand") total=$(grep -c '^#' "$cand")
echo " Стратегий в подборе: $total" >&2 echo " Стратегий в подборе: $total" >&2
# Правила не встали (нет kmod-nft-queue) — каждая стратегия мерилась бы как контроль. # Правила не встали (нет kmod-nft-queue) — каждая стратегия мерилась бы как контроль.
@@ -6400,8 +6481,9 @@ _rb_ztest() { # РЕЖИМ
{ echo "!! Не удалось поставить правила проверки — нужен пакет kmod-nft-queue" >&2; return 1; } { echo "!! Не удалось поставить правила проверки — нужен пакет kmod-nft-queue" >&2; return 1; }
echo " Проверяем, что открывается без Zapret" >&2 echo " Проверяем, что открывается без Zapret" >&2
r="$(_rb_zt_check "$urls")" r="$(_rb_zt_check "$urls")"
echo " открылось ${r% *} из ${r#* }" >&2 set -- $r; ok=$1 tot=$2 sv=$3
echo "Контрольный тест (Zapret выключен) → ${r% *}/${r#* }" > "$res" echo " открылось $ok из $tot" >&2
echo "Контрольный тест (Zapret выключен) → $ok/$tot · сервисы $sv" > "$res"
lines=$(grep -n '^#' "$cand" | cut -d: -f1) lines=$(grep -n '^#' "$cand" | cut -d: -f1)
for start in $lines; do for start in $lines; do
_rb_stopped && break _rb_stopped && break
@@ -6412,30 +6494,128 @@ _rb_ztest() { # РЕЖИМ
name=$(head -n1 "$RB_RUN/zt.block"); name="${name#\#}" name=$(head -n1 "$RB_RUN/zt.block"); name="${name#\#}"
echo " [$cur/$total] $name" >&2 echo " [$cur/$total] $name" >&2
r="$(_rb_zt_one "$RB_RUN/zt.block" "$urls")" r="$(_rb_zt_one "$RB_RUN/zt.block" "$urls")"
ok="${r% *}"; tot="${r#* }" set -- $r; ok=$1 tot=$2 sv=${3:-0}
if [ "$ok" = -1 ]; then echo " не запустилась" >&2; continue; fi if [ "$ok" = -1 ]; then echo " не запустилась" >&2; continue; fi
echo " открылось $ok из $tot" >&2 echo " открылось $ok из $tot" >&2
echo "$name → $ok/$tot" >> "$res" echo "$name → $ok/$tot · сервисы $sv" >> "$res"
done done
_rb_zt_down _rb_zt_down
# По числу удач, лучшие первыми. Разделитель «→» многобайтный — sort -t его не примет. # Лучшие первыми: сначала по общему счёту, при равном — по адресам неоткрывшихся сервисов. Ключ —
{ head -n1 "$res"; tail -n +2 "$res" | awk '{ n = $0; sub(/.* → /, "", n); split(n, a, "/"); print a[1] "\t" $0 }' | # одно число: sort в busybox без модификаторов ключа, а «→» многобайтный для sort -t.
{ head -n1 "$res"; tail -n +2 "$res" | awk '{ n = $0; sub(/.* → /, "", n); split(n, a, "/"); v = $0; sub(/.* · сервисы /, "", v); print (a[1] * 10000 + v) "\t" $0 }' |
sort -rn | cut -f2-; } > "$res.s" && mv "$res.s" "$res" sort -rn | cut -f2-; } > "$res.s" && mv "$res.s" "$res"
echo "$res" echo "$res"
} }
# ── равные лучшие: в фоне, после подбора ──
#
# Бонус, а не часть подбора: к этому моменту победитель применён и у человека всё работает.
# Здесь только выясняется, нет ли среди равных такой, у которой поток не рвётся, — и если есть,
# стратегия меняется молча. Всё в той же изоляции, что и подбор; прерывание безопасно, потому
# что настройка меняется одним движением в конце. Новый подбор или переустановка фон просто
# снимают.
RB_DEEP_NOTE="$RB_DIR/deep.note"
_rb_deep_stop() {
_job_alive redbtn_deep || return 0
_test_kill_pid_tree "$(cat "$JOBS_DIR/redbtn_deep.pid" 2>/dev/null)"
_rb_zt_down
pkill -f "qnum=$RB_ZT_QUEUE" 2>/dev/null
rm -f "$JOBS_DIR/redbtn_deep.pid"
}
do_redbtn_deep() { # ПРИМЕНЁННАЯ
local cur="$1" win sum before after
[ -s "$RB_RUN/deep.res" ] && [ -s "$RB_RUN/deep.cand" ] || return 0
sum="$(md5sum "$CONF" | cut -d' ' -f1)"
RB_PROBE_DOH="$(_rb_doh_pick)"
win="$(_rb_zt_deep_pick "$RB_RUN/deep.res")"
rm -f "$RB_RUN/deep.res" "$RB_RUN/deep.cand"
[ -n "$win" ] && [ "$win" != "$cur" ] || { echo "Оставляем $cur"; return 0; }
# За это время настройку могли сменить руками или тестером — тогда чужое не трогаем.
[ "$(md5sum "$CONF" | cut -d' ' -f1)" = "$sum" ] || { echo "Настройку Zapret успели сменить — не трогаем"; return 0; }
_rb_test_running && return 0
_rb_running && return 0
cp "$CONF" "$RB_RUN/deep.bak"
before="$(_rb_count_ok "$(_rb_check_all as_is)")"
_rb_zapret_apply "$win" || { rm -f "$RB_RUN/deep.bak"; return 0; }
after="$(_rb_count_ok "$(_rb_check_all as_is)")"
if [ "$after" -lt "$before" ]; then
cp "$RB_RUN/deep.bak" "$CONF"; zapret_restart
echo "С $win открывается меньше сервисов — вернули $cur"
else
echo "Переключили на $win"
printf '%s\n' "Лучшая стратегия: $win" > "$RB_DEEP_NOTE"
fi
rm -f "$RB_RUN/deep.bak"
}
_rb_result_svc() { printf '%s\n' "$1" | sed -n 's/.* · сервисы \([0-9]*\)$/\1/p'; }
# Цели неоткрывшихся сервисов для подбора: обязательные и дополнительные адреса каждого.
_rb_svc_targets() { # "id id..." ФАЙЛ
local id h
: > "$2"
for id in $1; do
for h in $(_rb_svc_field "$id" 5 | tr ',' ' ') $(_rb_svc_field "$id" 6 | tr ',' ' '); do
printf 'svc:%s|https://%s/\n' "$h" "$h" >> "$2"
done
done
}
_rb_zt_ties() { # ФАЙЛ_РЕЗУЛЬТАТОВ -> имена равных первому, не больше RB_DEEP_MAX
local key
key="$(tail -n +2 "$1" | head -n1)"; key="${key#* → }"
tail -n +2 "$1" | awk -v k="$key" '{ v = $0; sub(/^.* → /, "", v) } v == k { sub(/ → .*/, ""); print }' | head -n "$RB_DEEP_MAX"
}
# Равные лучшие — тот же общий счёт и те же адреса сервисов, что у первого. Если их больше
# одного, решает углублённая проверка: печатает имя победителя (или первого, если проверить не
# удалось или все набрали одинаково). Первый — применённый: другой побеждает, только набрав
# строго больше.
_rb_zt_deep_pick() { # ФАЙЛ_РЕЗУЛЬТАТОВ
local res="$1" best key ties n name blk="$RB_RUN/zt.dblock" tgt="$RB_RUN/zt.deep" r sc top=-1 win
best=$(tail -n +2 "$res" | head -n1)
win="${best%% → *}"
ties="$(_rb_zt_ties "$res")"
n=$(printf '%s\n' "$ties" | grep -c .)
[ "$n" -gt 1 ] || { echo "$win"; return 0; }
echo "Сравниваем $n равные стратегии" >&2
_rb_zt_deep_targets "$tgt" || { echo " список для сравнения не скачался — оставляем применённую" >&2; echo "$win"; return 0; }
_rb_zt_rules_up || { echo "$win"; return 0; }
printf '%s\n' "$ties" > "$RB_RUN/zt.ties"
while IFS= read -r name; do
_rb_stopped && break
_rb_zt_block "$name" "$blk" "$RB_RUN/deep.cand" || continue
echo " $name" >&2
r="$(_rb_zt_one "$blk" "$tgt" _rb_zt_deep)"
sc="${r%% *}"
[ "$sc" = -1 ] && { echo " не запустилась" >&2; continue; }
echo " $sc из ${r#* }" >&2
[ "$sc" -gt "$top" ] && { top="$sc"; win="$name"; }
done < "$RB_RUN/zt.ties"
_rb_zt_down
rm -f "$RB_RUN/zt.ties" "$blk" "$tgt"
echo "$win"
}
_rb_result_num() { printf '%s\n' "$1" | sed -n 's/.* → \([0-9]*\)\/.*/\1/p'; } _rb_result_num() { printf '%s\n' "$1" | sed -n 's/.* → \([0-9]*\)\/.*/\1/p'; }
# Печатает «имя удачи» лучшей стратегии, если она открыла больше, чем без Zapret. # Печатает «имя удачи» лучшей стратегии, если она открыла больше, чем без Zapret: больше общих
_rb_zapret_pick() { # РЕЖИМ_ТЕСТА # целей или, при равных, больше адресов неоткрывшихся сервисов.
local res best ctrl bok _rb_zapret_pick() { # РЕЖИМ_ТЕСТА [ЦЕЛИ_СЕРВИСОВ]
res="$(_rb_ztest "$1")" || return 1 local res best ctrl bok bsv csv name
res="$(_rb_ztest "$1" "${2:-}")" || return 1
best=$(tail -n +2 "$res" | head -n1) best=$(tail -n +2 "$res" | head -n1)
[ -n "$best" ] || return 1 [ -n "$best" ] || return 1
bok=$(_rb_result_num "$best") bok=$(_rb_result_num "$best"); bsv=$(_rb_result_svc "$best")
ctrl=$(_rb_result_num "$(head -n1 "$res")") ctrl=$(_rb_result_num "$(head -n1 "$res")"); csv=$(_rb_result_svc "$(head -n1 "$res")")
[ -n "$bok" ] || return 1 [ -n "$bok" ] || return 1
[ -n "$ctrl" ] && [ "$bok" -le "$ctrl" ] && return 1 if [ -n "$ctrl" ]; then
[ "$bok" -lt "$ctrl" ] && return 1
[ "$bok" -eq "$ctrl" ] && [ "${bsv:-0}" -le "${csv:-0}" ] && return 1
fi
cp "$res" "$RB_RUN/deep.res"; cp "$RB_RUN/zt.cand" "$RB_RUN/deep.cand"
echo "${best%% → *} $bok" echo "${best%% → *} $bok"
} }
@@ -6677,11 +6857,13 @@ redbtn_geo_watch() {
# ── основной проход ── # ── основной проход ──
do_redbtn_run() { do_redbtn_run() {
local mode="${1:-quick}" blk before after failed zfailed id zbak="$RB_RUN/zapret.before" pick name warp_ids="" local mode="${1:-quick}" blk before after failed zfailed id zbak="$RB_RUN/zapret.before" pick name warp_ids="" deep_cur=""
# Все три флага: «Стоп» ставит и флаг страницы steer, и без его снятия следующий подбор # Все три флага: «Стоп» ставит и флаг страницы steer, и без его снятия следующий подбор
# сразу выходил из подъёма туннелей. # сразу выходил из подъёма туннелей.
rm -f "$RB_STOP_FLAG" "$ST_STOP_FLAG" "$TEST_STOP_FLAG" rm -f "$RB_STOP_FLAG" "$ST_STOP_FLAG" "$TEST_STOP_FLAG"
_rb_deep_stop
mkdir -p "$RB_DIR" "$RB_RUN" mkdir -p "$RB_DIR" "$RB_RUN"
rm -f "$RB_RUN/deep.res" "$RB_RUN/deep.cand" "$RB_DEEP_NOTE"
_ensure_deps _ensure_deps
blk="$(_rb_blocker)" blk="$(_rb_blocker)"
case "$blk" in case "$blk" in
@@ -6713,17 +6895,21 @@ do_redbtn_run() {
# Подбор стратегии — только если что-то не открывается: рабочую настройку не трогаем. # Подбор стратегии — только если что-то не открывается: рабочую настройку не трогаем.
# ИИ-сервисы не в счёт: их закрывает геоблок, а его стратегия Zapret не снимает — ради них # ИИ-сервисы не в счёт: их закрывает геоблок, а его стратегия Zapret не снимает — ради них
# одних гонять тестер десять минут бессмысленно, им сразу WARP. # одних гонять тестер десять минут бессмысленно, им сразу WARP.
# Полный подбор человек выбирает сам — обычно после быстрого, когда что-то ушло в WARP или не
# открылось вовсе. Он идёт всегда: иначе после быстрого подбора, где всё уже открывается
# (в том числе через WARP), стратегии Flowseal не проверялись бы ни разу.
failed="$(_rb_failed "$before")" failed="$(_rb_failed "$before")"
zfailed="$(printf '%s\n' "$failed" | grep -vx 'geoblock')" zfailed="$(printf '%s\n' "$failed" | grep -vx 'geoblock')"
if [ -n "$zfailed" ]; then if [ -n "$zfailed" ] || [ "$mode" = full ]; then
_rb_phase zapret _rb_phase zapret
cp "$CONF" "$zbak" cp "$CONF" "$zbak"
local tmode=v local tmode=v svcf="$RB_RUN/zt.svc"
[ "$mode" = full ] && tmode=v_flowseal [ "$mode" = full ] && tmode=v_flowseal
_rb_svc_targets "$zfailed" "$svcf"
_rb_say "Подбираем стратегию Zapret — сеть работает на прежней настройке до конца подбора" _rb_say "Подбираем стратегию Zapret — сеть работает на прежней настройке до конца подбора"
# Подбор идёт в изоляции: клиенты до конца на прежней настройке. Неполный победитель # Подбор идёт в изоляции: клиенты до конца на прежней настройке. Неполный победитель
# остановленного подбора не применяется. # остановленного подбора не применяется.
if pick="$(_rb_zapret_pick "$tmode")" && ! _rb_stopped; then if pick="$(_rb_zapret_pick "$tmode" "$svcf")" && ! _rb_stopped; then
name="${pick% *}" name="${pick% *}"
_rb_say "Лучшая стратегия: $name (${pick##* } целей)" _rb_say "Лучшая стратегия: $name (${pick##* } целей)"
if _rb_zapret_apply "$name"; then if _rb_zapret_apply "$name"; then
@@ -6746,6 +6932,7 @@ do_redbtn_run() {
else else
before="$after" before="$after"
_rb_say "Стратегия $name применена" _rb_say "Стратегия $name применена"
deep_cur="$name"
fi fi
fi fi
elif ! _rb_stopped; then elif ! _rb_stopped; then
@@ -6859,6 +7046,15 @@ do_redbtn_run() {
_rb_warn "DNS over HTTPS перехватывает DNS сети — сервисы через WARP работать не будут, нажмите «Исправить» на странице Steer" _rb_warn "DNS over HTTPS перехватывает DNS сети — сервисы через WARP работать не будут, нажмите «Исправить» на странице Steer"
fi fi
_rb_say "Готово, обход подобран" _rb_say "Готово, обход подобран"
# Равные лучшие сравниваются уже после — обход к этому моменту работает.
if [ -n "$deep_cur" ] && [ -s "$RB_RUN/deep.res" ] && [ "$(_rb_zt_ties "$RB_RUN/deep.res" | grep -c .)" -gt 1 ]; then
# Правила проб снимаются ловушкой на выходе, а с ними и таблица изоляции — снимаем
# сейчас, чтобы ловушка не выдернула её из-под фоновой проверки.
_rb_probe_nft_down
trap - EXIT INT TERM
job_start redbtn_deep do_redbtn_deep "$deep_cur" >/dev/null
_rb_say "Лучшая стратегия определится позже — идёт сравнение равных"
fi
} }
@@ -6924,8 +7120,11 @@ redbtn_status() {
done done
local geo="" local geo=""
[ -s "$RB_GEO_RESOLVER" ] && geo=$(sed -n "s/^GEO_TITLE='\(.*\)'$/\1/p" "$RB_GEO_RESOLVER") [ -s "$RB_GEO_RESOLVER" ] && geo=$(sed -n "s/^GEO_TITLE='\(.*\)'$/\1/p" "$RB_GEO_RESOLVER")
printf '{"running":%s,"phase":"%s","last":"%s","blocker":"%s","steer":%s,"steer_installed":%s,"steer_busy":%s,"video":"%s","geo_dns":"%s","configured":%s,"services":[%s]}\n' \ local deep=false note=""
"$running" "$(esc "$phase")" "$last" "$blk" "$steer" "$(_st_installed && echo true || echo false)" "$(_job_alive steer && echo true || echo false)" "$(esc "$video")" "$(esc "$geo")" \ _job_alive redbtn_deep && deep=true
[ -s "$RB_DEEP_NOTE" ] && note="$(head -n1 "$RB_DEEP_NOTE")"
printf '{"running":%s,"deep":%s,"deep_note":"%s","phase":"%s","last":"%s","blocker":"%s","steer":%s,"steer_installed":%s,"steer_busy":%s,"video":"%s","geo_dns":"%s","configured":%s,"services":[%s]}\n' \
"$running" "$deep" "$(esc "$note")" "$(esc "$phase")" "$last" "$blk" "$steer" "$(_st_installed && echo true || echo false)" "$(_job_alive steer && echo true || echo false)" "$(esc "$video")" "$(esc "$geo")" \
"$([ -s "$RB_LAST" ] && echo true || echo false)" "$svc" "$([ -s "$RB_LAST" ] && echo true || echo false)" "$svc"
} }
@@ -8381,6 +8580,8 @@ return view.extend({
return; return;
} }
if (!busy && data.steer_busy) heroCard.appendChild(E('div', { 'class': 'zm-ab-note zm-ab-note-warn' }, 'На странице Steer идёт операция — запуск станет доступен, когда она закончится.')); if (!busy && data.steer_busy) heroCard.appendChild(E('div', { 'class': 'zm-ab-note zm-ab-note-warn' }, 'На странице Steer идёт операция — запуск станет доступен, когда она закончится.'));
if (!busy && data.deep) heroCard.appendChild(E('div', { 'class': 'zm-ab-note zm-st-note-ok' }, 'Лучшая стратегия определится позже — идёт сравнение равных.'));
else if (!busy && data.deep_note) heroCard.appendChild(E('div', { 'class': 'zm-ab-note zm-st-note-ok' }, data.deep_note));
if (busy) { if (busy) {
if (RUN_PHASES.indexOf(data.phase) >= 0 || lastAction === 'start') heroCard.appendChild(renderSteps()); if (RUN_PHASES.indexOf(data.phase) >= 0 || lastAction === 'start') heroCard.appendChild(renderSteps());