Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[resty.resolver] support ipv6 resolvers #511

Merged
merged 4 commits into from
Nov 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Fixed

- Loading installed luarocks from outside rover [PR #503](https://github.com/3scale/apicast/pull/503)
- Support IPv6 addresses in `/etc/resolv.conf` [PR #511](https://github.com/3scale/apicast/pull/511)

## [3.2.0-alpha1]

Expand Down
1 change: 1 addition & 0 deletions bin/busted
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ chdir "$dirname/..";
my $apicast = getcwd();

exec '/usr/bin/env', 'resty',
'--errlog-level', 'alert',
'--http-include', "$apicast/spec/fixtures/echo.conf",
"$apicast/bin/busted.lua",
'--config-file', "$apicast/.busted",
Expand Down
50 changes: 45 additions & 5 deletions gateway/src/resty/resolver.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ local find = string.find
local rep = string.rep
local unpack = unpack
local insert = table.insert
local getenv = os.getenv
local concat = table.concat
local io_type = io.type

require('resty.core.regex') -- to allow use of ngx.re.match in the init phase

local re_match = ngx.re.match
local resolver_cache = require 'resty.resolver.cache'
local dns_client = require 'resty.resolver.dns_client'
local resty_env = require 'resty.env'
local upstream = require 'ngx.upstream'
local re = require('ngx.re')
local semaphore = require "ngx.semaphore"
Expand Down Expand Up @@ -53,6 +56,14 @@ local function read_resolv_conf(path)
return output or "", err
end

local function ipv4(address)
return re_match(address, '^([0-9]{1,3}\\.){3}[0-9]{1,3}$', 'oj')
end

local function ipv6(address)
return re_match(address, '^\\[[a-f\\d:]+\\]$', 'oj')
end

local nameserver = {
mt = {
__tostring = function(t)
Expand All @@ -62,9 +73,35 @@ local nameserver = {
}

function nameserver.new(host, port)
if not ipv4(host) and not ipv6(host) then
-- then it is likely ipv6 without [ ] around
host = format('[%s]', host)
end
return setmetatable({ host, port or default_resolver_port }, nameserver.mt)
end

function _M.parse_resolver(resolver)
if not resolver then return end

local m, err = re_match(resolver, [[^
(
(?:\d{1,3}\.){3}\d{1,3} # ipv4
|
\[[a-f\d:]+\] # ipv6 in [ ] brackes, like [dead::beef]
|
[a-f\d:]+ # ipv6 without brackets
)
(?:\:(\d+))? # optional port
$]], 'ojx')

if m then
return nameserver.new(m[1], m[2])
else
return resolver, err or 'invalid address'
end
end


function _M.parse_nameservers(path)
local resolv_conf, err = read_resolv_conf(path)

Expand All @@ -76,7 +113,6 @@ function _M.parse_nameservers(path)

local search = { }
local nameservers = { search = search }
local resolver = getenv('RESOLVER')
local domains = match(resolv_conf, 'search%s+([^\n]+)')

ngx.log(ngx.DEBUG, 'search ', domains)
Expand All @@ -85,11 +121,15 @@ function _M.parse_nameservers(path)
insert(search, domain)
end

if resolver then
local m = re.split(resolver, ':', 'oj')
insert(nameservers, nameserver.new(m[1], m[2]))
local resolver
resolver, err = _M.parse_resolver(resty_env.value('RESOLVER'))

if err then
ngx.log(ngx.ERR, 'invalid resolver ', resolver, ' error: ', err)
elseif resolver then
-- we are going to use all resolvers, because we can't trust dnsmasq
-- see https://github.com/3scale/apicast/issues/321 for more details
insert(nameservers, resolver)
end

for server in gmatch(resolv_conf, 'nameserver%s+([^%s]+)') do
Expand Down
44 changes: 44 additions & 0 deletions spec/resty/resolver_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,36 @@ describe('resty.resolver', function()
pending('does query when cached cname missing address')
end)

describe('.parse_resolver', function()

it("handles invalid data", function()
assert.same({'foobar', 'invalid address'}, { resty_resolver.parse_resolver('foobar') })
end)

it("handles missing data", function()
assert.same({}, { resty_resolver.parse_resolver() })
end)

it("parses ipv4 without port", function()
assert.same({'192.168.0.1', 53}, resty_resolver.parse_resolver('192.168.0.1'))
end)

it("parses ipv4 with port", function()
assert.same({'192.168.0.1', '5353'}, resty_resolver.parse_resolver('192.168.0.1:5353'))
end)

it("parses ipv6 without port", function()
assert.same({'[dead::beef:5353]', 53}, resty_resolver.parse_resolver('dead::beef:5353'))
end)

it("parses ipv6 with port", function()
assert.same({'[dead::beef]', '5353'}, resty_resolver.parse_resolver('[dead::beef]:5353'))
end)
end)

describe('.parse_nameservers', function()
local tmpname
local resty_env = require('resty.env')

before_each(function()
tmpname = io.tmpfile()
Expand All @@ -155,6 +183,22 @@ describe('resty.resolver', function()
assert.same({ 'localdomain.example.com', 'local' }, search)
end)

it('ignores invalid RESOLVER', function()
resty_env.set('RESOLVER', 'invalid-nameserver')

local nameservers = resty_resolver.parse_nameservers('')

assert.equal(0, #nameservers)
end)

it('uses correct RESOLVER', function()
resty_env.set('RESOLVER', '192.168.0.1:53')

local nameservers = resty_resolver.parse_nameservers('')

assert.equal(1, #nameservers)
assert.same({'192.168.0.1', '53'}, nameservers[1])
end)
end)

end)
33 changes: 28 additions & 5 deletions t/resolver.t
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ use lib 't';
use TestAPIcast 'no_plan';

$ENV{TEST_NGINX_HTTP_CONFIG} = "$TestAPIcast::path/http.d/*.conf";
$ENV{RESOLVER} = '127.0.1.1:5353';
$ENV{TEST_NGINX_RESOLVER} = '127.0.1.1:5353';

$ENV{TEST_NGINX_RESOLV_CONF} = "$Test::Nginx::Util::HtmlDir/resolv.conf";

env_to_nginx(
'RESOLVER'
);
master_on();
log_level('warn');
run_tests();
Expand All @@ -17,9 +14,11 @@ __DATA__

=== TEST 1: uses all resolvers
both RESOLVER env variable and resolvers in resolv.conf should be used
--- main_config
env RESOLVER=$TEST_NGINX_RESOLVER;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_by_lua_block {
init_worker_by_lua_block {
require('resty.resolver').init('$TEST_NGINX_RESOLV_CONF')
}
--- config
Expand Down Expand Up @@ -67,3 +66,27 @@ servers: 2
2.3.4.5:6789
--- no_error_log
[error]


=== TEST 3: can have ipv6 RESOLVER
RESOLVER env variable can be IPv6 address
--- main_config
env RESOLVER=[dead::beef]:5353;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_worker_by_lua_block {
require('resty.resolver').init('$TEST_NGINX_RESOLV_CONF')
}
--- config
location = /t {
content_by_lua_block {
local nameservers = require('resty.resolver').nameservers()
ngx.say('nameservers: ', #nameservers, ' ', tostring(nameservers[1]))
}
}
--- request
GET /t
--- response_body
nameservers: 1 [dead::beef]:5353
--- user_files
>>> resolv.conf