├── .gitattributes ├── .gitignore ├── .travis.yml ├── Makefile ├── README.markdown ├── dist.ini ├── lib └── resty │ └── dns │ └── resolver.lua ├── t ├── TestDNS.pm ├── lib │ └── ljson.lua ├── mock.t └── sanity.t └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | nginx 8 | ctags 9 | tags 10 | a.lua 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | 15 | addons: 16 | apt: 17 | packages: 18 | - axel 19 | - cpanminus 20 | 21 | cache: 22 | apt: true 23 | directories: 24 | - download-cache 25 | 26 | env: 27 | global: 28 | - JOBS=3 29 | - TEST_NGINX_SLEEP=0.006 30 | - LUAJIT_PREFIX=/opt/luajit21 31 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 32 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 33 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 34 | matrix: 35 | - NGINX_VERSION=1.27.1 36 | - NGINX_VERSION=1.25.3 37 | 38 | install: 39 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 40 | - git clone https://github.com/openresty/openresty.git ../openresty 41 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 42 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 43 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 44 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 45 | - git clone https://github.com/openresty/nginx-devel-utils.git 46 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 47 | 48 | before_script: 49 | - cd luajit2/ 50 | - 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) 51 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 52 | - cd .. 53 | 54 | script: 55 | - disable_pcre2=--without-pcre2; answer=`nginx-devel-utils/ngx-ver-ge "$NGINX_VERSION" 1.25.1`; if [ "$answer" = "N" ]; then disable_pcre2=""; fi; export disable_pcre2; 56 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 57 | - ngx-build $NGINX_VERSION $disable_pcre2 --with-debug --with-cc-opt="-DDEBUG_MALLOC" --with-ipv6 --add-module=../lua-nginx-module > build.log 2>&1 || (cat build.log && exit 1) 58 | - prove -I. -r t 59 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/dns 14 | $(INSTALL) lib/resty/dns/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/dns/ 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-dns - Lua DNS resolver for the ngx_lua based on the cosocket API 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Description](#description) 12 | * [Synopsis](#synopsis) 13 | * [Methods](#methods) 14 | * [new](#new) 15 | * [destroy](#destroy) 16 | * [query](#query) 17 | * [tcp_query](#tcp_query) 18 | * [set_timeout](#set_timeout) 19 | * [compress_ipv6_addr](#compress_ipv6_addr) 20 | * [expand_ipv6_addr](#expand_ipv6_addr) 21 | * [arpa_str](#arpa_str) 22 | * [reverse_query](#reverse_query) 23 | * [Constants](#constants) 24 | * [TYPE_A](#type_a) 25 | * [TYPE_NS](#type_ns) 26 | * [TYPE_CNAME](#type_cname) 27 | * [TYPE_SOA](#type_soa) 28 | * [TYPE_PTR](#type_ptr) 29 | * [TYPE_MX](#type_mx) 30 | * [TYPE_TXT](#type_txt) 31 | * [TYPE_AAAA](#type_aaaa) 32 | * [TYPE_SRV](#type_srv) 33 | * [TYPE_SPF](#type_spf) 34 | * [CLASS_IN](#class_in) 35 | * [SECTION_AN](#section_an) 36 | * [SECTION_NS](#section_ns) 37 | * [SECTION_AR](#section_ar) 38 | * [Automatic Error Logging](#automatic-error-logging) 39 | * [Limitations](#limitations) 40 | * [TODO](#todo) 41 | * [Author](#author) 42 | * [Copyright and License](#copyright-and-license) 43 | * [See Also](#see-also) 44 | 45 | Status 46 | ====== 47 | 48 | This library is considered production ready. 49 | 50 | Description 51 | =========== 52 | 53 | This Lua library provides a DNS resolver for the ngx_lua nginx module: 54 | 55 | https://github.com/openresty/lua-nginx-module/#readme 56 | 57 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 58 | 100% nonblocking behavior. 59 | 60 | 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. 61 | 62 | 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. 63 | 64 | Note that, this library is bundled and enabled by default in the [OpenResty bundle](http://openresty.org/). 65 | 66 | IMPORTANT: to be able to generate unique ids, the random generator must be properly seeded using `math.randomseed` prior to using this module. 67 | 68 | Synopsis 69 | ======== 70 | 71 | ```nginx 72 | lua_package_path "/path/to/lua-resty-dns/lib/?.lua;;"; 73 | 74 | server { 75 | location = /dns { 76 | content_by_lua_block { 77 | local resolver = require "resty.dns.resolver" 78 | local r, err = resolver:new{ 79 | nameservers = {"8.8.8.8", {"8.8.4.4", 53} }, 80 | retrans = 5, -- 5 retransmissions on receive timeout 81 | timeout = 2000, -- 2 sec 82 | no_random = true, -- always start with first nameserver 83 | } 84 | 85 | if not r then 86 | ngx.say("failed to instantiate the resolver: ", err) 87 | return 88 | end 89 | 90 | local answers, err, tries = r:query("www.google.com", nil, {}) 91 | if not answers then 92 | ngx.say("failed to query the DNS server: ", err) 93 | ngx.say("retry historie:\n ", table.concat(tries, "\n ")) 94 | return 95 | end 96 | 97 | if answers.errcode then 98 | ngx.say("server returned error code: ", answers.errcode, 99 | ": ", answers.errstr) 100 | end 101 | 102 | for i, ans in ipairs(answers) do 103 | ngx.say(ans.name, " ", ans.address or ans.cname, 104 | " type:", ans.type, " class:", ans.class, 105 | " ttl:", ans.ttl) 106 | end 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | [Back to TOC](#table-of-contents) 113 | 114 | Methods 115 | ======= 116 | 117 | [Back to TOC](#table-of-contents) 118 | 119 | new 120 | --- 121 | `syntax: r, err = class:new(opts)` 122 | 123 | Creates a dns.resolver object. Returns `nil` and a message string on error. 124 | 125 | It accepts a `opts` table argument. The following options are supported: 126 | 127 | * `nameservers` 128 | 129 | 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. 130 | * `retrans` 131 | 132 | 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. 133 | * `timeout` 134 | 135 | the time in milliseconds for waiting for the response for a single attempt of request transmission. 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. 136 | * `no_recurse` 137 | 138 | a boolean flag controls whether to disable the "recursion desired" (RD) flag in the UDP request. Defaults to `false`. 139 | * `no_random` 140 | 141 | 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`. 142 | 143 | [Back to TOC](#table-of-contents) 144 | 145 | destroy 146 | ------- 147 | `syntax: r:destroy()` 148 | 149 | Destroy the dns.resolver object by releasing all the internal occupied resources. 150 | 151 | [Back to TOC](#table-of-contents) 152 | 153 | query 154 | ----- 155 | `syntax: answers, err, tries? = r:query(name, options?, tries?)` 156 | 157 | Performs a DNS standard query to the nameservers specified by the `new` method, 158 | and returns all the answer records in an array-like Lua table. In case of errors, it will 159 | return `nil` and a string describing the error instead. 160 | 161 | If the server returns a non-zero error code, the fields `errcode` and `errstr` will be set accordingly in the Lua table returned. 162 | 163 | Each entry in the `answers` returned table value is also a hash-like Lua table 164 | which usually takes some of the following fields: 165 | 166 | * `name` 167 | 168 | The resource record name. 169 | * `type` 170 | 171 | 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. 172 | * `address` 173 | 174 | The IPv4 or IPv6 address in their textual representations when the resource record type is either `1` (`TYPE_A`) or `28` (`TYPE_AAAA`), respectively. Successive 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. 175 | * `section` 176 | 177 | 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`). 178 | * `cname` 179 | 180 | The (decoded) record data value for `CNAME` resource records. Only present for `CNAME` records. 181 | * `ttl` 182 | 183 | The time-to-live (TTL) value in seconds for the current resource record. 184 | * `class` 185 | 186 | The current resource record class, possible values are `1` (`CLASS_IN`) or any other values allowed by RFC 1035. 187 | * `preference` 188 | 189 | The preference integer number for `MX` resource records. Only present for `MX` type records. 190 | * `exchange` 191 | 192 | The exchange domain name for `MX` resource records. Only present for `MX` type records. 193 | * `nsdname` 194 | 195 | A domain-name which specifies a host which should be authoritative for the specified class and domain. Usually present for `NS` type records. 196 | * `rdata` 197 | 198 | The raw resource data (RDATA) for resource records that are not recognized. 199 | * `txt` 200 | 201 | 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. 202 | * `ptrdname` 203 | 204 | The record value for `PTR` records. 205 | 206 | This method also takes an optional `options` argument table, which takes the following fields: 207 | 208 | * `qtype` 209 | 210 | 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`). 211 | * `authority_section` 212 | 213 | When set to a true value, the `answers` return value includes the `Authority` section of the DNS response. Default to `false`. 214 | * `additional_section` 215 | 216 | When set to a true value, the `answers` return value includes the `Additional` section of the DNS response. Default to `false`. 217 | 218 | The optional parameter `tries` can be provided as an empty table, and will be 219 | returned as a third result. The table will be an array with the error message 220 | for each (if any) failed try. 221 | 222 | When data truncation happens, the resolver will automatically retry using the TCP transport mode 223 | to query the current nameserver. All TCP connections are short lived. 224 | 225 | [Back to TOC](#table-of-contents) 226 | 227 | tcp_query 228 | --------- 229 | `syntax: answers, err = r:tcp_query(name, options?)` 230 | 231 | Just like the `query` method, but enforce the TCP transport mode instead of UDP. 232 | 233 | All TCP connections are short lived. 234 | 235 | Here is an example: 236 | 237 | ```lua 238 | local resolver = require "resty.dns.resolver" 239 | 240 | local r, err = resolver:new{ 241 | nameservers = { "8.8.8.8" } 242 | } 243 | if not r then 244 | ngx.say("failed to instantiate resolver: ", err) 245 | return 246 | end 247 | 248 | local ans, err = r:tcp_query("www.google.com", { qtype = r.TYPE_A }) 249 | if not ans then 250 | ngx.say("failed to query: ", err) 251 | return 252 | end 253 | 254 | local cjson = require "cjson" 255 | ngx.say("records: ", cjson.encode(ans)) 256 | ``` 257 | 258 | [Back to TOC](#table-of-contents) 259 | 260 | set_timeout 261 | ----------- 262 | `syntax: r:set_timeout(time)` 263 | 264 | Overrides the current `timeout` setting by the `time` argument in milliseconds for all the nameserver peers. 265 | 266 | [Back to TOC](#table-of-contents) 267 | 268 | compress_ipv6_addr 269 | ------------------ 270 | `syntax: compressed = resty.dns.resolver.compress_ipv6_addr(address)` 271 | 272 | Compresses the successive 16-bit zero groups in the textual format of the IPv6 address. 273 | 274 | For example, 275 | 276 | ```lua 277 | local resolver = require "resty.dns.resolver" 278 | local compress = resolver.compress_ipv6_addr 279 | local new_addr = compress("FF01:0:0:0:0:0:0:101") 280 | ``` 281 | 282 | will yield `FF01::101` in the `new_addr` return value. 283 | 284 | [Back to TOC](#table-of-contents) 285 | 286 | expand_ipv6_addr 287 | ------------------ 288 | `syntax: expanded = resty.dns.resolver.expand_ipv6_addr(address)` 289 | 290 | Expands the successive 16-bit zero groups in the textual format of the IPv6 address. 291 | 292 | For example, 293 | 294 | ```lua 295 | local resolver = require "resty.dns.resolver" 296 | local expand = resolver.expand_ipv6_addr 297 | local new_addr = expand("FF01::101") 298 | ``` 299 | 300 | will yield `FF01:0:0:0:0:0:0:101` in the `new_addr` return value. 301 | 302 | [Back to TOC](#table-of-contents) 303 | 304 | arpa_str 305 | ------------------ 306 | `syntax: arpa_record = resty.dns.resolver.arpa_str(address)` 307 | 308 | Generates the reverse domain name for PTR lookups for both IPv4 and IPv6 addresses. Compressed IPv6 addresses 309 | will be automatically expanded. 310 | 311 | For example, 312 | 313 | ```lua 314 | local resolver = require "resty.dns.resolver" 315 | local ptr4 = resolver.arpa_str("1.2.3.4") 316 | local ptr6 = resolver.arpa_str("FF01::101") 317 | ``` 318 | 319 | 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`. 320 | 321 | [Back to TOC](#table-of-contents) 322 | 323 | reverse_query 324 | ------------------ 325 | `syntax: answers, err = r:reverse_query(address)` 326 | 327 | Performs a PTR lookup for both IPv4 and IPv6 addresses. This function is basically a wrapper for the `query` command 328 | which uses the `arpa_str` command to convert the IP address on the fly. 329 | 330 | [Back to TOC](#table-of-contents) 331 | 332 | Constants 333 | ========= 334 | 335 | [Back to TOC](#table-of-contents) 336 | 337 | TYPE_A 338 | ------ 339 | 340 | The `A` resource record type, equal to the decimal number `1`. 341 | 342 | [Back to TOC](#table-of-contents) 343 | 344 | TYPE_NS 345 | ------- 346 | 347 | The `NS` resource record type, equal to the decimal number `2`. 348 | 349 | [Back to TOC](#table-of-contents) 350 | 351 | TYPE_CNAME 352 | ---------- 353 | 354 | The `CNAME` resource record type, equal to the decimal number `5`. 355 | 356 | [Back to TOC](#table-of-contents) 357 | 358 | TYPE_SOA 359 | ---------- 360 | 361 | The `SOA` resource record type, equal to the decimal number `6`. 362 | 363 | [Back to TOC](#table-of-contents) 364 | 365 | TYPE_PTR 366 | -------- 367 | 368 | The `PTR` resource record type, equal to the decimal number `12`. 369 | 370 | [Back to TOC](#table-of-contents) 371 | 372 | TYPE_MX 373 | ------- 374 | 375 | The `MX` resource record type, equal to the decimal number `15`. 376 | 377 | [Back to TOC](#table-of-contents) 378 | 379 | TYPE_TXT 380 | -------- 381 | 382 | The `TXT` resource record type, equal to the decimal number `16`. 383 | 384 | [Back to TOC](#table-of-contents) 385 | 386 | TYPE_AAAA 387 | --------- 388 | `syntax: typ = r.TYPE_AAAA` 389 | 390 | The `AAAA` resource record type, equal to the decimal number `28`. 391 | 392 | [Back to TOC](#table-of-contents) 393 | 394 | TYPE_SRV 395 | --------- 396 | `syntax: typ = r.TYPE_SRV` 397 | 398 | The `SRV` resource record type, equal to the decimal number `33`. 399 | 400 | See RFC 2782 for details. 401 | 402 | [Back to TOC](#table-of-contents) 403 | 404 | TYPE_SPF 405 | --------- 406 | `syntax: typ = r.TYPE_SPF` 407 | 408 | The `SPF` resource record type, equal to the decimal number `99`. 409 | 410 | See RFC 4408 for details. 411 | 412 | [Back to TOC](#table-of-contents) 413 | 414 | CLASS_IN 415 | -------- 416 | `syntax: class = r.CLASS_IN` 417 | 418 | The `Internet` resource record type, equal to the decimal number `1`. 419 | 420 | [Back to TOC](#table-of-contents) 421 | 422 | SECTION_AN 423 | ---------- 424 | `syntax: stype = r.SECTION_AN` 425 | 426 | Identifier of the `Answer` section in the DNS response. Equal to decimal number `1`. 427 | 428 | [Back to TOC](#table-of-contents) 429 | 430 | SECTION_NS 431 | ---------- 432 | `syntax: stype = r.SECTION_NS` 433 | 434 | Identifier of the `Authority` section in the DNS response. Equal to the decimal number `2`. 435 | 436 | [Back to TOC](#table-of-contents) 437 | 438 | SECTION_AR 439 | ---------- 440 | `syntax: stype = r.SECTION_AR` 441 | 442 | Identifier of the `Additional` section in the DNS response. Equal to the decimal number `3`. 443 | 444 | [Back to TOC](#table-of-contents) 445 | 446 | Automatic Error Logging 447 | ======================= 448 | 449 | By default, the underlying [ngx_lua](https://github.com/openresty/lua-nginx-module/#readme) module 450 | does error logging when socket errors happen. If you are already doing proper error 451 | 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, 452 | 453 | ```nginx 454 | lua_socket_log_errors off; 455 | ``` 456 | 457 | [Back to TOC](#table-of-contents) 458 | 459 | Limitations 460 | =========== 461 | 462 | * This library cannot be used in code contexts like `set_by_lua*`, `log_by_lua*`, and 463 | `header_filter_by_lua*` where the ngx_lua cosocket API is not available. 464 | * The `resty.dns.resolver` object instance cannot be stored in a Lua variable at the Lua module level, 465 | because it will then be shared by all the concurrent requests handled by the same nginx 466 | worker process (see 467 | https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker ) and 468 | result in bad race conditions when concurrent requests are trying to use the same `resty.dns.resolver` instance. 469 | You should always initiate `resty.dns.resolver` objects in function local 470 | variables or in the `ngx.ctx` table. These places all have their own data copies for 471 | each request. 472 | 473 | [Back to TOC](#table-of-contents) 474 | 475 | TODO 476 | ==== 477 | 478 | * Concurrent (or parallel) query mode 479 | * Better support for other resource record types like `TLSA`. 480 | 481 | [Back to TOC](#table-of-contents) 482 | 483 | Author 484 | ====== 485 | 486 | Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 487 | 488 | [Back to TOC](#table-of-contents) 489 | 490 | Copyright and License 491 | ===================== 492 | 493 | This module is licensed under the BSD license. 494 | 495 | Copyright (C) 2012-2019, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 496 | 497 | All rights reserved. 498 | 499 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 500 | 501 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 502 | 503 | * 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. 504 | 505 | 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. 506 | 507 | [Back to TOC](#table-of-contents) 508 | 509 | See Also 510 | ======== 511 | * the ngx_lua module: https://github.com/openresty/lua-nginx-module/#readme 512 | * the [lua-resty-memcached](https://github.com/agentzh/lua-resty-memcached) library. 513 | * the [lua-resty-redis](https://github.com/agentzh/lua-resty-redis) library. 514 | * the [lua-resty-mysql](https://github.com/agentzh/lua-resty-mysql) library. 515 | 516 | [Back to TOC](#table-of-contents) 517 | 518 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=lua-resty-dns 2 | abstract=Lua DNS resolver for the ngx_lua based on the cosocket API 3 | author=Yichun "agentzh" Zhang (agentzh) 4 | is_original=yes 5 | license=2bsd 6 | lib_dir=lib 7 | doc_dir=lib 8 | repo_link=https://github.com/openresty/lua-resty-dns 9 | main_module=lib/resty/dns/resolver.lua 10 | -------------------------------------------------------------------------------- /lib/resty/dns/resolver.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | -- local socket = require "socket" 5 | local bit = require "bit" 6 | local udp = ngx.socket.udp 7 | local rand = math.random 8 | local char = string.char 9 | local byte = string.byte 10 | local find = string.find 11 | local gsub = string.gsub 12 | local sub = string.sub 13 | local rep = string.rep 14 | local format = string.format 15 | local band = bit.band 16 | local rshift = bit.rshift 17 | local lshift = bit.lshift 18 | local insert = table.insert 19 | local concat = table.concat 20 | local re_sub = ngx.re.sub 21 | local tcp = ngx.socket.tcp 22 | local log = ngx.log 23 | local DEBUG = ngx.DEBUG 24 | local unpack = unpack 25 | local setmetatable = setmetatable 26 | local type = type 27 | local ipairs = ipairs 28 | 29 | 30 | local ok, new_tab = pcall(require, "table.new") 31 | if not ok then 32 | new_tab = function (narr, nrec) return {} end 33 | end 34 | 35 | 36 | local DOT_CHAR = byte(".") 37 | local ZERO_CHAR = byte("0") 38 | local COLON_CHAR = byte(":") 39 | 40 | local IP6_ARPA = "ip6.arpa" 41 | 42 | local TYPE_A = 1 43 | local TYPE_NS = 2 44 | local TYPE_CNAME = 5 45 | local TYPE_SOA = 6 46 | local TYPE_PTR = 12 47 | local TYPE_MX = 15 48 | local TYPE_TXT = 16 49 | local TYPE_AAAA = 28 50 | local TYPE_SRV = 33 51 | local TYPE_SPF = 99 52 | 53 | local CLASS_IN = 1 54 | 55 | local SECTION_AN = 1 56 | local SECTION_NS = 2 57 | local SECTION_AR = 3 58 | 59 | 60 | local _M = { 61 | _VERSION = '0.23', 62 | TYPE_A = TYPE_A, 63 | TYPE_NS = TYPE_NS, 64 | TYPE_CNAME = TYPE_CNAME, 65 | TYPE_SOA = TYPE_SOA, 66 | TYPE_PTR = TYPE_PTR, 67 | TYPE_MX = TYPE_MX, 68 | TYPE_TXT = TYPE_TXT, 69 | TYPE_AAAA = TYPE_AAAA, 70 | TYPE_SRV = TYPE_SRV, 71 | TYPE_SPF = TYPE_SPF, 72 | CLASS_IN = CLASS_IN, 73 | SECTION_AN = SECTION_AN, 74 | SECTION_NS = SECTION_NS, 75 | SECTION_AR = SECTION_AR 76 | } 77 | 78 | 79 | local resolver_errstrs = { 80 | "format error", -- 1 81 | "server failure", -- 2 82 | "name error", -- 3 83 | "not implemented", -- 4 84 | "refused", -- 5 85 | } 86 | 87 | local soa_int32_fields = { "serial", "refresh", "retry", "expire", "minimum" } 88 | 89 | local mt = { __index = _M } 90 | 91 | 92 | local arpa_tmpl = new_tab(72, 0) 93 | 94 | for i = 1, #IP6_ARPA do 95 | arpa_tmpl[64 + i] = byte(IP6_ARPA, i) 96 | end 97 | 98 | for i = 2, 64, 2 do 99 | arpa_tmpl[i] = DOT_CHAR 100 | end 101 | 102 | 103 | local function udp_socks_close(self) 104 | if self.socks == nil then 105 | return 106 | end 107 | 108 | for _, sock in ipairs(self.socks) do 109 | sock:close() 110 | end 111 | 112 | self.socks = nil 113 | end 114 | 115 | 116 | local function tcp_socks_close(self) 117 | if self.tcp_sock == nil then 118 | return 119 | end 120 | 121 | self.tcp_sock:close() 122 | self.tcp_sock = nil 123 | end 124 | 125 | 126 | function _M.new(class, opts) 127 | if not opts then 128 | return nil, "no options table specified" 129 | end 130 | 131 | local servers = opts.nameservers 132 | if not servers or #servers == 0 then 133 | return nil, "no nameservers specified" 134 | end 135 | 136 | local timeout = opts.timeout or 2000 -- default 2 sec 137 | 138 | local n = #servers 139 | 140 | local socks = {} 141 | 142 | for i = 1, n do 143 | local server = servers[i] 144 | local sock, err = udp() 145 | if not sock then 146 | return nil, "failed to create udp socket: " .. err 147 | end 148 | 149 | local host, port 150 | if type(server) == 'table' then 151 | host = server[1] 152 | port = server[2] or 53 153 | 154 | else 155 | host = server 156 | port = 53 157 | servers[i] = {host, port} 158 | end 159 | 160 | local ok, err = sock:setpeername(host, port) 161 | if not ok then 162 | return nil, "failed to set peer name: " .. err 163 | end 164 | 165 | sock:settimeout(timeout) 166 | 167 | insert(socks, sock) 168 | end 169 | 170 | local tcp_sock, err = tcp() 171 | if not tcp_sock then 172 | return nil, "failed to create tcp socket: " .. err 173 | end 174 | 175 | tcp_sock:settimeout(timeout) 176 | 177 | return setmetatable( 178 | { cur = opts.no_random and 1 or rand(1, n), 179 | socks = socks, 180 | tcp_sock = tcp_sock, 181 | servers = servers, 182 | retrans = opts.retrans or 5, 183 | no_recurse = opts.no_recurse, 184 | }, mt) 185 | end 186 | 187 | 188 | function _M:destroy() 189 | udp_socks_close(self) 190 | tcp_socks_close(self) 191 | self.cur = nil 192 | self.servers = nil 193 | self.retrans = nil 194 | self.no_recurse = nil 195 | end 196 | 197 | 198 | local function pick_sock(self, socks) 199 | local cur = self.cur 200 | 201 | if cur == #socks then 202 | self.cur = 1 203 | else 204 | self.cur = cur + 1 205 | end 206 | 207 | return socks[cur] 208 | end 209 | 210 | 211 | local function _get_cur_server(self) 212 | local cur = self.cur 213 | 214 | local servers = self.servers 215 | 216 | if cur == 1 then 217 | return servers[#servers] 218 | end 219 | 220 | return servers[cur - 1] 221 | end 222 | 223 | 224 | function _M.set_timeout(self, timeout) 225 | local socks = self.socks 226 | if not socks then 227 | return nil, "not initialized" 228 | end 229 | 230 | for i = 1, #socks do 231 | local sock = socks[i] 232 | sock:settimeout(timeout) 233 | end 234 | 235 | local tcp_sock = self.tcp_sock 236 | if not tcp_sock then 237 | return nil, "not initialized" 238 | end 239 | 240 | tcp_sock:settimeout(timeout) 241 | end 242 | 243 | 244 | local function _encode_name(s) 245 | return char(#s) .. s 246 | end 247 | 248 | 249 | local function _decode_name(buf, pos) 250 | local labels = {} 251 | local nptrs = 0 252 | local p = pos 253 | while nptrs < 128 do 254 | local fst = byte(buf, p) 255 | 256 | if not fst then 257 | return nil, 'truncated'; 258 | end 259 | 260 | -- print("fst at ", p, ": ", fst) 261 | 262 | if fst == 0 then 263 | if nptrs == 0 then 264 | pos = pos + 1 265 | end 266 | break 267 | end 268 | 269 | if band(fst, 0xc0) ~= 0 then 270 | -- being a pointer 271 | if nptrs == 0 then 272 | pos = pos + 2 273 | end 274 | 275 | nptrs = nptrs + 1 276 | 277 | local snd = byte(buf, p + 1) 278 | if not snd then 279 | return nil, 'truncated' 280 | end 281 | 282 | p = lshift(band(fst, 0x3f), 8) + snd + 1 283 | 284 | -- print("resolving ptr ", p, ": ", byte(buf, p)) 285 | 286 | else 287 | -- being a label 288 | local label = sub(buf, p + 1, p + fst) 289 | insert(labels, label) 290 | 291 | -- print("resolved label ", label) 292 | 293 | p = p + fst + 1 294 | 295 | if nptrs == 0 then 296 | pos = p 297 | end 298 | end 299 | end 300 | 301 | return concat(labels, "."), pos 302 | end 303 | 304 | 305 | local function _build_request(qname, id, no_recurse, opts) 306 | local qtype 307 | 308 | if opts then 309 | qtype = opts.qtype 310 | end 311 | 312 | if not qtype then 313 | qtype = 1 -- A record 314 | end 315 | 316 | local ident_hi = char(rshift(id, 8)) 317 | local ident_lo = char(band(id, 0xff)) 318 | 319 | local flags 320 | if no_recurse then 321 | -- print("found no recurse") 322 | flags = "\0\0" 323 | else 324 | flags = "\1\0" 325 | end 326 | 327 | local nqs = "\0\1" 328 | local nan = "\0\0" 329 | local nns = "\0\0" 330 | local nar = "\0\0" 331 | local typ = char(rshift(qtype, 8), band(qtype, 0xff)) 332 | local class = "\0\1" -- the Internet class 333 | 334 | if byte(qname, 1) == DOT_CHAR then 335 | return nil, "bad name" 336 | end 337 | 338 | local name = gsub(qname, "([^.]+)%.?", _encode_name) .. '\0' 339 | 340 | return { 341 | ident_hi, ident_lo, flags, nqs, nan, nns, nar, 342 | name, typ, class 343 | } 344 | end 345 | 346 | 347 | local function parse_section(answers, section, buf, start_pos, size, 348 | should_skip) 349 | local pos = start_pos 350 | 351 | for _ = 1, size do 352 | -- print(format("ans %d: qtype:%d qclass:%d", i, qtype, qclass)) 353 | local ans = {} 354 | 355 | if not should_skip then 356 | insert(answers, ans) 357 | end 358 | 359 | ans.section = section 360 | 361 | local name 362 | name, pos = _decode_name(buf, pos) 363 | if not name then 364 | return nil, pos 365 | end 366 | 367 | ans.name = name 368 | 369 | -- print("name: ", name) 370 | 371 | local type_hi = byte(buf, pos) 372 | local type_lo = byte(buf, pos + 1) 373 | local typ = lshift(type_hi, 8) + type_lo 374 | 375 | ans.type = typ 376 | 377 | -- print("type: ", typ) 378 | 379 | local class_hi = byte(buf, pos + 2) 380 | local class_lo = byte(buf, pos + 3) 381 | local class = lshift(class_hi, 8) + class_lo 382 | 383 | ans.class = class 384 | 385 | -- print("class: ", class) 386 | 387 | local byte_1, byte_2, byte_3, byte_4 = byte(buf, pos + 4, pos + 7) 388 | 389 | local ttl = lshift(byte_1, 24) + lshift(byte_2, 16) 390 | + lshift(byte_3, 8) + byte_4 391 | 392 | -- print("ttl: ", ttl) 393 | 394 | ans.ttl = ttl 395 | 396 | local len_hi = byte(buf, pos + 8) 397 | local len_lo = byte(buf, pos + 9) 398 | local len = lshift(len_hi, 8) + len_lo 399 | 400 | -- print("record len: ", len) 401 | 402 | pos = pos + 10 403 | 404 | if typ == TYPE_A then 405 | 406 | if len ~= 4 then 407 | return nil, "bad A record value length: " .. len 408 | end 409 | 410 | local addr_bytes = { byte(buf, pos, pos + 3) } 411 | local addr = concat(addr_bytes, ".") 412 | -- print("ipv4 address: ", addr) 413 | 414 | ans.address = addr 415 | 416 | pos = pos + 4 417 | 418 | elseif typ == TYPE_CNAME then 419 | 420 | local cname, p = _decode_name(buf, pos) 421 | if not cname then 422 | return nil, pos 423 | end 424 | 425 | if p - pos ~= len then 426 | return nil, format("bad cname record length: %d ~= %d", 427 | p - pos, len) 428 | end 429 | 430 | pos = p 431 | 432 | -- print("cname: ", cname) 433 | 434 | ans.cname = cname 435 | 436 | elseif typ == TYPE_AAAA then 437 | 438 | if len ~= 16 then 439 | return nil, "bad AAAA record value length: " .. len 440 | end 441 | 442 | local addr_bytes = { byte(buf, pos, pos + 15) } 443 | local flds = {} 444 | for i = 1, 16, 2 do 445 | local a = addr_bytes[i] 446 | local b = addr_bytes[i + 1] 447 | if a == 0 then 448 | insert(flds, format("%x", b)) 449 | 450 | else 451 | insert(flds, format("%x%02x", a, b)) 452 | end 453 | end 454 | 455 | -- we do not compress the IPv6 addresses by default 456 | -- due to performance considerations 457 | 458 | ans.address = concat(flds, ":") 459 | 460 | pos = pos + 16 461 | 462 | elseif typ == TYPE_MX then 463 | 464 | -- print("len = ", len) 465 | 466 | if len < 3 then 467 | return nil, "bad MX record value length: " .. len 468 | end 469 | 470 | local pref_hi = byte(buf, pos) 471 | local pref_lo = byte(buf, pos + 1) 472 | 473 | ans.preference = lshift(pref_hi, 8) + pref_lo 474 | 475 | local host, p = _decode_name(buf, pos + 2) 476 | if not host then 477 | return nil, pos 478 | end 479 | 480 | if p - pos ~= len then 481 | return nil, format("bad cname record length: %d ~= %d", 482 | p - pos, len) 483 | end 484 | 485 | ans.exchange = host 486 | 487 | pos = p 488 | 489 | elseif typ == TYPE_SRV then 490 | if len < 7 then 491 | return nil, "bad SRV record value length: " .. len 492 | end 493 | 494 | local prio_hi = byte(buf, pos) 495 | local prio_lo = byte(buf, pos + 1) 496 | ans.priority = lshift(prio_hi, 8) + prio_lo 497 | 498 | local weight_hi = byte(buf, pos + 2) 499 | local weight_lo = byte(buf, pos + 3) 500 | ans.weight = lshift(weight_hi, 8) + weight_lo 501 | 502 | local port_hi = byte(buf, pos + 4) 503 | local port_lo = byte(buf, pos + 5) 504 | ans.port = lshift(port_hi, 8) + port_lo 505 | 506 | local name, p = _decode_name(buf, pos + 6) 507 | if not name then 508 | return nil, pos 509 | end 510 | 511 | if p - pos ~= len then 512 | return nil, format("bad srv record length: %d ~= %d", 513 | p - pos, len) 514 | end 515 | 516 | ans.target = name 517 | 518 | pos = p 519 | 520 | elseif typ == TYPE_NS then 521 | 522 | local name, p = _decode_name(buf, pos) 523 | if not name then 524 | return nil, pos 525 | end 526 | 527 | if p - pos ~= len then 528 | return nil, format("bad cname record length: %d ~= %d", 529 | p - pos, len) 530 | end 531 | 532 | pos = p 533 | 534 | -- print("name: ", name) 535 | 536 | ans.nsdname = name 537 | 538 | elseif typ == TYPE_TXT or typ == TYPE_SPF then 539 | 540 | local key = (typ == TYPE_TXT) and "txt" or "spf" 541 | 542 | local slen = byte(buf, pos) 543 | if slen + 1 > len then 544 | -- truncate the over-run TXT record data 545 | slen = len 546 | end 547 | 548 | -- print("slen: ", len) 549 | 550 | local val = sub(buf, pos + 1, pos + slen) 551 | local last = pos + len 552 | pos = pos + slen + 1 553 | 554 | if pos < last then 555 | -- more strings to be processed 556 | -- this code path is usually cold, so we do not 557 | -- merge the following loop on this code path 558 | -- with the processing logic above. 559 | 560 | val = {val} 561 | local idx = 2 562 | repeat 563 | local slen = byte(buf, pos) 564 | if pos + slen + 1 > last then 565 | -- truncate the over-run TXT record data 566 | slen = last - pos - 1 567 | end 568 | 569 | val[idx] = sub(buf, pos + 1, pos + slen) 570 | idx = idx + 1 571 | pos = pos + slen + 1 572 | 573 | until pos >= last 574 | end 575 | 576 | ans[key] = val 577 | 578 | elseif typ == TYPE_PTR then 579 | 580 | local name, p = _decode_name(buf, pos) 581 | if not name then 582 | return nil, pos 583 | end 584 | 585 | if p - pos ~= len then 586 | return nil, format("bad cname record length: %d ~= %d", 587 | p - pos, len) 588 | end 589 | 590 | pos = p 591 | 592 | -- print("name: ", name) 593 | 594 | ans.ptrdname = name 595 | 596 | elseif typ == TYPE_SOA then 597 | local name, p = _decode_name(buf, pos) 598 | if not name then 599 | return nil, pos 600 | end 601 | ans.mname = name 602 | 603 | pos = p 604 | name, p = _decode_name(buf, pos) 605 | if not name then 606 | return nil, pos 607 | end 608 | ans.rname = name 609 | 610 | for _, field in ipairs(soa_int32_fields) do 611 | local byte_1, byte_2, byte_3, byte_4 = byte(buf, p, p + 3) 612 | ans[field] = lshift(byte_1, 24) + lshift(byte_2, 16) 613 | + lshift(byte_3, 8) + byte_4 614 | p = p + 4 615 | end 616 | 617 | pos = p 618 | 619 | else 620 | -- for unknown types, just forward the raw value 621 | 622 | ans.rdata = sub(buf, pos, pos + len - 1) 623 | pos = pos + len 624 | end 625 | end 626 | 627 | return pos 628 | end 629 | 630 | 631 | local function parse_response(buf, id, opts) 632 | local n = #buf 633 | if n < 12 then 634 | return nil, 'truncated'; 635 | end 636 | 637 | -- header layout: ident flags nqs nan nns nar 638 | 639 | local ident_hi = byte(buf, 1) 640 | local ident_lo = byte(buf, 2) 641 | local ans_id = lshift(ident_hi, 8) + ident_lo 642 | 643 | -- print("id: ", id, ", ans id: ", ans_id) 644 | 645 | if ans_id ~= id then 646 | -- identifier mismatch and throw it away 647 | log(DEBUG, "id mismatch in the DNS reply: ", ans_id, " ~= ", id) 648 | return nil, "id mismatch" 649 | end 650 | 651 | local flags_hi = byte(buf, 3) 652 | local flags_lo = byte(buf, 4) 653 | local flags = lshift(flags_hi, 8) + flags_lo 654 | 655 | -- print(format("flags: 0x%x", flags)) 656 | 657 | if band(flags, 0x8000) == 0 then 658 | return nil, format("bad QR flag in the DNS response") 659 | end 660 | 661 | if band(flags, 0x200) ~= 0 then 662 | return nil, "truncated" 663 | end 664 | 665 | local code = band(flags, 0xf) 666 | 667 | -- print(format("code: %d", code)) 668 | 669 | local nqs_hi = byte(buf, 5) 670 | local nqs_lo = byte(buf, 6) 671 | local nqs = lshift(nqs_hi, 8) + nqs_lo 672 | 673 | -- print("nqs: ", nqs) 674 | 675 | if nqs ~= 1 then 676 | return nil, format("bad number of questions in DNS response: %d", nqs) 677 | end 678 | 679 | local nan_hi = byte(buf, 7) 680 | local nan_lo = byte(buf, 8) 681 | local nan = lshift(nan_hi, 8) + nan_lo 682 | 683 | -- print("nan: ", nan) 684 | 685 | local nns_hi = byte(buf, 9) 686 | local nns_lo = byte(buf, 10) 687 | local nns = lshift(nns_hi, 8) + nns_lo 688 | 689 | local nar_hi = byte(buf, 11) 690 | local nar_lo = byte(buf, 12) 691 | local nar = lshift(nar_hi, 8) + nar_lo 692 | 693 | -- skip the question part 694 | 695 | local ans_qname, pos = _decode_name(buf, 13) 696 | if not ans_qname then 697 | return nil, pos 698 | end 699 | 700 | -- print("qname in reply: ", ans_qname) 701 | 702 | -- print("question: ", sub(buf, 13, pos)) 703 | 704 | if pos + 3 + nan * 12 > n then 705 | -- print(format("%d > %d", pos + 3 + nan * 12, n)) 706 | return nil, 'truncated'; 707 | end 708 | 709 | -- question section layout: qname qtype(2) qclass(2) 710 | 711 | --[[ 712 | local type_hi = byte(buf, pos) 713 | local type_lo = byte(buf, pos + 1) 714 | local ans_type = lshift(type_hi, 8) + type_lo 715 | ]] 716 | 717 | -- print("ans qtype: ", ans_type) 718 | 719 | local class_hi = byte(buf, pos + 2) 720 | local class_lo = byte(buf, pos + 3) 721 | local qclass = lshift(class_hi, 8) + class_lo 722 | 723 | -- print("ans qclass: ", qclass) 724 | 725 | if qclass ~= 1 then 726 | return nil, format("unknown query class %d in DNS response", qclass) 727 | end 728 | 729 | pos = pos + 4 730 | 731 | local answers = {} 732 | 733 | if code ~= 0 then 734 | answers.errcode = code 735 | answers.errstr = resolver_errstrs[code] or "unknown" 736 | end 737 | 738 | local authority_section, additional_section 739 | 740 | if opts then 741 | authority_section = opts.authority_section 742 | additional_section = opts.additional_section 743 | if opts.qtype == TYPE_SOA then 744 | authority_section = true 745 | end 746 | end 747 | 748 | local err 749 | 750 | pos, err = parse_section(answers, SECTION_AN, buf, pos, nan) 751 | 752 | if not pos then 753 | return nil, err 754 | end 755 | 756 | if not authority_section and not additional_section then 757 | return answers 758 | end 759 | 760 | pos, err = parse_section(answers, SECTION_NS, buf, pos, nns, 761 | not authority_section) 762 | 763 | if not pos then 764 | return nil, err 765 | end 766 | 767 | if not additional_section then 768 | return answers 769 | end 770 | 771 | pos, err = parse_section(answers, SECTION_AR, buf, pos, nar) 772 | 773 | if not pos then 774 | return nil, err 775 | end 776 | 777 | return answers 778 | end 779 | 780 | 781 | local function _gen_id(self) 782 | local id = self._id -- for regression testing 783 | if id then 784 | return id 785 | end 786 | return rand(0, 65535) -- two bytes 787 | end 788 | 789 | 790 | local function _tcp_query(self, query, id, opts) 791 | local sock = self.tcp_sock 792 | if not sock then 793 | return nil, "not initialized" 794 | end 795 | 796 | log(DEBUG, "query the TCP server due to reply truncation") 797 | 798 | local server = _get_cur_server(self) 799 | 800 | local ok, err = sock:connect(server[1], server[2]) 801 | if not ok then 802 | return nil, "failed to connect to TCP server " 803 | .. concat(server, ":") .. ": " .. err 804 | end 805 | 806 | query = concat(query, "") 807 | local len = #query 808 | 809 | local len_hi = char(rshift(len, 8)) 810 | local len_lo = char(band(len, 0xff)) 811 | 812 | local bytes, err = sock:send({len_hi, len_lo, query}) 813 | if not bytes then 814 | return nil, "failed to send query to TCP server " 815 | .. concat(server, ":") .. ": " .. err 816 | end 817 | 818 | local buf, err = sock:receive(2) 819 | if not buf then 820 | return nil, "failed to receive the reply length field from TCP server " 821 | .. concat(server, ":") .. ": " .. err 822 | end 823 | 824 | len_hi = byte(buf, 1) 825 | len_lo = byte(buf, 2) 826 | len = lshift(len_hi, 8) + len_lo 827 | 828 | -- print("tcp message len: ", len) 829 | 830 | buf, err = sock:receive(len) 831 | if not buf then 832 | return nil, "failed to receive the reply message body from TCP server " 833 | .. concat(server, ":") .. ": " .. err 834 | end 835 | 836 | local answers, err = parse_response(buf, id, opts) 837 | if not answers then 838 | return nil, "failed to parse the reply from the TCP server " 839 | .. concat(server, ":") .. ": " .. err 840 | end 841 | 842 | sock:close() 843 | 844 | return answers 845 | end 846 | 847 | 848 | function _M.tcp_query(self, qname, opts) 849 | local socks = self.socks 850 | if not socks then 851 | return nil, "not initialized" 852 | end 853 | 854 | pick_sock(self, socks) 855 | 856 | local id = _gen_id(self) 857 | 858 | local query, err = _build_request(qname, id, self.no_recurse, opts) 859 | if not query then 860 | return nil, err 861 | end 862 | 863 | return _tcp_query(self, query, id, opts) 864 | end 865 | 866 | 867 | function _M.query(self, qname, opts, tries) 868 | local socks = self.socks 869 | if not socks then 870 | return nil, "not initialized" 871 | end 872 | 873 | local id = _gen_id(self) 874 | 875 | local query, err = _build_request(qname, id, self.no_recurse, opts) 876 | if not query then 877 | return nil, err 878 | end 879 | 880 | -- local cjson = require "cjson" 881 | -- print("query: ", cjson.encode(concat(query, ""))) 882 | 883 | local retrans = self.retrans 884 | if tries then 885 | tries[1] = nil 886 | end 887 | 888 | -- print("retrans: ", retrans) 889 | 890 | for i = 1, retrans do 891 | local sock = pick_sock(self, socks) 892 | 893 | local ok 894 | ok, err = sock:send(query) 895 | if not ok then 896 | local server = _get_cur_server(self) 897 | err = "failed to send request to UDP server " 898 | .. concat(server, ":") .. ": " .. err 899 | 900 | else 901 | local buf 902 | 903 | for _ = 1, 128 do 904 | buf, err = sock:receive(4096) 905 | if err then 906 | local server = _get_cur_server(self) 907 | err = "failed to receive reply from UDP server " 908 | .. concat(server, ":") .. ": " .. err 909 | break 910 | end 911 | 912 | if buf then 913 | local answers 914 | answers, err = parse_response(buf, id, opts) 915 | if err == "truncated" then 916 | answers, err = _tcp_query(self, query, id, opts) 917 | end 918 | 919 | if err and err ~= "id mismatch" then 920 | break 921 | end 922 | 923 | if answers then 924 | return answers, nil, tries 925 | end 926 | end 927 | -- only here in case of an "id mismatch" 928 | end 929 | end 930 | 931 | if tries then 932 | tries[i] = err 933 | tries[i + 1] = nil -- ensure termination for user supplied table 934 | end 935 | end 936 | 937 | return nil, err, tries 938 | end 939 | 940 | 941 | function _M.compress_ipv6_addr(addr) 942 | local addr = re_sub(addr, "^(0:)+|(:0)+$|:(0:)+", "::", "jo") 943 | if addr == "::0" then 944 | addr = "::" 945 | end 946 | 947 | return addr 948 | end 949 | 950 | 951 | local function _expand_ipv6_addr(addr) 952 | if find(addr, "::", 1, true) then 953 | local ncol, addrlen = 8, #addr 954 | 955 | for i = 1, addrlen do 956 | if byte(addr, i) == COLON_CHAR then 957 | ncol = ncol - 1 958 | end 959 | end 960 | 961 | if byte(addr, 1) == COLON_CHAR then 962 | addr = "0" .. addr 963 | end 964 | 965 | if byte(addr, -1) == COLON_CHAR then 966 | addr = addr .. "0" 967 | end 968 | 969 | addr = re_sub(addr, "::", ":" .. rep("0:", ncol), "jo") 970 | end 971 | 972 | return addr 973 | end 974 | 975 | 976 | _M.expand_ipv6_addr = _expand_ipv6_addr 977 | 978 | 979 | function _M.arpa_str(addr) 980 | if find(addr, ":", 1, true) then 981 | addr = _expand_ipv6_addr(addr) 982 | local idx, hidx, addrlen = 1, 1, #addr 983 | 984 | for i = addrlen, 0, -1 do 985 | local s = byte(addr, i) 986 | if s == COLON_CHAR or not s then 987 | for _ = hidx, 4 do 988 | arpa_tmpl[idx] = ZERO_CHAR 989 | idx = idx + 2 990 | end 991 | hidx = 1 992 | else 993 | arpa_tmpl[idx] = s 994 | idx = idx + 2 995 | hidx = hidx + 1 996 | end 997 | end 998 | 999 | addr = char(unpack(arpa_tmpl)) 1000 | else 1001 | addr = re_sub(addr, [[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})]], 1002 | "$4.$3.$2.$1.in-addr.arpa", "ajo") 1003 | end 1004 | 1005 | return addr 1006 | end 1007 | 1008 | 1009 | function _M.reverse_query(self, addr) 1010 | return self.query(self, self.arpa_str(addr), 1011 | {qtype = self.TYPE_PTR}) 1012 | end 1013 | 1014 | 1015 | return _M 1016 | -------------------------------------------------------------------------------- /t/TestDNS.pm: -------------------------------------------------------------------------------- 1 | package TestDNS; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use 5.010001; 7 | use Test::Nginx::Socket::Lua -Base; 8 | #use JSON::XS; 9 | 10 | use constant { 11 | TYPE_A => 1, 12 | TYPE_SOA => 6, 13 | TYPE_TXT => 16, 14 | TYPE_CNAME => 5, 15 | TYPE_AAAA => 28, 16 | TYPE_SRV => 33, 17 | CLASS_INTERNET => 1, 18 | }; 19 | 20 | sub encode_name ($); 21 | sub encode_ipv4 ($); 22 | sub encode_ipv6 ($); 23 | sub encode_record ($); 24 | sub gen_dns_reply ($$); 25 | 26 | sub Test::Base::Filter::dns { 27 | my ($self, $code) = @_; 28 | 29 | my $args = $self->current_arguments; 30 | #warn "args: $args"; 31 | if (defined $args && $args ne 'tcp' && $args ne 'udp') { 32 | die "Invalid argument to the \"dns\" filter: $args\n"; 33 | } 34 | 35 | my $mode = $args // 'udp'; 36 | 37 | my $block = $self->current_block; 38 | 39 | my $pointer_spec = $block->dns_pointers; 40 | my @pointers; 41 | if (defined $pointer_spec) { 42 | my @loops = split /\s*,\s*/, $pointer_spec; 43 | for my $loop (@loops) { 44 | my @nodes = split /\s*=>\s*/, $loop; 45 | my $prev; 46 | for my $n (@nodes) { 47 | if ($n !~ /^\d+$/ || $n == 0) { 48 | die "bad name ID in the --- dns_pointers: $n\n"; 49 | } 50 | 51 | if (!defined $prev) { 52 | $prev = $n; 53 | next; 54 | } 55 | 56 | $pointers[$prev] = $n; 57 | } 58 | } 59 | } 60 | 61 | my $input = eval $code; 62 | if ($@) { 63 | die "failed to evaluate code $code: $@\n"; 64 | } 65 | 66 | if (!ref $input) { 67 | return $input; 68 | } 69 | 70 | if (ref $input eq 'ARRAY') { 71 | my @replies; 72 | for my $t (@$input) { 73 | push @replies, gen_dns_reply($t, $mode); 74 | } 75 | 76 | return \@replies; 77 | } 78 | 79 | if (ref $input eq 'HASH') { 80 | return gen_dns_reply($input, $mode); 81 | } 82 | 83 | return $input; 84 | } 85 | 86 | sub gen_dns_reply ($$) { 87 | my ($t, $mode) = @_; 88 | 89 | my @raw_names; 90 | push @raw_names, \($t->{qname}); 91 | 92 | my $answers = $t->{answer} // []; 93 | if (!ref $answers) { 94 | $answers = [$answers]; 95 | } 96 | 97 | my $authority_answers = $t->{authority} // []; 98 | if (!ref $authority_answers) { 99 | $authority_answers = [$authority_answers]; 100 | } 101 | 102 | my $additional_answers = $t->{additional} // []; 103 | if (!ref $additional_answers) { 104 | $additional_answers = [$additional_answers]; 105 | } 106 | 107 | for my $ans (@$answers) { 108 | push @raw_names, \($ans->{name}); 109 | if (defined $ans->{cname}) { 110 | push @raw_names, \($ans->{cname}); 111 | } 112 | } 113 | 114 | for my $nsans (@$authority_answers) { 115 | push @raw_names, \($nsans->{name}); 116 | if (defined $nsans->{cname}) { 117 | push @raw_names, \($nsans->{cname}); 118 | } 119 | } 120 | 121 | for my $arans (@$additional_answers) { 122 | push @raw_names, \($arans->{name}); 123 | if (defined $arans->{cname}) { 124 | push @raw_names, \($arans->{cname}); 125 | } 126 | } 127 | 128 | for my $rname (@raw_names) { 129 | $$rname = encode_name($$rname // ""); 130 | } 131 | 132 | my $qname = $t->{qname}; 133 | 134 | my $s = ''; 135 | 136 | my $id = $t->{id} // 0; 137 | 138 | $s .= pack("n", $id); 139 | #warn "id: ", length($s), " ", encode_json([$s]); 140 | 141 | my $qr = $t->{qr} // 1; 142 | 143 | my $opcode = $t->{opcode} // 0; 144 | 145 | my $aa = $t->{aa} // 0; 146 | 147 | my $tc = $t->{tc} // 0; 148 | my $rd = $t->{rd} // 1; 149 | my $ra = $t->{ra} // 1; 150 | 151 | my $ad = $t->{ad} // 0; 152 | my $cd = $t->{cd} // 0; 153 | 154 | my $rcode = $t->{rcode} // 0; 155 | 156 | my $flags = ($qr << 15) + ($opcode << 11) + ($aa << 10) + ($tc << 9) 157 | + ($rd << 8) + ($ra << 7) + ($ad << 4) + ($cd << 5) + $rcode; 158 | 159 | #warn sprintf("flags: %b", $flags); 160 | 161 | $flags = pack("n", $flags); 162 | $s .= $flags; 163 | 164 | #warn "flags: ", length($flags), " ", encode_json([$flags]); 165 | 166 | my $qdcount = $t->{qdcount} // 1; 167 | my $ancount = $t->{ancount} // scalar @$answers; 168 | my $nscount = $t->{nscount} // scalar @$authority_answers; 169 | my $arcount = $t->{arcount} // scalar @$additional_answers; 170 | 171 | $s .= pack("nnnn", $qdcount, $ancount, $nscount, $arcount); 172 | 173 | #warn "qname: ", length($qname), " ", encode_json([$qname]); 174 | 175 | $s .= $qname; 176 | 177 | my $qs_type = $t->{qtype} // TYPE_A; 178 | my $qs_class = $t->{qclass} // CLASS_INTERNET; 179 | 180 | $s .= pack("nn", $qs_type, $qs_class); 181 | 182 | for my $ans (@$answers) { 183 | $s .= encode_record($ans); 184 | } 185 | 186 | for my $nsans (@$authority_answers) { 187 | $s .= encode_record($nsans); 188 | } 189 | 190 | for my $arans (@$additional_answers) { 191 | $s .= encode_record($arans); 192 | } 193 | 194 | if ($mode eq 'tcp') { 195 | return pack("n", length($s)) . $s; 196 | } 197 | 198 | return $s; 199 | } 200 | 201 | sub encode_ipv4 ($) { 202 | my $txt = shift; 203 | my @bytes = split /\./, $txt; 204 | return pack("CCCC", @bytes), 4; 205 | } 206 | 207 | sub encode_ipv6 ($) { 208 | my $txt = shift; 209 | my @groups = split /:/, $txt; 210 | my $nils = 0; 211 | my $nonnils = 0; 212 | for my $g (@groups) { 213 | if ($g eq '') { 214 | $nils++; 215 | } else { 216 | $nonnils++; 217 | $g = hex($g); 218 | } 219 | } 220 | 221 | my $total = $nils + $nonnils; 222 | if ($total > 8 ) { 223 | die "Invalid IPv6 address: too many groups: $total: $txt"; 224 | } 225 | 226 | if ($nils) { 227 | my $found = 0; 228 | my @new_groups; 229 | for my $g (@groups) { 230 | if ($g eq '') { 231 | if ($found) { 232 | next; 233 | } 234 | 235 | for (1 .. 8 - $nonnils) { 236 | push @new_groups, 0; 237 | } 238 | 239 | $found = 1; 240 | 241 | } else { 242 | push @new_groups, $g; 243 | } 244 | } 245 | 246 | @groups = @new_groups; 247 | } 248 | 249 | if (@groups != 8) { 250 | die "Invalid IPv6 address: $txt: @groups\n"; 251 | } 252 | 253 | #warn "IPv6 groups: @groups"; 254 | 255 | return pack("nnnnnnnn", @groups), 16; 256 | } 257 | 258 | sub encode_name ($) { 259 | my $name = shift; 260 | $name =~ s/([^.]+)\.?/chr(length($1)) . $1/ge; 261 | $name .= "\0"; 262 | return $name; 263 | } 264 | 265 | sub encode_record ($) { 266 | my $ans = shift; 267 | my $name = $ans->{name}; 268 | my $type = $ans->{type}; 269 | my $class = $ans->{class}; 270 | my $ttl = $ans->{ttl}; 271 | my $rdlength = $ans->{rdlength}; 272 | my $rddata = $ans->{rddata}; 273 | 274 | my $ipv4 = $ans->{ipv4}; 275 | if (defined $ipv4) { 276 | my ($data, $len) = encode_ipv4($ipv4); 277 | $rddata //= $data; 278 | $rdlength //= $len; 279 | $type //= TYPE_A; 280 | $class //= CLASS_INTERNET; 281 | } 282 | 283 | my $ipv6 = $ans->{ipv6}; 284 | if (defined $ipv6) { 285 | my ($data, $len) = encode_ipv6($ipv6); 286 | $rddata //= $data; 287 | $rdlength //= $len; 288 | $type //= TYPE_AAAA; 289 | $class //= CLASS_INTERNET; 290 | } 291 | 292 | my $cname = $ans->{cname}; 293 | if (defined $cname) { 294 | $rddata //= $cname; 295 | $rdlength //= length $rddata; 296 | $type //= TYPE_CNAME; 297 | $class //= CLASS_INTERNET; 298 | } 299 | 300 | my $txt = $ans->{txt}; 301 | if (defined $txt) { 302 | $rddata //= $txt; 303 | $rdlength //= length $rddata; 304 | $type //= TYPE_TXT; 305 | $class //= CLASS_INTERNET; 306 | } 307 | 308 | 309 | my $srv = $ans->{srv}; 310 | if (defined $srv) { 311 | $rddata //= pack("nnn", $ans->{priority}, $ans->{weight}, $ans->{port}) . encode_name($srv); 312 | $rdlength //= length $rddata; 313 | $type //= TYPE_SRV; 314 | $class //= CLASS_INTERNET; 315 | } 316 | 317 | my $soa = $ans->{soa}; 318 | if (defined $soa) { 319 | my $data = encode_name($soa) . encode_name($ans->{rname}); 320 | $data .= pack("NNNNN", $ans->{serial}, $ans->{refresh}, $ans->{retry}, $ans->{expire}, $ans->{minimum}); 321 | $rddata //= $data; 322 | $rdlength //= length $rddata; 323 | $type //= TYPE_SOA; 324 | $class //= CLASS_INTERNET; 325 | } 326 | 327 | $type //= 0; 328 | $class //= 0; 329 | $ttl //= 0; 330 | 331 | return $name . pack("nnNn", $type, $class, $ttl, $rdlength) . $rddata; 332 | } 333 | 334 | 1 335 | -------------------------------------------------------------------------------- /t/lib/ljson.lua: -------------------------------------------------------------------------------- 1 | local ngx_null = ngx.null 2 | local tostring = tostring 3 | local byte = string.byte 4 | local gsub = string.gsub 5 | local sort = table.sort 6 | local pairs = pairs 7 | local ipairs = ipairs 8 | local concat = table.concat 9 | 10 | local ok, new_tab = pcall(require, "table.new") 11 | if not ok then 12 | new_tab = function (narr, nrec) return {} end 13 | end 14 | 15 | local _M = {} 16 | 17 | local metachars = { 18 | ['\t'] = '\\t', 19 | ["\\"] = "\\\\", 20 | ['"'] = '\\"', 21 | ['\r'] = '\\r', 22 | ['\n'] = '\\n', 23 | } 24 | 25 | local function encode_str(s) 26 | -- XXX we will rewrite this when string.buffer is implemented 27 | -- in LuaJIT 2.1 because string.gsub cannot be JIT compiled. 28 | return gsub(s, '["\\\r\n\t]', metachars) 29 | end 30 | 31 | local function is_arr(t) 32 | local exp = 1 33 | for k, _ in pairs(t) do 34 | if k ~= exp then 35 | return nil 36 | end 37 | exp = exp + 1 38 | end 39 | return exp - 1 40 | end 41 | 42 | local encode 43 | 44 | encode = function (v) 45 | if v == nil or v == ngx_null then 46 | return "null" 47 | end 48 | 49 | local typ = type(v) 50 | if typ == 'string' then 51 | return '"' .. encode_str(v) .. '"' 52 | end 53 | 54 | if typ == 'number' or typ == 'boolean' then 55 | return tostring(v) 56 | end 57 | 58 | if typ == 'table' then 59 | local n = is_arr(v) 60 | if n then 61 | local bits = new_tab(n, 0) 62 | for i, elem in ipairs(v) do 63 | bits[i] = encode(elem) 64 | end 65 | return "[" .. concat(bits, ",") .. "]" 66 | end 67 | 68 | local keys = {} 69 | local i = 0 70 | for key, _ in pairs(v) do 71 | i = i + 1 72 | keys[i] = key 73 | end 74 | sort(keys) 75 | 76 | local bits = new_tab(0, i) 77 | i = 0 78 | for _, key in ipairs(keys) do 79 | i = i + 1 80 | bits[i] = encode(key) .. ":" .. encode(v[key]) 81 | end 82 | return "{" .. concat(bits, ",") .. "}" 83 | end 84 | 85 | return '"<' .. typ .. '>"' 86 | end 87 | _M.encode = encode 88 | 89 | return _M 90 | -------------------------------------------------------------------------------- /t/mock.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | my $skip; 4 | 5 | BEGIN { 6 | if ($ENV{TEST_NGINX_CHECK_LEAK}) { 7 | $skip = "skipped in check leak mode"; 8 | } 9 | } 10 | 11 | use lib 't'; 12 | use TestDNS (defined $skip ? (skip_all => $skip) : ()); 13 | use Cwd qw(cwd); 14 | 15 | repeat_each(2); 16 | 17 | plan tests => repeat_each() * (3 * blocks() + 17); 18 | 19 | my $pwd = cwd(); 20 | 21 | our $HttpConfig = qq{ 22 | lua_package_path "$pwd/lib/?.lua;$pwd/t/lib/?.lua;;"; 23 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 24 | }; 25 | 26 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 27 | 28 | log_level('notice'); 29 | 30 | no_long_string(); 31 | 32 | run_tests(); 33 | 34 | __DATA__ 35 | 36 | === TEST 1: single answer reply, good A answer 37 | --- http_config eval: $::HttpConfig 38 | --- config 39 | location /t { 40 | content_by_lua ' 41 | local resolver = require "resty.dns.resolver" 42 | 43 | local r, err = resolver:new{ 44 | nameservers = { {"127.0.0.1", 1953} } 45 | } 46 | if not r then 47 | ngx.say("failed to instantiate resolver: ", err) 48 | return 49 | end 50 | 51 | r._id = 125 52 | 53 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 54 | if not ans then 55 | ngx.say("failed to query: ", err) 56 | return 57 | end 58 | 59 | local ljson = require "ljson" 60 | ngx.say("records: ", ljson.encode(ans)) 61 | '; 62 | } 63 | --- udp_listen: 1953 64 | --- udp_reply dns 65 | { 66 | id => 125, 67 | opcode => 0, 68 | qname => 'www.google.com', 69 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 70 | } 71 | --- request 72 | GET /t 73 | --- udp_query eval 74 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 75 | --- response_body 76 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1}] 77 | --- no_error_log 78 | [error] 79 | 80 | 81 | 82 | === TEST 2: empty answer reply 83 | --- http_config eval: $::HttpConfig 84 | --- config 85 | location /t { 86 | content_by_lua ' 87 | local resolver = require "resty.dns.resolver" 88 | 89 | local r, err = resolver:new{ 90 | nameservers = { {"127.0.0.1", 1953} } 91 | } 92 | if not r then 93 | ngx.say("failed to instantiate resolver: ", err) 94 | return 95 | end 96 | 97 | r._id = 125 98 | 99 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 100 | if not ans then 101 | ngx.say("failed to query: ", err) 102 | return 103 | end 104 | 105 | local ljson = require "ljson" 106 | ngx.say("records: ", ljson.encode(ans)) 107 | '; 108 | } 109 | --- udp_listen: 1953 110 | --- udp_reply dns 111 | { 112 | id => 125, 113 | qname => 'www.google.com', 114 | opcode => 0, 115 | } 116 | --- request 117 | GET /t 118 | --- response_body 119 | records: [] 120 | --- no_error_log 121 | [error] 122 | 123 | 124 | 125 | === TEST 3: one byte reply, truncated, without TCP server 126 | --- http_config eval: $::HttpConfig 127 | --- config 128 | location /t { 129 | content_by_lua ' 130 | local resolver = require "resty.dns.resolver" 131 | 132 | local r, err = resolver:new{ 133 | nameservers = { {"127.0.0.1", 1953} }, 134 | retrans = 1, 135 | } 136 | if not r then 137 | ngx.say("failed to instantiate resolver: ", err) 138 | return 139 | end 140 | 141 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 142 | if not ans then 143 | ngx.say("failed to query: ", err) 144 | return 145 | end 146 | 147 | local ljson = require "ljson" 148 | ngx.say("records: ", ljson.encode(ans)) 149 | '; 150 | } 151 | --- udp_listen: 1953 152 | --- udp_reply: a 153 | --- request 154 | GET /t 155 | --- response_body 156 | failed to query: failed to connect to TCP server 127.0.0.1:1953: connection refused 157 | --- error_log 158 | connect() failed 159 | 160 | 161 | 162 | === TEST 4: empty reply 163 | --- http_config eval: $::HttpConfig 164 | --- config 165 | location /t { 166 | content_by_lua ' 167 | local resolver = require "resty.dns.resolver" 168 | 169 | local r, err = resolver:new{ 170 | nameservers = { {"127.0.0.1", 1953} }, 171 | retrans = 1, 172 | } 173 | if not r then 174 | ngx.say("failed to instantiate resolver: ", err) 175 | return 176 | end 177 | 178 | r._id = 125 179 | 180 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 181 | if not ans then 182 | ngx.say("failed to query: ", err) 183 | return 184 | end 185 | 186 | local ljson = require "ljson" 187 | ngx.say("records: ", ljson.encode(ans)) 188 | '; 189 | } 190 | --- udp_listen: 1953 191 | --- udp_reply: 192 | --- request 193 | GET /t 194 | --- response_body 195 | failed to query: failed to connect to TCP server 127.0.0.1:1953: connection refused 196 | --- error_log 197 | connect() failed 198 | 199 | 200 | 201 | === TEST 5: two answers reply that contains AAAA records 202 | --- http_config eval: $::HttpConfig 203 | --- config 204 | location /t { 205 | content_by_lua ' 206 | local resolver = require "resty.dns.resolver" 207 | 208 | local r, err = resolver:new{ 209 | nameservers = { {"127.0.0.1", 1953} } 210 | } 211 | if not r then 212 | ngx.say("failed to instantiate resolver: ", err) 213 | return 214 | end 215 | 216 | r._id = 125 217 | 218 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 219 | if not ans then 220 | ngx.say("failed to query: ", err) 221 | return 222 | end 223 | 224 | local ljson = require "ljson" 225 | ngx.say("records: ", ljson.encode(ans)) 226 | '; 227 | } 228 | --- udp_listen: 1953 229 | --- udp_reply dns 230 | { 231 | id => 125, 232 | opcode => 0, 233 | qname => 'www.google.com', 234 | answer => [ 235 | { name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }, 236 | { name => "l.www.google.com", ipv6 => "::1", ttl => 0 }, 237 | ], 238 | } 239 | --- request 240 | GET /t 241 | --- response_body 242 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1},{"address":"0:0:0:0:0:0:0:1","class":1,"name":"l.www.google.com","section":1,"ttl":0,"type":28}] 243 | --- no_error_log 244 | [error] 245 | 246 | 247 | 248 | === TEST 6: good CNAME answer 249 | --- http_config eval: $::HttpConfig 250 | --- config 251 | location /t { 252 | content_by_lua ' 253 | local resolver = require "resty.dns.resolver" 254 | 255 | local r, err = resolver:new{ 256 | nameservers = { {"127.0.0.1", 1953} } 257 | } 258 | if not r then 259 | ngx.say("failed to instantiate resolver: ", err) 260 | return 261 | end 262 | 263 | r._id = 125 264 | 265 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 266 | if not ans then 267 | ngx.say("failed to query: ", err) 268 | return 269 | end 270 | 271 | local ljson = require "ljson" 272 | ngx.say("records: ", ljson.encode(ans)) 273 | '; 274 | } 275 | --- udp_listen: 1953 276 | --- udp_reply dns 277 | { 278 | id => 125, 279 | opcode => 0, 280 | qname => 'www.google.com', 281 | answer => [ 282 | { name => "www.google.com", cname => "blah.google.com", ttl => 125 }, 283 | ], 284 | } 285 | --- request 286 | GET /t 287 | --- response_body 288 | records: [{"class":1,"cname":"blah.google.com","name":"www.google.com","section":1,"ttl":125,"type":5}] 289 | --- no_error_log 290 | [error] 291 | 292 | 293 | 294 | === TEST 7: CNAME answer with bad rd length 295 | --- http_config eval: $::HttpConfig 296 | --- config 297 | location /t { 298 | content_by_lua ' 299 | local resolver = require "resty.dns.resolver" 300 | 301 | local r, err = resolver:new{ 302 | nameservers = { {"127.0.0.1", 1953} }, 303 | retrans = 1, 304 | } 305 | if not r then 306 | ngx.say("failed to instantiate resolver: ", err) 307 | return 308 | end 309 | 310 | r._id = 125 311 | 312 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 313 | if not ans then 314 | ngx.say("failed to query: ", err) 315 | return 316 | end 317 | 318 | local ljson = require "ljson" 319 | ngx.say("records: ", ljson.encode(ans)) 320 | '; 321 | } 322 | --- udp_listen: 1953 323 | --- udp_reply dns 324 | { 325 | id => 125, 326 | opcode => 0, 327 | qname => 'www.google.com', 328 | answer => [ 329 | { name => "www.google.com", cname => "blah.google.com", ttl => 125, rdlength => 3 }, 330 | ], 331 | } 332 | --- request 333 | GET /t 334 | --- response_body 335 | failed to query: bad cname record length: 17 ~= 3 336 | --- no_error_log 337 | [error] 338 | 339 | 340 | 341 | === TEST 8: single answer reply, bad A answer, wrong record length 342 | --- http_config eval: $::HttpConfig 343 | --- config 344 | location /t { 345 | content_by_lua ' 346 | local resolver = require "resty.dns.resolver" 347 | 348 | local r, err = resolver:new{ 349 | nameservers = { {"127.0.0.1", 1953} }, 350 | retrans = 1, 351 | } 352 | if not r then 353 | ngx.say("failed to instantiate resolver: ", err) 354 | return 355 | end 356 | 357 | r._id = 125 358 | 359 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 360 | if not ans then 361 | ngx.say("failed to query: ", err) 362 | return 363 | end 364 | 365 | local ljson = require "ljson" 366 | ngx.say("records: ", ljson.encode(ans)) 367 | '; 368 | } 369 | --- udp_listen: 1953 370 | --- udp_reply dns 371 | { 372 | id => 125, 373 | opcode => 0, 374 | qname => 'www.google.com', 375 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456, rdlength => 1 }], 376 | } 377 | --- request 378 | GET /t 379 | --- response_body 380 | failed to query: bad A record value length: 1 381 | --- no_error_log 382 | [error] 383 | 384 | 385 | 386 | === TEST 9: bad AAAA record, wrong len 387 | --- http_config eval: $::HttpConfig 388 | --- config 389 | location /t { 390 | content_by_lua ' 391 | local resolver = require "resty.dns.resolver" 392 | 393 | local r, err = resolver:new{ 394 | nameservers = { {"127.0.0.1", 1953} }, 395 | retrans = 1, 396 | } 397 | if not r then 398 | ngx.say("failed to instantiate resolver: ", err) 399 | return 400 | end 401 | 402 | r._id = 125 403 | 404 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 405 | if not ans then 406 | ngx.say("failed to query: ", err) 407 | return 408 | end 409 | 410 | local ljson = require "ljson" 411 | ngx.say("records: ", ljson.encode(ans)) 412 | '; 413 | } 414 | --- udp_listen: 1953 415 | --- udp_reply dns 416 | { 417 | id => 125, 418 | opcode => 0, 419 | qname => 'www.google.com', 420 | answer => [ 421 | { name => "l.www.google.com", ipv6 => "::1", ttl => 0, rdlength => 21 }, 422 | ], 423 | } 424 | --- request 425 | GET /t 426 | --- response_body 427 | failed to query: bad AAAA record value length: 21 428 | --- no_error_log 429 | [error] 430 | 431 | 432 | 433 | === TEST 10: timeout 434 | --- http_config eval: $::HttpConfig 435 | --- config 436 | location /t { 437 | content_by_lua ' 438 | local resolver = require "resty.dns.resolver" 439 | 440 | local r, err = resolver:new{ 441 | nameservers = { {"127.0.0.1", 1953} }, 442 | retrans = 1, 443 | } 444 | if not r then 445 | ngx.say("failed to instantiate resolver: ", err) 446 | return 447 | end 448 | 449 | r:set_timeout(100) -- in ms 450 | 451 | r._id = 125 452 | 453 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 454 | if not ans then 455 | ngx.say("failed to query: ", err) 456 | return 457 | end 458 | 459 | local ljson = require "ljson" 460 | ngx.say("records: ", ljson.encode(ans)) 461 | '; 462 | } 463 | --- udp_listen: 1953 464 | --- udp_reply_delay: 200ms 465 | --- udp_reply dns 466 | { 467 | id => 125, 468 | opcode => 0, 469 | qname => 'www.google.com', 470 | answer => [ 471 | { name => "l.www.google.com", ipv6 => "::1", ttl => 0 }, 472 | ], 473 | } 474 | --- request 475 | GET /t 476 | --- response_body 477 | failed to query: failed to receive reply from UDP server 127.0.0.1:1953: timeout 478 | --- error_log 479 | lua udp socket read timed out 480 | --- timeout: 3 481 | 482 | 483 | 484 | === TEST 11: not timeout finally (re-transmission works) 485 | --- http_config eval: $::HttpConfig 486 | --- config 487 | location /t { 488 | content_by_lua ' 489 | local resolver = require "resty.dns.resolver" 490 | 491 | local r, err = resolver:new{ 492 | nameservers = { {"127.0.0.1", 1953} }, 493 | retrans = 3, 494 | } 495 | if not r then 496 | ngx.say("failed to instantiate resolver: ", err) 497 | return 498 | end 499 | 500 | r:set_timeout(200) -- in ms 501 | 502 | r._id = 125 503 | 504 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 505 | if not ans then 506 | ngx.say("failed to query: ", err) 507 | return 508 | end 509 | 510 | local ljson = require "ljson" 511 | ngx.say("records: ", ljson.encode(ans)) 512 | '; 513 | } 514 | --- udp_listen: 1953 515 | --- udp_reply_delay: 500ms 516 | --- udp_reply dns 517 | { 518 | id => 125, 519 | opcode => 0, 520 | qname => 'www.google.com', 521 | answer => [ 522 | { name => "l.www.google.com", ipv6 => "FF01::101", ttl => 0 }, 523 | ], 524 | } 525 | --- request 526 | GET /t 527 | --- response_body 528 | records: [{"address":"ff01:0:0:0:0:0:0:101","class":1,"name":"l.www.google.com","section":1,"ttl":0,"type":28}] 529 | --- error_log 530 | lua udp socket read timed out 531 | --- timeout: 3 532 | 533 | 534 | 535 | === TEST 12: timeout finally (re-transmission works but not enough retrans times) 536 | --- http_config eval: $::HttpConfig 537 | --- config 538 | location /t { 539 | content_by_lua ' 540 | local resolver = require "resty.dns.resolver" 541 | 542 | local r, err = resolver:new{ 543 | nameservers = { {"127.0.0.1", 1953} }, 544 | retrans = 2, 545 | } 546 | if not r then 547 | ngx.say("failed to instantiate resolver: ", err) 548 | return 549 | end 550 | 551 | r:set_timeout(200) -- in ms 552 | 553 | r._id = 125 554 | 555 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 556 | if not ans then 557 | ngx.say("failed to query: ", err) 558 | return 559 | end 560 | 561 | local ljson = require "ljson" 562 | ngx.say("records: ", ljson.encode(ans)) 563 | '; 564 | } 565 | --- udp_listen: 1953 566 | --- udp_reply_delay: 500ms 567 | --- udp_reply dns 568 | { 569 | id => 125, 570 | opcode => 0, 571 | qname => 'www.google.com', 572 | answer => [ 573 | { name => "l.www.google.com", ipv6 => "FF01::101", ttl => 0 }, 574 | ], 575 | } 576 | --- request 577 | GET /t 578 | --- response_body 579 | failed to query: failed to receive reply from UDP server 127.0.0.1:1953: timeout 580 | --- error_log 581 | lua udp socket read timed out 582 | --- timeout: 3 583 | 584 | 585 | 586 | === TEST 13: RCODE - format error 587 | --- http_config eval: $::HttpConfig 588 | --- config 589 | location /t { 590 | content_by_lua ' 591 | local resolver = require "resty.dns.resolver" 592 | 593 | local r, err = resolver:new{ 594 | nameservers = { {"127.0.0.1", 1953} }, 595 | retrans = 3, 596 | } 597 | if not r then 598 | ngx.say("failed to instantiate resolver: ", err) 599 | return 600 | end 601 | 602 | r:set_timeout(200) -- in ms 603 | 604 | r._id = 125 605 | 606 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 607 | if not ans then 608 | ngx.say("failed to query: ", err) 609 | return 610 | end 611 | 612 | local ljson = require "ljson" 613 | ngx.say("records: ", ljson.encode(ans)) 614 | '; 615 | } 616 | --- udp_listen: 1953 617 | --- udp_reply dns 618 | { 619 | id => 125, 620 | rcode => 1, 621 | opcode => 0, 622 | qname => 'www.google.com', 623 | } 624 | --- request 625 | GET /t 626 | --- response_body 627 | records: {"errcode":1,"errstr":"format error"} 628 | --- no_error_log 629 | [error] 630 | 631 | 632 | 633 | === TEST 14: RCODE - server failure 634 | --- http_config eval: $::HttpConfig 635 | --- config 636 | location /t { 637 | content_by_lua ' 638 | local resolver = require "resty.dns.resolver" 639 | 640 | local r, err = resolver:new{ 641 | nameservers = { {"127.0.0.1", 1953} }, 642 | retrans = 3, 643 | } 644 | if not r then 645 | ngx.say("failed to instantiate resolver: ", err) 646 | return 647 | end 648 | 649 | r:set_timeout(200) -- in ms 650 | 651 | r._id = 125 652 | 653 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 654 | if not ans then 655 | ngx.say("failed to query: ", err) 656 | return 657 | end 658 | 659 | local ljson = require "ljson" 660 | 661 | if ans.errcode then 662 | ngx.say("error code: ", ans.errcode, ": ", ans.errstr) 663 | end 664 | 665 | for i, rec in ipairs(ans) do 666 | ngx.say("record: ", ljson.encode(rec)) 667 | end 668 | '; 669 | } 670 | --- udp_listen: 1953 671 | --- udp_reply dns 672 | { 673 | id => 125, 674 | rcode => 2, 675 | opcode => 0, 676 | qname => 'www.google.com', 677 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 678 | } 679 | --- request 680 | GET /t 681 | --- response_body 682 | error code: 2: server failure 683 | record: {"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1} 684 | --- no_error_log 685 | [error] 686 | 687 | 688 | 689 | === TEST 15: RCODE - name error 690 | --- http_config eval: $::HttpConfig 691 | --- config 692 | location /t { 693 | content_by_lua ' 694 | local resolver = require "resty.dns.resolver" 695 | 696 | local r, err = resolver:new{ 697 | nameservers = { {"127.0.0.1", 1953} }, 698 | retrans = 3, 699 | } 700 | if not r then 701 | ngx.say("failed to instantiate resolver: ", err) 702 | return 703 | end 704 | 705 | r:set_timeout(200) -- in ms 706 | 707 | r._id = 125 708 | 709 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 710 | if not ans then 711 | ngx.say("failed to query: ", err) 712 | return 713 | end 714 | 715 | local ljson = require "ljson" 716 | ngx.say("records: ", ljson.encode(ans)) 717 | '; 718 | } 719 | --- udp_listen: 1953 720 | --- udp_reply dns 721 | { 722 | id => 125, 723 | rcode => 3, 724 | opcode => 0, 725 | qname => 'www.google.com', 726 | } 727 | --- request 728 | GET /t 729 | --- response_body 730 | records: {"errcode":3,"errstr":"name error"} 731 | --- no_error_log 732 | [error] 733 | 734 | 735 | 736 | === TEST 16: RCODE - not implemented 737 | --- http_config eval: $::HttpConfig 738 | --- config 739 | location /t { 740 | content_by_lua ' 741 | local resolver = require "resty.dns.resolver" 742 | 743 | local r, err = resolver:new{ 744 | nameservers = { {"127.0.0.1", 1953} }, 745 | retrans = 3, 746 | } 747 | if not r then 748 | ngx.say("failed to instantiate resolver: ", err) 749 | return 750 | end 751 | 752 | r:set_timeout(200) -- in ms 753 | 754 | r._id = 125 755 | 756 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 757 | if not ans then 758 | ngx.say("failed to query: ", err) 759 | return 760 | end 761 | 762 | local ljson = require "ljson" 763 | ngx.say("records: ", ljson.encode(ans)) 764 | '; 765 | } 766 | --- udp_listen: 1953 767 | --- udp_reply dns 768 | { 769 | id => 125, 770 | rcode => 4, 771 | opcode => 0, 772 | qname => 'www.google.com', 773 | } 774 | --- request 775 | GET /t 776 | --- response_body 777 | records: {"errcode":4,"errstr":"not implemented"} 778 | --- no_error_log 779 | [error] 780 | 781 | 782 | 783 | === TEST 17: RCODE - refused 784 | --- http_config eval: $::HttpConfig 785 | --- config 786 | location /t { 787 | content_by_lua ' 788 | local resolver = require "resty.dns.resolver" 789 | 790 | local r, err = resolver:new{ 791 | nameservers = { {"127.0.0.1", 1953} }, 792 | retrans = 3, 793 | } 794 | if not r then 795 | ngx.say("failed to instantiate resolver: ", err) 796 | return 797 | end 798 | 799 | r:set_timeout(200) -- in ms 800 | 801 | r._id = 125 802 | 803 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 804 | if not ans then 805 | ngx.say("failed to query: ", err) 806 | return 807 | end 808 | 809 | local ljson = require "ljson" 810 | ngx.say("records: ", ljson.encode(ans)) 811 | '; 812 | } 813 | --- udp_listen: 1953 814 | --- udp_reply dns 815 | { 816 | id => 125, 817 | rcode => 5, 818 | opcode => 0, 819 | qname => 'www.google.com', 820 | } 821 | --- request 822 | GET /t 823 | --- response_body 824 | records: {"errcode":5,"errstr":"refused"} 825 | --- no_error_log 826 | [error] 827 | 828 | 829 | 830 | === TEST 18: RCODE - unknown 831 | --- http_config eval: $::HttpConfig 832 | --- config 833 | location /t { 834 | content_by_lua ' 835 | local resolver = require "resty.dns.resolver" 836 | 837 | local r, err = resolver:new{ 838 | nameservers = { {"127.0.0.1", 1953} }, 839 | retrans = 3, 840 | } 841 | if not r then 842 | ngx.say("failed to instantiate resolver: ", err) 843 | return 844 | end 845 | 846 | r:set_timeout(200) -- in ms 847 | 848 | r._id = 125 849 | 850 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 851 | if not ans then 852 | ngx.say("failed to query: ", err) 853 | return 854 | end 855 | 856 | local ljson = require "ljson" 857 | ngx.say("records: ", ljson.encode(ans)) 858 | '; 859 | } 860 | --- udp_listen: 1953 861 | --- udp_reply dns 862 | { 863 | id => 125, 864 | rcode => 6, 865 | opcode => 0, 866 | qname => 'www.google.com', 867 | } 868 | --- request 869 | GET /t 870 | --- response_body 871 | records: {"errcode":6,"errstr":"unknown"} 872 | --- no_error_log 873 | [error] 874 | 875 | 876 | 877 | === TEST 19: TC (TrunCation) = 1, no TCP server 878 | --- http_config eval: $::HttpConfig 879 | --- config 880 | location /t { 881 | content_by_lua ' 882 | local resolver = require "resty.dns.resolver" 883 | 884 | local r, err = resolver:new{ 885 | nameservers = { {"127.0.0.1", 1953} }, 886 | retrans = 1, 887 | } 888 | if not r then 889 | ngx.say("failed to instantiate resolver: ", err) 890 | return 891 | end 892 | 893 | r:set_timeout(1000) -- in ms 894 | 895 | r._id = 125 896 | 897 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 898 | if not ans then 899 | ngx.say("failed to query: ", err) 900 | return 901 | end 902 | 903 | local ljson = require "ljson" 904 | ngx.say("records: ", ljson.encode(ans)) 905 | '; 906 | } 907 | --- udp_listen: 1953 908 | --- udp_reply dns 909 | { 910 | id => 125, 911 | tc => 1, 912 | qname => 'www.google.com', 913 | } 914 | --- request 915 | GET /t 916 | --- response_body 917 | failed to query: failed to connect to TCP server 127.0.0.1:1953: connection refused 918 | --- error_log 919 | connect() failed 920 | 921 | 922 | 923 | === TEST 20: bad QR flag (0) 924 | --- http_config eval: $::HttpConfig 925 | --- config 926 | location /t { 927 | content_by_lua ' 928 | local resolver = require "resty.dns.resolver" 929 | 930 | local r, err = resolver:new{ 931 | nameservers = { {"127.0.0.1", 1953} }, 932 | retrans = 1, 933 | } 934 | if not r then 935 | ngx.say("failed to instantiate resolver: ", err) 936 | return 937 | end 938 | 939 | r:set_timeout(1000) -- in ms 940 | 941 | r._id = 125 942 | 943 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 944 | if not ans then 945 | ngx.say("failed to query: ", err) 946 | return 947 | end 948 | 949 | local ljson = require "ljson" 950 | ngx.say("records: ", ljson.encode(ans)) 951 | '; 952 | } 953 | --- udp_listen: 1953 954 | --- udp_reply dns 955 | { 956 | id => 125, 957 | qr => 0, 958 | qname => 'www.google.com', 959 | } 960 | --- request 961 | GET /t 962 | --- response_body 963 | failed to query: bad QR flag in the DNS response 964 | --- no_error_log 965 | [error] 966 | --- no_check_leak 967 | 968 | 969 | 970 | === TEST 21: Recursion Desired off 971 | --- http_config eval: $::HttpConfig 972 | --- config 973 | location /t { 974 | content_by_lua ' 975 | local resolver = require "resty.dns.resolver" 976 | 977 | local r, err = resolver:new{ 978 | nameservers = { {"127.0.0.1", 1953} }, 979 | retrans = 3, 980 | no_recurse = true, 981 | } 982 | if not r then 983 | ngx.say("failed to instantiate resolver: ", err) 984 | return 985 | end 986 | 987 | r:set_timeout(200) -- in ms 988 | 989 | r._id = 125 990 | 991 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 992 | if not ans then 993 | ngx.say("failed to query: ", err) 994 | return 995 | end 996 | 997 | local ljson = require "ljson" 998 | ngx.say("records: ", ljson.encode(ans)) 999 | '; 1000 | } 1001 | --- udp_listen: 1953 1002 | --- udp_query eval 1003 | "\x{00}}\x{00}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1004 | --- udp_reply dns 1005 | { 1006 | id => 125, 1007 | qname => 'www.google.com', 1008 | } 1009 | --- request 1010 | GET /t 1011 | --- response_body 1012 | records: [] 1013 | --- no_error_log 1014 | [error] 1015 | --- no_check_leak 1016 | 1017 | 1018 | 1019 | === TEST 22: id mismatch (timeout) 1020 | --- http_config eval: $::HttpConfig 1021 | --- config 1022 | location /t { 1023 | content_by_lua ' 1024 | local resolver = require "resty.dns.resolver" 1025 | 1026 | local r, err = resolver:new{ 1027 | nameservers = { {"127.0.0.1", 1953} }, 1028 | timeout = 10, 1029 | retrans = 2, 1030 | } 1031 | if not r then 1032 | ngx.say("failed to instantiate resolver: ", err) 1033 | return 1034 | end 1035 | 1036 | r._id = 125 1037 | 1038 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1039 | if not ans then 1040 | ngx.say("failed to query: ", err) 1041 | return 1042 | end 1043 | 1044 | local ljson = require "ljson" 1045 | ngx.say("records: ", ljson.encode(ans)) 1046 | '; 1047 | } 1048 | --- udp_listen: 1953 1049 | --- udp_reply dns 1050 | { 1051 | id => 126, 1052 | opcode => 0, 1053 | qname => 'www.google.com', 1054 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1055 | } 1056 | --- request 1057 | GET /t 1058 | --- udp_query eval 1059 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1060 | --- response_body 1061 | failed to query: failed to receive reply from UDP server 127.0.0.1:1953: timeout 1062 | --- error_log 1063 | id mismatch in the DNS reply: 126 ~= 125 1064 | --- log_level: debug 1065 | 1066 | 1067 | 1068 | === TEST 23: id mismatch (and then match) 1069 | --- http_config eval: $::HttpConfig 1070 | --- config 1071 | location /t { 1072 | content_by_lua ' 1073 | local resolver = require "resty.dns.resolver" 1074 | 1075 | local r, err = resolver:new{ 1076 | nameservers = { {"127.0.0.1", 1953} }, 1077 | timeout = 10, 1078 | retrans = 2, 1079 | } 1080 | if not r then 1081 | ngx.say("failed to instantiate resolver: ", err) 1082 | return 1083 | end 1084 | 1085 | r._id = 125 1086 | 1087 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1088 | if not ans then 1089 | ngx.say("failed to query: ", err) 1090 | return 1091 | end 1092 | 1093 | local ljson = require "ljson" 1094 | ngx.say("records: ", ljson.encode(ans)) 1095 | '; 1096 | } 1097 | --- udp_listen: 1953 1098 | --- udp_reply dns 1099 | [ 1100 | { 1101 | id => 126, 1102 | opcode => 0, 1103 | qname => 'www.google.com', 1104 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1105 | }, 1106 | { 1107 | id => 127, 1108 | opcode => 0, 1109 | qname => 'www.google.com', 1110 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1111 | }, 1112 | { 1113 | id => 120, 1114 | opcode => 0, 1115 | qname => 'www.google.com', 1116 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1117 | }, 1118 | { 1119 | id => 125, 1120 | opcode => 0, 1121 | qname => 'www.google.com', 1122 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1123 | }, 1124 | ] 1125 | --- request 1126 | GET /t 1127 | --- udp_query eval 1128 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1129 | --- response_body 1130 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1}] 1131 | --- error_log 1132 | id mismatch in the DNS reply: 126 ~= 125 1133 | id mismatch in the DNS reply: 120 ~= 125 1134 | id mismatch in the DNS reply: 127 ~= 125 1135 | --- log_level: debug 1136 | 1137 | 1138 | 1139 | === TEST 24: TC (TrunCation) = 1, with TCP server 1140 | --- http_config eval: $::HttpConfig 1141 | --- config 1142 | location /t { 1143 | content_by_lua ' 1144 | local resolver = require "resty.dns.resolver" 1145 | 1146 | local r, err = resolver:new{ 1147 | nameservers = { {"127.0.0.1", 1953} }, 1148 | retrans = 3, 1149 | } 1150 | if not r then 1151 | ngx.say("failed to instantiate resolver: ", err) 1152 | return 1153 | end 1154 | 1155 | r:set_timeout(1000) -- in ms 1156 | 1157 | r._id = 125 1158 | 1159 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1160 | if not ans then 1161 | ngx.say("failed to query: ", err) 1162 | return 1163 | end 1164 | 1165 | local ljson = require "ljson" 1166 | ngx.say("records: ", ljson.encode(ans)) 1167 | '; 1168 | } 1169 | --- udp_listen: 1953 1170 | --- udp_reply dns 1171 | { 1172 | id => 125, 1173 | tc => 1, 1174 | qname => 'www.google.com', 1175 | } 1176 | --- tcp_listen: 1953 1177 | --- tcp_query_len: 34 1178 | --- tcp_reply dns=tcp 1179 | { 1180 | id => 125, 1181 | opcode => 0, 1182 | qname => 'www.google.com', 1183 | answer => [ 1184 | { name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }, 1185 | { name => "l.www.google.com", ipv6 => "::1", ttl => 0 }, 1186 | ], 1187 | } 1188 | --- request 1189 | GET /t 1190 | --- response_body 1191 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1},{"address":"0:0:0:0:0:0:0:1","class":1,"name":"l.www.google.com","section":1,"ttl":0,"type":28}] 1192 | --- no_error_log 1193 | [error] 1194 | --- error_log 1195 | query the TCP server due to reply truncation 1196 | --- log_level: debug 1197 | 1198 | 1199 | 1200 | === TEST 25: one byte reply, truncated, with TCP server 1201 | --- http_config eval: $::HttpConfig 1202 | --- config 1203 | location /t { 1204 | content_by_lua ' 1205 | local resolver = require "resty.dns.resolver" 1206 | 1207 | local r, err = resolver:new{ 1208 | nameservers = { {"127.0.0.1", 1953} } 1209 | } 1210 | if not r then 1211 | ngx.say("failed to instantiate resolver: ", err) 1212 | return 1213 | end 1214 | 1215 | r._id = 125 1216 | 1217 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1218 | if not ans then 1219 | ngx.say("failed to query: ", err) 1220 | return 1221 | end 1222 | 1223 | local ljson = require "ljson" 1224 | ngx.say("records: ", ljson.encode(ans)) 1225 | '; 1226 | } 1227 | --- udp_listen: 1953 1228 | --- udp_reply: a 1229 | --- tcp_listen: 1953 1230 | --- tcp_query_len: 34 1231 | --- tcp_reply dns=tcp 1232 | { 1233 | id => 125, 1234 | opcode => 0, 1235 | qname => 'www.google.com', 1236 | answer => [ 1237 | { name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }, 1238 | { name => "l.www.google.com", ipv6 => "::1", ttl => 0 }, 1239 | ], 1240 | } 1241 | --- request 1242 | GET /t 1243 | --- response_body 1244 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1},{"address":"0:0:0:0:0:0:0:1","class":1,"name":"l.www.google.com","section":1,"ttl":0,"type":28}] 1245 | --- no_error_log 1246 | [error] 1247 | --- error_log 1248 | query the TCP server due to reply truncation 1249 | --- log_level: debug 1250 | 1251 | 1252 | 1253 | === TEST 26: single answer reply, TXT answer with a single char string 1254 | --- http_config eval: $::HttpConfig 1255 | --- config 1256 | location /t { 1257 | content_by_lua ' 1258 | local resolver = require "resty.dns.resolver" 1259 | 1260 | local r, err = resolver:new{ 1261 | nameservers = { {"127.0.0.1", 1953} } 1262 | } 1263 | if not r then 1264 | ngx.say("failed to instantiate resolver: ", err) 1265 | return 1266 | end 1267 | 1268 | r._id = 125 1269 | 1270 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_TXT }) 1271 | if not ans then 1272 | ngx.say("failed to query: ", err) 1273 | return 1274 | end 1275 | 1276 | local ljson = require "ljson" 1277 | ngx.say("records: ", ljson.encode(ans)) 1278 | '; 1279 | } 1280 | --- udp_listen: 1953 1281 | --- udp_reply dns 1282 | { 1283 | id => 125, 1284 | opcode => 0, 1285 | qtype => 16, # TXT 1286 | qname => 'www.google.com', 1287 | answer => [{ name => "www.google.com", txt => "\5hello", ttl => 123456 }], 1288 | } 1289 | --- request 1290 | GET /t 1291 | --- udp_query eval 1292 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{10}\x{00}\x{01}" 1293 | --- response_body 1294 | records: [{"class":1,"name":"www.google.com","section":1,"ttl":123456,"txt":"hello","type":16}] 1295 | --- no_error_log 1296 | [error] 1297 | 1298 | 1299 | 1300 | === TEST 27: single answer reply, TXT answer with a null char string 1301 | --- http_config eval: $::HttpConfig 1302 | --- config 1303 | location /t { 1304 | content_by_lua ' 1305 | local resolver = require "resty.dns.resolver" 1306 | 1307 | local r, err = resolver:new{ 1308 | nameservers = { {"127.0.0.1", 1953} } 1309 | } 1310 | if not r then 1311 | ngx.say("failed to instantiate resolver: ", err) 1312 | return 1313 | end 1314 | 1315 | r._id = 125 1316 | 1317 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_TXT }) 1318 | if not ans then 1319 | ngx.say("failed to query: ", err) 1320 | return 1321 | end 1322 | 1323 | local ljson = require "ljson" 1324 | ngx.say("records: ", ljson.encode(ans)) 1325 | '; 1326 | } 1327 | --- udp_listen: 1953 1328 | --- udp_reply dns 1329 | { 1330 | id => 125, 1331 | opcode => 0, 1332 | qtype => 16, # TXT 1333 | qname => 'www.google.com', 1334 | answer => [{ name => "www.google.com", txt => "\0", ttl => 123456 }], 1335 | } 1336 | --- request 1337 | GET /t 1338 | --- udp_query eval 1339 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{10}\x{00}\x{01}" 1340 | --- response_body 1341 | records: [{"class":1,"name":"www.google.com","section":1,"ttl":123456,"txt":"","type":16}] 1342 | --- no_error_log 1343 | [error] 1344 | 1345 | 1346 | 1347 | === TEST 28: single answer reply, TXT answer with a multiple char strings 1348 | --- http_config eval: $::HttpConfig 1349 | --- config 1350 | location /t { 1351 | content_by_lua ' 1352 | local resolver = require "resty.dns.resolver" 1353 | 1354 | local r, err = resolver:new{ 1355 | nameservers = { {"127.0.0.1", 1953} } 1356 | } 1357 | if not r then 1358 | ngx.say("failed to instantiate resolver: ", err) 1359 | return 1360 | end 1361 | 1362 | r._id = 125 1363 | 1364 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_TXT }) 1365 | if not ans then 1366 | ngx.say("failed to query: ", err) 1367 | return 1368 | end 1369 | 1370 | local ljson = require "ljson" 1371 | ngx.say("records: ", ljson.encode(ans)) 1372 | '; 1373 | } 1374 | --- udp_listen: 1953 1375 | --- udp_reply dns 1376 | { 1377 | id => 125, 1378 | opcode => 0, 1379 | qtype => 16, # TXT 1380 | qname => 'www.google.com', 1381 | answer => [{ name => "www.google.com", txt => "\5hello\5world", ttl => 123456 }], 1382 | } 1383 | --- request 1384 | GET /t 1385 | --- udp_query eval 1386 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{10}\x{00}\x{01}" 1387 | --- response_body 1388 | records: [{"class":1,"name":"www.google.com","section":1,"ttl":123456,"txt":["hello","world"],"type":16}] 1389 | --- no_error_log 1390 | [error] 1391 | 1392 | 1393 | 1394 | === TEST 29: single answer reply, multiple TXT answers 1395 | --- http_config eval: $::HttpConfig 1396 | --- config 1397 | location /t { 1398 | content_by_lua ' 1399 | local resolver = require "resty.dns.resolver" 1400 | 1401 | local r, err = resolver:new{ 1402 | nameservers = { {"127.0.0.1", 1953} } 1403 | } 1404 | if not r then 1405 | ngx.say("failed to instantiate resolver: ", err) 1406 | return 1407 | end 1408 | 1409 | r._id = 125 1410 | 1411 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_TXT }) 1412 | if not ans then 1413 | ngx.say("failed to query: ", err) 1414 | return 1415 | end 1416 | 1417 | local ljson = require "ljson" 1418 | ngx.say("records: ", ljson.encode(ans)) 1419 | '; 1420 | } 1421 | --- udp_listen: 1953 1422 | --- udp_reply dns 1423 | { 1424 | id => 125, 1425 | opcode => 0, 1426 | qtype => 16, # TXT 1427 | qname => 'www.google.com', 1428 | answer => [{ name => "www.google.com", txt => "\5hello\6world!", ttl => 123456 }, { name => "www.google.com", txt => "\4blah", ttl => 123456 }], 1429 | } 1430 | --- request 1431 | GET /t 1432 | --- udp_query eval 1433 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{10}\x{00}\x{01}" 1434 | --- response_body 1435 | records: [{"class":1,"name":"www.google.com","section":1,"ttl":123456,"txt":["hello","world!"],"type":16},{"class":1,"name":"www.google.com","section":1,"ttl":123456,"txt":"blah","type":16}] 1436 | --- no_error_log 1437 | [error] 1438 | 1439 | 1440 | 1441 | === TEST 30: reply with authority section 1442 | --- http_config eval: $::HttpConfig 1443 | --- config 1444 | location /t { 1445 | content_by_lua_block { 1446 | local resolver = require "resty.dns.resolver" 1447 | 1448 | local r, err = resolver:new{ 1449 | nameservers = { {"127.0.0.1", 1953} } 1450 | } 1451 | if not r then 1452 | ngx.say("failed to instantiate resolver: ", err) 1453 | return 1454 | end 1455 | 1456 | r._id = 125 1457 | 1458 | local ans, err = r:query("www.google.com", { qtype = r.SRV, authority_section = true }) 1459 | if not ans then 1460 | ngx.say("failed to query: ", err) 1461 | return 1462 | end 1463 | 1464 | local ljson = require "ljson" 1465 | ngx.say("records: ", ljson.encode(ans)) 1466 | } 1467 | } 1468 | --- udp_listen: 1953 1469 | --- udp_reply dns 1470 | { 1471 | id => 125, 1472 | opcode => 0, 1473 | qname => "www.google.com", 1474 | answer => [ 1475 | { 1476 | name => "www.google.com", 1477 | srv => "test_service.www.google.com", 1478 | weight => 0, 1479 | port => 8080, 1480 | priority => 1, 1481 | qtypt => 33, 1482 | ttl => 0 1483 | } 1484 | ], 1485 | authority => [ 1486 | { 1487 | name => "test_service.www.google.com", 1488 | ipv4 => "127.0.0.1", 1489 | ttl => 0 1490 | } 1491 | ] 1492 | } 1493 | --- request 1494 | GET /t 1495 | --- response_body 1496 | records: [{"class":1,"name":"www.google.com","port":8080,"priority":1,"section":1,"target":"test_service.www.google.com","ttl":0,"type":33,"weight":0},{"address":"127.0.0.1","class":1,"name":"test_service.www.google.com","section":2,"ttl":0,"type":1}] 1497 | --- no_error_log 1498 | [error] 1499 | 1500 | 1501 | 1502 | === TEST 31: reply with additional section 1503 | --- http_config eval: $::HttpConfig 1504 | --- config 1505 | location /t { 1506 | content_by_lua_block { 1507 | local resolver = require "resty.dns.resolver" 1508 | 1509 | local r, err = resolver:new{ 1510 | nameservers = { {"127.0.0.1", 1953} } 1511 | } 1512 | if not r then 1513 | ngx.say("failed to instantiate resolver: ", err) 1514 | return 1515 | end 1516 | 1517 | r._id = 125 1518 | 1519 | local ans, err = r:query("www.google.com", { qtype = r.SRV, additional_section = true }) 1520 | if not ans then 1521 | ngx.say("failed to query: ", err) 1522 | return 1523 | end 1524 | 1525 | local ljson = require "ljson" 1526 | ngx.say("records: ", ljson.encode(ans)) 1527 | } 1528 | } 1529 | --- udp_listen: 1953 1530 | --- udp_reply dns 1531 | { 1532 | id => 125, 1533 | opcode => 0, 1534 | qname => "www.google.com", 1535 | answer => [ 1536 | { 1537 | name => "www.google.com", 1538 | srv => "test_service.www.google.com", 1539 | weight => 0, 1540 | port => 8080, 1541 | priority => 1, 1542 | qtypt => 33, 1543 | ttl => 0 1544 | } 1545 | ], 1546 | authority => [ 1547 | { 1548 | name => "test_service.www.google.com", 1549 | ipv4 => "127.0.0.1", 1550 | ttl => 0 1551 | } 1552 | ], 1553 | additional => [ 1554 | { 1555 | name => "test_service.www.google.com", 1556 | ipv4 => "127.0.0.1", 1557 | ttl => 0 1558 | } 1559 | ] 1560 | } 1561 | --- request 1562 | GET /t 1563 | --- response_body 1564 | records: [{"class":1,"name":"www.google.com","port":8080,"priority":1,"section":1,"target":"test_service.www.google.com","ttl":0,"type":33,"weight":0},{"address":"127.0.0.1","class":1,"name":"test_service.www.google.com","section":3,"ttl":0,"type":1}] 1565 | --- no_error_log 1566 | [error] 1567 | 1568 | 1569 | 1570 | === TEST 32: reply all sections 1571 | --- http_config eval: $::HttpConfig 1572 | --- config 1573 | location /t { 1574 | content_by_lua_block { 1575 | local resolver = require "resty.dns.resolver" 1576 | 1577 | local r, err = resolver:new{ 1578 | nameservers = { {"127.0.0.1", 1953} } 1579 | } 1580 | if not r then 1581 | ngx.say("failed to instantiate resolver: ", err) 1582 | return 1583 | end 1584 | 1585 | r._id = 125 1586 | 1587 | local ans, err = r:query("www.google.com", { qtype = r.SRV, additional_section = true, authority_section = true }) 1588 | if not ans then 1589 | ngx.say("failed to query: ", err) 1590 | return 1591 | end 1592 | 1593 | local ljson = require "ljson" 1594 | ngx.say("records: ", ljson.encode(ans)) 1595 | } 1596 | } 1597 | --- udp_listen: 1953 1598 | --- udp_reply dns 1599 | { 1600 | id => 125, 1601 | opcode => 0, 1602 | qname => "www.google.com", 1603 | answer => [ 1604 | { 1605 | name => "www.google.com", 1606 | srv => "test_service.www.google.com", 1607 | weight => 0, 1608 | port => 8080, 1609 | priority => 1, 1610 | qtypt => 33, 1611 | ttl => 0 1612 | } 1613 | ], 1614 | authority => [ 1615 | { 1616 | name => "test_service.www.google.com", 1617 | ipv4 => "127.0.0.1", 1618 | ttl => 0 1619 | } 1620 | ], 1621 | additional => [ 1622 | { 1623 | name => "test_service.www.google.com", 1624 | ipv4 => "127.0.0.1", 1625 | ttl => 0 1626 | } 1627 | ] 1628 | } 1629 | --- request 1630 | GET /t 1631 | --- response_body 1632 | records: [{"class":1,"name":"www.google.com","port":8080,"priority":1,"section":1,"target":"test_service.www.google.com","ttl":0,"type":33,"weight":0},{"address":"127.0.0.1","class":1,"name":"test_service.www.google.com","section":2,"ttl":0,"type":1},{"address":"127.0.0.1","class":1,"name":"test_service.www.google.com","section":3,"ttl":0,"type":1}] 1633 | --- no_error_log 1634 | [error] 1635 | 1636 | 1637 | 1638 | === TEST 33: single answer reply, good A answer (AD is set) 1639 | --- http_config eval: $::HttpConfig 1640 | --- config 1641 | location /t { 1642 | content_by_lua ' 1643 | local resolver = require "resty.dns.resolver" 1644 | 1645 | local r, err = resolver:new{ 1646 | nameservers = { {"127.0.0.1", 1953} } 1647 | } 1648 | if not r then 1649 | ngx.say("failed to instantiate resolver: ", err) 1650 | return 1651 | end 1652 | 1653 | r._id = 125 1654 | 1655 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1656 | if not ans then 1657 | ngx.say("failed to query: ", err) 1658 | return 1659 | end 1660 | 1661 | local ljson = require "ljson" 1662 | ngx.say("records: ", ljson.encode(ans)) 1663 | '; 1664 | } 1665 | --- udp_listen: 1953 1666 | --- udp_reply dns 1667 | { 1668 | id => 125, 1669 | opcode => 0, 1670 | qname => 'www.google.com', 1671 | ad => 1, 1672 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1673 | } 1674 | --- request 1675 | GET /t 1676 | --- udp_query eval 1677 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1678 | --- response_body 1679 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1}] 1680 | --- no_error_log 1681 | [error] 1682 | 1683 | 1684 | 1685 | === TEST 34: single answer reply, good A answer (CD is set) 1686 | --- http_config eval: $::HttpConfig 1687 | --- config 1688 | location /t { 1689 | content_by_lua ' 1690 | local resolver = require "resty.dns.resolver" 1691 | 1692 | local r, err = resolver:new{ 1693 | nameservers = { {"127.0.0.1", 1953} } 1694 | } 1695 | if not r then 1696 | ngx.say("failed to instantiate resolver: ", err) 1697 | return 1698 | end 1699 | 1700 | r._id = 125 1701 | 1702 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 1703 | if not ans then 1704 | ngx.say("failed to query: ", err) 1705 | return 1706 | end 1707 | 1708 | local ljson = require "ljson" 1709 | ngx.say("records: ", ljson.encode(ans)) 1710 | '; 1711 | } 1712 | --- udp_listen: 1953 1713 | --- udp_reply dns 1714 | { 1715 | id => 125, 1716 | opcode => 0, 1717 | qname => 'www.google.com', 1718 | cd => 1, 1719 | answer => [{ name => "www.google.com", ipv4 => "127.0.0.1", ttl => 123456 }], 1720 | } 1721 | --- request 1722 | GET /t 1723 | --- udp_query eval 1724 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1725 | --- response_body 1726 | records: [{"address":"127.0.0.1","class":1,"name":"www.google.com","section":1,"ttl":123456,"type":1}] 1727 | --- no_error_log 1728 | [error] 1729 | 1730 | 1731 | 1732 | === TEST 35: reply with SOA followed by additional data 1733 | --- http_config eval: $::HttpConfig 1734 | --- config 1735 | location /t { 1736 | content_by_lua_block { 1737 | local resolver = require "resty.dns.resolver" 1738 | 1739 | local r, err = resolver:new{ 1740 | nameservers = { {"127.0.0.1", 1953} } 1741 | } 1742 | if not r then 1743 | ngx.say("failed to instantiate resolver: ", err) 1744 | return 1745 | end 1746 | 1747 | r._id = 125 1748 | 1749 | local ans, err = r:query("google.com", { qtype = r.TYPE_SOA, additional_section = true }) 1750 | if not ans then 1751 | ngx.say("failed to query: ", err) 1752 | return 1753 | end 1754 | 1755 | local ljson = require "ljson" 1756 | ngx.say("records: ", ljson.encode(ans)) 1757 | } 1758 | } 1759 | --- udp_listen: 1953 1760 | --- udp_reply dns 1761 | { 1762 | id => 125, 1763 | opcode => 0, 1764 | qname => "google.com", 1765 | aa => 1, 1766 | answer => [ 1767 | { 1768 | name => "google.com", 1769 | soa => "ns3.google.com", 1770 | rname => "dns-admin.google.com", 1771 | serial => 175802026, 1772 | refresh => 900, 1773 | retry => 900, 1774 | expire => 1800, 1775 | minimum => 60, 1776 | ttl => 0 1777 | } 1778 | ], 1779 | additional => [ 1780 | { 1781 | name => "ns3.google.com", 1782 | ipv4 => "127.0.0.1", 1783 | ttl => 0 1784 | } 1785 | ] 1786 | } 1787 | --- request 1788 | GET /t 1789 | --- udp_query eval 1790 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{06}google\x{03}com\x{00}\x{00}\x{06}\x{00}\x{01}" 1791 | --- response_body 1792 | records: [{"class":1,"expire":1800,"minimum":60,"mname":"ns3.google.com","name":"google.com","refresh":900,"retry":900,"rname":"dns-admin.google.com","section":1,"serial":175802026,"ttl":0,"type":6},{"address":"127.0.0.1","class":1,"name":"ns3.google.com","section":3,"ttl":0,"type":1}] 1793 | --- no_error_log 1794 | [error] 1795 | 1796 | 1797 | 1798 | === TEST 36: retry on connection failures 1799 | --- http_config eval: $::HttpConfig 1800 | --- config 1801 | location /t { 1802 | content_by_lua_block { 1803 | local resolver = require "resty.dns.resolver" 1804 | 1805 | local r, err = resolver:new{ 1806 | nameservers = { {"127.0.0.1", 20000} }, -- note: bad port 1807 | retrans = 3, 1808 | } 1809 | if not r then 1810 | ngx.say("failed to instantiate resolver: ", err) 1811 | return 1812 | end 1813 | 1814 | r._id = 125 1815 | 1816 | local ans, err, lst = r:query("www.google.com", { qtype = r.TYPE_A }, {}) 1817 | if not ans then 1818 | ngx.say("failed to query: ", err, " ", (lst[#lst] == err)) 1819 | for i, err in ipairs(lst) do 1820 | ngx.say(i, ": ", err) 1821 | end 1822 | return 1823 | end 1824 | -- should not reach here 1825 | } 1826 | } 1827 | --- request 1828 | GET /t 1829 | --- udp_query eval 1830 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1831 | --- response_body 1832 | failed to query: failed to receive reply from UDP server 127.0.0.1:20000: connection refused true 1833 | 1: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1834 | 2: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1835 | 3: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1836 | --- error_log 1837 | Connection refused 1838 | 1839 | 1840 | 1841 | === TEST 37: retry on connection failures, multiple servers 1842 | --- http_config eval: $::HttpConfig 1843 | --- config 1844 | location /t { 1845 | content_by_lua_block { 1846 | local resolver = require "resty.dns.resolver" 1847 | 1848 | local r, err = resolver:new{ 1849 | nameservers = { -- note: using bad ports 1850 | {"127.0.0.1", 20000}, 1851 | {"127.0.0.1", 20001}, 1852 | {"127.0.0.1", 20002}, 1853 | }, 1854 | retrans = 3, 1855 | } 1856 | if not r then 1857 | ngx.say("failed to instantiate resolver: ", err) 1858 | return 1859 | end 1860 | 1861 | r._id = 125 1862 | 1863 | local ans, err, lst = r:query("www.google.com", { qtype = r.TYPE_A }, {}) 1864 | if not ans then 1865 | ngx.say("failed to query:") 1866 | table.sort(lst) -- must sort because we have a random start 1867 | for i, err in ipairs(lst) do 1868 | ngx.say(i, ": ", err) 1869 | end 1870 | return 1871 | end 1872 | -- should not reach here 1873 | } 1874 | } 1875 | --- request 1876 | GET /t 1877 | --- udp_query eval 1878 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1879 | --- response_body 1880 | failed to query: 1881 | 1: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1882 | 2: failed to receive reply from UDP server 127.0.0.1:20001: connection refused 1883 | 3: failed to receive reply from UDP server 127.0.0.1:20002: connection refused 1884 | --- error_log 1885 | Connection refused 1886 | 1887 | 1888 | 1889 | === TEST 38: no_random always starts at first server 1890 | --- http_config eval: $::HttpConfig 1891 | --- config 1892 | location /t { 1893 | content_by_lua_block { 1894 | local resolver = require "resty.dns.resolver" 1895 | 1896 | local ans, err, lst 1897 | local tries = {} 1898 | for i = 1, 10 do 1899 | local r, err = resolver:new{ 1900 | nameservers = { -- note: using bad ports 1901 | {"127.0.0.1", 20000}, 1902 | {"127.0.0.1", 20001}, 1903 | {"127.0.0.1", 20002}, 1904 | }, 1905 | retrans = 1, 1906 | no_random = true, 1907 | } 1908 | if not r then 1909 | ngx.say("failed to instantiate resolver: ", err) 1910 | return 1911 | end 1912 | 1913 | --r._id = 125 1914 | 1915 | local ans, err, lst = r:query("www.google.com", { qtype = r.TYPE_A }, {}) 1916 | if ans then 1917 | error("query wasn't supposed to succeed") 1918 | end 1919 | for _, try in ipairs(lst) do 1920 | tries[#tries+1] = try 1921 | end 1922 | end 1923 | ngx.say("tries:") 1924 | --table.sort(lst) 1925 | for i, err in ipairs(tries) do 1926 | ngx.say(i, ": ", err) 1927 | end 1928 | return 1929 | } 1930 | } 1931 | --- request 1932 | GET /t 1933 | --- udp_query eval 1934 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1935 | --- response_body 1936 | tries: 1937 | 1: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1938 | 2: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1939 | 3: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1940 | 4: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1941 | 5: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1942 | 6: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1943 | 7: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1944 | 8: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1945 | 9: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1946 | 10: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1947 | --- error_log 1948 | Connection refused 1949 | 1950 | 1951 | 1952 | === TEST 39: no_random tries servers in defined order 1953 | --- http_config eval: $::HttpConfig 1954 | --- config 1955 | location /t { 1956 | content_by_lua_block { 1957 | local resolver = require "resty.dns.resolver" 1958 | 1959 | local r, err = resolver:new{ 1960 | nameservers = { -- note: using bad ports 1961 | {"127.0.0.1", 20000}, 1962 | {"127.0.0.1", 20001}, 1963 | {"127.0.0.1", 20002}, 1964 | }, 1965 | retrans = 3, 1966 | no_random = true, 1967 | } 1968 | if not r then 1969 | ngx.say("failed to instantiate resolver: ", err) 1970 | return 1971 | end 1972 | 1973 | r._id = 125 1974 | 1975 | local ans, err, lst = r:query("www.google.com", { qtype = r.TYPE_A }, {}) 1976 | if not ans then 1977 | ngx.say("failed to query:") 1978 | for i, err in ipairs(lst) do 1979 | ngx.say(i, ": ", err) 1980 | end 1981 | return 1982 | end 1983 | -- should not reach here 1984 | } 1985 | } 1986 | --- request 1987 | GET /t 1988 | --- udp_query eval 1989 | "\x{00}}\x{01}\x{00}\x{00}\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{03}www\x{06}google\x{03}com\x{00}\x{00}\x{01}\x{00}\x{01}" 1990 | --- response_body 1991 | failed to query: 1992 | 1: failed to receive reply from UDP server 127.0.0.1:20000: connection refused 1993 | 2: failed to receive reply from UDP server 127.0.0.1:20001: connection refused 1994 | 3: failed to receive reply from UDP server 127.0.0.1:20002: connection refused 1995 | --- error_log 1996 | Connection refused 1997 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 13 | 14 | our $HttpConfig = qq( 15 | lua_package_path "$pwd/t/lib/?.lua;$pwd/lib/?.lua;;"; 16 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 17 | resolver '$ENV{TEST_NGINX_RESOLVER}'; 18 | ); 19 | 20 | no_long_string(); 21 | 22 | run_tests(); 23 | 24 | __DATA__ 25 | 26 | === TEST 1: A records 27 | --- http_config eval: $::HttpConfig 28 | --- config 29 | location /t { 30 | content_by_lua ' 31 | local resolver = require "resty.dns.resolver" 32 | 33 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 34 | if not r then 35 | ngx.say("failed to instantiate resolver: ", err) 36 | return 37 | end 38 | 39 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_A }) 40 | if not ans then 41 | ngx.say("failed to query: ", err) 42 | return 43 | end 44 | 45 | local ljson = require "ljson" 46 | ngx.say("records: ", ljson.encode(ans)) 47 | '; 48 | } 49 | --- request 50 | GET /t 51 | --- response_body_like chop 52 | ^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$ 53 | --- no_error_log 54 | [error] 55 | --- no_check_leak 56 | 57 | 58 | 59 | === TEST 2: CNAME records 60 | --- http_config eval: $::HttpConfig 61 | --- config 62 | location /t { 63 | content_by_lua ' 64 | local resolver = require "resty.dns.resolver" 65 | 66 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 67 | if not r then 68 | ngx.say("failed to instantiate resolver: ", err) 69 | return 70 | end 71 | 72 | local ans, err = r:query("www.yahoo.com", { qtype = r.TYPE_CNAME }) 73 | if not ans then 74 | ngx.say("failed to query: ", err) 75 | return 76 | end 77 | 78 | local ljson = require "ljson" 79 | ngx.say("records: ", ljson.encode(ans)) 80 | '; 81 | } 82 | --- request 83 | GET /t 84 | --- response_body_like chop 85 | ^records: \[.*?"cname":"[-_a-z0-9.]+".*?\]$ 86 | --- no_error_log 87 | [error] 88 | --- no_check_leak 89 | 90 | 91 | 92 | === TEST 3: AAAA records 93 | --- http_config eval: $::HttpConfig 94 | --- config 95 | location /t { 96 | content_by_lua ' 97 | local resolver = require "resty.dns.resolver" 98 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 99 | if not r then 100 | ngx.say("failed to instantiate resolver: ", err) 101 | return 102 | end 103 | 104 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_AAAA }) 105 | if not ans then 106 | ngx.say("failed to query: ", err) 107 | return 108 | end 109 | 110 | local ljson = require "ljson" 111 | ngx.say("records: ", ljson.encode(ans)) 112 | '; 113 | } 114 | --- request 115 | GET /t 116 | --- response_body_like chop 117 | ^records: \[.*?"address":"[a-fA-F0-9]*(?::[a-fA-F0-9]*)+".*?\]$ 118 | --- no_error_log 119 | [error] 120 | --- no_check_leak 121 | 122 | 123 | 124 | === TEST 4: compress ipv6 addr 125 | --- http_config eval: $::HttpConfig 126 | --- config 127 | location /t { 128 | content_by_lua ' 129 | local resolver = require "resty.dns.resolver" 130 | 131 | local c = resolver.compress_ipv6_addr 132 | 133 | ngx.say(c("1080:0:0:0:8:800:200C:417A")) 134 | ngx.say(c("FF01:0:0:0:0:0:0:101")) 135 | ngx.say(c("0:0:0:0:0:0:0:1")) 136 | ngx.say(c("1:5:0:0:0:0:0:0")) 137 | ngx.say(c("7:25:0:0:0:3:0:0")) 138 | ngx.say(c("0:0:0:0:0:0:0:0")) 139 | '; 140 | } 141 | --- request 142 | GET /t 143 | --- response_body 144 | 1080::8:800:200C:417A 145 | FF01::101 146 | ::1 147 | 1:5:: 148 | 7:25::3:0:0 149 | :: 150 | --- no_error_log 151 | [error] 152 | 153 | 154 | 155 | === TEST 5: A records (TCP) 156 | --- http_config eval: $::HttpConfig 157 | --- config 158 | location /t { 159 | content_by_lua ' 160 | local resolver = require "resty.dns.resolver" 161 | 162 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 163 | if not r then 164 | ngx.say("failed to instantiate resolver: ", err) 165 | return 166 | end 167 | 168 | local ans, err = r:tcp_query("www.google.com", { qtype = r.TYPE_A }) 169 | if not ans then 170 | ngx.say("failed to query: ", err) 171 | return 172 | end 173 | 174 | local ljson = require "ljson" 175 | ngx.say("records: ", ljson.encode(ans)) 176 | '; 177 | } 178 | --- request 179 | GET /t 180 | --- response_body_like chop 181 | ^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$ 182 | --- no_error_log 183 | [error] 184 | --- no_check_leak 185 | 186 | 187 | 188 | === TEST 6: MX records 189 | --- http_config eval: $::HttpConfig 190 | --- config 191 | location /t { 192 | content_by_lua ' 193 | local resolver = require "resty.dns.resolver" 194 | 195 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 196 | if not r then 197 | ngx.say("failed to instantiate resolver: ", err) 198 | return 199 | end 200 | 201 | local ans, err = r:query("gmail.com", { qtype = r.TYPE_MX }) 202 | if not ans then 203 | ngx.say("failed to query: ", err) 204 | return 205 | end 206 | 207 | local ljson = require "ljson" 208 | ngx.say("records: ", ljson.encode(ans)) 209 | '; 210 | } 211 | --- request 212 | GET /t 213 | --- response_body_like chop 214 | ^records: \[\{.*?"preference":\d+,.*?"exchange":"[^"]+".*?\}\]$ 215 | --- no_error_log 216 | [error] 217 | --- no_check_leak 218 | 219 | 220 | 221 | === TEST 7: NS records 222 | --- http_config eval: $::HttpConfig 223 | --- config 224 | location /t { 225 | content_by_lua ' 226 | local resolver = require "resty.dns.resolver" 227 | 228 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 229 | if not r then 230 | ngx.say("failed to instantiate resolver: ", err) 231 | return 232 | end 233 | 234 | local ans, err = r:query("google.com", { qtype = r.TYPE_NS }) 235 | if not ans then 236 | ngx.say("failed to query: ", err) 237 | return 238 | end 239 | 240 | local ljson = require "ljson" 241 | ngx.say("records: ", ljson.encode(ans)) 242 | '; 243 | } 244 | --- request 245 | GET /t 246 | --- response_body_like chop 247 | ^records: \[\{.*?"nsdname":"[^"]+".*?\}\]$ 248 | --- no_error_log 249 | [error] 250 | --- no_check_leak 251 | 252 | 253 | 254 | === TEST 8: TXT query (no ans) 255 | --- SKIP 256 | --- http_config eval: $::HttpConfig 257 | --- config 258 | location /t { 259 | content_by_lua ' 260 | local resolver = require "resty.dns.resolver" 261 | 262 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 263 | if not r then 264 | ngx.say("failed to instantiate resolver: ", err) 265 | return 266 | end 267 | 268 | local ans, err = r:query("agentzh.org", { qtype = r.TYPE_TXT }) 269 | if not ans then 270 | ngx.say("failed to query: ", err) 271 | return 272 | end 273 | 274 | local ljson = require "ljson" 275 | ngx.say("records: ", ljson.encode(ans)) 276 | '; 277 | } 278 | --- request 279 | GET /t 280 | --- response_body 281 | records: {} 282 | --- no_error_log 283 | [error] 284 | --- timeout: 10 285 | 286 | 287 | 288 | === TEST 9: TXT query (with ans) 289 | --- http_config eval: $::HttpConfig 290 | --- config 291 | location /t { 292 | content_by_lua ' 293 | local resolver = require "resty.dns.resolver" 294 | 295 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 296 | if not r then 297 | ngx.say("failed to instantiate resolver: ", err) 298 | return 299 | end 300 | 301 | local ans, err = r:query("gmail.com", { qtype = r.TYPE_TXT }) 302 | if not ans then 303 | ngx.say("failed to query: ", err) 304 | return 305 | end 306 | 307 | local ljson = require "ljson" 308 | ngx.say("records: ", ljson.encode(ans)) 309 | '; 310 | } 311 | --- request 312 | GET /t 313 | --- response_body_like chop 314 | ^records: \[\{.*?"txt":"v=spf\d+\s[^"]+".*?\}\]$ 315 | --- no_error_log 316 | [error] 317 | --- no_check_leak 318 | 319 | 320 | 321 | === TEST 10: PTR query 322 | --- http_config eval: $::HttpConfig 323 | --- config 324 | location /t { 325 | content_by_lua ' 326 | local resolver = require "resty.dns.resolver" 327 | 328 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 329 | if not r then 330 | ngx.say("failed to instantiate resolver: ", err) 331 | return 332 | end 333 | 334 | local ans, err = r:query("4.4.8.8.in-addr.arpa", { qtype = r.TYPE_PTR }) 335 | if not ans then 336 | ngx.say("failed to query: ", err) 337 | return 338 | end 339 | 340 | local ljson = require "ljson" 341 | ngx.say("records: ", ljson.encode(ans)) 342 | '; 343 | } 344 | --- request 345 | GET /t 346 | --- response_body_like chop 347 | ^records: \[\{.*?"ptrdname":"dns\.google".*?\}\]$ 348 | --- no_error_log 349 | [error] 350 | --- no_check_leak 351 | 352 | 353 | 354 | === TEST 11: domains with a trailing dot 355 | --- http_config eval: $::HttpConfig 356 | --- config 357 | location /t { 358 | content_by_lua ' 359 | local resolver = require "resty.dns.resolver" 360 | 361 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 362 | if not r then 363 | ngx.say("failed to instantiate resolver: ", err) 364 | return 365 | end 366 | 367 | local ans, err = r:query("www.google.com.", { qtype = r.TYPE_A }) 368 | if not ans then 369 | ngx.say("failed to query: ", err) 370 | return 371 | end 372 | 373 | local ljson = require "ljson" 374 | ngx.say("records: ", ljson.encode(ans)) 375 | '; 376 | } 377 | --- request 378 | GET /t 379 | --- response_body_like chop 380 | ^records: \[.*?"address":"(?:\d{1,3}\.){3}\d+".*?\]$ 381 | --- no_error_log 382 | [error] 383 | --- no_check_leak 384 | 385 | 386 | 387 | === TEST 12: domains with a leading dot 388 | --- http_config eval: $::HttpConfig 389 | --- config 390 | location /t { 391 | content_by_lua ' 392 | local resolver = require "resty.dns.resolver" 393 | 394 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 395 | if not r then 396 | ngx.say("failed to instantiate resolver: ", err) 397 | return 398 | end 399 | 400 | local ans, err = r:query(".www.google.com", { qtype = r.TYPE_A }) 401 | if not ans then 402 | ngx.say("failed to query: ", err) 403 | return 404 | end 405 | 406 | local ljson = require "ljson" 407 | ngx.say("records: ", ljson.encode(ans)) 408 | '; 409 | } 410 | --- request 411 | GET /t 412 | --- response_body 413 | failed to query: bad name 414 | --- no_error_log 415 | [error] 416 | 417 | 418 | 419 | === TEST 13: SRV records or XMPP 420 | --- http_config eval: $::HttpConfig 421 | --- config 422 | location /t { 423 | content_by_lua ' 424 | local resolver = require "resty.dns.resolver" 425 | 426 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 427 | if not r then 428 | ngx.say("failed to instantiate resolver: ", err) 429 | return 430 | end 431 | 432 | local ans, err = r:query("_xmpp-client._tcp.jabber.org", { qtype = r.TYPE_SRV }) 433 | if not ans then 434 | ngx.say("failed to query: ", err) 435 | return 436 | end 437 | 438 | local ljson = require "ljson" 439 | ngx.say("records: ", ljson.encode(ans)) 440 | '; 441 | } 442 | --- request 443 | GET /t 444 | --- response_body_like chop 445 | ^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+},?)+\]$ 446 | --- no_error_log 447 | [error] 448 | --- no_check_leak 449 | 450 | 451 | 452 | === TEST 14: SPF query (with ans) 453 | SPF records are deprecated by RFC 7208 in favor of TXT records, and 454 | linkedin.com has migrated to such TXT records. 455 | --- http_config eval: $::HttpConfig 456 | --- config 457 | location /t { 458 | content_by_lua ' 459 | local resolver = require "resty.dns.resolver" 460 | 461 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 462 | if not r then 463 | ngx.say("failed to instantiate resolver: ", err) 464 | return 465 | end 466 | 467 | local ans, err = r:query("linkedin.com", { qtype = r.TYPE_SPF }) 468 | if not ans then 469 | ngx.say("failed to query: ", err) 470 | return 471 | end 472 | 473 | local ljson = require "ljson" 474 | ngx.say("records: ", ljson.encode(ans)) 475 | '; 476 | } 477 | --- request 478 | GET /t 479 | --- response_body 480 | records: [] 481 | --- no_error_log 482 | [error] 483 | --- no_check_leak 484 | 485 | 486 | 487 | === TEST 15: SPF query (no ans) 488 | --- http_config eval: $::HttpConfig 489 | --- config 490 | location /t { 491 | content_by_lua ' 492 | local resolver = require "resty.dns.resolver" 493 | 494 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 495 | if not r then 496 | ngx.say("failed to instantiate resolver: ", err) 497 | return 498 | end 499 | 500 | local ans, err = r:query("agentzh.org", { qtype = r.TYPE_SPF }) 501 | if not ans then 502 | ngx.say("failed to query: ", err) 503 | return 504 | end 505 | 506 | local ljson = require "ljson" 507 | ngx.say("records: ", ljson.encode(ans)) 508 | '; 509 | } 510 | --- request 511 | GET /t 512 | --- response_body 513 | records: [] 514 | --- no_error_log 515 | [error] 516 | --- timeout: 10 517 | 518 | 519 | 520 | === TEST 16: SPF query (as TXT with ans) 521 | --- http_config eval: $::HttpConfig 522 | --- config 523 | location /t { 524 | content_by_lua ' 525 | local resolver = require "resty.dns.resolver" 526 | 527 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 528 | if not r then 529 | ngx.say("failed to instantiate resolver: ", err) 530 | return 531 | end 532 | 533 | local ans, err = r:query("linkedin.com", { qtype = r.TYPE_TXT }) 534 | if not ans then 535 | ngx.say("failed to query: ", err) 536 | return 537 | end 538 | 539 | for i = 1, #ans do 540 | if string.find(ans[i].txt, "v=spf", nil, true) then 541 | local ljson = require "ljson" 542 | ngx.say("ans: ", ljson.encode(ans[i])) 543 | end 544 | end 545 | '; 546 | } 547 | --- request 548 | GET /t 549 | --- response_body_like chop 550 | ^ans: \{.*?"txt":"v=spf\d+\s[^"]+".*?\}$ 551 | --- no_error_log 552 | [error] 553 | --- no_check_leak 554 | 555 | 556 | 557 | === TEST 17: generate arpa_str 558 | --- http_config eval: $::HttpConfig 559 | --- config 560 | location /t { 561 | content_by_lua ' 562 | local resolver = require "resty.dns.resolver" 563 | local c = resolver.arpa_str 564 | ngx.say(c("1234:5678:abcd:ef99:1234:5678:abcd:ef99")) 565 | ngx.say(c("1080::8:800:200c:417a")) 566 | ngx.say(c("ff01::101")) 567 | ngx.say(c("::1")) 568 | ngx.say(c("::")) 569 | ngx.say(c("1::")) 570 | ngx.say(c("127.0.0.1")) 571 | ngx.say(c("251.252.253.254")) 572 | '; 573 | } 574 | --- request 575 | GET /t 576 | --- response_body 577 | 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 578 | 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 579 | 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 580 | 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 581 | 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 582 | 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 583 | 1.0.0.127.in-addr.arpa 584 | 254.253.252.251.in-addr.arpa 585 | --- no_error_log 586 | [error] 587 | 588 | 589 | 590 | === TEST 18: SOA records 591 | --- http_config eval: $::HttpConfig 592 | --- config 593 | location /t { 594 | content_by_lua ' 595 | local resolver = require "resty.dns.resolver" 596 | 597 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 598 | if not r then 599 | ngx.say("failed to instantiate resolver: ", err) 600 | return 601 | end 602 | 603 | local ans, err = r:query("www.google.com", { qtype = r.TYPE_SOA }) 604 | if not ans then 605 | ngx.say("failed to query: ", err) 606 | return 607 | end 608 | 609 | local ljson = require "ljson" 610 | ngx.say("records: ", ljson.encode(ans)) 611 | '; 612 | } 613 | --- request 614 | GET /t 615 | --- response_body_like chop 616 | ^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},?)+\]$ 617 | --- no_error_log 618 | [error] 619 | --- no_check_leak 620 | 621 | 622 | 623 | === TEST 19: RRTYPE larger than 255 624 | This test case skipped due to changes of the DNS response. 625 | --- SKIP 626 | --- http_config eval: $::HttpConfig 627 | --- config 628 | location /t { 629 | content_by_lua ' 630 | local resolver = require "resty.dns.resolver" 631 | 632 | local r, err = resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 633 | if not r then 634 | ngx.say("failed to instantiate resolver: ", err) 635 | return 636 | end 637 | 638 | local ans, err = r:query("comodo.com", { qtype = 257 }) 639 | if not ans then 640 | ngx.say("failed to query: ", err) 641 | return 642 | end 643 | 644 | local ljson = require "ljson" 645 | ngx.say("records: ", ljson.encode(ans)) 646 | '; 647 | } 648 | --- request 649 | GET /t 650 | --- response_body_like chop 651 | ^records: \[(?:{"class":1,"name":"comodo\.com","rdata":"[^"]+","section":1,"ttl":\d+,"type":257},?)+\]$ 652 | --- no_error_log 653 | [error] 654 | --- no_check_leak 655 | 656 | 657 | 658 | === TEST 20: SOA records 659 | --- http_config eval: $::HttpConfig 660 | --- config 661 | location /t { 662 | content_by_lua ' 663 | local resolver = require "resty.dns.resolver" 664 | 665 | local r, err = 666 | resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 667 | if not r then 668 | ngx.say("failed to instantiate resolver: ", err) 669 | return 670 | end 671 | 672 | local ans, err = r:query("google.com", { qtype = r.TYPE_SOA }) 673 | if not ans then 674 | ngx.say("failed to query: ", err) 675 | return 676 | end 677 | 678 | local ljson = require "ljson" 679 | ngx.say("records: ", ljson.encode(ans)) 680 | '; 681 | } 682 | --- request 683 | GET /t 684 | --- response_body_like chop 685 | ^records: \[.*?"name":"google.com".*?\]$ 686 | --- no_error_log 687 | [error] 688 | --- no_check_leak 689 | 690 | 691 | 692 | === TEST 21: destroy 693 | --- http_config eval: $::HttpConfig 694 | --- config 695 | location /t { 696 | content_by_lua_block { 697 | local resolver = require "resty.dns.resolver" 698 | 699 | local r, err = 700 | resolver:new{ nameservers = { "$TEST_NGINX_RESOLVER" } } 701 | if not r then 702 | ngx.say("failed to instantiate resolver: ", err) 703 | return 704 | end 705 | 706 | r:destroy() 707 | local udp = r.socks or "nil" 708 | local tcp = r.tcp_sock or "nil" 709 | ngx.say("udp socket: " .. udp .. ", tcp socket: " .. tcp) 710 | } 711 | } 712 | --- request 713 | GET /t 714 | --- response_body_like chop 715 | udp socket: nil, tcp socket: nil 716 | --- no_error_log 717 | [error] 718 | --- no_check_leak 719 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | write(buf) 5 | fun:__write_nocancel 6 | fun:ngx_log_error_core 7 | fun:ngx_resolver_read_response 8 | } 9 | { 10 | 11 | Memcheck:Cond 12 | fun:ngx_sprintf_num 13 | fun:ngx_vslprintf 14 | fun:ngx_log_error_core 15 | fun:ngx_resolver_read_response 16 | fun:ngx_epoll_process_events 17 | fun:ngx_process_events_and_timers 18 | fun:ngx_single_process_cycle 19 | fun:main 20 | } 21 | { 22 | 23 | Memcheck:Addr1 24 | fun:ngx_vslprintf 25 | fun:ngx_snprintf 26 | fun:ngx_sock_ntop 27 | fun:ngx_event_accept 28 | } 29 | { 30 | 31 | Memcheck:Param 32 | write(buf) 33 | fun:__write_nocancel 34 | fun:ngx_log_error_core 35 | fun:ngx_resolver_read_response 36 | fun:ngx_event_process_posted 37 | fun:ngx_process_events_and_timers 38 | fun:ngx_single_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Cond 44 | fun:ngx_sprintf_num 45 | fun:ngx_vslprintf 46 | fun:ngx_log_error_core 47 | fun:ngx_resolver_read_response 48 | fun:ngx_event_process_posted 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | 55 | Memcheck:Leak 56 | fun:malloc 57 | fun:ngx_alloc 58 | obj:* 59 | } 60 | { 61 | 62 | exp-sgcheck:SorG 63 | fun:ngx_http_lua_ndk_set_var_get 64 | } 65 | { 66 | 67 | exp-sgcheck:SorG 68 | fun:ngx_http_variables_init_vars 69 | fun:ngx_http_block 70 | } 71 | { 72 | 73 | exp-sgcheck:SorG 74 | fun:ngx_conf_parse 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_vslprintf 80 | fun:ngx_log_error_core 81 | } 82 | { 83 | 84 | Memcheck:Leak 85 | fun:malloc 86 | fun:ngx_alloc 87 | fun:ngx_calloc 88 | fun:ngx_event_process_init 89 | } 90 | { 91 | 92 | Memcheck:Param 93 | epoll_ctl(event) 94 | fun:epoll_ctl 95 | } 96 | { 97 | 98 | Memcheck:Leak 99 | fun:malloc 100 | fun:ngx_alloc 101 | fun:ngx_event_process_init 102 | } 103 | { 104 | 105 | Memcheck:Cond 106 | fun:ngx_conf_flush_files 107 | fun:ngx_single_process_cycle 108 | } 109 | { 110 | 111 | Memcheck:Cond 112 | fun:memcpy 113 | fun:ngx_vslprintf 114 | fun:ngx_log_error_core 115 | fun:ngx_http_charset_header_filter 116 | } 117 | { 118 | 119 | Memcheck:Param 120 | socketcall.setsockopt(optval) 121 | fun:setsockopt 122 | fun:drizzle_state_connect 123 | } 124 | { 125 | 126 | Memcheck:Leak 127 | fun:malloc 128 | fun:ngx_alloc 129 | fun:ngx_pool_cleanup_add 130 | } 131 | { 132 | 133 | Memcheck:Cond 134 | fun:ngx_conf_flush_files 135 | fun:ngx_single_process_cycle 136 | fun:main 137 | } 138 | { 139 | 140 | Memcheck:Leak 141 | fun:malloc 142 | fun:ngx_alloc 143 | fun:ngx_palloc_large 144 | fun:ngx_palloc 145 | fun:ngx_array_push 146 | fun:ngx_http_get_variable_index 147 | fun:ngx_http_memc_add_variable 148 | fun:ngx_http_memc_init 149 | fun:ngx_http_block 150 | fun:ngx_conf_parse 151 | fun:ngx_init_cycle 152 | fun:main 153 | } 154 | { 155 | 156 | Memcheck:Leak 157 | fun:malloc 158 | fun:ngx_alloc 159 | fun:ngx_event_process_init 160 | fun:ngx_single_process_cycle 161 | fun:main 162 | } 163 | { 164 | 165 | Memcheck:Leak 166 | fun:malloc 167 | fun:ngx_alloc 168 | fun:ngx_crc32_table_init 169 | fun:main 170 | } 171 | { 172 | 173 | Memcheck:Leak 174 | fun:malloc 175 | fun:ngx_alloc 176 | fun:ngx_event_process_init 177 | fun:ngx_worker_process_init 178 | fun:ngx_worker_process_cycle 179 | fun:ngx_spawn_process 180 | fun:ngx_start_worker_processes 181 | fun:ngx_master_process_cycle 182 | fun:main 183 | } 184 | { 185 | 186 | Memcheck:Leak 187 | fun:malloc 188 | fun:ngx_alloc 189 | fun:ngx_palloc_large 190 | fun:ngx_palloc 191 | fun:ngx_pcalloc 192 | fun:ngx_hash_init 193 | fun:ngx_http_variables_init_vars 194 | fun:ngx_http_block 195 | fun:ngx_conf_parse 196 | fun:ngx_init_cycle 197 | fun:main 198 | } 199 | { 200 | 201 | Memcheck:Leak 202 | fun:malloc 203 | fun:ngx_alloc 204 | fun:ngx_palloc_large 205 | fun:ngx_palloc 206 | fun:ngx_pcalloc 207 | fun:ngx_http_upstream_drizzle_create_srv_conf 208 | fun:ngx_http_upstream 209 | fun:ngx_conf_parse 210 | fun:ngx_http_block 211 | fun:ngx_conf_parse 212 | fun:ngx_init_cycle 213 | fun:main 214 | } 215 | { 216 | 217 | Memcheck:Leak 218 | fun:malloc 219 | fun:ngx_alloc 220 | fun:ngx_palloc_large 221 | fun:ngx_palloc 222 | fun:ngx_pcalloc 223 | fun:ngx_hash_keys_array_init 224 | fun:ngx_http_variables_add_core_vars 225 | fun:ngx_http_core_preconfiguration 226 | fun:ngx_http_block 227 | fun:ngx_conf_parse 228 | fun:ngx_init_cycle 229 | fun:main 230 | } 231 | { 232 | 233 | Memcheck:Leak 234 | fun:malloc 235 | fun:ngx_alloc 236 | fun:ngx_palloc_large 237 | fun:ngx_palloc 238 | fun:ngx_array_push 239 | fun:ngx_hash_add_key 240 | fun:ngx_http_add_variable 241 | fun:ngx_http_echo_add_variables 242 | fun:ngx_http_echo_handler_init 243 | fun:ngx_http_block 244 | fun:ngx_conf_parse 245 | fun:ngx_init_cycle 246 | } 247 | { 248 | 249 | Memcheck:Leak 250 | fun:malloc 251 | fun:ngx_alloc 252 | fun:ngx_palloc_large 253 | fun:ngx_palloc 254 | fun:ngx_pcalloc 255 | fun:ngx_http_upstream_drizzle_create_srv_conf 256 | fun:ngx_http_core_server 257 | fun:ngx_conf_parse 258 | fun:ngx_http_block 259 | fun:ngx_conf_parse 260 | fun:ngx_init_cycle 261 | fun:main 262 | } 263 | { 264 | 265 | Memcheck:Leak 266 | fun:malloc 267 | fun:ngx_alloc 268 | fun:ngx_palloc_large 269 | fun:ngx_palloc 270 | fun:ngx_pcalloc 271 | fun:ngx_http_upstream_drizzle_create_srv_conf 272 | fun:ngx_http_block 273 | fun:ngx_conf_parse 274 | fun:ngx_init_cycle 275 | fun:main 276 | } 277 | { 278 | 279 | Memcheck:Leak 280 | fun:malloc 281 | fun:ngx_alloc 282 | fun:ngx_palloc_large 283 | fun:ngx_palloc 284 | fun:ngx_array_push 285 | fun:ngx_hash_add_key 286 | fun:ngx_http_variables_add_core_vars 287 | fun:ngx_http_core_preconfiguration 288 | fun:ngx_http_block 289 | fun:ngx_conf_parse 290 | fun:ngx_init_cycle 291 | fun:main 292 | } 293 | { 294 | 295 | Memcheck:Leak 296 | fun:malloc 297 | fun:ngx_alloc 298 | fun:ngx_palloc_large 299 | fun:ngx_palloc 300 | fun:ngx_pcalloc 301 | fun:ngx_init_cycle 302 | fun:main 303 | } 304 | { 305 | 306 | Memcheck:Leak 307 | fun:malloc 308 | fun:ngx_alloc 309 | fun:ngx_palloc_large 310 | fun:ngx_palloc 311 | fun:ngx_hash_init 312 | fun:ngx_http_upstream_init_main_conf 313 | fun:ngx_http_block 314 | fun:ngx_conf_parse 315 | fun:ngx_init_cycle 316 | fun:main 317 | } 318 | { 319 | 320 | Memcheck:Leak 321 | fun:malloc 322 | fun:ngx_alloc 323 | fun:ngx_palloc_large 324 | fun:ngx_palloc 325 | fun:ngx_pcalloc 326 | fun:ngx_http_drizzle_keepalive_init 327 | fun:ngx_http_upstream_drizzle_init 328 | fun:ngx_http_upstream_init_main_conf 329 | fun:ngx_http_block 330 | fun:ngx_conf_parse 331 | fun:ngx_init_cycle 332 | fun:main 333 | } 334 | { 335 | 336 | Memcheck:Leak 337 | fun:malloc 338 | fun:ngx_alloc 339 | fun:ngx_palloc_large 340 | fun:ngx_palloc 341 | fun:ngx_hash_init 342 | fun:ngx_http_variables_init_vars 343 | fun:ngx_http_block 344 | fun:ngx_conf_parse 345 | fun:ngx_init_cycle 346 | fun:main 347 | } 348 | { 349 | 350 | Memcheck:Cond 351 | fun:index 352 | fun:expand_dynamic_string_token 353 | fun:_dl_map_object 354 | fun:map_doit 355 | fun:_dl_catch_error 356 | fun:do_preload 357 | fun:dl_main 358 | fun:_dl_sysdep_start 359 | fun:_dl_start 360 | } 361 | { 362 | 363 | Memcheck:Leak 364 | match-leak-kinds: definite 365 | fun:malloc 366 | fun:ngx_alloc 367 | fun:ngx_set_environment 368 | fun:ngx_single_process_cycle 369 | } 370 | { 371 | 372 | Memcheck:Leak 373 | match-leak-kinds: definite 374 | fun:malloc 375 | fun:ngx_alloc 376 | fun:ngx_set_environment 377 | fun:ngx_worker_process_init 378 | fun:ngx_worker_process_cycle 379 | } 380 | --------------------------------------------------------------------------------