summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Knot.files4
-rw-r--r--doc/reference.rst28
-rw-r--r--python/libknot/libknot/probe.py10
-rw-r--r--src/knot/Makefile.inc2
-rw-r--r--src/knot/common/fdset.c6
-rw-r--r--src/knot/common/fdset.h13
-rw-r--r--src/knot/conf/conf.c4
-rw-r--r--src/knot/conf/conf.h4
-rw-r--r--src/knot/conf/schema.c2
-rw-r--r--src/knot/conf/schema.h2
-rw-r--r--src/knot/conf/tools.c81
-rw-r--r--src/knot/ctl/commands.c2
-rw-r--r--src/knot/dnssec/ds_query.c5
-rw-r--r--src/knot/events/handlers/dnskey_sync.c5
-rw-r--r--src/knot/events/handlers/ds_push.c3
-rw-r--r--src/knot/events/handlers/notify.c5
-rw-r--r--src/knot/events/handlers/refresh.c13
-rw-r--r--src/knot/include/module.h4
-rw-r--r--src/knot/modules/stats/stats.c14
-rw-r--r--src/knot/modules/stats/stats.rst2
-rw-r--r--src/knot/nameserver/log.h4
-rw-r--r--src/knot/nameserver/process_query.c31
-rw-r--r--src/knot/nameserver/query_module.c1
-rw-r--r--src/knot/nameserver/update.c6
-rw-r--r--src/knot/query/quic-requestor.c1
-rw-r--r--src/knot/query/quic-requestor.h4
-rw-r--r--src/knot/query/requestor.c52
-rw-r--r--src/knot/query/requestor.h34
-rw-r--r--src/knot/query/tls-requestor.c57
-rw-r--r--src/knot/query/tls-requestor.h53
-rw-r--r--src/knot/server/handler.h12
-rw-r--r--src/knot/server/server.c103
-rw-r--r--src/knot/server/server.h3
-rw-r--r--src/knot/server/tcp-handler.c78
-rw-r--r--src/knot/server/udp-handler.c5
-rw-r--r--src/knot/updates/acl.c29
-rw-r--r--src/knot/updates/acl.h33
-rwxr-xr-xsrc/libknot/Makefile.inc2
-rw-r--r--src/libknot/quic/quic.c119
-rw-r--r--src/libknot/quic/quic.h14
-rw-r--r--src/libknot/quic/tls.c245
-rw-r--r--src/libknot/quic/tls.h129
-rw-r--r--src/libknot/quic/tls_common.c127
-rw-r--r--src/libknot/quic/tls_common.h45
-rw-r--r--tests-extra/tests/quic/xfr/test.py2
-rw-r--r--tests-extra/tests/tls/xfr/test.py105
-rw-r--r--tests-extra/tools/dnstest/server.py21
-rw-r--r--tests-extra/tools/dnstest/test.py9
48 files changed, 1206 insertions, 327 deletions
diff --git a/Knot.files b/Knot.files
index 428699938..da655444a 100644
--- a/Knot.files
+++ b/Knot.files
@@ -326,6 +326,8 @@ src/knot/query/quic-requestor.c
src/knot/query/quic-requestor.h
src/knot/query/requestor.c
src/knot/query/requestor.h
+src/knot/query/tls-requestor.c
+src/knot/query/tls-requestor.h
src/knot/server/dthreads.c
src/knot/server/dthreads.h
src/knot/server/handler.c
@@ -490,6 +492,8 @@ src/libknot/quic/quic.c
src/libknot/quic/quic.h
src/libknot/quic/quic_conn.c
src/libknot/quic/quic_conn.h
+src/libknot/quic/tls.c
+src/libknot/quic/tls.h
src/libknot/quic/tls_common.c
src/libknot/quic/tls_common.h
src/libknot/rdata.h
diff --git a/doc/reference.rst b/doc/reference.rst
index 39eb52d67..d4fc1ca93 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -217,6 +217,7 @@ General options related to the server.
dbus-init-delay: TIME
listen: ADDR[@INT] | STR ...
listen-quic: ADDR[@INT] ...
+ listen-tls: ADDR[@INT] ...
.. CAUTION::
When you change configuration parameters dynamically or via configuration file
@@ -705,6 +706,22 @@ Change of this parameter requires restart of the Knot server to take effect.
*Default:* not set
+.. _server_listen-tls:
+
+listen-tls
+----------
+
+One or more IP addresses (and optionally ports) where the server listens
+for incoming queries over TLS protocol (DoT).
+
+Change of this parameter requires restart of the Knot server to take effect.
+
+.. NOTE::
+ Incoming :ref:`DDNS<dynamic updates>` over TLS isn't supported.
+ The server always responds with SERVFAIL.
+
+*Default:* not set
+
.. _xdp section:
``xdp`` section
@@ -1429,6 +1446,7 @@ transfer, target for a notification, etc.).
address: ADDR[@INT] | STR ...
via: ADDR[@INT] ...
quic: BOOL
+ tls: BOOL
key: key_id
cert-key: BASE64 ...
block-notify-after-transfer: BOOL
@@ -1510,6 +1528,16 @@ with this remote.
*Default:* ``off``
+.. _remote_tls:
+
+tls
+---
+
+If this option is set, the TLS (DoT) protocol will be used for outgoing communication
+with this remote.
+
+*Default:* ``off``
+
.. _remote_key:
key
diff --git a/python/libknot/libknot/probe.py b/python/libknot/libknot/probe.py
index e6f09db3d..37b2cdff3 100644
--- a/python/libknot/libknot/probe.py
+++ b/python/libknot/libknot/probe.py
@@ -12,9 +12,9 @@ class KnotProbeDataProto(enum.IntEnum):
UDP = 0
TCP = 1
- QUIC = 3
- TLS = 4
- HTTPS = 5
+ QUIC = 2
+ TLS = 3
+ HTTPS = 4
class KnotProbeDataDNSHdr(ctypes.BigEndianStructure):
@@ -132,8 +132,10 @@ class KnotProbeData(ctypes.Structure):
string += COL("UDP", GRN)
elif self.proto == KnotProbeDataProto.TCP:
string += COL("TCP", RED)
- else:
+ elif self.proto == KnotProbeDataProto.QUIC:
string += COL("QUIC", ORG)
+ else:
+ string += COL("TLS", YELW)
if self.tcp_rtt > 0:
string += ", RTT %.2f ms" % (self.tcp_rtt / 1000)
string += "\n ID %u, " % self.query_hdr.id
diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc
index a10c2ff60..0cbc9f33f 100644
--- a/src/knot/Makefile.inc
+++ b/src/knot/Makefile.inc
@@ -125,6 +125,8 @@ libknotd_la_SOURCES = \
knot/query/query.h \
knot/query/requestor.c \
knot/query/requestor.h \
+ knot/query/tls-requestor.c \
+ knot/query/tls-requestor.h \
knot/common/dbus.c \
knot/common/dbus.h \
knot/common/evsched.c \
diff --git a/src/knot/common/fdset.c b/src/knot/common/fdset.c
index 548bed2b2..2bf4113f7 100644
--- a/src/knot/common/fdset.c
+++ b/src/knot/common/fdset.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -35,6 +35,7 @@ static int fdset_resize(fdset_t *set, const unsigned size)
assert(set);
MEM_RESIZE(set->ctx, size);
+ MEM_RESIZE(set->ctx2, size);
MEM_RESIZE(set->timeout, size);
#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
MEM_RESIZE(set->ev, size);
@@ -80,6 +81,7 @@ void fdset_clear(fdset_t *set)
}
free(set->ctx);
+ free(set->ctx2);
free(set->timeout);
#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
free(set->ev);
@@ -104,6 +106,7 @@ int fdset_add(fdset_t *set, const int fd, const fdset_event_t events, void *ctx)
const int idx = set->n++;
set->ctx[idx] = ctx;
+ set->ctx2[idx] = NULL;
set->timeout[idx] = 0;
#ifdef HAVE_EPOLL
set->ev[idx].data.fd = fd;
@@ -164,6 +167,7 @@ int fdset_remove(fdset_t *set, const unsigned idx)
/* Nothing else if it is the last one. Move last -> i if some remain. */
if (idx < last) {
set->ctx[idx] = set->ctx[last];
+ set->ctx2[idx] = set->ctx2[last];
set->timeout[idx] = set->timeout[last];
#if defined(HAVE_EPOLL) || defined (HAVE_KQUEUE)
set->ev[idx] = set->ev[last];
diff --git a/src/knot/common/fdset.h b/src/knot/common/fdset.h
index e0c3dbe62..81ec7a805 100644
--- a/src/knot/common/fdset.h
+++ b/src/knot/common/fdset.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -45,6 +45,7 @@ typedef struct {
unsigned n; /*!< Active fds. */
unsigned size; /*!< Array size (allocated). */
void **ctx; /*!< Context for each fd. */
+ void **ctx2; /*!< Another context for each fd. */
time_t *timeout; /*!< Timeout for each fd (seconds precision). */
#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
#ifdef HAVE_EPOLL
@@ -271,6 +272,16 @@ inline static void *fdset_it_get_ctx(const fdset_it_t *it)
}
/*!
+ * \brief Get a read/write pointer on (void *) second context.
+ */
+inline static void **fdset_ctx2(const fdset_t *set, const unsigned idx)
+{
+ assert(set && idx < set->n);
+
+ return &set->ctx2[idx];
+}
+
+/*!
* \brief Move iterator on next received event.
*
* \param it Target iterator.
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index 65adda29a..977d9d8cf 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -1376,6 +1376,8 @@ conf_remote_t conf_remote_txn(
conf_val_t val = conf_id_get_txn(conf, txn, C_RMT, C_QUIC, id);
out.quic = conf_bool(&val);
+ val = conf_id_get_txn(conf, txn, C_RMT, C_TLS, id);
+ out.tls = conf_bool(&val);
conf_val_t rundir_val = conf_get_txn(conf, txn, C_SRV, C_RUNDIR);
char *rundir = conf_abs_path(&rundir_val, NULL);
@@ -1395,7 +1397,7 @@ conf_remote_t conf_remote_txn(
conf_val_next(&val);
}
// Index overflow causes empty socket.
- out.addr = conf_addr_alt(&val, rundir, out.quic);
+ out.addr = conf_addr_alt(&val, rundir, out.quic || out.tls);
// Get outgoing address if family matches (optional).
uint16_t via_pos = 0;
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 81fd48c48..ea79915db 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -29,6 +29,8 @@ typedef struct {
struct sockaddr_storage via;
/*! QUIC context. */
bool quic;
+ /*! TLS context. */
+ bool tls;
/*! TSIG key. */
knot_tsig_key_t key;
/*! Suppress sending NOTIFY after zone transfer from this master. */
diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c
index 13d6cee38..04210626a 100644
--- a/src/knot/conf/schema.c
+++ b/src/knot/conf/schema.c
@@ -246,6 +246,7 @@ static const yp_item_t desc_server[] = {
{ C_DBUS_INIT_DELAY, YP_TINT, YP_VINT = { 0, INT32_MAX, 1, YP_STIME } },
{ C_LISTEN, YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI, { check_listen } },
{ C_LISTEN_QUIC, YP_TADDR, YP_VADDR = { 853 }, YP_FMULTI, { check_listen } },
+ { C_LISTEN_TLS, YP_TADDR, YP_VADDR = { 853 }, YP_FMULTI, { check_listen } },
{ C_COMMENT, YP_TSTR, YP_VNONE },
{ NULL }
};
@@ -339,6 +340,7 @@ static const yp_item_t desc_remote[] = {
{ C_ADDR, YP_TADDR, YP_VADDR = { 53, 853 }, YP_FMULTI },
{ C_VIA, YP_TADDR, YP_VNONE, YP_FMULTI },
{ C_QUIC, YP_TBOOL, YP_VNONE },
+ { C_TLS, YP_TBOOL, YP_VNONE },
{ C_KEY, YP_TREF, YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
{ C_CERT_KEY, YP_TB64, YP_VNONE, YP_FMULTI, { check_cert_pin } },
{ C_BLOCK_NOTIFY_XFR, YP_TBOOL, YP_VNONE },
diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h
index a5b2b50b3..815cf00cb 100644
--- a/src/knot/conf/schema.h
+++ b/src/knot/conf/schema.h
@@ -93,6 +93,7 @@
#define C_KSK_SIZE "\x08""ksk-size"
#define C_LISTEN "\x06""listen"
#define C_LISTEN_QUIC "\x0B""listen-quic"
+#define C_LISTEN_TLS "\x0A""listen-tls"
#define C_LOG "\x03""log"
#define C_MANUAL "\x06""manual"
#define C_MASTER "\x06""master"
@@ -166,6 +167,7 @@
#define C_TIMER "\x05""timer"
#define C_TIMER_DB "\x08""timer-db"
#define C_TIMER_DB_MAX_SIZE "\x11""timer-db-max-size"
+#define C_TLS "\x03""tls"
#define C_TPL "\x08""template"
#define C_UDP "\x03""udp"
#define C_UDP_MAX_PAYLOAD "\x0F""udp-max-payload"
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
index baec14fb6..c822621cd 100644
--- a/src/knot/conf/tools.c
+++ b/src/knot/conf/tools.c
@@ -43,9 +43,7 @@
#include "knot/updates/acl.h"
#include "knot/zone/serial.h"
#include "libknot/errcode.h"
-#ifdef ENABLE_QUIC
-#include "libknot/quic/quic.h"
-#endif // ENABLE_QUIC
+#include "libknot/quic/tls_common.h"
#include "libknot/yparser/yptrafo.h"
#include "libknot/xdp.h"
#include "contrib/files.h"
@@ -338,15 +336,13 @@ int check_xdp_listen(
int check_cert_pin(
knotd_conf_check_args_t *args)
{
-#ifdef ENABLE_QUIC
- if (args->data_len != sizeof(uint16_t) + KNOT_QUIC_PIN_LEN) {
+ if (args->data_len != sizeof(uint16_t) + KNOT_TLS_PIN_LEN) {
(void)snprintf(check_str, sizeof(check_str),
"invalid certificate pin, expected base64-encoded "
- "%u bytes", KNOT_QUIC_PIN_LEN);
+ "%u bytes", KNOT_TLS_PIN_LEN);
args->err_str = check_str;
return KNOT_EINVAL;
}
-#endif // ENABLE_QUIC
return KNOT_EOK;
}
@@ -544,7 +540,6 @@ static void check_mtu(knotd_conf_check_args_t *args, conf_val_t *xdp_listen)
#endif
}
-#ifdef ENABLE_QUIC
static bool listen_hit(const struct sockaddr_storage *ss1,
const struct sockaddr_storage *ss2)
{
@@ -555,7 +550,33 @@ static bool listen_hit(const struct sockaddr_storage *ss1,
return sockaddr_cmp(ss1, ss2, false) == 0;
}
}
-#endif // ENABLE_QUIC
+
+static bool listen_overlaps(
+ knotd_conf_check_args_t *args,
+ conf_val_t *chk_listen,
+ size_t chk_listen_count)
+{
+ conf_val_t listen_val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_LISTEN);
+ size_t listen_count = conf_val_count(&listen_val);
+
+ for (size_t i = 0; listen_count > 0 && i < chk_listen_count; i++) {
+ struct sockaddr_storage chk_addr = conf_addr(chk_listen, NULL);
+
+ for (size_t j = 0; j < listen_count; j++) {
+ struct sockaddr_storage listen_addr = conf_addr(&listen_val, NULL);
+ if (listen_hit(&chk_addr, &listen_addr)) {
+ return true;
+ }
+ conf_val_next(&listen_val);
+ }
+
+ conf_val(&listen_val);
+ conf_val_next(chk_listen);
+ }
+
+ return false;
+}
int check_server(
knotd_conf_check_args_t *args)
@@ -569,30 +590,26 @@ int check_server(
return KNOT_EINVAL;
}
+ conf_val_t listls_val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_LISTEN_TLS);
+ size_t listls_count = conf_val_count(&listls_val);
+ if (listls_count > 0) {
+ if (listen_overlaps(args, &listls_val, listls_count)) {
+ args->err_str = "TLS listen address/port overlaps "
+ "with TCP listen address/port";
+ return KNOT_EINVAL;
+ }
+ }
+
conf_val_t liquic_val = conf_get_txn(args->extra->conf, args->extra->txn,
C_SRV, C_LISTEN_QUIC);
size_t liquic_count = conf_val_count(&liquic_val);
if (liquic_count > 0) {
#ifdef ENABLE_QUIC
- conf_val_t listen_val = conf_get_txn(args->extra->conf, args->extra->txn,
- C_SRV, C_LISTEN);
- size_t listen_count = conf_val_count(&listen_val);
-
- for (size_t i = 0; listen_count > 0 && i < liquic_count; i++) {
- struct sockaddr_storage liquic_addr = conf_addr(&liquic_val, NULL);
-
- for (size_t j = 0; j < listen_count; j++) {
- struct sockaddr_storage listen_addr = conf_addr(&listen_val, NULL);
- if (listen_hit(&liquic_addr, &listen_addr)) {
- args->err_str = "QUIC listen address/port overlaps "
- "with UDP listen address/port";
- return KNOT_EINVAL;
- }
- conf_val_next(&listen_val);
- }
-
- conf_val(&listen_val);
- conf_val_next(&liquic_val);
+ if (listen_overlaps(args, &liquic_val, liquic_count)) {
+ args->err_str = "QUIC listen address/port overlaps "
+ "with UDP listen address/port";
+ return KNOT_EINVAL;
}
#else
args->err_str = "QUIC processing not available";
@@ -861,12 +878,18 @@ int check_remote(
return KNOT_EINVAL;
}
+ conf_val_t tls = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT,
+ C_TLS, args->id, args->id_len);
conf_val_t quic = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT,
C_QUIC, args->id, args->id_len);
if (quic.code == KNOT_EOK) {
#ifdef ENABLE_QUIC
- (void)0;
+ if (conf_bool(&quic) && conf_bool(&tls)) {
+ args->err_str = "remote can't use both QUIC and TLS";
+ return KNOT_EINVAL;
+ }
#else
+ (void)tls;
args->err_str = "QUIC not available";
return KNOT_EINVAL;
#endif
diff --git a/src/knot/ctl/commands.c b/src/knot/ctl/commands.c
index feedfa2be..de644f617 100644
--- a/src/knot/ctl/commands.c
+++ b/src/knot/ctl/commands.c
@@ -697,7 +697,7 @@ static int zones_apply_backup(ctl_args_t *args, bool restore_mode)
zone_backup_ctx_t *ctx = latest_backup_ctx(args);
/* QUIC - server key and cert backup. */
- ret = backup_quic(ctx, args->server->quic_active);
+ ret = backup_quic(ctx, args->server->quic_active || args->server->tls_active);
if (ret != KNOT_EOK) {
log_ctl_error("control, QUIC %s error (%s)",
restore_mode ? "restore" : "backup",
diff --git a/src/knot/dnssec/ds_query.c b/src/knot/dnssec/ds_query.c
index 375bb5021..0c6b9eb33 100644
--- a/src/knot/dnssec/ds_query.c
+++ b/src/knot/dnssec/ds_query.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -28,8 +28,7 @@
#define DS_CHECK_LOG(priority, zone, remote, flags, fmt, ...) \
ns_log(priority, zone, LOG_OPERATION_DS_CHECK, LOG_DIRECTION_OUT, remote, \
- ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \
- ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
+ flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
static bool match_key_ds(knot_kasp_key_t *key, knot_rdata_t *ds)
{
diff --git a/src/knot/events/handlers/dnskey_sync.c b/src/knot/events/handlers/dnskey_sync.c
index 8e6d8232c..539747297 100644
--- a/src/knot/events/handlers/dnskey_sync.c
+++ b/src/knot/events/handlers/dnskey_sync.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -26,8 +26,7 @@
#define DNSKEY_SYNC_LOG(priority, zone, remote, flags, fmt, ...) \
ns_log(priority, zone, LOG_OPERATION_DNSKEY_SYNC, LOG_DIRECTION_OUT, remote, \
- ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \
- ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
+ flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
static const unsigned remote_rrs[] = { KNOT_RRTYPE_DNSKEY, KNOT_RRTYPE_CDNSKEY, KNOT_RRTYPE_CDS };
#define REMOTE_NTYPES (sizeof(remote_rrs) / sizeof(remote_rrs[0]))
diff --git a/src/knot/events/handlers/ds_push.c b/src/knot/events/handlers/ds_push.c
index fd870d0f4..84734e6ab 100644
--- a/src/knot/events/handlers/ds_push.c
+++ b/src/knot/events/handlers/ds_push.c
@@ -38,8 +38,7 @@ struct ds_push_data {
#define DS_PUSH_LOG(priority, zone, remote, flags, fmt, ...) \
ns_log(priority, zone, LOG_OPERATION_DS_PUSH, LOG_DIRECTION_OUT, remote, \
- ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \
- ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
+ flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
static const knot_rdata_t remove_cds = { 5, { 0, 0, 0, 0, 0 } };
diff --git a/src/knot/events/handlers/notify.c b/src/knot/events/handlers/notify.c
index ccddf8855..605fc9335 100644
--- a/src/knot/events/handlers/notify.c
+++ b/src/knot/events/handlers/notify.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -83,8 +83,7 @@ static const knot_layer_api_t NOTIFY_API = {
#define NOTIFY_OUT_LOG(priority, zone, remote, flags, fmt, ...) \
ns_log(priority, zone, LOG_OPERATION_NOTIFY, LOG_DIRECTION_OUT, remote, \
- ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \
- ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
+ flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), fmt, ## __VA_ARGS__)
static int send_notify(conf_t *conf, zone_t *zone, const knot_rrset_t *soa,
const conf_remote_t *slave, int timeout, bool retry)
diff --git a/src/knot/events/handlers/refresh.c b/src/knot/events/handlers/refresh.c
index 32ee68d83..136ba8d72 100644
--- a/src/knot/events/handlers/refresh.c
+++ b/src/knot/events/handlers/refresh.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -67,20 +67,17 @@
* \endverbatim
*/
-#define PROTO(data) \
- ((data)->layer->flags & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP
-
#define REFRESH_LOG(priority, data, msg...) \
ns_log(priority, (data)->zone->name, LOG_OPERATION_REFRESH, LOG_DIRECTION_NONE, \
(data)->remote, 0, false, msg)
#define AXFRIN_LOG(priority, data, msg...) \
ns_log(priority, (data)->zone->name, LOG_OPERATION_AXFR, LOG_DIRECTION_IN, \
- (data)->remote, PROTO(data), (data)->layer->flags & KNOT_REQUESTOR_REUSED, msg)
+ (data)->remote, flags2proto((data)->layer->flags), (data)->layer->flags & KNOT_REQUESTOR_REUSED, msg)
#define IXFRIN_LOG(priority, data, msg...) \
ns_log(priority, (data)->zone->name, LOG_OPERATION_IXFR, LOG_DIRECTION_IN, \
- (data)->remote, PROTO(data), (data)->layer->flags & KNOT_REQUESTOR_REUSED, msg)
+ (data)->remote, flags2proto((data)->layer->flags), (data)->layer->flags & KNOT_REQUESTOR_REUSED, msg)
enum state {
REFRESH_STATE_INVALID = 0,
@@ -1197,9 +1194,7 @@ static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt)
data->xfr_type == XFR_TYPE_UPTODATE ?
LOG_OPERATION_IXFR : LOG_OPERATION_AXFR,
LOG_DIRECTION_IN, data->remote,
- (layer->flags & KNOT_REQUESTOR_QUIC ?
- KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP),
- &data->stats);
+ flags2proto(layer->flags), &data->stats);
/*
* TODO: Move finialization into finish
diff --git a/src/knot/include/module.h b/src/knot/include/module.h
index 375f44a3f..0c6a16b41 100644
--- a/src/knot/include/module.h
+++ b/src/knot/include/module.h
@@ -36,7 +36,7 @@
/*** Query module API. ***/
/*! Current module ABI version. */
-#define KNOTD_MOD_ABI_VERSION 500
+#define KNOTD_MOD_ABI_VERSION 600
/*! Module configuration name prefix. */
#define KNOTD_MOD_NAME_PREFIX "mod-"
@@ -393,6 +393,7 @@ typedef enum {
KNOTD_QUERY_PROTO_UDP = KNOT_PROBE_PROTO_UDP, /*!< Pure UDP. */
KNOTD_QUERY_PROTO_TCP = KNOT_PROBE_PROTO_TCP, /*!< Pure TCP. */
KNOTD_QUERY_PROTO_QUIC = KNOT_PROBE_PROTO_QUIC, /*!< QUIC/UDP. */
+ KNOTD_QUERY_PROTO_TLS = KNOT_PROBE_PROTO_TLS, /*!< TLS/TCP. */
} knotd_query_proto_t;
/*! Query processing specific flags. */
@@ -410,6 +411,7 @@ typedef struct {
unsigned thread_id; /*!< Current thread id. */
void *server; /*!< Server object private item. */
const struct knot_xdp_msg *xdp_msg; /*!< Possible XDP message context. */
+ struct gnutls_session_int *tls_session;/*!< TLS session (QUIC or DoT). */
struct knot_quic_conn *quic_conn; /*!< QUIC connection context. */
int64_t quic_stream; /*!< QUIC stream ID inside quic_conn. */
uint32_t measured_rtt; /*!< Measured RTT in usecs: QUIC or TCP-XDP. */
diff --git a/src/knot/modules/stats/stats.c b/src/knot/modules/stats/stats.c
index 26262ac1e..c5b797b11 100644
--- a/src/knot/modules/stats/stats.c
+++ b/src/knot/modules/stats/stats.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -118,9 +118,11 @@ enum {
PROTOCOL_UDP4 = 0,
PROTOCOL_TCP4,
PROTOCOL_QUIC4,
+ PROTOCOL_TLS4,
PROTOCOL_UDP6,
PROTOCOL_TCP6,
PROTOCOL_QUIC6,
+ PROTOCOL_TLS6,
PROTOCOL_UDP4_XDP,
PROTOCOL_TCP4_XDP,
PROTOCOL_QUIC4_XDP,
@@ -136,9 +138,11 @@ static char *protocol_to_str(uint32_t idx, uint32_t count)
case PROTOCOL_UDP4: return strdup("udp4");
case PROTOCOL_TCP4: return strdup("tcp4");
case PROTOCOL_QUIC4: return strdup("quic4");
+ case PROTOCOL_TLS4: return strdup("tls4");
case PROTOCOL_UDP6: return strdup("udp6");
case PROTOCOL_TCP6: return strdup("tcp6");
case PROTOCOL_QUIC6: return strdup("quic6");
+ case PROTOCOL_TLS6: return strdup("tls6");
case PROTOCOL_UDP4_XDP: return strdup("udp4-xdp");
case PROTOCOL_TCP4_XDP: return strdup("tcp4-xdp");
case PROTOCOL_QUIC4_XDP: return strdup("quic4-xdp");
@@ -521,6 +525,10 @@ static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
PROTOCOL_QUIC4, 1);
}
+ } else if (qdata->params->proto == KNOTD_QUERY_PROTO_TLS) {
+ assert(!xdp);
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TLS4, 1);
} else {
if (xdp) {
knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
@@ -547,6 +555,10 @@ static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
PROTOCOL_QUIC6, 1);
}
+ } else if (qdata->params->proto == KNOTD_QUERY_PROTO_TLS) {
+ assert(!xdp);
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TLS6, 1);
} else {
if (xdp) {
knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
diff --git a/src/knot/modules/stats/stats.rst b/src/knot/modules/stats/stats.rst
index 8acf1aa50..71cf87a9f 100644
--- a/src/knot/modules/stats/stats.rst
+++ b/src/knot/modules/stats/stats.rst
@@ -73,9 +73,11 @@ If enabled, all incoming requests are counted by the network protocol:
* udp4 - UDP over IPv4
* tcp4 - TCP over IPv4
* quic4 - QUIC over IPv4
+* tls4 - TLS over IPv4
* udp6 - UDP over IPv6
* tcp6 - TCP over IPv6
* quic6 - QUIC over IPv6
+* tls6 - TLS over IPv6
* udp4-xdp - UDP over IPv4 through XDP
* tcp4-xdp - TCP over IPv4 through XDP
* quic4-xdp - QUIC over IPv4 through XDP
diff --git a/src/knot/nameserver/log.h b/src/knot/nameserver/log.h
index 752de5549..014ed4658 100644
--- a/src/knot/nameserver/log.h
+++ b/src/knot/nameserver/log.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -81,6 +81,8 @@ static inline const char *log_conn_info(knotd_query_proto_t proto, bool pool)
return pool ? " TCP/pool" : " TCP";
case KNOTD_QUERY_PROTO_QUIC:
return pool ? " QUIC/0-RTT" : " QUIC";
+ case KNOTD_QUERY_PROTO_TLS:
+ return " TLS";
default:
return "";
}
diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c
index e3e07e144..e8442974a 100644
--- a/src/knot/nameserver/process_query.c
+++ b/src/knot/nameserver/process_query.c
@@ -30,9 +30,7 @@
#include "knot/nameserver/notify.h"
#include "knot/server/server.h"
#include "libknot/libknot.h"
-#ifdef ENABLE_QUIC
-#include "libknot/quic/quic.h"
-#endif // ENABLE_QUIC
+#include "libknot/quic/tls_common.h"
#include "contrib/base64.h"
#include "contrib/macros.h"
#include "contrib/mempattern.h"
@@ -390,8 +388,8 @@ static int answer_edns_put(knot_pkt_t *resp, knotd_qdata_t *qdata)
return ret;
}
- /* Align the response if QUIC with EDNS. */
- if (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) {
+ /* Align the response if QUIC or TLS with EDNS. */
+ if (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC || qdata->params->proto == KNOTD_QUERY_PROTO_TLS) {
int pad_len = knot_pkt_default_padding_size(resp, &qdata->opt_rr);
if (pad_len > -1) {
ret = knot_edns_reserve_option(&qdata->opt_rr, KNOT_EDNS_OPTION_PADDING,
@@ -713,27 +711,32 @@ bool process_query_acl_check(conf_t *conf, acl_action_t action,
const yp_name_t *item = (action == ACL_ACTION_NOTIFY) ? C_MASTER : C_NOTIFY;
conf_val_t rmts = conf_zone_get(conf, item, zone_name);
allowed = rmt_allowed(conf, &rmts, query_source, &tsig,
- qdata->params->quic_conn);
+ qdata->params->tls_session);
automatic = allowed;
}
if (!allowed) {
conf_val_t acl = conf_zone_get(conf, C_ACL, zone_name);
allowed = acl_allowed(conf, &acl, action, query_source, &tsig,
- zone_name, query, qdata->params->quic_conn);
+ zone_name, query, qdata->params->tls_session);
}
if (log_enabled_debug()) {
int pin_size = 0;
-#ifdef ENABLE_QUIC
- uint8_t bin_pin[KNOT_QUIC_PIN_LEN], pin[2 * KNOT_QUIC_PIN_LEN];
+ uint8_t bin_pin[KNOT_TLS_PIN_LEN], pin[2 * KNOT_TLS_PIN_LEN];
size_t bin_pin_size = sizeof(bin_pin);
- knot_quic_conn_pin(qdata->params->quic_conn, bin_pin, &bin_pin_size, false);
+ knot_quic_conn_pin2(qdata->params->tls_session, bin_pin, &bin_pin_size, false);
if (bin_pin_size > 0) {
pin_size = knot_base64_encode(bin_pin, bin_pin_size, pin, sizeof(pin));
}
-#else
- uint8_t pin[1];
-#endif // ENABLE_QUIC
+
+ const char *proto_str;
+ switch (qdata->params->proto) {
+ case KNOTD_QUERY_PROTO_UDP: proto_str = ", UDP"; break;
+ case KNOTD_QUERY_PROTO_TCP: proto_str = ", TCP"; break;
+ case KNOTD_QUERY_PROTO_QUIC: proto_str = ", QUIC"; break;
+ case KNOTD_QUERY_PROTO_TLS: proto_str = ", TLS"; break;
+ default: proto_str = "";
+ }
log_zone_debug(zone_name,
"ACL, %s, action %s, remote %s%s%s%s%s%.*s%s",
@@ -742,7 +745,7 @@ bool process_query_acl_check(conf_t *conf, acl_action_t action,
addr_str,
(key_name[0] != '\0') ? ", key " : "",
(key_name[0] != '\0') ? key_name : "",
- (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) ? ", QUIC" : "",
+ proto_str,
(pin_size > 0) ? " cert-key " : "",
(pin_size > 0) ? pin_size : 0,
(pin_size > 0) ? (const char *)pin : "",
diff --git a/src/knot/nameserver/query_module.c b/src/knot/nameserver/query_module.c
index f60d086a0..4d50980e7 100644
--- a/src/knot/nameserver/query_module.c
+++ b/src/knot/nameserver/query_module.c
@@ -616,6 +616,7 @@ uint32_t knotd_qdata_rtt(knotd_qdata_t *qdata)
switch (qdata->params->proto) {
case KNOTD_QUERY_PROTO_TCP:
+ case KNOTD_QUERY_PROTO_TLS:
if (qdata->params->xdp_msg != NULL) {
#ifdef ENABLE_XDP
return qdata->params->measured_rtt;
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 9529c1d6d..395db3a07 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -98,8 +98,8 @@ static int update_enqueue(zone_t *zone, knotd_qdata_t *qdata)
int update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
{
- /* DDNS over XDP not supported. */
- if (qdata->params->xdp_msg != NULL) {
+ /* DDNS over XDP and TLS not supported. */
+ if (qdata->params->xdp_msg != NULL || qdata->params->proto == KNOTD_QUERY_PROTO_TLS) {
qdata->rcode = KNOT_RCODE_SERVFAIL;
return KNOT_STATE_FAIL;
}
diff --git a/src/knot/query/quic-requestor.c b/src/knot/query/quic-requestor.c
index 42497939b..680d983dc 100644
--- a/src/knot/query/quic-requestor.c
+++ b/src/knot/query/quic-requestor.c
@@ -28,7 +28,6 @@
#include "knot/conf/conf.h" // please use this only for tiny stuff like quic-log
#include "knot/server/handler.h"
#include "libknot/error.h"
-#include "libknot/quic/quic.h"
#define QUIC_BUF_SIZE 4096
diff --git a/src/knot/query/quic-requestor.h b/src/knot/query/quic-requestor.h
index b5f479ee3..c606d6c9d 100644
--- a/src/knot/query/quic-requestor.h
+++ b/src/knot/query/quic-requestor.h
@@ -17,9 +17,7 @@
#pragma once
#include "contrib/sockaddr.h"
-
-struct knot_quic_creds;
-struct knot_quic_reply;
+#include "libknot/quic/quic.h"
int knot_qreq_connect(struct knot_quic_reply **out,
int fd,
diff --git a/src/knot/query/requestor.c b/src/knot/query/requestor.c
index 041549298..7e7ae0cbb 100644
--- a/src/knot/query/requestor.c
+++ b/src/knot/query/requestor.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -18,12 +18,13 @@
#include <sys/socket.h>
#include "libknot/attribute.h"
+#include "libknot/errcode.h"
+#include "libknot/quic/tls.h"
#include "knot/common/unreachable.h"
#include "knot/query/requestor.h"
#ifdef ENABLE_QUIC
#include "knot/query/quic-requestor.h"
#endif // ENABLE_QUIC
-#include "libknot/errcode.h"
#include "contrib/conn_pool.h"
#include "contrib/mempattern.h"
#include "contrib/net.h"
@@ -39,6 +40,11 @@ static bool use_quic(knot_request_t *request)
return (request->flags & KNOT_REQUEST_QUIC) != 0;
}
+static bool use_tls(knot_request_t *request)
+{
+ return (request->flags & KNOT_REQUEST_TLS) != 0;
+}
+
static bool is_answer_to_query(const knot_pkt_t *query, const knot_pkt_t *answer)
{
return knot_wire_get_id(query->wire) == knot_wire_get_id(answer->wire);
@@ -104,6 +110,19 @@ static int request_ensure_connected(knot_request_t *request, bool *reused_fd, in
#endif // ENABLE_QUIC
}
+ if (use_tls(request)) {
+ assert(!use_quic(request));
+
+ int ret = knot_tls_req_ctx_init(&request->tls_req_ctx, request->fd,
+ request->creds, request->pin,
+ request->pin_len, timeout_ms);
+ if (ret != KNOT_EOK) {
+ close(request->fd);
+ request->fd = -1;
+ return ret;
+ }
+ }
+
return KNOT_EOK;
}
@@ -124,7 +143,9 @@ static int request_send(knot_request_t *request, int timeout_ms, bool *reused_fd
&request->remote : NULL;
/* Send query. */
- if (use_quic(request)) {
+ if (use_tls(request)) {
+ ret = knot_tls_send_dns(request->tls_req_ctx.conn, wire, wire_len);
+ } else if (use_quic(request)) {
#ifdef ENABLE_QUIC
struct iovec tosend = { wire, wire_len };
return knot_qreq_send(request->quic_ctx, &tosend);
@@ -162,7 +183,9 @@ static int request_recv(knot_request_t *request, int timeout_ms)
}
/* Receive it */
- if (use_quic(request)) {
+ if (use_tls(request)) {
+ ret = knot_tls_recv_dns(request->tls_req_ctx.conn, resp->wire, resp->max_size);
+ } else if (use_quic(request)) {
#ifdef ENABLE_QUIC
struct iovec recvd = { resp->wire, resp->max_size };
ret = knot_qreq_recv(request->quic_ctx, &recvd, timeout_ms);
@@ -232,7 +255,7 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm,
request->edns = edns;
request->creds = creds;
- if (flags & KNOT_REQUEST_QUIC && pin_len > 0) {
+ if ((flags & (KNOT_REQUEST_QUIC | KNOT_REQUEST_TLS)) && pin_len > 0) {
request->pin_len = pin_len;
memcpy(request->pin, pin, pin_len);
}
@@ -248,7 +271,10 @@ knot_request_t *knot_request_make(knot_mm_t *mm,
knot_request_flag_t flags)
{
if (remote->quic) {
+ assert(!remote->tls);
flags |= KNOT_REQUEST_QUIC;
+ } else if (remote->tls) {
+ flags |= KNOT_REQUEST_TLS;
}
return knot_request_make_generic(mm, &remote->addr, &remote->via,
@@ -263,14 +289,19 @@ void knot_request_free(knot_request_t *request, knot_mm_t *mm)
}
if (request->quic_ctx != NULL) {
+ if (use_quic(request)) {
#ifdef ENABLE_QUIC
- knot_qreq_close(request->quic_ctx, true);
+ knot_qreq_close(request->quic_ctx, true);
#else
- assert(0);
+ assert(0);
#endif // ENABLE_QUIC
+ } else {
+ assert(use_tls(request));
+ knot_tls_req_ctx_deinit(&request->tls_req_ctx);
+ }
}
- if (request->fd >= 0 && use_tcp(request) &&
+ if (request->fd >= 0 && use_tcp(request) && !use_tls(request) &&
(request->flags & KNOT_REQUEST_KEEP)) {
request->fd = (int)conn_pool_put(global_conn_pool,
&request->source,
@@ -352,7 +383,7 @@ static int request_produce(knot_requestor_t *req, knot_request_t *last,
if (last->edns != NULL && !last->edns->no_edns) {
ret = query_put_edns(last->query, last->edns,
- (last->flags & KNOT_REQUEST_QUIC));
+ (last->flags & (KNOT_REQUEST_QUIC | KNOT_REQUEST_TLS)));
if (ret != KNOT_EOK) {
return ret;
}
@@ -377,6 +408,9 @@ static int request_produce(knot_requestor_t *req, knot_request_t *last,
if (last->flags & KNOT_REQUEST_QUIC) {
req->layer.flags |= KNOT_REQUESTOR_QUIC;
}
+ if (last->flags & KNOT_REQUEST_TLS) {
+ req->layer.flags |= KNOT_REQUESTOR_TLS;
+ }
}
return ret;
diff --git a/src/knot/query/requestor.h b/src/knot/query/requestor.h
index 9e499658d..97d9d04d4 100644
--- a/src/knot/query/requestor.h
+++ b/src/knot/query/requestor.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -23,26 +23,26 @@
#include "knot/nameserver/tsig_ctx.h"
#include "knot/query/layer.h"
#include "knot/query/query.h"
+#include "knot/query/tls-requestor.h"
#include "libknot/mm_ctx.h"
#include "libknot/rrtype/tsig.h"
-struct knot_quic_creds;
-struct knot_quic_reply;
-
typedef enum {
KNOT_REQUEST_NONE = 0, /*!< Empty flag. */
KNOT_REQUEST_UDP = 1 << 0, /*!< Use UDP for requests. */
KNOT_REQUEST_TFO = 1 << 1, /*!< Enable TCP Fast Open for requests. */
KNOT_REQUEST_KEEP = 1 << 2, /*!< Keep upstream TCP connection in pool for later reuse. */
KNOT_REQUEST_QUIC = 1 << 3, /*!< Use QUIC/UDP for requests. */
- KNOT_REQUEST_FWD = 1 << 4, /*!< Forwarded message, don't modify (TSIG, PADDING). */
+ KNOT_REQUEST_TLS = 1 << 4, /*!< Use DoT for requests. */
+ KNOT_REQUEST_FWD = 1 << 5, /*!< Forwarded message, don't modify (TSIG, PADDING). */
} knot_request_flag_t;
typedef enum {
KNOT_REQUESTOR_CLOSE = 1 << 0, /*!< Close the connection indication. */
KNOT_REQUESTOR_REUSED = 1 << 1, /*!< Reused FD indication (RO). */
KNOT_REQUESTOR_QUIC = 1 << 2, /*!< QUIC used indication (RO). */
- KNOT_REQUESTOR_IOFAIL = 1 << 3, /*!< Encountered error sending/recving data. */
+ KNOT_REQUESTOR_TLS = 1 << 3, /*!< DoT used indication (RO). */
+ KNOT_REQUESTOR_IOFAIL = 1 << 4, /*!< Encountered error sending/recving data. */
} knot_requestor_flag_t;
/*! \brief Requestor structure.
@@ -57,9 +57,14 @@ typedef struct {
/*! \brief Request data (socket, payload, response, TSIG and endpoints). */
typedef struct {
int fd;
- struct knot_quic_reply *quic_ctx;
- struct knot_quic_conn *quic_conn;
- int64_t quic_stream;
+ union {
+ struct {
+ struct knot_quic_reply *quic_ctx;
+ struct knot_quic_conn *quic_conn;
+ int64_t quic_stream;
+ };
+ knot_tls_req_ctx_t tls_req_ctx;
+ };
knot_request_flag_t flags;
struct sockaddr_storage remote, source;
knot_pkt_t *query;
@@ -74,6 +79,17 @@ typedef struct {
uint8_t pin[];
} knot_request_t;
+static inline knotd_query_proto_t flags2proto(unsigned layer_flags)
+{
+ knotd_query_proto_t proto = KNOTD_QUERY_PROTO_TCP;
+ if ((layer_flags & KNOT_REQUESTOR_QUIC)) {
+ proto = KNOTD_QUERY_PROTO_QUIC;
+ } else if ((layer_flags & KNOT_REQUESTOR_TLS)) {
+ proto = KNOTD_QUERY_PROTO_TLS;
+ }
+ return proto;
+}
+
/*!
* \brief Make request out of endpoints and query.
*
diff --git a/src/knot/query/tls-requestor.c b/src/knot/query/tls-requestor.c
new file mode 100644
index 000000000..01385dbe9
--- /dev/null
+++ b/src/knot/query/tls-requestor.c
@@ -0,0 +1,57 @@
+/* Copyright (C) 2024 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 <string.h>
+
+#include "knot/query/tls-requestor.h"
+#include "libknot/error.h"
+#include "libknot/quic/tls.h"
+
+int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx, int fd,
+ const struct knot_quic_creds *local_creds,
+ const uint8_t *peer_pin, uint8_t peer_pin_len,
+ int io_timeout_ms)
+{
+ struct knot_quic_creds *creds = knot_quic_init_creds_peer(local_creds,
+ peer_pin, peer_pin_len);
+ if (creds == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ ctx->ctx = knot_tls_ctx_new(creds, io_timeout_ms, false);
+ if (ctx->ctx == NULL) {
+ knot_quic_free_creds(creds);
+ return KNOT_ENOMEM;
+ }
+
+ ctx->conn = knot_tls_conn_new(ctx->ctx, fd);
+ if (ctx->conn == NULL) {
+ knot_tls_req_ctx_deinit(ctx);
+ return KNOT_ERROR;
+ }
+
+ return KNOT_EOK;
+}
+
+void knot_tls_req_ctx_deinit(knot_tls_req_ctx_t *ctx)
+{
+ if (ctx != NULL && ctx->ctx != NULL) {
+ knot_quic_free_creds(ctx->ctx->creds);
+ knot_tls_conn_del(ctx->conn);
+ knot_tls_ctx_free(ctx->ctx);
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
diff --git a/src/knot/query/tls-requestor.h b/src/knot/query/tls-requestor.h
new file mode 100644
index 000000000..a3103ff97
--- /dev/null
+++ b/src/knot/query/tls-requestor.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2024 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "libknot/quic/tls_common.h"
+
+/*!
+ * \brief TLS requestor context envelope, containing TLS general context and TLS connection.
+ */
+typedef struct knot_tls_req_ctx {
+ struct knot_tls_ctx *ctx;
+ struct knot_tls_conn *conn;
+} knot_tls_req_ctx_t;
+
+struct knot_quic_creds;
+
+/*!
+ * \brief Initialize TLS requestor context.
+ *
+ * \param ctx Context structure to be initialized.
+ * \param fd Opened TCP connection file descriptor.
+ * \param local_creds Local TLS credentials.
+ * \param peer_pin TLS peer pin.
+ * \param peer_pin_len TLS peer pin length.
+ * \param io_timeout_ms Configured io-timeout for TLS connection.
+ *
+ * \return KNOT_E*
+ */
+int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx, int fd,
+ const struct knot_quic_creds *local_creds,
+ const uint8_t *peer_pin, uint8_t peer_pin_len,
+ int io_timeout_ms);
+
+/*!
+ * \brief De-initialize TLS requestor context.
+ */
+void knot_tls_req_ctx_deinit(knot_tls_req_ctx_t *ctx);
diff --git a/src/knot/server/handler.h b/src/knot/server/handler.h
index a1cea7cd8..7488101ba 100644
--- a/src/knot/server/handler.h
+++ b/src/knot/server/handler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -55,7 +55,12 @@ inline static knotd_qdata_params_t params_init(knotd_query_proto_t proto,
inline static void params_update(knotd_qdata_params_t *params, uint32_t rtt,
struct knot_quic_conn *conn, int64_t stream_id)
{
+#ifdef ENABLE_QUIC
params->quic_conn = conn;
+ params->tls_session = conn == NULL ? NULL : conn->tls_session;
+#else
+ assert(conn == NULL);
+#endif
params->quic_stream = stream_id;
params->measured_rtt = rtt;
}
@@ -83,7 +88,12 @@ inline static void params_xdp_update(knotd_qdata_params_t *params,
params->remote = (struct sockaddr_storage *)&msg->ip_from;
params->local = (struct sockaddr_storage *)&msg->ip_to;
params->xdp_msg = msg;
+#ifdef ENABLE_QUIC
params->quic_conn = conn;
+ params->tls_session = conn == NULL ? NULL : conn->tls_session;
+#else
+ assert(conn == NULL);
+#endif
params->measured_rtt = rtt;
}
#endif // ENABLE_XDP
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index a82e1d39b..0ad47b137 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -27,8 +27,9 @@
#include "libknot/libknot.h"
#include "libknot/yparser/ypschema.h"
#include "libknot/xdp.h"
+#include "libknot/quic/tls_common.h"
#ifdef ENABLE_QUIC
-#include "libknot/quic/quic.h"
+#include "libknot/quic/quic.h" // knot_quic_session_*
#endif // ENABLE_QUIC
#include "knot/common/log.h"
#include "knot/common/stats.h"
@@ -236,14 +237,14 @@ static int disable_pmtudisc(int sock, int family)
return KNOT_EOK;
}
-static size_t quic_rmt_count(conf_t *conf)
+static size_t quic_rmt_count(conf_t *conf, const yp_name_t *proto)
{
size_t count = 0;
for (conf_iter_t iter = conf_iter(conf, C_RMT);
iter.code == KNOT_EOK; conf_iter_next(conf, &iter)) {
conf_val_t id = conf_iter_id(conf, &iter);
- conf_val_t rmt_quic = conf_id_get(conf, C_RMT, C_QUIC, &id);
+ conf_val_t rmt_quic = conf_id_get(conf, C_RMT, proto, &id);
if (conf_bool(&rmt_quic)) {
count++;
}
@@ -359,7 +360,7 @@ static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, bool route_
* \retval Pointer to a new initialized interface.
* \retval NULL if error.
*/
-static iface_t *server_init_iface(struct sockaddr_storage *addr, bool quic,
+static iface_t *server_init_iface(struct sockaddr_storage *addr, bool tls,
int udp_thread_count, int tcp_thread_count,
bool tcp_reuseport, bool socket_affinity)
{
@@ -376,14 +377,14 @@ static iface_t *server_init_iface(struct sockaddr_storage *addr, bool quic,
int udp_socket_count = 1;
int udp_bind_flags = 0;
- int tcp_socket_count = !quic ? 1 : 0;
+ int tcp_socket_count = tcp_thread_count > 0 ? 1 : 0;
int tcp_bind_flags = 0;
#ifdef ENABLE_REUSEPORT
udp_socket_count = udp_thread_count;
udp_bind_flags |= NET_BIND_MULTIPLE;
- if (!quic && tcp_reuseport) {
+ if (tcp_reuseport) {
tcp_socket_count = tcp_thread_count;
tcp_bind_flags |= NET_BIND_MULTIPLE;
}
@@ -455,7 +456,7 @@ static iface_t *server_init_iface(struct sockaddr_storage *addr, bool quic,
warn_flag_misc = false;
}
- if (quic) {
+ if (tls) {
ret = net_cmsg_ecn_enable(sock, addr->ss_family);
if (ret != KNOT_EOK && ret != KNOT_ENOTSUP && warn_ecn) {
log_warning("failed to enable ECN for QUIC");
@@ -557,7 +558,6 @@ static void log_sock_conf(conf_t *conf)
}
}
-#ifdef ENABLE_QUIC
static int check_file(char *path, char *role)
{
if (path == NULL) {
@@ -579,11 +579,9 @@ static int check_file(char *path, char *role)
log_error("QUIC, %s file '%s' (%s)", role, path, err_str);
return KNOT_EINVAL;
}
-#endif // ENABLE_QUIC
static int init_creds(server_t *server, conf_t *conf)
{
-#ifdef ENABLE_QUIC
char *cert_file = conf_tls(conf, C_CERT_FILE);
char *key_file = conf_tls(conf, C_KEY_FILE);
@@ -602,19 +600,19 @@ static int init_creds(server_t *server, conf_t *conf)
char *kasp_dir = conf_db(conf, C_KASP_DB);
ret = make_dir(kasp_dir, S_IRWXU | S_IRWXG, true);
if (ret != KNOT_EOK) {
- log_error("QUIC, failed to create directory '%s'", kasp_dir);
+ log_error("QUIC/TLS, failed to create directory '%s'", kasp_dir);
free(kasp_dir);
return ret;
}
key_file = abs_path(DFLT_QUIC_KEY_FILE, kasp_dir);
free(kasp_dir);
- log_debug("QUIC, using self-generated key '%s' with "
+ log_debug("QUIC/TLS, using self-generated key '%s' with "
"one-time certificate", key_file);
}
server->quic_creds = knot_quic_init_creds(cert_file, key_file);
free(cert_file);
if (server->quic_creds == NULL) {
- log_error("QUIC, failed to initialize server credentials with key '%s'",
+ log_error("QUIC/TLS, failed to initialize server credentials with key '%s'",
key_file);
free(key_file);
return KNOT_ERROR;
@@ -624,13 +622,10 @@ static int init_creds(server_t *server, conf_t *conf)
size_t pin_len;
uint8_t pin[128];
if ((pin_len = server_cert_pin(server, pin, sizeof(pin))) > 0) {
- log_info("QUIC, certificate public key %.*s", (int)pin_len, pin);
+ log_info("QUIC/TLS, certificate public key %.*s", (int)pin_len, pin);
}
return KNOT_EOK;
-#else
- return KNOT_ERROR;
-#endif // ENABLE_QUIC
}
/*! \brief Initialize bound sockets according to configuration. */
@@ -642,11 +637,13 @@ static int configure_sockets(conf_t *conf, server_t *s)
conf_val_t listen_val = conf_get(conf, C_SRV, C_LISTEN);
conf_val_t liquic_val = conf_get(conf, C_SRV, C_LISTEN_QUIC);
+ conf_val_t listls_val = conf_get(conf, C_SRV, C_LISTEN_TLS);
conf_val_t lisxdp_val = conf_get(conf, C_XDP, C_LISTEN);
conf_val_t rundir_val = conf_get(conf, C_SRV, C_RUNDIR);
uint16_t convent_quic = conf_val_count(&liquic_val);
+ uint16_t convent_tls = conf_val_count(&listls_val);
- if (listen_val.code == KNOT_EOK || liquic_val.code == KNOT_EOK) {
+ if (listen_val.code == KNOT_EOK || liquic_val.code == KNOT_EOK || listls_val.code == KNOT_EOK) {
log_sock_conf(conf);
} else if (lisxdp_val.code != KNOT_EOK) {
log_warning("no network interface configured");
@@ -672,7 +669,7 @@ static int configure_sockets(conf_t *conf, server_t *s)
size_t real_nifs = 0;
size_t nifs = conf_val_count(&listen_val) + conf_val_count(&liquic_val) +
- conf_val_count(&lisxdp_val);
+ conf_val_count(&listls_val) + conf_val_count(&lisxdp_val);
iface_t *newlist = calloc(nifs, sizeof(*newlist));
if (newlist == NULL) {
log_error("failed to allocate memory for network sockets");
@@ -722,6 +719,25 @@ static int configure_sockets(conf_t *conf, server_t *s)
conf_val_next(&liquic_val);
}
+ while (listls_val.code == KNOT_EOK) {
+ struct sockaddr_storage addr = conf_addr(&listls_val, rundir);
+ char addr_str[SOCKADDR_STRLEN] = { 0 };
+ sockaddr_tostr(addr_str, sizeof(addr_str), &addr);
+ log_info("binding to TLS interface %s", addr_str);
+
+ iface_t *new_if = server_init_iface(&addr, true, 0, size_tcp,
+ tcp_reuseport, socket_affinity);
+ if (new_if == NULL) {
+ server_deinit_iface_list(newlist, nifs);
+ free(rundir);
+ return KNOT_ERROR;
+ }
+ new_if->tls = true;
+ memcpy(&newlist[real_nifs++], new_if, sizeof(*newlist));
+ free(new_if);
+
+ conf_val_next(&listls_val);
+ }
free(rundir);
/* XDP sockets. */
@@ -758,8 +774,9 @@ static int configure_sockets(conf_t *conf, server_t *s)
nifs = real_nifs;
/* QUIC credentials initialization. */
- s->quic_active = conf->cache.xdp_quic > 0 || convent_quic > 0 || quic_rmt_count(conf) > 0;
- if (s->quic_active) {
+ s->quic_active = conf->cache.xdp_quic > 0 || convent_quic > 0 || quic_rmt_count(conf, C_QUIC) > 0;
+ s->tls_active = convent_tls > 0 || quic_rmt_count(conf, C_TLS) > 0;
+ if (s->quic_active || s->tls_active) {
if (init_creds(s, conf) != KNOT_EOK) {
server_deinit_iface_list(newlist, nifs);
return KNOT_ERROR;
@@ -884,9 +901,7 @@ void server_deinit(server_t *server)
global_sessticket_pool = NULL;
knot_unreachables_deinit(&global_unreachables);
-#if defined ENABLE_QUIC
knot_quic_free_creds(server->quic_creds);
-#endif // ENABLE_QUIC
}
static int server_init_handler(server_t *server, int index, int thread_count,
@@ -1057,9 +1072,10 @@ static bool listen_changed(conf_t *conf, server_t *server)
conf_val_t listen_val = conf_get(conf, C_SRV, C_LISTEN);
conf_val_t liquic_val = conf_get(conf, C_SRV, C_LISTEN_QUIC);
+ conf_val_t listls_val = conf_get(conf, C_SRV, C_LISTEN_TLS);
conf_val_t lisxdp_val = conf_get(conf, C_XDP, C_LISTEN);
size_t new_count = conf_val_count(&listen_val) + conf_val_count(&liquic_val) +
- conf_val_count(&lisxdp_val);
+ conf_val_count(&listls_val) + conf_val_count(&lisxdp_val);
size_t old_count = server->n_ifaces;
if (new_count != old_count) {
return true;
@@ -1074,7 +1090,9 @@ static bool listen_changed(conf_t *conf, server_t *server)
struct sockaddr_storage addr = conf_addr(&listen_val, rundir);
bool found = false;
for (size_t i = 0; i < server->n_ifaces; i++) {
- if (sockaddr_cmp(&addr, &server->ifaces[i].addr, false) == 0) {
+ iface_t *iface = &server->ifaces[i];
+ if (sockaddr_cmp(&addr, &iface->addr, false) == 0 &&
+ !iface->tls && iface->fd_xdp_count == 0) {
matches++;
found = true;
break;
@@ -1089,7 +1107,9 @@ static bool listen_changed(conf_t *conf, server_t *server)
struct sockaddr_storage addr = conf_addr(&liquic_val, rundir);
bool found = false;
for (size_t i = 0; i < server->n_ifaces; i++) {
- if (sockaddr_cmp(&addr, &server->ifaces[i].addr, false) == 0) {
+ iface_t *iface = &server->ifaces[i];
+ if (sockaddr_cmp(&addr, &iface->addr, false) == 0 &&
+ iface->tls && iface->fd_udp_count > 0) {
matches++;
found = true;
break;
@@ -1100,13 +1120,32 @@ static bool listen_changed(conf_t *conf, server_t *server)
}
conf_val_next(&liquic_val);
}
+ while (listls_val.code == KNOT_EOK) {
+ struct sockaddr_storage addr = conf_addr(&listls_val, rundir);
+ bool found = false;
+ for (size_t i = 0; i < server->n_ifaces; i++) {
+ iface_t *iface = &server->ifaces[i];
+ if (sockaddr_cmp(&addr, &iface->addr, false) == 0 &&
+ iface->tls && iface->fd_tcp_count > 0) {
+ matches++;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ break;
+ }
+ conf_val_next(&listls_val);
+ }
free(rundir);
while (lisxdp_val.code == KNOT_EOK) {
struct sockaddr_storage addr = conf_addr(&lisxdp_val, NULL);
bool found = false;
for (size_t i = 0; i < server->n_ifaces; i++) {
- if (sockaddr_cmp(&addr, &server->ifaces[i].addr, false) == 0) {
+ iface_t *iface = &server->ifaces[i];
+ if (sockaddr_cmp(&addr, &iface->addr, false) == 0 &&
+ iface->fd_xdp_count > 0) {
matches++;
found = true;
break;
@@ -1167,7 +1206,7 @@ static void warn_server_reconfigure(conf_t *conf, server_t *server)
}
if (warn_listen && server->ifaces != NULL && listen_changed(conf, server)) {
- log_warning(msg, "listen(-xdp,-quic)");
+ log_warning(msg, "listen(-xdp,-quic,-tls)");
warn_listen = false;
}
@@ -1412,7 +1451,7 @@ static int reconfigure_remote_pool(conf_t *conf, server_t *server)
#ifdef ENABLE_QUIC
if (global_sessticket_pool == NULL && server->quic_active) {
- size_t rmt_count = quic_rmt_count(conf);
+ size_t rmt_count = quic_rmt_count(conf, C_QUIC);
if (rmt_count > 0) {
size_t max_tickets = conf_bg_threads(conf) * rmt_count * 2; // Two addresses per remote.
conn_pool_t *new_pool =
@@ -1532,10 +1571,9 @@ void server_update_zones(conf_t *conf, server_t *server, reload_t mode)
size_t server_cert_pin(server_t *server, uint8_t *out, size_t out_size)
{
-#ifdef ENABLE_QUIC
int pin_size = 0;
- uint8_t bin_pin[KNOT_QUIC_PIN_LEN];
+ uint8_t bin_pin[KNOT_TLS_PIN_LEN];
size_t bin_pin_size = sizeof(bin_pin);
gnutls_x509_crt_t cert = NULL;
if (server->quic_creds != NULL &&
@@ -1547,7 +1585,4 @@ size_t server_cert_pin(server_t *server, uint8_t *out, size_t out_size)
gnutls_x509_crt_deinit(cert);
return (pin_size >= 0) ? pin_size : 0;
-#else
- return 0;
-#endif // ENABLE_QUIC
}
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index 0a3019691..77c417141 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -119,6 +119,7 @@ typedef struct server {
iface_t *ifaces;
size_t n_ifaces;
bool quic_active;
+ bool tls_active;
/*! \brief Pending changes to catalog member zones, update indication. */
catalog_update_t catalog_upd;
diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c
index b2866281e..bf08a0ed2 100644
--- a/src/knot/server/tcp-handler.c
+++ b/src/knot/server/tcp-handler.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -36,6 +36,7 @@
#include "knot/common/fdset.h"
#include "knot/nameserver/process_query.h"
#include "knot/query/layer.h"
+#include "libknot/quic/tls.h"
#include "contrib/macros.h"
#include "contrib/mempattern.h"
#include "contrib/net.h"
@@ -57,6 +58,7 @@ typedef struct tcp_context {
unsigned max_worker_fds; /*!< Max TCP clients per worker configuration + no. of ifaces. */
int idle_timeout; /*!< [s] TCP idle timeout configuration. */
int io_timeout; /*!< [ms] TCP send/recv timeout configuration. */
+ struct knot_tls_ctx *tls_ctx; /*!< DoT answering context. */
} tcp_context_t;
#define TCP_SWEEP_INTERVAL 2 /*!< [secs] granularity of connection sweeping. */
@@ -76,6 +78,16 @@ static void update_tcp_conf(tcp_context_t *tcp)
tcp->idle_timeout = pconf->cache.srv_tcp_idle_timeout;
tcp->io_timeout = pconf->cache.srv_tcp_io_timeout;
rcu_read_unlock();
+
+ if (tcp->tls_ctx != NULL) {
+ tcp->tls_ctx->io_timeout = tcp->io_timeout;
+ }
+}
+
+static void free_tls_ctx(fdset_t *set, int idx)
+{
+ void *tls_conn = *fdset_ctx2(set, idx);
+ knot_tls_conn_del(tls_conn);
}
/*! \brief Sweep TCP connection. */
@@ -93,6 +105,8 @@ static fdset_sweep_state_t tcp_sweep(fdset_t *set, int idx, _unused_ void *data)
log_notice("TCP, terminated inactive client, address %s", addr_str);
}
+ free_tls_ctx(set, idx);
+
return FDSET_SWEEP;
}
@@ -108,15 +122,14 @@ static void tcp_log_error(const struct sockaddr_storage *ss, const char *operati
}
static unsigned tcp_set_ifaces(const iface_t *ifaces, size_t n_ifaces,
- fdset_t *fds, int thread_id)
+ fdset_t *fds, int thread_id, bool *tls)
{
if (n_ifaces == 0) {
return 0;
}
for (const iface_t *i = ifaces; i != ifaces + n_ifaces; i++) {
- if (i->fd_tcp_count == 0 || i->tls) { // Ignore XDP and QUIC interfaces.
- assert(i->fd_xdp_count > 0 || i->tls);
+ if (i->fd_xdp_count > 0 || i->fd_tcp_count == 0) { // Ignore XDP and QUIC interfaces.
continue;
}
@@ -134,23 +147,34 @@ static unsigned tcp_set_ifaces(const iface_t *ifaces, size_t n_ifaces,
if (ret < 0) {
return 0;
}
+ if (i->tls) {
+ *tls = true;
+ }
}
return fdset_get_length(fds);
}
-static int tcp_handle(tcp_context_t *tcp, int fd, const sockaddr_t *remote,
+static int tcp_handle(tcp_context_t *tcp, int fd, knot_tls_conn_t *tls_conn, const sockaddr_t *remote,
const sockaddr_t *local, struct iovec *rx, struct iovec *tx)
{
/* Create query processing parameter. */
- knotd_qdata_params_t params = params_init(KNOTD_QUERY_PROTO_TCP, remote, local,
- fd, tcp->server, tcp->thread_id);
+ knotd_qdata_params_t params = params_init(tls_conn != NULL ? KNOTD_QUERY_PROTO_TLS :
+ KNOTD_QUERY_PROTO_TCP,
+ remote, local, fd, tcp->server, tcp->thread_id);
rx->iov_len = KNOT_WIRE_MAX_PKTSIZE;
tx->iov_len = KNOT_WIRE_MAX_PKTSIZE;
/* Receive data. */
- int recv = net_dns_tcp_recv(fd, rx->iov_base, rx->iov_len, tcp->io_timeout);
+ int recv;
+ if (tls_conn != NULL) {
+ assert(tcp->tls_ctx != NULL);
+ params.tls_session = tls_conn->session;
+ recv = knot_tls_recv_dns(tls_conn, rx->iov_base, rx->iov_len);
+ } else {
+ recv = net_dns_tcp_recv(fd, rx->iov_base, rx->iov_len, tcp->io_timeout);
+ }
if (recv > 0) {
rx->iov_len = recv;
} else {
@@ -166,8 +190,13 @@ static int tcp_handle(tcp_context_t *tcp, int fd, const sockaddr_t *remote,
knot_layer_produce(&tcp->layer, ans);
/* Send, if response generation passed and wasn't ignored. */
if (ans->size > 0 && send_state(tcp->layer.state)) {
- int sent = net_dns_tcp_send(fd, ans->wire, ans->size,
- tcp->io_timeout, NULL);
+ int sent;
+ if (tls_conn != NULL) {
+ sent = knot_tls_send_dns(tls_conn, ans->wire, ans->size);
+ } else {
+ sent = net_dns_tcp_send(fd, ans->wire, ans->size,
+ tcp->io_timeout, NULL);
+ }
if (sent != ans->size) {
tcp_log_error(params.remote, "send", sent);
handle_finish(&tcp->layer);
@@ -223,7 +252,18 @@ static int tcp_event_serve(tcp_context_t *tcp, unsigned i, const iface_t *iface)
}
}
- int ret = tcp_handle(tcp, fd, remote, local, &tcp->iov[0], &tcp->iov[1]);
+ /* Establish a TLS session. */
+ knot_tls_conn_t *tls_conn = *fdset_ctx2(&tcp->set, i);
+ assert(iface->tls || tls_conn == NULL);
+ if (iface->tls && tls_conn == NULL) {
+ tls_conn = knot_tls_conn_new(tcp->tls_ctx, fd);
+ if (tls_conn == NULL) {
+ return KNOT_ENOMEM;
+ }
+ *fdset_ctx2(&tcp->set, i) = tls_conn;
+ }
+
+ int ret = tcp_handle(tcp, fd, tls_conn, remote, local, &tcp->iov[0], &tcp->iov[1]);
if (ret == KNOT_EOK) {
/* Update socket activity timer. */
(void)fdset_set_watchdog(&tcp->set, i, tcp->idle_timeout);
@@ -274,6 +314,7 @@ static void tcp_wait_for_events(tcp_context_t *tcp)
/* Evaluate. */
if (should_close) {
+ free_tls_ctx(set, idx);
fdset_it_remove(&it);
}
}
@@ -326,13 +367,15 @@ int tcp_master(dthread_t *thread)
/* Prepare initial buffer for listening and bound sockets. */
if (fdset_init(&tcp.set, FDSET_RESIZE_STEP) != KNOT_EOK) {
+ ret = KNOT_ENOMEM;
goto finish;
}
/* Set descriptors for the configured interfaces. */
+ bool tls = false;
tcp.client_threshold = tcp_set_ifaces(handler->server->ifaces,
handler->server->n_ifaces,
- &tcp.set, thread_id);
+ &tcp.set, thread_id, &tls);
if (tcp.client_threshold == 0) {
goto finish; /* Terminate on zero interfaces. */
}
@@ -342,6 +385,16 @@ int tcp_master(dthread_t *thread)
update_sweep_timer(&next_sweep);
update_tcp_conf(&tcp);
+ /* Initialize TLS context. */
+ if (tls) {
+ tcp.tls_ctx = knot_tls_ctx_new(handler->server->quic_creds,
+ tcp.io_timeout, true);
+ if (tcp.tls_ctx == NULL) {
+ ret = KNOT_ENOMEM;
+ goto finish;
+ }
+ }
+
for (;;) {
/* Check for cancellation. */
if (dt_is_cancelled(thread)) {
@@ -360,6 +413,7 @@ int tcp_master(dthread_t *thread)
}
finish:
+ knot_tls_ctx_free(tcp.tls_ctx);
free(tcp.iov[0].iov_base);
free(tcp.iov[1].iov_base);
mp_delete(mm.ctx);
diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c
index 964479fe3..f1222f0d4 100644
--- a/src/knot/server/udp-handler.c
+++ b/src/knot/server/udp-handler.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -482,7 +482,6 @@ static int iface_udp_fd(const iface_t *iface, int thread_id, bool xdp_thread,
#endif
} else { // UDP thread.
if (iface->fd_udp_count == 0) { // No UDP interfaces.
- assert(iface->fd_xdp_count > 0);
return -1;
}
#ifdef ENABLE_REUSEPORT
@@ -508,7 +507,7 @@ static unsigned udp_set_ifaces(const server_t *server, size_t n_ifaces, fdset_t
#ifndef ENABLE_REUSEPORT
/* If loadbalanced SO_REUSEPORT isn't available, ensure that
* just one (first) UDP worker handles the QUIC sockets. */
- if (i->quic && thread_id > 0) {
+ if (i->tls && thread_id > 0) {
continue;
}
#endif
diff --git a/src/knot/updates/acl.c b/src/knot/updates/acl.c
index d29774799..4475b2876 100644
--- a/src/knot/updates/acl.c
+++ b/src/knot/updates/acl.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -18,16 +18,13 @@
#include "contrib/string.h"
#include "contrib/wire_ctx.h"
-#ifdef ENABLE_QUIC
-#include "libknot/quic/quic.h"
-#endif // ENABLE_QUIC
static bool cert_pin_check(const uint8_t *session_pin, size_t session_pin_size,
conf_val_t *pins)
{
if (pins->code == KNOT_ENOENT) { // No certificate pin authentication required.
return true;
- } else if (session_pin_size == 0) { // Not a QUIC connection.
+ } else if (session_pin_size == 0) { // Not a TLS/QUIC connection.
return false;
}
@@ -283,20 +280,15 @@ static bool check_addr_key(conf_t *conf, conf_val_t *addr_val, conf_val_t *key_v
bool acl_allowed(conf_t *conf, conf_val_t *acl, acl_action_t action,
const struct sockaddr_storage *addr, knot_tsig_key_t *tsig,
const knot_dname_t *zone_name, knot_pkt_t *query,
- struct knot_quic_conn *conn)
+ struct gnutls_session_int *tls_session)
{
if (acl == NULL || addr == NULL || tsig == NULL) {
return false;
}
-#ifdef ENABLE_QUIC
- uint8_t session_pin[KNOT_QUIC_PIN_LEN];
+ uint8_t session_pin[KNOT_TLS_PIN_LEN];
size_t session_pin_size = sizeof(session_pin);
- knot_quic_conn_pin(conn, session_pin, &session_pin_size, false);
-#else
- uint8_t session_pin[1];
- size_t session_pin_size = 0;
-#endif // ENABLE_QUIC
+ knot_quic_conn_pin2(tls_session, session_pin, &session_pin_size, false);
bool forward = false;
if (action == ACL_ACTION_UPDATE) {
@@ -392,20 +384,15 @@ next_acl:
}
bool rmt_allowed(conf_t *conf, conf_val_t *rmts, const struct sockaddr_storage *addr,
- knot_tsig_key_t *tsig, struct knot_quic_conn *conn)
+ knot_tsig_key_t *tsig, struct gnutls_session_int *tls_session)
{
if (!conf->cache.srv_auto_acl) {
return false;
}
-#ifdef ENABLE_QUIC
- uint8_t session_pin[KNOT_QUIC_PIN_LEN];
+ uint8_t session_pin[KNOT_TLS_PIN_LEN];
size_t session_pin_size = sizeof(session_pin);
- knot_quic_conn_pin(conn, session_pin, &session_pin_size, false);
-#else
- uint8_t session_pin[1];
- size_t session_pin_size = 0;
-#endif // ENABLE_QUIC
+ knot_quic_conn_pin2(tls_session, session_pin, &session_pin_size, false);
conf_mix_iter_t iter;
conf_mix_iter_init(conf, rmts, &iter);
diff --git a/src/knot/updates/acl.h b/src/knot/updates/acl.h
index 88d7de578..5072a2901 100644
--- a/src/knot/updates/acl.h
+++ b/src/knot/updates/acl.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -19,6 +19,7 @@
#include <stdbool.h>
#include <sys/socket.h>
+#include "libknot/quic/tls_common.h"
#include "libknot/tsig.h"
#include "knot/conf/conf.h"
@@ -51,21 +52,21 @@ typedef enum {
*
* If a proper ACL rule is found and tsig.name is not empty, tsig.secret is filled.
*
- * \param conf Configuration.
- * \param acl Pointer to ACL config multivalued identifier.
- * \param action ACL action.
- * \param addr IP address.
- * \param tsig TSIG parameters.
- * \param zone_name Zone name.
- * \param query Update query.
- * \param conn Possible QUIC connection.
+ * \param conf Configuration.
+ * \param acl Pointer to ACL config multivalued identifier.
+ * \param action ACL action.
+ * \param addr IP address.
+ * \param tsig TSIG parameters.
+ * \param zone_name Zone name.
+ * \param query Update query.
+ * \param tls_session Possible TLS session.
*
* \retval True if authenticated.
*/
bool acl_allowed(conf_t *conf, conf_val_t *acl, acl_action_t action,
const struct sockaddr_storage *addr, knot_tsig_key_t *tsig,
const knot_dname_t *zone_name, knot_pkt_t *query,
- struct knot_quic_conn *conn);
+ struct gnutls_session_int *tls_session);
/*!
* \brief Checks if the address and/or tsig key matches a remote from the list.
@@ -75,13 +76,13 @@ bool acl_allowed(conf_t *conf, conf_val_t *acl, acl_action_t action,
*
* If a proper REMOTE is found and tsig.name is not empty, tsig.secret is filled.
*
- * \param conf Configuration.
- * \param rmts Pointer to REMOTE config multivalued identifier.
- * \param addr IP address.
- * \param tsig TSIG parameters.
- * \param conn Possible QUIC connection.
+ * \param conf Configuration.
+ * \param rmts Pointer to REMOTE config multivalued identifier.
+ * \param addr IP address.
+ * \param tsig TSIG parameters.
+ * \param tls_session Possible TLS session.
*
* \retval True if authenticated.
*/
bool rmt_allowed(conf_t *conf, conf_val_t *rmts, const struct sockaddr_storage *addr,
- knot_tsig_key_t *tsig, struct knot_quic_conn *conn);
+ knot_tsig_key_t *tsig, struct gnutls_session_int *tls_session);
diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc
index dc39b5fb7..d09ff55e5 100755
--- a/src/libknot/Makefile.inc
+++ b/src/libknot/Makefile.inc
@@ -37,6 +37,7 @@ nobase_include_libknot_HEADERS = \
libknot/packet/wire.h \
libknot/probe/data.h \
libknot/probe/probe.h \
+ libknot/quic/tls.h \
libknot/quic/tls_common.h \
libknot/rdata.h \
libknot/rdataset.h \
@@ -79,6 +80,7 @@ libknot_la_SOURCES = \
libknot/packet/rrset-wire.c \
libknot/probe/data.c \
libknot/probe/probe.c \
+ libknot/quic/tls.c \
libknot/quic/tls_common.c \
libknot/rdataset.c \
libknot/rrset-dump.c \
diff --git a/src/libknot/quic/quic.c b/src/libknot/quic/quic.c
index 67b875a5d..6ee212932 100644
--- a/src/libknot/quic/quic.c
+++ b/src/libknot/quic/quic.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -33,7 +33,6 @@
#include "contrib/macros.h"
#include "contrib/sockaddr.h"
-#include "contrib/string.h"
#include "contrib/ucw/lists.h"
#include "libknot/endian.h"
#include "libdnssec/error.h"
@@ -58,19 +57,6 @@
#define TLS_CALLBACK_ERR (-1)
-const gnutls_datum_t doq_alpn = {
- (unsigned char *)"doq", 3
-};
-
-typedef struct knot_quic_creds {
- gnutls_certificate_credentials_t tls_cert;
- gnutls_anti_replay_t tls_anti_replay;
- gnutls_datum_t tls_ticket_key;
- bool peer;
- uint8_t peer_pin_len;
- uint8_t peer_pin[];
-} knot_quic_creds_t;
-
typedef struct knot_quic_session {
node_t n;
gnutls_datum_t tls_session;
@@ -160,51 +146,30 @@ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
static int tls_init_conn_session(knot_quic_conn_t *conn, bool server)
{
- if (gnutls_init(&conn->tls_session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) |
- GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_AUTO_SEND_TICKET |
- GNUTLS_NO_END_OF_EARLY_DATA) != GNUTLS_E_SUCCESS) {
- return TLS_CALLBACK_ERR;
- }
-
- gnutls_certificate_send_x509_rdn_sequence(conn->tls_session, 1);
- gnutls_certificate_server_set_request(conn->tls_session, GNUTLS_CERT_REQUEST);
-
- if (gnutls_priority_set_direct(conn->tls_session, QUIC_PRIORITIES,
- NULL) != GNUTLS_E_SUCCESS) {
+ int ret = knot_quic_conn_session(&conn->tls_session, conn->quic_table->creds,
+ QUIC_PRIORITIES, "\x03""doq", true, server);
+ if (ret != KNOT_EOK) {
return TLS_CALLBACK_ERR;
}
- if (server && gnutls_session_ticket_enable_server(conn->tls_session,
- &conn->quic_table->creds->tls_ticket_key) != GNUTLS_E_SUCCESS) {
- return TLS_CALLBACK_ERR;
+ if (server) {
+ ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session);
+ } else {
+ ret = ngtcp2_crypto_gnutls_configure_client_session(conn->tls_session);
}
-
- int ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session);
- if (ret != 0) {
+ if (ret != NGTCP2_NO_ERROR) {
return TLS_CALLBACK_ERR;
}
- gnutls_record_set_max_early_data_size(conn->tls_session, 0xffffffffu);
-
conn->conn_ref = (nc_conn_ref_placeholder_t) {
.get_conn = get_conn,
.user_data = conn
};
- _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref), "invalid placeholder for conn_ref");
+ _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref),
+ "invalid placeholder for conn_ref");
gnutls_session_set_ptr(conn->tls_session, &conn->conn_ref);
- if (server) {
- gnutls_anti_replay_enable(conn->tls_session, conn->quic_table->creds->tls_anti_replay);
-
- }
- if (gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE,
- conn->quic_table->creds->tls_cert) != GNUTLS_E_SUCCESS) {
- return TLS_CALLBACK_ERR;
- }
-
- gnutls_alpn_set_protocols(conn->tls_session, &doq_alpn, 1, GNUTLS_ALPN_MANDATORY);
-
ngtcp2_conn_set_tls_native_handle(conn->conn, conn->tls_session);
return KNOT_EOK;
@@ -260,54 +225,6 @@ uint16_t knot_quic_conn_local_port(knot_quic_conn_t *conn)
return ((const struct sockaddr_in6 *)path->local.addr)->sin6_port;
}
-_public_
-void knot_quic_conn_pin(knot_quic_conn_t *conn, uint8_t *pin, size_t *pin_size, bool local)
-{
- if (conn == NULL) {
- goto error;
- }
-
- const gnutls_datum_t *data = NULL;
- if (local) {
- data = gnutls_certificate_get_ours(conn->tls_session);
- } else {
- unsigned count = 0;
- data = gnutls_certificate_get_peers(conn->tls_session, &count);
- if (count == 0) {
- goto error;
- }
- }
- if (data == NULL) {
- goto error;
- }
-
- gnutls_x509_crt_t cert;
- int ret = gnutls_x509_crt_init(&cert);
- if (ret != GNUTLS_E_SUCCESS) {
- goto error;
- }
-
- ret = gnutls_x509_crt_import(cert, data, GNUTLS_X509_FMT_DER);
- if (ret != GNUTLS_E_SUCCESS) {
- gnutls_x509_crt_deinit(cert);
- goto error;
- }
-
- ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, pin, pin_size);
- if (ret != GNUTLS_E_SUCCESS) {
- gnutls_x509_crt_deinit(cert);
- goto error;
- }
-
- gnutls_x509_crt_deinit(cert);
-
- return;
-error:
- if (pin_size != NULL) {
- *pin_size = 0;
- }
-}
-
static void knot_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx)
{
(void)rand_ctx;
@@ -385,18 +302,8 @@ static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data)
ctx->flags |= KNOT_QUIC_CONN_HANDSHAKE_DONE;
if (!ngtcp2_conn_is_server(conn)) {
- knot_quic_creds_t *creds = ctx->quic_table->creds;
- if (creds->peer_pin_len == 0) {
- return 0;
- }
- uint8_t pin[KNOT_QUIC_PIN_LEN];
- size_t pin_size = sizeof(pin);
- knot_quic_conn_pin(ctx, pin, &pin_size, false);
- if (pin_size != creds->peer_pin_len ||
- const_time_memcmp(pin, creds->peer_pin, pin_size) != 0) {
- return NGTCP2_ERR_CALLBACK_FAILURE;
- }
- return 0;
+ return knot_quic_conn_pin_check(ctx->tls_session, ctx->quic_table->creds)
+ == KNOT_EOK ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
}
if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) {
diff --git a/src/libknot/quic/quic.h b/src/libknot/quic/quic.h
index 1af614208..b4acb3392 100644
--- a/src/libknot/quic/quic.h
+++ b/src/libknot/quic/quic.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 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
@@ -115,18 +115,6 @@ uint32_t knot_quic_conn_rtt(knot_quic_conn_t *conn);
uint16_t knot_quic_conn_local_port(knot_quic_conn_t *conn);
/*!
- * \brief Gets local or remote certificate pin.
- *
- * \note Zero output pin_size value means no certificate available or error.
- *
- * \param conn QUIC connection.
- * \param pin Output certificate pin.
- * \param pin_size Input size of the storage / output size of the stored pin.
- * \param local Local or remote certificate indication.
- */
-void knot_quic_conn_pin(knot_quic_conn_t *conn, uint8_t *pin, size_t *pin_size, bool local);
-
-/*!
* \brief Create new outgoing QUIC connection.
*
* \param table QUIC connections table to be added to.
diff --git a/src/libknot/quic/tls.c b/src/libknot/quic/tls.c
new file mode 100644
index 000000000..b4416237c
--- /dev/null
+++ b/src/libknot/quic/tls.c
@@ -0,0 +1,245 @@
+/* Copyright (C) 2024 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 <arpa/inet.h>
+#include <gnutls/crypto.h>
+#include <gnutls/gnutls.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libknot/quic/tls.h"
+
+#include "contrib/macros.h"
+#include "contrib/net.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+#include "libknot/quic/tls_common.h"
+
+// TODO re-consider those detailed
+#define TLS_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3"
+#define TLS_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:+GROUP-SECP384R1:+GROUP-SECP521R1"
+#define TLS_PRIORITIES "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"TLS_DEFAULT_VERSION":"TLS_DEFAULT_GROUPS
+
+#define EAGAIN_MAX_FOR_GNUTLS 10 // gnutls_record_recv() has been observed to return GNUTLS_E_AGAIN repetitively and excessively, leading to infinite loops. This limits the number of re-tries.
+
+_public_
+knot_tls_ctx_t *knot_tls_ctx_new(struct knot_quic_creds *creds, unsigned io_timeout,
+ bool server)
+{
+ knot_tls_ctx_t *res = calloc(1, sizeof(*res));
+ if (res == NULL) {
+ return NULL;
+ }
+
+ res->creds = creds;
+ res->handshake_timeout = GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT;
+ res->io_timeout = io_timeout;
+ res->server = server;
+
+ return res;
+}
+
+_public_
+void knot_tls_ctx_free(knot_tls_ctx_t *ctx)
+{
+ if (ctx != NULL) {
+ free(ctx);
+ }
+}
+
+static int poll_func(gnutls_transport_ptr_t ptr, unsigned timeout_ms)
+{
+ knot_tls_conn_t *conn = (knot_tls_conn_t *)ptr;
+
+ struct pollfd pfd = {
+ .fd = conn->fd,
+ .events = POLLIN
+ };
+
+ return poll(&pfd, 1, timeout_ms);
+}
+
+static ssize_t pull_func(gnutls_transport_ptr_t ptr, void *buf, size_t size)
+{
+ knot_tls_conn_t *conn = (knot_tls_conn_t *)ptr;
+ conn->recv_count++;
+ ssize_t ret = net_stream_recv(conn->fd, buf, size, conn->ctx->io_timeout);
+ if (ret < 0) {
+ conn->err_count++;
+ conn->last_err = ret;
+ }
+ return ret;
+}
+
+static ssize_t push_func(gnutls_transport_ptr_t ptr, const void *buf, size_t size)
+{
+ knot_tls_conn_t *conn = (knot_tls_conn_t *)ptr;
+ conn->send_count++;
+ ssize_t ret = net_stream_send(conn->fd, buf, size, conn->ctx->io_timeout);
+ if (ret < 0) {
+ conn->err_count++;
+ conn->last_err = ret;
+ }
+ return ret;
+}
+
+_public_
+knot_tls_conn_t *knot_tls_conn_new(knot_tls_ctx_t *ctx, int sock_fd)
+{
+ knot_tls_conn_t *res = calloc(1, sizeof(*res));
+ if (res == NULL) {
+ return NULL;
+ }
+ res->ctx = ctx;
+ res->fd = sock_fd;
+
+ int ret = knot_quic_conn_session(&res->session, ctx->creds, TLS_PRIORITIES,
+ "\x03""dot", false, ctx->server);
+ if (ret != KNOT_EOK) {
+ goto fail;
+ }
+
+ gnutls_transport_set_ptr(res->session, res);
+ gnutls_transport_set_pull_timeout_function(res->session, poll_func);
+ gnutls_transport_set_pull_function(res->session, pull_func);
+ gnutls_transport_set_push_function(res->session, push_func); // TODO employ gnutls_transport_set_vec_push_function for optimization
+ gnutls_handshake_set_timeout(res->session, ctx->handshake_timeout);
+ gnutls_record_set_timeout(res->session, ctx->io_timeout);
+
+ return res;
+fail:
+ gnutls_deinit(res->session);
+ free(res);
+ return NULL;
+}
+
+_public_
+void knot_tls_conn_del(knot_tls_conn_t *conn)
+{
+ if (conn != NULL) {
+ gnutls_deinit(conn->session);
+ free(conn);
+ }
+}
+
+inline static bool eagain_rcode(ssize_t gnutls_rcode)
+{
+ return gnutls_rcode == GNUTLS_E_AGAIN || gnutls_rcode == GNUTLS_E_INTERRUPTED;
+}
+
+_public_
+int knot_tls_handshake(knot_tls_conn_t *conn)
+{
+ if (conn->handshake_done) {
+ _Static_assert(KNOT_EOK == GNUTLS_E_SUCCESS, "EOK differs between libknot and GnuTLS");
+ return KNOT_EOK;
+ }
+ int ret, again = EAGAIN_MAX_FOR_GNUTLS;
+ do {
+ if (--again < 0) {
+ return KNOT_ETIMEOUT;
+ }
+ ret = gnutls_handshake(conn->session);
+ } while (eagain_rcode(ret));
+ // TODO filter error codes?
+
+ if (ret == KNOT_EOK) {
+ conn->handshake_done = true;
+ ret = knot_quic_conn_pin_check(conn->session, conn->ctx->creds);
+ }
+ return ret;
+}
+
+static ssize_t tls_io_fun(knot_tls_conn_t *conn, void *data, size_t size,
+ ssize_t (*io_cb)(gnutls_session_t, void *, size_t))
+{
+ ssize_t res = knot_tls_handshake(conn), orig_size = size, again = EAGAIN_MAX_FOR_GNUTLS;
+ if (res != KNOT_EOK) {
+ return res;
+ }
+
+ do {
+ if (--again < 0) {
+ return KNOT_ETIMEOUT;
+ }
+ res = io_cb(conn->session, data, size);
+ if (res > 0) {
+ data += res;
+ size -= res;
+ }
+ } while (eagain_rcode(res) || (res > 0 && size > 0));
+
+ conn->iofun_count++;
+
+ // TODO filter error codes?
+ return res > 0 ? orig_size : res;
+}
+
+static ssize_t gnutls_record_send_noconst(gnutls_session_t session,
+ void *data, size_t data_size)
+{
+ // just a wrapper, parameter 'data' is not (const void *) here
+ return gnutls_record_send(session, data, data_size);
+}
+
+_public_
+ssize_t knot_tls_recv(knot_tls_conn_t *conn, void *data, size_t size)
+{
+ return tls_io_fun(conn, data, size, gnutls_record_recv);
+}
+
+_public_
+ssize_t knot_tls_send(knot_tls_conn_t *conn, void *data, size_t size)
+{
+ return tls_io_fun(conn, data, size, gnutls_record_send_noconst);
+}
+
+_public_
+ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size)
+{
+ uint16_t dns_len;
+ ssize_t ret = knot_tls_recv(conn, &dns_len, sizeof(dns_len));
+ if (ret > 0 && ret < sizeof(dns_len)) {
+ ret = KNOT_EMALF;
+ } else if (ret == sizeof(dns_len)) {
+ dns_len = ntohs(dns_len);
+ if (dns_len > size) {
+ return KNOT_ESPACE;
+ }
+ ret = knot_tls_recv(conn, data, dns_len);
+ }
+
+ return ret;
+}
+
+_public_
+ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size)
+{
+ if (size > UINT16_MAX) {
+ return KNOT_EINVAL;
+ }
+
+ uint16_t dns_len = htons(size);
+ ssize_t ret = knot_tls_send(conn, &dns_len, sizeof(dns_len)); // TODO invent a way how to send length and data at once
+ if (ret > 0 && ret < sizeof(dns_len)) {
+ ret = KNOT_EMALF;
+ } else if (ret == sizeof(dns_len)) {
+ ret = knot_tls_send(conn, data, size);
+ }
+
+ return ret;
+}
diff --git a/src/libknot/quic/tls.h b/src/libknot/quic/tls.h
new file mode 100644
index 000000000..4b4ecfc6b
--- /dev/null
+++ b/src/libknot/quic/tls.h
@@ -0,0 +1,129 @@
+/* Copyright (C) 2024 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/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+typedef struct knot_tls_ctx {
+ struct knot_quic_creds *creds;
+ unsigned handshake_timeout;
+ unsigned io_timeout;
+ bool server;
+} knot_tls_ctx_t;
+
+typedef struct knot_tls_conn {
+ struct gnutls_session_int *session;
+ struct knot_tls_ctx *ctx;
+ int fd;
+ bool handshake_done;
+
+ // TODO: debug statistics. Remove(?) once well-tuned.
+ size_t recv_count;
+ size_t send_count;
+ size_t err_count;
+ ssize_t iofun_count;
+ int last_err;
+} knot_tls_conn_t;
+
+/*!
+ * \brief Initialize DoT answering context.
+ *
+ * \param creds Certificate credentials.
+ * \param io_timeout Connections' IO-timeout (in milliseconds).
+ * \param server Server context (otherwise client).
+ *
+ * \return Initialized context or NULL.
+ */
+knot_tls_ctx_t *knot_tls_ctx_new(struct knot_quic_creds *creds, unsigned io_timeout,
+ bool server);
+
+/*!
+ * \brief Free DoT answering context.
+ */
+void knot_tls_ctx_free(knot_tls_ctx_t *ctx);
+
+/*!
+ * \brief Initialize DoT connection.
+ *
+ * \param ctx DoT answering context.
+ * \param sock_fd Opened TCP connection socket.
+ *
+ * \return Connection struct or NULL.
+ */
+knot_tls_conn_t *knot_tls_conn_new(knot_tls_ctx_t *ctx, int sock_fd);
+
+/*!
+ * \brief Free DoT connection struct.
+ *
+ * \note Doesn't close the TCP connection socket.
+ */
+void knot_tls_conn_del(knot_tls_conn_t *conn);
+
+/*!
+ * \brief Perform the TLS handshake (via gnutls_handshake()).
+ *
+ * \note This is also done by the recv/send functions.
+ */
+int knot_tls_handshake(knot_tls_conn_t *conn);
+
+/*!
+ * \brief Receive data from a TLS connection.
+ *
+ * \param conn DoT connection.
+ * \param data Destination buffer.
+ * \param size Amount to be received.
+ *
+ * \return Either exactly 'size' or a negative error code.
+ */
+ssize_t knot_tls_recv(knot_tls_conn_t *conn, void *data, size_t size);
+
+/*!
+ * \brief Send data to a TLS connection.
+ *
+ * \param conn DoT connection.
+ * \param data The data.
+ * \param size Amount to be sent.
+ *
+ * \return Either exactly 'size' or a negative error code.
+ */
+ssize_t knot_tls_send(knot_tls_conn_t *conn, void *data, size_t size);
+
+/*!
+ * \brief Receive a size-word-prefixed DNS message.
+ *
+ * \param conn DoT connection.
+ * \param data Destination buffer.
+ * \param size Maximum buffer size.
+ *
+ * \return Either the DNS message size received or negative error code.
+ *
+ * \note The two-byte-size-prefix is stripped upon reception, not stored to the buffer.
+ */
+ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size);
+
+/*!
+ * \brief Send a size-word-prefixed DNS message.
+ *
+ * \param conn DoT connection.
+ * \param data DNS payload.
+ * \param size Payload size.
+ *
+ * \return Either exactly 'size' or a negative error code.
+ */
+ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size);
diff --git a/src/libknot/quic/tls_common.c b/src/libknot/quic/tls_common.c
index 07ca8093e..c4ef2cd55 100644
--- a/src/libknot/quic/tls_common.c
+++ b/src/libknot/quic/tls_common.c
@@ -14,13 +14,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-#include "libknot/quic/tls_common.h"
-
-#include "contrib/sockaddr.h"
-#include "contrib/string.h"
-#include "libknot/attribute.h"
-#include "libknot/error.h"
-
#include <fcntl.h>
#include <gnutls/crypto.h>
#include <gnutls/gnutls.h>
@@ -31,6 +24,13 @@
#include <time.h>
#include <unistd.h>
+#include "libknot/quic/tls_common.h"
+
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+
typedef struct knot_quic_creds {
gnutls_certificate_credentials_t tls_cert;
gnutls_anti_replay_t tls_anti_replay;
@@ -256,3 +256,116 @@ void knot_quic_free_creds(struct knot_quic_creds *creds)
}
free(creds);
}
+
+_public_
+int knot_quic_conn_session(struct gnutls_session_int **session,
+ struct knot_quic_creds *creds,
+ const char *priority,
+ const char *alpn,
+ bool early_data,
+ bool server)
+{
+ if (session == NULL || creds == NULL || priority == NULL || alpn == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ gnutls_init_flags_t early_flags = 0;
+ if (early_data) {
+ early_flags |= GNUTLS_ENABLE_EARLY_DATA;
+#ifdef ENABLE_QUIC // Next flags aren't available in older GnuTLS versions.
+ early_flags |= GNUTLS_NO_AUTO_SEND_TICKET | GNUTLS_NO_END_OF_EARLY_DATA;
+#endif
+ }
+
+ int ret = gnutls_init(session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | early_flags);
+ if (ret == GNUTLS_E_SUCCESS) {
+ gnutls_certificate_send_x509_rdn_sequence(*session, 1);
+ gnutls_certificate_server_set_request(*session, GNUTLS_CERT_REQUEST);
+ ret = gnutls_priority_set_direct(*session, priority, NULL);
+ }
+ if (server && ret == GNUTLS_E_SUCCESS) {
+ ret = gnutls_session_ticket_enable_server(*session, &creds->tls_ticket_key);
+ }
+ if (ret == GNUTLS_E_SUCCESS) {
+ const gnutls_datum_t alpn_datum = { (void *)&alpn[1], alpn[0] };
+ gnutls_alpn_set_protocols(*session, &alpn_datum, 1, GNUTLS_ALPN_MANDATORY);
+ if (early_data) {
+ gnutls_record_set_max_early_data_size(*session, 0xffffffffu);
+ }
+ if (server) {
+ gnutls_anti_replay_enable(*session, creds->tls_anti_replay);
+ }
+ ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, creds->tls_cert);
+ }
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_deinit(*session);
+ *session = NULL;
+ }
+ return ret == GNUTLS_E_SUCCESS ? KNOT_EOK : KNOT_ERROR;
+}
+
+_public_
+void knot_quic_conn_pin2(struct gnutls_session_int *session, uint8_t *pin, size_t *pin_size, bool local)
+{
+ if (session == NULL) {
+ goto error;
+ }
+
+ const gnutls_datum_t *data = NULL;
+ if (local) {
+ data = gnutls_certificate_get_ours(session);
+ } else {
+ unsigned count = 0;
+ data = gnutls_certificate_get_peers(session, &count);
+ if (count == 0) {
+ goto error;
+ }
+ }
+ if (data == NULL) {
+ goto error;
+ }
+
+ gnutls_x509_crt_t cert;
+ int ret = gnutls_x509_crt_init(&cert);
+ if (ret != GNUTLS_E_SUCCESS) {
+ goto error;
+ }
+
+ ret = gnutls_x509_crt_import(cert, data, GNUTLS_X509_FMT_DER);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_x509_crt_deinit(cert);
+ goto error;
+ }
+
+ ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, pin, pin_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_x509_crt_deinit(cert);
+ goto error;
+ }
+
+ gnutls_x509_crt_deinit(cert);
+
+ return;
+error:
+ if (pin_size != NULL) {
+ *pin_size = 0;
+ }
+}
+
+_public_
+int knot_quic_conn_pin_check(struct gnutls_session_int *session,
+ struct knot_quic_creds *creds)
+{
+ if (creds->peer_pin_len == 0) {
+ return KNOT_EOK;
+ }
+
+ uint8_t pin[KNOT_TLS_PIN_LEN];
+ size_t pin_size = sizeof(pin);
+ knot_quic_conn_pin2(session, pin, &pin_size, false);
+ if (pin_size != creds->peer_pin_len ||
+ const_time_memcmp(pin, creds->peer_pin, pin_size) != 0) {
+ return KNOT_EBADCERTKEY;
+ }
+ return KNOT_EOK;
+}
diff --git a/src/libknot/quic/tls_common.h b/src/libknot/quic/tls_common.h
index cf4b3bdf4..925501220 100644
--- a/src/libknot/quic/tls_common.h
+++ b/src/libknot/quic/tls_common.h
@@ -20,7 +20,7 @@
#include <stddef.h>
#include <stdint.h>
-#define KNOT_QUIC_PIN_LEN 32
+#define KNOT_TLS_PIN_LEN 32
struct gnutls_session_int;
struct gnutls_x509_crt_int;
@@ -64,3 +64,46 @@ int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_i
* \brief Deinit server TLS certificate for DoQ.
*/
void knot_quic_free_creds(struct knot_quic_creds *creds);
+
+/*!
+ * \brief Initialize GnuTLS session with credentials, ALPN, etc.
+ *
+ * \param session Out: initialized GnuTLS session struct.
+ * \param creds Certificate credentials.
+ * \param priority Session priority configuration.
+ * \param alpn ALPN string, first byte is the string length.
+ * \param early_data Allow early data.
+ * \param server Should be server session (otherwise client).
+ *
+ * \return KNOT_E*
+ */
+int knot_quic_conn_session(struct gnutls_session_int **session,
+ struct knot_quic_creds *creds,
+ const char *priority,
+ const char *alpn,
+ bool early_data,
+ bool server);
+
+/*!
+ * \brief Gets local or remote certificate pin.
+ *
+ * \note Zero output pin_size value means no certificate available or error.
+ *
+ * \param session TLS connection.
+ * \param pin Output certificate pin.
+ * \param pin_size Input size of the storage / output size of the stored pin.
+ * \param local Local or remote certificate indication.
+ */
+void knot_quic_conn_pin2(struct gnutls_session_int *session, uint8_t *pin,
+ size_t *pin_size, bool local);
+
+/*!
+ * \brief Checks remote certificate pin in the session against credentials.
+ *
+ * \param session TLS connection.
+ * \param creds TLS credentials.
+ *
+ * \return KNOT_EOK or KNOT_EBADCERTKEY
+ */
+int knot_quic_conn_pin_check(struct gnutls_session_int *session,
+ struct knot_quic_creds *creds);
diff --git a/tests-extra/tests/quic/xfr/test.py b/tests-extra/tests/quic/xfr/test.py
index fcdc6c066..2a6b3e18b 100644
--- a/tests-extra/tests/quic/xfr/test.py
+++ b/tests-extra/tests/quic/xfr/test.py
@@ -41,7 +41,7 @@ def check_error(server, msg):
if server.log_search(msg):
return
t.sleep(1)
- detail_log("Failed log expected")
+ detail_log("Failed log expected '%s' server %s" % (msg, server.name))
set_err("MISSING ERROR LOG")
def upd_check_zones(master, slave, zones, prev_serials):
diff --git a/tests-extra/tests/tls/xfr/test.py b/tests-extra/tests/tls/xfr/test.py
new file mode 100644
index 000000000..7db43a01c
--- /dev/null
+++ b/tests-extra/tests/tls/xfr/test.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+'''Test of zone transfers over TLS (XoT), almost identical to quic/xfr.'''
+
+from dnstest.test import Test
+from dnstest.utils import *
+import random
+import subprocess
+
+t = Test(tls=True, tsig=True, # TSIG needed to skip weaker ACL rules
+ quic=random.choice([False, True])) # QUIC should have no effect
+
+master = t.server("knot")
+slave = t.server("knot")
+rnd_zones = t.zone_rnd(1, records=50) + \
+ t.zone_rnd(1, records=500) + \
+ t.zone_rnd(1, records=1000)
+zones = t.zone(".") + rnd_zones
+
+t.link(zones, master, slave)
+
+for z in rnd_zones:
+ master.dnssec(z).enable = True
+
+if master.valgrind:
+ slave.quic_idle_close_timeout = 10 # for DoQ xfrs
+ slave.tcp_remote_io_timeout = 10000
+if slave.valgrind:
+ master.quic_idle_close_timeout = 10 # for sending DoQ notify
+
+MSG_DENIED_NOTIFY = "ACL, denied, action notify"
+MSG_DENIED_TRANSFER = "ACL, denied, action transfer"
+MSG_RMT_NOTAUTH = "server responded with error 'NOTAUTH'"
+MSG_RMT_BADCERT = "failed (unknown certificate key)"
+MSG_TSIG_ERROR = "failed (failed to verify TSIG)"
+
+def check_error(server, msg):
+ for i in range(10):
+ if server.log_search(msg):
+ return
+ t.sleep(1)
+ detail_log("Failed log expected '%s' server %s" % (msg, server.name))
+ set_err("MISSING ERROR LOG")
+
+def upd_check_zones(master, slave, zones, prev_serials):
+ for z in rnd_zones:
+ master.random_ddns(z, allow_empty=False)
+ serials = slave.zones_wait(zones, prev_serials)
+ t.xfr_diff(master, slave, zones, prev_serials)
+ return serials
+
+master.check_quic()
+
+t.start()
+
+tcpdump_pcap = t.out_dir + "/traffic.pcap"
+tcpdump_fout = t.out_dir + "/tcpdump.out"
+tcpdump_ferr = t.out_dir + "/tcpdump.err"
+
+tcpdump_proc = subprocess.Popen(["tcpdump", "-i", "lo", "-w", tcpdump_pcap,
+ "port", str(master.quic_port), "or", "port", str(slave.quic_port)],
+ stdout=open(tcpdump_fout, mode="a"), stderr=open(tcpdump_ferr, mode="a"))
+
+# Check initial AXFR without cert-key-based authentication
+serials = master.zones_wait(zones)
+slave.zones_wait(zones, serials, equal=True, greater=False)
+if slave.log_search(MSG_TSIG_ERROR):
+ set_err("INCOMPLETE TRANSFER")
+t.xfr_diff(master, slave, zones)
+
+# Check master not authenticated due to bad cert-key
+master.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+slave.gen_confile()
+slave.reload()
+master.ctl("zone-notify")
+check_error(master, MSG_RMT_NOTAUTH)
+check_error(slave, MSG_DENIED_NOTIFY)
+slave.ctl("zone-retransfer")
+check_error(slave, MSG_RMT_BADCERT)
+
+# Check IXFR with cert-key-based authenticated master
+master.fill_cert_key()
+slave.gen_confile()
+slave.reload()
+serials = upd_check_zones(master, slave, rnd_zones, serials)
+
+# Check slave not authenticated due to bad cert-key
+slave.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+master.gen_confile()
+master.reload()
+master.ctl("zone-notify")
+check_error(master, MSG_RMT_BADCERT)
+slave.ctl("zone-retransfer")
+check_error(slave, MSG_RMT_NOTAUTH)
+check_error(master, MSG_DENIED_TRANSFER)
+
+# Check IXFR with cert-key-based authenticated slave
+slave.fill_cert_key()
+master.gen_confile()
+master.reload()
+serials = upd_check_zones(master, slave, rnd_zones, serials)
+
+tcpdump_proc.terminate()
+
+t.end()
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index a4f61af63..d4c53263a 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -162,6 +162,7 @@ class Server(object):
self.port = 53 # Needed for keymgr when port not yet generated
self.xdp_port = None # 0 indicates that XDP is enabled but port not yet assigned
self.quic_port = None
+ self.tls_port = None
self.cert_key = str()
self.udp_workers = None
self.bg_workers = None
@@ -1367,6 +1368,8 @@ class Knot(Server):
s.item_str("listen", "%s@%s" % (self.addr, self.port))
if self.quic_port:
s.item_str("listen-quic", "%s@%s" % (self.addr, self.quic_port))
+ if self.tls_port:
+ s.item_str("listen-tls", "%s@%s" % (self.addr, self.tls_port))
if self.udp_workers:
s.item_str("udp-workers", self.udp_workers)
if self.bg_workers:
@@ -1427,9 +1430,9 @@ class Knot(Server):
s.begin("remote")
have_remote = True
s.id_item("id", master.name)
- if master.quic_port:
- s.item_str("address", "%s@%s" % (master.addr, master.quic_port))
- s.item_str("quic", "on")
+ if master.quic_port or master.tls_port:
+ s.item_str("address", "%s@%s" % (master.addr, master.tls_port or master.quic_port))
+ s.item_str("tls" if master.tls_port else "quic", "on")
if master.cert_key:
s.item_str("cert-key", master.cert_key)
else:
@@ -1450,9 +1453,9 @@ class Knot(Server):
s.begin("remote")
have_remote = True
s.id_item("id", slave.name)
- if slave.quic_port:
- s.item_str("address", "%s@%s" % (slave.addr, slave.quic_port))
- s.item_str("quic", "on")
+ if slave.quic_port or slave.tls_port:
+ s.item_str("address", "%s@%s" % (slave.addr, slave.tls_port or slave.quic_port))
+ s.item_str("tls" if slave.tls_port else "quic", "on")
if slave.cert_key:
s.item_str("cert-key", slave.cert_key)
else:
@@ -1484,9 +1487,9 @@ class Knot(Server):
s.begin("remote")
have_remote = True
s.id_item("id", remote.name)
- if remote.quic_port:
- s.item_str("address", "%s@%s" % (remote.addr, remote.quic_port))
- s.item_str("quic", "on")
+ if remote.quic_port or remote.tls_port:
+ s.item_str("address", "%s@%s" % (remote.addr, remote.tls_port or remote.quic_port))
+ s.item_str("tls" if remote.tls_port else "quic", "on")
if remote.cert_key:
s.item_str("cert-key", remote.cert_key)
else:
diff --git a/tests-extra/tools/dnstest/test.py b/tests-extra/tools/dnstest/test.py
index 2fc90b211..5ea03c35e 100644
--- a/tests-extra/tools/dnstest/test.py
+++ b/tests-extra/tools/dnstest/test.py
@@ -38,7 +38,7 @@ class Test(object):
rel_time = time.time()
start_time = 0
- def __init__(self, address=None, tsig=None, stress=True, quic=False):
+ def __init__(self, address=None, tsig=None, stress=True, quic=False, tls=False):
if not os.path.exists(Context().out_dir):
raise Exception("Output directory doesn't exist")
@@ -46,6 +46,7 @@ class Test(object):
self.data_dir = Context().test_dir + "/data/"
self.zones_dir = self.out_dir + "/zones/"
self.quic = quic
+ self.tls = tls
if address == 4 or address == 6:
self.addr = Test.LOCAL_ADDR_COMMON[address]
@@ -240,7 +241,11 @@ class Test(object):
server.port = self._gen_port()
server.ctlport = self._gen_port()
- server.quic_port = self._gen_port() if self.quic else None
+ if self.tls:
+ server.tls_port = self._gen_port()
+ server.quic_port = server.tls_port if self.quic else None
+ else:
+ server.quic_port = self._gen_port() if self.quic else None
server.xdp_port = self._gen_port() if server.xdp_port is not None else None
for server in self.servers: