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. *addressIPv4 или 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. Эти места имеют свои собственные копии данных для каждого запроса.
См. также
- модуль ngx_lua: https://github.com/openresty/lua-nginx-module/#readme
- библиотека lua-resty-memcached.
- библиотека lua-resty-redis.
- библиотека lua-resty-mysql.
GitHub
Вы можете найти дополнительные советы по настройке и документацию для этого модуля в репозитории GitHub для nginx-module-dns.