Skip to content

Commit 297df61

Browse files
committed
feature: added new Lua API for Nginx core's dynamic resolver.
1 parent 93bc9df commit 297df61

File tree

3 files changed

+360
-0
lines changed

3 files changed

+360
-0
lines changed

lib/ngx/resolver.lua

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
local base = require "resty.core.base"
2+
local get_request = base.get_request
3+
local ffi = require "ffi"
4+
local C = ffi.C
5+
local ffi_new = ffi.new
6+
local ffi_str = ffi.string
7+
local ffi_gc = ffi.gc
8+
local FFI_OK = base.FFI_OK
9+
local FFI_ERROR = base.FFI_ERROR
10+
local FFI_DONE = base.FFI_DONE
11+
local co_yield = coroutine._yield
12+
13+
local BUF_SIZE = 256
14+
local get_string_buf = base.get_string_buf
15+
local get_size_ptr = base.get_size_ptr
16+
17+
base.allows_subsystem("http")
18+
19+
ffi.cdef [[
20+
typedef intptr_t ngx_int_t;
21+
typedef unsigned char u_char;
22+
typedef struct ngx_http_lua_co_ctx_t *curcoctx_ptr;
23+
typedef struct ngx_http_resolver_ctx_t *rctx_ptr;
24+
25+
typedef struct {
26+
ngx_http_request_t *request;
27+
u_char *buf;
28+
size_t *buf_size;
29+
curcoctx_ptr curr_co_ctx;
30+
rctx_ptr rctx;
31+
ngx_int_t rc;
32+
unsigned ipv4:1;
33+
unsigned ipv6:1;
34+
} ngx_http_lua_resolver_ctx_t;
35+
36+
int ngx_http_lua_ffi_resolve(ngx_http_lua_resolver_ctx_t *ctx, const char *hostname);
37+
38+
void ngx_http_lua_ffi_resolver_destroy(ngx_http_lua_resolver_ctx_t *ctx);
39+
]]
40+
41+
local _M = { version = base.version }
42+
43+
local mt = {
44+
__gc = C.ngx_http_lua_ffi_resolver_destroy
45+
}
46+
47+
local Ctx = ffi.metatype("ngx_http_lua_resolver_ctx_t", mt)
48+
49+
function _M.resolve(hostname, ipv4, ipv6)
50+
assert(type(hostname) == "string", "hostname must be string")
51+
assert(ipv4 == nil or type(ipv4) == "boolean", "ipv4 must be boolean or nil")
52+
assert(ipv6 == nil or type(ipv6) == "boolean", "ipv6 must be boolean or nil")
53+
54+
local buf = get_string_buf(BUF_SIZE)
55+
local buf_size = get_size_ptr()
56+
buf_size[0] = BUF_SIZE
57+
58+
local ctx = Ctx({get_request(), buf, buf_size})
59+
60+
ctx.ipv4 = ipv4 == nil and 1 or ipv4
61+
ctx.ipv6 = ipv6 == nil and 0 or ipv6
62+
63+
local rc = C.ngx_http_lua_ffi_resolve(ctx, hostname)
64+
65+
local res, err
66+
if (rc == FFI_OK) then
67+
res, err = ffi_str(buf, buf_size[0]), nil
68+
elseif (rc == FFI_DONE) then
69+
res, err = co_yield()
70+
elseif (rc == FFI_ERROR) then
71+
res, err = nil, ffi_str(buf, buf_size[0])
72+
else
73+
res, err = nil, "unknown error"
74+
end
75+
76+
C.ngx_http_lua_ffi_resolver_destroy(ffi_gc(ctx, nil))
77+
78+
if err ~= nil then
79+
return res, err
80+
end
81+
82+
return res
83+
end
84+
85+
return _M

lib/ngx/resolver.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
Name
2+
====
3+
4+
`ngx.resolver` - Lua API for Nginx core's dynamic resolver.
5+
6+
Table of Contents
7+
=================
8+
9+
* [Name](#name)
10+
* [Status](#status)
11+
* [Synopsis](#synopsis)
12+
* [Methods](#methods)
13+
* [resolve](#resolve)
14+
* [Community](#community)
15+
* [English Mailing List](#english-mailing-list)
16+
* [Chinese Mailing List](#chinese-mailing-list)
17+
* [Bugs and Patches](#bugs-and-patches)
18+
* [Author](#author)
19+
* [Copyright and License](#copyright-and-license)
20+
* [See Also](#see-also)
21+
22+
Status
23+
======
24+
25+
TBD
26+
27+
Synopsis
28+
========
29+
30+
```nginx
31+
http {
32+
resolver 8.8.8.8;
33+
34+
upstream backend {
35+
server 0.0.0.0;
36+
37+
balancer_by_lua_block {
38+
local balancer = require 'ngx.balancer'
39+
40+
local ctx = ngx.ctx
41+
local ok, err = balancer.set_current_peer(ctx.peer_addr, ctx.peer_port)
42+
if not ok then
43+
ngx.log(ngx.ERR, "failed to set the peer: ", err)
44+
ngx.exit(500)
45+
end
46+
}
47+
}
48+
49+
server {
50+
listen 8080;
51+
52+
access_by_lua_block {
53+
local resolver = require 'ngx.resolver'
54+
55+
local ctx = ngx.ctx
56+
local addr, err = resolver.resolve('google.com', true, false)
57+
if addr then
58+
ctx.peer_addr = addr
59+
ctx.peer_port = 80
60+
end
61+
}
62+
63+
location / {
64+
proxy_pass http://backend;
65+
}
66+
}
67+
}
68+
```
69+
70+
[Back to TOC](#table-of-contents)
71+
72+
Methods
73+
=======
74+
75+
resolve
76+
-----------------
77+
**syntax:** *address,err = resolver.resolve(hostname, ipv4, ipv6)*
78+
79+
**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua**
80+
81+
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.
82+
83+
The `ipv4` and `ipv6`argument are boolean flags that controls whether A or AAAA DNS records we are interested in.
84+
Please, note that resolver has its own configuration option `ipv6=on|off`, which has higher precedence over above flags.
85+
The 'ipv4' flag has default value `true`.
86+
87+
It is required to configure the [resolver](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive in the `nginx.conf`.
88+
89+
90+
[Back to TOC](#table-of-contents)
91+
92+
Community
93+
=========
94+
95+
[Back to TOC](#table-of-contents)
96+
97+
English Mailing List
98+
--------------------
99+
100+
The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers.
101+
102+
[Back to TOC](#table-of-contents)
103+
104+
Chinese Mailing List
105+
--------------------
106+
107+
The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers.
108+
109+
[Back to TOC](#table-of-contents)
110+
111+
Bugs and Patches
112+
================
113+
114+
Please report bugs or submit patches by
115+
116+
1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues),
117+
1. or posting to the [OpenResty community](#community).
118+
119+
[Back to TOC](#table-of-contents)
120+
121+
Author
122+
======
123+
124+
TBD
125+
126+
Copyright and License
127+
=====================
128+
129+
TBD
130+
131+
[Back to TOC](#table-of-contents)
132+
133+
See Also
134+
========
135+
* library [lua-resty-core](https://github.com/openresty/lua-resty-core)
136+
* the ngx_lua module: https://github.com/openresty/lua-nginx-module
137+
* OpenResty: http://openresty.org
138+
139+
[Back to TOC](#table-of-contents)
140+

t/resolver.t

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
use Test::Nginx::Socket::Lua 'no_plan';
4+
use lib '.';
5+
use t::TestCore;
6+
7+
$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path";
8+
9+
run_tests();
10+
11+
__DATA__
12+
13+
14+
=== TEST 1: use resolver in rewrite_by_lua_block
15+
--- http_config
16+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
17+
--- config
18+
resolver 8.8.8.8;
19+
rewrite_by_lua "ngx.ctx.addr = require('ngx.resolver').resolve('google.com')";
20+
location = /resolve {
21+
content_by_lua "ngx.say(ngx.ctx.addr)";
22+
}
23+
--- request
24+
GET /resolve
25+
--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$
26+
27+
28+
29+
=== TEST 2: use resolver in access_by_lua_block
30+
--- http_config
31+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
32+
--- config
33+
resolver 8.8.8.8;
34+
access_by_lua "ngx.ctx.addr = require('ngx.resolver').resolve('google.com')";
35+
location = /resolve {
36+
content_by_lua "ngx.say(ngx.ctx.addr)";
37+
}
38+
--- request
39+
GET /resolve
40+
--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$
41+
42+
43+
44+
=== TEST 3: use resolver in content_by_lua_block
45+
--- http_config
46+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
47+
--- config
48+
resolver 8.8.8.8;
49+
location = /resolve {
50+
content_by_lua "ngx.say(require('ngx.resolver').resolve('google.com'))";
51+
}
52+
--- request
53+
GET /resolve
54+
--- response_body_like: ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$
55+
56+
57+
58+
=== TEST 4: query IPv6 addresses
59+
--- http_config
60+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
61+
--- config
62+
resolver 8.8.8.8;
63+
location = /resolve {
64+
content_by_lua "ngx.say(require('ngx.resolver').resolve('google.com', false, true))";
65+
}
66+
--- request
67+
GET /resolve
68+
--- response_body_like: ^[a-fA-F0-9:]+$
69+
70+
71+
72+
=== TEST 5: pass IPv4 address to resolver
73+
--- http_config
74+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
75+
--- config
76+
location = /resolve {
77+
content_by_lua "ngx.say(require('ngx.resolver').resolve('192.168.0.1'))";
78+
}
79+
--- request
80+
GET /resolve
81+
--- response_body
82+
192.168.0.1
83+
84+
85+
86+
=== TEST 6: pass IPv6 address to resolver
87+
--- http_config
88+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
89+
--- config
90+
location = /resolve {
91+
content_by_lua "ngx.say(require('ngx.resolver').resolve('2a00:1450:4010:c05::66'))";
92+
}
93+
--- request
94+
GET /resolve
95+
--- response_body
96+
2a00:1450:4010:c05::66
97+
98+
99+
100+
=== TEST 7: pass non-existent domain name to resolver
101+
--- http_config
102+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
103+
--- config
104+
resolver 8.8.8.8;
105+
resolver_timeout 1s;
106+
location = /resolve {
107+
content_by_lua "ngx.say(require('ngx.resolver').resolve('fake-name'))";
108+
}
109+
--- request
110+
GET /resolve
111+
--- response_body
112+
nilfake-name could not be resolved (3: Host not found)
113+
114+
115+
116+
=== TEST 8: check caching in Nginx resolver (2 cache hits)
117+
--- http_config
118+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
119+
--- config
120+
resolver 8.8.8.8 valid=30s;
121+
122+
location = /resolve {
123+
content_by_lua_block {
124+
local resolver = require 'ngx.resolver'
125+
ngx.say(resolver.resolve('google.com'))
126+
ngx.say(resolver.resolve('google.com'))
127+
ngx.say(resolver.resolve('google.com'))
128+
}
129+
}
130+
--- request
131+
GET /resolve
132+
--- grep_error_log: resolve cached
133+
--- grep_error_log_out
134+
resolve cached
135+
resolve cached

0 commit comments

Comments
 (0)