From 21c5263d491741a3ae982bc7874eb4f2f9724d09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20H=C3=A1k?= <jan.hak@nic.cz>
Date: Tue, 3 May 2022 09:45:45 +0200
Subject: [PATCH] kdig: quic connect

---
 src/utils/common/https.c |  20 +-
 src/utils/common/https.h |   5 +-
 src/utils/common/netio.c | 110 +++++--
 src/utils/common/quic.c  | 640 ++++++++++++++++++++++++++++++++++++++-
 src/utils/common/quic.h  |  36 ++-
 src/utils/common/tls.c   |  47 +--
 src/utils/common/tls.h   |   7 +-
 7 files changed, 794 insertions(+), 71 deletions(-)

diff --git a/src/utils/common/https.c b/src/utils/common/https.c
index e432b0524f..d3910b9be4 100644
--- a/src/utils/common/https.c
+++ b/src/utils/common/https.c
@@ -25,6 +25,7 @@
 #include "contrib/openbsd/strlcat.h"
 #include "contrib/openbsd/strlcpy.h"
 #include "contrib/url-parser/url_parser.h"
+#include "libknot/dname.h"
 #include "libknot/errcode.h"
 #include "utils/common/https.h"
 #include "utils/common/msg.h"
@@ -319,15 +320,15 @@ static int sockaddr_to_authority(char *buf, const size_t buf_len, const struct s
 	return KNOT_EOK;
 }
 
-int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote,
-                      bool fastopen, struct sockaddr_storage *addr)
+int https_ctx_connect(https_ctx_t *ctx, int sockfd, bool fastopen,
+                      struct sockaddr_storage *addr)
 {
 	if (ctx == NULL || addr == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	// Create TLS connection
-	int ret = tls_ctx_connect(ctx->tls, sockfd, remote, fastopen, addr);
+	int ret = tls_ctx_connect(ctx->tls, sockfd, fastopen, addr);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -345,11 +346,14 @@ int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote,
 
 	// Save authority server
 	if (ctx->authority == NULL) {
-		if (remote != NULL) {
-			ctx->authority = strdup(remote);
-		} else {
-			ctx->authority = (char*)calloc(HTTPS_AUTHORITY_LEN, sizeof(char));
-			ret = sockaddr_to_authority(ctx->authority, HTTPS_AUTHORITY_LEN, addr);
+		//TODO test
+		ctx->authority = (char*)calloc(KNOT_DNAME_TXT_MAXLEN + 1, sizeof(char));
+		unsigned int type = GNUTLS_NAME_DNS;
+		size_t len = KNOT_DNAME_TXT_MAXLEN + 1;
+		ret = gnutls_server_name_get(ctx->tls->session, ctx->authority, &len,
+		                             &type, 0);
+		if (ret == GNUTLS_E_SUCCESS && ctx->authority[0] == '\0') {
+			ret = sockaddr_to_authority(ctx->authority, KNOT_DNAME_TXT_MAXLEN + 1, addr);
 			if (ret != KNOT_EOK) {
 				free(ctx->authority);
 				ctx->authority = NULL;
diff --git a/src/utils/common/https.h b/src/utils/common/https.h
index 6c54baee15..aed1cd5dd6 100644
--- a/src/utils/common/https.h
+++ b/src/utils/common/https.h
@@ -96,7 +96,6 @@ int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *p
  *
  * \param ctx       HTTPS context.
  * \param sockfd    Socket descriptor.
- * \param remote    [optional] Remote name.
  * \param fastopen  Use TCP Fast Open indication.
  * \param addr      Socket address storage with address to server side.
  *
@@ -106,8 +105,8 @@ int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *p
  * \retval KNOT_NET_ETIMEOUT  When server respond takes too long.
  * \retval KNOT_NET_ECONNECT  When unnable to connect to the server.
  */
-int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote,
-                      bool fastopen, struct sockaddr_storage *addr);
+int https_ctx_connect(https_ctx_t *ctx, int sockfd, bool fastopen,
+                      struct sockaddr_storage *addr);
 
 /*!
  * \brief Send buffer as DNS message over HTTPS.
diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c
index de0729b907..28db3c9804 100644
--- a/src/utils/common/netio.c
+++ b/src/utils/common/netio.c
@@ -224,8 +224,7 @@ int net_init(const srv_info_t     *local,
 		// Prepare for HTTPS.
 		if (https_params != NULL && https_params->enable) {
 			ret = tls_ctx_init(&net->tls, tls_params,
-			                   GNUTLS_NONBLOCK, net->wait,
-			                   &doh_alpn, 1, NULL);
+			                   GNUTLS_NONBLOCK, net->wait);
 			if (ret != KNOT_EOK) {
 				net_clean(net);
 				return ret;
@@ -241,8 +240,7 @@ int net_init(const srv_info_t     *local,
 		if (quic_params != NULL && quic_params->enable) {
 			ret = tls_ctx_init(&net->tls, tls_params,
 			        GNUTLS_NONBLOCK | GNUTLS_ENABLE_EARLY_DATA |
-			        GNUTLS_NO_END_OF_EARLY_DATA, net->wait,
-			        quic_alpn, 4, QUIC_PRIORITY); // TODO will be 1 on release
+			        GNUTLS_NO_END_OF_EARLY_DATA, net->wait);
 			if (ret != KNOT_EOK) {
 				net_clean(net);
 				return ret;
@@ -256,8 +254,7 @@ int net_init(const srv_info_t     *local,
 #endif //LIBNGTCP2
 		{
 			ret = tls_ctx_init(&net->tls, tls_params,
-			                   GNUTLS_NONBLOCK, net->wait,
-			                   &dot_alpn, 1, NULL);
+			                   GNUTLS_NONBLOCK, net->wait);
 			if (ret != KNOT_EOK) {
 				net_clean(net);
 				return ret;
@@ -321,6 +318,45 @@ static int fastopen_send(int sockfd, const struct msghdr *msg, int timeout)
 #endif
 }
 
+static char *net_get_remote(const net_t *net)
+{
+	if (net->tls.params->sni != NULL) {
+		return net->tls.params->sni;
+	} else if (net->tls.params->hostname != NULL) {
+		return net->tls.params->hostname;
+	} else if (strchr(net->remote_str, ':') == NULL) {
+		char *at = strchr(net->remote_str, '@');
+		if (at != NULL && strncmp(net->remote->name, net->remote_str,
+		                          at - net->remote_str)) {
+			return net->remote->name;
+		}
+	}
+	return NULL;
+}
+
+#ifdef LIBNGTCP2
+static int fd_set_recv_ecn(int fd, int family)
+{
+	unsigned int tos = 1;
+	switch (family) {
+	case AF_INET:
+		if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos, sizeof(tos)) == -1) {
+			return knot_map_errno();
+		}
+		break;
+	case AF_INET6:
+		if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos, sizeof(tos)) == -1) {
+			return knot_map_errno();
+		}
+		break;
+	default:
+		return KNOT_EINVAL;
+	}
+	return KNOT_EOK;
+}
+#endif
+
+
 int net_connect(net_t *net)
 {
 	if (net == NULL || net->srv == NULL) {
@@ -365,8 +401,9 @@ int net_connect(net_t *net)
 		(void)bind(sockfd, (struct sockaddr *)&local, sockaddr_len(&local));
 	}
 
+	int ret = 0;
 	if (net->socktype == SOCK_STREAM) {
-		int  cs, err, ret = 0;
+		int  cs, err;
 		socklen_t err_len = sizeof(err);
 		bool fastopen = net->flags & NET_FLAGS_FASTOPEN;
 
@@ -403,34 +440,57 @@ int net_connect(net_t *net)
 #ifdef LIBNGHTTP2
 			if (net->https.params.enable) {
 				// Establish HTTPS connection.
-				char *remote = NULL;
-				if (net->tls.params->sni != NULL) {
-					remote = net->tls.params->sni;
-				} else if (net->tls.params->hostname != NULL) {
-					remote = net->tls.params->hostname;
-				} else if (strchr(net->remote_str, ':') == NULL) {
-					char *at = strchr(net->remote_str, '@');
-					if (at != NULL && strncmp(net->remote->name, net->remote_str,
-					                          at - net->remote_str)) {
-						remote = net->remote->name;
-					}
+				ret = tls_ctx_setup_remote_endpoint(&net->tls, &doh_alpn, 1, NULL,
+				        net_get_remote(net));
+				if (ret != 0) {
+					close(sockfd);
+					return ret;
 				}
-				ret = https_ctx_connect(&net->https, sockfd, remote, fastopen,
-				                        (struct sockaddr_storage *)net->srv->ai_addr);
-			} else {
+				ret = https_ctx_connect(&net->https, sockfd, fastopen,
+				        (struct sockaddr_storage *)net->srv->ai_addr);
+			} else
 #endif //LIBNGHTTP2
+			{
 				// Establish TLS connection.
-				ret = tls_ctx_connect(&net->tls, sockfd, net->tls.params->sni, fastopen,
-				                      (struct sockaddr_storage *)net->srv->ai_addr);
-#ifdef LIBNGHTTP2
+				ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, NULL,
+				        net_get_remote(net));
+				if (ret != 0) {
+					close(sockfd);
+					return ret;
+				}
+				ret = tls_ctx_connect(&net->tls, sockfd, fastopen,
+				        (struct sockaddr_storage *)net->srv->ai_addr);
 			}
-#endif //LIBNGHTTP2
 			if (ret != KNOT_EOK) {
 				close(sockfd);
 				return ret;
 			}
 		}
 	}
+#ifdef LIBNGTCP2
+	else if (net->socktype == SOCK_DGRAM) {
+		if (net->quic.params.enable) {
+			// Establish QUIC connection.
+			ret = fd_set_recv_ecn(sockfd, net->srv->ai_family);
+			if (ret != KNOT_EOK) {
+				close(sockfd);
+				return ret;
+			}
+			ret = tls_ctx_setup_remote_endpoint(&net->tls,
+			        doq_alpn, 4, QUIC_PRIORITY, net_get_remote(net));
+			if (ret != 0) {
+				close(sockfd);
+				return ret;
+			}
+			ret = quic_ctx_connect(&net->quic, sockfd,
+			        (struct addrinfo *)net->srv);
+			if (ret != KNOT_EOK) {
+				close(sockfd);
+				return ret;
+			}
+		}
+	}
+#endif
 
 	// Store socket descriptor.
 	net->sockfd = sockfd;
diff --git a/src/utils/common/quic.c b/src/utils/common/quic.c
index 730aa8b63f..ffa52c869e 100644
--- a/src/utils/common/quic.c
+++ b/src/utils/common/quic.c
@@ -14,15 +14,11 @@
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-#include <assert.h>
 #include <stddef.h>
-#include <gnutls/crypto.h>
-
-#include "libdnssec/error.h"
-#include "libdnssec/random.h"
 
 #include "libknot/errcode.h"
 #include "utils/common/quic.h"
+#include "utils/common/msg.h"
 
 int quic_params_copy(quic_params_t *dst, const quic_params_t *src)
 {
@@ -46,7 +42,24 @@ void quic_params_clean(quic_params_t *params)
 
 #ifdef LIBNGTCP2
 
-const gnutls_datum_t quic_alpn[] = {
+#include <assert.h>
+#include <poll.h>
+#include <gnutls/crypto.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include "libdnssec/error.h"
+#include "libdnssec/random.h"
+#include "libknot/xdp/tcp_iobuf.h"
+#include "utils/common/params.h"
+
+#define quic_ceil_duration_to_ms(x) (((x) + NGTCP2_MILLISECONDS - 1) / NGTCP2_MILLISECONDS)
+#define quic_get_encryption_level(level) ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(level)
+#define quic_send(ctx, sockfd, family) quic_send_data(ctx, sockfd, family, NULL, 0)
+#define quic_timeout(ts, wait) (((ts) + NGTCP2_SECONDS * (wait)) <= quic_timestamp())
+
+const gnutls_datum_t doq_alpn[] = {
 	{
 		.data = (unsigned char *)"doq",
 		.size = 3
@@ -62,6 +75,453 @@ const gnutls_datum_t quic_alpn[] = {
 	}
 };
 
+static void set_application_error(quic_ctx_t *ctx, uint64_t error, uint8_t *reason, size_t reasonlen)
+{
+	ctx->last_err = (ngtcp2_connection_close_error){
+		.type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION,
+		.error_code = error,
+		.reason = reason,
+		.reasonlen = reasonlen
+	};
+}
+
+
+static void set_transport_error(quic_ctx_t *ctx, uint64_t error, uint8_t *reason, size_t reasonlen)
+{
+	ctx->last_err = (ngtcp2_connection_close_error){
+		.type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT,
+		.error_code = error,
+		.reason = reason,
+		.reasonlen = reasonlen
+	};
+}
+
+static int recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags,
+        int64_t stream_id, uint64_t offset, const uint8_t *data,
+        size_t datalen, void *user_data, void *stream_user_data)
+{
+	(void)conn;
+	(void)flags;
+	(void)offset;
+	(void)stream_user_data;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+
+	if (stream_id != ctx->stream.id) {
+		return 0;
+	}
+
+	struct iovec in = {
+		.iov_base = (uint8_t *)data,
+		.iov_len = datalen
+	};
+
+	int ret = knot_tcp_inbuf_update(&ctx->stream.in_buffer, in,
+	                &ctx->stream.in_parsed, &ctx->stream.in_parsed_size,
+	                &ctx->stream.in_parsed_total);
+	if (ret != KNOT_EOK) {
+		return NGTCP2_ERR_CALLBACK_FAILURE;
+	}
+
+	ctx->idle_ts = quic_timestamp();
+	ctx->stream.in_parsed_it = 0;
+	return 0;
+}
+
+static int stream_open_cb(ngtcp2_conn *conn, int64_t stream_id,
+        void *user_data)
+{
+	(void)conn;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	set_application_error(ctx, DOQ_PROTOCOL_ERROR, NULL, 0);
+	return NGTCP2_ERR_CALLBACK_FAILURE;
+}
+
+static int acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
+        uint64_t offset, uint64_t datalen, void *user_data,
+        void *stream_user_data)
+{
+	(void)conn;
+	(void)offset;
+	(void)stream_user_data;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	if (ctx->stream.id == stream_id) {
+		ctx->stream.out_ack -= datalen;
+	}
+	return KNOT_EOK;
+}
+
+static int stream_close_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+        uint64_t app_error_code, void *user_data, void *stream_user_data)
+{
+	(void)conn;
+	(void)flags;
+	(void)app_error_code;
+	(void)stream_user_data;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	if (ctx && stream_id == ctx->stream.id) {
+		ctx->stream.id = -1;
+	}
+	return KNOT_EOK;
+}
+
+static int quic_open_bidi_stream(quic_ctx_t *ctx)
+{
+	if (ctx->stream.id != -1) {
+		return KNOT_EOK;
+	}
+
+	int ret = ngtcp2_conn_open_bidi_stream(ctx->conn, &ctx->stream.id, NULL);
+	if (ret) {
+		return KNOT_ERROR;
+	}
+
+	ctx->stream.resets = 3;
+
+	return KNOT_EOK;
+}
+
+static int extend_max_bidi_streams_cb(ngtcp2_conn *conn, uint64_t max_streams,
+        void *user_data)
+{
+	(void)conn;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	if(max_streams > 0) {
+		int ret = quic_open_bidi_stream(ctx);
+		if (ret != KNOT_EOK) {
+			return NGTCP2_ERR_CALLBACK_FAILURE;
+		}
+	}
+	return 0;
+}
+
+static void rand_cb(uint8_t *dest, size_t destlen,
+        const ngtcp2_rand_ctx *rand_ctx)
+{
+	(void)rand_ctx;
+
+	dnssec_random_buffer(dest, destlen);
+}
+
+static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
+        uint8_t *token, size_t cidlen, void *user_data)
+{
+	(void)conn;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+
+	if (dnssec_random_buffer(cid->data, cidlen) != DNSSEC_EOK) {
+		return NGTCP2_ERR_CALLBACK_FAILURE;
+	}
+	cid->datalen = cidlen;
+
+	if (ngtcp2_crypto_generate_stateless_reset_token(token, ctx->secret,
+	                sizeof(ctx->secret), cid) != 0) {
+		return NGTCP2_ERR_CALLBACK_FAILURE;
+	}
+
+	return 0;
+}
+
+static int stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id,
+                uint64_t final_size, uint64_t app_error_code, void *user_data,
+                void *stream_user_data)
+{
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	if (ctx->stream.id == stream_id) {
+		if (--ctx->stream.resets <= 0) {
+			//TODO test
+			set_transport_error(ctx, NGTCP2_PROTOCOL_VIOLATION, NULL, 0);
+			quic_ctx_close(ctx);
+		}
+	}
+
+	return 0;
+}
+
+static int handshake_confirmed_cb(ngtcp2_conn *conn, void *user_data)
+{
+	(void)conn;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)user_data;
+	ctx->state = CONNECTED;
+	return 0;
+}
+
+static const ngtcp2_callbacks quic_client_callbacks = {
+	ngtcp2_crypto_client_initial_cb,
+	NULL, /* recv_client_initial */
+	ngtcp2_crypto_recv_crypto_data_cb,
+	NULL, /* handshake_completed */
+	NULL, /* recv_version_negotiation */
+	ngtcp2_crypto_encrypt_cb,
+	ngtcp2_crypto_decrypt_cb,
+	ngtcp2_crypto_hp_mask_cb,
+	recv_stream_data_cb,
+	acked_stream_data_offset_cb,
+	stream_open_cb,
+	stream_close_cb,
+	NULL, /* recv_stateless_reset */
+	ngtcp2_crypto_recv_retry_cb,
+	extend_max_bidi_streams_cb,
+	NULL, /* extend_max_local_streams_uni */
+	rand_cb,
+	get_new_connection_id_cb,
+	NULL, /* remove_connection_id */
+	ngtcp2_crypto_update_key_cb,
+	NULL, /* path_validation */
+	NULL, /* select_preferred_address */
+	stream_reset_cb,
+	NULL, /* extend_max_remote_streams_bidi */
+	NULL, /* extend_max_remote_streams_uni */
+	NULL, /* extend_max_stream_data */
+	NULL, /* dcid_status */
+	handshake_confirmed_cb,
+	NULL, /* recv_new_token */
+	ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+	ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+	NULL, /* recv_datagram */
+	NULL, /* ack_datagram */
+	NULL, /* lost_datagram */
+	ngtcp2_crypto_get_path_challenge_data_cb,
+	NULL, /* stream_stop_sending */
+};
+
+static int hook_func(gnutls_session_t session, unsigned int htype,
+                     unsigned when, unsigned int incoming,
+                     const gnutls_datum_t *msg)
+{
+	(void)session;
+	(void)htype;
+	(void)when;
+	(void)incoming;
+	(void)msg;
+
+	return GNUTLS_E_SUCCESS;
+}
+
+static int secret_func(gnutls_session_t session,
+        gnutls_record_encryption_level_t gtls_level, const void *rx_secret,
+        const void *tx_secret, size_t secretlen)
+{
+	quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session);
+	ngtcp2_crypto_level level = quic_get_encryption_level(gtls_level);
+
+	if (rx_secret) {
+		int ret = ngtcp2_crypto_derive_and_install_rx_key(ctx->conn,
+		                NULL, NULL, NULL, level, rx_secret, secretlen);
+		if (ret != 0) {
+			return -1;
+		}
+
+		if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) {
+			quic_open_bidi_stream(ctx);
+		}
+	}
+
+	if (tx_secret &&
+	    ngtcp2_crypto_derive_and_install_tx_key(ctx->conn, NULL, NULL,
+	                NULL, level, tx_secret, secretlen) != 0) {
+		return -1;
+	}
+
+	return GNUTLS_E_SUCCESS;
+}
+
+static int read_func(gnutls_session_t session,
+        gnutls_record_encryption_level_t gtls_level,
+        gnutls_handshake_description_t htype, const void *data, size_t datalen)
+{
+	if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) {
+		return GNUTLS_E_SUCCESS;
+	}
+
+	quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session);
+	if (ngtcp2_conn_submit_crypto_data(ctx->conn,
+	        quic_get_encryption_level(gtls_level), (const uint8_t *)data,
+	        datalen) != 0) {
+		return -1;
+	}
+
+	return GNUTLS_E_SUCCESS;
+}
+
+static int alert_read_func(gnutls_session_t session,
+        gnutls_record_encryption_level_t gtls_level,
+        gnutls_alert_level_t alert_level, gnutls_alert_description_t alert)
+{
+	(void)gtls_level;
+	(void)alert_level;
+
+	quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session);
+	set_transport_error(ctx, NGTCP2_CRYPTO_ERROR | alert, NULL, 0);
+
+	return GNUTLS_E_SUCCESS;
+}
+
+static int tp_recv_func(gnutls_session_t session, const uint8_t *data,
+        size_t datalen)
+{
+	quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session);
+	ngtcp2_transport_params params;
+
+	int ret = ngtcp2_decode_transport_params(&params,
+	        NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, data,
+	        datalen);
+	if (ret != 0)	{
+		return -1;
+	}
+
+	ret = ngtcp2_conn_set_remote_transport_params(ctx->conn, &params);
+	if (ret != 0)	{
+		return -1;
+	}
+
+	return GNUTLS_E_SUCCESS;
+}
+
+static int tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata)
+{
+	quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session);
+	uint8_t buf[64];
+	ngtcp2_transport_params params;
+
+	ngtcp2_conn_get_local_transport_params(ctx->conn, &params);
+	ngtcp2_ssize nwrite = ngtcp2_encode_transport_params(buf, sizeof(buf),
+	        NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, &params);
+	if (nwrite < 0) {
+		return -1;
+	}
+
+	return gnutls_buffer_append_data(extdata, buf, (size_t)nwrite);
+}
+
+static int quic_setup_tls(tls_ctx_t *tls_ctx)
+{
+	gnutls_handshake_set_hook_function(tls_ctx->session,
+	        GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, hook_func);
+	gnutls_handshake_set_secret_function(tls_ctx->session, secret_func);
+	gnutls_handshake_set_read_function(tls_ctx->session, read_func);
+	gnutls_alert_set_read_function(tls_ctx->session, alert_read_func);
+	return gnutls_session_ext_register(tls_ctx->session,
+	        "QUIC Transport Parameters",
+	        NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS,
+	        tp_recv_func, tp_send_func, NULL, NULL, NULL,
+	        GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO |
+	        GNUTLS_EXT_FLAG_EE);
+}
+
+static int quic_send_data(quic_ctx_t *ctx, int sockfd, int family,
+        ngtcp2_vec *datav, size_t datavlen)
+{
+	uint8_t enc_buf[MAX_PACKET_SIZE];
+	struct iovec msg_iov = {
+		.iov_base = enc_buf,
+		.iov_len = 0
+	};
+	struct msghdr msg = {
+		.msg_iov = &msg_iov,
+		.msg_iovlen = 1
+	};
+	uint64_t ts = quic_timestamp();
+
+	while(1) {
+		int64_t stream = -1;
+		uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE;
+		if (datavlen != 0) {
+			flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
+			stream = ctx->stream.id;
+		}
+		ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream(ctx->conn,
+		                (ngtcp2_path *)ngtcp2_conn_get_path(ctx->conn),
+		                &ctx->pi, enc_buf, sizeof(enc_buf), NULL,
+		                flags, stream, datav, datavlen, ts);
+		if (nwrite < 0) {
+			switch(nwrite) {
+			case NGTCP2_ERR_WRITE_MORE:
+				assert(0);
+				continue;
+			case NGTCP2_ERR_STREAM_SHUT_WR:
+				ctx->stream.id = -1;
+				// [[ fallthrough ]]
+			default:
+				set_transport_error(ctx,
+				        ngtcp2_err_infer_quic_transport_error_code(nwrite),
+				        NULL, 0);
+				return KNOT_NET_ESEND;
+			}
+		} else if (nwrite == 0) {
+			ngtcp2_conn_update_pkt_tx_time(ctx->conn, ts);
+			return KNOT_EOK;
+		}
+		datav = NULL;
+		datavlen = 0;
+
+		msg_iov.iov_len = (size_t)nwrite;
+
+		int ret = quic_set_enc(sockfd, family, ctx->pi.ecn);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		if (sendmsg(sockfd, &msg, 0) == -1) {
+			set_transport_error(ctx, NGTCP2_INTERNAL_ERROR, NULL,
+			                    0);
+			return KNOT_NET_ESEND;
+		}
+	}
+	return KNOT_EOK;
+}
+
+static int quic_recv(quic_ctx_t *ctx, int sockfd)
+{
+	uint8_t enc_buf[MAX_PACKET_SIZE];
+	uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))];
+	struct sockaddr_in6 from = { 0 };
+	struct iovec msg_iov = {
+		.iov_base = enc_buf,
+		.iov_len = sizeof(enc_buf)
+	};
+	struct msghdr msg = {
+		.msg_name = &from,
+		.msg_namelen = sizeof(from),
+		.msg_iov = &msg_iov,
+		.msg_iovlen = 1,
+		.msg_control = msg_ctrl,
+		.msg_controllen = sizeof(msg_ctrl),
+		.msg_flags = 0
+	};
+
+	ssize_t nwrite = recvmsg(sockfd, &msg, 0);
+	if (nwrite <= 0) {
+		return knot_map_errno();
+	}
+	ctx->pi.ecn = quic_get_ecn(&msg, from.sin6_family);
+	if (ctx->pi.ecn == 0 && errno != 0) {
+		return knot_map_errno();
+	}
+
+	int ret = ngtcp2_conn_read_pkt(ctx->conn,
+	                               ngtcp2_conn_get_path(ctx->conn),
+	                               &ctx->pi, enc_buf, nwrite,
+	                               quic_timestamp());
+	if (ret != 0) {
+		if (ret == NGTCP2_ERR_DROP_CONN) {
+			ctx->state = CLOSED;
+		} else if (ngtcp2_err_is_fatal(ret)) {
+			set_transport_error(ctx,
+			        ngtcp2_err_infer_quic_transport_error_code(ret),
+			        NULL, 0);
+		}
+		return KNOT_NET_ERECV;
+	}
+	return KNOT_EOK;
+}
+
 uint64_t quic_timestamp(void)
 {
 	struct timespec ts;
@@ -78,16 +538,67 @@ int quic_generate_secret(uint8_t *buf, size_t buflen)
 	uint8_t rand[16], hash[32];
 	int ret = dnssec_random_buffer(rand, sizeof(rand));
 	if (ret != DNSSEC_EOK) {
-		return ret;
+		return KNOT_ERROR;
 	}
 	ret = gnutls_hash_fast(GNUTLS_DIG_SHA256, rand, sizeof(rand), hash);
 	if (ret != 0) {
-		return ret;
+		return KNOT_ERROR;
 	}
 	memcpy(buf, hash, buflen);
 	return KNOT_EOK;
 }
 
+int quic_set_enc(int sockfd, int family, uint32_t ecn)
+{
+	switch (family) {
+	case AF_INET:
+		if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, &ecn,
+		               (socklen_t)sizeof(ecn)) == -1) {
+			return knot_map_errno();
+		}
+		break;
+	case AF_INET6:
+		if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_TCLASS, &ecn,
+		               (socklen_t)sizeof(ecn)) == -1) {
+			return knot_map_errno();
+		}
+		break;
+	default:
+		return KNOT_ENOTSUP;
+	}
+	return KNOT_EOK;
+}
+
+uint32_t quic_get_ecn(struct msghdr *msg, const int family)
+{
+	errno = 0;
+	switch (family) {
+	case AF_INET:
+		for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg;
+		     cmsg = CMSG_NXTHDR(msg, cmsg)) {
+			if (cmsg->cmsg_level == IPPROTO_IP &&
+			    cmsg->cmsg_type == IP_TOS && cmsg->cmsg_len) {
+				return *(uint8_t *)CMSG_DATA(cmsg);
+			}
+		}
+		errno = ENOENT;
+		break;
+	case AF_INET6:
+		for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg;
+		     cmsg = CMSG_NXTHDR(msg, cmsg)) {
+			if (cmsg->cmsg_level == IPPROTO_IPV6 &&
+			    cmsg->cmsg_type == IPV6_TCLASS && cmsg->cmsg_len) {
+				return *(uint8_t *)CMSG_DATA(cmsg);
+			}
+		}
+		errno = ENOENT;
+		break;
+	default:
+		errno = ENOTSUP;
+	}
+
+	return 0;
+}
 
 static int verify_certificate(gnutls_session_t session)
 {
@@ -105,7 +616,7 @@ int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *para
 	ctx->tls = tls_ctx;
 	ctx->state = OPENING;
 	ctx->stream.id = -1;
-	ctx->timestamp = quic_timestamp();
+	set_application_error(ctx, DOQ_NO_ERROR, NULL, 0);
 	if (quic_generate_secret(ctx->secret, sizeof(ctx->secret)) != KNOT_EOK) {
 		tls_ctx_deinit(ctx->tls);
 		return KNOT_ENOMEM;
@@ -117,5 +628,116 @@ int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *para
 	return KNOT_EOK;
 }
 
+int quic_ctx_connect(quic_ctx_t *ctx, int sockfd, struct addrinfo *dst_addr)
+{
+	if (connect(sockfd, (const struct sockaddr *)(dst_addr->ai_addr),
+	            sizeof(struct sockaddr_storage)) != 0) {
+		tls_ctx_deinit(ctx->tls);
+		return knot_map_errno();
+	}
+
+	ngtcp2_cid dcid, scid;
+	scid.datalen = NGTCP2_MAX_CIDLEN;
+	int ret = dnssec_random_buffer(scid.data, scid.datalen);
+	if (ret != DNSSEC_EOK) {
+		tls_ctx_deinit(ctx->tls);
+		return ret;
+	}
+	dcid.datalen = 18;
+	ret = dnssec_random_buffer(dcid.data, dcid.datalen);
+	if (ret != DNSSEC_EOK) {
+		tls_ctx_deinit(ctx->tls);
+		return ret;
+	}
+
+	ctx->idle_ts = quic_timestamp();
+
+	ngtcp2_settings settings;
+	ngtcp2_settings_default(&settings);
+	settings.initial_ts = ctx->idle_ts;
+	settings.handshake_timeout = ctx->tls->wait * NGTCP2_SECONDS;
+
+	ngtcp2_transport_params params;
+	ngtcp2_transport_params_default(&params);
+	params.initial_max_streams_uni = 0;
+	params.initial_max_streams_bidi = 0;
+	params.initial_max_stream_data_bidi_local = MAX_PACKET_SIZE;
+	params.initial_max_data = MAX_PACKET_SIZE;
+
+	struct sockaddr_in6 src_addr;
+	socklen_t src_addr_len = sizeof(src_addr);
+	ret = getsockname(sockfd, (struct sockaddr *)&src_addr, &src_addr_len);
+	if (ret < 0) {
+		tls_ctx_deinit(ctx->tls);
+		return knot_map_errno();
+	}
+	ngtcp2_path path = {
+		.local = {
+			.addrlen = src_addr_len,
+			.addr = (struct sockaddr *)&src_addr
+		},
+		.remote = {
+			.addrlen = sizeof(*(dst_addr->ai_addr)),
+			.addr = (struct sockaddr *)(dst_addr->ai_addr)
+		},
+		.user_data = NULL
+	};
+
+	if (ngtcp2_conn_client_new(&ctx->conn, &dcid, &scid, &path,
+	                           NGTCP2_PROTO_VER_V1, &quic_client_callbacks,
+	                           &settings, &params, NULL, ctx) != 0) {
+		tls_ctx_deinit(ctx->tls);
+		return KNOT_NET_ECONNECT;
+	}
+
+	ret = quic_setup_tls(ctx->tls);
+	if (ret != KNOT_EOK) {
+		tls_ctx_deinit(ctx->tls);
+		return KNOT_NET_ECONNECT;
+	}
+	gnutls_session_set_ptr(ctx->tls->session, ctx);
+	ngtcp2_conn_set_tls_native_handle(ctx->conn, ctx->tls->session);
+
+	// Initialize poll descriptor structure.
+	struct pollfd pfd = {
+		.fd = sockfd,
+		.events = POLLIN,
+		.revents = 0,
+	};
+	ctx->tls->sockfd = sockfd;
+
+	int timeout = ctx->tls->wait * 1000;
+	while(ctx->state != CONNECTED) {
+		if (quic_timeout(ctx->idle_ts, ctx->tls->wait)) {
+			WARN("QUIC, peer took too long to respond\n");
+			tls_ctx_deinit(ctx->tls);
+			return KNOT_NET_ETIMEOUT;
+		}
+		ret = quic_send(ctx, sockfd, dst_addr->ai_family);
+		if (ret != KNOT_EOK) {
+			tls_ctx_deinit(ctx->tls);
+			return ret;
+		}
+
+		ret = poll(&pfd, 1, timeout);
+		if (ret < 0) {
+			tls_ctx_deinit(ctx->tls);
+			return knot_map_errno();
+		} else if (ret == 0) {
+			continue;
+		}
+
+		ret = quic_recv(ctx, sockfd);
+		if (ret != KNOT_EOK) {
+			tls_ctx_deinit(ctx->tls);
+			return ret;
+		}
+
+		ngtcp2_conn_get_remote_transport_params(ctx->conn, &params);
+		timeout = quic_ceil_duration_to_ms(params.max_ack_delay);
+	}
+
+	return KNOT_EOK;
+}
 
 #endif
diff --git a/src/utils/common/quic.h b/src/utils/common/quic.h
index 8b7b537412..1b4ad1e448 100644
--- a/src/utils/common/quic.h
+++ b/src/utils/common/quic.h
@@ -39,13 +39,36 @@ void quic_params_clean(quic_params_t *params);
 #define QUIC_DEFAULT_GROUPS  "-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1"
 #define QUIC_PRIORITY        "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_CIPHERS":"QUIC_DEFAULT_GROUPS
 
-
 typedef enum {
 	OPENING,
 	CONNECTED,
-	CLOSING
+	CLOSING,
+	CLOSED
 } quic_state_t;
 
+typedef enum {
+	/*! No error.  This is used when the connection or stream needs to be
+	    closed, but there is no error to signal. */
+	DOQ_NO_ERROR = 0x0,
+	/*! The DoQ implementation encountered an internal error and is
+	    incapable of pursuing the transaction or the connection. */
+	DOQ_INTERNAL_ERROR = 0x1,
+	/*! The DoQ implementation encountered a protocol error and is forcibly
+	    aborting the connection. */
+	DOQ_PROTOCOL_ERROR = 0x2,
+	/*! A DoQ client uses this to signal that it wants to cancel an
+	    outstanding transaction. */
+	DOQ_REQUEST_CANCELLED = 0x3,
+	/*! A DoQ implementation uses this to signal when closing a connection
+	    due to excessive load. */
+	DOQ_EXCESSIVE_LOAD = 0x4,
+	/*!  A DoQ implementation uses this in the absence of a more specific
+	     error code. */
+	DOQ_UNSPECIFIED_ERROR = 0x5,
+	/*! Alternative error code used for tests. */
+	DOQ_ERROR_RESERVED = 0xd098ea5e
+} quic_doq_error_t;
+
 typedef struct {
 	// Parameters
 	quic_params_t params;
@@ -60,6 +83,7 @@ typedef struct {
 		size_t in_parsed_size;
 		size_t in_parsed_total;
 		size_t in_parsed_it;
+		int resets;
 	} stream;
 	ngtcp2_connection_close_error last_err;
 	uint8_t secret[32];
@@ -70,12 +94,18 @@ typedef struct {
 	uint64_t idle_ts;
 } quic_ctx_t;
 
-extern const gnutls_datum_t quic_alpn[];
+extern const gnutls_datum_t doq_alpn[];
 
 uint64_t quic_timestamp(void);
 
 int quic_generate_secret(uint8_t *buf, size_t buflen);
 
+uint32_t quic_get_ecn(struct msghdr *msg, const int family);
+
+int quic_set_enc(int sockfd, int family, uint32_t ecn);
+
 int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *params);
 
+int quic_ctx_connect(quic_ctx_t *ctx, int sockfd, struct addrinfo *dst_addr);
+
 #endif //LIBNGTCP2
diff --git a/src/utils/common/tls.c b/src/utils/common/tls.c
index e66fd3fc65..311c63b9b3 100644
--- a/src/utils/common/tls.c
+++ b/src/utils/common/tls.c
@@ -427,8 +427,7 @@ static int verify_certificate(gnutls_session_t session)
 }
 
 int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params,
-        unsigned int flags, int wait, const gnutls_datum_t *alpn,
-        size_t alpn_size, const char *priority)
+        unsigned int flags, int wait)
 
 {
 	if (ctx == NULL || params == NULL || !params->enable) {
@@ -507,6 +506,23 @@ int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params,
 		return KNOT_ENOMEM;
 	}
 
+	ret = gnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE,
+	                             ctx->credentials);
+	if (ret != GNUTLS_E_SUCCESS) {
+		gnutls_deinit(ctx->session);
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn,
+        size_t alpn_size, const char *priority, const char *remote)
+{
+	if (ctx == NULL || ctx->session == NULL || ctx->credentials == NULL) {
+		return KNOT_EINVAL;
+	}
+	int ret = 0;
 	if (alpn != NULL) {
 		ret = gnutls_alpn_set_protocols(ctx->session, alpn, alpn_size, 0);
 		if (ret != GNUTLS_E_SUCCESS) {
@@ -525,34 +541,25 @@ int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params,
 		return KNOT_EINVAL;
 	}
 
-	ret = gnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE,
-	                             ctx->credentials);
-	if (ret != GNUTLS_E_SUCCESS) {
-		gnutls_deinit(ctx->session);
-		return KNOT_ERROR;
+	if (remote != NULL) {
+		ret = gnutls_server_name_set(ctx->session, GNUTLS_NAME_DNS, remote,
+		                             strlen(remote));
+		if (ret != GNUTLS_E_SUCCESS) {
+			gnutls_deinit(ctx->session);
+			return KNOT_EINVAL;
+		}
 	}
-
 	return KNOT_EOK;
 }
 
-int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote, bool fastopen,
-                    struct sockaddr_storage *addr)
+int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, bool fastopen,
+        struct sockaddr_storage *addr)
 {
 	if (ctx == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	int ret = 0;
-	// TODO mayble also move to `tls_ctx_init`
-	if (remote != NULL) {
-		ret = gnutls_server_name_set(ctx->session, GNUTLS_NAME_DNS, remote,
-		                             strlen(remote));
-		if (ret != GNUTLS_E_SUCCESS) {
-			gnutls_deinit(ctx->session);
-			return KNOT_NET_ECONNECT;
-		}
-	}
-
 	gnutls_session_set_ptr(ctx->session, ctx);
 
 	if (fastopen) {
diff --git a/src/utils/common/tls.h b/src/utils/common/tls.h
index 25201e261f..7c25ab76dc 100644
--- a/src/utils/common/tls.h
+++ b/src/utils/common/tls.h
@@ -68,9 +68,10 @@ void tls_params_clean(tls_params_t *params);
 int tls_certificate_verification(tls_ctx_t *ctx);
 
 int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params,
-        unsigned int flags, int wait, const gnutls_datum_t *alpn,
-        size_t alpn_size, const char *priority);
-int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote,
+        unsigned int flags, int wait);
+int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn,
+        size_t alpn_size, const char *priority, const char *remote);
+int tls_ctx_connect(tls_ctx_t *ctx, int sockfd,
         bool fastopen, struct sockaddr_storage *addr);
 
 int tls_ctx_send(tls_ctx_t *ctx, const uint8_t *buf, const size_t buf_len);
-- 
GitLab