Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • knot/knot-resolver
  • dkg/resolver
  • sbalazik/resolver
  • anb/knot-resolver
  • tkrizek/knot-resolver
  • jono/knot-resolver
  • analogic/knot-resolver
  • flokli/knot-resolver
  • hectorm/knot-resolver
  • aisha/knot-resolver
10 results
Show changes
Showing
with 4074 additions and 923 deletions
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "kresconfig.h"
#include "daemon/udp_queue.h"
#include "daemon/session2.h"
#include "lib/generic/array.h"
#include "lib/utils.h"
struct qr_task;
#include <sys/socket.h>
#if !ENABLE_SENDMMSG
int udp_queue_init_global(uv_loop_t *loop)
{
return 0;
}
/* Appease the linker in case this unused call isn't optimized out. */
void udp_queue_push(int fd, const struct sockaddr *sa, char *buf, size_t buf_len,
udp_queue_cb cb, void *baton)
{
abort();
}
void udp_queue_send_all(void)
{
}
#else
/* LATER: it might be useful to have this configurable during runtime,
* but the structures below would have to change a little (broken up). */
#define UDP_QUEUE_LEN 64
/** A queue of up to UDP_QUEUE_LEN messages, meant for the same socket. */
typedef struct {
int len; /**< The number of messages in the queue: 0..UDP_QUEUE_LEN */
struct mmsghdr msgvec[UDP_QUEUE_LEN]; /**< Parameter for sendmmsg() */
struct {
udp_queue_cb cb;
void *cb_baton;
struct iovec msg_iov[1]; /**< storage for .msgvec[i].msg_iov */
} items[UDP_QUEUE_LEN];
} udp_queue_t;
static udp_queue_t * udp_queue_create(void)
{
udp_queue_t *q = calloc(1, sizeof(*q));
kr_require(q != NULL);
for (int i = 0; i < UDP_QUEUE_LEN; ++i) {
struct msghdr *mhi = &q->msgvec[i].msg_hdr;
/* These shall remain always the same. */
mhi->msg_iov = q->items[i].msg_iov;
mhi->msg_iovlen = 1;
/* msg_name and msg_namelen will be per-call,
* and the rest is OK to remain zeroed all the time. */
}
return q;
}
/** Global state for udp_queue_*. Note: we never free the pointed-to memory. */
struct state {
/** Singleton map: fd -> udp_queue_t, as a simple array of pointers. */
udp_queue_t **udp_queues;
int udp_queues_len;
/** List of FD numbers that might have a non-empty queue. */
array_t(int) waiting_fds;
uv_check_t check_handle;
};
static struct state state = {0};
/** Empty the given queue. The queue is assumed to exist (but may be empty). */
static void udp_queue_send(int fd)
{
udp_queue_t *const q = state.udp_queues[fd];
if (!q->len) return;
int sent_len = sendmmsg(fd, q->msgvec, q->len, 0);
/* ATM we don't really do anything about failures. */
int err = sent_len < 0 ? errno : EAGAIN /* unknown error, really */;
for (int i = 0; i < q->len; ++i) {
if (q->items[i].cb)
q->items[i].cb(i < sent_len ? 0 : err, q->items[i].cb_baton);
}
q->len = 0;
}
/** Send all queued packets. */
void udp_queue_send_all(void)
{
for (int i = 0; i < state.waiting_fds.len; ++i) {
udp_queue_send(state.waiting_fds.at[i]);
}
state.waiting_fds.len = 0;
}
/** Periodical callback to send all queued packets. */
static void udp_queue_check(uv_check_t *handle)
{
udp_queue_send_all();
}
int udp_queue_init_global(uv_loop_t *loop)
{
int ret = uv_check_init(loop, &state.check_handle);
if (!ret) ret = uv_check_start(&state.check_handle, udp_queue_check);
return ret;
}
void udp_queue_push(int fd, const struct sockaddr *sa, char *buf, size_t buf_len,
udp_queue_cb cb, void *baton)
{
if (fd < 0) {
kr_log_error(SYSTEM, "ERROR: called udp_queue_push(fd = %d, ...)\n", fd);
abort();
}
/* Get a valid correct queue. */
if (fd >= state.udp_queues_len) {
const int new_len = fd + 1;
state.udp_queues = realloc(state.udp_queues, // NOLINT(bugprone-suspicious-realloc-usage): we just abort() below, so it's fine
sizeof(state.udp_queues[0]) * new_len); // NOLINT(bugprone-sizeof-expression): false-positive
if (!state.udp_queues) abort();
memset(state.udp_queues + state.udp_queues_len, 0,
sizeof(state.udp_queues[0]) * (new_len - state.udp_queues_len)); // NOLINT(bugprone-sizeof-expression): false-positive
state.udp_queues_len = new_len;
}
if (unlikely(state.udp_queues[fd] == NULL))
state.udp_queues[fd] = udp_queue_create();
udp_queue_t *const q = state.udp_queues[fd];
/* Append to the queue */
q->msgvec[q->len].msg_hdr.msg_name = (void *)sa;
q->msgvec[q->len].msg_hdr.msg_namelen = kr_sockaddr_len(sa);
q->items[q->len].cb = cb;
q->items[q->len].cb_baton = baton;
q->items[q->len].msg_iov[0] = (struct iovec){
.iov_base = buf,
.iov_len = buf_len,
};
if (q->len == 0)
array_push(state.waiting_fds, fd);
++(q->len);
if (q->len >= UDP_QUEUE_LEN) {
kr_assert(q->len == UDP_QUEUE_LEN);
udp_queue_send(fd);
/* We don't need to search state.waiting_fds;
* anyway, it's more efficient to let the hook do that. */
}
}
#endif
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <uv.h>
struct kr_request;
struct qr_task;
typedef void (*udp_queue_cb)(int status, void *baton);
/** Initialize the global state for udp_queue. */
int udp_queue_init_global(uv_loop_t *loop);
/** Send req->answer via UDP, possibly not immediately. */
void udp_queue_push(int fd, const struct sockaddr *sa, char *buf, size_t buf_len,
udp_queue_cb cb, void *baton);
/** Send all queued packets immediatelly. */
void udp_queue_send_all(void);
/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "kresconfig.h"
#include "daemon/worker.h"
#include <uv.h>
#include <lua.h>
#include <lauxlib.h>
#include <libknot/packet/pkt.h>
#include <libknot/descriptor.h>
#include <contrib/cleanup.h>
#include <contrib/ucw/lib.h>
#include <contrib/ucw/mempool.h>
#include <contrib/wire.h>
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
#include <malloc.h>
#endif
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>
#include "lib/utils.h"
#include "lib/layer.h"
#include "daemon/worker.h"
#include "daemon/bindings.h"
#include <gnutls/gnutls.h>
#if ENABLE_XDP
#include <libknot/xdp/xdp.h>
#endif
#include "daemon/bindings/api.h"
#include "daemon/engine.h"
#include "daemon/io.h"
#include "daemon/proxyv2.h"
#include "daemon/session2.h"
#include "daemon/tls.h"
#include "lib/cache/util.h" /* packet_ttl */
#include "lib/layer.h"
#include "lib/layer/iterate.h" /* kr_response_classify */
#include "lib/utils.h"
#include "daemon/defer.h"
/* @internal Union of various libuv objects for freelist. */
struct req
{
union {
/* Socket handles, these have session as their `handle->data` and own it. */
uv_udp_t udp;
uv_tcp_t tcp;
/* I/O events, these have only a reference to the task they're operating on. */
uv_udp_send_t send;
uv_write_t write;
uv_connect_t connect;
/* Timer events */
uv_timer_t timer;
} as;
};
/** @internal Debugging facility. */
#ifdef DEBUG
#define DEBUG_MSG(fmt...) printf("[daem] " fmt)
#else
#define DEBUG_MSG(fmt...)
/* Magic defaults for the worker. */
#ifndef MAX_PIPELINED
#define MAX_PIPELINED 100
#endif
#define MAX_DGRAM_LEN UINT16_MAX
#define VERBOSE_MSG(qry, ...) kr_log_q(qry, WORKER, __VA_ARGS__)
/** Client request state. */
struct request_ctx
{
struct kr_request req;
struct qr_task *task;
struct {
/** NULL if the request didn't come over network. */
struct session2 *session;
/** Requestor's address; separate because of UDP session "sharing". */
union kr_sockaddr addr;
/** Request communication address; if not from a proxy, same as addr. */
union kr_sockaddr comm_addr;
/** Local address. For AF_XDP we couldn't use session's,
* as the address might be different every time. */
union kr_sockaddr dst_addr;
/** Router's MAC address for XDP. */
ethaddr_t eth_from;
/** Our MAC address for XDP. */
ethaddr_t eth_to;
/** Whether XDP was used. */
bool xdp : 1;
} source;
};
/** Query resolution task. */
struct qr_task
{
struct request_ctx *ctx;
knot_pkt_t *pktbuf;
qr_tasklist_t waiting;
struct session2 *pending[MAX_PENDING];
uint16_t pending_count;
uint16_t timeouts;
uint16_t iter_count;
uint32_t refs;
bool finished : 1;
bool leading : 1;
uint64_t creation_time;
uint64_t send_time;
uint64_t recv_time;
struct kr_transport *transport;
};
/* Convenience macros */
#define qr_task_ref(task) \
do { ++(task)->refs; } while(0)
#define qr_task_unref(task) \
do { if (--(task)->refs == 0) { qr_task_free(task); } } while (0)
#define qr_valid_handle(task, checked) \
(!uv_is_closing((checked)) || (task)->source.handle == (checked))
do { \
if (task) \
kr_require((task)->refs > 0); \
if ((task) && --(task)->refs == 0) \
qr_task_free((task)); \
} while (0)
struct pl_dns_stream_sess_data {
struct protolayer_data h;
bool single : 1; /**< True: Stream only allows a single packet */
bool produced : 1; /**< True: At least one packet has been produced */
bool connected : 1; /**< True: The stream is connected */
bool half_closed : 1; /**< True: EOF was received, the stream is half-closed */
};
/* Forward decls */
static void qr_task_free(struct qr_task *task);
static int qr_task_step(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *packet);
/** @internal Get singleton worker. */
static inline struct worker_ctx *get_worker(void)
{
return uv_default_loop()->data;
}
static inline struct req *req_borrow(struct worker_ctx *worker)
{
struct req *req = NULL;
if (worker->pool_ioreq.len > 0) {
req = array_tail(worker->pool_ioreq);
array_pop(worker->pool_ioreq);
kr_asan_unpoison(req, sizeof(*req));
} else {
req = malloc(sizeof(*req));
}
return req;
}
static inline void req_release(struct worker_ctx *worker, struct req *req)
static int qr_task_step(struct qr_task *task,
const struct sockaddr *packet_source,
knot_pkt_t *packet);
static int qr_task_send(struct qr_task *task, struct session2 *session,
const struct comm_info *comm, knot_pkt_t *pkt);
static int qr_task_finalize(struct qr_task *task, int state);
static void qr_task_complete(struct qr_task *task);
static int worker_add_tcp_connected(const struct sockaddr* addr, struct session2 *session);
static int worker_del_tcp_connected(const struct sockaddr* addr);
static struct session2* worker_find_tcp_connected(const struct sockaddr* addr);
static int worker_add_tcp_waiting(const struct sockaddr* addr,
struct session2 *session);
static int worker_del_tcp_waiting(const struct sockaddr* addr);
static struct session2* worker_find_tcp_waiting(const struct sockaddr* addr);
static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt);
struct worker_ctx the_worker_value; /**< Static allocation is suitable for the singleton. */
struct worker_ctx *the_worker = NULL;
static inline void defer_sample_task(const struct qr_task *task)
{
if (!req || worker->pool_ioreq.len < 4 * MP_FREELIST_SIZE) {
array_push(worker->pool_ioreq, req);
kr_asan_poison(req, sizeof(*req));
} else {
free(req);
if (task && task->ctx->source.session) {
defer_sample_addr(&task->ctx->source.addr, task->ctx->source.session->stream);
defer_sample_state.price_factor16 = task->ctx->req.qsource.price_factor16;
}
}
/*! @internal Create a UDP/TCP handle */
static uv_handle_t *ioreq_spawn(struct qr_task *task, int socktype)
/*! @internal Create a UDP/TCP handle for an outgoing AF_INET* connection.
* socktype is SOCK_* */
static struct session2 *ioreq_spawn(int socktype, sa_family_t family,
enum kr_proto grp,
struct protolayer_data_param *layer_param,
size_t layer_param_count)
{
if (task->pending_count >= MAX_PENDING) {
bool precond = (socktype == SOCK_DGRAM || socktype == SOCK_STREAM)
&& (family == AF_INET || family == AF_INET6);
if (kr_fails_assert(precond)) {
kr_log_debug(WORKER, "ioreq_spawn: pre-condition failed\n");
return NULL;
}
/* Create connection for iterative query */
uv_handle_t *handle = (uv_handle_t *)req_borrow(task->worker);
if (!handle) {
struct session2 *s;
int ret = io_create(the_worker->loop, &s, socktype, family, grp,
layer_param, layer_param_count, true);
if (ret) {
if (ret == UV_EMFILE) {
the_worker->too_many_open = true;
the_worker->rconcurrent_highwatermark = the_worker->stats.rconcurrent;
}
return NULL;
}
io_create(task->worker->loop, handle, socktype);
/* Set current handle as a subrequest type. */
struct session *session = handle->data;
session->outgoing = true;
int ret = array_push(session->tasks, task);
if (ret < 0) {
io_deinit(handle);
req_release(task->worker, (struct req *)handle);
/* Bind to outgoing address, according to IP v4/v6. */
union kr_sockaddr *addr;
if (family == AF_INET) {
addr = (union kr_sockaddr *)&the_worker->out_addr4;
} else {
addr = (union kr_sockaddr *)&the_worker->out_addr6;
}
if (addr->ip.sa_family != AF_UNSPEC) {
if (kr_fails_assert(addr->ip.sa_family == family)) {
session2_force_close(s);
return NULL;
}
if (socktype == SOCK_DGRAM) {
uv_udp_t *udp = (uv_udp_t *)session2_get_handle(s);
ret = uv_udp_bind(udp, &addr->ip, 0);
} else if (socktype == SOCK_STREAM){
uv_tcp_t *tcp = (uv_tcp_t *)session2_get_handle(s);
ret = uv_tcp_bind(tcp, &addr->ip, 0);
}
}
if (ret != 0) {
session2_force_close(s);
return NULL;
}
qr_task_ref(task);
/* Connect or issue query datagram */
task->pending[task->pending_count] = handle;
task->pending_count += 1;
return handle;
return s;
}
static void ioreq_on_close(uv_handle_t *handle)
static void ioreq_kill_pending(struct qr_task *task)
{
struct worker_ctx *worker = get_worker();
/* Handle-type events own a session, must close it. */
struct session *session = handle->data;
struct qr_task *task = session->tasks.at[0];
io_deinit(handle);
qr_task_unref(task);
req_release(worker, (struct req *)handle);
for (uint16_t i = 0; i < task->pending_count; ++i) {
session2_kill_ioreq(task->pending[i], task);
}
task->pending_count = 0;
}
static void ioreq_kill(uv_handle_t *req)
/** Get a mempool. */
static inline struct mempool *pool_borrow(void)
{
assert(req);
if (!uv_is_closing(req)) {
uv_close(req, ioreq_on_close);
}
/* The implementation used to have extra caching layer,
* but it didn't work well. Now it's very simple. */
return mp_new((size_t)16 * 1024);
}
static void ioreq_killall(struct qr_task *task)
/** Return a mempool. */
static inline void pool_release(struct mempool *mp)
{
for (size_t i = 0; i < task->pending_count; ++i) {
ioreq_kill(task->pending[i]);
}
task->pending_count = 0;
mp_delete(mp);
}
/** @cond This memory layout is internal to mempool.c, use only for debugging. */
#if defined(__SANITIZE_ADDRESS__)
struct mempool_chunk {
struct mempool_chunk *next;
size_t size;
};
static void mp_poison(struct mempool *mp, bool poison)
/** Create a key for an outgoing subrequest: qname, qclass, qtype.
* @param key Destination buffer for key size, MUST be SUBREQ_KEY_LEN or larger.
* @return key length if successful or an error
*/
static const size_t SUBREQ_KEY_LEN = KR_RRKEY_LEN;
static int subreq_key(char *dst, knot_pkt_t *pkt)
{
if (!poison) { /* @note mempool is part of the first chunk, unpoison it first */
kr_asan_unpoison(mp, sizeof(*mp));
}
struct mempool_chunk *chunk = mp->state.last[0];
void *chunk_off = (void *)chunk - chunk->size;
if (poison) {
kr_asan_poison(chunk_off, chunk->size);
} else {
kr_asan_unpoison(chunk_off, chunk->size);
}
kr_require(pkt);
return kr_rrkey(dst, knot_pkt_qclass(pkt), knot_pkt_qname(pkt),
knot_pkt_qtype(pkt), knot_pkt_qtype(pkt));
}
#else
#define mp_poison(mp, enable)
#endif
/** @endcond */
static inline struct mempool *pool_borrow(struct worker_ctx *worker)
#if ENABLE_XDP
static uint8_t *alloc_wire_cb(struct kr_request *req, uint16_t *maxlen)
{
/* Recycle available mempool if possible */
struct mempool *mp = NULL;
if (worker->pool_mp.len > 0) {
mp = array_tail(worker->pool_mp);
array_pop(worker->pool_mp);
mp_poison(mp, 0);
} else { /* No mempool on the freelist, create new one */
mp = mp_new (4 * CPU_PAGE_SIZE);
if (kr_fails_assert(maxlen))
return NULL;
struct request_ctx *ctx = (struct request_ctx *)req;
/* We know it's an AF_XDP socket; otherwise this CB isn't assigned. */
uv_handle_t *handle = session2_get_handle(ctx->source.session);
if (kr_fails_assert(handle->type == UV_POLL))
return NULL;
xdp_handle_data_t *xhd = handle->data;
knot_xdp_msg_t out;
bool ipv6 = ctx->source.comm_addr.ip.sa_family == AF_INET6;
int ret = knot_xdp_send_alloc(xhd->socket, ipv6 ? KNOT_XDP_MSG_IPV6 : 0, &out);
if (ret != KNOT_EOK) {
kr_assert(ret == KNOT_ENOMEM);
*maxlen = 0;
return NULL;
}
return mp;
*maxlen = MIN(*maxlen, out.payload.iov_len);
return out.payload.iov_base;
}
static inline void pool_release(struct worker_ctx *worker, struct mempool *mp)
static void free_wire(const struct request_ctx *ctx)
{
/* Return mempool to ring or free it if it's full */
if (worker->pool_mp.len < MP_FREELIST_SIZE) {
mp_flush(mp);
array_push(worker->pool_mp, mp);
mp_poison(mp, 1);
} else {
mp_delete(mp);
}
if (kr_fails_assert(ctx->req.alloc_wire_cb == alloc_wire_cb))
return;
knot_pkt_t *ans = ctx->req.answer;
if (unlikely(ans == NULL)) /* dropped */
return;
if (likely(ans->wire == NULL)) /* sent most likely */
return;
if (!ctx->source.session)
return;
/* We know it's an AF_XDP socket; otherwise alloc_wire_cb isn't assigned. */
uv_handle_t *handle = session2_get_handle(ctx->source.session);
if (!handle || kr_fails_assert(handle->type == UV_POLL))
return;
xdp_handle_data_t *xhd = handle->data;
/* Freeing is done by sending an empty packet (the API won't really send it). */
knot_xdp_msg_t out;
out.payload.iov_base = ans->wire;
out.payload.iov_len = 0;
uint32_t sent = 0;
int ret = 0;
knot_xdp_send_free(xhd->socket, &out, 1);
kr_assert(ret == KNOT_EOK && sent == 0);
kr_log_debug(XDP, "freed unsent buffer, ret = %d\n", ret);
}
#endif
/* Helper functions for transport selection */
static inline bool is_tls_capable(struct sockaddr *address) {
tls_client_param_t *tls_entry = tls_client_param_get(
the_network->tls_client_params, address);
return tls_entry;
}
/** @internal Get key from current outgoing subrequest. */
static int subreq_key(char *dst, knot_pkt_t *pkt)
{
assert(pkt);
return kr_rrkey(dst, knot_pkt_qname(pkt), knot_pkt_qtype(pkt), knot_pkt_qclass(pkt));
static inline bool is_tcp_connected(struct sockaddr *address) {
return worker_find_tcp_connected(address);
}
static struct qr_task *qr_task_create(struct worker_ctx *worker, uv_handle_t *handle, const struct sockaddr *addr)
{
/* How much can client handle? */
struct engine *engine = worker->engine;
size_t pktbuf_max = KR_EDNS_PAYLOAD;
if (engine->resolver.opt_rr) {
pktbuf_max = MAX(knot_edns_get_payload(engine->resolver.opt_rr), pktbuf_max);
}
static inline bool is_tcp_waiting(struct sockaddr *address) {
return worker_find_tcp_waiting(address);
}
/* Recycle available mempool if possible */
/** Create and initialize a request_ctx (on a fresh mempool).
*
* session and addr point to the source of the request, and they are NULL
* in case the request didn't come from network.
*/
static struct request_ctx *request_create(struct session2 *session,
struct comm_info *comm,
uint32_t uid)
{
knot_mm_t pool = {
.ctx = pool_borrow(worker),
.ctx = pool_borrow(),
.alloc = (knot_mm_alloc_t) mp_alloc
};
/* Create resolution task */
struct qr_task *task = mm_alloc(&pool, sizeof(*task));
if (!task) {
mp_delete(pool.ctx);
/* Create request context */
struct request_ctx *ctx = mm_calloc(&pool, 1, sizeof(*ctx));
if (!ctx) {
pool_release(pool.ctx);
return NULL;
}
/* Create packet buffers for answer and subrequests */
task->req.pool = pool;
knot_pkt_t *pktbuf = knot_pkt_new(NULL, pktbuf_max, &task->req.pool);
if (!pktbuf) {
mp_delete(pool.ctx);
/* TODO Relocate pool to struct request */
if (session && kr_fails_assert(session->outgoing == false)) {
pool_release(pool.ctx);
return NULL;
}
pktbuf->size = 0;
task->req.answer = NULL;
task->pktbuf = pktbuf;
array_init(task->waiting);
task->addrlist = NULL;
task->pending_count = 0;
task->bytes_remaining = 0;
task->iter_count = 0;
task->timeouts = 0;
task->refs = 1;
task->finished = false;
task->leading = false;
task->worker = worker;
task->session = NULL;
task->source.handle = handle;
task->timeout = NULL;
task->on_complete = NULL;
task->req.qsource.key = NULL;
task->req.qsource.addr = NULL;
task->req.qsource.dst_addr = NULL;
/* Remember query source addr */
if (addr) {
size_t addr_len = sizeof(struct sockaddr_in);
if (addr->sa_family == AF_INET6)
addr_len = sizeof(struct sockaddr_in6);
memcpy(&task->source.addr, addr, addr_len);
task->req.qsource.addr = (const struct sockaddr *)&task->source.addr;
} else {
task->source.addr.ip4.sin_family = AF_UNSPEC;
}
/* Remember the destination address. */
if (handle) {
int addr_len = sizeof(task->source.dst_addr);
struct sockaddr *dst_addr = (struct sockaddr *)&task->source.dst_addr;
task->source.dst_addr.ip4.sin_family = AF_UNSPEC;
if (handle->type == UV_UDP) {
if (uv_udp_getsockname((uv_udp_t *)handle, dst_addr, &addr_len) == 0) {
task->req.qsource.dst_addr = dst_addr;
}
} else if (handle->type == UV_TCP) {
if (uv_tcp_getsockname((uv_tcp_t *)handle, dst_addr, &addr_len) == 0) {
task->req.qsource.dst_addr = dst_addr;
}
ctx->source.session = session;
if (comm && comm->xdp) {
#if ENABLE_XDP
if (kr_fails_assert(session)) {
pool_release(pool.ctx);
return NULL;
}
memcpy(ctx->source.eth_to, comm->eth_to, sizeof(ctx->source.eth_to));
memcpy(ctx->source.eth_from, comm->eth_from, sizeof(ctx->source.eth_from));
ctx->req.alloc_wire_cb = alloc_wire_cb;
#else
kr_assert(!EINVAL);
pool_release(pool.ctx);
return NULL;
#endif
}
worker->stats.concurrent += 1;
return task;
}
/* This is called when the task refcount is zero, free memory. */
static void qr_task_free(struct qr_task *task)
{
struct session *session = task->session;
struct kr_request *req = &ctx->req;
req->pool = pool;
req->vars_ref = LUA_NOREF;
req->uid = uid;
req->qsource.comm_flags.xdp = comm && comm->xdp;
req->qsource.price_factor16 = 1 << 16; // meaning *1.0
kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL);
array_init(req->qsource.headers);
if (session) {
/* Walk the session task list and remove itself. */
for (size_t i = 0; i < session->tasks.len; ++i) {
if (session->tasks.at[i] == task) {
array_del(session->tasks, i);
break;
}
}
/* Start reading again if the session is throttled and
* the number of outgoing requests is below watermark. */
uv_handle_t *handle = task->source.handle;
if (handle && session->tasks.len < task->worker->tcp_pipeline_max/2) {
if (!uv_is_closing(handle) && session->throttled) {
io_start_read(handle);
session->throttled = false;
}
kr_require(comm);
const struct sockaddr *src_addr = comm->src_addr;
const struct sockaddr *comm_addr = comm->comm_addr;
const struct sockaddr *dst_addr = comm->dst_addr;
const struct proxy_result *proxy = comm->proxy;
req->qsource.stream_id = -1;
session2_init_request(session, req);
req->qsource.flags = req->qsource.comm_flags;
if (proxy) {
req->qsource.flags.tcp = proxy->protocol == SOCK_STREAM;
req->qsource.flags.tls = proxy->has_tls;
}
/* We need to store a copy of peer address. */
memcpy(&ctx->source.addr.ip, src_addr, kr_sockaddr_len(src_addr));
req->qsource.addr = &ctx->source.addr.ip;
if (!comm_addr)
comm_addr = src_addr;
memcpy(&ctx->source.comm_addr.ip, comm_addr, kr_sockaddr_len(comm_addr));
req->qsource.comm_addr = &ctx->source.comm_addr.ip;
if (!dst_addr) /* We wouldn't have to copy in this case, but for consistency. */
dst_addr = session2_get_sockname(session);
memcpy(&ctx->source.dst_addr.ip, dst_addr, kr_sockaddr_len(dst_addr));
req->qsource.dst_addr = &ctx->source.dst_addr.ip;
}
/* Update stats */
struct worker_ctx *worker = task->worker;
worker->stats.concurrent -= 1;
/* Return mempool to ring or free it if it's full */
pool_release(worker, task->req.pool.ctx);
/* @note The 'task' is invalidated from now on. */
/* Decommit memory every once in a while */
static int mp_delete_count = 0;
if (++mp_delete_count == 100000) {
lua_gc(worker->engine->L, LUA_GCCOLLECT, 0);
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
malloc_trim(0);
#endif
mp_delete_count = 0;
}
req->selection_context.is_tls_capable = is_tls_capable;
req->selection_context.is_tcp_connected = is_tcp_connected;
req->selection_context.is_tcp_waiting = is_tcp_waiting;
array_init(req->selection_context.forwarding_targets);
array_reserve_mm(req->selection_context.forwarding_targets, 1, kr_memreserve, &req->pool);
the_worker->stats.rconcurrent += 1;
return ctx;
}
static int qr_task_start(struct qr_task *task, knot_pkt_t *query)
/** More initialization, related to the particular incoming query/packet. */
static int request_start(struct request_ctx *ctx, knot_pkt_t *query)
{
assert(task && query);
size_t answer_max = KNOT_WIRE_MIN_PKTSIZE;
if (!task->source.handle || task->source.handle->type == UV_TCP) {
answer_max = KNOT_WIRE_MAX_PKTSIZE;
} else if (knot_pkt_has_edns(query)) { /* EDNS */
answer_max = MAX(knot_edns_get_payload(query->opt_rr), KNOT_WIRE_MIN_PKTSIZE);
}
if (kr_fails_assert(query && ctx))
return kr_error(EINVAL);
knot_pkt_t *answer = knot_pkt_new(NULL, answer_max, &task->req.pool);
if (!answer) {
return kr_error(ENOMEM);
struct kr_request *req = &ctx->req;
req->qsource.size = query->size;
if (knot_pkt_has_tsig(query)) {
req->qsource.size += query->tsig_wire.len;
}
task->req.answer = answer;
/* Remember query source TSIG key */
if (query->tsig_rr) {
task->req.qsource.key = knot_rrset_copy(query->tsig_rr, &task->req.pool);
knot_pkt_t *pkt = knot_pkt_new(NULL, req->qsource.size, &req->pool);
if (!pkt) {
return kr_error(ENOMEM);
}
/* Remember query source EDNS data */
if (query->opt_rr) {
task->req.qsource.opt = knot_rrset_copy(query->opt_rr, &task->req.pool);
int ret = knot_pkt_copy(pkt, query);
if (ret != KNOT_EOK && ret != KNOT_ETRAIL) {
return kr_error(ENOMEM);
}
req->qsource.packet = pkt;
/* Start resolution */
struct worker_ctx *worker = task->worker;
struct engine *engine = worker->engine;
kr_resolve_begin(&task->req, &engine->resolver, answer);
worker->stats.queries += 1;
/* Throttle outbound queries only when high pressure */
if (worker->stats.concurrent < QUERY_RATE_THRESHOLD) {
task->req.options |= QUERY_NO_THROTTLE;
kr_resolve_begin(req, the_resolver);
the_worker->stats.queries += 1;
return kr_ok();
}
static void request_free(struct request_ctx *ctx)
{
/* Dereference any Lua vars table if exists */
if (ctx->req.vars_ref != LUA_NOREF) {
lua_State *L = the_engine->L;
/* Get worker variables table */
lua_rawgeti(L, LUA_REGISTRYINDEX, the_worker->vars_table_ref);
/* Get next free element (position 0) and store it under current reference (forming a list) */
lua_rawgeti(L, -1, 0);
lua_rawseti(L, -2, ctx->req.vars_ref);
/* Set current reference as the next free element */
lua_pushinteger(L, ctx->req.vars_ref);
lua_rawseti(L, -2, 0);
lua_pop(L, 1);
ctx->req.vars_ref = LUA_NOREF;
}
/* Free HTTP/2 headers for DoH requests. */
for(int i = 0; i < ctx->req.qsource.headers.len; i++) {
free(ctx->req.qsource.headers.at[i].name);
free(ctx->req.qsource.headers.at[i].value);
}
array_clear(ctx->req.qsource.headers);
/* Make sure to free XDP buffer in case it wasn't sent. */
if (ctx->req.alloc_wire_cb) {
#if ENABLE_XDP
free_wire(ctx);
#else
kr_assert(!EINVAL);
#endif
}
return 0;
/* Return mempool to ring or free it if it's full */
pool_release(ctx->req.pool.ctx);
/* @note The 'task' is invalidated from now on. */
the_worker->stats.rconcurrent -= 1;
}
/*@ Register qr_task within session. */
static int qr_task_register(struct qr_task *task, struct session *session)
static struct qr_task *qr_task_create(struct request_ctx *ctx)
{
int ret = array_reserve(session->tasks, session->tasks.len + 1);
if (ret != 0) {
return kr_error(ENOMEM);
/* Choose (initial) pktbuf size. As it is now, pktbuf can be used
* for UDP answers from upstream *and* from cache
* and for sending queries upstream */
uint16_t pktbuf_max = KR_EDNS_PAYLOAD;
const knot_rrset_t *opt_our = the_resolver->upstream_opt_rr;
if (opt_our) {
pktbuf_max = MAX(pktbuf_max, knot_edns_get_payload(opt_our));
}
/* Create resolution task */
struct qr_task *task = mm_calloc(&ctx->req.pool, 1, sizeof(*task));
if (!task) {
return NULL;
}
/* Create packet buffers for answer and subrequests */
knot_pkt_t *pktbuf = knot_pkt_new(NULL, pktbuf_max, &ctx->req.pool);
if (!pktbuf) {
mm_free(&ctx->req.pool, task);
return NULL;
}
array_push(session->tasks, task);
task->session = session;
pktbuf->size = 0;
task->ctx = ctx;
task->pktbuf = pktbuf;
array_init(task->waiting);
task->refs = 0;
kr_assert(ctx->task == NULL);
ctx->task = task;
/* Make the primary reference to task. */
qr_task_ref(task);
task->creation_time = kr_now();
the_worker->stats.concurrent += 1;
return task;
}
/* This is called when the task refcount is zero, free memory. */
static void qr_task_free(struct qr_task *task)
{
struct request_ctx *ctx = task->ctx;
if (kr_fails_assert(ctx))
return;
kr_require(ctx->task == NULL);
request_free(ctx);
/* Update stats */
the_worker->stats.concurrent -= 1;
}
/*@ Register new qr_task within session. */
static int qr_task_register(struct qr_task *task, struct session2 *session)
{
if (kr_fails_assert(!session->outgoing && session->stream))
return kr_error(EINVAL);
session2_tasklist_add(session, task);
struct request_ctx *ctx = task->ctx;
if (kr_fails_assert(ctx && (ctx->source.session == NULL || ctx->source.session == session)))
return kr_error(EINVAL);
ctx->source.session = session;
/* Soft-limit on parallel queries, there is no "slow down" RCODE
* that we could use to signalize to client, but we can stop reading,
* an in effect shrink TCP window size. To get more precise throttling,
* we would need to copy remainder of the unread buffer and reassemble
* when resuming reading. This is NYI. */
if (session->tasks.len >= task->worker->tcp_pipeline_max) {
uv_handle_t *handle = task->source.handle;
if (handle && !session->throttled && !uv_is_closing(handle)) {
io_stop_read(handle);
session->throttled = true;
}
if (session2_tasklist_get_len(session) >= the_worker->tcp_pipeline_max &&
!session->throttled && !session->closing) {
session2_stop_read(session);
session->throttled = true;
}
return 0;
}
static void qr_task_complete(struct qr_task *task)
{
struct worker_ctx *worker = task->worker;
struct request_ctx *ctx = task->ctx;
/* Kill pending I/O requests */
ioreq_killall(task);
assert(task->waiting.len == 0);
assert(task->leading == false);
/* Run the completion callback. */
if (task->on_complete) {
task->on_complete(worker, &task->req, task->baton);
ioreq_kill_pending(task);
kr_require(task->waiting.len == 0);
kr_require(task->leading == false);
struct session2 *s = ctx->source.session;
if (s) {
kr_require(!s->outgoing && session2_waitinglist_is_empty(s));
ctx->source.session = NULL;
session2_tasklist_del(s, task);
}
/* Release primary reference to task. */
qr_task_unref(task);
if (ctx->task == task) {
ctx->task = NULL;
qr_task_unref(task);
}
}
/* This is called when we send subrequest / answer */
static int qr_task_on_send(struct qr_task *task, uv_handle_t *handle, int status)
{
if (!task->finished) {
if (status == 0 && handle) {
/* For TCP we can be sure there will be no retransmit, so we flush
* the packet buffer so it can be reused again for reassembly. */
if (handle->type == UV_TCP) {
knot_pkt_t *pktbuf = task->pktbuf;
knot_pkt_clear(pktbuf);
pktbuf->size = 0;
}
io_start_read(handle); /* Start reading new query */
}
} else {
assert(task->timeout == NULL);
int qr_task_on_send(struct qr_task *task, struct session2 *s, int status)
{
if (task->finished) {
kr_require(task->leading == false);
qr_task_complete(task);
}
return status;
}
static void on_send(uv_udp_send_t *req, int status)
{
struct worker_ctx *worker = get_worker();
struct qr_task *task = req->data;
if (qr_valid_handle(task, (uv_handle_t *)req->handle)) {
qr_task_on_send(task, (uv_handle_t *)req->handle, status);
if (!s)
return status;
if (!s->stream && s->outgoing) {
// This should ensure that we are only dealing with our question to upstream
if (kr_fails_assert(!knot_wire_get_qr(task->pktbuf->wire)))
return status;
// start the timer
struct kr_query *qry = array_tail(task->ctx->req.rplan.pending);
if (kr_fails_assert(qry && task->transport))
return status;
size_t timeout = task->transport->timeout;
int ret = session2_timer_start(s, PROTOLAYER_EVENT_GENERAL_TIMEOUT,
timeout, 0);
/* Start next step with timeout, fatal if can't start a timer. */
if (ret != 0) {
subreq_finalize(task, &task->transport->address.ip, task->pktbuf);
qr_task_finalize(task, KR_STATE_FAIL);
}
}
qr_task_unref(task);
req_release(worker, (struct req *)req);
if (s->stream) {
if (status != 0) { // session probably not usable anymore; typically: ECONNRESET
const struct kr_request *req = &task->ctx->req;
if (kr_log_is_debug(WORKER, req)) {
const char *peer_str = NULL;
if (!s->outgoing) {
peer_str = "hidden"; // avoid logging downstream IPs
} else if (task->transport) {
peer_str = kr_straddr(&task->transport->address.ip);
}
if (!peer_str)
peer_str = "unknown"; // probably shouldn't happen
kr_log_req(req, 0, 0, WORKER,
"=> disconnected from '%s': %s\n",
peer_str, uv_strerror(status));
}
session2_force_close(s);
return status;
}
if (s->outgoing || s->closing)
return status;
if (s->throttled &&
session2_tasklist_get_len(s) < the_worker->tcp_pipeline_max/2) {
/* Start reading again if the session is throttled and
* the number of outgoing requests is below watermark. */
session2_start_read(s);
s->throttled = false;
}
}
return status;
}
static void on_write(uv_write_t *req, int status)
static void qr_task_wrap_finished(int status, struct session2 *session,
const struct comm_info *comm, void *baton)
{
struct worker_ctx *worker = get_worker();
struct qr_task *task = req->data;
if (qr_valid_handle(task, (uv_handle_t *)req->handle)) {
qr_task_on_send(task, (uv_handle_t *)req->handle, status);
}
struct qr_task *task = baton;
qr_task_on_send(task, session, status);
qr_task_unref(task);
req_release(worker, (struct req *)req);
}
static int qr_task_send(struct qr_task *task, uv_handle_t *handle, struct sockaddr *addr, knot_pkt_t *pkt)
static int qr_task_send(struct qr_task *task, struct session2 *session,
const struct comm_info *comm, knot_pkt_t *pkt)
{
if (!handle) {
return qr_task_on_send(task, handle, kr_error(EIO));
}
/* Synchronous push to TLS context, bypassing event loop. */
struct session *session = handle->data;
if (session->has_tls) {
int ret = tls_push(task, handle, pkt);
return qr_task_on_send(task, handle, ret);
}
if (!session)
return qr_task_on_send(task, NULL, kr_error(EIO));
int ret = 0;
struct req *send_req = req_borrow(task->worker);
if (!send_req) {
return qr_task_on_send(task, handle, kr_error(ENOMEM));
}
if (knot_wire_get_qr(pkt->wire) == 0) {
/*
* Query must be finalised using destination address before
* sending.
*
* Libuv does not offer a convenient way how to obtain a source
* IP address from a UDP handle that has been initialised using
* uv_udp_init(). The uv_udp_getsockname() fails because of the
* lazy socket initialisation.
*
* @note -- A solution might be opening a separate socket and
* trying to obtain the IP address from it.
*/
ret = kr_resolve_checkout(&task->req, NULL, addr,
handle->type == UV_UDP ? SOCK_DGRAM : SOCK_STREAM,
pkt);
if (ret != kr_ok()) {
return ret;
if (pkt == NULL)
pkt = worker_task_get_pktbuf(task);
if (session->outgoing && session->stream) {
size_t try_limit = session2_tasklist_get_len(session) + 1;
uint16_t msg_id = knot_wire_get_id(pkt->wire);
size_t try_count = 0;
while (session2_tasklist_find_msgid(session, msg_id) &&
try_count <= try_limit) {
++msg_id;
++try_count;
}
if (try_count > try_limit)
return kr_error(ENOENT);
worker_task_pkt_set_msgid(task, msg_id);
}
/* Note time for upstream RTT */
task->send_time = kr_now();
task->recv_time = 0; // task structure is being reused so we have to zero this out here
/* Send using given protocol */
ret = 0;
if (handle->type == UV_UDP) {
uv_buf_t buf = { (char *)pkt->wire, pkt->size };
send_req->as.send.data = task;
ret = uv_udp_send(&send_req->as.send, (uv_udp_t *)handle, &buf, 1, addr, &on_send);
} else {
uint16_t pkt_size = htons(pkt->size);
uv_buf_t buf[2] = {
{ (char *)&pkt_size, sizeof(pkt_size) },
{ (char *)pkt->wire, pkt->size }
};
send_req->as.write.data = task;
ret = uv_write(&send_req->as.write, (uv_stream_t *)handle, buf, 2, &on_write);
}
if (ret == 0) {
qr_task_ref(task); /* Pending ioreq on current task */
if (kr_fails_assert(!session->closing))
return qr_task_on_send(task, NULL, kr_error(EIO));
/* Pending '_finished' callback on current task */
qr_task_ref(task);
struct protolayer_payload payload = protolayer_payload_buffer(
(char *)pkt->wire, pkt->size, false);
payload.ttl = packet_ttl(pkt);
ret = session2_wrap(session, payload, comm, qr_task_wrap_finished, task);
if (ret >= 0) {
session2_touch(session);
if (session->outgoing) {
session2_tasklist_add(session, task);
}
if (the_worker->too_many_open &&
the_worker->stats.rconcurrent <
the_worker->rconcurrent_highwatermark - 10) {
the_worker->too_many_open = false;
}
ret = kr_ok();
} else {
req_release(task->worker, send_req);
if (ret == UV_EMFILE) {
the_worker->too_many_open = true;
the_worker->rconcurrent_highwatermark = the_worker->stats.rconcurrent;
ret = kr_error(UV_EMFILE);
}
session2_event(session, PROTOLAYER_EVENT_STATS_SEND_ERR, NULL);
}
/* Update statistics */
if (handle != task->source.handle && addr) {
if (handle->type == UV_UDP)
task->worker->stats.udp += 1;
else
task->worker->stats.tcp += 1;
if (addr->sa_family == AF_INET6)
task->worker->stats.ipv6 += 1;
else
task->worker->stats.ipv4 += 1;
/* Update outgoing query statistics */
if (session->outgoing && comm) {
session2_event(session, PROTOLAYER_EVENT_STATS_QRY_OUT, NULL);
if (comm->comm_addr->sa_family == AF_INET6)
the_worker->stats.ipv6 += 1;
else if (comm->comm_addr->sa_family == AF_INET)
the_worker->stats.ipv4 += 1;
}
return ret;
}
static void on_connect(uv_connect_t *req, int status)
static struct kr_query *task_get_last_pending_query(struct qr_task *task)
{
struct worker_ctx *worker = get_worker();
struct qr_task *task = req->data;
uv_stream_t *handle = req->handle;
if (qr_valid_handle(task, (uv_handle_t *)req->handle)) {
if (status == 0) {
struct sockaddr_storage addr;
int addr_len = sizeof(addr);
uv_tcp_getpeername((uv_tcp_t *)handle, (struct sockaddr *)&addr, &addr_len);
qr_task_send(task, (uv_handle_t *)handle, (struct sockaddr *)&addr, task->pktbuf);
} else {
qr_task_step(task, task->addrlist, NULL);
}
if (!task || task->ctx->req.rplan.pending.len == 0) {
return NULL;
}
qr_task_unref(task);
req_release(worker, (struct req *)req);
return array_tail(task->ctx->req.rplan.pending);
}
static void on_timer_close(uv_handle_t *handle)
static int send_waiting(struct session2 *session)
{
struct qr_task *task = handle->data;
req_release(task->worker, (struct req *)handle);
qr_task_unref(task);
if (session2_waitinglist_is_empty(session))
return 0;
defer_sample_state_t defer_prev_sample_state;
defer_sample_start(&defer_prev_sample_state);
int ret = 0;
do {
struct qr_task *t = session2_waitinglist_get(session);
defer_sample_task(t);
ret = qr_task_send(t, session, NULL, NULL);
defer_sample_restart();
if (ret != 0) {
struct sockaddr *peer = session2_get_peer(session);
session2_waitinglist_finalize(session, KR_STATE_FAIL);
session2_tasklist_finalize(session, KR_STATE_FAIL);
worker_del_tcp_connected(peer);
session2_close(session);
break;
}
session2_waitinglist_pop(session, true);
} while (!session2_waitinglist_is_empty(session));
defer_sample_stop(&defer_prev_sample_state, true);
return ret;
}
/* This is called when I/O timeouts */
static void on_timeout(uv_timer_t *req)
static void on_connect(uv_connect_t *req, int status)
{
struct qr_task *task = req->data;
kr_require(the_worker);
uv_stream_t *handle = req->handle;
struct session2 *session = handle->data;
struct sockaddr *peer = session2_get_peer(session);
free(req);
/* Penalize all tried nameservers with a timeout. */
struct worker_ctx *worker = task->worker;
if (task->leading && task->pending_count > 0) {
struct kr_query *qry = array_tail(task->req.rplan.pending);
struct sockaddr_in6 *addrlist = (struct sockaddr_in6 *)task->addrlist;
for (uint16_t i = 0; i < MIN(task->pending_count, task->addrlist_count); ++i) {
struct sockaddr *choice = (struct sockaddr *)(&addrlist[i]);
WITH_DEBUG {
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(choice->sa_family, kr_inaddr(choice), addr_str, sizeof(addr_str));
QRDEBUG(qry, "wrkr", "=> server: '%s' flagged as 'bad'\n", addr_str);
}
kr_nsrep_update_rtt(&qry->ns, choice, KR_NS_TIMEOUT,
worker->engine->resolver.cache_rtt, KR_NS_UPDATE);
if (kr_fails_assert(session->outgoing))
return;
if (session->closing) {
worker_del_tcp_waiting(peer);
kr_assert(session2_is_empty(session));
return;
}
const bool log_debug = kr_log_is_debug(WORKER, NULL);
/* Check if the connection is in the waiting list.
* If no, most likely this is timed out connection
* which was removed from waiting list by
* on_tcp_connect_timeout() callback. */
struct session2 *found_session = worker_find_tcp_waiting(peer);
if (!found_session || found_session != session) {
/* session isn't on the waiting list.
* it's timed out session. */
if (log_debug) {
const char *peer_str = kr_straddr(peer);
kr_log_debug(WORKER, "=> connected to '%s', but session "
"is already timed out, close\n",
peer_str ? peer_str : "");
}
kr_assert(session2_tasklist_is_empty(session));
session2_waitinglist_retry(session, false);
session2_close(session);
return;
}
/* Release timer handle */
task->timeout = NULL;
uv_close((uv_handle_t *)req, on_timer_close); /* Return borrowed task here */
/* Interrupt current pending request. */
task->timeouts += 1;
worker->stats.timeout += 1;
qr_task_step(task, NULL, NULL);
}
static bool retransmit(struct qr_task *task)
{
if (task && task->addrlist && task->addrlist_count > 0) {
uv_handle_t *subreq = ioreq_spawn(task, SOCK_DGRAM);
if (subreq) { /* Create connection for iterative query */
struct sockaddr_in6 *choice = &((struct sockaddr_in6 *)task->addrlist)[task->addrlist_turn];
if (qr_task_send(task, subreq, (struct sockaddr *)choice, task->pktbuf) == 0) {
task->addrlist_turn = (task->addrlist_turn + 1) % task->addrlist_count; /* Round robin */
return true;
}
found_session = worker_find_tcp_connected(peer);
if (found_session) {
/* session already in the connected list.
* Something went wrong, it can be due to races when kresd has tried
* to reconnect to upstream after unsuccessful attempt. */
if (log_debug) {
const char *peer_str = kr_straddr(peer);
kr_log_debug(WORKER, "=> connected to '%s', but peer "
"is already connected, close\n",
peer_str ? peer_str : "");
}
kr_assert(session2_tasklist_is_empty(session));
session2_waitinglist_retry(session, false);
session2_close(session);
return;
}
return false;
}
static void on_retransmit(uv_timer_t *req)
{
struct qr_task *task = req->data;
assert(task->finished == false);
assert(task->timeout != NULL);
if (status != 0) {
if (log_debug) {
const char *peer_str = kr_straddr(peer);
kr_log_debug(WORKER, "=> connection to '%s' failed (%s), flagged as 'bad'\n",
peer_str ? peer_str : "", uv_strerror(status));
}
worker_del_tcp_waiting(peer);
if (status != UV_ETIMEDOUT) {
/* In case of UV_ETIMEDOUT upstream has been
* already penalized in on_tcp_connect_timeout() */
session2_event(session, PROTOLAYER_EVENT_CONNECT_FAIL, NULL);
}
kr_assert(session2_tasklist_is_empty(session));
session2_close(session);
return;
}
uv_timer_stop(req);
if (!retransmit(req->data)) {
/* Not possible to spawn request, start timeout timer with remaining deadline. */
uint64_t timeout = KR_CONN_RTT_MAX - task->pending_count * KR_CONN_RETRY;
uv_timer_start(req, on_timeout, timeout, 0);
} else {
uv_timer_start(req, on_retransmit, KR_CONN_RETRY, 0);
if (log_debug) {
const char *peer_str = kr_straddr(peer);
kr_log_debug(WORKER, "=> connected to '%s'\n", peer_str ? peer_str : "");
}
session2_event(session, PROTOLAYER_EVENT_CONNECT, NULL);
session2_start_read(session);
session2_timer_stop(session);
session2_timer_start(session, PROTOLAYER_EVENT_GENERAL_TIMEOUT,
MAX_TCP_INACTIVITY, MAX_TCP_INACTIVITY);
}
static int timer_start(struct qr_task *task, uv_timer_cb cb, uint64_t timeout, uint64_t repeat)
static int transmit(struct qr_task *task)
{
assert(task->timeout == NULL);
struct worker_ctx *worker = task->worker;
uv_timer_t *timer = (uv_timer_t *)req_borrow(worker);
if (!timer) {
return kr_error(ENOMEM);
}
uv_timer_init(worker->loop, timer);
int ret = uv_timer_start(timer, cb, timeout, repeat);
if (ret != 0) {
uv_timer_stop(timer);
req_release(worker, (struct req *)timer);
return kr_error(ENOMEM);
}
timer->data = task;
qr_task_ref(task);
task->timeout = timer;
return 0;
if (!task)
return kr_error(EINVAL);
struct kr_transport* transport = task->transport;
struct sockaddr_in6 *choice = (struct sockaddr_in6 *)&transport->address;
if (!choice)
return kr_error(EINVAL);
if (task->pending_count >= MAX_PENDING)
return kr_error(EBUSY);
/* Checkout answer before sending it */
struct request_ctx *ctx = task->ctx;
int ret = kr_resolve_checkout(&ctx->req, NULL, transport, task->pktbuf);
if (ret)
return ret;
struct session2 *session = ioreq_spawn(SOCK_DGRAM, choice->sin6_family,
KR_PROTO_UDP53, NULL, 0);
if (!session)
return kr_error(EINVAL);
struct sockaddr *addr = (struct sockaddr *)choice;
struct sockaddr *peer = session2_get_peer(session);
kr_assert(peer->sa_family == AF_UNSPEC && session->outgoing);
kr_require(addr->sa_family == AF_INET || addr->sa_family == AF_INET6);
memcpy(peer, addr, kr_sockaddr_len(addr));
struct comm_info out_comm = {
.comm_addr = (struct sockaddr *)choice
};
if (the_network->enable_connect_udp && session->outgoing && !session->stream) {
uv_udp_t *udp = (uv_udp_t *)session2_get_handle(session);
int connect_tries = 3;
do {
ret = uv_udp_connect(udp, out_comm.comm_addr);
} while (ret == UV_EADDRINUSE && --connect_tries > 0);
if (ret < 0) {
kr_log_info(IO, "Failed to establish udp connection to %s: %s\n",
kr_straddr(out_comm.comm_addr), uv_strerror(ret));
}
}
ret = qr_task_send(task, session, &out_comm, task->pktbuf);
if (ret) {
session2_close(session);
return ret;
}
task->pending[task->pending_count] = session;
task->pending_count += 1;
session2_start_read(session); /* Start reading answer */
return kr_ok();
}
static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt)
{
if (!task || task->finished) {
return;
}
/* Close pending timer */
if (task->timeout) {
/* Timer was running so it holds reference to task, make sure the timer event
* never fires and release the reference on timer close instead. */
uv_timer_stop(task->timeout);
uv_close((uv_handle_t *)task->timeout, on_timer_close);
task->timeout = NULL;
}
ioreq_killall(task);
ioreq_kill_pending(task);
/* Clear from outgoing table. */
if (!task->leading)
return;
char key[KR_RRKEY_LEN];
int ret = subreq_key(key, task->pktbuf);
if (ret > 0) {
assert(map_get(&task->worker->outgoing, key) == task);
map_del(&task->worker->outgoing, key);
char key[SUBREQ_KEY_LEN];
const int klen = subreq_key(key, task->pktbuf);
if (klen > 0) {
void *val_deleted;
int ret = trie_del(the_worker->subreq_out, key, klen, &val_deleted);
kr_assert(ret == KNOT_EOK && val_deleted == task);
}
/* Notify waiting tasks. */
struct kr_query *leader_qry = array_tail(task->req.rplan.pending);
for (size_t i = task->waiting.len; i --> 0;) {
struct qr_task *follower = task->waiting.at[i];
/* Reuse MSGID and 0x20 secret */
if (follower->req.rplan.pending.len > 0) {
struct kr_query *qry = array_tail(follower->req.rplan.pending);
qry->id = leader_qry->id;
qry->secret = leader_qry->secret;
leader_qry->secret = 0; /* Next will be already decoded */
if (task->waiting.len > 0) {
struct kr_query *leader_qry = array_tail(task->ctx->req.rplan.pending);
defer_sample_state_t defer_prev_sample_state;
defer_sample_start(&defer_prev_sample_state);
for (size_t i = task->waiting.len; i > 0; i--) {
struct qr_task *follower = task->waiting.at[i - 1];
/* Reuse MSGID and 0x20 secret */
if (follower->ctx->req.rplan.pending.len > 0) {
struct kr_query *qry = array_tail(follower->ctx->req.rplan.pending);
qry->id = leader_qry->id;
qry->secret = leader_qry->secret;
// Note that this transport may not be present in `leader_qry`'s server selection
follower->transport = task->transport;
if(follower->transport) {
follower->transport->deduplicated = true;
}
leader_qry->secret = 0; /* Next will be already decoded */
}
qr_task_step(follower, packet_source, pkt);
qr_task_unref(follower);
defer_sample_restart();
}
qr_task_step(follower, packet_source, pkt);
qr_task_unref(follower);
defer_sample_stop(&defer_prev_sample_state, true);
task->waiting.len = 0;
}
task->waiting.len = 0;
task->leading = false;
}
static void subreq_lead(struct qr_task *task)
{
assert(task);
char key[KR_RRKEY_LEN];
if (subreq_key(key, task->pktbuf) > 0) {
assert(map_contains(&task->worker->outgoing, key) == false);
map_set(&task->worker->outgoing, key, task);
task->leading = true;
}
if (kr_fails_assert(task))
return;
char key[SUBREQ_KEY_LEN];
const int klen = subreq_key(key, task->pktbuf);
if (klen < 0)
return;
struct qr_task **tvp = (struct qr_task **)
trie_get_ins(the_worker->subreq_out, key, klen);
if (unlikely(!tvp))
return; /*ENOMEM*/
if (kr_fails_assert(*tvp == NULL))
return;
*tvp = task;
task->leading = true;
}
static bool subreq_enqueue(struct qr_task *task)
{
assert(task);
char key[KR_RRKEY_LEN];
if (subreq_key(key, task->pktbuf) > 0) {
struct qr_task *leader = map_get(&task->worker->outgoing, key);
if (leader) {
/* Enqueue itself to leader for this subrequest. */
int ret = array_reserve_mm(leader->waiting, leader->waiting.len + 1, kr_memreserve, &leader->req.pool);
if (ret == 0) {
array_push(leader->waiting, task);
qr_task_ref(task);
return true;
}
}
}
return false;
if (kr_fails_assert(task))
return false;
char key[SUBREQ_KEY_LEN];
const int klen = subreq_key(key, task->pktbuf);
if (klen < 0)
return false;
struct qr_task **leader = (struct qr_task **)
trie_get_try(the_worker->subreq_out, key, klen);
if (!leader /*ENOMEM*/ || !*leader)
return false;
/* Enqueue itself to leader for this subrequest. */
int ret = array_push_mm((*leader)->waiting, task,
kr_memreserve, &(*leader)->ctx->req.pool);
if (unlikely(ret < 0)) /*ENOMEM*/
return false;
qr_task_ref(task);
return true;
}
static int qr_task_finalize(struct qr_task *task, int state)
{
assert(task && task->leading == false);
kr_resolve_finish(&task->req, state);
kr_require(task && task->leading == false);
if (task->finished) {
return kr_ok();
}
defer_sample_task(task);
struct request_ctx *ctx = task->ctx;
struct session2 *source_session = ctx->source.session;
kr_resolve_finish(&ctx->req, state);
task->finished = true;
if (source_session == NULL) {
(void) qr_task_on_send(task, NULL, kr_error(EIO));
return state == KR_STATE_DONE ? kr_ok() : kr_error(EIO);
}
/* meant to be dropped */
if (unlikely(ctx->req.answer == NULL || ctx->req.options.NO_ANSWER)) {
/* For NO_ANSWER, a well-behaved layer should set the state to FAIL */
kr_assert(!ctx->req.options.NO_ANSWER || (ctx->req.state & KR_STATE_FAIL));
(void) qr_task_on_send(task, NULL, kr_ok());
return kr_ok();
}
if (source_session->closing ||
ctx->source.addr.ip.sa_family == AF_UNSPEC)
return kr_error(EINVAL);
/* Reference task as the callback handler can close it */
qr_task_ref(task);
/* Send back answer */
(void) qr_task_send(task, task->source.handle, (struct sockaddr *)&task->source.addr, task->req.answer);
return state == KNOT_STATE_DONE ? 0 : kr_error(EIO);
struct comm_info out_comm = {
.src_addr = &ctx->source.addr.ip,
.dst_addr = &ctx->source.dst_addr.ip,
.comm_addr = &ctx->source.comm_addr.ip,
.xdp = ctx->source.xdp
};
if (ctx->source.xdp) {
memcpy(out_comm.eth_from, ctx->source.eth_from, sizeof(out_comm.eth_from));
memcpy(out_comm.eth_to, ctx->source.eth_to, sizeof(out_comm.eth_to));
}
int ret = qr_task_send(task, source_session, &out_comm, ctx->req.answer);
if (ret != kr_ok()) {
(void) qr_task_on_send(task, NULL, kr_error(EIO));
/* Since source session is erroneous detach all tasks. */
while (!session2_tasklist_is_empty(source_session)) {
struct qr_task *t = session2_tasklist_del_first(source_session, false);
struct request_ctx *c = t->ctx;
kr_assert(c->source.session == source_session);
c->source.session = NULL;
/* Don't finalize them as there can be other tasks
* waiting for answer to this particular task.
* (ie. task->leading is true) */
worker_task_unref(t);
}
session2_close(source_session);
}
if (source_session->stream && !source_session->closing) {
struct pl_dns_stream_sess_data *stream =
protolayer_sess_data_get_proto(source_session, PROTOLAYER_TYPE_DNS_MULTI_STREAM);
if (!stream)
stream = protolayer_sess_data_get_proto(source_session, PROTOLAYER_TYPE_DNS_UNSIZED_STREAM);
if (!stream)
stream = protolayer_sess_data_get_proto(source_session, PROTOLAYER_TYPE_DNS_SINGLE_STREAM);
if (stream && stream->half_closed) {
session2_force_close(source_session);
}
}
qr_task_unref(task);
if (ret != kr_ok() || state != KR_STATE_DONE)
return kr_error(EIO);
return kr_ok();
}
static int udp_task_step(struct qr_task *task,
const struct sockaddr *packet_source, knot_pkt_t *packet)
{
/* If there is already outgoing query, enqueue to it. */
if (subreq_enqueue(task)) {
return kr_ok(); /* Will be notified when outgoing query finishes. */
}
/* Start transmitting */
int err = transmit(task);
if (err) {
subreq_finalize(task, packet_source, packet);
return qr_task_finalize(task, KR_STATE_FAIL);
}
/* Announce and start subrequest.
* @note Only UDP can lead I/O as it doesn't touch 'task->pktbuf' for reassembly.
*/
subreq_lead(task);
return kr_ok();
}
static int tcp_task_waiting_connection(struct session2 *session, struct qr_task *task)
{
if (kr_fails_assert(session->outgoing && !session->closing))
return kr_error(EINVAL);
/* Add task to the end of list of waiting tasks.
* It will be notified in on_connect() or qr_task_on_send(). */
int ret = session2_waitinglist_push(session, task);
if (ret < 0) {
return kr_error(EINVAL);
}
return kr_ok();
}
static int tcp_task_existing_connection(struct session2 *session, struct qr_task *task)
{
if (kr_fails_assert(session->outgoing && !session->closing))
return kr_error(EINVAL);
/* If there are any unsent queries, send it first. */
int ret = send_waiting(session);
if (ret != 0) {
return kr_error(EINVAL);
}
/* No unsent queries at that point. */
if (session2_tasklist_get_len(session) >= the_worker->tcp_pipeline_max) {
/* Too many outstanding queries, answer with SERVFAIL, */
return kr_error(EINVAL);
}
/* Send query to upstream. */
ret = qr_task_send(task, session, NULL, NULL);
if (ret != 0) {
/* Error, finalize task with SERVFAIL and
* close connection to upstream. */
session2_tasklist_finalize(session, KR_STATE_FAIL);
worker_del_tcp_connected(session2_get_peer(session));
session2_close(session);
return kr_error(EINVAL);
}
return kr_ok();
}
static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr *addr)
{
/* Check if there must be TLS */
tls_client_param_t *tls_entry = tls_client_param_get(
the_network->tls_client_params, addr);
uv_connect_t *conn = malloc(sizeof(uv_connect_t));
if (!conn) {
return kr_error(EINVAL);
}
struct session2 *session;
bool has_tls = tls_entry;
if (has_tls) {
struct protolayer_data_param param = {
.protocol = PROTOLAYER_TYPE_TLS,
.param = tls_entry
};
session = ioreq_spawn(SOCK_STREAM, addr->sa_family,
KR_PROTO_DOT, &param, 1);
} else {
session = ioreq_spawn(SOCK_STREAM, addr->sa_family,
KR_PROTO_TCP53, NULL, 0);
}
if (!session) {
free(conn);
return kr_error(EINVAL);
}
if (kr_fails_assert(session->secure == has_tls)) {
free(conn);
return kr_error(EINVAL);
}
/* Add address to the waiting list.
* Now it "is waiting to be connected to." */
int ret = worker_add_tcp_waiting(addr, session);
if (ret < 0) {
free(conn);
session2_close(session);
return kr_error(EINVAL);
}
conn->data = session;
/* Store peer address for the session. */
struct sockaddr *peer = session2_get_peer(session);
memcpy(peer, addr, kr_sockaddr_len(addr));
/* Start watchdog to catch eventual connection timeout. */
ret = session2_timer_start(session, PROTOLAYER_EVENT_CONNECT_TIMEOUT,
KR_CONN_RTT_MAX, 0);
if (ret != 0) {
worker_del_tcp_waiting(addr);
free(conn);
session2_close(session);
return kr_error(EINVAL);
}
struct kr_query *qry = task_get_last_pending_query(task);
if (kr_log_is_debug_qry(WORKER, qry)) {
const char *peer_str = kr_straddr(peer);
VERBOSE_MSG(qry, "=> connecting to: '%s'\n", peer_str ? peer_str : "");
}
/* Start connection process to upstream. */
ret = uv_tcp_connect(conn, (uv_tcp_t *)session2_get_handle(session),
addr , on_connect);
if (ret != 0) {
session2_timer_stop(session);
worker_del_tcp_waiting(addr);
free(conn);
session2_close(session);
qry->server_selection.error(qry, task->transport, KR_SELECTION_TCP_CONNECT_FAILED);
return kr_error(EAGAIN);
}
/* Add task to the end of list of waiting tasks.
* Will be notified either in on_connect() or in qr_task_on_send(). */
ret = session2_waitinglist_push(session, task);
if (ret < 0) {
session2_timer_stop(session);
worker_del_tcp_waiting(addr);
free(conn);
session2_close(session);
return kr_error(EINVAL);
}
return kr_ok();
}
static int tcp_task_step(struct qr_task *task,
const struct sockaddr *packet_source, knot_pkt_t *packet)
{
if (kr_fails_assert(task->pending_count == 0)) {
subreq_finalize(task, packet_source, packet);
return qr_task_finalize(task, KR_STATE_FAIL);
}
/* target */
const struct sockaddr *addr = &task->transport->address.ip;
if (addr->sa_family == AF_UNSPEC) {
/* Target isn't defined. Finalize task with SERVFAIL.
* Although task->pending_count is zero, there are can be followers,
* so we need to call subreq_finalize() to handle them properly. */
subreq_finalize(task, packet_source, packet);
return qr_task_finalize(task, KR_STATE_FAIL);
}
/* Checkout task before connecting */
struct request_ctx *ctx = task->ctx;
if (kr_resolve_checkout(&ctx->req, NULL, task->transport, task->pktbuf) != 0) {
subreq_finalize(task, packet_source, packet);
return qr_task_finalize(task, KR_STATE_FAIL);
}
int ret;
struct session2* session = NULL;
if ((session = worker_find_tcp_waiting(addr)) != NULL) {
/* Connection is in the list of waiting connections.
* It means that connection establishing is coming right now. */
ret = tcp_task_waiting_connection(session, task);
} else if ((session = worker_find_tcp_connected(addr)) != NULL) {
/* Connection has been already established. */
ret = tcp_task_existing_connection(session, task);
} else {
/* Make connection. */
ret = tcp_task_make_connection(task, addr);
}
if (ret != kr_ok()) {
subreq_finalize(task, addr, packet);
if (ret == kr_error(EAGAIN)) {
ret = qr_task_step(task, addr, NULL);
} else {
ret = qr_task_finalize(task, KR_STATE_FAIL);
}
}
return ret;
}
static int qr_task_step(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *packet)
static int qr_task_step(struct qr_task *task,
const struct sockaddr *packet_source, knot_pkt_t *packet)
{
defer_sample_task(task);
/* No more steps after we're finished. */
if (!task || task->finished) {
return kr_error(ESTALE);
}
/* Close pending I/O requests */
subreq_finalize(task, packet_source, packet);
if ((kr_now() - task->creation_time) >= KR_RESOLVE_TIME_LIMIT) {
struct kr_request *req = worker_task_request(task);
if (!kr_fails_assert(req))
kr_query_inform_timeout(req, req->current_query);
return qr_task_finalize(task, KR_STATE_FAIL);
}
/* Consume input and produce next query */
int sock_type = -1;
task->addrlist = NULL;
task->addrlist_count = 0;
task->addrlist_turn = 0;
int state = kr_resolve_consume(&task->req, packet_source, packet);
while (state == KNOT_STATE_PRODUCE) {
state = kr_resolve_produce(&task->req, &task->addrlist, &sock_type, task->pktbuf);
if (unlikely(++task->iter_count > KR_ITER_LIMIT || task->timeouts >= KR_TIMEOUT_LIMIT)) {
return qr_task_finalize(task, KNOT_STATE_FAIL);
struct request_ctx *ctx = task->ctx;
if (kr_fails_assert(ctx))
return qr_task_finalize(task, KR_STATE_FAIL);
struct kr_request *req = &ctx->req;
if (the_worker->too_many_open) {
/* */
struct kr_rplan *rplan = &req->rplan;
if (the_worker->stats.rconcurrent <
the_worker->rconcurrent_highwatermark - 10) {
the_worker->too_many_open = false;
} else {
if (packet && kr_rplan_empty(rplan)) {
/* new query; TODO - make this detection more obvious */
kr_resolve_consume(req, &task->transport, packet);
}
return qr_task_finalize(task, KR_STATE_FAIL);
}
}
/* We're done, no more iterations needed */
if (state & (KNOT_STATE_DONE|KNOT_STATE_FAIL)) {
return qr_task_finalize(task, state);
} else if (!task->addrlist || sock_type < 0) {
return qr_task_step(task, NULL, NULL);
// Report network RTT back to server selection
if (packet && task->send_time && task->recv_time) {
struct kr_query *qry = array_tail(req->rplan.pending);
qry->server_selection.update_rtt(qry, task->transport, task->recv_time - task->send_time);
}
/* Count available address choices */
struct sockaddr_in6 *choice = (struct sockaddr_in6 *)task->addrlist;
for (size_t i = 0; i < KR_NSREP_MAXADDR && choice->sin6_family != AF_UNSPEC; ++i) {
task->addrlist_count += 1;
choice += 1;
}
int state = kr_resolve_consume(req, &task->transport, packet);
/* Start fast retransmit with UDP, otherwise connect. */
int ret = 0;
if (sock_type == SOCK_DGRAM) {
/* If there is already outgoing query, enqueue to it. */
if (subreq_enqueue(task)) {
return kr_ok(); /* Will be notified when outgoing query finishes. */
}
/* Check current query NSLIST */
struct kr_query *qry = array_tail(task->req.rplan.pending);
/* Start transmitting */
if (retransmit(task)) {
assert(qry != NULL);
/* Retransmit at default interval, or more frequently if the mean
* RTT of the server is better. If the server is glued, use default rate. */
size_t timeout = qry->ns.score;
if (timeout > KR_NS_GLUED) {
/* We don't have information about variance in RTT, expect +10ms */
timeout = MIN(qry->ns.score + 10, KR_CONN_RETRY);
} else {
timeout = KR_CONN_RETRY;
task->transport = NULL;
while (state == KR_STATE_PRODUCE) {
state = kr_resolve_produce(req, &task->transport, task->pktbuf);
if (unlikely(++task->iter_count > KR_ITER_LIMIT ||
task->timeouts >= KR_TIMEOUT_LIMIT)) {
struct kr_rplan *rplan = &req->rplan;
struct kr_query *last = kr_rplan_last(rplan);
if (task->iter_count > KR_ITER_LIMIT) {
char *msg = "cancelling query due to exceeded iteration count limit";
VERBOSE_MSG(last, "%s of %d\n", msg, KR_ITER_LIMIT);
kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER,
"OGHD: exceeded iteration count limit");
}
ret = timer_start(task, on_retransmit, timeout, 0);
} else {
return qr_task_step(task, NULL, NULL);
}
/* Announce and start subrequest.
* @note Only UDP can lead I/O as it doesn't touch 'task->pktbuf' for reassembly.
*/
subreq_lead(task);
} else {
uv_connect_t *conn = (uv_connect_t *)req_borrow(task->worker);
if (!conn) {
return qr_task_step(task, NULL, NULL);
}
uv_handle_t *client = ioreq_spawn(task, sock_type);
if (!client) {
req_release(task->worker, (struct req *)conn);
return qr_task_step(task, NULL, NULL);
}
conn->data = task;
if (uv_tcp_connect(conn, (uv_tcp_t *)client, packet_source?packet_source:task->addrlist, on_connect) != 0) {
req_release(task->worker, (struct req *)conn);
return qr_task_step(task, NULL, NULL);
if (task->timeouts >= KR_TIMEOUT_LIMIT) {
char *msg = "cancelling query due to exceeded timeout retries limit";
VERBOSE_MSG(last, "%s of %d\n", msg, KR_TIMEOUT_LIMIT);
kr_request_set_extended_error(req, KNOT_EDNS_EDE_NREACH_AUTH, "QLPL");
}
return qr_task_finalize(task, KR_STATE_FAIL);
}
qr_task_ref(task); /* Connect request borrows task */
ret = timer_start(task, on_timeout, KR_CONN_RTT_MAX, 0);
}
/* Start next step with timeout, fatal if can't start a timer. */
if (ret != 0) {
subreq_finalize(task, packet_source, packet);
return qr_task_finalize(task, KNOT_STATE_FAIL);
/* We're done, no more iterations needed */
if (state & (KR_STATE_DONE|KR_STATE_FAIL)) {
return qr_task_finalize(task, state);
} else if (!task->transport || !task->transport->protocol) {
return qr_task_step(task, NULL, NULL);
}
switch (task->transport->protocol)
{
case KR_TRANSPORT_UDP:
return udp_task_step(task, packet_source, packet);
case KR_TRANSPORT_TCP: // fall through
case KR_TRANSPORT_TLS:
return tcp_task_step(task, packet_source, packet);
default:
kr_assert(!EINVAL);
return kr_error(EINVAL);
}
return 0;
}
static int parse_packet(knot_pkt_t *query)
static int worker_submit(struct session2 *session, struct comm_info *comm, knot_pkt_t *pkt)
{
if (!query){
if (!session || !pkt || session->closing)
return kr_error(EINVAL);
const bool is_query = pkt->size > KNOT_WIRE_OFFSET_FLAGS1
&& knot_wire_get_qr(pkt->wire) == 0;
const bool is_outgoing = session->outgoing;
int ret = 0;
if (is_query == is_outgoing)
ret = KNOT_ENOENT;
// For responses from upstream, try to find associated task and query.
// In case of errors, at least try to guess.
struct qr_task *task = NULL;
bool task_matched_id = false;
if (is_outgoing && pkt->size >= 2) {
const uint16_t id = knot_wire_get_id(pkt->wire);
task = session2_tasklist_del_msgid(session, id);
task_matched_id = task != NULL;
if (task_matched_id) // Note receive time for RTT calculation
task->recv_time = kr_now();
if (!task_matched_id) {
ret = KNOT_ENOENT;
VERBOSE_MSG(NULL, "=> DNS message with mismatching ID %d\n",
(int)id);
}
}
if (!task && is_outgoing && session->stream) {
// Source address of the reply got somewhat validated,
// so we try to at least guess which query, for error reporting.
task = session2_tasklist_get_first(session);
}
struct kr_query *qry = NULL;
if (task)
qry = array_tail(task->ctx->req.rplan.pending);
/* Parse query packet. */
int ret = knot_pkt_parse(query, 0);
if (ret != KNOT_EOK) {
return kr_error(EPROTO); /* Ignore malformed query. */
// Parse the packet, unless it's useless anyway.
if (ret == 0) {
ret = knot_pkt_parse(pkt, 0);
if (ret == KNOT_ETRAIL && is_outgoing
&& !kr_fails_assert(pkt->parsed < pkt->size)) {
// We deal with this later, so that RCODE takes priority.
ret = 0;
}
if (ret && kr_log_is_debug_qry(WORKER, qry)) {
VERBOSE_MSG(qry, "=> DNS message failed to parse, %s\n",
knot_strerror(ret));
}
}
/* Check if at least header is parsed. */
if (query->parsed < query->size) {
return kr_error(EMSGSIZE);
/* Badly formed query when using DoH leads to a Bad Request */
if (session->custom_emalf_handling && !is_outgoing && ret) {
session2_event(session, PROTOLAYER_EVENT_MALFORMED, NULL);
return ret;
}
return kr_ok();
}
const struct sockaddr *addr = comm ? comm->src_addr : NULL;
int worker_submit(struct worker_ctx *worker, uv_handle_t *handle, knot_pkt_t *msg, const struct sockaddr* addr)
{
if (!worker || !handle) {
return kr_error(EINVAL);
/* Ignore badly formed queries. */
if (ret) {
if (is_outgoing && qry) // unusuable response from somewhat validated IP
qry->server_selection.error(qry, task->transport, KR_SELECTION_MALFORMED);
if (!is_outgoing)
the_worker->stats.dropped += 1;
if (task_matched_id) // notify task that answer won't be coming anymore
qr_task_step(task, addr, NULL);
return kr_error(EILSEQ);
}
struct session *session = handle->data;
assert(session);
/* Parse packet */
int ret = parse_packet(msg);
/* Start new task on listening sockets,
* or resume if this is subrequest */
if (!is_outgoing) { /* request from a client */
struct request_ctx *ctx =
request_create(session, comm, knot_wire_get_id(pkt->wire));
if (!ctx)
return kr_error(ENOMEM);
/* Start new task on listening sockets, or resume if this is subrequest */
struct qr_task *task = NULL;
if (!session->outgoing) {
/* Ignore badly formed queries or responses. */
if (!msg || ret != 0 || knot_wire_get_qr(msg->wire)) {
if (msg) worker->stats.dropped += 1;
return kr_error(EINVAL); /* Ignore. */
ret = request_start(ctx, pkt);
if (ret != 0) {
request_free(ctx);
return kr_error(ENOMEM);
}
task = qr_task_create(worker, handle, addr);
task = qr_task_create(ctx);
if (!task) {
request_free(ctx);
return kr_error(ENOMEM);
}
ret = qr_task_start(task, msg);
if (ret != 0) {
qr_task_free(task);
if (session->stream && qr_task_register(task, session)) {
return kr_error(ENOMEM);
}
} else {
task = session->tasks.len > 0 ? array_tail(session->tasks) : NULL;
} else { /* response from upstream */
if (task == NULL) {
return kr_error(ENOENT);
}
if (kr_fails_assert(!session->closing))
return kr_error(EINVAL);
}
if (kr_fails_assert(!session->closing))
return kr_error(EINVAL);
/* Packet was successfully parsed.
* Task was created (found). */
session2_touch(session);
/* Consume input and produce next message */
return qr_task_step(task, addr, msg);
return qr_task_step(task, addr, pkt);
}
/* Return DNS/TCP message size. */
static int msg_size(const uint8_t *msg)
static int trie_add_tcp_session(trie_t *trie, const struct sockaddr *addr,
struct session2 *session)
{
return wire_read_u16(msg);
if (kr_fails_assert(trie && addr))
return kr_error(EINVAL);
struct kr_sockaddr_key_storage key;
ssize_t keylen = kr_sockaddr_key(&key, addr);
if (keylen < 0)
return keylen;
trie_val_t *val = trie_get_ins(trie, key.bytes, keylen);
if (*val != NULL)
return kr_error(EEXIST);
*val = session;
return kr_ok();
}
/* If buffering, close last task as it isn't live yet. */
static void discard_buffered(struct session *session)
static int trie_del_tcp_session(trie_t *trie, const struct sockaddr *addr)
{
if (session->buffering) {
qr_task_free(session->buffering);
session->buffering = NULL;
}
if (kr_fails_assert(trie && addr))
return kr_error(EINVAL);
struct kr_sockaddr_key_storage key;
ssize_t keylen = kr_sockaddr_key(&key, addr);
if (keylen < 0)
return keylen;
int ret = trie_del(trie, key.bytes, keylen, NULL);
return ret ? kr_error(ENOENT) : kr_ok();
}
int worker_end_tcp(struct worker_ctx *worker, uv_handle_t *handle)
static struct session2 *trie_find_tcp_session(trie_t *trie,
const struct sockaddr *addr)
{
if (!worker || !handle) {
return kr_error(EINVAL);
if (kr_fails_assert(trie && addr))
return NULL;
struct kr_sockaddr_key_storage key;
ssize_t keylen = kr_sockaddr_key(&key, addr);
if (keylen < 0)
return NULL;
trie_val_t *val = trie_get_try(trie, key.bytes, keylen);
return val ? *val : NULL;
}
static int worker_add_tcp_connected(const struct sockaddr* addr, struct session2 *session)
{
return trie_add_tcp_session(the_worker->tcp_connected, addr, session);
}
static int worker_del_tcp_connected(const struct sockaddr* addr)
{
return trie_del_tcp_session(the_worker->tcp_connected, addr);
}
static struct session2* worker_find_tcp_connected(const struct sockaddr* addr)
{
return trie_find_tcp_session(the_worker->tcp_connected, addr);
}
static int worker_add_tcp_waiting(const struct sockaddr* addr,
struct session2 *session)
{
return trie_add_tcp_session(the_worker->tcp_waiting, addr, session);
}
static int worker_del_tcp_waiting(const struct sockaddr* addr)
{
return trie_del_tcp_session(the_worker->tcp_waiting, addr);
}
static struct session2* worker_find_tcp_waiting(const struct sockaddr* addr)
{
return trie_find_tcp_session(the_worker->tcp_waiting, addr);
}
knot_pkt_t *worker_resolve_mk_pkt_dname(knot_dname_t *qname, uint16_t qtype, uint16_t qclass,
const struct kr_qflags *options)
{
knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_EDNS_MAX_UDP_PAYLOAD, NULL);
if (!pkt)
return NULL;
knot_pkt_put_question(pkt, qname, qclass, qtype);
knot_wire_set_rd(pkt->wire);
knot_wire_set_ad(pkt->wire);
/* Add OPT RR, including wire format so modules can see both representations.
* knot_pkt_put() copies the outside; we need to duplicate the inside manually. */
knot_rrset_t *opt = knot_rrset_copy(the_resolver->downstream_opt_rr, NULL);
if (!opt) {
knot_pkt_free(pkt);
return NULL;
}
/* If this is subrequest, notify parent task with empty input
* because in this case session doesn't own tasks, it has just
* borrowed the task from parent session. */
struct session *session = handle->data;
if (session->outgoing) {
worker_submit(worker, (uv_handle_t *)handle, NULL, NULL);
if (options->DNSSEC_WANT) {
knot_edns_set_do(opt);
}
knot_pkt_begin(pkt, KNOT_ADDITIONAL);
int ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, opt, KNOT_PF_FREE);
if (ret == KNOT_EOK) {
free(opt); /* inside is owned by pkt now */
} else {
discard_buffered(session);
knot_rrset_free(opt, NULL);
knot_pkt_free(pkt);
return NULL;
}
return 0;
if (options->DNSSEC_CD) {
knot_wire_set_cd(pkt->wire);
}
return pkt;
}
int worker_process_tcp(struct worker_ctx *worker, uv_stream_t *handle, const uint8_t *msg, ssize_t len)
knot_pkt_t *worker_resolve_mk_pkt(const char *qname_str, uint16_t qtype, uint16_t qclass,
const struct kr_qflags *options)
{
if (!worker || !handle) {
uint8_t qname[KNOT_DNAME_MAXLEN];
if (!knot_dname_from_str(qname, qname_str, sizeof(qname)))
return NULL;
return worker_resolve_mk_pkt_dname(qname, qtype, qclass, options);
}
struct qr_task *worker_resolve_start(knot_pkt_t *query, struct kr_qflags options)
{
if (kr_fails_assert(the_worker && query))
return NULL;
struct request_ctx *ctx = request_create(NULL, NULL, the_worker->next_request_uid);
if (!ctx)
return NULL;
/* Create task */
struct qr_task *task = qr_task_create(ctx);
if (!task) {
request_free(ctx);
return NULL;
}
/* Start task */
int ret = request_start(ctx, query);
if (ret != 0) {
/* task is attached to request context,
* so dereference (and deallocate) it first */
ctx->task = NULL;
qr_task_unref(task);
request_free(ctx);
return NULL;
}
the_worker->next_request_uid += 1;
if (the_worker->next_request_uid == 0)
the_worker->next_request_uid = UINT16_MAX + 1;
/* Set options late, as qr_task_start() -> kr_resolve_begin() rewrite it. */
kr_qflags_set(&task->ctx->req.options, options);
return task;
}
int worker_resolve_exec(struct qr_task *task, knot_pkt_t *query)
{
if (!task)
return kr_error(EINVAL);
return qr_task_step(task, NULL, query);
}
int worker_task_numrefs(const struct qr_task *task)
{
return task->refs;
}
struct kr_request *worker_task_request(struct qr_task *task)
{
if (!task || !task->ctx)
return NULL;
return &task->ctx->req;
}
int worker_task_finalize(struct qr_task *task, int state)
{
return qr_task_finalize(task, state);
}
int worker_task_step(struct qr_task *task, const struct sockaddr *packet_source,
knot_pkt_t *packet)
{
return qr_task_step(task, packet_source, packet);
}
void worker_task_ref(struct qr_task *task)
{
qr_task_ref(task);
}
void worker_task_unref(struct qr_task *task)
{
qr_task_unref(task);
}
void worker_task_timeout_inc(struct qr_task *task)
{
task->timeouts += 1;
}
knot_pkt_t *worker_task_get_pktbuf(const struct qr_task *task)
{
return task->pktbuf;
}
struct kr_transport *worker_task_get_transport(struct qr_task *task)
{
return task->transport;
}
struct session2 *worker_request_get_source_session(const struct kr_request *req)
{
static_assert(offsetof(struct request_ctx, req) == 0,
"Bad struct request_ctx definition.");
return ((struct request_ctx *)req)->source.session;
}
uint16_t worker_task_pkt_get_msgid(struct qr_task *task)
{
knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
uint16_t msg_id = knot_wire_get_id(pktbuf->wire);
return msg_id;
}
void worker_task_pkt_set_msgid(struct qr_task *task, uint16_t msgid)
{
knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
knot_wire_set_id(pktbuf->wire, msgid);
struct kr_query *q = task_get_last_pending_query(task);
if (q)
q->id = msgid;
}
uint64_t worker_task_creation_time(struct qr_task *task)
{
return task->creation_time;
}
void worker_task_subreq_finalize(struct qr_task *task)
{
subreq_finalize(task, NULL, NULL);
}
bool worker_task_finished(struct qr_task *task)
{
return task->finished;
}
/** Reserve worker buffers. We assume worker's been zeroed. */
static int worker_reserve(void)
{
the_worker->tcp_connected = trie_create(NULL);
the_worker->tcp_waiting = trie_create(NULL);
the_worker->subreq_out = trie_create(NULL);
mm_ctx_mempool(&the_worker->pkt_pool, 4 * sizeof(knot_pkt_t));
return kr_ok();
}
void worker_deinit(void)
{
if (kr_fails_assert(the_worker))
return;
trie_free(the_worker->tcp_connected);
trie_free(the_worker->tcp_waiting);
trie_free(the_worker->subreq_out);
the_worker->subreq_out = NULL;
for (int i = 0; i < the_worker->doh_qry_headers.len; i++)
free((void *)the_worker->doh_qry_headers.at[i]);
array_clear(the_worker->doh_qry_headers);
mp_delete(the_worker->pkt_pool.ctx);
the_worker->pkt_pool.ctx = NULL;
the_worker = NULL;
}
static inline knot_pkt_t *produce_packet(uint8_t *buf, size_t buf_len)
{
return knot_pkt_new(buf, buf_len, &the_worker->pkt_pool);
}
static enum protolayer_event_cb_result pl_dns_dgram_event_unwrap(
enum protolayer_event_type event, void **baton,
struct session2 *session, void *sess_data)
{
if (event != PROTOLAYER_EVENT_GENERAL_TIMEOUT)
return PROTOLAYER_EVENT_PROPAGATE;
if (session2_tasklist_get_len(session) != 1 ||
!session2_waitinglist_is_empty(session))
return PROTOLAYER_EVENT_PROPAGATE;
session2_timer_stop(session);
struct qr_task *task = session2_tasklist_get_first(session);
if (!task)
return PROTOLAYER_EVENT_PROPAGATE;
if (task->leading && task->pending_count > 0) {
struct kr_query *qry = array_tail(task->ctx->req.rplan.pending);
qry->server_selection.error(qry, task->transport, KR_SELECTION_QUERY_TIMEOUT);
}
/* Connection error or forced disconnect */
struct session *session = handle->data;
if (len <= 0 || !msg) {
/* If we have pending tasks, we must dissociate them from the
* connection so they don't try to access closed and freed handle.
* @warning Do not modify task if this is outgoing request as it is shared with originator.
*/
if (!session->outgoing) {
for (size_t i = 0; i < session->tasks.len; ++i) {
struct qr_task *task = session->tasks.at[i];
task->session = NULL;
task->source.handle = NULL;
}
session->tasks.len = 0;
}
return kr_error(ECONNRESET);
task->timeouts += 1;
the_worker->stats.timeout += 1;
qr_task_step(task, NULL, NULL);
return PROTOLAYER_EVENT_PROPAGATE;
}
static size_t pl_dns_dgram_wire_buf_overhead(bool outgoing)
{
if (outgoing) {
if (the_resolver->upstream_opt_rr)
return knot_edns_get_payload(the_resolver->upstream_opt_rr);
} else {
if (the_resolver->downstream_opt_rr)
return knot_edns_get_payload(the_resolver->downstream_opt_rr);
}
return KNOT_WIRE_MAX_PKTSIZE;
}
int submitted = 0;
ssize_t nbytes = 0;
struct qr_task *task = session->buffering;
static enum protolayer_iter_cb_result pl_dns_dgram_unwrap(
void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx)
{
struct session2 *session = ctx->session;
if (ctx->payload.type == PROTOLAYER_PAYLOAD_IOVEC) {
int ret = kr_ok();
for (int i = 0; i < ctx->payload.iovec.cnt; i++) {
const struct iovec *iov = &ctx->payload.iovec.iov[i];
if (iov->iov_len > MAX_DGRAM_LEN) {
session2_penalize(session);
ret = kr_error(EFBIG);
break;
}
/* If this is a new query, create a new task that we can use
* to buffer incoming message until it's complete. */
if (!session->outgoing) {
if (!task) {
task = qr_task_create(worker, (uv_handle_t *)handle, NULL);
if (!task) {
return kr_error(ENOMEM);
knot_pkt_t *pkt = produce_packet(
iov->iov_base, iov->iov_len);
if (!pkt) {
ret = KNOT_EMALF;
break;
}
session->buffering = task;
ret = worker_submit(session, ctx->comm, pkt);
if (ret)
break;
}
} else {
assert(session->tasks.len > 0);
task = array_tail(session->tasks);
}
/* At this point session must have either created new task or it's already assigned. */
assert(task);
assert(len > 0);
/* Start reading DNS/TCP message length */
knot_pkt_t *pkt_buf = task->pktbuf;
if (task->bytes_remaining == 0 && pkt_buf->size == 0) {
knot_pkt_clear(pkt_buf);
/* Read only one byte as TCP fragment may end at a 1B boundary
* which would lead to OOB read or improper reassembly length. */
pkt_buf->size = 1;
pkt_buf->wire[0] = msg[0];
len -= 1;
msg += 1;
if (len == 0) {
return 0;
mp_flush(the_worker->pkt_pool.ctx);
return protolayer_break(ctx, ret);
} else if (ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER) {
if (ctx->payload.buffer.len > MAX_DGRAM_LEN) {
session2_penalize(session);
return protolayer_break(ctx, kr_error(EFBIG));
}
knot_pkt_t *pkt = produce_packet(
ctx->payload.buffer.buf,
ctx->payload.buffer.len);
if (!pkt)
return protolayer_break(ctx, KNOT_EMALF);
int ret = worker_submit(session, ctx->comm, pkt);
mp_flush(the_worker->pkt_pool.ctx);
return protolayer_break(ctx, ret);
} else if (ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
const size_t msg_len = wire_buf_data_length(ctx->payload.wire_buf);
if (msg_len > MAX_DGRAM_LEN) {
session2_penalize(session);
return protolayer_break(ctx, kr_error(EFBIG));
}
knot_pkt_t *pkt = produce_packet(
wire_buf_data(ctx->payload.wire_buf),
msg_len);
if (!pkt)
return protolayer_break(ctx, KNOT_EMALF);
int ret = worker_submit(session, ctx->comm, pkt);
wire_buf_reset(ctx->payload.wire_buf);
mp_flush(the_worker->pkt_pool.ctx);
return protolayer_break(ctx, ret);
} else {
kr_assert(false && "Invalid payload");
return protolayer_break(ctx, kr_error(EINVAL));
}
/* Finish reading DNS/TCP message length. */
if (task->bytes_remaining == 0 && pkt_buf->size == 1) {
pkt_buf->wire[1] = msg[0];
nbytes = msg_size(pkt_buf->wire);
len -= 1;
msg += 1;
/* Cut off fragment length and start reading DNS message. */
pkt_buf->size = 0;
task->bytes_remaining = nbytes;
}
/* Message is too long, can't process it. */
ssize_t to_read = MIN(len, task->bytes_remaining);
if (pkt_buf->size + to_read > pkt_buf->max_size) {
pkt_buf->size = 0;
task->bytes_remaining = 0;
return kr_error(EMSGSIZE);
}
/* Buffer message and check if it's complete */
memcpy(pkt_buf->wire + pkt_buf->size, msg, to_read);
pkt_buf->size += to_read;
if (to_read >= task->bytes_remaining) {
task->bytes_remaining = 0;
/* Parse the packet and start resolving complete query */
int ret = parse_packet(pkt_buf);
if (ret == 0 && !session->outgoing) {
ret = qr_task_start(task, pkt_buf);
if (ret != 0) {
return ret;
}
ret = qr_task_register(task, session);
if (ret != 0) {
return ret;
}
/* Task is now registered in session, clear temporary. */
session->buffering = NULL;
submitted += 1;
}
static int pl_dns_stream_sess_init(struct session2 *session,
void *sess_data, void *param)
{
/* _UNSIZED_STREAM and _MULTI_STREAM - don't forget to split if needed
* at some point */
session->stream = true;
return kr_ok();
}
static int pl_dns_single_stream_sess_init(struct session2 *session,
void *sess_data, void *param)
{
session->stream = true;
struct pl_dns_stream_sess_data *stream = sess_data;
stream->single = true;
return kr_ok();
}
static enum protolayer_event_cb_result pl_dns_stream_resolution_timeout(
struct session2 *s)
{
if (kr_fails_assert(!s->closing))
return PROTOLAYER_EVENT_PROPAGATE;
if (!session2_tasklist_is_empty(s)) {
int finalized = session2_tasklist_finalize_expired(s);
the_worker->stats.timeout += finalized;
/* session2_tasklist_finalize_expired() may call worker_task_finalize().
* If session is a source session and there were IO errors,
* worker_task_finalize() can finalize all tasks and close session. */
if (s->closing)
return PROTOLAYER_EVENT_PROPAGATE;
}
if (!session2_tasklist_is_empty(s)) {
session2_timer_stop(s);
session2_timer_start(s,
PROTOLAYER_EVENT_GENERAL_TIMEOUT,
KR_RESOLVE_TIME_LIMIT / 2,
KR_RESOLVE_TIME_LIMIT / 2);
} else {
/* Normally it should not happen,
* but better to check if there anything in this list. */
if (!session2_waitinglist_is_empty(s)) {
defer_sample_state_t defer_prev_sample_state;
defer_sample_start(&defer_prev_sample_state);
do {
struct qr_task *t = session2_waitinglist_pop(s, false);
worker_task_finalize(t, KR_STATE_FAIL);
worker_task_unref(t);
the_worker->stats.timeout += 1;
if (s->closing)
return PROTOLAYER_EVENT_PROPAGATE;
defer_sample_restart();
} while (!session2_waitinglist_is_empty(s));
defer_sample_stop(&defer_prev_sample_state, true);
}
/* Start only new queries, not subrequests that are already pending */
if (ret == 0) {
ret = qr_task_step(task, NULL, pkt_buf);
uint64_t idle_in_timeout = the_network->tcp.in_idle_timeout;
uint64_t idle_time = kr_now() - s->last_activity;
if (idle_time < idle_in_timeout) {
idle_in_timeout -= idle_time;
session2_timer_stop(s);
session2_timer_start(s, PROTOLAYER_EVENT_GENERAL_TIMEOUT,
idle_in_timeout, idle_in_timeout);
} else {
struct sockaddr *peer = session2_get_peer(s);
char *peer_str = kr_straddr(peer);
kr_log_debug(IO, "=> closing connection to '%s'\n",
peer_str ? peer_str : "");
worker_del_tcp_waiting(peer);
worker_del_tcp_connected(peer);
session2_close(s);
}
/* Process next message part in the stream if no error so far */
if (ret != 0) {
return ret;
}
return PROTOLAYER_EVENT_PROPAGATE;
}
static enum protolayer_event_cb_result pl_dns_stream_connected(
struct session2 *session, struct pl_dns_stream_sess_data *stream)
{
if (kr_fails_assert(!stream->connected))
return PROTOLAYER_EVENT_PROPAGATE;
stream->connected = true;
struct sockaddr *peer = session2_get_peer(session);
if (session->outgoing && worker_del_tcp_waiting(peer) != 0) {
/* session isn't in list of waiting queries,
* something gone wrong */
goto fail;
}
int err = worker_add_tcp_connected(peer, session);
if (err) {
/* Could not add session to the list of connected, something
* went wrong. */
goto fail;
}
send_waiting(session);
return PROTOLAYER_EVENT_PROPAGATE;
fail:
session2_waitinglist_finalize(session, KR_STATE_FAIL);
kr_assert(session2_tasklist_is_empty(session));
session2_close(session);
return PROTOLAYER_EVENT_CONSUME;
}
static enum protolayer_event_cb_result pl_dns_stream_connection_fail(
struct session2 *session, enum kr_selection_error sel_err)
{
session2_timer_stop(session);
kr_assert(session2_tasklist_is_empty(session));
struct sockaddr *peer = session2_get_peer(session);
worker_del_tcp_waiting(peer);
struct qr_task *task = session2_waitinglist_get(session);
if (!task) {
/* Normally shouldn't happen. */
const char *peer_str = kr_straddr(peer);
VERBOSE_MSG(NULL, "=> connection to '%s' failed, empty waitinglist\n",
peer_str ? peer_str : "");
return PROTOLAYER_EVENT_PROPAGATE;
}
struct kr_query *qry = task_get_last_pending_query(task);
if (kr_log_is_debug_qry(WORKER, qry)) {
const char *peer_str = kr_straddr(peer);
bool timeout = sel_err == KR_SELECTION_TCP_CONNECT_TIMEOUT;
VERBOSE_MSG(qry, "=> connection to '%s' failed (%s)\n",
peer_str ? peer_str : "",
timeout ? "timeout" : "error");
}
if (qry)
qry->server_selection.error(qry, task->transport, sel_err);
the_worker->stats.timeout += session2_waitinglist_get_len(session);
session2_waitinglist_retry(session, true);
kr_assert(session2_tasklist_is_empty(session));
/* uv_cancel() doesn't support uv_connect_t request,
* so that we can't cancel it.
* There still exists possibility of successful connection
* for this request.
* So connection callback (on_connect()) must check
* if connection is in the list of waiting connection.
* If no, most likely this is timed out connection even if
* it was successful. */
return PROTOLAYER_EVENT_PROPAGATE;
}
static enum protolayer_event_cb_result pl_dns_stream_disconnected(
struct session2 *session, struct pl_dns_stream_sess_data *stream)
{
struct sockaddr *peer = session2_get_peer(session);
worker_del_tcp_waiting(peer);
worker_del_tcp_connected(peer);
if (!stream->connected)
return PROTOLAYER_EVENT_PROPAGATE;
stream->connected = false;
if (session2_is_empty(session))
return PROTOLAYER_EVENT_PROPAGATE;
defer_sample_state_t defer_prev_sample_state;
if (session->outgoing)
defer_sample_start(&defer_prev_sample_state);
while (!session2_waitinglist_is_empty(session)) {
struct qr_task *task = session2_waitinglist_pop(session, false);
kr_assert(task->refs > 1);
session2_tasklist_del(session, task);
if (session->outgoing) {
if (task->ctx->req.options.FORWARD) {
/* We are in TCP_FORWARD mode.
* To prevent failing at kr_resolve_consume()
* qry.flags.TCP must be cleared.
* TODO - refactoring is needed. */
struct kr_request *req = &task->ctx->req;
struct kr_rplan *rplan = &req->rplan;
struct kr_query *qry = array_tail(rplan->pending);
qry->flags.TCP = false;
}
qr_task_step(task, NULL, NULL);
defer_sample_restart();
} else {
kr_assert(task->ctx->source.session == session);
task->ctx->source.session = NULL;
}
if (len - to_read > 0 && !session->outgoing) {
ret = worker_process_tcp(worker, handle, msg + to_read, len - to_read);
if (ret < 0) {
return ret;
worker_task_unref(task);
}
while (!session2_tasklist_is_empty(session)) {
struct qr_task *task = session2_tasklist_del_first(session, false);
if (session->outgoing) {
if (task->ctx->req.options.FORWARD) {
struct kr_request *req = &task->ctx->req;
struct kr_rplan *rplan = &req->rplan;
struct kr_query *qry = array_tail(rplan->pending);
qry->flags.TCP = false;
}
submitted += ret;
qr_task_step(task, NULL, NULL);
defer_sample_restart();
} else {
kr_assert(task->ctx->source.session == session);
task->ctx->source.session = NULL;
}
} else {
task->bytes_remaining -= to_read;
worker_task_unref(task);
}
return submitted;
if (session->outgoing)
defer_sample_stop(&defer_prev_sample_state, true);
return PROTOLAYER_EVENT_PROPAGATE;
}
int worker_resolve(struct worker_ctx *worker, knot_pkt_t *query, unsigned options, worker_cb_t on_complete, void *baton)
static enum protolayer_event_cb_result pl_dns_stream_eof(
struct session2 *session, struct pl_dns_stream_sess_data *stream)
{
if (!worker || !query) {
return kr_error(EINVAL);
if (!session2_is_empty(session)) {
stream->half_closed = true;
return PROTOLAYER_EVENT_CONSUME;
}
return PROTOLAYER_EVENT_PROPAGATE;
}
/* Create task */
struct qr_task *task = qr_task_create(worker, NULL, NULL);
if (!task) {
return kr_error(ENOMEM);
static enum protolayer_event_cb_result pl_dns_stream_event_unwrap(
enum protolayer_event_type event, void **baton,
struct session2 *session, void *sess_data)
{
if (session->closing)
return PROTOLAYER_EVENT_PROPAGATE;
struct pl_dns_stream_sess_data *stream = sess_data;
switch (event) {
case PROTOLAYER_EVENT_GENERAL_TIMEOUT:
return pl_dns_stream_resolution_timeout(session);
case PROTOLAYER_EVENT_CONNECT_TIMEOUT:
return pl_dns_stream_connection_fail(session,
KR_SELECTION_TCP_CONNECT_TIMEOUT);
case PROTOLAYER_EVENT_CONNECT:
return pl_dns_stream_connected(session, stream);
case PROTOLAYER_EVENT_CONNECT_FAIL:;
enum kr_selection_error err = (*baton)
? *(enum kr_selection_error *)baton
: KR_SELECTION_TCP_CONNECT_FAILED;
return pl_dns_stream_connection_fail(session, err);
case PROTOLAYER_EVENT_DISCONNECT:
case PROTOLAYER_EVENT_CLOSE:
case PROTOLAYER_EVENT_FORCE_CLOSE:
return pl_dns_stream_disconnected(session, stream);
case PROTOLAYER_EVENT_EOF:
return pl_dns_stream_eof(session, stream);
default:
return PROTOLAYER_EVENT_PROPAGATE;
}
task->baton = baton;
task->on_complete = on_complete;
task->req.options |= options;
/* Start task */
int ret = qr_task_start(task, query);
if (ret != 0) {
qr_task_unref(task);
return ret;
}
static knot_pkt_t *stream_produce_packet(struct session2 *session,
struct wire_buf *wb,
bool *out_err)
{
*out_err = false;
if (wire_buf_data_length(wb) == 0) {
wire_buf_reset(wb);
return NULL;
}
return qr_task_step(task, NULL, query);
if (wire_buf_data_length(wb) < sizeof(uint16_t)) {
return NULL;
}
uint16_t msg_len = knot_wire_read_u16(wire_buf_data(wb));
if (msg_len == 0) {
*out_err = true;
session2_penalize(session);
return NULL;
}
if (msg_len >= wb->size) {
*out_err = true;
session2_penalize(session);
return NULL;
}
if (wire_buf_data_length(wb) < msg_len + sizeof(uint16_t)) {
return NULL;
}
uint8_t *wire = (uint8_t *)wire_buf_data(wb) + sizeof(uint16_t);
session->was_useful = true;
knot_pkt_t *pkt = produce_packet(wire, msg_len);
*out_err = (pkt == NULL);
return pkt;
}
/** Reserve worker buffers */
static int worker_reserve(struct worker_ctx *worker, size_t ring_maxlen)
static int stream_discard_packet(struct session2 *session,
struct wire_buf *wb,
const knot_pkt_t *pkt,
bool *out_err)
{
array_init(worker->pool_mp);
array_init(worker->pool_ioreq);
array_init(worker->pool_sessions);
if (array_reserve(worker->pool_mp, ring_maxlen) ||
array_reserve(worker->pool_ioreq, ring_maxlen) ||
array_reserve(worker->pool_sessions, ring_maxlen))
return kr_error(ENOMEM);
memset(&worker->pkt_pool, 0, sizeof(worker->pkt_pool));
worker->pkt_pool.ctx = mp_new (4 * sizeof(knot_pkt_t));
worker->pkt_pool.alloc = (knot_mm_alloc_t) mp_alloc;
worker->outgoing = map_make();
worker->tcp_pipeline_max = MAX_PIPELINED;
*out_err = true;
if (kr_fails_assert(wire_buf_data_length(wb) >= sizeof(uint16_t))) {
wire_buf_reset(wb);
return kr_error(EINVAL);
}
size_t msg_size = knot_wire_read_u16(wire_buf_data(wb));
uint8_t *wire = (uint8_t *)wire_buf_data(wb) + sizeof(uint16_t);
if (kr_fails_assert(msg_size + sizeof(uint16_t) <= wire_buf_data_length(wb))) {
/* TCP message length field is greater then
* number of bytes in buffer, must not happen. */
wire_buf_reset(wb);
return kr_error(EINVAL);
}
if (kr_fails_assert(wire == pkt->wire)) {
/* packet wirebuf must be located at the beginning
* of the session wirebuf, must not happen. */
wire_buf_reset(wb);
return kr_error(EINVAL);
}
if (kr_fails_assert(msg_size >= pkt->size)) {
wire_buf_reset(wb);
return kr_error(EINVAL);
}
wire_buf_trim(wb, msg_size + sizeof(uint16_t));
*out_err = false;
if (wire_buf_data_length(wb) == 0) {
wire_buf_reset(wb);
} else if (wire_buf_data_length(wb) < KNOT_WIRE_HEADER_SIZE) {
wire_buf_movestart(wb);
}
return kr_ok();
}
#define reclaim_freelist(list, type, cb) \
for (unsigned i = 0; i < list.len; ++i) { \
type *elm = list.at[i]; \
kr_asan_unpoison(elm, sizeof(type)); \
cb(elm); \
} \
array_clear(list)
static enum protolayer_iter_cb_result pl_dns_stream_unwrap(
void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx)
{
if (kr_fails_assert(ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF)) {
/* DNS stream only works with a wire buffer */
return protolayer_break(ctx, kr_error(EINVAL));
}
int status = kr_ok();
struct session2 *session = ctx->session;
struct pl_dns_stream_sess_data *stream_sess = sess_data;
struct wire_buf *wb = ctx->payload.wire_buf;
if (wire_buf_data_length(wb) == 0)
return protolayer_break(ctx, status);
const uint32_t max_iters = (wire_buf_data_length(wb) /
(KNOT_WIRE_HEADER_SIZE + KNOT_WIRE_QUESTION_MIN_SIZE)) + 1;
int iters = 0;
bool pkt_error = false;
knot_pkt_t *pkt = NULL;
while ((pkt = stream_produce_packet(session, wb, &pkt_error)) && iters < max_iters) {
if (kr_fails_assert(!pkt_error)) {
status = kr_error(EINVAL);
goto exit;
}
if (stream_sess->single && stream_sess->produced) {
if (kr_log_is_debug(WORKER, NULL)) {
kr_log_debug(WORKER, "Unexpected extra data from %s\n",
kr_straddr(ctx->comm->src_addr));
}
status = KNOT_EMALF;
goto exit;
}
void worker_reclaim(struct worker_ctx *worker)
stream_sess->produced = true;
int ret = worker_submit(session, ctx->comm, pkt);
/* Errors from worker_submit() are intentionally *not* handled
* in order to ensure the entire wire buffer is processed. */
if (ret == kr_ok()) {
iters += 1;
}
if (stream_discard_packet(session, wb, pkt, &pkt_error) < 0) {
/* Packet data isn't stored in memory as expected.
* something went wrong, normally should not happen. */
break;
}
}
/* worker_submit() may cause the session to close (e.g. due to IO
* write error when the packet triggers an immediate answer). This is
* an error state, as well as any wirebuf error. */
if (session->closing || pkt_error)
status = kr_error(EIO);
exit:
wire_buf_movestart(wb);
mp_flush(the_worker->pkt_pool.ctx);
if (status < 0)
session2_force_close(session);
return protolayer_break(ctx, status);
}
struct sized_iovs {
uint8_t nlen[2];
struct iovec iovs[];
};
static enum protolayer_iter_cb_result pl_dns_stream_wrap(
void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx)
{
if (ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER) {
if (kr_fails_assert(ctx->payload.buffer.len <= UINT16_MAX))
return protolayer_break(ctx, kr_error(EMSGSIZE));
const int iovcnt = 2;
struct sized_iovs *siov = mm_alloc(&ctx->pool,
sizeof(*siov) + iovcnt * sizeof(struct iovec));
kr_require(siov);
knot_wire_write_u16(siov->nlen, ctx->payload.buffer.len);
siov->iovs[0] = (struct iovec){
.iov_base = &siov->nlen,
.iov_len = sizeof(siov->nlen)
};
siov->iovs[1] = (struct iovec){
.iov_base = ctx->payload.buffer.buf,
.iov_len = ctx->payload.buffer.len
};
ctx->payload = protolayer_payload_iovec(siov->iovs, iovcnt, false);
return protolayer_continue(ctx);
} else if (ctx->payload.type == PROTOLAYER_PAYLOAD_IOVEC) {
const int iovcnt = 1 + ctx->payload.iovec.cnt;
struct sized_iovs *siov = mm_alloc(&ctx->pool,
sizeof(*siov) + iovcnt * sizeof(struct iovec));
kr_require(siov);
size_t total_len = 0;
for (int i = 0; i < ctx->payload.iovec.cnt; i++) {
const struct iovec *iov = &ctx->payload.iovec.iov[i];
total_len += iov->iov_len;
siov->iovs[i + 1] = *iov;
}
if (kr_fails_assert(total_len <= UINT16_MAX))
return protolayer_break(ctx, kr_error(EMSGSIZE));
knot_wire_write_u16(siov->nlen, total_len);
siov->iovs[0] = (struct iovec){
.iov_base = &siov->nlen,
.iov_len = sizeof(siov->nlen)
};
ctx->payload = protolayer_payload_iovec(siov->iovs, iovcnt, false);
return protolayer_continue(ctx);
} else {
kr_assert(false && "Invalid payload");
return protolayer_break(ctx, kr_error(EINVAL));
}
}
static void pl_dns_stream_request_init(struct session2 *session,
struct kr_request *req,
void *sess_data)
{
reclaim_freelist(worker->pool_mp, struct mempool, mp_delete);
reclaim_freelist(worker->pool_ioreq, struct req, free);
reclaim_freelist(worker->pool_sessions, struct session, session_free);
mp_delete(worker->pkt_pool.ctx);
worker->pkt_pool.ctx = NULL;
map_clear(&worker->outgoing);
req->qsource.comm_flags.tcp = true;
}
struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
int worker_id, int worker_count)
__attribute__((constructor))
static void worker_protolayers_init(void)
{
/* Load bindings */
engine_lualib(engine, "modules", lib_modules);
engine_lualib(engine, "net", lib_net);
engine_lualib(engine, "cache", lib_cache);
engine_lualib(engine, "event", lib_event);
engine_lualib(engine, "worker", lib_worker);
protolayer_globals[PROTOLAYER_TYPE_DNS_DGRAM] = (struct protolayer_globals){
.wire_buf_overhead_cb = pl_dns_dgram_wire_buf_overhead,
.wire_buf_max_overhead = KNOT_WIRE_MAX_PKTSIZE,
.unwrap = pl_dns_dgram_unwrap,
.event_unwrap = pl_dns_dgram_event_unwrap
};
protolayer_globals[PROTOLAYER_TYPE_DNS_UNSIZED_STREAM] = (struct protolayer_globals){
.sess_size = sizeof(struct pl_dns_stream_sess_data),
.wire_buf_overhead = KNOT_WIRE_MAX_PKTSIZE,
.sess_init = pl_dns_stream_sess_init,
.unwrap = pl_dns_dgram_unwrap,
.event_unwrap = pl_dns_stream_event_unwrap,
.request_init = pl_dns_stream_request_init
};
const struct protolayer_globals stream_common = {
.sess_size = sizeof(struct pl_dns_stream_sess_data),
.wire_buf_overhead = KNOT_WIRE_MAX_PKTSIZE,
.sess_init = NULL, /* replaced in specific layers below */
.unwrap = pl_dns_stream_unwrap,
.wrap = pl_dns_stream_wrap,
.event_unwrap = pl_dns_stream_event_unwrap,
.request_init = pl_dns_stream_request_init
};
protolayer_globals[PROTOLAYER_TYPE_DNS_MULTI_STREAM] = stream_common;
protolayer_globals[PROTOLAYER_TYPE_DNS_MULTI_STREAM].sess_init = pl_dns_stream_sess_init;
protolayer_globals[PROTOLAYER_TYPE_DNS_SINGLE_STREAM] = stream_common;
protolayer_globals[PROTOLAYER_TYPE_DNS_SINGLE_STREAM].sess_init = pl_dns_single_stream_sess_init;
}
int worker_init(void)
{
if (kr_fails_assert(the_worker == NULL))
return kr_error(EINVAL);
kr_bindings_register(the_engine->L); // TODO move
/* Create main worker. */
struct worker_ctx *worker = mm_alloc(pool, sizeof(*worker));
if (!worker) {
return NULL;
the_worker = &the_worker_value;
memset(the_worker, 0, sizeof(*the_worker));
uv_loop_t *loop = uv_default_loop();
the_worker->loop = loop;
/* Register table for worker per-request variables */
struct lua_State *L = the_engine->L;
lua_newtable(L);
lua_setfield(L, -2, "vars");
lua_getfield(L, -1, "vars");
the_worker->vars_table_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
the_worker->tcp_pipeline_max = MAX_PIPELINED;
the_worker->out_addr4.sin_family = AF_UNSPEC;
the_worker->out_addr6.sin6_family = AF_UNSPEC;
array_init(the_worker->doh_qry_headers);
int ret = worker_reserve();
if (ret) return ret;
the_worker->next_request_uid = UINT16_MAX + 1;
/* Set some worker.* fields in Lua */
lua_getglobal(L, "worker");
pid_t pid = getpid();
auto_free char *pid_str = NULL;
const char *inst_name = getenv("SYSTEMD_INSTANCE");
if (inst_name) {
lua_pushstring(L, inst_name);
} else {
ret = asprintf(&pid_str, "%ld", (long)pid);
kr_assert(ret > 0);
lua_pushstring(L, pid_str);
}
memset(worker, 0, sizeof(*worker));
worker->id = worker_id;
worker->count = worker_count;
worker->engine = engine;
worker_reserve(worker, MP_FREELIST_SIZE);
/* Register worker in Lua thread */
lua_pushlightuserdata(engine->L, worker);
lua_setglobal(engine->L, "__worker");
lua_getglobal(engine->L, "worker");
lua_pushnumber(engine->L, worker_id);
lua_setfield(engine->L, -2, "id");
lua_pushnumber(engine->L, getpid());
lua_setfield(engine->L, -2, "pid");
lua_pushnumber(engine->L, worker_count);
lua_setfield(engine->L, -2, "count");
lua_pop(engine->L, 1);
return worker;
}
#undef DEBUG_MSG
lua_setfield(L, -2, "id");
lua_pushnumber(L, pid);
lua_setfield(L, -2, "pid");
char cwd[PATH_MAX];
get_workdir(cwd, sizeof(cwd));
lua_pushstring(L, cwd);
lua_setfield(L, -2, "cwd");
loop->data = the_worker;
/* ^^^^ Now this shouldn't be used anymore, but it's hard to be 100% sure. */
return kr_ok();
}
#undef VERBOSE_MSG
/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "daemon/engine.h"
#include "lib/generic/array.h"
#include "lib/generic/map.h"
#include "lib/generic/trie.h"
/** Worker state (opaque). */
/** Query resolution task (opaque). */
struct qr_task;
/** Worker state. */
struct worker_ctx;
/** Worker callback */
typedef void (*worker_cb_t)(struct worker_ctx *worker, struct kr_request *req, void *baton);
/** Transport session (opaque). */
struct session2;
/** Data about the communication (defined in io.h). */
struct comm_info;
/** Create and initialize the worker. */
struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
int worker_id, int worker_count);
/** Pointer to the singleton worker. NULL if not initialized. */
KR_EXPORT extern struct worker_ctx *the_worker;
/**
* Process incoming packet (query or answer to subrequest).
* @return 0 or an error code
*/
int worker_submit(struct worker_ctx *worker, uv_handle_t *handle, knot_pkt_t *query,
const struct sockaddr* addr);
/** Create and initialize the worker.
* \return error code (ENOMEM) */
int worker_init(void);
/** Destroy the worker (free memory). */
void worker_deinit(void);
KR_EXPORT knot_pkt_t *worker_resolve_mk_pkt_dname(knot_dname_t *qname, uint16_t qtype, uint16_t qclass,
const struct kr_qflags *options);
/**
* Process incoming DNS/TCP message fragment(s).
* If the fragment contains only a partial message, it is buffered.
* If the fragment contains a complete query or completes current fragment, execute it.
* @return 0 or an error code
* Create a packet suitable for worker_resolve_start(). All in malloc() memory.
*/
int worker_process_tcp(struct worker_ctx *worker, uv_stream_t *handle,
const uint8_t *msg, ssize_t len);
KR_EXPORT knot_pkt_t *
worker_resolve_mk_pkt(const char *qname_str, uint16_t qtype, uint16_t qclass,
const struct kr_qflags *options);
/**
* End current DNS/TCP session, this disassociates pending tasks from this session
* which may be freely closed afterwards.
* Start query resolution with given query.
*
* @return task or NULL
*/
int worker_end_tcp(struct worker_ctx *worker, uv_handle_t *handle);
KR_EXPORT struct qr_task *
worker_resolve_start(knot_pkt_t *query, struct kr_qflags options);
/**
* Schedule query for resolution.
* Execute a request with given query.
* It expects task to be created with \fn worker_resolve_start.
*
* @return 0 or an error code
*/
int worker_resolve(struct worker_ctx *worker, knot_pkt_t *query, unsigned options,
worker_cb_t on_complete, void *baton);
KR_EXPORT int worker_resolve_exec(struct qr_task *task, knot_pkt_t *query);
/** @return struct kr_request associated with opaque task */
struct kr_request *worker_task_request(struct qr_task *task);
int worker_task_step(struct qr_task *task, const struct sockaddr *packet_source,
knot_pkt_t *packet);
int worker_task_numrefs(const struct qr_task *task);
/** Finalize given task */
int worker_task_finalize(struct qr_task *task, int state);
void worker_task_complete(struct qr_task *task);
void worker_task_ref(struct qr_task *task);
void worker_task_unref(struct qr_task *task);
void worker_task_timeout_inc(struct qr_task *task);
/** Collect worker mempools */
void worker_reclaim(struct worker_ctx *worker);
knot_pkt_t *worker_task_get_pktbuf(const struct qr_task *task);
struct kr_transport *worker_task_get_transport(struct qr_task *task);
/** Note: source session is NULL in case the request hasn't come over network. */
KR_EXPORT struct session2 *worker_request_get_source_session(const struct kr_request *req);
uint16_t worker_task_pkt_get_msgid(struct qr_task *task);
void worker_task_pkt_set_msgid(struct qr_task *task, uint16_t msgid);
uint64_t worker_task_creation_time(struct qr_task *task);
void worker_task_subreq_finalize(struct qr_task *task);
bool worker_task_finished(struct qr_task *task);
/** To be called after sending a DNS message. It mainly deals with cleanups. */
int qr_task_on_send(struct qr_task *task, struct session2 *s, int status);
/** Various worker statistics. Sync with wrk_stats() */
struct worker_stats {
size_t queries; /**< Total number of requests (from clients and internal ones). */
size_t concurrent; /**< The number of requests currently in processing. */
size_t rconcurrent; /*< TODO: remove? I see no meaningful difference from .concurrent. */
size_t dropped; /**< The number of requests dropped due to being badly formed. See #471. */
size_t timeout; /**< Number of outbound queries that timed out. */
size_t udp; /**< Number of outbound queries over UDP. */
size_t tcp; /**< Number of outbound queries over TCP (excluding TLS). */
size_t tls; /**< Number of outbound queries over TLS. */
size_t ipv4; /**< Number of outbound queries over IPv4.*/
size_t ipv6; /**< Number of outbound queries over IPv6. */
size_t err_udp; /**< Total number of write errors for UDP transport. */
size_t err_tcp; /**< Total number of write errors for TCP transport. */
size_t err_tls; /**< Total number of write errors for TLS transport. */
size_t err_http; /**< Total number of write errors for HTTP(S) transport. */
};
/** @cond internal */
/** Number of request within timeout window. */
#define MAX_PENDING KR_NSREP_MAXADDR
#define MAX_PENDING 4
/** Maximum response time from TCP upstream, milliseconds */
#define MAX_TCP_INACTIVITY (KR_RESOLVE_TIME_LIMIT + KR_CONN_RTT_MAX)
/** Freelist of available mempools. */
typedef array_t(void *) mp_freelist_t;
#ifndef RECVMMSG_BATCH /* see check_bufsize() */
#define RECVMMSG_BATCH 1
#endif
/** List of query resolution tasks. */
typedef array_t(struct qr_task *) qr_tasklist_t;
/** List of HTTP header names. */
typedef array_t(const char *) doh_headerlist_t;
/** \details Worker state is meant to persist during the whole life of daemon. */
struct worker_ctx {
struct engine *engine;
uv_loop_t *loop;
int id;
int count;
int count; /** unreliable, does not count systemd instance, do not use */
int vars_table_ref;
unsigned tcp_pipeline_max;
#if __linux__
uint8_t wire_buf[RECVMMSG_BATCH * KNOT_WIRE_MAX_PKTSIZE];
#else
uint8_t wire_buf[KNOT_WIRE_MAX_PKTSIZE];
#endif
struct {
size_t concurrent;
size_t udp;
size_t tcp;
size_t ipv4;
size_t ipv6;
size_t queries;
size_t dropped;
size_t timeout;
} stats;
map_t outgoing;
mp_freelist_t pool_mp;
mp_freelist_t pool_ioreq;
mp_freelist_t pool_sessions;
/** Addresses to bind for outgoing connections or AF_UNSPEC. */
struct sockaddr_in out_addr4;
struct sockaddr_in6 out_addr6;
struct worker_stats stats;
bool too_many_open;
size_t rconcurrent_highwatermark;
/** List of active outbound TCP sessions */
trie_t *tcp_connected;
/** List of outbound TCP sessions waiting to be accepted */
trie_t *tcp_waiting;
/** Subrequest leaders (struct qr_task*), indexed by qname+qtype+qclass. */
trie_t *subreq_out;
knot_mm_t pkt_pool;
};
unsigned int next_request_uid;
/** Query resolution task. */
struct qr_task
{
struct kr_request req;
struct worker_ctx *worker;
struct session *session;
knot_pkt_t *pktbuf;
array_t(struct qr_task *) waiting;
uv_handle_t *pending[MAX_PENDING];
uint16_t pending_count;
uint16_t addrlist_count;
uint16_t addrlist_turn;
uint16_t timeouts;
uint16_t iter_count;
uint16_t bytes_remaining;
struct sockaddr *addrlist;
uv_timer_t *timeout;
worker_cb_t on_complete;
void *baton;
struct {
union {
struct sockaddr_in ip4;
struct sockaddr_in6 ip6;
} addr;
union {
struct sockaddr_in ip4;
struct sockaddr_in6 ip6;
} dst_addr;
uv_handle_t *handle;
} source;
uint32_t refs;
bool finished : 1;
bool leading : 1;
/* HTTP Headers for DoH. */
doh_headerlist_t doh_qry_headers;
};
/** @endcond */
......
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* Module is intended to import resource records from file into resolver's cache.
* File supposed to be a standard DNS zone file
* which contains text representations of resource records.
* For now only root zone import is supported.
*
* Import process consists of two stages.
* 1) Zone file parsing and (optionally) ZONEMD verification.
* 2) DNSSEC validation and storage in cache.
*
* These stages are implemented as two separate functions
* (zi_zone_import and zi_zone_process) which run sequentially with a
* pause between them. This is done because resolver is a single-threaded
* application, so it can't process user's requests during the whole import
* process. Separation into two stages allows to reduce the
* continuous time interval when resolver can't serve user requests.
* Since root zone isn't large, it is imported as single chunk.
*/
#include "daemon/zimport.h"
#include <inttypes.h> /* PRIu64 */
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <uv.h>
#include <libknot/rrset.h>
#include <libzscanner/scanner.h>
#include <libdnssec/digest.h>
#include "daemon/worker.h"
#include "lib/dnssec/ta.h"
#include "lib/dnssec.h"
#include "lib/generic/trie.h"
#include "lib/utils.h"
/* Pause between parse and import stages, milliseconds. */
#define ZONE_IMPORT_PAUSE 100
// NAN normally comes from <math.h> but it's not guaranteed.
#ifndef NAN
#define NAN nan("")
#endif
struct zone_import_ctx {
knot_mm_t *pool; /// memory pool for all allocations (including struct itself)
knot_dname_t *origin;
uv_timer_t timer;
// from zi_config_t
zi_callback cb;
void *cb_param;
trie_t *rrsets; /// map: key_get() -> knot_rrset_t*, in ZONEMD order
uint32_t timestamp_rr; /// stamp of when RR data arrived (seconds since epoch)
struct kr_svldr_ctx *svldr; /// DNSSEC validator; NULL iff we don't validate
const knot_dname_t *last_cut; /// internal to zi_rrset_import()
uint8_t *digest_buf; /// temporary buffer for digest computation (on pool)
#define DIGEST_BUF_SIZE (64*1024 - 1)
#define DIGEST_ALG_COUNT 2
struct {
bool active; /// whether we want it computed
dnssec_digest_ctx_t *ctx;
const uint8_t *expected; /// expected digest (inside zonemd on pool)
} digests[DIGEST_ALG_COUNT]; /// we use indices 0 and 1 for SHA 384 and 512
};
typedef struct zone_import_ctx zone_import_ctx_t;
#define KEY_LEN (KNOT_DNAME_MAXLEN + 1 + 2 + 2)
/** Construct key for name, type and signed type (if type == RRSIG).
*
* Return negative error code in asserted cases.
*/
static int key_get(char buf[KEY_LEN], const knot_dname_t *name,
uint16_t type, uint16_t type_maysig, char **key_p)
{
char *lf = (char *)knot_dname_lf(name, (uint8_t *)buf);
if (kr_fails_assert(lf && key_p))
return kr_error(EINVAL);
int len = (unsigned char)lf[0];
lf++; // point to start of data
*key_p = lf;
// Check that LF is right-aligned to KNOT_DNAME_MAXLEN in buf.
if (kr_fails_assert(lf + len == buf + KNOT_DNAME_MAXLEN))
return kr_error(EINVAL);
buf[KNOT_DNAME_MAXLEN] = 0; // this ensures correct ZONEMD order
memcpy(buf + KNOT_DNAME_MAXLEN + 1, &type, sizeof(type));
len += 1 + sizeof(type);
if (type == KNOT_RRTYPE_RRSIG) {
memcpy(buf + KNOT_DNAME_MAXLEN + 1 + sizeof(type),
&type_maysig, sizeof(type_maysig));
len += sizeof(type_maysig);
}
return len;
}
/** Simple helper to retreive from zone_import_ctx_t::rrsets */
static knot_rrset_t * rrset_get(trie_t *rrsets, const knot_dname_t *name,
uint16_t type, uint16_t type_maysig)
{
char key_buf[KEY_LEN], *key;
const int len = key_get(key_buf, name, type, type_maysig, &key);
if (len < 0)
return NULL;
const trie_val_t *rrsig_p = trie_get_try(rrsets, key, len);
if (!rrsig_p)
return NULL;
kr_assert(*rrsig_p);
return *rrsig_p;
}
static int digest_rrset(trie_val_t *rr_p, void *z_import_v)
{
zone_import_ctx_t *z_import = z_import_v;
const knot_rrset_t *rr = *rr_p;
// ignore apex ZONEMD or its RRSIG, and also out of bailiwick records
const int origin_bailiwick = knot_dname_in_bailiwick(rr->owner, z_import->origin);
const bool is_apex = origin_bailiwick == 0;
if (is_apex && kr_rrset_type_maysig(rr) == KNOT_RRTYPE_ZONEMD)
return KNOT_EOK;
if (unlikely(origin_bailiwick < 0))
return KNOT_EOK;
const int len = knot_rrset_to_wire_extra(rr, z_import->digest_buf, DIGEST_BUF_SIZE,
0, NULL, KNOT_PF_ORIGTTL);
if (len < 0)
return kr_error(len);
// digest serialized RRSet
for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
if (!z_import->digests[i].active)
continue;
dnssec_binary_t bufbin = { len, z_import->digest_buf };
int ret = dnssec_digest(z_import->digests[i].ctx, &bufbin);
if (ret != KNOT_EOK)
return kr_error(ret);
}
return KNOT_EOK;
}
/** Verify ZONEMD in the stored zone, and return error code.
*
* ZONEMD signature is verified iff z_import->svldr != NULL
https://www.rfc-editor.org/rfc/rfc8976.html#name-verifying-zone-digest
*/
static int zonemd_verify(zone_import_ctx_t *z_import)
{
bool zonemd_is_valid = false;
// Find ZONEMD RR + RRSIG
knot_rrset_t * const rr_zonemd
= rrset_get(z_import->rrsets, z_import->origin, KNOT_RRTYPE_ZONEMD, 0);
if (!rr_zonemd) {
// no zonemd; let's compute the shorter digest and print info later
z_import->digests[KNOT_ZONEMD_ALGORITHM_SHA384 - 1].active = true;
goto do_digest;
}
// Validate ZONEMD RRSIG, if desired
if (z_import->svldr) {
const knot_rrset_t *rrsig_zonemd
= rrset_get(z_import->rrsets, z_import->origin,
KNOT_RRTYPE_RRSIG, KNOT_RRTYPE_ZONEMD);
int ret = rrsig_zonemd
? kr_svldr_rrset(rr_zonemd, &rrsig_zonemd->rrs, z_import->svldr)
: kr_error(ENOENT);
zonemd_is_valid = (ret == kr_ok());
if (!rrsig_zonemd) {
kr_log_error(PREFILL, "ZONEMD signature missing\n");
} else if (!zonemd_is_valid) {
kr_log_error(PREFILL, "ZONEMD signature failed to validate\n");
}
}
// Get SOA serial
const knot_rrset_t *soa = rrset_get(z_import->rrsets, z_import->origin,
KNOT_RRTYPE_SOA, 0);
if (!soa) {
kr_log_error(PREFILL, "SOA record not found\n");
return kr_error(ENOENT);
}
if (soa->rrs.count != 1) {
kr_log_error(PREFILL, "the SOA RR set is weird\n");
return kr_error(EINVAL);
} // length is checked by parser already
const uint32_t soa_serial = knot_soa_serial(soa->rrs.rdata);
// Figure out SOA+ZONEMD RR contents.
bool some_active = false;
knot_rdata_t *rd = rr_zonemd->rrs.rdata;
for (int i = 0; i < rr_zonemd->rrs.count; ++i, rd = knot_rdataset_next(rd)) {
if (rd->len < 6 || knot_zonemd_scheme(rd) != KNOT_ZONEMD_SCHEME_SIMPLE
|| knot_zonemd_soa_serial(rd) != soa_serial)
continue;
const int algo = knot_zonemd_algorithm(rd);
if (algo != KNOT_ZONEMD_ALGORITHM_SHA384 && algo != KNOT_ZONEMD_ALGORITHM_SHA512)
continue;
if (rd->len != 6 + knot_zonemd_digest_size(rd)) {
kr_log_error(PREFILL, "ZONEMD record has incorrect digest length\n");
return kr_error(EINVAL);
}
if (z_import->digests[algo - 1].active) {
kr_log_error(PREFILL, "multiple clashing ZONEMD records found\n");
return kr_error(EINVAL);
}
some_active = true;
z_import->digests[algo - 1].active = true;
z_import->digests[algo - 1].expected = knot_zonemd_digest(rd);
}
if (!some_active) {
kr_log_error(PREFILL, "ZONEMD record(s) found but none were usable\n");
return kr_error(ENOENT);
}
do_digest:
// Init memory, etc.
if (!z_import->digest_buf) {
z_import->digest_buf = mm_alloc(z_import->pool, DIGEST_BUF_SIZE);
if (!z_import->digest_buf)
return kr_error(ENOMEM);
}
for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
const int algo = i + 1;
if (!z_import->digests[i].active)
continue;
int ret = dnssec_digest_init(algo, &z_import->digests[i].ctx);
if (ret != KNOT_EOK) {
// free previous successful _ctx, if applicable
dnssec_binary_t digest = { 0 };
while (--i >= 0) {
if (z_import->digests[i].active)
dnssec_digest_finish(z_import->digests[i].ctx,
&digest);
}
return kr_error(ENOMEM);
}
}
// Actually compute the digest(s).
int ret = trie_apply(z_import->rrsets, digest_rrset, z_import);
dnssec_binary_t digs[DIGEST_ALG_COUNT] = { { 0 } };
for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
if (!z_import->digests[i].active)
continue;
int ret2 = dnssec_digest_finish(z_import->digests[i].ctx, &digs[i]);
if (ret == DNSSEC_EOK)
ret = ret2;
// we need to keep going to free all digests[*].ctx
}
if (ret != DNSSEC_EOK) {
for (int i = 0; i < DIGEST_ALG_COUNT; ++i)
free(digs[i].data);
kr_log_error(PREFILL, "error when computing digest: %s\n",
kr_strerror(ret));
return kr_error(ret);
}
// Now only check that one of the hashes match.
bool has_match = false;
for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
if (!z_import->digests[i].active)
continue;
// hexdump the hash for logging
char hash_str[digs[i].size * 2 + 1];
for (ssize_t j = 0; j < digs[i].size; ++j)
(void)sprintf(hash_str + 2*j, "%02x", digs[i].data[j]);
if (!z_import->digests[i].expected) {
kr_log_error(PREFILL, "no ZONEMD found; computed hash: %s\n",
hash_str);
} else if (memcmp(z_import->digests[i].expected, digs[i].data,
digs[i].size) != 0) {
kr_log_error(PREFILL, "ZONEMD hash mismatch; computed hash: %s\n",
hash_str);
} else {
kr_log_debug(PREFILL, "ZONEMD hash matches\n");
has_match = true;
continue;
}
}
for (int i = 0; i < DIGEST_ALG_COUNT; ++i)
free(digs[i].data);
bool ok = has_match && (zonemd_is_valid || !z_import->svldr);
return ok ? kr_ok() : kr_error(ENOENT);
}
/**
* @internal Import given rrset to cache.
*
* @return error code; we could've chosen to keep importing even if some RRset fails,
* but it would be harder to ensure that we don't generate too many logs
* and that we pass an error to the finishing callback.
*/
static int zi_rrset_import(trie_val_t *rr_p, void *z_import_v)
{
zone_import_ctx_t *z_import = z_import_v;
knot_rrset_t *rr = *rr_p;
if (rr->type == KNOT_RRTYPE_RRSIG)
return 0; // we do RRSIGs at once with their types
const int origin_bailiwick = knot_dname_in_bailiwick(rr->owner, z_import->origin);
if (unlikely(origin_bailiwick < 0)) {
KR_DNAME_GET_STR(owner_str, rr->owner);
kr_log_warning(PREFILL, "ignoring out of bailiwick record(s) on %s\n",
owner_str);
return 0; // well, let's continue without error
}
// Determine if this RRset is authoritative.
// We utilize that iteration happens in canonical order.
bool is_auth;
const int kdib = knot_dname_in_bailiwick(rr->owner, z_import->last_cut);
if (kdib == 0 && (rr->type == KNOT_RRTYPE_DS || rr->type == KNOT_RRTYPE_NSEC
|| rr->type == KNOT_RRTYPE_NSEC3)) {
// parent side of the zone cut (well, presumably in case of NSEC*)
is_auth = true;
} else if (kdib >= 0) {
// inside non-auth subtree
is_auth = false;
} else if (rr->type == KNOT_RRTYPE_NS && origin_bailiwick > 0) {
// entering non-auth subtree
z_import->last_cut = rr->owner;
is_auth = false;
} else {
// outside non-auth subtree
is_auth = true;
z_import->last_cut = NULL; // so that the next _in_bailiwick() is faster
}
// Rare case: `A` exactly on zone cut would be misdetected and fail validation;
// it's the only type ordered before NS.
if (unlikely(is_auth && rr->type < KNOT_RRTYPE_NS)) {
if (rrset_get(z_import->rrsets, rr->owner, KNOT_RRTYPE_NS, 0))
is_auth = false;
}
// Get and validate the corresponding RRSIGs, if authoritative.
const knot_rrset_t *rrsig = NULL;
if (is_auth) {
rrsig = rrset_get(z_import->rrsets, rr->owner, KNOT_RRTYPE_RRSIG, rr->type);
if (unlikely(!rrsig && z_import->svldr)) {
KR_DNAME_GET_STR(owner_str, rr->owner);
KR_RRTYPE_GET_STR(type_str, rr->type);
kr_log_error(PREFILL, "no records found for %s RRSIG %s\n",
owner_str, type_str);
return kr_error(ENOENT);
}
}
if (is_auth && z_import->svldr) {
int ret = kr_svldr_rrset(rr, &rrsig->rrs, z_import->svldr);
if (unlikely(ret)) {
KR_DNAME_GET_STR(owner_str, rr->owner);
KR_RRTYPE_GET_STR(type_str, rr->type);
kr_log_error(PREFILL, "validation failed for %s %s: %s\n",
owner_str, type_str, kr_strerror(ret));
return kr_error(ret);
}
}
uint8_t rank;
if (!is_auth) {
rank = KR_RANK_OMIT;
} else if (z_import->svldr) {
rank = KR_RANK_AUTH|KR_RANK_SECURE;
} else {
rank = KR_RANK_AUTH|KR_RANK_INSECURE;
}
int ret = kr_cache_insert_rr(&the_resolver->cache, rr, rrsig,
rank, z_import->timestamp_rr,
// Optim.: only stash NSEC* params at the apex.
origin_bailiwick == 0);
if (ret) {
kr_log_error(PREFILL, "caching an RRset failed: %s\n",
kr_strerror(ret));
return kr_error(ret);
}
return 0; // success
}
static void ctx_delete(zone_import_ctx_t *z_import)
{
if (kr_fails_assert(z_import)) return;
kr_svldr_free_ctx(z_import->svldr);
/* Free `z_import`'s pool, including `z_import` itself, because it is
* allocated inside said pool. */
mm_ctx_delete(z_import->pool);
}
static void timer_close(uv_handle_t *handle)
{
ctx_delete(handle->data);
}
/** @internal Iterate over parsed rrsets and try to import each of them. */
static void zi_zone_process(uv_timer_t *timer)
{
zone_import_ctx_t *z_import = timer->data;
kr_timer_t stopwatch;
kr_timer_start(&stopwatch);
int ret = trie_apply(z_import->rrsets, zi_rrset_import, z_import);
(void)kr_cache_commit(&the_resolver->cache); // RW transaction open
if (ret == 0) {
kr_log_info(PREFILL, "performance: validating and caching took %.3lf s\n",
kr_timer_elapsed(&stopwatch));
}
if (z_import->cb)
z_import->cb(kr_error(ret), z_import->cb_param);
uv_close((uv_handle_t *)timer, timer_close);
}
/** @internal Store rrset that has been imported to zone import context memory pool.
* @return -1 if failed; 0 if success. */
static int zi_record_store(zs_scanner_t *s)
{
if (s->r_data_length > UINT16_MAX) {
/* Due to knot_rrset_add_rdata(..., const uint16_t size, ...); */
kr_log_error(PREFILL, "line %"PRIu64": rdata is too long\n",
s->line_counter);
return -1;
}
if (knot_dname_size(s->r_owner) != strlen((const char *)(s->r_owner)) + 1) {
kr_log_error(PREFILL, "line %"PRIu64
": owner name contains zero byte, skip\n",
s->line_counter);
return 0;
}
zone_import_ctx_t *z_import = (zone_import_ctx_t *)s->process.data;
knot_rrset_t *new_rr = knot_rrset_new(s->r_owner, s->r_type, s->r_class,
s->r_ttl, z_import->pool);
if (!new_rr) {
kr_log_error(PREFILL, "line %"PRIu64": error creating rrset\n",
s->line_counter);
return -1;
}
int res = knot_rrset_add_rdata(new_rr, s->r_data, s->r_data_length,
z_import->pool);
if (res != KNOT_EOK) {
kr_log_error(PREFILL, "line %"PRIu64": error adding rdata to rrset\n",
s->line_counter);
return -1;
}
/* zscanner itself does not canonize - neither owner nor insides */
res = knot_rrset_rr_to_canonical(new_rr);
if (res != KNOT_EOK) {
kr_log_error(PREFILL, "line %"PRIu64": error when canonizing: %s\n",
s->line_counter, knot_strerror(res));
return -1;
}
/* Records in zone file may not be grouped by name and RR type.
* Use map to create search key and
* avoid ineffective searches across all the imported records. */
char key_buf[KEY_LEN], *key;
const int len = key_get(key_buf, new_rr->owner, new_rr->type,
kr_rrset_type_maysig(new_rr), &key);
if (len < 0) {
kr_log_error(PREFILL, "line %"PRIu64": error constructing rrkey\n",
s->line_counter);
return -1;
}
trie_val_t *rr_p = trie_get_ins(z_import->rrsets, key, len);
if (!rr_p)
return -1; // ENOMEM
if (*rr_p) {
knot_rrset_t *rr = *rr_p;
res = knot_rdataset_merge(&rr->rrs, &new_rr->rrs, z_import->pool);
} else {
*rr_p = new_rr;
}
if (res != 0) {
kr_log_error(PREFILL, "line %"PRIu64": error saving parsed rrset\n",
s->line_counter);
return -1;
}
return 0;
}
static int zi_state_parsing(zs_scanner_t *s)
{
bool empty = true;
while (zs_parse_record(s) == 0) {
switch (s->state) {
case ZS_STATE_DATA:
if (zi_record_store(s) != 0) {
return -1;
}
zone_import_ctx_t *z_import = (zone_import_ctx_t *) s->process.data;
empty = false;
if (s->r_type == KNOT_RRTYPE_SOA) {
z_import->origin = knot_dname_copy(s->r_owner,
z_import->pool);
}
break;
case ZS_STATE_ERROR:
kr_log_error(PREFILL, "line: %"PRIu64
": parse error; code: %i ('%s')\n",
s->line_counter, s->error.code,
zs_strerror(s->error.code));
return -1;
case ZS_STATE_INCLUDE:
kr_log_error(PREFILL, "line: %"PRIu64
": INCLUDE is not supported\n",
s->line_counter);
return -1;
case ZS_STATE_EOF:
case ZS_STATE_STOP:
if (empty) {
kr_log_error(PREFILL, "empty zone file\n");
return -1;
}
if (!((zone_import_ctx_t *) s->process.data)->origin) {
kr_log_error(PREFILL, "zone file doesn't contain SOA record\n");
return -1;
}
return (s->error.counter == 0) ? 0 : -1;
default:
kr_log_error(PREFILL, "line: %"PRIu64
": unexpected parse state: %i\n",
s->line_counter, s->state);
return -1;
}
}
return -1;
}
int zi_zone_import(const zi_config_t config)
{
const zi_config_t *c = &config;
if (kr_fails_assert(c && c->zone_file))
return kr_error(EINVAL);
knot_mm_t *pool = mm_ctx_mempool2((size_t)1024 * 1024);
zone_import_ctx_t *z_import = mm_calloc(pool, 1, sizeof(*z_import));
if (!z_import) return kr_error(ENOMEM);
z_import->pool = pool;
z_import->cb = c->cb;
z_import->cb_param = c->cb_param;
z_import->rrsets = trie_create(z_import->pool);
kr_timer_t stopwatch;
kr_timer_start(&stopwatch);
//// Parse the whole zone file into z_import->rrsets.
zs_scanner_t s_storage, *s = &s_storage;
/* zs_init(), zs_set_input_file(), zs_set_processing() returns -1 in case of error,
* so don't print error code as it meaningless. */
int ret = zs_init(s, c->origin, KNOT_CLASS_IN, c->ttl);
if (ret != 0) {
kr_log_error(PREFILL, "error initializing zone scanner instance, error: %i (%s)\n",
s->error.code, zs_strerror(s->error.code));
goto fail;
}
ret = zs_set_input_file(s, c->zone_file);
if (ret != 0) {
kr_log_error(PREFILL, "error opening zone file `%s`, error: %i (%s)\n",
c->zone_file, s->error.code, zs_strerror(s->error.code));
zs_deinit(s);
goto fail;
}
/* Don't set processing and error callbacks as we don't use automatic parsing.
* Parsing as well error processing will be performed in zi_state_parsing().
* Store pointer to zone import context for further use. */
ret = zs_set_processing(s, NULL, NULL, (void *)z_import);
if (ret != 0) {
kr_log_error(PREFILL, "zs_set_processing() failed for zone file `%s`, "
"error: %i (%s)\n",
c->zone_file, s->error.code, zs_strerror(s->error.code));
zs_deinit(s);
goto fail;
}
ret = zi_state_parsing(s);
zs_deinit(s);
const double time_parse = kr_timer_elapsed(&stopwatch);
if (ret != 0) {
kr_log_error(PREFILL, "error parsing zone file `%s`\n", c->zone_file);
goto fail;
}
kr_log_debug(PREFILL, "import started for zone file `%s`\n", c->zone_file);
KR_DNAME_GET_STR(zone_name_str, z_import->origin);
//// Choose timestamp_rr, according to config.
struct timespec now;
if (clock_gettime(CLOCK_REALTIME, &now)) {
ret = kr_error(errno);
kr_log_error(PREFILL, "failed to get current time: %s\n", kr_strerror(ret));
goto fail;
}
if (config.time_src == ZI_STAMP_NOW) {
z_import->timestamp_rr = now.tv_sec;
} else if (config.time_src == ZI_STAMP_MTIM) {
struct stat st;
if (stat(c->zone_file, &st) != 0) {
kr_log_debug(PREFILL, "failed to stat file `%s`: %s\n",
c->zone_file, strerror(errno));
goto fail;
}
z_import->timestamp_rr = st.st_mtime;
} else {
ret = kr_error(EINVAL);
goto fail;
}
//// Some sanity checks
const knot_rrset_t *soa = rrset_get(z_import->rrsets, z_import->origin,
KNOT_RRTYPE_SOA, 0);
if (z_import->timestamp_rr > now.tv_sec) {
kr_log_warning(PREFILL, "zone file `%s` comes from future\n", c->zone_file);
} else if (!soa) {
kr_log_warning(PREFILL, "missing %s SOA\n", zone_name_str);
} else if ((int64_t)z_import->timestamp_rr + soa->ttl < now.tv_sec) {
kr_log_warning(PREFILL, "%s SOA already expired\n", zone_name_str);
}
//// Initialize validator context with the DNSKEY.
if (c->downgrade)
goto zonemd;
const knot_rrset_t * const ds = c->ds ? c->ds :
kr_ta_get(the_resolver->trust_anchors, z_import->origin);
if (!ds) {
if (!kr_ta_closest(the_resolver, z_import->origin, KNOT_RRTYPE_DNSKEY))
goto zonemd; // our TAs say we're insecure
kr_log_error(PREFILL, "no DS found for `%s`, fail\n", zone_name_str);
ret = kr_error(ENOENT);
goto fail;
}
if (!knot_dname_is_equal(ds->owner, z_import->origin)) {
kr_log_error(PREFILL, "mismatching DS owner, fail\n");
ret = kr_error(EINVAL);
goto fail;
}
knot_rrset_t * const dnskey = rrset_get(z_import->rrsets, z_import->origin,
KNOT_RRTYPE_DNSKEY, 0);
if (!dnskey) {
kr_log_error(PREFILL, "no DNSKEY found for `%s`, fail\n", zone_name_str);
ret = kr_error(ENOENT);
goto fail;
}
knot_rrset_t * const dnskey_sigs = rrset_get(z_import->rrsets, z_import->origin,
KNOT_RRTYPE_RRSIG, KNOT_RRTYPE_DNSKEY);
if (!dnskey_sigs) {
kr_log_error(PREFILL, "no RRSIGs for DNSKEY found for `%s`, fail\n",
zone_name_str);
ret = kr_error(ENOENT);
goto fail;
}
kr_rrset_validation_ctx_t err_ctx;
z_import->svldr = kr_svldr_new_ctx(ds, dnskey, &dnskey_sigs->rrs,
z_import->timestamp_rr, &err_ctx);
if (!z_import->svldr) {
// log RRSIG stats; very similar to log_bogus_rrsig()
kr_log_error(PREFILL, "failed to validate DNSKEY for `%s` "
"(%u matching RRSIGs, %u expired, %u not yet valid, "
"%u invalid signer, %u invalid label count, %u invalid key, "
"%u invalid crypto, %u invalid NSEC)\n",
zone_name_str,
err_ctx.rrs_counters.matching_name_type,
err_ctx.rrs_counters.expired, err_ctx.rrs_counters.notyet,
err_ctx.rrs_counters.signer_invalid,
err_ctx.rrs_counters.labels_invalid,
err_ctx.rrs_counters.key_invalid,
err_ctx.rrs_counters.crypto_invalid,
err_ctx.rrs_counters.nsec_invalid);
ret = kr_error(ENOENT);
goto fail;
}
//// Do all ZONEMD processing, if desired.
zonemd: (void)0; // C can't have a variable definition following a label
double time_zonemd = NAN;
if (c->zonemd) {
kr_timer_start(&stopwatch);
ret = zonemd_verify(z_import);
time_zonemd = kr_timer_elapsed(&stopwatch);
} else {
ret = kr_ok();
}
kr_log_info(PREFILL, "performance: parsing took %.3lf s, hashing took %.3lf s\n",
time_parse, time_zonemd);
if (ret) goto fail;
//// Phase two, after a pause. Validate and import all the remaining records.
ret = uv_timer_init(the_worker->loop, &z_import->timer);
if (ret) goto fail;
z_import->timer.data = z_import;
ret = uv_timer_start(&z_import->timer, zi_zone_process, ZONE_IMPORT_PAUSE, 0);
if (ret) goto fail;
return kr_ok();
fail:
if (z_import->cb)
z_import->cb(kr_error(ret), z_import->cb_param);
if (kr_fails_assert(ret))
ret = ENOENT;
ctx_delete(z_import);
return kr_error(ret);
}
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <stdbool.h>
#include <libknot/rrset.h>
#include "lib/defines.h"
/**
* Completion callback
*
* @param state 0 for OK completion, < 0 for errors (unfinished)
* @param param pointer to user data
*/
typedef void (*zi_callback)(int state, void *param);
typedef struct {
/* Parser, see zs_init() */
const char *zone_file;
const char *origin;
uint32_t ttl;
/// Source of time: current real time, or file modification time.
enum { ZI_STAMP_NOW = 0, ZI_STAMP_MTIM } time_src;
/* Validator */
bool downgrade; /// true -> disable validation
bool zonemd; /// true -> verify zonemd
const knot_rrset_t *ds; /// NULL -> use trust anchors
zi_callback cb;
void *cb_param;
} zi_config_t;
/** Import zone from a file.
*
* Error can be directly returned in the first phase (parsing + ZONEMD);
* otherwise it will be kr_ok() and config->cb gets (optionally) called finally.
*
* Large zone would pause other processing for longer time;
* that's generally not advisable.
*
* Zone origin is detected from SOA, but it's certainly not perfect now.
*/
KR_EXPORT
int zi_zone_import(const zi_config_t config);
$ORIGIN example.
example. 86400 IN SOA ns1 admin 2018031900 (
1800 900 604800 86400 )
86400 IN NS ns1
86400 IN NS ns2
86400 IN ZONEMD 2018031900 1 1 (
BAAAAAAADa7aed71
6bc459f9340e3d7c
1370d4d24b7e2fc3
a1ddc0b9a87153b9
a9713b3c9ae5cc27
777f98b8e730044c )
ns1 3600 IN A 203.0.113.63
ns2 3600 IN AAAA 2001:db8::63
$ORIGIN example.
example. 86400 IN SOA ns1 admin 2018031900 (
1800 900 604800 86400 )
86400 IN NS ns1
86400 IN NS ns2
86400 IN ZONEMD 2018031900 1 1 (
c68090d90a7aed71
6bc459f9340e3d7c
1370d4d24b7e2fc3
a1ddc0b9a87153b9
a9713b3c9ae5cc27
777f98b8e730044c )
ns1 3600 IN A 203.0.113.63
ns2 3600 IN AAAA 2001:db8::63
$ORIGIN example.
example. 86400 IN SOA ns1 admin 2018031900 (
1800 900 604800 86400 )
86400 IN NS ns1
86400 IN NS ns2
86400 IN ZONEMD 2018031900 1 1 (
a3b69bad980a3504
e1cffcb0fd6397f9
3848071c93151f55
2ae2f6b1711d4bd2
d8b39808226d7b9d
b71e34b72077f8fe )
ns1 3600 IN A 203.0.113.63
NS2 3600 IN AAAA 2001:db8::63
occluded.sub 7200 IN TXT "I'm occluded but must be digested"
sub 7200 IN NS ns1
duplicate 300 IN TXT "I must be digested just once"
duplicate 300 IN TXT "I must be digested just once"
foo.test. 555 IN TXT "out-of-zone data must be excluded"
UPPERCASE 3600 IN TXT "canonicalize uppercase owner names"
* 777 IN PTR dont-forget-about-wildcards
mail 3600 IN MX 20 MAIL1
mail 3600 IN MX 10 Mail2.Example.
sortme 3600 IN AAAA 2001:db8::5:61
sortme 3600 IN AAAA 2001:db8::3:62
sortme 3600 IN AAAA 2001:db8::4:63
sortme 3600 IN AAAA 2001:db8::1:65
sortme 3600 IN AAAA 2001:db8::2:64
non-apex 900 IN ZONEMD 2018031900 1 1 (
616c6c6f77656420
6275742069676e6f
7265642e20616c6c
6f77656420627574
2069676e6f726564
2e20616c6c6f7765 )
$ORIGIN example.
example. 86400 IN SOA ns1 admin 2018031900 (
1800 900 604800 86400 )
example. 86400 IN NS ns1.example.
example. 86400 IN NS ns2.example.
example. 86400 IN ZONEMD 2018031900 1 1 (
62e6cf51b02e54b9
b5f967d547ce4313
6792901f9f88e637
493daaf401c92c27
9dd10f0edb1c56f8
080211f8480ee306 )
example. 86400 IN ZONEMD 2018031900 1 2 (
08cfa1115c7b948c
4163a901270395ea
226a930cd2cbcf2f
a9a5e6eb85f37c8a
4e114d884e66f176
eab121cb02db7d65
2e0cc4827e7a3204
f166b47e5613fd27 )
example. 86400 IN ZONEMD 2018031900 1 240 (
e2d523f654b9422a
96c5a8f44607bbee )
example. 86400 IN ZONEMD 2018031900 241 1 (
e1846540e33a9e41
89792d18d5d131f6
05fc283e )
ns1.example. 3600 IN A 203.0.113.63
ns2.example. 86400 IN TXT "This example has multiple digests"
NS2.EXAMPLE. 3600 IN AAAA 2001:db8::63
$ORIGIN example.
;; White-space had to be changed from the RFC, as libzscanner only allows spaces in base64 on some places.
uri.arpa. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018100702 10800 3600 1209600 3600
uri.arpa. 3600 IN RRSIG SOA 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. GzQw+QzwLDJr13REPGVmpEChjD1D2XlX0ie1DnWHpgaEw1E/dhs3lCN3 +BmHd4Kx3tffTRgiyq65HxR6feQ5v7VmAifjyXUYB1DZur1eP5q0Ms2y gCB3byoeMgCNsFS1oKZ2LdzNBRpy3oace8xQn1SpmHGfyrsgg+WbHKCT 1dY=
uri.arpa. 86400 IN NS a.iana-servers.net.
uri.arpa. 86400 IN NS b.iana-servers.net.
uri.arpa. 86400 IN NS c.iana-servers.net.
uri.arpa. 86400 IN NS ns2.lacnic.net.
uri.arpa. 86400 IN NS sec3.apnic.net.
uri.arpa. 86400 IN RRSIG NS 8 2 86400 20210217232440 20210120232440 37444 uri.arpa. M+Iei2lcewWGaMtkPlrhM9FpUAHXFkCHTVpeyrjxjEONeNgKtHZor5e4 V4qJBOzNqo8go/qJpWlFBm+T5Hn3asaBZVstFIYky38/C8UeRLPKq1hT THARYUlFrexr5fMtSUAVOgOQPSBfH3xBq/BgSccTdRb9clD+HE7djpqr LS4=
uri.arpa. 600 IN MX 10 pechora.icann.org.
uri.arpa. 600 IN RRSIG MX 8 2 600 20210217232440 20210120232440 37444 uri.arpa. kQAJQivmv6A5hqYBK8h6Z13ESY69gmosXwKI6WE09I8RFetfrxr24ecd nYd0lpnDtgNNSoHkYRSOoB+C4+zuJsoyAAzGo9uoWMWj97/2xeGhf3PT C9meQ9Ohi6hul9By7OR76XYmGhdWX8PBi60RUmZ1guslFBfQ8izwPqzu phs=
uri.arpa. 3600 IN NSEC ftp.uri.arpa. NS SOA MX RRSIG NSEC DNSKEY ZONEMD
uri.arpa. 3600 IN RRSIG NSEC 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. dU/rXLM/naWd1+1PiWiYVaNJyCkiuyZJSccr91pJI673T8r3685B4ODM YFafZRboVgwnl3ZrXddY6xOhZL3n9V9nxXZwjLJ2HJUojFoKcXTlpnUy YUYvVQ2kj4GHAo6fcGCEp5QFJ2KbCpeJoS+PhKGRRx28icCiNT4/uXQv O2E=
uri.arpa. 3600 IN DNSKEY 256 3 8 AwEAAbMxuFuLeVDuOwIMzYOTD/bTREjLflo7wOi6ieIJhqltEzgjNzmW Jf9kGwwDmzxU7kbthMEhBNBZNn84zmcyRSCMzuStWveL7xmqqUlE3swL 8kLOvdZvc75XnmpHrk3ndTyEb6eZM7slh2C63Oh6K8VR5VkiZAkEGg0u ZIT3NjsF
uri.arpa. 3600 IN DNSKEY 257 3 8 AwEAAdkTaWkZtZuRh7/OobBUFxM+ytTst+bCu0r9w+rEwXD7GbDs0pIM hMenrZzoAvmv1fQxw2MGs6Ri6yPKfNULcFOSt9l8i6BVBLI+SKTY6XXe DUQpSEmSaxohHeRPMQFzpysfjxINp/L2rGtZ7yPmxY/XRiFPSO0myqwG Ja9r06Zw9CHM5UDHKWV/E+zxPFq/I7CfPbrrzbUotBX7Z6Vh3Sarllbe 8cGUB2UFNaTRgwB0TwDBPRD5ER3w2Dzbry9NhbElTr7vVfhaGWeOGuqA UXwlXEg6CrNkmJXJ2F1Rzr9WHUzhp7uWxhAbmJREGfi2dEyPAbUAyCjB qhFaqglknvc=
uri.arpa. 3600 IN DNSKEY 257 3 8 AwEAAenQaBoFmDmvRT+/H5oNbm0Tr5FmNRNDEun0Jpj/ELkzeUrTWhNp QmZeIMC8I0kZ185tEvOnRvn8OvV39B17QIdrvvKGIh2HlgeDRCLolhao jfn2QM0DStjF/WWHpxJOmE6CIuvhqYEU37yoJscGAPpPVPzNvnL1HhYT aao1VRYWQ/maMrJ+bfHg+YX1N6M/8MnRjIKBif1FWjbCKvsn6dnuGGL9 oCWYUFJ3DwofXuhgPyZMkzPc88YkJj5EMvbMH4wtelbCwC+ivx732l0w /rXJn0ciQSOgoeVvDio8dIJmWQITWQAuP+q/ZHFEFHPlrP3gvQh5mcVS 48eLX71Bq7c=
uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 20210217232440 20210120232440 12670 uri.arpa. DBE2gkKAoxJCfz47KKxzoImN/0AKArhIVHE7TyTwy0DdRPo44V5R+vL6 thUxlQ1CJi2Rw0jwAXymx5Y3Q873pOEllH+4bJoIT4dmoBmPXfYWW7Cl vw9UPKHRP0igKHmCVwIeBYDTU3gfLcMTbR4nEWPDN0GxlL1Mf7ITaC2I oabo79Ip3M/MR8I3Vx/xZ4ZKKPHtLn3xUuJluPNanqJrED2gTslL2xWZ 1tqjsAjJv7JnJo2HJ8XVRB5zBto0IaJ2oBlqcjdcQ/0VlyoM8uOy1pDw HQ2BJl7322gNMHBP9HSiUPIOaIDNUCwW8eUcW6DIUk+s9u3GN1uTqwWz sYB/rA==
uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 20210217232440 20210120232440 30577 uri.arpa. Kx6HwP4UlkGc1UZ7SERXtQjPajOF4iUvkwDj7MEG1xbQFB1KoJiEb/ei W0qmSWdIhMDv8myhgauejRLyJxwxz8HDRV4xOeHWnRGfWBk4XGYwkejV zOHzoIArVdUVRbr2JKigcTOoyFN+uu52cNB7hRYu7dH5y1hlc6UbOnzR pMtGxcgVyKQ+/ARbIqGG3pegdEOvV49wTPWEiyY65P2urqhvnRg5ok/j zwAdMx4XGshiib7Ojq0sRVl2ZIzj4rFgY/qsSO8SEXEhMo2VuSkoJNio fVzYoqpxEeGnANkIT7Tx2xJL1BWyJxyc7E8Wr2QSgCcc+rYL6IkHDtJG Hy7TaQ==
uri.arpa. 3600 IN ZONEMD 2018100702 1 1 0DBC3C4DBFD75777C12CA19C337854B1577799901307C482E9D91D5D 15CD934D16319D98E30C4201CF25A1D5A0254960
uri.arpa. 3600 IN RRSIG ZONEMD 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. QDo4XZcL3HMyn8aAHyCUsu/Tqj4Gkth8xY1EqByOb8XOTwVtA4ZNQORE 1siqNqjtJUbeJPtJSbLNqCL7rCq0CzNNnBscv6IIf4gnqJZjlGtHO30o hXtKvEc4z7SU3IASsi6bB3nLmEAyERdYSeU6UBfx8vatQDIRhkgEnnWU Th4=
ftp.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^ftp://([^:/?#]*).*$!\\1!i" .
ftp.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. EygekDgl+Lyyq4NMSEpPyOrOywYf9Y3FAB4v1DT44J3R5QGidaH8l7ZF jHoYFI8sY64iYOCV4sBnX/dh6C1L5NgpY+8l5065Xu3vvjyzbtuJ2k6Y YwJrrCbvl5DDn53zAhhO2hL9uLgyLraZGi9i7TFGd0sm3zNyUF/EVL0C cxU=
ftp.uri.arpa. 3600 IN NSEC http.uri.arpa. NAPTR RRSIG NSEC
ftp.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. pbP4KxevPXCu/bDqcvXiuBppXyFEmtHyiy0eAN5gS7mi6mp9Z9bWFjx/ LdH9+6oFGYa5vGmJ5itu/4EDMe8iQeZbI8yrpM4TquB7RR/MGfBnTd8S +sjyQtlRYG7yqEu77Vd78Fme22BKPJ+MVqjS0JHMUE/YUGomPkAjLJJw wGw=
http.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^http://([^:/?#]*).*$!\\1!i" .
http.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. eTqbWvt1GvTeXozuvm4ebaAfkXFQKrtdu0cEiExto80sHIiCbO0WL8UD a/J3cDivtQca7LgUbOb6c17NESsrsVkc6zNPx5RK2tG7ZQYmhYmtqtfg 1oU5BRdHZ5TyqIXcHlw9Blo2pir1Y9IQgshhD7UOGkbkEmvB1Lrd0aHh AAg=
http.uri.arpa. 3600 IN NSEC mailto.uri.arpa. NAPTR RRSIG NSEC
http.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. R9rlNzw1CVz2N08q6DhULzcsuUm0UKcPaGAWEU40tr81jEDHsFHNM+kh CdOI8nDstzA42aee4rwCEgijxJpRCcY9hrO1Ysrrr2fdqNz60JikMdar vU5O0p0VXeaaJDfJQT44+o+YXaBwI7Qod3FTMx7aRib8i7istvPm1Rr7 ixA=
mailto.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^mailto:(.*)@(.*)$!\\2!i" .
mailto.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. Ch2zTG2F1plEvQPyIH4Yd80XXLjXOPvMbiqDjpJBcnCJsV8QF7kr0wTL nUT3dB+asQudOjPyzaHGwFlMzmrrAsszN4XAMJ6htDtFJdsgTMP/NkHh YRSmVv6rLeAhd+mVfObY12M//b/GGVTjeUI/gJaLW0fLVZxr1Fp5U5CR jyw=
mailto.uri.arpa. 3600 IN NSEC urn.uri.arpa. NAPTR RRSIG NSEC
mailto.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. fQUbSIE6E7JDi2rosah4SpCOTrKufeszFyj5YEavbQuYlQ5cNFvtm8Ku E2xXMRgRI4RGvM2leVqcoDw5hS3m2pOJLxH8l2WE72YjYvWhvnwc5Rof e/8yB/vaSK9WCnqN8y2q6Vmy73AGP0fuiwmuBra7LlkOiqmyx3amSFiz wms=
urn.uri.arpa. 604800 IN NAPTR 0 0 "" "" "/urn:([^:]+)/\\1/i" .
urn.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. CVt2Tgz0e5ZmaSXqRfNys/8OtVCk9nfP0zhezhN8Bo6MDt6yyKZ2kEEW JPjkN7PCYHjO8fGjnUn0AHZI2qBNv7PKHcpR42VY03q927q85a65weOO 1YE0vPYMzACpua9TOtfNnynM2Ws0uN9URxUyvYkXBdqOC81N3sx1dVEL cwc=
urn.uri.arpa. 3600 IN NSEC uri.arpa. NAPTR RRSIG NSEC
urn.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. JuKkMiC3/j9iM3V8/izcouXWAVGnSZjkOgEgFPhutMqoylQNRcSkbEZQ zFK8B/PIVdzZF0Y5xkO6zaKQjOzz6OkSaNPIo1a7Vyyl3wDY/uLCRRAH RJfpknuY7O+AUNXvVVIEYJqZggd4kl/Rjh1GTzPYZTRrVi5eQidI1LqC Oeg=
root-servers.net. 3600000 IN SOA a.root-servers.net. (
nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 )
root-servers.net. 3600000 IN NS a.root-servers.net.
root-servers.net. 3600000 IN NS b.root-servers.net.
root-servers.net. 3600000 IN NS c.root-servers.net.
root-servers.net. 3600000 IN NS d.root-servers.net.
root-servers.net. 3600000 IN NS e.root-servers.net.
root-servers.net. 3600000 IN NS f.root-servers.net.
root-servers.net. 3600000 IN NS g.root-servers.net.
root-servers.net. 3600000 IN NS h.root-servers.net.
root-servers.net. 3600000 IN NS i.root-servers.net.
root-servers.net. 3600000 IN NS j.root-servers.net.
root-servers.net. 3600000 IN NS k.root-servers.net.
root-servers.net. 3600000 IN NS l.root-servers.net.
root-servers.net. 3600000 IN NS m.root-servers.net.
a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30
a.root-servers.net. 3600000 IN A 198.41.0.4
b.root-servers.net. 3600000 IN MX 20 mail.isi.edu.
b.root-servers.net. 3600000 IN AAAA 2001:500:200::b
b.root-servers.net. 3600000 IN A 199.9.14.201
c.root-servers.net. 3600000 IN AAAA 2001:500:2::c
c.root-servers.net. 3600000 IN A 192.33.4.12
d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d
d.root-servers.net. 3600000 IN A 199.7.91.13
e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e
e.root-servers.net. 3600000 IN A 192.203.230.10
f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f
f.root-servers.net. 3600000 IN A 192.5.5.241
g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d
g.root-servers.net. 3600000 IN A 192.112.36.4
h.root-servers.net. 3600000 IN AAAA 2001:500:1::53
h.root-servers.net. 3600000 IN A 198.97.190.53
i.root-servers.net. 3600000 IN MX 10 mx.i.root-servers.org.
i.root-servers.net. 3600000 IN AAAA 2001:7fe::53
i.root-servers.net. 3600000 IN A 192.36.148.17
j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30
j.root-servers.net. 3600000 IN A 192.58.128.30
k.root-servers.net. 3600000 IN AAAA 2001:7fd::1
k.root-servers.net. 3600000 IN A 193.0.14.129
l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42
l.root-servers.net. 3600000 IN A 199.7.83.42
m.root-servers.net. 3600000 IN AAAA 2001:dc3::35
m.root-servers.net. 3600000 IN A 202.12.27.33
root-servers.net. 3600000 IN SOA a.root-servers.net. (
nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 )
root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 (
f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97
8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 )
-- unload modules which are not related to this test
-- SPDX-License-Identifier: GPL-3.0-or-later
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
-- do not listen, test is driven by config code
env.KRESD_NO_LISTEN = true
cache.size = 5*MB
log_groups({'prefil'})
--[[ This test checks ZONEMD computation on some model cases. (no DNSSEC validation)
https://www.rfc-editor.org/rfc/rfc8976.html#name-example-zones-with-digests
--]]
local function test_zone(file_name, success) return function()
local import_res = require('ffi').C.zi_zone_import({
zone_file = file_name,
zonemd = true,
downgrade = true,
})
if success == nil or success then
is(import_res, 0, 'zone import should start OK for file ' .. file_name)
else
isnt(import_res, 0, 'zone import should fail for file ' .. file_name)
end
worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
end end
return {
test_zone('tz-rfc-a1.zone'),
test_zone('tz-rfc-a1-bad.zone', false),
test_zone('tz-rfc-a2.zone'),
test_zone('tz-rfc-a3.zone'),
test_zone('tz-rfc-a4.zone'),
test_zone('tz-rfc-a5.zone'),
}
[project]
name = "knot-resolver"
# needed for make-archive
make_archive_script = "scripts/make-archive.sh"
[upstream]
# needed for get-archive
archive_url = "https://secure.nic.cz/files/knot-resolver/knot-resolver-{{ version }}.tar.xz"
signature_url = "https://secure.nic.cz/files/knot-resolver/knot-resolver-{{ version }}.tar.xz.asc"
version_script = "scripts/lib/upstream-version.sh"
[apkg]
compat = 4
# Maintainer: Knot Resolver team <knot-resolver@labs.nic.cz>
# Maintainer: Vladimír Čunát <vladimir.cunat@nic.cz>
# Contributor: Nicki Křížek <nicki@isc.org>
# Contributor: Ondřej Surý <ondrej@sury.org>
# Contributor: Oto Šťáva <oto.stava@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
pkgname=knot-resolver
pkgver={{ version }}
pkgrel={{ release }}
pkgdesc='Caching DNSSEC-validating DNS resolver'
arch=('x86_64' 'armv7h')
url='https://www.knot-resolver.cz/'
license=('GPL3')
depends=(
'dnssec-anchors'
'gnutls'
'knot'
'libuv'
'lmdb'
'luajit'
'systemd'
'libcap-ng'
'libnghttp2'
'jemalloc'
'python'
'python-yaml'
'python-aiohttp'
'python-typing_extensions'
'python-jinja'
'supervisor'
)
makedepends=(
'cmocka'
'meson'
'pkgconfig'
'python-build'
'python-installer'
'python-poetry'
'python-wheel'
'systemd-libs'
)
optdepends=(
'lua51-basexx: experimental_dot_auth module'
'lua51-cqueues: http and dns64 module, policy.rpz() function'
'lua51-http: http and prefill modules, trust_anchors bootstrap'
'lua51-psl: policy.slice_randomize_psl() function'
'python-prometheus_client: stats and metrics in Prometheus format'
'python-watchdog: files monitoring and reload on changes'
)
backup=('etc/knot-resolver/config.yaml')
options=(debug strip)
source=("knot-resolver-${pkgver}.tar.xz")
sha256sums=('SKIP')
build() {
cd "${srcdir}/${pkgname}-${pkgver}"
meson setup build \
--buildtype=release \
--prefix=/usr \
--sbindir=bin \
-D keyfile_default=/etc/trusted-key.key \
-D systemd_files=enabled \
-D malloc=jemalloc \
-D unit_tests=enabled
ninja -C build
python -Pm build --wheel --no-isolation
}
check() {
cd "${srcdir}/${pkgname}-${pkgver}"
meson test -C build
}
package() {
cd "${srcdir}/${pkgname}-${pkgver}"
DESTDIR=${pkgdir} ninja -C build install
# add knot-resolver.service to multi-user.target.wants to support enabling the service
install -d -m 0755 "${pkgdir}/usr/lib/systemd/system/multi-user.target.wants"
ln -s ../knot-resolver.service ${pkgdir}/usr/lib/systemd/system/multi-user.target.wants/knot-resolver.service
# remove modules with missing dependencies
rm "${pkgdir}/usr/lib/knot-resolver/kres_modules/etcd.lua"
# install knot-resolver Python module
python -Pm installer --destdir="$pkgdir" dist/*.whl
install -m 644 -D etc/config/config.yaml ${pkgdir}/etc/knot-resolver/config.yaml
}
knot-resolver ({{ version }}-cznic.{{ release }}) unstable; urgency=medium
* upstream package
* see NEWS or https://knot-resolver.cz
-- Jakub Ružička <jakub.ruzicka@nic.cz> {{ now }}
build_deb/
12
Source: knot-resolver
Section: net
Priority: optional
Maintainer: Knot Resolver <knot-resolver@labs.nic.cz>
Build-Depends:
debhelper (>= 12~),
dh-python,
libcap-ng-dev,
libcmocka-dev (>= 1.0.0),
libfstrm-dev,
libgnutls28-dev,
libjemalloc-dev,
libknot-dev (>= 3.0.2),
liblmdb-dev,
libluajit-5.1-dev,
libnghttp2-dev,
libprotobuf-c-dev,
libssl-dev,
libsystemd-dev (>= 227) [linux-any],
libuv1-dev,
luajit,
meson (>= 0.49),
pkg-config,
protobuf-c-compiler,
python3,
python3-dev,
python3-setuptools,
Standards-Version: 4.7.0
Homepage: https://www.knot-resolver.cz/
Rules-Requires-Root: no
Package: knot-resolver6
Architecture: any
Depends:
adduser,
dns-root-data,
python3-aiohttp,
python3-jinja2,
python3-yaml,
supervisor,
systemd,
${misc:Depends},
${python3:Depends},
${shlibs:Depends},
Breaks:
knot-resolver (<< 6),
knot-resolver-core (<< 6.0.8),
knot-resolver-manager (<< 6.0.8),
Replaces:
knot-resolver (<< 6),
knot-resolver-core (<< 6.0.8),
knot-resolver-manager (<< 6.0.8),
Recommends:
lua-basexx,
lua-cqueues,
lua-http,
lua-psl,
python3-prometheus-client,
python3-watchdog,
Suggests:
knot-resolver6-module-http,
Description: caching, DNSSEC-validating DNS resolver - core binaries
Knot Resolver is a caching full resolver implementation
written in C and LuaJIT, including both a resolver library and a
daemon. Modular architecture of the library keeps the core tiny and
efficient, and provides a state-machine like API for
extensions. There are three built-in modules - iterator, cache,
validator, and many external.
.
The Lua modules, switchable and shareable cache, and fast FFI
bindings makes it great to tap into resolution process, or be used
for your recursive DNS service. It's the OpenResty of DNS.
.
The server adopts a different scaling strategy than the rest of the
DNS recursors - no threading, shared-nothing architecture (except
MVCC cache that may be shared). You can start and stop additional
nodes depending on the contention without downtime.
.
Knot Resolver Manager hides the complexity of running several independent
resolver processes while ensuring zero-downtime reconfiguration with YAML/JSON
declarative configuration and an optional HTTP API for dynamic changes.
Package: knot-resolver6-dev
Architecture: any
Depends:
knot-resolver6 (= ${binary:Version}),
${misc:Depends},
Section: libdevel
Description: Knot Resolver development files
Knot Resolver is a caching full resolver implementation
written in C and LuaJIT, including both a resolver library and a
daemon. Modular architecture of the library keeps the core tiny and
efficient, and provides a state-machine like API for
extensions.
.
This package provides the development headers for Knot Resolver.
Package: knot-resolver6-module-dnstap
Architecture: any
Depends:
knot-resolver6 (= ${binary:Version}),
libfstrm0,
libprotobuf-c1,
${misc:Depends},
${shlibs:Depends},
Description: dnstap module for Knot Resolver
The Knot Resolver is a caching full resolver implementation
written in C and LuaJIT, including both a resolver library and a
daemon. Modular architecture of the library keeps the core tiny and
efficient, and provides a state-machine like API for
extensions. There are three built-in modules - iterator, cache,
validator, and many external.
.
This package contains dnstap module for logging DNS responses
to a unix socket in dnstap format.
Package: knot-resolver6-module-http
Architecture: all
Depends:
knot-resolver6 (>= ${source:Version}),
libjs-bootstrap,
libjs-d3,
libjs-jquery,
lua-cqueues (>= 20171014),
lua-http,
lua-mmdb,
systemd,
${misc:Depends},
Description: HTTP module for Knot Resolver
The Knot Resolver is a caching full resolver implementation
written in C and LuaJIT, including both a resolver library and a
daemon. Modular architecture of the library keeps the core tiny and
efficient, and provides a state-machine like API for
extensions. There are three built-in modules - iterator, cache,
validator, and many external.
.
This package contains HTTP/2 module for local visualization of the
resolver cache and queries.
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: knot-resolver
Source: https://www.knot-resolver.cz/
Files: *
Copyright: CZ.NIC
License: GPL-3.0+
Files: contrib/ccan/asprintf/*
Copyright: Rusty Russell
License: Expat
Files: contrib/ccan/compiler/*
Copyright: Rusty Russell
License: CC0
Files: tests/config/tapered/*
Copyright: 2012-2017, Peter Aronoff
License: BSD-3-clause
Files: contrib/murmurhash3/*
Copyright: Austin Appleby
License: CC0-1.0
Files: modules/http/static/dygraph.min.js
Copyright: 2006-2014 Dan Vanderkam <danvdk@gmail.com>
2016 Paul Miller
2011 Robert Konigsberg <konigsberg@google.com>
2013 David Eberlein <david.eberlein@ch.sauter-bc.com>
License: MIT
Files: contrib/ucw/*
Copyright: 1997-2015 Martin Mares
2005-2014 Tomas Valla
2006 Robert Spalek
2007-2015 Pavel Charvat
License: LGPL-2.1
Files: contrib/ccan/json/*
Copyright: 2011 Joey Adams
License: Expat
Files: modules/policy/lua-aho-corasick/*
Copyright: 2013 CloudFlare, Inc.
License: BSD-3-CloudFlare
Files: modules/http/static/jquery.js
Copyright: 2005-2011 John Resig, Brandon Aaron & Jörn Zaefferer
License: GPL-2 or Expat
Files: modules/http/static/d3.js
modules/http/static/topojson.js
Copyright: 2010-2015 Michael Bostock
License: BSD-3-clause
Files: modules/http/static/epoch.js
modules/http/static/epoch.css
Copyright: 2014 Fastly, Inc.
License: Expat
Files: modules/http/static/datamaps.world.min.js
Copyright: 2012 Mark DiMarco
License: Expat
Files: modules/http/static/bootstrap.min.css
modules/http/static/bootstrap.min.js
modules/http/static/bootstrap-theme.min.css
modules/http/static/glyphicons-halflings-regular.woff2
Copyright: 2012-2016 Thomas Park
2011-2015 Twitter, Inc.
License: Expat
Files: modules/http/static/selectize.min.js
modules/http/static/selectize.bootstrap3.css
Copyright: 2013–2015 Brian Reavis & contributors
License: Apache-2.0
Files: debian/*
Copyright: 2015 Ondřej Surý <ondrej@debian.org>
License: GPL-3.0+
License: LGPL-2.1
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/LGPL-2.1".
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
License: Expat
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License: CC0
Statement of Purpose
.
The laws of most jurisdictions throughout the world automatically
confer exclusive Copyright and Related Rights (defined below) upon
the creator and subsequent owner(s) (each and all, an "owner") of an
original work of authorship and/or a database (each, a "Work").
.
Certain owners wish to permanently relinquish those rights to a Work
for the purpose of contributing to a commons of creative, cultural
and scientific works ("Commons") that the public can reliably and
without fear of later claims of infringement build upon, modify,
incorporate in other works, reuse and redistribute as freely as
possible in any form whatsoever and for any purposes, including
without limitation commercial purposes. These owners may contribute
to the Commons to promote the ideal of a free culture and the further
production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the
use and efforts of others.
.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he
or she is an owner of Copyright and Related Rights in the Work,
voluntarily elects to apply CC0 to the Work and publicly distribute
the Work under its terms, with knowledge of his or her Copyright and
Related Rights in the Work and the meaning and intended legal effect
of CC0 on those rights.
.
1. Copyright and Related Rights. A Work made available under CC0 may
be protected by copyright and related or neighboring rights
("Copyright and Related Rights"). Copyright and Related Rights
include, but are not limited to, the following:
.
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or
performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a
Work, subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse
of data in a Work;
vi. database rights (such as those arising under Directive 96/9/EC
of the European Parliament and of the Council of 11 March 1996
on the legal protection of databases, and under any national
implementation thereof, including any amended or successor
version of such directive); and
vii. other similar, equivalent or corresponding rights throughout
the world based on applicable law or treaty, and any national
implementations thereof.
.
2. Waiver. To the greatest extent permitted by, but not in
contravention of, applicable law, Affirmer hereby overtly, fully,
permanently, irrevocably and unconditionally waives, abandons, and
surrenders all of Affirmer's Copyright and Related Rights and
associated claims and causes of action, whether now known or
unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for
the maximum duration provided by applicable law or treaty
(including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose
whatsoever, including without limitation commercial, advertising
or promotional purposes (the "Waiver"). Affirmer makes the Waiver
for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that
such Waiver shall not be subject to revocation, rescission,
cancellation, termination, or any other legal or equitable action
to disrupt the quiet enjoyment of the Work by the public as
contemplated by Affirmer's express Statement of Purpose.
.
3. Public License Fallback. Should any part of the Waiver for any
reason be judged legally invalid or ineffective under applicable
law, then the Waiver shall be preserved to the maximum extent
permitted taking into account Affirmer's express Statement of
Purpose. In addition, to the extent the Waiver is so judged
Affirmer hereby grants to each affected person a royalty-free, non
transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related
Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including
future time extensions), (iii) in any current or future medium and
for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed
effective as of the date CC0 was applied by Affirmer to the
Work. Should any part of the License for any reason be judged
legally invalid or ineffective under applicable law, such partial
invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he
or she will not (i) exercise any of his or her remaining Copyright
and Related Rights in the Work or (ii) assert any associated
claims and causes of action with respect to the Work, in either
case contrary to Affirmer's express Statement of Purpose.
.
4. Limitations and Disclaimers.
.
a. No trademark or patent rights held by Affirmer are waived,
abandoned, surrendered, licensed or otherwise affected by this
document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties
of title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects,
accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under
applicable law.
c. Affirmer disclaims responsibility for clearing rights of other
persons that may apply to the Work or any use thereof, including
without limitation any person's Copyright and Related Rights in
the Work. Further, Affirmer disclaims responsibility for
obtaining any necessary consents, permissions or other rights
required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is
not a party to this document and has no duty or obligation with
respect to this CC0 or use of the Work.
License: BSD-3-CloudFlare
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
.
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
.
3. Neither the name of CloudFlare, Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: BSD-3-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
.
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
.
3. Neither the name of the author nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
License: GPL-2
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
.
On Debian systems, the complete text of the GNU General Public
License version 2 can be found in "/usr/share/common-licenses/GPL-2".
License: Apache-2.0
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
.
https://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian systems, the full text of the Apache Software License version 2 can
be found in the file `/usr/share/common-licenses/Apache-2.0'.
License: MIT
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License: CC0-1.0
This work is licensed under the "Creative Commons Zero" license.
.
On debian systems, a copy of the Creative Commons Zero license may be
found at /usr/share/common-licenses/CC0-1.0.
License: public-domain
This work has been released into the public domain. The map
implementation builds off of prior public domain work from Dan
Bernstein (qhasm) and Adam Langley (critbit).