├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.markdown ├── dist.ini ├── lib └── resty │ └── dns │ └── server.lua └── t └── server.t /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | nginx 8 | ctags 9 | tags 10 | a.lua 11 | example.lua -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | compiler: 9 | - gcc 10 | 11 | env: 12 | global: 13 | - JOBS=3 14 | - NGX_BUILD_JOBS=$JOBS 15 | - LUAJIT_PREFIX=/opt/luajit21 16 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 17 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 18 | - LUA_INCLUDE_DIR=$LUAJIT_INC 19 | - LUA_CMODULE_DIR=/lib 20 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 21 | - TEST_NGINX_SLEEP=0.006 22 | matrix: 23 | - NGINX_VERSION=1.17.8 24 | 25 | before_install: 26 | - sudo apt-get install -qq -y axel cpanminus > build.log 2>&1 || (cat build.log && exit 1) 27 | 28 | install: 29 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 30 | - sudo cpanm --notest Test::Nginx::Socket > build.log 2>&1 || (cat build.log && exit 1) 31 | - git clone https://github.com/openresty/test-nginx.git 32 | - git clone https://github.com/openresty/openresty.git ../openresty 33 | - git clone https://github.com/openresty/nginx-devel-utils.git 34 | - git clone https://github.com/openresty/lua-cjson.git 35 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 36 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 37 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 38 | - git clone https://github.com/selboo/stream-lua-nginx-module.git ../stream-lua-nginx-module 39 | - git clone https://github.com/openresty/lua-resty-dns.git ../lua-resty-dns 40 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 41 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 42 | - mkdir -p ../lua-resty-core/lib/resty/dns/. 43 | - cp -rf ../lua-resty-dns/lib/resty/dns/* ../lua-resty-core/lib/resty/dns/. 44 | - cp -rf ./lib/resty/dns/* ../lua-resty-core/lib/resty/dns/. 45 | 46 | script: 47 | - pwd 48 | - ls -ltr ./ 49 | - cd test-nginx && (sudo cpanm . > build.log 2>&1 || (cat build.log && exit 1)) && cd .. 50 | - cd luajit2/ 51 | - 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) 52 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 53 | - cd .. 54 | - cd lua-cjson && make && sudo PATH=$PATH make install && cd .. 55 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 56 | - ngx-build $NGINX_VERSION --with-stream --add-module=../lua-nginx-module --add-module=../stream-lua-nginx-module --with-debug > build.log 2>&1 || (cat build.log && exit 1) 57 | - prove -r t/ 58 | 59 | after_failure: 60 | - cat t/servroot/conf/nginx.conf 61 | - cat t/servroot/logs/error.log 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /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/server.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/dns/ 15 | 16 | 17 | test: all 18 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | [![Build Status](https://travis-ci.org/vislee/lua-resty-dns-server.svg?branch=master)](https://travis-ci.org/vislee/lua-resty-dns-server) 5 | 6 | lua-resty-dns-server - Lua DNS server driver for the OpenResty 7 | 8 | Table of Contents 9 | ================= 10 | 11 | * [Name](#name) 12 | * [Status](#status) 13 | * [Description](#description) 14 | * [Synopsis](#synopsis) 15 | * [Methods](#methods) 16 | * [new](#new) 17 | * [decode_request](#decode_request) 18 | * [create_a_answer](#create_a_answer) 19 | * [create_aaaa_answer](#create_aaaa_answer) 20 | * [create_cname_answer](#create_cname_answer) 21 | * [create_txt_answer](#create_txt_answer) 22 | * [create_ns_answer](#create_ns_answer) 23 | * [create_soa_answer](#create_soa_answer) 24 | * [create_mx_answer](#create_mx_answer) 25 | * [create_srv_answer](#create_srv_answer) 26 | * [create_response_header](#create_response_header) 27 | * [encode_response](#encode_response) 28 | * [Constants](#constants) 29 | * [TYPE_A](#type_a) 30 | * [TYPE_NS](#type_ns) 31 | * [TYPE_CNAME](#type_cname) 32 | * [TYPE_SOA](#type_soa) 33 | * [TYPE_MX](#type_mx) 34 | * [TYPE_TXT](#type_txt) 35 | * [TYPE_AAAA](#type_aaaa) 36 | * [TYPE_SRV](#type_srv) 37 | * [TYPE_ANY](#type_any) 38 | * [RCODE_NOT_IMPLEMENTED](#rcode_not_implemented) 39 | * [TODO](#todo) 40 | * [Author](#author) 41 | * [Copyright and License](#copyright-and-license) 42 | * [See Also](#see-also) 43 | 44 | Status 45 | ====== 46 | 47 | This library is still under early development and is still experimental. 48 | 49 | Description 50 | =========== 51 | 52 | This Lua library provies a DNS server driver for the ngx_lua nginx module: 53 | 54 | https://github.com/openresty/stream-lua-nginx-module/#readme 55 | 56 | Synopsis 57 | ======== 58 | 59 | ```nginx 60 | lua_package_path "/path/to/lua-resty-dns-server/lib/?.lua;;"; 61 | 62 | stream { 63 | server { 64 | listen 53 udp; 65 | content_by_lua_block { 66 | local server = require 'resty.dns.server' 67 | local sock, err = ngx.req.socket() 68 | if not sock then 69 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 70 | return ngx.exit(ngx.ERROR) 71 | end 72 | 73 | local req, err = sock:receive() 74 | if not req then 75 | ngx.log(ngx.ERR, "failed to receive: ", err) 76 | return ngx.exit(ngx.ERROR) 77 | end 78 | 79 | local dns = server:new() 80 | local request, err = dns:decode_request(req) 81 | if not request then 82 | ngx.log(ngx.ERR, "failed to decode request: ", err) 83 | 84 | local resp = dns:encode_response() 85 | local ok, err = sock:send(resp) 86 | if not ok then 87 | ngx.log(ngx.ERR, "failed to send: ", err) 88 | ngx.exit(ngx.ERROR) 89 | end 90 | 91 | return 92 | end 93 | 94 | local query = request.questions[1] 95 | ngx.log(ngx.DEBUG, "qname: ", query.qname, " qtype: ", query.qtype) 96 | 97 | local subnet = request.subnet[1] 98 | if subnet then 99 | ngx.log(ngx.DEBUG, "subnet addr: ", subnet.address, " mask: ", subnet.mask, " family: ", subnet.family) 100 | end 101 | 102 | local cname = "sinacloud.com" 103 | 104 | if query.qtype == server.TYPE_CNAME or 105 | query.qtype == server.TYPE_AAAA or query.qtype == server.TYPE_A then 106 | 107 | local err = dns:create_cname_answer(query.qname, 600, cname) 108 | if err then 109 | ngx.log(ngx.ERR, "failed to create cname answer: ", err) 110 | return 111 | end 112 | else 113 | dns:create_soa_answer("test.com", 600, "a.root-test.com", "vislee.test.com", 1515161223, 1800, 900, 604800, 86400) 114 | end 115 | 116 | local resp = dns:encode_response() 117 | local ok, err = sock:send(resp) 118 | if not ok then 119 | ngx.log(ngx.ERR, "failed to send: ", err) 120 | return 121 | end 122 | } 123 | } 124 | 125 | server { 126 | listen 53; 127 | content_by_lua_block { 128 | local bit = require 'bit' 129 | local lshift = bit.lshift 130 | local rshift = bit.rshift 131 | local band = bit.band 132 | local byte = string.byte 133 | local char = string.char 134 | local server = require 'resty.dns.server' 135 | 136 | local sock, err = ngx.req.socket() 137 | if not sock then 138 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 139 | return ngx.exit(ngx.ERROR) 140 | end 141 | 142 | local buf, err = sock:receive(2) 143 | if not buf then 144 | ngx.log(ngx.ERR, "failed to receive: ", err) 145 | return ngx.exit(ngx.ERROR) 146 | end 147 | 148 | local len_hi = byte(buf, 1) 149 | local len_lo = byte(buf, 2) 150 | local len = lshift(len_hi, 8) + len_lo 151 | local data, err = sock:receive(len) 152 | if not data then 153 | ngx.log(ngx.ERR, "failed to receive: ", err) 154 | return ngx.exit(ngx.ERROR) 155 | end 156 | 157 | local dns = server:new() 158 | local request, err = dns:decode_request(data) 159 | if not request then 160 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 161 | return 162 | end 163 | 164 | local query = request.questions[1] 165 | ngx.log(ngx.DEBUG, "qname: ", query.qname, " qtype: ", query.qtype) 166 | 167 | local subnet = request.subnet[1] 168 | if subnet then 169 | ngx.log(ngx.DEBUG, "subnet addr: ", subnet.address, " mask: ", subnet.mask, " family: ", subnet.family) 170 | end 171 | 172 | if query.qtype == server.TYPE_CNAME or query.qtype == server.TYPE_A then 173 | dns:create_cname_answer(query.qname, 600, "sinacloud.com") 174 | elseif query.qtype == server.TYPE_AAAA then 175 | local resp_header, err = dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 176 | resp_header.ra = 0 177 | else 178 | dns:create_soa_answer("test.com", 600, "a.root-test.com", "vislee.test.com", 1515161223, 1800, 900, 604800, 86400) 179 | end 180 | 181 | local resp = dns:encode_response() 182 | local len = #resp 183 | local len_hi = char(rshift(len, 8)) 184 | local len_lo = char(band(len, 0xff)) 185 | 186 | local ok, err = sock:send({len_hi, len_lo, resp}) 187 | if not ok then 188 | ngx.log(ngx.ERR, "failed to send: ", err) 189 | return 190 | end 191 | return 192 | } 193 | } 194 | } 195 | 196 | ``` 197 | 198 | [Back to TOC](#table-of-contents) 199 | 200 | Methods 201 | ======= 202 | 203 | [Back to TOC](#table-of-contents) 204 | 205 | new 206 | --- 207 | `syntax: s, err = class:new()` 208 | 209 | Creates a dns.server object. Returns `nil` and an message string on error. 210 | 211 | [Back to TOC](#table-of-contents) 212 | 213 | decode_request 214 | -------------- 215 | `syntax: request, err = s:decode_request(buf)` 216 | 217 | Parse the DNS request. 218 | 219 | The request returned the lua table which takes some of the following fields: 220 | 221 | * `header`: The `header` is also a lua table which usually takes some of the following fields: 222 | 223 | * `id` : The identifier assigned by the program that generates any kind of query. 224 | * `qr` : The field specifies whether this message is a query (`0`), or a response (`1`). 225 | * `opcode` : The field specifies kind of query in this message. 226 | * `tc` : The field specifies that this message was truncated due to length greater than that permitted on the transmission channel. 227 | * `rd` : Recursion Desired. If `RD` is set, it directs the name server to pursue the query recursively. 228 | * `rcode` : response code. 229 | * `qdcount` : The field specifying the number of entries in the question section. 230 | 231 | * `questions` : Each entry in the `questions` is also a lua table which takes some of the following: 232 | 233 | * `qname` : A domain name of query. 234 | * `qtype` : Specifies the type of the query. 235 | * `qclass` : Specifies the class of the query. Usually the field is `IN` for the Internet. 236 | 237 | 238 | [Back to TOC](#table-of-contents) 239 | 240 | create_a_answer 241 | -------------- 242 | `syntax: err = s:create_a_answer(name, ttl, ipv4)` 243 | 244 | Create the A records. Returns `nil` or an message string on error. 245 | which usually takes some of the following fields: 246 | 247 | * `name` 248 | 249 | The resource record name. 250 | * `ttl` 251 | 252 | The time-to-live (TTL) value in seconds for the current resource record. 253 | * `ipv4` 254 | 255 | The IPv4 address. 256 | 257 | [Back to TOC](#table-of-contents) 258 | 259 | create_aaaa_answer 260 | --------------- 261 | `syntax: err = s:create_aaaa_answer(name, ttl, ipv6)` 262 | 263 | Create the AAAA records. Returns `nil` or an message string on error. 264 | which usually takes some of the following fields: 265 | 266 | * `name` 267 | 268 | The resource record name. 269 | * `ttl` 270 | 271 | The time-to-live (TTL) value in seconds for the current resource record. 272 | * `ipv6` 273 | 274 | The IPv6 address. 275 | 276 | [Back to TOC](#table-of-contents) 277 | 278 | create_cname_answer 279 | ------------------- 280 | `syntax: err = s:create_cname_answer(name, ttl, cname)` 281 | 282 | Create the CNAME records. Returns `nil` or an message string on error. 283 | which usually takes some of the following fields: 284 | 285 | * `name` 286 | 287 | The resource record name. 288 | * `ttl` 289 | 290 | The time-to-live (TTL) value in seconds for the current resource record. 291 | * `cname` 292 | 293 | The name for an alias. 294 | 295 | [Back to TOC](#table-of-contents) 296 | 297 | create_txt_answer 298 | ------------------ 299 | `syntax: err = s:create_txt_answer(name, ttl, txt)` 300 | 301 | Create the txt records. Returns `nil` or an message string on error. 302 | which usually takes some of the following fields: 303 | 304 | * `name` 305 | 306 | The resource record name. 307 | * `ttl` 308 | 309 | The time-to-live (TTL) value in seconds for the current resource record. 310 | * `txt` 311 | 312 | The text strings. 313 | 314 | [Back to TOC](#table-of-contents) 315 | 316 | create_ns_answer 317 | ---------------- 318 | `syntax: err = s:create_ns_answer(name, ttl, nsdname)` 319 | 320 | Create the NS records. Returns `nil` or an message string on error. 321 | which usually takes some of the following fields: 322 | 323 | * `name` 324 | 325 | The resource record name. 326 | * `ttl` 327 | 328 | The time-to-live (TTL) value in seconds for the current resource record. 329 | * `nsdname` 330 | 331 | The specifies a host which should be authoritative for the specified class and domain. 332 | 333 | [Back to TOC](#table-of-contents) 334 | 335 | create_soa_answer 336 | ----------------- 337 | `syntax: err = s:create_soa_answer(name, ttl, mname, rname, serial, refresh, retry, expire, minimum)` 338 | 339 | Create the SOA records. Returns `nil` or an message string on error. 340 | which usually takes some of the following fields: 341 | 342 | * `name` 343 | 344 | The resource record name. 345 | * `ttl` 346 | 347 | The time-to-live (TTL) value in seconds for the current resource record. 348 | * `mname` 349 | 350 | The the name server that was the original or primary source of data for this zone. 351 | * `rname` 352 | 353 | The mailbox of the person responsible for this zone. 354 | * `serial` 355 | 356 | The unsigned 32 bit version number of the original copy of the zone. 357 | * `refresh` 358 | 359 | A 32 bit time interval before the zone should be refreshed. 360 | * `retry` 361 | 362 | A 32 bit time interval that should elapse before a failed refresh should be retried. 363 | * `expire` 364 | 365 | A 32 bit time value that specifies the upper limit on the time interval that can elapse before the zone is no longer authoritative. 366 | * `minimum` 367 | 368 | The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone. 369 | 370 | [Back to TOC](#table-of-contents) 371 | 372 | create_mx_answer 373 | ---------------- 374 | `syntax: err = s:create_mx_answer(name, ttl, preference, exchange)` 375 | 376 | Create the MX records. Returns `nil` or an message string on error. 377 | which usually takes some of the following fields: 378 | 379 | * `name` 380 | 381 | The resource record name. 382 | * `ttl` 383 | 384 | The time-to-live (TTL) value in seconds for the current resource record. 385 | * `preference` 386 | 387 | The preference of this mail exchange. 388 | * `exchange` 389 | 390 | The mail exchange. 391 | 392 | [Back to TOC](#table-of-contents) 393 | 394 | create_srv_answer 395 | ----------------- 396 | `syntax: err = s:create_srv_answer(name, ttl, priority, weight, port, target)` 397 | 398 | Create the SRV records. Returns `nil` or an message string on error. 399 | which usually takes some of the following fields: 400 | 401 | * `name` 402 | 403 | The resource record name. 404 | * `ttl` 405 | 406 | The time-to-live (TTL) value in seconds for the current resource record. 407 | * `priority` 408 | 409 | The priority of this target host. 410 | * `weight` 411 | 412 | The weight field specifies a relative weight for entries with the same priority. 413 | * `port` 414 | 415 | The port on this target host of this service. 416 | * `target` 417 | 418 | The domain name of the target host. 419 | 420 | [Back to TOC](#table-of-contents) 421 | 422 | create_response_header 423 | ----------------- 424 | `syntax: resp_header, err = s:create_response_header(rcode)` 425 | 426 | [Back to TOC](#table-of-contents) 427 | 428 | encode_response 429 | --------------- 430 | `syntax: resp = s:encode_response()` 431 | 432 | Encode the DNS answers. Returns an message string on response or `nil`. 433 | 434 | [Back to TOC](#table-of-contents) 435 | 436 | Constants 437 | ========= 438 | 439 | [Back to TOC](#table-of-contents) 440 | 441 | TYPE_A 442 | ------ 443 | 444 | The `A` resource record type, equal to the decimal number `1`. 445 | 446 | [Back to TOC](#table-of-contents) 447 | 448 | TYPE_NS 449 | ------- 450 | 451 | The `NS` resource record type, equal to the decimal number `2`. 452 | 453 | [Back to TOC](#table-of-contents) 454 | 455 | TYPE_CNAME 456 | ---------- 457 | 458 | The `CNAME` resource record type, equal to the decimal number `5`. 459 | 460 | [Back to TOC](#table-of-contents) 461 | 462 | TYPE_SOA 463 | ---------- 464 | 465 | The `SOA` resource record type, equal to the decimal number `6`. 466 | 467 | [Back to TOC](#table-of-contents) 468 | 469 | TYPE_MX 470 | ------- 471 | 472 | The `MX` resource record type, equal to the decimal number `15`. 473 | 474 | [Back to TOC](#table-of-contents) 475 | 476 | TYPE_TXT 477 | -------- 478 | 479 | The `TXT` resource record type, equal to the decimal number `16`. 480 | 481 | [Back to TOC](#table-of-contents) 482 | 483 | TYPE_AAAA 484 | --------- 485 | `syntax: typ = s.TYPE_AAAA` 486 | 487 | The `AAAA` resource record type, equal to the decimal number `28`. 488 | 489 | [Back to TOC](#table-of-contents) 490 | 491 | TYPE_SRV 492 | --------- 493 | `syntax: typ = s.TYPE_SRV` 494 | 495 | The `SRV` resource record type, equal to the decimal number `33`. 496 | 497 | See RFC 2782 for details. 498 | 499 | [Back to TOC](#table-of-contents) 500 | 501 | TYPE_ANY 502 | --------- 503 | `syntax: typ = s.TYPE_ANY` 504 | 505 | The all resource record type, equal to the decimal number `255`. 506 | 507 | [Back to TOC](#table-of-contents) 508 | 509 | RCODE_FORMAT_ERROR 510 | ------------------ 511 | 512 | [Back to TOC](#table-of-contents) 513 | 514 | RCODE_NOT_IMPLEMENTED 515 | --------------------- 516 | 517 | [Back to TOC](#table-of-contents) 518 | 519 | TODO 520 | ==== 521 | 522 | [Back to TOC](#table-of-contents) 523 | 524 | Author 525 | ====== 526 | 527 | wenqiang li(vislee) 528 | 529 | guocan xu(selboo) 530 | 531 | [Back to TOC](#table-of-contents) 532 | 533 | 534 | Copyright and License 535 | ===================== 536 | 537 | This module is licensed under the BSD license. 538 | 539 | Copyright (C) 2018-2019, by vislee. 540 | 541 | All rights reserved. 542 | 543 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 544 | 545 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 546 | 547 | * 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. 548 | 549 | 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. 550 | 551 | [Back to TOC](#table-of-contents) 552 | 553 | See Also 554 | ======== 555 | * the stream-lua-nginx-module: https://github.com/openresty/stream-lua-nginx-module/#readme 556 | * the [lua-resty-dns](https://github.com/openresty/lua-resty-dns) library. 557 | * this [ngx_stream_ipdb_module](https://github.com/vislee/ngx_stream_ipdb_module) library can support region resolution. 558 | 559 | [Back to TOC](#table-of-contents) 560 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=lua-resty-dns-server 2 | abstract=Lua DNS server driver for the OpenResty 3 | author=vislee 4 | is_original=no 5 | license=2bsd 6 | lib_dir=lib 7 | repo_link=https://github.com/vislee/lua-resty-dns-server 8 | main_module=lib/resty/dns/server.lua -------------------------------------------------------------------------------- /lib/resty/dns/server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2018 liwq 2 | 3 | local bit = require "bit" 4 | local byte = string.byte 5 | local strsub = string.sub 6 | local strlen = string.len 7 | local char = string.char 8 | local gsub = string.gsub 9 | local sfmt = string.format 10 | local lshift = bit.lshift 11 | local rshift = bit.rshift 12 | local band = bit.band 13 | local concat = table.concat 14 | 15 | -- https://www.ietf.org/rfc/rfc2929.txt 16 | -- https://www.ietf.org/rfc/rfc1034.txt 17 | -- https://www.ietf.org/rfc/rfc1035.txt 18 | -- https://www.ietf.org/rfc/rfc2782.txt 19 | -- https://www.ietf.org/rfc/rfc3596.txt 20 | -- https://www.ietf.org/rfc/rfc2671.txt 21 | local TYPE_A = 1 22 | local TYPE_NS = 2 23 | local TYPE_CNAME = 5 24 | local TYPE_SOA = 6 25 | local TYPE_PTR = 12 26 | local TYPE_MX = 15 27 | local TYPE_TXT = 16 28 | local TYPE_AAAA = 28 29 | local TYPE_SRV = 33 30 | local TYPE_SPF = 99 31 | local TYPE_ANY = 255 32 | 33 | local CLASS_IN = 1 34 | 35 | local SECTION_UK = 0 36 | local SECTION_AN = 1 37 | local SECTION_NS = 2 38 | local SECTION_AR = 3 39 | 40 | local CNAME_SENTRY = 'CNAME_SENTRY' 41 | 42 | -- rfc2929: 2.3 RCODE Assignment 43 | local RCODE_OK = 0 44 | local RCODE_FORMAT_ERROR = 1 45 | local RCODE_SERVER_FAILURE = 2 46 | local RCODE_NAME_ERROR = 3 47 | local RCODE_NOT_IMPLEMENTED = 4 48 | local RCODE_REFUSED = 5 49 | local RCODE_NOTZONE = 10 50 | local RCODE_BADVERS = 16 51 | 52 | -- http://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml 53 | local ADDR_FAMILY_IP = 1 54 | local ADDR_FAMILY_IP6 = 2 55 | 56 | local _M = { 57 | _VERSION = '0.02', 58 | TYPE_A = TYPE_A, 59 | TYPE_NS = TYPE_NS, 60 | TYPE_CNAME = TYPE_CNAME, 61 | TYPE_SOA = TYPE_SOA, 62 | TYPE_PTR = TYPE_PTR, 63 | TYPE_MX = TYPE_MX, 64 | TYPE_TXT = TYPE_TXT, 65 | TYPE_AAAA = TYPE_AAAA, 66 | TYPE_SRV = TYPE_SRV, 67 | TYPE_SPF = TYPE_SPF, 68 | TYPE_ANY = TYPE_ANY, 69 | 70 | RCODE_FORMAT_ERROR = RCODE_FORMAT_ERROR, 71 | RCODE_SERVER_FAILURE = RCODE_SERVER_FAILURE, 72 | RCODE_NAME_ERROR = RCODE_NAME_ERROR, 73 | RCODE_NOT_IMPLEMENTED = RCODE_NOT_IMPLEMENTED, 74 | RCODE_REFUSED = RCODE_REFUSED, 75 | RCODE_NOTZONE = RCODE_NOTZONE, 76 | RCODE_BADVERS = RCODE_BADVERS, 77 | } 78 | 79 | local mt = { __index = _M } 80 | 81 | function _M.new(class) 82 | return setmetatable({ 83 | pos = 0, 84 | buf = "", 85 | request = {header = {}, questions = {}, additionals = {}, subnet = {}}, 86 | response = {header = { 87 | id = 0, 88 | qr = 1, 89 | opcode = 0, 90 | aa = 0, 91 | tc = 0, 92 | rd = 0, 93 | ra = 0, 94 | z = 0, 95 | rcode = 0, 96 | qdcount = 0, 97 | ancount = 0, 98 | nscount = 0, 99 | arcount = 0 100 | }, 101 | ansections = {}, 102 | nssections = {}, 103 | arsections = {} 104 | }, 105 | cnames = {count = 0, }, 106 | }, mt) 107 | end 108 | 109 | 110 | function _M.decode_request(self, req) 111 | self.buf = self.buf .. req 112 | 113 | -- parse id 114 | if self.pos + 2 > strlen(self.buf) then 115 | return nil, "request too short" 116 | end 117 | 118 | self.pos = self.pos + 2 -- pos=2 119 | local ident_hi, ident_lo = byte(self.buf, self.pos - 1, self.pos) 120 | self.request.header.id = lshift(ident_hi, 8) + ident_lo 121 | self.response.header.id = self.request.header.id 122 | ngx.log(ngx.DEBUG, "dns server request id: ", self.request.header.id) 123 | 124 | -- parse flags 125 | if self.pos + 2 > strlen(self.buf) then 126 | self.response.header.rcode = RCODE_FORMAT_ERROR 127 | return nil, "request too short" 128 | end 129 | 130 | self.pos = self.pos + 2 -- pos=4 131 | local flags_hi, flags_lo = byte(self.buf, self.pos - 1, self.pos) 132 | local flags = lshift(flags_hi, 8) + flags_lo 133 | ngx.log(ngx.DEBUG, "dns server request flags: ", flags) 134 | 135 | self.request.header.qr = rshift(band(flags, 0x8000), 15) 136 | self.request.header.opcode = rshift(band(flags, 0x7800), 11) 137 | self.request.header.aa = rshift(band(flags, 0x0400), 10) 138 | self.request.header.tc = rshift(band(flags, 0x0200), 9) 139 | self.request.header.rd = rshift(band(flags, 0x0100), 8) 140 | self.response.header.rd = self.request.header.rd 141 | self.request.header.ra = rshift(band(flags, 0x0080), 7) 142 | self.request.header.z = rshift(band(flags, 0x0070), 4) 143 | self.request.header.rcode = band(flags, 0x000F) 144 | 145 | if self.request.header.qr ~= 0 then 146 | self.response.header.rcode = RCODE_FORMAT_ERROR 147 | ngx.log(ngx.ERR, "bad QR flag in the DNS request") 148 | return nil, "bad QR flag in the DNS request" 149 | end 150 | 151 | -- parse qdcount 152 | if self.pos + 2 > strlen(self.buf) then 153 | self.response.header.rcode = RCODE_FORMAT_ERROR 154 | return nil, "request too short" 155 | end 156 | 157 | self.pos = self.pos + 2 -- pos=6 158 | local qdc_hi, qdc_lo = byte(self.buf, self.pos - 1, self.pos) 159 | self.request.header.qdcount = lshift(qdc_hi, 8) + qdc_lo 160 | ngx.log(ngx.DEBUG, "dns server request qdcount: ", self.request.header.qdcount) 161 | 162 | if self.request.header.qdcount ~= 1 then 163 | self.response.header.rcode = RCODE_FORMAT_ERROR 164 | ngx.log(ngx.ERR, "bad qdcount in the DNS request") 165 | return nil, "bad qdcount in the DNS request" 166 | end 167 | 168 | -- parse ancount 169 | if self.pos + 2 > strlen(self.buf) then 170 | self.response.header.rcode = RCODE_FORMAT_ERROR 171 | return nil, "request too short" 172 | end 173 | 174 | self.pos = self.pos + 2 -- pos=8 175 | local anc_hi, anc_lo = byte(self.buf, self.pos - 1, self.pos) 176 | self.request.header.ancount = lshift(anc_hi, 8) + anc_lo 177 | ngx.log(ngx.DEBUG, "dns server request ancount: ", self.request.header.ancount) 178 | 179 | -- parse nscount 180 | if self.pos + 2 > strlen(self.buf) then 181 | self.response.header.rcode = RCODE_FORMAT_ERROR 182 | return nil, "request too short" 183 | end 184 | 185 | self.pos = self.pos + 2 -- pos=10 186 | local nsc_hi, nsc_lo = byte(self.buf, self.pos - 1, self.pos) 187 | self.request.header.nscount = lshift(nsc_hi, 8) + nsc_lo 188 | 189 | -- parse arcount 190 | if self.pos + 2 > strlen(self.buf) then 191 | self.response.header.rcode = RCODE_FORMAT_ERROR 192 | return nil, "request too short" 193 | end 194 | self.pos = self.pos + 2 -- pos=12 195 | local arc_hi, arc_lo = byte(self.buf, self.pos - 1, self.pos) 196 | self.request.header.arcount = lshift(arc_hi, 8) + arc_lo 197 | ngx.log(ngx.DEBUG, "dns server request arcount: ", self.request.header.arcount) 198 | 199 | for i = 1, self.request.header.qdcount do 200 | -- parse qname 201 | if self.pos + 1 > strlen(self.buf) then 202 | self.response.header.rcode = RCODE_FORMAT_ERROR 203 | return nil, "request too short" 204 | end 205 | 206 | local qnames = {} 207 | local qname_len = byte(self.buf, self.pos + 1) 208 | while qname_len > 0 do 209 | if self.pos + 1 + qname_len > strlen(self.buf) then 210 | self.response.header.rcode = RCODE_FORMAT_ERROR 211 | return nil, "request too short" 212 | end 213 | self.pos = self.pos + 1 214 | qnames[#qnames + 1] = strsub(self.buf, self.pos + 1, self.pos + qname_len) 215 | self.pos = self.pos + qname_len 216 | qname_len = byte(self.buf, self.pos + 1) 217 | end 218 | self.pos = self.pos + 1 -- "\0" 219 | 220 | local qname = concat(qnames, '.') 221 | ngx.log(ngx.DEBUG, "dns server request qname: ", qname) 222 | 223 | -- parse qtype 224 | if self.pos + 2 + 2 > strlen(self.buf) then 225 | self.response.header.rcode = RCODE_FORMAT_ERROR 226 | return nil, "request too short" 227 | end 228 | 229 | self.pos = self.pos + 2 230 | local typ_hi, typ_lo = byte(self.buf, self.pos - 1, self.pos) 231 | local qtype = lshift(typ_hi, 8) + typ_lo 232 | 233 | -- parse qclass 234 | self.pos = self.pos + 2 235 | local class_hi, class_lo = byte(self.buf, self.pos - 1, self.pos) 236 | local qclass = lshift(class_hi, 8) + class_lo 237 | 238 | self.request.questions[i] = {qname = qname, qtype = qtype, qclass = qclass} 239 | self.response.header.qdcount = i 240 | end 241 | 242 | 243 | -- EDNS0(rfc1035,rfc2671,rfc7871) 244 | for i = 1, self.request.header.arcount do 245 | -- empty name 246 | self.pos = self.pos + 1 247 | local qname_len = byte(self.buf, self.pos) 248 | 249 | -- parse TYPE(OPT) 250 | self.pos = self.pos + 2 251 | local opt_type_hi, opt_type_lo = byte(self.buf, self.pos - 1, self.pos) 252 | local opt_type = lshift(opt_type_hi, 8) + opt_type_lo 253 | 254 | -- rfc6891, 6.1.2 255 | if opt_type == 41 and qname_len == 0 then 256 | 257 | -- parse CLASS(UDP payload size) 258 | self.pos = self.pos + 2 259 | local udp_size_hi, udp_size_lo = byte(self.buf, self.pos - 1, self.pos) 260 | local udp_size = lshift(udp_size_hi, 8) + udp_size_lo 261 | 262 | -- parse TTL(RCODE and flags) 263 | self.pos = self.pos + 2 264 | local ext_rcode_hi, ext_rcode_lo = byte(self.buf, self.pos - 1, self.pos) 265 | local ext_rcode = lshift(ext_rcode_hi, 8) + ext_rcode_lo 266 | 267 | self.pos = self.pos + 2 268 | local opt_ver_hi, opt_ver_lo = byte(self.buf, self.pos - 1, self.pos) 269 | local opt_ver = lshift(opt_ver_hi, 8) + opt_ver_lo 270 | if opt_ver ~= 0 then 271 | self.response.header.rcode = RCODE_BADVERS 272 | return nil, "bad EDNS0 opt version(" .. opt_ver .. ")" 273 | end 274 | 275 | -- parse RDLENGTH(describes RDATA) 276 | self.pos = self.pos + 2 277 | local rdlen_hi, rdlen_lo = byte(self.buf, self.pos - 1, self.pos) 278 | local rdlen = lshift(rdlen_hi, 8) + rdlen_lo 279 | 280 | -- parse RDATA(OPTION) 281 | -- rfc7871, 6. Option Format 282 | 283 | if rdlen > 0 then 284 | -- parse OPTION-CODE 285 | self.pos = self.pos + 2 286 | local opt_code_hi, opt_code_lo = byte(self.buf, self.pos - 1, self.pos) 287 | local opt_code = lshift(opt_code_hi, 8) + opt_code_lo 288 | 289 | -- parse OPTION-LENGTH 290 | self.pos = self.pos + 2 291 | local opt_len_hi, opt_len_lo = byte(self.buf, self.pos - 1, self.pos) 292 | local opt_len = lshift(opt_len_hi, 8) + opt_len_lo 293 | 294 | -- parse OPTION-DATA 295 | -- parse FAMILY 296 | self.pos = self.pos + 2 297 | local opt_family_hi, opt_family_lo = byte(self.buf, self.pos - 1, self.pos) 298 | local opt_family = lshift(opt_family_hi, 8) + opt_family_lo 299 | 300 | -- parse SOURCE PREFIX-LENGTH, SCOPE PREFIX-LENGTH 301 | self.pos = self.pos + 2 302 | local source_prefix_len, scope_prefix_len = byte(self.buf, self.pos - 1, self.pos) 303 | 304 | -- parse address ... 305 | -- opt_len include (2B opt_family, 1B source_prefix_len, 1B scope_prefix_len) 306 | local address 307 | local addr_len = opt_len - 4 308 | if opt_family == ADDR_FAMILY_IP then 309 | local ipv4 = {0, 0, 0, 0} 310 | for i = 1, addr_len do 311 | self.pos = self.pos + 1 312 | ipv4[i] = byte(self.buf, self.pos) 313 | end 314 | address = concat(ipv4, ".") 315 | 316 | elseif opt_family == ADDR_FAMILY_IP6 then 317 | local ipv6 = {0, 0, 0, 0, 0, 0, 0, 0} 318 | local idx = 1 319 | for i = 1, addr_len, 2 do 320 | self.pos = self.pos + 2 321 | local v6_item_hi, v6_item_lo = byte(self.buf, self.pos - 1, self.pos) 322 | local v6_item = lshift(v6_item_hi, 8) + v6_item_lo 323 | ipv6[idx] = sfmt("%04x", v6_item) 324 | idx = idx + 1 325 | end 326 | address = concat(ipv6, ":") 327 | end 328 | 329 | self.request.subnet[#self.request.subnet + 1] = {address = address, 330 | mask = source_prefix_len, 331 | family = opt_family} 332 | end 333 | else 334 | ngx.log(ngx.WARN, "parse EDNS0 error. qname_len: ", 335 | qname_len, " opt_type: ", opt_type) 336 | end 337 | end 338 | 339 | self.response.header.rcode = RCODE_OK 340 | 341 | return self.request 342 | end 343 | 344 | 345 | function _M.create_response_header(self, rcode) 346 | if rcode > RCODE_REFUSED then 347 | return nil, "rcode error" 348 | end 349 | 350 | self.response.header.id = self.request.header.id 351 | self.response.header.qr = 1 352 | self.response.header.opcode = 0 353 | self.response.header.aa = 0 354 | self.response.header.tc = 0 355 | self.response.header.rd = 1 356 | self.response.header.ra = 0 357 | self.response.header.z = 0 358 | self.response.header.rcode = rcode 359 | 360 | self.response.header.qdcount = self.request.header.qdcount 361 | self.response.header.ancount = 0 362 | self.response.header.nscount = 0 363 | self.response.header.arcount = 0 364 | 365 | return self.response.header 366 | end 367 | 368 | 369 | local function _encode_4byt(x) 370 | local hi_hi = band(rshift(x, 24), 0x00FF) 371 | local hi_lo = band(rshift(x, 16), 0x00FF) 372 | local lo_hi = band(rshift(x, 8), 0x00FF) 373 | local lo_lo = band(x, 0x00FF) 374 | 375 | return char(hi_hi, hi_lo, lo_hi, lo_lo) 376 | end 377 | 378 | 379 | local function _encode_2byt(x) 380 | local hi = band(rshift(x, 8), 0x00FF) 381 | local lo = band(x, 0x00FF) 382 | 383 | return char(hi, lo) 384 | end 385 | 386 | 387 | local function _encode_name(name) 388 | return gsub(name, "([^.]+)%.?", function(label) return char(#label) .. label end) .. '\0' 389 | end 390 | 391 | 392 | local function _encode_a(ipv4) 393 | return gsub(ipv4, "([^.]+)%.?", function(s) return char(tonumber(s)) end) 394 | end 395 | 396 | local function _encode_aaaa(ipv6) 397 | return gsub(ipv6, "([^:]+)%:?", function(s) return _encode_2byt(tonumber(string.format("0x%s", s)) or 0x00) end) 398 | end 399 | 400 | 401 | local function _encode_txt(txt) 402 | return char(#txt) .. txt 403 | end 404 | 405 | 406 | local function _encode_soa(mname, rname, serial, refresh, retry, expire, minimum) 407 | return mname .. rname .. _encode_4byt(serial) .. _encode_4byt(tonumber(refresh) or 900) .. 408 | _encode_4byt(tonumber(retry) or 900) .. _encode_4byt(tonumber(expire) or 1800) .. 409 | _encode_4byt(tonumber(minimum) or 60) 410 | end 411 | 412 | 413 | local function _encode_mx(preference, exchange) 414 | return '\0' .. char(tonumber(preference) or 10) .. exchange 415 | end 416 | 417 | 418 | local function _encode_srv(priority, weight, port, target) 419 | return _encode_2byt(priority) .. _encode_2byt(weight) .. _encode_2byt(port) .. target 420 | end 421 | 422 | 423 | function _M.encode_response(self) 424 | local buf = "" 425 | -- id 426 | buf = buf .. _encode_2byt(self.response.header.id) 427 | 428 | -- flags 429 | local flags_hi = lshift(self.response.header.qr, 7) + lshift(self.response.header.opcode, 3) + 430 | lshift(self.response.header.aa, 2) + lshift(self.response.header.tc, 1) + self.response.header.rd 431 | local flags_lo = lshift(self.response.header.ra, 7) + lshift(self.response.header.z, 4) + self.response.header.rcode 432 | buf = buf .. char(flags_hi, flags_lo) 433 | 434 | buf = buf .. _encode_2byt(self.response.header.qdcount) 435 | buf = buf .. _encode_2byt(self.response.header.ancount) 436 | buf = buf .. _encode_2byt(self.response.header.nscount) 437 | buf = buf .. _encode_2byt(self.response.header.arcount) 438 | 439 | for i = 1, self.response.header.qdcount do 440 | buf = buf .. _encode_name(self.request.questions[i].qname) 441 | buf = buf .. _encode_2byt(self.request.questions[i].qtype) 442 | buf = buf .. _encode_2byt(self.request.questions[i].qclass) 443 | end 444 | 445 | for i = 1, self.response.header.ancount do 446 | buf = buf .. _encode_name(self.response.ansections[i].name) 447 | buf = buf .. _encode_2byt(self.response.ansections[i].type) 448 | buf = buf .. _encode_2byt(self.response.ansections[i].class) 449 | buf = buf .. _encode_4byt(self.response.ansections[i].ttl or 0x258) 450 | buf = buf .. _encode_2byt(self.response.ansections[i].rdlength) 451 | buf = buf .. self.response.ansections[i].rdata 452 | end 453 | 454 | for i = 1, self.response.header.nscount do 455 | buf = buf .. _encode_name(self.response.nssections[i].name) 456 | buf = buf .. _encode_2byt(self.response.nssections[i].type) 457 | buf = buf .. _encode_2byt(self.response.nssections[i].class) 458 | buf = buf .. _encode_4byt(self.response.nssections[i].ttl or 0x258) 459 | buf = buf .. _encode_2byt(self.response.nssections[i].rdlength) 460 | buf = buf .. self.response.nssections[i].rdata 461 | end 462 | 463 | for i = 1, self.response.header.arcount do 464 | buf = buf .. _encode_name(self.response.arsections[i].name) 465 | buf = buf .. _encode_2byt(self.response.arsections[i].type) 466 | buf = buf .. _encode_2byt(self.response.arsections[i].class) 467 | buf = buf .. _encode_4byt(self.response.arsections[i].ttl or 0x258) 468 | buf = buf .. _encode_2byt(self.response.arsections[i].rdlength) 469 | buf = buf .. self.response.arsections[i].rdata 470 | end 471 | 472 | return buf 473 | end 474 | 475 | 476 | function _M.create_a_answer(self, name, ttl, ipv4) 477 | if not name or #name == 0 then 478 | return "name nil" 479 | end 480 | 481 | if not ipv4 or #ipv4 == 0 then 482 | return "ipv4 nil" 483 | end 484 | 485 | if not ngx.re.match(ipv4, '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$') then 486 | ngx.log(ngx.ERR, "ipv4 format error") 487 | return "ipv4 format error" 488 | end 489 | 490 | if self.cnames.count > 0 and self.cnames[name] and self.cnames[name] ~= CNAME_SENTRY then 491 | return "conflict cname" 492 | end 493 | 494 | local query = self.request.questions[1] 495 | local section = SECTION_UK 496 | if (query.qtype == TYPE_A or query.qtype == TYPE_ANY) and query.qname == name and self.cnames.count == 0 then 497 | section = SECTION_AN 498 | end 499 | 500 | if self.cnames.count > 0 and self.cnames[name] == CNAME_SENTRY then 501 | section = SECTION_AN 502 | end 503 | 504 | local answer = {} 505 | answer.name = name 506 | answer.type = TYPE_A 507 | answer.class = CLASS_IN 508 | answer.ttl = ttl 509 | answer.rdlength = 4 510 | answer.rdata = _encode_a(ipv4) 511 | 512 | if section == SECTION_AN then 513 | self.response.header.ancount = self.response.header.ancount + 1 514 | self.response.ansections[self.response.header.ancount] = answer 515 | else 516 | self.response.header.arcount = self.response.header.arcount + 1 517 | self.response.arsections[self.response.header.arcount] = answer 518 | end 519 | 520 | return nil 521 | end 522 | 523 | 524 | function _M.create_cname_answer(self, name, ttl, cname) 525 | if not name or #name == 0 then 526 | return "name nil" 527 | end 528 | 529 | if not cname or #cname == 0 then 530 | return "cname nil" 531 | end 532 | 533 | if self.cnames.count > 0 and not self.cnames[name] then 534 | return "cname linked error" 535 | end 536 | 537 | if self.cnames.count > 0 and self.cnames[name] ~= CNAME_SENTRY then 538 | return "cname conflict" 539 | end 540 | 541 | local answer = {} 542 | answer.name = name 543 | answer.type = TYPE_CNAME 544 | answer.class = CLASS_IN 545 | answer.ttl = ttl 546 | answer.rdlength = strlen(_encode_name(cname)) 547 | answer.rdata = _encode_name(cname) 548 | 549 | self.cnames[name] = cname 550 | self.cnames[cname] = CNAME_SENTRY 551 | self.cnames.count = self.cnames.count + 1 552 | 553 | self.response.header.ancount = self.response.header.ancount + 1 554 | self.response.ansections[self.response.header.ancount] = answer 555 | 556 | return nil 557 | end 558 | 559 | 560 | function _M.create_txt_answer(self, name, ttl, txt) 561 | if not name or #name == 0 then 562 | return "name nil" 563 | end 564 | 565 | if not txt or #txt == 0 then 566 | return "txt nil" 567 | end 568 | 569 | local answer = {} 570 | answer.name = name 571 | answer.type = TYPE_TXT 572 | answer.class = CLASS_IN 573 | answer.ttl = ttl 574 | answer.rdlength = strlen(txt) + 1 575 | answer.rdata = _encode_txt(txt) 576 | 577 | self.response.header.ancount = self.response.header.ancount + 1 578 | self.response.ansections[self.response.header.ancount] = answer 579 | 580 | return nil 581 | end 582 | 583 | 584 | function _M.create_ns_answer(self, name, ttl, nsdname) 585 | if not name or #name == 0 then 586 | return "name nil" 587 | end 588 | 589 | if not nsdname or #nsdname == 0 then 590 | return "nsdname nil" 591 | end 592 | 593 | local answer = {} 594 | answer.name = name 595 | answer.type = TYPE_NS 596 | answer.class = CLASS_IN 597 | answer.ttl = ttl 598 | answer.rdlength = strlen(_encode_name(nsdname)) 599 | answer.rdata = _encode_name(nsdname) 600 | 601 | local query = self.request.questions[1] 602 | if query.qname == name and (query.qtype == TYPE_NS or query.qtype == TYPE_ANY) then 603 | self.response.header.ancount = self.response.header.ancount + 1 604 | self.response.ansections[self.response.header.ancount] = answer 605 | else 606 | self.response.header.nscount = self.response.header.nscount + 1 607 | self.response.nssections[self.response.header.nscount] = answer 608 | end 609 | 610 | return nil 611 | end 612 | 613 | 614 | function _M.create_aaaa_answer(self, name, ttl, ipv6) 615 | if not name or #name == 0 then 616 | return "name nil" 617 | end 618 | 619 | if not ipv6 or #ipv6 == 0 then 620 | return "ipv6 nil" 621 | end 622 | 623 | if not ngx.re.match(ipv6, "^[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+$") then 624 | ngx.log(ngx.ERR, "not match ipv6") 625 | return "ipv6 format error" 626 | end 627 | 628 | if self.cnames.count > 0 and self.cnames[name] and self.cnames[name] ~= CNAME_SENTRY then 629 | return "conflict cname" 630 | end 631 | 632 | local query = self.request.questions[1] 633 | local section = SECTION_UK 634 | if (query.qtype == TYPE_AAAA or query.qtype == TYPE_ANY) and query.qname == name and self.cnames.count == 0 then 635 | section = SECTION_AN 636 | end 637 | 638 | if self.cnames.count > 0 and self.cnames[name] == CNAME_SENTRY then 639 | section = SECTION_AN 640 | end 641 | 642 | local answer = {} 643 | answer.name = name 644 | answer.type = TYPE_AAAA 645 | answer.class = CLASS_IN 646 | answer.ttl = ttl 647 | answer.rdlength = 16 648 | answer.rdata = _encode_aaaa(ipv6) 649 | 650 | 651 | if section == SECTION_AN then 652 | self.response.header.ancount = self.response.header.ancount + 1 653 | self.response.ansections[self.response.header.ancount] = answer 654 | else 655 | self.response.header.arcount = self.response.header.arcount + 1 656 | self.response.arsections[self.response.header.arcount] = answer 657 | end 658 | 659 | return nil 660 | end 661 | 662 | 663 | function _M.create_soa_answer(self, name, ttl, mname, rname, serial, refresh, retry, expire, minimum) 664 | if not name or #name == 0 then 665 | return "name nil" 666 | end 667 | 668 | if not mname or #mname == 0 then 669 | return "mname nil" 670 | end 671 | 672 | if not rname or #rname == 0 then 673 | return "rname nil" 674 | end 675 | 676 | if not tonumber(serial) then 677 | return "serial is not number" 678 | end 679 | 680 | local query = self.request.questions[1] 681 | 682 | local answer = {} 683 | answer.name = name 684 | answer.type = TYPE_SOA 685 | answer.class = CLASS_IN 686 | answer.ttl = ttl 687 | answer.rdlength = strlen(_encode_name(mname)) + strlen(_encode_name(rname)) + 5 * 4 688 | answer.rdata = _encode_soa(_encode_name(mname), _encode_name(rname), serial, refresh , retry, expire, minimum) 689 | 690 | if query.qtype == TYPE_SOA and query.qname == name then 691 | self.response.header.ancount = self.response.header.ancount + 1 692 | self.response.ansections[self.response.header.ancount] = answer 693 | else 694 | self.response.header.nscount = self.response.header.nscount + 1 695 | self.response.nssections[self.response.header.nscount] = answer 696 | end 697 | 698 | return nil 699 | end 700 | 701 | 702 | function _M.create_mx_answer(self, name, ttl, preference, exchange) 703 | if not name or #name == 0 then 704 | return "name nil" 705 | end 706 | 707 | if not exchange == nil or #exchange == 0 then 708 | return "exchange nil" 709 | end 710 | 711 | local answer = {} 712 | answer.name = name 713 | answer.type = TYPE_MX 714 | answer.class = CLASS_IN 715 | answer.ttl = ttl 716 | answer.rdlength = strlen(_encode_name(exchange)) + 2 717 | answer.rdata = _encode_mx(preference, _encode_name(exchange)) 718 | 719 | self.response.header.ancount = self.response.header.ancount + 1 720 | self.response.ansections[self.response.header.ancount] = answer 721 | 722 | return nil 723 | end 724 | 725 | 726 | function _M.create_srv_answer(self, name, ttl, priority, weight, port, target) 727 | if not name or #name == 0 then 728 | return "name nil" 729 | end 730 | 731 | if not target or #target == 0 then 732 | return "target nil" 733 | end 734 | 735 | if not tonumber(port) or tonumber(port) > 65536 or tonumber(port) < 0 then 736 | return "port error" 737 | end 738 | 739 | local answer = {} 740 | 741 | answer.name = name 742 | answer.type = TYPE_SRV 743 | answer.class = CLASS_IN 744 | answer.ttl = ttl 745 | answer.rdlength = 3 * 2 + strlen(_encode_name(target)) 746 | answer.rdata = _encode_srv(priority, weight, port, _encode_name(target)) 747 | 748 | self.response.header.ancount = self.response.header.ancount + 1 749 | self.response.ansections[self.response.header.ancount] = answer 750 | 751 | return nil 752 | end 753 | 754 | return _M 755 | -------------------------------------------------------------------------------- /t/server.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use Test::Nginx::Socket::Lua::Stream; 4 | 5 | repeat_each(1); 6 | 7 | log_level('warn'); 8 | 9 | plan tests => repeat_each() * (blocks() * 3); 10 | 11 | no_long_string(); 12 | #no_diff(); 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | 18 | === TEST 1: test CNAME records 19 | --- stream_server_config 20 | content_by_lua_block { 21 | local resolver = require "resty.dns.resolver" 22 | local r, err = resolver:new{ 23 | nameservers = {{"127.0.0.1", 1986} } 24 | } 25 | if not r then 26 | ngx.say("failed to instantiate resolver: ", err) 27 | return 28 | end 29 | 30 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_CNAME }) 31 | if not answers then 32 | ngx.say("failed to query: ", err) 33 | return 34 | end 35 | 36 | if answers[1].name ~= "www.test.com" or answers[1].cname ~= "sinacloud.com" or answers[1].ttl ~= 600 or answers[1].type ~= r.TYPE_CNAME then 37 | ngx.say("error") 38 | else 39 | ngx.say("ok") 40 | end 41 | } 42 | 43 | --- stream_server_config2 44 | content_by_lua_block { 45 | local bit = require 'bit' 46 | local lshift = bit.lshift 47 | local rshift = bit.rshift 48 | local band = bit.band 49 | local byte = string.byte 50 | local char = string.char 51 | local server = require 'resty.dns.server' 52 | 53 | local sock, err = ngx.req.socket() 54 | if not sock then 55 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 56 | return ngx.exit(ngx.ERROR) 57 | end 58 | 59 | sock:settimeout(1000) 60 | local buf, err = sock:receive(2) 61 | if not buf then 62 | ngx.say(string.format("sock receive error: %s", err)) 63 | return 64 | end 65 | 66 | local len_hi = byte(buf, 1) 67 | local len_lo = byte(buf, 2) 68 | local len = lshift(len_hi, 8) + len_lo 69 | local data, err = sock:receive(len) 70 | if not data then 71 | ngx.log(ngx.ERR, "failed to receive: ", err) 72 | return ngx.exit(ngx.ERROR) 73 | end 74 | 75 | local dns = server:new() 76 | local request, err = dns:decode_request(data) 77 | if not request then 78 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 79 | return 80 | end 81 | 82 | local query = request.questions[1] 83 | if query.qname == "www.test.com" and query.qtype == server.TYPE_CNAME then 84 | dns:create_cname_answer(query.qname, 600, "sinacloud.com") 85 | else 86 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 87 | end 88 | 89 | local resp = dns:encode_response() 90 | local len = #resp 91 | local len_hi = char(rshift(len, 8)) 92 | local len_lo = char(band(len, 0xff)) 93 | 94 | local ok, err = sock:send({len_hi, len_lo, resp}) 95 | if not ok then 96 | ngx.log(ngx.ERR, "failed to send: ", err) 97 | return 98 | end 99 | return 100 | } 101 | --- stream_response_like 102 | ok 103 | --- error_log 104 | stream lua tcp socket read timed out 105 | 106 | 107 | === TEST 2: test A records 108 | --- stream_server_config 109 | content_by_lua_block { 110 | local resolver = require "resty.dns.resolver" 111 | local r, err = resolver:new{ 112 | nameservers = {{"127.0.0.1", 1986} } 113 | } 114 | if not r then 115 | ngx.say("failed to instantiate resolver: ", err) 116 | return 117 | end 118 | 119 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_A }) 120 | if not answers then 121 | ngx.say("failed to query: ", err) 122 | return 123 | end 124 | 125 | if answers[1].name ~= "www.test.com" or answers[1].address ~= "127.0.0.1" or answers[1].ttl ~= 300 or answers[1].type ~= r.TYPE_A then 126 | ngx.say("error") 127 | else 128 | ngx.say("ok") 129 | end 130 | } 131 | 132 | --- stream_server_config2 133 | content_by_lua_block { 134 | local bit = require 'bit' 135 | local lshift = bit.lshift 136 | local rshift = bit.rshift 137 | local band = bit.band 138 | local byte = string.byte 139 | local char = string.char 140 | local server = require 'resty.dns.server' 141 | 142 | local sock, err = ngx.req.socket() 143 | if not sock then 144 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 145 | return ngx.exit(ngx.ERROR) 146 | end 147 | 148 | sock:settimeout(1000) 149 | local buf, err = sock:receive(2) 150 | if not buf then 151 | ngx.say(string.format("sock receive error: %s", err)) 152 | return 153 | end 154 | 155 | local len_hi = byte(buf, 1) 156 | local len_lo = byte(buf, 2) 157 | local len = lshift(len_hi, 8) + len_lo 158 | local data, err = sock:receive(len) 159 | if not data then 160 | ngx.log(ngx.ERR, "failed to receive: ", err) 161 | return ngx.exit(ngx.ERROR) 162 | end 163 | 164 | local dns = server:new() 165 | local request, err = dns:decode_request(data) 166 | if not request then 167 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 168 | return 169 | end 170 | 171 | local query = request.questions[1] 172 | if query.qname == "www.test.com" and query.qtype == server.TYPE_A then 173 | dns:create_a_answer(query.qname, 300, "127.0.0.1") 174 | else 175 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 176 | end 177 | 178 | local resp = dns:encode_response() 179 | local len = #resp 180 | local len_hi = char(rshift(len, 8)) 181 | local len_lo = char(band(len, 0xff)) 182 | 183 | local ok, err = sock:send({len_hi, len_lo, resp}) 184 | if not ok then 185 | ngx.log(ngx.ERR, "failed to send: ", err) 186 | return 187 | end 188 | return 189 | } 190 | --- stream_response_like 191 | ok 192 | 193 | --- error_log 194 | stream lua tcp socket read timed out 195 | 196 | 197 | === TEST 3: test AAAA records 198 | --- stream_server_config 199 | content_by_lua_block { 200 | local resolver = require "resty.dns.resolver" 201 | local r, err = resolver:new{ 202 | nameservers = {{"127.0.0.1", 1986} } 203 | } 204 | if not r then 205 | ngx.say("failed to instantiate resolver: ", err) 206 | return 207 | end 208 | 209 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_AAAA }) 210 | if not answers then 211 | ngx.say("failed to query: ", err) 212 | return 213 | end 214 | 215 | if answers[1].name ~= "www.test.com" or answers[1].address ~= "1001:1002:1003:1004:1005:1006:1007:1008" or 216 | answers[1].ttl ~= 300 or answers[1].type ~= r.TYPE_AAAA then 217 | ngx.say("error") 218 | else 219 | ngx.say("ok") 220 | end 221 | } 222 | 223 | --- stream_server_config2 224 | content_by_lua_block { 225 | local bit = require 'bit' 226 | local lshift = bit.lshift 227 | local rshift = bit.rshift 228 | local band = bit.band 229 | local byte = string.byte 230 | local char = string.char 231 | local server = require 'resty.dns.server' 232 | 233 | local sock, err = ngx.req.socket() 234 | if not sock then 235 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 236 | return ngx.exit(ngx.ERROR) 237 | end 238 | 239 | sock:settimeout(1000) 240 | local buf, err = sock:receive(2) 241 | if not buf then 242 | ngx.say(string.format("sock receive error: %s", err)) 243 | return 244 | end 245 | 246 | local len_hi = byte(buf, 1) 247 | local len_lo = byte(buf, 2) 248 | local len = lshift(len_hi, 8) + len_lo 249 | local data, err = sock:receive(len) 250 | if not data then 251 | ngx.log(ngx.ERR, "failed to receive: ", err) 252 | return ngx.exit(ngx.ERROR) 253 | end 254 | 255 | local dns = server:new() 256 | local request, err = dns:decode_request(data) 257 | if not request then 258 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 259 | return 260 | end 261 | 262 | local query = request.questions[1] 263 | if query.qname == "www.test.com" and query.qtype == server.TYPE_AAAA then 264 | dns:create_aaaa_answer(query.qname, 300, "1001:1002:1003:1004:1005:1006:1007:1008") 265 | else 266 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 267 | end 268 | 269 | local resp = dns:encode_response() 270 | local len = #resp 271 | local len_hi = char(rshift(len, 8)) 272 | local len_lo = char(band(len, 0xff)) 273 | 274 | local ok, err = sock:send({len_hi, len_lo, resp}) 275 | if not ok then 276 | ngx.log(ngx.ERR, "failed to send: ", err) 277 | return 278 | end 279 | return 280 | } 281 | --- stream_response_like 282 | ok 283 | 284 | --- error_log 285 | stream lua tcp socket read timed out 286 | 287 | 288 | === TEST 4: test TXT records 289 | --- stream_server_config 290 | content_by_lua_block { 291 | local resolver = require "resty.dns.resolver" 292 | local r, err = resolver:new{ 293 | nameservers = {{"127.0.0.1", 1986} } 294 | } 295 | if not r then 296 | ngx.say("failed to instantiate resolver: ", err) 297 | return 298 | end 299 | 300 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_TXT }) 301 | if not answers then 302 | ngx.say("failed to query: ", err) 303 | return 304 | end 305 | 306 | if answers[1].name ~= "www.test.com" or answers[1].txt ~= "v=spf1 include:_spf.test.com ~all" or 307 | answers[1].ttl ~= 3000 or answers[1].type ~= r.TYPE_TXT then 308 | ngx.say("error") 309 | else 310 | ngx.say("ok") 311 | end 312 | } 313 | 314 | --- stream_server_config2 315 | content_by_lua_block { 316 | local bit = require 'bit' 317 | local lshift = bit.lshift 318 | local rshift = bit.rshift 319 | local band = bit.band 320 | local byte = string.byte 321 | local char = string.char 322 | local server = require 'resty.dns.server' 323 | 324 | local sock, err = ngx.req.socket() 325 | if not sock then 326 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 327 | return ngx.exit(ngx.ERROR) 328 | end 329 | 330 | sock:settimeout(1000) 331 | local buf, err = sock:receive(2) 332 | if not buf then 333 | ngx.say(string.format("sock receive error: %s", err)) 334 | return 335 | end 336 | 337 | local len_hi = byte(buf, 1) 338 | local len_lo = byte(buf, 2) 339 | local len = lshift(len_hi, 8) + len_lo 340 | local data, err = sock:receive(len) 341 | if not data then 342 | ngx.log(ngx.ERR, "failed to receive: ", err) 343 | return ngx.exit(ngx.ERROR) 344 | end 345 | 346 | local dns = server:new() 347 | local request, err = dns:decode_request(data) 348 | if not request then 349 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 350 | return 351 | end 352 | 353 | local query = request.questions[1] 354 | if query.qname == "www.test.com" and query.qtype == server.TYPE_TXT then 355 | dns:create_txt_answer(query.qname, 3000, "v=spf1 include:_spf.test.com ~all") 356 | else 357 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 358 | end 359 | 360 | local resp = dns:encode_response() 361 | local len = #resp 362 | local len_hi = char(rshift(len, 8)) 363 | local len_lo = char(band(len, 0xff)) 364 | 365 | local ok, err = sock:send({len_hi, len_lo, resp}) 366 | if not ok then 367 | ngx.log(ngx.ERR, "failed to send: ", err) 368 | return 369 | end 370 | return 371 | } 372 | --- stream_response_like 373 | ok 374 | 375 | --- error_log 376 | stream lua tcp socket read timed out 377 | 378 | === TEST 5: test NS records 379 | --- stream_server_config 380 | content_by_lua_block { 381 | local resolver = require "resty.dns.resolver" 382 | local r, err = resolver:new{ 383 | nameservers = {{"127.0.0.1", 1986} } 384 | } 385 | if not r then 386 | ngx.say("failed to instantiate resolver: ", err) 387 | return 388 | end 389 | 390 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_NS }) 391 | if not answers or #answers ~=2 then 392 | ngx.say("failed to query: ", err) 393 | return 394 | end 395 | 396 | if answers[1].name ~= "www.test.com" or answers[1].ttl ~= 3000 or answers[1].type ~= r.TYPE_NS or 397 | (answers[1].nsdname ~= "ns1.test.com" and answers[1].nsdname ~= "ns2.test.com") then 398 | ngx.say("error") 399 | return 400 | else 401 | ngx.say("ok") 402 | end 403 | } 404 | 405 | --- stream_server_config2 406 | content_by_lua_block { 407 | local bit = require 'bit' 408 | local lshift = bit.lshift 409 | local rshift = bit.rshift 410 | local band = bit.band 411 | local byte = string.byte 412 | local char = string.char 413 | local server = require 'resty.dns.server' 414 | 415 | local sock, err = ngx.req.socket() 416 | if not sock then 417 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 418 | return ngx.exit(ngx.ERROR) 419 | end 420 | 421 | sock:settimeout(1000) 422 | local buf, err = sock:receive(2) 423 | if not buf then 424 | ngx.say(string.format("sock receive error: %s", err)) 425 | return 426 | end 427 | 428 | local len_hi = byte(buf, 1) 429 | local len_lo = byte(buf, 2) 430 | local len = lshift(len_hi, 8) + len_lo 431 | local data, err = sock:receive(len) 432 | if not data then 433 | ngx.log(ngx.ERR, "failed to receive: ", err) 434 | return ngx.exit(ngx.ERROR) 435 | end 436 | 437 | local dns = server:new() 438 | local request, err = dns:decode_request(data) 439 | if not request then 440 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 441 | return 442 | end 443 | 444 | local query = request.questions[1] 445 | if query.qname == "www.test.com" and query.qtype == server.TYPE_NS then 446 | dns:create_ns_answer(query.qname, 3000, "ns1.test.com") 447 | dns:create_ns_answer(query.qname, 3000, "ns2.test.com") 448 | else 449 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 450 | end 451 | 452 | local resp = dns:encode_response() 453 | local len = #resp 454 | local len_hi = char(rshift(len, 8)) 455 | local len_lo = char(band(len, 0xff)) 456 | 457 | local ok, err = sock:send({len_hi, len_lo, resp}) 458 | if not ok then 459 | ngx.log(ngx.ERR, "failed to send: ", err) 460 | return 461 | end 462 | return 463 | } 464 | --- stream_response_like 465 | ok 466 | 467 | --- error_log 468 | stream lua tcp socket read timed out 469 | 470 | 471 | === TEST 6: test SOA records 472 | --- stream_server_config 473 | content_by_lua_block { 474 | local resolver = require "resty.dns.resolver" 475 | local r, err = resolver:new{ 476 | nameservers = {{"127.0.0.1", 1986} } 477 | } 478 | if not r then 479 | ngx.say("failed to instantiate resolver: ", err) 480 | return 481 | end 482 | 483 | local answers, err = r:tcp_query("test.com", { qtype = r.TYPE_SOA }) 484 | if not answers then 485 | ngx.say("failed to query: ", err) 486 | return 487 | end 488 | 489 | if answers[1].name ~= "test.com" or answers[1].ttl ~= 60 or answers[1].type ~= r.TYPE_SOA or 490 | answers[1].mname ~= "ns1.test.com" or answers[1].rname ~= "dns-admin.test.com" or 491 | answers[1].serial ~= 181394707 or answers[1].refresh ~= 900 or answers[1].retry ~= 900 or 492 | answers[1].expire ~= 2400 or (answers[1].mininum and answers[1].mininum ~= 61) or (answers[1].minimum and answers[1].minimum ~= 61) then 493 | ngx.say("error") 494 | return 495 | else 496 | ngx.say("ok") 497 | end 498 | } 499 | 500 | --- stream_server_config2 501 | content_by_lua_block { 502 | local bit = require 'bit' 503 | local lshift = bit.lshift 504 | local rshift = bit.rshift 505 | local band = bit.band 506 | local byte = string.byte 507 | local char = string.char 508 | local server = require 'resty.dns.server' 509 | 510 | local sock, err = ngx.req.socket() 511 | if not sock then 512 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 513 | return ngx.exit(ngx.ERROR) 514 | end 515 | 516 | sock:settimeout(1000) 517 | local buf, err = sock:receive(2) 518 | if not buf then 519 | ngx.say(string.format("sock receive error: %s", err)) 520 | return 521 | end 522 | 523 | local len_hi = byte(buf, 1) 524 | local len_lo = byte(buf, 2) 525 | local len = lshift(len_hi, 8) + len_lo 526 | local data, err = sock:receive(len) 527 | if not data then 528 | ngx.log(ngx.ERR, "failed to receive: ", err) 529 | return ngx.exit(ngx.ERROR) 530 | end 531 | 532 | local dns = server:new() 533 | local request, err = dns:decode_request(data) 534 | if not request then 535 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 536 | return 537 | end 538 | 539 | local query = request.questions[1] 540 | if query.qname == "test.com" and query.qtype == server.TYPE_SOA then 541 | dns:create_soa_answer(query.qname, 60, "ns1.test.com", "dns-admin.test.com", 181394707, 900, 900, 2400, 61) 542 | else 543 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 544 | end 545 | 546 | local resp = dns:encode_response() 547 | local len = #resp 548 | local len_hi = char(rshift(len, 8)) 549 | local len_lo = char(band(len, 0xff)) 550 | 551 | local ok, err = sock:send({len_hi, len_lo, resp}) 552 | if not ok then 553 | ngx.log(ngx.ERR, "failed to send: ", err) 554 | return 555 | end 556 | return 557 | } 558 | --- stream_response_like 559 | ok 560 | 561 | --- error_log 562 | stream lua tcp socket read timed out 563 | 564 | === TEST 7: test MX records 565 | --- stream_server_config 566 | content_by_lua_block { 567 | local resolver = require "resty.dns.resolver" 568 | local r, err = resolver:new{ 569 | nameservers = {{"127.0.0.1", 1986} } 570 | } 571 | if not r then 572 | ngx.say("failed to instantiate resolver: ", err) 573 | return 574 | end 575 | 576 | local answers, err = r:tcp_query("test.com", { qtype = r.TYPE_MX }) 577 | if not answers then 578 | ngx.say("failed to query: ", err) 579 | return 580 | end 581 | 582 | if answers[1].name ~= "test.com" or answers[1].ttl ~= 60 or answers[1].type ~= r.TYPE_MX or 583 | answers[1].preference ~= 10 or answers[1].exchange ~= "aspmx.l.test.com" then 584 | ngx.say("error") 585 | return 586 | else 587 | ngx.say("ok") 588 | end 589 | } 590 | 591 | --- stream_server_config2 592 | content_by_lua_block { 593 | local bit = require 'bit' 594 | local lshift = bit.lshift 595 | local rshift = bit.rshift 596 | local band = bit.band 597 | local byte = string.byte 598 | local char = string.char 599 | local server = require 'resty.dns.server' 600 | 601 | local sock, err = ngx.req.socket() 602 | if not sock then 603 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 604 | return ngx.exit(ngx.ERROR) 605 | end 606 | 607 | sock:settimeout(1000) 608 | local buf, err = sock:receive(2) 609 | if not buf then 610 | ngx.say(string.format("sock receive error: %s", err)) 611 | return 612 | end 613 | 614 | local len_hi = byte(buf, 1) 615 | local len_lo = byte(buf, 2) 616 | local len = lshift(len_hi, 8) + len_lo 617 | local data, err = sock:receive(len) 618 | if not data then 619 | ngx.log(ngx.ERR, "failed to receive: ", err) 620 | return ngx.exit(ngx.ERROR) 621 | end 622 | 623 | local dns = server:new() 624 | local request, err = dns:decode_request(data) 625 | if not request then 626 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 627 | return 628 | end 629 | 630 | local query = request.questions[1] 631 | if query.qname == "test.com" and query.qtype == server.TYPE_MX then 632 | dns:create_mx_answer(query.qname, 60, 10, "aspmx.l.test.com") 633 | else 634 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 635 | end 636 | 637 | local resp = dns:encode_response() 638 | local len = #resp 639 | local len_hi = char(rshift(len, 8)) 640 | local len_lo = char(band(len, 0xff)) 641 | 642 | local ok, err = sock:send({len_hi, len_lo, resp}) 643 | if not ok then 644 | ngx.log(ngx.ERR, "failed to send: ", err) 645 | return 646 | end 647 | return 648 | } 649 | --- stream_response_like 650 | ok 651 | 652 | --- error_log 653 | stream lua tcp socket read timed out 654 | 655 | === TEST 8: test SRV records 656 | --- stream_server_config 657 | content_by_lua_block { 658 | local resolver = require "resty.dns.resolver" 659 | local r, err = resolver:new{ 660 | nameservers = {{"127.0.0.1", 1986} } 661 | } 662 | if not r then 663 | ngx.say("failed to instantiate resolver: ", err) 664 | return 665 | end 666 | 667 | local answers, err = r:tcp_query("_xmpp-server._tcp.test.com", { qtype = r.TYPE_SRV }) 668 | if not answers then 669 | ngx.say("failed to query: ", err) 670 | return 671 | end 672 | 673 | if answers[1].name ~= "_xmpp-server._tcp.test.com" or answers[1].ttl ~= 60 or answers[1].type ~= r.TYPE_SRV or 674 | answers[1].priority ~= 10 or answers[1].weight ~= 1 or answers[1].port ~= 4343 or answers[1].target ~= "xmpp-server.l.test.com" then 675 | ngx.say("error") 676 | return 677 | else 678 | ngx.say("ok") 679 | end 680 | } 681 | 682 | --- stream_server_config2 683 | content_by_lua_block { 684 | local bit = require 'bit' 685 | local lshift = bit.lshift 686 | local rshift = bit.rshift 687 | local band = bit.band 688 | local byte = string.byte 689 | local char = string.char 690 | local server = require 'resty.dns.server' 691 | 692 | local sock, err = ngx.req.socket() 693 | if not sock then 694 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 695 | return ngx.exit(ngx.ERROR) 696 | end 697 | 698 | sock:settimeout(1000) 699 | local buf, err = sock:receive(2) 700 | if not buf then 701 | ngx.say(string.format("sock receive error: %s", err)) 702 | return 703 | end 704 | 705 | local len_hi = byte(buf, 1) 706 | local len_lo = byte(buf, 2) 707 | local len = lshift(len_hi, 8) + len_lo 708 | local data, err = sock:receive(len) 709 | if not data then 710 | ngx.log(ngx.ERR, "failed to receive: ", err) 711 | return ngx.exit(ngx.ERROR) 712 | end 713 | 714 | local dns = server:new() 715 | local request, err = dns:decode_request(data) 716 | if not request then 717 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 718 | return 719 | end 720 | 721 | local query = request.questions[1] 722 | if query.qname == "_xmpp-server._tcp.test.com" and query.qtype == server.TYPE_SRV then 723 | dns:create_srv_answer(query.qname, 60, 10, 1, 4343, "xmpp-server.l.test.com") 724 | else 725 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 726 | end 727 | 728 | local resp = dns:encode_response() 729 | local len = #resp 730 | local len_hi = char(rshift(len, 8)) 731 | local len_lo = char(band(len, 0xff)) 732 | 733 | local ok, err = sock:send({len_hi, len_lo, resp}) 734 | if not ok then 735 | ngx.log(ngx.ERR, "failed to send: ", err) 736 | return 737 | end 738 | return 739 | } 740 | --- stream_response_like 741 | ok 742 | 743 | --- error_log 744 | stream lua tcp socket read timed out 745 | 746 | === TEST 9: test NS and AR section 747 | --- stream_server_config 748 | content_by_lua_block { 749 | local resolver = require "resty.dns.resolver" 750 | local r, err = resolver:new{ 751 | nameservers = {{"127.0.0.1", 1986} } 752 | } 753 | if not r then 754 | ngx.say("failed to instantiate resolver: ", err) 755 | return 756 | end 757 | 758 | local answers, err = r:tcp_query("www.test.com", { qtype = r.TYPE_A, authority_section=true, additional_section=true }) 759 | if not answers or #answers ~= 3 then 760 | ngx.say("failed to query: ", err) 761 | return 762 | end 763 | 764 | for _, ans in ipairs(answers) do 765 | if ans.section == r.SECTION_AN then 766 | if ans.name ~= "www.test.com" or ans.ttl ~= 60 or ans.address ~= "11.11.11.11" then 767 | ngx.say("error") 768 | return 769 | end 770 | elseif ans.section == r.SECTION_NS then 771 | if ans.name ~= "test.com" or ans.ttl ~= 600 or ans.nsdname ~= "ns1.test.com" then 772 | ngx.say("error") 773 | return 774 | end 775 | elseif ans.section == r.SECTION_AR then 776 | if ans.name ~= "ns1.test.com" or ans.ttl ~= 300 or ans.address ~= "22.22.22.22" then 777 | ngx.say("error") 778 | return 779 | end 780 | else 781 | ngx.say("error") 782 | end 783 | end 784 | ngx.say("ok") 785 | } 786 | 787 | --- stream_server_config2 788 | content_by_lua_block { 789 | local bit = require 'bit' 790 | local lshift = bit.lshift 791 | local rshift = bit.rshift 792 | local band = bit.band 793 | local byte = string.byte 794 | local char = string.char 795 | local server = require 'resty.dns.server' 796 | 797 | local sock, err = ngx.req.socket() 798 | if not sock then 799 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 800 | return ngx.exit(ngx.ERROR) 801 | end 802 | 803 | sock:settimeout(1000) 804 | local buf, err = sock:receive(2) 805 | if not buf then 806 | ngx.say(string.format("sock receive error: %s", err)) 807 | return 808 | end 809 | 810 | local len_hi = byte(buf, 1) 811 | local len_lo = byte(buf, 2) 812 | local len = lshift(len_hi, 8) + len_lo 813 | local data, err = sock:receive(len) 814 | if not data then 815 | ngx.log(ngx.ERR, "failed to receive: ", err) 816 | return ngx.exit(ngx.ERROR) 817 | end 818 | 819 | local dns = server:new() 820 | local request, err = dns:decode_request(data) 821 | if not request then 822 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 823 | return 824 | end 825 | 826 | local query = request.questions[1] 827 | if query.qname == "www.test.com" and query.qtype == server.TYPE_A then 828 | dns:create_a_answer(query.qname, 60, "11.11.11.11") 829 | dns:create_ns_answer("test.com", 600, "ns1.test.com") 830 | dns:create_a_answer("ns1.test.com", 300, "22.22.22.22") 831 | else 832 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 833 | end 834 | 835 | local resp = dns:encode_response() 836 | local len = #resp 837 | local len_hi = char(rshift(len, 8)) 838 | local len_lo = char(band(len, 0xff)) 839 | 840 | local ok, err = sock:send({len_hi, len_lo, resp}) 841 | if not ok then 842 | ngx.log(ngx.ERR, "failed to send: ", err) 843 | return 844 | end 845 | return 846 | } 847 | --- stream_response_like 848 | ok 849 | 850 | --- error_log 851 | stream lua tcp socket read timed out 852 | 853 | 854 | === TEST 10: test all section 855 | --- stream_server_config 856 | content_by_lua_block { 857 | local resolver = require "resty.dns.resolver" 858 | local r, err = resolver:new{ 859 | nameservers = {{"127.0.0.1", 1986} } 860 | } 861 | if not r then 862 | ngx.say("failed to instantiate resolver: ", err) 863 | return 864 | end 865 | 866 | local answers, err = r:tcp_query("test.com", { qtype = 255}) 867 | if not answers or #answers ~= 5 then 868 | ngx.say("failed to query: ", err) 869 | return 870 | end 871 | 872 | for _, ans in ipairs(answers) do 873 | if ans.section ~= r.SECTION_AN then 874 | ngx.say("error") 875 | return 876 | end 877 | 878 | if ans.type == r.TYPE_TXT then 879 | if ans.name ~= "test.com" or ans.txt ~= "v=spf1 include:_spf.test.com ~all" or ans.ttl ~= 3000 then 880 | ngx.say("error") 881 | return 882 | end 883 | elseif ans.type == r.TYPE_MX then 884 | if ans.name ~= "test.com" or ans.ttl ~= 60 or ans.preference ~= 10 or ans.exchange ~= "aspmx.l.test.com" then 885 | ngx.say("error") 886 | return 887 | end 888 | elseif ans.type == r.TYPE_A then 889 | if ans.name ~= "test.com" or ans.address ~= "33.33.33.33" or ans.ttl ~= 300 then 890 | ngx.say("error") 891 | return 892 | end 893 | elseif ans.type == r.TYPE_NS then 894 | if ans.name ~= "test.com" or ans.ttl ~= 3000 or (ans.nsdname ~= "ns1.test.com" and ans.nsdname ~= "ns2.test.com") then 895 | ngx.say("error") 896 | return 897 | end 898 | else 899 | ngx.say("error") 900 | return 901 | end 902 | end 903 | ngx.say("ok") 904 | } 905 | 906 | --- stream_server_config2 907 | content_by_lua_block { 908 | local bit = require 'bit' 909 | local lshift = bit.lshift 910 | local rshift = bit.rshift 911 | local band = bit.band 912 | local byte = string.byte 913 | local char = string.char 914 | local server = require 'resty.dns.server' 915 | 916 | local sock, err = ngx.req.socket() 917 | if not sock then 918 | ngx.log(ngx.ERR, "failed to get the request socket: ", err) 919 | return ngx.exit(ngx.ERROR) 920 | end 921 | 922 | sock:settimeout(1000) 923 | local buf, err = sock:receive(2) 924 | if not buf then 925 | ngx.say(string.format("sock receive error: %s", err)) 926 | return 927 | end 928 | 929 | local len_hi = byte(buf, 1) 930 | local len_lo = byte(buf, 2) 931 | local len = lshift(len_hi, 8) + len_lo 932 | local data, err = sock:receive(len) 933 | if not data then 934 | ngx.log(ngx.ERR, "failed to receive: ", err) 935 | return ngx.exit(ngx.ERROR) 936 | end 937 | 938 | local dns = server:new() 939 | local request, err = dns:decode_request(data) 940 | if not request then 941 | ngx.log(ngx.ERR, "failed to decode dns request: ", err) 942 | return 943 | end 944 | 945 | local query = request.questions[1] 946 | if query.qname == "test.com" and query.qtype == server.TYPE_ANY then 947 | dns:create_txt_answer(query.qname, 3000, "v=spf1 include:_spf.test.com ~all") 948 | dns:create_mx_answer(query.qname, 60, 10, "aspmx.l.test.com") 949 | dns:create_a_answer(query.qname, 300, "33.33.33.33") 950 | dns:create_ns_answer(query.qname, 3000, "ns1.test.com") 951 | dns:create_ns_answer(query.qname, 3000, "ns2.test.com") 952 | else 953 | dns:create_response_header(server.RCODE_NOT_IMPLEMENTED) 954 | end 955 | 956 | local resp = dns:encode_response() 957 | local len = #resp 958 | local len_hi = char(rshift(len, 8)) 959 | local len_lo = char(band(len, 0xff)) 960 | 961 | local ok, err = sock:send({len_hi, len_lo, resp}) 962 | if not ok then 963 | ngx.log(ngx.ERR, "failed to send: ", err) 964 | return 965 | end 966 | return 967 | } 968 | --- stream_response_like 969 | ok 970 | 971 | --- error_log 972 | stream lua tcp socket read timed out 973 | --------------------------------------------------------------------------------