跳转至

dns-server: Lua 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-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

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-dns-server v0.2,于 2019 年 7 月 23 日发布。


此 Lua 库为 ngx_lua NGINX 模块提供了 DNS 服务器驱动程序:

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

    应与该区域的任何 RR 一起导出的无符号 32 位最小 TTL 字段。

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

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