Files
XTLS_Xray-docs-next/docs/ru/development/lua/guide/dns.md
T

7.7 KiB

Скрипты DNS

Когда скрипт указан через dns.script, обработка встроенного DNS передаётся функции Lua HandleDNSQuery.

Минимальный пример

Добавьте в существующую конфигурацию:

{
  "dns": {
    "script": "dns.lua",
    "servers": ["1.1.1.1"]
  }
}

Сохраните dns.lua:

local dns = require("xray.dns")
local server = dns.Servers[1]

function HandleDNSQuery(domain, ipv4, ipv6, fake)
    return server:Query(domain, ipv4, ipv6, fake)
end

Этот фрагмент нужно объединить с полной конфигурацией. Правила поиска файла описаны в разделе Пути к файлам скриптов.

Пример передаёт параметры вышестоящему серверу без изменений и напрямую возвращает результат запроса. Полный список параметров, ограничения возвращаемых значений и поведение при ошибках описаны в HandleDNSQuery.

Связь с конфигурацией DNS

До передачи запроса скрипту по-прежнему выполняются проверка домена встроенным DNS, глобальные ограничения типов запросов и обработка Hosts. Если Hosts уже вернул допустимые IP-адреса или явно отклонил запрос, скрипт не вызывается. Если Hosts заменил домен, скрипт запрашивает заменённый домен.

При включении скрипта он управляет выбором вышестоящего сервера, поэтому domains, skipFallback, finalQuery, disableFallback, disableFallbackIfMatch и enableParallelQuery больше не имеют практического значения.

Для запроса к конкретному серверу используйте serverObj:Query. Настройки отдельного сервера, которые продолжают действовать, перечислены в описании этого API.

::: tip Совет Если соответствие между серверами и правилами фильтрации постоянно, удобнее и быстрее настроить expectedIPs / unexpectedIPs напрямую. Можно также не задавать эти параметры и фильтровать результаты в Lua методами вроде ipMatcherObj:FilterIPs, что даёт больше гибкости. :::

Пример: выбор сервера по домену и фильтрация результатов

Следующий пример выбирает вышестоящие серверы по категории домена и при необходимости фильтрует китайские IP-адреса. Для каждого сервера задаётся id, по которому скрипт выполняет запросы и выбирает резервный сервер:

{
  "dns": {
    "script": "dns.lua",
    "tag": "dns-proxy",
    "servers": [
      { "id": "cf", "address": "1.1.1.1" },
      { "id": "google", "address": "8.8.8.8" },
      { "id": "cn114", "address": "114.114.114.114", "tag": "dns-direct" },
      { "id": "cn223", "address": "223.5.5.5", "tag": "dns-direct" },
      {
        "id": "google-ecs",
        "address": "8.8.8.8",
        "clientIp": "222.85.85.85"
      },
      {
        "id": "google-alt-ecs",
        "address": "8.8.4.4",
        "clientIp": "222.85.85.85"
      }
    ]
  },
  "routing": {
    "rules": [
      { "inboundTag": ["dns-direct"], "outboundTag": "direct" },
      { "inboundTag": ["dns-proxy"], "outboundTag": "proxy" }
    ]
  },
  "outbounds": [
    { "tag": "direct", "protocol": "freedom" },
    {
      "tag": "proxy",
      "protocol": "vless",
      "settings": {
        // ...
      }
    }
  ]
}

dns-direct и dns-proxy — входящие теги запросов к вышестоящим DNS-серверам. Правила маршрутизации выше выбирают для них прямое исходящее подключение direct и прокси VLESS proxy соответственно.

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

local geodata = require("xray.geodata")
local dns = require("xray.dns")
local servers = {}
for _, server in ipairs(dns.Servers) do
    servers[server.ID] = server
end

local googleDomainMatcher = geodata.BuildDomainMatcher("geosite:google")
local cnDomainMatcher = geodata.BuildDomainMatcher("geosite:cn")
local foreignDomainMatcher = geodata.BuildDomainMatcher("geosite:geolocation-!cn")
local cnIPMatcher = geodata.BuildIPMatcher("geoip:cn")

function HandleDNSQuery(domain, ipv4, ipv6, fake)
    local queries
    if googleDomainMatcher:MatchAny(domain) then
        -- Использовать публичный DNS для доменов Google.
        queries = {{"cf"}, {"google"}}
    elseif cnDomainMatcher:MatchAny(domain) then
        -- Для китайских доменов сначала запросить прямой DNS, оставить китайские IP, затем перейти к DNS через прокси.
        queries = {
            {"cn114", "cn"}, {"cn223", "cn"}, {"cf"}, {"google"}
        }
    elseif foreignDomainMatcher:MatchAny(domain) then
        -- Для некитайских доменов сначала исключить китайские IP, затем попробовать запросы с ECS.
        queries = {
            {"cf", "non-cn"}, {"google", "non-cn"},
            {"google-ecs"}, {"google-alt-ecs"}
        }
    else
        -- Для доменов вне списков сначала искать китайские IP через ECS, затем перейти к обычному публичному DNS.
        queries = {
            {"google-ecs", "cn"}, {"google-alt-ecs", "cn"},
            {"cf"}, {"google"}
        }
    end

    local lastError = "Нет DNS-результатов, удовлетворяющих условиям"
    for _, query in ipairs(queries) do
        local ips, ttl, err =
            servers[query[1]]:Query(domain, ipv4, ipv6, fake)
        if err then
            lastError = err
        else
            if query[2] then
                local inCn, outsideCn = cnIPMatcher:FilterIPs(ips)
                if query[2] == "cn" then
                    ips = inCn
                else
                    ips = outsideCn
                end
            end
            if ips and #ips > 0 then
                return ips, ttl, nil
            end
        end
    end
    return nil, 0, lastError
end

Инициализация верхнего уровня и сохранение состояния скриптов DNS описаны в разделе Жизненный цикл пула.