---
date: 2026-07-17
tags:
  - xray
  - routing
  - geoip
  - geosite
  - balancer
  - config
aliases:
  - Маршрутизация Xray
  - Xray routing
  - RoutingObject
  - domainStrategy
  - Балансировщик Xray
link: https://xtls.github.io/ru/config/routing.html
---

# 🧭 Маршрутизация в Xray: как трафик распределяется по outbound

> [!info] О чём заметка
> Разбор модуля **маршрутизации** (routing) в [[xray/project-x|Xray-core]] — механизма, который для каждого соединения решает, через какой исходящий канал (outbound) его отправить: напрямую, через прокси или заблокировать. Это то, что на практике позволяет открывать российские сайты мимо туннеля, а заблокированные — через сервер (см. [[xray/clients-and-routing|практику раздельной маршрутизации]]). Здесь — устройство правил и стратегий, сверенное с официальной документацией и с чтением исходного кода `app/router/` и `app/dispatcher/`. Про сами протоколы, поверх которых всё это работает — [[xray/vless|VLESS]], [[xray/reality|REALITY]], [[xray/xtls-vision|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`) делает так:

1. Определяет цель (адрес и порт) и, при включённом **sniffing**, «подсматривает» первые байты, чтобы распознать протокол (TLS/HTTP/QUIC/BitTorrent) и извлечь домен из SNI/Host.
2. Собирает **контекст маршрутизации** — целевой адрес, домен, тег входящего канала (inbound), источник, тип сети.
3. Вызывает роутер: `Router.PickRoute()` перебирает правила сверху вниз и возвращает первое подходящее.
4. Правило указывает `outboundTag` (или балансировщик) — трафик уходит в этот исходящий канал.

Проще говоря: маршрутизатор — это диспетчер на развилке. Он смотрит на каждое соединение, сверяет его со списком правил по порядку и на первом же совпадении направляет по нужной «дороге» (outbound). Не совпало ни с чем — едет по дороге по умолчанию.

> [!note] Куда идёт трафик, если правила не сработали
> Если ни одно правило не совпало, соединение уходит в **outbound по умолчанию** — на практике это **первый** исходящий канал в конфиге. Поэтому распространённый приём — поставить первым нужный дефолт (например прямое соединение или основной прокси), либо в самом конце правил добавить «ловушку» `"network": "tcp,udp"` без других условий, которая ловит вообще всё оставшееся и явно задаёт назначение.

## RoutingObject: структура

Секция `routing` в конфиге состоит из трёх частей:

```json
{
  "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`:

```json
{
  "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-репозитории доменов](https://github.com/v2fly/domain-list-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|Project X (Xray-core)]] — обзор ядра и всех технологий
- [[xray/vless|Протокол VLESS]] — протокол, поверх которого работают outbound (в т.ч. `vlessRoute`)
- [[xray/reality|REALITY]] и [[xray/xtls-vision|XTLS и Vision]] — чем шифруется и оптимизируется прокси-outbound
- 🔗 [Официальная дока: маршрутизация](https://xtls.github.io/ru/config/routing.html) — первоисточник по RoutingObject
- 🔗 [v2fly/domain-list-community](https://github.com/v2fly/domain-list-community) — полный список geosite-доменов

---

> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/xray/routing.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).
