Обновить правила репозитория и подписи исходников в 99 заметках, не затрагивая пользовательские незакоммиченные файлы. Сделать Forgejo Actions содержательным: проверять опубликованный commit, а не пустое рабочее дерево после checkout.
26 KiB
| date | tags | aliases | link | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-07-17 |
|
|
https://xtls.github.io/ru/config/routing.html |
🧭 Маршрутизация в Xray: как трафик распределяется по outbound
[!info] О чём заметка Разбор модуля маршрутизации (routing) в xray/project-x — механизма, который для каждого соединения решает, через какой исходящий канал (outbound) его отправить: напрямую, через прокси или заблокировать. Это то, что на практике позволяет открывать российские сайты мимо туннеля, а заблокированные — через сервер (см. xray/clients-and-routing). Здесь — устройство правил и стратегий, сверенное с официальной документацией и с чтением исходного кода
app/router/иapp/dispatcher/. Про сами протоколы, поверх которых всё это работает — xray/vless, xray/reality, xray/xtls-vision.
TL;DR
- Маршрутизация отправляет входящий трафик в разные outbound по правилам. Типичная задача — разделить внутренний и внешний трафик (
.ruнапрямую, заблокированное через прокси). - Правила проверяются сверху вниз, первое сработавшее выигрывает (first match wins). Если не сработало ни одно — трафик идёт в outbound по умолчанию.
- Внутри одного правила все условия соединяются логическим И (AND); внутри одного условия (список доменов, IP, портов) — ИЛИ (OR).
domainStrategyрешает, резолвить ли домен в IP для сопоставления:AsIs(не резолвить),IPIfNonMatch(резолвить, если правила не сработали),IPOnDemand(резолвить сразу).- Есть встроенные списки:
geosite:(домены, файлgeosite.dat) иgeoip:(IP-диапазоны стран, файлgeoip.dat) — напримерgeoip:cn,geoip:private,geosite:category-ads-all. - Балансировщики распределяют трафик между несколькими outbound по стратегиям
random/roundRobin/leastPing/leastLoad. - Важно: резолв домена для маршрутизации не меняет фактический адрес соединения — цель остаётся исходной.
Общая картина: как соединение проходит через маршрутизацию
Когда приходит соединение, диспетчер (app/dispatcher) делает так:
- Определяет цель (адрес и порт) и, при включённом sniffing, «подсматривает» первые байты, чтобы распознать протокол (TLS/HTTP/QUIC/BitTorrent) и извлечь домен из SNI/Host.
- Собирает контекст маршрутизации — целевой адрес, домен, тег входящего канала (inbound), источник, тип сети.
- Вызывает роутер:
Router.PickRoute()перебирает правила сверху вниз и возвращает первое подходящее. - Правило указывает
outboundTag(или балансировщик) — трафик уходит в этот исходящий канал.
Проще говоря: маршрутизатор — это диспетчер на развилке. Он смотрит на каждое соединение, сверяет его со списком правил по порядку и на первом же совпадении направляет по нужной «дороге» (outbound). Не совпало ни с чем — едет по дороге по умолчанию.
[!note] Куда идёт трафик, если правила не сработали Если ни одно правило не совпало, соединение уходит в outbound по умолчанию — на практике это первый исходящий канал в конфиге. Поэтому распространённый приём — поставить первым нужный дефолт (например прямое соединение или основной прокси), либо в самом конце правил добавить «ловушку»
"network": "tcp,udp"без других условий, которая ловит вообще всё оставшееся и явно задаёт назначение.
RoutingObject: структура
Секция routing в конфиге состоит из трёх частей:
{
"routing": {
"domainStrategy": "AsIs",
"rules": [],
"balancers": []
}
}
domainStrategy— стратегия разрешения доменов (см. ниже).rules— массив правил, проверяются по порядку.balancers— конфигурации балансировщиков (опционально).
domainStrategy: резолвить домен в IP или нет
Это, пожалуй, самая недопонятая настройка. Она определяет, разрешает ли маршрутизатор доменное имя в IP-адрес ради сопоставления с IP-правилами (geoip: и т.п.). Три значения:
AsIs(по умолчанию) — никакого резолва. Работает только с доменом (из цели или из sniffing). IP-правила для доменной цели не сработают, потому что IP просто неоткуда взять.IPIfNonMatch— сначала правила прогоняются как есть (по домену). Если ни одно не совпало, домен резолвится в IP, и весь список правил прогоняется заново — уже с IP. Это даёт доменным правилам приоритет, а IP-правила служат «вторым проходом».IPOnDemand— домен резолвится в IP сразу, ещё до первого прохода, и IP доступен всем правилам с самого начала.
[!note] Разрешение ленивое — чтобы не тормозить Даже в
IPOnDemandфактический DNS-запрос не делается заранее: он откладывается до первого правила с IP-условием, которое реально запросит IP (в коде —ResolvableContext.GetTargetIPs, результат кешируется). Если раньше сработало доменное или другое правило — резолв вообще не понадобится, и задержки не будет. Результат содержит и IPv4, и IPv6; сузить семейство можно черезqueryStrategyвстроенного DNS.
[!important] Резолв НЕ меняет реальный адрес соединения Разрешение домена в IP используется только для сопоставления IP-правил. Фактическая цель запроса остаётся исходной — домен уходит на выбранный outbound и резолвится уже там (транспортом или сервером). Это подтверждается кодом: маршрутизатор держит IP в отдельном кеше поверх контекста и никогда не переписывает целевой адрес.
Отдельно: при включённых sniffing + routeOnly маршрутизатор видит и IP, и домен, распознанный из трафика. Домен из sniffing имеет приоритет над исходным целевым адресом — и при резолве, и при сопоставлении.
RuleObject: правило и его условия
Правило описывает набор условий и цель (outboundTag или balancerTag).
[!danger] Все условия внутри правила — логическое И Если в одном правиле указано несколько условий (например
domainиport), они должны выполниться одновременно, иначе правило не срабатывает. А вот внутри одного условия (список доменов, список IP, список портов) действует ИЛИ — достаточно совпадения любого элемента.
Условие по домену (domain)
Список шаблонов домена. Типы (по префиксу):
| Префикс | Тип | Пример | Матчит |
|---|---|---|---|
full: |
Полное совпадение | full:xray.com |
только xray.com |
domain: |
Домен и поддомены (рекомендуется) | domain:xray.com |
xray.com, www.xray.com, но не wxray.com |
keyword: |
Подстрока | keyword:sina.com |
sina.com, sina.com.cn, www.sina.com |
regexp: |
Регулярное выражение | regexp:\.goo.*\.com$ |
www.google.com, но не google.com |
dotless: |
Домен без точек | dotless:pc- |
pc-alice (для NetBIOS-имён во внутренней сети) |
geosite: |
Встроенный список | geosite:cn |
все домены из списка cn |
ext: |
Список из файла | ext:geosite.dat:cn |
эквивалент geosite:cn |
[!warning] «Голый» домен без префикса — это keyword (подстрока), а не полное совпадение Если написать в
domainпросто"baidu.com"без префикса, ядро трактует это как подстроку (keyword), а не как точное имя. Это подтверждается кодом (тип по умолчанию —Domain_Substr). Чтобы поймать домен и поддомены, используйте явныйdomain:baidu.com; для точного совпадения —full:.
Технически на десктопе домены матчатся через MPH (minimal perfect hash) — быструю структуру; на iOS/Android — линейным перебором. Регулярки чувствительны к регистру.
Условие по IP (ip, sourceIP, localIP)
Список диапазонов. Форматы: одиночный IP (127.0.0.1), CIDR (10.0.0.0/8, ::/0), встроенный список стран (geoip:cn), спецзначение geoip:private (все приватные адреса), файл (ext:geoip.dat:cn).
Инверсия ! — важная и хитрая деталь. !geoip:cn означает «всё, что НЕ входит в geoip:cn». Логика объединения по документации: несколько отрицаний соединяются через И (AND), а положительные условия и совокупность отрицательных — через ИЛИ (OR).
Пример: ip: ["!geoip:cn", "!geoip:us", "geoip:telegram"] матчит IP, которые «не Китай И не США», ИЛИ являются IP Telegram.
[!note] Нюанс реализации инверсии По коду AND между отрицаниями держится строго внутри одной «корзины» — отдельно для CIDR и отдельно для geoip. То есть
!geoip:cn+!geoip:usдействительно объединяются через AND, но отрицаемый CIDR и отрицаемый!geoip:cnокажутся в разных корзинах и соединятся через OR. Для типовых конфигов (несколько!geoip:) поведение совпадает с документацией.
Условия по портам и сети
port— порт назначения. Форматы:443, диапазон1000-2000, смесь через запятую53,443,1000-2000.sourcePort— порт источника (тот же формат).localPort— порт локального inbound (полезно, когда inbound слушает диапазон портов).network—tcp,udpилиtcp,udp. Условие"network": "tcp,udp"без других полей ловит весь трафик (ядро на 4-м уровне знает только TCP и UDP) — удобно как catch-all в конце списка правил.
Прочие условия
sourceIP(псевдонимsource) — IP источника (IP/CIDR/geoip).localIP— IP, на который пришёл inbound (приlisten: 0.0.0.0). Не работает для UDP.user— email пользователя-источника; поддерживаетregexp:.inboundTag— тег входящего канала, через который пришло соединение.protocol—http/tls/quic/bittorrent. Требует включённого sniffing, иначе тип пуст. Матч по префиксу (правилоhttpпоймает и распознанныйhttp1).attrs— сопоставление HTTP-заголовков и псевдозаголовков h2 (:method,:path), например{":method": "GET"}. Тоже требует sniffing; все указанные ключи должны совпасть (AND), значения поддерживают регулярки.ruleTag— метка правила. На маршрутизацию не влияет, но при срабатывании пишет в лог (уровень Info) строкуHit route rule: [<ruleTag>]— удобно для отладки, какое правило сработало.
Условие по процессу (process)
Сопоставление по процессу-источнику — работает только для локальных соединений (Windows/Linux). Три режима: имя процесса (curl — ядро само отбрасывает .exe), абсолютный путь (C:/Windows/System32/curl.exe, слеши всегда прямые), папка (путь с завершающим / — все процессы внутри).
Два синтаксических сахара очень полезны:
self/— матчит сам процесс ядра Xray. Главное применение — защита от петель маршрутизации (routing loops): трафик, порождённый самим ядром, не заворачивается обратно в себя.xray/— матчит все процессы Xray из того же бинарника (подставляется абсолютный путь к текущему исполняемому файлу).
На Android для сопоставления по приложениям нужно, чтобы клиентская обёртка зарегистрировала искатель процессов через RegisterAndroidProcessFinder().
vlessRoute: маршрутизация по байтам UUID
Необычная возможность: VLESS-клиент может изменить 7-й и 8-й байты своего UUID (в записи xxxxxxxx-xxxx-0000-xxxx-... это выделенные нули) на любое значение и использовать их как данные маршрутизации vlessRoute. Это позволяет клиенту управлять частью серверной маршрутизации, не меняя никаких внешних полей конфига.
Значение кодируется как big-endian uint16 (проще: воспринимайте эти 4 hex-символа как шестнадцатеричное число → в десятичное: 0001→1, 000e→14, 38b2→14514), а в правиле vlessRoute пишется в том же синтаксисе, что и порты (одиночные значения и диапазоны).
Механика (по коду): при проверке пользователя эти два байта обнуляются (ProcessUUID в validator.go), поэтому аутентификация по UUID не ломается — а исходное значение байтов сервер запоминает и использует для матчинга правила.
webhook: уведомление при срабатывании правила
Опционально к правилу можно привязать webhook — при попадании в правило ядро шлёт POST-запрос с JSON о соединении (email, protocol, network, source, destination, originalTarget, routeTarget, inboundTag, outboundTag, ts и др.). Полезно для алертов и статистики (например, «зафиксирован bittorrent»).
Поля объекта: url (обычный HTTP(S)-адрес или путь Unix-сокета, в т.ч. abstract-сокет @abstract / @@padded на Linux), deduplication (окно в секундах — повторные события по тому же пользователю в этот период не шлются), headers (доп. заголовки запроса, например X-API-Key).
Балансировщики нагрузки
Если правило указывает balancerTag вместо outboundTag, Xray выбирает конкретный outbound через балансировщик. Балансировщик описывается в balancers:
{
"tag": "balancer",
"selector": ["out"],
"fallbackTag": "outbound",
"strategy": { "type": "roundRobin" }
}
selector— массив префиксов тегов. Балансировщик работает с теми outbound, чьи теги начинаются с указанных префиксов:["a"]захватитa,ab,abc. Это префиксное совпадение (HasPrefix), не точное.fallbackTag— куда уйти, если по данным наблюдения все outbound недоступны.strategy— стратегия выбора.
[!tip] Если оба тега заданы — приоритет у outboundTag В одном правиле можно указать
outboundTagиbalancerTag, но действует только один. По документации приоритет уoutboundTag.
Четыре стратегии
| Стратегия | Как выбирает | Нужен наблюдатель |
|---|---|---|
random (по умолчанию) |
Случайный из кандидатов | Опционально |
roundRobin |
По очереди, циклически | Опционально |
leastPing |
С наименьшей задержкой | Да |
leastLoad |
Самый стабильный (по разбросу RTT) | Да (burst) |
random/roundRobinмогут работать и без наблюдателя. Если наблюдатель есть (подключается при заданномfallbackTag), недоступные узлы исключаются, а узлы без данных наблюдения считаются доступными.leastPing/leastLoadтребуют наблюдателя обязательно; узлы, не покрытые наблюдением, исключаются. Если все недоступны иfallbackTagне задан — берётся outbound по умолчанию.
Данные о доступности и задержке узлов поставляет observatory (простой health-check: HTTP-запрос через каждый outbound, даёт Alive + задержку) или burstObservatory (богаче: скользящее окно замеров со статистикой — среднее, макс/мин, доля отказов, стандартное отклонение RTT). Для leastLoad нужен именно burst, потому что он опирается на разброс RTT.
Настройки leastLoad
Стратегия leastLoad тонко настраивается через settings:
expected— сколько лучших узлов оставить (трафик случайно распределяется между ними).maxRTT— отбрасывать узлы с задержкой выше порога.tolerance— допустимая доля неудачных замеров (например0.01= 1%).baselines— пороги стандартного отклонения RTT для отбора «стабильных» узлов.costs— веса для outbound (regexp/match/value); чем большеvalue, тем менее вероятен выбор узла.
По коду leastLoad сортирует узлы по «стоимости отклонения RTT» (стабильность важнее среднего пинга), отбирает лучшие expected, а внутри них выбирает случайно — так нагрузка равномерно ложится на несколько хороших узлов.
Встроенные списки geosite и geoip
Xray поставляется с двумя файлами-справочниками:
geosite.dat— именованные списки доменов, используются какgeosite:имя. Полезные:category-ads-all(реклама + рекламные сети),cn(сайты материкового Китая),google,microsoft,apple,telegram,geolocation-cn/geolocation-!cn(китайские / некитайские),tld-cn/tld-!cn(домены по TLD). Полный список — в community-репозитории доменов.geoip.dat— списки IP-диапазонов по странам, используются какgeoip:код_страны(почти все страны). Плюс спец-значениеgeoip:private(приватные подсети).
Именно на этих списках строятся типовые правила «китайские/российские сайты — напрямую, остальное — через прокси». Синтаксис ext:файл:тег позволяет подключать свои .dat-файлы из директории ресурсов.
Практический смысл для обхода блокировок
Всё это — фундамент раздельной маршрутизации: правило с geoip:ru/geosite: и outboundTag: direct пускает российские сайты напрямую, а «ловушка» в конце гонит остальное через прокси-outbound. На стороне пользователя те же правила приезжают готовым набором (например через HAPP и routing.help) — как это выглядит на практике, разобрано в xray/clients-and-routing.
📚 См. также
- xray/clients-and-routing — практика: готовые правила
.ruнапрямую - xray/project-x — обзор ядра и всех технологий
- xray/vless — протокол, поверх которого работают outbound (в т.ч.
vlessRoute) - xray/reality и xray/xtls-vision — чем шифруется и оптимизируется прокси-outbound
- 🔗 Официальная дока: маршрутизация — первоисточник по RoutingObject
- 🔗 v2fly/domain-list-community — полный список geosite-доменов
[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.