Перейти к содержанию

dns: DNS резольвер для nginx-module-lua

Установка

Если вы еще не настроили подписку на RPM-репозиторий, зарегистрируйтесь. Затем вы можете продолжить с следующими шагами.

CentOS/RHEL 7 или Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-dns

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-dns

Чтобы использовать эту Lua библиотеку с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-dns v0.23, выпущенную 6 августа 2023 года.


Эта Lua библиотека предоставляет DNS резольвер для модуля ngx_lua nginx:

https://github.com/openresty/lua-nginx-module/#readme

Эта Lua библиотека использует API cosocket ngx_lua, который обеспечивает 100% неблокирующее поведение.

Обратите внимание, что требуется как минимум ngx_lua 0.5.12 или OpenResty 1.2.1.11.

Также требуется библиотека bit. Если вы используете LuaJIT 2.0 с ngx_lua, то библиотека bit уже доступна по умолчанию.

Обратите внимание, что эта библиотека включена и активирована по умолчанию в пакете OpenResty.

ВАЖНО: чтобы иметь возможность генерировать уникальные идентификаторы, генератор случайных чисел должен быть правильно инициализирован с помощью math.randomseed перед использованием этого модуля.

Синопсис

server {
    location = /dns {
        content_by_lua_block {
            local resolver = require "resty.dns.resolver"
            local r, err = resolver:new{
                nameservers = {"8.8.8.8", {"8.8.4.4", 53} },
                retrans = 5,  -- 5 повторных попыток при таймауте
                timeout = 2000,  -- 2 сек
                no_random = true, -- всегда начинать с первого nameserver
            }

            if not r then
                ngx.say("не удалось создать резольвер: ", err)
                return
            end

            local answers, err, tries = r:query("www.google.com", nil, {})
            if not answers then
                ngx.say("не удалось запросить DNS сервер: ", err)
                ngx.say("история повторных попыток:\n  ", table.concat(tries, "\n  "))
                return
            end

            if answers.errcode then
                ngx.say("сервер вернул код ошибки: ", answers.errcode,
                        ": ", answers.errstr)
            end

            for i, ans in ipairs(answers) do
                ngx.say(ans.name, " ", ans.address or ans.cname,
                        " тип:", ans.type, " класс:", ans.class,
                        " ttl:", ans.ttl)
            end
        }
    }
}

Методы

new

синтаксис: r, err = class:new(opts)

Создает объект dns.resolver. Возвращает nil и строку сообщения об ошибке в случае ошибки.

Принимает аргумент в виде таблицы opts. Поддерживаются следующие параметры:

  • nameservers

    список nameserver'ов, которые будут использоваться. Каждый элемент nameserver может быть либо строкой с именем хоста, либо таблицей, содержащей как строку с именем хоста, так и номер порта. Nameserver выбирается простым алгоритмом кругового выбора для каждого вызова метода query. Этот параметр обязателен. * retrans

    общее количество попыток повторной отправки DNS-запроса, когда получение ответа DNS превышает таймаут, установленный в параметре timeout. По умолчанию 5 раз. При попытке повторной отправки запроса будет выбран следующий nameserver согласно алгоритму кругового выбора. * timeout

    время в миллисекундах для ожидания ответа на одну попытку передачи запроса. Обратите внимание, что это ''не'' максимальное общее время ожидания перед тем, как сдаться, максимальное общее время ожидания можно рассчитать по выражению timeout x retrans. Параметр timeout также можно изменить, вызвав метод set_timeout. Значение по умолчанию для timeout составляет 2000 миллисекунд, или 2 секунды. * no_recurse

    логический флаг, который управляет тем, отключать ли флаг "рекурсия желательна" (RD) в UDP-запросе. По умолчанию false. * no_random

    логический флаг, который управляет тем, будет ли случайно выбран первый nameserver для запроса, если true, всегда будет начинаться с первого указанного nameserver. По умолчанию false.

destroy

синтаксис: r:destroy()

Уничтожает объект dns.resolver, освобождая все внутренние занятые ресурсы.

query

синтаксис: answers, err, tries? = r:query(name, options?, tries?)

Выполняет стандартный DNS-запрос к nameserver'ам, указанным в методе new, и возвращает все ответные записи в виде таблицы Lua, похожей на массив. В случае ошибок будет возвращено nil и строка, описывающая ошибку.

Если сервер возвращает ненулевой код ошибки, поля errcode и errstr будут установлены соответственно в возвращаемой таблице Lua.

Каждый элемент в возвращаемой таблице answers также является таблицей Lua, похожей на хеш, которая обычно содержит некоторые из следующих полей:

  • name

    Имя ресурсной записи. * type

    Текущий тип ресурсной записи, возможные значения: 1 (TYPE_A), 5 (TYPE_CNAME), 28 (TYPE_AAAA) и любые другие значения, разрешенные RFC 1035. * address

    IPv4 или IPv6 адрес в текстовом представлении, когда тип ресурсной записи равен 1 (TYPE_A) или 28 (TYPE_AAAA) соответственно. Последовательные группы из 16 бит нулей в IPv6 адресах по умолчанию не будут сжиматься, если вы хотите этого, вам нужно вызвать статический метод compress_ipv6_addr. * section

    Идентификатор секции, к которой принадлежит текущая ответная запись. Возможные значения: 1 (SECTION_AN), 2 (SECTION_NS) и 3 (SECTION_AR). * cname

    (декодированное) значение данных записи для ресурсных записей CNAME. Присутствует только для записей CNAME. * ttl

    Значение времени жизни (TTL) в секундах для текущей ресурсной записи. * class

    Текущий класс ресурсной записи, возможные значения: 1 (CLASS_IN) или любые другие значения, разрешенные RFC 1035. * preference

    Целое число предпочтения для ресурсных записей MX. Присутствует только для записей типа MX. * exchange

    Доменное имя обмена для ресурсных записей MX. Присутствует только для записей типа MX. * nsdname

    Доменное имя, которое указывает на хост, который должен быть авторитетным для указанного класса и домена. Обычно присутствует для записей типа NS. * rdata

    Сырые данные ресурса (RDATA) для ресурсных записей, которые не распознаются. * txt

    Значение записи для записей TXT. Когда в этой записи только одна строка, это поле принимает одно значение Lua-строки. В противном случае это поле принимает таблицу Lua, содержащую все строки. * ptrdname

    Значение записи для записей PTR.

Этот метод также принимает необязательный аргумент options, который принимает следующие поля:

  • qtype

    Тип вопроса. Возможные значения: 1 (TYPE_A), 5 (TYPE_CNAME), 28 (TYPE_AAAA) или любое другое значение QTYPE, указанное в RFC 1035 и RFC 3596. По умолчанию 1 (TYPE_A). * authority_section

    Когда установлено в истинное значение, возвращаемое значение answers включает секцию Authority ответа DNS. По умолчанию false. * additional_section

    Когда установлено в истинное значение, возвращаемое значение answers включает секцию Additional ответа DNS. По умолчанию false.

Необязательный параметр tries может быть предоставлен в виде пустой таблицы и будет возвращен как третий результат. Таблица будет массивом с сообщением об ошибке для каждой (если есть) неудачной попытки.

Когда происходит усечение данных, резольвер автоматически повторит попытку, используя режим TCP для запроса текущего nameserver. Все TCP-соединения имеют короткий срок жизни.

tcp_query

синтаксис: answers, err = r:tcp_query(name, options?)

Так же, как и метод query, но принудительно использует режим TCP вместо UDP.

Все TCP-соединения имеют короткий срок жизни.

Вот пример:

    local resolver = require "resty.dns.resolver"

    local r, err = resolver:new{
        nameservers = { "8.8.8.8" }
    }
    if not r then
        ngx.say("не удалось создать резольвер: ", err)
        return
    end

    local ans, err = r:tcp_query("www.google.com", { qtype = r.TYPE_A })
    if not ans then
        ngx.say("не удалось запросить: ", err)
        return
    end

    local cjson = require "cjson"
    ngx.say("записи: ", cjson.encode(ans))

set_timeout

синтаксис: r:set_timeout(time)

Переопределяет текущее значение timeout параметра на значение time в миллисекундах для всех peers nameserver.

compress_ipv6_addr

синтаксис: compressed = resty.dns.resolver.compress_ipv6_addr(address)

Сжимает последовательные группы из 16 бит нулей в текстовом формате IPv6 адреса.

Например,

    local resolver = require "resty.dns.resolver"
    local compress = resolver.compress_ipv6_addr
    local new_addr = compress("FF01:0:0:0:0:0:0:101")

вернет FF01::101 в значении new_addr.

expand_ipv6_addr

синтаксис: expanded = resty.dns.resolver.expand_ipv6_addr(address)

Расширяет последовательные группы из 16 бит нулей в текстовом формате IPv6 адреса.

Например,

    local resolver = require "resty.dns.resolver"
    local expand = resolver.expand_ipv6_addr
    local new_addr = expand("FF01::101")

вернет FF01:0:0:0:0:0:0:101 в значении new_addr.

arpa_str

синтаксис: arpa_record = resty.dns.resolver.arpa_str(address)

Генерирует обратное доменное имя для PTR-запросов как для IPv4, так и для IPv6 адресов. Сжатые IPv6 адреса будут автоматически расширены.

Например,

    local resolver = require "resty.dns.resolver"
    local ptr4 = resolver.arpa_str("1.2.3.4")
    local ptr6 = resolver.arpa_str("FF01::101")

вернет 4.3.2.1.in-addr.arpa для ptr4 и 1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.F.F.ip6.arpa для ptr6.

reverse_query

синтаксис: answers, err = r:reverse_query(address)

Выполняет PTR-запрос как для IPv4, так и для IPv6 адресов. Эта функция по сути является оберткой для команды query, которая использует команду arpa_str для динамического преобразования IP-адреса.

Константы

TYPE_A

Тип ресурсной записи A, равный десятичному числу 1.

TYPE_NS

Тип ресурсной записи NS, равный десятичному числу 2.

TYPE_CNAME

Тип ресурсной записи CNAME, равный десятичному числу 5.

TYPE_SOA

Тип ресурсной записи SOA, равный десятичному числу 6.

TYPE_PTR

Тип ресурсной записи PTR, равный десятичному числу 12.

TYPE_MX

Тип ресурсной записи MX, равный десятичному числу 15.

TYPE_TXT

Тип ресурсной записи TXT, равный десятичному числу 16.

TYPE_AAAA

синтаксис: typ = r.TYPE_AAAA

Тип ресурсной записи AAAA, равный десятичному числу 28.

TYPE_SRV

синтаксис: typ = r.TYPE_SRV

Тип ресурсной записи SRV, равный десятичному числу 33.

Смотрите RFC 2782 для подробностей.

TYPE_SPF

синтаксис: typ = r.TYPE_SPF

Тип ресурсной записи SPF, равный десятичному числу 99.

Смотрите RFC 4408 для подробностей.

CLASS_IN

синтаксис: class = r.CLASS_IN

Тип ресурсной записи Internet, равный десятичному числу 1.

SECTION_AN

синтаксис: stype = r.SECTION_AN

Идентификатор секции Answer в ответе DNS. Равен десятичному числу 1.

SECTION_NS

синтаксис: stype = r.SECTION_NS

Идентификатор секции Authority в ответе DNS. Равен десятичному числу 2.

SECTION_AR

синтаксис: stype = r.SECTION_AR

Идентификатор секции Additional в ответе DNS. Равен десятичному числу 3.

Автоматическая регистрация ошибок

По умолчанию модуль ngx_lua выполняет регистрацию ошибок, когда происходят ошибки сокета. Если вы уже выполняете правильную обработку ошибок в своем собственном Lua коде, то рекомендуется отключить эту автоматическую регистрацию ошибок, отключив директиву lua_socket_log_errors модуля ngx_lua, а именно,

    lua_socket_log_errors off;

Ограничения

  • Эта библиотека не может использоваться в контекстах кода, таких как set_by_lua*, log_by_lua* и header_filter_by_lua*, где API cosocket ngx_lua недоступен.
  • Экземпляр объекта resty.dns.resolver не может быть сохранен в переменной Lua на уровне модуля Lua, потому что он будет разделяться всеми параллельными запросами, обрабатываемыми одним и тем же рабочим процессом nginx (см. https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker) и приведет к плохим условиям гонки, когда параллельные запросы пытаются использовать один и тот же экземпляр resty.dns.resolver. Вы всегда должны инициализировать объекты resty.dns.resolver в локальных переменных функции или в таблице ngx.ctx. Эти места имеют свои собственные копии данных для каждого запроса.

См. также

GitHub

Вы можете найти дополнительные советы по настройке и документацию для этого модуля в репозитории GitHub для nginx-module-dns.