Commit f4e78b04 authored by Petr Špaček's avatar Petr Špaček

Merge branch 'sim_qry' into 'master'

outbound TCP connections sharing; TLS over outbound TCP connections

Closes #104

See merge request knot/knot-resolver!379
parents 5aea7a1f cfea59ca
......@@ -134,6 +134,7 @@ respdiff:iter:udp:linux:amd64:
stage: test
script:
- source <(./scripts/coverage_env.sh "$(pwd)" "$(pwd)/coverage.stats/respdiff" "iter/udp" --export)
- ulimit -n "$(ulimit -Hn)" # applies only for kresd ATM
- PREFIX=$(pwd)/.local ./ci/respdiff/start-resolvers.sh
- ./ci/respdiff/run-respdiff-tests.sh udp
- cat results/respdiff.txt
......
......@@ -398,6 +398,187 @@ static int net_tls(lua_State *L)
return 1;
}
static int print_tls_param(const char *key, void *val, void *data)
{
if (!val) {
return 0;
}
struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)val;
lua_State *L = (lua_State *)data;
lua_createtable(L, 0, 3);
lua_createtable(L, entry->pins.len, 0);
for (size_t i = 0; i < entry->pins.len; ++i) {
lua_pushnumber(L, i + 1);
lua_pushstring(L, entry->pins.at[i]);
lua_settable(L, -3);
}
lua_setfield(L, -2, "pins");
lua_createtable(L, entry->ca_files.len, 0);
for (size_t i = 0; i < entry->ca_files.len; ++i) {
lua_pushnumber(L, i + 1);
lua_pushstring(L, entry->ca_files.at[i]);
lua_settable(L, -3);
}
lua_setfield(L, -2, "ca_files");
lua_createtable(L, entry->hostnames.len, 0);
for (size_t i = 0; i < entry->hostnames.len; ++i) {
lua_pushnumber(L, i + 1);
lua_pushstring(L, entry->hostnames.at[i]);
lua_settable(L, -3);
}
lua_setfield(L, -2, "hostnames");
lua_setfield(L, -2, key);
return 0;
}
static int print_tls_client_params(lua_State *L)
{
struct engine *engine = engine_luaget(L);
if (!engine) {
return 0;
}
struct network *net = &engine->net;
if (!net) {
return 0;
}
if (net->tls_client_params.root == 0 ) {
return 0;
}
lua_newtable(L);
map_walk(&net->tls_client_params, print_tls_param, (void *)L);
return 1;
}
static int net_tls_client(lua_State *L)
{
struct engine *engine = engine_luaget(L);
if (!engine) {
return 0;
}
struct network *net = &engine->net;
if (!net) {
return 0;
}
/* Only return current credentials. */
if (lua_gettop(L) == 0) {
return print_tls_client_params(L);
}
const char *full_addr = NULL;
bool pin_exists = false;
bool ca_file_exists = false;
if ((lua_gettop(L) == 1) && lua_isstring(L, 1)) {
full_addr = lua_tostring(L, 1);
} else if ((lua_gettop(L) == 2) && lua_isstring(L, 1) && lua_istable(L, 2)) {
full_addr = lua_tostring(L, 1);
pin_exists = true;
} else if ((lua_gettop(L) == 3) && lua_isstring(L, 1) && lua_istable(L, 2)) {
full_addr = lua_tostring(L, 1);
ca_file_exists = true;
} else if ((lua_gettop(L) == 4) && lua_isstring(L, 1) &&
lua_istable(L, 2) && lua_istable(L, 3)) {
full_addr = lua_tostring(L, 1);
pin_exists = true;
ca_file_exists = true;
} else {
format_error(L, "net.tls_client takes one parameter (\"address\"), two parameters (\"address\",\"pin\"), three parameters (\"address\", \"ca_file\", \"hostname\") or four ones: (\"address\", \"pin\", \"ca_file\", \"hostname\")");
lua_error(L);
}
char addr[INET6_ADDRSTRLEN];
uint16_t port = 0;
if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) {
format_error(L, "invalid IP address");
lua_error(L);
}
if (port == 0) {
port = 853;
}
if (!pin_exists && !ca_file_exists) {
int r = tls_client_params_set(&net->tls_client_params,
addr, port, NULL, NULL, NULL);
if (r != 0) {
lua_pushstring(L, strerror(ENOMEM));
lua_error(L);
}
lua_pushboolean(L, true);
return 1;
}
if (pin_exists) {
/* iterate over table with pins
* http://www.lua.org/manual/5.1/manual.html#lua_next */
lua_pushnil(L); /* first key */
while (lua_next(L, 2)) { /* pin table is in stack at index 2 */
/* pin now at index -1, key at index -2*/
const char *pin = lua_tostring(L, -1);
int r = tls_client_params_set(&net->tls_client_params,
addr, port, NULL, NULL, pin);
if (r != 0) {
lua_pushstring(L, strerror(ENOMEM));
lua_error(L);
}
lua_pop(L, 1);
}
}
int ca_table_index = 2;
int hostname_table_index = 3;
if (ca_file_exists) {
if (pin_exists) {
ca_table_index = 3;
hostname_table_index = 4;
}
} else {
lua_pushboolean(L, true);
return 1;
}
/* iterate over ca filenames */
lua_pushnil(L);
while (lua_next(L, ca_table_index)) {
const char *ca_file = lua_tostring(L, -1);
int r = tls_client_params_set(&net->tls_client_params,
addr, port, ca_file, NULL, NULL);
if (r != 0) {
lua_pushstring(L, strerror(ENOMEM));
lua_error(L);
}
/* removes 'value'; keeps 'key' for next iteration */
lua_pop(L, 1);
}
/* iterate over hostnames */
lua_pushnil(L);
while (lua_next(L, hostname_table_index)) {
const char *hostname = lua_tostring(L, -1);
int r = tls_client_params_set(&net->tls_client_params,
addr, port, NULL, hostname, NULL);
if (r != 0) {
lua_pushstring(L, strerror(ENOMEM));
lua_error(L);
}
/* removes 'value'; keeps 'key' for next iteration */
lua_pop(L, 1);
}
lua_pushboolean(L, true);
return 1;
}
static int net_tls_padding(lua_State *L)
{
struct engine *engine = engine_luaget(L);
......@@ -508,6 +689,8 @@ int lib_net(lua_State *L)
{ "bufsize", net_bufsize },
{ "tcp_pipeline", net_pipeline },
{ "tls", net_tls },
{ "tls_server", net_tls },
{ "tls_client", net_tls_client },
{ "tls_padding", net_tls_padding },
{ "outgoing_v4", net_outgoing_v4 },
{ "outgoing_v6", net_outgoing_v6 },
......
......@@ -33,6 +33,8 @@
} \
} while (0)
void io_release(uv_handle_t *handle);
static void check_bufsize(uv_handle_t* handle)
{
/* We want to buffer at least N waves in advance.
......@@ -48,15 +50,18 @@ static void check_bufsize(uv_handle_t* handle)
static void session_clear(struct session *s)
{
assert(s->outgoing || s->tasks.len == 0);
assert(s->tasks.len == 0 && s->waiting.len == 0);
array_clear(s->tasks);
array_clear(s->waiting);
tls_free(s->tls_ctx);
tls_client_ctx_free(s->tls_client_ctx);
memset(s, 0, sizeof(*s));
}
void session_free(struct session *s)
{
if (s) {
assert(s->tasks.len == 0 && s->waiting.len == 0);
session_clear(s);
free(s);
}
......@@ -89,6 +94,8 @@ static void session_release(struct worker_ctx *worker, uv_handle_t *handle)
if (!s) {
return;
}
assert(s->waiting.len == 0 && s->tasks.len == 0);
assert(s->buffering == NULL);
if (!s->outgoing && handle->type == UV_TCP) {
worker_end_tcp(worker, handle); /* to free the buffering task */
}
......@@ -103,7 +110,7 @@ static void session_release(struct worker_ctx *worker, uv_handle_t *handle)
static uv_stream_t *handle_alloc(uv_loop_t *loop)
{
uv_stream_t *handle = calloc(1, sizeof(*handle));
uv_stream_t *handle = calloc(1, sizeof(uv_handles_t));
if (!handle) {
return NULL;
}
......@@ -111,6 +118,17 @@ static uv_stream_t *handle_alloc(uv_loop_t *loop)
return handle;
}
static uv_stream_t *handle_borrow(uv_loop_t *loop)
{
struct worker_ctx *worker = loop->data;
void *req = worker_iohandle_borrow(worker);
if (!req) {
return NULL;
}
return (uv_stream_t *)req;
}
static void handle_getbuf(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
{
/* Worker has single buffer which is reused for all incoming
......@@ -139,13 +157,19 @@ void udp_recv(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf,
{
uv_loop_t *loop = handle->loop;
struct worker_ctx *worker = loop->data;
struct session *s = handle->data;
if (s->closing) {
return;
}
if (nread <= 0) {
if (nread < 0) { /* Error response, notify resolver */
worker_submit(worker, (uv_handle_t *)handle, NULL, addr);
} /* nread == 0 is for freeing buffers, we don't need to do this */
return;
}
if (addr->sa_family == AF_UNSPEC) {
return;
}
knot_pkt_t *query = knot_pkt_new(buf->base, nread, &worker->pkt_pool);
if (query) {
query->max_size = KNOT_WIRE_MAX_PKTSIZE;
......@@ -158,8 +182,11 @@ static int udp_bind_finalize(uv_handle_t *handle)
{
check_bufsize((uv_handle_t *)handle);
/* Handle is already created, just create context. */
handle->data = session_new();
assert(handle->data);
struct session *session = session_new();
assert(session);
session->outgoing = false;
session->handle = handle;
handle->data = session;
return io_start_read((uv_handle_t *)handle);
}
......@@ -189,20 +216,17 @@ int udp_bindfd(uv_udp_t *handle, int fd)
return udp_bind_finalize((uv_handle_t *)handle);
}
static void tcp_timeout(uv_handle_t *timer)
{
uv_handle_t *handle = timer->data;
uv_close(handle, io_free);
}
static void tcp_timeout_trigger(uv_timer_t *timer)
{
uv_handle_t *handle = timer->data;
struct session *session = handle->data;
struct session *session = timer->data;
struct worker_ctx *worker = timer->loop->data;
assert(session->outgoing == false);
if (session->tasks.len > 0) {
uv_timer_again(timer);
} else {
uv_close((uv_handle_t *)timer, tcp_timeout);
uv_timer_stop(timer);
worker_session_close(session);
}
}
......@@ -210,12 +234,24 @@ static void tcp_recv(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf)
{
uv_loop_t *loop = handle->loop;
struct session *s = handle->data;
if (s->closing) {
return;
}
/* nread might be 0, which does not indicate an error or EOF.
* This is equivalent to EAGAIN or EWOULDBLOCK under read(2). */
if (nread == 0) {
return;
}
if (nread == UV_EOF) {
nread = 0;
}
struct worker_ctx *worker = loop->data;
/* TCP pipelining is rather complicated and requires cooperation from the worker
* so the whole message reassembly and demuxing logic is inside worker */
int ret = 0;
if (s->has_tls) {
ret = tls_process(worker, handle, (const uint8_t *)buf->base, nread);
ret = s->outgoing ? tls_client_process(worker, handle, (const uint8_t *)buf->base, nread) :
tls_process(worker, handle, (const uint8_t *)buf->base, nread);
} else {
ret = worker_process_tcp(worker, handle, (const uint8_t *)buf->base, nread);
}
......@@ -226,14 +262,14 @@ static void tcp_recv(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf)
if (!s->outgoing && !uv_is_closing((uv_handle_t *)&s->timeout)) {
uv_timer_stop(&s->timeout);
if (s->tasks.len == 0) {
uv_close((uv_handle_t *)&s->timeout, tcp_timeout);
worker_session_close(s);
} else { /* If there are tasks running, defer until they finish. */
uv_timer_start(&s->timeout, tcp_timeout_trigger, 1, KR_CONN_RTT_MAX/2);
}
}
/* Connection spawned at least one request, reset its deadline for next query.
* https://tools.ietf.org/html/rfc7766#section-6.2.3 */
} else if (ret > 0 && !s->outgoing) {
} else if (ret > 0 && !s->outgoing && !s->closing) {
uv_timer_again(&s->timeout);
}
mp_flush(worker->pkt_pool.ctx);
......@@ -244,14 +280,15 @@ static void _tcp_accept(uv_stream_t *master, int status, bool tls)
if (status != 0) {
return;
}
uv_stream_t *client = handle_alloc(master->loop);
uv_stream_t *client = handle_borrow(master->loop);
if (!client) {
return;
}
memset(client, 0, sizeof(*client));
io_create(master->loop, (uv_handle_t *)client, SOCK_STREAM);
if (uv_accept(master, client) != 0) {
uv_close((uv_handle_t *)client, io_free);
uv_close((uv_handle_t *)client, io_release);
return;
}
......@@ -259,13 +296,21 @@ static void _tcp_accept(uv_stream_t *master, int status, bool tls)
* It will re-check every half of a request time limit if the connection
* is idle and should be terminated, this is an educated guess. */
struct session *session = client->data;
assert(session->outgoing == false);
struct sockaddr *addr = &(session->peer.ip);
int addr_len = sizeof(union inaddr);
int ret = uv_tcp_getpeername((uv_tcp_t *)client, addr, &addr_len);
if (ret || addr->sa_family == AF_UNSPEC) {
worker_session_close(session);
return;
}
session->has_tls = tls;
if (tls && !session->tls_ctx) {
session->tls_ctx = tls_new(master->loop->data);
}
uv_timer_t *timer = &session->timeout;
uv_timer_init(master->loop, timer);
timer->data = client;
uv_timer_start(timer, tcp_timeout_trigger, KR_CONN_RTT_MAX/2, KR_CONN_RTT_MAX/2);
io_start_read((uv_handle_t *)client);
}
......@@ -371,16 +416,21 @@ int tcp_bindfd_tls(uv_tcp_t *handle, int fd)
void io_create(uv_loop_t *loop, uv_handle_t *handle, int type)
{
int ret = -1;
if (type == SOCK_DGRAM) {
uv_udp_init(loop, (uv_udp_t *)handle);
} else {
uv_tcp_init(loop, (uv_tcp_t *)handle);
ret = uv_udp_init(loop, (uv_udp_t *)handle);
} else if (type == SOCK_STREAM) {
ret = uv_tcp_init(loop, (uv_tcp_t *)handle);
uv_tcp_nodelay((uv_tcp_t *)handle, 1);
}
assert(ret == 0);
struct worker_ctx *worker = loop->data;
handle->data = session_borrow(worker);
assert(handle->data);
struct session *session = session_borrow(worker);
assert(session);
session->handle = handle;
handle->data = session;
session->timeout.data = session;
uv_timer_init(worker->loop, &session->timeout);
}
void io_deinit(uv_handle_t *handle)
......@@ -388,6 +438,7 @@ void io_deinit(uv_handle_t *handle)
if (!handle) {
return;
}
struct session *session = handle->data;
uv_loop_t *loop = handle->loop;
if (loop && loop->data) {
struct worker_ctx *worker = loop->data;
......@@ -407,13 +458,25 @@ void io_free(uv_handle_t *handle)
free(handle);
}
void io_release(uv_handle_t *handle)
{
if (!handle) {
return;
}
uv_loop_t *loop = handle->loop;
struct worker_ctx *worker = loop->data;
io_deinit(handle);
worker_iohandle_release(worker, handle);
}
int io_start_read(uv_handle_t *handle)
{
if (handle->type == UV_UDP) {
return uv_udp_recv_start((uv_udp_t *)handle, &handle_getbuf, &udp_recv);
} else {
} else if (handle->type == UV_TCP) {
return uv_read_start((uv_stream_t *)handle, &handle_getbuf, &tcp_recv);
}
assert(false);
}
int io_stop_read(uv_handle_t *handle)
......
......@@ -18,22 +18,35 @@
#include <uv.h>
#include <libknot/packet/pkt.h>
#include <gnutls/gnutls.h>
#include "lib/generic/array.h"
#include "daemon/worker.h"
struct qr_task;
struct tls_ctx_t;
struct tls_client_ctx_t;
/* Per-session (TCP or UDP) persistent structure,
* that exists between remote counterpart and a local socket.
*/
struct session {
bool outgoing;
bool outgoing; /**< True: to upstream; false: from a client. */
bool throttled;
bool has_tls;
bool connected;
bool closing;
union inaddr peer;
uv_handle_t *handle;
uv_timer_t timeout;
struct qr_task *buffering; /**< Worker buffers the incomplete TCP query here. */
struct tls_ctx_t *tls_ctx;
array_t(struct qr_task *) tasks;
struct tls_client_ctx_t *tls_client_ctx;
uint8_t msg_hdr[4]; /**< Buffer for DNS message header. */
ssize_t msg_hdr_idx; /**< The number of bytes in msg_hdr filled so far. */
qr_tasklist_t tasks;
qr_tasklist_t waiting;
ssize_t bytes_to_skip;
};
void session_free(struct session *s);
......
......@@ -18,6 +18,7 @@
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <libgen.h>
#include <uv.h>
......
......@@ -51,6 +51,7 @@ void network_init(struct network *net, uv_loop_t *loop)
if (net != NULL) {
net->loop = loop;
net->endpoints = map_make();
net->tls_client_params = map_make();
}
}
......@@ -106,6 +107,7 @@ void network_deinit(struct network *net)
map_walk(&net->endpoints, free_key, 0);
map_clear(&net->endpoints);
tls_credentials_free(net->tls_credentials);
tls_client_params_free(&net->tls_client_params);
net->tls_credentials = NULL;
}
}
......@@ -138,7 +140,7 @@ static int open_endpoint(struct network *net, struct endpoint *ep, struct sockad
{
int ret = 0;
if (flags & NET_UDP) {
ep->udp = malloc(sizeof(*ep->udp));
ep->udp = malloc(sizeof(uv_handles_t));
if (!ep->udp) {
return kr_error(ENOMEM);
}
......@@ -151,7 +153,7 @@ static int open_endpoint(struct network *net, struct endpoint *ep, struct sockad
ep->flags |= NET_UDP;
}
if (flags & NET_TCP) {
ep->tcp = malloc(sizeof(*ep->tcp));
ep->tcp = malloc(sizeof(uv_handles_t));
if (!ep->tcp) {
return kr_error(ENOMEM);
}
......@@ -183,7 +185,7 @@ static int open_endpoint_fd(struct network *net, struct endpoint *ep, int fd, in
if (ep->udp) {
return kr_error(EEXIST);
}
ep->udp = malloc(sizeof(*ep->udp));
ep->udp = malloc(sizeof(uv_handles_t));// malloc(sizeof(*ep->udp));
if (!ep->udp) {
return kr_error(ENOMEM);
}
......@@ -195,12 +197,11 @@ static int open_endpoint_fd(struct network *net, struct endpoint *ep, int fd, in
}
ep->flags |= NET_UDP;
return kr_ok();
}
if (sock_type == SOCK_STREAM) {
} else if (sock_type == SOCK_STREAM) {
if (ep->tcp) {
return kr_error(EEXIST);
}
ep->tcp = malloc(sizeof(*ep->tcp));
ep->tcp = malloc(sizeof(uv_handles_t));
if (!ep->tcp) {
return kr_error(ENOMEM);
}
......
......@@ -46,6 +46,7 @@ struct network {
uv_loop_t *loop;
map_t endpoints;
struct tls_credentials *tls_credentials;
map_t tls_client_params;
};
void network_init(struct network *net, uv_loop_t *loop);
......
This diff is collapsed.
......@@ -20,11 +20,14 @@
#include <gnutls/gnutls.h>
#include <libknot/packet/pkt.h>
#include "lib/defines.h"
#include "lib/generic/array.h"
#include "lib/generic/map.h"
#define MAX_TLS_PADDING KR_EDNS_PAYLOAD
#define TLS_MAX_UNCORK_RETRIES 100
struct tls_ctx_t;
struct tls_credentials;
struct tls_client_ctx_t;
struct tls_credentials {
int count;
char *tls_cert;
......@@ -34,6 +37,22 @@ struct tls_credentials {
char *ephemeral_servicename;
};
struct tls_client_paramlist_entry {
array_t(const char *) ca_files;
array_t(const char *) hostnames;
array_t(const char *) pins;
gnutls_certificate_credentials_t credentials;
};
typedef enum tls_client_hs_state {
TLS_HS_NOT_STARTED = 0,
TLS_HS_IN_PROGRESS,
TLS_HS_DONE,
TLS_HS_LAST
} tls_client_hs_state_t;
typedef int (*tls_handshake_cb) (struct session *session, int status);
/*! Create an empty TLS context in query context */
struct tls_ctx_t* tls_new(struct worker_ctx *worker);
......@@ -66,3 +85,35 @@ void tls_credentials_log_pins(struct tls_credentials *tls_credentials);
/*! Generate new ephemeral TLS credentials. */
struct tls_credentials * tls_get_ephemeral_credentials(struct engine *engine);
/*! Set TLS authentication parameters for given address. */
int tls_client_params_set(map_t *tls_client_paramlist,
const char *addr, uint16_t port,
const char *ca_file, const char *hostname, const char *pin);
/*! Free TLS authentication parameters. */
int tls_client_params_free(map_t *tls_client_paramlist);
/*! Allocate new client TLS context */
struct tls_client_ctx_t *tls_client_ctx_new(const struct tls_client_paramlist_entry *entry);
int tls_client_process(struct worker_ctx *worker, uv_stream_t *handle,
const uint8_t *buf, ssize_t nread);
/*! Free client TLS context */
void tls_client_ctx_free(struct tls_client_ctx_t *ctx);
int tls_client_connect_start(struct tls_client_ctx_t *ctx, struct session *session,
tls_handshake_cb handshake_cb);
void tls_client_close(struct tls_client_ctx_t *ctx);
int tls_client_push(struct qr_task *task, uv_handle_t *handle, knot_pkt_t *pkt);
tls_client_hs_state_t tls_client_get_hs_state(const struct tls_client_ctx_t *ctx);
int tls_client_set_hs_state(struct tls_client_ctx_t *ctx, tls_client_hs_state_t state);
int tls_client_ctx_set_params(struct tls_client_ctx_t *ctx,
const struct tls_client_paramlist_entry *entry,
struct session *session);
......@@ -114,6 +114,8 @@ static gnutls_x509_privkey_t get_ephemeral_privkey ()
bad_data:
close(datafd);
datafd = -1;
}
if (data.data != NULL) {
gnutls_free(data.data);
data.data = NULL;
}
......@@ -150,8 +152,12 @@ static gnutls_x509_privkey_t get_ephemeral_privkey ()
}
done:
_lock_unlock(&lock, EPHEMERAL_PRIVKEY_FILENAME ".lock");
if (datafd != -1)
if (datafd != -1) {
close(datafd);
}
if (data.data != NULL) {
gnutls_free(data.data);
}
return privkey;
}
......
This diff is collapsed.
......@@ -21,8 +21,13 @@
#include "lib/generic/map.h"
/** Query resolution task (opaque). */
struct qr_task;
/** Worker state (opaque). */
struct worker_ctx;
/** Transport session (opaque). */
struct session;
/** Union of various libuv objects for freelist. */
/** Worker callback */
typedef void (*worker_cb_t)(struct worker_ctx *worker, struct kr_request *req, void *baton);
......@@ -31,14 +36,20 @@ struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
int worker_id, int worker_count);
/**
* Process incoming packet (query or answer to subrequest).
* Process an incoming packet (query from a client or answer from upstream).
*
* @param worker the singleton worker
* @param handle socket through which the request came
* @param query the packet, or NULL on an error from the transport layer
* @param addr the address from which the packet came (or NULL, possibly, on error)
* @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);
/**
* Process incoming DNS/TCP message fragment(s).
* Process incoming DNS message fragment(s) that arrived over a stream (TCP, TLS).
*
* 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 the number of newly-completed requests (>=0) or an error code
......@@ -55,6 +66,7 @@ int worker_end_tcp(struct worker_ctx *worker, uv_handle_t *handle);
/**
* Schedule query for resolution.
*
* After resolution finishes, invoke on_complete with baton.
* @return 0 or an error code
*
* @note the options passed are |-combined with struct kr_context::options
......@@ -66,15 +78,32 @@ int worker_resolve(struct worker_ctx *worker, knot_pkt_t *query, struct kr_qflag
/** Collect worker mempools */
void worker_reclaim(struct worker_ctx *worker);
/** Closes given session */
void worker_session_close(struct session *session);
void *worker_iohandle_borrow(struct worker_ctx *worker);
void worker_iohandle_release(struct worker_ctx *worker, void *h);