From 6f7b6d90ca1a953fda9086e07355eeb757a84dc2 Mon Sep 17 00:00:00 2001 From: thefosk Date: Wed, 11 May 2016 16:31:08 -0700 Subject: [PATCH] Removing dnsmasq dependency --- kong-0.8.1-0.rockspec | 2 +- kong.yml | 23 +++--- kong/cli/services/dnsmasq.lua | 52 ------------ kong/cli/services/nginx.lua | 2 + kong/cli/utils/dns.lua | 80 +++++++++++++++++++ kong/cli/utils/services.lua | 1 - kong/core/resolver.lua | 21 +++-- kong/tools/config_defaults.lua | 17 +--- kong/tools/config_loader.lua | 20 +++-- kong/tools/dns_resolver.lua | 34 ++++---- .../04-admin_api/apis_routes_spec.lua | 4 +- spec/plugins/oauth2/api_spec.lua | 2 +- spec/unit/14-config_loader_spec.lua | 5 -- spec/unit/16-resolver_spec.lua | 2 +- spec/unit/17-cli-utils_spec.lua | 75 +++++++++++++++++ 15 files changed, 222 insertions(+), 118 deletions(-) delete mode 100644 kong/cli/services/dnsmasq.lua create mode 100644 kong/cli/utils/dns.lua create mode 100644 spec/unit/17-cli-utils_spec.lua diff --git a/kong-0.8.1-0.rockspec b/kong-0.8.1-0.rockspec index d33f644e1de..80434f93617 100644 --- a/kong-0.8.1-0.rockspec +++ b/kong-0.8.1-0.rockspec @@ -54,6 +54,7 @@ build = { ["kong.cli.utils.ssl"] = "kong/cli/utils/ssl.lua", ["kong.cli.utils.input"] = "kong/cli/utils/input.lua", ["kong.cli.utils.services"] = "kong/cli/utils/services.lua", + ["kong.cli.utils.dns"] = "kong/cli/utils/dns.lua", ["kong.cli.cmds.config"] = "kong/cli/cmds/config.lua", ["kong.cli.cmds.quit"] = "kong/cli/cmds/quit.lua", ["kong.cli.cmds.stop"] = "kong/cli/cmds/stop.lua", @@ -65,7 +66,6 @@ build = { ["kong.cli.cmds.migrations"] = "kong/cli/cmds/migrations.lua", ["kong.cli.cmds.cluster"] = "kong/cli/cmds/cluster.lua", ["kong.cli.services.base_service"] = "kong/cli/services/base_service.lua", - ["kong.cli.services.dnsmasq"] = "kong/cli/services/dnsmasq.lua", ["kong.cli.services.serf"] = "kong/cli/services/serf.lua", ["kong.cli.services.nginx"] = "kong/cli/services/nginx.lua", diff --git a/kong.yml b/kong.yml index cc571743e2c..d2a5a076579 100644 --- a/kong.yml +++ b/kong.yml @@ -52,20 +52,17 @@ # ssl_key_path: /path/to/certificate.key ###### -## Specify how Kong performs DNS resolution (in the `dns_resolvers_available` property) you want to use. -## Options are: "dnsmasq" (You will need dnsmasq to be installed) or "server". -# dns_resolver: dnsmasq +## By default Kong will resolve DNS addresses using the local system settings as configured in `/etc/resolv.conf` +## and `/etc/hosts`. You can customize the DNS resolution settings by changing the following properties. +#dns_resolver: -###### -## A dictionary of DNS resolvers Kong can use, and their respective properties. -## Currently `dnsmasq` (default, http://www.thekelleys.org.uk/dnsmasq/doc.html) and `server` are supported. -## By choosing `dnsmasq`, Kong will resolve hostnames using the local `/etc/hosts` file and `resolv.conf` -## configuration. By choosing `server`, you can specify a custom DNS server. -# dns_resolvers_available: - # server: - # address: "8.8.8.8:53" - # dnsmasq: - # port: 8053 + ###### + ## You can override the DNS server being used by specifying a custom DNS server. + # address: "8.8.8.8:53" + + ###### + ## SRV record resolutions are enabled by default. Disabling them will slightly improve the performance. + # srv: true ###### ## Cluster settings between Kong nodes. diff --git a/kong/cli/services/dnsmasq.lua b/kong/cli/services/dnsmasq.lua deleted file mode 100644 index 0067ae37d5a..00000000000 --- a/kong/cli/services/dnsmasq.lua +++ /dev/null @@ -1,52 +0,0 @@ -local BaseService = require "kong.cli.services.base_service" -local logger = require "kong.cli.utils.logger" -local IO = require "kong.tools.io" - -local Dnsmasq = BaseService:extend() - -local SERVICE_NAME = "dnsmasq" - -function Dnsmasq:new(configuration) - self._configuration = configuration - Dnsmasq.super.new(self, SERVICE_NAME, self._configuration.nginx_working_dir) -end - -function Dnsmasq:prepare() - return Dnsmasq.super.prepare(self, self._configuration.nginx_working_dir) -end - -function Dnsmasq:start() - if self._configuration.dns_resolver.dnsmasq then - if self:is_running() then - return nil, SERVICE_NAME.." is already running" - end - - local cmd, err = Dnsmasq.super._get_cmd(self) - if err then - return nil, err - end - - -- dnsmasq always listens on the local 127.0.0.1 address - local res, code = IO.os_execute(cmd.." -p "..self._configuration.dns_resolver.port.." --pid-file="..self._pid_file_path.." -N -o --listen-address=127.0.0.1") - if code == 0 then - while not self:is_running() do - -- Wait for PID file to be created - end - - setmetatable(self._configuration.dns_resolver, require "kong.tools.printable") - logger:info(string.format([[dnsmasq............%s]], tostring(self._configuration.dns_resolver))) - return true - else - return nil, res - end - end - return true -end - -function Dnsmasq:stop() - if self._configuration.dns_resolver.dnsmasq then - Dnsmasq.super.stop(self, true) -- Killing dnsmasq just with "kill PID" apparently doesn't terminate it - end -end - -return Dnsmasq \ No newline at end of file diff --git a/kong/cli/services/nginx.lua b/kong/cli/services/nginx.lua index a0c4b62abc1..c607ea3d0b9 100644 --- a/kong/cli/services/nginx.lua +++ b/kong/cli/services/nginx.lua @@ -72,6 +72,8 @@ local function prepare_nginx_configuration(configuration, ssl_config) local current_user = get_current_user() + logger:info("DNS resolver set to "..configuration.dns_resolver.address.." on port "..configuration.dns_resolver.port) + -- Extract nginx config from kong config, replace any needed value local nginx_config = configuration.nginx local nginx_inject = { diff --git a/kong/cli/utils/dns.lua b/kong/cli/utils/dns.lua new file mode 100644 index 00000000000..3e00e8af424 --- /dev/null +++ b/kong/cli/utils/dns.lua @@ -0,0 +1,80 @@ +local stringy = require "stringy" +local IO = require "kong.tools.io" +local utils = require "kong.tools.utils" + +local _M = {} + +function _M.parse_dns_address(address) + local with_port = string.match(address, "%[") + local parts = stringy.split(address, ":") + return { + address = address, + host = address:match("%[?([%w%.:]+)%]?"), + port = with_port and tonumber(parts[#parts]) or 53 + } +end + +function _M.parse_resolv_entry(v) + local address = v:match("%s*nameserver%s+([%[%]%w%.:]+)") + if address then + return _M.parse_dns_address(address) + end +end + +function _M.parse_options_entry(v) + local options = v:match("%s*options%s+[%s%w:]+") + if options then + local timeout, attempts + for option in options:gmatch("%S+") do + timeout = timeout or tonumber(option:match("%s*timeout:(%d+)%s*")) + attempts = attempts or tonumber(option:match("%s*attempts:(%d+)%s*")) + end + + if timeout or attempts then + return {timeout = timeout and timeout * 1000 or timeout, attempts = attempts} -- In milliseconds + end + end +end + +function _M.parse_etc_hosts() + local contents = IO.read_file("/etc/hosts") + if contents then + local results = {} + local lines = stringy.split(contents, "\n") + for _, line in ipairs(lines) do + if not stringy.startswith(stringy.strip(line), "#") then + local ip_address + for entry in line:gmatch("%S+") do + if not ip_address then + ip_address = entry + else + if not results[entry] then + results[entry] = ip_address + end + end + end + end + end + return results + end +end + +function _M.find_first_namespace() + local contents = IO.read_file("/etc/resolv.conf") + if contents then + -- Find first nameserver + local lines = stringy.split(contents, "\n") + local options, nameserver + + for _, line in ipairs(lines) do + nameserver = nameserver or _M.parse_resolv_entry(line) + options = options or _M.parse_options_entry(line) + end + + if nameserver then + return utils.table_merge(nameserver, options and options or {}) + end + end +end + +return _M \ No newline at end of file diff --git a/kong/cli/utils/services.lua b/kong/cli/utils/services.lua index 9561bc2bea7..eb2cee5a014 100644 --- a/kong/cli/utils/services.lua +++ b/kong/cli/utils/services.lua @@ -12,7 +12,6 @@ _M.STATUSES = { -- Services ordered by priority local services = { - require "kong.cli.services.dnsmasq", require "kong.cli.services.serf", require "kong.cli.services.nginx" } diff --git a/kong/core/resolver.lua b/kong/core/resolver.lua index 7a842425760..9a1fec0654f 100644 --- a/kong/core/resolver.lua +++ b/kong/core/resolver.lua @@ -256,12 +256,21 @@ function _M.execute(request_uri, request_headers) upstream_host = parsed_url.host..":"..upstream_port end - -- Resolve the appropriate DNS - local dns = DnsResolver(singletons.configuration.dns_resolver.address) - local resolution, err = dns:resolve(parsed_url.host, parsed_url.port) - if err then - ngx.log(ngx.ERR, err) - return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) + -- Resolve the appropriate DNS address, first by checking /etc/hosts, then by invoking the DNS server + local resolution + local etc_host = singletons.configuration.dns_etc_hosts[parsed_url.host] -- Check /etc/hosts first + if etc_host then + resolution = {host = etc_host} + else + -- Resolve using the DNS server + local dns_config = singletons.configuration.dns_resolver + local dns = DnsResolver(dns_config.host, dns_config.port, dns_config.timeout, dns_config.attempts) + local err + resolution, err = dns:resolve(parsed_url.host, parsed_url.port, dns_config.srv) + if err then + ngx.log(ngx.ERR, err) + return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) --TODO: this throw an error even when the resolution fails, it should return 502 (?) + end end if not resolution.port then diff --git a/kong/tools/config_defaults.lua b/kong/tools/config_defaults.lua index 18d4db5b404..8b01bfcad45 100644 --- a/kong/tools/config_defaults.lua +++ b/kong/tools/config_defaults.lua @@ -6,22 +6,11 @@ return { ["admin_api_listen"] = {type = "string", default = "0.0.0.0:8001"}, ["cluster_listen"] = {type = "string", default = "0.0.0.0:7946"}, ["cluster_listen_rpc"] = {type = "string", default = "127.0.0.1:7373"}, - ["dns_resolver"] = {type = "string", default = "dnsmasq", enum = {"server", "dnsmasq"}}, - ["dns_resolvers_available"] = { + ["dns_resolver"] = { type = "table", content = { - ["server"] = { - type = "table", - content = { - ["address"] = {type = "string", default = "8.8.8.8"} - } - }, - ["dnsmasq"] = { - type = "table", - content = { - ["port"] = {type = "number", default = 8053} - } - } + ["address"] = {type = "string", nullable = false}, + ["srv"] = {type = "boolean", default = true} } }, ["cluster"] = { diff --git a/kong/tools/config_loader.lua b/kong/tools/config_loader.lua index aebccb780a9..1483c6c6853 100644 --- a/kong/tools/config_loader.lua +++ b/kong/tools/config_loader.lua @@ -5,6 +5,7 @@ local logger = require "kong.cli.utils.logger" local stringy = require "stringy" local constants = require "kong.constants" local config_defaults = require "kong.tools.config_defaults" +local dns_utils = require "kong.cli.utils.dns" local function get_type(value, val_type) if val_type == "array" and utils.is_array(value) then @@ -178,14 +179,19 @@ function _M.load(config_path) -- Adding computed properties config.pid_file = IO.path:join(config.nginx_working_dir, constants.CLI.NGINX_PID) config.dao_config = config[config.database] - if config.dns_resolver == "dnsmasq" then - config.dns_resolver = { - address = "127.0.0.1:"..config.dns_resolvers_available.dnsmasq.port, - port = config.dns_resolvers_available.dnsmasq.port, - dnsmasq = true - } + + -- DNS resolver + config.dns_etc_hosts = dns_utils.parse_etc_hosts() + if config.dns_resolver and config.dns_resolver.address then + config.dns_resolver = dns_utils.parse_dns_address(config.dns_resolver.address) else - config.dns_resolver = {address = config.dns_resolvers_available.server.address} + -- Find from resolv.conf + local resolv_nameserver = dns_utils.find_first_namespace() + if not resolv_nameserver then + logger:error("Can't parse the system DNS settings. Please manually specify a DNS resolver in the configuaration") + os.exit(1) + end + config.dns_resolver = resolv_nameserver end -- Load absolute path for the nginx working directory diff --git a/kong/tools/dns_resolver.lua b/kong/tools/dns_resolver.lua index a30773a78b1..8d46c295f5e 100644 --- a/kong/tools/dns_resolver.lua +++ b/kong/tools/dns_resolver.lua @@ -3,22 +3,24 @@ local stringy = require "stringy" local cache = require "kong.tools.database_cache" local resolver = require "resty.dns.resolver" +local DEFAULT_TIMEOUT = 2000 +local DEFAULT_ATTEMPTS = 5 + local DnsResolver = Object:extend() -function DnsResolver:new(resolver_address) - local dns_resolver_parts = stringy.split(resolver_address, ":") - self.resolver = { - host = dns_resolver_parts[1], - port = dns_resolver_parts[2] - } +function DnsResolver:new(host, port, timeout, attempts) + self.host = host + self.port = port + self.timeout = timeout or DEFAULT_TIMEOUT + self.attempts = attempts or DEFAULT_ATTEMPTS end function DnsResolver:query(address, type) -- Init the resolver local r, err = resolver:new{ - nameservers = {{self.resolver.host, self.resolver.port}}, - retrans = 5, - timeout = 2000 + nameservers = {{self.host, self.port}}, + retrans = self.attempts, + timeout = self.timeout } if not r then return nil, "Startup error: "..err @@ -37,7 +39,7 @@ function DnsResolver:query(address, type) return answers end -function DnsResolver:resolve(address, port) +function DnsResolver:resolve(address, port, enable_srv) -- Retrieve from cache local cache_key = cache.dns_key(address) local result = cache.get(cache_key) @@ -59,11 +61,13 @@ function DnsResolver:resolve(address, port) -- Query for SRV record local final_port = port - local answers, err = self:query(address, "SRV") - if not err and #answers > 0 then -- Ignore the error because some DNS servers don't support SRV - local srv_answer = answers[1] - if srv_answer.port > 0 then - final_port = srv_answer.port + if enable_srv then + local answers, err = self:query(address, "SRV") + if not err and #answers > 0 then -- Ignore the error because some DNS servers don't support SRV + local srv_answer = answers[1] + if srv_answer.port > 0 then + final_port = srv_answer.port + end end end diff --git a/spec/integration/04-admin_api/apis_routes_spec.lua b/spec/integration/04-admin_api/apis_routes_spec.lua index 743eaf48bd3..dfca3642de8 100644 --- a/spec/integration/04-admin_api/apis_routes_spec.lua +++ b/spec/integration/04-admin_api/apis_routes_spec.lua @@ -91,7 +91,7 @@ describe("Admin API", function() assert.equal(201, status) end end) - it_content_types("#only should not update if some required fields are missing", function(content_type) + it_content_types("should not update if some required fields are missing", function(content_type) return function() local response, status = http_client.put(BASE_URL, { id = api.id, @@ -104,7 +104,7 @@ describe("Admin API", function() assert.equal("created_at is required", body.created_at) end end) - it_content_types("#only should update if exists", function(content_type) + it_content_types("should update if exists", function(content_type) return function() local response, status = http_client.put(BASE_URL, { id = api.id, diff --git a/spec/plugins/oauth2/api_spec.lua b/spec/plugins/oauth2/api_spec.lua index b1709226730..7e88bcfd700 100644 --- a/spec/plugins/oauth2/api_spec.lua +++ b/spec/plugins/oauth2/api_spec.lua @@ -137,7 +137,7 @@ describe("OAuth 2 Credentials API", function() end) describe("PUT", function() - it("#only [SUCCESS] should create a oauth2 token", function() + it("[SUCCESS] should create a oauth2 token", function() local response, status = http_client.put(BASE_URL, {credential_id = credential.id, expires_in = 10}) assert.equal(201, status) token = json.decode(response) diff --git a/spec/unit/14-config_loader_spec.lua b/spec/unit/14-config_loader_spec.lua index 3517f5d088b..a93b09b11b1 100644 --- a/spec/unit/14-config_loader_spec.lua +++ b/spec/unit/14-config_loader_spec.lua @@ -206,11 +206,6 @@ describe("Configuration validation", function() end end) end) - it("should validate the selected dns_resolver property", function() - local ok, errors = config.validate({dns_resolver = "foo"}) - assert.False(ok) - assert.equal("must be one of: 'server, dnsmasq'", errors.dns_resolver) - end) it("should validate the host:port listen addresses", function() -- Missing port local ok, errors = config.validate({proxy_listen = "foo"}) diff --git a/spec/unit/16-resolver_spec.lua b/spec/unit/16-resolver_spec.lua index 032b29d7c3b..1550b4664fe 100644 --- a/spec/unit/16-resolver_spec.lua +++ b/spec/unit/16-resolver_spec.lua @@ -30,7 +30,7 @@ singletons.dao = { } singletons.configuration = { - dns_resolver = {address="127.0.0.1:8053"} + dns_resolver = {address="8.8.8.8:53"} } local apis_dics diff --git a/spec/unit/17-cli-utils_spec.lua b/spec/unit/17-cli-utils_spec.lua new file mode 100644 index 00000000000..cf1444a6cab --- /dev/null +++ b/spec/unit/17-cli-utils_spec.lua @@ -0,0 +1,75 @@ +local dns = require "kong.cli.utils.dns" +local utils = require "kong.tools.utils" + +describe("CLI Utils", function() + describe("DNS", function() + it("should properly parse the resolv.conf nameserver directive", function() + assert.falsy(dns.parse_resolv_entry("asd")) + assert.falsy(dns.parse_resolv_entry("1.1.1.1")) + assert.falsy(dns.parse_resolv_entry("1.1.1.1:53")) + + local result = dns.parse_resolv_entry("nameserver 1.1.1.1") + assert.equals("1.1.1.1", result.address) + assert.equals("1.1.1.1", result.host) + assert.equals(53, result.port) + + local result = dns.parse_resolv_entry("nameserver 1.1.1.1 #hello") + assert.equals("1.1.1.1", result.address) + assert.equals("1.1.1.1", result.host) + assert.equals(53, result.port) + + local result = dns.parse_resolv_entry("nameserver [1.1.1.1]:8000") + assert.equals("[1.1.1.1]:8000", result.address) + assert.equals("1.1.1.1", result.host) + assert.equals(8000, result.port) + + local result = dns.parse_resolv_entry("nameserver 2001:db8:a0b:12f0::1") + assert.equals("2001:db8:a0b:12f0::1", result.address) + assert.equals("2001:db8:a0b:12f0::1", result.host) + assert.equals(53, result.port) + + local result = dns.parse_resolv_entry("nameserver [2001:db8:a0b:12f0::1]:8000") + assert.equals("[2001:db8:a0b:12f0::1]:8000", result.address) + assert.equals("2001:db8:a0b:12f0::1", result.host) + assert.equals(8000, result.port) + + local result = dns.parse_resolv_entry(" nameserver [2001:db8:a0b:12f0::1]:8000 #hello") + assert.equals("[2001:db8:a0b:12f0::1]:8000", result.address) + assert.equals("2001:db8:a0b:12f0::1", result.host) + assert.equals(8000, result.port) + end) + it("should retrieve the first nameserver", function() + local result = dns.find_first_namespace() + assert.truthy(result.address) + assert.truthy(result.host) + assert.truthy(result.port) + end) + it("should parse options", function() + local result = dns.parse_options_entry("asd") + assert.falsy(result) + + local result = dns.parse_options_entry("options ") + assert.falsy(result) + + local result = dns.parse_options_entry("options hello world") + assert.falsy(result) + + local result = dns.parse_options_entry("options hello timeout:43 world") + assert.are.same({timeout = 43000}, result) + + local result = dns.parse_options_entry("options hello attempts:4 world") + assert.are.same({attempts = 4}, result) + + local result = dns.parse_options_entry("options hello attempts:4 world timeout:55") + assert.are.same({attempts = 4, timeout = 55000}, result) + + local result = dns.parse_options_entry("options hello attempts:4 world timeout:55 #hello") + assert.are.same({attempts = 4, timeout = 55000}, result) + end) + it("should parse /etc/hosts", function() + local result = dns.parse_etc_hosts() + assert.truthy(result) + assert.truthy(utils.table_size(result) > 0) + end) + end) +end)