Squashed 'src/deps/src/lua-resty-dns/' content from commit 869d2fbb0
git-subtree-dir: src/deps/src/lua-resty-dns git-subtree-split: 869d2fbb009b6ada93a5a10cb93acd1cc12bd53f
This commit is contained in:
commit
0f4a0cb0ef
|
@ -0,0 +1 @@
|
|||
*.t linguist-language=Text
|
|
@ -0,0 +1,10 @@
|
|||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
go
|
||||
t/servroot/
|
||||
reindex
|
||||
nginx
|
||||
ctags
|
||||
tags
|
||||
a.lua
|
|
@ -0,0 +1,53 @@
|
|||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
os: linux
|
||||
|
||||
language: c
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- axel
|
||||
- cpanminus
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- download-cache
|
||||
|
||||
env:
|
||||
global:
|
||||
- JOBS=3
|
||||
- TEST_NGINX_SLEEP=0.006
|
||||
- LUAJIT_PREFIX=/opt/luajit21
|
||||
- LUAJIT_LIB=$LUAJIT_PREFIX/lib
|
||||
- LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1
|
||||
- LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH
|
||||
matrix:
|
||||
- NGINX_VERSION=1.9.15
|
||||
- NGINX_VERSION=1.19.3
|
||||
|
||||
install:
|
||||
- sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1)
|
||||
- git clone https://github.com/openresty/openresty.git ../openresty
|
||||
- git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module
|
||||
- git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core
|
||||
- git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache
|
||||
- git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx
|
||||
- git clone https://github.com/openresty/nginx-devel-utils.git
|
||||
- git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2
|
||||
|
||||
before_script:
|
||||
- cd luajit2/
|
||||
- make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1)
|
||||
- sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1)
|
||||
- cd ..
|
||||
|
||||
script:
|
||||
- export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH
|
||||
- ngx-build $NGINX_VERSION --with-debug --with-cc-opt="-DDEBUG_MALLOC" --with-ipv6 --add-module=../lua-nginx-module > build.log 2>&1 || (cat build.log && exit 1)
|
||||
- prove -r t
|
|
@ -0,0 +1,18 @@
|
|||
OPENRESTY_PREFIX=/usr/local/openresty
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
LUA_INCLUDE_DIR ?= $(PREFIX)/include
|
||||
LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
|
||||
INSTALL ?= install
|
||||
|
||||
.PHONY: all test install
|
||||
|
||||
all: ;
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/dns
|
||||
$(INSTALL) lib/resty/dns/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/dns/
|
||||
|
||||
test: all
|
||||
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
Name
|
||||
====
|
||||
|
||||
lua-resty-dns - Lua DNS resolver for the ngx_lua based on the cosocket API
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
* [Name](#name)
|
||||
* [Status](#status)
|
||||
* [Description](#description)
|
||||
* [Synopsis](#synopsis)
|
||||
* [Methods](#methods)
|
||||
* [new](#new)
|
||||
* [query](#query)
|
||||
* [tcp_query](#tcp_query)
|
||||
* [set_timeout](#set_timeout)
|
||||
* [compress_ipv6_addr](#compress_ipv6_addr)
|
||||
* [expand_ipv6_addr](#expand_ipv6_addr)
|
||||
* [arpa_str](#arpa_str)
|
||||
* [reverse_query](#reverse_query)
|
||||
* [Constants](#constants)
|
||||
* [TYPE_A](#type_a)
|
||||
* [TYPE_NS](#type_ns)
|
||||
* [TYPE_CNAME](#type_cname)
|
||||
* [TYPE_SOA](#type_soa)
|
||||
* [TYPE_PTR](#type_ptr)
|
||||
* [TYPE_MX](#type_mx)
|
||||
* [TYPE_TXT](#type_txt)
|
||||
* [TYPE_AAAA](#type_aaaa)
|
||||
* [TYPE_SRV](#type_srv)
|
||||
* [TYPE_SPF](#type_spf)
|
||||
* [CLASS_IN](#class_in)
|
||||
* [SECTION_AN](#section_an)
|
||||
* [SECTION_NS](#section_ns)
|
||||
* [SECTION_AR](#section_ar)
|
||||
* [Automatic Error Logging](#automatic-error-logging)
|
||||
* [Limitations](#limitations)
|
||||
* [TODO](#todo)
|
||||
* [Author](#author)
|
||||
* [Copyright and License](#copyright-and-license)
|
||||
* [See Also](#see-also)
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
This library is considered production ready.
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
This Lua library provies a DNS resolver for the ngx_lua nginx module:
|
||||
|
||||
https://github.com/openresty/lua-nginx-module/#readme
|
||||
|
||||
This Lua library takes advantage of ngx_lua's cosocket API, which ensures
|
||||
100% nonblocking behavior.
|
||||
|
||||
Note that at least [ngx_lua 0.5.12](https://github.com/openresty/lua-nginx-module/tags) or [OpenResty 1.2.1.11](http://openresty.org/#Download) is required.
|
||||
|
||||
Also, the [bit library](http://bitop.luajit.org/) is also required. If you're using LuaJIT 2.0 with ngx_lua, then the `bit` library is already available by default.
|
||||
|
||||
Note that, this library is bundled and enabled by default in the [OpenResty bundle](http://openresty.org/).
|
||||
|
||||
IMPORTANT: to be able generate unique ids, the random generator must be properly seeded using `math.randomseed` prior to using this module.
|
||||
|
||||
Synopsis
|
||||
========
|
||||
|
||||
```nginx
|
||||
lua_package_path "/path/to/lua-resty-dns/lib/?.lua;;";
|
||||
|
||||
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 retransmissions on receive timeout
|
||||
timeout = 2000, -- 2 sec
|
||||
no_random = true, -- always start with first nameserver
|
||||
}
|
||||
|
||||
if not r then
|
||||
ngx.say("failed to instantiate the resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local answers, err, tries = r:query("www.google.com", nil, {})
|
||||
if not answers then
|
||||
ngx.say("failed to query the DNS server: ", err)
|
||||
ngx.say("retry historie:\n ", table.concat(tries, "\n "))
|
||||
return
|
||||
end
|
||||
|
||||
if answers.errcode then
|
||||
ngx.say("server returned error code: ", answers.errcode,
|
||||
": ", answers.errstr)
|
||||
end
|
||||
|
||||
for i, ans in ipairs(answers) do
|
||||
ngx.say(ans.name, " ", ans.address or ans.cname,
|
||||
" type:", ans.type, " class:", ans.class,
|
||||
" ttl:", ans.ttl)
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Methods
|
||||
=======
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
new
|
||||
---
|
||||
`syntax: r, err = class:new(opts)`
|
||||
|
||||
Creates a dns.resolver object. Returns `nil` and an message string on error.
|
||||
|
||||
It accepts a `opts` table argument. The following options are supported:
|
||||
|
||||
* `nameservers`
|
||||
|
||||
a list of nameservers to be used. Each nameserver entry can be either a single hostname string or a table holding both the hostname string and the port number. The nameserver is picked up by a simple round-robin algorithm for each `query` method call. This option is required.
|
||||
* `retrans`
|
||||
|
||||
the total number of times of retransmitting the DNS request when receiving a DNS response times out according to the `timeout` setting. Defaults to `5` times. When trying to retransmit the query, the next nameserver according to the round-robin algorithm will be picked up.
|
||||
* `timeout`
|
||||
|
||||
the time in milliseconds for waiting for the respond for a single attempt of request transmition. note that this is ''not'' the maximal total waiting time before giving up, the maximal total waiting time can be calculated by the expression `timeout x retrans`. The `timeout` setting can also be changed by calling the `set_timeout` method. The default `timeout` setting is 2000 milliseconds, or 2 seconds.
|
||||
* `no_recurse`
|
||||
|
||||
a boolean flag controls whether to disable the "recursion desired" (RD) flag in the UDP request. Defaults to `false`.
|
||||
* `no_random`
|
||||
|
||||
a boolean flag controls whether to randomly pick the nameserver to query first, if `true` will always start with the first nameserver listed. Defaults to `false`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
query
|
||||
-----
|
||||
`syntax: answers, err, tries? = r:query(name, options?, tries?)`
|
||||
|
||||
Performs a DNS standard query to the nameservers specified by the `new` method,
|
||||
and returns all the answer records in an array-like Lua table. In case of errors, it will
|
||||
return `nil` and a string describing the error instead.
|
||||
|
||||
If the server returns a non-zero error code, the fields `errcode` and `errstr` will be set accordingly in the Lua table returned.
|
||||
|
||||
Each entry in the `answers` returned table value is also a hash-like Lua table
|
||||
which usually takes some of the following fields:
|
||||
|
||||
* `name`
|
||||
|
||||
The resource record name.
|
||||
* `type`
|
||||
|
||||
The current resource record type, possible values are `1` (`TYPE_A`), `5` (`TYPE_CNAME`), `28` (`TYPE_AAAA`), and any other values allowed by RFC 1035.
|
||||
* `address`
|
||||
|
||||
The IPv4 or IPv6 address in their textual representations when the resource record type is either `1` (`TYPE_A`) or `28` (`TYPE_AAAA`), respectively. Secussesive 16-bit zero groups in IPv6 addresses will not be compressed by default, if you want that, you need to call the `compress_ipv6_addr` static method instead.
|
||||
* `section`
|
||||
|
||||
The identifier of the section that the current answer record belongs to. Possible values are `1` (`SECTION_AN`), `2` (`SECTION_NS`), and `3` (`SECTION_AR`).
|
||||
* `cname`
|
||||
|
||||
The (decoded) record data value for `CNAME` resource records. Only present for `CNAME` records.
|
||||
* `ttl`
|
||||
|
||||
The time-to-live (TTL) value in seconds for the current resource record.
|
||||
* `class`
|
||||
|
||||
The current resource record class, possible values are `1` (`CLASS_IN`) or any other values allowed by RFC 1035.
|
||||
* `preference`
|
||||
|
||||
The preference integer number for `MX` resource records. Only present for `MX` type records.
|
||||
* `exchange`
|
||||
|
||||
The exchange domain name for `MX` resource records. Only present for `MX` type records.
|
||||
* `nsdname`
|
||||
|
||||
A domain-name which specifies a host which should be authoritative for the specified class and domain. Usually present for `NS` type records.
|
||||
* `rdata`
|
||||
|
||||
The raw resource data (RDATA) for resource records that are not recognized.
|
||||
* `txt`
|
||||
|
||||
The record value for `TXT` records. When there is only one character string in this record, then this field takes a single Lua string. Otherwise this field takes a Lua table holding all the strings.
|
||||
* `ptrdname`
|
||||
|
||||
The record value for `PTR` records.
|
||||
|
||||
This method also takes an optional `options` argument table, which takes the following fields:
|
||||
|
||||
* `qtype`
|
||||
|
||||
The type of the question. Possible values are `1` (`TYPE_A`), `5` (`TYPE_CNAME`), `28` (`TYPE_AAAA`), or any other QTYPE value specified by RFC 1035 and RFC 3596. Default to `1` (`TYPE_A`).
|
||||
* `authority_section`
|
||||
|
||||
When set to a true value, the `answers` return value includes the `Authority` section of the DNS response. Default to `false`.
|
||||
* `additional_section`
|
||||
|
||||
When set to a true value, the `answers` return value includes the `Additional` section of the DNS response. Default to `false`.
|
||||
|
||||
The optional parameter `tries` can be provided as an empty table, and will be
|
||||
returned as a third result. The table will be an array with the error message
|
||||
for each (if any) failed try.
|
||||
|
||||
When data truncation happens, the resolver will automatically retry using the TCP transport mode
|
||||
to query the current nameserver. All TCP connections are short lived.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
tcp_query
|
||||
---------
|
||||
`syntax: answers, err = r:tcp_query(name, options?)`
|
||||
|
||||
Just like the `query` method, but enforce the TCP transport mode instead of UDP.
|
||||
|
||||
All TCP connections are short lived.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```lua
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{
|
||||
nameservers = { "8.8.8.8" }
|
||||
}
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:tcp_query("www.google.com", { qtype = r.TYPE_A })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local cjson = require "cjson"
|
||||
ngx.say("records: ", cjson.encode(ans))
|
||||
```
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
set_timeout
|
||||
-----------
|
||||
`syntax: r:set_timeout(time)`
|
||||
|
||||
Overrides the current `timeout` setting by the `time` argument in milliseconds for all the nameserver peers.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
compress_ipv6_addr
|
||||
------------------
|
||||
`syntax: compressed = resty.dns.resolver.compress_ipv6_addr(address)`
|
||||
|
||||
Compresses the successive 16-bit zero groups in the textual format of the IPv6 address.
|
||||
|
||||
For example,
|
||||
|
||||
```lua
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local compress = resolver.compress_ipv6_addr
|
||||
local new_addr = compress("FF01:0:0:0:0:0:0:101")
|
||||
```
|
||||
|
||||
will yield `FF01::101` in the `new_addr` return value.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
expand_ipv6_addr
|
||||
------------------
|
||||
`syntax: expanded = resty.dns.resolver.expand_ipv6_addr(address)`
|
||||
|
||||
Expands the successive 16-bit zero groups in the textual format of the IPv6 address.
|
||||
|
||||
For example,
|
||||
|
||||
```lua
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local expand = resolver.expand_ipv6_addr
|
||||
local new_addr = expand("FF01::101")
|
||||
```
|
||||
|
||||
will yield `FF01:0:0:0:0:0:0:101` in the `new_addr` return value.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
arpa_str
|
||||
------------------
|
||||
`syntax: arpa_record = resty.dns.resolver.arpa_str(address)`
|
||||
|
||||
Generates the reverse domain name for PTR lookups for both IPv4 and IPv6 addresses. Compressed IPv6 addresses
|
||||
will be automatically expanded.
|
||||
|
||||
For example,
|
||||
|
||||
```lua
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local ptr4 = resolver.arpa_str("1.2.3.4")
|
||||
local ptr6 = resolver.arpa_str("FF01::101")
|
||||
```
|
||||
|
||||
will yield `4.3.2.1.in-addr.arpa` for `ptr4` and `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` for `ptr6`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
reverse_query
|
||||
------------------
|
||||
`syntax: answers, err = r:reverse_query(address)`
|
||||
|
||||
Performs a PTR lookup for both IPv4 and IPv6 addresses. This function is basically a wrapper for the `query` command
|
||||
which uses the `arpa_str` command to convert the IP address on the fly.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Constants
|
||||
=========
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_A
|
||||
------
|
||||
|
||||
The `A` resource record type, equal to the decimal number `1`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_NS
|
||||
-------
|
||||
|
||||
The `NS` resource record type, equal to the decimal number `2`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_CNAME
|
||||
----------
|
||||
|
||||
The `CNAME` resource record type, equal to the decimal number `5`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_SOA
|
||||
----------
|
||||
|
||||
The `SOA` resource record type, equal to the decimal number `6`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_PTR
|
||||
--------
|
||||
|
||||
The `PTR` resource record type, equal to the decimal number `12`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_MX
|
||||
-------
|
||||
|
||||
The `MX` resource record type, equal to the decimal number `15`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_TXT
|
||||
--------
|
||||
|
||||
The `TXT` resource record type, equal to the decimal number `16`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_AAAA
|
||||
---------
|
||||
`syntax: typ = r.TYPE_AAAA`
|
||||
|
||||
The `AAAA` resource record type, equal to the decimal number `28`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_SRV
|
||||
---------
|
||||
`syntax: typ = r.TYPE_SRV`
|
||||
|
||||
The `SRV` resource record type, equal to the decimal number `33`.
|
||||
|
||||
See RFC 2782 for details.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TYPE_SPF
|
||||
---------
|
||||
`syntax: typ = r.TYPE_SPF`
|
||||
|
||||
The `SPF` resource record type, equal to the decimal number `99`.
|
||||
|
||||
See RFC 4408 for details.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
CLASS_IN
|
||||
--------
|
||||
`syntax: class = r.CLASS_IN`
|
||||
|
||||
The `Internet` resource record type, equal to the decimal number `1`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
SECTION_AN
|
||||
----------
|
||||
`syntax: stype = r.SECTION_AN`
|
||||
|
||||
Identifier of the `Answer` section in the DNS response. Equal to decimal number `1`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
SECTION_NS
|
||||
----------
|
||||
`syntax: stype = r.SECTION_NS`
|
||||
|
||||
Identifier of the `Authority` section in the DNS response. Equal to the decimal number `2`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
SECTION_AR
|
||||
----------
|
||||
`syntax: stype = r.SECTION_AR`
|
||||
|
||||
Idnetifier of the `Additional` section in the DNS response. Equal to the decimal number `3`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Automatic Error Logging
|
||||
=======================
|
||||
|
||||
By default the underlying [ngx_lua](https://github.com/openresty/lua-nginx-module/#readme) module
|
||||
does error logging when socket errors happen. If you are already doing proper error
|
||||
handling in your own Lua code, then you are recommended to disable this automatic error logging by turning off [ngx_lua](https://github.com/openresty/lua-nginx-module/#readme)'s [lua_socket_log_errors](https://github.com/openresty/lua-nginx-module/#lua_socket_log_errors) directive, that is,
|
||||
|
||||
```nginx
|
||||
lua_socket_log_errors off;
|
||||
```
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Limitations
|
||||
===========
|
||||
|
||||
* This library cannot be used in code contexts like `set_by_lua*`, `log_by_lua*`, and
|
||||
`header_filter_by_lua*` where the ngx_lua cosocket API is not available.
|
||||
* The `resty.dns.resolver` object instance cannot be stored in a Lua variable at the Lua module level,
|
||||
because it will then be shared by all the concurrent requests handled by the same nginx
|
||||
worker process (see
|
||||
https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker ) and
|
||||
result in bad race conditions when concurrent requests are trying to use the same `resty.dns.resolver` instance.
|
||||
You should always initiate `resty.dns.resolver` objects in function local
|
||||
variables or in the `ngx.ctx` table. These places all have their own data copies for
|
||||
each request.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
* Concurrent (or parallel) query mode
|
||||
* Better support for other resource record types like `TLSA`.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
Yichun "agentzh" Zhang (章亦春) <agentzh@gmail.com>, OpenResty Inc.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
Copyright and License
|
||||
=====================
|
||||
|
||||
This module is licensed under the BSD license.
|
||||
|
||||
Copyright (C) 2012-2019, by Yichun "agentzh" Zhang (章亦春) <agentzh@gmail.com>, OpenResty Inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
See Also
|
||||
========
|
||||
* the ngx_lua module: https://github.com/openresty/lua-nginx-module/#readme
|
||||
* the [lua-resty-memcached](https://github.com/agentzh/lua-resty-memcached) library.
|
||||
* the [lua-resty-redis](https://github.com/agentzh/lua-resty-redis) library.
|
||||
* the [lua-resty-mysql](https://github.com/agentzh/lua-resty-mysql) library.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
name=lua-resty-dns
|
||||
abstract=Lua DNS resolver for the ngx_lua based on the cosocket API
|
||||
author=Yichun "agentzh" Zhang (agentzh)
|
||||
is_original=yes
|
||||
license=2bsd
|
||||
lib_dir=lib
|
||||
doc_dir=lib
|
||||
repo_link=https://github.com/openresty/lua-resty-dns
|
||||
main_module=lib/resty/dns/resolver.lua
|
|
@ -0,0 +1,982 @@
|
|||
-- Copyright (C) Yichun Zhang (agentzh)
|
||||
|
||||
|
||||
-- local socket = require "socket"
|
||||
local bit = require "bit"
|
||||
local udp = ngx.socket.udp
|
||||
local rand = math.random
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
local find = string.find
|
||||
local gsub = string.gsub
|
||||
local sub = string.sub
|
||||
local rep = string.rep
|
||||
local format = string.format
|
||||
local band = bit.band
|
||||
local rshift = bit.rshift
|
||||
local lshift = bit.lshift
|
||||
local insert = table.insert
|
||||
local concat = table.concat
|
||||
local re_sub = ngx.re.sub
|
||||
local tcp = ngx.socket.tcp
|
||||
local log = ngx.log
|
||||
local DEBUG = ngx.DEBUG
|
||||
local unpack = unpack
|
||||
local setmetatable = setmetatable
|
||||
local type = type
|
||||
local ipairs = ipairs
|
||||
|
||||
|
||||
local ok, new_tab = pcall(require, "table.new")
|
||||
if not ok then
|
||||
new_tab = function (narr, nrec) return {} end
|
||||
end
|
||||
|
||||
|
||||
local DOT_CHAR = byte(".")
|
||||
local ZERO_CHAR = byte("0")
|
||||
local COLON_CHAR = byte(":")
|
||||
|
||||
local IP6_ARPA = "ip6.arpa"
|
||||
|
||||
local TYPE_A = 1
|
||||
local TYPE_NS = 2
|
||||
local TYPE_CNAME = 5
|
||||
local TYPE_SOA = 6
|
||||
local TYPE_PTR = 12
|
||||
local TYPE_MX = 15
|
||||
local TYPE_TXT = 16
|
||||
local TYPE_AAAA = 28
|
||||
local TYPE_SRV = 33
|
||||
local TYPE_SPF = 99
|
||||
|
||||
local CLASS_IN = 1
|
||||
|
||||
local SECTION_AN = 1
|
||||
local SECTION_NS = 2
|
||||
local SECTION_AR = 3
|
||||
|
||||
|
||||
local _M = {
|
||||
_VERSION = '0.22',
|
||||
TYPE_A = TYPE_A,
|
||||
TYPE_NS = TYPE_NS,
|
||||
TYPE_CNAME = TYPE_CNAME,
|
||||
TYPE_SOA = TYPE_SOA,
|
||||
TYPE_PTR = TYPE_PTR,
|
||||
TYPE_MX = TYPE_MX,
|
||||
TYPE_TXT = TYPE_TXT,
|
||||
TYPE_AAAA = TYPE_AAAA,
|
||||
TYPE_SRV = TYPE_SRV,
|
||||
TYPE_SPF = TYPE_SPF,
|
||||
CLASS_IN = CLASS_IN,
|
||||
SECTION_AN = SECTION_AN,
|
||||
SECTION_NS = SECTION_NS,
|
||||
SECTION_AR = SECTION_AR
|
||||
}
|
||||
|
||||
|
||||
local resolver_errstrs = {
|
||||
"format error", -- 1
|
||||
"server failure", -- 2
|
||||
"name error", -- 3
|
||||
"not implemented", -- 4
|
||||
"refused", -- 5
|
||||
}
|
||||
|
||||
local soa_int32_fields = { "serial", "refresh", "retry", "expire", "minimum" }
|
||||
|
||||
local mt = { __index = _M }
|
||||
|
||||
|
||||
local arpa_tmpl = new_tab(72, 0)
|
||||
|
||||
for i = 1, #IP6_ARPA do
|
||||
arpa_tmpl[64 + i] = byte(IP6_ARPA, i)
|
||||
end
|
||||
|
||||
for i = 2, 64, 2 do
|
||||
arpa_tmpl[i] = DOT_CHAR
|
||||
end
|
||||
|
||||
|
||||
function _M.new(class, opts)
|
||||
if not opts then
|
||||
return nil, "no options table specified"
|
||||
end
|
||||
|
||||
local servers = opts.nameservers
|
||||
if not servers or #servers == 0 then
|
||||
return nil, "no nameservers specified"
|
||||
end
|
||||
|
||||
local timeout = opts.timeout or 2000 -- default 2 sec
|
||||
|
||||
local n = #servers
|
||||
|
||||
local socks = {}
|
||||
|
||||
for i = 1, n do
|
||||
local server = servers[i]
|
||||
local sock, err = udp()
|
||||
if not sock then
|
||||
return nil, "failed to create udp socket: " .. err
|
||||
end
|
||||
|
||||
local host, port
|
||||
if type(server) == 'table' then
|
||||
host = server[1]
|
||||
port = server[2] or 53
|
||||
|
||||
else
|
||||
host = server
|
||||
port = 53
|
||||
servers[i] = {host, port}
|
||||
end
|
||||
|
||||
local ok, err = sock:setpeername(host, port)
|
||||
if not ok then
|
||||
return nil, "failed to set peer name: " .. err
|
||||
end
|
||||
|
||||
sock:settimeout(timeout)
|
||||
|
||||
insert(socks, sock)
|
||||
end
|
||||
|
||||
local tcp_sock, err = tcp()
|
||||
if not tcp_sock then
|
||||
return nil, "failed to create tcp socket: " .. err
|
||||
end
|
||||
|
||||
tcp_sock:settimeout(timeout)
|
||||
|
||||
return setmetatable(
|
||||
{ cur = opts.no_random and 1 or rand(1, n),
|
||||
socks = socks,
|
||||
tcp_sock = tcp_sock,
|
||||
servers = servers,
|
||||
retrans = opts.retrans or 5,
|
||||
no_recurse = opts.no_recurse,
|
||||
}, mt)
|
||||
end
|
||||
|
||||
|
||||
local function pick_sock(self, socks)
|
||||
local cur = self.cur
|
||||
|
||||
if cur == #socks then
|
||||
self.cur = 1
|
||||
else
|
||||
self.cur = cur + 1
|
||||
end
|
||||
|
||||
return socks[cur]
|
||||
end
|
||||
|
||||
|
||||
local function _get_cur_server(self)
|
||||
local cur = self.cur
|
||||
|
||||
local servers = self.servers
|
||||
|
||||
if cur == 1 then
|
||||
return servers[#servers]
|
||||
end
|
||||
|
||||
return servers[cur - 1]
|
||||
end
|
||||
|
||||
|
||||
function _M.set_timeout(self, timeout)
|
||||
local socks = self.socks
|
||||
if not socks then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
for i = 1, #socks do
|
||||
local sock = socks[i]
|
||||
sock:settimeout(timeout)
|
||||
end
|
||||
|
||||
local tcp_sock = self.tcp_sock
|
||||
if not tcp_sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
tcp_sock:settimeout(timeout)
|
||||
end
|
||||
|
||||
|
||||
local function _encode_name(s)
|
||||
return char(#s) .. s
|
||||
end
|
||||
|
||||
|
||||
local function _decode_name(buf, pos)
|
||||
local labels = {}
|
||||
local nptrs = 0
|
||||
local p = pos
|
||||
while nptrs < 128 do
|
||||
local fst = byte(buf, p)
|
||||
|
||||
if not fst then
|
||||
return nil, 'truncated';
|
||||
end
|
||||
|
||||
-- print("fst at ", p, ": ", fst)
|
||||
|
||||
if fst == 0 then
|
||||
if nptrs == 0 then
|
||||
pos = pos + 1
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
if band(fst, 0xc0) ~= 0 then
|
||||
-- being a pointer
|
||||
if nptrs == 0 then
|
||||
pos = pos + 2
|
||||
end
|
||||
|
||||
nptrs = nptrs + 1
|
||||
|
||||
local snd = byte(buf, p + 1)
|
||||
if not snd then
|
||||
return nil, 'truncated'
|
||||
end
|
||||
|
||||
p = lshift(band(fst, 0x3f), 8) + snd + 1
|
||||
|
||||
-- print("resolving ptr ", p, ": ", byte(buf, p))
|
||||
|
||||
else
|
||||
-- being a label
|
||||
local label = sub(buf, p + 1, p + fst)
|
||||
insert(labels, label)
|
||||
|
||||
-- print("resolved label ", label)
|
||||
|
||||
p = p + fst + 1
|
||||
|
||||
if nptrs == 0 then
|
||||
pos = p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return concat(labels, "."), pos
|
||||
end
|
||||
|
||||
|
||||
local function _build_request(qname, id, no_recurse, opts)
|
||||
local qtype
|
||||
|
||||
if opts then
|
||||
qtype = opts.qtype
|
||||
end
|
||||
|
||||
if not qtype then
|
||||
qtype = 1 -- A record
|
||||
end
|
||||
|
||||
local ident_hi = char(rshift(id, 8))
|
||||
local ident_lo = char(band(id, 0xff))
|
||||
|
||||
local flags
|
||||
if no_recurse then
|
||||
-- print("found no recurse")
|
||||
flags = "\0\0"
|
||||
else
|
||||
flags = "\1\0"
|
||||
end
|
||||
|
||||
local nqs = "\0\1"
|
||||
local nan = "\0\0"
|
||||
local nns = "\0\0"
|
||||
local nar = "\0\0"
|
||||
local typ = char(rshift(qtype, 8), band(qtype, 0xff))
|
||||
local class = "\0\1" -- the Internet class
|
||||
|
||||
if byte(qname, 1) == DOT_CHAR then
|
||||
return nil, "bad name"
|
||||
end
|
||||
|
||||
local name = gsub(qname, "([^.]+)%.?", _encode_name) .. '\0'
|
||||
|
||||
return {
|
||||
ident_hi, ident_lo, flags, nqs, nan, nns, nar,
|
||||
name, typ, class
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function parse_section(answers, section, buf, start_pos, size,
|
||||
should_skip)
|
||||
local pos = start_pos
|
||||
|
||||
for _ = 1, size do
|
||||
-- print(format("ans %d: qtype:%d qclass:%d", i, qtype, qclass))
|
||||
local ans = {}
|
||||
|
||||
if not should_skip then
|
||||
insert(answers, ans)
|
||||
end
|
||||
|
||||
ans.section = section
|
||||
|
||||
local name
|
||||
name, pos = _decode_name(buf, pos)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
ans.name = name
|
||||
|
||||
-- print("name: ", name)
|
||||
|
||||
local type_hi = byte(buf, pos)
|
||||
local type_lo = byte(buf, pos + 1)
|
||||
local typ = lshift(type_hi, 8) + type_lo
|
||||
|
||||
ans.type = typ
|
||||
|
||||
-- print("type: ", typ)
|
||||
|
||||
local class_hi = byte(buf, pos + 2)
|
||||
local class_lo = byte(buf, pos + 3)
|
||||
local class = lshift(class_hi, 8) + class_lo
|
||||
|
||||
ans.class = class
|
||||
|
||||
-- print("class: ", class)
|
||||
|
||||
local byte_1, byte_2, byte_3, byte_4 = byte(buf, pos + 4, pos + 7)
|
||||
|
||||
local ttl = lshift(byte_1, 24) + lshift(byte_2, 16)
|
||||
+ lshift(byte_3, 8) + byte_4
|
||||
|
||||
-- print("ttl: ", ttl)
|
||||
|
||||
ans.ttl = ttl
|
||||
|
||||
local len_hi = byte(buf, pos + 8)
|
||||
local len_lo = byte(buf, pos + 9)
|
||||
local len = lshift(len_hi, 8) + len_lo
|
||||
|
||||
-- print("record len: ", len)
|
||||
|
||||
pos = pos + 10
|
||||
|
||||
if typ == TYPE_A then
|
||||
|
||||
if len ~= 4 then
|
||||
return nil, "bad A record value length: " .. len
|
||||
end
|
||||
|
||||
local addr_bytes = { byte(buf, pos, pos + 3) }
|
||||
local addr = concat(addr_bytes, ".")
|
||||
-- print("ipv4 address: ", addr)
|
||||
|
||||
ans.address = addr
|
||||
|
||||
pos = pos + 4
|
||||
|
||||
elseif typ == TYPE_CNAME then
|
||||
|
||||
local cname, p = _decode_name(buf, pos)
|
||||
if not cname then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
if p - pos ~= len then
|
||||
return nil, format("bad cname record length: %d ~= %d",
|
||||
p - pos, len)
|
||||
end
|
||||
|
||||
pos = p
|
||||
|
||||
-- print("cname: ", cname)
|
||||
|
||||
ans.cname = cname
|
||||
|
||||
elseif typ == TYPE_AAAA then
|
||||
|
||||
if len ~= 16 then
|
||||
return nil, "bad AAAA record value length: " .. len
|
||||
end
|
||||
|
||||
local addr_bytes = { byte(buf, pos, pos + 15) }
|
||||
local flds = {}
|
||||
for i = 1, 16, 2 do
|
||||
local a = addr_bytes[i]
|
||||
local b = addr_bytes[i + 1]
|
||||
if a == 0 then
|
||||
insert(flds, format("%x", b))
|
||||
|
||||
else
|
||||
insert(flds, format("%x%02x", a, b))
|
||||
end
|
||||
end
|
||||
|
||||
-- we do not compress the IPv6 addresses by default
|
||||
-- due to performance considerations
|
||||
|
||||
ans.address = concat(flds, ":")
|
||||
|
||||
pos = pos + 16
|
||||
|
||||
elseif typ == TYPE_MX then
|
||||
|
||||
-- print("len = ", len)
|
||||
|
||||
if len < 3 then
|
||||
return nil, "bad MX record value length: " .. len
|
||||
end
|
||||
|
||||
local pref_hi = byte(buf, pos)
|
||||
local pref_lo = byte(buf, pos + 1)
|
||||
|
||||
ans.preference = lshift(pref_hi, 8) + pref_lo
|
||||
|
||||
local host, p = _decode_name(buf, pos + 2)
|
||||
if not host then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
if p - pos ~= len then
|
||||
return nil, format("bad cname record length: %d ~= %d",
|
||||
p - pos, len)
|
||||
end
|
||||
|
||||
ans.exchange = host
|
||||
|
||||
pos = p
|
||||
|
||||
elseif typ == TYPE_SRV then
|
||||
if len < 7 then
|
||||
return nil, "bad SRV record value length: " .. len
|
||||
end
|
||||
|
||||
local prio_hi = byte(buf, pos)
|
||||
local prio_lo = byte(buf, pos + 1)
|
||||
ans.priority = lshift(prio_hi, 8) + prio_lo
|
||||
|
||||
local weight_hi = byte(buf, pos + 2)
|
||||
local weight_lo = byte(buf, pos + 3)
|
||||
ans.weight = lshift(weight_hi, 8) + weight_lo
|
||||
|
||||
local port_hi = byte(buf, pos + 4)
|
||||
local port_lo = byte(buf, pos + 5)
|
||||
ans.port = lshift(port_hi, 8) + port_lo
|
||||
|
||||
local name, p = _decode_name(buf, pos + 6)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
if p - pos ~= len then
|
||||
return nil, format("bad srv record length: %d ~= %d",
|
||||
p - pos, len)
|
||||
end
|
||||
|
||||
ans.target = name
|
||||
|
||||
pos = p
|
||||
|
||||
elseif typ == TYPE_NS then
|
||||
|
||||
local name, p = _decode_name(buf, pos)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
if p - pos ~= len then
|
||||
return nil, format("bad cname record length: %d ~= %d",
|
||||
p - pos, len)
|
||||
end
|
||||
|
||||
pos = p
|
||||
|
||||
-- print("name: ", name)
|
||||
|
||||
ans.nsdname = name
|
||||
|
||||
elseif typ == TYPE_TXT or typ == TYPE_SPF then
|
||||
|
||||
local key = (typ == TYPE_TXT) and "txt" or "spf"
|
||||
|
||||
local slen = byte(buf, pos)
|
||||
if slen + 1 > len then
|
||||
-- truncate the over-run TXT record data
|
||||
slen = len
|
||||
end
|
||||
|
||||
-- print("slen: ", len)
|
||||
|
||||
local val = sub(buf, pos + 1, pos + slen)
|
||||
local last = pos + len
|
||||
pos = pos + slen + 1
|
||||
|
||||
if pos < last then
|
||||
-- more strings to be processed
|
||||
-- this code path is usually cold, so we do not
|
||||
-- merge the following loop on this code path
|
||||
-- with the processing logic above.
|
||||
|
||||
val = {val}
|
||||
local idx = 2
|
||||
repeat
|
||||
local slen = byte(buf, pos)
|
||||
if pos + slen + 1 > last then
|
||||
-- truncate the over-run TXT record data
|
||||
slen = last - pos - 1
|
||||
end
|
||||
|
||||
val[idx] = sub(buf, pos + 1, pos + slen)
|
||||
idx = idx + 1
|
||||
pos = pos + slen + 1
|
||||
|
||||
until pos >= last
|
||||
end
|
||||
|
||||
ans[key] = val
|
||||
|
||||
elseif typ == TYPE_PTR then
|
||||
|
||||
local name, p = _decode_name(buf, pos)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
if p - pos ~= len then
|
||||
return nil, format("bad cname record length: %d ~= %d",
|
||||
p - pos, len)
|
||||
end
|
||||
|
||||
pos = p
|
||||
|
||||
-- print("name: ", name)
|
||||
|
||||
ans.ptrdname = name
|
||||
|
||||
elseif typ == TYPE_SOA then
|
||||
local name, p = _decode_name(buf, pos)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
ans.mname = name
|
||||
|
||||
pos = p
|
||||
name, p = _decode_name(buf, pos)
|
||||
if not name then
|
||||
return nil, pos
|
||||
end
|
||||
ans.rname = name
|
||||
|
||||
for _, field in ipairs(soa_int32_fields) do
|
||||
local byte_1, byte_2, byte_3, byte_4 = byte(buf, p, p + 3)
|
||||
ans[field] = lshift(byte_1, 24) + lshift(byte_2, 16)
|
||||
+ lshift(byte_3, 8) + byte_4
|
||||
p = p + 4
|
||||
end
|
||||
|
||||
pos = p
|
||||
|
||||
else
|
||||
-- for unknown types, just forward the raw value
|
||||
|
||||
ans.rdata = sub(buf, pos, pos + len - 1)
|
||||
pos = pos + len
|
||||
end
|
||||
end
|
||||
|
||||
return pos
|
||||
end
|
||||
|
||||
|
||||
local function parse_response(buf, id, opts)
|
||||
local n = #buf
|
||||
if n < 12 then
|
||||
return nil, 'truncated';
|
||||
end
|
||||
|
||||
-- header layout: ident flags nqs nan nns nar
|
||||
|
||||
local ident_hi = byte(buf, 1)
|
||||
local ident_lo = byte(buf, 2)
|
||||
local ans_id = lshift(ident_hi, 8) + ident_lo
|
||||
|
||||
-- print("id: ", id, ", ans id: ", ans_id)
|
||||
|
||||
if ans_id ~= id then
|
||||
-- identifier mismatch and throw it away
|
||||
log(DEBUG, "id mismatch in the DNS reply: ", ans_id, " ~= ", id)
|
||||
return nil, "id mismatch"
|
||||
end
|
||||
|
||||
local flags_hi = byte(buf, 3)
|
||||
local flags_lo = byte(buf, 4)
|
||||
local flags = lshift(flags_hi, 8) + flags_lo
|
||||
|
||||
-- print(format("flags: 0x%x", flags))
|
||||
|
||||
if band(flags, 0x8000) == 0 then
|
||||
return nil, format("bad QR flag in the DNS response")
|
||||
end
|
||||
|
||||
if band(flags, 0x200) ~= 0 then
|
||||
return nil, "truncated"
|
||||
end
|
||||
|
||||
local code = band(flags, 0xf)
|
||||
|
||||
-- print(format("code: %d", code))
|
||||
|
||||
local nqs_hi = byte(buf, 5)
|
||||
local nqs_lo = byte(buf, 6)
|
||||
local nqs = lshift(nqs_hi, 8) + nqs_lo
|
||||
|
||||
-- print("nqs: ", nqs)
|
||||
|
||||
if nqs ~= 1 then
|
||||
return nil, format("bad number of questions in DNS response: %d", nqs)
|
||||
end
|
||||
|
||||
local nan_hi = byte(buf, 7)
|
||||
local nan_lo = byte(buf, 8)
|
||||
local nan = lshift(nan_hi, 8) + nan_lo
|
||||
|
||||
-- print("nan: ", nan)
|
||||
|
||||
local nns_hi = byte(buf, 9)
|
||||
local nns_lo = byte(buf, 10)
|
||||
local nns = lshift(nns_hi, 8) + nns_lo
|
||||
|
||||
local nar_hi = byte(buf, 11)
|
||||
local nar_lo = byte(buf, 12)
|
||||
local nar = lshift(nar_hi, 8) + nar_lo
|
||||
|
||||
-- skip the question part
|
||||
|
||||
local ans_qname, pos = _decode_name(buf, 13)
|
||||
if not ans_qname then
|
||||
return nil, pos
|
||||
end
|
||||
|
||||
-- print("qname in reply: ", ans_qname)
|
||||
|
||||
-- print("question: ", sub(buf, 13, pos))
|
||||
|
||||
if pos + 3 + nan * 12 > n then
|
||||
-- print(format("%d > %d", pos + 3 + nan * 12, n))
|
||||
return nil, 'truncated';
|
||||
end
|
||||
|
||||
-- question section layout: qname qtype(2) qclass(2)
|
||||
|
||||
--[[
|
||||
local type_hi = byte(buf, pos)
|
||||
local type_lo = byte(buf, pos + 1)
|
||||
local ans_type = lshift(type_hi, 8) + type_lo
|
||||
]]
|
||||
|
||||
-- print("ans qtype: ", ans_type)
|
||||
|
||||
local class_hi = byte(buf, pos + 2)
|
||||
local class_lo = byte(buf, pos + 3)
|
||||
local qclass = lshift(class_hi, 8) + class_lo
|
||||
|
||||
-- print("ans qclass: ", qclass)
|
||||
|
||||
if qclass ~= 1 then
|
||||
return nil, format("unknown query class %d in DNS response", qclass)
|
||||
end
|
||||
|
||||
pos = pos + 4
|
||||
|
||||
local answers = {}
|
||||
|
||||
if code ~= 0 then
|
||||
answers.errcode = code
|
||||
answers.errstr = resolver_errstrs[code] or "unknown"
|
||||
end
|
||||
|
||||
local authority_section, additional_section
|
||||
|
||||
if opts then
|
||||
authority_section = opts.authority_section
|
||||
additional_section = opts.additional_section
|
||||
if opts.qtype == TYPE_SOA then
|
||||
authority_section = true
|
||||
end
|
||||
end
|
||||
|
||||
local err
|
||||
|
||||
pos, err = parse_section(answers, SECTION_AN, buf, pos, nan)
|
||||
|
||||
if not pos then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if not authority_section and not additional_section then
|
||||
return answers
|
||||
end
|
||||
|
||||
pos, err = parse_section(answers, SECTION_NS, buf, pos, nns,
|
||||
not authority_section)
|
||||
|
||||
if not pos then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if not additional_section then
|
||||
return answers
|
||||
end
|
||||
|
||||
pos, err = parse_section(answers, SECTION_AR, buf, pos, nar)
|
||||
|
||||
if not pos then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return answers
|
||||
end
|
||||
|
||||
|
||||
local function _gen_id(self)
|
||||
local id = self._id -- for regression testing
|
||||
if id then
|
||||
return id
|
||||
end
|
||||
return rand(0, 65535) -- two bytes
|
||||
end
|
||||
|
||||
|
||||
local function _tcp_query(self, query, id, opts)
|
||||
local sock = self.tcp_sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
log(DEBUG, "query the TCP server due to reply truncation")
|
||||
|
||||
local server = _get_cur_server(self)
|
||||
|
||||
local ok, err = sock:connect(server[1], server[2])
|
||||
if not ok then
|
||||
return nil, "failed to connect to TCP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
end
|
||||
|
||||
query = concat(query, "")
|
||||
local len = #query
|
||||
|
||||
local len_hi = char(rshift(len, 8))
|
||||
local len_lo = char(band(len, 0xff))
|
||||
|
||||
local bytes, err = sock:send({len_hi, len_lo, query})
|
||||
if not bytes then
|
||||
return nil, "failed to send query to TCP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
end
|
||||
|
||||
local buf, err = sock:receive(2)
|
||||
if not buf then
|
||||
return nil, "failed to receive the reply length field from TCP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
end
|
||||
|
||||
len_hi = byte(buf, 1)
|
||||
len_lo = byte(buf, 2)
|
||||
len = lshift(len_hi, 8) + len_lo
|
||||
|
||||
-- print("tcp message len: ", len)
|
||||
|
||||
buf, err = sock:receive(len)
|
||||
if not buf then
|
||||
return nil, "failed to receive the reply message body from TCP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
end
|
||||
|
||||
local answers, err = parse_response(buf, id, opts)
|
||||
if not answers then
|
||||
return nil, "failed to parse the reply from the TCP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
end
|
||||
|
||||
sock:close()
|
||||
|
||||
return answers
|
||||
end
|
||||
|
||||
|
||||
function _M.tcp_query(self, qname, opts)
|
||||
local socks = self.socks
|
||||
if not socks then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
pick_sock(self, socks)
|
||||
|
||||
local id = _gen_id(self)
|
||||
|
||||
local query, err = _build_request(qname, id, self.no_recurse, opts)
|
||||
if not query then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return _tcp_query(self, query, id, opts)
|
||||
end
|
||||
|
||||
|
||||
function _M.query(self, qname, opts, tries)
|
||||
local socks = self.socks
|
||||
if not socks then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
local id = _gen_id(self)
|
||||
|
||||
local query, err = _build_request(qname, id, self.no_recurse, opts)
|
||||
if not query then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- local cjson = require "cjson"
|
||||
-- print("query: ", cjson.encode(concat(query, "")))
|
||||
|
||||
local retrans = self.retrans
|
||||
if tries then
|
||||
tries[1] = nil
|
||||
end
|
||||
|
||||
-- print("retrans: ", retrans)
|
||||
|
||||
for i = 1, retrans do
|
||||
local sock = pick_sock(self, socks)
|
||||
|
||||
local ok
|
||||
ok, err = sock:send(query)
|
||||
if not ok then
|
||||
local server = _get_cur_server(self)
|
||||
err = "failed to send request to UDP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
|
||||
else
|
||||
local buf
|
||||
|
||||
for _ = 1, 128 do
|
||||
buf, err = sock:receive(4096)
|
||||
if err then
|
||||
local server = _get_cur_server(self)
|
||||
err = "failed to receive reply from UDP server "
|
||||
.. concat(server, ":") .. ": " .. err
|
||||
break
|
||||
end
|
||||
|
||||
if buf then
|
||||
local answers
|
||||
answers, err = parse_response(buf, id, opts)
|
||||
if err == "truncated" then
|
||||
answers, err = _tcp_query(self, query, id, opts)
|
||||
end
|
||||
|
||||
if err and err ~= "id mismatch" then
|
||||
break
|
||||
end
|
||||
|
||||
if answers then
|
||||
return answers, nil, tries
|
||||
end
|
||||
end
|
||||
-- only here in case of an "id mismatch"
|
||||
end
|
||||
end
|
||||
|
||||
if tries then
|
||||
tries[i] = err
|
||||
tries[i + 1] = nil -- ensure termination for user supplied table
|
||||
end
|
||||
end
|
||||
|
||||
return nil, err, tries
|
||||
end
|
||||
|
||||
|
||||
function _M.compress_ipv6_addr(addr)
|
||||
local addr = re_sub(addr, "^(0:)+|(:0)+$|:(0:)+", "::", "jo")
|
||||
if addr == "::0" then
|
||||
addr = "::"
|
||||
end
|
||||
|
||||
return addr
|
||||
end
|
||||
|
||||
|
||||
local function _expand_ipv6_addr(addr)
|
||||
if find(addr, "::", 1, true) then
|
||||
local ncol, addrlen = 8, #addr
|
||||
|
||||
for i = 1, addrlen do
|
||||
if byte(addr, i) == COLON_CHAR then
|
||||
ncol = ncol - 1
|
||||
end
|
||||
end
|
||||
|
||||
if byte(addr, 1) == COLON_CHAR then
|
||||
addr = "0" .. addr
|
||||
end
|
||||
|
||||
if byte(addr, -1) == COLON_CHAR then
|
||||
addr = addr .. "0"
|
||||
end
|
||||
|
||||
addr = re_sub(addr, "::", ":" .. rep("0:", ncol), "jo")
|
||||
end
|
||||
|
||||
return addr
|
||||
end
|
||||
|
||||
|
||||
_M.expand_ipv6_addr = _expand_ipv6_addr
|
||||
|
||||
|
||||
function _M.arpa_str(addr)
|
||||
if find(addr, ":", 1, true) then
|
||||
addr = _expand_ipv6_addr(addr)
|
||||
local idx, hidx, addrlen = 1, 1, #addr
|
||||
|
||||
for i = addrlen, 0, -1 do
|
||||
local s = byte(addr, i)
|
||||
if s == COLON_CHAR or not s then
|
||||
for _ = hidx, 4 do
|
||||
arpa_tmpl[idx] = ZERO_CHAR
|
||||
idx = idx + 2
|
||||
end
|
||||
hidx = 1
|
||||
else
|
||||
arpa_tmpl[idx] = s
|
||||
idx = idx + 2
|
||||
hidx = hidx + 1
|
||||
end
|
||||
end
|
||||
|
||||
addr = char(unpack(arpa_tmpl))
|
||||
else
|
||||
addr = re_sub(addr, [[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})]],
|
||||
"$4.$3.$2.$1.in-addr.arpa", "ajo")
|
||||
end
|
||||
|
||||
return addr
|
||||
end
|
||||
|
||||
|
||||
function _M.reverse_query(self, addr)
|
||||
return self.query(self, self.arpa_str(addr),
|
||||
{qtype = self.TYPE_PTR})
|
||||
end
|
||||
|
||||
|
||||
return _M
|
|
@ -0,0 +1,334 @@
|
|||
package TestDNS;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use 5.010001;
|
||||
use Test::Nginx::Socket::Lua -Base;
|
||||
#use JSON::XS;
|
||||
|
||||
use constant {
|
||||
TYPE_A => 1,
|
||||
TYPE_SOA => 6,
|
||||
TYPE_TXT => 16,
|
||||
TYPE_CNAME => 5,
|
||||
TYPE_AAAA => 28,
|
||||
TYPE_SRV => 33,
|
||||
CLASS_INTERNET => 1,
|
||||
};
|
||||
|
||||
sub encode_name ($);
|
||||
sub encode_ipv4 ($);
|
||||
sub encode_ipv6 ($);
|
||||
sub encode_record ($);
|
||||
sub gen_dns_reply ($$);
|
||||
|
||||
sub Test::Base::Filter::dns {
|
||||
my ($self, $code) = @_;
|
||||
|
||||
my $args = $self->current_arguments;
|
||||
#warn "args: $args";
|
||||
if (defined $args && $args ne 'tcp' && $args ne 'udp') {
|
||||
die "Invalid argument to the \"dns\" filter: $args\n";
|
||||
}
|
||||
|
||||
my $mode = $args // 'udp';
|
||||
|
||||
my $block = $self->current_block;
|
||||
|
||||
my $pointer_spec = $block->dns_pointers;
|
||||
my @pointers;
|
||||
if (defined $pointer_spec) {
|
||||
my @loops = split /\s*,\s*/, $pointer_spec;
|
||||
for my $loop (@loops) {
|
||||
my @nodes = split /\s*=>\s*/, $loop;
|
||||
my $prev;
|
||||
for my $n (@nodes) {
|
||||
if ($n !~ /^\d+$/ || $n == 0) {
|
||||
die "bad name ID in the --- dns_pointers: $n\n";
|
||||
}
|
||||
|
||||
if (!defined $prev) {
|
||||
$prev = $n;
|
||||
next;
|
||||
}
|
||||
|
||||
$pointers[$prev] = $n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $input = eval $code;
|
||||
if ($@) {
|
||||
die "failed to evaluate code $code: $@\n";
|
||||
}
|
||||
|
||||
if (!ref $input) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
if (ref $input eq 'ARRAY') {
|
||||
my @replies;
|
||||
for my $t (@$input) {
|
||||
push @replies, gen_dns_reply($t, $mode);
|
||||
}
|
||||
|
||||
return \@replies;
|
||||
}
|
||||
|
||||
if (ref $input eq 'HASH') {
|
||||
return gen_dns_reply($input, $mode);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
sub gen_dns_reply ($$) {
|
||||
my ($t, $mode) = @_;
|
||||
|
||||
my @raw_names;
|
||||
push @raw_names, \($t->{qname});
|
||||
|
||||
my $answers = $t->{answer} // [];
|
||||
if (!ref $answers) {
|
||||
$answers = [$answers];
|
||||
}
|
||||
|
||||
my $authority_answers = $t->{authority} // [];
|
||||
if (!ref $authority_answers) {
|
||||
$authority_answers = [$authority_answers];
|
||||
}
|
||||
|
||||
my $additional_answers = $t->{additional} // [];
|
||||
if (!ref $additional_answers) {
|
||||
$additional_answers = [$additional_answers];
|
||||
}
|
||||
|
||||
for my $ans (@$answers) {
|
||||
push @raw_names, \($ans->{name});
|
||||
if (defined $ans->{cname}) {
|
||||
push @raw_names, \($ans->{cname});
|
||||
}
|
||||
}
|
||||
|
||||
for my $nsans (@$authority_answers) {
|
||||
push @raw_names, \($nsans->{name});
|
||||
if (defined $nsans->{cname}) {
|
||||
push @raw_names, \($nsans->{cname});
|
||||
}
|
||||
}
|
||||
|
||||
for my $arans (@$additional_answers) {
|
||||
push @raw_names, \($arans->{name});
|
||||
if (defined $arans->{cname}) {
|
||||
push @raw_names, \($arans->{cname});
|
||||
}
|
||||
}
|
||||
|
||||
for my $rname (@raw_names) {
|
||||
$$rname = encode_name($$rname // "");
|
||||
}
|
||||
|
||||
my $qname = $t->{qname};
|
||||
|
||||
my $s = '';
|
||||
|
||||
my $id = $t->{id} // 0;
|
||||
|
||||
$s .= pack("n", $id);
|
||||
#warn "id: ", length($s), " ", encode_json([$s]);
|
||||
|
||||
my $qr = $t->{qr} // 1;
|
||||
|
||||
my $opcode = $t->{opcode} // 0;
|
||||
|
||||
my $aa = $t->{aa} // 0;
|
||||
|
||||
my $tc = $t->{tc} // 0;
|
||||
my $rd = $t->{rd} // 1;
|
||||
my $ra = $t->{ra} // 1;
|
||||
|
||||
my $ad = $t->{ad} // 0;
|
||||
my $cd = $t->{cd} // 0;
|
||||
|
||||
my $rcode = $t->{rcode} // 0;
|
||||
|
||||
my $flags = ($qr << 15) + ($opcode << 11) + ($aa << 10) + ($tc << 9)
|
||||
+ ($rd << 8) + ($ra << 7) + ($ad << 4) + ($cd << 5) + $rcode;
|
||||
|
||||
#warn sprintf("flags: %b", $flags);
|
||||
|
||||
$flags = pack("n", $flags);
|
||||
$s .= $flags;
|
||||
|
||||
#warn "flags: ", length($flags), " ", encode_json([$flags]);
|
||||
|
||||
my $qdcount = $t->{qdcount} // 1;
|
||||
my $ancount = $t->{ancount} // scalar @$answers;
|
||||
my $nscount = $t->{nscount} // scalar @$authority_answers;
|
||||
my $arcount = $t->{arcount} // scalar @$additional_answers;
|
||||
|
||||
$s .= pack("nnnn", $qdcount, $ancount, $nscount, $arcount);
|
||||
|
||||
#warn "qname: ", length($qname), " ", encode_json([$qname]);
|
||||
|
||||
$s .= $qname;
|
||||
|
||||
my $qs_type = $t->{qtype} // TYPE_A;
|
||||
my $qs_class = $t->{qclass} // CLASS_INTERNET;
|
||||
|
||||
$s .= pack("nn", $qs_type, $qs_class);
|
||||
|
||||
for my $ans (@$answers) {
|
||||
$s .= encode_record($ans);
|
||||
}
|
||||
|
||||
for my $nsans (@$authority_answers) {
|
||||
$s .= encode_record($nsans);
|
||||
}
|
||||
|
||||
for my $arans (@$additional_answers) {
|
||||
$s .= encode_record($arans);
|
||||
}
|
||||
|
||||
if ($mode eq 'tcp') {
|
||||
return pack("n", length($s)) . $s;
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub encode_ipv4 ($) {
|
||||
my $txt = shift;
|
||||
my @bytes = split /\./, $txt;
|
||||
return pack("CCCC", @bytes), 4;
|
||||
}
|
||||
|
||||
sub encode_ipv6 ($) {
|
||||
my $txt = shift;
|
||||
my @groups = split /:/, $txt;
|
||||
my $nils = 0;
|
||||
my $nonnils = 0;
|
||||
for my $g (@groups) {
|
||||
if ($g eq '') {
|
||||
$nils++;
|
||||
} else {
|
||||
$nonnils++;
|
||||
$g = hex($g);
|
||||
}
|
||||
}
|
||||
|
||||
my $total = $nils + $nonnils;
|
||||
if ($total > 8 ) {
|
||||
die "Invalid IPv6 address: too many groups: $total: $txt";
|
||||
}
|
||||
|
||||
if ($nils) {
|
||||
my $found = 0;
|
||||
my @new_groups;
|
||||
for my $g (@groups) {
|
||||
if ($g eq '') {
|
||||
if ($found) {
|
||||
next;
|
||||
}
|
||||
|
||||
for (1 .. 8 - $nonnils) {
|
||||
push @new_groups, 0;
|
||||
}
|
||||
|
||||
$found = 1;
|
||||
|
||||
} else {
|
||||
push @new_groups, $g;
|
||||
}
|
||||
}
|
||||
|
||||
@groups = @new_groups;
|
||||
}
|
||||
|
||||
if (@groups != 8) {
|
||||
die "Invalid IPv6 address: $txt: @groups\n";
|
||||
}
|
||||
|
||||
#warn "IPv6 groups: @groups";
|
||||
|
||||
return pack("nnnnnnnn", @groups), 16;
|
||||
}
|
||||
|
||||
sub encode_name ($) {
|
||||
my $name = shift;
|
||||
$name =~ s/([^.]+)\.?/chr(length($1)) . $1/ge;
|
||||
$name .= "\0";
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub encode_record ($) {
|
||||
my $ans = shift;
|
||||
my $name = $ans->{name};
|
||||
my $type = $ans->{type};
|
||||
my $class = $ans->{class};
|
||||
my $ttl = $ans->{ttl};
|
||||
my $rdlength = $ans->{rdlength};
|
||||
my $rddata = $ans->{rddata};
|
||||
|
||||
my $ipv4 = $ans->{ipv4};
|
||||
if (defined $ipv4) {
|
||||
my ($data, $len) = encode_ipv4($ipv4);
|
||||
$rddata //= $data;
|
||||
$rdlength //= $len;
|
||||
$type //= TYPE_A;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
my $ipv6 = $ans->{ipv6};
|
||||
if (defined $ipv6) {
|
||||
my ($data, $len) = encode_ipv6($ipv6);
|
||||
$rddata //= $data;
|
||||
$rdlength //= $len;
|
||||
$type //= TYPE_AAAA;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
my $cname = $ans->{cname};
|
||||
if (defined $cname) {
|
||||
$rddata //= $cname;
|
||||
$rdlength //= length $rddata;
|
||||
$type //= TYPE_CNAME;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
my $txt = $ans->{txt};
|
||||
if (defined $txt) {
|
||||
$rddata //= $txt;
|
||||
$rdlength //= length $rddata;
|
||||
$type //= TYPE_TXT;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
|
||||
my $srv = $ans->{srv};
|
||||
if (defined $srv) {
|
||||
$rddata //= pack("nnn", $ans->{priority}, $ans->{weight}, $ans->{port}) . encode_name($srv);
|
||||
$rdlength //= length $rddata;
|
||||
$type //= TYPE_SRV;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
my $soa = $ans->{soa};
|
||||
if (defined $soa) {
|
||||
my $data = encode_name($soa) . encode_name($ans->{rname});
|
||||
$data .= pack("NNNNN", $ans->{serial}, $ans->{refresh}, $ans->{retry}, $ans->{expire}, $ans->{minimum});
|
||||
$rddata //= $data;
|
||||
$rdlength //= length $rddata;
|
||||
$type //= TYPE_SOA;
|
||||
$class //= CLASS_INTERNET;
|
||||
}
|
||||
|
||||
$type //= 0;
|
||||
$class //= 0;
|
||||
$ttl //= 0;
|
||||
|
||||
return $name . pack("nnNn", $type, $class, $ttl, $rdlength) . $rddata;
|
||||
}
|
||||
|
||||
1
|
|
@ -0,0 +1,89 @@
|
|||
local ngx_null = ngx.null
|
||||
local tostring = tostring
|
||||
local byte = string.byte
|
||||
local gsub = string.gsub
|
||||
local sort = table.sort
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local concat = table.concat
|
||||
|
||||
local ok, new_tab = pcall(require, "table.new")
|
||||
if not ok then
|
||||
new_tab = function (narr, nrec) return {} end
|
||||
end
|
||||
|
||||
local _M = {}
|
||||
|
||||
local metachars = {
|
||||
['\t'] = '\\t',
|
||||
["\\"] = "\\\\",
|
||||
['"'] = '\\"',
|
||||
['\r'] = '\\r',
|
||||
['\n'] = '\\n',
|
||||
}
|
||||
|
||||
local function encode_str(s)
|
||||
-- XXX we will rewrite this when string.buffer is implemented
|
||||
-- in LuaJIT 2.1 because string.gsub cannot be JIT compiled.
|
||||
return gsub(s, '["\\\r\n\t]', metachars)
|
||||
end
|
||||
|
||||
local function is_arr(t)
|
||||
local exp = 1
|
||||
for k, _ in pairs(t) do
|
||||
if k ~= exp then
|
||||
return nil
|
||||
end
|
||||
exp = exp + 1
|
||||
end
|
||||
return exp - 1
|
||||
end
|
||||
|
||||
local encode
|
||||
|
||||
encode = function (v)
|
||||
if v == nil or v == ngx_null then
|
||||
return "null"
|
||||
end
|
||||
|
||||
local typ = type(v)
|
||||
if typ == 'string' then
|
||||
return '"' .. encode_str(v) .. '"'
|
||||
end
|
||||
|
||||
if typ == 'number' or typ == 'boolean' then
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
if typ == 'table' then
|
||||
local n = is_arr(v)
|
||||
if n then
|
||||
local bits = new_tab(n, 0)
|
||||
for i, elem in ipairs(v) do
|
||||
bits[i] = encode(elem)
|
||||
end
|
||||
return "[" .. concat(bits, ",") .. "]"
|
||||
end
|
||||
|
||||
local keys = {}
|
||||
local i = 0
|
||||
for key, _ in pairs(v) do
|
||||
i = i + 1
|
||||
keys[i] = key
|
||||
end
|
||||
sort(keys)
|
||||
|
||||
local bits = new_tab(0, i)
|
||||
i = 0
|
||||
for _, key in ipairs(keys) do
|
||||
i = i + 1
|
||||
bits[i] = encode(key) .. ":" .. encode(v[key])
|
||||
end
|
||||
return "{" .. concat(bits, ",") .. "}"
|
||||
end
|
||||
|
||||
return '"<' .. typ .. '>"'
|
||||
end
|
||||
_M.encode = encode
|
||||
|
||||
return _M
|
|
@ -0,0 +1,686 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket::Lua;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
repeat_each(2);
|
||||
|
||||
plan tests => repeat_each() * (3 * blocks());
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
|
||||
|
||||
our $HttpConfig = qq(
|
||||
lua_package_path "$pwd/t/lib/?.lua;$pwd/lib/?.lua;;";
|
||||
lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;";
|
||||
resolver '$ENV{TEST_NGINX_RESOLVER}';
|
||||
);
|
||||
|
||||
no_long_string();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: A records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("www.google.com", { qtype = r.TYPE_A })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 2: CNAME records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("www.yahoo.com", { qtype = r.TYPE_CNAME })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"cname":"[-_a-z0-9.]+".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 3: AAAA records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("www.google.com", { qtype = r.TYPE_AAAA })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"address":"[a-fA-F0-9]*(?::[a-fA-F0-9]*)+".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 4: compress ipv6 addr
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local c = resolver.compress_ipv6_addr
|
||||
|
||||
ngx.say(c("1080:0:0:0:8:800:200C:417A"))
|
||||
ngx.say(c("FF01:0:0:0:0:0:0:101"))
|
||||
ngx.say(c("0:0:0:0:0:0:0:1"))
|
||||
ngx.say(c("1:5:0:0:0:0:0:0"))
|
||||
ngx.say(c("7:25:0:0:0:3:0:0"))
|
||||
ngx.say(c("0:0:0:0:0:0:0:0"))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
1080::8:800:200C:417A
|
||||
FF01::101
|
||||
::1
|
||||
1:5::
|
||||
7:25::3:0:0
|
||||
::
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 5: A records (TCP)
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:tcp_query("www.google.com", { qtype = r.TYPE_A })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 6: MX records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("gmail.com", { qtype = r.TYPE_MX })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[\{.*?"preference":\d+,.*?"exchange":"[^"]+".*?\}\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 7: NS records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("google.com", { qtype = r.TYPE_NS })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[\{.*?"nsdname":"[^"]+".*?\}\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 8: TXT query (no ans)
|
||||
--- SKIP
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("agentzh.org", { qtype = r.TYPE_TXT })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
records: {}
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- timeout: 10
|
||||
|
||||
|
||||
|
||||
=== TEST 9: TXT query (with ans)
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("gmail.com", { qtype = r.TYPE_TXT })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[\{.*?"txt":"v=spf\d+\s[^"]+".*?\}\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 10: PTR query
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("4.4.8.8.in-addr.arpa", { qtype = r.TYPE_PTR })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[\{.*?"ptrdname":"dns\.google".*?\}\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 11: domains with a trailing dot
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("www.google.com.", { qtype = r.TYPE_A })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 12: domains with a leading dot
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query(".www.google.com", { qtype = r.TYPE_A })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
failed to query: bad name
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 13: SRV records or XMPP
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("_xmpp-client._tcp.jabber.org", { qtype = r.TYPE_SRV })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[(?:{"class":1,"name":"_xmpp-client._tcp.jabber.org","port":\d+,"priority":\d+,"section":1,"target":"[\w.]+\.jabber.org","ttl":\d+,"type":33,"weight":\d+},?)+\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 14: SPF query (with ans)
|
||||
SPF records are deprecated by RFC 7208 in favor of TXT records, and
|
||||
linkedin.com has migrated to such TXT records.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("linkedin.com", { qtype = r.TYPE_SPF })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
records: []
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 15: SPF query (no ans)
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("agentzh.org", { qtype = r.TYPE_SPF })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
records: []
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- timeout: 10
|
||||
|
||||
|
||||
|
||||
=== TEST 16: SPF query (as TXT with ans)
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("linkedin.com", { qtype = r.TYPE_TXT })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, #ans do
|
||||
if string.find(ans[i].txt, "v=spf", nil, true) then
|
||||
local ljson = require "ljson"
|
||||
ngx.say("ans: ", ljson.encode(ans[i]))
|
||||
end
|
||||
end
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^ans: \{.*?"txt":"v=spf\d+\s[^"]+".*?\}$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 17: generate arpa_str
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local c = resolver.arpa_str
|
||||
ngx.say(c("1234:5678:abcd:ef99:1234:5678:abcd:ef99"))
|
||||
ngx.say(c("1080::8:800:200c:417a"))
|
||||
ngx.say(c("ff01::101"))
|
||||
ngx.say(c("::1"))
|
||||
ngx.say(c("::"))
|
||||
ngx.say(c("1::"))
|
||||
ngx.say(c("127.0.0.1"))
|
||||
ngx.say(c("251.252.253.254"))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
9.9.f.e.d.c.b.a.8.7.6.5.4.3.2.1.9.9.f.e.d.c.b.a.8.7.6.5.4.3.2.1.ip6.arpa
|
||||
a.7.1.4.c.0.0.2.0.0.8.0.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.0.1.ip6.arpa
|
||||
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
|
||||
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.0.0.0.0.0.0.ip6.arpa
|
||||
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.0.0.0.0.0.0.0.ip6.arpa
|
||||
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.0.0.0.1.0.0.0.ip6.arpa
|
||||
1.0.0.127.in-addr.arpa
|
||||
254.253.252.251.in-addr.arpa
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 18: SOA records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("www.google.com", { qtype = r.TYPE_SOA })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[(?:{"class":1,"expire":\d+,"minimum":\d+,"mname":"ns\d+\.google\.com","name":"google\.com","refresh":\d+,"retry":\d+,"rname":"dns-admin\.google\.com","section":2,"serial":\d+,"ttl":\d+,"type":6},?)+\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 19: RRTYPE larger than 255
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("comodo.com", { qtype = 257 })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[(?:{"class":1,"name":"comodo\.com","rdata":"[^"]+","section":1,"ttl":\d+,"type":257},?)+\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
||||
|
||||
|
||||
|
||||
=== TEST 20: SOA records
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua '
|
||||
local resolver = require "resty.dns.resolver"
|
||||
|
||||
local r, err =
|
||||
resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } }
|
||||
if not r then
|
||||
ngx.say("failed to instantiate resolver: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ans, err = r:query("google.com", { qtype = r.TYPE_SOA })
|
||||
if not ans then
|
||||
ngx.say("failed to query: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
local ljson = require "ljson"
|
||||
ngx.say("records: ", ljson.encode(ans))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body_like chop
|
||||
^records: \[.*?"name":"google.com".*?\]$
|
||||
--- no_error_log
|
||||
[error]
|
||||
--- no_check_leak
|
|
@ -0,0 +1,379 @@
|
|||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
fun:__write_nocancel
|
||||
fun:ngx_log_error_core
|
||||
fun:ngx_resolver_read_response
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:ngx_sprintf_num
|
||||
fun:ngx_vslprintf
|
||||
fun:ngx_log_error_core
|
||||
fun:ngx_resolver_read_response
|
||||
fun:ngx_epoll_process_events
|
||||
fun:ngx_process_events_and_timers
|
||||
fun:ngx_single_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Addr1
|
||||
fun:ngx_vslprintf
|
||||
fun:ngx_snprintf
|
||||
fun:ngx_sock_ntop
|
||||
fun:ngx_event_accept
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
fun:__write_nocancel
|
||||
fun:ngx_log_error_core
|
||||
fun:ngx_resolver_read_response
|
||||
fun:ngx_event_process_posted
|
||||
fun:ngx_process_events_and_timers
|
||||
fun:ngx_single_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:ngx_sprintf_num
|
||||
fun:ngx_vslprintf
|
||||
fun:ngx_log_error_core
|
||||
fun:ngx_resolver_read_response
|
||||
fun:ngx_event_process_posted
|
||||
fun:ngx_process_events_and_timers
|
||||
fun:ngx_single_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
obj:*
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
exp-sgcheck:SorG
|
||||
fun:ngx_http_lua_ndk_set_var_get
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
exp-sgcheck:SorG
|
||||
fun:ngx_http_variables_init_vars
|
||||
fun:ngx_http_block
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
exp-sgcheck:SorG
|
||||
fun:ngx_conf_parse
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
exp-sgcheck:SorG
|
||||
fun:ngx_vslprintf
|
||||
fun:ngx_log_error_core
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_calloc
|
||||
fun:ngx_event_process_init
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_event_process_init
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:ngx_conf_flush_files
|
||||
fun:ngx_single_process_cycle
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:memcpy
|
||||
fun:ngx_vslprintf
|
||||
fun:ngx_log_error_core
|
||||
fun:ngx_http_charset_header_filter
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Param
|
||||
socketcall.setsockopt(optval)
|
||||
fun:setsockopt
|
||||
fun:drizzle_state_connect
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_pool_cleanup_add
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:ngx_conf_flush_files
|
||||
fun:ngx_single_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_array_push
|
||||
fun:ngx_http_get_variable_index
|
||||
fun:ngx_http_memc_add_variable
|
||||
fun:ngx_http_memc_init
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_event_process_init
|
||||
fun:ngx_single_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_crc32_table_init
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_event_process_init
|
||||
fun:ngx_worker_process_init
|
||||
fun:ngx_worker_process_cycle
|
||||
fun:ngx_spawn_process
|
||||
fun:ngx_start_worker_processes
|
||||
fun:ngx_master_process_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_hash_init
|
||||
fun:ngx_http_variables_init_vars
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_http_upstream_drizzle_create_srv_conf
|
||||
fun:ngx_http_upstream
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_hash_keys_array_init
|
||||
fun:ngx_http_variables_add_core_vars
|
||||
fun:ngx_http_core_preconfiguration
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_array_push
|
||||
fun:ngx_hash_add_key
|
||||
fun:ngx_http_add_variable
|
||||
fun:ngx_http_echo_add_variables
|
||||
fun:ngx_http_echo_handler_init
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_http_upstream_drizzle_create_srv_conf
|
||||
fun:ngx_http_core_server
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_http_upstream_drizzle_create_srv_conf
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_array_push
|
||||
fun:ngx_hash_add_key
|
||||
fun:ngx_http_variables_add_core_vars
|
||||
fun:ngx_http_core_preconfiguration
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_hash_init
|
||||
fun:ngx_http_upstream_init_main_conf
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_pcalloc
|
||||
fun:ngx_http_drizzle_keepalive_init
|
||||
fun:ngx_http_upstream_drizzle_init
|
||||
fun:ngx_http_upstream_init_main_conf
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_palloc_large
|
||||
fun:ngx_palloc
|
||||
fun:ngx_hash_init
|
||||
fun:ngx_http_variables_init_vars
|
||||
fun:ngx_http_block
|
||||
fun:ngx_conf_parse
|
||||
fun:ngx_init_cycle
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:index
|
||||
fun:expand_dynamic_string_token
|
||||
fun:_dl_map_object
|
||||
fun:map_doit
|
||||
fun:_dl_catch_error
|
||||
fun:do_preload
|
||||
fun:dl_main
|
||||
fun:_dl_sysdep_start
|
||||
fun:_dl_start
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_set_environment
|
||||
fun:ngx_single_process_cycle
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
fun:ngx_alloc
|
||||
fun:ngx_set_environment
|
||||
fun:ngx_worker_process_init
|
||||
fun:ngx_worker_process_cycle
|
||||
}
|
Loading…
Reference in New Issue