Настройка

Split-tunneling в Xray: где ломается DIRECT

Routing и DNS — два разных уровня. Список прямых доменов идеален, а сервис всё равно видит чужую гео, потому что DNS утёк в туннель.

8 мин чтения Обновлено · Dele Deleev

Задача и ловушка

Часть доменов должна идти напрямую — мимо VPN, с реального IP клиента, — а остальное через exit-ноду. В Xray это делается routing-правилами с outboundTag: direct. Частая ловушка: список DIRECT-доменов идеален, а конкретные сервисы всё равно ломаются или видят «чужую» гео. Причина в том, что DNS-резолв и routing в Xray — два разных уровня, и один может тихо утечь в туннель, пока другой настроен верно.

Где живёт split-tunneling

В централизованном шаблоне подписки (формат XRAY_JSON), не в серверном профиле ноды. Панель добавляет юзерские proxy-outbounds в начало массива; в шаблоне статически лежат только direct (freedom) и block (blackhole). Правила:

"routing": {
  "domainStrategy": "AsIs",
  "rules": [
    { "type": "field", "protocol": ["bittorrent"], "outboundTag": "block" },
    { "type": "field", "ip": ["geoip:private"], "outboundTag": "direct" },
    { "type": "field", "domain": ["domain:bank.example", "full:portal.example", "regexp:.*\\.gov\\.example$"], "outboundTag": "direct" }
  ]
}

Префиксы доменов: domain:x.tld — suffix-match (*.x.tld плюс сам домен), full: — точное совпадение, regexp: — регулярка.

Ключевой урок: routing по домену ≠ DNS через нужный резолвер

Даже при верном DIRECT-правиле Xray внутри клиента резолвит имена через свой dns.servers — например, публичный резолвер. Этот DNS-запрос уходит через туннель на exit-ноду в другой стране. Локальный сервис видит DNS-запрос с чужого IP и не отвечает, хотя трафик по routing помечен DIRECT. Чтобы DNS резолвился локально, нужны сразу несколько настроек:

  • routing.domainStrategy: AsIs — Xray матчит по строке домена и сам не резолвит;
  • direct.settings.domainStrategy: UseIP — freedom-outbound резолвит сам, через системный DNS ОС клиента, запрос выходит с реального IP;
  • dns.servers с локальными адресами и skipFallback: true для нужных доменов — DNS мимо туннеля;
  • sniffing: {enabled: true, destOverride: ["http","tls","quic"], routeOnly: true} на inbounds — SNI читается для матча, но не подменяет destination (важно для QUIC/ECH).

Максимальная «прозрачность» — DNS через системный резолвер: в dns.servers[0].address поставить localhost. Xray делегирует резолв системному getaddrinfo(), то есть DNS ОС клиента. Тогда ASN DNS-резолвера совпадает с ASN клиента, и приложения с антифродом по DNS-ASN не видят рассогласования. port для localhost смысла не имеет — удалить.

Подводные камни

  • Правки шаблона применяются только через PATCH в API панели. Прямой UPDATE в БД проходит, но в выдаче остаётся старая версия — backend кэширует шаблон в памяти, и кэш сбрасывает только API-запрос.
  • Разные клиенты — разный формат routing. Полный split едет только в XRAY_JSON (Happ и совместимые). Шаблоны Clash/Mihomo/sing-box требуют своего синтаксиса и по умолчанию пустые — весь трафик там пойдёт в туннель. Заполнять отдельно.
  • base64-формат подписки не передаёт DNS-секцию — «прозрачность» работает, только когда подписка отдаётся как JSON.
  • Порты в routing — это destination-порты внутри туннеля (BitTorrent-пиры, SMTP, UPnP), не порты подключения к нодам. Не путать уровни.
  • Дефолтный шаблон панели не пустой. В предустановленном шаблоне уже лежат унаследованные правила — локальные подсети мимо туннеля, спецобработка BitTorrent. При заливке своего шаблона явно проверьте, что унаследованное не противоречит ожидаемому: в одном случае BitTorrent по умолчанию уходил прямым выходом вместо фильтрации.
  • bittorrent → block через protocol-rule срабатывает только при включённом sniffing; порт-rule на 6881/51413 — страховка на случай его промаха.

Проверка результата

После каждого изменения — двойной тест в клиенте: локальный сервис (например, поисковик региона) должен показывать реальный IP, а зарубежный — IP exit-ноды. Одна проверка покрывает оба уровня сразу: и routing, и DNS.

Вывод. Split-tunneling в Xray — два независимых уровня: routing (куда слать трафик) и DNS (где резолвить имя). Можно собрать идеальный список DIRECT-доменов и всё равно получить обрыв, если DNS про эти домены уходит через туннель. Лечится связкой domainStrategy AsIs + UseIP на direct + локальные dns.servers (или localhost) + sniffing routeOnly.

Источники

Частые вопросы

Частые вопросы

Список DIRECT-доменов идеален, а сервис всё равно видит чужую гео — почему?
Routing и DNS в Xray — два разных уровня. Даже при верном DIRECT-правиле DNS-запрос об этом домене может уйти через туннель на exit-ноду, и локальный сервис увидит чужой IP.
Какие настройки нужны, чтобы DNS резолвился локально вместе с DIRECT?
Связка routing.domainStrategy AsIs, direct.settings.domainStrategy UseIP, локальные dns.servers со skipFallback и sniffing с routeOnly true на inbounds.
Как сделать DNS максимально «прозрачным» для антифрода по ASN?
Указать в dns.servers[0].address localhost — тогда Xray делегирует резолв системному DNS ОС клиента, и ASN резолвера совпадёт с ASN клиента.
Почему правка шаблона подписки иногда не применяется к клиентам?
Backend кэширует шаблон в памяти, и сбросить кэш может только PATCH-запрос через API панели, а не прямой UPDATE в базе данных.
Split-tunneling работает одинаково во всех VPN-клиентах?
Нет. Полный split доступен только в формате XRAY_JSON (Happ и совместимые клиенты); шаблоны Clash, Mihomo и sing-box требуют своего синтаксиса и по умолчанию пустые.
Похожее

Ещё в базе знаний

Подключите рабочий VPN

Инструкции базы знаний написаны под ключи VPN PRO: ключ выдаёт Telegram-бот за минуту, работоспособность гарантируем. 3 дня бесплатно, без карты.

Гарантия работоспособности · оплата СБП, картой МИР или криптовалютой