跳转至

dns: nginx-module-lua 的 DNS 解析器

安装

如果您尚未设置 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,于 2023 年 8 月 6 日发布。


此 Lua 库为 ngx_lua nginx 模块提供了一个 DNS 解析器:

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

此 Lua 库利用了 ngx_lua 的 cosocket API,确保 100% 非阻塞行为。

请注意,至少需要 ngx_lua 0.5.12OpenResty 1.2.1.11

此外,还需要 bit library。如果您使用的是与 ngx_lua 兼容的 LuaJIT 2.0,则 bit 库默认情况下已可用。

请注意,此库在 OpenResty bundle 中默认捆绑并启用。

重要提示:为了能够生成唯一的 ID,必须在使用此模块之前使用 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, -- 始终从第一个名称服务器开始
            }

            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

    要使用的名称服务器列表。每个名称服务器条目可以是单个主机名字符串或一个包含主机名字符串和端口号的表。名称服务器通过简单的轮询算法在每次 query 方法调用中选择。此选项是必需的。 * retrans

    当接收 DNS 响应超时时,重新发送 DNS 请求的总次数,依据 timeout 设置。默认为 5 次。在尝试重新发送查询时,将根据轮询算法选择下一个名称服务器。 * timeout

    等待单次请求传输响应的时间(以毫秒为单位)。请注意,这不是放弃之前的最大总等待时间,最大总等待时间可以通过表达式 timeout x retrans 计算。timeout 设置也可以通过调用 set_timeout 方法进行更改。默认的 timeout 设置为 2000 毫秒,即 2 秒。 * no_recurse

    一个布尔标志,控制是否在 UDP 请求中禁用“期望递归”(RD)标志。默认为 false。 * no_random

    一个布尔标志,控制是否随机选择要首先查询的名称服务器,如果为 true,则始终从列出的第一个名称服务器开始。默认为 false

destroy

语法: r:destroy()

通过释放所有内部占用的资源来销毁 dns.resolver 对象。

query

语法: answers, err, tries? = r:query(name, options?, tries?)

new 方法指定的名称服务器执行标准 DNS 查询,并以类似数组的 Lua 表返回所有答案记录。如果发生错误,将返回 nil 和描述错误的字符串。

如果服务器返回非零错误代码,则返回的 Lua 表中将相应设置 errcodeerrstr 字段。

返回的 answers 表中的每个条目也是一个类似哈希的 Lua 表,通常包含以下一些字段:

  • name

    资源记录名称。 * type

    当前资源记录类型,可能的值为 1 (TYPE_A)、5 (TYPE_CNAME)、28 (TYPE_AAAA) 和 RFC 1035 允许的其他值。 * address

    当资源记录类型为 1 (TYPE_A) 或 28 (TYPE_AAAA) 时,分别以文本表示形式返回的 IPv4 或 IPv6 地址。IPv6 地址中的连续 16 位零组默认不会被压缩,如果您希望这样,您需要调用 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) 或 RFC 1035 和 RFC 3596 指定的任何其他 QTYPE 值。默认为 1 (TYPE_A)。 * authority_section

    当设置为真值时,answers 返回值包括 DNS 响应的 Authority 部分。默认为 false。 * additional_section

    当设置为真值时,answers 返回值包括 DNS 响应的 Additional 部分。默认为 false

可选参数 tries 可以提供为空表,并将作为第三个结果返回。该表将是一个数组,包含每次失败尝试的错误消息(如果有)。

当发生数据截断时,解析器将自动重试使用 TCP 传输模式查询当前名称服务器。所有 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)

通过 time 参数(以毫秒为单位)覆盖所有名称服务器对等体的当前 timeout 设置。

compress_ipv6_addr

语法: compressed = resty.dns.resolver.compress_ipv6_addr(address)

压缩 IPv6 地址文本格式中的连续 16 位零组。

例如,

    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)

扩展 IPv6 地址文本格式中的连续 16 位零组。

例如,

    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")

将为 ptr4 生成 4.3.2.1.in-addr.arpa,为 ptr6 生成 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

reverse_query

语法: answers, err = r:reverse_query(address)

对 IPv4 和 IPv6 地址执行 PTR 查找。此函数基本上是 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

DNS 响应中 Answer 部分的标识符。等于十进制数字 1

SECTION_NS

语法: stype = r.SECTION_NS

DNS 响应中 Authority 部分的标识符。等于十进制数字 2

SECTION_AR

语法: stype = r.SECTION_AR

DNS 响应中 Additional 部分的标识符。等于十进制数字 3

自动错误日志记录

默认情况下,底层的 ngx_lua 模块在发生套接字错误时会进行错误日志记录。如果您在自己的 Lua 代码中已经进行了适当的错误处理,建议您通过关闭 ngx_lualua_socket_log_errors 指令来禁用此自动错误日志记录,即,

    lua_socket_log_errors off;

限制

  • 此库不能在 set_by_lua*log_by_lua*header_filter_by_lua* 等上下文中使用,因为 ngx_lua cosocket API 在这些上下文中不可用。
  • resty.dns.resolver 对象实例不能在 Lua 模块级别存储在 Lua 变量中,因为它将被同一 nginx 工作进程处理的所有并发请求共享(请参见 https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker),并导致在并发请求尝试使用同一 resty.dns.resolver 实例时出现不良竞争条件。您应该始终在函数局部变量或 ngx.ctx 表中初始化 resty.dns.resolver 对象。这些地方对每个请求都有自己的数据副本。

另请参阅

GitHub

您可以在 nginx-module-dns 的 GitHub 仓库 中找到此模块的其他配置提示和文档。