--- /dev/null
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "contrib/base64url.h"
+#include "libknot/errcode.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/*! \brief Maximal length of binary input to Base64url encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+/*! \brief Base64url padding character. */
+static const uint8_t base64url_pad = '\0';
+/*! \brief Base64 alphabet. */
+static const uint8_t base64url_enc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+/*! \brief Indicates bad Base64 character. */
+#define KO 255
+/*! \brief Indicates Base64 padding character. */
+#define PD 64
+
+/*! \brief Transformation and validation table for decoding Base64. */
+static const uint8_t base64url_dec[256] = {
+ [ 0] = PD, [ 43] = KO, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO,
+ [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO,
+ [ 2] = KO, ['-'] = 62, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO,
+ [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO,
+ [ 4] = KO, [ 47] = KO, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO,
+ [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO,
+ [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO,
+ [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO,
+ [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO,
+ [ 9] = KO, ['4'] = 56, ['_'] = 63, [138] = KO, [181] = KO, [224] = KO,
+ [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO,
+ [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO,
+ [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO,
+ [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO,
+ [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO,
+ [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO,
+ [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO,
+ [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO,
+ [ 18] = KO, [ 61] = KO, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO,
+ [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO,
+ [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO,
+ [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO,
+ [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO,
+ [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO,
+ [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO,
+ [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO,
+ [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO,
+ [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO,
+ [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO,
+ [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO,
+ [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO,
+ [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO,
+ [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO,
+ [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO,
+ [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO,
+ [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO,
+ [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO,
+ [ 37] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO,
+ [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO,
+ [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO,
+ [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO,
+ [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO,
+ [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO,
+};
+
+int32_t kr_base64url_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) {
+ return KNOT_ERANGE;
+ }
+
+ uint8_t rest_len = in_len % 3;
+ const uint8_t *stop = in + in_len - rest_len;
+ uint8_t *text = out;
+
+ // Encoding loop takes 3 bytes and creates 4 characters.
+ while (in < stop) {
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64url_enc[(in[1] & 0x0F) << 2 | in[2] >> 6];
+ text[3] = base64url_enc[in[2] & 0x3F];
+ text += 4;
+ in += 3;
+ }
+
+ // Processing of padding, if any.
+ switch (rest_len) {
+ case 2:
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64url_enc[(in[1] & 0x0F) << 2];
+ text[3] = base64url_pad;
+ text += 3;
+ break;
+ case 1:
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4];
+ text[2] = base64url_pad;
+ text[3] = base64url_pad;
+ text += 2;
+ break;
+ }
+ return (text - out);
+}
+
+int32_t kr_base64url_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return KNOT_ERANGE;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 2) / 3) * 4;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Encode data.
+ int32_t ret = kr_base64url_encode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
+
+int32_t kr_base64url_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) {
+ return KNOT_ERANGE;
+ }
+
+ const uint8_t *stop = in + in_len;
+ uint8_t *bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4;
+
+ // Decoding loop takes 4 characters and creates 3 bytes.
+ while (in < stop) {
+ // Filling and transforming 4 Base64 chars.
+ c1 = base64url_dec[in[0]] ;
+ c2 = (in + 1 < stop) ? base64url_dec[in[1]] : PD;
+ c3 = (in + 2 < stop) ? base64url_dec[in[2]] : PD;
+ c4 = (in + 3 < stop) ? base64url_dec[in[3]] : PD;
+
+ // Check 4. char if is bad or padding.
+ if (c4 >= PD) {
+ if (c4 == PD && pad_len == 0) {
+ pad_len = 1;
+ } else {
+ return KNOT_BASE64_ECHAR;
+ }
+ }
+
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (c3 == PD && pad_len == 1) {
+ pad_len = 2;
+ } else {
+ return KNOT_BASE64_ECHAR;
+ }
+ }
+
+ // Check 1. and 2. chars if are not padding.
+ if (c2 >= PD || c1 >= PD) {
+ return KNOT_BASE64_ECHAR;
+ }
+
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ case 0:
+ bin[2] = (c3 << 6) + c4;
+ // FALLTHROUGH
+ case 1:
+ bin[1] = (c2 << 4) + (c3 >> 2);
+ // FALLTHROUGH
+ case 2:
+ bin[0] = (c1 << 2) + (c2 >> 4);
+ }
+
+ // Update output end.
+ switch (pad_len) {
+ case 0:
+ bin += 3;
+ break;
+ case 1:
+ bin += 2;
+ break;
+ case 2:
+ bin += 1;
+ break;
+ }
+
+ in += 4;
+ }
+
+ return (bin - out);
+}
+
+int32_t kr_base64url_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 3) / 4) * 3;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Decode data.
+ int32_t ret = kr_base64url_decode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
--- /dev/null
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Base64url implementation (RFC 4648).
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*!
+ * \brief Encodes binary data using Base64.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t kr_base64url_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Encodes binary data using Base64 and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t kr_base64url_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*!
+ * \brief Decodes text data using Base64.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t kr_base64url_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Decodes text data using Base64 and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t kr_base64url_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*! @} */
'ucw/mempool-fmt.c',
'murmurhash3/murmurhash3.c',
'base32hex.c',
- 'base64.c'
+ 'base64.c',
+ 'base64url.c'
])
contrib_inc = include_directories('.', '..')
-/* Copyright (C) 2015-2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/** Listen on an address list represented by the top of lua stack.
* \note kind ownership is not transferred
* \return success */
-static bool net_listen_addrs(lua_State *L, int port, bool tls, const char *kind, bool freebind)
+static bool net_listen_addrs(lua_State *L, int port, bool tls, bool http, const char *kind, bool freebind)
{
/* Case: table with 'addr' field; only follow that field directly. */
lua_getfield(L, -1, "addr");
if (str != NULL) {
struct network *net = &the_worker->engine->net;
int ret = 0;
- endpoint_flags_t flags = { .tls = tls, .freebind = freebind };
+ endpoint_flags_t flags = { .tls = tls, .http = http, .freebind = freebind };
if (!kind && !flags.tls) { /* normal UDP */
flags.sock_type = SOCK_DGRAM;
ret = network_listen(net, str, port, flags);
lua_error_p(L, "bad type for address");
lua_pushnil(L);
while (lua_next(L, -2)) {
- if (!net_listen_addrs(L, port, tls, kind, freebind))
+ if (!net_listen_addrs(L, port, tls, http, kind, freebind))
return false;
lua_pop(L, 1);
}
}
bool tls = (port == KR_DNS_TLS_PORT);
+ bool http = (port == KR_DNS_HTTP_PORT);
+ http = tls = (port == KR_DNS_DOH_PORT);
bool freebind = false;
const char *kind = NULL;
if (n > 2 && !lua_isnil(L, 3)) {
lua_getfield(L, 3, "kind");
const char *k = lua_tostring(L, -1);
if (k && strcasecmp(k, "dns") == 0) {
- tls = false;
+ tls = http = false;
} else
if (k && strcasecmp(k, "tls") == 0) {
tls = true;
+ http = false;
+ } else
+ //TODO temporary moved HTTP (without TLS) here
+ if (k && strcasecmp(k, "http") == 0) {
+ tls = false;
+ http = true;
+ } else
+ if (k && strcasecmp(k, "doh") == 0) {
+ tls = http = true;
} else
if (k) {
kind = k;
/* Now focus on the first argument. */
lua_settop(L, 1);
- if (!net_listen_addrs(L, port, tls, kind, freebind))
+ if (!net_listen_addrs(L, port, tls, http, kind, freebind))
lua_error_p(L, "net.listen() failed to bind");
lua_pushboolean(L, true);
return 1;
--- /dev/null
+/*
+ * Copyright (C) 2020 CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Jan Hák <jan.hak@nic.cz>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <nghttp2/nghttp2.h>
+#include <uv.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "daemon/io.h"
+#include "daemon/http.h"
+#include "daemon/worker.h"
+#include "daemon/session.h"
+
+#include "contrib/base64url.h"
+
+#define MAKE_NV(K, KS, V, VS) \
+ { (uint8_t *)K, (uint8_t *)V, KS, VS, NGHTTP2_NV_FLAG_NONE }
+
+#define MAKE_STATIC_NV(K, V) \
+ MAKE_NV(K, sizeof(K) - 1, V, sizeof(V) - 1)
+
+#define HTTP_MAX_CONCURRENT_STREAMS 1
+
+#define MAX_DECIMAL_LENGTH(VT) (CHAR_BIT * sizeof(VT) / 3) + 3
+
+struct http_data_buffer {
+ uint8_t *data;
+ const uint8_t *end;
+};
+
+static char const server_logstring[] = "http";
+
+static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data)
+{
+ struct http_ctx_t *ctx = (struct http_ctx_t *)user_data;
+ return ctx->send_cb(data, length, ctx->user_ctx);
+}
+
+static int header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data)
+{
+ //TODO some validation.. When POST, no DNS variable in path...
+ //In knot we parse path using some static lib, think of use it too but not necessary
+ static const char key[] = "dns=";
+ struct http_ctx_t *ctx = (struct http_ctx_t *)user_data;
+ if (!strcasecmp(":path", (const char *)name)) {
+ char *beg = strstr((const char *)value, key);
+ if (beg) {
+ beg += sizeof(key) - 1;
+ char *end = strchrnul(beg, '&');
+ ctx->wire_len = kr_base64url_decode((uint8_t*)beg, end - beg, ctx->wire + sizeof(uint16_t), ctx->wire_len - sizeof(uint16_t));
+ ctx->request_stream_id = frame->hd.stream_id;
+ }
+ }
+ return 0;
+}
+
+static int query_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data)
+{
+ struct http_ctx_t *ctx = (struct http_ctx_t *)user_data;
+ memcpy(ctx->wire + sizeof(uint16_t), data, len);
+ ctx->wire_len = len;
+ ctx->request_stream_id = stream_id;
+ return 0;
+}
+
+struct http_ctx_t* http_new(http_send_callback cb, void *user_ctx)
+{
+ assert(cb != NULL);
+
+ nghttp2_session_callbacks *callbacks;
+ nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, query_recv_callback);
+
+ struct http_ctx_t *ctx = calloc(1UL, sizeof(struct http_ctx_t));
+ ctx->send_cb = cb;
+ ctx->user_ctx = user_ctx;
+ ctx->request_stream_id = -1;
+
+ nghttp2_session_server_new(&ctx->session, callbacks, ctx);
+ nghttp2_session_callbacks_del(callbacks);
+
+ static const nghttp2_settings_entry iv[] = {
+ { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
+ };
+
+ nghttp2_submit_settings(ctx->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(*iv) );
+
+ return ctx;
+}
+
+ssize_t http_process_input_data(struct session *s, const uint8_t *in_buf, ssize_t in_buf_len)
+{
+ struct http_ctx_t *http_p = session_http_get_server_ctx(s);
+ if (!http_p->session) {
+ return kr_error(ENOSYS);
+ }
+
+ http_p->wire = session_wirebuf_get_free_start(s);
+ http_p->wire_len = session_wirebuf_get_free_size(s);
+ ssize_t ret = 0;
+ if ((ret = nghttp2_session_mem_recv(http_p->session, in_buf, in_buf_len)) < 0) {
+ kr_log_error("[%s] nghttp2_session_mem_recv failed: %s (%zd)\n", server_logstring, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ if ((ret = nghttp2_session_send(http_p->session)) < 0) {
+ kr_log_error("[%s] nghttp2_session_send failed: %s (%zd)\n", server_logstring, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ ssize_t submitted = 0;
+ if (http_p->request_stream_id >= 0) {
+ knot_wire_write_u16(http_p->wire, http_p->wire_len);
+ submitted = http_p->wire_len + sizeof(uint16_t);
+ }
+
+ return submitted;
+}
+
+static ssize_t send_response_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
+{
+ struct http_data_buffer *buffer = (struct http_data_buffer *)source->ptr;
+ struct http_ctx_t *ctx = (struct http_ctx_t *)user_data; //TODO remove maybe
+ size_t send = MIN(buffer->end - buffer->data, length);
+ memcpy(buf, buffer->data, send);
+ buffer->data += send;
+ //*data_flags |= (buffer->data == buffer->end) ? NGHTTP2_DATA_FLAG_EOF : 0;
+ if (buffer->data == buffer->end) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ ctx->request_stream_id = -1;
+ }
+ return send;
+}
+
+int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, uv_write_cb cb)
+{
+ if (!pkt || !handle || !handle->data) {
+ return kr_error(EINVAL);
+ }
+
+ struct session *s = handle->data;
+ struct http_ctx_t *http_ctx = session_http_get_server_ctx(s);
+
+ assert (http_ctx);
+ assert (!session_flags(s)->outgoing);
+
+ char size[MAX_DECIMAL_LENGTH(pkt->size)] = { 0 };
+ int size_len = sprintf(size, "%ld", pkt->size);
+
+ struct http_data_buffer data_buff = {
+ .data = pkt->wire,
+ .end = pkt->wire + pkt->size
+ };
+
+ const nghttp2_data_provider data_prd = {
+ .source = {
+ .ptr = &data_buff
+ },
+ .read_callback = send_response_callback
+ };
+
+ nghttp2_nv hdrs[] = {
+ MAKE_STATIC_NV(":status", "200"),
+ MAKE_STATIC_NV("content-type", "application/dns-message"),
+ MAKE_NV("content-length", 14, size, size_len)
+ };
+
+ int ret = nghttp2_submit_response(http_ctx->session, http_ctx->request_stream_id, hdrs, sizeof(hdrs)/sizeof(*hdrs), &data_prd);
+ if (ret != 0) {
+ kr_log_error("[%s] nghttp2_submit_response failed: %s (%d)\n", server_logstring, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ if((ret = nghttp2_session_send(http_ctx->session)) < 0) {
+ kr_log_error("[%s] nghttp2_session_send failed: %s (%d)\n", server_logstring, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ /* The data is now accepted in gnutls internal buffers, the message can be treated as sent */
+ req->handle = (uv_stream_t *)handle;
+ cb(req, 0);
+
+ return kr_ok();
+}
+
+void http_free(struct http_ctx_t *ctx)
+{
+ if (ctx == NULL || ctx->session == NULL) {
+ return;
+ }
+ nghttp2_session_del(ctx->session);
+ ctx->session = NULL;
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2020 CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Jan Hák <jan.hak@nic.cz>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <uv.h>
+#include <nghttp2/nghttp2.h>
+#include <libknot/packet/pkt.h>
+
+/** Transport session (opaque). */
+struct session;
+
+typedef ssize_t(*http_send_callback)(const uint8_t *buffer, const size_t buffer_len, void *user_ctx);
+
+struct http_ctx_t {
+ struct nghttp2_session *session;
+ http_send_callback send_cb;
+ void *user_ctx;
+ int32_t request_stream_id;
+ uint8_t *wire;
+ int32_t wire_len;
+};
+
+struct http_ctx_t* http_new(http_send_callback cb, void *user_ctx);
+ssize_t http_process_input_data(struct session *s, const uint8_t *buf, ssize_t nread);
+int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, uv_write_cb cb);
+void http_free(struct http_ctx_t *ctx);
\ No newline at end of file
-/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "daemon/network.h"
#include "daemon/worker.h"
#include "daemon/tls.h"
+#include "daemon/http.h"
#include "daemon/session.h"
#include "contrib/cleanup.h"
#include "lib/utils.h"
uv_handle_t *h = (uv_handle_t *)handle;
check_bufsize(h);
/* Handle is already created, just create context. */
- struct session *s = session_new(h, false);
+ struct session *s = session_new(h, false, false);
assert(s);
session_flags(s)->outgoing = false;
data = session_wirebuf_get_free_start(s);
data_len = consumed;
}
+ if (session_flags(s)->has_http) {
+ consumed = http_process_input_data(s, data, data_len);
+ if (consumed < 0) {
+ if (kr_verbose_status) {
+ struct sockaddr *peer = session_get_peer(s);
+ char *peer_str = kr_straddr(peer);
+ kr_log_verbose("[io] => connection to '%s': "
+ "error processing HTTP 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;
+ }
/* data points to start of the free space in session wire buffer.
Simple increase internal counter. */
mp_flush(the_worker->pkt_pool.ctx);
}
-static void _tcp_accept(uv_stream_t *master, int status, bool tls)
+static void on_write(uv_write_t *req, int status)
+{
+ struct qr_task *task = req->data;
+ if (task) {
+ uv_handle_t *h = (uv_handle_t *)req->handle;
+ qr_task_on_send(task, h, status);
+ }
+ free(req);
+}
+
+static ssize_t tcp_send(const uint8_t *buffer, const size_t buffer_len, void *user_ctx)
+{
+ //TODO not complete, probably do not respect the sending policy in the software
+ struct session *session = user_ctx;
+ uv_handle_t *handle = session_get_handle(session);
+ //const uint8_t *buffer_backup = (const uint8_t *)calloc(buffer_len, sizeof(*buffer));
+ //if (!buffer_backup) {
+ // return kr_error(EIO);
+ //}
+ //memcpy(buffer_backup, buffer, buffer_len);
+
+ uv_write_t *req = (uv_write_t *)calloc(1, sizeof(uv_write_t));
+ if (!req) {
+ return kr_error(EIO);
+ }
+
+ const uv_buf_t uv_buffer = {
+ //.base = buffer_backup,
+ .base = buffer,
+ .len = buffer_len
+ };
+ uv_write(req, (uv_stream_t *)handle, &uv_buffer, 1, on_write);
+ return buffer_len;
+}
+
+static ssize_t tls_send(const uint8_t *buffer, const size_t buffer_len, void *user_ctx)
+{
+ struct tls_ctx_t *ctx = user_ctx;
+ ssize_t len = 0;
+ if ((len = gnutls_record_send(ctx->c.tls_session, buffer, buffer_len)) < 0) {
+ return kr_error(EIO);
+ }
+ return len;
+}
+
+static void _tcp_accept(uv_stream_t *master, int status, bool tls, bool http)
{
if (status != 0) {
return;
return;
}
int res = io_create(master->loop, (uv_handle_t *)client,
- SOCK_STREAM, AF_UNSPEC, tls);
+ SOCK_STREAM, AF_UNSPEC, tls, http);
if (res) {
if (res == UV_EMFILE) {
worker->too_many_open = true;
session_tls_set_server_ctx(s, ctx);
}
}
+ if (http) {
+ struct http_ctx_t *ctx = session_http_get_server_ctx(s);
+ if (!ctx) {
+ ctx = http_new((tls) ? tls_send : tcp_send,
+ (tls) ? (void*)session_tls_get_server_ctx(s) : (void*)s
+ );
+ if (!ctx) {
+ session_close(s);
+ return;
+ }
+
+ struct tls_ctx_t *tls_ctx = session_tls_get_server_ctx(s);
+ if (tls_ctx) {
+ const gnutls_datum_t protos[] = {
+ {(unsigned char *)"h2", 2}
+ };
+ ret = gnutls_alpn_set_protocols(tls_ctx->c.tls_session,
+ protos, sizeof(protos)/sizeof(*protos),
+ 0);
+ if (ret != GNUTLS_E_SUCCESS) {
+ session_close(s);
+ return;
+ }
+ }
+ session_http_set_server_ctx(s, ctx);
+ }
+ }
session_timer_start(s, tcp_timeout_trigger, timeout, idle_in_timeout);
io_start_read((uv_handle_t *)client);
}
static void tcp_accept(uv_stream_t *master, int status)
{
- _tcp_accept(master, status, false);
+ _tcp_accept(master, status, false, false);
}
static void tls_accept(uv_stream_t *master, int status)
{
- _tcp_accept(master, status, true);
+ _tcp_accept(master, status, true, false);
+}
+
+static void http_accept(uv_stream_t *master, int status)
+{
+ _tcp_accept(master, status, false, true);
}
-int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls)
+static void https_accept(uv_stream_t *master, int status)
{
- const uv_connection_cb connection = has_tls ? tls_accept : tcp_accept;
+ _tcp_accept(master, status, true, true);
+}
+
+int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http)
+{
+ uv_connection_cb connection;
+ if (has_tls && has_http) {
+ connection = https_accept;
+ } else if (has_tls) {
+ connection = tls_accept;
+ } else if (has_http) {
+ connection = http_accept;
+ } else {
+ connection = tcp_accept;
+ }
+
if (!handle) {
return kr_error(EINVAL);
}
return 0;
}
-int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls)
+int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls, bool has_http)
{
int ret = -1;
if (type == SOCK_DGRAM) {
if (ret != 0) {
return ret;
}
- struct session *s = session_new(handle, has_tls);
+ struct session *s = session_new(handle, has_tls, has_http);
if (s == NULL) {
ret = -1;
}
-/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/** Initialize a UDP handle and start listening. */
int io_listen_udp(uv_loop_t *loop, uv_udp_t *handle, int fd);
/** Initialize a TCP handle and start listening. */
-int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls);
+int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http);
/** Initialize a pipe handle and start listening. */
int io_listen_pipe(uv_loop_t *loop, uv_pipe_t *handle, int fd);
* \param family = AF_*
* \param has_tls has meanings only when type is SOCK_STREAM */
int io_create(uv_loop_t *loop, uv_handle_t *handle, int type,
- unsigned family, bool has_tls);
+ unsigned family, bool has_tls, bool has_http);
void io_deinit(uv_handle_t *handle);
void io_free(uv_handle_t *handle);
'bindings/worker.c',
'engine.c',
'ffimodule.c',
+ 'http.c',
'io.c',
'main.c',
'network.c',
'tls.c',
'tls_ephemeral_credentials.c',
'tls_session_ticket-srv.c',
+ 'http.c',
'udp_queue.c',
'worker.c',
'zimport.c',
gnutls,
libsystemd,
capng,
+ nghttp2,
]
-/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
return kr_error(ENOMEM);
}
return io_listen_tcp(net->loop, ep_handle, ep->fd,
- net->tcp_backlog, ep->flags.tls);
+ net->tcp_backlog, ep->flags.tls, ep->flags.http);
} /* else */
assert(!EINVAL);
-/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2015-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
typedef struct {
int sock_type; /**< SOCK_DGRAM or SOCK_STREAM */
bool tls; /**< only used together with .kind == NULL and .tcp */
+ bool http; /**< only used together with .kind == NULL and .tcp */
const char *kind; /**< tag for other types than the three usual */
bool freebind; /**< used for binding to non-local address **/
} endpoint_flags_t;
-/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2018-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "daemon/session.h"
#include "daemon/engine.h"
#include "daemon/tls.h"
+#include "daemon/http.h"
#include "daemon/worker.h"
#include "daemon/io.h"
#include "lib/generic/queue.h"
struct tls_ctx *tls_ctx; /**< server side tls-related data. */
struct tls_client_ctx *tls_client_ctx; /**< client side tls-related data. */
+ struct http_ctx_t *http_ctx; /**< server side http-related data. */
+
trie_t *tasks; /**< list of tasks assotiated with given session. */
queue_t(struct qr_task *) waiting; /**< list of tasks waiting for sending to upstream. */
queue_deinit(session->waiting);
tls_free(session->tls_ctx);
tls_client_ctx_free(session->tls_client_ctx);
+ http_free(session->http_ctx);
memset(session, 0, sizeof(*session));
}
return tls_ctx;
}
+struct http_ctx_t *session_http_get_server_ctx(const struct session *session)
+{
+ return session->http_ctx;
+}
+
+void session_http_set_server_ctx(struct session *session, struct http_ctx_t *ctx)
+{
+ session->http_ctx = ctx;
+}
+
uv_handle_t *session_get_handle(struct session *session)
{
return session->handle;
return h->data;
}
-struct session *session_new(uv_handle_t *handle, bool has_tls)
+struct session *session_new(uv_handle_t *handle, bool has_tls, bool has_http)
{
if (!handle) {
return NULL;
wire_buffer_size += TLS_CHUNK_SIZE;
session->sflags.has_tls = true;
}
+ if (has_http) {
+ session->sflags.has_http = true;
+ }
uint8_t *wire_buf = malloc(wire_buffer_size);
if (!wire_buf) {
free(session);
-/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2018-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
bool outgoing : 1; /**< True: to upstream; false: from a client. */
bool throttled : 1; /**< True: data reading from peer is temporarily stopped. */
bool has_tls : 1; /**< True: given session uses TLS. */
+ bool has_http : 1; /**< True: given session uses HTTP. */
bool connected : 1; /**< True: TCP connection is established. */
bool closing : 1; /**< True: session close sequence is in progress. */
bool wirebuf_error : 1; /**< True: last operation with wirebuf ended up with an error. */
/* Allocate new session for a libuv handle.
* If handle->tyoe is UV_UDP, tls parameter will be ignored. */
-struct session *session_new(uv_handle_t *handle, bool has_tls);
+struct session *session_new(uv_handle_t *handle, bool has_tls, bool has_http);
/* Clear and free given session. */
void session_free(struct session *session);
/* Clear session. */
* server and client. */
struct tls_common_ctx *session_tls_get_common_ctx(const struct session *session);
+/** Get pointer to server-side http-related data. */
+struct http_ctx_t *session_http_get_server_ctx(const struct session *session);
+/** Set pointer to server-side http-related data. */
+void session_http_set_server_ctx(struct session *session, struct http_ctx_t *ctx);
+
/** Get pointer to underlying libuv handle for IO operations. */
uv_handle_t *session_get_handle(struct session *session);
struct session *session_get(uv_handle_t *h);
-/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "daemon/io.h"
#include "daemon/session.h"
#include "daemon/tls.h"
+#include "daemon/http.h"
#include "daemon/udp_queue.h"
#include "daemon/zimport.h"
#include "lib/layer.h"
/*! @internal Create a UDP/TCP handle for an outgoing AF_INET* connection.
* socktype is SOCK_* */
static uv_handle_t *ioreq_spawn(struct worker_ctx *worker,
- int socktype, sa_family_t family, bool has_tls)
+ int socktype, sa_family_t family, bool has_tls,
+ bool has_http)
{
bool precond = (socktype == SOCK_DGRAM || socktype == SOCK_STREAM)
&& (family == AF_INET || family == AF_INET6);
if (!handle) {
return NULL;
}
- int ret = io_create(worker->loop, handle, socktype, family, has_tls);
+ int ret = io_create(worker->loop, handle, socktype, family, has_tls, has_http);
if (ret) {
if (ret == UV_EMFILE) {
worker->too_many_open = true;
req->qsource.dst_addr = session_get_sockname(session);
req->qsource.flags.tcp = session_get_handle(session)->type == UV_TCP;
req->qsource.flags.tls = session_flags(session)->has_tls;
+ req->qsource.flags.http = session_flags(session)->has_http;
/* We need to store a copy of peer address. */
memcpy(&ctx->source.addr.ip, peer, kr_sockaddr_len(peer));
req->qsource.addr = &ctx->source.addr.ip;
struct worker_ctx *worker = ctx->worker;
/* Send using given protocol */
assert(!session_flags(session)->closing);
- if (session_flags(session)->has_tls) {
+ if (session_flags(session)->has_http) {
+ uv_write_t *write_req = (uv_write_t *)ioreq;
+ write_req->data = task;
+ ret = http_write(write_req, handle, pkt, &on_write);
+ } else if (session_flags(session)->has_tls) {
uv_write_t *write_req = (uv_write_t *)ioreq;
write_req->data = task;
ret = tls_write(write_req, handle, pkt, &on_write);
if (kr_resolve_checkout(&ctx->req, NULL, (struct sockaddr *)choice, SOCK_DGRAM, task->pktbuf) != 0) {
return ret;
}
- ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false);
+ ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false, false);
if (!ret) {
return ret;
}
tls_client_ctx_free(tls_ctx);
return kr_error(EINVAL);
}
+ bool has_http = false;
bool has_tls = (tls_ctx != NULL);
- uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls);
+ uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls, has_http);
if (!client) {
tls_client_ctx_free(tls_ctx);
free(conn);
-/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2014-2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
* Defines.
*/
#define KR_DNS_PORT 53
+#define KR_DNS_HTTP_PORT 80
+#define KR_DNS_DOH_PORT 443
#define KR_DNS_TLS_PORT 853
#define KR_EDNS_VERSION 0
#define KR_EDNS_PAYLOAD 4096 /* Default UDP payload (max unfragmented UDP is 1452B) */
endif
gnutls = dependency('gnutls')
luajit = dependency('luajit')
+nghttp2 = dependency('libnghttp2')
# NOTE avoid using link_args for luajit due to a macOS issue
# https://github.com/Homebrew/homebrew-core/issues/37169
luajit_inc = luajit.partial_dependency(compile_args: true, includes: true)