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

DNS volume 2 #271

Merged
merged 11 commits into from
Feb 24, 2017
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Use stale DNS cache when there is a query in progress for that record [PR #260](https://github.com/3scale/apicast/pull/260)
- Bump s2i-openresty to 1.11.2.2-2 [PR #260](https://github.com/3scale/apicast/pull/260)
- Echo API on port 8081 listens accepts any Host [PR #268](https://github.com/3scale/apicast/pull/268)
- Always use DNS search scopes [PR #271](https://github.com/3scale/apicast/pull/271)

### Added

Expand Down
7 changes: 4 additions & 3 deletions apicast/src/configuration_loader/remote_v1.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local tonumber = tonumber

local resty_url = require 'resty.url'
local http = require "resty.resolver.http"
local socket_resolver = require('resty.resolver.socket')
local configuration_parser = require 'configuration_parser'
local user_agent = require 'user_agent'
local env = require 'resty.env'
Expand Down Expand Up @@ -46,15 +47,15 @@ function _M.wait(endpoint, timeout)
end

while now < fin do
local sock = ngx.socket.tcp()
local sock = socket_resolver.new(ngx.socket.tcp())
local ok

ok, err = sock:connect(host, port)

if ok then
ngx.log(ngx.DEBUG, 'connected to ' .. host .. ':' .. tostring(port))
sock:close()
return true
break
else
ngx.log(ngx.DEBUG, 'failed to connect to ' .. host .. ':' .. tostring(port) .. ': ' .. err)
end
Expand All @@ -64,7 +65,7 @@ function _M.wait(endpoint, timeout)
now = ngx.now()
end

return nil, err
return not err, err
end

function _M.download(endpoint, _)
Expand Down
10 changes: 2 additions & 8 deletions apicast/src/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ local setmetatable = setmetatable
local exit = ngx.exit
local encode_args = ngx.encode_args
local resty_resolver = require 'resty.resolver'
local dns_resolver = require 'resty.resolver.dns'
local empty = {}

local response_codes = env.enabled('APICAST_RESPONSE_CODES')
Expand Down Expand Up @@ -287,9 +286,7 @@ end
function _M.set_upstream(service)
local upstream = _M.get_upstream(service)

ngx.ctx.dns = dns_resolver:new{ nameservers = resty_resolver.nameservers() }
ngx.ctx.resolver = resty_resolver.new(ngx.ctx.dns)
ngx.ctx.upstream = ngx.ctx.resolver:get_servers(upstream.server, { port = upstream.port })
ngx.ctx.upstream = resty_resolver:instance():get_servers(upstream.server, { port = upstream.port })

ngx.var.proxy_pass = upstream.uri
ngx.req.set_header('Host', upstream.host or ngx.var.host)
Expand All @@ -307,10 +304,7 @@ function _M:set_backend_upstream(service)
local scheme, _, _, server, port, path =
url[1], url[2], url[3], url[4], url[5] or resty_url.default_port(url[1]), url[6] or ''

ngx.ctx.dns = ngx.ctx.dns or dns_resolver:new{ nameservers = resty_resolver.nameservers() }
ngx.ctx.resolver = ngx.ctx.resolver or resty_resolver.new(ngx.ctx.dns)

local backend_upstream = ngx.ctx.resolver:get_servers(server, { port = port or nil })
local backend_upstream = resty_resolver:instance():get_servers(server, { port = port or nil })
ngx.log(ngx.DEBUG, '[resolver] resolved backend upstream: ', #backend_upstream)
ngx.ctx.backend_upstream = backend_upstream

Expand Down
80 changes: 47 additions & 33 deletions apicast/src/resty/resolver.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ local io_type = io.type
local re_match = ngx.re.match
local semaphore = require "ngx.semaphore"
local resolver_cache = require 'resty.resolver.cache'
local dns_client = require 'resty.resolver.dns_client'
local re = require('ngx.re')

local init = semaphore.new(1)
Expand All @@ -30,16 +31,12 @@ local default_resolver_port = 53
local _M = {
_VERSION = '0.1',
_nameservers = {},
search = {}
search = { '' }
}

local mt = { __index = _M }

function _M.parse_nameservers(path)
local search = {}
local nameservers = { search = search }
local resolver = getenv('RESOLVER')

local function read_resolv_conf(path)
path = path or '/etc/resolv.conf'

local handle, err
Expand All @@ -56,13 +53,25 @@ function _M.parse_nameservers(path)
handle:seek("set")
output = handle:read("*a")
handle:close()
else
ngx.log(ngx.ERR, 'resolver could not get nameservers: ', err)
return nil, err
end
ngx.log(ngx.DEBUG, '/etc/resolv.conf:\n', output)

local domains = match(output, 'search%s+([^\n]+)')
return output or "", err
end

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

if err then
ngx.log(ngx.WARN, 'resolver could not get nameservers: ', err)
end

ngx.log(ngx.DEBUG, '/etc/resolv.conf:\n', resolv_conf)

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

ngx.log(ngx.DEBUG, 'search ', domains)
for domain in gmatch(domains or '', '([^%s]+)') do
ngx.log(ngx.DEBUG, 'search domain: ', domain)
Expand All @@ -75,7 +84,7 @@ function _M.parse_nameservers(path)
return nameservers
end

for nameserver in gmatch(output, 'nameserver%s+([^%s]+)') do
for nameserver in gmatch(resolv_conf, 'nameserver%s+([^%s]+)') do
-- TODO: implement port matching based on https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=549190
if nameserver ~= resolver then
insert(nameservers, { nameserver, default_resolver_port } )
Expand Down Expand Up @@ -133,6 +142,18 @@ function _M.new(dns, opts)
}, mt)
end

function _M:instance()
local ctx = ngx.ctx
local resolver = ctx.resolver

if not resolver then
local dns = dns_client:instance(self.nameservers())
resolver = self.new(dns)
ctx.resolver = resolver
end

return resolver
end

local function new_server(answer, port)
if not answer then return nil, 'missing answer' end
Expand Down Expand Up @@ -164,14 +185,6 @@ local function is_ip(address)
end
end

local function has_tld(qname)
return match(qname, '%.')
end

local function have_addresses(answers)
return answers and next(answers.addresses or {}) and #answers > 0 and not answers.errcode
end

local function convert_answers(answers, port)
local servers = {}

Expand All @@ -184,6 +197,8 @@ local function convert_answers(answers, port)
return servers
end

local empty = {}

local function lookup(dns, qname, search, options)
ngx.log(ngx.DEBUG, 'resolver query: ', qname)

Expand All @@ -193,22 +208,18 @@ local function lookup(dns, qname, search, options)
ngx.log(ngx.DEBUG, 'host is ip address: ', qname)
answers = { new_answer(qname) }
else
answers, err = dns:query(qname, options)

if not has_tld(qname) and not have_addresses(answers) then
for i=1, #search do
for i=1, #search do
local query = qname .. '.' .. search[i]
ngx.log(ngx.DEBUG, 'resolver query: ', qname, ' search: ', search[i], ' query: ', query)
answers, err = dns:query(query, options)

local query = qname .. '.' .. search[i]
ngx.log(ngx.DEBUG, 'resolver query: ', qname, ' search: ', search[i], ' query: ', query)
answers, err = dns:query(query, options)

if answers and not answers.errcode and #answers > 0 then
break
end
if answers and not answers.errcode and #answers > 0 then
break
end
end
end

ngx.log(ngx.DEBUG, 'resolver query: ', qname, ' finished with ', #(answers or empty), ' answers')
return answers, err
end

Expand All @@ -225,8 +236,7 @@ function _M.get_servers(self, qname, opts)
end

local cache = self.cache
local search = self.search or {}

local search = self.search or _M.search

-- TODO: pass proper options to dns resolver (like SRV query type)

Expand All @@ -245,13 +255,17 @@ function _M.get_servers(self, qname, opts)
end

if err then
ngx.log(ngx.DEBUG, 'query for ', qname, ' finished with error: ', err)
return {}, err
end

if not answers then
ngx.log(ngx.DEBUG, 'query for ', qname, ' finished with no answers')
return {}, 'no answers'
end

ngx.log(ngx.DEBUG, 'query for ', qname, ' finished with ' , #answers, ' answers')

local servers = convert_answers(answers, opts.port)

servers.query = qname
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,32 @@ function _M.new(_, options)
}, mt)
end

function _M:instance(nameservers)
local ctx = ngx.ctx
local resolver = ctx.dns

if not resolver then
resolver = self:new({ nameservers = nameservers })
ctx.dns = resolver
end

return resolver
end

function _M:init_resolvers()
local resolvers = self.resolvers
local nameservers = self.nameservers

ngx.log(ngx.DEBUG, 'initializing ', #nameservers, ' nameservers')
for i=1,#nameservers do
insert(resolvers, { nameservers[i], resty_resolver:new({ nameservers = { nameservers[i] }}) })
insert(resolvers, {
nameserver = nameservers[i],
resolver = resty_resolver:new({
nameservers = { nameservers[i] },
timeout = 2900
})
})
ngx.log(ngx.DEBUG, 'nameserver ', nameservers[i][1],':',nameservers[i][2] or 53, ' initialized')
end

self.initialized = true
Expand All @@ -42,18 +62,16 @@ local function query(resolver, qname, opts, nameserver)
return resolver:query(qname, opts)
end

function _M.query(self, qname, opts)
local resolvers = self.resolvers

if not self.initialized then
resolvers = self:init_resolvers()
end

local function parallel_query(resolvers, qname, opts)
local threads = {}
local n = #resolvers

if n < 1 then
return nil, 'no resolvers'
end

for i=1, n do
insert(threads, th_spawn(query, resolvers[i][2], qname, opts, resolvers[i][1]))
insert(threads, th_spawn(query, resolvers[i].resolver, qname, opts, resolvers[i].nameserver))
end

local answers, err
Expand All @@ -75,4 +93,29 @@ function _M.query(self, qname, opts)
return answers, err
end

local function serial_query(resolvers, qname, opts)
local answers, err

for i=1, #resolvers do
answers, err = query(resolvers[i].resolver, qname, opts, resolvers[i].nameserver)

if answers and not answers.errcode and not err then
break
end
end

return answers
end

function _M.query(self, qname, opts)
local resolvers = self.resolvers

if not self.initialized then
resolvers = self:init_resolvers()
end

-- this is here so you can try the other one when suspicous something is wrong
return (parallel_query or serial_query)(resolvers, qname, opts)
end

return _M
4 changes: 1 addition & 3 deletions apicast/src/resty/resolver/http.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
local resty_http = require 'resty.http'
local dns_resolver = require 'resty.resolver.dns'
local resty_resolver = require 'resty.resolver'
local round_robin = require 'resty.balancer.round_robin'

Expand All @@ -12,9 +11,8 @@ local mt = { __index = _M }

function _M.new()
local http = resty_http:new()
local dns = dns_resolver:new{ nameservers = resty_resolver.nameservers() }

http.resolver = resty_resolver.new(dns)
http.resolver = resty_resolver:instance()
http.balancer = round_robin.new()

return setmetatable(http, mt)
Expand Down
Loading