diff options
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: |