diff --git a/lib/ngx/resolver.lua b/lib/ngx/resolver.lua new file mode 100644 index 000000000..19965c42a --- /dev/null +++ b/lib/ngx/resolver.lua @@ -0,0 +1,89 @@ +local base = require "resty.core.base" +local get_request = base.get_request +local ffi = require "ffi" +local C = ffi.C +local ffi_str = ffi.string +local ffi_gc = ffi.gc +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local FFI_DONE = base.FFI_DONE +local co_yield = coroutine._yield + +local BUF_SIZE = 256 +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr + +base.allows_subsystem("http") + +ffi.cdef [[ +typedef intptr_t ngx_int_t; +typedef unsigned char u_char; +typedef struct ngx_http_lua_co_ctx_t *curcoctx_ptr; +typedef struct ngx_http_resolver_ctx_t *rctx_ptr; + +typedef struct { + ngx_http_request_t *request; + u_char *buf; + size_t *buf_size; + curcoctx_ptr curr_co_ctx; + rctx_ptr rctx; + ngx_int_t rc; + unsigned ipv4:1; + unsigned ipv6:1; +} ngx_http_lua_resolver_ctx_t; + +int ngx_http_lua_ffi_resolve(ngx_http_lua_resolver_ctx_t *ctx, + const char *hostname); + +void ngx_http_lua_ffi_resolver_destroy(ngx_http_lua_resolver_ctx_t *ctx); +]] + +local _M = { version = base.version } + +local mt = { + __gc = C.ngx_http_lua_ffi_resolver_destroy +} + +local Ctx = ffi.metatype("ngx_http_lua_resolver_ctx_t", mt) + +function _M.resolve(hostname, ipv4, ipv6) + local buf = get_string_buf(BUF_SIZE) + local buf_size = get_size_ptr() + buf_size[0] = BUF_SIZE + + local ctx = Ctx() + ctx.request = get_request() + ctx.buf = buf + ctx.buf_size = buf_size + + if ipv4 == nil or ipv4 then + ctx.ipv4 = 1 + end + + if ipv6 then + ctx.ipv6 = 1 + end + + local rc = C.ngx_http_lua_ffi_resolve(ctx, hostname) + + local res, err + if (rc == FFI_OK) then + res, err = ffi_str(buf, buf_size[0]), nil + elseif (rc == FFI_DONE) then + res, err = co_yield() + elseif (rc == FFI_ERROR) then + res, err = nil, ffi_str(buf, buf_size[0]) + else + res, err = nil, "unknown error" + end + + C.ngx_http_lua_ffi_resolver_destroy(ffi_gc(ctx, nil)) + + if err ~= nil then + return res, err + end + + return res +end + +return _M \ No newline at end of file diff --git a/lib/ngx/resolver.md b/lib/ngx/resolver.md new file mode 100644 index 000000000..3d4a3e447 --- /dev/null +++ b/lib/ngx/resolver.md @@ -0,0 +1,139 @@ +Name +==== + +`ngx.resolver` - Lua API for Nginx core's dynamic resolver. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Methods](#methods) + * [resolve](#resolve) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +TBD + +Synopsis +======== + +```nginx + http { + resolver 8.8.8.8; + + upstream backend { + server 0.0.0.0; + + balancer_by_lua_block { + local balancer = require 'ngx.balancer' + + local ctx = ngx.ctx + local ok, err = balancer.set_current_peer(ctx.peer_addr, ctx.peer_port) + if not ok then + ngx.log(ngx.ERR, "failed to set the peer: ", err) + ngx.exit(500) + end + } + } + + server { + listen 8080; + + access_by_lua_block { + local resolver = require 'ngx.resolver' + + local ctx = ngx.ctx + local addr, err = resolver.resolve('google.com', true, false) + if addr then + ctx.peer_addr = addr + ctx.peer_port = 80 + end + } + + location / { + proxy_pass http://backend; + } + } + } +``` + +[Back to TOC](#table-of-contents) + +Method +======= + +resolve +----------------- +**syntax:** *address,err = resolver.resolve(hostname, ipv4, ipv6)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** + +Resolve `hostname` into IP address by using Nginx core's dynamic resolver. Returns IP address string. In case of error, `nil` will be returned as well as a string describing the error. + +The `ipv4` and `ipv6`argument are boolean flags that controls whether A or AAAA DNS records we are interested in. +Please, note that resolver has its own configuration option `ipv6=on|off`, which has higher precedence over above flags. +The 'ipv4' flag has default value `true`. + +It is required to configure the [resolver](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive in the `nginx.conf`. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +TBD + +Copyright and License +===================== + +TBD + +[Back to TOC](#table-of-contents) + +See Also +======== +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: http://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/t/resolver.t b/t/resolver.t new file mode 100644 index 000000000..26fed409f --- /dev/null +++ b/t/resolver.t @@ -0,0 +1,134 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua 'no_plan'; +use lib '.'; +use t::TestCore; + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; + +run_tests(); + +__DATA__ + +=== TEST 1: use resolver in rewrite_by_lua_block +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8; + rewrite_by_lua "ngx.ctx.addr = require('ngx.resolver').resolve('google.com')"; + location = /resolve { + content_by_lua "ngx.say(ngx.ctx.addr)"; + } +--- request +GET /resolve +--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$ + + + +=== TEST 2: use resolver in access_by_lua_block +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8; + access_by_lua "ngx.ctx.addr = require('ngx.resolver').resolve('google.com')"; + location = /resolve { + content_by_lua "ngx.say(ngx.ctx.addr)"; + } +--- request +GET /resolve +--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$ + + + +=== TEST 3: use resolver in content_by_lua_block +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8; + location = /resolve { + content_by_lua "ngx.say(require('ngx.resolver').resolve('google.com'))"; + } +--- request +GET /resolve +--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$ + + + +=== TEST 4: query IPv6 addresses +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8; + location = /resolve { + content_by_lua "ngx.say(require('ngx.resolver').resolve('google.com', false, true))"; + } +--- request +GET /resolve +--- response_body_like: ^[a-fA-F0-9:]+$ + + + +=== TEST 5: pass IPv4 address to resolver +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + location = /resolve { + content_by_lua "ngx.say(require('ngx.resolver').resolve('192.168.0.1'))"; + } +--- request +GET /resolve +--- response_body +192.168.0.1 + + + +=== TEST 6: pass IPv6 address to resolver +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + location = /resolve { + content_by_lua "ngx.say(require('ngx.resolver').resolve('2a00:1450:4010:c05::66'))"; + } +--- request +GET /resolve +--- response_body +2a00:1450:4010:c05::66 + + + +=== TEST 7: pass non-existent domain name to resolver +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8; + resolver_timeout 1s; + location = /resolve { + content_by_lua "ngx.say(require('ngx.resolver').resolve('fake-name'))"; + } +--- request +GET /resolve +--- response_body +nilfake-name could not be resolved (3: Host not found) + + + +=== TEST 8: check caching in Nginx resolver (2 cache hits) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; +--- config + resolver 8.8.8.8 valid=30s; + + location = /resolve { + content_by_lua_block { + local resolver = require 'ngx.resolver' + ngx.say(resolver.resolve('google.com')) + ngx.say(resolver.resolve('google.com')) + ngx.say(resolver.resolve('google.com')) + } + } +--- request +GET /resolve +--- grep_error_log: resolve cached +--- grep_error_log_out +resolve cached +resolve cached