]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
session2: protocol layer API
authorOto Šťáva <oto.stava@nic.cz>
Mon, 18 Jul 2022 06:51:39 +0000 (08:51 +0200)
committerOto Šťáva <oto.stava@nic.cz>
Thu, 26 Jan 2023 11:56:07 +0000 (12:56 +0100)
daemon/lua/kres-gen-30.lua
daemon/lua/kres-gen-31.lua
daemon/lua/kres-gen-32.lua
daemon/meson.build
daemon/session2.c [new file with mode: 0644]
daemon/session2.h [new file with mode: 0644]
lib/log.c
lib/log.h

index 3a409b622e61a9c4d2bdfaed252514cce3ef7996..77063727918d6d54145f63e9e04942afb79e2746 100644 (file)
@@ -314,7 +314,7 @@ struct kr_server_selection {
        struct local_state *local_state;
 };
 typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_PROTOLAYER, LOG_GRP_REQDBG};
 
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
index f142b7948f1b8dd54259cce3b77ef103cbac8fb2..2a90f839c0a2057d8c1fe32ab226fc77aa3fdf2b 100644 (file)
@@ -314,7 +314,7 @@ struct kr_server_selection {
        struct local_state *local_state;
 };
 typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_PROTOLAYER, LOG_GRP_REQDBG};
 
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
index 3f9a0d678e9ae6e0f602e39875f271efe172d2e4..5c7ab7dfe753c72f191e6db59d317e996d937449 100644 (file)
@@ -315,7 +315,7 @@ struct kr_server_selection {
        struct local_state *local_state;
 };
 typedef int kr_log_level_t;
-enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_PROTOLAYER, LOG_GRP_REQDBG};
 
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
index 68a2646682a134b9178770c3154f6fbe84c4a0f8..1ff28ec031d26ab7a4d81e82e8dffb5aff416585 100644 (file)
@@ -15,6 +15,7 @@ kresd_src = files([
   'network.c',
   'proxyv2.c',
   'session.c',
+  'session2.c',
   'tls.c',
   'tls_ephemeral_credentials.c',
   'tls_session_ticket-srv.c',
diff --git a/daemon/session2.c b/daemon/session2.c
new file mode 100644 (file)
index 0000000..0dcb013
--- /dev/null
@@ -0,0 +1,549 @@
+/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <ucw/lib.h>
+#include <sys/socket.h>
+
+#include "lib/log.h"
+#include "lib/utils.h"
+
+#include "daemon/session2.h"
+
+
+typedef void (*session2_push_cb)(struct session2 *s, int status,
+                                 void *target, void *baton);
+
+static int session2_transport_pushv(struct session2 *s,
+                                    const struct iovec *iov, int iovcnt,
+                                    void *target,
+                                    session2_push_cb cb, void *baton);
+static inline int session2_transport_push(struct session2 *s,
+                                          char *buf, size_t buf_len,
+                                          void *target,
+                                          session2_push_cb cb, void *baton);
+
+struct protolayer_globals protolayer_globals[PROTOLAYER_PROTOCOL_COUNT] = {0};
+
+
+enum protolayer_protocol protolayer_grp_doudp[] = {
+       PROTOLAYER_UDP,
+       PROTOLAYER_DNS_DGRAM,
+       PROTOLAYER_NULL
+};
+
+enum protolayer_protocol protolayer_grp_dotcp[] = {
+       PROTOLAYER_TCP,
+       PROTOLAYER_DNS_MSTREAM,
+       PROTOLAYER_NULL
+};
+
+enum protolayer_protocol protolayer_grp_dot[] = {
+       PROTOLAYER_TCP,
+       PROTOLAYER_TLS,
+       PROTOLAYER_DNS_MSTREAM,
+       PROTOLAYER_NULL
+};
+
+enum protolayer_protocol protolayer_grp_doh[] = {
+       PROTOLAYER_TCP,
+       PROTOLAYER_TLS,
+       PROTOLAYER_HTTP,
+       PROTOLAYER_DNS_DGRAM,
+       PROTOLAYER_NULL
+};
+
+
+enum protolayer_protocol *protolayer_grps[PROTOLAYER_GRP_COUNT] = {
+#define XX(id, name, desc) [PROTOLAYER_GRP_##id] = protolayer_grp_##name,
+       PROTOLAYER_GRP_MAP(XX)
+#undef XX
+};
+
+char *protolayer_grp_descs[PROTOLAYER_GRP_COUNT] = {
+#define XX(id, name, desc) [PROTOLAYER_GRP_##id] = desc,
+       PROTOLAYER_GRP_MAP(XX)
+#undef XX
+};
+
+
+/** Gets context for the layer with the specified index from the manager. */
+static inline struct protolayer_data *protolayer_manager_get(
+               struct protolayer_manager *m, size_t layer_ix)
+{
+       if (kr_fails_assert(layer_ix < m->num_layers))
+               return NULL;
+
+       const size_t *offsets = (size_t *)m->data;
+       char *pl_data_beg = m->data + (m->num_layers * sizeof(*offsets));
+       return (struct protolayer_data *)(pl_data_beg + offsets[layer_ix]);
+}
+
+static inline void protolayer_cb_ctx_next(struct protolayer_cb_ctx *ctx)
+{
+       if (ctx->direction == PROTOLAYER_UNWRAP)
+               ctx->layer_ix++;
+       else
+               ctx->layer_ix--;
+}
+
+static int protolayer_cb_ctx_finish(struct protolayer_cb_ctx *ctx, int ret,
+                                    bool reset_layers)
+{
+       if (reset_layers) {
+               struct protolayer_manager *m = ctx->manager;
+               struct protolayer_globals *globals = &protolayer_globals[m->grp];
+               for (size_t i = 0; i < m->num_layers; i++) {
+                       struct protolayer_data *d = protolayer_manager_get(m, i);
+                       if (globals->reset)
+                               globals->reset(m, d);
+               }
+       }
+
+       if (ctx->status)
+               kr_log_debug(PROTOLAYER, "layer iteration of group '%s' ended with status %d",
+                               protolayer_grp_descs[ctx->manager->grp], ret);
+
+       if (ctx->finished_cb)
+               ctx->finished_cb(ret, ctx->finished_cb_target,
+                               ctx->finished_cb_baton);
+       free(ctx);
+       return ret;
+}
+
+/** Processes as many layers as possible synchronously, returning when either
+ * a layer has gone asynchronous, or when the whole sequence has finished.
+ *
+ * May be called multiple times on the same `ctx` to continue processing
+ * after an asynchronous operation. */
+static int protolayer_step(struct protolayer_cb_ctx *ctx)
+{
+       while (true) {
+               struct protolayer_data *ldata = protolayer_manager_get(
+                               ctx->manager, ctx->layer_ix);
+               if (kr_fails_assert(ldata)) {
+                       /* Probably layer index or data corruption */
+                       return kr_error(EINVAL);
+               }
+
+               enum protolayer_protocol protocol = ldata->protocol;
+               struct protolayer_globals *globals = &protolayer_globals[protocol];
+
+               if (!ldata->processed) { /* Avoid repetition */
+                       ctx->async_mode = false;
+                       ctx->status = 0;
+                       ctx->result = PROTOLAYER_CB_NULL;
+
+                       protolayer_cb cb = (ctx->direction == PROTOLAYER_UNWRAP)
+                               ? globals->unwrap : globals->wrap;
+
+                       cb(ldata, ctx);
+                       ldata->processed = true;
+               }
+
+               if (!ctx->result) {
+                       ctx->async_mode = true;
+                       return PROTOLAYER_RET_ASYNC; /* Next step is callback */
+               }
+
+               if (ctx->result == PROTOLAYER_CB_WAIT) {
+                       kr_assert(ctx->status == 0);
+                       return protolayer_cb_ctx_finish(
+                                       ctx, PROTOLAYER_RET_WAITING, false);
+               }
+
+               if (ctx->result == PROTOLAYER_CB_BREAK) {
+                       kr_assert(ctx->status <= 0);
+                       return protolayer_cb_ctx_finish(
+                                       ctx, PROTOLAYER_RET_NORMAL, true);
+               }
+
+               if (kr_fails_assert(ctx->status == 0)) {
+                       /* Status should be zero without a BREAK. */
+                       return protolayer_cb_ctx_finish(
+                                       ctx, kr_error(ECANCELED), true);
+               }
+
+               if (ctx->result == PROTOLAYER_CB_CONTINUE) {
+                       protolayer_cb_ctx_next(ctx);
+                       continue;
+               }
+
+               /* Should never get here */
+               kr_assert(false);
+               return protolayer_cb_ctx_finish(ctx, kr_error(EINVAL), true);
+       }
+}
+
+/** Submits the specified buffer to the sequence of layers represented by the
+ * specified protolayer manager. The sequence will be processed in the
+ * specified direction.
+ *
+ * Returns 0 when all layers have finished, 1 when some layers are asynchronous
+ * and waiting for continuation, 2 when a layer is waiting for more data,
+ * or a negative number for errors (kr_error). */
+static int protolayer_manager_submit(
+               struct protolayer_manager *manager,
+               enum protolayer_direction direction,
+               char *buf, size_t buf_len, void *target,
+               protolayer_finished_cb cb, void *baton)
+{
+       size_t layer_ix = (direction == PROTOLAYER_UNWRAP)
+               ? 0 : manager->num_layers - 1;
+
+       struct protolayer_cb_ctx *ctx = malloc(sizeof(*ctx)); // TODO - mempool?
+       kr_require(ctx);
+
+       *ctx = (struct protolayer_cb_ctx) {
+               .data = { .target = target },
+               .direction = direction,
+               .layer_ix = layer_ix,
+               .manager = manager,
+               .finished_cb = cb,
+               .finished_cb_target = target,
+               .finished_cb_baton = baton
+       };
+       protolayer_set_buffer(ctx, buf, buf_len);
+
+       return protolayer_step(ctx);
+}
+
+
+struct protolayer_manager *protolayer_manager_new(struct session2 *s,
+                                                  enum protolayer_grp grp)
+{
+       if (kr_fails_assert(grp))
+               return NULL;
+
+       size_t num_layers = 0;
+       size_t size = sizeof(struct protolayer_manager);
+       enum protolayer_protocol *protocols = protolayer_grps[grp];
+       if (kr_fails_assert(protocols))
+               return NULL;
+       enum protolayer_protocol *p = protocols;
+
+       /* Space for offset index */
+       for (; *p; p++)
+               num_layers++;
+       if (kr_fails_assert(num_layers))
+               return NULL;
+       size_t offsets[num_layers];
+       size += sizeof(offsets);
+
+       /* Space for layer-specific data, guaranteeing alignment */
+       size_t total_data_size = 0;
+       for (size_t i = 0; i < num_layers; i++) {
+               offsets[i] = total_data_size;
+               size_t d = protolayer_globals[protocols[i]].data_size;
+               size += ALIGN_TO(d, CPU_STRUCT_ALIGN);
+       }
+       size += total_data_size;
+
+       /* Allocate and initialize manager */
+       struct protolayer_manager *m = malloc(size);
+       kr_require(m);
+       m->grp = grp;
+       m->session = s;
+       m->num_layers = num_layers;
+       memcpy(m->data, offsets, sizeof(offsets));
+
+       /* Initialize layer data */
+       for (size_t i = 0; i < num_layers; i++) {
+               struct protolayer_globals *globals = &protolayer_globals[protocols[i]];
+               struct protolayer_data *data = protolayer_manager_get(m, i);
+               data->protocol = protocols[i];
+               data->size = globals->data_size;
+               globals->init(m, data);
+       }
+
+       return m;
+}
+
+void protolayer_manager_free(struct protolayer_manager *m)
+{
+       if (!m) return;
+
+       for (size_t i = 0; i < m->num_layers; i++) {
+               struct protolayer_data *data = protolayer_manager_get(m, i);
+               protolayer_globals[data->protocol].deinit(m, data);
+       }
+
+       free(m);
+}
+
+void protolayer_continue(struct protolayer_cb_ctx *ctx)
+{
+       if (ctx->async_mode) {
+               protolayer_cb_ctx_next(ctx);
+               protolayer_step(ctx);
+       } else {
+               ctx->result = PROTOLAYER_CB_CONTINUE;
+       }
+}
+
+void protolayer_wait(struct protolayer_cb_ctx *ctx)
+{
+       if (ctx->async_mode) {
+               protolayer_cb_ctx_finish(ctx, PROTOLAYER_RET_WAITING, false);
+       } else {
+               ctx->result = PROTOLAYER_CB_WAIT;
+       }
+}
+
+void protolayer_break(struct protolayer_cb_ctx *ctx, int status)
+{
+       ctx->status = status;
+       if (ctx->async_mode) {
+               protolayer_cb_ctx_finish(ctx, PROTOLAYER_RET_NORMAL, true);
+       } else {
+               ctx->result = PROTOLAYER_CB_BREAK;
+       }
+}
+
+static void protolayer_push_finished(struct session2 *s, int status, void *target, void *baton)
+{
+       protolayer_break(baton, status);
+}
+
+void protolayer_pushv(struct protolayer_cb_ctx *ctx,
+                      struct iovec *iov, int iovcnt,
+                      void *target)
+{
+       int ret = session2_transport_pushv(ctx->manager->session, iov, iovcnt,
+                       target, protolayer_push_finished, ctx);
+       if (ret && ctx->finished_cb)
+               ctx->finished_cb(ret, ctx->finished_cb_target,
+                               ctx->finished_cb_baton);
+}
+
+void protolayer_push(struct protolayer_cb_ctx *ctx, char *buf, size_t buf_len,
+                     void *target)
+{
+       int ret = session2_transport_push(ctx->manager->session, buf, buf_len,
+                       target, protolayer_push_finished, ctx);
+       if (ret && ctx->finished_cb)
+               ctx->finished_cb(ret, ctx->finished_cb_target,
+                               ctx->finished_cb_baton);
+}
+
+
+struct session2 *session2_new(enum session2_transport_type transport_type,
+                              void *transport_ctx,
+                              enum protolayer_grp layer_grp,
+                              bool outgoing)
+{
+       kr_require(transport_type && transport_ctx && layer_grp);
+
+       struct session2 *s = malloc(sizeof(*s));
+       kr_require(s);
+
+       s->transport.type = transport_type;
+       s->transport.ctx = transport_ctx;
+
+       s->layers = protolayer_manager_new(s, layer_grp);
+       if (!s->layers) {
+               free(s);
+               return NULL;
+       }
+
+       s->outgoing = outgoing;
+
+       return s;
+}
+
+void session2_free(struct session2 *s)
+{
+       protolayer_manager_free(s->layers);
+       free(s);
+}
+
+int session2_unwrap(struct session2 *s, char *buf, size_t buf_len, void *target,
+                    protolayer_finished_cb cb, void *baton)
+{
+       return protolayer_manager_submit(s->layers, PROTOLAYER_UNWRAP,
+                       buf, buf_len, target, cb, baton);
+}
+
+int session2_wrap(struct session2 *s, char *buf, size_t buf_len, void *target,
+                  protolayer_finished_cb cb, void *baton)
+{
+       return protolayer_manager_submit(s->layers, PROTOLAYER_WRAP,
+                       buf, buf_len, target, cb, baton);
+}
+
+
+struct parent_pushv_ctx {
+       struct session2 *session;
+       session2_push_cb cb;
+       void *target;
+       void *baton;
+
+       char *buf;
+       size_t buf_len;
+};
+
+static void session2_transport_parent_pushv_finished(int status, void *target, void *baton)
+{
+       struct parent_pushv_ctx *ctx = baton;
+       if (ctx->cb)
+               ctx->cb(ctx->session, status, target, ctx->baton);
+       free(ctx->buf);
+       free(ctx);
+}
+
+static void session2_transport_udp_pushv_finished(uv_udp_send_t *req, int status)
+{
+       struct parent_pushv_ctx *ctx = req->data;
+       if (ctx->cb)
+               ctx->cb(ctx->session, status, ctx->target, ctx->baton);
+       free(ctx->buf);
+       free(ctx);
+       free(req);
+}
+
+static void session2_transport_stream_pushv_finished(uv_write_t *req, int status)
+{
+       struct parent_pushv_ctx *ctx = req->data;
+       if (ctx->cb)
+               ctx->cb(ctx->session, status, ctx->target, ctx->baton);
+       free(ctx->buf);
+       free(ctx);
+       free(req);
+}
+
+static int concat_iovs(const struct iovec *iov, int iovcnt, char **buf, size_t *buf_len)
+{
+       if (!iov || iovcnt <= 0)
+               return kr_error(ENODATA);
+
+       size_t len = 0;
+       for (int i = 0; i < iovcnt; i++) {
+               size_t old_len = len;
+               len += iov[i].iov_len;
+               if (kr_fails_assert(len >= old_len)) {
+                       *buf = NULL;
+                       return kr_error(EFBIG);
+               }
+       }
+
+       *buf_len = len;
+       if (len == 0) {
+               *buf = NULL;
+               return kr_ok();
+       }
+
+       *buf = malloc(len);
+       kr_require(*buf);
+
+       char *c = *buf;
+       for (int i = 0; i < iovcnt; i++) {
+               if (iov[i].iov_len == 0)
+                       continue;
+               memcpy(c, iov[i].iov_base, iov[i].iov_len);
+               c += iov[i].iov_len;
+       }
+
+       return kr_ok();
+}
+
+static int session2_transport_pushv(struct session2 *s,
+                                    const struct iovec *iov, int iovcnt,
+                                    void *target,
+                                    session2_push_cb cb, void *baton)
+{
+       if (kr_fails_assert(s))
+               return kr_error(EINVAL);
+
+       struct parent_pushv_ctx *ctx = malloc(sizeof(*ctx));
+       kr_require(ctx);
+       *ctx = (struct parent_pushv_ctx) {
+               .session = s,
+               .cb = cb,
+               .baton = baton,
+               .target = target
+       };
+
+       switch (s->transport.type) {
+       case SESSION2_TRANSPORT_HANDLE:;
+               uv_handle_t *handle = s->transport.handle;
+               if (kr_fails_assert(handle)) {
+                       free(ctx);
+                       return kr_error(EINVAL);
+               }
+
+               if (handle->type == UV_UDP) {
+                       uv_udp_send_t *req = malloc(sizeof(*req));
+                       req->data = ctx;
+                       uv_udp_send(req, (uv_udp_t *)handle,
+                                       (uv_buf_t *)iov, iovcnt, target,
+                                       session2_transport_udp_pushv_finished);
+                       return kr_ok();
+               } else if (handle->type == UV_TCP) {
+                       uv_write_t *req = malloc(sizeof(*req));
+                       req->data = ctx;
+                       uv_write(req, (uv_stream_t *)handle, (uv_buf_t *)iov, iovcnt,
+                                       session2_transport_stream_pushv_finished);
+                       return kr_ok();
+               }
+
+               kr_assert(false && "Unsupported handle");
+               free(ctx);
+               return kr_error(EINVAL);
+
+       case SESSION2_TRANSPORT_PARENT:;
+               struct session2 *parent = s->transport.parent;
+               if (kr_fails_assert(parent)) {
+                       free(ctx);
+                       return kr_error(EINVAL);
+               }
+               int ret = concat_iovs(iov, iovcnt, &ctx->buf, &ctx->buf_len);
+               if (ret) {
+                       free(ctx);
+                       return ret;
+               }
+               session2_wrap(parent, ctx->buf, ctx->buf_len, target,
+                               session2_transport_parent_pushv_finished, ctx);
+               return kr_ok();
+
+       default:
+               kr_assert(false && "Invalid transport");
+               free(ctx);
+               return kr_error(EINVAL);
+       }
+}
+
+struct push_ctx {
+       struct iovec iov;
+       session2_push_cb cb;
+       void *baton;
+};
+
+static void session2_transport_single_push_finished(struct session2 *s,
+                                                    int status,
+                                                    void *target, void *baton)
+{
+       struct push_ctx *ctx = baton;
+       if (ctx->cb)
+               ctx->cb(s, status, target, ctx->baton);
+       free(ctx);
+}
+
+static inline int session2_transport_push(struct session2 *s,
+                                          char *buf, size_t buf_len,
+                                          void *target,
+                                          session2_push_cb cb, void *baton)
+{
+       struct push_ctx *ctx = malloc(sizeof(*ctx));
+       kr_require(ctx);
+       *ctx = (struct push_ctx) {
+               .iov = {
+                       .iov_base = buf,
+                       .iov_len = buf_len
+               },
+               .cb = cb,
+               .baton = baton
+       };
+
+       return session2_transport_pushv(s, &ctx->iov, 1, target,
+                       session2_transport_single_push_finished, ctx);
+}
diff --git a/daemon/session2.h b/daemon/session2.h
new file mode 100644 (file)
index 0000000..8cf1c34
--- /dev/null
@@ -0,0 +1,345 @@
+/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <uv.h>
+
+#include "contrib/mempattern.h"
+
+/* Forward declarations */
+struct session2;
+struct protolayer_cb_ctx;
+
+/** Protocol types - individual implementations of protocol layers. */
+enum protolayer_protocol {
+       PROTOLAYER_NULL = 0,
+       PROTOLAYER_TCP,
+       PROTOLAYER_UDP,
+       PROTOLAYER_TLS,
+       PROTOLAYER_HTTP,
+
+       PROTOLAYER_UDP_TO_QCONN,
+       PROTOLAYER_QCONN_TO_QSTREAM,
+
+       PROTOLAYER_DNS_DGRAM,
+       PROTOLAYER_DNS_MSTREAM, /* DoTCP allows multiple packets per stream */
+       PROTOLAYER_DNS_SSTREAM, /* DoQ only allows a single packet per stream */
+
+       PROTOLAYER_PROTOCOL_COUNT
+};
+
+#define PROTOLAYER_GRP_MAP(XX) \
+       XX(DOUDP, doudp, "DNS UDP") \
+       XX(DOTCP, dotcp, "DNS TCP") \
+       XX(DOT, dot, "DNS-over-TLS") \
+       XX(DOH, doh, "DNS-over-HTTPS")
+
+/** Pre-defined sequences of protocol layers. */
+enum protolayer_grp {
+       PROTOLAYER_GRP_NULL = 0,
+#define XX(id, name, desc) PROTOLAYER_GRP_##id,
+       PROTOLAYER_GRP_MAP(XX)
+#undef XX
+       PROTOLAYER_GRP_COUNT
+};
+
+/** Maps protocol layer group IDs to human-readable descriptions.
+ * E.g. PROTOLAYER_GRP_DOH has description 'DNS-over-HTTPS'. */
+extern char *protolayer_grp_descs[];
+
+/** Flow control indicators for protocol layer `wrap` and `unwrap` callbacks.
+ * Use with `protolayer_continue`, `protolayer_wait` and `protolayer_break`
+ * functions. */
+enum protolayer_cb_result {
+       PROTOLAYER_CB_NULL = 0,
+
+       PROTOLAYER_CB_CONTINUE,
+       PROTOLAYER_CB_WAIT,
+       PROTOLAYER_CB_BREAK,
+       PROTOLAYER_CB_PUSH,
+};
+
+enum protolayer_direction {
+       PROTOLAYER_WRAP,
+       PROTOLAYER_UNWRAP,
+};
+
+enum protolayer_ret {
+       /** Returned when a protolayer context iteration has finished
+        * processing, i.e. with _BREAK. */
+       PROTOLAYER_RET_NORMAL = 0,
+
+       /** Returned when a protolayer context iteration is waiting for an
+        * asynchronous callback to a continuation function. This will never be
+        * passed to `protolayer_finished_cb`, only returned by
+        * `session2_unwrap` or `session2_wrap`. */
+       PROTOLAYER_RET_ASYNC,
+
+       /** Returned when a protolayer context iteration has ended on a layer
+        * that needs more data from another buffer. */
+       PROTOLAYER_RET_WAITING,
+};
+
+/** Called when a context iteration (started by `session2_unwrap` or
+ * `session2_wrap`) has ended - i.e. the input buffer will not be processed
+ * any further.
+ *
+ * `status` may be one of `enum protolayer_ret` or a negative
+ * number indicating an error.
+ * `target` is the `target` parameter passed to the `session2_(un)wrap`
+ * function.
+ * `baton` is the `baton` parameter passed to the
+ * `session2_(un)wrap` function. */
+typedef void (*protolayer_finished_cb)(int status, void *target, void *baton);
+
+enum protolayer_cb_data_type {
+       PROTOLAYER_CB_DATA_NULL = 0,
+       PROTOLAYER_CB_DATA_BUFFER,
+       PROTOLAYER_CB_DATA_IOVEC,
+};
+
+/** Context for protocol layer callbacks, containing buffer data and internal
+ * information for protocol layer manager. */
+struct protolayer_cb_ctx {
+       /* read-write */
+
+       /** Data processed by the sequence of layers. All the data is always
+        * owned by its creator. It is also the layer (group) implementor's
+        * responsibility to keep data compatible in between layers. No data is
+        * ever (de-)allocated by the protolayer manager! */
+       struct {
+               enum protolayer_cb_data_type type;
+               union {
+                       /** Only valid if `type` is `_BUFFER`. */
+                       struct {
+                               char *buf;
+                               size_t len;
+                       } buffer;
+
+                       /** Only valid if `type` is `_IOVEC`. */
+                       struct {
+                               struct iovec *iov;
+                               int cnt;
+                       } iovec;
+               };
+               /** Always valid; may be `NULL`. */
+               void *target;
+       } data;
+
+       /* internal manager information - private */
+       enum protolayer_direction direction;
+       bool async_mode;
+       unsigned int layer_ix;
+       struct protolayer_manager *manager;
+       int status;
+       enum protolayer_cb_result result;
+
+       /* callback for when the layer iteration has ended - read-only */
+       protolayer_finished_cb finished_cb;
+       void *finished_cb_target;
+       void *finished_cb_baton;
+};
+
+/** Convenience function to put a buffer pointer to the specified context. */
+static inline void protolayer_set_buffer(struct protolayer_cb_ctx *ctx,
+                                         char *buf, size_t len)
+{
+       ctx->data.type = PROTOLAYER_CB_DATA_BUFFER;
+       ctx->data.buffer.buf = buf;
+       ctx->data.buffer.len = len;
+}
+
+/** Convenience function to put an iovec pointer to the specified context. */
+static inline void protolayer_set_iovec(struct protolayer_cb_ctx *ctx,
+                                        struct iovec *iov, int iovcnt)
+{
+       ctx->data.type = PROTOLAYER_CB_DATA_IOVEC;
+       ctx->data.iovec.iov = iov;
+       ctx->data.iovec.cnt = iovcnt;
+}
+
+
+/** Common header for per-session layer-specific data. When implementing
+ * a new layer, this is to be put at the beginning of the struct. */
+#define PROTOLAYER_DATA_HEADER struct {\
+       enum protolayer_protocol protocol;\
+       size_t size; /**< Size of the entire struct (incl. header) */\
+       bool processed; /**< Safeguard so that the layer does not get executed
+                        * multiple times. */\
+}
+
+/** Per-session layer-specific data - generic struct. */
+struct protolayer_data {
+       PROTOLAYER_DATA_HEADER;
+       uint8_t data[];
+};
+
+typedef void (*protolayer_cb)(struct protolayer_data *layer,
+                              struct protolayer_cb_ctx *ctx);
+typedef int (*protolayer_data_cb)(struct protolayer_manager *manager,
+                                  struct protolayer_data *layer);
+
+/** The default implementation for the `struct protolayer_globals::reset`
+ * callback. Simply calls the `deinit` and `init` callbacks. */
+int protolayer_data_reset_default(struct protolayer_manager *manager,
+                                  struct protolayer_data *layer);
+
+
+/** A collection of protocol layers and their layer-specific data. */
+struct protolayer_manager {
+       enum protolayer_grp grp;
+       struct session2 *session;
+       size_t num_layers;
+       char data[];
+};
+
+/** Allocates and initializes a new manager. */
+struct protolayer_manager *protolayer_manager_new(struct session2 *s,
+                                                  enum protolayer_grp grp);
+
+/** Deinitializes all layer data in the manager and deallocates it. */
+void protolayer_manager_free(struct protolayer_manager *m);
+
+
+/** Global data for a specific layered protocol. */
+struct protolayer_globals {
+       size_t data_size;          /**< Size of the layer-specific data struct. */
+       protolayer_data_cb init;   /**< Initializes the layer-specific data struct. */
+       protolayer_data_cb deinit; /**< De-initializes the layer-specific data struct. */
+       protolayer_data_cb reset;  /**< Resets the layer-specific data struct
+                                   * after finishing a sequence. Default
+                                   * implementation is available as
+                                   * `protolayer_data_reset_default`. */
+       protolayer_cb unwrap;      /**< Strips the buffer of protocol-specific
+                                   * data. E.g. a HTTP layer removes HTTP
+                                   * status and headers. */
+       protolayer_cb wrap;        /**< Wraps the buffer into protocol-specific
+                                   * data. E.g. a HTTP layer adds HTTP status
+                                   * and headers. */
+};
+
+/** Global data about layered protocols. Indexed by `enum protolayer_protocol`. */
+extern struct protolayer_globals protolayer_globals[PROTOLAYER_PROTOCOL_COUNT];
+
+/** *Continuation function* - signals the protolayer manager to continue
+ * processing the next layer. */
+void protolayer_continue(struct protolayer_cb_ctx *ctx);
+
+/** *Continuation function* - signals that the layer needs more data to produce
+ * a new buffer for the next layer. */
+void protolayer_wait(struct protolayer_cb_ctx *ctx);
+
+/** *Continuation function* - signals that the layer wants to stop processing
+ * of the buffer and clean up, possibly due to an error (indicated by
+ * `status`).
+ *
+ * `status` must be 0 or a negative integer. */
+void protolayer_break(struct protolayer_cb_ctx *ctx, int status);
+
+/** *Continuation function* - pushes data to the session's transport and
+ * signals that the layer wants to stop processing of the buffer and clean up.
+ *
+ * `target` is the target data for the transport - in most cases, it will be
+ * unused and may be `NULL`; except for UDP, where it must point to a `struct
+ * sockaddr_*` to indicate the target address.
+ *
+ * This function is meant to be called by the `wrap` callback of first layer in
+ * the sequence.  */
+void protolayer_pushv(struct protolayer_cb_ctx *ctx,
+                      struct iovec *iov, int iovcnt, void *target);
+
+/** *Continuation function* - pushes data to the session's transport and
+ * signals that the layer wants to stop processing of the buffer and clean up.
+ *
+ * `target` is the target data for the transport - in most cases, it will be
+ * unused and may be `NULL`; except for UDP, where it must point to a `struct
+ * sockaddr_*` to indicate the target address.
+ *
+ * This function is meant to be called by the `wrap` callback of first layer in
+ * the sequence.  */
+void protolayer_push(struct protolayer_cb_ctx *ctx, char *buf, size_t buf_len,
+                     void *target);
+
+
+/** Indicates how a session sends data in the `wrap` direction and receives
+ * data in the `unwrap` direction. */
+enum session2_transport_type {
+       SESSION2_TRANSPORT_NULL = 0,
+       SESSION2_TRANSPORT_HANDLE,
+       SESSION2_TRANSPORT_PARENT,
+};
+
+struct session2 {
+       struct {
+               enum session2_transport_type type;
+               union {
+                       void *ctx;
+                       uv_handle_t *handle;
+                       struct session2 *parent;
+               };
+       } transport;
+
+       struct protolayer_manager *layers;
+       bool outgoing : 1;
+};
+
+/** Allocates and initializes a new session with the specified protocol layer
+ * group, and the provided transport context. */
+struct session2 *session2_new(enum session2_transport_type transport_type,
+                              void *transport_ctx,
+                              enum protolayer_grp layer_grp,
+                              bool outgoing);
+
+/** Allocates and initializes a new session with the specified protocol layer
+ * group, using a *libuv handle* as its transport. */
+static inline struct session2 *session2_new_handle(uv_handle_t *handle,
+                                                   enum protolayer_grp layer_grp,
+                                                   bool outgoing)
+{
+       return session2_new(SESSION2_TRANSPORT_HANDLE, handle, layer_grp,
+                       outgoing);
+}
+
+/** Allocates and initializes a new session with the specified protocol layer
+ * group, using a *parent session* as its transport. */
+static inline struct session2 *session2_new_child(struct session2 *parent,
+                                                  enum protolayer_grp layer_grp,
+                                                  bool outgoing)
+{
+       return session2_new(SESSION2_TRANSPORT_PARENT, parent, layer_grp,
+                       outgoing);
+}
+
+/** De-allocates the session. */
+void session2_free(struct session2 *s);
+
+/** Sends the specified buffer to be processed in the `unwrap` direction by the
+ * session's protocol layers. The `target` parameter may contain a pointer to
+ * transport-specific data, e.g. for UDP, it shall contain a pointer to the
+ * sender's `struct sockaddr_*`.
+ *
+ * Once all layers are processed, `cb` is called with `baton` passed as one
+ * of its parameters. `cb` may also be `NULL`. See `protolayer_finished_cb` for
+ * more info.
+ *
+ * Returns one of `enum protolayer_ret` or a negative number
+ * indicating an error. */
+int session2_unwrap(struct session2 *s, char *buf, size_t buf_len, void *target,
+                    protolayer_finished_cb cb, void *baton);
+
+/** Sends the specified buffer to be processed in the `wrap` direction by the
+ * session's protocol layers. The `target` parameter may contain a pointer to
+ * some data specific to the producer-consumer layer of this session.
+ *
+ * Once all layers are processed, `cb` is called with `baton` passed as one
+ * of its parameters. `cb` may also be `NULL`. See `protolayer_finished_cb` for
+ * more info.
+ *
+ * Returns one of `enum protolayer_ret` or a negative number
+ * indicating an error. */
+int session2_wrap(struct session2 *s, char *buf, size_t buf_len, void *target,
+                  protolayer_finished_cb cb, void *baton);
index 1a3d7154172dbfedff585b09001daab06f1e2c12..57efcfb0b0905911548cf76793b0c42244d9720d 100644 (file)
--- a/lib/log.c
+++ b/lib/log.c
@@ -78,6 +78,7 @@ const log_group_names_t log_group_names[] = {
        GRP_NAME_ITEM(LOG_GRP_DEVEL),
        GRP_NAME_ITEM(LOG_GRP_RENUMBER),
        GRP_NAME_ITEM(LOG_GRP_EDE),
+       GRP_NAME_ITEM(LOG_GRP_PROTOLAYER),
        GRP_NAME_ITEM(LOG_GRP_REQDBG),
        { NULL, LOG_GRP_UNKNOWN },
 };
index 1a0237a18e5c9f1dd5c0220d17f350cb3f5d8f30..954f74a6700b236fa9e11c92fe29ae484954f725 100644 (file)
--- a/lib/log.h
+++ b/lib/log.h
@@ -79,6 +79,7 @@ enum kr_log_group {
        LOG_GRP_DEVEL,
        LOG_GRP_RENUMBER,
        LOG_GRP_EDE,
+       LOG_GRP_PROTOLAYER,
        /* ^^ Add new log groups above ^^. */
        LOG_GRP_REQDBG, /* Must be first non-displayed entry in enum! */
 };
@@ -131,6 +132,7 @@ enum kr_log_group {
 #define LOG_GRP_DEVEL_TAG              "devel"         /**< ``devel``: for development purposes */
 #define LOG_GRP_RENUMBER_TAG           "renum"         /**< ``renum``: operation related to renumber */
 #define LOG_GRP_EDE_TAG                        "exterr"        /**< ``exterr``: extended error module */
+#define LOG_GRP_PROTOLAYER_TAG         "prlayr"        /**< ``prlayr``: protocol layer system (session2) */
 #define LOG_GRP_REQDBG_TAG             "reqdbg"        /**< ``reqdbg``: debug logs enabled by policy actions */
 ///@}