From 265b9a5f11b70577a13098ba4d297d5347ca5360 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Mon, 2 Mar 2026 01:23:09 +0300 Subject: [PATCH] Create XRAY-SINGBOX-ROUTING.ru.md --- docs/XRAY-SINGBOX-ROUTING.ru.md | 321 ++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 docs/XRAY-SINGBOX-ROUTING.ru.md diff --git a/docs/XRAY-SINGBOX-ROUTING.ru.md b/docs/XRAY-SINGBOX-ROUTING.ru.md new file mode 100644 index 0000000..ba269bb --- /dev/null +++ b/docs/XRAY-SINGBOX-ROUTING.ru.md @@ -0,0 +1,321 @@ +# SNI-маршрутизация в xray-core / sing-box + TLS-fronting + +## Термины (в контексте этого кейса) + +- **TLS-fronting домен** — домен, который фигурирует в TLS ClientHello как **SNI** (например, `petrovich.ru`): он используется как "маска" на L7 и как ключ маршрутизации в прокси-роутере. +- **xray-core / sing-box** — локальный или удалённый L7/TLS-роутер (прокси), который: + 1) принимает входящее TCP/TLS-соединение, + 2) читает TLS ClientHello, + 3) извлекает SNI, + 4) по SNI выбирает outbound/апстрим, + 5) устанавливает новое TCP-соединение к целевому хосту уже **от себя**. +- **SNI (Server Name Indication)** — поле в TLS ClientHello, где клиент Telegram сообщает доменное имя для "маскировки" +- **DNS-resolve на стороне L7-роутера** — если выходной адрес задан доменом (или роутер решил "всё равно идти по SNI"), то DNS резолвится **на стороне xray/sing-box**, а не на стороне Telegram-клиента + +--- + +## Ключевая идея: куда на самом деле идёт соединение решает не то, что вы указали клиенту, а то как L7-роутер трактует SNI + +Механика: + +1) Telegram-клиенту вы можете указать **IP/домен telemt**,как "сервер". +2) Между клиентом и telemt стоит xray-core/sing-box, который принимает TCP, читает TLS ClientHello и видит **SNI=petrovich.ru** +3) Дальше роутер говорит: "Вижу SNI - направить на апстрим/маршрут N" +4) И устанавливает исходящее соединение не "по тому IP, который пользователь подразумевал", а **по домену из SNI** (или по сопоставлению SNI→outbound), используя для определния его IP собственный DNS-кеш или резолвер +5) `petrovich.ru` по A-записи указывает **не на IP telemt**, а значит при L7-маршрутизации трафик уйдёт на "оригинальный" сайт за этим доменом, а не в telemt: Telegram-клиент, естественно, не сможет получить ожидаемое поведение, потому что ответить с handshake на той стороне некому + +--- + +## Схема №1 "Как это НЕ работает" + +```text +Telegram Client + | + | (указан IP/домен telemt) + v +telemt instance +```` + +Ожидание: "я указал telemt -> значит трафик попадёт в telemt" - **нет!** + +--- + +## Схема №2. "Как это реально работает с TLS/L7-роутером и SNI" + +```text +Telegram Client + | + | 1) TCP/TLS connection: + | - ClientHello: + | - SNI=petrovich.ru + v +xray-core / sing-box / любой L7 router + | + | 2) читает ClientHello -> вытаскивает SNI + | 3) выбирает маршрут по SNI + | 4) делает DNS для petrovich.ru + | 5) подключается к полученному IP по TLS с этим SNI + v +"Оригинальный" сайт, A-запись которого не на telemt + | + X не telemt -> Telegram-клиент не коннектится как ожидалось +``` + +--- + +## Почему указанный в клиенте IP/домен telemt "не спасает" + +Потому что в таком режиме xray/sing-box выступает как **точка терминации TCP/TLS**, можно сказать - TLS-инспектор на уровне ClientHello, это означает: + +* TCP-сессия от Telegram-клиента заканчивается на xray/sing-box +* Дальше создаётся **новая** TCP-сессия "от имени" xray/sing-box к апстриму +* Выбор апстрима делается правилами роутинга, а в TLS-сценариях самый удобный и распространённый ключ — **SNI** + +То есть, "куда идти дальше" определяется логикой L7-роутера: + +* либо правилами вида `if SNI == petrovich.ru -> outbound X`, +* либо более "автоматическим" поведением: `подключаться к тому хосту, который указан в SNI`, +* плюс кэш DNS и собственные резолверы роутера + +--- + +## Что именно извлекается из TLS ClientHello и почему этого достаточно + +TLS ClientHello отправляется **в начале** TLS-сессии и, в классическом TLS без ECH, содержит SNI в открытом виде. + +Упрощённо: + +```text +ClientHello: + - supported_versions + - cipher_suites + - extensions: + - server_name: petrovich.ru <-- SNI + - alpn: h2/http1.1/... + - ... +``` + +Роутеру не нужно расшифровывать трафик и завершать TLS "как сервер" — часто достаточно просто прочитать первые пакеты и распарсить ClientHello, чтобы получить SNI и принять решение + +--- + +## Типовой алгоритм SNI-роутинга + +1. Принять входящий TCP. +2. Подождать первые байты. +3. Определить протокол: + + * если видим TLS ClientHello → парсим SNI/ALPN +4. Применить route rules: + + * match по `server_name` / `domain` / `tls.sni` +5. Выбрать outbound: + + * direct / proxy / specific upstream / detour +6. Установить исходящее соединение: + + * либо на фиксированный IP:порт, + * либо на домен через DNS-resolve на стороне роутера +7. Начать проксирование данных между входом и выходом + +--- + +## Почему "A-запись фронтинг-домена не на telemt" ломает кейс + +### Ситуация + +* В ClientHello: `SNI = petrovich.ru` +* DNS: `petrovich.ru -> 203.0.113.77` - "оригинальный" сайт +* telemt живёт на: `198.51.100.10` + +### Что делает роутер + +* Видит SNI `petrovich.ru` +* Либо: + + * (а) напрямую коннектится к `petrovich.ru:443`, резолвя A-запись в `203.0.113.77`, + * либо: + * (б) выбирает outbound, который указывает на `petrovich.ru` как destination, + * либо: + * (в) делает sniffing/override destination по SNI + +В итоге исходящий коннект идёт на `203.0.113.77:443`, а не на telemt! +Другой сервер, другой протокол, другая логика, где telemt не участвует + +--- + +## "Где именно происходит подмена destination на SNI" + +Это зависит от конфигурации, но типовые варианты: + +### Вариант A: outbound задан доменом (и он совпадает с SNI) + +Правило по SNI выбирает outbound, у которого destination задан доменом фронтинга, +тогда DNS резолвится на стороне роутера и вы уходите на "оригинальный" хост + +### Вариант B: destination override / sniffing + +Роутер "снифает" SNI и **перезаписывает** destination на домен из SNI (даже если вход изначально был на IP telemt), +это особенно коварно: пользователь видит "я подключаюсь к IP telemt", но роутер после sniffing решает иначе + +### Вариант C: split DNS / кеш / независимый резолвер + +Даже если клиент "где-то" резолвит иначе, это не важно: конечный DNS для исходящего коннекта — на стороне xray/sing-box, +который может иметь: + +* свой DoH/DoT, +* свой кеш, +* свои правила fake-ip / system resolver, +* и, как следствие, своя "карта" **домен/SNI -> IP** + +--- + +## Признаки того, что трафик "утёк на оригинал", а не попал в telemt + +* На стороне telemt отсутствуют входящие соединения/логи +* На стороне роутера видно, что destination — домен фронтинга, а IP соответствует публичному сайту +* TLS-метрики/сертификат на выходе соответствует "оригинальному" сайту в записах трафика +* Telegram-клиент получает неожиданный тип ответов/ошибку handshaking/timeout в debug-режиме + +--- + +## Best-practice решение для этого кейса: свой домен фронтинга + заглушка на telemt + Let's Encrypt + +### Цель + +Сделать так, чтобы: + +* SNI (фронтинг-домен) **резолвился в IP telemt**, +* на IP telemt реально был TLS-сервис с валидным сертификатом под этот домен, +* даже если кто-то "попробует открыть домен как сайт", он увидит нормальную заглушку, а не "пустоту" + +### Что это даёт + +* xray/sing-box, маршрутизируя по SNI, будет неизбежно приходить на telemt, потому что DNS(SNI-домен) → IP telemt +* Внешний вид будет правдоподобным: обычный домен с обычным сертификатом +* Устойчивость: меньше сюрпризов от DNS-кеша/перерезолва/"умных" правил роутера + +--- + +## Рекомендуемая схема (целевое состояние) + +```text +Telegram Client + | + | TLS ClientHello: SNI = hello.example.com + v +xray-core / sing-box + | + | Route by SNI -> outbound -> connect to hello.example.com:443 + | DNS(hello.example.com) = IP telemt + v +telemt instance (IP telemt) + | + | TLS cert for hello.example.com (Let's Encrypt) + | + сайт-заглушка / health endpoint + v +OK +``` + +--- + +## Практический чеклист (минимальный) + +1. Купить/иметь домен: `hello.example.com` +2. В DNS: + + * `A hello.example.com -> ` + * (опционально) AAAA, если используете IPv6 и он стабилен +3. На telemt-хосте: + + * поднять TLS endpoint на 443 с валидным сертификатом LE под `hello.example.com` + * отдать "заглушку" (например, статический сайт), чтобы домен выглядел как обычный веб-сервис +4. В xray/sing-box правилах: + + * маршрутизировать нужный трафик по SNI = `hello.example.com` в "правильный" outbound (к telemt) + * избегать конфигураций, где destination override уводит на чужой домен +5. Важно: + + * если вы используете кеш DNS на роутере — сбросить/обновить его после смены A-записи + +--- + +## Пояснение про сайт-заглушку + +Для эмуляции TLS, telemt имеет подсистему TLS-F в `src/tls_front`: +- её модуль - fetcher, собирает TLS-профили, чтоб максимально поведенчески корректно повторять TLS конкретно указанного сайта + +Когда вы указываете сайт, который не отвечает по TLS: +- fetcher не может собрать TLS-профиль и происходит fallback на `fake_cert_len` - примитивный алгоритм, +- он забивает служебную информацию TLS рандомными байтами, +- простые системы DPI не распознают это +- однако, продвинутые системы, такие как nEdge или Fraud Control в сетях мобильной связи легко заблокируют или замедлят такой трафик + +Создав сайт-заглушку с Let's Encrypt сертификатом, вы даёте TLS-F возможность получить данные сертификата и корректно его "повторять" в дальнейшем + +--- + +## Вариант конфиг-подхода: "SNI строго привязываем к telemt - фиксированный IP" + +Чтобы полностью исключить зависимость от DNS если вам это нужно, можно сделать outbound, который ходит на **фиксированный IP telemt**, но при этом выставляет SNI/Host как `hello.example.com`. + +Идея: + +* destination: `IP:443` +* SNI: `hello.example.com` +* сертификат на telemt именно под `hello.example.com` + +Так вы получаете: + +* TLS выглядит корректно, ведь SNI совпадает с сертификатом, +* а routing никогда не уйдёт на "оригинал", потому что A-запись указывает на telemt и контроллируется вами! + +Но в вашем описании проблема как раз в том, что роутер "сам решает по SNI и резолвит домен", поэтому самый универсальный вариант — сделать так, чтобы DNS всегда приводил в telemt + +--- + +## Пример логики правил на псевдоконфиге L7-роутера + +```text +if inbound is TLS and sni == "hello.example.com": + route -> outbound "telemt" +else: + route -> outbound "default" +``` + +Outbound `telemt`: + +* destination: `hello.example.com:443` +* TLS enabled +* SNI: `hello.example.com` + +--- + +## Отдельно: что может неожиданно сломать даже "правильный" DNS + +* **Кеширование DNS** на xray/sing-box или на системном резолвере, особенно при смене A-записи +* **Split-horizon DNS**: разные ответы внутри/снаружи, попытки подмены/терминирования в других точках +* **IPv6**: если есть AAAA и он указывает не туда, роутер может предпочесть IPv6: помните, что поддержка v6 нестабильна и не рекомендуется в prod +* **DoH/DoT** на роутере: он может резолвить не тем резолвером, которым вы проверяли + +Минимальная гигиена: + +* контролировать A/AAAA, +* держать TTL разумным, +* проверять, каким резолвером пользуется именно роутер, +* при необходимости отключить/ограничить destination override + +--- + +## Итог + +В режиме TLS-fronting с xray-core/sing-box как L7/TLS-роутером **SNI становится приоритетным "source-of-truth" для маршрутизации** + +Если фронтинг-домен по DNS указывает не на IP telemt, роутер честно уводит трафик на "оригинальный" сайт, потому что он строит исходящее соединение "по SNI" + +Надёжное решение для этого кейса: + +* использовать **свой домен** для фронтинга, +* направить его **A/AAAA** на IP telemt, +* поднять на telemt **TLS-сервис с Let’s Encrypt сертификатом** под этот домен, +* (желательно) держать **сайт-заглушку**, чтобы 443 выглядел как обычный HTTPS