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

dns-server: Драйвер DNS-сервера Lua для 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-server

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-server

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

Этот документ описывает lua-resty-dns-server v0.2, выпущенный 23 июля 2019 года.


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

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

Синопсис

stream {
    server {
        listen 53 udp;
        content_by_lua_block {
            local server = require 'resty.dns.server'
            local sock, err = ngx.req.socket()
            if not sock then
                ngx.log(ngx.ERR, "не удалось получить сокет запроса: ", err)
                return ngx.exit(ngx.ERROR)
            end

            local req, err = sock:receive()
            if not req then
                ngx.log(ngx.ERR, "не удалось получить: ", err)
                return ngx.exit(ngx.ERROR)
            end

            local dns = server:new()
            local request, err = dns:decode_request(req)
            if not request then
                ngx.log(ngx.ERR, "не удалось декодировать запрос: ", err)

                local resp = dns:encode_response()
                local ok, err = sock:send(resp)
                if not ok then
                    ngx.log(ngx.ERR, "не удалось отправить: ", err)
                    ngx.exit(ngx.ERROR)
                end

                return
            end

            local query = request.questions[1]
            ngx.log(ngx.DEBUG, "qname: ", query.qname, " qtype: ", query.qtype)

            local subnet = request.subnet[1]
            if subnet then
                ngx.log(ngx.DEBUG, "адрес подсети: ",  subnet.address, " маска: ", subnet.mask, " семейство: ", subnet.family)
            end

            local cname = "sinacloud.com"

            if query.qtype == server.TYPE_CNAME or
                query.qtype == server.TYPE_AAAA or query.qtype == server.TYPE_A then

                local err = dns:create_cname_answer(query.qname, 600, cname)
                if err then
                    ngx.log(ngx.ERR, "не удалось создать ответ cname: ", err)
                    return
                end
            else
                dns:create_soa_answer("test.com", 600, "a.root-test.com", "vislee.test.com", 1515161223, 1800, 900, 604800, 86400)
            end

            local resp = dns:encode_response()
            local ok, err = sock:send(resp)
            if not ok then
                ngx.log(ngx.ERR, "не удалось отправить: ", err)
                return
            end
        }
    }

    server {
        listen 53;
        content_by_lua_block {
            local bit    = require 'bit'
            local lshift = bit.lshift
            local rshift = bit.rshift
            local band   = bit.band
            local byte   = string.byte
            local char   = string.char
            local server = require 'resty.dns.server'

            local sock, err = ngx.req.socket()
            if not sock then
                ngx.log(ngx.ERR, "не удалось получить сокет запроса: ", err)
                return ngx.exit(ngx.ERROR)
            end

            local buf, err = sock:receive(2)
            if not buf then
                ngx.log(ngx.ERR, "не удалось получить: ", err)
                return ngx.exit(ngx.ERROR)
            end

            local len_hi = byte(buf, 1)
            local len_lo = byte(buf, 2)
            local len = lshift(len_hi, 8) + len_lo
            local data, err = sock:receive(len)
            if not data then
                ngx.log(ngx.ERR, "не удалось получить: ", err)
                return ngx.exit(ngx.ERROR)
            end

            local dns = server:new()
            local request, err = dns:decode_request(data)
            if not request then
                ngx.log(ngx.ERR, "не удалось декодировать dns запрос: ", err)
                return
            end

            local query = request.questions[1]
            ngx.log(ngx.DEBUG, "qname: ", query.qname, " qtype: ", query.qtype)

            local subnet = request.subnet[1]
            if subnet then
                ngx.log(ngx.DEBUG, "адрес подсети: ",  subnet.address, " маска: ", subnet.mask, " семейство: ", subnet.family)
            end

            if query.qtype == server.TYPE_CNAME or query.qtype == server.TYPE_A then
                dns:create_cname_answer(query.qname, 600, "sinacloud.com")
            elseif query.qtype == server.TYPE_AAAA then
                local resp_header, err = dns:create_response_header(server.RCODE_NOT_IMPLEMENTED)
                resp_header.ra = 0
            else
                dns:create_soa_answer("test.com", 600, "a.root-test.com", "vislee.test.com", 1515161223, 1800, 900, 604800, 86400)
            end

            local resp = dns:encode_response()
            local len = #resp
            local len_hi = char(rshift(len, 8))
            local len_lo = char(band(len, 0xff))

            local ok, err = sock:send({len_hi, len_lo, resp})
            if not ok then
                ngx.log(ngx.ERR, "не удалось отправить: ", err)
                return
            end
            return
        }
    }
}

Методы

new

syntax: s, err = class:new()

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

decode_request

syntax: request, err = s:decode_request(buf)

Парсит DNS-запрос.

Запрос возвращает таблицу lua, которая содержит некоторые из следующих полей:

  • header: header также является таблицей lua, которая обычно содержит некоторые из следующих полей:

    • id : Идентификатор, назначенный программой, генерирующей любой вид запроса.
    • qr : Поле указывает, является ли это сообщение запросом (0) или ответом (1).
    • opcode : Поле указывает тип запроса в этом сообщении.
    • tc : Поле указывает, что это сообщение было усечено из-за превышения допустимой длины на канале передачи.
    • rd : Рекурсия желательна. Если RD установлен, он указывает серверу имен продолжать запрос рекурсивно.
    • rcode : код ответа.
    • qdcount : Поле, указывающее количество записей в секции вопросов.
  • questions : Каждая запись в questions также является таблицей lua, которая содержит некоторые из следующих полей:

    • qname : Доменное имя запроса.
    • qtype : Указывает тип запроса.
    • qclass : Указывает класс запроса. Обычно это поле IN для Интернета.

create_a_answer

syntax: err = s:create_a_answer(name, ttl, ipv4)

Создает A-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    IPv4-адрес.

create_aaaa_answer

syntax: err = s:create_aaaa_answer(name, ttl, ipv6)

Создает AAAA-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    IPv6-адрес.

create_cname_answer

syntax: err = s:create_cname_answer(name, ttl, cname)

Создает CNAME-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Имя для псевдонима.

create_txt_answer

syntax: err = s:create_txt_answer(name, ttl, txt)

Создает txt-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Текстовые строки.

create_ns_answer

syntax: err = s:create_ns_answer(name, ttl, nsdname)

Создает NS-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Указывает хост, который должен быть авторитетным для указанного класса и домена.

create_soa_answer

syntax: err = s:create_soa_answer(name, ttl, mname, rname, serial, refresh, retry, expire, minimum)

Создает SOA-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Имя сервера имен, который был оригинальным или основным источником данных для этой зоны. * rname

    Почтовый ящик лица, ответственного за эту зону. * serial

    Неподписанный 32-битный номер версии оригинальной копии зоны. * refresh

    32-битный временной интервал, через который зона должна быть обновлена. * retry

    32-битный временной интервал, который должен пройти, прежде чем неудачное обновление должно быть повторено. * expire

    32-битное значение времени, которое указывает верхний предел временного интервала, который может пройти, прежде чем зона перестанет быть авторитетной. * minimum

    Неподписанное 32-битное минимальное поле TTL, которое должно экспортироваться с любой RR из этой зоны.

create_mx_answer

syntax: err = s:create_mx_answer(name, ttl, preference, exchange)

Создает MX-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Предпочтение этого почтового обмена. * exchange

    Почтовый обмен.

create_srv_answer

syntax: err = s:create_srv_answer(name, ttl, priority, weight, port, target)

Создает SRV-записи. Возвращает nil или строку сообщения в случае ошибки. которые обычно содержат некоторые из следующих полей:

  • name

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

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

    Приоритет этого целевого хоста. * weight

    Поле веса указывает относительный вес для записей с одинаковым приоритетом. * port

    Порт на этом целевом хосте для этой службы. * target

    Доменное имя целевого хоста.

create_response_header

syntax: resp_header, err = s:create_response_header(rcode)

encode_response

syntax: resp = s:encode_response()

Кодирует ответы DNS. Возвращает строку сообщения об ответе или nil.

Константы

TYPE_A

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

TYPE_NS

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

TYPE_CNAME

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

TYPE_SOA

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

TYPE_MX

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

TYPE_TXT

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

TYPE_AAAA

syntax: typ = s.TYPE_AAAA

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

TYPE_SRV

syntax: typ = s.TYPE_SRV

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

Смотрите RFC 2782 для получения дополнительных сведений.

TYPE_ANY

syntax: typ = s.TYPE_ANY

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

RCODE_FORMAT_ERROR

RCODE_NOT_IMPLEMENTED

См. также

GitHub

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