Skip to content

fix(sni): синхронизация профилей без routes и пересборка списка SNI по замеру 03.09.2026 - #20

Open
Inhibit0r wants to merge 1 commit into
howdeploy:mainfrom
Inhibit0r:fix/sni-management
Open

fix(sni): синхронизация профилей без routes и пересборка списка SNI по замеру 03.09.2026#20
Inhibit0r wants to merge 1 commit into
howdeploy:mainfrom
Inhibit0r:fix/sni-management

Conversation

@Inhibit0r

Copy link
Copy Markdown

Что здесь

Три независимые правки вокруг SNI, одним коммитом.


1. Баг jq: смена SNI молча не доезжала до легаси-профилей

update_all_profiles_on_port() обновляла профили выражением:

(.routes // []) |= map(if .port == $port then .[$f] = $v else . end)

Левая часть |= обязана быть путевым выражением, а (.routes // []) им не является. На профиле, у которого поля routes нет вовсе (легаси: один маршрут, .port/.sni в корне), jq падает:

jq: error (at <stdin>:1): Invalid path expression with result []

safe_jq_write глушит stderr (2>/dev/null), так что причина не видна, файл не меняется, и профиль остаётся со старым SNI, пока inbound уже переехал на новый. Клиент после этого не подключается.

Заменено на:

if has("routes") then .routes |= map(if .port == $port then .[$f] = $v else . end) else . end

Тот же паттерн нашёлся в update_all_profiles_port_reference() — он ломал перенос порта на ровно тех же профилях. Починены оба места, иначе правка закрывает половину дырки.

Проверка, jq 1.7.1:

выражение объект с routes объект без routes
старое ok Invalid path expression with result [], exit 5
новое ok ok, exit 0

Регрессия закреплена: validation/test-legacy-profile-port-sync.sh — на непатченном main падает (✗ Ошибка jq — файл НЕ изменён), с патчем проходит.


2. Жёстко зашитый SNI у tcp-xudp — уже исправлен в main

Литерал "api-maps.yandex.ru" в вызове _append_route_json для метки tcp-xudp в текущем origin/main отсутствует: коммит d9a4462 переписал его на $xudp_sni, а пункт меню вызывает create_profile с пустым SNI, то есть тоже уходит в _default_sni_for_transport. Ничего изобретать не стал.

Для протокола, почему уместна именно $xudp_sni, а не соседки:

  • xudp_sni = _default_sni_for_transport "tcp-xudp" — уважает self-steal домен (_selfsteal_domain_value), а без него отдаёт закреплённый за XUDP api-maps.yandex.ru;
  • default_sni берётся для транспорта tcp, то есть первую строку sni_list.txt — сегодня она совпадает, но развалится при следующей перестановке списка;
  • grpc_sni жёстко www.cloudflare.com ради стабильного HTTP/2 — к XUDP отношения не имеет.

Зато живой остаток той же болезни в main нашёлся и починен: аварийный дефолт в _default_sni_for_transport

[[ -z "$from_list" ]] && from_list="www.ozon.ru"

срабатывает, когда sni_list.txt пуст или недоступен, и выдавал новым профилям измеренно заблокированный SNI. Теперь api-maps.yandex.ru.


3. sni_list.txt: список был не просто устаревшим, а вредным

Шапка утверждала «БЕЛЫЙ СПИСОК РФ — РКН их не блокирует, использовать В ПЕРВУЮ ОЧЕРЕДЬ» и ставила в приоритет 1 крупный РФ e-commerce и банки. Замер показал обратное: маскировка именно под них самая палевная.

Замер 03.09.2026, MegaFon (Москва), TLS-рукопожатие к тестовый VPS:

  • не проходят: www.ozon.ru, www.wildberries.ru, www.tinkoff.ru, cdn.jsdelivr.net
  • проходят: www.cloudflare.com, api-maps.yandex.ru, www.microsoft.com, www.samsung.com, swcdn.apple.com, dl.google.com, gateway.icloud.com, yandex.ru, www.lovelive-anime.jp
  • www.microsoft.com рукопожатие проходит, но по долям трафика на MegaFon деградировал: 11 соединений против 236 у www.samsung.com при сопоставимом общем объёме → как маскировка почти бесполезен, оставлен в приоритете 3 с пометкой.
  • как dest не годятся и потому не добавлены: mail.ru (только TLS 1.2, Reality требует 1.3) и cdn-telegram.org (не резолвится). Оба факта записаны в шапку файла.

Что сделано:

  • приоритет 1 — только прошедшие замер: api-maps.yandex.ru, yandex.ru, www.samsung.com, dl.google.com, swcdn.apple.com, gateway.icloud.com, www.cloudflare.com, www.lovelive-anime.jp;
  • приоритет 2 — не замерялись и не из «палевного» класса: Яндекс-CDN, VK, госпорталы, медиа, логистика;
  • приоритет 3 — иностранные без замера + деградировавший www.microsoft.com;
  • приоритет 4 — три провалившихся домена (перекатегоризованы в fallback), остальной крупный РФ e-commerce и банки того же класса, прежний fallback;
  • в шапке — дата замера, оператор, IP и прямое предупреждение: фильтрация различается по оператору, региону и дню, перед применением перепроверять (probe test или openssl s_client -servername).

Формат домен|категория|приоритет и четыре категории сохранены, структура файла тоже. Ни один домен не удалён, добавлены только 7 имён из замера. Порядок здесь не косметика: первая незакомментированная строка — это SNI по умолчанию для новых профилей (_default_sni_for_transport), а первые 20 строк — меню выбора SNI.


Сопутствующее — без него правка 3 неполна

  • install.sh — аварийный embedded-список SNI (пишется, когда curl не смог скачать sni_list.txt) начинался с www.ozon.ru. Это вторая копия тех же вредных данных: починить только sni_list.txt значило оставить установку при сетевом сбое на заблокированном SNI. Заменён на верх нового списка.
  • _migrate_sni_list_2026 — сверяет строки с shipped-файлом через grep -Fxq по полной строке. После смены приоритетов совпадение бы пропало, и миграция на каждом сервере без маркера впустую переписывала бы файл и дёргала safe_restart_xray. Приоритеты в массиве приведены к новому файлу; проверено, что все 6 строк снова находятся. Заодно проверено, что swcdn.apple.com / gateway.icloud.com не попадают под удаление apple/icloud в этой миграции (условие требует членства в KNOWN_DEFAULTS_v1, где их нет).
  • change_sni_menu — у трёх провалившихся замер доменов описания «макс. устойчивость для мобильных» и «стабильный вариант» стали прямой дезинформацией, заменены на факт замера; добавлены описания для доменов нового приоритета 1, иначе верх меню рендерился бы как «Дополнительный вариант». Убран дубль ключа sni_desc["api-maps.yandex.ru"].

Проверки

bash -n xrayebator install.sh update.sh uninstall.sh   → чисто (4/4)

validation/test-*.sh на macOS, bash 3.2.57:

baseline (чистый origin/main) после правок
test-bbr-removal-migration.sh FAIL(1) FAIL(1)
test-cascade-upstream-import.sh FAIL(1), declare -A FAIL(1)
test-main-menu-numbering.sh FAIL(127), mapfile FAIL(127)
test-legacy-profile-port-sync.sh — (новый) PASS
остальные 13 PASS PASS

Список падающих совпадает с baseline — это ограничения bash 3.2 на macOS (declare -A, mapfile), а не следствие правок. На Linux с bash 5 они не воспроизводятся.

Целостность sni_list.txt: строк не по формату — 0, потерянных доменов — 0, дубликатов — 0, всего 51 запись.

Не проверено: сами замеры ТСПУ не воспроизводились в этом PR — порядок доменов взят из полевых данных 03.09.2026 как есть. Прогона create/delete профиля на живом сервере тоже не было, только bash -n и validation-набор.


🤖 Generated with Claude Code

Три независимые правки, все по SNI.

1) update_all_profiles_on_port и update_all_profiles_port_reference падали на
   профилях без поля routes. Левая часть |= обязана быть путевым выражением, а
   `(.routes // []) |=` им не является: jq выдаёт "Invalid path expression with
   result []", safe_jq_write глушит stderr — и легаси-профиль (один маршрут,
   .port/.sni в корне) молча оставался со старым SNI или портом. Заменено на
   `if has("routes") then .routes |= ... else . end`. Регрессия закрыта
   validation/test-legacy-profile-port-sync.sh: на непатченном main тест падает
   ("Ошибка jq — файл НЕ изменён"), с патчем проходит.

2) sni_list.txt пересобран по замеру 03.09.2026, MegaFon (Москва),
   TLS-рукопожатие к 85.215.189.108. Прежняя шапка утверждала «РКН их не
   блокирует — использовать в первую очередь» про крупный РФ e-commerce и банки;
   замер показал обратное — маскировка под них самая палевная.
   Не проходят: www.ozon.ru, www.wildberries.ru, www.tinkoff.ru,
   cdn.jsdelivr.net. Первые три понижены до приоритета 4 и перекатегоризованы в
   fallback; крупный РФ e-commerce и банки того же класса (ozon/wb-поддомены,
   sberbank, alfabank, nspk, uralsib, vtb, avito) тоже уехали в 4.
   Проходят и подняты в приоритет 1: api-maps.yandex.ru, yandex.ru,
   www.samsung.com, dl.google.com, swcdn.apple.com, gateway.icloud.com,
   www.cloudflare.com, www.lovelive-anime.jp. Первая строка файла = SNI по
   умолчанию для новых профилей, теперь это api-maps.yandex.ru.
   www.microsoft.com рукопожатие проходит, но деградировал по долям трафика на
   MegaFon: 11 соединений против 236 у www.samsung.com при сопоставимом объёме —
   оставлен в приоритете 3 с пометкой.
   mail.ru (только TLS 1.2 при требуемом Reality TLS 1.3) и cdn-telegram.org
   (не резолвится) как dest не годятся и не добавлены.
   В шапке файла теперь дата замера и предупреждение: фильтрация различается по
   оператору, региону и дню, перед применением перепроверять (probe test).
   Ни один домен из списка не удалён, добавлены только имена из замера.

3) Жёстко зашитый SNI у маршрута tcp-xudp: в текущем main уже исправлен
   (d9a4462), _append_route_json "tcp-xudp" получает $xudp_sni из
   _default_sni_for_transport, литерала там нет. Живой остаток той же проблемы —
   аварийный дефолт from_list="www.ozon.ru" в _default_sni_for_transport,
   срабатывающий при пустом или недоступном sni_list.txt: он выдавал новым
   профилям заблокированный SNI. Заменён на api-maps.yandex.ru, прошедший
   тот же замер.

Сопутствующее, без чего правка 2 неполна:
- install.sh: аварийный список SNI (пишется, когда curl не смог скачать
  sni_list.txt) начинался с www.ozon.ru — заменён на верх нового списка.
- _migrate_sni_list_2026: строки to_add сверены с новым sni_list.txt, иначе
  grep -Fxq считал бы список устаревшим и переписывал файл с лишним рестартом
  Xray.
- change_sni_menu: добавлены описания доменов нового приоритета 1; у трёх
  провалившихся замер описания «макс. устойчивость» и «стабильный вариант»
  заменены на факт замера.

Проверено: bash -n для xrayebator, install.sh, update.sh, uninstall.sh — чисто.
validation/test-*.sh на macOS (bash 3.2) падают тремя тестами
(test-bbr-removal-migration, test-cascade-upstream-import — declare -A,
test-main-menu-numbering — mapfile); тот же список падает на чистом origin/main,
это не следствие правок.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant