From: Petr Špaček Date: Tue, 16 Apr 2019 07:10:15 +0000 (+0200) Subject: http: use sockets from net.listen() X-Git-Tag: v4.0.0~4^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=929b761e6bc87ce526ff37d2b0cbdf2499b3ce23;p=thirdparty%2Fknot-resolver.git http: use sockets from net.listen() We still need to somehow solve socket closure etc. --- diff --git a/modules/http/http.lua.in b/modules/http/http.lua.in index 2f966f180..dd3fc3e40 100644 --- a/modules/http/http.lua.in +++ b/modules/http/http.lua.in @@ -2,6 +2,11 @@ if not stats then modules.load('stats') end if not bogus_log then modules.load('bogus_log') end +ffi = require('ffi') +cqueues = require('cqueues') +cqueues.socket = require('cqueues.socket') +assert(cqueues.VERSION >= 20150112) -- fdopen changed semantics + -- This is a module that does the heavy lifting to provide an HTTP/2 enabled -- server that supports TLS by default and provides endpoint for other modules -- in order to enable them to export restful APIs and websocket streams. @@ -17,7 +22,7 @@ local has_mmdb, mmdb = pcall(require, 'mmdb') -- Module declaration local M = { servers = {}, - templates = {} + templates = {} -- configuration templates } -- Map extensions to MIME type @@ -292,17 +297,16 @@ function mergeconf(...) return merged end -M.templates.default = { +-- inherited by all configurations +M.templates.builtin = { cq = worker.bg_worker.cq, cert = 'self.crt', key = 'self.key', ephemeral = true, - reuseport = true, - host = 'localhost', - port = 8053, client_timeout = 5 } -M.templates.default.onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212 +-- 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) @@ -313,15 +317,11 @@ M.templates.default.onerror = function(myserver, context, op, err, errno) -- lua end -- @function Listen on given HTTP(s) host -function M.add_interface(conf) - local crt, key, ephemeral - if conf.cert then - conf.ephemeral = false - if not conf.key then - error('certificate provided, but missing key') - end - end - local conf = mergeconf(M.templates.default, conf) +function M.add_socket(socket, kind) + assert(M.servers[socket] == 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.socket = cqueues.socket.fdopen(socket) if conf.tls ~= false then -- Check if a cert file was specified -- Read or create self-signed x509 certificate @@ -339,7 +339,7 @@ function M.add_interface(conf) key = assert(pkey.new(f:read('*all'))) f:close() end - elseif ephemeral then + elseif conf.ephemeral then crt, key = updatecert(conf.cert, conf.key) end -- Check loaded certificate @@ -355,21 +355,11 @@ function M.add_interface(conf) 'port binding will fail on some instances') end -- Check if UNIX socket path is used - local addr_str - if not conf.path then - conf.host = conf.host or 'localhost' - conf.port = conf.port or 8053 - addr_str = string.format('%s@%d', conf.host, conf.port) - else - if conf.host or conf.port then - error('either "path", or "host" and "port" must be provided') - end - addr_str = conf.path - end + local addr_str -- TODO conf.ctx = crt and tlscontext(crt, key) conf.onstream = routes -- Create TLS context and start listening - local s, err = http_server.listen(conf) + local s, err = http_server.new(conf) -- Manually call :listen() so that we are bound before calling :localname() if s then err = select(2, s:listen()) @@ -377,14 +367,15 @@ function M.add_interface(conf) if err then panic('failed to listen on %s: %s', addr_str, err) end - table.insert(M.servers, {server = s, config = conf}) + M.servers[socket] = {kind = kind, server = s, config = conf} -- Create certificate renewal timer if ephemeral - if crt and ephemeral then + if crt and conf.ephemeral then local _, expiry = crt:getLifetime() expiry = math.max(0, expiry - (os.time() - 3 * 24 * 3600)) event.after(expiry, function () log('[http] refreshed ephemeral certificate') crt, key = updatecert(conf.cert, conf.key) + -- TODO servers sharing cert?! s.ctx = tlscontext(crt, key) end) end @@ -396,22 +387,44 @@ function M.init() if worker.id == 0 then worker.coroutine(prometheus.init) end + net.register_endpoint_kind('doh', cb_socket) + net.register_endpoint_kind('webmgmt', cb_socket) end -- @function Cleanup module function M.deinit() - for i, server in ipairs(M.servers) do - server.server:close() - M.servers[i] = nil + for socket, instance in pairs(M.servers) do + instance.server:close() + M.servers[socket] = nil + -- TODO stop refresh timer end prometheus.deinit() + net.register_endpoint_kind('doh') + net.register_endpoint_kind('webmgmt') +end + +-- @function Listen for config changes from net.listen()/net.close() +function cb_socket(...) + local added, endpoint, _ = unpack({...}) + endpoint = ffi.cast('struct endpoint **', endpoint)[0] + local kind = ffi.string(endpoint.flags.kind) + local socket = endpoint.fd + M.add_socket(socket, kind) end --- @function Configure module -function M.config(conf) - if conf == true then conf = {} end - assert(type(conf) == 'table', 'config { host = "...", port = 443, cert = "...", key = "..." }') - -- Configure web interface for resolver +-- @function Configure module, i.e. store configuration template and restart servers +-- kind = socket type +function M.config(conf, kind) + 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') + end + end if conf.geoip then if has_mmdb then M.geoip = mmdb.open(conf.geoip) @@ -419,7 +432,8 @@ function M.config(conf) error('[http] mmdblua library not found, please remove GeoIP configuration') end end - M.add_interface(conf) + M.templates[kind] = conf + -- TODO restart configured servers of this kind end return M