summaryrefslogtreecommitdiffstats
path: root/src/knot/query
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/query')
-rw-r--r--src/knot/query/capture.c63
-rw-r--r--src/knot/query/capture.h32
-rw-r--r--src/knot/query/layer.h136
-rw-r--r--src/knot/query/query.c85
-rw-r--r--src/knot/query/query.h66
-rw-r--r--src/knot/query/requestor.c378
-rw-r--r--src/knot/query/requestor.h119
7 files changed, 879 insertions, 0 deletions
diff --git a/src/knot/query/capture.c b/src/knot/query/capture.c
new file mode 100644
index 0000000..43f3e54
--- /dev/null
+++ b/src/knot/query/capture.c
@@ -0,0 +1,63 @@
+/* Copyright (C) 2016 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/>.
+ */
+
+#include <assert.h>
+
+#include "knot/query/capture.h"
+
+static int reset(knot_layer_t *ctx)
+{
+ return KNOT_STATE_PRODUCE;
+}
+
+static int finish(knot_layer_t *ctx)
+{
+ return KNOT_STATE_NOOP;
+}
+
+static int begin(knot_layer_t *ctx, void *module_param)
+{
+ ctx->data = module_param; /* struct capture_param */
+ return reset(ctx);
+}
+
+static int prepare_query(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+ return KNOT_STATE_CONSUME;
+}
+
+static int capture(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+ assert(pkt && ctx && ctx->data);
+ struct capture_param *param = ctx->data;
+
+ knot_pkt_copy(param->sink, pkt);
+
+ return KNOT_STATE_DONE;
+}
+
+const knot_layer_api_t *query_capture_api(void)
+{
+ static const knot_layer_api_t API = {
+ .begin = begin,
+ .reset = reset,
+ .finish = finish,
+ .consume = capture,
+ .produce = prepare_query,
+ };
+
+ return &API;
+}
diff --git a/src/knot/query/capture.h b/src/knot/query/capture.h
new file mode 100644
index 0000000..41f8270
--- /dev/null
+++ b/src/knot/query/capture.h
@@ -0,0 +1,32 @@
+/* Copyright (C) 2016 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/>.
+ */
+
+#pragma once
+
+#include "knot/query/layer.h"
+#include "libknot/packet/pkt.h"
+
+/*!
+ * \brief Processing module for packet capture.
+ */
+const knot_layer_api_t *query_capture_api(void);
+
+/*!
+ * \brief Processing module parameters.
+ */
+struct capture_param {
+ knot_pkt_t *sink; /*!< Container for captured response. */
+};
diff --git a/src/knot/query/layer.h b/src/knot/query/layer.h
new file mode 100644
index 0000000..119ae5d
--- /dev/null
+++ b/src/knot/query/layer.h
@@ -0,0 +1,136 @@
+/* Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include "libknot/packet/pkt.h"
+#include "libknot/mm_ctx.h"
+#include "knot/nameserver/tsig_ctx.h"
+
+/*!
+ * \brief Layer processing states.
+ *
+ * Each state represents the state machine transition,
+ * and determines readiness for the next action.
+ */
+typedef enum {
+ KNOT_STATE_NOOP = 0, //!< Invalid.
+ KNOT_STATE_CONSUME, //!< Consume data.
+ KNOT_STATE_PRODUCE, //!< Produce data.
+ KNOT_STATE_RESET, //!< Restart processing.
+ KNOT_STATE_DONE, //!< Finished.
+ KNOT_STATE_FAIL, //!< Error.
+ KNOT_STATE_FINAL, //!< Finished and finalized.
+ KNOT_STATE_IGNORE, //!< Data has been ignored.
+} knot_layer_state_t;
+
+typedef struct knot_layer_api knot_layer_api_t;
+
+/*! \brief Packet processing context. */
+typedef struct {
+ const knot_layer_api_t *api; //!< Layer API.
+ knot_mm_t *mm; //!< Processing memory context.
+ knot_layer_state_t state; //!< Processing state.
+ void *data; //!< Module specific.
+ tsig_ctx_t *tsig; //!< TODO: remove
+ unsigned flags; //!< Custom flags.
+} knot_layer_t;
+
+/*! \brief Packet processing module API. */
+struct knot_layer_api {
+ int (*begin)(knot_layer_t *ctx, void *params);
+ int (*reset)(knot_layer_t *ctx);
+ int (*finish)(knot_layer_t *ctx);
+ int (*consume)(knot_layer_t *ctx, knot_pkt_t *pkt);
+ int (*produce)(knot_layer_t *ctx, knot_pkt_t *pkt);
+};
+
+/*! \brief Helper for conditional layer call. */
+#define LAYER_CALL(layer, func, ...) \
+ assert(layer->api); \
+ if (layer->api->func) { \
+ layer->state = layer->api->func(layer, ##__VA_ARGS__); \
+ }
+
+/*!
+ * \brief Initialize packet processing context.
+ *
+ * \param ctx Layer context.
+ * \param mm Memory context.
+ * \param api Layer API.
+ */
+inline static void knot_layer_init(knot_layer_t *ctx, knot_mm_t *mm,
+ const knot_layer_api_t *api)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->mm = mm;
+ ctx->api = api;
+ ctx->state = KNOT_STATE_NOOP;
+}
+
+/*!
+ * \brief Prepare packet processing.
+ *
+ * \param ctx Layer context.
+ * \param params Initialization params.
+ */
+inline static void knot_layer_begin(knot_layer_t *ctx, void *params)
+{
+ LAYER_CALL(ctx, begin, params);
+}
+
+/*!
+ * \brief Reset current packet processing context.
+ *
+ * \param ctx Layer context.
+ */
+inline static void knot_layer_reset(knot_layer_t *ctx)
+{
+ LAYER_CALL(ctx, reset);
+}
+
+/*!
+ * \brief Finish and close packet processing context.
+ *
+ * \param ctx Layer context.
+ */
+inline static void knot_layer_finish(knot_layer_t *ctx)
+{
+ LAYER_CALL(ctx, finish);
+}
+
+/*!
+ * \brief Add more data to layer processing.
+ *
+ * \param ctx Layer context.
+ * \param pkt Data packet.
+ */
+inline static void knot_layer_consume(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+ LAYER_CALL(ctx, consume, pkt);
+}
+
+/*!
+ * \brief Generate output from layer.
+ *
+ * \param ctx Layer context.
+ * \param pkt Data packet.
+ */
+inline static void knot_layer_produce(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+ LAYER_CALL(ctx, produce, pkt);
+}
diff --git a/src/knot/query/query.c b/src/knot/query/query.c
new file mode 100644
index 0000000..877851a
--- /dev/null
+++ b/src/knot/query/query.c
@@ -0,0 +1,85 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#include "knot/query/query.h"
+
+#include "contrib/wire_ctx.h"
+#include "libdnssec/random.h"
+
+void query_init_pkt(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return;
+ }
+
+ knot_pkt_clear(pkt);
+ knot_wire_set_id(pkt->wire, dnssec_random_uint16_t());
+}
+
+query_edns_data_t query_edns_data_init(conf_t *conf, int remote_family,
+ query_edns_opt_t opts)
+{
+ assert(conf);
+
+ query_edns_data_t edns = {
+ .max_payload = remote_family == AF_INET ?
+ conf->cache.srv_udp_max_payload_ipv4 :
+ conf->cache.srv_udp_max_payload_ipv6,
+ .do_flag = (opts & QUERY_EDNS_OPT_DO),
+ .expire_option = (opts & QUERY_EDNS_OPT_EXPIRE)
+ };
+
+ return edns;
+}
+
+int query_put_edns(knot_pkt_t *pkt, const query_edns_data_t *edns)
+{
+ if (!pkt || !edns) {
+ return KNOT_EINVAL;
+ }
+
+ // Construct EDNS RR
+
+ knot_rrset_t opt_rr = { 0 };
+ int ret = knot_edns_init(&opt_rr, edns->max_payload, 0, KNOT_EDNS_VERSION, &pkt->mm);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (edns->do_flag) {
+ knot_edns_set_do(&opt_rr);
+ }
+
+ if (edns->expire_option) {
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_EXPIRE, 0, NULL, &pkt->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &pkt->mm);
+ return ret;
+ }
+ }
+
+ // Add result into the packet
+
+ knot_pkt_begin(pkt, KNOT_ADDITIONAL);
+
+ ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NOCOMP, &opt_rr, KNOT_PF_FREE);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &pkt->mm);
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
diff --git a/src/knot/query/query.h b/src/knot/query/query.h
new file mode 100644
index 0000000..fbf437d
--- /dev/null
+++ b/src/knot/query/query.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#pragma once
+
+#include "knot/conf/conf.h"
+#include "knot/nameserver/log.h"
+#include "libknot/packet/pkt.h"
+
+/*!
+ * \brief EDNS data.
+ */
+typedef struct {
+ uint16_t max_payload;
+ bool do_flag;
+ bool expire_option;
+} query_edns_data_t;
+
+typedef enum {
+ QUERY_EDNS_OPT_DO = 1 << 0,
+ QUERY_EDNS_OPT_EXPIRE = 1 << 1,
+} query_edns_opt_t;
+
+/*!
+ * \brief Initialize new packet.
+ *
+ * Clear the packet and generate random transaction ID.
+ *
+ * \param pkt Packet to initialize.
+ */
+void query_init_pkt(knot_pkt_t *pkt);
+
+/*!
+ * \brief Initialize EDNS parameters from server configuration.
+ *
+ * \param[in] conf Server configuration.
+ * \param[in] remote_family Address family for remote host.
+ * \param[in] opts EDNS options.
+ *
+ * \return EDNS parameters.
+ */
+query_edns_data_t query_edns_data_init(conf_t *conf, int remote_family,
+ query_edns_opt_t opts);
+
+/*!
+ * \brief Append EDNS into the packet.
+ *
+ * \param pkt Packet to add EDNS into.
+ * \param edns EDNS data.
+ *
+ * \return KNOT_E*
+ */
+int query_put_edns(knot_pkt_t *pkt, const query_edns_data_t *edns);
diff --git a/src/knot/query/requestor.c b/src/knot/query/requestor.c
new file mode 100644
index 0000000..8643f74
--- /dev/null
+++ b/src/knot/query/requestor.c
@@ -0,0 +1,378 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#include <assert.h>
+
+#include "libknot/attribute.h"
+#include "knot/common/unreachable.h"
+#include "knot/query/requestor.h"
+#include "libknot/errcode.h"
+#include "contrib/conn_pool.h"
+#include "contrib/mempattern.h"
+#include "contrib/net.h"
+#include "contrib/sockaddr.h"
+
+static bool use_tcp(knot_request_t *request)
+{
+ return (request->flags & KNOT_REQUEST_UDP) == 0;
+}
+
+static bool is_answer_to_query(const knot_pkt_t *query, const knot_pkt_t *answer)
+{
+ return knot_wire_get_id(query->wire) == knot_wire_get_id(answer->wire);
+}
+
+/*! \brief Ensure a socket is connected. */
+static int request_ensure_connected(knot_request_t *request, bool *reused_fd)
+{
+ if (request->fd >= 0) {
+ return KNOT_EOK;
+ }
+
+ int sock_type = use_tcp(request) ? SOCK_STREAM : SOCK_DGRAM;
+
+ if (sock_type == SOCK_STREAM) {
+ request->fd = conn_pool_get(global_conn_pool,
+ &request->source,
+ &request->remote);
+ if (request->fd >= 0) {
+ if (reused_fd != NULL) {
+ *reused_fd = true;
+ }
+ return KNOT_EOK;
+ }
+
+ if (knot_unreachable_is(global_unreachables, &request->remote,
+ &request->source)) {
+ return KNOT_EUNREACH;
+ }
+ }
+
+ request->fd = net_connected_socket(sock_type,
+ &request->remote,
+ &request->source,
+ request->flags & KNOT_REQUEST_TFO);
+ if (request->fd < 0) {
+ if (request->fd == KNOT_ETIMEOUT) {
+ knot_unreachable_add(global_unreachables, &request->remote,
+ &request->source);
+ }
+ return request->fd;
+ }
+
+ return KNOT_EOK;
+}
+
+static int request_send(knot_request_t *request, int timeout_ms, bool *reused_fd)
+{
+ /* Initiate non-blocking connect if not connected. */
+ *reused_fd = false;
+ int ret = request_ensure_connected(request, reused_fd);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Send query, construct if not exists. */
+ knot_pkt_t *query = request->query;
+ uint8_t *wire = query->wire;
+ size_t wire_len = query->size;
+ struct sockaddr_storage *tfo_addr = (request->flags & KNOT_REQUEST_TFO) ?
+ &request->remote : NULL;
+
+ /* Send query. */
+ if (use_tcp(request)) {
+ ret = net_dns_tcp_send(request->fd, wire, wire_len, timeout_ms,
+ tfo_addr);
+ if (ret == KNOT_ETIMEOUT) { // Includes establishing conn which times out.
+ knot_unreachable_add(global_unreachables, &request->remote,
+ &request->source);
+ }
+ } else {
+ ret = net_dgram_send(request->fd, wire, wire_len, NULL);
+ }
+ if (ret < 0) {
+ return ret;
+ } else if (ret != wire_len) {
+ return KNOT_ECONN;
+ }
+
+ return KNOT_EOK;
+}
+
+static int request_recv(knot_request_t *request, int timeout_ms)
+{
+ knot_pkt_t *resp = request->resp;
+ knot_pkt_clear(resp);
+
+ /* Wait for readability */
+ int ret = request_ensure_connected(request, NULL);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Receive it */
+ if (use_tcp(request)) {
+ ret = net_dns_tcp_recv(request->fd, resp->wire, resp->max_size, timeout_ms);
+ } else {
+ ret = net_dgram_recv(request->fd, resp->wire, resp->max_size, timeout_ms);
+ }
+ if (ret <= 0) {
+ resp->size = 0;
+ if (ret == 0) {
+ return KNOT_ECONN;
+ }
+ return ret;
+ }
+
+ resp->size = ret;
+ return ret;
+}
+
+knot_request_t *knot_request_make(knot_mm_t *mm,
+ const struct sockaddr_storage *remote,
+ const struct sockaddr_storage *source,
+ knot_pkt_t *query,
+ const knot_tsig_key_t *tsig_key,
+ knot_request_flag_t flags)
+{
+ if (remote == NULL || query == NULL) {
+ return NULL;
+ }
+
+ knot_request_t *request = mm_calloc(mm, 1, sizeof(*request));
+ if (request == NULL) {
+ return NULL;
+ }
+
+ request->resp = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, mm);
+ if (request->resp == NULL) {
+ mm_free(mm, request);
+ return NULL;
+ }
+
+ request->query = query;
+ request->fd = -1;
+ request->flags = flags;
+ memcpy(&request->remote, remote, sockaddr_len(remote));
+ if (source) {
+ memcpy(&request->source, source, sockaddr_len(source));
+ } else {
+ request->source.ss_family = AF_UNSPEC;
+ }
+
+ if (tsig_key && tsig_key->algorithm == DNSSEC_TSIG_UNKNOWN) {
+ tsig_key = NULL;
+ }
+ tsig_init(&request->tsig, tsig_key);
+
+ return request;
+}
+
+void knot_request_free(knot_request_t *request, knot_mm_t *mm)
+{
+ if (request == NULL) {
+ return;
+ }
+
+ if (request->fd >= 0 && use_tcp(request) &&
+ (request->flags & KNOT_REQUEST_KEEP)) {
+ request->fd = conn_pool_put(global_conn_pool,
+ &request->source,
+ &request->remote,
+ request->fd);
+ }
+ if (request->fd >= 0) {
+ close(request->fd);
+ }
+ knot_pkt_free(request->query);
+ knot_pkt_free(request->resp);
+ tsig_cleanup(&request->tsig);
+
+ mm_free(mm, request);
+}
+
+int knot_requestor_init(knot_requestor_t *requestor,
+ const knot_layer_api_t *proc, void *proc_param,
+ knot_mm_t *mm)
+{
+ if (requestor == NULL || proc == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(requestor, 0, sizeof(*requestor));
+
+ requestor->mm = mm;
+ knot_layer_init(&requestor->layer, mm, proc);
+ knot_layer_begin(&requestor->layer, proc_param);
+
+ return KNOT_EOK;
+}
+
+void knot_requestor_clear(knot_requestor_t *requestor)
+{
+ if (requestor == NULL) {
+ return;
+ }
+
+ knot_layer_finish(&requestor->layer);
+
+ memset(requestor, 0, sizeof(*requestor));
+}
+
+static int request_reset(knot_requestor_t *req, knot_request_t *last)
+{
+ knot_layer_reset(&req->layer);
+ tsig_reset(&last->tsig);
+
+ if (req->layer.flags & KNOT_REQUESTOR_CLOSE) {
+ req->layer.flags &= ~KNOT_REQUESTOR_CLOSE;
+ if (last->fd >= 0) {
+ close(last->fd);
+ last->fd = -1;
+ }
+ }
+
+ if (req->layer.state == KNOT_STATE_RESET) {
+ return KNOT_EPROCESSING;
+ }
+
+ return KNOT_EOK;
+}
+
+static int request_produce(knot_requestor_t *req, knot_request_t *last,
+ int timeout_ms)
+{
+ knot_layer_produce(&req->layer, last->query);
+
+ int ret = tsig_sign_packet(&last->tsig, last->query);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // TODO: verify condition
+ if (req->layer.state == KNOT_STATE_CONSUME) {
+ bool reused_fd = false;
+ ret = request_send(last, timeout_ms, &reused_fd);
+ if (reused_fd) {
+ req->layer.flags |= KNOT_REQUESTOR_REUSED;
+ } else {
+ req->layer.flags &= ~KNOT_REQUESTOR_REUSED;
+ }
+ }
+
+ return ret;
+}
+
+static int request_consume(knot_requestor_t *req, knot_request_t *last,
+ int timeout_ms)
+{
+ int ret = request_recv(last, timeout_ms);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = knot_pkt_parse(last->resp, 0);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (!is_answer_to_query(last->query, last->resp)) {
+ return KNOT_EMALF;
+ }
+
+ ret = tsig_verify_packet(&last->tsig, last->resp);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (tsig_unsigned_count(&last->tsig) >= 100) {
+ return KNOT_TSIG_EBADSIG;
+ }
+
+ knot_layer_consume(&req->layer, last->resp);
+
+ return KNOT_EOK;
+}
+
+static bool layer_active(knot_layer_state_t state)
+{
+ switch (state) {
+ case KNOT_STATE_CONSUME:
+ case KNOT_STATE_PRODUCE:
+ case KNOT_STATE_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int request_io(knot_requestor_t *req, knot_request_t *last,
+ int timeout_ms)
+{
+ switch (req->layer.state) {
+ case KNOT_STATE_CONSUME:
+ return request_consume(req, last, timeout_ms);
+ case KNOT_STATE_PRODUCE:
+ return request_produce(req, last, timeout_ms);
+ case KNOT_STATE_RESET:
+ return request_reset(req, last);
+ default:
+ return KNOT_EINVAL;
+ }
+}
+
+int knot_requestor_exec(knot_requestor_t *requestor, knot_request_t *request,
+ int timeout_ms)
+{
+ if (requestor == NULL || request == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = KNOT_EOK;
+
+ requestor->layer.tsig = &request->tsig;
+
+ /* Do I/O until the processing is satisfied or fails. */
+ while (layer_active(requestor->layer.state)) {
+ ret = request_io(requestor, request, timeout_ms);
+ if (ret != KNOT_EOK) {
+ knot_layer_finish(&requestor->layer);
+ return ret;
+ }
+ }
+
+ /* Expect complete request. */
+ switch (requestor->layer.state) {
+ case KNOT_STATE_DONE:
+ request->flags |= KNOT_REQUEST_KEEP;
+ break;
+ case KNOT_STATE_IGNORE:
+ ret = KNOT_ERROR;
+ break;
+ default:
+ ret = KNOT_EPROCESSING;
+ }
+
+ /* Verify last TSIG */
+ if (tsig_unsigned_count(&request->tsig) != 0) {
+ ret = KNOT_TSIG_EBADSIG;
+ }
+
+ /* Finish current query processing. */
+ knot_layer_finish(&requestor->layer);
+
+ return ret;
+}
diff --git a/src/knot/query/requestor.h b/src/knot/query/requestor.h
new file mode 100644
index 0000000..aa90cd5
--- /dev/null
+++ b/src/knot/query/requestor.h
@@ -0,0 +1,119 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include "knot/nameserver/tsig_ctx.h"
+#include "knot/query/layer.h"
+#include "libknot/mm_ctx.h"
+#include "libknot/rrtype/tsig.h"
+
+typedef enum {
+ KNOT_REQUEST_NONE = 0, /*!< Empty flag. */
+ KNOT_REQUEST_UDP = 1 << 0, /*!< Use UDP for requests. */
+ KNOT_REQUEST_TFO = 1 << 1, /*!< Enable TCP Fast Open for requests. */
+ KNOT_REQUEST_KEEP = 1 << 2, /*!< Keep upstream TCP connection in pool for later reuse. */
+} knot_request_flag_t;
+
+typedef enum {
+ KNOT_REQUESTOR_CLOSE = 1 << 0, /*!< Close the connection indication. */
+ KNOT_REQUESTOR_REUSED = 1 << 1, /*!< Reused FD indication. */
+} knot_requestor_flag_t;
+
+/*! \brief Requestor structure.
+ *
+ * Requestor holds a FIFO of pending queries.
+ */
+typedef struct {
+ knot_mm_t *mm; /*!< Memory context. */
+ knot_layer_t layer; /*!< Response processing layer. */
+} knot_requestor_t;
+
+/*! \brief Request data (socket, payload, response, TSIG and endpoints). */
+typedef struct {
+ int fd;
+ knot_request_flag_t flags;
+ struct sockaddr_storage remote, source;
+ knot_pkt_t *query;
+ knot_pkt_t *resp;
+ tsig_ctx_t tsig;
+
+ knot_sign_context_t sign; /*!< Required for async. DDNS processing. */
+} knot_request_t;
+
+/*!
+ * \brief Make request out of endpoints and query.
+ *
+ * \param mm Memory context.
+ * \param remote Remote endpoint address.
+ * \param source Source address (or NULL).
+ * \param query Query message.
+ * \param tsig_key TSIG key for authentication.
+ * \param flags Request flags.
+ *
+ * \return Prepared request or NULL in case of error.
+ */
+knot_request_t *knot_request_make(knot_mm_t *mm,
+ const struct sockaddr_storage *remote,
+ const struct sockaddr_storage *source,
+ knot_pkt_t *query,
+ const knot_tsig_key_t *tsig_key,
+ knot_request_flag_t flags);
+
+/*!
+ * \brief Free request and associated data.
+ *
+ * \param request Freed request.
+ * \param mm Memory context.
+ */
+void knot_request_free(knot_request_t *request, knot_mm_t *mm);
+
+/*!
+ * \brief Initialize requestor structure.
+ *
+ * \param requestor Requestor instance.
+ * \param proc Response processing module.
+ * \param proc_param Processing module context.
+ * \param mm Memory context.
+ *
+ * \return KNOT_EOK or error
+ */
+int knot_requestor_init(knot_requestor_t *requestor,
+ const knot_layer_api_t *proc, void *proc_param,
+ knot_mm_t *mm);
+
+/*!
+ * \brief Clear the requestor structure and close pending queries.
+ *
+ * \param requestor Requestor instance.
+ */
+void knot_requestor_clear(knot_requestor_t *requestor);
+
+/*!
+ * \brief Execute a request.
+ *
+ * \param requestor Requestor instance.
+ * \param request Request instance.
+ * \param timeout_ms Timeout of each operation in milliseconds (-1 for infinity).
+ *
+ * \return KNOT_EOK or error
+ */
+int knot_requestor_exec(knot_requestor_t *requestor,
+ knot_request_t *request,
+ int timeout_ms);