]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
http: respect socket type provided by net.listen() and systemd
authorPetr Špaček <petr.spacek@nic.cz>
Tue, 16 Apr 2019 11:45:33 +0000 (13:45 +0200)
committerPetr Špaček <petr.spacek@nic.cz>
Thu, 18 Apr 2019 08:08:16 +0000 (10:08 +0200)
modules/http/http.lua.in
modules/http/http.test.lua

index 33e80fa6e1531fc080624ac3d0ee7d2d9adbc51a..f76b00673cefe1bc2c288b03fc5e9e9b8b4bac02 100644 (file)
@@ -25,6 +25,34 @@ local M = {
        templates = {}  -- configuration templates
 }
 
+-- inherited by all configurations
+M.templates._builtin = {
+       cq = worker.bg_worker.cq,
+       cert = 'self.crt',
+       key = 'self.key',
+       ephemeral = true,
+       client_timeout = 5
+}
+-- log errors but do not throw
+M.templates._builtin.onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
+       local msg = '[http] ' .. op .. ' on ' .. tostring(context) .. ' failed'
+       if err then
+               msg = msg .. ': ' .. tostring(err)
+       end
+       if verbose() then
+               log(msg)
+       end
+end
+
+-- M.config() without explicit "kind" modifies this
+M.templates._default = {}
+
+-- DoH
+M.templates.doh = {}
+
+-- management endpoint
+M.templates.webmgmt = {}
+
 -- Map extensions to MIME type
 local mime_types = {
        js = 'application/javascript',
@@ -91,32 +119,36 @@ local function serve_root()
 end
 
 -- Export HTTP service endpoints
-M.endpoints = {
-       ['/'] = {'text/html', serve_root()},
-}
+M.templates.doh.endpoints = {}
+M.templates.webmgmt.endpoints = {}
+local mgmt_endpoints = M.templates.webmgmt.endpoints
+
+mgmt_endpoints['/'] = {'text/html', serve_root()}
 
 -- Export static pages
 for _, pg in ipairs(pages) do
-       M.endpoints['/'..pg] = pgload(pg)
+       mgmt_endpoints['/'..pg] = pgload(pg)
 end
 
 -- Export built-in prometheus interface
 local prometheus = require('kres_modules.prometheus')
 for k, v in pairs(prometheus.endpoints) do
-       M.endpoints[k] = v
+       mgmt_endpoints[k] = v
 end
 M.prometheus = prometheus
 
 -- Export built-in trace interface
 local http_trace = require('kres_modules.http_trace')
 for k, v in pairs(http_trace.endpoints) do
-       M.endpoints[k] = v
+       mgmt_endpoints[k] = v
 end
 M.trace = http_trace
 
+M.templates.doh.endpoints = {}
 local http_doh = require('kres_modules.http_doh')
 for k, v in pairs(http_doh.endpoints) do
-       M.endpoints[k] = v
+       mgmt_endpoints[k] = v
+       M.templates.doh.endpoints[k] = v
 end
 M.doh = http_doh
 
@@ -171,6 +203,7 @@ end
 
 -- Web server service closure
 local function route(endpoints)
+       assert(type(endpoints) == 'table', 'endpoints are not a table, is it a botched template?')
        return function (_, stream)
                -- HTTP/2: We're only permitted to send in open/half-closed (remote)
                local connection = stream.connection
@@ -290,6 +323,7 @@ end
 function mergeconf(...)
        local merged = {}
        for _, intable in ipairs({...}) do
+               assert(type(intable) == 'table', 'cannot merge non-tables')
                for key, val in pairs(intable) do
                        merged[key] = val
                end
@@ -297,31 +331,12 @@ function mergeconf(...)
        return merged
 end
 
--- inherited by all configurations
-M.templates.builtin = {
-       cq = worker.bg_worker.cq,
-       cert = 'self.crt',
-       key = 'self.key',
-       ephemeral = true,
-       client_timeout = 5
-}
--- log errors but do not throw
-M.templates.builtin.onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
-       local msg = '[http] ' .. op .. ' on ' .. tostring(context) .. ' failed'
-       if err then
-               msg = msg .. ': ' .. tostring(err)
-       end
-       if verbose() then
-               log(msg)
-       end
-end
-
 -- @function Listen on given socket
 -- using configuration for specific "kind" of HTTP server
 function add_socket(fd, kind)
        assert(M.servers[fd] == nil, 'socket is already served by an HTTP instance')
        local conf, crt, key
-       conf = mergeconf(M.templates.builtin, M.templates.default, M.templates[kind] or {})
+       conf = mergeconf(M.templates._builtin, M.templates._default, M.templates[kind] or {})
        conf.socket = cqueues.socket.fdopen(fd)
        if conf.tls ~= false then
                -- Check if a cert file was specified
@@ -349,7 +364,7 @@ function add_socket(fd, kind)
                end
        end
        -- Compose server handler
-       local routes = route(conf.endpoints or M.endpoints)
+       local routes = route(conf.endpoints)
        -- Enable SO_REUSEPORT by default (unless explicitly turned off)
        if not reuseport and worker.id > 0 then
                warn('[http] the "reuseport" option is disabled and multiple forks are used, ' ..
@@ -425,28 +440,61 @@ function cb_socket(...)
        end
 end
 
--- @function Configure module, i.e. store configuration template and restart servers
--- kind = socket type
+-- @function Configure module, i.e. store new configuration template
+-- kind = socket type (doh/webmgmt)
 function M.config(conf, kind)
-       kind = kind or 'default'
+       if conf == nil and kind == nil then
+               -- default module config, nothing to do
+               return
+       end
+
+       kind = kind or '_default'
        assert(type(kind) == 'string')
-       conf = conf or {}
-       assert(type(conf) == 'table', 'config { cert = "...", key = "..." }')
-       if conf.cert then
-               conf.ephemeral = false
-               if not conf.key then
-                       error('certificate provided, but missing key')
+
+       local operation
+       if M.templates[kind] then  -- config on an existing template
+               if conf then    operation = 'modify'
+               else            operation = 'delete' end
+       else  -- config for not-yet-existing template
+               if conf then    operation = 'add'
+               else            panic('endpoint kind "%s" does not exist, '
+                                       .. 'nothing to delete', kind) end
+       end
+
+       if operation == 'modify' or operation == 'add' then
+               assert(type(conf) == 'table', 'config { cert = "...", key = "..." }')
+
+               if conf.cert then
+                       conf.ephemeral = false
+                       if not conf.key then
+                               error('certificate provided, but missing key')
+                       end
+               end
+               if conf.geoip then
+                       if has_mmdb then
+                               M.geoip = mmdb.open(conf.geoip)
+                       else
+                               error('[http] mmdblua library not found, please remove GeoIP configuration')
+                       end
                end
        end
-       if conf.geoip then
-               if has_mmdb then
-                       M.geoip = mmdb.open(conf.geoip)
-               else
-                       error('[http] mmdblua library not found, please remove GeoIP configuration')
+
+       for _, instance in pairs(M.servers) do
+               -- modification cannot be implemented as
+               -- remove_socket + add_socket because remove closes the socket
+               if instance.kind == kind then
+                       panic('unable to modify configration for '
+                               .. 'endpoint kind "%s" because it is in '
+                               .. 'use, use net.close() first', kind)
                end
        end
+
+       if operation == 'add' then
+               net.register_endpoint_kind(kind, cb_socket)
+       elseif operation == 'delete' then
+               net.register_endpoint_kind(kind)
+       end
        M.templates[kind] = conf
-       -- TODO restart configured servers of this kind
 end
 
 return M
index b05f5a506cbbcd36335cf5ae23df1c64406791f3..bc765f58bb74bcf2c4764d12624d61aefdec1aa9 100644 (file)
@@ -5,24 +5,38 @@ if not has_http then
        done()
 else
        local request = require('http.request')
-       local endpoints = require('kres_modules.http').endpoints
+
+       modules.load('http')
+       local endpoints = http.templates.webmgmt.endpoints
 
        -- custom endpoints
        endpoints['/test'] = {'text/custom', function () return 'hello' end}
 
-       -- setup resolver
-       modules = {
-               http = {
-                       port = 0, -- Select random port
-                       tls = false,
-                       endpoints = endpoints,
-               }
-       }
+       -- setup HTTP module with an additional endpoint
+       http.config({
+               tls = false,
+               endpoints = endpoints,
+       }, 'webtest')
 
-       local server = http.servers[1].server
-       ok(server ~= nil, 'creates server instance')
-       local _, host, port = server:localname()
-       ok(host and port, 'binds to an interface')
+       local bound
+       for i = 1,1000 do
+               bound = net.listen('127.0.0.1', math.random(1025,65535), { kind = 'webtest'} )
+               if bound then
+                       break
+               end
+       end
+       assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+       -- globals for this module
+       local _, host, port
+       local function start_server()
+               local server_fd = next(http.servers)
+               assert(server_fd)
+               local server = http.servers[server_fd].server
+               ok(server ~= nil, 'creates server instance')
+               _, host, port = server:localname()
+               ok(host and port, 'binds to an interface')
+       end
 
        -- helper for returning useful values to test on
        local function http_get(uri)
@@ -82,6 +96,7 @@ else
 
        -- plan tests
        local tests = {
+               start_server,
                test_builtin_pages,
        }