Files
2026-09-12 06:38:54 +08:00

32 KiB
Raw Permalink Blame History

Встроенный DNS-сервер

DNS-сервер

Встроенный модуль DNS в Xray имеет три основных назначения:

  • На этапе маршрутизации: разрешение доменов в IP и сопоставление правил на основе полученных IP для разделения трафика. ::: details Подробное объяснение Разрешение доменного имени для маршрутизации по IP зависит от значения routing.domainStrategy. Встроенный DNS-сервер может использоваться для запросов только при следующих значениях:

    • "IPIfNonMatch": если в первом проходе маршрутизации не сработало ни одно правило, разрешение выполняется при условии, что цель содержит доменное имя и хотя бы одно правило содержит условие ip.
    • "IPOnDemand": разрешение выполняется, если цель содержит доменное имя и встречается правило с условием ip.

    :::

  • На этапе исходящего подключения: разрешение целевых доменных имен для подключения или передачи удаленному прокси-серверу. ::: details Подробное объяснение

    • Например, если в исходящем подключении VLESS задать targetStrategy равным UseIP, целевой домен проксируемого запроса сначала разрешается локальным встроенным модулем DNS, затем полученный IP передается удаленному прокси-серверу.
    • Если в исходящем подключении VLESS задать sockopt.domainStrategy равным UseIP, домен сервера VLESS разрешается встроенным модулем DNS, затем устанавливается соединение с полученным IP.
    • Если в исходящем подключении Freedom задать sockopt.domainStrategy равным UseIP, целевой домен запроса разрешается встроенным модулем DNS, затем устанавливается соединение с полученным IP.
    • Протокол WireGuard не допускает передачу доменного имени в качестве цели, поэтому его исходящее подключение может использовать встроенный модуль DNS для разрешения доменов в IP.

    :::

  • Перехват DNS-трафика в режиме TUN/прозрачного прокси с помощью маршрутизации и исходящего подключения DNS для направления запросов в этот модуль; либо использование Tunnel для открытия порта 53 и работы в качестве рекурсивного DNS-сервера. ::: details Подробное объяснение

    • Поддерживаются только базовые IP-запросы (записи A и AAAA). Записи CNAME будут запрашиваться повторно до тех пор, пока не будет возвращена запись A/AAAA. Другие типы запросов не попадают во встроенный DNS-сервер, а либо отбрасываются, либо передаются другим серверам в зависимости от вашей конфигурации исходящего подключения.

    :::

Процесс обработки DNS

Домен сначала проходит проверку сопоставления Hosts (см. поле hosts). Если нужный IP не найден, для запроса используется DNS-сервер.

Затем ядро начинает строить список серверов, сортируя их в зависимости от запрашиваемого домена по следующим правилам.

  • Построение списка 1: содержит серверы, у которых поле domains успешно совпало с запрашиваемым доменом, в порядке их появления в конфигурационном файле.
  • Проверка disableFallback: если true, построение списка 2 пропускается.
  • Проверка disableFallbackIfMatch: если true и список 1 не пуст, построение списка 2 пропускается.
  • Построение списка 2: содержит серверы, не входящие в список 1, у которых skipFallback не равно true, в порядке их появления в конфигурационном файле.
  • Итоговый список серверов = список 1 + список 2.

Примечание: любой DNS-сервер с FinalQuery, равным true, напрямую усекает последующую часть списка.

При выполнении DNS-запроса ядро последовательно опрашивает серверы из итогового списка, фильтруя результаты с помощью expectedIPs и unexpectedIPs; если результат пуст, переходит к следующему серверу в списке. (При enableParallelQuery, равном true, поведение немного отличается, подробнее см. описание этого поля.)

DnsObject

DnsObject соответствует разделу dns в конфигурационном файле.

{
  "dns": {
    "hosts": {
      "baidu.com": "127.0.0.1",
      "dns.google": ["8.8.8.8", "8.8.4.4"]
    },
    "servers": [
      "8.8.8.8",
      "8.8.4.4",
      {
        "address": "1.2.3.4",
        "port": 5353,
        "domains": ["domain:xray.com"],
        "expectedIPs": ["geoip:cn"],
        "skipFallback": false,
        "clientIP": "1.2.3.4"
      },
      {
        "address": "https://8.8.8.8/dns-query",
        "domains": ["geosite:netflix"],
        "skipFallback": true,
        "queryStrategy": "UseIPv4"
      },
      {
        "address": "https://1.1.1.1/dns-query",
        "domains": ["geosite:openai"],
        "skipFallback": true,
        "queryStrategy": "UseIPv6"
      },
      "localhost"
    ],
    "clientIp": "1.2.3.4",
    "queryStrategy": "UseIP",
    "disableCache": false,
    "serveStale": false,
    "serveExpiredTTL": 0,
    "disableFallback": false,
    "disableFallbackIfMatch": false,
    "enableParallelQuery": false,
    "useSystemHosts": false,
    "tag": "dns_inbound"
  }
}

hosts: map{string: address} | map{string: [address]}

Статический список сопоставлений. Значения представляют собой записи в форме "домен": "адрес" или "домен": ["адрес 1", "адрес 2"]. Адреса могут быть IP или доменными именами. При разрешении домена ядро проверяет все записи сопоставления и возвращает все совпавшие IP-адреса. Если совпадений не найдено, переходит к этапу DNS-запроса.

Целевое значение маппинга может быть доменом. Когда ядро завершает сопоставление и маппинг содержит доменное имя(а), поведение меняется следующим образом:

  • Если маппинг содержит одновременно IP-адреса и доменные имена, доменные имена удаляются, и возвращаются только IP-адреса.
  • Если маппинг содержит несколько доменных имен, возникает неоднозначность: запрос завершается неудачей.
  • Если маппинг содержит ровно одно доменное имя, это доменное имя повторно передаётся в модуль Hosts для рекурсивного разрешения, повторяя описанные шаги; максимальная глубина рекурсии — 5.
  • Если вышеописанная рекурсивная проверка не вернула IP, и итоговый результат содержит ровно одно доменное имя, это доменное имя заменяет исходный запрошенный домен и отправляется на этап DNS-запроса.
  • В частности, если «домен» имеет формат знака решётки и следующего за ним числа (например, #3), любой запрос, совпавший с этой записью, немедленно завершается неудачей. Если запрос поступил от DNS outbound, ядро вернёт пустой ответ и соответствующий этому числу rcode, чтобы отклонить запрос.

Формат сопоставления (domain:, full: и т.д.) аналогичен domain в системе маршрутизации. Отличие в том, что без префикса здесь по умолчанию используется full: (аналогично стандартному файлу hosts).

servers: [string | DnsServerObject ]

Список DNS-серверов. Поддерживаются два типа: адрес DNS (строка) и DnsServerObject.

Если значение "localhost", используется конфигурация DNS локальной системы.

Если значение — адрес DNS "IP:Port", например, "8.8.8.8:53", Xray будет использовать указанный UDP-порт этого адреса для DNS-запроса. Запрос следует правилам маршрутизации. Если порт не указан, по умолчанию используется 53.

Если значение в формате "tcp://host:port", например, "tcp://8.8.8.8:53", Xray будет использовать DNS over TCP. Запрос следует правилам маршрутизации. По умолчанию порт 53.

Если значение в формате "tcp+local://host:port", например, "tcp+local://8.8.8.8:53", Xray будет использовать TCP Local Mode (TCPL). DNS-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound для снижения задержек. По умолчанию порт 53.

Если значение в формате "https://host:port/dns-query", например, "https://dns.google/dns-query", Xray будет использовать DNS over HTTPS (RFC8484, сокращенно DoH). Некоторые провайдеры имеют сертификаты для IP-адресов, поэтому можно указывать IP напрямую, например, https://1.1.1.1/dns-query. Также можно использовать нестандартные порты и пути, например, "https://a.b.c.d:8443/my-dns-query".

Если значение в формате "h2c://host:port/dns-query", например, "h2c://dns.google/dns-query", Xray будет использовать формат запроса DNS over HTTPS, но отправит его в открытом виде (h2c). Это нельзя использовать напрямую; требуется настроить Freedom Outbound + streamSettings с TLS, чтобы обернуть запрос в нормальный DoH. Используется для специальных целей, например, для настройки SNI в DoH-запросе или использования utls отпечатков.

Если значение в формате "https+local://host:port/dns-query", например, "https+local://dns.google/dns-query", Xray будет использовать DoH Local Mode (DOHL). DoH-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound. Обычно подходит для использования на сервере. Поддерживаются нестандартные порты и пути.

Если значение в формате "quic+local://host", например, "quic+local://dns.adguard.com", Xray будет использовать DNS over QUIC Local Mode (DOQL). DNS-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound. Требуется поддержка DNS over QUIC сервером. По умолчанию используется порт 853, можно указать нестандартный.

Если значение fakedns, используется функционал FakeDNS.

::: tip TIP 1 При использовании localhost DNS-запросы системы не контролируются Xray. Требуется дополнительная настройка, чтобы перенаправить системные DNS-запросы в Xray. :::

::: tip TIP 2 DNS-клиенты, инициализированные различными правилами, будут отображаться в логе запуска Xray с уровнем info, например, режимы local DOH, remote DOH и udp. :::

::: tip TIP 3 (v1.4.0+) В логах можно включить журналирование DNS-запросов. :::

::: tip TIP 4 DNS-сервер по умолчанию проходит сопоставление в системе маршрутизации, если только он не содержит +local. При использовании доменных имён внутри него обратите внимание на возможные петли (loop); может помочь hosts. :::

clientIp: string

IP-адрес, используемый в расширении EDNS Client Subnet (ECS).

Должен быть валидным IPv4 или IPv6. При фактической отправке последние биты автоматически стираются: отправляются подсети /24 для IPv4 и /96 для IPv6.

queryStrategy: "UseIP" | "UseIPv4" | "UseIPv6" | "UseSystem"

Ограничивает возможности всех серверов в модуле DNS, а также задает значение по умолчанию для типов IP-запросов, инициированных самим Xray.

Значение по умолчанию UseIP разрешает запросы A + AAAA. Если тип IP не указан в запросе, инициированном самим Xray, у вышестоящего DNS-сервера запрашиваются одновременно A и AAAA записи. UseIPv4 запрашивает и разрешает только A записи; UseIPv6 запрашивает и разрешает только AAAA записи.

UseSystem адаптируется к сетевой среде операционной системы. Перед запросом проверяется наличие шлюзов по умолчанию для IPv4 и IPv6, чтобы ограничить возможности серверов и установить тип запроса. В ОС с графическим интерфейсом проверка выполняется в реальном времени, в среде командной строки — только один раз.

{
  "dns": {
    "servers": [
      "https://1.1.1.1/dns-query",
      {
        "address": "https://8.8.8.8/dns-query",
        "domains": ["geosite:netflix"],
        "skipFallback": true,
        "queryStrategy": "UseIPv4" // Для доменов netflix запрашивать A запись
      },
      {
        "address": "https://1.1.1.1/dns-query",
        "domains": ["geosite:openai"],
        "skipFallback": true,
        "queryStrategy": "UseIPv6" // Для доменов openai запрашивать AAAA запись
      }
    ],
    "queryStrategy": "UseIP" // Глобально запрашивать одновременно A и AAAA записи
  }
}

::: tip TIP 1 Глобальное значение "queryStrategy" имеет приоритет. Если "queryStrategy" во вложенном элементе конфликтует с глобальным "queryStrategy", запрос вложенного элемента вернет пустой ответ. :::

::: tip TIP 2 Если параметр "queryStrategy" во вложенном элементе не указан, используется глобальное значение "queryStrategy". Поведение аналогично версиям Xray-core до v1.8.6. :::

Например:
Глобальный "queryStrategy": "UseIPv6" и вложенный "queryStrategy": "UseIPv4" — конфликт.
Глобальный "queryStrategy": "UseIPv4" и вложенный "queryStrategy": "UseIPv6" — конфликт.
Глобальный "queryStrategy": "UseIP" и вложенный "queryStrategy": "UseIPv6" — не конфликтуют.
Глобальный "queryStrategy": "UseIP" и вложенный "queryStrategy": "UseIPv4" — не конфликтуют.

{
  "dns": {
    "servers": [
      "https://1.1.1.1/dns-query",
      {
        "address": "https://8.8.8.8/dns-query",
        "domains": ["geosite:netflix"],
        "skipFallback": true,
        "queryStrategy": "UseIPv6" // Конфликт: глобальный "UseIPv4" и "UseIPv6" вложенного элемента
      }
    ],
    "queryStrategy": "UseIPv4"
  }
}

Запрос домена Netflix получит пустой ответ из-за конфликта значений "queryStrategy". Домен Netflix будет запрошен через https://1.1.1.1/dns-query и получит запись A.

disableCache: true | false

true отключает кэширование DNS. По умолчанию false (не отключено).

Это не влияет на localhost DNS (системный DNS), который всегда следует поведению кэширования DNS в Golang (cgo и pure go могут немного отличаться).

serveStale: true | false

true включает оптимистичное кэширование DNS (DNS optimistic caching). По умолчанию false (не включено).

Работает только если кэширование включено на сервере (зависит от disableCache).

serveExpiredTTL: number

Срок жизни оптимистичного кэша в секундах. По умолчанию 0 (никогда не истекает).

Если сервер использует кэш и включено оптимистичное кэширование: когда основной кэш истек, но оптимистичный кэш еще действителен, немедленно возвращается устаревшая запись DNS из кэша, а обновление кэша происходит в фоновом режиме. Это снижает Latency.

disableFallback: true | false

true отключает fallback-запросы DNS. По умолчанию false (не отключено).

disableFallbackIfMatch: true | false

true отключает fallback-запросы, если сработал список приоритетных доменов DNS-сервера. По умолчанию false (не отключено).

enableParallelQuery: true | false

true включает параллельные запросы. По умолчанию false (не включено).

Отказоустойчивость (failover) DNS по умолчанию последовательна: запрос к следующему серверу отправляется только после неудачи предыдущего или несовпадения expectedIPs / unexpectedIPs.

При включении параллельных запросов они отправляются асинхронно ко всем выбранным DNS-серверам, и применяется стратегия «Динамическая группировка, гонка внутри группы, откат между группами».

Динамическая группировка: соседние серверы в списке выбранных считаются одной группой, если их параметры clientIP, skipFallback, queryStrategy, tag, domains, expectedIPs, unexpectedIPs полностью совпадают.

Гонка внутри группы: если любой DNS-сервер в группе успешно выполнил запрос и полученный IP соответствует expectedIPs / unexpectedIPs, группа считается успешной. Результаты остальных серверов группы игнорируются.

Откат между группами: пока первая группа выполняет запрос, система ждет. Если первая группа успешна — возвращается IP. Если все серверы первой группы потерпели неудачу или IP не подошел — происходит откат (fallback) к следующей группе. Если все группы потерпели неудачу, возвращается пустой ответ.

useSystemHosts: true | false

Если true, системный файл hosts добавляется к hosts встроенного DNS.

tag: string

Для трафика запросов, исходящих от встроенного DNS (кроме режимов localhost, fakedns, TCPL, DOHL и DOQL), можно использовать этот тег для сопоставления в маршрутизации через inboundTag.

DnsServerObject

{
  "address": "1.2.3.4",
  "port": 5353,
  "domains": ["domain:xray.com"],
  "expectedIPs": ["geoip:cn"],
  "unexpectedIPs": ["geoip:cloudflare"],
  "skipFallback": false,
  "finalQuery": false,
  "tag": "dns-tag",
  "clientIP": "1.2.3.4",
  "queryStrategy": "UseIPv4",
  "disableCache": false
}

address: address

Список DNS-серверов. Поддерживаются два типа: адрес DNS (строка) и DnsServerObject.

Если значение "localhost", используется конфигурация DNS локальной системы.

Если значение — адрес DNS "IP", например, "8.8.8.8", Xray будет использовать указанный UDP-порт этого адреса для DNS-запроса. Запрос следует правилам маршрутизации. По умолчанию используется порт 53.

Если значение в формате "tcp://host", например, "tcp://8.8.8.8", Xray будет использовать DNS over TCP. Запрос следует правилам маршрутизации. По умолчанию порт 53.

Если значение в формате "tcp+local://host", например, "tcp+local://8.8.8.8", Xray будет использовать TCP Local Mode (TCPL). DNS-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound для снижения задержек. Если порт не указан, по умолчанию используется 53.

Если значение в формате "https://host:port/dns-query", например, "https://dns.google/dns-query", Xray будет использовать DNS over HTTPS (RFC8484, сокращенно DoH). Некоторые провайдеры имеют сертификаты для IP-адресов, поэтому можно указывать IP напрямую, например, https://1.1.1.1/dns-query. Также можно использовать нестандартные порты и пути, например, "https://a.b.c.d:8443/my-dns-query".

Если значение в формате "https+local://host:port/dns-query", например, "https+local://dns.google/dns-query", Xray будет использовать DoH Local Mode (DOHL). DoH-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound для снижения задержек. Обычно подходит для использования на сервере. Поддерживаются нестандартные порты и пути.

Если значение в формате "quic+local://host:port", например, "quic+local://dns.adguard.com", Xray будет использовать DOQ Local Mode (DOQL). DNS-запрос не проходит через компонент маршрутизации, а отправляется напрямую через Freedom outbound. Требуется поддержка DNS over QUIC сервером. По умолчанию используется порт 853, можно указать нестандартный.

Если значение fakedns, используется функционал FakeDNS.

::: tip О режиме local и доменах самих DNS-серверов DNS-запросы, отправляемые модулем DNS, бывают двух типов:

Режим local: соединение устанавливается ядром напрямую во внешнюю сеть. Если адрес является доменом, он будет разрешен самой системой. Логика здесь проста.

В режиме non-local DNS-запросы поступают в систему маршрутизации как внутренние запросы, а их inboundTag задается полем tag в конфигурации DNS. Если запрос направлен в локальное исходящее подключение Freedom, доменное имя самого DNS-сервера разрешается согласно sockopt.domainStrategy этого подключения (возможны петли). Если запрос направлен в исходящее подключение удаленного прокси, домен можно передать удаленной стороне для разрешения.

Поскольку обычным пользователям сложно разобраться в этой логике, рекомендуется (особенно в среде Transparent Proxy) напрямую указывать соответствующие IP для серверов с доменными именами в опции hosts модуля DNS, чтобы предотвратить возникновение петель (loop).

Кстати, DNS-запросы в режиме non-local автоматически пропускают этапы резолвинга IPIfNonMatch и IPOnDemand в модуле маршрутизации, чтобы их резолвинг не был отправлен обратно в модуль DNS, вызывая бесконечный цикл. :::

port: number

Порт DNS-сервера, например 53. Если не указано, по умолчанию 53. Для режимов DOH, DOHL, DOQL этот параметр недействителен; нестандартные порты следует указывать в URL.

domains: [string]

Список доменов. Домены из этого списка будут приоритетно запрашиваться через данный сервер. Формат доменов аналогичен конфигурации маршрутизации.

expectedIPs:[string]

Список диапазонов IP. Формат аналогичен конфигурации маршрутизации.

Если этот параметр настроен, Xray DNS проверит возвращенный IP и вернет его только в том случае, если он входит в список expectedIPs.

Если в списке присутствует *, и после фильтрации IP не найден, будет возвращен исходный IP, чтобы запрос не завершился ошибкой.

unexpectedIPs: [string]

Обратная версия expectedIPs. Исключает IP, входящие в этот список. Звездочка работает так же.

skipFallback: true | false

true — пропускать этот сервер при выполнении DNS fallback запросов. По умолчанию false (не пропускать).

timeoutMs: number

Таймаут DNS-сервера. По умолчанию 4000 мс.

Это не влияет на localhost DNS (системный DNS), который всегда следует поведению таймаута DNS в Golang (cgo и pure go могут немного отличаться).

finalQuery: true | false

Если true, запрос к этому DNS-серверу будет последней попыткой; fallback не будет инициирован.

queryStrategy: "UseIP" | "UseIPv4" | "UseIPv6" | "UseSystem"

Если не указано, наследуется из глобальной конфигурации. Если указано, позволяет дополнительно ограничить возможности этого сервера, а также задать значение по умолчанию для типов IP-запросов, инициированных самим Xray.

Внимание: этот параметр всегда ограничен глобальным queryStrategy.

Следующие параметры, если не указаны, наследуются из глобальной конфигурации, но могут переопределять её здесь

tag: string

clientIP: [string]

disableCache: true | false

serveStale: true | false

serveExpiredTTL: number