diff --git a/ZapretManager_LuCI.sh b/ZapretManager_LuCI.sh index 9fcecd9..7e1c535 100644 --- a/ZapretManager_LuCI.sh +++ b/ZapretManager_LuCI.sh @@ -4847,7 +4847,7 @@ ST_CRON_CMD="/etc/init.d/steer enabled && { for i in zmwarp zmwarp2 zmwarp3; do # страница, его ключи и точка переживают обновление. У каждого своя регистрация: одни ключи # на трёх устройствах Cloudflare считает одним клиентом. ST_WARP_N=3 -ST_WARP_UP="$ST_DIR/warp.up" # поднятые туннели «интерфейс колония», лучший первым +ST_WARP_UP="$ST_DIR/warp.up" # поднятые туннели «интерфейс колония», лучший первым (_st_warp_order) # Российские колонии Cloudflare: выход у них внутри страны, за теми же ТСПУ, что и без туннеля, — # через них не снимается ни геоблок, ни остальные блокировки. Берутся только в запас. ST_RU_COLOS="DME SVX LED KJA REN OVB KZN AER VVO" @@ -5140,19 +5140,6 @@ _st_warp_zone() { fi } -_st_warp_hs_epoch() { awg show "$ST_WARP_IF" latest-handshakes 2>/dev/null | awk '{print $2}' | head -n1; } - -_st_warp_handshake() { # ждать рукопожатия до 10 с - local i=0 hs - while [ "$i" -lt 10 ]; do - hs=$(_st_warp_hs_epoch) - [ -n "$hs" ] && [ "$hs" != 0 ] && return 0 - ping -c1 -W1 -I "$ST_WARP_IF" 1.1.1.1 >/dev/null 2>&1 - i=$((i + 1)) - done - return 1 -} - _st_warp_is_ru() { case " $ST_RU_COLOS " in *" $1 "*) return 0 ;; esac; return 1; } # ── несколько туннелей ── @@ -5229,9 +5216,9 @@ _st_colo_of() { # ИНТЕРФЕЙС sed -n 's/^colo=//p' | head -n1) [ -n "$c" ] && echo "$c" } -_st_warp_colo() { _st_colo_of "$ST_WARP_IF"; } -# Какие порты провайдер выпускает к WARP — один раз, дальше из файла. +# Какие порты провайдер выпускает к WARP — один раз, дальше из файла. Ищутся два: разведка +# ведёт все адреса первым, второй — запасной при окончательной привязке точки (_st_warp_up). _st_warp_ports() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА АДРЕС local f="$ST_DIR/warp.ports" ok="" p [ -s "$f" ] && { cat "$f"; return 0; } @@ -5245,10 +5232,16 @@ _st_warp_ports() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА АДРЕС echo "$ok" } -# Разведка для одного туннеля. Печатает «адрес порт колония» лучшей точки. Занятая колония — -# запас (другой адрес в той же колонии лучше никакого), российская — последний запас. +# Разведка для одного туннеля. Печатает «адрес порт колония ключ» лучшей точки. Занятая +# колония — запас (другой адрес в той же колонии лучше никакого), российская — последний запас. +# +# Ключ — качество точки одним числом, меньше — лучше: разряд запаса (0 — наружная колония, +# своя или занятая соседом; 1 — колония не узналась; 2 — российская; 3 — HTTPS не проходит), +# за ним потери и круг. По нему _st_warp_order ставит туннели в warp.up лучшим первым. Занятая +# колония в разряде не отличается от свободной: «занята соседом» важно, когда точку выбирают +# для одного туннеля, а сравнивать туннели между собой — это уже только связь. _st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛОНИИ ЗАНЯТЫЕ_АДРЕСА - local dev="$1" peer="$2" busy=" $3 " busyip=" $4 " r="$ST_RUN/scan.$1" cand ip ports port loss rtt colo pick f + local dev="$1" peer="$2" busy=" $3 " busyip=" $4 " r="$ST_RUN/scan.$1" cand ip ports port loss rtt colo pick f cls mkdir -p "$ST_RUN" : > "$r"; : > "$r.same"; : > "$r.nc"; : > "$r.ru"; : > "$r.notls" # Якоря .1 первыми: на них указывает сам engage.cloudflareclient.com; случайные — за @@ -5295,7 +5288,9 @@ _st_warp_scan() { # ИНТЕРФЕЙС КЛЮЧ_УЗЛА ЗАНЯТЫЕ_КОЛО # не понимает модификаторов у ключей, см. brb/warp.sh). for f in "$r" "$r.same" "$r.nc" "$r.ru" "$r.notls"; do [ -s "$f" ] || continue - pick=$(awk '{ printf "%d\t%s %s %s\n", $1 * 100000 + $2, $3, $4, $5 }' "$f" | sort -n | head -n1 | cut -f2) + case "$f" in *.nc) cls=1 ;; *.ru) cls=2 ;; *.notls) cls=3 ;; *) cls=0 ;; esac + pick=$(awk -v c="$cls" '{ k = c * 100000000 + $1 * 100000 + $2; printf "%d\t%s %s %s %d\n", k, $3, $4, $5, k }' "$f" | + sort -n | head -n1 | cut -f2) case "$f" in *.same) echo " другой колонии нет — та же, но через другой адрес" >&2 ;; *.ru) echo " наружных колоний нет — беру российскую: блокировки через неё не снимаются" >&2 ;; @@ -5314,13 +5309,56 @@ _st_warp_alive_if() { # ИНТЕРФЕЙС — рукопожатие не ст } _st_warp_ready_if() { ifstatus "$1" 2>/dev/null | grep -q '"up": true' && awg show "$1" peers 2>/dev/null | grep -q .; } +# Порядок туннелей в warp.up — лучший первым. Порядок здесь не для красоты: его читают все, +# кому из нескольких туннелей нужен один или кто перебирает их по очереди. +# - _st_warp_first: через первый живой пробуются сервисы, качаются списки, идёт самопроверка. +# - Мост Telegram (warp.lst, см. _st_tgws_warp): берёт первый живой туннель из списка и к +# следующему переходит, только когда тот не дал даже TCP, — порядок для него и есть выбор. +# - Движок (devices в spec, prefer: latency): туннели он меряет сам, но замеры в пределах +# допуска (50 мс) считает равными и из равных берёт стоящий выше, — порядок решает ничьи, а +# когда замерить не вышло ни одного, выбор идёт просто по порядку. +# +# Ключ — от разведки (см. _st_warp_scan). У туннеля, оставленного живым, свежего замера нет, и +# дальше два случая. +# - Разведки не было ни у одного — туннели остаются на прежних местах. Переставлять их по +# трём пингам значило бы на каждом «Применить» и каждом прогоне автообхода тасовать список +# по дрожанию задержки, а новый порядок — это другой spec и перезапуск движка с обрывом +# соединений ради ничего. +# - Хоть один разведан заново — список всё равно меняется, и оставленным меряется то же, что +# мерила разведка (потери и круг тремя пингами изнутри живого туннеля, сессию это не +# трогает), с разрядом запаса 0: колония у них узналась и она не российская, иначе туннель +# ушёл бы в разведку. Без замера новичка не с чем было бы сравнить. +# При равных ключах держится порядок строк, то есть номеров туннелей. +_st_warp_order() { # ФАЙЛ «интерфейс колония ключ|-» -> тот же файл «интерфейс колония» + local f="$1" i c k pos=0 fresh=0 + grep -q ' [0-9][0-9]*$' "$f" && fresh=1 + : > "$f.key" + while read -r i c k; do + [ -n "$i" ] || continue + pos=$((pos + 1)) + if [ "$fresh" = 0 ]; then + # Прежнее место; туннеля, которого в прошлом списке не было, — в хвост по номеру. + k=$(awk -v i="$i" '$1 == i { print NR; exit }' "$ST_WARP_UP" 2>/dev/null) + [ -n "$k" ] || k=$((100 + pos)) + elif [ "$k" = - ]; then + set -- $(_st_warp_probe "$i") + k=$(($1 * 100000 + $2)) + fi + # Ключ и номер строки — нулями до одной ширины, чтобы простой sort сравнил их как числа + # (busybox sort не понимает модификаторов у ключей, см. brb/warp.sh). + printf '%012d %03d %s %s\n' "$k" "$pos" "$i" "$c" >> "$f.key" + done < "$f" + sort "$f.key" | awk '{ print $3, $4 }' > "$f" + rm -f "$f.key" +} + # Поднять туннели. repick — не держаться прежних точек, подобрать заново. # # Сначала ключи и интерфейсы ВСЕХ туннелей одним reload, и только потом разведка на живых # интерфейсах через `awg set`: запись в uci с ifup посреди разведки делала `network reload`, и # netifd перезапускал соседние туннели (на стенде так пропадал уже подобранный первый). _st_warp_up() { # [repick] - local repick="$1" n i peer got busy="" busyip="" ru=0 c ready="" w need=0 colos="" + local repick="$1" n i peer got busy="" busyip="" ru=0 c ready="" w need=0 colos="" alt p _st_install_awg || return 1 # Какие порты провайдер выпускает, меняется: при подборе заново спрашиваем снова. [ "$repick" = repick ] && rm -f "$ST_DIR/warp.ports" @@ -5365,7 +5403,9 @@ _st_warp_up() { # [repick] # держаться за неё только потому, что туннель жив, — значит оставить геоблок навсегда # (на тестовом роутере так и вышло: HEL, DME, HEL). Такой туннель разводится заново. if [ "$repick" != repick ] && _st_warp_alive_if "$i" && c="$(_st_colo_of "$i")" && ! _st_warp_is_ru "$c"; then - got="$(uci -q get "network.${i}_peer.endpoint_host") $(uci -q get "network.${i}_peer.endpoint_port") $c" + # Ключа качества нет («-»): свежего замера у оставленного туннеля нет, его место в + # warp.up решает _st_warp_order. + got="$(uci -q get "network.${i}_peer.endpoint_host") $(uci -q get "network.${i}_peer.endpoint_port") $c -" _rb_say "WARP $n работает: колония $c" else echo " WARP $n: разведка" @@ -5375,8 +5415,24 @@ _st_warp_up() { # [repick] # endpoint` нового рукопожатия не начинает — нужен тот же сброс узла, что в # разведке, иначе туннель полминуты молчит и идёт не в ту колонию. if ! _st_warp_link "$i" "$peer" "$1" "$2"; then - _rb_warn "WARP $n: выбранная точка $1:$2 не ответила повторно" - continue + # Запасной порт — второй найденный в warp.ports. Разведка ведёт все адреса + # ОДНИМ портом, первым выпущенным, и точка, ответившая на нём минуту назад, + # может замолчать именно на нём: провайдер режет UDP к WARP по портам, и не + # всегда с первой минуты. Адрес тот же, значит и колония та же — она за + # адресом, не за портом; ключ качества остаётся от разведки. + alt="" + for p in $(cat "$ST_DIR/warp.ports" 2>/dev/null); do + [ "$p" = "$2" ] && continue + _st_stopped && break + _st_warp_link "$i" "$peer" "$1" "$p" && { alt="$p"; break; } + done + if [ -z "$alt" ]; then + _rb_warn "WARP $n: выбранная точка $1:$2 не ответила повторно" + continue + fi + _rb_say "WARP $n: точка $1:$2 не ответила повторно, беру запасной порт $alt" + got="$1 $alt $3 $4" + set -- $got fi _rb_say "WARP $n работает: $1:$2, колония $3" else @@ -5387,8 +5443,7 @@ _st_warp_up() { # [repick] set -- $got busy="$busy $3"; busyip="$busyip $1" _st_warp_is_ru "$3" && ru=1 - colos="${colos:+$colos, }$3" - echo "$i $3" >> "$ST_WARP_UP.tmp" + echo "$i $3 ${4:--}" >> "$ST_WARP_UP.tmp" # В uci — для следующей загрузки. Без reload: живой интерфейс уже на этой точке. uci set "network.${i}_peer.endpoint_host=$1" uci set "network.${i}_peer.endpoint_port=$2" @@ -5400,7 +5455,9 @@ _st_warp_up() { # [repick] return 1 fi uci commit network + _st_warp_order "$ST_WARP_UP.tmp" mv "$ST_WARP_UP.tmp" "$ST_WARP_UP" + colos="$(awk '{ printf "%s%s", (NR > 1 ? ", " : ""), $2 }' "$ST_WARP_UP")" # Не поднявшиеся — погасить: висящий без рукопожатия туннель движок принял бы за живой. for n in $ready; do i="$(_st_wif "$n")"