From: Oto Šťáva Date: Mon, 18 Jul 2022 06:51:39 +0000 (+0200) Subject: session2: protocol layer API X-Git-Tag: v6.0.2~42^2~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cf3efe4da790bb0164e5bb5a94e604967585e028;p=thirdparty%2Fknot-resolver.git session2: protocol layer API --- diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua index 3a409b622..770637279 100644 --- a/daemon/lua/kres-gen-30.lua +++ b/daemon/lua/kres-gen-30.lua @@ -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; diff --git a/daemon/lua/kres-gen-31.lua b/daemon/lua/kres-gen-31.lua index f142b7948..2a90f839c 100644 --- a/daemon/lua/kres-gen-31.lua +++ b/daemon/lua/kres-gen-31.lua @@ -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; diff --git a/daemon/lua/kres-gen-32.lua b/daemon/lua/kres-gen-32.lua index 3f9a0d678..5c7ab7dfe 100644 --- a/daemon/lua/kres-gen-32.lua +++ b/daemon/lua/kres-gen-32.lua @@ -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; diff --git a/daemon/meson.build b/daemon/meson.build index 68a264668..1ff28ec03 100644 --- a/daemon/meson.build +++ b/daemon/meson.build @@ -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 index 000000000..0dcb0134b --- /dev/null +++ b/daemon/session2.c @@ -0,0 +1,549 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#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 index 000000000..8cf1c3439 --- /dev/null +++ b/daemon/session2.h @@ -0,0 +1,345 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include +#include + +#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); diff --git a/lib/log.c b/lib/log.c index 1a3d71541..57efcfb0b 100644 --- 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 }, }; diff --git a/lib/log.h b/lib/log.h index 1a0237a18..954f74a67 100644 --- 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 */ ///@}