diff options
Diffstat (limited to '')
48 files changed, 7986 insertions, 0 deletions
diff --git a/daemon/lua/controlsock.test.lua b/daemon/lua/controlsock.test.lua new file mode 100644 index 0000000..0cce03d --- /dev/null +++ b/daemon/lua/controlsock.test.lua @@ -0,0 +1,169 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local cqsocket = require('cqueues.socket') +local strerror = require('cqueues.errno').strerror +local timeout = 5 -- seconds, per socket operation + +-- TODO: we get memory leaks from cqueues, but CI runs this without leak detection anyway + +local ctrl_sock_txt, ctrl_sock_bin, ctrl_sock_txt_longcmd, ctrl_sock_bin_longcmd +local ctrl_sock_txt_partcmd, ctrl_sock_bin_partcmd + +local function onerr_fail(_, method, errno, stacklevel) + local errmsg = string.format('socket error: method %s error %d (%s)', + method, errno, strerror(errno)) + fail(debug.traceback(errmsg, stacklevel)) +end + + +local function switch_to_binary_mode(sock) + data = sock:xread(2, nil, timeout) + sock:xwrite('__binary\n', nil, timeout) + same(data, '> ', 'probably successful switch to binary mode') +end + +local function socket_connect(path) + sock = cqsocket.connect({ path = path, nonblock = true }) + sock:onerror(onerr_fail) + sock:setmode('bn', 'bn') + + return sock +end + +local function socket_fixture() + local path = worker.cwd..'/control/'..worker.pid + same(true, net.listen(path, nil, {kind = 'control'}), 'new control sockets were created') + + ctrl_sock_txt = socket_connect(path) + ctrl_sock_txt_longcmd = socket_connect(path) + ctrl_sock_txt_partcmd = socket_connect(path) + + ctrl_sock_bin = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin) + ctrl_sock_bin_longcmd = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin_longcmd) + ctrl_sock_bin_partcmd = socket_connect(path) + switch_to_binary_mode(ctrl_sock_bin_partcmd) +end + +local function test_text_prompt() + data = ctrl_sock_txt:xread(2, nil, timeout) + same(data, '> ', 'text prompt looks like expected') +end + +local function test_text_single_command() + local string = "this is test" + local input = string.format("'%s'\n", string) + local expect = input + ctrl_sock_txt:xwrite(input, nil, timeout) + data = ctrl_sock_txt:xread(#expect, nil, timeout) + same(data, expect, + 'text mode returns output in expected format') +end + +local function binary_xread_len(sock) + data = sock:xread(4, nil, timeout) + local len = tonumber(data:byte(1)) + for i=2,4 do + len = bit.bor(bit.lshift(len, 8), tonumber(data:byte(i))) + end + + return len +end + +local function test_binary_more_syscalls() + local len + + ctrl_sock_bin:xwrite('worker.p', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('id\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + + ctrl_sock_bin:xwrite('worker.p', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('id\nworker.id\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, string.format("'%s'", worker.id), + 'binary mode returns string in expected format') + + ctrl_sock_bin:xwrite('worker.pid', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns output in expected format') + + ctrl_sock_bin:xwrite('worker.pid', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\nworker.id', nil, timeout) + worker.sleep(0.01) + ctrl_sock_bin:xwrite('\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, string.format("'%s'", worker.id), + 'binary mode returns string in expected format') + + ctrl_sock_bin:xwrite('worker.pid\nworker.pid\nworker.pid\nworker.pid\n', nil, timeout) + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') + len = binary_xread_len(ctrl_sock_bin) + data = ctrl_sock_bin:xread(len, nil, timeout) + same(data, tostring(worker.pid), + 'binary mode returns number in expected format') +end + +local function test_close_incomplete_cmd() + ctrl_sock_txt_partcmd:xwrite('worker.p', nil, timeout) + ctrl_sock_txt_partcmd:close() + pass('close text socket with short incomplete command') + + ctrl_sock_bin_partcmd:xwrite('worker.p', nil, timeout) + ctrl_sock_bin_partcmd:close() + pass('close binary socket with short incomplete command') +end + + +local function test_close_during_transfer() + ctrl_sock_txt_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout) + ctrl_sock_txt_longcmd:close() + pass('close text socket with long incomplete command') + + ctrl_sock_bin_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout) + ctrl_sock_bin_longcmd:close() + pass('close binary socket with long incomplete command') +end + +local tests = { + socket_fixture, + test_text_prompt, -- prompt after connect + test_text_single_command, + test_text_prompt, -- new prompt when command is finished + test_close_incomplete_cmd, + test_close_during_transfer, + test_binary_more_syscalls, + test_text_single_command, -- command in text mode after execute commands in binary mode + test_text_prompt, -- new prompt when command is finished +} +return tests diff --git a/daemon/lua/distro-preconfig.lua.in b/daemon/lua/distro-preconfig.lua.in new file mode 100644 index 0000000..df155c2 --- /dev/null +++ b/daemon/lua/distro-preconfig.lua.in @@ -0,0 +1,19 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +log_target('syslog') -- assume running as OS service + +local ffi = require('ffi') +local id = os.getenv('SYSTEMD_INSTANCE') +if not id then + log_warn(ffi.C.LOG_GRP_SYSTEM, 'environment variable $SYSTEMD_INSTANCE not set') +else + -- Bind to control socket in run_dir + worker.control_path = '@run_dir@/control/' + local path = worker.control_path..id + local ok, err = pcall(net.listen, path, nil, { kind = 'control' }) + if not ok then + log_warn(ffi.C.LOG_GRP_NETWORK, 'bind to '..path..' failed '..err) + end +end + +-- Set cache location +rawset(cache, 'current_storage', 'lmdb://@systemd_cache_dir@') diff --git a/daemon/lua/kluautil.lua b/daemon/lua/kluautil.lua new file mode 100644 index 0000000..d8569b9 --- /dev/null +++ b/daemon/lua/kluautil.lua @@ -0,0 +1,94 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') +local kluautil = {} + +-- Get length of table +function kluautil.kr_table_len(t) + if type(t) ~= 'table' then + return nil + end + + local len = 0 + for _ in pairs(t) do + len = len + 1 + end + return len +end + +-- pack varargs including nil arguments into a table +function kluautil.kr_table_pack(...) + local tab = {...} + tab.n = select('#', ...) + return tab +end + +-- unpack table produced by kr_table_pack and including nil values +function kluautil.kr_table_unpack(tab) + return unpack(tab, 1, tab.n) +end + +-- Fetch over HTTPS +function kluautil.kr_https_fetch(url, out_file, ca_file) + local http_ok, http_request = pcall(require, 'http.request') + local httptls_ok, http_tls = pcall(require, 'http.tls') + local openssl_ok, openssl_ctx = pcall(require, 'openssl.ssl.context') + + if not http_ok or not httptls_ok or not openssl_ok then + return nil, 'error: lua-http and luaossl libraries are missing (but required)' + end + local cqerrno = require('cqueues.errno') + + assert(string.match(url, '^https://')) + + local req = http_request.new_from_uri(url) + req.tls = true + if ca_file then + req.ctx = openssl_ctx.new() + local store = req.ctx:getStore() + local load_ok, errmsg = pcall(store.add, store, ca_file) + if not load_ok then + return nil, errmsg + end + else -- use defaults + req.ctx = http_tls.new_client_context() + end + + req.ctx:setVerify(openssl_ctx.VERIFY_PEER) + + local headers, stream, errmsg = req:go() + if not headers then + errmsg = errmsg or 'unknown error' + if type(errmsg) == 'number' then + errmsg = cqerrno.strerror(errmsg) .. + ' (' .. tostring(errmsg) .. ')' + end + return nil, 'HTTP client library error: ' .. errmsg + end + if headers:get(':status') ~= "200" then + return nil, 'HTTP status != 200, got ' .. headers:get(':status') + end + + local err + err, errmsg = stream:save_body_to_file(out_file) + if err == nil then + return nil, errmsg + end + + out_file:seek('set', 0) + + return true +end + +-- Copy a lua string to C (to knot_mm_t or nil=malloc, zero-terminated). +function kluautil.kr_string2c(str, mempool) + if str == nil then return nil end + local result = ffi.C.mm_realloc(mempool, nil, #str + 1, 0) + if result == nil then panic("not enough memory") end + ffi.copy(result, str) + return ffi.cast('const char *', result) +end + +kluautil.list_dir = kluautil_list_dir + +return kluautil diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua new file mode 100644 index 0000000..7639e79 --- /dev/null +++ b/daemon/lua/kres-gen-30.lua @@ -0,0 +1,641 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') +--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[ +typedef long time_t; +typedef long __time_t; +typedef long __suseconds_t; +struct timeval { + __time_t tv_sec; + __suseconds_t tv_usec; +}; + +typedef struct knot_dump_style knot_dump_style_t; +extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT; +struct kr_cdb_api {}; +struct lru {}; +typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t; +typedef struct { + uint16_t pos; + uint16_t flags; + uint16_t compress_ptr[16]; +} knot_rrinfo_t; +typedef unsigned char knot_dname_t; +typedef struct { + uint16_t len; + uint8_t data[]; +} knot_rdata_t; +typedef struct { + uint16_t count; + uint32_t size; + knot_rdata_t *rdata; +} knot_rdataset_t; + +typedef struct knot_mm { + void *ctx, *alloc, *free; +} knot_mm_t; + +typedef void *(*map_alloc_f)(void *, size_t); +typedef void (*map_free_f)(void *baton, void *ptr); +typedef void (*trace_log_f) (const struct kr_request *, const char *); +typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); +typedef bool (*addr_info_f)(struct sockaddr*); +typedef void (*zi_callback)(int state, void *param); +typedef struct { + knot_dname_t *_owner; + uint32_t _ttl; + uint16_t type; + uint16_t rclass; + knot_rdataset_t rrs; + void *additional; +} knot_rrset_t; + +struct kr_module; +typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *); +typedef unsigned char knot_dname_storage_t[255]; +typedef struct knot_pkt knot_pkt_t; +typedef struct { + uint8_t *ptr[15]; +} knot_edns_options_t; +typedef struct { + knot_pkt_t *pkt; + uint16_t pos; + uint16_t count; +} knot_pktsection_t; +typedef struct knot_compr { + uint8_t *wire; + knot_rrinfo_t *rrinfo; + struct { + uint16_t pos; + uint8_t labels; + } suffix; +} knot_compr_t; +struct knot_pkt { + uint8_t *wire; + size_t size; + size_t max_size; + size_t parsed; + uint16_t reserved; + uint16_t qname_size; + uint16_t rrset_count; + uint16_t flags; + knot_rrset_t *opt_rr; + knot_rrset_t *tsig_rr; + knot_edns_options_t *edns_opts; + struct { + uint8_t *pos; + size_t len; + } tsig_wire; + knot_section_t current; + knot_pktsection_t sections[3]; + size_t rrset_allocd; + knot_rrinfo_t *rr_info; + knot_rrset_t *rr; + knot_mm_t mm; + knot_compr_t compr; +}; +typedef struct trie trie_t; +struct kr_qflags { + _Bool NO_MINIMIZE : 1; + _Bool NO_IPV6 : 1; + _Bool NO_IPV4 : 1; + _Bool TCP : 1; + _Bool NO_ANSWER : 1; + _Bool RESOLVED : 1; + _Bool AWAIT_IPV4 : 1; + _Bool AWAIT_IPV6 : 1; + _Bool AWAIT_CUT : 1; + _Bool NO_EDNS : 1; + _Bool CACHED : 1; + _Bool NO_CACHE : 1; + _Bool EXPIRING : 1; + _Bool ALLOW_LOCAL : 1; + _Bool DNSSEC_WANT : 1; + _Bool DNSSEC_BOGUS : 1; + _Bool DNSSEC_INSECURE : 1; + _Bool DNSSEC_CD : 1; + _Bool STUB : 1; + _Bool ALWAYS_CUT : 1; + _Bool DNSSEC_WEXPAND : 1; + _Bool PERMISSIVE : 1; + _Bool STRICT : 1; + _Bool BADCOOKIE_AGAIN : 1; + _Bool CNAME : 1; + _Bool REORDER_RR : 1; + _Bool TRACE : 1; + _Bool NO_0X20 : 1; + _Bool DNSSEC_NODS : 1; + _Bool DNSSEC_OPTOUT : 1; + _Bool NONAUTH : 1; + _Bool FORWARD : 1; + _Bool DNS64_MARK : 1; + _Bool CACHE_TRIED : 1; + _Bool NO_NS_FOUND : 1; + _Bool PKT_IS_SANE : 1; + _Bool DNS64_DISABLE : 1; +}; +typedef struct ranked_rr_array_entry { + uint32_t qry_uid; + uint8_t rank; + uint8_t revalidation_cnt; + _Bool cached : 1; + _Bool yielded : 1; + _Bool to_wire : 1; + _Bool expiring : 1; + _Bool in_progress : 1; + _Bool dont_cache : 1; + knot_rrset_t *rr; +} ranked_rr_array_entry_t; +typedef struct { + ranked_rr_array_entry_t **at; + size_t len; + size_t cap; +} ranked_rr_array_t; +typedef struct kr_http_header_array_entry { + char *name; + char *value; +} kr_http_header_array_entry_t; +typedef struct { + kr_http_header_array_entry_t *at; + size_t len; + size_t cap; +} kr_http_header_array_t; +typedef struct { + union kr_sockaddr *at; + size_t len; + size_t cap; +} kr_sockaddr_array_t; +struct kr_zonecut { + knot_dname_t *name; + knot_rrset_t *key; + knot_rrset_t *trust_anchor; + struct kr_zonecut *parent; + trie_t *nsset; + knot_mm_t *pool; +}; +typedef struct { + struct kr_query **at; + size_t len; + size_t cap; +} kr_qarray_t; +struct kr_rplan { + kr_qarray_t pending; + kr_qarray_t resolved; + struct kr_query *initial; + struct kr_request *request; + knot_mm_t *pool; + uint32_t next_uid; +}; +struct kr_request_qsource_flags { + _Bool tcp : 1; + _Bool tls : 1; + _Bool http : 1; + _Bool xdp : 1; +}; +struct kr_extended_error { + int32_t info_code; + const char *extra_text; +}; +struct kr_request { + struct kr_context *ctx; + knot_pkt_t *answer; + struct kr_query *current_query; + struct { + const struct sockaddr *addr; + const struct sockaddr *comm_addr; + const struct sockaddr *dst_addr; + const knot_pkt_t *packet; + struct kr_request_qsource_flags flags; + struct kr_request_qsource_flags comm_flags; + size_t size; + int32_t stream_id; + kr_http_header_array_t headers; + } qsource; + struct { + unsigned int rtt; + const struct kr_transport *transport; + } upstream; + struct kr_qflags options; + int state; + ranked_rr_array_t answ_selected; + ranked_rr_array_t auth_selected; + ranked_rr_array_t add_selected; + _Bool answ_validated; + _Bool auth_validated; + uint8_t rank; + struct kr_rplan rplan; + trace_log_f trace_log; + trace_callback_f trace_finish; + int vars_ref; + knot_mm_t pool; + unsigned int uid; + struct { + addr_info_f is_tls_capable; + addr_info_f is_tcp_connected; + addr_info_f is_tcp_waiting; + kr_sockaddr_array_t forwarding_targets; + } selection_context; + unsigned int count_no_nsaddr; + unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; + struct kr_extended_error extended_error; +}; +enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32}; +typedef struct kr_cdb * kr_cdb_pt; +struct kr_cdb_stats { + uint64_t open; + uint64_t close; + uint64_t count; + uint64_t count_entries; + uint64_t clear; + uint64_t commit; + uint64_t read; + uint64_t read_miss; + uint64_t write; + uint64_t remove; + uint64_t remove_miss; + uint64_t match; + uint64_t match_miss; + uint64_t read_leq; + uint64_t read_leq_miss; + double usage_percent; +}; +typedef struct uv_timer_s uv_timer_t; +struct kr_cache { + kr_cdb_pt db; + const struct kr_cdb_api *api; + struct kr_cdb_stats stats; + uint32_t ttl_min; + uint32_t ttl_max; + struct timeval checkpoint_walltime; + uint64_t checkpoint_monotime; + uv_timer_t *health_timer; +}; +typedef struct kr_layer { + int state; + struct kr_request *req; + const struct kr_layer_api *api; + knot_pkt_t *pkt; + struct sockaddr *dst; + _Bool is_stream; +} kr_layer_t; +typedef struct kr_layer_api { + int (*begin)(kr_layer_t *); + int (*reset)(kr_layer_t *); + int (*finish)(kr_layer_t *); + int (*consume)(kr_layer_t *, knot_pkt_t *); + int (*produce)(kr_layer_t *, knot_pkt_t *); + int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int); + int (*answer_finalize)(kr_layer_t *); + void *data; + int cb_slots[]; +} kr_layer_api_t; +struct kr_prop { + kr_prop_cb *cb; + const char *name; + const char *info; +}; +struct kr_module { + char *name; + int (*init)(struct kr_module *); + int (*deinit)(struct kr_module *); + int (*config)(struct kr_module *, const char *); + const kr_layer_api_t *layer; + const struct kr_prop *props; + void *lib; + void *data; +}; +struct kr_server_selection { + _Bool initialized; + void (*choose_transport)(struct kr_query *, struct kr_transport **); + void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int); + void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error); + struct local_state *local_state; +}; +typedef int kr_log_level_t; +enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG}; + +kr_layer_t kr_layer_t_static; +_Bool kr_dbg_assertion_abort; +int kr_dbg_assertion_fork; + +typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type, + const struct kr_query *qry); + +void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl); +struct kr_query { + struct kr_query *parent; + knot_dname_t *sname; + uint16_t stype; + uint16_t sclass; + uint16_t id; + uint16_t reorder; + struct kr_qflags flags; + struct kr_qflags forward_flags; + uint32_t secret; + uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; + uint64_t creation_time_mono; + uint64_t timestamp_mono; + struct timeval timestamp; + struct kr_zonecut zone_cut; + struct kr_layer_pickle *deferred; + int8_t cname_depth; + struct kr_query *cname_parent; + struct kr_request *request; + kr_stale_cb stale_cb; + struct kr_server_selection server_selection; +}; +struct kr_context { + struct kr_qflags options; + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; + trie_t *trust_anchors; + trie_t *negative_anchors; + int32_t vld_limit_crypto; + struct kr_zonecut root_hints; + struct kr_cache cache; + unsigned int cache_rtt_tout_retry_interval; + char _stub[]; +}; +struct kr_transport { + knot_dname_t *ns_name; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +const char *knot_strerror(int); +knot_dname_t *knot_dname_copy(const knot_dname_t *, knot_mm_t *); +knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t); +int knot_dname_in_bailiwick(const knot_dname_t *, const knot_dname_t *); +_Bool knot_dname_is_equal(const knot_dname_t *, const knot_dname_t *); +size_t knot_dname_labels(const uint8_t *, const uint8_t *); +size_t knot_dname_size(const knot_dname_t *); +void knot_dname_to_lower(knot_dname_t *); +char *knot_dname_to_str(char *, const knot_dname_t *, size_t); +knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *, uint16_t); +int knot_rdataset_merge(knot_rdataset_t *, const knot_rdataset_t *, knot_mm_t *); +int knot_rrset_add_rdata(knot_rrset_t *, const uint8_t *, uint16_t, knot_mm_t *); +void knot_rrset_free(knot_rrset_t *, knot_mm_t *); +int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *); +int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *); +size_t knot_rrset_size(const knot_rrset_t *); +int knot_pkt_begin(knot_pkt_t *, knot_section_t); +int knot_pkt_put_question(knot_pkt_t *, const knot_dname_t *, uint16_t, uint16_t); +int knot_pkt_put_rotate(knot_pkt_t *, uint16_t, const knot_rrset_t *, uint16_t, uint16_t); +knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *); +void knot_pkt_free(knot_pkt_t *); +int knot_pkt_parse(knot_pkt_t *, unsigned int); +knot_rrset_t *kr_request_ensure_edns(struct kr_request *); +knot_pkt_t *kr_request_ensure_answer(struct kr_request *); +int kr_request_set_extended_error(struct kr_request *, int, const char *); +struct kr_rplan *kr_resolve_plan(struct kr_request *); +knot_mm_t *kr_resolve_pool(struct kr_request *); +struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t); +int kr_rplan_pop(struct kr_rplan *, struct kr_query *); +struct kr_query *kr_rplan_resolved(struct kr_rplan *); +struct kr_query *kr_rplan_last(struct kr_rplan *); +int kr_forward_add_target(struct kr_request *, const struct sockaddr *); +_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *); +void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...); +void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...); +const char *kr_log_grp2name(enum kr_log_group); +void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...); +int kr_make_query(struct kr_query *, knot_pkt_t *); +void kr_pkt_make_auth_header(knot_pkt_t *); +int kr_pkt_put(knot_pkt_t *, const knot_dname_t *, uint32_t, uint16_t, uint16_t, const uint8_t *, uint16_t); +int kr_pkt_recycle(knot_pkt_t *); +int kr_pkt_clear_payload(knot_pkt_t *); +_Bool kr_pkt_has_wire(const knot_pkt_t *); +_Bool kr_pkt_has_dnssec(const knot_pkt_t *); +uint16_t kr_pkt_qclass(const knot_pkt_t *); +uint16_t kr_pkt_qtype(const knot_pkt_t *); +char *kr_pkt_text(const knot_pkt_t *); +void kr_rnd_buffered(void *, unsigned int); +uint32_t kr_rrsig_sig_inception(const knot_rdata_t *); +uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *); +uint16_t kr_rrsig_type_covered(const knot_rdata_t *); +const char *kr_inaddr(const struct sockaddr *); +int kr_inaddr_family(const struct sockaddr *); +int kr_inaddr_len(const struct sockaddr *); +int kr_inaddr_str(const struct sockaddr *, char *, size_t *); +int kr_sockaddr_cmp(const struct sockaddr *, const struct sockaddr *); +int kr_sockaddr_len(const struct sockaddr *); +uint16_t kr_inaddr_port(const struct sockaddr *); +int kr_straddr_family(const char *); +int kr_straddr_subnet(void *, const char *); +int kr_bitcmp(const char *, const char *, int); +int kr_family_len(int); +struct sockaddr *kr_straddr_socket(const char *, int, knot_mm_t *); +int kr_straddr_split(const char *, char * restrict, uint16_t *); +_Bool kr_rank_test(uint8_t, uint8_t); +int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *); +int kr_ranked_rrarray_finalize(ranked_rr_array_t *, uint32_t, knot_mm_t *); +void kr_qflags_set(struct kr_qflags *, struct kr_qflags); +void kr_qflags_clear(struct kr_qflags *, struct kr_qflags); +int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const void *, int); +_Bool kr_zonecut_is_empty(struct kr_zonecut *); +void kr_zonecut_set(struct kr_zonecut *, const knot_dname_t *); +uint64_t kr_now(void); +const char *kr_strptime_diff(const char *, const char *, const char *, double *); +time_t kr_file_mtime(const char *); +long long kr_fssize(const char *); +const char *kr_dirent_name(const struct dirent *); +void lru_free_items_impl(struct lru *); +struct lru *lru_create_impl(unsigned int, unsigned int, knot_mm_t *, knot_mm_t *); +void *lru_get_impl(struct lru *, const char *, unsigned int, unsigned int, _Bool, _Bool *); +void *mm_realloc(knot_mm_t *, void *, size_t, size_t); +knot_rrset_t *kr_ta_get(trie_t *, const knot_dname_t *); +int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t); +int kr_ta_del(trie_t *, const knot_dname_t *); +void kr_ta_clear(trie_t *); +_Bool kr_dnssec_key_ksk(const uint8_t *); +_Bool kr_dnssec_key_revoked(const uint8_t *); +int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t); +int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t); +int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool, knot_dname_t **); +int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t, _Bool); +int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t); +int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int); +int kr_cache_commit(struct kr_cache *); +uint32_t packet_ttl(const knot_pkt_t *); +typedef struct { + int sock_type; + _Bool tls; + _Bool http; + _Bool xdp; + _Bool freebind; + const char *kind; +} endpoint_flags_t; +typedef struct { + char **at; + size_t len; + size_t cap; +} addr_array_t; +typedef struct { + int fd; + endpoint_flags_t flags; +} flagged_fd_t; +typedef struct { + flagged_fd_t *at; + size_t len; + size_t cap; +} flagged_fd_array_t; +typedef struct { + const char **at; + size_t len; + size_t cap; +} config_array_t; +struct args { + addr_array_t addrs; + addr_array_t addrs_tls; + flagged_fd_array_t fds; + int control_fd; + int forks; + config_array_t config; + const char *rundir; + _Bool interactive; + _Bool quiet; + _Bool tty_binary_output; +}; +typedef struct { + const char *zone_file; + const char *origin; + uint32_t ttl; + enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src; + _Bool downgrade; + _Bool zonemd; + const knot_rrset_t *ds; + zi_callback cb; + void *cb_param; +} zi_config_t; +struct args *the_args; +struct endpoint { + void *handle; + int fd; + int family; + uint16_t port; + int16_t nic_queue; + _Bool engaged; + endpoint_flags_t flags; +}; +struct request_ctx { + struct kr_request req; + struct worker_ctx *worker; + struct qr_task *task; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +struct qr_task { + struct request_ctx *ctx; + /* beware: hidden stub, to avoid qr_tasklist_t */ +}; +int worker_resolve_exec(struct qr_task *, knot_pkt_t *); +knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *); +struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); +int zi_zone_import(const zi_config_t); +struct engine { + struct kr_context resolver; + char _stub[]; +}; +struct worker_ctx { + struct engine *engine; + char _stub[]; +}; +struct worker_ctx *the_worker; +typedef struct { + uint8_t bitmap[32]; + uint8_t length; +} zs_win_t; +typedef struct { + uint8_t excl_flag; + uint16_t addr_family; + uint8_t prefix_length; +} zs_apl_t; +typedef struct { + uint32_t d1; + uint32_t d2; + uint32_t m1; + uint32_t m2; + uint32_t s1; + uint32_t s2; + uint32_t alt; + uint64_t siz; + uint64_t hp; + uint64_t vp; + int8_t lat_sign; + int8_t long_sign; + int8_t alt_sign; +} zs_loc_t; +typedef enum {ZS_STATE_NONE, ZS_STATE_DATA, ZS_STATE_ERROR, ZS_STATE_INCLUDE, ZS_STATE_EOF, ZS_STATE_STOP} zs_state_t; +typedef struct zs_scanner zs_scanner_t; +typedef struct zs_scanner { + int cs; + int top; + int stack[16]; + _Bool multiline; + uint64_t number64; + uint64_t number64_tmp; + uint32_t decimals; + uint32_t decimal_counter; + uint32_t item_length; + uint32_t item_length_position; + uint8_t *item_length_location; + uint32_t buffer_length; + uint8_t buffer[65535]; + char include_filename[65535]; + char *path; + zs_win_t windows[256]; + int16_t last_window; + zs_apl_t apl; + zs_loc_t loc; + uint8_t addr[16]; + _Bool long_string; + uint8_t *dname; + uint32_t *dname_length; + uint32_t dname_tmp_length; + uint32_t r_data_tail; + uint32_t zone_origin_length; + uint8_t zone_origin[318]; + uint16_t default_class; + uint32_t default_ttl; + zs_state_t state; + struct { + _Bool automatic; + void (*record)(zs_scanner_t *); + void (*error)(zs_scanner_t *); + void (*comment)(zs_scanner_t *); + void *data; + } process; + struct { + const char *start; + const char *current; + const char *end; + _Bool eof; + _Bool mmaped; + } input; + struct { + char *name; + int descriptor; + } file; + struct { + int code; + uint64_t counter; + _Bool fatal; + } error; + uint64_t line_counter; + uint32_t r_owner_length; + uint8_t r_owner[318]; + uint16_t r_class; + uint32_t r_ttl; + uint16_t r_type; + uint32_t r_data_length; + uint8_t r_data[65535]; +} zs_scanner_t; +void zs_deinit(zs_scanner_t *); +int zs_init(zs_scanner_t *, const char *, const uint16_t, const uint32_t); +int zs_parse_record(zs_scanner_t *); +int zs_set_input_file(zs_scanner_t *, const char *); +int zs_set_input_string(zs_scanner_t *, const char *, size_t); +const char *zs_strerror(const int); +]] diff --git a/daemon/lua/kres-gen-31.lua b/daemon/lua/kres-gen-31.lua new file mode 100644 index 0000000..e555a6a --- /dev/null +++ b/daemon/lua/kres-gen-31.lua @@ -0,0 +1,650 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') +--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[ +typedef long time_t; +typedef long __time_t; +typedef long __suseconds_t; +struct timeval { + __time_t tv_sec; + __suseconds_t tv_usec; +}; + +typedef struct knot_dump_style knot_dump_style_t; +extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT; +struct kr_cdb_api {}; +struct lru {}; +typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t; +typedef struct { + uint16_t pos; + uint16_t flags; + uint16_t compress_ptr[16]; +} knot_rrinfo_t; +typedef unsigned char knot_dname_t; +typedef struct { + uint16_t len; + uint8_t data[]; +} knot_rdata_t; +typedef struct { + uint16_t count; + uint32_t size; + knot_rdata_t *rdata; +} knot_rdataset_t; + +typedef struct knot_mm { + void *ctx, *alloc, *free; +} knot_mm_t; + +typedef void *(*map_alloc_f)(void *, size_t); +typedef void (*map_free_f)(void *baton, void *ptr); +typedef void (*trace_log_f) (const struct kr_request *, const char *); +typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); +typedef bool (*addr_info_f)(struct sockaddr*); +typedef void (*zi_callback)(int state, void *param); +typedef struct { + knot_dname_t *_owner; + uint32_t _ttl; + uint16_t type; + uint16_t rclass; + knot_rdataset_t rrs; + void *additional; +} knot_rrset_t; + +struct kr_module; +typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *); +typedef unsigned char knot_dname_storage_t[255]; +typedef struct knot_pkt knot_pkt_t; +typedef struct { + uint8_t *ptr[18]; +} knot_edns_options_t; +typedef struct { + knot_pkt_t *pkt; + uint16_t pos; + uint16_t count; +} knot_pktsection_t; +typedef struct knot_compr { + uint8_t *wire; + knot_rrinfo_t *rrinfo; + struct { + uint16_t pos; + uint8_t labels; + } suffix; +} knot_compr_t; +struct knot_pkt { + uint8_t *wire; + size_t size; + size_t max_size; + size_t parsed; + uint16_t reserved; + uint16_t qname_size; + uint16_t rrset_count; + uint16_t flags; + knot_rrset_t *opt_rr; + knot_rrset_t *tsig_rr; + knot_edns_options_t *edns_opts; + struct { + uint8_t *pos; + size_t len; + } tsig_wire; + knot_section_t current; + knot_pktsection_t sections[3]; + size_t rrset_allocd; + knot_rrinfo_t *rr_info; + knot_rrset_t *rr; + knot_mm_t mm; + knot_compr_t compr; +}; +typedef struct trie trie_t; +struct kr_qflags { + _Bool NO_MINIMIZE : 1; + _Bool NO_IPV6 : 1; + _Bool NO_IPV4 : 1; + _Bool TCP : 1; + _Bool NO_ANSWER : 1; + _Bool RESOLVED : 1; + _Bool AWAIT_IPV4 : 1; + _Bool AWAIT_IPV6 : 1; + _Bool AWAIT_CUT : 1; + _Bool NO_EDNS : 1; + _Bool CACHED : 1; + _Bool NO_CACHE : 1; + _Bool EXPIRING : 1; + _Bool ALLOW_LOCAL : 1; + _Bool DNSSEC_WANT : 1; + _Bool DNSSEC_BOGUS : 1; + _Bool DNSSEC_INSECURE : 1; + _Bool DNSSEC_CD : 1; + _Bool STUB : 1; + _Bool ALWAYS_CUT : 1; + _Bool DNSSEC_WEXPAND : 1; + _Bool PERMISSIVE : 1; + _Bool STRICT : 1; + _Bool BADCOOKIE_AGAIN : 1; + _Bool CNAME : 1; + _Bool REORDER_RR : 1; + _Bool TRACE : 1; + _Bool NO_0X20 : 1; + _Bool DNSSEC_NODS : 1; + _Bool DNSSEC_OPTOUT : 1; + _Bool NONAUTH : 1; + _Bool FORWARD : 1; + _Bool DNS64_MARK : 1; + _Bool CACHE_TRIED : 1; + _Bool NO_NS_FOUND : 1; + _Bool PKT_IS_SANE : 1; + _Bool DNS64_DISABLE : 1; +}; +typedef struct ranked_rr_array_entry { + uint32_t qry_uid; + uint8_t rank; + uint8_t revalidation_cnt; + _Bool cached : 1; + _Bool yielded : 1; + _Bool to_wire : 1; + _Bool expiring : 1; + _Bool in_progress : 1; + _Bool dont_cache : 1; + knot_rrset_t *rr; +} ranked_rr_array_entry_t; +typedef struct { + ranked_rr_array_entry_t **at; + size_t len; + size_t cap; +} ranked_rr_array_t; +typedef struct kr_http_header_array_entry { + char *name; + char *value; +} kr_http_header_array_entry_t; +typedef struct { + kr_http_header_array_entry_t *at; + size_t len; + size_t cap; +} kr_http_header_array_t; +typedef struct { + union kr_sockaddr *at; + size_t len; + size_t cap; +} kr_sockaddr_array_t; +struct kr_zonecut { + knot_dname_t *name; + knot_rrset_t *key; + knot_rrset_t *trust_anchor; + struct kr_zonecut *parent; + trie_t *nsset; + knot_mm_t *pool; +}; +typedef struct { + struct kr_query **at; + size_t len; + size_t cap; +} kr_qarray_t; +struct kr_rplan { + kr_qarray_t pending; + kr_qarray_t resolved; + struct kr_query *initial; + struct kr_request *request; + knot_mm_t *pool; + uint32_t next_uid; +}; +struct kr_request_qsource_flags { + _Bool tcp : 1; + _Bool tls : 1; + _Bool http : 1; + _Bool xdp : 1; +}; +struct kr_extended_error { + int32_t info_code; + const char *extra_text; +}; +struct kr_request { + struct kr_context *ctx; + knot_pkt_t *answer; + struct kr_query *current_query; + struct { + const struct sockaddr *addr; + const struct sockaddr *comm_addr; + const struct sockaddr *dst_addr; + const knot_pkt_t *packet; + struct kr_request_qsource_flags flags; + struct kr_request_qsource_flags comm_flags; + size_t size; + int32_t stream_id; + kr_http_header_array_t headers; + } qsource; + struct { + unsigned int rtt; + const struct kr_transport *transport; + } upstream; + struct kr_qflags options; + int state; + ranked_rr_array_t answ_selected; + ranked_rr_array_t auth_selected; + ranked_rr_array_t add_selected; + _Bool answ_validated; + _Bool auth_validated; + uint8_t rank; + struct kr_rplan rplan; + trace_log_f trace_log; + trace_callback_f trace_finish; + int vars_ref; + knot_mm_t pool; + unsigned int uid; + struct { + addr_info_f is_tls_capable; + addr_info_f is_tcp_connected; + addr_info_f is_tcp_waiting; + kr_sockaddr_array_t forwarding_targets; + } selection_context; + unsigned int count_no_nsaddr; + unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; + struct kr_extended_error extended_error; +}; +enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32}; +typedef struct kr_cdb * kr_cdb_pt; +struct kr_cdb_stats { + uint64_t open; + uint64_t close; + uint64_t count; + uint64_t count_entries; + uint64_t clear; + uint64_t commit; + uint64_t read; + uint64_t read_miss; + uint64_t write; + uint64_t remove; + uint64_t remove_miss; + uint64_t match; + uint64_t match_miss; + uint64_t read_leq; + uint64_t read_leq_miss; + double usage_percent; +}; +typedef struct uv_timer_s uv_timer_t; +struct kr_cache { + kr_cdb_pt db; + const struct kr_cdb_api *api; + struct kr_cdb_stats stats; + uint32_t ttl_min; + uint32_t ttl_max; + struct timeval checkpoint_walltime; + uint64_t checkpoint_monotime; + uv_timer_t *health_timer; +}; +typedef struct kr_layer { + int state; + struct kr_request *req; + const struct kr_layer_api *api; + knot_pkt_t *pkt; + struct sockaddr *dst; + _Bool is_stream; +} kr_layer_t; +typedef struct kr_layer_api { + int (*begin)(kr_layer_t *); + int (*reset)(kr_layer_t *); + int (*finish)(kr_layer_t *); + int (*consume)(kr_layer_t *, knot_pkt_t *); + int (*produce)(kr_layer_t *, knot_pkt_t *); + int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int); + int (*answer_finalize)(kr_layer_t *); + void *data; + int cb_slots[]; +} kr_layer_api_t; +struct kr_prop { + kr_prop_cb *cb; + const char *name; + const char *info; +}; +struct kr_module { + char *name; + int (*init)(struct kr_module *); + int (*deinit)(struct kr_module *); + int (*config)(struct kr_module *, const char *); + const kr_layer_api_t *layer; + const struct kr_prop *props; + void *lib; + void *data; +}; +struct kr_server_selection { + _Bool initialized; + void (*choose_transport)(struct kr_query *, struct kr_transport **); + void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int); + void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error); + struct local_state *local_state; +}; +typedef int kr_log_level_t; +enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG}; + +kr_layer_t kr_layer_t_static; +_Bool kr_dbg_assertion_abort; +int kr_dbg_assertion_fork; + +typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type, + const struct kr_query *qry); + +void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl); +struct kr_query { + struct kr_query *parent; + knot_dname_t *sname; + uint16_t stype; + uint16_t sclass; + uint16_t id; + uint16_t reorder; + struct kr_qflags flags; + struct kr_qflags forward_flags; + uint32_t secret; + uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; + uint64_t creation_time_mono; + uint64_t timestamp_mono; + struct timeval timestamp; + struct kr_zonecut zone_cut; + struct kr_layer_pickle *deferred; + int8_t cname_depth; + struct kr_query *cname_parent; + struct kr_request *request; + kr_stale_cb stale_cb; + struct kr_server_selection server_selection; +}; +struct kr_context { + struct kr_qflags options; + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; + trie_t *trust_anchors; + trie_t *negative_anchors; + int32_t vld_limit_crypto; + struct kr_zonecut root_hints; + struct kr_cache cache; + unsigned int cache_rtt_tout_retry_interval; + char _stub[]; +}; +struct kr_transport { + knot_dname_t *ns_name; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +const char *knot_strerror(int); +knot_dname_t *knot_dname_copy(const knot_dname_t *, knot_mm_t *); +knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t); +int knot_dname_in_bailiwick(const knot_dname_t *, const knot_dname_t *); +_Bool knot_dname_is_equal(const knot_dname_t *, const knot_dname_t *); +size_t knot_dname_labels(const uint8_t *, const uint8_t *); +size_t knot_dname_size(const knot_dname_t *); +void knot_dname_to_lower(knot_dname_t *); +char *knot_dname_to_str(char *, const knot_dname_t *, size_t); +knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *, uint16_t); +int knot_rdataset_merge(knot_rdataset_t *, const knot_rdataset_t *, knot_mm_t *); +int knot_rrset_add_rdata(knot_rrset_t *, const uint8_t *, uint16_t, knot_mm_t *); +void knot_rrset_free(knot_rrset_t *, knot_mm_t *); +int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *); +int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *); +size_t knot_rrset_size(const knot_rrset_t *); +int knot_pkt_begin(knot_pkt_t *, knot_section_t); +int knot_pkt_put_question(knot_pkt_t *, const knot_dname_t *, uint16_t, uint16_t); +int knot_pkt_put_rotate(knot_pkt_t *, uint16_t, const knot_rrset_t *, uint16_t, uint16_t); +knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *); +void knot_pkt_free(knot_pkt_t *); +int knot_pkt_parse(knot_pkt_t *, unsigned int); +knot_rrset_t *kr_request_ensure_edns(struct kr_request *); +knot_pkt_t *kr_request_ensure_answer(struct kr_request *); +int kr_request_set_extended_error(struct kr_request *, int, const char *); +struct kr_rplan *kr_resolve_plan(struct kr_request *); +knot_mm_t *kr_resolve_pool(struct kr_request *); +struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t); +int kr_rplan_pop(struct kr_rplan *, struct kr_query *); +struct kr_query *kr_rplan_resolved(struct kr_rplan *); +struct kr_query *kr_rplan_last(struct kr_rplan *); +int kr_forward_add_target(struct kr_request *, const struct sockaddr *); +_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *); +void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...); +void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...); +const char *kr_log_grp2name(enum kr_log_group); +void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...); +int kr_make_query(struct kr_query *, knot_pkt_t *); +void kr_pkt_make_auth_header(knot_pkt_t *); +int kr_pkt_put(knot_pkt_t *, const knot_dname_t *, uint32_t, uint16_t, uint16_t, const uint8_t *, uint16_t); +int kr_pkt_recycle(knot_pkt_t *); +int kr_pkt_clear_payload(knot_pkt_t *); +_Bool kr_pkt_has_wire(const knot_pkt_t *); +_Bool kr_pkt_has_dnssec(const knot_pkt_t *); +uint16_t kr_pkt_qclass(const knot_pkt_t *); +uint16_t kr_pkt_qtype(const knot_pkt_t *); +char *kr_pkt_text(const knot_pkt_t *); +void kr_rnd_buffered(void *, unsigned int); +uint32_t kr_rrsig_sig_inception(const knot_rdata_t *); +uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *); +uint16_t kr_rrsig_type_covered(const knot_rdata_t *); +const char *kr_inaddr(const struct sockaddr *); +int kr_inaddr_family(const struct sockaddr *); +int kr_inaddr_len(const struct sockaddr *); +int kr_inaddr_str(const struct sockaddr *, char *, size_t *); +int kr_sockaddr_cmp(const struct sockaddr *, const struct sockaddr *); +int kr_sockaddr_len(const struct sockaddr *); +uint16_t kr_inaddr_port(const struct sockaddr *); +int kr_straddr_family(const char *); +int kr_straddr_subnet(void *, const char *); +int kr_bitcmp(const char *, const char *, int); +int kr_family_len(int); +struct sockaddr *kr_straddr_socket(const char *, int, knot_mm_t *); +int kr_straddr_split(const char *, char * restrict, uint16_t *); +_Bool kr_rank_test(uint8_t, uint8_t); +int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *); +int kr_ranked_rrarray_finalize(ranked_rr_array_t *, uint32_t, knot_mm_t *); +void kr_qflags_set(struct kr_qflags *, struct kr_qflags); +void kr_qflags_clear(struct kr_qflags *, struct kr_qflags); +int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const void *, int); +_Bool kr_zonecut_is_empty(struct kr_zonecut *); +void kr_zonecut_set(struct kr_zonecut *, const knot_dname_t *); +uint64_t kr_now(void); +const char *kr_strptime_diff(const char *, const char *, const char *, double *); +time_t kr_file_mtime(const char *); +long long kr_fssize(const char *); +const char *kr_dirent_name(const struct dirent *); +void lru_free_items_impl(struct lru *); +struct lru *lru_create_impl(unsigned int, unsigned int, knot_mm_t *, knot_mm_t *); +void *lru_get_impl(struct lru *, const char *, unsigned int, unsigned int, _Bool, _Bool *); +void *mm_realloc(knot_mm_t *, void *, size_t, size_t); +knot_rrset_t *kr_ta_get(trie_t *, const knot_dname_t *); +int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t); +int kr_ta_del(trie_t *, const knot_dname_t *); +void kr_ta_clear(trie_t *); +_Bool kr_dnssec_key_ksk(const uint8_t *); +_Bool kr_dnssec_key_revoked(const uint8_t *); +int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t); +int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t); +int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool, knot_dname_t **); +int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t, _Bool); +int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t); +int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int); +int kr_cache_commit(struct kr_cache *); +uint32_t packet_ttl(const knot_pkt_t *); +typedef struct { + int sock_type; + _Bool tls; + _Bool http; + _Bool xdp; + _Bool freebind; + const char *kind; +} endpoint_flags_t; +typedef struct { + char **at; + size_t len; + size_t cap; +} addr_array_t; +typedef struct { + int fd; + endpoint_flags_t flags; +} flagged_fd_t; +typedef struct { + flagged_fd_t *at; + size_t len; + size_t cap; +} flagged_fd_array_t; +typedef struct { + const char **at; + size_t len; + size_t cap; +} config_array_t; +struct args { + addr_array_t addrs; + addr_array_t addrs_tls; + flagged_fd_array_t fds; + int control_fd; + int forks; + config_array_t config; + const char *rundir; + _Bool interactive; + _Bool quiet; + _Bool tty_binary_output; +}; +typedef struct { + const char *zone_file; + const char *origin; + uint32_t ttl; + enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src; + _Bool downgrade; + _Bool zonemd; + const knot_rrset_t *ds; + zi_callback cb; + void *cb_param; +} zi_config_t; +struct args *the_args; +struct endpoint { + void *handle; + int fd; + int family; + uint16_t port; + int16_t nic_queue; + _Bool engaged; + endpoint_flags_t flags; +}; +struct request_ctx { + struct kr_request req; + struct worker_ctx *worker; + struct qr_task *task; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +struct qr_task { + struct request_ctx *ctx; + /* beware: hidden stub, to avoid qr_tasklist_t */ +}; +int worker_resolve_exec(struct qr_task *, knot_pkt_t *); +knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *); +struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); +int zi_zone_import(const zi_config_t); +struct engine { + struct kr_context resolver; + char _stub[]; +}; +struct worker_ctx { + struct engine *engine; + char _stub[]; +}; +struct worker_ctx *the_worker; +typedef struct { + uint8_t *params_position; + uint8_t *mandatory_position; + uint8_t *param_position; + int32_t last_key; +} zs_svcb_t; +typedef struct { + uint8_t bitmap[32]; + uint8_t length; +} zs_win_t; +typedef struct { + uint8_t excl_flag; + uint16_t addr_family; + uint8_t prefix_length; +} zs_apl_t; +typedef struct { + uint32_t d1; + uint32_t d2; + uint32_t m1; + uint32_t m2; + uint32_t s1; + uint32_t s2; + uint32_t alt; + uint64_t siz; + uint64_t hp; + uint64_t vp; + int8_t lat_sign; + int8_t long_sign; + int8_t alt_sign; +} zs_loc_t; +typedef enum {ZS_STATE_NONE, ZS_STATE_DATA, ZS_STATE_ERROR, ZS_STATE_INCLUDE, ZS_STATE_EOF, ZS_STATE_STOP} zs_state_t; +typedef struct zs_scanner zs_scanner_t; +typedef struct zs_scanner { + int cs; + int top; + int stack[16]; + _Bool multiline; + uint64_t number64; + uint64_t number64_tmp; + uint32_t decimals; + uint32_t decimal_counter; + uint32_t item_length; + uint32_t item_length_position; + uint8_t *item_length_location; + uint8_t *item_length2_location; + uint32_t buffer_length; + uint8_t buffer[65535]; + char include_filename[65535]; + char *path; + zs_win_t windows[256]; + int16_t last_window; + zs_apl_t apl; + zs_loc_t loc; + zs_svcb_t svcb; + uint8_t addr[16]; + _Bool long_string; + _Bool comma_list; + uint8_t *dname; + uint32_t *dname_length; + uint32_t dname_tmp_length; + uint32_t r_data_tail; + uint32_t zone_origin_length; + uint8_t zone_origin[318]; + uint16_t default_class; + uint32_t default_ttl; + zs_state_t state; + struct { + _Bool automatic; + void (*record)(zs_scanner_t *); + void (*error)(zs_scanner_t *); + void (*comment)(zs_scanner_t *); + void *data; + } process; + struct { + const char *start; + const char *current; + const char *end; + _Bool eof; + _Bool mmaped; + } input; + struct { + char *name; + int descriptor; + } file; + struct { + int code; + uint64_t counter; + _Bool fatal; + } error; + uint64_t line_counter; + uint32_t r_owner_length; + uint8_t r_owner[318]; + uint16_t r_class; + uint32_t r_ttl; + uint16_t r_type; + uint32_t r_data_length; + uint8_t r_data[65535]; +} zs_scanner_t; +void zs_deinit(zs_scanner_t *); +int zs_init(zs_scanner_t *, const char *, const uint16_t, const uint32_t); +int zs_parse_record(zs_scanner_t *); +int zs_set_input_file(zs_scanner_t *, const char *); +int zs_set_input_string(zs_scanner_t *, const char *, size_t); +const char *zs_strerror(const int); +]] diff --git a/daemon/lua/kres-gen-32.lua b/daemon/lua/kres-gen-32.lua new file mode 100644 index 0000000..31a5c5d --- /dev/null +++ b/daemon/lua/kres-gen-32.lua @@ -0,0 +1,651 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') +--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[ +typedef long time_t; +typedef long __time_t; +typedef long __suseconds_t; +struct timeval { + __time_t tv_sec; + __suseconds_t tv_usec; +}; + +typedef struct knot_dump_style knot_dump_style_t; +extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT; +struct kr_cdb_api {}; +struct lru {}; +typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t; +typedef struct { + uint16_t pos; + uint16_t flags; + uint16_t compress_ptr[16]; +} knot_rrinfo_t; +typedef unsigned char knot_dname_t; +typedef struct { + uint16_t len; + uint8_t data[]; +} knot_rdata_t; +typedef struct { + uint16_t count; + uint32_t size; + knot_rdata_t *rdata; +} knot_rdataset_t; + +typedef struct knot_mm { + void *ctx, *alloc, *free; +} knot_mm_t; + +typedef void *(*map_alloc_f)(void *, size_t); +typedef void (*map_free_f)(void *baton, void *ptr); +typedef void (*trace_log_f) (const struct kr_request *, const char *); +typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); +typedef bool (*addr_info_f)(struct sockaddr*); +typedef void (*zi_callback)(int state, void *param); +typedef struct { + knot_dname_t *_owner; + uint32_t _ttl; + uint16_t type; + uint16_t rclass; + knot_rdataset_t rrs; + void *additional; +} knot_rrset_t; + +struct kr_module; +typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *); +typedef unsigned char knot_dname_storage_t[255]; +typedef struct knot_pkt knot_pkt_t; +typedef struct { + uint8_t *ptr[18]; +} knot_edns_options_t; +typedef struct { + knot_pkt_t *pkt; + uint16_t pos; + uint16_t count; +} knot_pktsection_t; +typedef struct knot_compr { + uint8_t *wire; + knot_rrinfo_t *rrinfo; + struct { + uint16_t pos; + uint8_t labels; + } suffix; +} knot_compr_t; +struct knot_pkt { + uint8_t *wire; + size_t size; + size_t max_size; + size_t parsed; + uint16_t reserved; + uint16_t qname_size; + uint16_t rrset_count; + uint16_t flags; + knot_rrset_t *opt_rr; + knot_rrset_t *tsig_rr; + knot_edns_options_t *edns_opts; + struct { + uint8_t *pos; + size_t len; + } tsig_wire; + knot_section_t current; + knot_pktsection_t sections[3]; + size_t rrset_allocd; + knot_rrinfo_t *rr_info; + knot_rrset_t *rr; + knot_mm_t mm; + knot_compr_t compr; + knot_dname_storage_t lower_qname; +}; +typedef struct trie trie_t; +struct kr_qflags { + _Bool NO_MINIMIZE : 1; + _Bool NO_IPV6 : 1; + _Bool NO_IPV4 : 1; + _Bool TCP : 1; + _Bool NO_ANSWER : 1; + _Bool RESOLVED : 1; + _Bool AWAIT_IPV4 : 1; + _Bool AWAIT_IPV6 : 1; + _Bool AWAIT_CUT : 1; + _Bool NO_EDNS : 1; + _Bool CACHED : 1; + _Bool NO_CACHE : 1; + _Bool EXPIRING : 1; + _Bool ALLOW_LOCAL : 1; + _Bool DNSSEC_WANT : 1; + _Bool DNSSEC_BOGUS : 1; + _Bool DNSSEC_INSECURE : 1; + _Bool DNSSEC_CD : 1; + _Bool STUB : 1; + _Bool ALWAYS_CUT : 1; + _Bool DNSSEC_WEXPAND : 1; + _Bool PERMISSIVE : 1; + _Bool STRICT : 1; + _Bool BADCOOKIE_AGAIN : 1; + _Bool CNAME : 1; + _Bool REORDER_RR : 1; + _Bool TRACE : 1; + _Bool NO_0X20 : 1; + _Bool DNSSEC_NODS : 1; + _Bool DNSSEC_OPTOUT : 1; + _Bool NONAUTH : 1; + _Bool FORWARD : 1; + _Bool DNS64_MARK : 1; + _Bool CACHE_TRIED : 1; + _Bool NO_NS_FOUND : 1; + _Bool PKT_IS_SANE : 1; + _Bool DNS64_DISABLE : 1; +}; +typedef struct ranked_rr_array_entry { + uint32_t qry_uid; + uint8_t rank; + uint8_t revalidation_cnt; + _Bool cached : 1; + _Bool yielded : 1; + _Bool to_wire : 1; + _Bool expiring : 1; + _Bool in_progress : 1; + _Bool dont_cache : 1; + knot_rrset_t *rr; +} ranked_rr_array_entry_t; +typedef struct { + ranked_rr_array_entry_t **at; + size_t len; + size_t cap; +} ranked_rr_array_t; +typedef struct kr_http_header_array_entry { + char *name; + char *value; +} kr_http_header_array_entry_t; +typedef struct { + kr_http_header_array_entry_t *at; + size_t len; + size_t cap; +} kr_http_header_array_t; +typedef struct { + union kr_sockaddr *at; + size_t len; + size_t cap; +} kr_sockaddr_array_t; +struct kr_zonecut { + knot_dname_t *name; + knot_rrset_t *key; + knot_rrset_t *trust_anchor; + struct kr_zonecut *parent; + trie_t *nsset; + knot_mm_t *pool; +}; +typedef struct { + struct kr_query **at; + size_t len; + size_t cap; +} kr_qarray_t; +struct kr_rplan { + kr_qarray_t pending; + kr_qarray_t resolved; + struct kr_query *initial; + struct kr_request *request; + knot_mm_t *pool; + uint32_t next_uid; +}; +struct kr_request_qsource_flags { + _Bool tcp : 1; + _Bool tls : 1; + _Bool http : 1; + _Bool xdp : 1; +}; +struct kr_extended_error { + int32_t info_code; + const char *extra_text; +}; +struct kr_request { + struct kr_context *ctx; + knot_pkt_t *answer; + struct kr_query *current_query; + struct { + const struct sockaddr *addr; + const struct sockaddr *comm_addr; + const struct sockaddr *dst_addr; + const knot_pkt_t *packet; + struct kr_request_qsource_flags flags; + struct kr_request_qsource_flags comm_flags; + size_t size; + int32_t stream_id; + kr_http_header_array_t headers; + } qsource; + struct { + unsigned int rtt; + const struct kr_transport *transport; + } upstream; + struct kr_qflags options; + int state; + ranked_rr_array_t answ_selected; + ranked_rr_array_t auth_selected; + ranked_rr_array_t add_selected; + _Bool answ_validated; + _Bool auth_validated; + uint8_t rank; + struct kr_rplan rplan; + trace_log_f trace_log; + trace_callback_f trace_finish; + int vars_ref; + knot_mm_t pool; + unsigned int uid; + struct { + addr_info_f is_tls_capable; + addr_info_f is_tcp_connected; + addr_info_f is_tcp_waiting; + kr_sockaddr_array_t forwarding_targets; + } selection_context; + unsigned int count_no_nsaddr; + unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; + struct kr_extended_error extended_error; +}; +enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32}; +typedef struct kr_cdb * kr_cdb_pt; +struct kr_cdb_stats { + uint64_t open; + uint64_t close; + uint64_t count; + uint64_t count_entries; + uint64_t clear; + uint64_t commit; + uint64_t read; + uint64_t read_miss; + uint64_t write; + uint64_t remove; + uint64_t remove_miss; + uint64_t match; + uint64_t match_miss; + uint64_t read_leq; + uint64_t read_leq_miss; + double usage_percent; +}; +typedef struct uv_timer_s uv_timer_t; +struct kr_cache { + kr_cdb_pt db; + const struct kr_cdb_api *api; + struct kr_cdb_stats stats; + uint32_t ttl_min; + uint32_t ttl_max; + struct timeval checkpoint_walltime; + uint64_t checkpoint_monotime; + uv_timer_t *health_timer; +}; +typedef struct kr_layer { + int state; + struct kr_request *req; + const struct kr_layer_api *api; + knot_pkt_t *pkt; + struct sockaddr *dst; + _Bool is_stream; +} kr_layer_t; +typedef struct kr_layer_api { + int (*begin)(kr_layer_t *); + int (*reset)(kr_layer_t *); + int (*finish)(kr_layer_t *); + int (*consume)(kr_layer_t *, knot_pkt_t *); + int (*produce)(kr_layer_t *, knot_pkt_t *); + int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int); + int (*answer_finalize)(kr_layer_t *); + void *data; + int cb_slots[]; +} kr_layer_api_t; +struct kr_prop { + kr_prop_cb *cb; + const char *name; + const char *info; +}; +struct kr_module { + char *name; + int (*init)(struct kr_module *); + int (*deinit)(struct kr_module *); + int (*config)(struct kr_module *, const char *); + const kr_layer_api_t *layer; + const struct kr_prop *props; + void *lib; + void *data; +}; +struct kr_server_selection { + _Bool initialized; + void (*choose_transport)(struct kr_query *, struct kr_transport **); + void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int); + void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error); + struct local_state *local_state; +}; +typedef int kr_log_level_t; +enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG}; + +kr_layer_t kr_layer_t_static; +_Bool kr_dbg_assertion_abort; +int kr_dbg_assertion_fork; + +typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type, + const struct kr_query *qry); + +void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl); +struct kr_query { + struct kr_query *parent; + knot_dname_t *sname; + uint16_t stype; + uint16_t sclass; + uint16_t id; + uint16_t reorder; + struct kr_qflags flags; + struct kr_qflags forward_flags; + uint32_t secret; + uint32_t uid; + int32_t vld_limit_crypto_remains; + uint32_t vld_limit_uid; + uint64_t creation_time_mono; + uint64_t timestamp_mono; + struct timeval timestamp; + struct kr_zonecut zone_cut; + struct kr_layer_pickle *deferred; + int8_t cname_depth; + struct kr_query *cname_parent; + struct kr_request *request; + kr_stale_cb stale_cb; + struct kr_server_selection server_selection; +}; +struct kr_context { + struct kr_qflags options; + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; + trie_t *trust_anchors; + trie_t *negative_anchors; + int32_t vld_limit_crypto; + struct kr_zonecut root_hints; + struct kr_cache cache; + unsigned int cache_rtt_tout_retry_interval; + char _stub[]; +}; +struct kr_transport { + knot_dname_t *ns_name; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +const char *knot_strerror(int); +knot_dname_t *knot_dname_copy(const knot_dname_t *, knot_mm_t *); +knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t); +int knot_dname_in_bailiwick(const knot_dname_t *, const knot_dname_t *); +_Bool knot_dname_is_equal(const knot_dname_t *, const knot_dname_t *); +size_t knot_dname_labels(const uint8_t *, const uint8_t *); +size_t knot_dname_size(const knot_dname_t *); +void knot_dname_to_lower(knot_dname_t *); +char *knot_dname_to_str(char *, const knot_dname_t *, size_t); +knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *, uint16_t); +int knot_rdataset_merge(knot_rdataset_t *, const knot_rdataset_t *, knot_mm_t *); +int knot_rrset_add_rdata(knot_rrset_t *, const uint8_t *, uint16_t, knot_mm_t *); +void knot_rrset_free(knot_rrset_t *, knot_mm_t *); +int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *); +int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *); +size_t knot_rrset_size(const knot_rrset_t *); +int knot_pkt_begin(knot_pkt_t *, knot_section_t); +int knot_pkt_put_question(knot_pkt_t *, const knot_dname_t *, uint16_t, uint16_t); +int knot_pkt_put_rotate(knot_pkt_t *, uint16_t, const knot_rrset_t *, uint16_t, uint16_t); +knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *); +void knot_pkt_free(knot_pkt_t *); +int knot_pkt_parse(knot_pkt_t *, unsigned int); +knot_rrset_t *kr_request_ensure_edns(struct kr_request *); +knot_pkt_t *kr_request_ensure_answer(struct kr_request *); +int kr_request_set_extended_error(struct kr_request *, int, const char *); +struct kr_rplan *kr_resolve_plan(struct kr_request *); +knot_mm_t *kr_resolve_pool(struct kr_request *); +struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t); +int kr_rplan_pop(struct kr_rplan *, struct kr_query *); +struct kr_query *kr_rplan_resolved(struct kr_rplan *); +struct kr_query *kr_rplan_last(struct kr_rplan *); +int kr_forward_add_target(struct kr_request *, const struct sockaddr *); +_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *); +void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...); +void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...); +const char *kr_log_grp2name(enum kr_log_group); +void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...); +int kr_make_query(struct kr_query *, knot_pkt_t *); +void kr_pkt_make_auth_header(knot_pkt_t *); +int kr_pkt_put(knot_pkt_t *, const knot_dname_t *, uint32_t, uint16_t, uint16_t, const uint8_t *, uint16_t); +int kr_pkt_recycle(knot_pkt_t *); +int kr_pkt_clear_payload(knot_pkt_t *); +_Bool kr_pkt_has_wire(const knot_pkt_t *); +_Bool kr_pkt_has_dnssec(const knot_pkt_t *); +uint16_t kr_pkt_qclass(const knot_pkt_t *); +uint16_t kr_pkt_qtype(const knot_pkt_t *); +char *kr_pkt_text(const knot_pkt_t *); +void kr_rnd_buffered(void *, unsigned int); +uint32_t kr_rrsig_sig_inception(const knot_rdata_t *); +uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *); +uint16_t kr_rrsig_type_covered(const knot_rdata_t *); +const char *kr_inaddr(const struct sockaddr *); +int kr_inaddr_family(const struct sockaddr *); +int kr_inaddr_len(const struct sockaddr *); +int kr_inaddr_str(const struct sockaddr *, char *, size_t *); +int kr_sockaddr_cmp(const struct sockaddr *, const struct sockaddr *); +int kr_sockaddr_len(const struct sockaddr *); +uint16_t kr_inaddr_port(const struct sockaddr *); +int kr_straddr_family(const char *); +int kr_straddr_subnet(void *, const char *); +int kr_bitcmp(const char *, const char *, int); +int kr_family_len(int); +struct sockaddr *kr_straddr_socket(const char *, int, knot_mm_t *); +int kr_straddr_split(const char *, char * restrict, uint16_t *); +_Bool kr_rank_test(uint8_t, uint8_t); +int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *); +int kr_ranked_rrarray_finalize(ranked_rr_array_t *, uint32_t, knot_mm_t *); +void kr_qflags_set(struct kr_qflags *, struct kr_qflags); +void kr_qflags_clear(struct kr_qflags *, struct kr_qflags); +int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const void *, int); +_Bool kr_zonecut_is_empty(struct kr_zonecut *); +void kr_zonecut_set(struct kr_zonecut *, const knot_dname_t *); +uint64_t kr_now(void); +const char *kr_strptime_diff(const char *, const char *, const char *, double *); +time_t kr_file_mtime(const char *); +long long kr_fssize(const char *); +const char *kr_dirent_name(const struct dirent *); +void lru_free_items_impl(struct lru *); +struct lru *lru_create_impl(unsigned int, unsigned int, knot_mm_t *, knot_mm_t *); +void *lru_get_impl(struct lru *, const char *, unsigned int, unsigned int, _Bool, _Bool *); +void *mm_realloc(knot_mm_t *, void *, size_t, size_t); +knot_rrset_t *kr_ta_get(trie_t *, const knot_dname_t *); +int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t); +int kr_ta_del(trie_t *, const knot_dname_t *); +void kr_ta_clear(trie_t *); +_Bool kr_dnssec_key_ksk(const uint8_t *); +_Bool kr_dnssec_key_revoked(const uint8_t *); +int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t); +int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t); +int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool, knot_dname_t **); +int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t, _Bool); +int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t); +int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int); +int kr_cache_commit(struct kr_cache *); +uint32_t packet_ttl(const knot_pkt_t *); +typedef struct { + int sock_type; + _Bool tls; + _Bool http; + _Bool xdp; + _Bool freebind; + const char *kind; +} endpoint_flags_t; +typedef struct { + char **at; + size_t len; + size_t cap; +} addr_array_t; +typedef struct { + int fd; + endpoint_flags_t flags; +} flagged_fd_t; +typedef struct { + flagged_fd_t *at; + size_t len; + size_t cap; +} flagged_fd_array_t; +typedef struct { + const char **at; + size_t len; + size_t cap; +} config_array_t; +struct args { + addr_array_t addrs; + addr_array_t addrs_tls; + flagged_fd_array_t fds; + int control_fd; + int forks; + config_array_t config; + const char *rundir; + _Bool interactive; + _Bool quiet; + _Bool tty_binary_output; +}; +typedef struct { + const char *zone_file; + const char *origin; + uint32_t ttl; + enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src; + _Bool downgrade; + _Bool zonemd; + const knot_rrset_t *ds; + zi_callback cb; + void *cb_param; +} zi_config_t; +struct args *the_args; +struct endpoint { + void *handle; + int fd; + int family; + uint16_t port; + int16_t nic_queue; + _Bool engaged; + endpoint_flags_t flags; +}; +struct request_ctx { + struct kr_request req; + struct worker_ctx *worker; + struct qr_task *task; + /* beware: hidden stub, to avoid hardcoding sockaddr lengths */ +}; +struct qr_task { + struct request_ctx *ctx; + /* beware: hidden stub, to avoid qr_tasklist_t */ +}; +int worker_resolve_exec(struct qr_task *, knot_pkt_t *); +knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *); +struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); +int zi_zone_import(const zi_config_t); +struct engine { + struct kr_context resolver; + char _stub[]; +}; +struct worker_ctx { + struct engine *engine; + char _stub[]; +}; +struct worker_ctx *the_worker; +typedef struct { + uint8_t *params_position; + uint8_t *mandatory_position; + uint8_t *param_position; + int32_t last_key; +} zs_svcb_t; +typedef struct { + uint8_t bitmap[32]; + uint8_t length; +} zs_win_t; +typedef struct { + uint8_t excl_flag; + uint16_t addr_family; + uint8_t prefix_length; +} zs_apl_t; +typedef struct { + uint32_t d1; + uint32_t d2; + uint32_t m1; + uint32_t m2; + uint32_t s1; + uint32_t s2; + uint32_t alt; + uint64_t siz; + uint64_t hp; + uint64_t vp; + int8_t lat_sign; + int8_t long_sign; + int8_t alt_sign; +} zs_loc_t; +typedef enum {ZS_STATE_NONE, ZS_STATE_DATA, ZS_STATE_ERROR, ZS_STATE_INCLUDE, ZS_STATE_EOF, ZS_STATE_STOP} zs_state_t; +typedef struct zs_scanner zs_scanner_t; +typedef struct zs_scanner { + int cs; + int top; + int stack[16]; + _Bool multiline; + uint64_t number64; + uint64_t number64_tmp; + uint32_t decimals; + uint32_t decimal_counter; + uint32_t item_length; + uint32_t item_length_position; + uint8_t *item_length_location; + uint8_t *item_length2_location; + uint32_t buffer_length; + uint8_t buffer[65535]; + char include_filename[65535]; + char *path; + zs_win_t windows[256]; + int16_t last_window; + zs_apl_t apl; + zs_loc_t loc; + zs_svcb_t svcb; + uint8_t addr[16]; + _Bool long_string; + _Bool comma_list; + uint8_t *dname; + uint32_t *dname_length; + uint32_t dname_tmp_length; + uint32_t r_data_tail; + uint32_t zone_origin_length; + uint8_t zone_origin[318]; + uint16_t default_class; + uint32_t default_ttl; + zs_state_t state; + struct { + _Bool automatic; + void (*record)(zs_scanner_t *); + void (*error)(zs_scanner_t *); + void (*comment)(zs_scanner_t *); + void *data; + } process; + struct { + const char *start; + const char *current; + const char *end; + _Bool eof; + _Bool mmaped; + } input; + struct { + char *name; + int descriptor; + } file; + struct { + int code; + uint64_t counter; + _Bool fatal; + } error; + uint64_t line_counter; + uint32_t r_owner_length; + uint8_t r_owner[318]; + uint16_t r_class; + uint32_t r_ttl; + uint16_t r_type; + uint32_t r_data_length; + uint8_t r_data[65535]; +} zs_scanner_t; +void zs_deinit(zs_scanner_t *); +int zs_init(zs_scanner_t *, const char *, const uint16_t, const uint32_t); +int zs_parse_record(zs_scanner_t *); +int zs_set_input_file(zs_scanner_t *, const char *); +int zs_set_input_string(zs_scanner_t *, const char *, size_t); +const char *zs_strerror(const int); +]] diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh new file mode 100755 index 0000000..70afb40 --- /dev/null +++ b/daemon/lua/kres-gen.sh @@ -0,0 +1,353 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later + +# Run with "ninja kres-gen" to re-generate $1 +set -o pipefail -o errexit -o nounset + +cd "$(dirname ${0})" +OUTNAME="$1" +CDEFS="../../scripts/gen-cdefs.sh" +LIBKRES="${MESON_BUILD_ROOT}/lib/libkres.so" +KRESD="${MESON_BUILD_ROOT}/daemon/kresd" +if [ ! -e "$LIBKRES" ]; then + # We probably use static libkres. + LIBKRES="$KRESD" +fi + +for REQFILE in "$CDEFS" "$LIBKRES" "$KRESD" +do + test '!' -s "$REQFILE" -a -r "$REQFILE" \ + && echo "Required file $REQFILE cannot be read, did you build binaries and shared libraries?" \ + && exit 1 +done + +# Write to "$OUTNAME" instead of stdout +mv "$OUTNAME"{,.bak} ||: +exec 5<&1- # move stdout into FD 5 +exec 1<>"$OUTNAME" # replace stdout with file + +restore() { + exec 1>&- # close stdout redirected into "$OUTNAME" + exec 1<&5- # restore original stdout + mv -v "$OUTNAME"{,.fail} ||: + mv -v "$OUTNAME"{.bak,} ||: + (>&2 echo "Failed to re-generate $OUTNAME! Missing debugsymbols? Missing shared library?") +} +trap restore ERR INT TERM + +### Dev's guide +# +# C declarations for lua are (mostly) generated to simplify maintenance. +# (Avoid typos, accidental mismatches, etc.) +# +# To regenerate the C definitions for lua: +# - you need to have debugging symbols for knot-dns and knot-resolver; +# you get those by compiling with -g; for knot-dns it might be enough +# to just install it with debugging symbols included (in your distro way) +# - run ninja kres-gen +# - the knot-dns libraries are found via pkg-config +# - you also need gdb on $PATH + +printf -- "-- SPDX-License-Identifier: GPL-3.0-or-later\n\n" +printf -- "local ffi = require('ffi')\n" +printf -- "--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[\n" + +# Some system dependencies. TODO: this generated part isn't perfectly portable. +${CDEFS} ${LIBKRES} types <<-EOF + typedef time_t + __time_t + __suseconds_t + struct timeval +EOF + +## Various types (mainly), from libknot and libkres + +printf " +typedef struct knot_dump_style knot_dump_style_t; +extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT; +struct kr_cdb_api {}; +struct lru {}; +" + +${CDEFS} ${LIBKRES} types <<-EOF + knot_section_t + knot_rrinfo_t + knot_dname_t + knot_rdata_t + knot_rdataset_t +EOF + +# The generator doesn't work well with typedefs of functions. +printf " +typedef struct knot_mm { + void *ctx, *alloc, *free; +} knot_mm_t; + +typedef void *(*map_alloc_f)(void *, size_t); +typedef void (*map_free_f)(void *baton, void *ptr); +typedef void (*trace_log_f) (const struct kr_request *, const char *); +typedef void (*trace_callback_f)(struct kr_request *); +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); +typedef bool (*addr_info_f)(struct sockaddr*); +typedef void (*zi_callback)(int state, void *param); +" + +genResType() { + echo "$1" | ${CDEFS} ${LIBKRES} types +} + +# No simple way to fixup this rename in ./kres.lua AFAIK. +genResType "knot_rrset_t" | sed 's/\<owner\>/_owner/; s/\<ttl\>/_ttl/' + +printf " +struct kr_module; +typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *); +typedef unsigned char knot_dname_storage_t[255]; +" + +${CDEFS} ${LIBKRES} types <<-EOF + #knot_pkt_t contains indirect recursion + typedef knot_pkt_t + knot_edns_options_t + knot_pktsection_t + knot_compr_t + struct knot_pkt + #trie_t inside is private to libknot + typedef trie_t + # libkres + struct kr_qflags + ranked_rr_array_entry_t + ranked_rr_array_t + kr_http_header_array_entry_t + kr_http_header_array_t + kr_sockaddr_array_t + struct kr_zonecut + kr_qarray_t + struct kr_rplan + struct kr_request_qsource_flags + struct kr_extended_error + struct kr_request + enum kr_rank + typedef kr_cdb_pt + struct kr_cdb_stats + typedef uv_timer_t + struct kr_cache + # lib/layer.h + kr_layer_t + kr_layer_api_t + # lib/module.h + struct kr_prop + struct kr_module + struct kr_server_selection + kr_log_level_t + enum kr_log_group +EOF + +# static variables; these lines might not be simple to generate +printf " +kr_layer_t kr_layer_t_static; +_Bool kr_dbg_assertion_abort; +int kr_dbg_assertion_fork; +" + +printf " +typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type, + const struct kr_query *qry); + +void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl); +" + +## Some definitions would need too many deps, so shorten them. + +genResType "struct kr_query" + +genResType "struct kr_context" | sed '/module_array_t/,$ d' +printf "\tchar _stub[];\n};\n" + + +echo "struct kr_transport" | ${CDEFS} ${KRESD} types | sed '/union /,$ d' +printf "\t/* beware: hidden stub, to avoid hardcoding sockaddr lengths */\n};\n" + +## libknot API +${CDEFS} libknot functions <<-EOF +# Utils + knot_strerror +# Domain names + knot_dname_copy + knot_dname_from_str + knot_dname_in_bailiwick + knot_dname_is_equal + knot_dname_labels + knot_dname_size + knot_dname_to_lower + knot_dname_to_str +# Resource records + knot_rdataset_at + knot_rdataset_merge + knot_rrset_add_rdata + knot_rrset_free + knot_rrset_txt_dump + knot_rrset_txt_dump_data + knot_rrset_size +# Packet + knot_pkt_begin + knot_pkt_put_question + knot_pkt_put_rotate + knot_pkt_new + knot_pkt_free + knot_pkt_parse +EOF + +## libkres API +${CDEFS} ${LIBKRES} functions <<-EOF +# Resolution request + kr_request_ensure_edns + kr_request_ensure_answer + kr_request_set_extended_error + kr_resolve_plan + kr_resolve_pool +# Resolution plan + kr_rplan_push + kr_rplan_pop + kr_rplan_resolved + kr_rplan_last +# Forwarding + kr_forward_add_target +# Utils + kr_log_is_debug_fun + kr_log_req1 + kr_log_q1 + kr_log_grp2name + kr_log_fmt + kr_make_query + kr_pkt_make_auth_header + kr_pkt_put + kr_pkt_recycle + kr_pkt_clear_payload + kr_pkt_has_wire + kr_pkt_has_dnssec + kr_pkt_qclass + kr_pkt_qtype + kr_pkt_text + kr_rnd_buffered + kr_rrsig_sig_inception + kr_rrsig_sig_expiration + kr_rrsig_type_covered + kr_inaddr + kr_inaddr_family + kr_inaddr_len + kr_inaddr_str + kr_sockaddr_cmp + kr_sockaddr_len + kr_inaddr_port + kr_straddr_family + kr_straddr_subnet + kr_bitcmp + kr_family_len + kr_straddr_socket + kr_straddr_split + kr_rank_test + kr_ranked_rrarray_add + kr_ranked_rrarray_finalize + kr_qflags_set + kr_qflags_clear + kr_zonecut_add + kr_zonecut_is_empty + kr_zonecut_set + kr_now + kr_strptime_diff + kr_file_mtime + kr_fssize + kr_dirent_name + lru_free_items_impl + lru_create_impl + lru_get_impl + mm_realloc +# Trust anchors + kr_ta_get + kr_ta_add + kr_ta_del + kr_ta_clear +# DNSSEC + kr_dnssec_key_ksk + kr_dnssec_key_revoked + kr_dnssec_key_tag + kr_dnssec_key_match +# Cache + kr_cache_closest_apex + kr_cache_insert_rr + kr_cache_remove + kr_cache_remove_subtree + kr_cache_commit + # FIXME: perhaps rename this exported symbol + packet_ttl +EOF + + +## kresd itself: worker stuff + +${CDEFS} ${KRESD} types <<-EOF + endpoint_flags_t + # struct args is a bit complex + addr_array_t + flagged_fd_t + flagged_fd_array_t + config_array_t + struct args + zi_config_t +EOF +echo "struct args *the_args;" + +echo "struct endpoint" | ${CDEFS} ${KRESD} types | sed 's/uv_handle_t \*/void */' +echo "struct request_ctx" | ${CDEFS} ${KRESD} types | sed '/struct {/,$ d' +printf "\t/* beware: hidden stub, to avoid hardcoding sockaddr lengths */\n};\n" + +echo "struct qr_task" | ${CDEFS} ${KRESD} types | sed '/pktbuf/,$ d' +printf "\t/* beware: hidden stub, to avoid qr_tasklist_t */\n};\n" + + +${CDEFS} ${KRESD} functions <<-EOF + worker_resolve_exec + worker_resolve_mk_pkt + worker_resolve_start + zi_zone_import +EOF + +echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/struct network/,$ d' +printf "\tchar _stub[];\n};\n" + +echo "struct worker_ctx" | ${CDEFS} ${KRESD} types | sed '/uv_loop_t/,$ d' +printf "\tchar _stub[];\n};\n" + +echo "struct worker_ctx *the_worker;" + + +## libzscanner API for ./zonefile.lua +if pkg-config libknot --atleast-version=3.1; then + echo "zs_svcb_t" | ${CDEFS} libzscanner types +fi +${CDEFS} libzscanner types <<-EOF + zs_win_t + zs_apl_t + zs_loc_t + zs_state_t + #zs_scanner_t contains recursion + typedef zs_scanner_t + zs_scanner_t +EOF +${CDEFS} libzscanner functions <<-EOF + zs_deinit + zs_init + zs_parse_record + zs_set_input_file + zs_set_input_string + zs_strerror +EOF + +printf "]]\n" + +rm "$OUTNAME".bak ||: +(>&2 echo "Successfully re-generated ${PWD}/$OUTNAME") + +exit 0 diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua new file mode 100644 index 0000000..4dc2b40 --- /dev/null +++ b/daemon/lua/kres.lua @@ -0,0 +1,1143 @@ +-- LuaJIT ffi bindings for libkres, a DNS resolver library. +-- SPDX-License-Identifier: GPL-3.0-or-later +-- +-- @note Since it's statically compiled, it expects to find the symbols in the C namespace. + +local kres -- the module + +local kluautil = require('kluautil') +local ffi = require('ffi') +local bit = require('bit') +local bor = bit.bor +local band = bit.band +local C = ffi.C +local knot = ffi.load(libknot_SONAME) + +-- Inverse table +local function itable(t, tolower) + local it = {} + for k,v in pairs(t) do it[v] = tolower and string.lower(k) or k end + return it +end + +-- Byte order conversions +local function htonl(x) return x end +local htons = htonl +if ffi.abi('le') then + htonl = bit.bswap + function htons(x) return bit.rshift(htonl(x), 16) end +end + +-- Basic types +local u16_p = ffi.typeof('uint16_t *') + +-- Various declarations that are very stable. +ffi.cdef[[ +/* + * Data structures + */ + +struct sockaddr { + uint16_t sa_family; + uint8_t _stub[]; /* Do not touch */ +}; + +struct knot_error { + int code; +}; + +/* + * libc APIs + */ +void * malloc(size_t size); +void free(void *ptr); +int inet_pton(int af, const char *src, void *dst); +int gettimeofday(struct timeval *tv, struct timezone *tz); +]] + +require('kres-gen') + +-- Error code representation +local knot_error_t = ffi.typeof('struct knot_error') +ffi.metatype(knot_error_t, { + -- Convert libknot error strings + __tostring = function(self) + return ffi.string(knot.knot_strerror(self.code)) + end, +}); + +-- Constant tables +local const_class = { + IN = 1, + CH = 3, + NONE = 254, + ANY = 255, +} +local const_type = { + A = 1, + NS = 2, + MD = 3, + MF = 4, + CNAME = 5, + SOA = 6, + MB = 7, + MG = 8, + MR = 9, + NULL = 10, + WKS = 11, + PTR = 12, + HINFO = 13, + MINFO = 14, + MX = 15, + TXT = 16, + RP = 17, + AFSDB = 18, + X25 = 19, + ISDN = 20, + RT = 21, + NSAP = 22, + ['NSAP-PTR'] = 23, + SIG = 24, + KEY = 25, + PX = 26, + GPOS = 27, + AAAA = 28, + LOC = 29, + NXT = 30, + EID = 31, + NIMLOC = 32, + SRV = 33, + ATMA = 34, + NAPTR = 35, + KX = 36, + CERT = 37, + A6 = 38, + DNAME = 39, + SINK = 40, + OPT = 41, + APL = 42, + DS = 43, + SSHFP = 44, + IPSECKEY = 45, + RRSIG = 46, + NSEC = 47, + DNSKEY = 48, + DHCID = 49, + NSEC3 = 50, + NSEC3PARAM = 51, + TLSA = 52, + SMIMEA = 53, + HIP = 55, + NINFO = 56, + RKEY = 57, + TALINK = 58, + CDS = 59, + CDNSKEY = 60, + OPENPGPKEY = 61, + CSYNC = 62, + ZONEMD = 63, + SVCB = 64, + HTTPS = 65, + + SPF = 99, + UINFO = 100, + UID = 101, + GID = 102, + UNSPEC = 103, + NID = 104, + L32 = 105, + L64 = 106, + LP = 107, + EUI48 = 108, + EUI64 = 109, + TKEY = 249, + TSIG = 250, + IXFR = 251, + AXFR = 252, + MAILB = 253, + MAILA = 254, + ANY = 255, + URI = 256, + CAA = 257, + AVC = 258, + DOA = 259, + TA = 32768, + DLV = 32769, +} +local const_section = { + ANSWER = 0, + AUTHORITY = 1, + ADDITIONAL = 2, +} +local const_opcode = { + QUERY = 0, + IQUERY = 1, + STATUS = 2, + NOTIFY = 4, + UPDATE = 5, +} +local const_rcode = { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMPL = 4, + REFUSED = 5, + YXDOMAIN = 6, + YXRRSET = 7, + NXRRSET = 8, + NOTAUTH = 9, + NOTZONE = 10, + BADVERS = 16, + BADCOOKIE = 23, +} +-- This corresponds to `enum kr_rank`, it's not possible to do this without introspection unfortunately +local const_rank = { + INITIAL = 0, + OMIT = 1, + TRY = 2, + INDET = 4, + BOGUS = 5, + MISMATCH = 6, + MISSING = 7, + INSECURE = 8, + AUTH = 16, + SECURE = 32 +} +local const_extended_error = { + NONE = -1, + OTHER = 0, + DNSKEY_ALG = 1, + DS_DIGEST = 2, + STALE = 3, + FORGED = 4, + INDETERMINATE = 5, + BOGUS = 6, + SIG_EXPIRED = 7, + SIG_NOTYET = 8, + DNSKEY_MISS = 9, + RRSIG_MISS = 10, + DNSKEY_BIT = 11, + NSEC_MISS = 12, + CACHED_ERR = 13, + NOT_READY = 14, + BLOCKED = 15, + CENSORED = 16, + FILTERED = 17, + PROHIBITED = 18, + STALE_NXD = 19, + NOTAUTH = 20, + NOTSUP = 21, + NREACH_AUTH = 22, + NETWORK = 23, + INV_DATA = 24, +} + +-- Constant tables +local const_class_str = itable(const_class) +local const_type_str = itable(const_type) +local const_rcode_str = itable(const_rcode) +local const_opcode_str = itable(const_opcode) +local const_section_str = itable(const_section) +local const_rank_str = itable(const_rank) +local const_extended_error_str = itable(const_extended_error) + +-- Metatype for RR types to allow anonymous types +setmetatable(const_type, { + __index = function (t, k) + local v = rawget(t, k) + if v then return v end + -- Allow TYPE%d notation + if string.find(k, 'TYPE', 1, true) then + return tonumber(k:sub(5)) + end + -- Unknown type + return + end +}) + +-- Metatype for RR types to allow anonymous string types +setmetatable(const_type_str, { + __index = function (t, k) + local v = rawget(t, k) + if v then return v end + return string.format('TYPE%d', k) + end +}) + +-- Metatype for timeval +local timeval_t = ffi.typeof('struct timeval') + +-- Metatype for sockaddr +local addr_buf = ffi.new('char[16]') +local str_addr_buf = ffi.new('char[46 + 1 + 6 + 1]') -- INET6_ADDRSTRLEN + #port + \0 +local str_addr_buf_len = ffi.sizeof(str_addr_buf) +local sockaddr_t = ffi.typeof('struct sockaddr') +ffi.metatype( sockaddr_t, { + __index = { + len = function(sa) return C.kr_inaddr_len(sa) end, + ip = function (sa) return C.kr_inaddr(sa) end, + family = function (sa) return C.kr_inaddr_family(sa) end, + port = function (sa) return C.kr_inaddr_port(sa) end, + }, + __tostring = function(sa) + assert(ffi.istype(sockaddr_t, sa)) + local len = ffi.new('size_t[1]', str_addr_buf_len) + local ret = C.kr_inaddr_str(sa, str_addr_buf, len) + if ret ~= 0 then + error('kr_inaddr_str failed: ' .. tostring(ret)) + end + return ffi.string(str_addr_buf) + end, + +}) + +-- Parametrized LRU table +local typed_lru_t = 'struct { $ value_type[1]; struct lru * lru; }' + +-- Metatype for LRU +local lru_metatype = { + -- Create a new LRU with given value type + -- By default the LRU will have a capacity of 65536 elements + -- Note: At the point the parametrized type must be finalized + __new = function (ct, max_slots, alignment) + -- {0} will make sure that the value is coercible to a number + local o = ffi.new(ct, {0}, C.lru_create_impl(max_slots or 65536, alignment or 1, nil, nil)) + if o.lru == nil then + return + end + return o + end, + -- Destructor to clean allocated memory + __gc = function (self) + assert(self.lru ~= nil) + C.lru_free_items_impl(self.lru) + C.free(self.lru) + self.lru = nil + end, + __index = { + -- Look up key and return reference to current + -- Note: The key will be inserted if it doesn't exist + get_ref = function (self, key, key_len, allow_insert) + local insert = allow_insert and true or false + local ptr = C.lru_get_impl(self.lru, key, key_len or #key, ffi.sizeof(self.value_type[0]), insert, nil) + if ptr ~= nil then + return ffi.cast(self.value_type, ptr) + end + end, + -- Look up key and return current value + get = function (self, key, key_len) + local ref = self:get_ref(key, key_len, false) + if ref then + return ref[0] + end + end, + -- Set value for key to given value + set = function (self, key, value, key_len) + local ref = self:get_ref(key, key_len, true) + if ref then + ref[0] = value + return true + end + end, + }, +} + +-- Pretty print for domain name +local function dname2str(dname) + if dname == nil then return end + local text_name = ffi.gc(C.knot_dname_to_str(nil, dname, 0), C.free) + if text_name ~= nil then + return ffi.string(text_name) + end +end + +-- Convert dname pointer to wireformat string +local function dname2wire(name) + if name == nil then return nil end + return ffi.string(name, knot.knot_dname_size(name)) +end + +-- Parse RDATA, from presentation to wire-format. +-- in: a table of strings, each a line describing RRTYPE+RDATA +-- out: a table of RDATA strings in wire-format +local function parse_rdata(strs, nothing) + local zonefile = require('zonefile') + if type(strs) ~= 'table' or nothing ~= nil then -- accidents like forgetting braces + error('a table of string(s) is expected', 2) + end + local res = {} + for _, line in ipairs(strs) do + if type(line) ~= 'string' then + error('table must contain strings', 2) + end + local rrs = zonefile.string('. ' .. line) + if #rrs == 0 then error('failed to parse line: ' .. line, 2) end + for _, rr in ipairs(rrs) do + table.insert(res, rr.rdata) + end + end + return res +end + +-- RR sets created in Lua must have a destructor to release allocated memory +local function rrset_free(rr) + if rr._owner ~= nil then ffi.C.free(rr._owner) end + if rr:rdcount() > 0 then ffi.C.free(rr.rrs.rdata) end +end + +-- Metatype for RR set. Beware, the indexing is 0-based (rdata, get, tostring). +local rrset_buflen = (64 + 1) * 1024 +local rrset_buf = ffi.new('char[?]', rrset_buflen) +local knot_rrset_pt = ffi.typeof('knot_rrset_t *') +local knot_rrset_t = ffi.typeof('knot_rrset_t') +ffi.metatype( knot_rrset_t, { + -- Create a new empty RR set object with an allocated owner and a destructor + __new = function (ct, owner, rrtype, rrclass, ttl) + local rr = ffi.new(ct) + C.kr_rrset_init(rr, + owner and knot.knot_dname_copy(owner, nil), + rrtype or 0, + rrclass or const_class.IN, + ttl or 0) + return ffi.gc(rr, rrset_free) + end, + -- BEWARE: `owner` and `rdata` are typed as a plain lua strings + -- and not the real types they represent. + __tostring = function(rr) + assert(ffi.istype(knot_rrset_t, rr)) + return rr:txt_dump() + end, + __index = { + owner = function(rr) + assert(ffi.istype(knot_rrset_t, rr)) + return dname2wire(rr._owner) + end, + ttl = function(rr) + assert(ffi.istype(knot_rrset_t, rr)) + return tonumber(rr._ttl) + end, + class = function(rr, val) + assert(ffi.istype(knot_rrset_t, rr)) + if val then + rr.rclass = val + end + return tonumber(rr.rclass) + end, + rdata_pt = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount()) + return knot.knot_rdataset_at(rr.rrs, i) + end, + rdata = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr)) + local rd = rr:rdata_pt(i) + return ffi.string(rd.data, rd.len) + end, + get = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount()) + return {owner = rr:owner(), + ttl = rr:ttl(), + class = tonumber(rr.rclass), + type = tonumber(rr.type), + rdata = rr:rdata(i)} + end, + tostring = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr) + and (i == nil or (i >= 0 and i < rr:rdcount())) ) + if rr:rdcount() > 0 then + local ret + if i ~= nil then + ret = knot.knot_rrset_txt_dump_data(rr, i, rrset_buf, rrset_buflen, C.KR_DUMP_STYLE_DEFAULT) + else + ret = -1 + end + return ret >= 0 and ffi.string(rrset_buf) + end + end, + + -- Dump the rrset in presentation format (dig-like). + txt_dump = function(rr, style) + assert(ffi.istype(knot_rrset_t, rr)) + local bufsize = 1024 + local dump = ffi.new('char *[1]', C.malloc(bufsize)) + -- ^ one pointer to a string + local size = ffi.new('size_t[1]', { bufsize }) -- one size_t = bufsize + + local ret = knot.knot_rrset_txt_dump(rr, dump, size, + style or C.KR_DUMP_STYLE_DEFAULT) + local result = nil + if ret >= 0 then + result = ffi.string(dump[0], ret) + end + C.free(dump[0]) + return result + end, + txt_fields = function(rr, i) + assert(ffi.istype(knot_rrset_t, rr)) + assert(i >= 0 and i < rr:rdcount()) + local bufsize = 1024 + local dump = ffi.new('char *', C.malloc(bufsize)) + ffi.gc(dump, C.free) + + local ret = knot.knot_rrset_txt_dump_data(rr, i, dump, 1024, + C.KR_DUMP_STYLE_DEFAULT) + if ret >= 0 then + local out = {} + out.owner = dname2str(rr:owner()) + out.ttl = rr:ttl() + out.class = kres.tostring.class[rr:class()] + out.type = kres.tostring.type[rr.type] + out.rdata = ffi.string(dump, ret) + return out + else + panic('knot_rrset_txt_dump_data failure ' .. tostring(ret)) + end + end, + -- Return RDATA count for this RR set + rdcount = function(rr) + assert(ffi.istype(knot_rrset_t, rr)) + return tonumber(rr.rrs.count) + end, + -- Add binary RDATA to the RR set + add_rdata = function (rr, rdata, rdlen, no_ttl) + assert(ffi.istype(knot_rrset_t, rr)) + assert(no_ttl == nil, 'add_rdata() can not accept TTL anymore') + local ret = knot.knot_rrset_add_rdata(rr, rdata, tonumber(rdlen), nil) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + -- Merge data from another RR set into the current one + merge_rdata = function (rr, source) + assert(ffi.istype(knot_rrset_t, rr)) + assert(ffi.istype(knot_rrset_t, source)) + local ret = knot.knot_rdataset_merge(rr.rrs, source.rrs, nil) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + -- Return type covered by this RRSIG + type_covered = function(rr, i) + i = i or 0 + assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount()) + if rr.type ~= const_type.RRSIG then return end + return tonumber(C.kr_rrsig_type_covered(knot.knot_rdataset_at(rr.rrs, i))) + end, + -- Check whether a RRSIG is covering current RR set + is_covered_by = function(rr, rrsig) + assert(ffi.istype(knot_rrset_t, rr)) + assert(ffi.istype(knot_rrset_t, rrsig)) + assert(rrsig.type == const_type.RRSIG) + return (rr.type == rrsig:type_covered() and rr:owner() == rrsig:owner()) + end, + -- Return RR set wire size + wire_size = function(rr) + assert(ffi.istype(knot_rrset_t, rr)) + return tonumber(knot.knot_rrset_size(rr)) + end, + }, +}) + +-- Destructor for packet accepts pointer to pointer +local knot_pkt_t = ffi.typeof('knot_pkt_t') + +-- Helpers for reading/writing 16-bit numbers from packet wire +local function pkt_u16(pkt, off, val) + assert(ffi.istype(knot_pkt_t, pkt)) + local ptr = ffi.cast(u16_p, pkt.wire + off) + if val ~= nil then ptr[0] = htons(val) end + return (htons(ptr[0])) +end + +-- Helpers for reading/writing message header flags +local function pkt_bit(pkt, byteoff, bitmask, val) + -- If the value argument is passed, set/clear the desired bit + if val ~= nil then + if val then pkt.wire[byteoff] = bit.bor(pkt.wire[byteoff], bitmask) + else pkt.wire[byteoff] = bit.band(pkt.wire[byteoff], bit.bnot(bitmask)) end + return true + end + return (bit.band(pkt.wire[byteoff], bitmask) ~= 0) +end + +local function knot_pkt_rr(section, i) + assert(section and ffi.istype('knot_pktsection_t', section) + and i >= 0 and i < section.count) + local ret = section.pkt.rr + section.pos + i + assert(ffi.istype(knot_rrset_pt, ret)) + return ret +end + +-- Metatype for packet +ffi.metatype( knot_pkt_t, { + __new = function (_, size, wire) + if size < 12 or size > 65535 then + error('packet size must be <12, 65535>') + end + + local pkt = knot.knot_pkt_new(nil, size, nil) + if pkt == nil then + error(string.format('failed to allocate a packet of size %d', size)) + end + if wire == nil then + C.kr_rnd_buffered(pkt.wire, 2) -- randomize the query ID + else + assert(size <= #wire) + ffi.copy(pkt.wire, wire, size) + pkt.size = size + pkt.parsed = 0 + end + + return ffi.gc(pkt[0], knot.knot_pkt_free) + end, + __tostring = function(pkt) + return pkt:tostring() + end, + __len = function(pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return tonumber(pkt.size) + end, + __ipairs = function(self) + return ipairs(self:section(const_section.ANSWER)) + end, + __index = { + -- Header + id = function(pkt, val) return pkt_u16(pkt, 0, val) end, + qdcount = function(pkt, val) return pkt_u16(pkt, 4, val) end, + ancount = function(pkt, val) return pkt_u16(pkt, 6, val) end, + nscount = function(pkt, val) return pkt_u16(pkt, 8, val) end, + arcount = function(pkt, val) return pkt_u16(pkt, 10, val) end, + opcode = function (pkt, val) + assert(ffi.istype(knot_pkt_t, pkt)) + pkt.wire[2] = (val) and bit.bor(bit.band(pkt.wire[2], 0x78), 8 * val) or pkt.wire[2] + return (bit.band(pkt.wire[2], 0x78) / 8) + end, + rcode = function (pkt, val) + assert(ffi.istype(knot_pkt_t, pkt)) + pkt.wire[3] = (val) and bor(band(pkt.wire[3], 0xf0), val) or pkt.wire[3] + return band(pkt.wire[3], 0x0f) + end, + rd = function (pkt, val) return pkt_bit(pkt, 2, 0x01, val) end, + tc = function (pkt, val) return pkt_bit(pkt, 2, 0x02, val) end, + aa = function (pkt, val) return pkt_bit(pkt, 2, 0x04, val) end, + qr = function (pkt, val) return pkt_bit(pkt, 2, 0x80, val) end, + cd = function (pkt, val) return pkt_bit(pkt, 3, 0x10, val) end, + ad = function (pkt, val) return pkt_bit(pkt, 3, 0x20, val) end, + ra = function (pkt, val) return pkt_bit(pkt, 3, 0x80, val) end, + -- "do" is a reserved word in Lua; only getter + dobit = function(pkt, val) + assert(val == nil, 'dobit is getter only') + assert(ffi.istype(knot_pkt_t, pkt)) + return C.kr_pkt_has_dnssec(pkt) + end, + -- Question + qname = function(pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + -- inlined knot_pkt_qname(), basically but not lower-cased + if pkt == nil or pkt.qname_size == 0 then return nil end + return ffi.string(pkt.wire + 12, pkt.qname_size) + end, + qclass = function(pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return C.kr_pkt_qclass(pkt) + end, + qtype = function(pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return C.kr_pkt_qtype(pkt) + end, + rrsets = function (pkt, section_id) + assert(ffi.istype(knot_pkt_t, pkt)) + local records = {} + local section = pkt.sections + section_id + for i = 1, section.count do + local rrset = knot_pkt_rr(section, i - 1) + table.insert(records, rrset) + end + return records + end, + section = function (pkt, section_id) + assert(ffi.istype(knot_pkt_t, pkt)) + local records = {} + local section = pkt.sections + section_id + for i = 1, section.count do + local rrset = knot_pkt_rr(section, i - 1) + for k = 1, rrset:rdcount() do + table.insert(records, rrset:get(k - 1)) + end + end + return records + end, + begin = function (pkt, section) + assert(ffi.istype(knot_pkt_t, pkt)) + assert(section >= pkt.current, 'cannot rewind to already written section') + assert(const_section_str[section], string.format('invalid section: %s', section)) + local ret = knot.knot_pkt_begin(pkt, section) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + put = function (pkt, owner, ttl, rclass, rtype, rdata) + assert(ffi.istype(knot_pkt_t, pkt)) + local ret = C.kr_pkt_put(pkt, owner, ttl, rclass, rtype, rdata, #rdata) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + -- Put an RR set in the packet + -- Note: the packet doesn't take ownership of the RR set + put_rr = function (pkt, rr, rotate, flags) + assert(ffi.istype(knot_pkt_t, pkt)) + assert(ffi.istype(knot_rrset_t, rr)) + local ret = C.knot_pkt_put_rotate(pkt, 0, rr, rotate or 0, flags or 0) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + -- Checks whether the packet has a wire, i.e. the .size is not + -- equal to KR_PKT_SIZE_NOWIRE + has_wire = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return C.kr_pkt_has_wire(pkt) + end, + recycle = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + local ret = C.kr_pkt_recycle(pkt) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + clear_payload = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + local ret = C.kr_pkt_clear_payload(pkt) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + question = function(pkt, qname, qclass, qtype) + assert(ffi.istype(knot_pkt_t, pkt)) + assert(qclass ~= nil, string.format('invalid class: %s', qclass)) + assert(qtype ~= nil, string.format('invalid type: %s', qtype)) + local ret = C.knot_pkt_put_question(pkt, qname, qclass, qtype) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + towire = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return ffi.string(pkt.wire, pkt.size) + end, + tostring = function(pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + return ffi.string(ffi.gc(C.kr_pkt_text(pkt), C.free)) + end, + -- Return number of remaining empty bytes in the packet + -- This is generally useful to check if there's enough space + remaining_bytes = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + local occupied = pkt.size + pkt.reserved + assert(pkt.max_size >= occupied) + return tonumber(pkt.max_size - occupied) + end, + -- Packet manipulation + parse = function (pkt) + assert(ffi.istype(knot_pkt_t, pkt)) + local ret = knot.knot_pkt_parse(pkt, 0) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + -- Resize packet wire to a new size + resize = function (pkt, new_size) + assert(ffi.istype(knot_pkt_t, pkt)) + local ptr = C.mm_realloc(pkt.mm, pkt.wire, new_size, pkt.max_size) + if ptr == nil then return end + pkt.wire = ptr + pkt.max_size = new_size + return true + end, + }, +}) +-- Metatype for query +local kr_query_t = ffi.typeof('struct kr_query') +ffi.metatype( kr_query_t, { + __index = { + -- Return query domain name + name = function(qry) + assert(ffi.istype(kr_query_t, qry)) + return dname2wire(qry.sname) + end, + -- Write this query into packet + write = function(qry, pkt) + assert(ffi.istype(kr_query_t, qry)) + assert(ffi.istype(knot_pkt_t, pkt)) + local ret = C.kr_make_query(qry, pkt) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + }, +}) + +-- helper for trace_chain_callbacks +-- ignores return values from successful calls but logs tracebacks for throws +local function void_xpcall_log_tb(func, req, msg) + local ok, err = xpcall(func, debug.traceback, req, msg) + if not ok then + log_error(ffi.C.LOG_GRP_SYSTEM, 'callback %s req %s msg %s stack traceback:\n%s', func, req, msg, err) + end +end + +local function void_xpcall_finish_tb(func, req) + local ok, err = xpcall(func, debug.traceback, req) + if not ok then + log_error(ffi.C.LOG_GRP_SYSTEM, 'callback %s req %s stack traceback:\n%s', func, req, err) + end +end + + +-- Metatype for request +local kr_request_t = ffi.typeof('struct kr_request') +ffi.metatype( kr_request_t, { + __index = { + -- makes sense only when request is finished + all_from_cache = function(req) + assert(ffi.istype(kr_request_t, req)) + local rplan = ffi.C.kr_resolve_plan(req) + if tonumber(rplan.pending.len) > 0 then + -- an unresolved query, + -- i.e. something is missing from the cache + return false + end + for idx=0, tonumber(rplan.resolved.len) - 1 do + if not rplan.resolved.at[idx].flags.CACHED then + return false + end + end + return true + end, + current = function(req) + assert(ffi.istype(kr_request_t, req)) + if req.current_query == nil then return nil end + return req.current_query + end, + -- returns the initial query that started the request + initial = function(req) + assert(ffi.istype(kr_request_t, req)) + local rplan = C.kr_resolve_plan(req) + if rplan.initial == nil then return nil end + return rplan.initial + end, + -- Return last query on the resolution plan + last = function(req) + assert(ffi.istype(kr_request_t, req)) + local query = C.kr_rplan_last(C.kr_resolve_plan(req)) + if query == nil then return end + return query + end, + resolved = function(req) + assert(ffi.istype(kr_request_t, req)) + local qry = C.kr_rplan_resolved(C.kr_resolve_plan(req)) + if qry == nil then return nil end + return qry + end, + -- returns first resolved sub query for a request + first_resolved = function(req) + assert(ffi.istype(kr_request_t, req)) + local rplan = C.kr_resolve_plan(req) + if not rplan or rplan.resolved.len < 1 then return nil end + return rplan.resolved.at[0] + end, + push = function(req, qname, qtype, qclass, flags, parent) + assert(ffi.istype(kr_request_t, req)) + flags = kres.mk_qflags(flags) -- compatibility + local rplan = C.kr_resolve_plan(req) + local qry = C.kr_rplan_push(rplan, parent, qname, qclass, qtype) + if qry ~= nil and flags ~= nil then + C.kr_qflags_set(qry.flags, flags) + end + return qry + end, + pop = function(req, qry) + assert(ffi.istype(kr_request_t, req)) + return C.kr_rplan_pop(C.kr_resolve_plan(req), qry) + end, + selected_tostring = function(req) + assert(ffi.istype(kr_request_t, req)) + local buf = {} + if #req.answ_selected ~= 0 then + table.insert(buf, ';; selected from ANSWER sections:\n') + table.insert(buf, tostring(req.answ_selected)) + end + if #req.auth_selected ~= 0 then + table.insert(buf, ';; selected from AUTHORITY sections:\n') + table.insert(buf, tostring(req.auth_selected)) + end + if #req.add_selected ~= 0 then + table.insert(buf, ';; selected from ADDITIONAL sections:\n') + table.insert(buf, tostring(req.add_selected)) + end + return table.concat(buf, '') + end, + set_extended_error = function(req, code, msg) + assert(ffi.istype(kr_request_t, req)) + msg = kluautil.kr_string2c(msg, req.pool) + ffi.C.kr_request_set_extended_error(req, code, msg) + end, + + -- chain new callbacks after the old ones + -- creates new wrapper functions as necessary + -- note: callbacks are FFI cdata pointers so tests must + -- use explicit "cb == nil", just "if cb" does not work + -- + trace_chain_callbacks = function (req, new_log, new_finish) + local log_wrapper + if req.trace_log == nil then + req.trace_log = new_log + else + local old_log = req.trace_log + log_wrapper = ffi.cast('trace_log_f', + function(cbreq, msg) + jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed + void_xpcall_log_tb(old_log, cbreq, msg) + void_xpcall_log_tb(new_log, cbreq, msg) + end) + req.trace_log = log_wrapper + end + local old_finish = req.trace_finish + if not (log_wrapper ~= nil or old_finish ~= nil) then + req.trace_finish = new_finish + else + local fin_wrapper + fin_wrapper = ffi.cast('trace_callback_f', + function(cbreq) + jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed + if old_finish ~= nil then + void_xpcall_finish_tb(old_finish, cbreq) + end + if new_finish ~= nil then + void_xpcall_finish_tb(new_finish, cbreq) + end + -- beware: finish callbacks can call log callback + if log_wrapper ~= nil then + log_wrapper:free() + end + fin_wrapper:free() + end) + req.trace_finish = fin_wrapper + end + end, + + -- Return per-request variable table + -- The request can store anything in this Lua table and it will be freed + -- when the request is closed, it doesn't have to worry about contents. + vars = function (req) + assert(ffi.istype(kr_request_t, req)) + -- Return variable if it's already stored + local var = worker.vars[req.vars_ref] + if var then + return var + end + -- Either take a slot number from freelist + -- or find a first free slot (expand the table) + local ref = worker.vars[0] + if ref then + worker.vars[0] = worker.vars[ref] + else + ref = #worker.vars + 1 + end + -- Create new variables table + var = {} + worker.vars[ref] = var + -- Save reference in the request + req.vars_ref = ref + return var + end, + -- Ensure that answer has EDNS if needed; can't fail. + ensure_edns = function (req) + assert(ffi.istype(kr_request_t, req)) + return C.kr_request_ensure_edns(req) + end, + -- Ensure that answer exists and return it; can't fail. + ensure_answer = function (req) + assert(ffi.istype(kr_request_t, req)) + return C.kr_request_ensure_answer(req) + end, + }, +}) + +-- C array iterator +local function c_array_iter(t, i) + i = i + 1 + if i >= t.len then return end + return i, t.at[i][0] +end + +-- Metatype for a single ranked record array entry (one RRset) +local function rank_tostring(rank) + local names = {} + for name, value in pairs(const_rank) do + if ffi.C.kr_rank_test(rank, value) then + table.insert(names, string.lower(name)) + end + end + table.sort(names) -- pairs() above doesn't give a stable ordering + return string.format('0%.2o (%s)', rank, table.concat(names, ' ')) +end + +local ranked_rr_array_entry_t = ffi.typeof('ranked_rr_array_entry_t') +ffi.metatype(ranked_rr_array_entry_t, { + __tostring = function(self) + return string.format('; ranked rrset to_wire %s, rank %s, cached %s, qry_uid %s, revalidations %s\n%s', + self.to_wire, rank_tostring(self.rank), self.cached, self.qry_uid, + self.revalidation_cnt, string.format('%s', self.rr)) + end +}) + +-- Metatype for ranked record array (array of RRsets) +local ranked_rr_array_t = ffi.typeof('ranked_rr_array_t') +ffi.metatype(ranked_rr_array_t, { + __len = function(self) + return tonumber(self.len) + end, + __ipairs = function (self) + return c_array_iter, self, -1 + end, + __index = { + get = function (self, i) + if i < 0 or i > self.len then return nil end + return self.at[i][0] + end, + }, + __tostring = function(self) + local buf = {} + for _, rrset in ipairs(self) do + table.insert(buf, tostring(rrset)) + end + return table.concat(buf, '') + end +}) + +-- Cache metatype +local kr_cache_t = ffi.typeof('struct kr_cache') +ffi.metatype( kr_cache_t, { + __index = { + insert = function (self, rr, rrsig, rank, timestamp) + assert(ffi.istype(kr_cache_t, self)) + assert(ffi.istype(knot_rrset_t, rr), 'RR must be a rrset type') + assert(not rrsig or ffi.istype(knot_rrset_t, rrsig), 'RRSIG must be nil or of the rrset type') + -- Get current timestamp + if not timestamp then + local now = timeval_t() + C.gettimeofday(now, nil) + timestamp = tonumber(now.tv_sec) + end + -- Insert record into cache + local ret = C.kr_cache_insert_rr(self, rr, rrsig, tonumber(rank or 0), + timestamp, true) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + commit = function (self) + assert(ffi.istype(kr_cache_t, self)) + local ret = C.kr_cache_commit(self) + if ret ~= 0 then return nil, knot_error_t(ret) end + return true + end, + }, +}) + +-- Pretty-print a single RR (which is a table with .owner .ttl .type .rdata) +-- Extension: append .comment if exists. +local function rr2str(rr, style) + -- Construct a single-RR temporary set while minimizing copying. + local ret + do + local rrs = knot_rrset_t(rr.owner, rr.type, kres.class.IN, rr.ttl) + rrs:add_rdata(rr.rdata, #rr.rdata) + ret = rrs:txt_dump(style) + end + + -- Trim the newline and append comment (optionally). + if ret then + if ret:byte(-1) == string.byte('\n', -1) then + ret = ret:sub(1, -2) + end + if rr.comment then + ret = ret .. ' ;' .. rr.comment + end + end + return ret +end + +-- Module API +kres = { + -- Constants + class = const_class, + type = const_type, + section = const_section, + rcode = const_rcode, + opcode = const_opcode, + rank = const_rank, + extended_error = const_extended_error, + + -- Constants to strings + tostring = { + class = const_class_str, + type = const_type_str, + section = const_section_str, + rcode = const_rcode_str, + opcode = const_opcode_str, + rank = const_rank_str, + extended_eror = const_extended_error_str, + }, + + -- Create a struct kr_qflags from a single flag name or a list of names. + mk_qflags = function (names) + local kr_qflags = ffi.typeof('struct kr_qflags') + if names == 0 or names == nil then -- compatibility: nil is common in lua + names = {} + elseif type(names) == 'string' then + names = {names} + elseif ffi.istype(kr_qflags, names) then + return names + end + + local fs = ffi.new(kr_qflags) + for _, name in pairs(names) do + fs[name] = true + end + return fs + end, + + CONSUME = 1, PRODUCE = 2, DONE = 4, FAIL = 8, YIELD = 16, + + -- Export types + rrset = knot_rrset_t, + packet = knot_pkt_t, + lru = function (max_size, value_type) + value_type = value_type or ffi.typeof('uint64_t') + local ct = ffi.typeof(typed_lru_t, value_type) + return ffi.metatype(ct, lru_metatype)(max_size, ffi.alignof(value_type)) + end, + + -- Metatypes. Beware that any pointer will be cast silently... + pkt_t = function (udata) return ffi.cast('knot_pkt_t *', udata) end, + request_t = function (udata) return ffi.cast('struct kr_request *', udata) end, + sockaddr_t = function (udata) return ffi.cast('struct sockaddr *', udata) end, + + -- Global API functions + -- Convert a lua string to a lower-case wire format (inside GC-ed ffi.string). + str2dname = function(name) + if type(name) ~= 'string' then return end + local dname = ffi.gc(C.knot_dname_from_str(nil, name, 0), C.free) + if dname == nil then return nil end + ffi.C.knot_dname_to_lower(dname); + return dname2wire(dname) + end, + dname2str = dname2str, + dname2wire = dname2wire, + parse_rdata = parse_rdata, + + rr2str = rr2str, + str2ip = function (ip) + local family = C.kr_straddr_family(ip) + local ret = C.inet_pton(family, ip, addr_buf) + if ret ~= 1 then return nil end + return ffi.string(addr_buf, C.kr_family_len(family)) + end, + context = function () return ffi.C.the_worker.engine.resolver end, + + knot_pkt_rr = knot_pkt_rr, +} + +return kres diff --git a/daemon/lua/krprint.lua b/daemon/lua/krprint.lua new file mode 100644 index 0000000..dd25a9b --- /dev/null +++ b/daemon/lua/krprint.lua @@ -0,0 +1,340 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local base_class = { + cur_indent = 0, +} + +-- shared constructor: use as serializer_class:new() +function base_class.new(class, on_unrepresentable) + on_unrepresentable = on_unrepresentable or 'comment' + if on_unrepresentable ~= 'comment' + and on_unrepresentable ~= 'error' then + error('unsupported val2expr on_unrepresentable option ' + .. tostring(on_unrepresentable)) + end + local inst = {} + inst.on_unrepresentable = on_unrepresentable + inst.done = {} + inst.tab_key_path = {} + setmetatable(inst, class.__inst_mt) + return inst +end + +-- format comment with leading/ending whitespace if needed +function base_class.format_note(_, note, ws_prefix, ws_suffix) + if note == nil then + return '' + else + return string.format('%s--[[ %s ]]%s', + ws_prefix or '', note, ws_suffix or '') + end +end + +function base_class.indent_head(self) + return string.rep(' ', self.cur_indent) +end + +function base_class.indent_inc(self) + self.cur_indent = self.cur_indent + self.indent_step +end + +function base_class.indent_dec(self) + self.cur_indent = self.cur_indent - self.indent_step +end + +function base_class._fallback(self, val) + if self.on_unrepresentable == 'comment' then + return 'nil', string.format('missing %s', val) + elseif self.on_unrepresentable == 'error' then + local key_path_msg + if #self.tab_key_path > 0 then + local str_key_path = {} + for _, key in ipairs(self.tab_key_path) do + table.insert(str_key_path, + string.format('%s %s', type(key), self:string(tostring(key)))) + end + local key_path = '[' .. table.concat(str_key_path, '][') .. ']' + key_path_msg = string.format(' (found at [%s])', key_path) + else + key_path_msg = '' + end + error(string.format('cannot serialize type %s%s', type(val), key_path_msg), 2) + end +end + +function base_class.val2expr(self, val) + local val_type = type(val) + local val_repr = self[val_type] + if val_repr then + return val_repr(self, val) + else + return self:_fallback(val) + end +end + +-- "nil" is a Lua keyword so assignment below is workaround to create +-- function base_class.nil(self, val) +base_class['nil'] = function(_, val) + assert(type(val) == 'nil') + return 'nil' +end + +function base_class.number(_, val) + assert(type(val) == 'number') + if val == math.huge then + return 'math.huge' + elseif val == -math.huge then + return '-math.huge' + elseif tostring(val) == 'nan' then + return 'tonumber(\'nan\')' + else + return string.format("%.60f", val) + end +end + +function base_class.char_is_printable(_, c) + -- ASCII (from space to ~) and not ' or \ + return (c >= 0x20 and c < 0x7f) + and c ~= 0x27 and c ~= 0x5C +end + +function base_class.string(self, val) + assert(type(val) == 'string') + local chars = {'\''} + for i = 1, #val do + local c = string.byte(val, i) + if self:char_is_printable(c) then + table.insert(chars, string.char(c)) + else + table.insert(chars, string.format('\\%03d', c)) + end + end + table.insert(chars, '\'') + return table.concat(chars) +end + +function base_class.boolean(_, val) + assert(type(val) == 'boolean') + return tostring(val) +end + +local function ordered_iter(unordered_tt) + local keys = {} + for k in pairs(unordered_tt) do + table.insert(keys, k) + end + table.sort(keys, + function (a, b) + if type(a) ~= type(b) then + return type(a) < type(b) + end + if type(a) == 'number' then + return a < b + else + return tostring(a) < tostring(b) + end + end) + local i = 0 + return function() + i = i + 1 + if keys[i] ~= nil then + return keys[i], unordered_tt[keys[i]] + end + end +end + +function base_class.table(self, tab) + assert(type(tab) == 'table') + if self.done[tab] then + error('cyclic reference', 0) + end + self.done[tab] = true + + local items = {'{'} + local previdx = 0 + self:indent_inc() + for idx, val in ordered_iter(tab) do + local errors, valok, valexpr, valnote, idxok, idxexpr, idxnote + errors = {} + -- push current index onto key path stack to make it available to sub-printers + table.insert(self.tab_key_path, idx) + + valok, valexpr, valnote = pcall(self.val2expr, self, val) + if not valok then + table.insert(errors, string.format('value: %s', valexpr)) + end + + local addidx + if previdx and type(idx) == 'number' and idx - 1 == previdx then + -- monotonic sequence, do not print key + previdx = idx + addidx = false + else + -- end of monotonic sequence + -- from now on print keys as well + previdx = nil + addidx = true + end + + if addidx then + idxok, idxexpr, idxnote = pcall(self.val2expr, self, idx) + if not idxok or idxexpr == 'nil' then + table.insert(errors, string.format('key: not serializable', idxexpr)) + end + end + + local item = '' + if #errors == 0 then + -- finally serialize one [key=]?value expression + local indent = self:indent_head() + local note + if addidx then + note = self:format_note(idxnote, nil, self.key_val_sep) + item = string.format('%s%s[%s]%s=%s', + indent, note, + idxexpr, self.key_val_sep, self.key_val_sep) + indent = '' + end + note = self:format_note(valnote, nil, self.item_sep) + item = item .. string.format('%s%s%s,', indent, note, valexpr) + else + local errmsg = string.format('cannot print %s = %s (%s)', + self:string(tostring(idx)), + self:string(tostring(val)), + table.concat(errors, ', ')) + if self.on_unrepresentable == 'error' then + error(errmsg, 0) + else + errmsg = string.format('--[[ missing %s ]]', errmsg) + item = errmsg + end + end + table.insert(items, item) + table.remove(self.tab_key_path) -- pop current index from key path stack + end -- one key+value + self:indent_dec() + table.insert(items, self:indent_head() .. '}') + return table.concat(items, self.item_sep), string.format('%s follows', tab) +end + +-- machine readable variant, cannot represent all types and repeated references to a table +local serializer_class = { + indent_step = 0, + item_sep = ' ', + key_val_sep = ' ', + __inst_mt = {} +} +-- inheritance form base class (for :new()) +setmetatable(serializer_class, { __index = base_class }) +-- class instances with following metatable inherit all class members +serializer_class.__inst_mt.__index = serializer_class + +local function static_serializer(val, on_unrepresentable) + local inst = serializer_class:new(on_unrepresentable) + local expr, note = inst:val2expr(val) + return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr) + end + +-- human friendly variant, not stable and not intended for machine consumption +local pprinter_class = { + indent_step = 4, + item_sep = '\n', + key_val_sep = ' ', + __inst_mt = {}, +} + +-- should be always empty because pretty-printer has fallback for all types +function pprinter_class.format_note() + return '' +end + +function pprinter_class._fallback(self, val) + if self.on_unrepresentable == 'error' then + base_class._fallback(self, val) + end + return tostring(val) +end + +function pprinter_class.char_is_printable(_, c) + -- ASCII (from space to ~) + tab or newline + -- and not ' or \ + return ((c >= 0x20 and c < 0x7f) + or c == 0x09 or c == 0x0A) + and c ~= 0x27 and c ~= 0x5C +end + +-- "function" is a Lua keyword so assignment below is workaround to create +-- function pprinter_class.function(self, f) +pprinter_class['function'] = function(self, f) +-- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of +-- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1 + assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)") + local debuginfo = debug.getinfo(f) + local func_args = {} + local args_str + if debuginfo.what == 'C' then -- names N/A + args_str = '(?)' + goto add_name + end + + pcall(function() + local oldhook + local delay = 2 + local function hook() + delay = delay - 1 + if delay == 0 then -- call this only for the introspected function + -- stack depth 2 is the introspected function + for i = 1, debuginfo.nparams do + local k = debug.getlocal(2, i) + table.insert(func_args, k) + end + if debuginfo.isvararg then + table.insert(func_args, "...") + end + debug.sethook(oldhook) + error('aborting the call to introspected function') + end + end + oldhook = debug.sethook(hook, "c") -- invoke hook() on function call + f(unpack({})) -- huh? + end) + args_str = "(" .. table.concat(func_args, ", ") .. ")" + ::add_name:: + local name + if #self.tab_key_path > 0 then + name = string.format('function %s', self.tab_key_path[#self.tab_key_path]) + else + name = 'function ' + end + return string.format('%s%s: %s', name, args_str, string.sub(tostring(f), 11)) +end + +-- default tostring method is better suited for human-intended output +function pprinter_class.number(_, number) + return tostring(number) +end + +local function deserialize_lua(serial) + assert(type(serial) == 'string') + local deserial_func = loadstring('return ' .. serial) + if type(deserial_func) ~= 'function' then + panic('input is not a valid Lua expression') + end + return deserial_func() +end + +setmetatable(pprinter_class, { __index = base_class }) +pprinter_class.__inst_mt.__index = pprinter_class + +local function static_pprint(val, on_unrepresentable) + local inst = pprinter_class:new(on_unrepresentable) + local expr, note = inst:val2expr(val) + return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr) +end + +local M = { + serialize_lua = static_serializer, + deserialize_lua = deserialize_lua, + pprint = static_pprint +} + +return M diff --git a/daemon/lua/krprint.test.lua b/daemon/lua/krprint.test.lua new file mode 100644 index 0000000..9218052 --- /dev/null +++ b/daemon/lua/krprint.test.lua @@ -0,0 +1,292 @@ +local serialize_lua = require('krprint').serialize_lua +local deserialize_lua = require('krprint').deserialize_lua + +local function gen_string(maxlen) + maxlen = maxlen or 100 + local len = math.random(0, maxlen) + local buf = {} + for _=1,len do + table.insert(buf, string.char(math.random(0, 255))) + end + return table.concat(buf) +end + +local function test_de_serialization(orig_val, desc) + local serial = serialize_lua(orig_val) + ok(type(serial) == 'string' and #serial > 0, + 'serialization returns non-empty string: ' .. desc) + local deserial_val = deserialize_lua(serial) + same(type(orig_val), type(deserial_val), + 'deserialized value has the same type: ' .. desc) + if type(orig_val) == 'number' then + -- nan cannot be compared using == operator + if tostring(orig_val) == 'nan' and tostring(deserial_val) == 'nan' then + pass('nan value serialized and deserialized') + elseif orig_val ~= math.huge and orig_val ~= -math.huge then + -- tolerance measured experimentally on x86_64 LuaJIT 2.1.0-beta3 + local tolerance = 1e-14 + ok(math.abs(orig_val - deserial_val) <= tolerance, + 'deserialized number is within tolerance ' .. tolerance) + else + same(orig_val, deserial_val, 'deserialization returns the same infinity:' .. desc) + end + else + same(orig_val, deserial_val, + 'deserialization returns the same value: ' .. desc) + end +end + +local function test_de_serialization_autodesc(orig_val) + test_de_serialization(orig_val, tostring(orig_val)) +end + +local function test_bool() + test_de_serialization_autodesc(true) + same('true', table_print(true), 'table_print handles true') + test_de_serialization_autodesc(false) + same('false', table_print(false), 'table_print handles false') +end + +local function test_nil() + test_de_serialization_autodesc(nil) + same('nil', table_print(nil), 'table_print handles nil') +end + +local function gen_number_int() + local number + -- make "small" numbers more likely so they actually happen + if math.random() < 0.5 then + number = math.random(-2^32, 2^32) + else + number = math.random(-2^48, 2^48) + end + return number +end + +local function gen_number_float() + return math.random() +end + +local function test_number() + test_de_serialization_autodesc(0) + same('0', table_print(0), 'table_print handles 0') + test_de_serialization_autodesc(-math.huge) + same('-inf', table_print(-math.huge), 'table_print handles -infinity') + test_de_serialization_autodesc(math.huge) + same('inf', table_print(math.huge), 'table_print handles +infinity') + test_de_serialization_autodesc(tonumber('nan')) + same('nan', table_print(tonumber('nan')), 'table_print handles nan') + for _=1,20 do -- integers + test_de_serialization_autodesc(gen_number_int()) + -- bigger numbers might end up with non-exact representation + local smallnumber = math.random(-2^32, 2^32) + same(tostring(smallnumber), table_print(smallnumber), + 'table_print handles small numbers') + end + for _=1,20 do -- floats + local float = math.random() + same(tostring(float), table_print(float), + 'table_print handles floats') + test_de_serialization_autodesc(gen_number_float()) + end +end + +local function test_string() + test_de_serialization('', 'empty string') + for _=1,20 do + local str = gen_string(1024*10) + test_de_serialization(str, 'random string length ' .. #str) + end +end + +local function gen_number() + -- pure random would not produce special cases often enough + local generators = { + function() return 0 end, + function() return -math.huge end, + function() return math.huge end, + gen_number_int, + gen_number_float, + } + return generators[math.random(1, #generators)]() +end + +local function gen_boolean() + local options = {true, false} + return options[math.random(1, #options)] +end + +local function gen_table_atomic() + -- nil keys or values are not allowed + -- nested tables are handled elsewhere + local supported_types = { + gen_number, + gen_string, + gen_boolean, + } + val = supported_types[math.random(1, #supported_types)]() + return val +end + +local function gen_test_tables_supported(level) + level = level or 1 + local max_level = 5 + local max_items_per_table = 20 + local t = {} + for _=1, math.random(0, max_items_per_table) do + local val_as_table = (level <= max_level) and math.random() < 0.1 + local key, val + -- tapered.same method cannot compare keys with type table + key = gen_table_atomic() + if val_as_table then + val = gen_test_tables_supported(level + 1) + else + val = gen_table_atomic() + end + t[key] = val + end + return t +end + +local marker = 'this string must be present somewhere in output' +local function gen_marker() + return marker +end + +local kluautil = require('kluautil') +local function random_modify_table(t, always, generator) + assert(generator) + local tab_len = kluautil.kr_table_len(t) + local modified = false + -- modify some values + for key, val in pairs(t) do + if math.random(1, tab_len) == 1 then + if type(val) == 'table' then + modified = modified or random_modify_table(val, false, generator) + else + t[key] = generator() + modified = true + end + end + end + if always and not modified then + -- fallback, add an unsupported key + t[generator()] = true + modified = true + end + return modified +end + +local function test_table_supported() + for i=1,10 do + local t = gen_test_tables_supported() + test_de_serialization(t, 'random table no. ' .. i) + assert(random_modify_table(t, true, gen_marker)) + local str = table_print(t) + ok(string.find(str, marker, 1, true), + 'table_print works on complex serializable tables') + end +end + +local ffi = require('ffi') +local const_func = tostring +local const_thread = coroutine.create(tostring) +local const_userdata = ffi.C +local const_cdata = ffi.new('int') + +local function gen_unsupported_atomic() + -- nested tables are handled elsewhere + local unsupported_types = { + const_func, + const_thread, + const_userdata, + const_cdata + } + val = unsupported_types[math.random(1, #unsupported_types)] + return val +end + +local function test_unsupported(val, desc) + desc = desc or string.format('unsupported %s', type(val)) + return function() + boom(serialize_lua, { val, 'error' }, string.format( + 'attempt to serialize %s in error mode ' + .. 'causes error', desc)) + local output = serialize_lua(val, 'comment') + same('string', type(output), + string.format('attempt to serialize %s in ' + .. 'comment mode returned a string', + desc)) + ok(string.find(output, '--', 1, true), + 'returned string contains a comment') + output = table_print(val) + same('string', type(output), + string.format('table_print can stringify %s', desc)) + if type(val) ~= 'table' then + ok(string.find(output, type(val), 1, true), + 'exotic type is mentioned in table_print output') + end + end +end + +local function gen_test_tables_unsupported() + local t = gen_test_tables_supported() + random_modify_table(t, true, gen_unsupported_atomic) + return t +end + +local function test_unsupported_table() + for i=1,10 do + local t = gen_test_tables_unsupported() + test_unsupported(t, 'random unsupported table no. ' .. i)() + assert(random_modify_table(t, true, gen_marker)) + local str = table_print(t) + ok(string.find(str, marker, 1, true), + 'table_print works on complex unserializable tables') + end +end + +local function func_2vararg_5ret(arg1, arg2, ...) + return select('#', ...), nil, arg1 + arg2, false, nil +end +local function func_ret_nil() return nil end +local function func_ret_nothing() return end + +local function test_pprint_func() + local t = { [false] = func_2vararg_5ret } + local output = table_print(t) + ok(string.find(output, 'function false(arg1, arg2, ...)', 1, true), + 'function parameters are pretty printed') +end + +local function test_pprint_func_ret() + local output = table_print(func_2vararg_5ret(1, 2, 'bla')) + local exp = [[ +1 -- result # 1 +nil -- result # 2 +3 -- result # 3 +false -- result # 4 +nil -- result # 5]] + same(output, exp, 'multiple return values are pretty printed') + + output = table_print(func_ret_nil()) + same(output, 'nil', 'single return value does not have extra comments') + + output = table_print(func_ret_nothing()) + same(output, nil, 'no return values to be printed cause nil output') +end + +return { + test_bool, + test_nil, + test_number, + test_string, + test_table_supported, + test_unsupported(const_func), + test_unsupported(const_thread), + test_unsupported(const_userdata), + test_unsupported(const_cdata), + test_unsupported_table, + test_pprint_func, + test_pprint_func_ret, +} diff --git a/daemon/lua/log.test.lua b/daemon/lua/log.test.lua new file mode 100644 index 0000000..ec5abd2 --- /dev/null +++ b/daemon/lua/log.test.lua @@ -0,0 +1,42 @@ +local function test_log_level() + same(log_level(), 'notice', 'default level is notice') + same(verbose(), false, 'verbose is not set by default') + same(log_level('crit'), 'crit', '"crit" level can be set') + same(log_level('err'), 'err', '"err" level can be set') + same(log_level('warning'), 'warning', '"warning" level can be set') + same(log_level('notice'), 'notice', '"notice" level can be set') + same(log_level('info'), 'info', '"info" level can be set') + same(log_level('debug'), 'debug', '"debug" level can be set') + same(verbose(), true, 'verbose is active when debug level is set') + same(verbose(false), false, 'verbose can be used to turn off debug level') + same(log_level(), 'notice', 'verbose returns log level to notice') + boom(log_level, { 'xxx' }, "unknown level can't be used") + boom(log_level, { 7 }, "numbered levels aren't supported") + boom(log_level, { 1, 2 }, "level doesn't take multiple arguments") +end + +local function test_log_target() + same(log_target(), 'stderr', 'default target is stderr') + same(log_target('stdout'), 'stdout', 'stdout target can be set') + same(log_target('syslog'), 'syslog', 'syslog target can be set') + same(log_target('stderr'), 'stderr', 'stderr target can be set') + boom(log_level, { 'xxx' }, "unknown target can't be used") + boom(log_level, { 'stderr', 'syslog' }, "target doesn't take multiple arguments") +end + +local function test_log_groups() + same(log_groups(), {}, 'no groups are logged by default') + same(log_groups({'system'}), {'system'}, 'configure "system" group') + same(log_groups({'devel'}), {'devel'}, 'another call overrides previously set groups') + same(log_groups({'devel', 'system'}), {'system', 'devel'}, 'configure multiple groups') + same(log_groups({}), {}, 'clear groups with empty table') + same(log_groups({'nonexistent'}), {}, "nonexistent group is ignored") + boom(log_groups, { 'string' }, "group argument can't be string") + boom(log_groups, { 1, 2 }, "group doesn't take multiple arguments") +end + +return { + test_log_level, + test_log_target, + test_log_groups, +} diff --git a/daemon/lua/map.test.integr/deckard.yaml b/daemon/lua/map.test.integr/deckard.yaml new file mode 100644 index 0000000..2fe920d --- /dev/null +++ b/daemon/lua/map.test.integr/deckard.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd3 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua +- name: kresd2 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua +- name: kresd1 + binary: kresd + additional: + - --noninteractive + templates: + - daemon/lua/map.test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + - tests/config/tapered/src/tapered.lua + configs: + - config + - hints + - tapered.lua diff --git a/daemon/lua/map.test.integr/kresd_config.j2 b/daemon/lua/map.test.integr/kresd_config.j2 new file mode 100644 index 0000000..ae403c7 --- /dev/null +++ b/daemon/lua/map.test.integr/kresd_config.j2 @@ -0,0 +1,193 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local ffi = require('ffi') +log_info(ffi.C.LOG_GRP_TESTS, 'my PID = %d', worker.pid) + +trust_anchors.remove('.') + +cache.size = 2*MB + +net = { '{{SELF_ADDR}}' } + +{% if QMIN == "false" %} +option('NO_MINIMIZE', true) +{% else %} +option('NO_MINIMIZE', false) +{% endif %} + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) + +local kluautil = require('kluautil') +local tap = require('tapered') +local checks_total = 16 +local n_instances = 3 -- must match deckard.yaml + +worker.control_path = worker.cwd .. '/../kresd3/control/' +net.listen(worker.control_path .. worker.pid, nil, {kind = 'control'}) +assert(#net.list() >= 3) -- UDP, TCP, control + +-- debug, kept for future use +--log_level("debug") +log_debug(ffi.C.LOG_GRP_TESTS, '%s', worker.control_path) +log_debug(ffi.C.LOG_GRP_TESTS, '%s', table_print(net.list())) + +function wait_for_sockets() + log_info(ffi.C.LOG_GRP_TESTS, 'waiting for control sockets') + local timeout = 5000 -- ms + local start_time = tonumber(ffi.C.kr_now()) + local now + while true do + now = tonumber(ffi.C.kr_now()) + if now > start_time + timeout then + log_info(ffi.C.LOG_GRP_TESTS, 'timeout while waiting for control sockets to appear') + os.exit(3) + end + local pids = kluautil.list_dir(worker.control_path) + if #pids == n_instances then + -- debug, kept for future use + log_debug(ffi.C.LOG_GRP_TESTS, 'got control sockets:') + log_debug(ffi.C.LOG_GRP_TESTS, table_print(pids)) + break + else + worker.sleep(0.1) + end + end + log_info(ffi.C.LOG_GRP_TESTS, 'PIDs are visible now (waiting took %d ms)', now - start_time) +end + +-- expression should throw Lua error: +-- wrap it in a function which runs the expression on leader and follower +-- separately so we can guarantee both cases are covered +function boom_follower_and_leader(boom_expr, desc) + local variants = {leader = '~=', follower = '=='} + for name, operator in pairs(variants) do + -- beware, newline is not allowed in expr + local full_expr = string.format( + 'if (worker.pid %s %s) then return true ' + .. 'else return %s end', + operator, worker.pid, boom_expr) + local full_desc = name .. ': ' + if desc then + full_desc = full_desc .. desc .. ' (' .. boom_expr .. ')' + else + full_desc = full_desc .. boom_expr + end + tap.boom(map, {full_expr}, full_desc) + end +end + +function tests() + -- add delay to each test to force scheduler to interleave tests and DNS queries + local test_delay = 20 / 1000 -- seconds + log_info(ffi.C.LOG_GRP_TESTS, 'starting map() tests now') + + tap.boom(map, {'1 ++ 1'}, 'syntax error in command is detected') + worker.sleep(test_delay) + + -- array of integers + local pids = map('worker.pid') + tap.same(pids.n, n_instances, 'all pids were obtained') + table.sort(pids) + worker.sleep(test_delay) + + -- expression produces array of integers + local pids_plus_one = map('worker.pid + 1') + tap.same(pids_plus_one.n, n_instances, 'all pids were obtained') + table.sort(pids_plus_one) + for idx=1,n_instances do + tap.same(pids[idx] + 1, pids_plus_one[idx], + 'increment expression worked') + end + worker.sleep(test_delay) + + -- error detection + boom_follower_and_leader('error("explosion")') + worker.sleep(test_delay) + + -- unsupported number of return values + boom_follower_and_leader('1, 2') + worker.sleep(test_delay) + boom_follower_and_leader('unpack({})') + worker.sleep(test_delay) + + -- unsupported return type + boom_follower_and_leader( + 'function() print("this cannot be serialized") end') + worker.sleep(test_delay) + + tap.same({n = n_instances}, map('nil'), + 'nil values are counted as returned') + worker.sleep(test_delay) + + local exp = {n = n_instances} + for i=1,n_instances do + table.insert(exp, {nil, 2, nil, n=3}) + end + local got = map('require("kluautil").kr_table_pack(nil, 2, nil)') + tap.same(got, exp, 'kr_table_pack handles nil values') + worker.sleep(test_delay) +end + +local started = false +function tests_start() + -- just in case, duplicates should not happen + if started then + log_info(ffi.C.LOG_GRP_TESTS, 'huh? duplicate test invocation ignored, a retransmit?') + return + end + started = true + log_info(ffi.C.LOG_GRP_TESTS, 'start query triggered, scheduling tests') + + -- DNS queries and map() commands must be serviced while sleep is running + worker.coroutine(function() worker.sleep(3600) end) + + worker.coroutine(tests) +end +-- Deckard query will trigger tests +policy.add(policy.suffix(tests_start, {'\5start\0'})) + +function tests_done() + print('final query triggered') + event.after(0, function() + tap.done(checks_total) + end) +end +-- Deckard query will execute tap.done() which will call os.exit() +-- i.e. this callback has to be called only after answer to Deckard was sent +policy.add(policy.suffix(tests_done, {'\4done\0'}), true) + +-- add delay to each query to force scheduler to interleave tests and DNS queries +policy.add(policy.all( + function() + local delay = 10 -- ms + log_info(ffi.C.LOG_GRP_TESTS, 'packet delayed by %d ms', delay) + worker.sleep(delay / 1000) + end)) + +wait_for_sockets() + +{% if DAEMON_NAME == "kresd1" %} + +-- forward to Deckard test server +policy.add(policy.all(policy.FORWARD('192.0.2.1'))) + +{% else %} + +-- forward to next kresd instance in chain +{# find out IP address of kresd instance with lower number, + i.e. kresd2 forwards to kresd1 #} +policy.add(policy.all(policy.FORWARD('{{ PROGRAMS[ "kresd" ~ (DAEMON_NAME[-1]|int() - 1)]["address"] }}'))) + +{% endif %} diff --git a/daemon/lua/map.test.integr/query-while-map-is-running.rpl b/daemon/lua/map.test.integr/query-while-map-is-running.rpl new file mode 100644 index 0000000..8590fc8 --- /dev/null +++ b/daemon/lua/map.test.integr/query-while-map-is-running.rpl @@ -0,0 +1,312 @@ +; does not make any practical difference so we limit ourselves to single test run +query-minimization: off +CONFIG_END + +SCENARIO_BEGIN Empty answers to any query - forwarding without validation + +; forwarding target +RANGE_BEGIN 1 1000000 + ADDRESS 192.0.2.1 + +; NODATA to everything +ENTRY_BEGIN +MATCH opcode +ADJUST copy_id copy_query +REPLY NOERROR QR +SECTION QUESTION +. IN SOA +SECTION ANSWER +. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400 +ENTRY_END +RANGE_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +start. IN TXT +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +start. IN TXT +SECTION ANSWER +ENTRY_END + + +STEP 1001 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1001. IN TXT +ENTRY_END + +STEP 1002 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1001. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1003 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1003. IN TXT +ENTRY_END + +STEP 1004 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1003. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1005 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1005. IN TXT +ENTRY_END + +STEP 1006 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1005. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1007 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1007. IN TXT +ENTRY_END + +STEP 1008 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1007. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1009 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1009. IN TXT +ENTRY_END + +STEP 1010 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1009. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1011 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1011. IN TXT +ENTRY_END + +STEP 1012 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1011. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1013 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1013. IN TXT +ENTRY_END + +STEP 1014 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1013. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1015 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1015. IN TXT +ENTRY_END + +STEP 1016 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1015. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1017 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1017. IN TXT +ENTRY_END + +STEP 1018 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1017. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1019 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1019. IN TXT +ENTRY_END + +STEP 1020 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1019. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1021 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1021. IN TXT +ENTRY_END + +STEP 1022 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1021. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1023 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1023. IN TXT +ENTRY_END + +STEP 1024 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1023. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1025 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1025. IN TXT +ENTRY_END + +STEP 1026 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1025. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1027 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1027. IN TXT +ENTRY_END + +STEP 1028 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1027. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1029 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1029. IN TXT +ENTRY_END + +STEP 1030 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1029. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1031 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test1031. IN TXT +ENTRY_END + +STEP 1032 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +test1031. IN TXT +SECTION ANSWER +ENTRY_END + +STEP 1033 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +done. IN TXT +ENTRY_END + +STEP 1034 CHECK_ANSWER +ENTRY_BEGIN +REPLY NOERROR QR RD RA +MATCH opcode rcode flags question answer +SECTION QUESTION +done. IN TXT +SECTION ANSWER +ENTRY_END + +SCENARIO_END diff --git a/daemon/lua/meson.build b/daemon/lua/meson.build new file mode 100644 index 0000000..b19777c --- /dev/null +++ b/daemon/lua/meson.build @@ -0,0 +1,118 @@ +# daemon: lua modules +# SPDX-License-Identifier: GPL-3.0-or-later + +config_tests += [ + ['controlsock', files('controlsock.test.lua')], + ['krprint', files('krprint.test.lua')], + ['log', files('log.test.lua')], + ['ta', files('trust_anchors.test/ta.test.lua')], + ['ta_bootstrap', files('trust_anchors.test/bootstrap.test.lua'), ['y2k38']], +] + +integr_tests += [ + ['map', meson.current_source_dir() / 'map.test.integr'], +] + +lua_config = configuration_data() +lua_config.set('keyfile_default', keyfile_default) +lua_config.set('etc_dir', etc_dir) +lua_config.set('run_dir', run_dir) +lua_config.set('systemd_cache_dir', systemd_cache_dir) +lua_config.set('unmanaged', managed_ta ? 'false' : 'true') + +trust_anchors = configure_file( + input: 'trust_anchors.lua.in', + output: 'trust_anchors.lua', + configuration: lua_config, +) + +sandbox = configure_file( + input: 'sandbox.lua.in', + output: 'sandbox.lua', + configuration: lua_config, +) + +distro_preconfig = configure_file( + input: 'distro-preconfig.lua.in', + output: 'distro-preconfig.lua', + configuration: lua_config, +) + +# Unfortunately the different ABI implies different contents of 'kres-gen.lua'. +if libknot.version().version_compare('>= 3.2') + kres_gen_fname = 'kres-gen-32.lua' +elif libknot.version().version_compare('>= 3.1') + kres_gen_fname = 'kres-gen-31.lua' +else + kres_gen_fname = 'kres-gen-30.lua' +endif + +kres_gen_lua = configure_file( + input: kres_gen_fname, + output: 'kres-gen.lua', + copy: true, +) + +run_target( # run manually to re-generate kres-gen.lua + 'kres-gen', + command: [ find_program('./kres-gen.sh'), kres_gen_fname ], +) + +# A simple config test: check that sizes of some structures match +# in C and pre-generated lua bindings. +# The point is that regeneration is quite expensive in time and dependencies, +# but this basic sanity check could be ran always, except for cross compilation, +# as we *run* luajit to find out the real sizes. +if get_option('kres_gen_test') and not meson.is_cross_build() + types_to_check = [ + { 'tname': 'time_t', 'incl': '#include <sys/time.h>' }, + { 'tname': 'struct timeval', 'incl' : '#include <sys/time.h>' }, + { 'tname': 'zs_scanner_t', 'incl': '#include <libzscanner/scanner.h>', 'dep': libzscanner }, + { 'tname': 'knot_pkt_t', 'incl' : '#include <libknot/packet/pkt.h>', 'dep': libknot }, + ] + # Construct the lua tester as a meson string. + kres_gen_test_luastr = ''' + dofile('@0@') + local ffi = require('ffi') + '''.format(meson.current_source_dir() / kres_gen_fname) + foreach ttc: types_to_check + # We're careful with adding just includes; otherwise it's more fragile (e.g. linking flags). + if 'dep' in ttc + dep = ttc.get('dep').partial_dependency(includes: true, compile_args: true) + else + dep = [] + endif + tsize = meson.get_compiler('c').sizeof(ttc.get('tname'), prefix: ttc.get('incl'), + dependencies: dep) + kres_gen_test_luastr += ''' + assert(ffi.sizeof(ffi.typeof('@0@')) == @1@, + 'Lua binding for C type ' .. '@0@' .. ' has incorrect size: ' + .. ffi.sizeof(ffi.typeof('@0@')) + ) + '''.format(ttc.get('tname'), tsize) + endforeach + # Now feed it directly into luajit. + kres_gen_test = run_command(find_program('luajit'), '-e', kres_gen_test_luastr, check: false) + if kres_gen_test.returncode() != 0 + error('if you use released Knot* versions, please contact us: https://www.knot-resolver.cz/contact/\n' + + kres_gen_test.stderr().strip()) + endif +endif + +lua_src = [ + files('postconfig.lua'), + files('kres.lua'), + kres_gen_lua, + sandbox, + trust_anchors, + files('zonefile.lua'), + files('kluautil.lua'), + files('krprint.lua'), + distro_preconfig, +] + +# install daemon lua sources +install_data( + lua_src, + install_dir: lib_dir, +) diff --git a/daemon/lua/postconfig.lua b/daemon/lua/postconfig.lua new file mode 100644 index 0000000..ac71660 --- /dev/null +++ b/daemon/lua/postconfig.lua @@ -0,0 +1,70 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') +local C = ffi.C + +local function count_sockets() + local dns_socks = 0 + local control_socks = 0 + for _, socket in ipairs(net.list()) do + if socket.kind == 'control' then + control_socks = control_socks + 1 + elseif (socket.kind == 'dns' or + socket.kind == 'xdp' or + socket.kind == 'tls' or + socket.kind == 'doh_legacy' or + socket.kind == 'doh2') then + dns_socks = dns_socks + 1 + end + end + return dns_socks, control_socks +end + +local n_dns_socks, n_control_socks = count_sockets() + +-- Check and set control sockets path +worker.control_path = worker.control_path or (worker.cwd .. '/control/') + +-- Bind to control socket by default +if n_control_socks == 0 and not env.KRESD_NO_LISTEN then + local path = worker.control_path..worker.pid + local ok, err = pcall(net.listen, path, nil, { kind = 'control' }) + if not ok then + log_warn(C.LOG_GRP_NETWORK, 'bind to '..path..' failed '..err) + end +end + +-- Listen on localhost +if n_dns_socks == 0 and not env.KRESD_NO_LISTEN then + local ok, err = pcall(net.listen, '127.0.0.1') + if not ok then + error('bind to 127.0.0.1@53 '..err) + end + -- Binding to other ifaces may fail + ok, err = pcall(net.listen, '127.0.0.1', 853) + if not ok then + log_info(ffi.C.LOG_GRP_NETWORK, 'bind to 127.0.0.1@853 '..err) + end + ok, err = pcall(net.listen, '::1') + if not ok then + log_info(ffi.C.LOG_GRP_NETWORK, 'bind to ::1@53 '..err) + end + ok, err = pcall(net.listen, '::1', 853) + if not ok then + log_info(ffi.C.LOG_GRP_NETWORK, 'bind to ::1@853 '..err) + end + -- Exit when kresd isn't listening on any interfaces + n_dns_socks, _ = count_sockets() + if n_dns_socks == 0 then + panic('not listening on any interface, exiting...') + end +end +-- Open cache if not set/disabled +if not cache.current_size then + cache.size = 100 * MB +end + +-- If no addresses for root servers are set, load them from the default file +if C.kr_zonecut_is_empty(kres.context().root_hints) then + _hint_root_file() +end diff --git a/daemon/lua/sandbox.lua.in b/daemon/lua/sandbox.lua.in new file mode 100644 index 0000000..7c6a818 --- /dev/null +++ b/daemon/lua/sandbox.lua.in @@ -0,0 +1,833 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local debug = require('debug') +local ffi = require('ffi') +local kluautil = require('kluautil') +local krprint = require("krprint") + +-- Units +kB = 1024 +MB = 1024*kB +GB = 1024*MB +-- Time +sec = 1000 +second = sec +minute = 60 * sec +min = minute +hour = 60 * minute +day = 24 * hour + +-- Logging + +-- from syslog.h +LOG_CRIT = 2 +LOG_ERR = 3 +LOG_WARNING = 4 +LOG_NOTICE = 5 +LOG_INFO = 6 +LOG_DEBUG = 7 + +local function curr_file() return debug.getinfo(4,'S').source end +local function curr_line() return debug.getinfo(4,'l').currentline end + +local function log_fmt(grp, level, fmt, ...) + ffi.C.kr_log_fmt(grp, level, + 'CODE_FILE='..curr_file(), 'CODE_LINE='..curr_line(), 'CODE_FUNC=', + '[%-6s] %s\n', ffi.C.kr_log_grp2name(grp), string.format(fmt, ...)) +end + +function log_req(req, qry_uid, indent, grp, fmt, ...) + ffi.C.kr_log_req1(req, qry_uid, indent, grp, ffi.C.kr_log_grp2name(grp), + '%s\n', string.format(fmt, ...)) +end + +function log_qry(qry, grp, fmt, ...) + ffi.C.kr_log_q1(qry, grp, ffi.C.kr_log_grp2name(grp), + '%s\n', string.format(fmt, ...)) +end + +function panic(fmt, ...) + print(debug.traceback('error occurred here (config filename:lineno is ' + .. 'at the bottom, if config is involved):', 2)) + error(string.format('ERROR: '.. fmt, ...), 0) +end + +function log_error(grp, fmt, ...) + log_fmt(grp, LOG_ERR, fmt, ...) +end + +function log_warn(grp, fmt, ...) + log_fmt(grp, LOG_WARNING, fmt, ...) +end + +function log_notice(grp, fmt, ...) + log_fmt(grp, LOG_NOTICE, fmt, ...) +end + +function log_info(grp, fmt, ...) + log_fmt(grp, LOG_INFO, fmt, ...) +end + +function log_debug(grp, fmt, ...) + log_fmt(grp, LOG_DEBUG, fmt, ...) +end + +function log(fmt, ...) + log_notice(ffi.C.LOG_GRP_MODULE, fmt, ...) +end + +-- Resolver bindings +kres = require('kres') +if rawget(kres, 'str2dname') ~= nil then + todname = kres.str2dname +end + +worker.resolve_pkt = function (pkt, options, finish, init) + options = kres.mk_qflags(options) + local task = ffi.C.worker_resolve_start(pkt, options) + + -- Deal with finish and init callbacks + if finish ~= nil then + local finish_cb + finish_cb = ffi.cast('trace_callback_f', + function (req) + jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed + finish(req.answer, req) + finish_cb:free() + end) + task.ctx.req.trace_finish = finish_cb + end + if init ~= nil then + init(task.ctx.req) + end + + return ffi.C.worker_resolve_exec(task, pkt) == 0 +end + +worker.resolve = function (qname, qtype, qclass, options, finish, init) + -- Alternatively use named arguments + if type(qname) == 'table' then + local t = qname + qname = t.name + qtype = t.type + qclass = t.class + options = t.options + finish = t.finish + init = t.init + end + qtype = qtype or kres.type.A + qclass = qclass or kres.class.IN + options = kres.mk_qflags(options) + -- LATER: nicer errors for rubbish in qname, qtype, qclass? + local pkt = ffi.C.worker_resolve_mk_pkt(qname, qtype, qclass, options) + if pkt == nil then + panic('failure in worker.resolve(); probably invalid qname "%s"', qname) + end + local ret = worker.resolve_pkt(pkt, options, finish, init) + ffi.C.knot_pkt_free(pkt); + return ret +end +resolve = worker.resolve + +-- Shorthand for aggregated per-worker information +worker.info = function () + local t = worker.stats() + t.pid = worker.pid + return t +end + +-- Resolver mode of operation +local current_mode = 'normal' +local mode_table = { normal=0, strict=1, permissive=2 } +function mode(m) + if not m then return current_mode end + if not mode_table[m] then error('unsupported mode: '..m) end + -- Update current operation mode + current_mode = m + option('STRICT', current_mode == 'strict') + option('PERMISSIVE', current_mode == 'permissive') + return true +end + +-- Trivial option alias +function reorder_RR(val) + return option('REORDER_RR', val) +end + +-- Get/set resolver options via name (string) +function option(name, val) + local flags = kres.context().options; + -- Note: no way to test existence of flags[name] but we want error anyway. + name = string.upper(name) -- convenience + if val ~= nil then + if (val ~= true) and (val ~= false) then + panic('invalid option value: ' .. tostring(val)) + end + flags[name] = val; + end + return flags[name]; +end + +-- Function aliases +-- `env.VAR returns os.getenv(VAR)` +env = {} +setmetatable(env, { + __index = function (_, k) return os.getenv(k) end +}) + +debugging = {} +setmetatable(debugging, { + __index = function(_, k) + if k == 'assertion_abort' then return ffi.C.kr_dbg_assertion_abort + elseif k == 'assertion_fork' then return ffi.C.kr_dbg_assertion_fork + else panic('invalid debugging option: ' .. tostring(k)) + end + end, + __newindex = function(_, k, v) + if k == 'assertion_abort' then ffi.C.kr_dbg_assertion_abort = v + elseif k == 'assertion_fork' then ffi.C.kr_dbg_assertion_fork = v + else panic('invalid debugging option: ' .. tostring(k)) + end + end +}) + +-- Quick access to interfaces +-- `net.<iface>` => `net.interfaces()[iface]` +-- `net = {addr1, ..}` => `net.listen(name, addr1)` +-- `net.ipv{4,6} = {true, false}` => enable/disable IPv{4,6} +setmetatable(net, { + __index = function (t, k) + local v = rawget(t, k) + if v then return v + elseif k == 'ipv6' then return not option('NO_IPV6') + elseif k == 'ipv4' then return not option('NO_IPV4') + else return net.interfaces()[k] + end + end, + __newindex = function (t,k,v) + if k == 'ipv6' then return option('NO_IPV6', not v) + elseif k == 'ipv4' then return option('NO_IPV4', not v) + else + local iname = rawget(net.interfaces(), v) + if iname then t.listen(iname) + else t.listen(v) + end + end + end +}) + +-- Syntactic sugar for module loading +-- `modules.<name> = <config>` +setmetatable(modules, { + __newindex = function (_, k, v) + if type(k) == 'number' then + k, v = v, nil + end + if not rawget(_G, k) then + modules.load(k) + k = string.match(k, '[%w_]+') + local mod = _G[k] + local config = mod and rawget(mod, 'config') + if mod ~= nil and config ~= nil then + if k ~= v then config(v) + else config() + end + end + end + end +}) + +-- Set up lua table for a C module. (Internal function.) +function modules_create_table_for_c(kr_module_ud) + local kr_module = ffi.cast('struct kr_module **', kr_module_ud)[0] + --- Set up the global table named according to the module. + if kr_module.config == nil and kr_module.props == nil then + return + end + local module = {} + local module_name = ffi.string(kr_module.name) + _G[module_name] = module + + --- Construct lua functions for properties. + if kr_module.props ~= nil then + local i = 0 + while true do + local prop = kr_module.props[i] + local cb = prop.cb + if cb == nil then break; end + module[ffi.string(prop.name)] = + function (arg) -- lua wrapper around kr_prop_cb function typedef + local arg_conv + if type(arg) == 'table' or type(arg) == 'boolean' then + arg_conv = tojson(arg) + elseif arg ~= nil then + arg_conv = tostring(arg) + end + local ret_cstr = cb(ffi.C.the_worker.engine, kr_module, arg_conv) + if ret_cstr == nil then + return nil + end + -- LATER(optim.): superfluous copying + local ret_str = ffi.string(ret_cstr) + -- This is a bit ugly, but the API is that invalid JSON + -- should be just returned as string :-( + local status, ret = pcall(fromjson, ret_str) + if not status then ret = ret_str end + ffi.C.free(ret_cstr) + return ret + end + i = i + 1 + end + end + + --- Construct lua function for config(). + if kr_module.config ~= nil then + module.config = + function (arg) + local arg_conv + if type(arg) == 'table' or type(arg) == 'boolean' then + arg_conv = tojson(arg) + elseif arg ~= nil then + arg_conv = tostring(arg) + end + return kr_module.config(kr_module, arg_conv) + end + end + + --- Add syntactic sugar for get() and set() properties. + --- That also "catches" any commands like `moduleName.foo = bar`. + local m_index, m_newindex + local get_f = rawget(module, 'get') + if get_f ~= nil then + m_index = function (_, key) + return get_f(key) + end + else + m_index = function () + error('module ' .. module_name .. ' does not support indexing syntax sugar') + end + end + local set_f = rawget(module, 'set') + if set_f ~= nil then + m_newindex = function (_, key, value) + -- This will produce a nasty error on some non-string parameters. + -- Still, we already use it with integer values, e.g. in predict module :-/ + return set_f(key .. ' ' .. value) + end + else + m_newindex = function () + error('module ' .. module_name .. ' does not support assignment syntax sugar') + end + end + setmetatable(module, { + -- note: the two functions only get called for *missing* indices + __index = m_index, + __newindex = m_newindex, + }) +end + +local layer_ctx = ffi.C.kr_layer_t_static +-- Utilities internal for lua layer glue; see ../ffimodule.c +modules_ffi_layer_wrap1 = function (layer_cb) + return layer_cb(layer_ctx.state, layer_ctx.req) +end +modules_ffi_layer_wrap2 = function (layer_cb) + return layer_cb(layer_ctx.state, layer_ctx.req, layer_ctx.pkt) +end +modules_ffi_layer_wrap_checkout = function (layer_cb) + return layer_cb(layer_ctx.state, layer_ctx.req, layer_ctx.pkt, + layer_ctx.dst, layer_ctx.is_stream) +end +modules_ffi_wrap_modcb = function (cb, kr_module_ud) -- this one isn't for layer + local kr_module = ffi.cast('struct kr_module **', kr_module_ud)[0] + return cb(kr_module) +end + +-- Return filesystem size where the cache resides. +cache.fssize = function () + local path = cache.current_storage or '.' + -- As it is now, `path` may or may not include the lmdb:// prefix. + if string.sub(path, 1, 7) == 'lmdb://' then + path = string.sub(path, 8) + end + if #path == 0 then + path = '.' + end + local size = tonumber(ffi.C.kr_fssize(path)) + if size < 0 then + panic('cache.fssize(): %s', ffi.string(ffi.C.knot_strerror(size))) + else + return size + end +end + +cache.clear = function (name, exact_name, rr_type, chunk_size, callback, prev_state) + if name == nil or (name == '.' and not exact_name) then + -- keep same output format as for 'standard' clear + local total_count = cache.count() + if not cache.clear_everything() then + error('unable to clear everything') + end + return {count = total_count} + end + -- Check parameters, in order, and set defaults if missing. + local dname = kres.str2dname(name) + if not dname then error('cache.clear(): incorrect name passed') end + if exact_name == nil then exact_name = false end + if type(exact_name) ~= 'boolean' + then error('cache.clear(): incorrect exact_name passed') end + + local cach = kres.context().cache; + local rettable = {} + -- Apex warning. If the caller passes a custom callback, + -- we assume they are advanced enough not to need the check. + -- The point is to avoid repeating the check in each callback iteration. + if callback == nil then + local apex_array = ffi.new('knot_dname_t *[1]') -- C: dname **apex_array + local ret = ffi.C.kr_cache_closest_apex(cach, dname, false, apex_array) + if ret < 0 then + error(ffi.string(ffi.C.knot_strerror(ret))) end + if not ffi.C.knot_dname_is_equal(apex_array[0], dname) then + local apex_str = kres.dname2str(apex_array[0]) + rettable.not_apex = 'to clear proofs of non-existence call ' + .. 'cache.clear(\'' .. tostring(apex_str) ..'\')' + rettable.subtree = apex_str + end + ffi.C.free(apex_array[0]) + end + + if rr_type ~= nil then + -- Special case, without any subtree searching. + if not exact_name + then error('cache.clear(): specifying rr_type only supported with exact_name') end + if chunk_size or callback + then error('cache.clear(): chunk_size and callback parameters not supported with rr_type') end + local ret = ffi.C.kr_cache_remove(cach, dname, rr_type) + if ret < 0 then error(ffi.string(ffi.C.knot_strerror(ret))) end + return {count = 1} + end + + if chunk_size == nil then chunk_size = 100 end + if type(chunk_size) ~= 'number' or chunk_size <= 0 + then error('cache.clear(): chunk_size has to be a positive integer') end + + -- Do the C call, and add chunk_size warning. + rettable.count = ffi.C.kr_cache_remove_subtree(cach, dname, exact_name, chunk_size) + if rettable.count == chunk_size then + local msg_extra = '' + if callback == nil then + msg_extra = '; the default callback will continue asynchronously' + end + rettable.chunk_limit = 'chunk size limit reached' .. msg_extra + end + + -- Default callback function: repeat after 1ms + if callback == nil then callback = + function (cbname, cbexact_name, cbrr_type, cbchunk_size, cbself, cbprev_state, cbrettable) + if cbrettable.count < 0 then error(ffi.string(ffi.C.knot_strerror(cbrettable.count))) end + if cbprev_state == nil then cbprev_state = { round = 0 } end + if type(cbprev_state) ~= 'table' + then error('cache.clear() callback: incorrect prev_state passed') end + cbrettable.round = cbprev_state.round + 1 + if (cbrettable.count == cbchunk_size) then + event.after(1, function () + cache.clear(cbname, cbexact_name, cbrr_type, cbchunk_size, cbself, cbrettable) + end) + elseif cbrettable.round > 1 then + log_info(ffi.C.LOG_GRP_CACHE, 'asynchronous cache.clear(\'' .. cbname .. '\', ' + .. tostring(cbexact_name) .. ') finished') + end + return cbrettable + end + end + return callback(name, exact_name, rr_type, chunk_size, callback, prev_state, rettable) +end +-- Syntactic sugar for cache +-- `cache[x] -> cache.get(x)` +-- `cache.{size|storage} = value` +setmetatable(cache, { + __index = function (t, k) + local res = rawget(t, k) + if not res and not rawget(t, 'current_size') then return res end + -- Beware: t.get returns empty table on failure to find. + -- That would be confusing here (breaking kresc), so return nil instead. + res = t.get(k) + if res and next(res) ~= nil then return res else return nil end + end, + __newindex = function (t,k,v) + -- Defaults + local storage = rawget(t, 'current_storage') + if not storage then storage = 'lmdb://' end + local size = rawget(t, 'current_size') + if not size then size = 10*MB end + -- Declarative interface for cache + if k == 'size' then t.open(v, storage) + elseif k == 'storage' then t.open(size, v) end + end +}) + +-- Make sandboxed environment +local function make_sandbox(defined) + local __protected = { + worker = true, env = true, debugging = true, modules = true, + cache = true, net = true, trust_anchors = true + } + + -- Compute and export the list of top-level names (hidden otherwise) + local nl = "" + for n in pairs(defined) do + nl = nl .. n .. "\n" + end + + return setmetatable({ __orig_name_list = nl }, { + __index = defined, + __newindex = function (_, k, v) + if __protected[k] then + for k2,v2 in pairs(v) do + defined[k][k2] = v2 + end + else + defined[k] = v + end + end + }) +end + +-- Compatibility sandbox +_G = make_sandbox(getfenv(0)) +setfenv(0, _G) + +-- Load default modules +trust_anchors = require('trust_anchors') +modules.load('ta_update') +modules.load('ta_signal_query') +modules.load('policy') +modules.load('priming') +modules.load('detect_time_skew') +modules.load('detect_time_jump') +modules.load('ta_sentinel') +modules.load('edns_keepalive') +modules.load('refuse_nord') +modules.load('watchdog') +modules.load('extended_error') + +-- Load keyfile_default +trust_anchors.add_file('@keyfile_default@', @unmanaged@) + +local function eval_cmd_compile(line, raw) + -- Compatibility sandbox code loading + local function load_code(code) + if getfenv then -- Lua 5.1 + return loadstring(code) + else -- Lua 5.2+ + return load(code, nil, 't', _ENV) + end + end + local err, chunk + chunk, err = load_code(raw and 'return '..line or 'return table_print('..line..')') + if err then + chunk, err = load_code(line) + end + return chunk, err +end + +-- Interactive command evaluation +function eval_cmd(line, raw) + local chunk, err = eval_cmd_compile(line, raw) + if not err then + return chunk() + else + error(err) + end +end + +-- Pretty printing +local pprint = require('krprint').pprint +function table_print(...) + local strs = {} + local nargs = select('#', ...) + if nargs == 0 then + return nil + end + for n=1,nargs do + local arg = select(n, ...) + local arg_str = pprint(arg) + if nargs > 1 then + table.insert(strs, string.format("%s\t-- result # %d", arg_str, n)) + else + table.insert(strs, arg_str) + end + end + return table.concat(strs, '\n') +end + +-- This extends the worker module to allow asynchronous execution of functions and nonblocking I/O. +-- The current implementation combines cqueues for Lua interface, and event.socket() in order to not +-- block resolver engine while waiting for I/O or timers. +-- +local has_cqueues, cqueues = pcall(require, 'cqueues') +if has_cqueues then + + -- Export the asynchronous sleep function + worker.sleep = cqueues.sleep + + -- Create metatable for workers to define the API + -- It can schedule multiple cqueues and yield execution when there's a wait for blocking I/O or timer + local asynchronous_worker_mt = { + work = function (self) + local ok, err, _, co = self.cq:step(0) + if not ok then + log_warn(ffi.C.LOG_GRP_SYSTEM, '%s error: %s %s', self.name or 'worker', err, debug.traceback(co)) + end + -- Reschedule timeout or create new one + local timeout = self.cq:timeout() + if timeout then + -- Throttle timeouts to avoid too frequent wakeups + if timeout == 0 then timeout = 0.00001 end + -- Convert from seconds to duration + timeout = timeout * sec + if not self.next_timeout then + self.next_timeout = event.after(timeout, self.on_step) + else + event.reschedule(self.next_timeout, timeout) + end + else -- Cancel running timeout when there is no next deadline + if self.next_timeout then + event.cancel(self.next_timeout) + self.next_timeout = nil + end + end + end, + wrap = function (self, f) + self.cq:wrap(f) + end, + loop = function (self) + self.on_step = function () self:work() end + self.event_fd = event.socket(self.cq:pollfd(), self.on_step) + end, + close = function (self) + if self.event_fd then + event.cancel(self.event_fd) + self.event_fd = nil + end + end, + } + + -- Implement the coroutine worker with cqueues + local function worker_new (name) + return setmetatable({name = name, cq = cqueues.new()}, { __index = asynchronous_worker_mt }) + end + + -- Create a default background worker + worker.bg_worker = worker_new('worker.background') + worker.bg_worker:loop() + + -- Wrap a function for asynchronous execution + function worker.coroutine (f) + worker.bg_worker:wrap(f) + end +else + -- Disable asynchronous execution + local function disabled () + error('Lua library cqueues is required for asynchronous execution (luaJIT requires library for Lua 5.1)') + end + worker.sleep = disabled + worker.map = disabled + worker.coroutine = disabled + worker.bg_worker = setmetatable({}, { __index = disabled }) +end + +-- Global commands for map() + +-- must be public because it is called from eval_cmd() +-- when map() commands are read from control socket +function _map_luaobj_call_wrapper(cmd) + local func = eval_cmd_compile(cmd, true) + local ret = kluautil.kr_table_pack(xpcall(func, debug.traceback)) + local ok, serial = pcall(krprint.serialize_lua, ret, 'error') + if not ok then + log_error(ffi.C.LOG_GRP_SYSTEM, 'failed to serialize map() response %s (%s)', + table_print(ret), serial) + return krprint.serialize_lua( + kluautil.kr_table_pack(false, "returned values cannot be serialized: " + .. serial)) + else + return serial + end +end + +local function _sock_errmsg(path, desc) + return string.format( + 'map() error while communicating with %s: %s', + path, desc) +end + +local function _sock_check(sock, call, params, path, desc) + local errprefix = _sock_errmsg(path, desc) .. ': ' + local retvals = kluautil.kr_table_pack(pcall(call, unpack(params))) + local ok = retvals[1] + if not ok then + error(errprefix .. tostring(retvals[2])) + end + local rerr, werr = sock:error() + if rerr or werr then + error(string.format('%sread error %s; write error %s', errprefix, rerr, werr)) + end + if retvals[2] == nil then + error(errprefix .. 'unexpected nil result') + end + return unpack(retvals, 2, retvals.n) +end + +local function _sock_assert(condition, path, desc) + if not condition then + error(_sock_errmsg(path, desc)) + end +end + +local function map_send_recv(cmd, path) + local bit = require('bit') + local socket = require('cqueues.socket') + local s = socket.connect({ path = path }) + s:setmaxerrs(0) + s:setmode('bn', 'bn') + local status, err = pcall(s.connect, s) + if not status then + log_error(ffi.C.LOG_GRP_NETWORK, 'map() error while connecting to control socket %s: ' + .. '%s (ignoring this socket)', path, err) + return nil + end + local ret = _sock_check(s, s.write, {s, '__binary\n'}, path, + 'write __binary') + _sock_assert(ret, path, + 'write __binary result') + local recv = _sock_check(s, s.read, {s, 2}, path, + 'read reply to __binary') + _sock_assert(recv and recv == '> ', path, + 'unexpected reply to __binary') + _sock_check(s, s.write, {s, cmd..'\n'}, path, + 'command write') + recv = _sock_check(s, s.read, {s, 4}, path, + 'response length read') + _sock_assert(recv and #recv == 4, path, + 'length of response length preamble does not match') + local len = tonumber(recv:byte(1)) + for i=2,4 do + len = bit.bor(bit.lshift(len, 8), tonumber(recv:byte(i))) + end + ret = _sock_check(s, s.read, {s, len}, path, + 'read response') + _sock_assert(ret and #ret == len, path, + 'actual response length does not match length in preamble') + s:close() + return ret +end + +-- internal use only +-- Call cmd on each instance via control sockets. +-- @param format - "luaobj" if individual results should be Lua objects +-- - "strings" for eval_cmd output for each instance +-- @returns table with results, one item per instance + key n=number of instances +-- (order of return values is undefined) +-- @throws Lua error if: +-- - communication failed in the middle of transaction +-- - a result is not serializable +-- - individual call throws an error +-- - number of return values != 1 per instance per call +-- - cmd execution state is undefined after an error +-- Connection errors at the beginning are ignored to paper over leftover dead sockets. +function map(cmd, format) + local local_sockets = {} + local results = {} + + if (type(cmd) ~= 'string') then + panic('map() command must be a string') end + if string.find(cmd, '\n', 1, true) then + panic('map() command cannot contain literal \\n, escape it with \\010') end + if (#cmd <= 0) then + panic('map() command must be non-empty') end + -- syntax check on input command to detect typos early + local chunk, err = eval_cmd_compile(cmd, false) + if not chunk then + panic('failure when compiling map() command: %s', err) + end + + format = format or 'luaobj' + if (format ~= 'luaobj' and format ~= 'strings') then + panic('map() output format must be luaobj or strings') end + if format == 'luaobj' then + cmd = '_map_luaobj_call_wrapper([=====[' .. cmd .. ']=====])' + end + + -- find out control socket paths + for _,v in pairs(net.list()) do + if (v['kind'] == 'control') and (v['transport']['family'] == 'unix') then + table.insert(local_sockets, string.match(v['transport']['path'], '^.*/([^/]+)$')) + end + end + local filetab = kluautil.list_dir(worker.control_path) + if next(filetab) == nil then + panic('no control sockets found in directory %s', + worker.control_path) + end + + local result_count = 0 + -- finally execute it on all instances + for _, file in ipairs(filetab) do + local local_exec = false + for _, lsoc in ipairs(local_sockets) do + if file == lsoc then + local_exec = true + end + end + local path = worker.control_path..file + local path_name = (local_exec and 'this instance') or path + log_info(ffi.C.LOG_GRP_SYSTEM, 'executing map() on %s: command %s', path_name, cmd) + local ret + if local_exec then + ret = eval_cmd(cmd) + else + ret = map_send_recv(cmd, path) + -- skip dead sockets (leftovers from dead instances) + if ret == nil then + goto continue + end + end + result_count = result_count + 1 + -- return value is output from eval_cmd + -- i.e. string including "quotes" and Lua escaping in between + assert(type(ret) == 'string', 'map() protocol error, ' + .. 'string not retured by follower') + assert(#ret >= 2 and + string.sub(ret, 1, 1) == "'" + and string.sub(ret, -1, -1) == "'", + 'map() protocol error, value returned by follower does ' + .. 'not look like a string') + -- deserialize string: remove "quotes" and de-escape bytes + ret = krprint.deserialize_lua(ret) + if format == 'luaobj' then + -- ret should be table with xpcall results serialized into string + ret = krprint.deserialize_lua(ret) + assert(type(ret) == 'table', 'map() protocol error, ' + .. 'table with results not retured by follower') + if (ret.n ~= 2) then + log_error(ffi.C.LOG_GRP_SYSTEM, 'got unsupported map() response: %s', table_print(ret)) + panic('unexpected number of return values in map() response: ' + .. 'only single return value is allowed, ' + .. 'use kluautil.kr_table_pack() helper') + end + local ok, retval = ret[1], ret[2] + if ok == false then + panic('error when executing map() command on control socket %s: ' + .. '%s. command execution state is now undefined!', + path, retval) + end + -- drop wrapper table and return only the actual return value + ret = retval + end + results[result_count] = ret + ::continue:: + end + results.n = result_count + return results +end diff --git a/daemon/lua/trust_anchors.lua.in b/daemon/lua/trust_anchors.lua.in new file mode 100644 index 0000000..56a7f95 --- /dev/null +++ b/daemon/lua/trust_anchors.lua.in @@ -0,0 +1,532 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +-- Load the module +local ffi = require 'ffi' +local kres = require('kres') +local C = ffi.C + +local trust_anchors -- the public pseudo-module, exported as global variable + +-- RFC5011 state table +local key_state = { + Start = 'Start', AddPend = 'AddPend', Valid = 'Valid', + Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed' +} + +local function upgrade_required(msg) + if msg then + msg = msg .. '\n' + else + msg = '' + end + panic('Configuration upgrade required: ' .. msg .. 'Please refer to ' .. + 'https://knot-resolver.readthedocs.io/en/stable/upgrading.html') +end + +-- TODO: Move bootstrap to a separate module or even its own binary +-- remove UTC timezone specification if present or throw error +local function time2utc(orig_timespec) + local patterns = {'[+-]00:00$', 'Z$'} + for _, pattern in ipairs(patterns) do + local timespec, removals = string.gsub(orig_timespec, pattern, '') + if removals == 1 then + return timespec + end + end + error(string.format('unsupported time specification: %s', orig_timespec)) +end + +local function keydigest_is_valid(valid_from, valid_until) + local format = '%Y-%m-%dT%H:%M:%S' + local time_now = os.date('!%Y-%m-%dT%H:%M:%S') -- ! forces UTC + local time_diff = ffi.new('double[1]') + local err = ffi.C.kr_strptime_diff( + format, time_now, time2utc(valid_from), time_diff) + if (err ~= nil) then + error(string.format('failed to process "validFrom" constraint: %s', + ffi.string(err))) + end + local from_ok = time_diff[0] > 0 + + -- optional attribute + local until_ok = true + if valid_until then + err = ffi.C.kr_strptime_diff( + format, time_now, time2utc(valid_until), time_diff) + if (err ~= nil) then + error(string.format('failed to process "validUntil" constraint: %s', + ffi.string(err))) + end + until_ok = time_diff[0] < 0 + end + return from_ok and until_ok +end + +local function parse_xml_keydigest(attrs, inside, output) + local fields = {} + local _, n = string.gsub(attrs, "([%w]+)=\"([^\"]*)\"", function (k, v) fields[k] = v end) + assert(n >= 1, + string.format('cannot parse XML attributes from "%s"', attrs)) + assert(fields['validFrom'], + string.format('mandatory KeyDigest XML attribute validFrom ' .. + 'not found in "%s"', attrs)) + local valid_attrs = {id = true, validFrom = true, validUntil = true} + for key, _ in pairs(fields) do + assert(valid_attrs[key], + string.format('unsupported KeyDigest attribute "%s" found in "%s"', + key, attrs)) + end + + _, n = string.gsub(inside, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end) + assert(n >= 1, + string.format('error parsing KeyDigest XML elements from "%s"', + inside)) + local mandatory_elements = {'KeyTag', 'Algorithm', 'DigestType', 'Digest'} + for _, key in ipairs(mandatory_elements) do + assert(fields[key], + string.format('mandatory element %s is missing in "%s"', + key, inside)) + end + assert(n == 4, string.format('found %d elements but expected 4 in %s', n, inside)) + table.insert(output, fields) -- append to list of parsed keydigests +end + +local function generate_ds(keydigests) + local rrset = '' + for _, fields in ipairs(keydigests) do + local rr = string.format( + '. 0 IN DS %s %s %s %s', + fields.KeyTag, fields.Algorithm, fields.DigestType, fields.Digest) + if keydigest_is_valid(fields['validFrom'], fields['validUntil']) then + rrset = rrset .. '\n' .. rr + else + log_info(ffi.C.LOG_GRP_TA, 'skipping trust anchor "%s" ' .. + 'because it is outside of validity range', rr) + end + end + return rrset +end + +local function assert_str_match(str, pattern, expected) + local count = 0 + for _ in string.gmatch(str, pattern) do + count = count + 1 + end + assert(count == expected, + string.format('expected %d occurences of "%s" but got %d in "%s"', + expected, pattern, count, str)) +end + +-- Fetch root anchors in XML over HTTPS, returning a zone-file-style string +-- or false in case of error, and a message. +local function bootstrap(url, ca) + local kluautil = require('kluautil') + local file = io.tmpfile() + -- RFC 7958, sec. 2, but we don't do precise XML parsing. + -- @todo ICANN certificate is verified against current CA + -- this is not ideal, as it should rather verify .xml signature which + -- is signed by ICANN long-lived cert, but luasec has no PKCS7 + local rcode, errmsg = kluautil.kr_https_fetch(url, file, ca) + if rcode == nil then + file:close() + return false, string.format('[ ta ] fetch of "%s" failed: %s', url, errmsg) + end + + local xml = file:read("*a") + file:close() + + -- we support only minimal subset of https://tools.ietf.org/html/rfc7958 + assert_str_match(xml, '<?xml version="1%.0" encoding="UTF%-8"%?>', 1) + assert_str_match(xml, '<TrustAnchor ', 1) + assert_str_match(xml, '<Zone>.</Zone>', 1) + assert_str_match(xml, '</TrustAnchor>', 1) + + -- Parse root trust anchor, one digest at a time, converting to a zone-file-style string. + local keydigests = {} + string.gsub(xml, "<KeyDigest([^>]*)>(.-)</KeyDigest>", function(attrs, inside) + parse_xml_keydigest(attrs, inside, keydigests) + end) + local rrset = generate_ds(keydigests) + if rrset == '' then + return false, string.format('[ ta ] no valid trust anchors found at "%s"', url) + end + local msg = '[ ta ] Root trust anchors bootstrapped over https with pinned certificate.\n' + .. ' You SHOULD verify them manually against original source:\n' + .. ' https://www.iana.org/dnssec/files\n' + .. '[ ta ] Bootstrapped root trust anchors are:' + .. rrset + return rrset, msg +end + +local function bootstrap_write(rrstr, filename) + local fname_tmp = filename .. '.lock.' .. tostring(worker.pid); + local file = assert(io.open(fname_tmp, 'w')) + file:write(rrstr) + file:close() + assert(os.rename(fname_tmp, filename)) +end +-- Bootstrap end + +-- Update ta.comment and return decorated line representing the RR +-- This is meant to be in zone-file format. +local function ta_rr_str(ta) + ta.comment = ' ' .. ta.state .. ':' .. (ta.timer or '') + .. ' ; KeyTag:' .. ta.key_tag -- the tag is just for humans + local rr_str = kres.rr2str(ta) .. '\n' + if ta.state ~= key_state.Valid and ta.state ~= key_state.Missing then + rr_str = '; '..rr_str -- Invalidate key string (for older kresd versions) + end + return rr_str +end + +-- Write keyset to a file. States and timers are stored in comments. +local function keyset_write(keyset) + if not keyset.managed then -- not to be persistent, this is an error! + panic('internal error: keyset_write called for an unmanaged TA') + end + local fname_tmp = keyset.filename .. '.lock.' .. tostring(worker.pid); + local file = assert(io.open(fname_tmp, 'w')) + for i = 1, #keyset do + file:write(ta_rr_str(keyset[i])) + end + file:close() + assert(os.rename(fname_tmp, keyset.filename)) +end + +-- Search the values of a table and return the corresponding key (or nil). +local function table_search(t, val) + for k, v in pairs(t) do + if v == val then + return k + end + end + return nil +end + +-- For each RR, parse .state and .timer from .comment. +local function keyset_parse_comments(tas, default_state) + for _, ta in pairs(tas) do + ta.state = default_state + if ta.comment then + string.gsub(ta.comment, '^%s*(%a+):(%d*)', function (state, time) + if table_search(key_state, state) then + ta.state = state + end + ta.timer = tonumber(time) -- nil on failure + end) + ta.comment = nil + end + end + return tas +end + +-- Read keyset from a file xor a string. (This includes the key states and timers.) +local function keyset_read(path, str) + if (path == nil) == (str == nil) then -- exactly one of them must be nil + return nil, "internal ERROR: incorrect call to TA's keyset_read" + end + -- First load the regular entries, trusting them. + local zonefile = require('zonefile') + local tas, err + if path ~= nil then + tas, err = zonefile.file(path) + else + tas, err = zonefile.string(str) + end + if not tas then + return tas, err + end + keyset_parse_comments(tas, key_state.Valid) + + -- The untrusted keys are commented out but important to load. + local line_iter + if path ~= nil then + line_iter = io.lines(path) + else + line_iter = string.gmatch(str, "[^\n]+") + end + for line in line_iter do + if line:sub(1, 2) == '; ' then + -- Ignore the line if it fails to parse including recognized .state. + local l_set = zonefile.string(line:sub(3)) + if l_set and l_set[1] then + keyset_parse_comments(l_set) + if l_set[1].state then + table.insert(tas, l_set[1]) + end + end + end + end + + -- Fill tas[*].key_tag + for _, ta in pairs(tas) do + local ta_keytag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata) + if not (ta_keytag >= 0 and ta_keytag <= 65535) then + return nil, string.format('invalid key: "%s": %s', + kres.rr2str(ta), ffi.string(C.knot_strerror(ta_keytag))) + end + ta.key_tag = ta_keytag + end + + -- Fill tas.owner + if not tas[1] then + return nil, "empty TA set" + end + local owner = tas[1].owner + for _, ta in ipairs(tas) do + if ta.owner ~= owner then + return nil, string.format("do not mix %s and %s TAs in single file/string", + kres.dname2str(ta.owner), kres.dname2str(owner)) + end + end + tas.owner = owner + + return tas +end + +-- Replace current TAs for given owner by the "trusted" ones from passed keyset. +-- Return true iff no TA errored out and at least one is in VALID state. +local function keyset_publish(keyset) + local store = kres.context().trust_anchors + local count = 0 + local has_error = false + C.kr_ta_del(store, keyset.owner) + for _, ta in ipairs(keyset) do + -- Key MAY be used as a TA only in these two states (RFC5011, 4.2) + if ta.state == key_state.Valid or ta.state == key_state.Missing then + if C.kr_ta_add(store, ta.owner, ta.type, ta.ttl, ta.rdata, #ta.rdata) == 0 then + count = count + 1 + else + ta.state = 'ERROR' + has_error = true + end + end + end + if count == 0 then + log_error(ffi.C.LOG_GRP_TA, 'ERROR: no anchors are trusted for ' .. + kres.dname2str(keyset.owner) .. ' !') + end + return count > 0 and not has_error +end + +local function add_file(path, unmanaged) + local managed = not unmanaged + if not ta_update then + modules.load('ta_update') + end + if managed then + if not io.open(path .. '.lock', 'w') then + error("[ ta ] ERROR: write access needed to keyfile dir '"..path.."'") + end + os.remove(path .. ".lock") + end + + -- Bootstrap TA for root zone if keyfile doesn't exist + if managed and not io.open(path, 'r') then + if trust_anchors.keysets['\0'] then + error(string.format( + "[ ta ] keyfile '%s' doesn't exist and root key is already installed, " + .. "cannot bootstrap; provide a path to valid file with keys", path)) + end + log_info(ffi.C.LOG_GRP_TA, "keyfile '%s': doesn't exist, bootstrapping", path); + local rrstr, msg = bootstrap(trust_anchors.bootstrap_url, trust_anchors.bootstrap_ca) + if not rrstr then + msg = msg .. '\n' + .. '[ ta ] Failed to bootstrap root trust anchors!' + error(msg) + end + print(msg) + bootstrap_write(rrstr, path) + -- continue as if the keyfile was there + end + + -- Parse the file and check its sanity + local keyset, err = keyset_read(path) + if not keyset then + panic("[ ta ] ERROR: failed to read anchors from '%s' (%s)", path, err) + end + keyset.filename = path + keyset.managed = managed + + local owner = keyset.owner + local owner_str = kres.dname2str(owner) + local keyset_orig = trust_anchors.keysets[owner] + if keyset_orig then + log_warn(ffi.C.LOG_GRP_TA, 'warning: overriding previously set trust anchors for ' .. owner_str) + if keyset_orig.managed and ta_update then + ta_update.stop(owner) + end + end + trust_anchors.keysets[owner] = keyset + + -- Replace the TA store used for validation + if keyset_publish(keyset) then + log_info(ffi.C.LOG_GRP_TA, 'installed trust anchors for domain ' .. owner_str .. ' are:\n' + .. trust_anchors.summary(owner)) + end + -- TODO: if failed and for root, try to rebootstrap? + + ta_update.start(owner, managed) +end + +local function remove(zname) + local owner = kres.str2dname(zname) + if not trust_anchors.keysets[owner] then + return false + end + + if ta_update then + ta_update.stop(owner) + end + trust_anchors.keysets[owner] = nil + local store = kres.context().trust_anchors + C.kr_ta_del(store, owner) + return true +end + +local function ta_str(owner) + local owner_str = kres.dname2str(owner) .. ' ' + local msg = '' + for _, nta in pairs(trust_anchors.insecure) do + if owner == kres.str2dname(nta) then + msg = owner_str .. 'is negative trust anchor\n' + end + end + if not trust_anchors.keysets[owner] then + if #msg > 0 then -- it is normal that NTA does not have explicit TA + return msg + else + return owner_str .. 'has no explicit trust anchors\n' + end + end + if #msg > 0 then + msg = msg .. 'WARNING! negative trust anchor also has an explicit TA\n' + end + for _, ta in ipairs(trust_anchors.keysets[owner]) do + msg = msg .. ta_rr_str(ta) + end + return msg +end + +-- TA store management, for user docs see ../README.rst +trust_anchors = { + -- [internal] table indexed by dname; + -- each item is a list of RRs and additionally contains: + -- - owner - that dname (for simplicity) + -- - [optional] filename in which to persist the state, + -- implying unmanaged TA if nil + -- The RR tables also contain some additional TA-specific fields. + keysets = {}, + + -- Documented properties: + insecure = {}, + + bootstrap_url = 'https://data.iana.org/root-anchors/root-anchors.xml', + bootstrap_ca = '@etc_dir@/icann-ca.pem', + + -- Load keys from a file, 5011-managed by default. + -- If managed and the file doesn't exist, try bootstrapping the root into it. + add_file = add_file, + config = function() upgrade_required('trust_anchors.config was removed, use trust_anchors.add_file()') end, + remove = remove, + + keyset_publish = keyset_publish, + keyset_write = keyset_write, + key_state = key_state, + + -- Add DS/DNSKEY record(s) (unmanaged) + add = function (keystr) + local keyset, err = keyset_read(nil, keystr) + if keyset ~= nil then + local owner = keyset.owner + local owner_str = kres.dname2str(owner) + local keyset_orig = trust_anchors.keysets[owner] + -- Set up trust_anchors.keysets[owner] + if keyset_orig then + if keyset_orig.managed then + panic('[ ta ] it is impossible to add an unmanaged TA for zone ' + .. owner_str .. ' which already has a managed TA') + end + log_warn(ffi.C.LOG_GRP_TA, 'warning: extending previously set trust anchors for ' + .. owner_str) + for _, ta in ipairs(keyset) do + table.insert(keyset_orig, ta) + end + end + -- Replace the TA store used for validation + if not keyset_publish(keyset) then + err = "when publishing the TA set" + -- trust_anchors.keysets[owner] was already updated to the + -- (partially) failing state, but I'm not sure how much to improve this + end + keyset.managed = false + trust_anchors.keysets[owner] = keyset + + end + log_info(ffi.C.LOG_GRP_TA, 'New TA state:\n' .. trust_anchors.summary()) + if err then + panic('[ ta ] .add() failed: ' .. err) + end + end, + + -- Negative TA management + set_insecure = function (list) + assert(type(list) == 'table', 'parameter must be list of domain names (e.g. {"a.test", "b.example"})') + local store = kres.context().negative_anchors + for i = 1, #list do + local dname = kres.str2dname(list[i]) + if trust_anchors.keysets[dname] then + error('cannot add NTA '..list[i]..' because it is TA. Use trust_anchors.remove() instead') + end + end + + C.kr_ta_clear(store) + for i = 1, #list do + local dname = kres.str2dname(list[i]) + C.kr_ta_add(store, dname, kres.type.DS, 0, nil, 0) + end + trust_anchors.insecure = list + end, + -- Return textual representation of all TAs (incl. negative) + -- It's meant for human consumption. + summary = function (single_owner) + if single_owner then -- single domain + return ta_str(single_owner) + end + + -- all domains + local msg = '' + local ta_count = 0 + local seen = {} + for _, nta_str in pairs(trust_anchors.insecure) do + local owner = kres.str2dname(nta_str) + seen[owner] = true + msg = msg .. ta_str(owner) + end + for owner, _ in pairs(trust_anchors.keysets) do + if not seen[owner] then + ta_count = ta_count + 1 + msg = msg .. ta_str(owner) + end + end + if ta_count == 0 then + msg = msg .. 'No valid trust anchors, DNSSEC validation is disabled\n' + end + return msg + end, +} + +-- Syntactic sugar for TA store +setmetatable(trust_anchors, { + __newindex = function (t,k,v) + if k == 'file' then + upgrade_required('trust_anchors.file was removed, use trust_anchors.add_file()') + elseif k == 'negative' then + upgrade_required('trust_anchors.negative was removed, use trust_anchors.set_insecure()') + elseif k == 'keyfile_default' then + upgrade_required('trust_anchors.keyfile_default is now compiled in, see trust_anchors.remove()') + else rawset(t, k, v) end + end, +}) + +return trust_anchors diff --git a/daemon/lua/trust_anchors.rst b/daemon/lua/trust_anchors.rst new file mode 100644 index 0000000..40f79b6 --- /dev/null +++ b/daemon/lua/trust_anchors.rst @@ -0,0 +1,123 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. warning:: Options in this section are intended only for expert users and + normally should not be needed. + +Since version 4.0, **DNSSEC validation is enabled by default**. +If you really need to turn DNSSEC off and are okay with lowering security of your +system by doing so, add the following snippet to your configuration file. + +.. code-block:: lua + + -- turns off DNSSEC validation + trust_anchors.remove('.') + +The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates +and :rfc:`7646` negative trust anchors. Depending on your distribution, DNSSEC +trust anchors should be either maintained in accordance with the distro-wide +policy, or automatically maintained by the resolver itself. + +In practice this means that you can forget about it and your favorite Linux +distribution will take care of it for you. + +Following functions allow to modify DNSSEC configuration *if you really have to*: + + +.. function:: trust_anchors.add_file(keyfile[, readonly = false]) + + :param string keyfile: path to the file. + :param readonly: if true, do not attempt to update the file. + + The format is standard zone file, though additional information may be persisted in comments. + Either DS or DNSKEY records can be used for TAs. + If the file does not exist, bootstrapping of *root* TA will be attempted. + If you want to use bootstrapping, install `lua-http`_ library. + + Each file can only contain records for a single domain. + The TAs will be updated according to :rfc:`5011` and persisted in the file (if allowed). + + Example output: + + .. code-block:: lua + + > trust_anchors.add_file('root.key') + [ ta ] new state of trust anchors for a domain: + . 165488 DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 + nil + + [ ta ] key: 19036 state: Valid + +.. function:: trust_anchors.remove(zonename) + + Remove specified trust anchor from trusted key set. Removing trust anchor for the root zone effectively disables DNSSEC validation (unless you configured another trust anchor). + + .. code-block:: lua + + > trust_anchors.remove('.') + true + + If you want to disable DNSSEC validation for a particular domain but keep it enabled for the rest of DNS tree, use :func:`trust_anchors.set_insecure`. + +.. envvar:: trust_anchors.hold_down_time = 30 * day + + :return: int (default: 30 * day) + + Modify RFC5011 hold-down timer to given value. Intended only for testing purposes. Example: ``30 * sec`` + +.. envvar:: trust_anchors.refresh_time = nil + + :return: int (default: nil) + + Modify RFC5011 refresh timer to given value (not set by default), this will force trust anchors + to be updated every N seconds periodically instead of relying on RFC5011 logic and TTLs. + Intended only for testing purposes. + Example: ``10 * sec`` + +.. envvar:: trust_anchors.keep_removed = 0 + + :return: int (default: 0) + + How many ``Removed`` keys should be held in history (and key file) before being purged. + Note: all ``Removed`` keys will be purged from key file after restarting the process. + + +.. function:: trust_anchors.set_insecure(nta_set) + + :param table nta_list: List of domain names (text format) representing NTAs. + + When you use a domain name as an *negative trust anchor* (NTA), DNSSEC validation will be turned off at/below these names. + Each function call replaces the previous NTA set. You can find the current active set in ``trust_anchors.insecure`` variable. + If you want to disable DNSSEC validation completely use :func:`trust_anchors.remove` function instead. + + Example output: + + .. code-block:: lua + + > trust_anchors.set_insecure({ 'bad.boy', 'example.com' }) + > trust_anchors.insecure + [1] => bad.boy + [2] => example.com + + .. warning:: If you set NTA on a name that is not a zone cut, + it may not always affect names not separated from the NTA by a zone cut. + +.. function:: trust_anchors.add(rr_string) + + :param string rr_string: DS/DNSKEY records in presentation format (e.g. ``. 3600 IN DS 19036 8 2 49AAC11...``) + + Inserts DS/DNSKEY record(s) into current keyset. These will not be managed or updated, use it only for testing + or if you have a specific use case for not using a keyfile. + + .. note:: Static keys are very error-prone and should not be used in production. Use :func:`trust_anchors.add_file` instead. + + Example output: + + .. code-block:: lua + + > trust_anchors.add('. 3600 IN DS 19036 8 2 49AAC11...') + +.. function:: trust_anchors.summary() + + Return string with summary of configured DNSSEC trust anchors, including negative TAs. + +.. _lua-http: https://luarocks.org/modules/daurnimator/http diff --git a/daemon/lua/trust_anchors.test/bootstrap.test.lua b/daemon/lua/trust_anchors.test/bootstrap.test.lua new file mode 100644 index 0000000..7dd248b --- /dev/null +++ b/daemon/lua/trust_anchors.test/bootstrap.test.lua @@ -0,0 +1,112 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules.load('ta_update') + +-- check prerequisites +local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') +if not has_http then + -- skipping bootstrap tests because http module is not not installed + os.exit(77) +end + +local cqueues = require("cqueues") +local socket = require("cqueues.socket") + +-- unload modules which are not related to this test +if ta_signal_query then + modules.unload('ta_signal_query') +end +if priming then + modules.unload('priming') +end +if detect_time_skew then + modules.unload('detect_time_skew') +end + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function () return 1 end) +event.cancel(ev) +ev = event.after(0, function () return 1 end) + + +-- do not attempt to contact outside world using DNS, operate only on cache +net.ipv4 = false +net.ipv6 = false +-- do not listen, test is driven by config code +env.KRESD_NO_LISTEN = true + +-- start test webserver +local function start_webserver() + -- srvout = io.popen('luajit webserv.lua') + -- TODO + os.execute('luajit webserv.lua >/dev/null 2>&1 &') + -- assert(srvout, 'failed to start webserver') +end + +local function wait_for_webserver() + local starttime = os.time() + local connected = false + while not connected and os.difftime(os.time(), starttime) < 10 do + local con = socket.connect("localhost", 8080) + connected, msg = pcall(con.connect, con, 3) + cqueues.sleep (0.3) + end + assert(connected, string.format('unable to connect to web server: %s', msg)) +end + +local host = 'https://localhost:8080/' +-- avoid interference with configured keyfile_default +trust_anchors.remove('.') + +local function test_err_cert() + trust_anchors.bootstrap_ca = 'x509/wrongca.pem' + trust_anchors.bootstrap_url = host .. 'ok1.xml' + boom(trust_anchors.add_file, {'ok1.keys'}, + 'fake server certificate is detected') +end + +local function test_err_xml(testname, testdesc) + return function() + trust_anchors.bootstrap_ca = 'x509/ca.pem' + trust_anchors.bootstrap_url = host .. testname .. '.xml' + boom(trust_anchors.add_file, {testname .. '.keys'}, testdesc) + end +end + +-- dumb test, right now it cannot check content of keys because +-- it does not get written until refresh fetches DNSKEY from network +-- (and bypassing network using policy bypasses also validation +-- so it does not test anything) +local function test_ok_xml(testname, testdesc) + return function() + trust_anchors.bootstrap_url = host .. testname .. '.xml' + trust_anchors.remove('.') + same(trust_anchors.add_file(testname .. '.keys'), nil, testdesc) + end +end + +return { + start_webserver, + wait_for_webserver, + test_err_cert, + test_err_xml('err_attr_extra_attr', 'bogus TA XML with an extra attribute'), + test_err_xml('err_attr_validfrom_invalid', 'bogus TA XML with invalid validFrom value'), + test_err_xml('err_attr_validfrom_missing', 'bogus TA XML without mandatory validFrom attribute'), + test_err_xml('err_elem_extra', 'bogus TA XML with an extra element'), + test_err_xml('err_elem_missing', 'bogus TA XML without mandatory element'), + test_err_xml('err_multi_ta', 'bogus TA XML with multiple TAs'), + test_err_xml('unsupp_nonroot', 'unsupported TA XML for non-root zone'), + test_err_xml('unsupp_xml_v11', 'unsupported TA XML with XML v1.1'), + test_err_xml('ok0_badtimes', 'TA XML with no valid keys'), + test_ok_xml('ok1_expired1', 'TA XML with 1 valid and 1 expired key'), + test_ok_xml('ok1_notyet1', 'TA XML with 1 valid and 1 not yet valid key'), + test_ok_xml('ok1', 'TA XML with 1 valid key'), + test_ok_xml('ok2', 'TA XML with 2 valid keys'), +} diff --git a/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml b/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml new file mode 100644 index 0000000..2a87957 --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="FC4A93EC-9F4E-4597-A766-AD6723E4A56E" source="https://localhost/err_attr_extra_attr.xml"> +<Zone>.</Zone> +<KeyDigest unknownattr="test" id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00"> +<KeyTag>19036</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest> +</KeyDigest> +<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> +<KeyTag>20326</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml b/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml new file mode 100644 index 0000000..5a4c68c --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="ABD668AB-52DF-4A59-80E3-16CE6341BC55" source="https://localhost/err_attr_validfrom_invalid.xml"> +<Zone>.</Zone> +<KeyDigest id="Kjqmt7v" validFrom="2010-07-32T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00"> +<KeyTag>19036</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest> +</KeyDigest> +<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> +<KeyTag>20326</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml b/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml new file mode 100644 index 0000000..1261b09 --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="3513058C-4041-40CC-AF0A-D3CCD70F962B" source="https://localhost/err_attr_validfrom_missing.xml"> +<Zone>.</Zone> +<KeyDigest id="Kjqmt7v" validUntil="2019-01-11T00:00:00+00:00"> +<KeyTag>19036</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest> +</KeyDigest> +<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> +<KeyTag>20326</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/err_elem_extra.xml b/daemon/lua/trust_anchors.test/err_elem_extra.xml new file mode 100644 index 0000000..150a3b1 --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_elem_extra.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="B1854D58-1867-4FA7-872F-0099D394114D" source="https://localhost/err_elem_extra.xml"> +<Zone>.</Zone> +<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00"> +<KeyTag>19036</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest> +</KeyDigest> +<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> +<KeyTag>20326</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest> +<UnknownElement>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</UnknownElement> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/err_elem_missing.xml b/daemon/lua/trust_anchors.test/err_elem_missing.xml new file mode 100644 index 0000000..899e1d0 --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_elem_missing.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="BB074095-3A42-4B13-9CC1-CFFF644D4D54" source="https://localhost/err_elem_missing.xml"> +<Zone>.</Zone> +<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00"> +<KeyTag>19036</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest> +</KeyDigest> +<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> +<KeyTag>20326</KeyTag> +<Algorithm>8</Algorithm> +<!-- this element is missing: DigestType>2</DigestType--> +<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/err_multi_ta.xml b/daemon/lua/trust_anchors.test/err_multi_ta.xml new file mode 100644 index 0000000..20cd73f --- /dev/null +++ b/daemon/lua/trust_anchors.test/err_multi_ta.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml"> +<Zone>.</Zone> +<KeyDigest id="1" validFrom="2000-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> +<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml"> +<Zone>test.</Zone> +<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/ok0_badtimes.xml b/daemon/lua/trust_anchors.test/ok0_badtimes.xml new file mode 100644 index 0000000..4535a41 --- /dev/null +++ b/daemon/lua/trust_anchors.test/ok0_badtimes.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="EDEDAA08-D2A0-421E-81DC-AF11F5A0CDCD" source="https://localhost/ok0_badtimes.xml"> +<Zone>.</Zone> +<KeyDigest id="E" validFrom="2000-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00"> +<KeyTag>1</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE</Digest> +</KeyDigest> +<KeyDigest id="F" validFrom="2001-01-01T00:00:00+00:00" validUntil="2001-01-01T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/ok1.xml b/daemon/lua/trust_anchors.test/ok1.xml new file mode 100644 index 0000000..117495c --- /dev/null +++ b/daemon/lua/trust_anchors.test/ok1.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="82E6CB77-12DF-4E61-BF49-367FB95A8BAA" source="https://localhost/ok1.xml"> +<Zone>.</Zone> +<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/ok1_expired1.xml b/daemon/lua/trust_anchors.test/ok1_expired1.xml new file mode 100644 index 0000000..f1269da --- /dev/null +++ b/daemon/lua/trust_anchors.test/ok1_expired1.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="68463155-A857-4C7E-BCA6-2F6CC2EAC1BE" source="https://localhost/ok1_expired1.xml"> +<Zone>.</Zone> +<KeyDigest id="F" validFrom="1990-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00"> +<KeyTag>1</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest> +</KeyDigest> +<KeyDigest id="1" validFrom="2000-01-01T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/ok1_notyet1.xml b/daemon/lua/trust_anchors.test/ok1_notyet1.xml new file mode 100644 index 0000000..7b5881b --- /dev/null +++ b/daemon/lua/trust_anchors.test/ok1_notyet1.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="507B39D5-049E-467C-9E9A-F5BE597C9DDA" source="https://localhost/ok1_notyet1.xml"> +<Zone>.</Zone> +<KeyDigest id="1" validFrom="2010-07-15T00:00:00+00:00"> +<KeyTag>1</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +<KeyDigest id="2" validFrom="2050-12-31T23:59:59+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/ok2.xml b/daemon/lua/trust_anchors.test/ok2.xml new file mode 100644 index 0000000..149f6b5 --- /dev/null +++ b/daemon/lua/trust_anchors.test/ok2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="1DECEB91-0591-44A1-95CF-1788337514B8" source="https://localhost/ok2.xml"> +<Zone>.</Zone> +<KeyDigest id="K1" validFrom="2010-07-15T00:00:00+00:00"> +<KeyTag>1</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +<KeyDigest id="K2" validFrom="2011-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>2222222222222222222222222222222222222222222222222222222222222222</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/regen.sh b/daemon/lua/trust_anchors.test/regen.sh new file mode 100755 index 0000000..9e7dac1 --- /dev/null +++ b/daemon/lua/trust_anchors.test/regen.sh @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +for F in *.xml; do sed -i "s/TrustAnchor id=\"[^\"]*\"/TrustAnchor id=\"$(uuidgen | tr '[[:lower:]]' '[[:upper:]]')\"/" $F; done +for F in *.xml; do sed -i "s#source=\"[^\"]*\"#source=\"https://localhost/$F\"#" $F; done diff --git a/daemon/lua/trust_anchors.test/root.keys b/daemon/lua/trust_anchors.test/root.keys new file mode 100644 index 0000000..e292b5a --- /dev/null +++ b/daemon/lua/trust_anchors.test/root.keys @@ -0,0 +1 @@ +. IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D diff --git a/daemon/lua/trust_anchors.test/ta.test.lua b/daemon/lua/trust_anchors.test/ta.test.lua new file mode 100644 index 0000000..b977bc9 --- /dev/null +++ b/daemon/lua/trust_anchors.test/ta.test.lua @@ -0,0 +1,85 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +trust_anchors.remove('.') + +local ffi = require('ffi') + +-- count warning messages +warn_msg = {} +overriding_msg="warning: overriding previously set trust anchors for ." +warn_msg[overriding_msg] = 0 +function log_warn(grp, fmt, ...) --luacheck: no unused args + msg = string.format(fmt, ...) + if warn_msg[msg] ~= nil then + warn_msg[msg] = warn_msg[msg] + 1 + end +end + +-- Test that adding a revoked DNSKEY is refused. +local function test_revoked_key() + local ta_c = kres.context().trust_anchors + same(ffi.C.kr_ta_del(ta_c, '\0'), 0, 'remove root TAs if any') + -- same() doesn't consider nil and typed NULL pointer equal, so we work around: + same(ffi.C.kr_ta_get(ta_c, '\0') == nil, true, 'no TA for root is used') + local key_crypto = 'AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFV' + .. 'QUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37' + .. 'NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAz' + .. 'vN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7O' + .. 'yQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=' + boom(trust_anchors.add, { '. 3600 DNSKEY 385 3 8 ' .. key_crypto }, 'refuse revoked key') + same(ffi.C.kr_ta_get(ta_c, '\0') == nil, true, 'no TA for root is used') + -- Test that we don't have another problem in the key + trust_anchors.add('. 3600 DNSKEY 257 3 8 ' .. key_crypto) + local root_ta = ffi.C.kr_ta_get(ta_c, '\0') + same(root_ta == nil, false, 'we got non-NULL TA RRset') + same(root_ta.rrs.count, 1, 'the root TA set contains one RR') +end + +local function test_remove() + -- uses root key from the previous test + assert(trust_anchors.keysets['\0'], 'root key must be there from previous test') + local ta_c = kres.context().trust_anchors + local root_ta = ffi.C.kr_ta_get(ta_c, '\0') + assert(root_ta ~= nil, 'we got non-NULL TA RRset') + assert(root_ta.rrs.count, 1, 'we have a root TA set to be deleted') + + trust_anchors.remove('.') + + same(trust_anchors.keysets['\0'], nil, 'Lua interface does not have the removed key') + root_ta = ffi.C.kr_ta_get(ta_c, '\0') + same(root_ta == nil, true, 'C interface does not have the removed key') +end + +local function test_add_file() + boom(trust_anchors.add_file, {'nonwriteable/root.keys', false}, + "Managed trust anchor in non-writeable directory") + + boom(trust_anchors.add_file, {'nonexistent.keys', true}, + "Nonexistent unmanaged trust anchor file") + + is(warn_msg[overriding_msg], 0, "No override warning messages at start of test") + trust_anchors.add_file('root.keys', true) + trust_anchors.add_file('root.keys', true) + is(warn_msg[overriding_msg], 1, "Warning message when override trust anchors") + + is(trust_anchors.keysets['\0'][1].key_tag, 20326, + "Loaded KeyTag from root.keys") +end + +local function test_nta() + assert(trust_anchors.keysets['\0'], 'root key must be there from previous tests') + + trust_anchors.set_insecure({'example.com'}) + is(trust_anchors.insecure[1], 'example.com', 'Add example.com to NTA list') + boom(trust_anchors.set_insecure, {{'.'}}, 'Got error when adding TA . to NTA list') + is(#trust_anchors.insecure, 1, 'Check one item in NTA list') + is(trust_anchors.insecure[1], 'example.com', 'Check previous NTA list') +end + +return { + test_revoked_key, + test_remove, + test_add_file, + test_nta, +} + diff --git a/daemon/lua/trust_anchors.test/unsupp_nonroot.xml b/daemon/lua/trust_anchors.test/unsupp_nonroot.xml new file mode 100644 index 0000000..51b3c0a --- /dev/null +++ b/daemon/lua/trust_anchors.test/unsupp_nonroot.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TrustAnchor id="8449BFB8-FD6C-4082-B0FE-1A3E3399203B" source="https://localhost/unsupp_nonroot.xml"> +<Zone>test.</Zone> +<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml b/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml new file mode 100644 index 0000000..87a4b57 --- /dev/null +++ b/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml @@ -0,0 +1,10 @@ +<?xml version="1.1" encoding="UTF-8"?> +<TrustAnchor id="3612AE1C-E8F3-4FD8-B8CD-96C7FDACC7A5" source="https://localhost/unsupp_xml_v11.xml"> +<Zone>.</Zone> +<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00"> +<KeyTag>2</KeyTag> +<Algorithm>8</Algorithm> +<DigestType>2</DigestType> +<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest> +</KeyDigest> +</TrustAnchor> diff --git a/daemon/lua/trust_anchors.test/webserv.lua b/daemon/lua/trust_anchors.test/webserv.lua new file mode 100644 index 0000000..c108bba --- /dev/null +++ b/daemon/lua/trust_anchors.test/webserv.lua @@ -0,0 +1,236 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +-- 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. +-- One example is statistics module that can stream live metrics on the website, +-- or publish metrics on request for Prometheus scraper. +local http_server = require('http.server') +local http_headers = require('http.headers') +local http_websocket = require('http.websocket') +local http_util = require "http.util" +local x509, pkey = require('openssl.x509'), require('openssl.pkey') + +-- Module declaration +local M = {} + +-- Export HTTP service endpoints +M.endpoints = { + ['/'] = {'text/html', 'test'}, +} + +-- Serve known requests, for methods other than GET +-- the endpoint must be a closure and not a preloaded string +local function serve(endpoints, h, stream) + local hsend = http_headers.new() + local path = h:get(':path') + local entry = endpoints[path] + if not entry then -- Accept top-level path match + entry = endpoints[path:match '^/[^/?]*'] + end + -- Unpack MIME and data + local data, mime, ttl, err + if entry then + mime = entry[1] + data = entry[2] + ttl = entry[4] + end + -- Get string data out of service endpoint + if type(data) == 'function' then + local set_mime, set_ttl + data, err, set_mime, set_ttl = data(h, stream) + -- Override default endpoint mime/TTL + if set_mime then mime = set_mime end + if set_ttl then ttl = set_ttl end + -- Handler doesn't provide any data + if data == false then return end + if type(data) == 'number' then return tostring(data), err end + -- Methods other than GET require handler to be closure + elseif h:get(':method') ~= 'GET' then + return '501', '' + end + if not mime or type(data) ~= 'string' then + return '404', '' + else + -- Serve content type appropriately + hsend:append(':status', '200') + hsend:append('content-type', mime) + hsend:append('content-length', tostring(#data)) + if ttl then + hsend:append('cache-control', string.format('max-age=%d', ttl)) + end + assert(stream:write_headers(hsend, false)) + assert(stream:write_chunk(data, true)) + end +end + +-- Web server service closure +local function route(endpoints) + return function (_, stream) + -- HTTP/2: We're only permitted to send in open/half-closed (remote) + local connection = stream.connection + if connection.version >= 2 then + if stream.state ~= 'open' and stream.state ~= 'half closed (remote)' then + return + end + end + -- Start reading headers + local h = assert(stream:get_headers()) + local m = h:get(':method') + local path = h:get(':path') + -- Upgrade connection to WebSocket + local ws = http_websocket.new_from_stream(stream, h) + if ws then + assert(ws:accept { protocols = {'json'} }) + -- Continue streaming results to client + local ep = endpoints[path] + local cb = ep[3] + if cb then + cb(h, ws) + end + ws:close() + return + else + local ok, err, reason = http_util.yieldable_pcall(serve, endpoints, h, stream) + if not ok or err then + print(string.format('%s err %s %s: %s (%s)', os.date(), m, path, err or '500', reason)) + -- Method is not supported + local hsend = http_headers.new() + hsend:append(':status', err or '500') + if reason then + assert(stream:write_headers(hsend, false)) + assert(stream:write_chunk(reason, true)) + else + assert(stream:write_headers(hsend, true)) + end + else + print(string.format('%s ok %s %s', os.date(), m, path)) + end + end + end +end + +-- @function Prefer HTTP/2 or HTTP/1.1 +local function alpnselect(_, protos) + for _, proto in ipairs(protos) do + if proto == 'h2' or proto == 'http/1.1' then + return proto + end + end + return nil +end + +-- @function Create TLS context +local function tlscontext(crt, key) + local http_tls = require('http.tls') + local ctx = http_tls.new_server_context() + if ctx.setAlpnSelect then + ctx:setAlpnSelect(alpnselect) + end + assert(ctx:setPrivateKey(key)) + assert(ctx:setCertificate(crt)) + return ctx +end + +-- @function Listen on given HTTP(s) host +function M.add_interface(conf) + local crt, key + if conf.tls ~= false then + assert(conf.cert, 'cert missing') + assert(conf.key, 'private key missing') + -- Check if a cert file was specified + -- Read x509 certificate + local f = io.open(conf.cert, 'r') + if f then + crt = assert(x509.new(f:read('*all'))) + f:close() + -- Continue reading key file + if crt then + f = io.open(conf.key, 'r') + key = assert(pkey.new(f:read('*all'))) + f:close() + end + end + -- Check loaded certificate + assert(crt and key, + string.format('failed to load certificate "%s"', conf.cert)) + end + -- Compose server handler + local routes = route(conf.endpoints or M.endpoints) + -- 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 8453 + 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 + -- Create TLS context and start listening + local s, err = http_server.listen { + -- cq = worker.bg_worker.cq, + host = conf.host, + port = conf.port, + path = conf.path, + v6only = conf.v6only, + unlink = conf.unlink, + reuseaddr = conf.reuseaddr, + reuseport = conf.reuseport, + client_timeout = conf.client_timeout or 5, + ctx = crt and tlscontext(crt, key), + tls = conf.tls, + onstream = routes, + -- Log errors, but do not throw + 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 + print(msg) + end, + } + -- Manually call :listen() so that we are bound before calling :localname() + if s then + err = select(2, s:listen()) + end + assert(not err, string.format('failed to listen on %s: %s', addr_str, err)) + return s +end + +-- init +local files = { + 'ok0_badtimes.xml', + 'ok1.xml', + 'ok1_expired1.xml', + 'ok1_notyet1.xml', + 'ok2.xml', + 'err_attr_validfrom_missing.xml', + 'err_attr_validfrom_invalid.xml', + 'err_attr_extra_attr.xml', + 'err_elem_missing.xml', + 'err_elem_extra.xml', + 'err_multi_ta.xml', + 'unsupp_nonroot.xml', + 'unsupp_xml_v11.xml' +} + +-- Export static pages specified at command line +for _, name in ipairs(files) do + local fd = io.open(name) + assert(fd, string.format('unable to open file "%s"', name)) + M.endpoints['/' .. name] = { 'text/xml', fd:read('*a') } + fd:close() +end + +local server = M.add_interface({ + host = 'localhost', + port = 8080, + tls = true, + cert = 'x509/server.pem', + key = 'x509/server-key.pem' + }) + +server:loop() diff --git a/daemon/lua/trust_anchors.test/x509/ca-key.pem b/daemon/lua/trust_anchors.test/x509/ca-key.pem new file mode 100644 index 0000000..2e95b23 --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/ca-key.pem @@ -0,0 +1,182 @@ +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: High (3072 bits) + +modulus: + 00:9e:ee:f2:d8:41:ae:2c:93:8a:01:1f:88:5b:d6:85 + 29:2f:91:9d:37:fc:35:88:7f:53:71:87:fc:17:71:e7 + 15:57:06:2d:54:fb:19:98:80:82:ec:1a:99:2d:57:cb + 5f:dd:28:26:d8:95:fb:65:b2:be:e1:11:86:69:14:7e + 32:5c:c0:02:0b:5d:11:78:69:50:20:25:3e:15:fb:8a + 46:d1:83:f9:3b:84:46:9c:69:21:44:d5:09:1d:7a:04 + cc:f3:6a:ea:4c:1b:da:7c:40:dd:1c:6f:f6:85:b4:ea + 75:98:34:79:11:fb:cf:d3:18:70:64:25:33:8a:31:b6 + 93:67:d4:32:67:61:1c:d0:7b:85:61:54:c6:fb:51:b6 + 87:1d:d4:b8:58:40:a9:c5:32:ce:e0:b9:90:37:0d:58 + e4:33:70:c5:c5:91:f2:18:f5:e0:08:ad:17:8b:cf:72 + f1:26:6c:9c:88:d2:9e:06:4c:02:5d:4e:7c:93:af:8d + 72:93:75:1d:60:0c:f7:34:09:a8:e6:f2:80:4a:14:81 + 24:40:4b:45:19:85:2e:ad:8e:97:4c:ff:ec:d0:9f:e6 + a0:b7:c0:a9:a0:ad:d2:02:2d:13:55:f3:df:f8:f9:f1 + f3:3e:35:e9:08:2b:db:11:93:57:13:55:c6:ba:c6:d7 + ff:7d:e1:fa:8c:47:5d:da:bf:31:56:80:aa:34:97:43 + bb:9e:ff:d3:e6:13:a9:c2:99:49:c2:1e:da:f2:c7:d2 + d6:f7:5f:70:36:91:2f:ea:36:e9:88:44:08:a3:1a:0a + c0:e0:4b:48:82:9a:c9:72:29:9c:09:24:63:b3:c2:9f + 2a:f6:e8:3a:c4:46:03:8d:70:ae:14:bb:3a:d6:c6:62 + 93:24:7f:bc:0a:c8:a2:20:53:3c:9f:5c:15:45:05:3d + 1b:38:17:d4:fe:6b:6a:c2:16:f3:14:73:c2:c3:c7:36 + e3:f1:f8:e5:28:84:4e:37:d4:68:e8:82:70:20:53:fc + 01: + +public exponent: + 01:00:01: + +private exponent: + 44:4a:68:0e:84:2a:52:fd:12:4f:69:3d:2e:38:fe:fe + b3:71:de:1c:30:42:d0:63:e5:76:e2:f7:6f:1b:82:2f + a9:34:fe:45:85:9f:79:e7:be:59:b5:14:1c:67:9c:fb + 94:0b:ac:a5:63:cc:a1:e6:2c:1e:89:69:37:bd:96:7c + 0d:5e:73:82:6e:7b:13:42:2d:2b:a2:d5:0a:9d:0a:cd + 63:39:51:de:40:f8:16:3d:16:0e:7d:7b:6d:2f:00:e1 + 0f:b6:e0:f5:d3:02:0e:61:d0:a0:67:7b:85:f8:36:c6 + 50:a0:3b:65:7e:cd:cd:e6:b2:64:55:97:cf:c9:8c:a9 + c9:f3:63:b5:08:05:59:8f:b9:c0:18:ad:67:4f:b5:1e + 59:b3:0d:82:de:46:14:75:c0:6e:cf:4d:28:5a:93:d7 + 7a:42:fa:b7:e9:fe:1c:bb:89:88:30:d7:ed:3b:36:28 + 68:5a:42:e8:87:97:5f:1d:49:e6:cd:d2:b9:a2:b5:23 + d8:df:5d:cf:c6:98:9a:e0:99:7a:33:52:75:22:ce:ca + 85:eb:d9:92:6a:d5:49:c0:cb:df:b1:a2:98:b5:6c:37 + 85:c2:e1:6a:13:48:22:72:02:a7:e2:e0:f3:f3:0c:ed + 42:f6:83:ba:71:f0:ef:8f:ce:6a:59:30:be:9d:5f:23 + 06:c3:0e:49:5c:8f:6a:8d:c2:c3:c5:07:45:55:78:f0 + bd:29:01:cb:ac:ec:b1:40:7d:78:cc:4d:cb:f9:60:a4 + a2:f5:aa:21:0b:3e:da:1b:d9:f0:99:19:44:57:21:09 + ba:0f:f9:05:8e:ee:59:4f:59:08:b1:67:51:02:80:4e + 34:c7:5d:25:79:8c:84:f7:be:15:02:28:9c:f9:b9:ca + fc:6a:ed:d0:5f:df:be:ce:c4:96:63:23:2e:db:e1:85 + 1e:45:16:2c:24:b0:5e:7a:62:bf:36:00:8b:c8:90:61 + c2:68:4b:95:b0:ce:41:77:a3:a0:5d:09:72:01:a0:01 + + +prime1: + 00:ca:fe:eb:14:07:13:a9:ef:b6:d5:6c:52:02:39:b6 + 6e:55:b6:dd:70:fc:c6:04:7c:07:81:9b:98:a4:da:db + f1:66:b8:33:91:fd:00:15:6e:72:0d:ab:0b:6f:be:34 + c8:d9:82:58:7e:09:7f:e4:6f:c0:70:99:53:68:c7:53 + d2:8f:97:22:f5:e8:e4:be:5f:e1:29:0f:27:a7:66:74 + b2:cc:96:a1:d2:ca:2a:40:4b:70:cc:7a:16:4f:c9:4f + 49:16:11:d5:f8:da:f6:92:06:1c:45:c3:f8:17:c4:1d + 65:9a:2a:3f:33:be:33:f8:84:03:26:49:d8:52:25:f8 + 19:ce:31:00:c0:b6:55:71:74:03:53:e8:0c:ef:85:64 + 54:d4:8f:68:08:87:da:cb:9b:55:6a:2e:2b:c2:95:36 + c4:dd:09:62:c0:6f:9e:e9:cc:ea:96:4e:e6:2d:6c:72 + c1:54:92:11:29:91:af:4d:cd:08:7c:f8:6f:28:9d:ca + c5: + +prime2: + 00:c8:6e:b7:af:c3:c6:b9:df:49:ad:ea:b8:62:b7:43 + e6:04:d9:5a:df:c3:f9:a3:0d:b5:e6:2d:9d:f7:c4:ff + 38:c0:cb:03:9c:c1:d1:6f:b4:fb:cf:81:c4:9e:94:2a + d5:e2:a1:77:a8:7b:8f:d1:34:7a:c2:f5:38:ec:0d:35 + a0:5e:3c:af:e9:2d:f4:f9:32:ae:da:c1:1e:62:74:e5 + ab:3d:3b:3d:d0:88:fc:53:59:0f:21:30:ed:24:ac:a7 + 5a:a5:b7:f4:cc:5a:96:ad:79:a3:41:74:56:ad:39:14 + 0a:27:a4:10:18:19:33:f2:1a:aa:b4:36:9d:fa:3f:fc + 71:42:1e:a0:96:8e:0f:de:46:87:ba:6c:38:17:d0:7e + c8:4a:cb:4a:29:1c:44:b9:88:29:c7:6f:b7:4f:3f:00 + cd:0c:6b:0f:77:a1:5a:f1:80:21:91:b3:68:ca:0d:b5 + c9:6d:04:f4:98:94:9f:09:f9:a8:58:ea:34:9c:d8:f0 + 0d: + +coefficient: + 00:bf:7b:93:68:64:ea:5e:b5:f4:b6:8c:91:49:aa:2b + b0:a5:74:40:73:45:23:b6:74:ae:7a:55:ae:9d:8a:bb + 3b:6d:3a:7d:c4:7a:c0:82:7f:0e:ef:57:1c:86:e2:56 + 30:5b:0c:d9:d1:52:cf:df:10:4f:c8:4a:75:b1:b8:b1 + 59:9b:01:02:a2:4d:29:aa:63:e5:11:0a:17:ae:1e:79 + ed:5d:10:fc:f0:8a:8d:f6:77:f8:78:17:1e:07:ee:d1 + de:59:ed:d7:fb:94:bf:c9:7c:f7:f3:a0:8d:66:d3:94 + 9a:7f:d1:7a:89:87:71:17:96:90:4e:be:7b:54:5e:51 + 03:c6:35:af:5f:ea:5d:cc:31:ab:56:4b:75:6a:14:b6 + c0:1a:bf:fb:e9:54:ba:ad:c3:52:e9:85:03:db:b2:e8 + 0b:18:60:37:19:f9:07:87:e7:b2:d8:3a:0d:c9:d5:f0 + f0:73:60:fc:9e:e0:9e:b1:ea:52:71:c7:fd:27:0a:22 + 42: + +exp1: + 04:a5:69:04:00:55:76:e2:41:b1:08:d5:a7:af:62:79 + 8c:04:af:74:d2:94:45:ae:01:0d:fa:5d:b8:08:3a:58 + 80:1d:5f:30:cc:35:a0:47:f2:dc:55:39:e1:c8:dc:b4 + 6c:26:0b:98:76:e7:32:77:4c:54:47:6e:1d:4b:d2:a3 + 53:1e:06:72:d2:6d:c9:dd:af:ed:9c:a7:2f:b1:ac:a2 + 1b:04:a7:97:87:81:08:0f:b1:f9:3c:22:1e:99:60:f3 + 2f:4a:21:37:9c:eb:5f:d4:3b:f9:6c:ce:d4:dc:6c:3f + d3:13:7c:76:d6:b7:a1:cc:83:b5:f0:a2:be:de:97:9b + 1f:99:07:87:61:a4:fa:ff:c8:c3:b6:df:f4:eb:7c:ac + 64:61:13:e4:7e:17:87:e9:7a:3e:ff:e7:88:80:99:cc + 4f:b2:d3:4b:cc:42:3a:df:b1:ce:d6:e7:75:ca:b1:a5 + b3:25:d4:b6:ba:da:e4:50:f9:0c:c3:32:e8:1f:14:71 + + +exp2: + 00:9d:9d:bb:a3:63:b0:96:20:8a:5f:52:f2:b6:e6:69 + 65:ac:30:84:ee:ec:bd:16:45:44:e3:02:c2:73:c2:9d + f5:b2:5e:b3:b3:85:13:3e:e6:33:13:66:78:09:40:79 + 43:03:5a:78:af:ac:a3:57:20:0e:dd:db:5b:6c:fd:a2 + 4b:3f:70:37:e1:85:fb:5c:30:48:22:cc:b5:29:35:c6 + 1a:58:27:8b:1f:bf:69:b5:dd:96:31:42:b9:6a:1a:bf + ec:5f:df:7e:89:69:3f:8e:a1:d6:09:36:04:a7:69:f8 + 61:57:f5:03:96:ff:d2:c2:b3:c7:c3:ba:23:97:54:d4 + 1e:f8:a0:ff:26:06:07:62:83:52:5e:fe:95:49:dc:f2 + a6:6d:72:da:19:e4:1a:03:50:99:92:35:3f:10:f9:79 + 96:c6:0a:36:fa:9b:8f:d9:d4:2a:11:da:e5:2f:e7:82 + 2a:29:2a:39:72:f7:84:ed:a2:3f:89:d4:7b:95:50:5f + cd: + + +Public Key PIN: + pin-sha256:u7TPTyh/innOijbJFG3Y4pWghApErLvhCQUZNXBlVFU= +Public Key ID: + sha256:bbb4cf4f287f8a79ce8a36c9146dd8e295a0840a44acbbe10905193570655455 + sha1:92b7d0c4d107e2a73f827b87866aef9ff4379cc8 + +-----BEGIN RSA PRIVATE KEY----- +MIIG5AIBAAKCAYEAnu7y2EGuLJOKAR+IW9aFKS+RnTf8NYh/U3GH/Bdx5xVXBi1U ++xmYgILsGpktV8tf3Sgm2JX7ZbK+4RGGaRR+MlzAAgtdEXhpUCAlPhX7ikbRg/k7 +hEacaSFE1QkdegTM82rqTBvafEDdHG/2hbTqdZg0eRH7z9MYcGQlM4oxtpNn1DJn +YRzQe4VhVMb7UbaHHdS4WECpxTLO4LmQNw1Y5DNwxcWR8hj14AitF4vPcvEmbJyI +0p4GTAJdTnyTr41yk3UdYAz3NAmo5vKAShSBJEBLRRmFLq2Ol0z/7NCf5qC3wKmg +rdICLRNV89/4+fHzPjXpCCvbEZNXE1XGusbX/33h+oxHXdq/MVaAqjSXQ7ue/9Pm +E6nCmUnCHtryx9LW919wNpEv6jbpiEQIoxoKwOBLSIKayXIpnAkkY7PCnyr26DrE +RgONcK4UuzrWxmKTJH+8CsiiIFM8n1wVRQU9GzgX1P5rasIW8xRzwsPHNuPx+OUo +hE431GjognAgU/wBAgMBAAECggGAREpoDoQqUv0ST2k9Ljj+/rNx3hwwQtBj5Xbi +928bgi+pNP5FhZ95575ZtRQcZ5z7lAuspWPMoeYsHolpN72WfA1ec4JuexNCLSui +1QqdCs1jOVHeQPgWPRYOfXttLwDhD7bg9dMCDmHQoGd7hfg2xlCgO2V+zc3msmRV +l8/JjKnJ82O1CAVZj7nAGK1nT7UeWbMNgt5GFHXAbs9NKFqT13pC+rfp/hy7iYgw +1+07NihoWkLoh5dfHUnmzdK5orUj2N9dz8aYmuCZejNSdSLOyoXr2ZJq1UnAy9+x +opi1bDeFwuFqE0gicgKn4uDz8wztQvaDunHw74/Oalkwvp1fIwbDDklcj2qNwsPF +B0VVePC9KQHLrOyxQH14zE3L+WCkovWqIQs+2hvZ8JkZRFchCboP+QWO7llPWQix +Z1ECgE40x10leYyE974VAiic+bnK/Grt0F/fvs7ElmMjLtvhhR5FFiwksF56Yr82 +AIvIkGHCaEuVsM5Bd6OgXQlyAaABAoHBAMr+6xQHE6nvttVsUgI5tm5Vtt1w/MYE +fAeBm5ik2tvxZrgzkf0AFW5yDasLb740yNmCWH4Jf+RvwHCZU2jHU9KPlyL16OS+ +X+EpDyenZnSyzJah0soqQEtwzHoWT8lPSRYR1fja9pIGHEXD+BfEHWWaKj8zvjP4 +hAMmSdhSJfgZzjEAwLZVcXQDU+gM74VkVNSPaAiH2subVWouK8KVNsTdCWLAb57p +zOqWTuYtbHLBVJIRKZGvTc0IfPhvKJ3KxQKBwQDIbrevw8a530mt6rhit0PmBNla +38P5ow215i2d98T/OMDLA5zB0W+0+8+BxJ6UKtXioXeoe4/RNHrC9TjsDTWgXjyv +6S30+TKu2sEeYnTlqz07PdCI/FNZDyEw7SSsp1qlt/TMWpateaNBdFatORQKJ6QQ +GBkz8hqqtDad+j/8cUIeoJaOD95Gh7psOBfQfshKy0opHES5iCnHb7dPPwDNDGsP +d6Fa8YAhkbNoyg21yW0E9JiUnwn5qFjqNJzY8A0CgcAEpWkEAFV24kGxCNWnr2J5 +jASvdNKURa4BDfpduAg6WIAdXzDMNaBH8txVOeHI3LRsJguYducyd0xUR24dS9Kj +Ux4GctJtyd2v7ZynL7GsohsEp5eHgQgPsfk8Ih6ZYPMvSiE3nOtf1Dv5bM7U3Gw/ +0xN8dta3ocyDtfCivt6Xmx+ZB4dhpPr/yMO23/TrfKxkYRPkfheH6Xo+/+eIgJnM +T7LTS8xCOt+xztbndcqxpbMl1La62uRQ+QzDMugfFHECgcEAnZ27o2OwliCKX1Ly +tuZpZawwhO7svRZFROMCwnPCnfWyXrOzhRM+5jMTZngJQHlDA1p4r6yjVyAO3dtb +bP2iSz9wN+GF+1wwSCLMtSk1xhpYJ4sfv2m13ZYxQrlqGr/sX99+iWk/jqHWCTYE +p2n4YVf1A5b/0sKzx8O6I5dU1B74oP8mBgdig1Je/pVJ3PKmbXLaGeQaA1CZkjU/ +EPl5lsYKNvqbj9nUKhHa5S/ngiopKjly94Ttoj+J1HuVUF/NAoHBAL97k2hk6l61 +9LaMkUmqK7CldEBzRSO2dK56Va6dirs7bTp9xHrAgn8O71cchuJWMFsM2dFSz98Q +T8hKdbG4sVmbAQKiTSmqY+URCheuHnntXRD88IqN9nf4eBceB+7R3lnt1/uUv8l8 +9/OgjWbTlJp/0XqJh3EXlpBOvntUXlEDxjWvX+pdzDGrVkt1ahS2wBq/++lUuq3D +UumFA9uy6AsYYDcZ+QeH57LYOg3J1fDwc2D8nuCesepSccf9JwoiQg== +-----END RSA PRIVATE KEY----- diff --git a/daemon/lua/trust_anchors.test/x509/ca.pem b/daemon/lua/trust_anchors.test/x509/ca.pem new file mode 100644 index 0000000..e3c3ca2 --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEGTCCAoGgAwIBAgIUXWsAXOOaZw+h37N9gUc/XLw3KHwwDQYJKoZIhvcNAQEL +BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMCAXDTIwMDEw +NzA5MzQwOVoYDzk5OTkxMjMxMjM1OTU5WjAjMSEwHwYDVQQDExhLbm90IFJlc29s +dmVyIHRlc3RpbmcgQ0EwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCe +7vLYQa4sk4oBH4hb1oUpL5GdN/w1iH9TcYf8F3HnFVcGLVT7GZiAguwamS1Xy1/d +KCbYlftlsr7hEYZpFH4yXMACC10ReGlQICU+FfuKRtGD+TuERpxpIUTVCR16BMzz +aupMG9p8QN0cb/aFtOp1mDR5EfvP0xhwZCUzijG2k2fUMmdhHNB7hWFUxvtRtocd +1LhYQKnFMs7guZA3DVjkM3DFxZHyGPXgCK0Xi89y8SZsnIjSngZMAl1OfJOvjXKT +dR1gDPc0Cajm8oBKFIEkQEtFGYUurY6XTP/s0J/moLfAqaCt0gItE1Xz3/j58fM+ +NekIK9sRk1cTVca6xtf/feH6jEdd2r8xVoCqNJdDu57/0+YTqcKZScIe2vLH0tb3 +X3A2kS/qNumIRAijGgrA4EtIgprJcimcCSRjs8KfKvboOsRGA41wrhS7OtbGYpMk +f7wKyKIgUzyfXBVFBT0bOBfU/mtqwhbzFHPCw8c24/H45SiETjfUaOiCcCBT/AEC +AwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQAMB0GA1Ud +DgQWBBSSt9DE0Qfipz+Ce4eGau+f9DecyDANBgkqhkiG9w0BAQsFAAOCAYEAA45p +Ak7ebzk2ss5FHhJDvHrhoTZG2/esNEQhtv70nuRPnm3j8UGDxYyydwjf4+W9DT1v +53QNfvbOIPcOUsGArAItmI7K6ltkBSS6ymO8T1ZY4vYw+77jJZ1EeYS6kjdan7dK +f2Zz23CVbuq8BOc/Ob6ChepEq/MIb3g/Y6FowuWqeC85s61GW9MKr5GeG0oSyYAO +UZdFnwa8QLCZ2IzQcwnolkAw2A/5TDxovINy9Lb5U3kyphC9vhjPqr8PJ5q/KVuK +vcHvEsrsSNPvW/WcxkziV1oJTnjvr/69mwAme8+xjjF90GhrNaQF1YOoijuZuQaS +Q+0qmwZbsMtcqAABKQALHfLGsGAA5MKip49khIQWuIAS8P2vb+hzbqQRLjq1uW7B +dEGvBHF0QebDZOXJeXEYK/b7btWa9kNedD2FvBx5c9QNiWwh7jZENkICKnhI7E+n +d5/gsKVa1glKwbMagZBSJgFtjZe/eo/LcoK82m4VuOUCJSe0Kd0McrSZ7XZX +-----END CERTIFICATE----- diff --git a/daemon/lua/trust_anchors.test/x509/ca.tmpl b/daemon/lua/trust_anchors.test/x509/ca.tmpl new file mode 100644 index 0000000..ed801af --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/ca.tmpl @@ -0,0 +1,4 @@ +cn = Knot Resolver testing CA +ca +cert_signing_key +expiration_days = -1 diff --git a/daemon/lua/trust_anchors.test/x509/gen.sh b/daemon/lua/trust_anchors.test/x509/gen.sh new file mode 100755 index 0000000..7251f12 --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/gen.sh @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# CA +certtool --generate-privkey > ca-key.pem +certtool --generate-self-signed --load-privkey ca-key.pem --template ca.tmpl --outfile ca.pem + +# server cert signed by CA above +certtool --generate-privkey > server-key.pem +certtool --generate-certificate --load-privkey server-key.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem --template server.tmpl --outfile server.pem + +# wrong CA - unrelated to others +certtool --generate-privkey > wrongca-key.pem +certtool --generate-self-signed --load-privkey wrongca-key.pem --template wrongca.tmpl --outfile wrongca.pem diff --git a/daemon/lua/trust_anchors.test/x509/server-key.pem b/daemon/lua/trust_anchors.test/x509/server-key.pem new file mode 100644 index 0000000..9eaef8a --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/server-key.pem @@ -0,0 +1,182 @@ +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: High (3072 bits) + +modulus: + 00:c3:46:2a:27:c8:39:e4:de:fa:24:45:6c:00:26:80 + 61:ca:dd:a1:24:34:1b:93:1c:13:c8:5a:cf:af:6a:ef + 34:b9:89:83:02:76:51:ad:67:bf:ed:39:ee:0a:15:57 + 91:6e:fa:68:60:78:22:62:fa:0a:55:12:03:b3:0c:8e + b4:ca:cd:2b:9d:a2:43:b5:5a:48:a0:3d:4a:1f:77:a4 + a6:d4:87:eb:79:99:df:d4:b4:a3:cf:91:03:a0:c5:82 + 39:f5:75:20:4b:90:b9:3b:72:65:a7:75:39:a6:62:58 + 65:b0:9c:40:5c:c7:c4:4c:d3:1e:cc:74:18:74:15:23 + 44:fd:51:59:b2:b7:70:95:6b:a0:be:d5:e4:72:59:2b + df:a5:a2:06:c8:e1:bd:17:80:25:b3:cf:8e:e8:ad:b2 + f7:04:b1:9e:b8:72:0b:c5:dc:cd:a5:b6:f5:c9:1a:eb + 63:78:75:9c:5d:c5:03:a9:4b:7b:d6:cd:5c:5f:8d:2e + d0:b4:0d:96:55:c7:1e:c7:ac:13:46:b8:ec:9c:36:b9 + 6a:1d:f0:7c:41:00:c6:bd:1f:81:7e:1d:48:1d:59:bc + e2:61:a6:d1:2d:52:10:3f:63:93:a9:14:d8:03:27:21 + b0:d4:07:24:78:04:2b:86:c8:2b:0f:eb:a7:b3:3e:e2 + 81:62:2a:4a:07:d9:fd:f6:77:7f:50:88:ee:bb:7d:31 + 53:a8:97:bf:30:07:37:41:e9:52:16:15:74:a8:64:ed + 93:46:38:56:b4:89:d9:0c:62:4b:64:a9:64:ff:fc:9a + d6:19:a7:84:98:28:04:b4:95:76:ac:4a:42:6a:fb:67 + 5b:b4:37:e6:e6:e2:52:d3:e9:38:8b:76:10:55:f1:e6 + 8e:d8:73:eb:17:d1:54:41:d4:5b:76:2d:70:7f:f5:0d + 7d:d2:d6:f8:05:33:18:ab:dd:10:8a:5b:21:ee:3d:78 + 9d:cd:c9:c0:c6:98:4e:a6:0a:41:f0:97:91:83:c2:c8 + 4b: + +public exponent: + 01:00:01: + +private exponent: + 23:88:1f:e1:8f:40:61:91:e5:28:36:6d:99:75:68:04 + e3:5a:02:99:48:d5:ff:a5:ab:3f:d8:ae:53:b7:fc:80 + b6:85:fc:0d:b5:a3:d5:0e:bc:d0:98:aa:e4:b0:cf:77 + 4a:1f:4c:60:c9:5b:50:71:38:f2:13:ce:12:85:65:6e + 26:3c:c1:03:f4:e3:a7:1b:1f:7d:f0:c9:0d:02:c3:36 + 0c:14:13:57:d4:14:f3:6a:4f:28:54:b5:b9:4a:57:10 + de:c6:0a:33:55:c9:2e:b4:f9:24:48:63:4e:10:35:0f + 83:dc:5a:a5:c7:3f:c3:ce:e2:9a:c7:41:2f:d6:2c:cd + bf:de:4e:99:03:61:fb:fe:52:88:86:f9:03:89:90:3f + 28:af:5b:d6:af:a4:ad:a3:06:b9:3a:3a:41:c2:61:7f + 2b:1e:7a:c8:0b:10:73:57:63:20:15:33:91:fd:50:f9 + 8f:90:ae:fc:2c:fe:26:8e:f2:a0:ba:4b:65:a3:95:f1 + d8:30:d4:fa:8d:12:1b:8b:58:1d:66:10:cd:41:22:1e + b0:7a:f4:e6:0f:76:3f:0a:0f:9c:44:e2:19:cf:c6:4d + de:3a:f4:96:70:c7:e6:2d:98:27:0e:ac:3b:32:41:37 + 4e:05:b2:22:af:7b:38:92:16:40:fb:5c:96:b0:86:da + 96:c6:77:c3:66:78:07:80:5c:2a:46:dc:9a:bf:fc:0c + 2f:ee:f7:a1:b3:77:b4:50:75:a2:b7:36:9d:28:73:ee + 7a:ab:a6:0c:f6:92:18:8f:ff:16:28:90:7f:16:4f:f1 + 6d:77:99:dd:a6:46:95:6a:6c:7a:15:48:53:b3:17:0b + 30:aa:0d:c8:68:33:2b:4d:40:da:74:cf:9b:73:1b:cd + 5d:f0:a9:d1:00:6f:db:de:55:ec:d4:24:96:bb:da:50 + b4:d5:e1:87:35:5c:d4:50:c3:03:d5:d5:ee:03:65:4b + 68:9c:07:5c:59:28:78:bd:d1:4b:cb:8d:85:8b:5b:c1 + + +prime1: + 00:cc:8d:55:38:2d:57:cb:d2:4b:57:5b:3f:a2:6d:91 + 4b:9c:54:29:98:9d:1d:bb:36:a6:e8:ba:e9:50:db:83 + cf:c1:45:24:16:70:e5:51:40:eb:23:6b:fa:be:d5:d5 + 00:27:ed:99:c7:7c:6b:16:79:77:0b:f3:ff:58:35:4e + 6c:58:68:51:d3:20:3c:57:b7:7d:bc:6b:fd:a7:c3:38 + 9a:f2:7a:8a:b6:71:a1:6e:5e:64:7e:a8:c5:7c:58:70 + fb:8d:63:b3:27:cc:1a:97:1e:04:da:d5:34:b5:d1:aa + f1:96:39:89:5e:cb:e7:75:ab:7e:ac:8c:fe:62:3e:cc + 93:66:88:d7:cd:c6:2d:db:9e:2f:f7:d1:6e:96:99:d2 + 32:61:f4:9b:f5:48:fe:e7:90:b7:a2:ab:89:90:c1:ae + 67:5d:18:7a:c1:a3:84:97:09:47:13:df:d2:85:46:46 + c7:77:3b:9f:b5:74:5c:f6:ec:a0:a7:66:0e:d1:d7:a4 + e1: + +prime2: + 00:f4:63:70:fa:dd:7e:3d:1a:2b:5b:47:79:56:e9:c7 + 7f:6b:50:41:60:45:af:59:e0:77:b2:76:4e:40:ff:f8 + 55:9d:77:3b:c1:00:6a:c6:84:6a:09:a5:45:e6:fc:e6 + e6:92:72:32:fc:93:8f:93:d9:db:fc:8c:43:d2:7a:ea + 4b:0e:ee:1d:dc:e0:27:08:83:16:aa:de:37:59:39:c0 + 21:26:b5:34:49:f2:1f:7b:0c:d4:3c:0f:e5:06:ac:23 + 7b:85:b1:39:35:44:ec:70:48:c5:10:86:02:ea:36:4a + f1:20:a2:b2:c9:8d:d3:f6:5a:86:72:4b:8b:28:07:04 + 39:8d:01:fa:75:3a:35:40:c2:21:c3:ac:50:da:2f:3e + 30:ee:ab:f7:7d:81:a3:77:5e:b7:03:be:52:fb:a4:70 + 92:5d:fd:09:ae:52:33:b8:7b:9c:e2:2a:77:f7:23:4d + c5:4b:82:f1:fb:0a:09:62:e6:5f:32:1e:7b:c7:c6:66 + ab: + +coefficient: + 00:c3:2d:d8:18:32:30:a1:fa:2c:23:d0:ea:b4:60:0f + 29:67:50:4a:5a:61:aa:6d:15:0e:4b:66:43:35:ee:39 + 4c:e7:8d:31:73:b0:bb:04:4d:e5:bc:28:ea:dc:77:81 + 35:bb:f7:80:13:96:04:4c:45:9c:43:6f:64:e0:a3:51 + 4b:7e:6c:b6:7d:c2:a6:e0:94:e6:6c:34:4e:62:71:ea + c0:c0:ab:30:30:c1:3a:39:0e:cd:f0:cc:0e:31:b5:fc + 61:64:1e:29:1c:cd:fc:69:c0:02:7c:2a:fe:86:d5:e2 + 7b:8d:fe:ae:3d:3a:6c:1b:b3:b4:0d:b1:1b:d1:4d:37 + 36:ea:d7:15:f3:6e:02:b1:86:98:51:02:fc:62:df:30 + f2:de:9f:03:6d:27:45:d7:c7:a8:04:ba:76:18:01:09 + 34:d2:57:f9:10:50:ea:ae:0e:ae:c6:a4:cd:f9:fd:b1 + 25:b2:45:20:bc:50:2d:9b:80:c1:39:08:97:d2:75:9b + f7: + +exp1: + 00:8a:b8:6a:8b:cf:8c:54:08:c8:d9:74:63:82:67:25 + fb:0e:08:b1:b0:f3:14:7f:ab:3f:a4:63:65:e9:55:05 + 5e:36:a7:0a:23:41:ea:f2:a0:c1:16:63:9b:48:22:41 + f2:7a:21:93:81:8a:ea:20:f2:bc:fe:59:39:d8:fb:45 + b5:0a:7b:ac:ca:2e:79:5d:cf:6d:b1:03:d7:a1:17:2e + e3:3e:00:46:e4:15:c9:b1:cc:c8:00:71:ba:84:6a:82 + 2c:c6:a5:4f:91:74:c4:af:a9:47:07:95:41:ca:f0:67 + 2a:b1:83:51:9a:fd:53:7a:24:94:a2:b6:77:a9:ef:06 + d4:0b:dc:4f:e6:18:39:6f:50:27:1d:bc:65:70:32:df + 2f:15:e9:4a:7f:1d:42:e9:8d:e6:4b:a6:63:83:cd:25 + d6:a9:76:f9:81:2a:c0:b7:a1:2e:17:d7:59:b0:d2:89 + 1b:aa:cb:bf:b2:d2:38:5d:a8:fa:06:ac:9a:ee:4a:7d + 61: + +exp2: + 00:92:66:af:db:d8:ac:33:36:66:1a:bc:6a:78:22:7c + 1c:5c:d1:2b:18:dd:25:fa:95:79:9f:33:38:15:c0:41 + a8:28:38:b1:57:21:44:d5:bf:a5:36:3a:07:f2:24:36 + be:91:a4:4f:de:f7:16:df:df:76:e5:87:b1:69:79:b0 + b9:5e:2c:4f:3f:6e:18:74:04:f3:a3:50:93:9f:a3:f4 + f0:e7:1b:4e:43:ae:04:25:d6:bd:9d:6d:78:29:d3:1d + 3e:76:0c:80:d4:e4:81:2f:92:a8:5b:09:ac:dd:59:c0 + f3:4a:35:ad:1d:09:15:9d:53:05:8f:9a:a9:b6:44:dd + c7:0c:2d:cf:38:42:b2:7c:24:cf:cd:44:80:fa:f3:aa + 31:ee:08:9e:ae:54:e6:f4:2f:8d:3b:74:dc:89:5b:2d + 04:c1:c1:3f:f7:69:cf:0a:09:23:26:69:82:8c:4e:5d + dc:7f:2b:e6:82:18:b5:1e:c6:1a:e9:0f:51:df:8f:7f + 19: + + +Public Key PIN: + pin-sha256:pFSHHHovr50DJ04K3wEJcyxth+nszZdClOet/CRN9cU= +Public Key ID: + sha256:a454871c7a2faf9d03274e0adf0109732c6d87e9eccd974294e7adfc244df5c5 + sha1:5137ef343399ccf38d6566803ddce123da640553 + +-----BEGIN RSA PRIVATE KEY----- +MIIG5QIBAAKCAYEAw0YqJ8g55N76JEVsACaAYcrdoSQ0G5McE8haz69q7zS5iYMC +dlGtZ7/tOe4KFVeRbvpoYHgiYvoKVRIDswyOtMrNK52iQ7VaSKA9Sh93pKbUh+t5 +md/UtKPPkQOgxYI59XUgS5C5O3Jlp3U5pmJYZbCcQFzHxEzTHsx0GHQVI0T9UVmy +t3CVa6C+1eRyWSvfpaIGyOG9F4Als8+O6K2y9wSxnrhyC8XczaW29cka62N4dZxd +xQOpS3vWzVxfjS7QtA2WVccex6wTRrjsnDa5ah3wfEEAxr0fgX4dSB1ZvOJhptEt +UhA/Y5OpFNgDJyGw1AckeAQrhsgrD+unsz7igWIqSgfZ/fZ3f1CI7rt9MVOol78w +BzdB6VIWFXSoZO2TRjhWtInZDGJLZKlk//ya1hmnhJgoBLSVdqxKQmr7Z1u0N+bm +4lLT6TiLdhBV8eaO2HPrF9FUQdRbdi1wf/UNfdLW+AUzGKvdEIpbIe49eJ3NycDG +mE6mCkHwl5GDwshLAgMBAAECggGAI4gf4Y9AYZHlKDZtmXVoBONaAplI1f+lqz/Y +rlO3/IC2hfwNtaPVDrzQmKrksM93Sh9MYMlbUHE48hPOEoVlbiY8wQP046cbH33w +yQ0CwzYMFBNX1BTzak8oVLW5SlcQ3sYKM1XJLrT5JEhjThA1D4PcWqXHP8PO4prH +QS/WLM2/3k6ZA2H7/lKIhvkDiZA/KK9b1q+kraMGuTo6QcJhfyseesgLEHNXYyAV +M5H9UPmPkK78LP4mjvKguktlo5Xx2DDU+o0SG4tYHWYQzUEiHrB69OYPdj8KD5xE +4hnPxk3eOvSWcMfmLZgnDqw7MkE3TgWyIq97OJIWQPtclrCG2pbGd8NmeAeAXCpG +3Jq//Awv7vehs3e0UHWitzadKHPuequmDPaSGI//FiiQfxZP8W13md2mRpVqbHoV +SFOzFwswqg3IaDMrTUDadM+bcxvNXfCp0QBv295V7NQklrvaULTV4Yc1XNRQwwPV +1e4DZUtonAdcWSh4vdFLy42Fi1vBAoHBAMyNVTgtV8vSS1dbP6JtkUucVCmYnR27 +NqbouulQ24PPwUUkFnDlUUDrI2v6vtXVACftmcd8axZ5dwvz/1g1TmxYaFHTIDxX +t328a/2nwzia8nqKtnGhbl5kfqjFfFhw+41jsyfMGpceBNrVNLXRqvGWOYley+d1 +q36sjP5iPsyTZojXzcYt254v99FulpnSMmH0m/VI/ueQt6KriZDBrmddGHrBo4SX +CUcT39KFRkbHdzuftXRc9uygp2YO0dek4QKBwQD0Y3D63X49GitbR3lW6cd/a1BB +YEWvWeB3snZOQP/4VZ13O8EAasaEagmlReb85uaScjL8k4+T2dv8jEPSeupLDu4d +3OAnCIMWqt43WTnAISa1NEnyH3sM1DwP5QasI3uFsTk1ROxwSMUQhgLqNkrxIKKy +yY3T9lqGckuLKAcEOY0B+nU6NUDCIcOsUNovPjDuq/d9gaN3XrcDvlL7pHCSXf0J +rlIzuHuc4ip39yNNxUuC8fsKCWLmXzIee8fGZqsCgcEAirhqi8+MVAjI2XRjgmcl ++w4IsbDzFH+rP6RjZelVBV42pwojQeryoMEWY5tIIkHyeiGTgYrqIPK8/lk52PtF +tQp7rMoueV3PbbED16EXLuM+AEbkFcmxzMgAcbqEaoIsxqVPkXTEr6lHB5VByvBn +KrGDUZr9U3oklKK2d6nvBtQL3E/mGDlvUCcdvGVwMt8vFelKfx1C6Y3mS6Zjg80l +1ql2+YEqwLehLhfXWbDSiRuqy7+y0jhdqPoGrJruSn1hAoHBAJJmr9vYrDM2Zhq8 +angifBxc0SsY3SX6lXmfMzgVwEGoKDixVyFE1b+lNjoH8iQ2vpGkT973Ft/fduWH +sWl5sLleLE8/bhh0BPOjUJOfo/Tw5xtOQ64EJda9nW14KdMdPnYMgNTkgS+SqFsJ +rN1ZwPNKNa0dCRWdUwWPmqm2RN3HDC3POEKyfCTPzUSA+vOqMe4Inq5U5vQvjTt0 +3IlbLQTBwT/3ac8KCSMmaYKMTl3cfyvmghi1HsYa6Q9R349/GQKBwQDDLdgYMjCh ++iwj0Oq0YA8pZ1BKWmGqbRUOS2ZDNe45TOeNMXOwuwRN5bwo6tx3gTW794ATlgRM +RZxDb2Tgo1FLfmy2fcKm4JTmbDROYnHqwMCrMDDBOjkOzfDMDjG1/GFkHikczfxp +wAJ8Kv6G1eJ7jf6uPTpsG7O0DbEb0U03NurXFfNuArGGmFEC/GLfMPLenwNtJ0XX +x6gEunYYAQk00lf5EFDqrg6uxqTN+f2xJbJFILxQLZuAwTkIl9J1m/c= +-----END RSA PRIVATE KEY----- diff --git a/daemon/lua/trust_anchors.test/x509/server.pem b/daemon/lua/trust_anchors.test/x509/server.pem new file mode 100644 index 0000000..b42f07e --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/server.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEfTCCAuWgAwIBAgIUIREQSLx52Sc9PFWI6Nwe3YzRp3MwDQYJKoZIhvcNAQEL +BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMCAXDTIwMDEw +NzA5MzQwOVoYDzk5OTkxMjMxMjM1OTU5WjA8MRIwEAYDVQQDEwlsb2NhbGhvc3Qx +JjAkBgNVBAoTHUZha2UgRE5TIHJvb3Qgb3JnIHRlc3Qgc2VydmVyMIIBojANBgkq +hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw0YqJ8g55N76JEVsACaAYcrdoSQ0G5Mc +E8haz69q7zS5iYMCdlGtZ7/tOe4KFVeRbvpoYHgiYvoKVRIDswyOtMrNK52iQ7Va +SKA9Sh93pKbUh+t5md/UtKPPkQOgxYI59XUgS5C5O3Jlp3U5pmJYZbCcQFzHxEzT +Hsx0GHQVI0T9UVmyt3CVa6C+1eRyWSvfpaIGyOG9F4Als8+O6K2y9wSxnrhyC8Xc +zaW29cka62N4dZxdxQOpS3vWzVxfjS7QtA2WVccex6wTRrjsnDa5ah3wfEEAxr0f +gX4dSB1ZvOJhptEtUhA/Y5OpFNgDJyGw1AckeAQrhsgrD+unsz7igWIqSgfZ/fZ3 +f1CI7rt9MVOol78wBzdB6VIWFXSoZO2TRjhWtInZDGJLZKlk//ya1hmnhJgoBLSV +dqxKQmr7Z1u0N+bm4lLT6TiLdhBV8eaO2HPrF9FUQdRbdi1wf/UNfdLW+AUzGKvd +EIpbIe49eJ3NycDGmE6mCkHwl5GDwshLAgMBAAGjgY0wgYowDAYDVR0TAQH/BAIw +ADAUBgNVHREEDTALgglsb2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYD +VR0PAQH/BAUDAwegADAdBgNVHQ4EFgQUUTfvNDOZzPONZWaAPdzhI9pkBVMwHwYD +VR0jBBgwFoAUkrfQxNEH4qc/gnuHhmrvn/Q3nMgwDQYJKoZIhvcNAQELBQADggGB +AFfFgv5J1eb8h33tnvDJ/dLBSA7Soz1NXK8iha18CH1uxW2lo+6iJl7g191WD/3W +m/LdpRU3h5ewbD6An3FSA0I25cYQD1vlH7vdI+xu3hIuFhQVnkxGbwISzlM5vat8 +1Ry7z/RpHmQA4V4z4R/PuYcQHQG5tINMPySmbfHBK/Ju+nnmSTJ/p3Z7sVaSCfNN +l37me0w197QU3ovNtA61xHa77VUSJeaAC+zOOXUBZ8Rc5PqhYOf6AJbIBk7tPNei +XH5Yyg3UT0i7V09vUViXK8EXbMX1VWsw59Et4Ro1YouS6TN34i2w8FtKg1+amQLr +UXmQW1lkzx23FdGG4T0fFPtWuJCL6ioc0J6vS7xt0xkbrri9U2thC7gvrKLGCJ6J +hWTGoKwjcBHpoLsT62XogHlctagkyXfjJ1Piik7k2JjqvmyteFlDDkOToLQmaCuI +LIBmOnO9mEig3y7T72cnL8QM+nb+c70cssfCW3LBTHb893J4QAOt5RN6LQUlFc49 +sQ== +-----END CERTIFICATE----- diff --git a/daemon/lua/trust_anchors.test/x509/server.tmpl b/daemon/lua/trust_anchors.test/x509/server.tmpl new file mode 100644 index 0000000..8021616 --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/server.tmpl @@ -0,0 +1,7 @@ +organization = Fake DNS root org test server +cn = localhost +tls_www_server +encryption_key +signing_key +dns_name = localhost +expiration_days = -1 diff --git a/daemon/lua/trust_anchors.test/x509/wrongca-key.pem b/daemon/lua/trust_anchors.test/x509/wrongca-key.pem new file mode 100644 index 0000000..1ddc1ad --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/wrongca-key.pem @@ -0,0 +1,182 @@ +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: High (3072 bits) + +modulus: + 00:bb:d7:47:1f:55:ed:c0:08:af:1d:32:d2:69:ef:77 + d2:f3:f6:86:7e:f3:97:e2:35:72:d4:0a:87:1e:75:76 + bf:59:29:be:cd:e6:ad:6d:7d:62:47:19:fb:ed:24:94 + 7f:2b:d6:0c:68:cf:cd:ee:f3:5e:b2:db:11:44:4b:7f + 30:ce:d2:a7:75:a7:37:83:c0:41:d6:a1:87:22:48:fa + ef:d1:15:ed:c9:d2:73:ab:e1:7c:94:4d:b2:96:80:cf + 5a:5c:7e:96:f6:02:fa:a4:8b:b1:05:b0:27:f5:d7:38 + bd:20:37:ed:12:c0:22:07:a9:a6:5e:47:bd:1d:33:27 + a2:cd:4c:0c:70:ba:6e:d9:13:6f:7b:a1:72:e8:f4:be + e3:86:1b:a2:b3:a1:07:cf:93:e8:3a:26:51:3e:af:bc + da:80:b1:92:56:8b:21:e7:1d:d9:f9:0c:a9:68:b7:04 + d8:6d:1f:6f:98:90:fb:fb:35:18:71:3c:50:73:b1:45 + b1:e7:ee:7b:84:5d:57:95:33:37:b0:0f:eb:85:8f:8d + b0:7f:10:17:80:03:99:1b:62:0c:1d:72:6f:e5:77:38 + c8:75:96:61:36:4b:28:ae:17:a4:f9:81:90:4d:4b:85 + 61:39:be:6c:ca:c0:a9:cd:4e:45:27:47:84:82:3d:7f + c6:a7:00:d7:90:64:7c:a5:e9:f8:f6:92:d2:72:54:a7 + 95:5f:fc:93:1d:c9:1a:78:6e:3a:1a:1f:8f:a2:41:d2 + 04:5c:19:32:54:16:f2:97:6f:7c:f9:24:d7:a6:e2:07 + cf:9f:9e:64:27:81:5f:5a:77:65:4f:7b:b2:81:78:3f + a3:22:17:d3:ba:06:71:d5:09:6a:c2:85:ba:35:f7:71 + 01:b4:63:c7:70:62:98:58:80:a2:40:27:c0:e2:d5:fd + 60:e0:5a:7a:9c:bf:7b:e6:34:78:f1:16:e8:28:d9:92 + dc:e6:2e:b6:d7:1a:83:4b:86:92:d6:81:ce:8e:50:0a + d5: + +public exponent: + 01:00:01: + +private exponent: + 7a:27:5e:66:1f:60:54:60:91:58:80:a3:5b:26:d2:9a + 89:f2:88:b6:68:3d:1e:6b:39:b8:70:fc:3b:af:91:c0 + 90:00:58:c7:d7:ba:72:98:76:5f:dc:a2:fb:2d:ad:b0 + 21:d6:ba:0d:33:0e:2d:d5:70:81:09:7b:6a:19:5a:a6 + 67:e9:8f:e3:30:12:27:08:d1:07:fd:d5:3e:53:8d:74 + 85:59:28:60:f6:0e:28:f9:a3:25:62:7d:bf:e8:16:70 + 21:f4:64:c1:a9:60:4b:bf:58:28:65:cd:26:cf:86:63 + 5f:5f:5f:39:b1:5e:af:f3:00:71:11:60:07:6c:2b:db + 70:7c:83:1e:8f:ee:e4:16:02:8a:b8:8c:5c:b8:44:a6 + fb:a0:5f:27:47:92:27:c8:7c:dd:cb:eb:4b:c3:c7:21 + a5:4d:54:e8:18:e4:bc:42:aa:6c:8e:72:60:d9:9c:3a + 0e:84:c1:f2:ca:5e:43:97:dc:c4:4e:bf:d6:ec:b2:70 + 08:41:13:01:48:bc:36:a2:eb:5e:67:b6:6a:a4:b6:4a + 24:fa:fd:6d:ef:5b:77:bc:0c:7d:95:9a:84:ec:3f:97 + aa:7c:07:76:80:f5:3a:49:f4:99:ee:cf:17:12:83:e8 + db:ef:22:60:67:62:f8:3e:f9:bc:18:2b:84:fc:a9:82 + 95:8d:91:27:8e:ba:87:15:65:1e:9f:b3:95:5f:dc:40 + 2f:15:eb:7e:0a:d7:69:80:7b:8a:e2:29:89:3a:2e:eb + a9:05:c1:1e:5d:23:0d:a0:d7:c4:95:4d:09:85:8c:af + 90:23:36:04:66:a9:16:d7:d4:e2:aa:5a:6d:44:5a:6c + c8:e8:a0:08:fa:de:19:20:5f:e3:06:17:e5:65:c6:55 + ef:0f:0d:ff:3e:1c:c5:98:ee:34:d3:07:81:11:fe:e9 + 15:87:e6:9a:76:44:bd:cb:a0:38:63:9a:af:d1:7c:a7 + db:26:e2:cd:4a:a2:8a:7f:b8:dc:7a:55:00:4c:20:c1 + + +prime1: + 00:c9:f5:14:59:49:3b:95:1f:15:b0:0c:83:cb:f4:6a + 48:60:2a:af:8b:d5:83:16:aa:71:5a:af:11:63:c6:c1 + 0a:91:af:5b:bd:6e:9c:cb:d7:eb:bf:c7:31:9f:22:46 + 01:cf:3b:3c:cb:ba:7d:ad:e5:bb:d8:7c:d2:5d:52:20 + 14:ea:70:08:9e:29:98:31:20:78:9e:b6:3e:90:e8:ef + c8:2f:45:d4:35:04:71:a1:84:18:50:a9:a5:12:b7:14 + 4e:42:3e:93:50:9d:2f:c1:bd:45:f3:4e:86:61:0b:bc + 3b:ed:78:c7:2b:ba:4b:a0:ef:e6:0e:a9:9a:f4:aa:73 + 23:b8:51:c7:d3:dd:fd:a7:1c:c1:69:32:ea:26:32:6d + 40:b0:0a:cd:0d:fa:b4:f4:56:ed:e8:d4:96:08:80:fd + 43:44:8c:fb:bb:af:81:d7:bb:71:c6:7c:3a:d2:a7:83 + e6:28:2d:2f:00:05:82:d7:cc:59:db:d9:e5:4f:a4:67 + 05: + +prime2: + 00:ee:1b:2a:48:37:fa:7c:94:35:36:ac:83:5f:2c:98 + e3:07:43:d1:2c:80:0e:a2:b8:7a:eb:e2:70:f6:49:77 + b3:42:05:fe:06:cf:3f:ca:0f:0d:44:1c:74:0a:77:f7 + 31:9f:30:fb:d9:44:71:11:e6:4a:ff:ef:ae:77:98:3e + 73:a0:77:21:a6:e0:66:9a:cf:5f:eb:3b:39:62:0b:ba + 1b:9b:1a:a5:58:4c:7e:17:fc:64:61:93:89:f0:c0:0f + ce:55:18:7e:d4:33:87:32:0e:53:51:5f:03:b4:05:4a + 5c:e7:5b:10:e5:b7:88:e5:04:b2:53:45:98:2f:9d:fb + 32:f5:2f:d9:59:54:ce:91:83:4c:37:ee:ab:5a:05:40 + 85:05:03:ae:b4:3d:96:c2:67:6b:28:25:91:87:ed:d1 + 3a:0f:4b:38:a5:81:b3:5b:6f:3e:33:27:1e:9a:4a:e6 + 3c:7c:be:9f:45:72:5b:eb:e3:dd:6c:73:ae:0d:07:bd + 91: + +coefficient: + 45:53:87:ab:71:9c:14:af:6c:00:44:bb:de:d5:72:ed + e9:21:f2:19:e5:4d:30:92:8e:9b:b7:f6:db:9e:ea:71 + b3:c2:89:01:4a:49:1f:2e:f8:34:57:e0:36:9a:20:84 + a8:b0:8a:0b:2a:d6:da:36:22:c2:ac:a2:85:99:f7:5d + 3f:2e:71:ab:e5:f7:bd:b2:8c:6f:44:33:aa:2d:cf:38 + 8c:d6:77:c7:d5:68:88:f1:f9:80:c2:e2:b8:58:26:bd + de:d6:8d:d5:c9:43:dc:e2:af:2e:d3:c5:19:4e:d5:14 + 33:bc:15:58:6f:05:eb:8d:0d:fa:40:a3:b7:77:24:4b + 30:a7:c2:8b:89:08:24:4d:fb:2e:3c:ad:ff:e3:d7:8b + 9c:f2:07:0d:79:3c:5e:f5:83:94:32:e2:16:dc:a9:22 + b4:f4:09:6a:f6:af:7d:9c:41:dc:be:23:7e:c4:6d:d6 + f9:e6:8e:3c:2d:00:fa:ac:d2:c8:6e:c5:6d:52:74:cd + + +exp1: + 4d:20:f9:2d:84:47:6a:13:1e:10:47:27:4a:8c:44:ce + f1:53:3c:09:d6:78:22:fe:e3:1d:b4:00:9b:2f:7b:e8 + 12:6d:7b:46:e4:68:a3:7d:09:ff:0b:0f:0b:6c:66:7a + 28:6f:c2:2f:38:40:e9:59:f4:9c:a0:47:22:f6:cb:63 + d1:89:09:f1:85:87:27:33:f4:7d:00:b2:f2:5a:d3:c0 + 8b:35:4a:ef:18:8c:61:17:f6:c5:4f:94:c8:89:fd:0a + 4a:48:65:b0:82:e7:8b:41:42:e6:c2:15:96:18:8a:42 + 04:d6:7c:92:59:aa:aa:83:14:44:83:47:b7:ab:25:1f + fe:33:d5:72:37:b4:b8:ce:c5:9a:ec:a3:fa:04:86:2f + 0f:4c:80:b5:97:0a:e6:ca:10:40:3c:78:34:35:37:04 + 2a:b9:01:26:d3:c7:6d:e1:9b:79:27:56:bb:be:d8:23 + dd:32:2c:62:00:b8:d0:bb:ad:91:c6:2c:ca:76:ca:15 + + +exp2: + 30:d8:19:c0:5e:db:5f:9a:f7:9f:93:9c:0f:76:12:96 + df:f2:a5:82:3f:72:c1:26:9e:f0:ac:af:07:96:e2:9b + 3f:3c:03:74:5a:27:77:c7:c6:ac:e6:39:57:bc:6c:55 + 1d:96:ea:d3:13:1b:2e:d4:d3:25:d5:81:30:bf:66:70 + 49:c6:a6:7c:99:23:f3:35:ff:33:3e:1e:f3:61:fc:77 + 95:45:ce:0d:63:03:aa:df:f7:a7:9c:a0:7b:66:aa:d7 + 64:d5:75:8f:0a:52:fd:8d:ba:c1:c2:7f:fb:f9:e9:db + 4d:0a:7d:58:e2:61:8e:b9:7b:eb:61:27:6a:fd:39:7e + a6:95:7e:3c:b9:0c:f7:04:bc:29:ed:27:f1:7b:8a:54 + bf:46:96:1c:1b:56:45:e2:f9:34:6f:20:7f:85:e5:99 + c7:71:62:d9:70:d5:de:37:df:c6:96:8b:cc:92:f8:d0 + 07:b7:02:ed:38:1c:6b:33:7f:44:b4:26:4c:3d:fe:41 + + + +Public Key PIN: + pin-sha256:UOonm3sEw21t/nC/tr24q9sX/HPV9mo0/M3Ya8rAwLs= +Public Key ID: + sha256:50ea279b7b04c36d6dfe70bfb6bdb8abdb17fc73d5f66a34fccdd86bcac0c0bb + sha1:b963cfb8eb202ccad2bb988dfa9e00cc52c1a4ba + +-----BEGIN RSA PRIVATE KEY----- +MIIG4gIBAAKCAYEAu9dHH1XtwAivHTLSae930vP2hn7zl+I1ctQKhx51dr9ZKb7N +5q1tfWJHGfvtJJR/K9YMaM/N7vNestsRREt/MM7Sp3WnN4PAQdahhyJI+u/RFe3J +0nOr4XyUTbKWgM9aXH6W9gL6pIuxBbAn9dc4vSA37RLAIgeppl5HvR0zJ6LNTAxw +um7ZE297oXLo9L7jhhuis6EHz5PoOiZRPq+82oCxklaLIecd2fkMqWi3BNhtH2+Y +kPv7NRhxPFBzsUWx5+57hF1XlTM3sA/rhY+NsH8QF4ADmRtiDB1yb+V3OMh1lmE2 +SyiuF6T5gZBNS4VhOb5sysCpzU5FJ0eEgj1/xqcA15BkfKXp+PaS0nJUp5Vf/JMd +yRp4bjoaH4+iQdIEXBkyVBbyl298+STXpuIHz5+eZCeBX1p3ZU97soF4P6MiF9O6 +BnHVCWrChbo193EBtGPHcGKYWICiQCfA4tX9YOBaepy/e+Y0ePEW6CjZktzmLrbX +GoNLhpLWgc6OUArVAgMBAAECggGAeideZh9gVGCRWICjWybSmonyiLZoPR5rObhw +/DuvkcCQAFjH17pymHZf3KL7La2wIda6DTMOLdVwgQl7ahlapmfpj+MwEicI0Qf9 +1T5TjXSFWShg9g4o+aMlYn2/6BZwIfRkwalgS79YKGXNJs+GY19fXzmxXq/zAHER +YAdsK9twfIMej+7kFgKKuIxcuESm+6BfJ0eSJ8h83cvrS8PHIaVNVOgY5LxCqmyO +cmDZnDoOhMHyyl5Dl9zETr/W7LJwCEETAUi8NqLrXme2aqS2SiT6/W3vW3e8DH2V +moTsP5eqfAd2gPU6SfSZ7s8XEoPo2+8iYGdi+D75vBgrhPypgpWNkSeOuocVZR6f +s5Vf3EAvFet+CtdpgHuK4imJOi7rqQXBHl0jDaDXxJVNCYWMr5AjNgRmqRbX1OKq +Wm1EWmzI6KAI+t4ZIF/jBhflZcZV7w8N/z4cxZjuNNMHgRH+6RWH5pp2RL3LoDhj +mq/RfKfbJuLNSqKKf7jcelUATCDBAoHBAMn1FFlJO5UfFbAMg8v0akhgKq+L1YMW +qnFarxFjxsEKka9bvW6cy9frv8cxnyJGAc87PMu6fa3lu9h80l1SIBTqcAieKZgx +IHietj6Q6O/IL0XUNQRxoYQYUKmlErcUTkI+k1CdL8G9RfNOhmELvDvteMcrukug +7+YOqZr0qnMjuFHH0939pxzBaTLqJjJtQLAKzQ36tPRW7ejUlgiA/UNEjPu7r4HX +u3HGfDrSp4PmKC0vAAWC18xZ29nlT6RnBQKBwQDuGypIN/p8lDU2rINfLJjjB0PR +LIAOorh66+Jw9kl3s0IF/gbPP8oPDUQcdAp39zGfMPvZRHER5kr/7653mD5zoHch +puBmms9f6zs5Ygu6G5sapVhMfhf8ZGGTifDAD85VGH7UM4cyDlNRXwO0BUpc51sQ +5beI5QSyU0WYL537MvUv2VlUzpGDTDfuq1oFQIUFA660PZbCZ2soJZGH7dE6D0s4 +pYGzW28+MycemkrmPHy+n0VyW+vj3Wxzrg0HvZECgcBNIPkthEdqEx4QRydKjETO +8VM8CdZ4Iv7jHbQAmy976BJte0bkaKN9Cf8LDwtsZnoob8IvOEDpWfScoEci9stj +0YkJ8YWHJzP0fQCy8lrTwIs1Su8YjGEX9sVPlMiJ/QpKSGWwgueLQULmwhWWGIpC +BNZ8klmqqoMURINHt6slH/4z1XI3tLjOxZrso/oEhi8PTIC1lwrmyhBAPHg0NTcE +KrkBJtPHbeGbeSdWu77YI90yLGIAuNC7rZHGLMp2yhUCgcAw2BnAXttfmvefk5wP +dhKW3/Klgj9ywSae8KyvB5bimz88A3RaJ3fHxqzmOVe8bFUdlurTExsu1NMl1YEw +v2ZwScamfJkj8zX/Mz4e82H8d5VFzg1jA6rf96ecoHtmqtdk1XWPClL9jbrBwn/7 ++enbTQp9WOJhjrl762Enav05fqaVfjy5DPcEvCntJ/F7ilS/RpYcG1ZF4vk0byB/ +heWZx3Fi2XDV3jffxpaLzJL40Ae3Au04HGszf0S0Jkw9/kECgcBFU4ercZwUr2wA +RLve1XLt6SHyGeVNMJKOm7f2257qcbPCiQFKSR8u+DRX4DaaIISosIoLKtbaNiLC +rKKFmfddPy5xq+X3vbKMb0Qzqi3POIzWd8fVaIjx+YDC4rhYJr3e1o3VyUPc4q8u +08UZTtUUM7wVWG8F640N+kCjt3ckSzCnwouJCCRN+y48rf/j14uc8gcNeTxe9YOU +MuIW3KkitPQJavavfZxB3L4jfsRt1vnmjjwtAPqs0shuxW1SdM0= +-----END RSA PRIVATE KEY----- diff --git a/daemon/lua/trust_anchors.test/x509/wrongca.pem b/daemon/lua/trust_anchors.test/x509/wrongca.pem new file mode 100644 index 0000000..fc3e43f --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/wrongca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEETCCAnmgAwIBAgIUNVTN+if8IQU0I1n4qyVF9qqhuo0wDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAxMUQW5vdGhlciB1bnJlbGF0ZWQgQ0EwIBcNMjAwMTA3MDkz +NDA5WhgPOTk5OTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFEFub3RoZXIgdW5yZWxh +dGVkIENBMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAu9dHH1XtwAiv +HTLSae930vP2hn7zl+I1ctQKhx51dr9ZKb7N5q1tfWJHGfvtJJR/K9YMaM/N7vNe +stsRREt/MM7Sp3WnN4PAQdahhyJI+u/RFe3J0nOr4XyUTbKWgM9aXH6W9gL6pIux +BbAn9dc4vSA37RLAIgeppl5HvR0zJ6LNTAxwum7ZE297oXLo9L7jhhuis6EHz5Po +OiZRPq+82oCxklaLIecd2fkMqWi3BNhtH2+YkPv7NRhxPFBzsUWx5+57hF1XlTM3 +sA/rhY+NsH8QF4ADmRtiDB1yb+V3OMh1lmE2SyiuF6T5gZBNS4VhOb5sysCpzU5F +J0eEgj1/xqcA15BkfKXp+PaS0nJUp5Vf/JMdyRp4bjoaH4+iQdIEXBkyVBbyl298 ++STXpuIHz5+eZCeBX1p3ZU97soF4P6MiF9O6BnHVCWrChbo193EBtGPHcGKYWICi +QCfA4tX9YOBaepy/e+Y0ePEW6CjZktzmLrbXGoNLhpLWgc6OUArVAgMBAAGjQzBB +MA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcEADAdBgNVHQ4EFgQUuWPP +uOsgLMrSu5iN+p4AzFLBpLowDQYJKoZIhvcNAQELBQADggGBAEobHXRbd6wfUmyf +P5v6qdJQMqtNGlU8eYzizEyuyovSlL+g9wgQ/91RAYK26FXzOuRz9Cg/ZWYVHqiG +rRWWwcfzY7qHo3HGkpDSIjD53TAoK46ICD4+EreG+JBvy1P3Ij/VX7M07swIg8Ff +6O4CnJpKAFaSr9wT8Ac3oCu+vymgLajMocNYV/UFVND+TLi6sx0zcMfCgW2vhSWk +PRulxL76xq97vjWoveqDiFS41cPOAghd4hUmzRFByX6XPBx6YZddSUF+QZt92K4Z +YEU4UbKqhbiBoZMGaQ8DzM2T44WPISrRZ0QpeS+pXwVjbDfoUbBWYAjFA8EHhPOi +oewIIYnarItI3z3iccErOeKPPVQh5QW3/CwO4XSnvTEBkhf2EjG25UAHZ8LZy0t8 +Sw1raGJPYJV/qNVeIzLKd3tYmNpcmddYqS+ei2yBOoO5UPdbYaH1gTAZ4BbOhOml +BJKJWcekpJrZAVTBNRectxsMXB8fHYL65Wa+w3cRqsZRjTbTEg== +-----END CERTIFICATE----- diff --git a/daemon/lua/trust_anchors.test/x509/wrongca.tmpl b/daemon/lua/trust_anchors.test/x509/wrongca.tmpl new file mode 100644 index 0000000..0e8491b --- /dev/null +++ b/daemon/lua/trust_anchors.test/x509/wrongca.tmpl @@ -0,0 +1,4 @@ +cn = Another unrelated CA +ca +cert_signing_key +expiration_days = -1 diff --git a/daemon/lua/zonefile.lua b/daemon/lua/zonefile.lua new file mode 100644 index 0000000..8ea3a08 --- /dev/null +++ b/daemon/lua/zonefile.lua @@ -0,0 +1,93 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +-- LuaJIT ffi bindings for zscanner, a DNS zone parser. +-- Author: Marek Vavrusa <marek.vavrusa@nic.cz> + +local ffi = require('ffi') +local libzscanner = ffi.load(libzscanner_SONAME) + +-- Wrap scanner context +local zs_scanner_t = ffi.typeof('zs_scanner_t') +ffi.metatype( zs_scanner_t, { + __gc = function(zs) return libzscanner.zs_deinit(zs) end, + __new = function(ct, origin, class, ttl) + if not class then class = 1 end + if not ttl then ttl = 3600 end + local parser = ffi.new(ct) + libzscanner.zs_init(parser, origin, class, ttl) + return parser + end, + __index = { + open = function (zs, file) + assert(ffi.istype(zs, zs_scanner_t)) + local ret = libzscanner.zs_set_input_file(zs, file) + if ret ~= 0 then return false, zs:strerr() end + return true + end, + parse = function(zs, input) + assert(ffi.istype(zs, zs_scanner_t)) + if input ~= nil then libzscanner.zs_set_input_string(zs, input, #input) end + local ret = libzscanner.zs_parse_record(zs) + -- Return current state only when parsed correctly, otherwise return error + if ret == 0 and zs.state ~= "ZS_STATE_ERROR" then + return zs.state == "ZS_STATE_DATA" + else + return false, zs:strerr() + end + end, + current_rr = function(zs) + assert(ffi.istype(zs, zs_scanner_t)) + return { + owner = ffi.string(zs.r_owner, zs.r_owner_length), + ttl = tonumber(zs.r_ttl), + class = tonumber(zs.r_class), + type = tonumber(zs.r_type), + rdata = ffi.string(zs.r_data, zs.r_data_length), + comment = zs:current_comment(), + } + end, + strerr = function(zs) + assert(ffi.istype(zs, zs_scanner_t)) + return ffi.string(libzscanner.zs_strerror(zs.error.code)) + end, + current_comment = function(zs) + if zs.buffer_length > 0 then + return ffi.string(zs.buffer, zs.buffer_length - 1) + else + return nil + end + end + }, +}) + +-- Module API +local rrparser = { + new = zs_scanner_t, + + -- Parse a file into a list of RRs + file = function (path) + local zs = zs_scanner_t() + local ok, err = zs:open(path) + if not ok then + return ok, err + end + local results = {} + while zs:parse() do + table.insert(results, zs:current_rr()) + end + return results + end, + + -- Parse a string into a list of RRs. + string = function (input) + local zs = zs_scanner_t() + local results = {} + local ok = zs:parse(input) + while ok do + table.insert(results, zs:current_rr()) + ok = zs:parse() + end + return results + end, +} +return rrparser |