]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon: adapt DNS-over-HTTPS for protocol layers
authorOto Šťáva <oto.stava@nic.cz>
Wed, 21 Sep 2022 06:50:14 +0000 (08:50 +0200)
committerOto Šťáva <oto.stava@nic.cz>
Thu, 26 Jan 2023 11:56:08 +0000 (12:56 +0100)
Has a few problems that need to be solved:
 - Answers are being truncated, because PROTOLAYER_DNS_DGRAM treats the
   communication as UDP. Since `session->stream` has multiple
   responsibilities, it needs to be split up into two (or more)
   different flags; then we'll be able to use it properly.
 - There is an unsolved use-after-free

daemon/http.c
daemon/http.h
daemon/io.c
daemon/main.c
daemon/meson.build
daemon/session2.c
daemon/session2.h
daemon/tls.c
daemon/worker.c
lib/resolve.c
tests/config/doh2.test.lua

index 0c6f361dd7b60f4bbbe1c7bd66f9e4b5da7acb4e..27c19bfd036615a7365a2153fa687ca6247ab61c 100644 (file)
@@ -6,22 +6,14 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#include <errno.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <nghttp2/nghttp2.h>
 
-#include "daemon/io.h"
-#include "daemon/http.h"
+#include "contrib/base64url.h"
+#include "contrib/cleanup.h"
+#include "daemon/session2.h"
 #include "daemon/worker.h"
-#include "daemon/session.h"
-#include "lib/layer/iterate.h" /* kr_response_classify */
-#include "lib/cache/util.h"
-#include "lib/generic/array.h"
 
-#include "contrib/cleanup.h"
-#include "contrib/base64url.h"
+#include "daemon/http.h"
 
 /** Makes a `nghttp2_nv`. `K` is the key, `KS` is the key length,
  * `V` is the value, `VS` is the value length. */
 #define HTTP_FRAME_HDLEN 9
 #define HTTP_FRAME_PADLEN 1
 
-#define MAX_DECIMAL_LENGTH(VT) ((CHAR_BIT * sizeof(VT) / 3) + 3)
-
-struct http_data {
-       uint8_t *buf;
-       size_t len;
-       size_t pos;
-       uint32_t ttl;
-       uv_write_cb on_write;
-       uv_write_t *req;
+struct http_stream {
+       int32_t id;
+       kr_http_header_array_t *headers;
 };
 
+typedef queue_t(struct http_stream) queue_http_stream;
 typedef array_t(nghttp2_nv) nghttp2_array_t;
 
-static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
-                             nghttp2_data_provider *prov, enum http_status status);
-static int http_send_response_rst_stream(struct http_ctx *ctx, int32_t stream_id,
-                             nghttp2_data_provider *prov, enum http_status status);
+enum http_method {
+       HTTP_METHOD_NONE = 0,
+       HTTP_METHOD_GET = 1,
+       HTTP_METHOD_POST = 2,
+       HTTP_METHOD_HEAD = 3, /**< Same as GET, except it does not return payload.
+                              * Required to be implemented by RFC 7231. */
+};
+
+/** HTTP status codes returned by kresd.
+ * This is obviously non-exhaustive of all HTTP status codes, feel free to add
+ * more if needed. */
+enum http_status {
+       HTTP_STATUS_OK                              = 200,
+       HTTP_STATUS_BAD_REQUEST                     = 400,
+       HTTP_STATUS_NOT_FOUND                       = 404,
+       HTTP_STATUS_PAYLOAD_TOO_LARGE               = 413,
+       HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE          = 415,
+       HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+       HTTP_STATUS_NOT_IMPLEMENTED                 = 501,
+};
+
+struct pl_http_sess_data {
+       PROTOLAYER_DATA_HEADER();
+       struct nghttp2_session *h2;
+
+       queue_http_stream streams;  /* Streams present in the wire buffer. */
+       trie_t *stream_write_data;  /* Dictionary of stream data that needs to be freed after write. */
+       int32_t incomplete_stream;
+       int32_t last_stream;   /* The last used stream - mostly the same as incomplete_stream, but can be used after
+                                 completion for sending HTTP status codes. */
+       enum http_method current_method;
+       char *uri_path;
+       kr_http_header_array_t *headers;
+       enum http_status status;
+       struct wire_buf wire_buf;
+};
+
+struct http_send_ctx {
+       struct pl_http_sess_data *sess_data;
+       uint8_t data[];
+};
+
 
 /** Checks if `status` has the correct `category`.
  * E.g. status 200 has category 2, status 404 has category 4, 501 has category 5 etc. */
@@ -71,103 +96,16 @@ static inline bool http_status_has_category(enum http_status status, int categor
        return status / 100 == category;
 }
 
-/*
- * Write HTTP/2 protocol data to underlying transport layer.
- */
-static ssize_t send_callback(nghttp2_session *h2, const uint8_t *data, size_t length,
-                            int flags, void *user_data)
-{
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
-       return ctx->send_cb(data, length, ctx->session);
-}
-
 /*
  * Sets the HTTP status of the specified `context`, but only if its status has
  * not already been changed to an unsuccessful one.
  */
-static inline void set_status(struct http_ctx *ctx, enum http_status status)
+static inline void set_status(struct pl_http_sess_data *ctx, enum http_status status)
 {
        if (http_status_has_category(ctx->status, 2))
                ctx->status = status;
 }
 
-/*
- * Send padding length (if greater than zero).
- */
-static int send_padlen(struct http_ctx *ctx, size_t padlen)
-{
-       int ret;
-       uint8_t buf;
-
-       if (padlen == 0)
-               return 0;
-
-       buf = (uint8_t)padlen;
-       ret = ctx->send_cb(&buf, HTTP_FRAME_PADLEN, ctx->session);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       return 0;
-}
-
-/*
- * Send HTTP/2 zero-byte padding.
- *
- * This sends only padlen-1 bytes of padding (if any), since padlen itself
- * (already sent) is also considered padding. Refer to RFC7540, section 6.1
- */
-static int send_padding(struct http_ctx *ctx, uint8_t padlen)
-{
-       static const uint8_t buf[UINT8_MAX];
-       int ret;
-
-       if (padlen <= 1)
-               return 0;
-
-       ret = ctx->send_cb(buf, padlen - 1, ctx->session);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       return 0;
-}
-
-/*
- * Write entire DATA frame to underlying transport layer.
- *
- * This function reads directly from data provider to avoid copying packet wire buffer.
- */
-static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const uint8_t *framehd,
-                             size_t length, nghttp2_data_source *source, void *user_data)
-{
-       struct http_data *data;
-       int ret;
-       struct http_ctx *ctx;
-
-       ctx = (struct http_ctx *)user_data;
-       data = (struct http_data*)source->ptr;
-
-       ret = ctx->send_cb(framehd, HTTP_FRAME_HDLEN, ctx->session);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       ret = send_padlen(ctx, frame->data.padlen);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       ret = ctx->send_cb(data->buf + data->pos, length, ctx->session);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-       data->pos += length;
-       if (kr_fails_assert(data->pos <= data->len))
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       ret = send_padding(ctx, (uint8_t)frame->data.padlen);
-       if (ret < 0)
-               return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-       return 0;
-}
-
 /*
  * Check endpoint and uri path
  */
@@ -219,7 +157,7 @@ static kr_http_header_array_t *headers_dup(kr_http_header_array_t *src)
 /*
  * Process a query from URI path if there's base64url encoded dns variable.
  */
-static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stream_id)
+static int process_uri_path(struct pl_http_sess_data *ctx, const char* path, int32_t stream_id)
 {
        if (!ctx || !path)
                return kr_error(EINVAL);
@@ -248,19 +186,19 @@ static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stre
        if (end == NULL)
                end = beg + strlen(beg);
 
-       ctx->buf_pos = sizeof(uint16_t);  /* Reserve 2B for dnsmsg len. */
-       remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
-       dest = ctx->buf + ctx->buf_pos;
+       struct wire_buf *wb = &ctx->wire_buf;
+       remaining = wire_buf_free_space_length(wb);
+       dest = wire_buf_free_space(wb);
 
        /* Decode dns message from the parameter */
        int ret = kr_base64url_decode((uint8_t*)beg, end - beg, dest, remaining);
        if (ret < 0) {
-               ctx->buf_pos = 0;
+               wire_buf_reset(wb);
                kr_log_debug(DOH, "[%p] base64url decode failed %s\n", (void *)ctx->h2, kr_strerror(ret));
                return ret;
        }
 
-       ctx->buf_pos += ret;
+       wire_buf_consume(wb, ret);
 
        struct http_stream stream = {
                .id = stream_id,
@@ -289,8 +227,9 @@ void http_free_headers(kr_http_header_array_t *headers)
        array_clear(*headers);
        free(headers);
 }
+
 /* Return the http ctx into a pristine state in which no stream is being processed. */
-static void http_cleanup_stream(struct http_ctx *ctx)
+static void http_cleanup_stream(struct pl_http_sess_data *ctx)
 {
        ctx->incomplete_stream = -1;
        ctx->current_method = HTTP_METHOD_NONE;
@@ -300,6 +239,254 @@ static void http_cleanup_stream(struct http_ctx *ctx)
        ctx->headers = NULL;
 }
 
+/** Convenience function for pushing `nghttp2_nv` made with MAKE_*_NV into
+ * arrays. */
+static inline void push_nv(nghttp2_array_t *arr, nghttp2_nv nv)
+{
+       array_push(*arr, nv);
+}
+
+/*
+ * Send dns response provided by the HTTP/2 data provider.
+ *
+ * Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control.
+ */
+static int http_send_response(struct pl_http_sess_data *http, int32_t stream_id,
+                              nghttp2_data_provider *prov, enum http_status status)
+{
+       nghttp2_session *h2 = http->h2;
+       int ret;
+
+       nghttp2_array_t hdrs;
+       array_init(hdrs);
+       array_reserve(hdrs, 5);
+
+       auto_free char *status_str = NULL;
+       if (likely(status == HTTP_STATUS_OK)) {
+               push_nv(&hdrs, MAKE_STATIC_NV(":status", "200"));
+       } else {
+               int status_len = asprintf(&status_str, "%d", (int)status);
+               kr_require(status_len >= 0);
+               push_nv(&hdrs, MAKE_STATIC_KEY_NV(":status", status_str, status_len));
+       }
+       push_nv(&hdrs, MAKE_STATIC_NV("access-control-allow-origin", "*"));
+
+       struct protolayer_iter_ctx *ctx = NULL;
+       auto_free char *size = NULL;
+       auto_free char *max_age = NULL;
+
+       if (http->current_method == HTTP_METHOD_HEAD && prov) {
+               /* HEAD method is the same as GET but only returns headers,
+                * so let's clean up the data here as we don't need it. */
+               protolayer_break(prov->source.ptr, kr_ok());
+               prov = NULL;
+       }
+
+       if (prov) {
+               ctx = prov->source.ptr;
+               const char *directive_max_age = "max-age=";
+               int max_age_len;
+               int size_len;
+
+               size_len = asprintf(&size, "%zu", protolayer_payload_size(&ctx->payload));
+               kr_require(size_len >= 0);
+
+               max_age_len = asprintf(&max_age, "%s%" PRIu32, directive_max_age, ctx->payload.ttl);
+               kr_require(max_age_len >= 0);
+
+               push_nv(&hdrs, MAKE_STATIC_NV("content-type", "application/dns-message"));
+               push_nv(&hdrs, MAKE_STATIC_KEY_NV("content-length", size, size_len));
+               push_nv(&hdrs, MAKE_STATIC_KEY_NV("cache-control", max_age, max_age_len));
+       }
+
+       ret = nghttp2_submit_response(h2, stream_id, hdrs.at, hdrs.len, prov);
+       array_clear(hdrs);
+       if (ret != 0) {
+               kr_log_debug(DOH, "[%p] nghttp2_submit_response failed: %s\n", (void *)h2, nghttp2_strerror(ret));
+               if (ctx)
+                       protolayer_break(ctx, kr_error(EIO));
+               return kr_error(EIO);
+       }
+
+       /* Keep reference to data, since we need to free it later on.
+        * Due to HTTP/2 flow control, this stream data may be sent at a later point, or not at all.
+        */
+       trie_val_t *stream_data_p = trie_get_ins(http->stream_write_data, (char *)&stream_id, sizeof(stream_id));
+       if (kr_fails_assert(stream_data_p)) {
+               kr_log_debug(DOH, "[%p] failed to insert to stream_write_data\n", (void *)h2);
+               if (ctx)
+                       protolayer_break(ctx, kr_error(EIO));
+               return kr_error(EIO);
+       }
+       *stream_data_p = ctx;
+       ret = nghttp2_session_send(h2);
+       if(ret) {
+               kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s\n", (void *)h2, nghttp2_strerror(ret));
+
+               /* At this point, there was an error in some nghttp2 callback. The protolayer_break()
+                * function which also calls free(ctx) may or may not have been called. Therefore,
+                * we must guarantee it will have been called by explicitly closing the stream. */
+               nghttp2_submit_rst_stream(h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR);
+               return kr_error(EIO);
+       }
+
+       return 0;
+}
+
+/*
+ * Same as `http_send_response`, but resets the HTTP stream afterwards. Used
+ * for sending negative status messages.
+ */
+static int http_send_response_rst_stream(struct pl_http_sess_data *ctx, int32_t stream_id,
+                                         nghttp2_data_provider *prov, enum http_status status)
+{
+       int ret = http_send_response(ctx, stream_id, prov, status);
+       if (ret)
+               return ret;
+
+       ctx->last_stream = -1;
+       nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_NO_ERROR);
+       ret = nghttp2_session_send(ctx->h2);
+       return ret;
+}
+
+static void callback_finished_free_baton(int status, struct session2 *session,
+                                         const void *target, void *baton)
+{
+       free(baton);
+}
+
+/*
+ * Write HTTP/2 protocol data to underlying transport layer.
+ */
+static ssize_t send_callback(nghttp2_session *h2, const uint8_t *data, size_t length,
+                            int flags, void *user_data)
+{
+       struct pl_http_sess_data *http = user_data;
+       struct http_send_ctx *send_ctx = malloc(sizeof(*send_ctx) + length);
+       kr_require(send_ctx);
+       send_ctx->sess_data = http;
+       memcpy(send_ctx->data, data, length);
+
+       kr_log_debug(DOH, "[%p] send_callback: %p\n", (void *)h2, (void *)send_ctx->data);
+       session2_wrap_after(http->session, PROTOLAYER_HTTP,
+                       protolayer_buffer(send_ctx->data, length), NULL,
+                       callback_finished_free_baton, send_ctx);
+
+       return length;
+}
+
+struct http_send_data_ctx {
+       uint8_t padlen;
+       struct iovec iov[];
+};
+
+static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const uint8_t *framehd,
+                             size_t length, nghttp2_data_source *source, void *user_data)
+{
+       struct pl_http_sess_data *http = user_data;
+
+/* I'm not yet sure if the below code is correct... the other one should be,
+ * but it's probably considerably slower. */
+#if 1
+       int has_padding = !!(frame->data.padlen);
+       uint8_t padlen = (frame->data.padlen > 1) ? frame->data.padlen : 2;
+
+       struct protolayer_iter_ctx *ctx = source->ptr;
+       struct protolayer_payload pld = ctx->payload;
+
+       struct iovec bufiov;
+       struct iovec *dataiov;
+       int dataiovcnt;
+       if (pld.type == PROTOLAYER_PAYLOAD_BUFFER) {
+               bufiov = (struct iovec){ pld.buffer.buf, pld.buffer.len };
+               dataiov = &bufiov;
+               dataiovcnt = 1;
+       } else if (pld.type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
+               bufiov = (struct iovec){
+                       wire_buf_data(pld.wire_buf),
+                       wire_buf_data_length(pld.wire_buf)
+               };
+               dataiov = &bufiov;
+               dataiovcnt = 1;
+       } else if (pld.type == PROTOLAYER_PAYLOAD_IOVEC) {
+               dataiov = pld.iovec.iov;
+               dataiovcnt = pld.iovec.cnt;
+       } else {
+               kr_assert(false && "Invalid payload");
+               protolayer_break(ctx, kr_error(EINVAL));
+               return kr_error(EINVAL);
+       }
+
+       int iovcnt = 1 + dataiovcnt + (2 * has_padding);
+       struct http_send_data_ctx *sdctx = calloc(iovcnt, sizeof(*ctx) + sizeof(struct iovec[iovcnt]));
+       sdctx->padlen = padlen;
+
+       struct iovec *iov = sdctx->iov;
+       static const uint8_t padding[UINT8_MAX];
+
+       int cur = 0;
+       iov[cur++] = (struct iovec){ (void *)framehd, HTTP_FRAME_HDLEN };
+
+       if (has_padding)
+               iov[cur++] = (struct iovec){ &sdctx->padlen, HTTP_FRAME_PADLEN };
+
+       memcpy(&iov[cur], dataiov, sizeof(struct iovec[dataiovcnt]));
+       cur += dataiovcnt;
+
+       if (has_padding)
+               iov[cur++] = (struct iovec){ (void *)padding, padlen - 1 };
+
+       kr_assert(cur == iovcnt);
+       int ret = session2_wrap_after(http->session, PROTOLAYER_HTTP,
+                       protolayer_iovec(iov, cur),
+                       NULL, callback_finished_free_baton, sdctx);
+
+       if (ret < 0)
+               return ret;
+       return 0;
+#else
+       struct protolayer_iter_ctx *ctx = source->ptr;
+       if (kr_fails_assert(ctx)) {
+               return NGHTTP2_ERR_WOULDBLOCK;
+       }
+
+       size_t total_len = HTTP_FRAME_HDLEN + length + frame->data.padlen;
+       struct http_send_ctx *send_ctx = malloc(sizeof(*send_ctx) + total_len);
+       kr_require(send_ctx);
+
+       send_ctx->sess_data = http;
+       uint8_t *cur = send_ctx->data;
+
+       /* TODO - remove these unnecessary copies */
+
+       /* Frame header */
+       memcpy(cur, framehd, HTTP_FRAME_HDLEN);
+       cur += HTTP_FRAME_HDLEN;
+
+       /* Length of frame padding */
+       if (frame->data.padlen) {
+               *cur = frame->data.padlen - 1;
+               cur++;
+       }
+
+       /* Data */
+       size_t copied = protolayer_payload_copy(cur, &ctx->payload, length);
+       cur += copied;
+
+       /* Padding */
+       if (frame->data.padlen > 1)
+               bzero(cur, frame->data.padlen - 1);
+
+       kr_log_debug(DOH, "[%p] send_data_callback: %p\n", (void *)h2, (void *)send_ctx->data);
+       session2_wrap_after(http->session, PROTOLAYER_HTTP,
+                       protolayer_buffer(send_ctx->data, total_len), NULL,
+                       callback_finished_free_baton, send_ctx);
+
+       return 0;
+#endif
+}
+
 /*
  * Save stream id from first header's frame.
  *
@@ -310,7 +497,7 @@ static void http_cleanup_stream(struct http_ctx *ctx)
 static int begin_headers_callback(nghttp2_session *h2, const nghttp2_frame *frame,
                                 void *user_data)
 {
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
+       struct pl_http_sess_data *ctx = user_data;
        int32_t stream_id = frame->hd.stream_id;
 
        if (frame->hd.type != NGHTTP2_HEADERS ||
@@ -342,7 +529,7 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
                           const uint8_t *name, size_t namelen, const uint8_t *value,
                           size_t valuelen, uint8_t flags, void *user_data)
 {
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
+       struct pl_http_sess_data *ctx = user_data;
        int32_t stream_id = frame->hd.stream_id;
 
        if (frame->hd.type != NGHTTP2_HEADERS)
@@ -436,9 +623,7 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
 static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t stream_id,
                                    const uint8_t *data, size_t len, void *user_data)
 {
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
-       ssize_t remaining;
-       ssize_t required;
+       struct pl_http_sess_data *ctx = user_data;
        bool is_first = queue_len(ctx->streams) == 0 || queue_tail(ctx->streams).id != ctx->incomplete_stream;
 
        if (ctx->incomplete_stream != stream_id) {
@@ -449,8 +634,10 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t
                return 0;
        }
 
-       remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
-       required = len;
+       struct wire_buf *wb = &ctx->wire_buf;
+
+       ssize_t remaining = wire_buf_free_space_length(wb);
+       ssize_t required = len;
        /* First data chunk of the new stream */
        if (is_first)
                required += sizeof(uint16_t);
@@ -462,14 +649,8 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t
        }
 
        if (is_first) {
-               /* FIXME: reserving the 2B length should be done elsewhere,
-                * ideally for both POST and GET at the same time. The right
-                * place would probably be after receiving HEADERS frame in
-                * on_frame_recv()
-                *
-                * queue_push() should be moved: see FIXME in
+               /* queue_push() should be moved: see FIXME in
                 * submit_to_wirebuffer() */
-               ctx->buf_pos = sizeof(uint16_t);  /* Reserve 2B for dnsmsg len. */
                struct http_stream stream = {
                        .id = stream_id,
                        .headers = headers_dup(ctx->headers)
@@ -477,15 +658,14 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t
                queue_push(ctx->streams, stream);
        }
 
-       memmove(ctx->buf + ctx->buf_pos, data, len);
-       ctx->buf_pos += len;
+       memmove(wire_buf_free_space(wb), data, len);
+       wire_buf_consume(wb, len);
        return 0;
 }
 
-static int submit_to_wirebuffer(struct http_ctx *ctx)
+static int submit_to_wirebuffer(struct pl_http_sess_data *ctx)
 {
        int ret = -1;
-       ssize_t len;
 
        /* Free http_ctx's headers - by now the stream has obtained its own
         * copy of the headers which it can operate on. */
@@ -510,7 +690,9 @@ static int submit_to_wirebuffer(struct http_ctx *ctx)
        http_free_headers(ctx->headers);
        ctx->headers = NULL;
 
-       len = ctx->buf_pos - sizeof(uint16_t);
+       struct wire_buf *wb = &ctx->wire_buf;
+
+       ssize_t len = wire_buf_data_length(wb) - sizeof(uint16_t);
        if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
                kr_log_debug(DOH, "[%p] invalid dnsmsg size: %zd B\n", (void *)ctx->h2, len);
                set_status(ctx, (len <= 0)
@@ -520,12 +702,9 @@ static int submit_to_wirebuffer(struct http_ctx *ctx)
                goto cleanup;
        }
 
-       /* Submit data to wirebuffer. */
-       knot_wire_write_u16(ctx->buf, len);
-       ctx->submitted += ctx->buf_pos;
-       ctx->buf += ctx->buf_pos;
-       ctx->buf_pos = 0;
        ret = 0;
+       session2_unwrap_after(ctx->session, PROTOLAYER_HTTP,
+                       protolayer_wire_buf(wb), NULL, NULL, NULL);
 cleanup:
        http_cleanup_stream(ctx);
        return ret;
@@ -541,14 +720,12 @@ cleanup:
  */
 static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *frame, void *user_data)
 {
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
+       struct pl_http_sess_data *ctx = user_data;
        int32_t stream_id = frame->hd.stream_id;
        if(kr_fails_assert(stream_id != -1))
                return NGHTTP2_ERR_CALLBACK_FAILURE;
 
        if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) {
-               ctx->streaming = false;
-
                if (ctx->current_method == HTTP_METHOD_GET || ctx->current_method == HTTP_METHOD_HEAD) {
                        if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) {
                                /* End processing - don't submit to wirebuffer. */
@@ -565,65 +742,77 @@ static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *fram
 }
 
 /*
- * Call on_write() callback for written (or failed) packet data.
+ * Cleanup for closed streams.
  */
-static void on_pkt_write(struct http_data *data, int status)
+static int on_stream_close_callback(nghttp2_session *h2, int32_t stream_id,
+                                    uint32_t error_code, void *user_data)
 {
-       if (!data || !data->req || !data->on_write)
-               return;
+       struct protolayer_iter_ctx *ctx;
+       struct pl_http_sess_data *http = user_data;
+       int ret;
+
+       /* Ensure connection state is cleaned up in case the stream gets
+        * unexpectedly closed, e.g. by PROTOCOL_ERROR issued from nghttp2. */
+       if (http->incomplete_stream == stream_id)
+               http_cleanup_stream(http);
 
-       data->on_write(data->req, status);
-       free(data);
+       ret = trie_del(http->stream_write_data, (char *)&stream_id, sizeof(stream_id), (trie_val_t*)&ctx);
+       if (ret == KNOT_EOK && ctx)
+               protolayer_break(ctx, error_code == 0 ? 0 : kr_error(EIO));
+
+       return 0;
 }
 
-static int stream_write_data_free_err(trie_val_t *val, void *null)
+int http_send_status(struct pl_http_sess_data *ctx, enum http_status status)
 {
-       on_pkt_write(*val, kr_error(EIO));
+       if (ctx->last_stream >= 0)
+               return http_send_response_rst_stream(
+                               ctx, ctx->last_stream, NULL, status);
+
        return 0;
 }
 
 /*
- * Cleanup for closed streams.
+ * Provide data from buffer to HTTP/2 library.
+ *
+ * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY
+ * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback.
+ *
+ * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback
  */
-static int on_stream_close_callback(nghttp2_session *h2, int32_t stream_id,
-                                   uint32_t error_code, void *user_data)
+static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf,
+                            size_t length, uint32_t *data_flags,
+                            nghttp2_data_source *source, void *user_data)
 {
-       struct http_data *data;
-       struct http_ctx *ctx = (struct http_ctx *)user_data;
-       int ret;
+       struct protolayer_iter_ctx *ctx = source->ptr;
+       size_t avail = protolayer_payload_size(&ctx->payload);
+       size_t send = MIN(avail, length);
 
-       /* Ensure connection state is cleaned up in case the stream gets
-        * unexpectedly closed, e.g. by PROTOCOL_ERROR issued from nghttp2. */
-       if (ctx->incomplete_stream == stream_id)
-               http_cleanup_stream(ctx);
-
-       ret = trie_del(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id), (trie_val_t*)&data);
-       if (ret == KNOT_EOK && data)
-               on_pkt_write(data, error_code == 0 ? 0 : kr_error(EIO));
+       if (avail == send)
+               *data_flags |= NGHTTP2_DATA_FLAG_EOF;
 
-       return 0;
+       *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+       return send;
 }
 
-/*
- * Setup and initialize connection with new HTTP/2 context.
- */
-struct http_ctx* http_new(struct session *session, http_send_callback send_cb)
+static int pl_http_sess_init(struct protolayer_manager *manager,
+                             void *data, void *param)
 {
-       if (!session || !send_cb)
-               return NULL;
+       struct pl_http_sess_data *http = data;
 
        nghttp2_session_callbacks *callbacks;
-       struct http_ctx *ctx = NULL;
        static const nghttp2_settings_entry iv[] = {
                { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
        };
 
-       if (nghttp2_session_callbacks_new(&callbacks) < 0)
-               return ctx;
+       int ret = nghttp2_session_callbacks_new(&callbacks);
+       if (ret < 0)
+               return ret;
+
        nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
        nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback);
-       nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
        nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, begin_headers_callback);
+       nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
                callbacks, data_chunk_recv_callback);
        nghttp2_session_callbacks_set_on_frame_recv_callback(
@@ -631,323 +820,176 @@ struct http_ctx* http_new(struct session *session, http_send_callback send_cb)
        nghttp2_session_callbacks_set_on_stream_close_callback(
                callbacks, on_stream_close_callback);
 
-       ctx = calloc(1UL, sizeof(struct http_ctx));
-       if (!ctx)
-               goto finish;
-
-       ctx->send_cb = send_cb;
-       ctx->session = session;
-       queue_init(ctx->streams);
-       ctx->stream_write_data = trie_create(NULL);
-       ctx->incomplete_stream = -1;
-       ctx->last_stream = -1;
-       ctx->submitted = 0;
-       ctx->streaming = true;
-       ctx->current_method = HTTP_METHOD_NONE;
-       ctx->uri_path = NULL;
-       ctx->status = HTTP_STATUS_OK;
-
-       nghttp2_session_server_new(&ctx->h2, callbacks, ctx);
-       nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
-               iv, sizeof(iv)/sizeof(*iv));
-
-       struct sockaddr *peer = session_get_peer(session);
-       kr_log_debug(DOH, "[%p] h2 session created for %s\n", (void *)ctx->h2, kr_straddr(peer));
-finish:
-       nghttp2_session_callbacks_del(callbacks);
-       return ctx;
-}
+       queue_init(http->streams);
+       http->stream_write_data = trie_create(NULL);
+       http->incomplete_stream = -1;
+       http->last_stream = -1;
+       http->current_method = HTTP_METHOD_NONE;
+       http->uri_path = NULL;
+       http->status = HTTP_STATUS_OK;
+       wire_buf_init(&http->wire_buf, KNOT_WIRE_MAX_PKTSIZE);
 
-/*
- * Process inbound HTTP/2 data and return number of bytes read into session wire buffer.
- *
- * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc.
- *
- * Returns 1 if stream has not ended yet, 0 if the stream has ended, or
- * a negative value on error.
- */
-int http_process_input_data(struct session *session, const uint8_t *buf, ssize_t nread,
-                           ssize_t *out_submitted)
-{
-       struct http_ctx *ctx = session_http_get_server_ctx(session);
-       ssize_t ret = 0;
-
-       if (!ctx->h2)
-               return kr_error(ENOSYS);
-       if (kr_fails_assert(ctx->session == session))
-               return kr_error(EINVAL);
+       ret = nghttp2_session_server_new(&http->h2, callbacks, http);
+       if (ret < 0)
+               goto exit_callbacks;
+       nghttp2_submit_settings(http->h2, NGHTTP2_FLAG_NONE, iv, ARRAY_SIZE(iv));
 
-       /* FIXME It is possible for the TLS/HTTP processing to be cut off at
-        * any point, waiting for more data. If we're using POST which is split
-        * into multiple DATA frames and such a stream is in the middle of
-        * processing, resetting buf_pos will corrupt its contents (and the
-        * query will be ignored).  This may also be problematic in other
-        * cases.  */
-       ctx->submitted = 0;
-       ctx->streaming = true;
-       ctx->buf = session_wirebuf_get_free_start(session);
-       ctx->buf_pos = 0;
-       ctx->buf_size = session_wirebuf_get_free_size(session);
-
-       ret = nghttp2_session_mem_recv(ctx->h2, buf, nread);
-       if (ret < 0) {
-               kr_log_debug(DOH, "[%p] nghttp2_session_mem_recv failed: %s (%zd)\n",
-                            (void *)ctx->h2, nghttp2_strerror(ret), ret);
-               return kr_error(EIO);
-       }
+       struct sockaddr *peer = session2_get_peer(manager->session);
+       kr_log_debug(DOH, "[%p] h2 session created for %s\n", (void *)http->h2, kr_straddr(peer));
 
-       ret = nghttp2_session_send(ctx->h2);
-       if (ret < 0) {
-               kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s (%zd)\n",
-                            (void *)ctx->h2, nghttp2_strerror(ret), ret);
-               return kr_error(EIO);
-       }
+       manager->session->http = true;
 
-       if (!http_status_has_category(ctx->status, 2)) {
-               *out_submitted = 0;
-               http_send_status(session, ctx->status);
-               http_cleanup_stream(ctx);
-               return 0;
-       }
+       ret = kr_ok();
 
-       *out_submitted = ctx->submitted;
-       return ctx->streaming;
+exit_callbacks:
+       nghttp2_session_callbacks_del(callbacks);
+       return ret;
 }
 
-int http_send_status(struct session *session, enum http_status status)
+static int stream_write_data_break_err(trie_val_t *val, void *baton)
 {
-       struct http_ctx *ctx = session_http_get_server_ctx(session);
-       if (ctx->last_stream >= 0)
-               return http_send_response_rst_stream(
-                               ctx, ctx->last_stream, NULL, status);
-
+       protolayer_break(*val, kr_error(EIO));
        return 0;
 }
 
-/*
- * Provide data from buffer to HTTP/2 library.
- *
- * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY
- * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback.
- *
- * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback
- */
-static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf,
-                            size_t length, uint32_t *data_flags,
-                            nghttp2_data_source *source, void *user_data)
+static int pl_http_sess_deinit(struct protolayer_manager *manager,
+                               void *data)
 {
-       struct http_data *data;
-       size_t avail;
-       size_t send;
+       struct pl_http_sess_data *http = data;
 
-       data = (struct http_data*)source->ptr;
-       avail = data->len - data->pos;
-       send = MIN(avail, length);
+       kr_log_debug(DOH, "[%p] h2 session freed\n", (void *)http->h2);
 
-       if (avail == send)
-               *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+       while (queue_len(http->streams) > 0) {
+               struct http_stream *stream = &queue_head(http->streams);
+               http_free_headers(stream->headers);
+               queue_pop(http->streams);
+       }
 
-       *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
-       return send;
-}
+       trie_apply(http->stream_write_data, stream_write_data_break_err, NULL);
+       trie_free(http->stream_write_data);
 
-/** Convenience function for pushing `nghttp2_nv` made with MAKE_*_NV into
- * arrays. */
-static inline void push_nv(nghttp2_array_t *arr, nghttp2_nv nv)
-{
-       array_push(*arr, nv);
+       http_cleanup_stream(http);
+       queue_deinit(http->streams);
+       wire_buf_deinit(&http->wire_buf);
+       nghttp2_session_del(http->h2);
+
+       return 0;
 }
 
-/*
- * Send dns response provided by the HTTP/2 data provider.
- *
- * Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control.
- */
-static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
-                             nghttp2_data_provider *prov, enum http_status status)
+static enum protolayer_iter_cb_result pl_http_unwrap(
+               void *sess_data, void *iter_data,
+               struct protolayer_iter_ctx *ctx)
 {
-       nghttp2_session *h2 = ctx->h2;
-       int ret;
-
-       nghttp2_array_t hdrs;
-       array_init(hdrs);
-       array_reserve(hdrs, 5);
-
-       auto_free char *status_str = NULL;
-       if (likely(status == HTTP_STATUS_OK)) {
-               push_nv(&hdrs, MAKE_STATIC_NV(":status", "200"));
-       } else {
-               int status_len = asprintf(&status_str, "%d", (int)status);
-               kr_require(status_len >= 0);
-               push_nv(&hdrs, MAKE_STATIC_KEY_NV(":status", status_str, status_len));
-       }
-       push_nv(&hdrs, MAKE_STATIC_NV("access-control-allow-origin", "*"));
-
-       struct http_data *data = NULL;
-       auto_free char *size = NULL;
-       auto_free char *max_age = NULL;
-
-       if (ctx->current_method == HTTP_METHOD_HEAD && prov) {
-               /* HEAD method is the same as GET but only returns headers,
-                * so let's clean up the data here as we don't need it. */
-               free(prov->source.ptr);
-               prov = NULL;
-       }
-
-       if (prov) {
-               data = (struct http_data*)prov->source.ptr;
-               const char *directive_max_age = "max-age=";
-               int max_age_len;
-               int size_len;
+       struct pl_http_sess_data *http = sess_data;
+       ssize_t ret = 0;
 
-               size_len = asprintf(&size, "%zu", data->len);
-               kr_require(size_len >= 0);
-               max_age_len = asprintf(&max_age, "%s%" PRIu32, directive_max_age, data->ttl);
-               kr_require(max_age_len >= 0);
+       if (!http->h2)
+               return kr_error(ENOSYS);
 
-               push_nv(&hdrs, MAKE_STATIC_NV("content-type", "application/dns-message"));
-               push_nv(&hdrs, MAKE_STATIC_KEY_NV("content-length", size, size_len));
-               push_nv(&hdrs, MAKE_STATIC_KEY_NV("cache-control", max_age, max_age_len));
+       struct protolayer_payload pld = ctx->payload;
+       if (pld.type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
+               pld = protolayer_as_buffer(&pld);
        }
 
-       ret = nghttp2_submit_response(h2, stream_id, hdrs.at, hdrs.len, prov);
-       array_clear(hdrs);
-       if (ret != 0) {
-               kr_log_debug(DOH, "[%p] nghttp2_submit_response failed: %s\n", (void *)h2, nghttp2_strerror(ret));
-               free(data);
-               return kr_error(EIO);
+       if (pld.type == PROTOLAYER_PAYLOAD_BUFFER) {
+               ret = nghttp2_session_mem_recv(http->h2,
+                               pld.buffer.buf, pld.buffer.len);
+               if (ret < 0) {
+                       kr_log_debug(DOH, "[%p] nghttp2_session_mem_recv failed: %s (%zd)\n",
+                                       (void *)http->h2, nghttp2_strerror(ret), ret);
+                       return protolayer_break(ctx, kr_error(EIO));
+               }
+       } else if (pld.type == PROTOLAYER_PAYLOAD_IOVEC) {
+               for (int i = 0; i < pld.iovec.cnt; i++) {
+                       ret = nghttp2_session_mem_recv(http->h2,
+                                       pld.iovec.iov[i].iov_base,
+                                       pld.iovec.iov[i].iov_len);
+                       if (ret < 0) {
+                               kr_log_debug(DOH, "[%p] nghttp2_session_mem_recv failed: %s (%zd)\n",
+                                               (void *)http->h2, nghttp2_strerror(ret), ret);
+                               return protolayer_break(ctx, kr_error(EIO));
+                       }
+               }
+       } else {
+               kr_assert(false && "Invalid payload type");
+               return protolayer_break(ctx, kr_error(EIO));
        }
 
-       /* Keep reference to data, since we need to free it later on.
-        * Due to HTTP/2 flow control, this stream data may be sent at a later point, or not at all.
-        */
-       trie_val_t *stream_data_p = trie_get_ins(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id));
-       if (kr_fails_assert(stream_data_p)) {
-               kr_log_debug(DOH, "[%p] failed to insert to stream_write_data\n", (void *)h2);
-               free(data);
+       ret = nghttp2_session_send(http->h2);
+       if (ret < 0) {
+               kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s (%zd)\n",
+                            (void *)http->h2, nghttp2_strerror(ret), ret);
                return kr_error(EIO);
        }
-       *stream_data_p = data;
-       ret = nghttp2_session_send(h2);
-       if(ret < 0) {
-               kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s\n", (void *)h2, nghttp2_strerror(ret));
 
-               /* At this point, there was an error in some nghttp2 callback. The on_pkt_write()
-                * callback which also calls free(data) may or may not have been called. Therefore,
-                * we must guarantee it will have been called by explicitly closing the stream.
-                * Afterwards, we have no option but to pretend this function was a success. If we
-                * returned an error, qr_task_send() logic would lead to a double-free because
-                * on_write() was already called. */
-               nghttp2_submit_rst_stream(h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR);
-               return 0;
+       if (!http_status_has_category(http->status, 2)) {
+               http_send_status(http, http->status);
+               http_cleanup_stream(http);
+               return protolayer_break(ctx, kr_error(EIO));
        }
 
-       return 0;
-}
-
-/*
- * Same as `http_send_response`, but resets the HTTP stream afterwards. Used
- * for sending negative status messages.
- */
-static int http_send_response_rst_stream(struct http_ctx *ctx, int32_t stream_id,
-                             nghttp2_data_provider *prov, enum http_status status)
-{
-       int ret = http_send_response(ctx, stream_id, prov, status);
-       if (ret)
-               return ret;
-
-       ctx->last_stream = -1;
-       nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_NO_ERROR);
-       ret = nghttp2_session_send(ctx->h2);
-       return ret;
+       return protolayer_break(ctx, kr_ok());
 }
 
-
-/*
- * Send HTTP/2 stream data created from packet's wire buffer.
- *
- * If this function returns an error, the on_write() callback isn't (and
- * mustn't be!) called, since such errors are handled in an upper layer - in
- * qr_task_step() in daemon/worker.
- */
-static int http_write_pkt(struct http_ctx *ctx, knot_pkt_t *pkt, int32_t stream_id,
-                         uv_write_t *req, uv_write_cb on_write)
+static enum protolayer_iter_cb_result pl_http_wrap(
+               void *sess_data, void *iter_data,
+               struct protolayer_iter_ctx *ctx)
 {
-       struct http_data *data;
        nghttp2_data_provider prov;
 
-       data = malloc(sizeof(struct http_data));
-       if (!data)
-               return kr_error(ENOMEM);
-
-       data->buf = pkt->wire;
-       data->len = pkt->size;
-       data->pos = 0;
-       data->on_write = on_write;
-       data->req = req;
-       data->ttl = packet_ttl(pkt);
-
-       prov.source.ptr = data;
+       prov.source.ptr = ctx;
        prov.read_callback = read_callback;
 
-       return http_send_response(ctx, stream_id, &prov, HTTP_STATUS_OK);
+       struct pl_http_sess_data *http = sess_data;
+       int32_t stream_id = http->last_stream;
+       int ret = http_send_response(sess_data, stream_id, &prov, HTTP_STATUS_OK);
+       if (ret)
+               return protolayer_break(ctx, ret);
+
+       return protolayer_async();
 }
 
-/*
- * Write request to HTTP/2 stream.
- *
- * Packet wire buffer must stay valid until the on_write callback.
- */
-int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, int32_t stream_id,
-              uv_write_cb on_write)
+static bool pl_http_event_unwrap(enum protolayer_event_type event,
+                                 void **baton,
+                                 struct protolayer_manager *manager,
+                                 void *sess_data)
 {
-       struct session *session;
-       struct http_ctx *ctx;
-       int ret;
-
-       if (!req || !pkt || !handle || !handle->data || stream_id < 0)
-               return kr_error(EINVAL);
-       req->handle = (uv_stream_t *)handle;
-
-       session = handle->data;
-       if (session_flags(session)->outgoing)
-               return kr_error(ENOSYS);
-
-       ctx = session_http_get_server_ctx(session);
-       if (!ctx || !ctx->h2)
-               return kr_error(EINVAL);
+       struct pl_http_sess_data *http = sess_data;
 
-       ret = http_write_pkt(ctx, pkt, stream_id, req, on_write);
-       if (ret < 0)
-               return ret;
+       if (event == PROTOLAYER_EVENT_MALFORMED) {
+               http_send_status(http, HTTP_STATUS_BAD_REQUEST);
+               return true;
+       }
 
-       return kr_ok();
+       return true;
 }
 
-/*
- * Release HTTP/2 context.
- */
-void http_free(struct http_ctx *ctx)
+static void pl_http_request_init(struct protolayer_manager *manager,
+                                 struct kr_request *req,
+                                 void *sess_data)
 {
-       if (!ctx)
-               return;
+       struct pl_http_sess_data *http = sess_data;
 
-       kr_log_debug(DOH, "[%p] h2 session freed\n", (void *)ctx->h2);
+       req->qsource.comm_flags.http = true;
 
-       /* Clean up any headers whose ownership may not have been transferred.
-        * This may happen when connection is abruptly ended (e.g. due to errors while
-        * processing HTTP stream. */
-       while (queue_len(ctx->streams) > 0) {
-               struct http_stream stream = queue_head(ctx->streams);
-               http_free_headers(stream.headers);
-               queue_pop(ctx->streams);
+       struct http_stream *stream = &queue_head(http->streams);
+       req->qsource.stream_id = stream->id;
+       if (stream->headers) {
+               req->qsource.headers = *stream->headers;
+               free(stream->headers);
+               stream->headers = NULL;
        }
+}
 
-       trie_apply(ctx->stream_write_data, stream_write_data_free_err, NULL);
-       trie_free(ctx->stream_write_data);
-
-       http_cleanup_stream(ctx);
-       queue_deinit(ctx->streams);
-       nghttp2_session_del(ctx->h2);
-       free(ctx);
+void http_protolayers_init()
+{
+       protolayer_globals[PROTOLAYER_HTTP] = (struct protolayer_globals) {
+               .sess_size = sizeof(struct pl_http_sess_data),
+               .sess_init = pl_http_sess_init,
+               .sess_deinit = pl_http_sess_deinit,
+               .unwrap = pl_http_unwrap,
+               .wrap = pl_http_wrap,
+               .event_unwrap = pl_http_event_unwrap,
+               .request_init = pl_http_request_init
+       };
 }
index 0749e3b8bdb6982b7c6273444ef7499fb73b9e60..fc7d6a4c78d0a5fa02b1b123c065c33062085fec 100644 (file)
@@ -1,85 +1,6 @@
-/*
- * Copyright (C) CZ.NIC, z.s.p.o
- *
- * Initial Author: Jan Hák <jan.hak@nic.cz>
- *
- * SPDX-License-Identifier: GPL-3.0-or-later
+/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#pragma once
-
-#include <uv.h>
-#include <libknot/packet/pkt.h>
-
-#if ENABLE_DOH2
-#include <nghttp2/nghttp2.h>
-#endif
-
-#include "lib/generic/queue.h"
-#include "lib/generic/trie.h"
-
-/** Transport session (opaque). */
-struct session;
-
-typedef ssize_t(*http_send_callback)(const uint8_t *buffer,
-                                    const size_t buffer_len,
-                                    struct session *session);
-
-struct http_stream {
-       int32_t id;
-       kr_http_header_array_t *headers;
-};
-
-typedef queue_t(struct http_stream) queue_http_stream;
-
-typedef enum {
-       HTTP_METHOD_NONE = 0,
-       HTTP_METHOD_GET = 1,
-       HTTP_METHOD_POST = 2,
-       HTTP_METHOD_HEAD = 3, /**< Same as GET, except it does not return payload.
-                              * Required to be implemented by RFC 7231. */
-} http_method_t;
-
-/** HTTP status codes returned by kresd.
- * This is obviously non-exhaustive of all HTTP status codes, feel free to add
- * more if needed. */
-enum http_status {
-       HTTP_STATUS_OK                              = 200,
-       HTTP_STATUS_BAD_REQUEST                     = 400,
-       HTTP_STATUS_NOT_FOUND                       = 404,
-       HTTP_STATUS_PAYLOAD_TOO_LARGE               = 413,
-       HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE          = 415,
-       HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
-       HTTP_STATUS_NOT_IMPLEMENTED                 = 501,
-};
-
-struct http_ctx {
-       struct nghttp2_session *h2;
-       http_send_callback send_cb;
-       struct session *session;
-       queue_http_stream streams;  /* Streams present in the wire buffer. */
-       trie_t *stream_write_data;  /* Dictionary of stream data that needs to be freed after write. */
-       int32_t incomplete_stream;
-       int32_t last_stream;   /* The last used stream - mostly the same as incomplete_stream, but can be used after
-                                 completion for sending HTTP status codes. */
-       ssize_t submitted;
-       http_method_t current_method;
-       char *uri_path;
-       kr_http_header_array_t *headers;
-       uint8_t *buf;  /* Part of the wire_buf that belongs to current HTTP/2 stream. */
-       ssize_t buf_pos;
-       ssize_t buf_size;
-       enum http_status status;
-       bool streaming;             /* True: not all data in the stream has been received yet. */
-};
-
-#if ENABLE_DOH2
-struct http_ctx* http_new(struct session *session, http_send_callback send_cb);
-int http_process_input_data(struct session *session, const uint8_t *buf, ssize_t nread,
-                           ssize_t *out_submitted);
-int http_send_status(struct session *session, enum http_status status);
-int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t* pkt, int32_t stream_id,
-              uv_write_cb on_write);
-void http_free(struct http_ctx *ctx);
-void http_free_headers(kr_http_header_array_t *headers);
-#endif
+/** Initializes the protocol layers managed by http. */
+void http_protolayers_init();
index c7e1f1f4f3559d47be4861f049593b296945de30..938da46437810b477708aadb006c5c7ec9c73d2e 100644 (file)
@@ -134,9 +134,19 @@ static int family_to_freebind_option(sa_family_t sa_family, int *level, int *nam
 struct pl_udp_iter_data {
        PROTOLAYER_DATA_HEADER();
        struct proxy_result proxy;
+       struct comm_info comm;
        bool has_proxy;
 };
 
+static int pl_udp_iter_init(struct protolayer_manager *manager,
+                            struct protolayer_iter_ctx *ctx,
+                            void *iter_data)
+{
+       struct pl_udp_iter_data *udp = iter_data;
+       ctx->comm = &udp->comm;
+       return kr_ok();
+}
+
 static enum protolayer_iter_cb_result pl_udp_unwrap(
                void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx)
 {
@@ -151,7 +161,7 @@ static enum protolayer_iter_cb_result pl_udp_unwrap(
 
        char *data = ctx->payload.buffer.buf;
        ssize_t data_len = ctx->payload.buffer.len;
-       struct comm_info *comm = &ctx->comm;
+       struct comm_info *comm = ctx->comm;
        comm->comm_addr = ctx->target;
        comm->src_addr = ctx->target;
        if (!s->outgoing && proxy_header_present(data, data_len)) {
@@ -274,7 +284,7 @@ static enum protolayer_iter_cb_result pl_tcp_unwrap(
 
        char *data = wire_buf_data(ctx->payload.wire_buf); /* layer's or session's wirebuf */
        ssize_t data_len = wire_buf_data_length(ctx->payload.wire_buf);
-       struct comm_info *comm = &ctx->comm;
+       struct comm_info *comm = ctx->comm;
        comm->src_addr = peer;
        comm->comm_addr = peer;
        comm->dst_addr = NULL;
@@ -349,6 +359,7 @@ void io_protolayers_init(void)
 {
        protolayer_globals[PROTOLAYER_UDP] = (struct protolayer_globals){
                .iter_size = sizeof(struct pl_udp_iter_data),
+               .iter_init = pl_udp_iter_init,
                .unwrap = pl_udp_unwrap,
                .event_wrap = pl_udp_event_wrap,
        };
@@ -496,97 +507,8 @@ static void tcp_recv(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf)
        }
 
        session2_unwrap(s, protolayer_wire_buf(&s->wire_buf), NULL, NULL, NULL);
-
-//     ssize_t consumed = 0;
-//     if (session_flags(s)->has_tls) {
-//             /* buf->base points to start of the tls receive buffer.
-//                Decode data free space in session wire buffer. */
-//             consumed = tls_process_input_data(s, data, data_len);
-//             if (consumed < 0) {
-//                     if (kr_log_is_debug(IO, NULL)) {
-//                             char *peer_str = kr_straddr(src_addr);
-//                             kr_log_debug(IO, "=> connection to '%s': "
-//                                            "error processing TLS data, close\n",
-//                                            peer_str ? peer_str : "");
-//                     }
-//                     worker_end_tcp(s);
-//                     return;
-//             } else if (consumed == 0) {
-//                     return;
-//             }
-//             data = session_wirebuf_get_free_start(s);
-//             data_len = consumed;
-//     }
-//#if ENABLE_DOH2
-//     int streaming = 1;
-//     if (session_flags(s)->has_http) {
-//             streaming = http_process_input_data(s, data, data_len,
-//                             &consumed);
-//             if (streaming < 0) {
-//                     if (kr_log_is_debug(IO, NULL)) {
-//                             char *peer_str = kr_straddr(src_addr);
-//                             kr_log_debug(IO, "=> connection to '%s': "
-//                                    "error processing HTTP data, close\n",
-//                                    peer_str ? peer_str : "");
-//                     }
-//                     worker_end_tcp(s);
-//                     return;
-//             }
-//             if (consumed == 0) {
-//                     return;
-//             }
-//             data = session_wirebuf_get_free_start(s);
-//             data_len = consumed;
-//     }
-//#endif
-//
-//     /* data points to start of the free space in session wire buffer.
-//        Simple increase internal counter. */
-//     consumed = session_wirebuf_consume(s, data, data_len);
-//     kr_assert(consumed == data_len);
-//
-//     struct io_comm_data comm = {
-//             .src_addr = src_addr,
-//             .comm_addr = session_get_peer(s),
-//             .dst_addr = dst_addr,
-//             .proxy = session_proxy_get(s)
-//     };
-//     int ret = session_wirebuf_process(s, &comm);
-//     if (ret < 0) {
-//             /* An error has occurred, close the session. */
-//             worker_end_tcp(s);
-//     }
-//     session_wirebuf_compress(s);
-//     mp_flush(the_worker->pkt_pool.ctx);
-//#if ENABLE_DOH2
-//     if (session_flags(s)->has_http && streaming == 0 && ret == 0) {
-//             ret = http_send_status(s, HTTP_STATUS_BAD_REQUEST);
-//             if (ret) {
-//                     /* An error has occurred, close the session. */
-//                     worker_end_tcp(s);
-//             }
-//     }
-//#endif
 }
 
-/* TODO: http */
-//#if ENABLE_DOH2
-//static ssize_t tls_send(const uint8_t *buf, const size_t len, struct session *session)
-//{
-//     struct tls_ctx *ctx = session_tls_get_server_ctx(session);
-//     ssize_t sent = 0;
-//     kr_require(ctx);
-//
-//     sent = gnutls_record_send(ctx->c.tls_session, buf, len);
-//     if (sent < 0) {
-//             kr_log_debug(DOH, "gnutls_record_send failed: %s (%zd)\n",
-//                            gnutls_strerror_name(sent), sent);
-//             return kr_error(EIO);
-//     }
-//     return sent;
-//}
-//#endif
-
 static void _tcp_accept(uv_stream_t *master, int status, enum protolayer_grp grp)
 {
        if (status != 0) {
@@ -614,7 +536,6 @@ static void _tcp_accept(uv_stream_t *master, int status, enum protolayer_grp grp
        /* struct session was allocated \ borrowed from memory pool. */
        struct session2 *s = client->data;
        kr_require(s->outgoing == false);
-//     kr_require(s->secure == tls); /* TODO */
 
        if (uv_accept(master, (uv_stream_t *)client) != 0) {
                /* close session, close underlying uv handles and
@@ -645,62 +566,7 @@ static void _tcp_accept(uv_stream_t *master, int status, enum protolayer_grp grp
         * is idle and should be terminated, this is an educated guess. */
 
        uint64_t idle_in_timeout = the_network->tcp.in_idle_timeout;
-
        uint64_t timeout = KR_CONN_RTT_MAX / 2;
-       /* TODO: tls, http */
-//     if (tls) {
-//             timeout += TLS_MAX_HANDSHAKE_TIME;
-//             struct tls_ctx *ctx = session_tls_get_server_ctx(s);
-//             if (!ctx) {
-//                     ctx = tls_new();
-//                     if (!ctx) {
-//                             session_close(s);
-//                             return;
-//                     }
-//                     ctx->c.session = s;
-//                     ctx->c.handshake_state = TLS_HS_IN_PROGRESS;
-//
-//                     /* Configure ALPN. */
-//                     gnutls_datum_t proto;
-//                     if (!http) {
-//                             proto.data = (unsigned char *)"dot";
-//                             proto.size = 3;
-//                     } else {
-//                             proto.data = (unsigned char *)"h2";
-//                             proto.size = 2;
-//                     }
-//                     unsigned int flags = 0;
-//#if GNUTLS_VERSION_NUMBER >= 0x030500
-//                     /* Mandatory ALPN means the protocol must match if and
-//                      * only if ALPN extension is used by the client. */
-//                     flags |= GNUTLS_ALPN_MANDATORY;
-//#endif
-//                     ret = gnutls_alpn_set_protocols(ctx->c.tls_session, &proto, 1, flags);
-//                     if (ret != GNUTLS_E_SUCCESS) {
-//                             session_close(s);
-//                             return;
-//                     }
-//
-//                     session_tls_set_server_ctx(s, ctx);
-//             }
-//     }
-//#if ENABLE_DOH2
-//     if (http) {
-//             struct http_ctx *ctx = session_http_get_server_ctx(s);
-//             if (!ctx) {
-//                     if (!tls) {  /* Plain HTTP is not supported. */
-//                             session_close(s);
-//                             return;
-//                     }
-//                     ctx = http_new(s, tls_send);
-//                     if (!ctx) {
-//                             session_close(s);
-//                             return;
-//                     }
-//                     session_http_set_server_ctx(s, ctx);
-//             }
-//     }
-//#endif
        session2_event(s, PROTOLAYER_EVENT_CONNECT, NULL);
        session2_timer_start(s, timeout, idle_in_timeout);
        io_start_read((uv_handle_t *)client);
index e83ab7f5eb3c79eb465e27e513d27b06b3e6d942..6d11c18816381a136eb7c37250ea4b0062c4b4fe 100644 (file)
 #include "daemon/network.h"
 #include "daemon/udp_queue.h"
 #include "daemon/worker.h"
+
+#ifdef ENABLE_DOH2
+#include "daemon/http.h"
+#endif
+
 #include "lib/defines.h"
 #include "lib/dnssec.h"
 #include "lib/log.h"
@@ -571,6 +576,9 @@ int main(int argc, char **argv)
 
        io_protolayers_init();
        tls_protolayers_init();
+#ifdef ENABLE_DOH2
+       http_protolayers_init();
+#endif
 
        /* Start listening, in the sense of network_listen_fd(). */
        if (start_listening(&the_args->fds) != 0) {
index 723d22a435219b5c637a29983670f042a700a9ad..02833bafd9581b63ef86da72b5a142544ab20f38 100644 (file)
@@ -22,9 +22,10 @@ kresd_src = files([
   'worker.c',
   'zimport.c',
 ])
-#if nghttp2.found()
-#  kresd_src += files(['http.c'])
-#endif
+
+if nghttp2.found()
+  kresd_src += files(['http.c'])
+endif
 
 c_src_lint += kresd_src
 
@@ -50,7 +51,7 @@ kresd_deps = [
   gnutls,
   libsystemd,
   capng,
-#  nghttp2,
+  nghttp2,
   malloc,
 ]
 
index 2c78d8769c349fba52bb33c09527160f6a186ae0..8546fb962dc0e9b9c4874cff125bc1f4da12ccd0 100644 (file)
@@ -57,6 +57,13 @@ static enum protolayer_protocol protolayer_grp_doh[] = {
 };
 
 
+char *protolayer_protocol_names[PROTOLAYER_PROTOCOL_COUNT] = {
+       [PROTOLAYER_NULL] = "(null)",
+#define XX(cid) [PROTOLAYER_##cid] = #cid,
+       PROTOLAYER_PROTOCOL_MAP(XX)
+#undef XX
+};
+
 /** Sequences of layers, mapped by `enum protolayer_grp`.
  *
  * To define a new group, add a new entry in the `PROTOLAYER_GRP_MAP` macro and
@@ -66,7 +73,7 @@ static enum protolayer_protocol protolayer_grp_doh[] = {
  * one defined as *Variable name* (2nd parameter) in the `PROTOLAYER_GRP_MAP`
  * macro. */
 static enum protolayer_protocol *protolayer_grps[PROTOLAYER_GRP_COUNT] = {
-#define XX(cid, vid, name) [PROTOLAYER_GRP_##cid] = protolayer_grp_##vid,
+#define XX(cid, vid, name, alpn) [PROTOLAYER_GRP_##cid] = protolayer_grp_##vid,
        PROTOLAYER_GRP_MAP(XX)
 #undef XX
 };
@@ -74,7 +81,7 @@ static enum protolayer_protocol *protolayer_grps[PROTOLAYER_GRP_COUNT] = {
 /** Human-readable names for protocol layer groups. */
 char *protolayer_grp_names[PROTOLAYER_GRP_COUNT] = {
        [PROTOLAYER_GRP_NULL] = "(null)",
-#define XX(cid, vid, name) [PROTOLAYER_GRP_##cid] = name,
+#define XX(cid, vid, name, alpn) [PROTOLAYER_GRP_##cid] = name,
        PROTOLAYER_GRP_MAP(XX)
 #undef XX
 };
@@ -95,6 +102,59 @@ char *protolayer_payload_names[PROTOLAYER_PAYLOAD_COUNT] = {
 #undef XX
 };
 
+size_t protolayer_payload_size(const struct protolayer_payload *payload)
+{
+       if (payload->type == PROTOLAYER_PAYLOAD_BUFFER) {
+               return payload->buffer.len;
+       } else if (payload->type == PROTOLAYER_PAYLOAD_IOVEC) {
+               size_t sum = 0;
+               for (int i = 0; i < payload->iovec.cnt; i++) {
+                       sum += payload->iovec.iov[i].iov_len;
+               }
+               return sum;
+       } else if (payload->type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
+               return wire_buf_data_length(payload->wire_buf);
+       } else if(!payload->type) {
+               return 0;
+       } else {
+               kr_assert(false && "Invalid payload type");
+               return 0;
+       }
+}
+
+size_t protolayer_payload_copy(void *dest,
+                               const struct protolayer_payload *payload,
+                               size_t max_len)
+{
+       const size_t pld_size = protolayer_payload_size(payload);
+       const size_t copy_size = MIN(max_len, pld_size);
+
+       if (payload->type == PROTOLAYER_PAYLOAD_BUFFER) {
+               memcpy(dest, payload->buffer.buf, copy_size);
+               return copy_size;
+       } else if (payload->type == PROTOLAYER_PAYLOAD_IOVEC) {
+               char *cur = dest;
+               size_t remaining = copy_size;
+               for (int i = 0; i < payload->iovec.cnt && remaining; i++) {
+                       size_t l = payload->iovec.iov[i].iov_len;
+                       size_t to_copy = MIN(l, remaining);
+                       memcpy(cur, payload->iovec.iov[i].iov_base, to_copy);
+                       remaining -= l;
+                       cur += l;
+               }
+
+               kr_assert(remaining == 0 && (cur - (char *)dest) == copy_size);
+               return copy_size;
+       } else if (payload->type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
+               memcpy(dest, wire_buf_data(payload->wire_buf), copy_size);
+               return copy_size;
+       } else if(!payload->type) {
+               return 0;
+       } else {
+               kr_assert(false && "Invalid payload type");
+               return 0;
+       }
+}
 
 struct protolayer_payload protolayer_as_buffer(const struct protolayer_payload *payload)
 {
@@ -104,6 +164,7 @@ struct protolayer_payload protolayer_as_buffer(const struct protolayer_payload *
        if (payload->type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
                struct protolayer_payload new_payload = {
                        .type = PROTOLAYER_PAYLOAD_BUFFER,
+                       .ttl = payload->ttl,
                        .buffer = {
                                .buf = wire_buf_data(payload->wire_buf),
                                .len = wire_buf_data_length(payload->wire_buf)
@@ -119,6 +180,21 @@ struct protolayer_payload protolayer_as_buffer(const struct protolayer_payload *
        };
 }
 
+size_t protolayer_queue_count_payload(protolayer_iter_ctx_queue_t *queue)
+{
+       if (!queue || queue_len(*queue) == 0)
+               return 0;
+
+       size_t sum = 0;
+       queue_it_t(struct protolayer_iter_ctx *) it = queue_it_begin(*queue);
+       for (; !queue_it_finished(it); queue_it_next(it)) {
+               struct protolayer_iter_ctx *ctx = queue_it_val(it);
+               sum += protolayer_payload_size(&ctx->payload);
+       }
+
+       return sum;
+}
+
 
 /** Gets layer-specific session data for the layer with the specified index
  * from the manager. */
@@ -186,6 +262,17 @@ static inline void protolayer_iter_ctx_next(struct protolayer_iter_ctx *ctx)
                ctx->layer_ix--;
 }
 
+static const char *layer_name(enum protolayer_grp grp, ssize_t layer_ix)
+{
+       enum protolayer_protocol p = protolayer_grps[grp][layer_ix];
+       return protolayer_protocol_names[p];
+}
+
+static inline const char *layer_name_ctx(struct protolayer_iter_ctx *ctx)
+{
+       return layer_name(ctx->manager->grp, ctx->layer_ix);
+}
+
 static int protolayer_iter_ctx_finish(struct protolayer_iter_ctx *ctx, int ret)
 {
        struct session2 *session = ctx->manager->session;
@@ -195,16 +282,18 @@ static int protolayer_iter_ctx_finish(struct protolayer_iter_ctx *ctx, int ret)
        for (size_t i = 0; i < m->num_layers; i++) {
                struct protolayer_data *d = protolayer_iter_data_get(ctx, i);
                if (globals->iter_deinit)
-                       globals->iter_deinit(m, d);
+                       globals->iter_deinit(m, ctx, d);
        }
 
        if (ret)
-               kr_log_debug(PROTOLAYER, "layer context of group '%s' ended with return code %d\n",
-                               protolayer_grp_names[ctx->manager->grp], ret);
+               kr_log_debug(PROTOLAYER, "layer context of group '%s' (on %u: %s) ended with return code %d\n",
+                               protolayer_grp_names[ctx->manager->grp],
+                               ctx->layer_ix, layer_name_ctx(ctx), ret);
 
        if (ctx->status)
-               kr_log_debug(PROTOLAYER, "iteration of group '%s' layer %u ended with status %d\n",
-                               protolayer_grp_names[ctx->manager->grp], ctx->layer_ix, ctx->status);
+               kr_log_debug(PROTOLAYER, "iteration of group '%s' (on %u: %s) ended with status %d\n",
+                               protolayer_grp_names[ctx->manager->grp],
+                               ctx->layer_ix, layer_name_ctx(ctx), ctx->status);
 
        if (ctx->finished_cb)
                ctx->finished_cb(ret, session, ctx->finished_cb_target,
@@ -344,17 +433,18 @@ static int protolayer_manager_submit(
 
        if (kr_log_is_debug(PROTOLAYER, NULL)) {
                const char *sess_dir = manager->session->outgoing ? "out" : "in";
-               kr_log_debug(PROTOLAYER, "[%s] %s submitted to grp '%s' in %s direction (layer %zu)\n",
+               kr_log_debug(PROTOLAYER, "[%s] %s submitted to grp '%s' in %s direction (%zu: %s)\n",
                                sess_dir,
                                protolayer_payload_names[payload.type],
                                protolayer_grp_names[manager->grp],
                                (direction == PROTOLAYER_UNWRAP) ? "unwrap" : "wrap",
-                               layer_ix);
+                               layer_ix, layer_name(manager->grp, layer_ix));
        }
 
        *ctx = (struct protolayer_iter_ctx) {
                .payload = payload,
                .target = target,
+               .comm = &manager->session->comm,
                .direction = direction,
                .layer_ix = layer_ix,
                .manager = manager,
@@ -373,7 +463,7 @@ static int protolayer_manager_submit(
                }
 
                if (globals->iter_init)
-                       globals->iter_init(manager, iter_data);
+                       globals->iter_init(manager, ctx, iter_data);
        }
 
        return protolayer_step(ctx);
@@ -1016,6 +1106,19 @@ void session2_event_after(struct session2 *s, enum protolayer_protocol protocol,
        session2_event_unwrap(s, start_ix + 1, event, baton);
 }
 
+void session2_init_request(struct session2 *s, struct kr_request *req)
+{
+       struct protolayer_manager *m = s->layers;
+       for (ssize_t i = 0; i < m->num_layers; i++) {
+               enum protolayer_protocol p = protolayer_grps[s->layers->grp][i];
+               struct protolayer_globals *globals = &protolayer_globals[p];
+               if (globals->request_init) {
+                       struct protolayer_data *sess_data = protolayer_sess_data_get(m, i);
+                       globals->request_init(m, req, sess_data);
+               }
+       }
+}
+
 
 struct session2_pushv_ctx {
        struct session2 *session;
index 21634362db54172a2ee3c60724678d18392f1db4..7516ffe395faf760de258bcda861a4c471408146 100644 (file)
@@ -80,27 +80,35 @@ struct comm_info {
  * To use protocols within sessions, protocol layer groups also need to be
  * defined, to indicate the order in which individual protocols are to be
  * processed. See `PROTOLAYER_GRP_MAP` below for more details. */
+#define PROTOLAYER_PROTOCOL_MAP(XX) \
+       /* General transport protocols */\
+       XX(UDP) \
+       XX(TCP) \
+       XX(TLS) \
+       XX(HTTP) \
+       \
+       /* QUIC (not yet implemented) */\
+       XX(UDP_TO_QCONN)\
+       XX(QCONN_TO_QSTREAM)\
+       \
+       /* DNS (`worker`) */\
+       XX(DNS_DGRAM)\
+       XX(DNS_MSTREAM)\
+       XX(DNS_SSTREAM)
+
+/** The identifiers of protocol layer types. */
 enum protolayer_protocol {
        PROTOLAYER_NULL = 0,
-
-       /* General transport protocols */
-       PROTOLAYER_UDP,
-       PROTOLAYER_TCP,
-       PROTOLAYER_TLS,
-       PROTOLAYER_HTTP,
-
-       /* QUIC (not yet implemented) */
-       PROTOLAYER_UDP_TO_QCONN,
-       PROTOLAYER_QCONN_TO_QSTREAM,
-
-       /* DNS (`worker`) */
-       PROTOLAYER_DNS_DGRAM,
-       PROTOLAYER_DNS_MSTREAM,
-       PROTOLAYER_DNS_SSTREAM,
-
+#define XX(cid) PROTOLAYER_##cid,
+       PROTOLAYER_PROTOCOL_MAP(XX)
+#undef XX
        PROTOLAYER_PROTOCOL_COUNT /* must be the last! */
 };
 
+/** Maps protocol layer type IDs to string names.
+ * E.g. PROTOLAYER_HTTP has name 'HTTP'. */
+extern char *protolayer_protocol_names[];
+
 /** Protocol layer groups. Each of these represents a sequence of layers in the
  * unwrap direction (wrap direction being the opposite). The sequence dictates
  * the order, in which individual layers are processed. This macro is used to
@@ -112,17 +120,18 @@ enum protolayer_protocol {
  * Parameters are:
  *   1. Constant name (for e.g. PROTOLAYER_GRP_* constants)
  *   2. Variable name (for e.g. protolayer_grp_* arrays)
- *   3. Human-readable name for logging */
+ *   3. Human-readable name for logging
+ *   4. ALPN protocol identifier (for TLS) */
 #define PROTOLAYER_GRP_MAP(XX) \
-       XX(DOUDP, doudp, "DNS UDP") \
-       XX(DOTCP, dotcp, "DNS TCP") \
-       XX(DOTLS, dot, "DNS-over-TLS") \
-       XX(DOHTTPS, doh, "DNS-over-HTTPS")
+       XX(DOUDP, doudp, "DNS UDP", "") \
+       XX(DOTCP, dotcp, "DNS TCP", "") \
+       XX(DOTLS, dot, "DNS-over-TLS", "dot") \
+       XX(DOHTTPS, doh, "DNS-over-HTTPS", "h2")
 
 /** The identifiers of pre-defined protocol layer sequences. */
 enum protolayer_grp {
        PROTOLAYER_GRP_NULL = 0,
-#define XX(cid, vid, name) PROTOLAYER_GRP_##cid,
+#define XX(cid, vid, name, alpn) PROTOLAYER_GRP_##cid,
        PROTOLAYER_GRP_MAP(XX)
 #undef XX
        PROTOLAYER_GRP_COUNT
@@ -195,6 +204,7 @@ typedef void (*protolayer_finished_cb)(int status, struct session2 *session,
        XX(TIMEOUT) /**< Signal that the session has timed out. */\
        XX(CONNECT) /**< Signal that a connection has been established. */\
        XX(CONNECT_FAIL) /**< Signal that a connection could not have been established. */\
+       XX(MALFORMED) /**< Signal that a malformed request has been received. */\
        XX(DISCONNECT) /**< Signal that a connection has ended. */\
        XX(STATS_SEND_ERR) /**< Failed task send - update stats. */\
        XX(STATS_QRY_OUT) /**< Outgoing query submission - update stats. */
@@ -239,10 +249,11 @@ extern char *protolayer_payload_names[];
  * is ever (de-)allocated by the protolayer manager! */
 struct protolayer_payload {
        enum protolayer_payload_type type;
+       unsigned int ttl; /**< time-to-live hint (for e.g. HTTP Cache-Control) */
        union {
                /** Only valid if `type` is `_BUFFER`. */
                struct {
-                       char *buf;
+                       void *buf;
                        size_t len;
                } buffer;
 
@@ -268,8 +279,9 @@ struct protolayer_iter_ctx {
        const void *target;
        /** Communication information. Typically written into by one of the
         * first layers facilitating transport protocol processing.
-        * Zero-initialized in the beginning. */
-       struct comm_info comm;
+        * Points to session-wide comm info by default, may be changed
+        * by a layer to point elsewhere. */
+       struct comm_info *comm;
 
 /* callback for when the layer iteration has ended - read-only: */
        protolayer_finished_cb finished_cb;
@@ -289,8 +301,19 @@ struct protolayer_iter_ctx {
        alignas(CPU_STRUCT_ALIGN) char data[];
 };
 
+/** Gets the total size of the specified payload. */
+size_t protolayer_payload_size(const struct protolayer_payload *payload);
+
+/** Copies the specified payload to `dest`. Only `max_len` or the size of the
+ * payload is written, whichever is less.
+ *
+ * Returns the actual length of copied data. */
+size_t protolayer_payload_copy(void *dest,
+                               const struct protolayer_payload *payload,
+                               size_t max_len);
+
 /** Convenience function to get a buffer-type payload. */
-static inline struct protolayer_payload protolayer_buffer(char *buf, size_t len)
+static inline struct protolayer_payload protolayer_buffer(void *buf, size_t len)
 {
        return (struct protolayer_payload){
                .type = PROTOLAYER_PAYLOAD_BUFFER,
@@ -332,6 +355,13 @@ static inline struct protolayer_payload protolayer_wire_buf(struct wire_buf *wir
  * to be reused. */
 struct protolayer_payload protolayer_as_buffer(const struct protolayer_payload *payload);
 
+/** A predefined queue type for iteration context. */
+typedef queue_t(struct protolayer_iter_ctx *) protolayer_iter_ctx_queue_t;
+
+/** Iterates through the specified `queue` and gets the sum of all payloads
+ * available in it. */
+size_t protolayer_queue_count_payload(protolayer_iter_ctx_queue_t *queue);
+
 /** Mandatory header members for any layer-specific data. */
 #define PROTOLAYER_DATA_HEADER() struct {\
        struct session2 *session; /**< Pointer to the owner session. */\
@@ -392,6 +422,10 @@ typedef int (*protolayer_data_sess_init_cb)(struct protolayer_manager *manager,
                                             void *data,
                                             void *param);
 
+typedef int (*protolayer_iter_data_cb)(struct protolayer_manager *manager,
+                                       struct protolayer_iter_ctx *ctx,
+                                       void *data);
+
 /** Function type for (de)initialization callbacks of layers.
  *
  * `data` points to the layer-specific data struct.
@@ -401,6 +435,14 @@ typedef int (*protolayer_data_sess_init_cb)(struct protolayer_manager *manager,
 typedef int (*protolayer_data_cb)(struct protolayer_manager *manager,
                                   void *data);
 
+/** Function type for (de)initialization callbacks of DNS requests.
+ *
+ * `req` points to the request for initialization.
+ * `sess_data` points to layer-specific session data struct. */
+typedef void (*protolayer_request_cb)(struct protolayer_manager *manager,
+                                      struct kr_request *req,
+                                      void *sess_data);
+
 /** A collection of protocol layers and their layer-specific data, tied to a
  * session. The manager contains a sequence of protocol layers (determined by
  * `grp`), which define how the data processed by the session is to be
@@ -470,11 +512,11 @@ struct protolayer_globals {
        /** Called at the beginning of a layer sequence to initialize
         * layer-specific iteration data. Optional. The data is always
         * zero-initialized during iteration context initialization. */
-       protolayer_data_cb iter_init;
+       protolayer_iter_data_cb iter_init;
 
        /** Called at the end of a layer sequence to deinitialize
         * layer-specific iteration data. Optional. */
-       protolayer_data_cb iter_deinit;
+       protolayer_iter_data_cb iter_deinit;
 
        /** Strips the buffer of protocol-specific data. E.g. a HTTP layer
         * removes HTTP status and headers. Optional - iteration continues
@@ -493,6 +535,9 @@ struct protolayer_globals {
        /** Processes events in the wrap order (bounced back by the session).
         * Optional - iteration continues automatically if this is NULL. */
        protolayer_event_cb event_wrap;
+
+       /** Modifies the provided request for use with the layer. */
+       protolayer_request_cb request_init;
 };
 
 /** Global data about layered protocols. Mapped by `enum protolayer_protocol`.
@@ -644,6 +689,11 @@ struct session2 {
        queue_t(struct qr_task *) waiting; /**< List of tasks waiting for
                                            * sending to upstream. */
 
+       /** Communication information. Typically written into by one of the
+        * first layers facilitating transport protocol processing.
+        * Zero-initialized by default. */
+       struct comm_info comm;
+
        /** Managed buffer for data received by `io`. */
        struct wire_buf wire_buf;
 
@@ -669,6 +719,10 @@ struct session2 {
         * Set during protocol layer initialization by the stream-based layer. */
        bool stream : 1;
 
+       /** If true, the session contains a HTTP protocol layer.
+        * Set during protocol layer initialization by the HTTP layer. */
+       bool http : 1;
+
        /** If true, a connection is established. Only applicable to sessions
         * using connection-based protocols. One of the stream-based protocol
         * layers is going to be the writer for this flag. */
@@ -874,6 +928,8 @@ void session2_event(struct session2 *s, enum protolayer_event_type type, void *b
 void session2_event_after(struct session2 *s, enum protolayer_protocol protocol,
                           enum protolayer_event_type type, void *baton);
 
+void session2_init_request(struct session2 *s, struct kr_request *req);
+
 /** Removes the specified request task from the session's tasklist. The session
  * must be outgoing. If the session is UDP, a signal to close is also sent to it. */
 void session2_kill_ioreq(struct session2 *session, struct qr_task *task);
index ae1384ccb680e18f39c7a02cf29ecfda1745e9a8..2f1773353eb0bbe5df791b16236f5619d5f5a36b 100644 (file)
@@ -23,7 +23,6 @@
 #include "daemon/tls.h"
 #include "daemon/worker.h"
 #include "daemon/session2.h"
-#include "lib/generic/queue.h"
 
 #define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE (60*60*24*7)
 #define GNUTLS_PIN_MIN_VERSION  0x030400
 #define DEBUG_MSG(...)
 #endif
 
+static const gnutls_datum_t tls_grp_alpn[PROTOLAYER_GRP_COUNT] = {
+#define XX(cid, vid, name, alpn) [PROTOLAYER_GRP_##cid] = \
+       { .data = (unsigned char *)alpn, .size = sizeof(alpn) - 1 },
+       PROTOLAYER_GRP_MAP(XX)
+#undef XX
+};
+
 typedef enum tls_client_hs_state {
        TLS_HS_NOT_STARTED = 0,
        TLS_HS_IN_PROGRESS,
@@ -50,18 +56,14 @@ typedef enum tls_client_hs_state {
        TLS_HS_LAST
 } tls_hs_state_t;
 
-typedef int (*tls_handshake_cb) (struct session2 *session, int status);
-typedef queue_t(struct protolayer_iter_ctx *) pl_cb_ctx_queue_t;
-
 struct pl_tls_sess_data {
        PROTOLAYER_DATA_HEADER();
        bool client_side;
        gnutls_session_t tls_session;
        tls_hs_state_t handshake_state;
-       pl_cb_ctx_queue_t unwrap_queue;
-       pl_cb_ctx_queue_t wrap_queue;
+       protolayer_iter_ctx_queue_t unwrap_queue;
+       protolayer_iter_ctx_queue_t wrap_queue;
        struct wire_buf unwrap_buf;
-       tls_handshake_cb handshake_cb;
        size_t write_queue_size;
        union {
                struct tls_credentials *server_credentials;
@@ -96,34 +98,6 @@ static int kres_gnutls_set_priority(gnutls_session_t session) {
        return err;
 }
 
-static size_t count_avail_payload(pl_cb_ctx_queue_t *queue)
-{
-       if (queue_len(*queue) == 0)
-               return 0;
-
-       size_t avail = 0;
-       queue_it_t(struct protolayer_iter_ctx *) it = queue_it_begin(*queue);
-       for (; !queue_it_finished(it); queue_it_next(it)) {
-               struct protolayer_iter_ctx *ctx = queue_it_val(it);
-               struct protolayer_payload *pld = &ctx->payload;
-               if (pld->type == PROTOLAYER_PAYLOAD_BUFFER) {
-                       avail += pld->buffer.len;
-               } else if (pld->type == PROTOLAYER_PAYLOAD_IOVEC) {
-                       for (int i = 0; i < pld->iovec.cnt; i++) {
-                               avail += pld->iovec.iov[i].iov_len;
-                       }
-               } else if (pld->type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
-                       avail += wire_buf_data_length(pld->wire_buf);
-               } else if(!pld->type) {
-                       continue;
-               } else {
-                       kr_assert(false && "Invalid payload type");
-               }
-       }
-
-       return avail;
-}
-
 static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
 {
        struct pl_tls_sess_data *tls = h;
@@ -132,7 +106,7 @@ static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
                return -1;
        }
 
-       size_t avail = count_avail_payload(&tls->unwrap_queue);
+       size_t avail = protolayer_queue_count_payload(&tls->unwrap_queue);
        DEBUG_MSG("[%s] pull wanted: %zu avail: %zu\n",
                        tls->client_side ? "tls_client" : "tls",
                        len, avail);
@@ -154,7 +128,7 @@ static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
                        memcpy(dest, pld->buffer.buf, to_copy);
                        dest += to_copy;
                        len -= to_copy;
-                       pld->buffer.buf += to_copy;
+                       pld->buffer.buf = (char *)pld->buffer.buf + to_copy;
                        pld->buffer.len -= to_copy;
                        transfer += to_copy;
 
@@ -223,7 +197,7 @@ static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
 }
 
 struct kres_gnutls_push_ctx {
-       void *sess_data;
+       struct pl_tls_sess_data *sess_data;
        struct iovec iov[];
 };
 
@@ -888,7 +862,7 @@ static int tls_pull_timeout_func(gnutls_transport_ptr_t h, unsigned int ms)
                return -1;
        }
 
-       size_t avail = count_avail_payload(&tls->unwrap_queue);
+       size_t avail = protolayer_queue_count_payload(&tls->unwrap_queue);
        DEBUG_MSG("[%s] timeout check: available: %zu\n",
                  tls->client_side ? "tls_client" : "tls", avail);
        if (!avail) {
@@ -994,6 +968,23 @@ static int pl_tls_sess_server_init(struct protolayer_manager *manager,
                                          tls->tls_session);
        }
 
+       const gnutls_datum_t *alpn = &tls_grp_alpn[manager->grp];
+       if (alpn->size) { /* ALPN is a non-empty string */
+               flags = 0;
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+               /* Mandatory ALPN means the protocol must match if and
+                * only if ALPN extension is used by the client. */
+               flags |= GNUTLS_ALPN_MANDATORY;
+#endif
+
+               ret = gnutls_alpn_set_protocols(tls->tls_session, alpn, 1, flags);
+               if (ret != GNUTLS_E_SUCCESS) {
+                       kr_log_error(TLS, "gnutls_alpn_set_protocols(): %s (%d)\n", gnutls_strerror_name(ret), ret);
+                       pl_tls_sess_data_deinit(tls);
+                       return ret;
+               }
+       }
+
        return kr_ok();
 }
 
@@ -1098,7 +1089,7 @@ static enum protolayer_iter_cb_result pl_tls_unwrap(void *sess_data, void *iter_
                                wire_buf_free_space(&tls->unwrap_buf),
                                wire_buf_free_space_length(&tls->unwrap_buf));
                if (count == GNUTLS_E_AGAIN) {
-                       if (count_avail_payload(&tls->unwrap_queue) == 0) {
+                       if (protolayer_queue_count_payload(&tls->unwrap_queue) == 0) {
                                /* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
                                break;
                        }
@@ -1152,7 +1143,7 @@ static enum protolayer_iter_cb_result pl_tls_unwrap(void *sess_data, void *iter_
        }
 
        /* Here all data must be consumed. */
-       while (count_avail_payload(&tls->unwrap_queue) > 0) {
+       while (protolayer_queue_count_payload(&tls->unwrap_queue) > 0) {
                /* Something went wrong, better return error.
                 * This is most probably due to gnutls_record_recv() did not
                 * consume all available network data by calling kres_gnutls_pull().
index e57d0cacf95dd814ac19e03514c31ccbbfbfe545..5a1b4be84e7a406f9dcd1e0084ae10de17cadb88 100644 (file)
@@ -30,8 +30,9 @@
 #include "daemon/proxyv2.h"
 #include "daemon/session2.h"
 #include "daemon/tls.h"
-#include "daemon/http.h"
+#include "lib/cache/util.h" /* packet_ttl */
 #include "lib/layer.h"
+#include "lib/layer/iterate.h" /* kr_response_classify */
 #include "lib/utils.h"
 
 
@@ -349,8 +350,6 @@ static struct request_ctx *request_create(struct session2 *session,
 
                req->qsource.comm_flags.tcp = session->stream;
                req->qsource.comm_flags.tls = session->secure;
-//             req->qsource.comm_flags.http = session->has_http; /* TODO */
-               req->qsource.comm_flags.http = false;
 
                req->qsource.flags = req->qsource.comm_flags;
                if (proxy) {
@@ -360,19 +359,8 @@ static struct request_ctx *request_create(struct session2 *session,
 
                req->qsource.stream_id = -1;
 
-               /* TODO: http */
-//#if ENABLE_DOH2
-//             if (req->qsource.comm_flags.http) {
-//                     struct http_ctx *http_ctx = session_http_get_server_ctx(session);
-//                     struct http_stream stream = queue_head(http_ctx->streams);
-//                     req->qsource.stream_id = stream.id;
-//                     if (stream.headers) {
-//                             req->qsource.headers = *stream.headers;
-//                             free(stream.headers);
-//                             stream.headers = NULL;
-//                     }
-//             }
-//#endif
+               session2_init_request(session, req);
+
                /* We need to store a copy of peer address. */
                memcpy(&ctx->source.addr.ip, src_addr, kr_sockaddr_len(src_addr));
                req->qsource.addr = &ctx->source.addr.ip;
@@ -646,7 +634,6 @@ static int qr_task_send(struct qr_task *task, struct session2 *session,
                return qr_task_on_send(task, NULL, kr_error(EIO));
 
        int ret = 0;
-//     struct request_ctx *ctx = task->ctx; /* TODO: used with doh below */
 
        if (addr == NULL)
                addr = session2_get_peer(session);
@@ -675,24 +662,11 @@ static int qr_task_send(struct qr_task *task, struct session2 *session,
        if (kr_fails_assert(!session->closing))
                return qr_task_on_send(task, NULL, kr_error(EIO));
 
-       /* TODO: doh */
-//     if (session_flags(session)->has_http) {
-//#if ENABLE_DOH2
-//             uv_write_t *write_req = (uv_write_t *)ioreq;
-//             write_req->data = task;
-//             ret = http_write(write_req, handle, pkt, ctx->req.qsource.stream_id, &on_write);
-//#else
-//             ret = kr_error(ENOPROTOOPT);
-//#endif
-//     }
-//
-//     *snip*
-
        /* Pending '_finished' callback on current task */
        qr_task_ref(task);
-       ret = session2_wrap(session,
-                       protolayer_buffer((char *)pkt->wire, pkt->size),
-                       addr, qr_task_wrap_finished, task);
+       struct protolayer_payload payload = protolayer_buffer((char *)pkt->wire, pkt->size);
+       payload.ttl = packet_ttl(pkt);
+       ret = session2_wrap(session, payload, addr, qr_task_wrap_finished, task);
 
        if (ret >= 0) {
                session2_touch(session);
@@ -1498,20 +1472,13 @@ int worker_submit(struct session2 *session, struct comm_info *comm,
        if (ret == KNOT_ETRAIL && is_outgoing && !kr_fails_assert(pkt->parsed < pkt->size))
                ret = KNOT_EOK; // we deal with this later, so that `selection` applies
 
-       struct http_ctx *http_ctx = NULL;
-#if ENABLE_DOH2
-       /* TODO: http. Devise a way to do this... don't know yet */
-//     http_ctx = session_http_get_server_ctx(session);
-//
-//     /* Badly formed query when using DoH leads to a Bad Request */
-//     if (http_ctx && !is_outgoing && ret) {
-//             http_send_status(session, HTTP_STATUS_BAD_REQUEST);
-//             return ret;
-//     }
-#endif
-
-       if (!is_outgoing && http_ctx && queue_len(http_ctx->streams) <= 0)
-               return kr_error(ENOENT);
+       /* Badly formed query when using DoH leads to a Bad Request */
+       /* TODO: Do not necessarily tie it to HTTP - it should probably be a
+        * more generic flag */
+       if (session->http && !is_outgoing && ret) {
+               session2_event(session, PROTOLAYER_EVENT_MALFORMED, NULL);
+               return ret;
+       }
 
        /* Ignore badly formed queries. */
        if (ret && kr_log_is_debug(WORKER, NULL)) {
@@ -1532,8 +1499,6 @@ int worker_submit(struct session2 *session, struct comm_info *comm,
                struct request_ctx *ctx =
                        request_create(session, comm, eth_from,
                                       eth_to, knot_wire_get_id(pkt->wire));
-               if (http_ctx)
-                       queue_pop(http_ctx->streams);
                if (!ctx)
                        return kr_error(ENOMEM);
 
@@ -1925,7 +1890,7 @@ static enum protolayer_iter_cb_result pl_dns_dgram_unwrap(
                                break;
                        }
 
-                       ret = worker_submit(session, &ctx->comm, NULL, NULL, pkt);
+                       ret = worker_submit(session, ctx->comm, NULL, NULL, pkt);
                        if (ret)
                                break;
                }
@@ -1939,7 +1904,7 @@ static enum protolayer_iter_cb_result pl_dns_dgram_unwrap(
                if (!pkt)
                        return protolayer_break(ctx, KNOT_EMALF);
 
-               int ret = worker_submit(session, &ctx->comm, NULL, NULL, pkt);
+               int ret = worker_submit(session, ctx->comm, NULL, NULL, pkt);
                mp_flush(the_worker->pkt_pool.ctx);
                return protolayer_break(ctx, ret);
        } else if (ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF) {
@@ -1949,7 +1914,7 @@ static enum protolayer_iter_cb_result pl_dns_dgram_unwrap(
                if (!pkt)
                        return protolayer_break(ctx, KNOT_EMALF);
 
-               int ret = worker_submit(session, &ctx->comm, NULL, NULL, pkt);
+               int ret = worker_submit(session, ctx->comm, NULL, NULL, pkt);
                wire_buf_reset(ctx->payload.wire_buf);
                mp_flush(the_worker->pkt_pool.ctx);
                return protolayer_break(ctx, ret);
@@ -2002,6 +1967,7 @@ static int pl_dns_sstream_sess_init(struct protolayer_manager *manager,
 }
 
 static int pl_dns_stream_iter_init(struct protolayer_manager *manager,
+                                   struct protolayer_iter_ctx *ctx,
                                    void *iter_data)
 {
        struct pl_dns_stream_iter_data *stream = iter_data;
@@ -2010,6 +1976,7 @@ static int pl_dns_stream_iter_init(struct protolayer_manager *manager,
 }
 
 static int pl_dns_stream_iter_deinit(struct protolayer_manager *manager,
+                                     struct protolayer_iter_ctx *ctx,
                                      void *iter_data)
 {
        struct pl_dns_stream_iter_data *stream = iter_data;
@@ -2249,7 +2216,7 @@ static enum protolayer_iter_cb_result pl_dns_stream_unwrap(
                if (stream_sess->single && stream_sess->produced) {
                        if (kr_log_is_debug(WORKER, NULL)) {
                                kr_log_debug(WORKER, "Unexpected extra data from %s\n",
-                                               kr_straddr(ctx->comm.src_addr));
+                                               kr_straddr(ctx->comm->src_addr));
                        }
                        mp_flush(the_worker->pkt_pool.ctx);
                        worker_end_tcp(session);
@@ -2260,7 +2227,7 @@ static enum protolayer_iter_cb_result pl_dns_stream_unwrap(
                if (!pkt)
                        return protolayer_break(ctx, KNOT_EMALF);
 
-               int ret = worker_submit(session, &ctx->comm, NULL, NULL, pkt);
+               int ret = worker_submit(session, ctx->comm, NULL, NULL, pkt);
                wire_buf_movestart(wb);
                mp_flush(the_worker->pkt_pool.ctx);
                if (ret) {
index a44e090542e750ee7ffee98e7c23e69ed9280089..46df95ac7c883e95a7ee26a19c9b248c5f25afbf 100644 (file)
@@ -804,7 +804,7 @@ knot_pkt_t *kr_request_ensure_answer(struct kr_request *request)
        uint16_t answer_max;
        const struct kr_request_qsource_flags *qs_flags = &request->qsource.flags;
        const struct kr_request_qsource_flags *qs_cflags = &request->qsource.comm_flags;
-       if (kr_fails_assert(!(qs_flags->tls || qs_cflags->tls || qs_cflags->http) || qs_flags->tcp))
+       if (kr_fails_assert(!(qs_flags->tls || qs_cflags->tls) || qs_flags->tcp || qs_cflags->http))
                goto fail;
        if (!request->qsource.addr || qs_flags->tcp || qs_cflags->tcp) {
                // not on UDP
index 2360e7f48f60a958fc431bf3762b57f41e48ae6f..c35c7a4f7d4d7b84f8f8dda54311bf1d912aa38e 100644 (file)
@@ -77,7 +77,8 @@ end
 local bound, port
 local host = '127.0.0.1'
 for _  = 1,10 do
-       port = math.random(30000, 39999)
+       --port = math.random(30000, 39999)
+       port = 30000
        bound = pcall(net.listen, host, port, { kind = 'doh2'})
        if bound then
                break