Skip to content

Commit

Permalink
Merge pull request #511 from 3scale/resolver-ipv6
Browse files Browse the repository at this point in the history
[resty.resolver] support ipv6 resolvers
  • Loading branch information
davidor authored Nov 30, 2017
2 parents 0697b47 + f4fa5b3 commit 1657f15
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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

0 comments on commit 1657f15

Please sign in to comment.