diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-12 04:45:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-12 04:45:07 +0000 |
commit | 0335817ced71e8355806ea0445aa3b105a22364c (patch) | |
tree | dffe735f2668a4728d8567feaf7ccb2d73076bac /src | |
parent | Adding upstream version 3.3.9. (diff) | |
download | knot-0335817ced71e8355806ea0445aa3b105a22364c.tar.xz knot-0335817ced71e8355806ea0445aa3b105a22364c.zip |
Adding upstream version 3.4.0.upstream/3.4.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
190 files changed, 7212 insertions, 3754 deletions
diff --git a/src/Makefile.in b/src/Makefile.in index 09daaa2..8630517 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -160,28 +160,29 @@ host_triplet = @host@ @STATIC_MODULE_queryacl_TRUE@am__append_41 = $(knot_modules_queryacl_la_SOURCES) @SHARED_MODULE_queryacl_TRUE@am__append_42 = knot/modules/queryacl.la @STATIC_MODULE_rrl_TRUE@am__append_43 = $(knot_modules_rrl_la_SOURCES) -@SHARED_MODULE_rrl_TRUE@am__append_44 = knot/modules/rrl.la -@STATIC_MODULE_stats_TRUE@am__append_45 = $(knot_modules_stats_la_SOURCES) -@SHARED_MODULE_stats_TRUE@am__append_46 = knot/modules/stats.la -@STATIC_MODULE_synthrecord_TRUE@am__append_47 = $(knot_modules_synthrecord_la_SOURCES) -@SHARED_MODULE_synthrecord_TRUE@am__append_48 = knot/modules/synthrecord.la -@STATIC_MODULE_whoami_TRUE@am__append_49 = $(knot_modules_whoami_la_SOURCES) -@SHARED_MODULE_whoami_TRUE@am__append_50 = knot/modules/whoami.la +@STATIC_MODULE_rrl_TRUE@am__append_44 = $(math_LIBS) +@SHARED_MODULE_rrl_TRUE@am__append_45 = knot/modules/rrl.la +@STATIC_MODULE_stats_TRUE@am__append_46 = $(knot_modules_stats_la_SOURCES) +@SHARED_MODULE_stats_TRUE@am__append_47 = knot/modules/stats.la +@STATIC_MODULE_synthrecord_TRUE@am__append_48 = $(knot_modules_synthrecord_la_SOURCES) +@SHARED_MODULE_synthrecord_TRUE@am__append_49 = knot/modules/synthrecord.la +@STATIC_MODULE_whoami_TRUE@am__append_50 = $(knot_modules_whoami_la_SOURCES) +@SHARED_MODULE_whoami_TRUE@am__append_51 = knot/modules/whoami.la bin_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) sbin_PROGRAMS = $(am__EXEEXT_3) $(am__EXEEXT_4) $(am__EXEEXT_5) -@HAVE_LIBUTILS_TRUE@am__append_51 = libknotus.la -@EMBEDDED_LIBNGTCP2_TRUE@@HAVE_LIBUTILS_TRUE@am__append_52 = $(libembngtcp2_LIBS) -@HAVE_UTILS_TRUE@am__append_53 = kdig khost knsec3hash knsupdate -@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_54 = $(DNSTAP_CFLAGS) -@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_55 = $(libdnstap_LIBS) -@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_56 = $(DNSTAP_CFLAGS) -@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_57 = $(libdnstap_LIBS) -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_58 = kxdpgun -@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_59 = $(gnutls_CFLAGS) -@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_60 = $(gnutls_LIBS) -@HAVE_DAEMON_TRUE@am__append_61 = knotc knotd -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am__append_62 = kzonecheck kzonesign -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am__append_63 = keymgr kjournalprint kcatalogprint +@HAVE_LIBUTILS_TRUE@am__append_52 = libknotus.la +@EMBEDDED_LIBNGTCP2_TRUE@@HAVE_LIBUTILS_TRUE@am__append_53 = $(libembngtcp2_LIBS) +@HAVE_UTILS_TRUE@am__append_54 = kdig khost knsec3hash knsupdate +@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_55 = $(DNSTAP_CFLAGS) +@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_56 = $(libdnstap_LIBS) +@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_57 = $(DNSTAP_CFLAGS) +@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__append_58 = $(libdnstap_LIBS) +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_59 = kxdpgun +@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_60 = $(gnutls_CFLAGS) +@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__append_61 = $(gnutls_LIBS) +@HAVE_DAEMON_TRUE@am__append_62 = knotc knotd +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am__append_63 = kzonecheck kzonesign +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am__append_64 = keymgr kjournalprint kcatalogprint subdir = src SUBDIRS = ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -198,7 +199,8 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(include_libdnssec_HEADERS) \ $(include_libknotd_HEADERS) $(include_libzscanner_HEADERS) \ - $(am__nobase_include_libknot_HEADERS_DIST) $(am__DIST_COMMON) + $(am__nobase_include_libknot_HEADERS_DIST) $(noinst_HEADERS) \ + $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = knotd.pc libknot.pc libdnssec.pc libzscanner.pc @@ -359,9 +361,12 @@ knot_modules_queryacl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ @SHARED_MODULE_queryacl_TRUE@am_knot_modules_queryacl_la_rpath = \ @SHARED_MODULE_queryacl_TRUE@ -rpath $(pkglibdir) @SHARED_MODULE_rrl_TRUE@knot_modules_rrl_la_DEPENDENCIES = \ -@SHARED_MODULE_rrl_TRUE@ $(am__DEPENDENCIES_3) +@SHARED_MODULE_rrl_TRUE@ $(am__DEPENDENCIES_3) \ +@SHARED_MODULE_rrl_TRUE@ $(am__DEPENDENCIES_1) am_knot_modules_rrl_la_OBJECTS = knot/modules/rrl/la-rrl.lo \ - knot/modules/rrl/la-functions.lo + knot/modules/rrl/la-functions.lo \ + knot/modules/rrl/la-kru-generic.lo \ + knot/modules/rrl/la-kru-avx2.lo knot_modules_rrl_la_OBJECTS = $(am_knot_modules_rrl_la_OBJECTS) knot_modules_rrl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -627,17 +632,19 @@ am__libknot_la_SOURCES_DIST = libknot/codes.c \ libknot/descriptor.c libknot/dname.c libknot/error.c \ libknot/db/db_lmdb.c libknot/db/db_trie.c libknot/packet/pkt.c \ libknot/packet/rrset-wire.c libknot/probe/data.c \ - libknot/probe/probe.c libknot/rdataset.c libknot/rrset-dump.c \ - libknot/rrset.c libknot/rrtype/naptr.c libknot/rrtype/opt.c \ - libknot/rrtype/tsig.c libknot/tsig-op.c libknot/tsig.c \ - libknot/yparser/yparser.c libknot/yparser/ypbody.c \ - libknot/yparser/ypformat.c libknot/yparser/ypschema.c \ - libknot/yparser/yptrafo.c libknot/xdp/tcp_iobuf.c \ - libknot/xdp/bpf-kernel-obj.c libknot/xdp/bpf-kernel-obj.h \ - libknot/xdp/bpf-user.c libknot/xdp/bpf-user.h \ - libknot/xdp/eth.c libknot/xdp/msg_init.h \ - libknot/xdp/protocols.h libknot/xdp/tcp.c libknot/xdp/xdp.c \ - libknot/quic/quic.c libknot/quic/quic_conn.c + libknot/probe/probe.c libknot/quic/tls.c \ + libknot/quic/tls_common.c libknot/rdataset.c \ + libknot/rrset-dump.c libknot/rrset.c libknot/rrtype/naptr.c \ + libknot/rrtype/opt.c libknot/rrtype/tsig.c libknot/tsig-op.c \ + libknot/tsig.c libknot/yparser/yparser.c \ + libknot/yparser/ypbody.c libknot/yparser/ypformat.c \ + libknot/yparser/ypschema.c libknot/yparser/yptrafo.c \ + libknot/xdp/tcp_iobuf.c libknot/xdp/bpf-kernel-obj.c \ + libknot/xdp/bpf-kernel-obj.h libknot/xdp/bpf-user.c \ + libknot/xdp/bpf-user.h libknot/xdp/eth.c \ + libknot/xdp/msg_init.h libknot/xdp/protocols.h \ + libknot/xdp/tcp.c libknot/xdp/xdp.c libknot/quic/quic.c \ + libknot/quic/quic_conn.c @ENABLE_XDP_TRUE@am__objects_1 = libknot/xdp/la-bpf-kernel-obj.lo \ @ENABLE_XDP_TRUE@ libknot/xdp/la-bpf-user.lo \ @ENABLE_XDP_TRUE@ libknot/xdp/la-eth.lo libknot/xdp/la-tcp.lo \ @@ -650,7 +657,8 @@ am_libknot_la_OBJECTS = libknot/la-codes.lo \ libknot/la-error.lo libknot/db/la-db_lmdb.lo \ libknot/db/la-db_trie.lo libknot/packet/la-pkt.lo \ libknot/packet/la-rrset-wire.lo libknot/probe/la-data.lo \ - libknot/probe/la-probe.lo libknot/la-rdataset.lo \ + libknot/probe/la-probe.lo libknot/quic/la-tls.lo \ + libknot/quic/la-tls_common.lo libknot/la-rdataset.lo \ libknot/la-rrset-dump.lo libknot/la-rrset.lo \ libknot/rrtype/la-naptr.lo libknot/rrtype/la-opt.lo \ libknot/rrtype/la-tsig.lo libknot/la-tsig-op.lo \ @@ -665,10 +673,11 @@ libknot_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ @STATIC_MODULE_dnstap_TRUE@am__DEPENDENCIES_10 = \ @STATIC_MODULE_dnstap_TRUE@ $(am__DEPENDENCIES_4) @STATIC_MODULE_geoip_TRUE@am__DEPENDENCIES_11 = $(am__DEPENDENCIES_1) +@STATIC_MODULE_rrl_TRUE@am__DEPENDENCIES_12 = $(am__DEPENDENCIES_1) libknotd_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_7) $(am__DEPENDENCIES_10) \ - $(am__DEPENDENCIES_11) + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_7) \ + $(am__DEPENDENCIES_10) $(am__DEPENDENCIES_11) \ + $(am__DEPENDENCIES_12) am__libknotd_la_SOURCES_DIST = knot/catalog/catalog_db.c \ knot/catalog/catalog_db.h knot/catalog/catalog_update.c \ knot/catalog/catalog_update.h knot/catalog/generate.c \ @@ -705,15 +714,15 @@ am__libknotd_la_SOURCES_DIST = knot/catalog/catalog_db.c \ knot/events/handlers/flush.c \ knot/events/handlers/freeze_thaw.c knot/events/handlers/load.c \ knot/events/handlers/notify.c knot/events/handlers/refresh.c \ - knot/events/handlers/update.c knot/events/replan.c \ - knot/events/replan.h knot/nameserver/axfr.c \ - knot/nameserver/axfr.h knot/nameserver/chaos.c \ - knot/nameserver/chaos.h knot/nameserver/internet.c \ - knot/nameserver/internet.h knot/nameserver/ixfr.c \ - knot/nameserver/ixfr.h knot/nameserver/log.h \ - knot/nameserver/notify.c knot/nameserver/notify.h \ - knot/nameserver/nsec_proofs.c knot/nameserver/nsec_proofs.h \ - knot/nameserver/process_query.c \ + knot/events/handlers/update.c knot/events/handlers/validate.c \ + knot/events/replan.c knot/events/replan.h \ + knot/nameserver/axfr.c knot/nameserver/axfr.h \ + knot/nameserver/chaos.c knot/nameserver/chaos.h \ + knot/nameserver/internet.c knot/nameserver/internet.h \ + knot/nameserver/ixfr.c knot/nameserver/ixfr.h \ + knot/nameserver/log.h knot/nameserver/notify.c \ + knot/nameserver/notify.h knot/nameserver/nsec_proofs.c \ + knot/nameserver/nsec_proofs.h knot/nameserver/process_query.c \ knot/nameserver/process_query.h knot/nameserver/query_module.c \ knot/nameserver/query_module.h knot/nameserver/tsig_ctx.c \ knot/nameserver/tsig_ctx.h knot/nameserver/update.c \ @@ -721,14 +730,15 @@ am__libknotd_la_SOURCES_DIST = knot/catalog/catalog_db.c \ knot/nameserver/xfr.h knot/query/capture.c \ knot/query/capture.h knot/query/layer.h knot/query/query.c \ knot/query/query.h knot/query/requestor.c \ - knot/query/requestor.h knot/common/evsched.c \ - knot/common/evsched.h knot/common/fdset.c knot/common/fdset.h \ - knot/common/log.c knot/common/log.h knot/common/process.c \ - knot/common/process.h knot/common/stats.c knot/common/stats.h \ - knot/common/systemd.c knot/common/systemd.h \ - knot/common/unreachable.c knot/common/unreachable.h \ - knot/journal/journal_basic.c knot/journal/journal_basic.h \ - knot/journal/journal_metadata.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 knot/common/evsched.h \ + knot/common/fdset.c knot/common/fdset.h knot/common/log.c \ + knot/common/log.h knot/common/process.c knot/common/process.h \ + knot/common/stats.c knot/common/stats.h knot/common/systemd.c \ + knot/common/systemd.h knot/common/unreachable.c \ + knot/common/unreachable.h knot/journal/journal_basic.c \ + knot/journal/journal_basic.h knot/journal/journal_metadata.c \ knot/journal/journal_metadata.h knot/journal/journal_read.c \ knot/journal/journal_read.h knot/journal/journal_write.c \ knot/journal/journal_write.h knot/journal/knot_lmdb.c \ @@ -774,7 +784,8 @@ am__libknotd_la_SOURCES_DIST = knot/catalog/catalog_db.c \ knot/modules/onlinesign/nsec_next.h knot/modules/probe/probe.c \ knot/modules/queryacl/queryacl.c knot/modules/rrl/rrl.c \ knot/modules/rrl/functions.c knot/modules/rrl/functions.h \ - knot/modules/stats/stats.c \ + knot/modules/rrl/kru-generic.c knot/modules/rrl/kru-avx2.c \ + knot/modules/rrl/kru.h knot/modules/stats/stats.c \ knot/modules/synthrecord/synthrecord.c \ knot/modules/whoami/whoami.c @ENABLE_QUIC_TRUE@am__objects_3 = \ @@ -801,7 +812,9 @@ am__objects_18 = knot/modules/probe/libknotd_la-probe.lo am__objects_20 = knot/modules/queryacl/libknotd_la-queryacl.lo @STATIC_MODULE_queryacl_TRUE@am__objects_21 = $(am__objects_20) am__objects_22 = knot/modules/rrl/libknotd_la-rrl.lo \ - knot/modules/rrl/libknotd_la-functions.lo + knot/modules/rrl/libknotd_la-functions.lo \ + knot/modules/rrl/libknotd_la-kru-generic.lo \ + knot/modules/rrl/libknotd_la-kru-avx2.lo @STATIC_MODULE_rrl_TRUE@am__objects_23 = $(am__objects_22) am__objects_24 = knot/modules/stats/libknotd_la-stats.lo @STATIC_MODULE_stats_TRUE@am__objects_25 = $(am__objects_24) @@ -850,6 +863,7 @@ am_libknotd_la_OBJECTS = knot/catalog/libknotd_la-catalog_db.lo \ knot/events/handlers/libknotd_la-notify.lo \ knot/events/handlers/libknotd_la-refresh.lo \ knot/events/handlers/libknotd_la-update.lo \ + knot/events/handlers/libknotd_la-validate.lo \ knot/events/libknotd_la-replan.lo \ knot/nameserver/libknotd_la-axfr.lo \ knot/nameserver/libknotd_la-chaos.lo \ @@ -865,6 +879,8 @@ am_libknotd_la_OBJECTS = knot/catalog/libknotd_la-catalog_db.lo \ knot/query/libknotd_la-capture.lo \ knot/query/libknotd_la-query.lo \ knot/query/libknotd_la-requestor.lo \ + knot/query/libknotd_la-tls-requestor.lo \ + knot/common/libknotd_la-dbus.lo \ knot/common/libknotd_la-evsched.lo \ knot/common/libknotd_la-fdset.lo \ knot/common/libknotd_la-log.lo \ @@ -921,12 +937,11 @@ libknotd_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(libknotd_la_LDFLAGS) $(LDFLAGS) -o $@ @HAVE_DAEMON_TRUE@am_libknotd_la_rpath = -@EMBEDDED_LIBNGTCP2_TRUE@@HAVE_LIBUTILS_TRUE@am__DEPENDENCIES_12 = $(am__DEPENDENCIES_6) +@EMBEDDED_LIBNGTCP2_TRUE@@HAVE_LIBUTILS_TRUE@am__DEPENDENCIES_13 = $(am__DEPENDENCIES_6) @HAVE_LIBUTILS_TRUE@libknotus_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ @HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_1) \ @HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_1) \ -@HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_1) \ -@HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_12) +@HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_13) am__libknotus_la_SOURCES_DIST = utils/common/exec.c \ utils/common/exec.h utils/common/hex.c utils/common/hex.h \ utils/common/https.c utils/common/https.h \ @@ -972,17 +987,17 @@ libzscanner_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ am__kcatalogprint_SOURCES_DIST = utils/kcatalogprint/main.c @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am_kcatalogprint_OBJECTS = utils/kcatalogprint/kcatalogprint-main.$(OBJEXT) kcatalogprint_OBJECTS = $(am_kcatalogprint_OBJECTS) -am__DEPENDENCIES_13 = libknotd.la libknot.la libdnssec.la \ +am__DEPENDENCIES_14 = libknotd.la libknot.la libdnssec.la \ libzscanner.la $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) -@HAVE_LIBUTILS_TRUE@am__DEPENDENCIES_14 = libknotus.la libknot.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +@HAVE_LIBUTILS_TRUE@am__DEPENDENCIES_15 = libknotus.la libknot.la \ @HAVE_LIBUTILS_TRUE@ libdnssec.la $(am__DEPENDENCIES_3) \ @HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_1) \ @HAVE_LIBUTILS_TRUE@ $(am__DEPENDENCIES_1) @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@kcatalogprint_DEPENDENCIES = \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_13) \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) \ +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) kcatalogprint_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(kcatalogprint_LDFLAGS) $(LDFLAGS) -o $@ @@ -994,10 +1009,10 @@ am__kdig_SOURCES_DIST = utils/kdig/kdig_exec.c utils/kdig/kdig_exec.h \ @HAVE_UTILS_TRUE@ utils/kdig/kdig-kdig_main.$(OBJEXT) \ @HAVE_UTILS_TRUE@ utils/kdig/kdig-kdig_params.$(OBJEXT) kdig_OBJECTS = $(am_kdig_OBJECTS) -@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__DEPENDENCIES_15 = \ +@HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@am__DEPENDENCIES_16 = \ @HAVE_DNSTAP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_4) -@HAVE_UTILS_TRUE@kdig_DEPENDENCIES = $(am__DEPENDENCIES_14) \ -@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) +@HAVE_UTILS_TRUE@kdig_DEPENDENCIES = $(am__DEPENDENCIES_15) \ +@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_16) am__keymgr_SOURCES_DIST = utils/keymgr/bind_privkey.c \ utils/keymgr/bind_privkey.h utils/keymgr/functions.c \ utils/keymgr/functions.h utils/keymgr/keystore.c \ @@ -1010,8 +1025,8 @@ am__keymgr_SOURCES_DIST = utils/keymgr/bind_privkey.c \ @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ utils/keymgr/keymgr-main.$(OBJEXT) keymgr_OBJECTS = $(am_keymgr_OBJECTS) @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@keymgr_DEPENDENCIES = \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_13) \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) \ +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) keymgr_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(keymgr_LDFLAGS) $(LDFLAGS) -o $@ @@ -1025,14 +1040,14 @@ am__khost_SOURCES_DIST = utils/kdig/kdig_exec.c utils/kdig/kdig_exec.h \ @HAVE_UTILS_TRUE@ utils/khost/khost-khost_main.$(OBJEXT) \ @HAVE_UTILS_TRUE@ utils/khost/khost-khost_params.$(OBJEXT) khost_OBJECTS = $(am_khost_OBJECTS) -@HAVE_UTILS_TRUE@khost_DEPENDENCIES = $(am__DEPENDENCIES_14) \ -@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) +@HAVE_UTILS_TRUE@khost_DEPENDENCIES = $(am__DEPENDENCIES_15) \ +@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_16) am__kjournalprint_SOURCES_DIST = utils/kjournalprint/main.c @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am_kjournalprint_OBJECTS = utils/kjournalprint/kjournalprint-main.$(OBJEXT) kjournalprint_OBJECTS = $(am_kjournalprint_OBJECTS) @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@kjournalprint_DEPENDENCIES = \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_13) \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) \ +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) kjournalprint_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(kjournalprint_LDFLAGS) $(LDFLAGS) -o $@ @@ -1045,8 +1060,8 @@ am__knotc_SOURCES_DIST = utils/knotc/commands.c utils/knotc/commands.h \ @HAVE_DAEMON_TRUE@ utils/knotc/knotc-process.$(OBJEXT) \ @HAVE_DAEMON_TRUE@ utils/knotc/knotc-main.$(OBJEXT) knotc_OBJECTS = $(am_knotc_OBJECTS) -@HAVE_DAEMON_TRUE@knotc_DEPENDENCIES = $(am__DEPENDENCIES_13) \ -@HAVE_DAEMON_TRUE@ $(am__DEPENDENCIES_14) +@HAVE_DAEMON_TRUE@knotc_DEPENDENCIES = $(am__DEPENDENCIES_14) \ +@HAVE_DAEMON_TRUE@ $(am__DEPENDENCIES_15) knotc_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(knotc_LDFLAGS) $(LDFLAGS) -o $@ @@ -1054,7 +1069,7 @@ am__knotd_SOURCES_DIST = utils/knotd/main.c @HAVE_DAEMON_TRUE@am_knotd_OBJECTS = utils/knotd/knotd-main.$(OBJEXT) knotd_OBJECTS = $(am_knotd_OBJECTS) @HAVE_DAEMON_TRUE@knotd_DEPENDENCIES = $(am__DEPENDENCIES_1) \ -@HAVE_DAEMON_TRUE@ $(am__DEPENDENCIES_13) $(am__DEPENDENCIES_1) +@HAVE_DAEMON_TRUE@ $(am__DEPENDENCIES_14) $(am__DEPENDENCIES_1) knotd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(knotd_LDFLAGS) $(LDFLAGS) -o $@ @@ -1075,28 +1090,31 @@ am__knsupdate_SOURCES_DIST = utils/knsupdate/knsupdate_exec.c \ @HAVE_UTILS_TRUE@ utils/knsupdate/knsupdate-knsupdate_main.$(OBJEXT) \ @HAVE_UTILS_TRUE@ utils/knsupdate/knsupdate-knsupdate_params.$(OBJEXT) knsupdate_OBJECTS = $(am_knsupdate_OBJECTS) -@HAVE_UTILS_TRUE@knsupdate_DEPENDENCIES = $(am__DEPENDENCIES_14) \ +@HAVE_UTILS_TRUE@knsupdate_DEPENDENCIES = $(am__DEPENDENCIES_15) \ @HAVE_UTILS_TRUE@ libzscanner.la am__kxdpgun_SOURCES_DIST = utils/kxdpgun/ip_route.c \ utils/kxdpgun/ip_route.h utils/kxdpgun/load_queries.c \ - utils/kxdpgun/load_queries.h utils/kxdpgun/main.c + utils/kxdpgun/load_queries.h utils/kxdpgun/main.c \ + utils/kxdpgun/main.h utils/kxdpgun/stats.c \ + utils/kxdpgun/stats.h @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am_kxdpgun_OBJECTS = utils/kxdpgun/kxdpgun-ip_route.$(OBJEXT) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/kxdpgun-load_queries.$(OBJEXT) \ -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/kxdpgun-main.$(OBJEXT) +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/kxdpgun-main.$(OBJEXT) \ +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/kxdpgun-stats.$(OBJEXT) kxdpgun_OBJECTS = $(am_kxdpgun_OBJECTS) -@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__DEPENDENCIES_16 = $(am__DEPENDENCIES_1) +@ENABLE_QUIC_TRUE@@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@am__DEPENDENCIES_17 = $(am__DEPENDENCIES_1) @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@kxdpgun_DEPENDENCIES = libknot.la \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_3) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_1) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_1) \ -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_16) +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_17) am__kzonecheck_SOURCES_DIST = utils/kzonecheck/main.c \ utils/kzonecheck/zone_check.c utils/kzonecheck/zone_check.h @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am_kzonecheck_OBJECTS = utils/kzonecheck/kzonecheck-main.$(OBJEXT) \ @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ utils/kzonecheck/kzonecheck-zone_check.$(OBJEXT) kzonecheck_OBJECTS = $(am_kzonecheck_OBJECTS) @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@kzonecheck_DEPENDENCIES = \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_13) +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) kzonecheck_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(kzonecheck_LDFLAGS) $(LDFLAGS) -o $@ @@ -1104,8 +1122,8 @@ am__kzonesign_SOURCES_DIST = utils/kzonesign/main.c @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@am_kzonesign_OBJECTS = utils/kzonesign/kzonesign-main.$(OBJEXT) kzonesign_OBJECTS = $(am_kzonesign_OBJECTS) @HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@kzonesign_DEPENDENCIES = \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_13) \ -@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_14) \ +@HAVE_DAEMON_TRUE@@HAVE_UTILS_TRUE@ $(am__DEPENDENCIES_15) kzonesign_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(kzonesign_LDFLAGS) $(LDFLAGS) -o $@ @@ -1200,6 +1218,7 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ knot/catalog/$(DEPDIR)/libknotd_la-catalog_update.Plo \ knot/catalog/$(DEPDIR)/libknotd_la-generate.Plo \ knot/catalog/$(DEPDIR)/libknotd_la-interpret.Plo \ + knot/common/$(DEPDIR)/libknotd_la-dbus.Plo \ knot/common/$(DEPDIR)/libknotd_la-evsched.Plo \ knot/common/$(DEPDIR)/libknotd_la-fdset.Plo \ knot/common/$(DEPDIR)/libknotd_la-log.Plo \ @@ -1247,6 +1266,7 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ knot/events/handlers/$(DEPDIR)/libknotd_la-notify.Plo \ knot/events/handlers/$(DEPDIR)/libknotd_la-refresh.Plo \ knot/events/handlers/$(DEPDIR)/libknotd_la-update.Plo \ + knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Plo \ knot/journal/$(DEPDIR)/libknotd_la-journal_basic.Plo \ knot/journal/$(DEPDIR)/libknotd_la-journal_metadata.Plo \ knot/journal/$(DEPDIR)/libknotd_la-journal_read.Plo \ @@ -1276,8 +1296,12 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ knot/modules/queryacl/$(DEPDIR)/la-queryacl.Plo \ knot/modules/queryacl/$(DEPDIR)/libknotd_la-queryacl.Plo \ knot/modules/rrl/$(DEPDIR)/la-functions.Plo \ + knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Plo \ + knot/modules/rrl/$(DEPDIR)/la-kru-generic.Plo \ knot/modules/rrl/$(DEPDIR)/la-rrl.Plo \ knot/modules/rrl/$(DEPDIR)/libknotd_la-functions.Plo \ + knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Plo \ + knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Plo \ knot/modules/rrl/$(DEPDIR)/libknotd_la-rrl.Plo \ knot/modules/stats/$(DEPDIR)/la-stats.Plo \ knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Plo \ @@ -1300,6 +1324,7 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ knot/query/$(DEPDIR)/libknotd_la-query.Plo \ knot/query/$(DEPDIR)/libknotd_la-quic-requestor.Plo \ knot/query/$(DEPDIR)/libknotd_la-requestor.Plo \ + knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Plo \ knot/server/$(DEPDIR)/libknotd_la-dthreads.Plo \ knot/server/$(DEPDIR)/libknotd_la-handler.Plo \ knot/server/$(DEPDIR)/libknotd_la-proxyv2.Plo \ @@ -1380,6 +1405,8 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ libknot/probe/$(DEPDIR)/la-probe.Plo \ libknot/quic/$(DEPDIR)/la-quic.Plo \ libknot/quic/$(DEPDIR)/la-quic_conn.Plo \ + libknot/quic/$(DEPDIR)/la-tls.Plo \ + libknot/quic/$(DEPDIR)/la-tls_common.Plo \ libknot/rrtype/$(DEPDIR)/la-naptr.Plo \ libknot/rrtype/$(DEPDIR)/la-opt.Plo \ libknot/rrtype/$(DEPDIR)/la-tsig.Plo \ @@ -1438,6 +1465,7 @@ am__depfiles_remade = contrib/$(DEPDIR)/libcontrib_la-base32hex.Plo \ utils/kxdpgun/$(DEPDIR)/kxdpgun-ip_route.Po \ utils/kxdpgun/$(DEPDIR)/kxdpgun-load_queries.Po \ utils/kxdpgun/$(DEPDIR)/kxdpgun-main.Po \ + utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po \ utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Po \ utils/kzonecheck/$(DEPDIR)/kzonecheck-zone_check.Po \ utils/kzonesign/$(DEPDIR)/kzonesign-main.Po @@ -1528,7 +1556,8 @@ am__nobase_include_libknot_HEADERS_DIST = libknot/attribute.h \ libknot/db/db_trie.h libknot/packet/compr.h \ libknot/packet/pkt.h libknot/packet/rrset-wire.h \ libknot/packet/wire.h libknot/probe/data.h \ - libknot/probe/probe.h libknot/rdata.h libknot/rdataset.h \ + libknot/probe/probe.h libknot/quic/tls.h \ + libknot/quic/tls_common.h libknot/rdata.h libknot/rdataset.h \ libknot/rrset-dump.h libknot/rrset.h libknot/rrtype/dnskey.h \ libknot/rrtype/ds.h libknot/rrtype/naptr.h \ libknot/rrtype/nsec.h libknot/rrtype/nsec3.h \ @@ -1545,7 +1574,7 @@ am__nobase_include_libknot_HEADERS_DIST = libknot/attribute.h \ libknot/quic/quic_conn.h HEADERS = $(include_libdnssec_HEADERS) $(include_libknotd_HEADERS) \ $(include_libzscanner_HEADERS) \ - $(nobase_include_libknot_HEADERS) + $(nobase_include_libknot_HEADERS) $(noinst_HEADERS) RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive am__recursive_targets = \ @@ -1751,6 +1780,8 @@ infodir = @infodir@ install_sh = @install_sh@ libbpf_CFLAGS = @libbpf_CFLAGS@ libbpf_LIBS = @libbpf_LIBS@ +libdbus_CFLAGS = @libdbus_CFLAGS@ +libdbus_LIBS = @libdbus_LIBS@ libdir = @libdir@ libdnssec_SONAME = @libdnssec_SONAME@ libdnssec_SOVERSION = @libdnssec_SOVERSION@ @@ -1762,8 +1793,6 @@ libfstrm_CFLAGS = @libfstrm_CFLAGS@ libfstrm_LIBS = @libfstrm_LIBS@ libidn2_CFLAGS = @libidn2_CFLAGS@ libidn2_LIBS = @libidn2_LIBS@ -libidn_CFLAGS = @libidn_CFLAGS@ -libidn_LIBS = @libidn_LIBS@ libknot_SONAME = @libknot_SONAME@ libknot_SOVERSION = @libknot_SOVERSION@ libknot_VERSION_INFO = @libknot_VERSION_INFO@ @@ -1781,7 +1810,6 @@ libprotobuf_c_CFLAGS = @libprotobuf_c_CFLAGS@ libprotobuf_c_LIBS = @libprotobuf_c_LIBS@ liburcu_CFLAGS = @liburcu_CFLAGS@ liburcu_LIBS = @liburcu_LIBS@ -liburcu_PKGCONFIG = @liburcu_PKGCONFIG@ libxdp_CFLAGS = @libxdp_CFLAGS@ libxdp_LIBS = @libxdp_LIBS@ libzscanner_SONAME = @libzscanner_SONAME@ @@ -1849,7 +1877,7 @@ CLEANFILES = $(am__append_5) libzscanner/scanner.c BUILT_SOURCES = $(am__append_4) libzscanner/scanner.c lib_LTLIBRARIES = libdnssec.la libknot.la libzscanner.la noinst_LTLIBRARIES = libcontrib.la $(am__append_3) $(am__append_6) \ - $(am__append_19) $(am__append_51) + $(am__append_19) $(am__append_52) pkgconfig_DATA = libdnssec.pc libknot.pc libzscanner.pc \ $(am__append_20) libcontrib_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) \ @@ -1859,6 +1887,7 @@ libcontrib_la_LIBADD = $(pthread_LIBS) libcontrib_LIBS = libcontrib.la $(am__append_2) libcontrib_la_SOURCES = \ contrib/asan.h \ + contrib/atomic.h \ contrib/base32hex.c \ contrib/base32hex.h \ contrib/base64.c \ @@ -2121,25 +2150,26 @@ nobase_include_libknot_HEADERS = libknot/attribute.h libknot/codes.h \ libknot/db/db.h libknot/db/db_lmdb.h libknot/db/db_trie.h \ libknot/packet/compr.h libknot/packet/pkt.h \ libknot/packet/rrset-wire.h libknot/packet/wire.h \ - libknot/probe/data.h libknot/probe/probe.h libknot/rdata.h \ - libknot/rdataset.h libknot/rrset-dump.h libknot/rrset.h \ - libknot/rrtype/dnskey.h libknot/rrtype/ds.h \ - libknot/rrtype/naptr.h libknot/rrtype/nsec.h \ - libknot/rrtype/nsec3.h libknot/rrtype/nsec3param.h \ - libknot/rrtype/opt.h libknot/rrtype/rdname.h \ - libknot/rrtype/rrsig.h libknot/rrtype/soa.h \ - libknot/rrtype/svcb.h libknot/rrtype/tsig.h \ - libknot/rrtype/zonemd.h libknot/tsig-op.h libknot/tsig.h \ - libknot/wire.h libknot/yparser/yparser.h \ - libknot/yparser/ypformat.h libknot/yparser/ypschema.h \ - libknot/yparser/yptrafo.h libknot/version.h \ - libknot/xdp/tcp_iobuf.h libknot/xdp.h $(am__append_11) \ - $(am__append_15) + libknot/probe/data.h libknot/probe/probe.h libknot/quic/tls.h \ + libknot/quic/tls_common.h libknot/rdata.h libknot/rdataset.h \ + libknot/rrset-dump.h libknot/rrset.h libknot/rrtype/dnskey.h \ + libknot/rrtype/ds.h libknot/rrtype/naptr.h \ + libknot/rrtype/nsec.h libknot/rrtype/nsec3.h \ + libknot/rrtype/nsec3param.h libknot/rrtype/opt.h \ + libknot/rrtype/rdname.h libknot/rrtype/rrsig.h \ + libknot/rrtype/soa.h libknot/rrtype/svcb.h \ + libknot/rrtype/tsig.h libknot/rrtype/zonemd.h \ + libknot/tsig-op.h libknot/tsig.h libknot/wire.h \ + libknot/yparser/yparser.h libknot/yparser/ypformat.h \ + libknot/yparser/ypschema.h libknot/yparser/yptrafo.h \ + libknot/version.h libknot/xdp/tcp_iobuf.h libknot/xdp.h \ + $(am__append_11) $(am__append_15) libknot_la_SOURCES = libknot/codes.c libknot/control/control.c \ libknot/cookies.c libknot/descriptor.c libknot/dname.c \ libknot/error.c libknot/db/db_lmdb.c libknot/db/db_trie.c \ libknot/packet/pkt.c libknot/packet/rrset-wire.c \ - libknot/probe/data.c libknot/probe/probe.c libknot/rdataset.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 libknot/rrset.c libknot/rrtype/naptr.c \ libknot/rrtype/opt.c libknot/rrtype/tsig.c libknot/tsig-op.c \ libknot/tsig.c libknot/yparser/yparser.c \ @@ -2169,15 +2199,15 @@ nodist_libzscanner_la_SOURCES = \ libknotd_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) \ $(libkqueue_CFLAGS) $(liburcu_CFLAGS) $(lmdb_CFLAGS) \ - $(systemd_CFLAGS) $(gnutls_CFLAGS) $(libngtcp2_CFLAGS) \ + $(systemd_CFLAGS) $(libdbus_CFLAGS) $(gnutls_CFLAGS) \ -DKNOTD_MOD_STATIC $(am__append_28) $(am__append_32) libknotd_la_LDFLAGS = $(AM_LDFLAGS) -export-symbols-regex '^knotd_' libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS) \ - $(libngtcp2_LIBS) $(am__append_17) $(am__append_29) \ - $(am__append_33) + $(am__append_17) $(am__append_29) $(am__append_33) \ + $(am__append_44) libknotd_LIBS = libknotd.la libknot.la libdnssec.la libzscanner.la \ $(libcontrib_LIBS) $(liburcu_LIBS) $(lmdb_LIBS) \ - $(systemd_LIBS) $(gnutls_LIBS) + $(systemd_LIBS) $(libdbus_LIBS) $(gnutls_LIBS) include_libknotddir = $(includedir)/knot include_libknotd_HEADERS = \ @@ -2219,15 +2249,15 @@ libknotd_la_SOURCES = knot/catalog/catalog_db.c \ knot/events/handlers/flush.c \ knot/events/handlers/freeze_thaw.c knot/events/handlers/load.c \ knot/events/handlers/notify.c knot/events/handlers/refresh.c \ - knot/events/handlers/update.c knot/events/replan.c \ - knot/events/replan.h knot/nameserver/axfr.c \ - knot/nameserver/axfr.h knot/nameserver/chaos.c \ - knot/nameserver/chaos.h knot/nameserver/internet.c \ - knot/nameserver/internet.h knot/nameserver/ixfr.c \ - knot/nameserver/ixfr.h knot/nameserver/log.h \ - knot/nameserver/notify.c knot/nameserver/notify.h \ - knot/nameserver/nsec_proofs.c knot/nameserver/nsec_proofs.h \ - knot/nameserver/process_query.c \ + knot/events/handlers/update.c knot/events/handlers/validate.c \ + knot/events/replan.c knot/events/replan.h \ + knot/nameserver/axfr.c knot/nameserver/axfr.h \ + knot/nameserver/chaos.c knot/nameserver/chaos.h \ + knot/nameserver/internet.c knot/nameserver/internet.h \ + knot/nameserver/ixfr.c knot/nameserver/ixfr.h \ + knot/nameserver/log.h knot/nameserver/notify.c \ + knot/nameserver/notify.h knot/nameserver/nsec_proofs.c \ + knot/nameserver/nsec_proofs.h knot/nameserver/process_query.c \ knot/nameserver/process_query.h knot/nameserver/query_module.c \ knot/nameserver/query_module.h knot/nameserver/tsig_ctx.c \ knot/nameserver/tsig_ctx.h knot/nameserver/update.c \ @@ -2235,14 +2265,15 @@ libknotd_la_SOURCES = knot/catalog/catalog_db.c \ knot/nameserver/xfr.h knot/query/capture.c \ knot/query/capture.h knot/query/layer.h knot/query/query.c \ knot/query/query.h knot/query/requestor.c \ - knot/query/requestor.h knot/common/evsched.c \ - knot/common/evsched.h knot/common/fdset.c knot/common/fdset.h \ - knot/common/log.c knot/common/log.h knot/common/process.c \ - knot/common/process.h knot/common/stats.c knot/common/stats.h \ - knot/common/systemd.c knot/common/systemd.h \ - knot/common/unreachable.c knot/common/unreachable.h \ - knot/journal/journal_basic.c knot/journal/journal_basic.h \ - knot/journal/journal_metadata.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 knot/common/evsched.h \ + knot/common/fdset.c knot/common/fdset.h knot/common/log.c \ + knot/common/log.h knot/common/process.c knot/common/process.h \ + knot/common/stats.c knot/common/stats.h knot/common/systemd.c \ + knot/common/systemd.h knot/common/unreachable.c \ + knot/common/unreachable.h knot/journal/journal_basic.c \ + knot/journal/journal_basic.h knot/journal/journal_metadata.c \ knot/journal/journal_metadata.h knot/journal/journal_read.c \ knot/journal/journal_read.h knot/journal/journal_write.c \ knot/journal/journal_write.h knot/journal/knot_lmdb.c \ @@ -2279,15 +2310,15 @@ libknotd_la_SOURCES = knot/catalog/catalog_db.c \ $(am__append_21) $(am__append_23) $(am__append_25) \ $(am__append_27) $(am__append_31) $(am__append_35) \ $(am__append_37) $(am__append_39) $(am__append_41) \ - $(am__append_43) $(am__append_45) $(am__append_47) \ - $(am__append_49) + $(am__append_43) $(am__append_46) $(am__append_48) \ + $(am__append_50) KNOTD_MOD_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) KNOTD_MOD_LDFLAGS = $(AM_LDFLAGS) -module -shared -avoid-version pkglib_LTLIBRARIES = $(am__append_22) $(am__append_24) \ $(am__append_26) $(am__append_30) $(am__append_34) \ $(am__append_36) $(am__append_38) $(am__append_40) \ - $(am__append_42) $(am__append_44) $(am__append_46) \ - $(am__append_48) $(am__append_50) + $(am__append_42) $(am__append_45) $(am__append_47) \ + $(am__append_49) $(am__append_51) knot_modules_authsignal_la_SOURCES = knot/modules/authsignal/authsignal.c @SHARED_MODULE_authsignal_TRUE@knot_modules_authsignal_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) @SHARED_MODULE_authsignal_TRUE@knot_modules_authsignal_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) @@ -2328,11 +2359,15 @@ knot_modules_queryacl_la_SOURCES = knot/modules/queryacl/queryacl.c @SHARED_MODULE_queryacl_TRUE@knot_modules_queryacl_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) knot_modules_rrl_la_SOURCES = knot/modules/rrl/rrl.c \ knot/modules/rrl/functions.c \ - knot/modules/rrl/functions.h + knot/modules/rrl/functions.h \ + knot/modules/rrl/kru-generic.c \ + knot/modules/rrl/kru-avx2.c \ + knot/modules/rrl/kru.h +noinst_HEADERS = knot/modules/rrl/kru.inc.c @SHARED_MODULE_rrl_TRUE@knot_modules_rrl_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) @SHARED_MODULE_rrl_TRUE@knot_modules_rrl_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) -@SHARED_MODULE_rrl_TRUE@knot_modules_rrl_la_LIBADD = $(libcontrib_LIBS) +@SHARED_MODULE_rrl_TRUE@knot_modules_rrl_la_LIBADD = $(libcontrib_LIBS) $(math_LIBS) knot_modules_stats_la_SOURCES = knot/modules/stats/stats.c @SHARED_MODULE_stats_TRUE@knot_modules_stats_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) @SHARED_MODULE_stats_TRUE@knot_modules_stats_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) @@ -2353,7 +2388,7 @@ knot_modules_whoami_la_SOURCES = knot/modules/whoami/whoami.c @HAVE_LIBUTILS_TRUE@libknotus_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAG_EXCLUDE_LIBS) @HAVE_LIBUTILS_TRUE@libknotus_la_LIBADD = $(libidn2_LIBS) \ @HAVE_LIBUTILS_TRUE@ $(libidn_LIBS) $(libnghttp2_LIBS) \ -@HAVE_LIBUTILS_TRUE@ $(libngtcp2_LIBS) $(am__append_52) +@HAVE_LIBUTILS_TRUE@ $(libngtcp2_LIBS) $(am__append_53) @HAVE_LIBUTILS_TRUE@libknotus_LIBS = libknotus.la libknot.la libdnssec.la $(libcontrib_LIBS) \ @HAVE_LIBUTILS_TRUE@ $(gnutls_LIBS) $(libedit_LIBS) @@ -2416,11 +2451,11 @@ knot_modules_whoami_la_SOURCES = knot/modules/whoami/whoami.c @HAVE_UTILS_TRUE@ utils/knsupdate/knsupdate_params.h @HAVE_UTILS_TRUE@kdig_CPPFLAGS = $(libknotus_la_CPPFLAGS) \ -@HAVE_UTILS_TRUE@ $(am__append_54) -@HAVE_UTILS_TRUE@kdig_LDADD = $(libknotus_LIBS) $(am__append_55) +@HAVE_UTILS_TRUE@ $(am__append_55) +@HAVE_UTILS_TRUE@kdig_LDADD = $(libknotus_LIBS) $(am__append_56) @HAVE_UTILS_TRUE@khost_CPPFLAGS = $(libknotus_la_CPPFLAGS) \ -@HAVE_UTILS_TRUE@ $(am__append_56) -@HAVE_UTILS_TRUE@khost_LDADD = $(libknotus_LIBS) $(am__append_57) +@HAVE_UTILS_TRUE@ $(am__append_57) +@HAVE_UTILS_TRUE@khost_LDADD = $(libknotus_LIBS) $(am__append_58) @HAVE_UTILS_TRUE@knsec3hash_CPPFLAGS = $(libknotus_la_CPPFLAGS) @HAVE_UTILS_TRUE@knsec3hash_LDADD = libknot.la libdnssec.la $(libcontrib_LIBS) @HAVE_UTILS_TRUE@knsupdate_CPPFLAGS = $(libknotus_la_CPPFLAGS) @@ -2430,17 +2465,20 @@ knot_modules_whoami_la_SOURCES = knot/modules/whoami/whoami.c @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/ip_route.h \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/load_queries.c \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/load_queries.h \ -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/main.c +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/main.c \ +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/main.h \ +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/stats.c \ +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ utils/kxdpgun/stats.h @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@kxdpgun_CPPFLAGS = \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(libknotus_la_CPPFLAGS) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(libmnl_CFLAGS) \ -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__append_59) +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__append_60) @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@kxdpgun_LDADD = libknot.la \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(libcontrib_LIBS) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(libmnl_LIBS) \ @ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(pthread_LIBS) \ -@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__append_60) +@ENABLE_XDP_TRUE@@HAVE_UTILS_TRUE@ $(am__append_61) @HAVE_DAEMON_TRUE@knotc_SOURCES = \ @HAVE_DAEMON_TRUE@ utils/knotc/commands.c \ @HAVE_DAEMON_TRUE@ utils/knotc/commands.h \ @@ -2890,6 +2928,10 @@ knot/modules/rrl/la-rrl.lo: knot/modules/rrl/$(am__dirstamp) \ knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) knot/modules/rrl/la-functions.lo: knot/modules/rrl/$(am__dirstamp) \ knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) +knot/modules/rrl/la-kru-generic.lo: knot/modules/rrl/$(am__dirstamp) \ + knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) +knot/modules/rrl/la-kru-avx2.lo: knot/modules/rrl/$(am__dirstamp) \ + knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) knot/modules/rrl.la: $(knot_modules_rrl_la_OBJECTS) $(knot_modules_rrl_la_DEPENDENCIES) $(EXTRA_knot_modules_rrl_la_DEPENDENCIES) knot/modules/$(am__dirstamp) $(AM_V_CCLD)$(knot_modules_rrl_la_LINK) $(am_knot_modules_rrl_la_rpath) $(knot_modules_rrl_la_OBJECTS) $(knot_modules_rrl_la_LIBADD) $(LIBS) @@ -3352,6 +3394,16 @@ libknot/probe/la-data.lo: libknot/probe/$(am__dirstamp) \ libknot/probe/$(DEPDIR)/$(am__dirstamp) libknot/probe/la-probe.lo: libknot/probe/$(am__dirstamp) \ libknot/probe/$(DEPDIR)/$(am__dirstamp) +libknot/quic/$(am__dirstamp): + @$(MKDIR_P) libknot/quic + @: > libknot/quic/$(am__dirstamp) +libknot/quic/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) libknot/quic/$(DEPDIR) + @: > libknot/quic/$(DEPDIR)/$(am__dirstamp) +libknot/quic/la-tls.lo: libknot/quic/$(am__dirstamp) \ + libknot/quic/$(DEPDIR)/$(am__dirstamp) +libknot/quic/la-tls_common.lo: libknot/quic/$(am__dirstamp) \ + libknot/quic/$(DEPDIR)/$(am__dirstamp) libknot/la-rdataset.lo: libknot/$(am__dirstamp) \ libknot/$(DEPDIR)/$(am__dirstamp) libknot/la-rrset-dump.lo: libknot/$(am__dirstamp) \ @@ -3408,12 +3460,6 @@ libknot/xdp/la-tcp.lo: libknot/xdp/$(am__dirstamp) \ libknot/xdp/$(DEPDIR)/$(am__dirstamp) libknot/xdp/la-xdp.lo: libknot/xdp/$(am__dirstamp) \ libknot/xdp/$(DEPDIR)/$(am__dirstamp) -libknot/quic/$(am__dirstamp): - @$(MKDIR_P) libknot/quic - @: > libknot/quic/$(am__dirstamp) -libknot/quic/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) libknot/quic/$(DEPDIR) - @: > libknot/quic/$(DEPDIR)/$(am__dirstamp) libknot/quic/la-quic.lo: libknot/quic/$(am__dirstamp) \ libknot/quic/$(DEPDIR)/$(am__dirstamp) libknot/quic/la-quic_conn.lo: libknot/quic/$(am__dirstamp) \ @@ -3566,6 +3612,9 @@ knot/events/handlers/libknotd_la-refresh.lo: \ knot/events/handlers/libknotd_la-update.lo: \ knot/events/handlers/$(am__dirstamp) \ knot/events/handlers/$(DEPDIR)/$(am__dirstamp) +knot/events/handlers/libknotd_la-validate.lo: \ + knot/events/handlers/$(am__dirstamp) \ + knot/events/handlers/$(DEPDIR)/$(am__dirstamp) knot/events/libknotd_la-replan.lo: knot/events/$(am__dirstamp) \ knot/events/$(DEPDIR)/$(am__dirstamp) knot/nameserver/$(am__dirstamp): @@ -3615,12 +3664,16 @@ knot/query/libknotd_la-query.lo: knot/query/$(am__dirstamp) \ knot/query/$(DEPDIR)/$(am__dirstamp) knot/query/libknotd_la-requestor.lo: knot/query/$(am__dirstamp) \ knot/query/$(DEPDIR)/$(am__dirstamp) +knot/query/libknotd_la-tls-requestor.lo: knot/query/$(am__dirstamp) \ + knot/query/$(DEPDIR)/$(am__dirstamp) knot/common/$(am__dirstamp): @$(MKDIR_P) knot/common @: > knot/common/$(am__dirstamp) knot/common/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) knot/common/$(DEPDIR) @: > knot/common/$(DEPDIR)/$(am__dirstamp) +knot/common/libknotd_la-dbus.lo: knot/common/$(am__dirstamp) \ + knot/common/$(DEPDIR)/$(am__dirstamp) knot/common/libknotd_la-evsched.lo: knot/common/$(am__dirstamp) \ knot/common/$(DEPDIR)/$(am__dirstamp) knot/common/libknotd_la-fdset.lo: knot/common/$(am__dirstamp) \ @@ -3792,6 +3845,12 @@ knot/modules/rrl/libknotd_la-rrl.lo: knot/modules/rrl/$(am__dirstamp) \ knot/modules/rrl/libknotd_la-functions.lo: \ knot/modules/rrl/$(am__dirstamp) \ knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) +knot/modules/rrl/libknotd_la-kru-generic.lo: \ + knot/modules/rrl/$(am__dirstamp) \ + knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) +knot/modules/rrl/libknotd_la-kru-avx2.lo: \ + knot/modules/rrl/$(am__dirstamp) \ + knot/modules/rrl/$(DEPDIR)/$(am__dirstamp) knot/modules/stats/libknotd_la-stats.lo: \ knot/modules/stats/$(am__dirstamp) \ knot/modules/stats/$(DEPDIR)/$(am__dirstamp) @@ -4017,6 +4076,8 @@ utils/kxdpgun/kxdpgun-load_queries.$(OBJEXT): \ utils/kxdpgun/$(DEPDIR)/$(am__dirstamp) utils/kxdpgun/kxdpgun-main.$(OBJEXT): utils/kxdpgun/$(am__dirstamp) \ utils/kxdpgun/$(DEPDIR)/$(am__dirstamp) +utils/kxdpgun/kxdpgun-stats.$(OBJEXT): utils/kxdpgun/$(am__dirstamp) \ + utils/kxdpgun/$(DEPDIR)/$(am__dirstamp) kxdpgun$(EXEEXT): $(kxdpgun_OBJECTS) $(kxdpgun_DEPENDENCIES) $(EXTRA_kxdpgun_DEPENDENCIES) @rm -f kxdpgun$(EXEEXT) @@ -4259,6 +4320,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@knot/catalog/$(DEPDIR)/libknotd_la-catalog_update.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/catalog/$(DEPDIR)/libknotd_la-generate.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/catalog/$(DEPDIR)/libknotd_la-interpret.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/common/$(DEPDIR)/libknotd_la-dbus.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/common/$(DEPDIR)/libknotd_la-evsched.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/common/$(DEPDIR)/libknotd_la-fdset.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/common/$(DEPDIR)/libknotd_la-log.Plo@am__quote@ # am--include-marker @@ -4306,6 +4368,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@knot/events/handlers/$(DEPDIR)/libknotd_la-notify.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/events/handlers/$(DEPDIR)/libknotd_la-refresh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/events/handlers/$(DEPDIR)/libknotd_la-update.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/journal/$(DEPDIR)/libknotd_la-journal_basic.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/journal/$(DEPDIR)/libknotd_la-journal_metadata.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/journal/$(DEPDIR)/libknotd_la-journal_read.Plo@am__quote@ # am--include-marker @@ -4335,8 +4398,12 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/queryacl/$(DEPDIR)/la-queryacl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/queryacl/$(DEPDIR)/libknotd_la-queryacl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/la-functions.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/la-kru-generic.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/la-rrl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/libknotd_la-functions.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/rrl/$(DEPDIR)/libknotd_la-rrl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/stats/$(DEPDIR)/la-stats.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Plo@am__quote@ # am--include-marker @@ -4359,6 +4426,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@knot/query/$(DEPDIR)/libknotd_la-query.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/query/$(DEPDIR)/libknotd_la-quic-requestor.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/query/$(DEPDIR)/libknotd_la-requestor.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/server/$(DEPDIR)/libknotd_la-dthreads.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/server/$(DEPDIR)/libknotd_la-handler.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@knot/server/$(DEPDIR)/libknotd_la-proxyv2.Plo@am__quote@ # am--include-marker @@ -4441,6 +4509,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@libknot/probe/$(DEPDIR)/la-probe.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libknot/quic/$(DEPDIR)/la-quic.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libknot/quic/$(DEPDIR)/la-quic_conn.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libknot/quic/$(DEPDIR)/la-tls.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@libknot/quic/$(DEPDIR)/la-tls_common.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libknot/rrtype/$(DEPDIR)/la-naptr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libknot/rrtype/$(DEPDIR)/la-opt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@libknot/rrtype/$(DEPDIR)/la-tsig.Plo@am__quote@ # am--include-marker @@ -4499,6 +4569,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@utils/kxdpgun/$(DEPDIR)/kxdpgun-ip_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@utils/kxdpgun/$(DEPDIR)/kxdpgun-load_queries.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@utils/kxdpgun/$(DEPDIR)/kxdpgun-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@utils/kzonecheck/$(DEPDIR)/kzonecheck-zone_check.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@utils/kzonesign/$(DEPDIR)/kzonesign-main.Po@am__quote@ # am--include-marker @@ -4624,6 +4695,20 @@ knot/modules/rrl/la-functions.lo: knot/modules/rrl/functions.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_rrl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/la-functions.lo `test -f 'knot/modules/rrl/functions.c' || echo '$(srcdir)/'`knot/modules/rrl/functions.c +knot/modules/rrl/la-kru-generic.lo: knot/modules/rrl/kru-generic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_rrl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/rrl/la-kru-generic.lo -MD -MP -MF knot/modules/rrl/$(DEPDIR)/la-kru-generic.Tpo -c -o knot/modules/rrl/la-kru-generic.lo `test -f 'knot/modules/rrl/kru-generic.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-generic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/rrl/$(DEPDIR)/la-kru-generic.Tpo knot/modules/rrl/$(DEPDIR)/la-kru-generic.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/modules/rrl/kru-generic.c' object='knot/modules/rrl/la-kru-generic.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_rrl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/la-kru-generic.lo `test -f 'knot/modules/rrl/kru-generic.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-generic.c + +knot/modules/rrl/la-kru-avx2.lo: knot/modules/rrl/kru-avx2.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_rrl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/rrl/la-kru-avx2.lo -MD -MP -MF knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Tpo -c -o knot/modules/rrl/la-kru-avx2.lo `test -f 'knot/modules/rrl/kru-avx2.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-avx2.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Tpo knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/modules/rrl/kru-avx2.c' object='knot/modules/rrl/la-kru-avx2.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_rrl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/la-kru-avx2.lo `test -f 'knot/modules/rrl/kru-avx2.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-avx2.c + knot/modules/stats/la-stats.lo: knot/modules/stats/stats.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(knot_modules_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/stats/la-stats.lo -MD -MP -MF knot/modules/stats/$(DEPDIR)/la-stats.Tpo -c -o knot/modules/stats/la-stats.lo `test -f 'knot/modules/stats/stats.c' || echo '$(srcdir)/'`knot/modules/stats/stats.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/stats/$(DEPDIR)/la-stats.Tpo knot/modules/stats/$(DEPDIR)/la-stats.Plo @@ -5429,6 +5514,20 @@ libknot/probe/la-probe.lo: libknot/probe/probe.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot/probe/la-probe.lo `test -f 'libknot/probe/probe.c' || echo '$(srcdir)/'`libknot/probe/probe.c +libknot/quic/la-tls.lo: libknot/quic/tls.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot/quic/la-tls.lo -MD -MP -MF libknot/quic/$(DEPDIR)/la-tls.Tpo -c -o libknot/quic/la-tls.lo `test -f 'libknot/quic/tls.c' || echo '$(srcdir)/'`libknot/quic/tls.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libknot/quic/$(DEPDIR)/la-tls.Tpo libknot/quic/$(DEPDIR)/la-tls.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libknot/quic/tls.c' object='libknot/quic/la-tls.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot/quic/la-tls.lo `test -f 'libknot/quic/tls.c' || echo '$(srcdir)/'`libknot/quic/tls.c + +libknot/quic/la-tls_common.lo: libknot/quic/tls_common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot/quic/la-tls_common.lo -MD -MP -MF libknot/quic/$(DEPDIR)/la-tls_common.Tpo -c -o libknot/quic/la-tls_common.lo `test -f 'libknot/quic/tls_common.c' || echo '$(srcdir)/'`libknot/quic/tls_common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libknot/quic/$(DEPDIR)/la-tls_common.Tpo libknot/quic/$(DEPDIR)/la-tls_common.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libknot/quic/tls_common.c' object='libknot/quic/la-tls_common.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot/quic/la-tls_common.lo `test -f 'libknot/quic/tls_common.c' || echo '$(srcdir)/'`libknot/quic/tls_common.c + libknot/la-rdataset.lo: libknot/rdataset.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknot_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot/la-rdataset.lo -MD -MP -MF libknot/$(DEPDIR)/la-rdataset.Tpo -c -o libknot/la-rdataset.lo `test -f 'libknot/rdataset.c' || echo '$(srcdir)/'`libknot/rdataset.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libknot/$(DEPDIR)/la-rdataset.Tpo libknot/$(DEPDIR)/la-rdataset.Plo @@ -5877,6 +5976,13 @@ knot/events/handlers/libknotd_la-update.lo: knot/events/handlers/update.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/events/handlers/libknotd_la-update.lo `test -f 'knot/events/handlers/update.c' || echo '$(srcdir)/'`knot/events/handlers/update.c +knot/events/handlers/libknotd_la-validate.lo: knot/events/handlers/validate.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/events/handlers/libknotd_la-validate.lo -MD -MP -MF knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Tpo -c -o knot/events/handlers/libknotd_la-validate.lo `test -f 'knot/events/handlers/validate.c' || echo '$(srcdir)/'`knot/events/handlers/validate.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Tpo knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/events/handlers/validate.c' object='knot/events/handlers/libknotd_la-validate.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/events/handlers/libknotd_la-validate.lo `test -f 'knot/events/handlers/validate.c' || echo '$(srcdir)/'`knot/events/handlers/validate.c + knot/events/libknotd_la-replan.lo: knot/events/replan.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/events/libknotd_la-replan.lo -MD -MP -MF knot/events/$(DEPDIR)/libknotd_la-replan.Tpo -c -o knot/events/libknotd_la-replan.lo `test -f 'knot/events/replan.c' || echo '$(srcdir)/'`knot/events/replan.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/events/$(DEPDIR)/libknotd_la-replan.Tpo knot/events/$(DEPDIR)/libknotd_la-replan.Plo @@ -5982,6 +6088,20 @@ knot/query/libknotd_la-requestor.lo: knot/query/requestor.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/query/libknotd_la-requestor.lo `test -f 'knot/query/requestor.c' || echo '$(srcdir)/'`knot/query/requestor.c +knot/query/libknotd_la-tls-requestor.lo: knot/query/tls-requestor.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/query/libknotd_la-tls-requestor.lo -MD -MP -MF knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Tpo -c -o knot/query/libknotd_la-tls-requestor.lo `test -f 'knot/query/tls-requestor.c' || echo '$(srcdir)/'`knot/query/tls-requestor.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Tpo knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/query/tls-requestor.c' object='knot/query/libknotd_la-tls-requestor.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/query/libknotd_la-tls-requestor.lo `test -f 'knot/query/tls-requestor.c' || echo '$(srcdir)/'`knot/query/tls-requestor.c + +knot/common/libknotd_la-dbus.lo: knot/common/dbus.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/common/libknotd_la-dbus.lo -MD -MP -MF knot/common/$(DEPDIR)/libknotd_la-dbus.Tpo -c -o knot/common/libknotd_la-dbus.lo `test -f 'knot/common/dbus.c' || echo '$(srcdir)/'`knot/common/dbus.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/common/$(DEPDIR)/libknotd_la-dbus.Tpo knot/common/$(DEPDIR)/libknotd_la-dbus.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/common/dbus.c' object='knot/common/libknotd_la-dbus.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/common/libknotd_la-dbus.lo `test -f 'knot/common/dbus.c' || echo '$(srcdir)/'`knot/common/dbus.c + knot/common/libknotd_la-evsched.lo: knot/common/evsched.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/common/libknotd_la-evsched.lo -MD -MP -MF knot/common/$(DEPDIR)/libknotd_la-evsched.Tpo -c -o knot/common/libknotd_la-evsched.lo `test -f 'knot/common/evsched.c' || echo '$(srcdir)/'`knot/common/evsched.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/common/$(DEPDIR)/libknotd_la-evsched.Tpo knot/common/$(DEPDIR)/libknotd_la-evsched.Plo @@ -6416,6 +6536,20 @@ knot/modules/rrl/libknotd_la-functions.lo: knot/modules/rrl/functions.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/libknotd_la-functions.lo `test -f 'knot/modules/rrl/functions.c' || echo '$(srcdir)/'`knot/modules/rrl/functions.c +knot/modules/rrl/libknotd_la-kru-generic.lo: knot/modules/rrl/kru-generic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/rrl/libknotd_la-kru-generic.lo -MD -MP -MF knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Tpo -c -o knot/modules/rrl/libknotd_la-kru-generic.lo `test -f 'knot/modules/rrl/kru-generic.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-generic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Tpo knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/modules/rrl/kru-generic.c' object='knot/modules/rrl/libknotd_la-kru-generic.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/libknotd_la-kru-generic.lo `test -f 'knot/modules/rrl/kru-generic.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-generic.c + +knot/modules/rrl/libknotd_la-kru-avx2.lo: knot/modules/rrl/kru-avx2.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/rrl/libknotd_la-kru-avx2.lo -MD -MP -MF knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Tpo -c -o knot/modules/rrl/libknotd_la-kru-avx2.lo `test -f 'knot/modules/rrl/kru-avx2.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-avx2.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Tpo knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='knot/modules/rrl/kru-avx2.c' object='knot/modules/rrl/libknotd_la-kru-avx2.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knot/modules/rrl/libknotd_la-kru-avx2.lo `test -f 'knot/modules/rrl/kru-avx2.c' || echo '$(srcdir)/'`knot/modules/rrl/kru-avx2.c + knot/modules/stats/libknotd_la-stats.lo: knot/modules/stats/stats.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libknotd_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knot/modules/stats/libknotd_la-stats.lo -MD -MP -MF knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Tpo -c -o knot/modules/stats/libknotd_la-stats.lo `test -f 'knot/modules/stats/stats.c' || echo '$(srcdir)/'`knot/modules/stats/stats.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Tpo knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Plo @@ -6934,6 +7068,20 @@ utils/kxdpgun/kxdpgun-main.obj: utils/kxdpgun/main.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kxdpgun_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o utils/kxdpgun/kxdpgun-main.obj `if test -f 'utils/kxdpgun/main.c'; then $(CYGPATH_W) 'utils/kxdpgun/main.c'; else $(CYGPATH_W) '$(srcdir)/utils/kxdpgun/main.c'; fi` +utils/kxdpgun/kxdpgun-stats.o: utils/kxdpgun/stats.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kxdpgun_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT utils/kxdpgun/kxdpgun-stats.o -MD -MP -MF utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Tpo -c -o utils/kxdpgun/kxdpgun-stats.o `test -f 'utils/kxdpgun/stats.c' || echo '$(srcdir)/'`utils/kxdpgun/stats.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Tpo utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utils/kxdpgun/stats.c' object='utils/kxdpgun/kxdpgun-stats.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kxdpgun_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o utils/kxdpgun/kxdpgun-stats.o `test -f 'utils/kxdpgun/stats.c' || echo '$(srcdir)/'`utils/kxdpgun/stats.c + +utils/kxdpgun/kxdpgun-stats.obj: utils/kxdpgun/stats.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kxdpgun_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT utils/kxdpgun/kxdpgun-stats.obj -MD -MP -MF utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Tpo -c -o utils/kxdpgun/kxdpgun-stats.obj `if test -f 'utils/kxdpgun/stats.c'; then $(CYGPATH_W) 'utils/kxdpgun/stats.c'; else $(CYGPATH_W) '$(srcdir)/utils/kxdpgun/stats.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Tpo utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utils/kxdpgun/stats.c' object='utils/kxdpgun/kxdpgun-stats.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kxdpgun_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o utils/kxdpgun/kxdpgun-stats.obj `if test -f 'utils/kxdpgun/stats.c'; then $(CYGPATH_W) 'utils/kxdpgun/stats.c'; else $(CYGPATH_W) '$(srcdir)/utils/kxdpgun/stats.c'; fi` + utils/kzonecheck/kzonecheck-main.o: utils/kzonecheck/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(kzonecheck_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT utils/kzonecheck/kzonecheck-main.o -MD -MP -MF utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Tpo -c -o utils/kzonecheck/kzonecheck-main.o `test -f 'utils/kzonecheck/main.c' || echo '$(srcdir)/'`utils/kzonecheck/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Tpo utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Po @@ -7577,6 +7725,7 @@ distclean: distclean-recursive -rm -f knot/catalog/$(DEPDIR)/libknotd_la-catalog_update.Plo -rm -f knot/catalog/$(DEPDIR)/libknotd_la-generate.Plo -rm -f knot/catalog/$(DEPDIR)/libknotd_la-interpret.Plo + -rm -f knot/common/$(DEPDIR)/libknotd_la-dbus.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-evsched.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-fdset.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-log.Plo @@ -7624,6 +7773,7 @@ distclean: distclean-recursive -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-notify.Plo -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-refresh.Plo -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-update.Plo + -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_basic.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_metadata.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_read.Plo @@ -7653,8 +7803,12 @@ distclean: distclean-recursive -rm -f knot/modules/queryacl/$(DEPDIR)/la-queryacl.Plo -rm -f knot/modules/queryacl/$(DEPDIR)/libknotd_la-queryacl.Plo -rm -f knot/modules/rrl/$(DEPDIR)/la-functions.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/la-kru-generic.Plo -rm -f knot/modules/rrl/$(DEPDIR)/la-rrl.Plo -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-functions.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Plo -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-rrl.Plo -rm -f knot/modules/stats/$(DEPDIR)/la-stats.Plo -rm -f knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Plo @@ -7677,6 +7831,7 @@ distclean: distclean-recursive -rm -f knot/query/$(DEPDIR)/libknotd_la-query.Plo -rm -f knot/query/$(DEPDIR)/libknotd_la-quic-requestor.Plo -rm -f knot/query/$(DEPDIR)/libknotd_la-requestor.Plo + -rm -f knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-dthreads.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-handler.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-proxyv2.Plo @@ -7759,6 +7914,8 @@ distclean: distclean-recursive -rm -f libknot/probe/$(DEPDIR)/la-probe.Plo -rm -f libknot/quic/$(DEPDIR)/la-quic.Plo -rm -f libknot/quic/$(DEPDIR)/la-quic_conn.Plo + -rm -f libknot/quic/$(DEPDIR)/la-tls.Plo + -rm -f libknot/quic/$(DEPDIR)/la-tls_common.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-naptr.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-opt.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-tsig.Plo @@ -7817,6 +7974,7 @@ distclean: distclean-recursive -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-ip_route.Po -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-load_queries.Po -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-main.Po + -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po -rm -f utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Po -rm -f utils/kzonecheck/$(DEPDIR)/kzonecheck-zone_check.Po -rm -f utils/kzonesign/$(DEPDIR)/kzonesign-main.Po @@ -7946,6 +8104,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f knot/catalog/$(DEPDIR)/libknotd_la-catalog_update.Plo -rm -f knot/catalog/$(DEPDIR)/libknotd_la-generate.Plo -rm -f knot/catalog/$(DEPDIR)/libknotd_la-interpret.Plo + -rm -f knot/common/$(DEPDIR)/libknotd_la-dbus.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-evsched.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-fdset.Plo -rm -f knot/common/$(DEPDIR)/libknotd_la-log.Plo @@ -7993,6 +8152,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-notify.Plo -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-refresh.Plo -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-update.Plo + -rm -f knot/events/handlers/$(DEPDIR)/libknotd_la-validate.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_basic.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_metadata.Plo -rm -f knot/journal/$(DEPDIR)/libknotd_la-journal_read.Plo @@ -8022,8 +8182,12 @@ maintainer-clean: maintainer-clean-recursive -rm -f knot/modules/queryacl/$(DEPDIR)/la-queryacl.Plo -rm -f knot/modules/queryacl/$(DEPDIR)/libknotd_la-queryacl.Plo -rm -f knot/modules/rrl/$(DEPDIR)/la-functions.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/la-kru-avx2.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/la-kru-generic.Plo -rm -f knot/modules/rrl/$(DEPDIR)/la-rrl.Plo -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-functions.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-avx2.Plo + -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-kru-generic.Plo -rm -f knot/modules/rrl/$(DEPDIR)/libknotd_la-rrl.Plo -rm -f knot/modules/stats/$(DEPDIR)/la-stats.Plo -rm -f knot/modules/stats/$(DEPDIR)/libknotd_la-stats.Plo @@ -8046,6 +8210,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f knot/query/$(DEPDIR)/libknotd_la-query.Plo -rm -f knot/query/$(DEPDIR)/libknotd_la-quic-requestor.Plo -rm -f knot/query/$(DEPDIR)/libknotd_la-requestor.Plo + -rm -f knot/query/$(DEPDIR)/libknotd_la-tls-requestor.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-dthreads.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-handler.Plo -rm -f knot/server/$(DEPDIR)/libknotd_la-proxyv2.Plo @@ -8128,6 +8293,8 @@ maintainer-clean: maintainer-clean-recursive -rm -f libknot/probe/$(DEPDIR)/la-probe.Plo -rm -f libknot/quic/$(DEPDIR)/la-quic.Plo -rm -f libknot/quic/$(DEPDIR)/la-quic_conn.Plo + -rm -f libknot/quic/$(DEPDIR)/la-tls.Plo + -rm -f libknot/quic/$(DEPDIR)/la-tls_common.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-naptr.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-opt.Plo -rm -f libknot/rrtype/$(DEPDIR)/la-tsig.Plo @@ -8186,6 +8353,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-ip_route.Po -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-load_queries.Po -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-main.Po + -rm -f utils/kxdpgun/$(DEPDIR)/kxdpgun-stats.Po -rm -f utils/kzonecheck/$(DEPDIR)/kzonecheck-main.Po -rm -f utils/kzonecheck/$(DEPDIR)/kzonecheck-zone_check.Po -rm -f utils/kzonesign/$(DEPDIR)/kzonesign-main.Po diff --git a/src/config.h.in b/src/config.h.in index 0be849d..3e82c0d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -3,12 +3,6 @@ /* Define if building universal (internal helper macro) */ #undef AC_APPLE_UNIVERSAL_BUILD -/* Passed CFLAGS from environment */ -#undef CONFIGURE_CFLAGS - -/* Params passed to configure */ -#undef CONFIGURE_PARAMS - /* Configure summary */ #undef CONFIGURE_SUMMARY @@ -18,8 +12,11 @@ /* POSIX capabilities available */ #undef ENABLE_CAP_NG +/* libdbus D-Bus available */ +#undef ENABLE_DBUS_LIBDBUS + /* systemd D-Bus available */ -#undef ENABLE_DBUS +#undef ENABLE_DBUS_SYSTEMD /* PKCS #11 support available */ #undef ENABLE_PKCS11 @@ -48,12 +45,12 @@ /* Define to 1 if you have the <arpa/nameser.h> header file. */ #undef HAVE_ARPA_NAMESER_H -/* Define to 1 if you have '__atomic' functions. */ -#undef HAVE_ATOMIC - /* Define to 1 if you have the <bsd/string.h> header file. */ #undef HAVE_BSD_STRING_H +/* Define to 1 if you have C11 'atomic' functions. */ +#undef HAVE_C11_ATOMIC + /* Define if FreeBSD-like cpuset_t exists. */ #undef HAVE_CPUSET_BSD @@ -66,9 +63,6 @@ /* Define to 1 if you have the <dlfcn.h> header file. */ #undef HAVE_DLFCN_H -/* GnuTLS ED25519 support available */ -#undef HAVE_ED25519 - /* GnuTLS ED448 support available */ #undef HAVE_ED448 @@ -84,24 +78,18 @@ /* explicit_memset available */ #undef HAVE_EXPLICIT_MEMSET -/* gnutls_privkey_export_x509 available */ -#undef HAVE_EXPORT_X509 - /* Define to 1 if you have the `fgetln' function. */ #undef HAVE_FGETLN +/* Define to 1 if you have GCC-style '__atomic' functions. */ +#undef HAVE_GCC_ATOMIC + /* Define to 1 if you have the `getline' function. */ #undef HAVE_GETLINE -/* gnutls_memset available */ -#undef HAVE_GNUTLS_MEMSET - /* gnutls_early_cipher_get available */ #undef HAVE_GNUTLS_QUIC -/* GnuTLS reproducible signing available */ -#undef HAVE_GNUTLS_REPRODUCIBLE - /* Define to 1 if you have the `initgroups' function. */ #undef HAVE_INITGROUPS @@ -138,12 +126,6 @@ /* Define to 1 if you have the `setgroups' function. */ #undef HAVE_SETGROUPS -/* gnutls_privkey_sign_data2 available */ -#undef HAVE_SIGN_DATA2 - -/* Define to 1 if you have the <stdatomic.h> header file. */ -#undef HAVE_STDATOMIC_H - /* Define to 1 if you have the <stdint.h> header file. */ #undef HAVE_STDINT_H @@ -165,9 +147,6 @@ /* Define to 1 if you have the `strlcpy' function. */ #undef HAVE_STRLCPY -/* Define to 1 if you have '__sync' functions. */ -#undef HAVE_SYNC_ATOMIC - /* Define to 1 if you have the `sysctlbyname' function. */ #undef HAVE_SYSCTLBYNAME @@ -193,9 +172,6 @@ /* Define to 1 to enable IDN support */ #undef LIBIDN -/* Define to proper libidn header */ -#undef LIBIDN_HEADER - /* Define to 1 to enable DoH support */ #undef LIBNGHTTP2 diff --git a/src/contrib/Makefile.inc b/src/contrib/Makefile.inc index e1577d5..d64c2eb 100644 --- a/src/contrib/Makefile.inc +++ b/src/contrib/Makefile.inc @@ -24,6 +24,7 @@ EXTRA_DIST += \ libcontrib_la_SOURCES = \ contrib/asan.h \ + contrib/atomic.h \ contrib/base32hex.c \ contrib/base32hex.h \ contrib/base64.c \ diff --git a/src/contrib/asan.h b/src/contrib/asan.h index 5feb2c1..ef6fe66 100644 --- a/src/contrib/asan.h +++ b/src/contrib/asan.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 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 @@ -25,13 +25,20 @@ #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) void __asan_poison_memory_region(void const volatile *addr, size_t size); void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +#if defined(__GNUC__) && !defined(__clang__) /* A faulty GCC workaround. */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif #define ASAN_POISON_MEMORY_REGION(addr, size) \ __asan_poison_memory_region((addr), (size)) +#if defined(__GNUC__) && !defined(__clang__) /* End of the workaround. */ + #pragma GCC diagnostic pop +#endif #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ __asan_unpoison_memory_region((addr), (size)) -#else +#else /* __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) */ #define ASAN_POISON_MEMORY_REGION(addr, size) \ ((void)(addr), (void)(size)) #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ ((void)(addr), (void)(size)) -#endif +#endif /* __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) */ diff --git a/src/contrib/atomic.h b/src/contrib/atomic.h new file mode 100644 index 0000000..b8dace1 --- /dev/null +++ b/src/contrib/atomic.h @@ -0,0 +1,73 @@ +/* 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/>. + */ + +/*! + * \brief C11 atomic operations with fallbacks. + */ + +#pragma once + +#ifdef HAVE_C11_ATOMIC /* C11 */ + #define KNOT_HAVE_ATOMIC + + #include <stdatomic.h> + + #define ATOMIC_SET(dst, val) atomic_store_explicit(&(dst), (val), memory_order_relaxed) + #define ATOMIC_GET(src) atomic_load_explicit(&(src), memory_order_relaxed) + #define ATOMIC_ADD(dst, val) (void)atomic_fetch_add_explicit(&(dst), (val), memory_order_relaxed) + #define ATOMIC_SUB(dst, val) (void)atomic_fetch_sub_explicit(&(dst), (val), memory_order_relaxed) + #define ATOMIC_XCHG(dst, val) atomic_exchange_explicit(&(dst), (val), memory_order_relaxed) + + typedef atomic_uint_fast16_t knot_atomic_uint16_t; + typedef atomic_uint_fast64_t knot_atomic_uint64_t; + typedef atomic_size_t knot_atomic_size_t; + typedef _Atomic (void *) knot_atomic_ptr_t; + typedef atomic_bool knot_atomic_bool; +#elif defined(HAVE_GCC_ATOMIC) /* GCC __atomic */ + #define KNOT_HAVE_ATOMIC + + #include <stdint.h> + #include <stdbool.h> + + #define ATOMIC_SET(dst, val) __atomic_store_n(&(dst), (val), __ATOMIC_RELAXED) + #define ATOMIC_GET(src) __atomic_load_n(&(src), __ATOMIC_RELAXED) + #define ATOMIC_ADD(dst, val) __atomic_add_fetch(&(dst), (val), __ATOMIC_RELAXED) + #define ATOMIC_SUB(dst, val) __atomic_sub_fetch(&(dst), (val), __ATOMIC_RELAXED) + #define ATOMIC_XCHG(dst, val) __atomic_exchange_n(&(dst), (val), __ATOMIC_RELAXED) + + typedef uint16_t knot_atomic_uint16_t; + typedef uint64_t knot_atomic_uint64_t; + typedef size_t knot_atomic_size_t; + typedef void* knot_atomic_ptr_t; + typedef bool knot_atomic_bool; +#else /* Fallback, non-atomic. */ + #warning "Atomic operations not availabe, using unreliable replacement." + + #include <stdint.h> + #include <stdbool.h> + + #define ATOMIC_SET(dst, val) ((dst) = (val)) + #define ATOMIC_GET(src) (src) + #define ATOMIC_ADD(dst, val) ((dst) += (val)) + #define ATOMIC_SUB(dst, val) ((dst) -= (val)) + #define ATOMIC_XCHG(dst, val) ({ __typeof__ (dst) _z = (dst); (dst) = (val); _z; }) + + typedef uint16_t knot_atomic_uint16_t; + typedef uint64_t knot_atomic_uint64_t; + typedef size_t knot_atomic_size_t; + typedef void* knot_atomic_ptr_t; + typedef bool knot_atomic_bool; +#endif diff --git a/src/contrib/files.c b/src/contrib/files.c index 5b38469..f3a4b78 100644 --- a/src/contrib/files.c +++ b/src/contrib/files.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 @@ -92,16 +92,29 @@ static int remove_file(const char *path, const struct stat *stat, int type, stru { (void)stat; (void)ftw; - if (type == FTW_DP) { + + switch (type) { + case FTW_D: + case FTW_DNR: + case FTW_DP: return rmdir(path); - } else { + default: return unlink(path); } } -bool remove_path(const char *path) +static int remove_in_dir(const char *path, const struct stat *stat, int type, struct FTW *ftw) +{ + return (ftw->level > 0) ? remove_file(path, stat, type, ftw) : 0; +} + +int remove_path(const char *path, bool keep_apex) { - return (0 == nftw(path, remove_file, 1, FTW_DEPTH | FTW_PHYS)); + if (0 != nftw(path, keep_apex ? remove_in_dir : remove_file, + 1, FTW_DEPTH | FTW_PHYS)) { + return knot_map_errno(); + } + return KNOT_EOK; } int make_dir(const char *path, mode_t mode, bool ignore_existing) diff --git a/src/contrib/files.h b/src/contrib/files.h index c505ac8..4250a47 100644 --- a/src/contrib/files.h +++ b/src/contrib/files.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 @@ -52,9 +52,12 @@ bool same_path(const char *path1, const char *path2); /*! * \brief Delete file or directory (recursive). * - * \return true on success, false when one or more files failed to be removed. + * \param[in] path Absolute path or a relative path suffix; a string. + * \param[in] keep_apex If true, don't remove the starting point (apex). + * + * \return KNOT_E* */ -bool remove_path(const char *path); +int remove_path(const char *path, bool keep_apex); /*! * Equivalent to mkdir(2), can succeed if the directory already exists. diff --git a/src/contrib/json.c b/src/contrib/json.c index d44da87..5173f90 100644 --- a/src/contrib/json.c +++ b/src/contrib/json.c @@ -217,6 +217,13 @@ void jsonw_int(jsonw_t *w, const char *key, int value) fprintf(w->out, "%d", value); } +void jsonw_double(jsonw_t *w, const char *key, double value) +{ + assert(w); + + align_key(w, key); + fprintf(w->out, "%.4f", value); +} void jsonw_bool(jsonw_t *w, const char *key, bool value) { diff --git a/src/contrib/json.h b/src/contrib/json.h index cf8abe6..17513bc 100644 --- a/src/contrib/json.h +++ b/src/contrib/json.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 @@ -82,6 +82,11 @@ void jsonw_ulong(jsonw_t *w, const char *key, unsigned long value); void jsonw_int(jsonw_t *w, const char *key, int value); /*! + * Write double as JSON. + */ +void jsonw_double(jsonw_t *w, const char *key, double value); + +/*! * Write boolean value as JSON. */ void jsonw_bool(jsonw_t *w, const char *key, bool value); diff --git a/src/contrib/mempattern.c b/src/contrib/mempattern.c index f57139d..f86f0ac 100644 --- a/src/contrib/mempattern.c +++ b/src/contrib/mempattern.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 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 @@ -114,9 +114,15 @@ void mm_ctx_init(knot_mm_t *mm) mm->free = free; } +// UBSAN type punning workaround +static void *mp_alloc_wrap(void *ctx, size_t size) +{ + return mp_alloc(ctx, size); +} + void mm_ctx_mempool(knot_mm_t *mm, size_t chunk_size) { mm->ctx = mp_new(chunk_size); - mm->alloc = (knot_mm_alloc_t)mp_alloc; + mm->alloc = mp_alloc_wrap; mm->free = mm_nofree; } diff --git a/src/contrib/spinlock.h b/src/contrib/spinlock.h index 837f500..a7ec5ec 100644 --- a/src/contrib/spinlock.h +++ b/src/contrib/spinlock.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -15,48 +15,23 @@ */ /*! - * \brief Multiplatform spinlock. + * \brief A C11 spinlock (POSIX pthread spinlock as a fallback). */ #pragma once #if (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) -/* Not tested and activated yet. */ -/* #define HAVE_STDATOMIC */ -#endif - -#if defined(__APPLE__) -# if defined(MAC_OS_X_VERSION_10_12) || \ - MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 -# define APPLE_SPIN_NEW -# else -# define APPLE_SPIN_OLD -# endif /* MAC_OS_X_VERSION_10_12 ... */ -#endif /* __APPLE__ */ - -#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) -# include <stdbool.h> -#elif defined(HAVE_STDATOMIC) -# include <stdbool.h> -# include <stdatomic.h> -#elif defined(APPLE_SPIN_NEW) -# include <os/lock.h> -#elif defined(APPLE_SPIN_OLD) -# include <libkern/OSAtomic.h> + #define HAVE_STDATOMIC + #include <stdatomic.h> + #include <stdbool.h> #else /* POSIX pthread spinlock. */ -# include <pthread.h> + #include <pthread.h> #endif /*! \brief Spinlock lock variable type. */ typedef -#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) - bool /*!< Spinlock lock - a simple & fast atomic version. */ -#elif defined(HAVE_STDATOMIC) +#if defined(HAVE_STDATOMIC) atomic_bool /*!< Spinlock lock - a simple & fast atomic version, C11 */ -#elif defined(APPLE_SPIN_NEW) - os_unfair_lock /*!< Spinlock lock - a newer macOS version (macOS >= 10.12). */ -#elif defined(APPLE_SPIN_OLD) - OSSpinLock /*!< Spinlock lock - an older macOS version (macOS < 10.12). */ #else /* POSIX */ pthread_spinlock_t /*!< Spinlock lock - a POSIX pthread version. */ #endif @@ -65,14 +40,8 @@ typedef /*! \brief Initialize the spinlock pointed to by the parameter "lock". */ static inline void knot_spin_init(knot_spin_t *lock) { -#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) - *lock = false; -#elif defined(HAVE_STDATOMIC) +#if defined(HAVE_STDATOMIC) atomic_init(lock, false); -#elif defined(APPLE_SPIN_NEW) - *lock = OS_UNFAIR_LOCK_INIT; -#elif defined(APPLE_SPIN_OLD) - *lock = OS_SPINLOCK_INIT; #else /* POSIX */ pthread_spin_init(lock, PTHREAD_PROCESS_PRIVATE); #endif @@ -81,8 +50,7 @@ static inline void knot_spin_init(knot_spin_t *lock) /*! \brief Destroy the spinlock pointed to by the parameter "lock". */ static inline void knot_spin_destroy(knot_spin_t *lock) { -#if defined(HAVE_SYNC_ATOMIC) || defined(HAVE_ATOMIC) || defined(HAVE_STDATOMIC) || \ - defined(APPLE_SPIN_NEW) || defined(APPLE_SPIN_OLD) +#if defined(HAVE_STDATOMIC) /* Nothing needed here. */ #else /* POSIX */ pthread_spin_destroy(lock); @@ -92,23 +60,11 @@ static inline void knot_spin_destroy(knot_spin_t *lock) /*! \brief Lock the spinlock pointed to by the parameter "lock". */ static inline void knot_spin_lock(knot_spin_t *lock) { -#if defined(HAVE_SYNC_ATOMIC) - while (__sync_lock_test_and_set(lock, 1)) { - } -#elif defined(HAVE_ATOMIC) - int expected = 0; - while (!__atomic_compare_exchange_n(lock, &expected, 1, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { - expected = 0; - } -#elif defined(HAVE_STDATOMIC) - int expected = 0; - while (!atomic_compare_exchange_strong(lock, &expected, false)) { - expected = 0; +#if defined(HAVE_STDATOMIC) + bool expected = false; + while (!atomic_compare_exchange_strong(lock, &expected, true)) { + expected = false; } -#elif defined(APPLE_SPIN_NEW) - os_unfair_lock_lock(lock); -#elif defined(APPLE_SPIN_OLD) - OSSpinLockLock(lock); #else /* POSIX */ pthread_spin_lock(lock); #endif @@ -117,16 +73,8 @@ static inline void knot_spin_lock(knot_spin_t *lock) /*! \brief Unlock the spinlock pointed to by the parameter "lock". */ static inline void knot_spin_unlock(knot_spin_t *lock) { -#if defined(HAVE_SYNC_ATOMIC) - __sync_lock_release(lock); -#elif defined(HAVE_ATOMIC) - __atomic_clear(lock, __ATOMIC_RELAXED); -#elif defined(HAVE_STDATOMIC) +#if defined(HAVE_STDATOMIC) atomic_store(lock, false); -#elif defined(APPLE_SPIN_NEW) - os_unfair_lock_unlock(lock); -#elif defined(APPLE_SPIN_OLD) - OSSpinLockUnlock(lock); #else /* POSIX */ pthread_spin_unlock(lock); #endif diff --git a/src/contrib/string.c b/src/contrib/string.c index 272116e..6fa2d0a 100644 --- a/src/contrib/string.c +++ b/src/contrib/string.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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,16 +26,16 @@ /* #include <string.h> is needed. */ #elif defined(HAVE_EXPLICIT_MEMSET) /* #include <string.h> is needed. */ -#elif defined(HAVE_GNUTLS_MEMSET) - #include <gnutls/gnutls.h> #else - #define USE_CUSTOM_MEMSET + #include <gnutls/gnutls.h> #endif #include "contrib/string.h" #include "contrib/ctype.h" #include "contrib/tolower.h" +const char *configure_summary = CONFIGURE_SUMMARY; + uint8_t *memdup(const uint8_t *data, size_t data_size) { uint8_t *result = (uint8_t *)malloc(data_size); @@ -137,11 +137,6 @@ int const_time_memcmp(const void *s1, const void *s2, size_t n) return equal; } -#if defined(USE_CUSTOM_MEMSET) -typedef void *(*memset_t)(void *, int, size_t); -static volatile memset_t volatile_memset = memset; -#endif - void *memzero(void *s, size_t n) { #if defined(HAVE_EXPLICIT_BZERO) /* In OpenBSD since 5.5. */ @@ -161,14 +156,9 @@ void *memzero(void *s, size_t n) return s; #elif defined(HAVE_EXPLICIT_MEMSET) /* In NetBSD since 7.0. */ return explicit_memset(s, 0, n); -#elif defined(HAVE_GNUTLS_MEMSET) /* In GnuTLS since 3.4.0. */ +#else gnutls_memset(s, 0, n); return s; -#else /* Knot custom solution as a fallback. */ - /* Warning: the use of the return value is *probably* needed - * so as to avoid the volatile_memset() to be optimized out. - */ - return volatile_memset(s, 0, n); #endif } diff --git a/src/contrib/string.h b/src/contrib/string.h index ad3c990..3e113b1 100644 --- a/src/contrib/string.h +++ b/src/contrib/string.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -24,6 +24,8 @@ #include <stddef.h> #include <stdint.h> +extern const char *configure_summary; + /*! * \brief Create a copy of a binary buffer. * diff --git a/src/contrib/time.h b/src/contrib/time.h index 20d241e..b12b366 100644 --- a/src/contrib/time.h +++ b/src/contrib/time.h @@ -88,6 +88,11 @@ inline static int knot_time_cmp(knot_time_t a, knot_time_t b) { return (a == b ? 0 : 1) * ((a && b) == 0 ? -1 : 1) * (a < b ? -1 : 1); } +inline static bool knot_time_lt (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) < 0; } +inline static bool knot_time_leq(knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) <= 0; } +inline static bool knot_time_eq (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) == 0; } +inline static bool knot_time_geq(knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) >= 0; } +inline static bool knot_time_gt (knot_time_t a, knot_time_t b) { return knot_time_cmp(a, b) > 0; } /*! * \brief Return the smaller (=earlier) from given two timestamps. diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index f67fe7f..0cbc9f3 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -1,12 +1,11 @@ libknotd_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(libkqueue_CFLAGS) \ $(liburcu_CFLAGS) $(lmdb_CFLAGS) $(systemd_CFLAGS) \ - $(gnutls_CFLAGS) $(libngtcp2_CFLAGS) -DKNOTD_MOD_STATIC + $(libdbus_CFLAGS) $(gnutls_CFLAGS) -DKNOTD_MOD_STATIC libknotd_la_LDFLAGS = $(AM_LDFLAGS) -export-symbols-regex '^knotd_' -libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS) \ - $(libngtcp2_LIBS) +libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS) libknotd_LIBS = libknotd.la libknot.la libdnssec.la libzscanner.la \ $(libcontrib_LIBS) $(liburcu_LIBS) $(lmdb_LIBS) \ - $(systemd_LIBS) $(gnutls_LIBS) + $(systemd_LIBS) $(libdbus_LIBS) $(gnutls_LIBS) if EMBEDDED_LIBNGTCP2 libknotd_la_LIBADD += $(libembngtcp2_LIBS) @@ -93,6 +92,7 @@ libknotd_la_SOURCES = \ knot/events/handlers/notify.c \ knot/events/handlers/refresh.c \ knot/events/handlers/update.c \ + knot/events/handlers/validate.c \ knot/events/replan.c \ knot/events/replan.h \ knot/nameserver/axfr.c \ @@ -125,6 +125,10 @@ 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 \ knot/common/evsched.h \ knot/common/fdset.c \ diff --git a/src/knot/catalog/catalog_db.c b/src/knot/catalog/catalog_db.c index b483f4d..2ea776f 100644 --- a/src/knot/catalog/catalog_db.c +++ b/src/knot/catalog/catalog_db.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -44,7 +44,7 @@ int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *nam if (*res == '\0') { return -1; } - res = knot_wire_next_label(res, NULL); + res = knot_dname_next_label(res); } return res - subname; } diff --git a/src/knot/catalog/catalog_update.c b/src/knot/catalog/catalog_update.c index edfd8c5..71b7dbe 100644 --- a/src/knot/catalog/catalog_update.c +++ b/src/knot/catalog/catalog_update.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 @@ -132,7 +132,7 @@ static const knot_dname_t *get_uniq(const knot_dname_t *ptr_owner, int labels = knot_dname_labels(ptr_owner, NULL); labels -= knot_dname_labels(catz, NULL); assert(labels >= 2); - return ptr_owner + knot_dname_prefixlen(ptr_owner, labels - 2, NULL); + return ptr_owner + knot_dname_prefixlen(ptr_owner, labels - 2); } static bool same_uniq(const knot_dname_t *owner1, const knot_dname_t *catz1, diff --git a/src/knot/catalog/interpret.c b/src/knot/catalog/interpret.c index 7337105..3e8e5db 100644 --- a/src/knot/catalog/interpret.c +++ b/src/knot/catalog/interpret.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 @@ -105,7 +105,7 @@ static const knot_dname_t *property_get_member(const zone_node_t *prop_node, knot_rdataset_t *ptr = node_rdataset(prop_node->parent, KNOT_RRTYPE_PTR); if (ptr == NULL) { // fallback: search in provided complete zone contents - const knot_dname_t *memb_name = knot_wire_next_label(prop_node->owner, NULL); + const knot_dname_t *memb_name = knot_dname_next_label(prop_node->owner); const zone_node_t *memb_node = zone_contents_find_node(complete_conts, memb_name); ptr = node_rdataset(memb_node, KNOT_RRTYPE_PTR); if (memb_node != NULL) { diff --git a/src/knot/common/dbus.c b/src/knot/common/dbus.c new file mode 100644 index 0000000..38ae5fa --- /dev/null +++ b/src/knot/common/dbus.c @@ -0,0 +1,243 @@ +/* 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 <stdarg.h> + +#include "knot/common/dbus.h" +#include "knot/common/log.h" + +#define ENABLE_DBUS (ENABLE_DBUS_SYSTEMD | ENABLE_DBUS_LIBDBUS) + +#if defined(ENABLE_DBUS_SYSTEMD) + +#include <systemd/sd-bus.h> +#define VALUE_OF(x) (x) +typedef sd_bus * dbus_ctx_t; + +#elif defined(ENABLE_DBUS_LIBDBUS) + +#include <assert.h> +#include <dbus/dbus.h> +#define VALUE_OF(x) (&(x)) +typedef DBusConnection * dbus_ctx_t; + +#else + +typedef struct {} * dbus_ctx_t; // Dummy + +#endif // ENABLE_DBUS_LIBDBUS + +static dbus_ctx_t _dbus = NULL; + +int dbus_open(void) +{ + if (_dbus != NULL) { + return KNOT_EOK; + } +#if defined(ENABLE_DBUS_SYSTEMD) + int ret = sd_bus_open_system(&_dbus); + if (ret < 0) { + goto error_systemd; + } + + /* Take a well-known service name so that clients can find us. */ + ret = sd_bus_request_name(_dbus, KNOT_DBUS_NAME, 0); + if (ret < 0) { + goto error_systemd; + } + + log_info("d-bus: connected to system bus"); + return KNOT_EOK; +error_systemd: + log_error("d-bus: failed to open system bus (%s)", knot_strerror(ret)); + dbus_close(); + return ret; +#elif defined(ENABLE_DBUS_LIBDBUS) + DBusError err; + dbus_error_init(&err); + + _dbus = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err) == TRUE) { + goto error_libdbus; + } + + /* Take a well-known service name so that clients can find us. */ + dbus_bus_request_name(_dbus, KNOT_DBUS_NAME, 0, &err); + if (dbus_error_is_set(&err) == TRUE) { + goto error_libdbus; + } + + dbus_error_free(&err); + log_info("d-bus: connected to system bus"); + return KNOT_EOK; +error_libdbus: + log_error("d-bus: failed to open system bus (%s)", err.message); + dbus_error_free(&err); + dbus_close(); + return KNOT_ERROR; +#endif + log_error("d-bus: not supported"); + return KNOT_ENOTSUP; +} + +void dbus_close(void) +{ + if (_dbus == NULL) { + return; + } +#if defined(ENABLE_DBUS_SYSTEMD) + _dbus = sd_bus_unref(_dbus); +#elif defined(ENABLE_DBUS_LIBDBUS) + dbus_connection_unref(_dbus); + _dbus = NULL; +#endif // ENABLE_DBUS_LIBDBUS +} + +#if ENABLE_DBUS +static void emit_event(const char *event, char *first_arg_type, ...) +{ + int ret = KNOT_ENOENT; + if (_dbus == NULL) { + goto failed; + } + +#if defined(ENABLE_DBUS_SYSTEMD) + sd_bus_message *msg = NULL; + ret = sd_bus_message_new_signal(_dbus, &msg, KNOT_DBUS_PATH, + KNOT_DBUS_NAME".events", event); + if (ret < 0) { + goto failed; + } + + va_list args; + va_start(args, first_arg_type); + ret = sd_bus_message_appendv(msg, first_arg_type, args); + if (ret < 0) { + sd_bus_message_unref(msg); + va_end(args); + goto failed; + } + /* + * \note sd_bus_message_send(msg) or even sd_bus_emit_signalv() can + * be used with a newer systemd. + */ + ret = sd_bus_send(sd_bus_message_get_bus(msg), msg, NULL); + if (ret < 0) { + sd_bus_message_unref(msg); + va_end(args); + goto failed; + } + va_end(args); +#elif defined(ENABLE_DBUS_LIBDBUS) + DBusMessage *msg = NULL; + msg = dbus_message_new_signal(KNOT_DBUS_PATH, KNOT_DBUS_NAME".events", + event); + if (msg == NULL) { + ret = KNOT_ENOMEM; + goto failed; + } + + /* + * \note This loop considers only basic data types; composite ones, + * such as arrays, result in undefined behavior. + */ + va_list args; + va_start(args, first_arg_type); + for (const char *type = first_arg_type; *type; ++type) { + dbus_bool_t bret = dbus_message_append_args(msg, *type, + va_arg(args, void *), + DBUS_TYPE_INVALID); + if (bret == FALSE) { + dbus_message_unref(msg); + va_end(args); + assert(0); // Read note + ret = KNOT_EINVAL; + goto failed; + } + } + + if (dbus_connection_send(_dbus, msg, NULL) == 0) { + dbus_message_unref(msg); + va_end(args); + ret = KNOT_NET_ESEND; + goto failed; + } + dbus_message_unref(msg); + va_end(args); +#endif // ENABLE_DBUS_LIBDBUS + return; +failed: + log_error("d-bus: failed to emit signal '%s' (%s)", event, knot_strerror(ret)); +} +#endif // ENABLE_DBUS + +void dbus_emit_running(bool up) +{ +#if ENABLE_DBUS + emit_event(up ? KNOT_BUS_EVENT_STARTED : KNOT_BUS_EVENT_STOPPED, ""); +#endif // ENABLE_DBUS +} + +void dbus_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial) +{ +#if ENABLE_DBUS + knot_dname_txt_storage_t buff; + char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); + if (zone_str != NULL) { + emit_event(KNOT_BUS_EVENT_ZONE_UPD, "su", VALUE_OF(zone_str), + VALUE_OF(serial)); + } +#endif // ENABLE_DBUS +} + +void dbus_emit_keys_updated(const knot_dname_t *zone_name) +{ +#if ENABLE_DBUS + knot_dname_txt_storage_t buff; + char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); + if (zone_str != NULL) { + emit_event(KNOT_BUS_EVENT_ZONE_KEYS_UPD, "s", + VALUE_OF(zone_str)); + } +#endif // ENABLE_DBUS +} + +void dbus_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag, + const char *keyid) +{ +#if ENABLE_DBUS + knot_dname_txt_storage_t buff; + char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); + if (zone_str != NULL) { + emit_event(KNOT_BUS_EVENT_ZONE_KSK_SUBM, "sqs", + VALUE_OF(zone_str), VALUE_OF(keytag), + VALUE_OF(keyid)); + } +#endif // ENABLE_DBUS +} + +void dbus_emit_zone_invalid(const knot_dname_t *zone_name, uint32_t remaining_secs) +{ +#if ENABLE_DBUS + knot_dname_txt_storage_t buff; + char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); + if (zone_str != NULL) { + emit_event(KNOT_BUS_EVENT_ZONE_INVALID, "su", + VALUE_OF(zone_str), + VALUE_OF(remaining_secs)); + } +#endif // ENABLE_DBUS +} diff --git a/src/knot/common/dbus.h b/src/knot/common/dbus.h new file mode 100644 index 0000000..3a529f3 --- /dev/null +++ b/src/knot/common/dbus.h @@ -0,0 +1,86 @@ +/* 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/>. + */ + +/*! + * \brief D-Bus API wrappers. + */ + +#pragma once + +#include "libknot/libknot.h" + +#define KNOT_DBUS_NAME "cz.nic.knotd" +#define KNOT_DBUS_PATH "/cz/nic/knotd" + +#define KNOT_BUS_EVENT_STARTED "started" +#define KNOT_BUS_EVENT_STOPPED "stopped" +#define KNOT_BUS_EVENT_ZONE_UPD "zone_updated" +#define KNOT_BUS_EVENT_ZONE_KEYS_UPD "keys_updated" +#define KNOT_BUS_EVENT_ZONE_KSK_SUBM "zone_ksk_submission" +#define KNOT_BUS_EVENT_ZONE_INVALID "zone_dnssec_invalid" + +/*! + * \brief Creates unique D-Bus sender reference (common for whole process). + * + * \retval KNOT_EOK on successful create of reference. + * \retval Negative value on error. + */ +int dbus_open(void); + +/*! + * \brief Closes D-Bus. + */ +void dbus_close(void); + +/*! + * \brief Emit event signal for started daemon. + * + * \param up Indication if the server has been started. + */ +void dbus_emit_running(bool up); + +/*! + * \brief Emit event signal for updated zones. + * + * \param zone_name Zone name. + * \param serial Current zone SOA serial. + */ +void dbus_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial); + +/*! + * \brief Emit event signal for updated DNSSEC key set. + * + * \param zone_name Zone name. + */ +void dbus_emit_keys_updated(const knot_dname_t *zone_name); + +/*! + * \brief Emit event signal for KSK submission. + * + * \param zone_name Zone name. + * \param keytag Keytag of the ready key. + * \param keyid KASP id of the ready key. + */ +void dbus_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag, + const char *keyid); + +/*! + * \brief Emit event signal for failed DNSSEC validation. + * + * \param zone_name Zone name. + * \param remaining_secs Remaining time until a RRSIG expires. + */ +void dbus_emit_zone_invalid(const knot_dname_t *zone_name, uint32_t remaining_secs); diff --git a/src/knot/common/fdset.c b/src/knot/common/fdset.c index a0a0212..2bf4113 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]; @@ -326,8 +330,7 @@ void fdset_sweep(fdset_t *set, const fdset_sweep_cb_t cb, void *data) while (idx < set->n) { /* Check sweep state, remove if requested. */ if (set->timeout[idx] > 0 && set->timeout[idx] <= now.tv_sec) { - const int fd = fdset_get_fd(set, idx); - if (cb(set, fd, data) == FDSET_SWEEP) { + if (cb(set, idx, data) == FDSET_SWEEP) { (void)fdset_remove(set, idx); continue; } diff --git a/src/knot/common/fdset.h b/src/knot/common/fdset.h index e0c3dbe..81ec7a8 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/common/stats.c b/src/knot/common/stats.c index 79c0f23..da31e3d 100644 --- a/src/knot/common/stats.c +++ b/src/knot/common/stats.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 @@ -24,140 +24,168 @@ #include "knot/common/stats.h" #include "knot/common/log.h" #include "knot/nameserver/query_module.h" +#include "libknot/xdp.h" -struct { - bool active_dumper; - pthread_t dumper; - uint32_t timer; - server_t *server; -} stats = { 0 }; - -typedef struct { - FILE *fd; - const list_t *query_modules; - const knot_dname_t *zone; - unsigned threads; - int level; - bool zone_emitted; - bool zone_name_emitted; - bool module_emitted; -} dump_ctx_t; - -#define DUMP_STR(fd, level, name, ...) do { \ - fprintf(fd, "%-.*s"name":\n", level, " ", ##__VA_ARGS__); \ -} while (0) - -#define DUMP_CTR(fd, level, name, idx, val) do { \ - fprintf(fd, "%-.*s"name": %"PRIu64"\n", level, " ", idx, val); \ -} while (0) - -static uint64_t server_zone_count(server_t *server) +static uint64_t stats_get_counter(knot_atomic_uint64_t **stats_vals, uint32_t offset, + unsigned threads) { - return knot_zonedb_size(server->zone_db); + uint64_t res = 0; + for (unsigned i = 0; i < threads; i++) { + res += ATOMIC_GET(stats_vals[i][offset]); + } + return res; } -const stats_item_t server_stats[] = { - { "zone-count", { .server_val = server_zone_count } }, - { 0 } -}; - -static uint64_t zone_size(zone_t *zone) +int stats_xdp(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx) { - return zone->contents != NULL ? zone->contents->size : 0; +#ifdef ENABLE_XDP +#define DUMP(structure, item_name, avg) { \ + params.item_begin = true; \ + params.item = #item_name; \ + params.id = stats[0].if_name; \ + params.value = 0; \ + for (int j = 0; j < i->fd_xdp_count; j++) { \ + params.value += stats[j].structure.item_name; \ + } \ + if (avg) { \ + params.value /= i->fd_xdp_count; \ + } \ + int ret = fcn(¶ms, ctx); \ + if (ret != KNOT_EOK) { \ + return ret; \ + } \ } + stats_dump_params_t params = { .section = "xdp" }; -static uint64_t zone_max_ttl(zone_t *zone) -{ - return zone->contents != NULL ? zone->contents->max_ttl : 0; + if (ctx->section != NULL && strcasecmp(ctx->section, params.section) != 0) { + return KNOT_EOK; + } + + for (const iface_t *i = ctx->server->ifaces; + i != ctx->server->ifaces + ctx->server->n_ifaces; i++) { + if (i->fd_xdp_count == 0) { + continue; + } + knot_xdp_stats_t stats[i->fd_xdp_count]; + for (int j = 0; j < i->fd_xdp_count; j++) { + knot_xdp_socket_stats(i->xdp_sockets[j], &stats[j]); + } + + DUMP(socket, rx_dropped, false); + DUMP(socket, rx_invalid, false); + DUMP(socket, tx_invalid, false); + DUMP(socket, rx_full, false); + DUMP(socket, fq_empty, false); + DUMP(socket, tx_empty, false); + DUMP(rings, tx_busy, true); + DUMP(rings, fq_fill, true); + DUMP(rings, rx_fill, true); + DUMP(rings, tx_fill, true); + DUMP(rings, cq_fill, true); + } +#undef DUMP +#endif + return KNOT_EOK; } -const stats_item_t zone_contents_stats[] = { - { "size", { .zone_val = zone_size } }, - { "max-ttl", { .zone_val = zone_max_ttl } }, - { 0 } -}; +#define DUMP_VAL(params, it, val) { \ + (params).item = (it); \ + (params).value = (val); \ + int ret = fcn(&(params), ctx); \ + if (ret != KNOT_EOK) { \ + return ret; \ + } \ +} -uint64_t stats_get_counter(uint64_t **stats_vals, uint32_t offset, unsigned threads) +int stats_server(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx) { - uint64_t res = 0; - for (unsigned i = 0; i < threads; i++) { - res += ATOMIC_GET(stats_vals[i][offset]); + stats_dump_params_t params = { .section = "server" }; + + if (ctx->section != NULL && strcasecmp(ctx->section, params.section) != 0) { + return KNOT_EOK; } - return res; + + DUMP_VAL(params, "zone-count", knot_zonedb_size(ctx->server->zone_db)); + + return KNOT_EOK; } -static void dump_common(dump_ctx_t *ctx, knotd_mod_t *mod) +int stats_zone(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx) { - // Dump zone name. - if (ctx->zone != NULL) { - // Prevent from zone section override. - if (!ctx->zone_emitted) { - ctx->level = 0; - DUMP_STR(ctx->fd, ctx->level++, "zone"); - ctx->zone_emitted = true; - } + knot_dname_txt_storage_t zone; + stats_dump_params_t params = { .section = "zone", .zone = zone }; - if (!ctx->zone_name_emitted) { - ctx->level = 1; - knot_dname_txt_storage_t name; - if (knot_dname_to_str(name, ctx->zone, sizeof(name)) == NULL) { - return; - } - DUMP_STR(ctx->fd, ctx->level++, "\"%s\"", name); - ctx->zone_name_emitted = true; - } + if (ctx->section != NULL && strcasecmp(ctx->section, params.section) != 0) { + return KNOT_EOK; } - if (!ctx->module_emitted) { - DUMP_STR(ctx->fd, ctx->level++, "%s", mod->id->name + 1); - ctx->module_emitted = true; + assert(ctx->zone); + zone_contents_t *contents = ctx->zone->contents; + + if (knot_dname_to_str(zone, ctx->zone->name, sizeof(zone)) == NULL) { + return KNOT_EINVAL; } + + DUMP_VAL(params, "size", contents != NULL ? contents->size : 0); + DUMP_VAL(params, "max-ttl", contents != NULL ? contents->max_ttl : 0); + + return KNOT_EOK; } -static void dump_counter(dump_ctx_t *ctx, knotd_mod_t *mod, mod_ctr_t *ctr) +static int stats_counter(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx, + stats_dump_params_t *params, knotd_mod_t *mod, mod_ctr_t *ctr) { - uint64_t counter = stats_get_counter(mod->stats_vals, ctr->offset, ctx->threads); - if (counter == 0) { - // Skip empty counter. - return; - } + params->id = NULL; + params->value_pos = 0; - dump_common(ctx, mod); + uint64_t val = stats_get_counter(mod->stats_vals, ctr->offset, ctx->threads); - DUMP_CTR(ctx->fd, ctx->level, "%s", ctr->name, counter); + DUMP_VAL(*params, ctr->name, val); + + return KNOT_EOK; } -static void dump_counters(dump_ctx_t *ctx, knotd_mod_t *mod, mod_ctr_t *ctr) +static int stats_counters(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx, + stats_dump_params_t *params, knotd_mod_t *mod, mod_ctr_t *ctr) { - bool counter_emitted = false; - for (uint32_t j = 0; j < ctr->count; j++) { - uint64_t counter = stats_get_counter(mod->stats_vals, ctr->offset + j, ctx->threads); - if (counter == 0) { - // Skip empty counter. - continue; - } + char id[64]; + params->id = id; + params->value_pos = 0; + params->item_begin = true; - dump_common(ctx, mod); - - if (!counter_emitted) { - DUMP_STR(ctx->fd, ctx->level, "%s", ctr->name); - counter_emitted = true; - } + for (uint32_t i = 0; i < ctr->count; i++) { + uint64_t val = stats_get_counter(mod->stats_vals, ctr->offset + i, + ctx->threads); if (ctr->idx_to_str != NULL) { - char *str = ctr->idx_to_str(j, ctr->count); - if (str != NULL) { - DUMP_CTR(ctx->fd, ctx->level + 1, "%s", str, counter); - free(str); + char *str = ctr->idx_to_str(i, ctr->count); + if (str == NULL) { + continue; } + (void)snprintf(id, sizeof(id), "%s", str); + free(str); } else { - DUMP_CTR(ctx->fd, ctx->level + 1, "%u", j, counter); + (void)snprintf(id, sizeof(id), "%u", i); } + + DUMP_VAL(*params, ctr->name, val); + params->value_pos++; } + + params->id = NULL; + + return KNOT_EOK; } -static void dump_modules(dump_ctx_t *ctx) +int stats_modules(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx) { + if (ctx->section != NULL && strncasecmp(ctx->section, "mod-", strlen("mod-")) != 0) { + return KNOT_EOK; + } + + knot_dname_txt_storage_t zone; + stats_dump_params_t params = { 0 }; + knotd_mod_t *mod; WALK_LIST(mod, *ctx->query_modules) { // Skip modules without statistics. @@ -169,39 +197,114 @@ static void dump_modules(dump_ctx_t *ctx) ctx->threads = knotd_mod_threads(mod); } + params.section = mod->id->name + 1; + params.module_begin = true; + if (ctx->zone != NULL && params.zone == NULL) { + params.zone = knot_dname_to_str(zone, ctx->zone->name, + sizeof(zone)); + if (params.zone == NULL) { + return KNOT_EINVAL; + } + } + // Dump module counters. - ctx->module_emitted = false; for (int i = 0; i < mod->stats_count; i++) { mod_ctr_t *ctr = mod->stats_info + i; if (ctr->name == NULL) { // Empty counter. continue; } - if (ctr->count == 1) { - // Simple counter. - dump_counter(ctx, mod, ctr); - } else { - // Array of counters. - dump_counters(ctx, mod, ctr); + int ret = (ctr->count == 1) ? + stats_counter(fcn, ctx, ¶ms, mod, ctr) : + stats_counters(fcn, ctx, ¶ms, mod, ctr); + if (ret != KNOT_EOK) { + return ret; } } - if (ctx->module_emitted) { - ctx->level--; + } + + return KNOT_EOK; +} + +struct { + bool active_dumper; + pthread_t dumper; + uint32_t timer; + server_t *server; +} stats = { 0 }; + +typedef struct { + FILE *fd; + bool section_emitted; + bool zone_section_emitted; + bool zone_emitted; + bool id_emitted; +} dump_ctx_t; + +static int dump_ctr(stats_dump_params_t *params, stats_dump_ctx_t *dump_ctx) +{ + dump_ctx_t *ctx = dump_ctx->ctx; + + if (params->value == 0) { + return KNOT_EOK; + } + + const char *INDENT = " "; + unsigned indent = 0; + + if (params->zone != NULL) { + if (!ctx->zone_section_emitted) { + fprintf(ctx->fd, "zone:\n"); + ctx->zone_section_emitted = true; + } + + if (!ctx->zone_emitted) { + fprintf(ctx->fd, " \"%s\":\n", params->zone); + ctx->zone_emitted = true; } + indent += 2; } + + if (!ctx->section_emitted || params->module_begin) { + fprintf(ctx->fd, "%-.*s%s:\n", indent, INDENT, params->section); + ctx->section_emitted = true; + params->module_begin = false; + } + indent++; + + if (params->id != NULL) { + if (params->item_begin) { + fprintf(ctx->fd, "%-.*s%s:\n", indent, INDENT, params->item); + params->item_begin = false; + } + indent++; + fprintf(ctx->fd, "%-.*s%s: %"PRIu64"\n", indent, INDENT, + params->id, params->value); + } else { + fprintf(ctx->fd, "%-.*s%s: %"PRIu64"\n", indent, INDENT, + params->item, params->value); + } + + return KNOT_EOK; } -static void zone_stats_dump(zone_t *zone, dump_ctx_t *ctx) +static void zone_stats_dump(zone_t *zone, stats_dump_ctx_t *dump_ctx) { if (EMPTY_LIST(zone->query_modules)) { return; } - ctx->query_modules = &zone->query_modules; - ctx->zone = zone->name; - ctx->zone_name_emitted = false; + // Reset per-zone context. + dump_ctx_t *ctx = dump_ctx->ctx; + *ctx = (dump_ctx_t){ + .fd = ctx->fd, + .zone_section_emitted = ctx->zone_section_emitted, + }; + + dump_ctx->zone = zone; + dump_ctx->query_modules = &zone->query_modules; - dump_modules(ctx); + (void)stats_modules(dump_ctr, dump_ctx); } static void dump_to_file(conf_t *conf, FILE *fd, server_t *server) @@ -224,22 +327,28 @@ static void dump_to_file(conf_t *conf, FILE *fd, server_t *server) "identity: %s\n", date, ident); - dump_ctx_t ctx = { - .fd = fd, + dump_ctx_t ctx = { .fd = fd }; + + stats_dump_ctx_t dump_ctx = { + .server = server, .query_modules = conf->query_modules, + .ctx = &ctx, }; - // Dump server statistics. - DUMP_STR(ctx.fd, ctx.level, "server"); - for (const stats_item_t *item = server_stats; item->name != NULL; item++) { - DUMP_CTR(ctx.fd, ctx.level + 1, "%s", item->name, item->server_val(server)); - } + // Dump server counters. + (void)stats_server(dump_ctr, &dump_ctx); + + // Dump XDP counters. + ctx = (dump_ctx_t){ .fd = fd }; + (void)stats_xdp(dump_ctr, &dump_ctx); - // Dump global statistics. - dump_modules(&ctx); + // Dump global module counters. + ctx = (dump_ctx_t){ .fd = fd }; + (void)stats_modules(dump_ctr, &dump_ctx); - // Dump zone statistics. - knot_zonedb_foreach(server->zone_db, zone_stats_dump, &ctx); + // Dump per zone module counters (fixed zone counters not included). + ctx = (dump_ctx_t){ .fd = fd }; + knot_zonedb_foreach(server->zone_db, zone_stats_dump, &dump_ctx); } static void dump_stats(server_t *server) @@ -321,7 +430,6 @@ void stats_reconfigure(conf_t *conf, server_t *server) return; } - // Update server context. stats.server = server; conf_val_t val = conf_get(conf, C_STATS, C_TIMER); diff --git a/src/knot/common/stats.h b/src/knot/common/stats.h index 5cb51ca..4391178 100644 --- a/src/knot/common/stats.h +++ b/src/knot/common/stats.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 @@ -20,36 +20,65 @@ #pragma once +#include "contrib/atomic.h" #include "knot/server/server.h" -typedef uint64_t (*stats_server_val_f)(server_t *server); -typedef uint64_t (*stats_zone_val_f)(zone_t *zone); +/*! + * \brief Parameters for a statistic metric dump callback. + */ +typedef struct { + const char *section; + const char *item; + const char *id; + const char *zone; + uint64_t value; + unsigned value_pos; // Counted from 0. + + bool module_begin; // Indication of a new module. + bool item_begin; // Indication of a new item. +} stats_dump_params_t; /*! - * \brief Statistics metrics item. + * \brief Statistic metric context. */ typedef struct { - const char *name; /*!< Metrics name. */ - union { - stats_server_val_f server_val; /*!< Server metrics value getter. */ - stats_zone_val_f zone_val; /*!< Zone metrics value getter. */ - }; -} stats_item_t; + server_t *server; + zone_t *zone; + const list_t *query_modules; + + const char *section; // Optional section specification. + const char *item; // Optional item specification. + bool match; // Indication of non-empty [[section[.item]] selection. + + unsigned threads; // Internal cache for the number of workers. + + void *ctx; +} stats_dump_ctx_t; + +/*! + * \brief Statistic metric dump callback. + */ +typedef int (*stats_dump_ctr_f)(stats_dump_params_t *, stats_dump_ctx_t *); + +/*! + * \brief XDP metrics. + */ +int stats_xdp(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx); /*! - * \brief Basic server metrics. + * \brief Server metrics. */ -extern const stats_item_t server_stats[]; +int stats_server(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx); /*! - * \brief Basic zone metrics. + * \brief Zone metrics. */ -extern const stats_item_t zone_contents_stats[]; +int stats_zone(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx); /*! - * \brief Read out value of single counter summed across threads. + * \brief Modules metrics. */ -uint64_t stats_get_counter(uint64_t **stats_vals, uint32_t offset, unsigned threads); +int stats_modules(stats_dump_ctr_f fcn, stats_dump_ctx_t *ctx); /*! * \brief Reconfigures the statistics facility. diff --git a/src/knot/common/systemd.c b/src/knot/common/systemd.c index 5a72fb4..3f9d9a6 100644 --- a/src/knot/common/systemd.c +++ b/src/knot/common/systemd.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 @@ -14,8 +14,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <stdarg.h> -#include <stdio.h> #include <stdlib.h> #include "knot/common/systemd.h" @@ -43,12 +41,6 @@ static int systemd_zone_load_timeout(void) } #endif -#ifdef ENABLE_DBUS -#include <systemd/sd-bus.h> - -static sd_bus *_dbus = NULL; -#endif - void systemd_zone_load_timeout_notify(void) { #ifdef ENABLE_SYSTEMD @@ -92,91 +84,3 @@ void systemd_stopping_notify(void) sd_notify(0, "STOPPING=1\nSTATUS="); #endif } - -int systemd_dbus_open(void) -{ -#ifdef ENABLE_DBUS - if (_dbus != NULL) { - return KNOT_EOK; - } - - int ret = sd_bus_open_system(&_dbus); - if (ret < 0) { - return ret; - } - - /* Take a well-known service name so that clients can find us. */ - ret = sd_bus_request_name(_dbus, KNOT_DBUS_NAME, 0); - if (ret < 0) { - systemd_dbus_close(); - return ret; - } - - return KNOT_EOK; -#else - return KNOT_ENOTSUP; -#endif -} - -void systemd_dbus_close(void) -{ -#ifdef ENABLE_DBUS - _dbus = sd_bus_unref(_dbus); -#endif -} - -#define emit_event(event, ...) \ - sd_bus_emit_signal(_dbus, KNOT_DBUS_PATH, KNOT_DBUS_NAME".events", \ - event, __VA_ARGS__) - -void systemd_emit_running(bool up) -{ -#ifdef ENABLE_DBUS - emit_event(up ? KNOT_BUS_EVENT_STARTED : KNOT_BUS_EVENT_STOPPED, ""); -#endif -} - -void systemd_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial) -{ -#ifdef ENABLE_DBUS - knot_dname_txt_storage_t buff; - char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); - if (zone_str != NULL) { - emit_event(KNOT_BUS_EVENT_ZONE_UPD, "su", zone_str, serial); - } -#endif -} - -void systemd_emit_keys_updated(const knot_dname_t *zone_name) -{ -#ifdef ENABLE_DBUS - knot_dname_txt_storage_t buff; - char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); - if (zone_str != NULL) { - emit_event(KNOT_BUS_EVENT_ZONE_KEYS_UPD, "s", zone_str); - } -#endif -} - -void systemd_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag, - const char *keyid) -{ -#ifdef ENABLE_DBUS - knot_dname_txt_storage_t buff; - char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); - if (zone_str != NULL) { - emit_event(KNOT_BUS_EVENT_ZONE_KSK_SUBM, "sqs", zone_str, keytag, keyid); - } -#endif -} - -void systemd_emit_zone_invalid(const knot_dname_t *zone_name) -{ -#ifdef ENABLE_DBUS - knot_dname_txt_storage_t buff; - char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff)); - if (zone_str != NULL) { - emit_event(KNOT_BUS_EVENT_ZONE_INVALID, "s", zone_str); - } -#endif -} diff --git a/src/knot/common/systemd.h b/src/knot/common/systemd.h index c6fe260..2038e94 100644 --- a/src/knot/common/systemd.h +++ b/src/knot/common/systemd.h @@ -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 @@ -20,18 +20,6 @@ #pragma once -#include "libknot/libknot.h" - -#define KNOT_DBUS_NAME "cz.nic.knotd" -#define KNOT_DBUS_PATH "/cz/nic/knotd" - -#define KNOT_BUS_EVENT_STARTED "started" -#define KNOT_BUS_EVENT_STOPPED "stopped" -#define KNOT_BUS_EVENT_ZONE_UPD "zone_updated" -#define KNOT_BUS_EVENT_ZONE_KEYS_UPD "keys_updated" -#define KNOT_BUS_EVENT_ZONE_KSK_SUBM "zone_ksk_submission" -#define KNOT_BUS_EVENT_ZONE_INVALID "zone_dnssec_invalid" - /*! * \brief Notify systemd about zone loading start. */ @@ -59,55 +47,3 @@ void systemd_reloading_notify(void); * \brief Notify systemd about service is stopping. */ void systemd_stopping_notify(void); - -/*! - * \brief Creates unique D-Bus sender reference (common for whole process). - * - * \retval KNOT_EOK on successful create of reference. - * \retval Negative value on error. - */ -int systemd_dbus_open(void); - -/*! - * \brief Closes D-Bus. - */ -void systemd_dbus_close(void); - -/*! - * \brief Emit event signal for started daemon. - * - * \param up Indication if the server has been started. - */ -void systemd_emit_running(bool up); - -/*! - * \brief Emit event signal for updated zones. - * - * \param zone_name Zone name. - * \param serial Current zone SOA serial. - */ -void systemd_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial); - -/*! - * \brief Emit event signal for updated DNSSEC key set. - * - * \param zone_name Zone name. - */ -void systemd_emit_keys_updated(const knot_dname_t *zone_name); - -/*! - * \brief Emit event signal for KSK submission. - * - * \param zone_name Zone name. - * \param keytag Keytag of the ready key. - * \param keyid KASP id of the ready key. - */ -void systemd_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag, - const char *keyid); - -/*! - * \brief Emit event signal for failed DNSSEC validation. - * - * \param zone_name Zone name. - */ -void systemd_emit_zone_invalid(const knot_dname_t *zone_name); diff --git a/src/knot/conf/base.c b/src/knot/conf/base.c index 51caf10..4683171 100644 --- a/src/knot/conf/base.c +++ b/src/knot/conf/base.c @@ -130,6 +130,9 @@ static void init_cache( static bool running_xdp_tcp; static uint16_t running_xdp_quic; static bool running_route_check; + static uint16_t running_ring_size; + static uint16_t running_busypoll_budget; + static uint16_t running_busypoll_timeout; static size_t running_udp_threads; static size_t running_tcp_threads; static size_t running_xdp_threads; @@ -148,6 +151,9 @@ static void init_cache( running_xdp_quic = conf_get_int(conf, C_XDP, C_QUIC_PORT); } running_route_check = conf_get_bool(conf, C_XDP, C_ROUTE_CHECK); + running_ring_size = conf_get_int(conf, C_XDP, C_RING_SIZE); + running_busypoll_budget = conf_get_int(conf, C_XDP, C_BUSYPOLL_BUDGET); + running_busypoll_timeout = conf_get_int(conf, C_XDP, C_BUSYPOLL_TIMEOUT); running_udp_threads = conf_udp_threads(conf); running_tcp_threads = conf_tcp_threads(conf); running_xdp_threads = conf_xdp_threads(conf); @@ -235,6 +241,12 @@ static void init_cache( conf->cache.xdp_route_check = running_route_check; + conf->cache.xdp_ring_size = running_ring_size; + + conf->cache.xdp_busypoll_budget = running_busypoll_budget; + + conf->cache.xdp_busypoll_timeout = running_busypoll_timeout; + val = conf_get(conf, C_CTL, C_TIMEOUT); conf->cache.ctl_timeout = conf_int(&val) * 1000; /* infinite_adjust() call isn't needed, 0 is adjusted later anyway. */ @@ -335,9 +347,10 @@ int conf_new( ret = out->api->init(&out->db, NULL, &lmdb_opts); // Remove the database to ensure it is temporary. - if (!remove_path(lmdb_opts.path)) { - CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'", - lmdb_opts.path); + int ret2 = remove_path(lmdb_opts.path, false); + if (ret2 != KNOT_EOK) { + CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s' (%s)", + lmdb_opts.path, knot_strerror(ret2)); } } else { // Set the specified database. diff --git a/src/knot/conf/base.h b/src/knot/conf/base.h index 5c77cac..1ac5ef5 100644 --- a/src/knot/conf/base.h +++ b/src/knot/conf/base.h @@ -165,6 +165,9 @@ typedef struct { const char *srv_version; uint32_t srv_quic_idle_close; uint16_t xdp_quic; + uint16_t xdp_ring_size; + uint16_t xdp_busypoll_budget; + uint16_t xdp_busypoll_timeout; int ctl_timeout; bool xdp_udp; bool xdp_tcp; diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index 55ee971..5e9ed83 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.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 @@ -606,8 +606,9 @@ void conf_mix_iter_next( } } -int64_t conf_int( - conf_val_t *val) +int64_t conf_int_alt( + conf_val_t *val, + bool alternative) { assert(val != NULL && val->item != NULL); assert(val->item->type == YP_TINT || @@ -618,7 +619,7 @@ int64_t conf_int( conf_val(val); return yp_int(val->data); } else { - return val->item->var.i.dflt; + return alternative ? val->item->var.i.dflt_alt : val->item->var.i.dflt; } } @@ -1063,7 +1064,7 @@ static int str_label( size_t index = labels - right_index - 1; // Create a dname from the single label. - size_t prefix_len = knot_dname_prefixlen(zone, index, NULL); + size_t prefix_len = knot_dname_prefixlen(zone, index); size_t label_len = *(zone + prefix_len); memcpy(label, zone + prefix_len, 1 + label_len); label[1 + label_len] = '\0'; @@ -1379,6 +1380,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); @@ -1398,7 +1401,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 562722d..f79a284 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 @@ -44,6 +44,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. */ @@ -469,13 +471,20 @@ void conf_mix_iter_next( /*! * Gets the numeric value of the item. * - * \param[in] val Item value. + * \param[in] val Item value. + * \param[in] alternative Use alternative default value. * * \return Integer. */ -int64_t conf_int( - conf_val_t *val +int64_t conf_int_alt( + conf_val_t *val, + bool alternative ); +inline static int64_t conf_int( + conf_val_t *val) +{ + return conf_int_alt(val, false); +} /*! * Gets the boolean value of the item. diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index 7b30a2f..fd55fc8 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -64,9 +64,7 @@ static const knot_lookup_t dnssec_key_algs[] = { { DNSSEC_KEY_ALGORITHM_RSA_SHA512, "rsasha512" }, { DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256, "ecdsap256sha256" }, { DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384, "ecdsap384sha384" }, -#ifdef HAVE_ED25519 { DNSSEC_KEY_ALGORITHM_ED25519, "ed25519" }, -#endif #ifdef HAVE_ED448 { DNSSEC_KEY_ALGORITHM_ED448, "ed448" }, #endif @@ -249,15 +247,8 @@ 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 }, - // Legacy items. - { C_LISTEN_XDP, YP_TADDR, YP_VADDR = { 0 }, YP_FMULTI, { legacy_item } }, - { C_MAX_TCP_CLIENTS, YP_TINT, YP_VINT = { 0, INT32_MAX, 0 }, YP_FNONE, { legacy_item } }, - { C_TCP_HSHAKE_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, - { C_TCP_REPLY_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, - { C_MAX_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, - { C_MAX_IPV4_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, - { C_MAX_IPV6_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, { NULL } }; @@ -274,10 +265,10 @@ static const yp_item_t desc_xdp[] = { { C_TCP_IDLE_RESET, YP_TINT, YP_VINT = { 1, INT32_MAX, 20, YP_STIME } }, { C_TCP_RESEND, YP_TINT, YP_VINT = { 1, INT32_MAX, 5, YP_STIME } }, { C_ROUTE_CHECK, YP_TBOOL, YP_VNONE }, - { C_EXTRA_FRAMES, YP_TBOOL, YP_VNONE }, + { C_RING_SIZE, YP_TINT, YP_VINT = { 4, 32768, 2048 } }, + { C_BUSYPOLL_BUDGET, YP_TINT, YP_VINT = { 0, UINT16_MAX, 0 } }, + { C_BUSYPOLL_TIMEOUT, YP_TINT, YP_VINT = { 1, UINT16_MAX, 20 } }, { C_COMMENT, YP_TSTR, YP_VNONE }, - // Legacy items. - { C_QUIC_LOG, YP_TBOOL, YP_VNONE, YP_FNONE, { legacy_item } }, { NULL } }; @@ -350,6 +341,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 }, @@ -432,7 +424,7 @@ static const yp_item_t desc_policy[] = { CONF_IO_FRLD_ZONES }, { C_RRSIG_REFRESH, YP_TINT, YP_VINT = { 1, INT32_MAX, YP_NIL, YP_STIME }, CONF_IO_FRLD_ZONES }, - { C_RRSIG_PREREFRESH, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME }, + { C_RRSIG_PREREFRESH, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME, DAYS(1) }, CONF_IO_FRLD_ZONES }, { C_REPRO_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, { C_NSEC3, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, @@ -506,27 +498,12 @@ static const yp_item_t desc_policy[] = { { C_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, \ YP_FMULTI | FLAGS, { check_modref } }, \ { C_COMMENT, YP_TSTR, YP_VNONE }, \ - /* Legacy items.*/ \ - { C_DISABLE_ANY, YP_TBOOL, YP_VNONE, YP_FNONE, { legacy_item } }, \ - { C_MAX_ZONE_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, \ - { C_MAX_JOURNAL_USAGE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, \ - { C_MAX_JOURNAL_DEPTH, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0 }, YP_FNONE, { legacy_item } }, \ - { C_MAX_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, \ - { C_MIN_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, \ static const yp_item_t desc_template[] = { { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, { C_GLOBAL_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, YP_FMULTI | CONF_IO_FRLD_MOD, { check_modref } }, ZONE_ITEMS(CONF_IO_FRLD_ZONES) - // Legacy items. - { C_TIMER_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } }, - { C_MAX_TIMER_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, - { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } }, - { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, 0 }, YP_FNONE, { legacy_item } }, - { C_MAX_JOURNAL_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, - { C_KASP_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } }, - { C_MAX_KASP_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, { NULL } }; diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h index dc5a04e..d2966e6 100644 --- a/src/knot/conf/schema.h +++ b/src/knot/conf/schema.h @@ -33,6 +33,8 @@ #define C_BACKLOG "\x07""backlog" #define C_BG_WORKERS "\x12""background-workers" #define C_BLOCK_NOTIFY_XFR "\x1B""block-notify-after-transfer" +#define C_BUSYPOLL_BUDGET "\x0F""busypoll-budget" +#define C_BUSYPOLL_TIMEOUT "\x10""busypoll-timeout" #define C_CATALOG_DB "\x0A""catalog-db" #define C_CATALOG_DB_MAX_SIZE "\x13""catalog-db-max-size" #define C_CATALOG_GROUP "\x0D""catalog-group" @@ -65,7 +67,6 @@ #define C_ECS "\x12""edns-client-subnet" #define C_EXPIRE_MAX_INTERVAL "\x13""expire-max-interval" #define C_EXPIRE_MIN_INTERVAL "\x13""expire-min-interval" -#define C_EXTRA_FRAMES "\x0C""extra-frames" #define C_FILE "\x04""file" #define C_GLOBAL_MODULE "\x0D""global-module" #define C_ID "\x02""id" @@ -94,6 +95,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" @@ -117,7 +119,6 @@ #define C_PROXY_ALLOWLIST "\x0F""proxy-allowlist" #define C_QUIC "\x04""quic" #define C_QUIC_IDLE_CLOSE "\x17""quic-idle-close-timeout" -#define C_QUIC_LOG "\x08""quic-log" #define C_QUIC_MAX_CLIENTS "\x10""quic-max-clients" #define C_QUIC_OUTBUF_MAX_SIZE "\x14""quic-outbuf-max-size" #define C_QUIC_PORT "\x09""quic-port" @@ -127,6 +128,7 @@ #define C_RETRY_MAX_INTERVAL "\x12""retry-max-interval" #define C_RETRY_MIN_INTERVAL "\x12""retry-min-interval" #define C_REVERSE_GEN "\x10""reverse-generate" +#define C_RING_SIZE "\x09""ring-size" #define C_RMT "\x06""remote" #define C_RMTS "\x07""remotes" #define C_RMT_POOL_LIMIT "\x11""remote-pool-limit" @@ -167,6 +169,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" @@ -192,24 +195,6 @@ #define C_ZSK_LIFETIME "\x0C""zsk-lifetime" #define C_ZSK_SIZE "\x08""zsk-size" -// Legacy items. -#define C_DISABLE_ANY "\x0B""disable-any" -#define C_LISTEN_XDP "\x0A""listen-xdp" -#define C_MAX_TIMER_DB_SIZE "\x11""max-timer-db-size" -#define C_MAX_JOURNAL_DB_SIZE "\x13""max-journal-db-size" -#define C_MAX_KASP_DB_SIZE "\x10""max-kasp-db-size" -#define C_TCP_HSHAKE_TIMEOUT "\x15""tcp-handshake-timeout" -#define C_TCP_REPLY_TIMEOUT "\x11""tcp-reply-timeout" -#define C_MAX_TCP_CLIENTS "\x0F""max-tcp-clients" -#define C_MAX_UDP_PAYLOAD "\x0F""max-udp-payload" -#define C_MAX_IPV4_UDP_PAYLOAD "\x14""max-ipv4-udp-payload" -#define C_MAX_IPV6_UDP_PAYLOAD "\x14""max-ipv6-udp-payload" -#define C_MAX_ZONE_SIZE "\x0D""max-zone-size" -#define C_MAX_REFRESH_INTERVAL "\x14""max-refresh-interval" -#define C_MIN_REFRESH_INTERVAL "\x14""min-refresh-interval" -#define C_MAX_JOURNAL_DEPTH "\x11""max-journal-depth" -#define C_MAX_JOURNAL_USAGE "\x11""max-journal-usage" - enum { KEYSTORE_BACKEND_PEM = 1, KEYSTORE_BACKEND_PKCS11 = 2, diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c index e232fba..1ea8446 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" @@ -345,15 +343,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; } @@ -551,7 +547,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) { @@ -562,7 +557,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) @@ -576,30 +597,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"; @@ -713,10 +730,6 @@ int check_policy( C_NSEC3_ITER, args->id, args->id_len); unsigned algorithm = conf_opt(&alg); - if (algorithm < DNSSEC_KEY_ALGORITHM_RSA_SHA256) { - CONF_LOG(LOG_NOTICE, "algorithm %u is deprecated and shouldn't be used for DNSSEC signing", - algorithm); - } int64_t ksk_size = conf_int(&ksk); if (ksk_size != YP_NIL && !dnssec_algorithm_key_size_check(algorithm, ksk_size)) { @@ -779,14 +792,6 @@ int check_policy( return KNOT_EINVAL; } -#ifndef HAVE_GNUTLS_REPRODUCIBLE - conf_val_t repro_sign = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, - C_REPRO_SIGNING, args->id, args->id_len); - if (conf_bool(&repro_sign)) { - CONF_LOG(LOG_WARNING, "reproducible signing not available, signing normally"); - } -#endif - if (conf_bool(&nsec3)) { uint16_t iters = conf_int(&nsec3_iters); if (iters > 0) { @@ -880,12 +885,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 7febce1..56e1d67 100644 --- a/src/knot/ctl/commands.c +++ b/src/knot/ctl/commands.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 @@ -17,12 +17,14 @@ #include <string.h> #include <unistd.h> #include <sys/time.h> +#include <time.h> #include <urcu.h> #include "knot/common/log.h" #include "knot/common/stats.h" #include "knot/conf/confio.h" #include "knot/ctl/commands.h" +#include "knot/ctl/process.h" #include "knot/dnssec/key-events.h" #include "knot/events/events.h" #include "knot/events/handlers.h" @@ -36,6 +38,7 @@ #include "knot/zone/zonefile.h" #include "libknot/libknot.h" #include "libknot/yparser/yptrafo.h" +#include "contrib/atomic.h" #include "contrib/files.h" #include "contrib/string.h" #include "contrib/strtonum.h" @@ -68,7 +71,7 @@ static struct { sizeof(((send_ctx_t *)0)->ttl) + sizeof(((send_ctx_t *)0)->type) + sizeof(((send_ctx_t *)0)->rdata)]; -} ctl_globals; +} ctl_globals[CTL_MAX_CONCURRENT + 1]; static bool allow_blocking_while_ctl_txn(zone_event_type_t event) { @@ -645,47 +648,49 @@ static int zone_backup_cmd(zone_t *zone, ctl_args_t *args) return KNOT_EOK; } + int ret = KNOT_EOK; + pthread_mutex_lock(&zone->cu_lock); if (zone->backup_ctx != NULL) { log_zone_warning(zone->name, "backup or restore already in progress, skipping zone"); ctx->failed = true; - return KNOT_EPROGRESS; + ret = KNOT_EPROGRESS; } - if (ctx->restore_mode && zone->control_update != NULL) { + if (ctx->restore_mode && zone->control_update != NULL && ret == KNOT_EOK) { log_zone_warning(zone->name, "restoring backup not possible due to open control transaction"); ctx->failed = true; - return KNOT_TXN_EEXISTS; + ret = KNOT_TXN_EEXISTS; + } + + if (ret == KNOT_EOK) { + zone->backup_ctx = ctx; } + pthread_mutex_unlock(&zone->cu_lock); ctx->zone_count++; - int ret; - if (!ctx->backup_global) { + if (!ctx->backup_global && ret == KNOT_EOK) { ret = global_backup(ctx, zone_catalog(zone), zone->name); - if (ret != KNOT_EOK) { - return ret; - } } - if (ctx->backup_params & BACKUP_PARAM_KEYSONLY) { + bool finish = false; + if ((ctx->backup_params & BACKUP_PARAM_KEYSONLY) && ret == KNOT_EOK) { ret = zone_backup_keysonly(ctx, conf(), zone); - if (ret != KNOT_EOK) { - return ret; - } - if (ctx->restore_mode) { + if (ctx->restore_mode && ret == KNOT_EOK) { ret = zone_keys_load(zone, args); - if (ret != KNOT_EOK) { - return ret; - } } if (!(ctx->backup_params & BACKUP_PARAM_EVENT)) { - return ret; + finish = true; } } - zone->backup_ctx = ctx; + if (ret != KNOT_EOK || finish) { + zone->backup_ctx = NULL; + return ret; + } + pthread_mutex_lock(&ctx->readers_mutex); ctx->readers++; pthread_mutex_unlock(&ctx->readers_mutex); @@ -720,7 +725,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", @@ -761,6 +766,11 @@ static int zone_sign(zone_t *zone, _unused_ ctl_args_t *args) return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true); } +static int zone_validate(zone_t *zone, _unused_ ctl_args_t *args) +{ + return schedule_trigger(zone, args, ZONE_EVENT_VALIDATE, true); +} + static int zone_keys_load(zone_t *zone, _unused_ ctl_args_t *args) { conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); @@ -811,8 +821,8 @@ static int zone_ksk_sbm_confirm(zone_t *zone, _unused_ ctl_args_t *args) conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); if (ret == KNOT_EOK && conf_bool(&val)) { - // NOT zone_events_schedule_user(), intentionally! - ret = schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, false); + // NOT zone_events_schedule_user() or schedule_trigger(), intentionally! + zone_events_schedule_now(zone, ZONE_EVENT_DNSSEC); } return ret; @@ -846,17 +856,23 @@ static int zone_xfr_thaw(zone_t *zone, _unused_ ctl_args_t *args) return KNOT_EOK; } -static int zone_txn_begin(zone_t *zone, _unused_ ctl_args_t *args) +static int zone_txn_begin_l(zone_t *zone, _unused_ ctl_args_t *args) { - if (zone->control_update != NULL) { + if (zone->control_update != NULL || conf()->io.txn != NULL) { return KNOT_TXN_EEXISTS; } - if (zone->backup_ctx != NULL && zone->backup_ctx->restore_mode) { + struct zone_backup_ctx *backup_ctx = zone->backup_ctx; + if (backup_ctx != NULL && backup_ctx->restore_mode) { log_zone_warning(zone->name, "zone restore pending, try opening control transaction later"); return KNOT_EAGAIN; } + if (zone->events.running && zone->events.type >= 0 && zone->events.blocking[zone->events.type] != NULL) { + log_zone_warning(zone->name, "some blocking event running, try opening control transaction later"); + return KNOT_EAGAIN; + } + zone->control_update = malloc(sizeof(zone_update_t)); if (zone->control_update == NULL) { return KNOT_ENOMEM; @@ -872,7 +888,15 @@ static int zone_txn_begin(zone_t *zone, _unused_ ctl_args_t *args) return ret; } -static int zone_txn_commit(zone_t *zone, _unused_ ctl_args_t *args) +static int zone_txn_begin(zone_t *zone, ctl_args_t *args) +{ + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_txn_begin_l(zone, args); + pthread_mutex_unlock(&zone->cu_lock); + return ret; +} + +static int zone_txn_commit_l(zone_t *zone, _unused_ ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; @@ -934,15 +958,26 @@ static int zone_txn_commit(zone_t *zone, _unused_ ctl_args_t *args) return KNOT_EOK; } +static int zone_txn_commit(zone_t *zone, ctl_args_t *args) +{ + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_txn_commit_l(zone, args); + pthread_mutex_unlock(&zone->cu_lock); + return ret; +} + static int zone_txn_abort(zone_t *zone, _unused_ ctl_args_t *args) { + pthread_mutex_lock(&zone->cu_lock); if (zone->control_update == NULL) { args->suppress = true; + pthread_mutex_unlock(&zone->cu_lock); return KNOT_TXN_ENOTEXISTS; } zone_control_clear(zone); + pthread_mutex_unlock(&zone->cu_lock); return KNOT_EOK; } @@ -1089,7 +1124,7 @@ static int get_owner(uint8_t *out, size_t out_len, knot_dname_t *origin, static int zone_read(zone_t *zone, ctl_args_t *args) { - send_ctx_t *ctx = &ctl_globals.send_ctx; + send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; @@ -1131,7 +1166,7 @@ static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag) return KNOT_TXN_ENOTEXISTS; } - send_ctx_t *ctx = &ctl_globals.send_ctx; + send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; @@ -1170,7 +1205,10 @@ static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag) static int zone_txn_get(zone_t *zone, ctl_args_t *args) { - return zone_flag_txn_get(zone, args, NULL); + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_flag_txn_get(zone, args, NULL); + pthread_mutex_unlock(&zone->cu_lock); + return ret; } static int send_changeset_part(changeset_t *ch, send_ctx_t *ctx, bool from) @@ -1233,7 +1271,7 @@ static int send_changeset(changeset_t *ch, send_ctx_t *ctx) return send_changeset_part(ch, ctx, false); } -static int zone_txn_diff(zone_t *zone, ctl_args_t *args) +static int zone_txn_diff_l(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; @@ -1245,7 +1283,7 @@ static int zone_txn_diff(zone_t *zone, ctl_args_t *args) return zone_flag_txn_get(zone, args, CTL_FLAG_DIFF_ADD); } - send_ctx_t *ctx = &ctl_globals.send_ctx; + send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; @@ -1254,6 +1292,14 @@ static int zone_txn_diff(zone_t *zone, ctl_args_t *args) return send_changeset(&zone->control_update->change, ctx); } +static int zone_txn_diff(zone_t *zone, ctl_args_t *args) +{ + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_txn_diff_l(zone, args); + pthread_mutex_unlock(&zone->cu_lock); + return ret; +} + static int get_ttl(zone_t *zone, ctl_args_t *args, uint32_t *ttl) { knot_dname_storage_t owner; @@ -1297,8 +1343,8 @@ static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args, const char *ttl = need_ttl ? args->data[KNOT_CTL_IDX_TTL] : NULL; // Prepare a buffer for a reconstructed record. - const size_t buff_len = sizeof(ctl_globals.txt_rr); - char *buff = ctl_globals.txt_rr; + const size_t buff_len = sizeof(ctl_globals[args->thread_idx].txt_rr); + char *buff = ctl_globals[args->thread_idx].txt_rr; // Choose default TTL if none was specified. uint32_t default_ttl = 0; @@ -1321,7 +1367,7 @@ static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args, size_t rdata_len = ret; // Parse the record. - zs_scanner_t *scanner = &ctl_globals.scanner; + zs_scanner_t *scanner = &ctl_globals[args->thread_idx].scanner; if (zs_init(scanner, origin, KNOT_CLASS_IN, default_ttl) != 0 || zs_set_input_string(scanner, buff, rdata_len) != 0 || zs_parse_record(scanner) != 0 || @@ -1347,7 +1393,7 @@ parser_failed: return ret; } -static int zone_txn_set(zone_t *zone, ctl_args_t *args) +static int zone_txn_set_l(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; @@ -1371,7 +1417,15 @@ static int zone_txn_set(zone_t *zone, ctl_args_t *args) return ret; } -static int zone_txn_unset(zone_t *zone, ctl_args_t *args) +static int zone_txn_set(zone_t *zone, ctl_args_t *args) +{ + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_txn_set_l(zone, args); + pthread_mutex_unlock(&zone->cu_lock); + return ret; +} + +static int zone_txn_unset_l(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; @@ -1421,6 +1475,14 @@ static int zone_txn_unset(zone_t *zone, ctl_args_t *args) } } +static int zone_txn_unset(zone_t *zone, ctl_args_t *args) +{ + pthread_mutex_lock(&zone->cu_lock); + int ret = zone_txn_unset_l(zone, args); + pthread_mutex_unlock(&zone->cu_lock); + return ret; +} + static bool zone_exists(const knot_dname_t *zone, void *data) { assert(zone); @@ -1487,7 +1549,7 @@ static int purge_orphan_member_cb(const knot_dname_t *member, const knot_dname_t orphan->name = (knot_dname_t *)member; orphan->server = server; - purge_flag_t params = + const purge_flag_t params = PURGE_ZONE_TIMERS | PURGE_ZONE_JOURNAL | PURGE_ZONE_KASPDB | PURGE_ZONE_BEST | PURGE_ZONE_LOG; @@ -1530,7 +1592,7 @@ static int catalog_orphans_sweep(server_t *server) knot_strerror(ret)); } } else { - log_error("can't open catalog for purging (%s)", + log_error("can not open catalog for purging (%s)", knot_strerror(ret)); } @@ -1683,7 +1745,7 @@ static int zone_purge(zone_t *zone, ctl_args_t *args) (void)zone_events_schedule_blocking(zone, ZONE_EVENT_EXPIRE, true); } - purge_flag_t params = + const purge_flag_t params = MATCH_OR_FILTER(args, CTL_FILTER_PURGE_TIMERS) * PURGE_ZONE_TIMERS | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_ZONEFILE) * PURGE_ZONE_ZONEFILE | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_JOURNAL) * PURGE_ZONE_JOURNAL | @@ -1695,213 +1757,82 @@ static int zone_purge(zone_t *zone, ctl_args_t *args) return selective_zone_purge(conf(), zone, params); } -static int send_stats_ctr(mod_ctr_t *ctr, uint64_t **stats_vals, unsigned threads, - ctl_args_t *args, knot_ctl_data_t *data) +int ctl_dump_ctr(stats_dump_params_t *params, stats_dump_ctx_t *ctx) { - char index[128]; - char value[32]; - - if (ctr->count == 1) { - uint64_t counter = stats_get_counter(stats_vals, ctr->offset, threads); - int ret = snprintf(value, sizeof(value), "%"PRIu64, counter); - if (ret <= 0 || ret >= sizeof(value)) { - return KNOT_ESPACE; - } - - (*data)[KNOT_CTL_IDX_ID] = NULL; - (*data)[KNOT_CTL_IDX_DATA] = value; + ctl_args_t *args = ctx->ctx; - ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, data); - if (ret != KNOT_EOK) { - return ret; - } - } else { - bool force = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], - CTL_FLAG_FORCE); - - for (uint32_t i = 0; i < ctr->count; i++) { - uint64_t counter = stats_get_counter(stats_vals, ctr->offset + i, threads); - - // Skip empty counters. - if (counter == 0 && !force) { - continue; - } - - int ret; - if (ctr->idx_to_str) { - char *str = ctr->idx_to_str(i, ctr->count); - if (str == NULL) { - continue; - } - ret = snprintf(index, sizeof(index), "%s", str); - free(str); - } else { - ret = snprintf(index, sizeof(index), "%u", i); - } - if (ret <= 0 || ret >= sizeof(index)) { - return KNOT_ESPACE; - } - - ret = snprintf(value, sizeof(value), "%"PRIu64, counter); - if (ret <= 0 || ret >= sizeof(value)) { - return KNOT_ESPACE; - } - - (*data)[KNOT_CTL_IDX_ID] = index; - (*data)[KNOT_CTL_IDX_DATA] = value; - - knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA : - KNOT_CTL_TYPE_EXTRA; - ret = knot_ctl_send(args->ctl, type, data); - if (ret != KNOT_EOK) { - return ret; - } - } + if (ctx->item != NULL && strcasecmp(ctx->item, params->item) != 0) { + return KNOT_EOK; } + ctx->match = true; - return KNOT_EOK; -} - -static int modules_stats(list_t *query_modules, ctl_args_t *args, knot_ctl_data_t *data) -{ - if (query_modules == NULL) { + if (params->value == 0 && + !ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) { return KNOT_EOK; } - const char *section = args->data[KNOT_CTL_IDX_SECTION]; - const char *item = args->data[KNOT_CTL_IDX_ITEM]; - - bool section_found = (section == NULL) ? true : false; - bool item_found = (item == NULL) ? true : false; - - knotd_mod_t *mod; - WALK_LIST(mod, *query_modules) { - // Skip modules without statistics. - if (mod->stats_count == 0) { - continue; - } - - // Check for specific module. - if (section != NULL) { - if (section_found) { - break; - } else if (strcasecmp(mod->id->name + 1, section) == 0) { - section_found = true; - } else { - continue; - } - } - - (*data)[KNOT_CTL_IDX_SECTION] = mod->id->name + 1; - - unsigned threads = knotd_mod_threads(mod); - - for (int i = 0; i < mod->stats_count; i++) { - mod_ctr_t *ctr = mod->stats_info + i; - - // Skip empty counter. - if (ctr->name == NULL) { - continue; - } - - // Check for specific counter. - if (item != NULL) { - if (item_found) { - break; - } else if (strcasecmp(ctr->name, item) == 0) { - item_found = true; - } else { - continue; - } - } + char value[32]; + int ret = snprintf(value, sizeof(value), "%"PRIu64, params->value); + if (ret <= 0 || ret >= sizeof(value)) { + return KNOT_ESPACE; + } - (*data)[KNOT_CTL_IDX_ITEM] = ctr->name; + knot_ctl_data_t data = { + [KNOT_CTL_IDX_SECTION] = params->section, + [KNOT_CTL_IDX_ITEM] = params->item, + [KNOT_CTL_IDX_ID] = params->id, + [KNOT_CTL_IDX_ZONE] = params->zone, + [KNOT_CTL_IDX_DATA] = value, + }; - // Send the counters. - int ret = send_stats_ctr(ctr, mod->stats_vals, threads, args, data); - if (ret != KNOT_EOK) { - return ret; - } - } - } + knot_ctl_type_t type = (params->value_pos == 0) ? + KNOT_CTL_TYPE_DATA : KNOT_CTL_TYPE_EXTRA; - return (section_found && item_found) ? KNOT_EOK : KNOT_ENOENT; + return knot_ctl_send(args->ctl, type, &data); } static int common_stats(ctl_args_t *args, zone_t *zone) { - const char *section = args->data[KNOT_CTL_IDX_SECTION]; - const char *item = args->data[KNOT_CTL_IDX_ITEM]; - - char value[32]; - knot_ctl_data_t data = { - [KNOT_CTL_IDX_DATA] = value + stats_dump_ctx_t dump_ctx = { + .server = args->server, + .zone = zone, + .section = args->data[KNOT_CTL_IDX_SECTION], + .item = args->data[KNOT_CTL_IDX_ITEM], + .ctx = args, }; - knot_dname_txt_storage_t name = ""; - if (zone != NULL) { - if (knot_dname_to_str(name, zone->name, sizeof(name)) == NULL) { - return KNOT_EINVAL; - } - data[KNOT_CTL_IDX_ZONE] = name; - } - - bool found = (section == NULL) ? true : false; - - // Process zone metrics. - const char *section_name = (zone != NULL) ? "zone" : "server"; - if (section == NULL || strcasecmp(section, section_name) == 0) { - data[KNOT_CTL_IDX_SECTION] = section_name; - - const stats_item_t *items = (zone != NULL) ? zone_contents_stats : - server_stats; - for (const stats_item_t *i = items; i->name != NULL; i++) { - if (item != NULL) { - if (found) { - break; - } else if (strcmp(i->name, item) == 0) { - found = true; - } else { - continue; - } - } else { - found = true; - } +#define STATS_CHECK(ret, send) { \ + if (ret != KNOT_EOK) { \ + if ((send)) { /* Prevents duplicit zone error logs. */ \ + send_error(args, knot_strerror(ret)); \ + } \ + return ret; \ + } \ +} - data[KNOT_CTL_IDX_ITEM] = i->name; - int ret = snprintf(value, sizeof(value), "%"PRIu64, - (zone != NULL) ? i->zone_val(zone) : - i->server_val(args->server)); - if (ret <= 0 || ret >= sizeof(value)) { - ret = KNOT_ESPACE; - send_error(args, knot_strerror(ret)); - return ret; - } + if (zone == NULL) { + int ret = stats_server(ctl_dump_ctr, &dump_ctx); + STATS_CHECK(ret, true); - ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data); - if (ret != KNOT_EOK) { - send_error(args, knot_strerror(ret)); - return ret; - } - } - } + ret = stats_xdp(ctl_dump_ctr, &dump_ctx); + STATS_CHECK(ret, true); - if (section == NULL || strncasecmp(section, "mod-", strlen("mod-")) == 0) { - list_t *query_modules = (zone != NULL) ? &zone->query_modules : - conf()->query_modules; - int ret = modules_stats(query_modules, args, &data); - if (ret != KNOT_EOK) { - send_error(args, knot_strerror(ret)); - return ret; - } + dump_ctx.query_modules = conf()->query_modules; + ret = stats_modules(ctl_dump_ctr, &dump_ctx); + STATS_CHECK(ret, true); + } else { + int ret = stats_zone(ctl_dump_ctr, &dump_ctx); + STATS_CHECK(ret, false); - found = true; + dump_ctx.query_modules = &zone->query_modules; + ret = stats_modules(ctl_dump_ctr, &dump_ctx); + STATS_CHECK(ret, false); } - if (!found) { - send_error(args, knot_strerror(KNOT_EINVAL)); - return KNOT_EINVAL; + if (!dump_ctx.match) { + STATS_CHECK(KNOT_EINVAL, zone == NULL); } +#undef STATS_CHECK return KNOT_EOK; } @@ -1932,6 +1863,8 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd) return zones_apply_backup(args, true); case CTL_ZONE_SIGN: return zones_apply(args, zone_sign); + case CTL_ZONE_VALIDATE: + return zones_apply(args, zone_validate); case CTL_ZONE_KEYS_LOAD: return zones_apply(args, zone_keys_load); case CTL_ZONE_KEY_ROLL: @@ -1976,6 +1909,27 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd) } } +static void check_zone_txn(zone_t *zone, const knot_dname_t **exists) +{ + if (zone->control_update != NULL) { + *exists = zone->name; + } +} + +static int check_no_zone_txn(server_t *server, const char *action) +{ + const knot_dname_t *zone_txn_exists = NULL; + knot_zonedb_foreach(server->zone_db, check_zone_txn, &zone_txn_exists); + if (zone_txn_exists != NULL) { + knot_dname_txt_storage_t zone_str; + knot_dname_to_str(zone_str, zone_txn_exists, sizeof(zone_str)); + log_warning("%s rejected due to existing transaction for zone %s", + action, zone_str); + return KNOT_TXN_EEXISTS; + } + return KNOT_EOK; +} + static int server_status(ctl_args_t *args) { const char *type = args->data[KNOT_CTL_IDX_TYPE]; @@ -1998,7 +1952,7 @@ static int server_status(ctl_args_t *args) conf()->cache.srv_xdp_threads, conf()->cache.srv_bg_threads, running_bkg_wrk, wrk_queue); } else if (strcasecmp(type, "configure") == 0) { - ret = snprintf(buff, sizeof(buff), "%s", CONFIGURE_SUMMARY); + ret = snprintf(buff, sizeof(buff), "%s", configure_summary); } else if (strcasecmp(type, "cert-key") == 0) { uint8_t pin[128]; size_t pin_len = server_cert_pin(args->server, pin, sizeof(pin)); @@ -2034,7 +1988,10 @@ static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd) ret = KNOT_CTL_ESTOP; break; case CTL_RELOAD: - ret = server_reload(args->server, RELOAD_FULL); + ret = check_no_zone_txn(args->server, "server reload"); + if (ret == KNOT_EOK) { + ret = server_reload(args->server, RELOAD_FULL); + } if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); } @@ -2171,7 +2128,10 @@ static int ctl_conf_txn(ctl_args_t *args, ctl_cmd_t cmd) switch (cmd) { case CTL_CONF_BEGIN: - ret = conf_io_begin(false); + ret = check_no_zone_txn(args->server, "config, transaction"); + if (ret == KNOT_EOK) { + ret = conf_io_begin(false); + } break; case CTL_CONF_ABORT: conf_io_abort(false); @@ -2368,56 +2328,64 @@ static int ctl_conf_modify(ctl_args_t *args, ctl_cmd_t cmd) return ret; } +typedef enum { + CTL_LOCK_NONE = 0x00, + CTL_LOCK_SRV_R = 0x01, // Can run in parallel with other R commands. + CTL_LOCK_SRV_W = 0x02, // Cannot run in parallel with other commands. +} ctl_lock_flag_t; + typedef struct { const char *name; int (*fcn)(ctl_args_t *, ctl_cmd_t); + ctl_lock_flag_t locks; } desc_t; static const desc_t cmd_table[] = { [CTL_NONE] = { "" }, - [CTL_STATUS] = { "status", ctl_server }, - [CTL_STOP] = { "stop", ctl_server }, - [CTL_RELOAD] = { "reload", ctl_server }, - [CTL_STATS] = { "stats", ctl_stats }, - - [CTL_ZONE_STATUS] = { "zone-status", ctl_zone }, - [CTL_ZONE_RELOAD] = { "zone-reload", ctl_zone }, - [CTL_ZONE_REFRESH] = { "zone-refresh", ctl_zone }, - [CTL_ZONE_RETRANSFER] = { "zone-retransfer", ctl_zone }, - [CTL_ZONE_NOTIFY] = { "zone-notify", ctl_zone }, - [CTL_ZONE_FLUSH] = { "zone-flush", ctl_zone }, - [CTL_ZONE_BACKUP] = { "zone-backup", ctl_zone }, - [CTL_ZONE_RESTORE] = { "zone-restore", ctl_zone }, - [CTL_ZONE_SIGN] = { "zone-sign", ctl_zone }, - [CTL_ZONE_KEYS_LOAD] = { "zone-keys-load", ctl_zone }, - [CTL_ZONE_KEY_ROLL] = { "zone-key-rollover", ctl_zone }, - [CTL_ZONE_KSK_SBM] = { "zone-ksk-submitted", ctl_zone }, - [CTL_ZONE_FREEZE] = { "zone-freeze", ctl_zone }, - [CTL_ZONE_THAW] = { "zone-thaw", ctl_zone }, - [CTL_ZONE_XFR_FREEZE] = { "zone-xfr-freeze", ctl_zone }, - [CTL_ZONE_XFR_THAW] = { "zone-xfr-thaw", ctl_zone }, - - [CTL_ZONE_READ] = { "zone-read", ctl_zone }, - [CTL_ZONE_BEGIN] = { "zone-begin", ctl_zone }, - [CTL_ZONE_COMMIT] = { "zone-commit", ctl_zone }, - [CTL_ZONE_ABORT] = { "zone-abort", ctl_zone }, - [CTL_ZONE_DIFF] = { "zone-diff", ctl_zone }, - [CTL_ZONE_GET] = { "zone-get", ctl_zone }, - [CTL_ZONE_SET] = { "zone-set", ctl_zone }, - [CTL_ZONE_UNSET] = { "zone-unset", ctl_zone }, - [CTL_ZONE_PURGE] = { "zone-purge", ctl_zone }, - [CTL_ZONE_STATS] = { "zone-stats", ctl_zone }, - - [CTL_CONF_LIST] = { "conf-list", ctl_conf_list }, - [CTL_CONF_READ] = { "conf-read", ctl_conf_read }, - [CTL_CONF_BEGIN] = { "conf-begin", ctl_conf_txn }, - [CTL_CONF_COMMIT] = { "conf-commit", ctl_conf_txn }, - [CTL_CONF_ABORT] = { "conf-abort", ctl_conf_txn }, - [CTL_CONF_DIFF] = { "conf-diff", ctl_conf_read }, - [CTL_CONF_GET] = { "conf-get", ctl_conf_read }, - [CTL_CONF_SET] = { "conf-set", ctl_conf_modify }, - [CTL_CONF_UNSET] = { "conf-unset", ctl_conf_modify }, + [CTL_STATUS] = { "status", ctl_server, CTL_LOCK_SRV_R }, + [CTL_STOP] = { "stop", ctl_server, CTL_LOCK_SRV_R }, + [CTL_RELOAD] = { "reload", ctl_server, CTL_LOCK_SRV_W }, + [CTL_STATS] = { "stats", ctl_stats, CTL_LOCK_SRV_R }, + + [CTL_ZONE_STATUS] = { "zone-status", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_RELOAD] = { "zone-reload", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_REFRESH] = { "zone-refresh", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_RETRANSFER] = { "zone-retransfer", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_NOTIFY] = { "zone-notify", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_FLUSH] = { "zone-flush", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_BACKUP] = { "zone-backup", ctl_zone, CTL_LOCK_SRV_W }, // Backup and restore must be exclusive as the global backup ctx is accessed. + [CTL_ZONE_RESTORE] = { "zone-restore", ctl_zone, CTL_LOCK_SRV_W }, + [CTL_ZONE_SIGN] = { "zone-sign", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_VALIDATE] = { "zone-validate", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_KEYS_LOAD] = { "zone-keys-load", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_KEY_ROLL] = { "zone-key-rollover", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_KSK_SBM] = { "zone-ksk-submitted", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_FREEZE] = { "zone-freeze", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_THAW] = { "zone-thaw", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_XFR_FREEZE] = { "zone-xfr-freeze", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_XFR_THAW] = { "zone-xfr-thaw", ctl_zone, CTL_LOCK_SRV_R }, + + [CTL_ZONE_READ] = { "zone-read", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_BEGIN] = { "zone-begin", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_COMMIT] = { "zone-commit", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_ABORT] = { "zone-abort", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_DIFF] = { "zone-diff", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_GET] = { "zone-get", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_SET] = { "zone-set", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_UNSET] = { "zone-unset", ctl_zone, CTL_LOCK_SRV_R }, + [CTL_ZONE_PURGE] = { "zone-purge", ctl_zone, CTL_LOCK_SRV_W }, + [CTL_ZONE_STATS] = { "zone-stats", ctl_zone, CTL_LOCK_SRV_R }, + + [CTL_CONF_LIST] = { "conf-list", ctl_conf_list, CTL_LOCK_SRV_R }, // Can either read live conf or conf txn. The latter would deserve CTL_LOCK_SRV_W, but when conf txn exists, all cmds are done by single thread anyway. + [CTL_CONF_READ] = { "conf-read", ctl_conf_read, CTL_LOCK_SRV_R }, + [CTL_CONF_BEGIN] = { "conf-begin", ctl_conf_txn, CTL_LOCK_SRV_W }, // It's locked only during conf-begin, not for the whole duration of the transaction. + [CTL_CONF_COMMIT] = { "conf-commit", ctl_conf_txn, CTL_LOCK_SRV_W }, + [CTL_CONF_ABORT] = { "conf-abort", ctl_conf_txn, CTL_LOCK_SRV_W }, + [CTL_CONF_DIFF] = { "conf-diff", ctl_conf_read, CTL_LOCK_SRV_W }, + [CTL_CONF_GET] = { "conf-get", ctl_conf_read, CTL_LOCK_SRV_W }, + [CTL_CONF_SET] = { "conf-set", ctl_conf_modify, CTL_LOCK_SRV_W }, + [CTL_CONF_UNSET] = { "conf-unset", ctl_conf_modify, CTL_LOCK_SRV_W }, }; #define MAX_CTL_CODE (sizeof(cmd_table) / sizeof(desc_t) - 1) @@ -2446,13 +2414,52 @@ ctl_cmd_t ctl_str_to_cmd(const char *cmd_str) return CTL_NONE; } +static int ctl_lock(server_t *server, ctl_lock_flag_t flags, uint64_t timeout_ms) +{ + struct timespec ts; + int ret = clock_gettime(CLOCK_REALTIME, &ts); + if (ret != 0) { + return KNOT_ERROR; + } + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000LU; + + if ((flags & CTL_LOCK_SRV_W)) { + assert(!(flags & CTL_LOCK_SRV_R)); +#if !defined(__APPLE__) + ret = pthread_rwlock_timedwrlock(&server->ctl_lock, &ts); +#else + ret = pthread_rwlock_wrlock(&server->ctl_lock); +#endif + } + if ((flags & CTL_LOCK_SRV_R)) { +#if !defined(__APPLE__) + ret = pthread_rwlock_timedrdlock(&server->ctl_lock, &ts); +#else + ret = pthread_rwlock_rdlock(&server->ctl_lock); +#endif + } + return (ret != 0 ? KNOT_EBUSY : KNOT_EOK); +} + +static void ctl_unlock(server_t *server) +{ + pthread_rwlock_unlock(&server->ctl_lock); +} + int ctl_exec(ctl_cmd_t cmd, ctl_args_t *args) { if (args == NULL) { return KNOT_EINVAL; } - return cmd_table[cmd].fcn(args, cmd); + int ret = ctl_lock(args->server, cmd_table[cmd].locks, conf()->cache.ctl_timeout); + if (ret == KNOT_EOK) { + ret = cmd_table[cmd].fcn(args, cmd); + ctl_unlock(args->server); + } + + return ret; } bool ctl_has_flag(const char *flags, const char *flag) diff --git a/src/knot/ctl/commands.h b/src/knot/ctl/commands.h index ed7e75c..b0f33d8 100644 --- a/src/knot/ctl/commands.h +++ b/src/knot/ctl/commands.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 @@ -87,6 +87,7 @@ typedef enum { CTL_ZONE_BACKUP, CTL_ZONE_RESTORE, CTL_ZONE_SIGN, + CTL_ZONE_VALIDATE, CTL_ZONE_KEYS_LOAD, CTL_ZONE_KEY_ROLL, CTL_ZONE_KSK_SBM, @@ -124,6 +125,7 @@ typedef struct { knot_ctl_data_t data; server_t *server; bool suppress; // Suppress error reporting in the "all zones" ctl commands. + unsigned thread_idx; } ctl_args_t; /*! diff --git a/src/knot/ctl/process.c b/src/knot/ctl/process.c index 50fde21..9e6e0df 100644 --- a/src/knot/ctl/process.c +++ b/src/knot/ctl/process.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 @@ -20,7 +20,7 @@ #include "libknot/error.h" #include "contrib/string.h" -int ctl_process(knot_ctl_t *ctl, server_t *server) +int ctl_process(knot_ctl_t *ctl, server_t *server, int thread_idx, bool *exclusive) { if (ctl == NULL || server == NULL) { return KNOT_EINVAL; @@ -29,7 +29,8 @@ int ctl_process(knot_ctl_t *ctl, server_t *server) ctl_args_t args = { .ctl = ctl, .type = KNOT_CTL_TYPE_END, - .server = server + .server = server, + .thread_idx = thread_idx, }; // Strip redundant/unprocessed data units in the current block. @@ -90,11 +91,21 @@ int ctl_process(knot_ctl_t *ctl, server_t *server) continue; } + if ((cmd == CTL_CONF_COMMIT || cmd == CTL_CONF_ABORT) && !*exclusive) { + log_ctl_warning("control, invalid reception of '%s'", cmd_name); + continue; + } + // Execute the command. int cmd_ret = ctl_exec(cmd, &args); switch (cmd_ret) { case KNOT_EOK: strip = false; + if (cmd == CTL_CONF_BEGIN) { + *exclusive = true; + } else if (cmd == CTL_CONF_COMMIT || cmd == CTL_CONF_ABORT) { + *exclusive = false; + } case KNOT_CTL_ESTOP: case KNOT_CTL_EZONE: // KNOT_CTL_EZONE - don't change strip, but don't be reported diff --git a/src/knot/ctl/process.h b/src/knot/ctl/process.h index ab0f75f..2ae6ea0 100644 --- a/src/knot/ctl/process.h +++ b/src/knot/ctl/process.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 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,12 +19,16 @@ #include "libknot/libknot.h" #include "knot/server/server.h" +#define CTL_MAX_CONCURRENT 8 // Number of CTL threads EXCLUDING the main thread which can also process CTL. + /*! * Processes incoming control commands. * - * \param[in] ctl Control context. - * \param[in] server Server instance. + * \param[in] ctl Control context. + * \param[in] server Server instance. + * \param[in] thread_idx Index of a thread which performs the operation. + * \param[out] exclusive All following CTLs shall (not) be processed exclusively by this thread. * * \return Error code, KNOT_EOK if successful. */ -int ctl_process(knot_ctl_t *ctl, server_t *server); +int ctl_process(knot_ctl_t *ctl, server_t *server, int thread_idx, bool *exclusive); diff --git a/src/knot/dnssec/context.c b/src/knot/dnssec/context.c index 1a19f68..24b41f7 100644 --- a/src/knot/dnssec/context.c +++ b/src/knot/dnssec/context.c @@ -213,11 +213,13 @@ int kdnssec_ctx_init(conf_t *conf, kdnssec_ctx_t *ctx, const knot_dname_t *zone_ goto init_error; } - ctx->policy = calloc(1, sizeof(*ctx->policy)); + ctx->policy = calloc(1, sizeof(*ctx->policy) + sizeof(*ctx->stats)); if (ctx->policy == NULL) { ret = KNOT_ENOMEM; goto init_error; } + ctx->stats = (void *)ctx->policy + sizeof(*ctx->policy); + knot_spin_init(&ctx->stats->lock); ret = kasp_db_get_saved_ttls(ctx->kasp_db, zone_name, &ctx->policy->saved_max_ttl, @@ -287,6 +289,7 @@ void kdnssec_ctx_deinit(kdnssec_ctx_t *ctx) } if (ctx->policy != NULL) { + knot_spin_destroy(&ctx->stats->lock); free(ctx->policy->string); knot_dynarray_foreach(parent, knot_kasp_parent_t, i, ctx->policy->parents) { free(i->addr); @@ -331,18 +334,22 @@ int kdnssec_validation_ctx(conf_t *conf, kdnssec_ctx_t *ctx, const zone_contents return KNOT_ENOMEM; } - ctx->policy = calloc(1, sizeof(*ctx->policy)); + ctx->policy = calloc(1, sizeof(*ctx->policy) + sizeof(*ctx->stats)); if (ctx->policy == NULL) { free(ctx->zone); return KNOT_ENOMEM; } + ctx->stats = (void *)ctx->policy + sizeof(*ctx->policy); + knot_spin_init(&ctx->stats->lock); policy_from_zone(ctx->policy, zone); if (conf != NULL) { conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner); conf_id_fix_default(&policy_id); - conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id); - ctx->policy->signing_threads = conf_int(&num_threads); + conf_val_t val = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id); + ctx->policy->signing_threads = conf_int(&val); + val = conf_id_get(conf, C_POLICY, C_RRSIG_REFRESH, &policy_id); + ctx->policy->rrsig_refresh_before = conf_int_alt(&val, true); } else { ctx->policy->signing_threads = MAX(dt_optimal_size(), 1); } diff --git a/src/knot/dnssec/context.h b/src/knot/dnssec/context.h index 756bc56..2d126db 100644 --- a/src/knot/dnssec/context.h +++ b/src/knot/dnssec/context.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -16,14 +16,20 @@ #pragma once -#include <time.h> - #include "libdnssec/keystore.h" - +#include "contrib/spinlock.h" +#include "contrib/time.h" #include "knot/conf/conf.h" #include "knot/dnssec/kasp/kasp_zone.h" #include "knot/dnssec/kasp/policy.h" +typedef struct { + size_t rrsig_count; + knot_time_t expire; + + knot_spin_t lock; +} zone_sign_stats_t; + /*! * \brief DNSSEC signing context. */ @@ -38,6 +44,8 @@ typedef struct { char *kasp_zone_path; + zone_sign_stats_t *stats; + bool rrsig_drop_existing; bool keep_deleted_keys; bool keytag_conflict; diff --git a/src/knot/dnssec/ds_query.c b/src/knot/dnssec/ds_query.c index 2ac91cc..0b85247 100644 --- a/src/knot/dnssec/ds_query.c +++ b/src/knot/dnssec/ds_query.c @@ -28,8 +28,8 @@ #define DS_CHECK_LOG(priority, zone, remote, flags, fmt, ...) \ ns_log(priority, zone, LOG_OPERATION_DS_CHECK, LOG_DIRECTION_OUT, &(remote)->addr, \ - ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \ - ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, fmt, ## __VA_ARGS__) + flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, \ + fmt, ## __VA_ARGS__) static bool match_key_ds(knot_kasp_key_t *key, knot_rdata_t *ds) { diff --git a/src/knot/dnssec/key-events.c b/src/knot/dnssec/key-events.c index 58d7ec0..db762b6 100644 --- a/src/knot/dnssec/key-events.c +++ b/src/knot/dnssec/key-events.c @@ -17,8 +17,8 @@ #include <assert.h> #include "contrib/macros.h" +#include "knot/common/dbus.h" #include "knot/common/log.h" -#include "knot/common/systemd.h" #include "knot/dnssec/kasp/keystate.h" #include "knot/dnssec/key-events.h" #include "knot/dnssec/policy.h" @@ -881,7 +881,7 @@ int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_sign_roll_flags_t flags, if (ret == KNOT_EOK && reschedule->keys_changed) { ret = kdnssec_ctx_commit(ctx); if (ret == KNOT_EOK && (ctx->dbus_event & DBUS_EVENT_KEYS_UPDATED)) { - systemd_emit_keys_updated(ctx->zone->dname); + dbus_emit_keys_updated(ctx->zone->dname); } } @@ -891,7 +891,7 @@ int knot_dnssec_key_rollover(kdnssec_ctx_t *ctx, zone_sign_roll_flags_t flags, log_fmt_zone(LOG_NOTICE, LOG_SOURCE_ZONE, ctx->zone->dname, param, "DNSSEC, KSK submission, waiting for confirmation"); if (ctx->dbus_event & DBUS_EVENT_ZONE_SUBMISSION) { - systemd_emit_zone_submission(ctx->zone->dname, ready_keytag, ready_keyid); + dbus_emit_zone_submission(ctx->zone->dname, ready_keytag, ready_keyid); } } diff --git a/src/knot/dnssec/key_records.c b/src/knot/dnssec/key_records.c index 9b22f7a..366ab4a 100644 --- a/src/knot/dnssec/key_records.c +++ b/src/knot/dnssec/key_records.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -189,7 +189,7 @@ int key_records_dump(char **buf, size_t *buf_size, const key_records_t *r, bool return ret >= 0 ? KNOT_EOK : ret; } -int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx, knot_time_t *expires) +int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx) { dnssec_sign_ctx_t *sign_ctx; int ret = dnssec_sign_new(&sign_ctx, key->key); @@ -198,20 +198,20 @@ int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_ } if (!knot_rrset_empty(&r->dnskey) && knot_zone_sign_use_key(key, &r->dnskey)) { - ret = knot_sign_rrset(&r->rrsig, &r->dnskey, key->key, sign_ctx, kctx, NULL, expires); + ret = knot_sign_rrset(&r->rrsig, &r->dnskey, key->key, sign_ctx, kctx, NULL); } if (ret == KNOT_EOK && !knot_rrset_empty(&r->cdnskey) && knot_zone_sign_use_key(key, &r->cdnskey)) { - ret = knot_sign_rrset(&r->rrsig, &r->cdnskey, key->key, sign_ctx, kctx, NULL, expires); + ret = knot_sign_rrset(&r->rrsig, &r->cdnskey, key->key, sign_ctx, kctx, NULL); } if (ret == KNOT_EOK && !knot_rrset_empty(&r->cds) && knot_zone_sign_use_key(key, &r->cds)) { - ret = knot_sign_rrset(&r->rrsig, &r->cds, key->key, sign_ctx, kctx, NULL, expires); + ret = knot_sign_rrset(&r->rrsig, &r->cds, key->key, sign_ctx, kctx, NULL); } dnssec_sign_free(sign_ctx); return ret; } -int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp) +int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp, knot_time_t min_valid) { kctx->now = timestamp; int ret = kasp_zone_keys_from_rr(kctx->zone, &r->dnskey.rrs, false, &kctx->keytag_conflict); @@ -224,12 +224,17 @@ int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timest return KNOT_ENOMEM; } - ret = knot_validate_rrsigs(&r->dnskey, &r->rrsig, sign_ctx, false); + knot_time_t until = 0; + ret = knot_validate_rrsigs(&r->dnskey, &r->rrsig, sign_ctx, false, &until); if (ret == KNOT_EOK && !knot_rrset_empty(&r->cdnskey)) { - ret = knot_validate_rrsigs(&r->cdnskey, &r->rrsig, sign_ctx, false); + ret = knot_validate_rrsigs(&r->cdnskey, &r->rrsig, sign_ctx, false, &until); } if (ret == KNOT_EOK && !knot_rrset_empty(&r->cds)) { - ret = knot_validate_rrsigs(&r->cds, &r->rrsig, sign_ctx, false); + ret = knot_validate_rrsigs(&r->cds, &r->rrsig, sign_ctx, false, &until); + } + + if (ret == KNOT_EOK && knot_time_lt(until, min_valid)) { + ret = KNOT_ESOON_EXPIRE; } zone_sign_ctx_free(sign_ctx); diff --git a/src/knot/dnssec/key_records.h b/src/knot/dnssec/key_records.h index b53ed86..dd28b4f 100644 --- a/src/knot/dnssec/key_records.h +++ b/src/knot/dnssec/key_records.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -39,10 +39,10 @@ int key_records_intersect(key_records_t *r, const key_records_t *against); int key_records_dump(char **buf, size_t *buf_size, const key_records_t *r, bool verbose); -int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx, knot_time_t *expires); +int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx); // WARNING this modifies 'kctx' with updated timestamp and with zone_keys from r->dnskey -int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp); +int key_records_verify(key_records_t *r, kdnssec_ctx_t *kctx, knot_time_t timestamp, knot_time_t min_valid); size_t key_records_serialized_size(const key_records_t *r); diff --git a/src/knot/dnssec/rrset-sign.c b/src/knot/dnssec/rrset-sign.c index 4c9e904..1dc9edc 100644 --- a/src/knot/dnssec/rrset-sign.c +++ b/src/knot/dnssec/rrset-sign.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 @@ -157,13 +157,13 @@ static int sign_ctx_add_self(dnssec_sign_ctx_t *ctx, const uint8_t *rdata) */ static int sign_ctx_add_records(dnssec_sign_ctx_t *ctx, const knot_rrset_t *covered) { - size_t rrwl = knot_rrset_size(covered); + size_t rrwl = knot_rrset_size_estimate(covered); uint8_t *rrwf = malloc(rrwl); if (!rrwf) { return KNOT_ENOMEM; } - int written = knot_rrset_to_wire_extra(covered, rrwf, rrwl, 0, NULL, KNOT_PF_BUFENOUGH); + int written = knot_rrset_to_wire_extra(covered, rrwf, rrwl, 0, NULL, 0); if (written < 0) { free(rrwf); return written; @@ -263,7 +263,7 @@ static int rrsigs_create_rdata(knot_rrset_t *rrsigs, dnssec_sign_ctx_t *ctx, int knot_sign_rrset(knot_rrset_t *rrsigs, const knot_rrset_t *covered, const dnssec_key_t *key, dnssec_sign_ctx_t *sign_ctx, - const kdnssec_ctx_t *dnssec_ctx, knot_mm_t *mm, knot_time_t *expires) + const kdnssec_ctx_t *dnssec_ctx, knot_mm_t *mm) { if (knot_rrset_empty(covered) || !key || !sign_ctx || !dnssec_ctx || rrsigs->type != KNOT_RRTYPE_RRSIG || @@ -279,8 +279,11 @@ int knot_sign_rrset(knot_rrset_t *rrsigs, const knot_rrset_t *covered, int ret = rrsigs_create_rdata(rrsigs, sign_ctx, covered, key, (uint32_t)sig_incept, (uint32_t)sig_expire, sign_flags, mm); - if (ret == KNOT_EOK && expires != NULL) { - *expires = knot_time_min(*expires, sig_expire); + if (ret == KNOT_EOK) { + knot_spin_lock(&dnssec_ctx->stats->lock); + dnssec_ctx->stats->rrsig_count++; + dnssec_ctx->stats->expire = knot_time_min(dnssec_ctx->stats->expire, sig_expire); + knot_spin_unlock(&dnssec_ctx->stats->lock); } return ret; } @@ -300,7 +303,7 @@ int knot_sign_rrset2(knot_rrset_t *rrsigs, const knot_rrset_t *rrset, } int ret = knot_sign_rrset(rrsigs, rrset, key->key, sign_ctx->sign_ctxs[i], - sign_ctx->dnssec_ctx, mm, NULL); + sign_ctx->dnssec_ctx, mm); if (ret != KNOT_EOK) { return ret; } diff --git a/src/knot/dnssec/rrset-sign.h b/src/knot/dnssec/rrset-sign.h index 8e00402..ba0f027 100644 --- a/src/knot/dnssec/rrset-sign.h +++ b/src/knot/dnssec/rrset-sign.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -31,7 +31,6 @@ * \param sign_ctx Signing context. * \param dnssec_ctx DNSSEC context. * \param mm Memory context. - * \param expires Out: When will the new RRSIG expire. * * \return Error code, KNOT_EOK if successful. */ @@ -40,8 +39,7 @@ int knot_sign_rrset(knot_rrset_t *rrsigs, const dnssec_key_t *key, dnssec_sign_ctx_t *sign_ctx, const kdnssec_ctx_t *dnssec_ctx, - knot_mm_t *mm, - knot_time_t *expires); + knot_mm_t *mm); /*! * \brief Create RRSIG RR for given RR set, choose which key to use. diff --git a/src/knot/dnssec/zone-events.c b/src/knot/dnssec/zone-events.c index faf093d..2ca35ac 100644 --- a/src/knot/dnssec/zone-events.c +++ b/src/knot/dnssec/zone-events.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 @@ -20,6 +20,7 @@ #include "libdnssec/random.h" #include "libknot/libknot.h" #include "knot/conf/conf.h" +#include "knot/common/dbus.h" #include "knot/common/log.h" #include "knot/dnssec/key-events.h" #include "knot/dnssec/key_records.h" @@ -157,7 +158,6 @@ int knot_dnssec_zone_sign(zone_update_t *update, const knot_dname_t *zone_name = update->new_cont->apex->owner; kdnssec_ctx_t ctx = { 0 }; zone_keyset_t keyset = { 0 }; - knot_time_t zone_expire = 0; int result = kdnssec_ctx_init(conf, &ctx, zone_name, zone_kaspdb(update->zone), NULL); if (result != KNOT_EOK) { @@ -259,7 +259,7 @@ int knot_dnssec_zone_sign(zone_update_t *update, goto done; } - result = knot_zone_sign(update, &keyset, &ctx, &zone_expire); + result = knot_zone_sign(update, &keyset, &ctx); if (result != KNOT_EOK) { log_zone_error(zone_name, "DNSSEC, failed to sign zone content (%s)", knot_strerror(result)); @@ -302,13 +302,14 @@ int knot_dnssec_zone_sign(zone_update_t *update, } } - log_zone_info(zone_name, "DNSSEC, successfully signed, serial %u", - zone_contents_serial(update->new_cont)); + log_zone_info(zone_name, "DNSSEC, successfully signed, serial %u, new RRSIGs %zu", + zone_contents_serial(update->new_cont), ctx.stats->rrsig_count); done: if (result == KNOT_EOK) { - reschedule->next_sign = schedule_next(&ctx, &keyset, ctx.offline_next_time, zone_expire); + reschedule->next_sign = schedule_next(&ctx, &keyset, ctx.offline_next_time, ctx.stats->expire); reschedule->plan_dnskey_sync = ctx.policy->has_dnskey_sync; + update->new_cont->dnssec_expire = ctx.stats->expire; } else { reschedule->next_sign = knot_dnssec_failover_delay(&ctx); reschedule->next_rollover = 0; @@ -329,7 +330,6 @@ int knot_dnssec_sign_update(zone_update_t *update, conf_t *conf) const knot_dname_t *zone_name = update->new_cont->apex->owner; kdnssec_ctx_t ctx = { 0 }; zone_keyset_t keyset = { 0 }; - knot_time_t zone_expire = 0; int result = kdnssec_ctx_init(conf, &ctx, zone_name, zone_kaspdb(update->zone), NULL); if (result != KNOT_EOK) { @@ -381,7 +381,7 @@ int knot_dnssec_sign_update(zone_update_t *update, conf_t *conf) goto done; } - result = knot_zone_sign_update(update, &keyset, &ctx, &zone_expire); + result = knot_zone_sign_update(update, &keyset, &ctx); if (result != KNOT_EOK) { log_zone_error(zone_name, "DNSSEC, failed to sign changeset (%s)", knot_strerror(result)); @@ -434,17 +434,18 @@ int knot_dnssec_sign_update(zone_update_t *update, conf_t *conf) } } - log_zone_info(zone_name, "DNSSEC, incrementally signed, serial %u", - zone_contents_serial(update->new_cont)); + log_zone_info(zone_name, "DNSSEC, incrementally signed, serial %u, new RRSIGs %zu", + zone_contents_serial(update->new_cont), ctx.stats->rrsig_count); done: if (result == KNOT_EOK) { - knot_time_t next = knot_time_min(ctx.offline_next_time, zone_expire); + knot_time_t next = knot_time_min(ctx.offline_next_time, ctx.stats->expire); // NOTE: this is usually NOOP since signing planned earlier zone_events_schedule_at(update->zone, ZONE_EVENT_DNSSEC, (time_t)(next ? next : -1)); if (ctx.policy->has_dnskey_sync) { zone_events_schedule_now(update->zone, ZONE_EVENT_DNSKEY_SYNC); } + update->new_cont->dnssec_expire = knot_time_min(update->zone->contents->dnssec_expire, ctx.stats->expire); } free_zone_keys(&keyset); @@ -462,25 +463,83 @@ knot_time_t knot_dnssec_failover_delay(const kdnssec_ctx_t *ctx) } } -int knot_dnssec_validate_zone(zone_update_t *update, conf_t *conf, knot_time_t now, bool incremental) +static void log_validation_error(zone_update_t *update, const char *msg_valid, + int ret, bool warning) +{ + unsigned level = warning ? LOG_WARNING : LOG_ERR; + + log_fmt_zone(level, LOG_SOURCE_ZONE, update->zone->name, NULL, + "DNSSEC, %svalidation failed (%s)", msg_valid, knot_strerror(ret)); + + char type_str[16]; + knot_dname_txt_storage_t name_str; + if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL && + knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) { + log_fmt_zone(level, LOG_SOURCE_ZONE, update->zone->name, NULL, + "DNSSEC, validation hint: %s %s", name_str, type_str); + } +} + +int knot_dnssec_validate_zone(zone_update_t *update, conf_t *conf, + knot_time_t now, bool incremental, bool log_plan) { kdnssec_ctx_t ctx = { 0 }; int ret = kdnssec_validation_ctx(conf, &ctx, update->new_cont); + if (ret != KNOT_EOK) { + goto end; + } if (now != 0) { ctx.now = now; } + + ret = knot_zone_check_nsec_chain(update, &ctx, incremental); if (ret == KNOT_EOK) { - ret = knot_zone_check_nsec_chain(update, &ctx, incremental); - } - if (ret == KNOT_EOK) { - knot_time_t unused = 0; assert(ctx.validation_mode); if (incremental) { - ret = knot_zone_sign_update(update, NULL, &ctx, &unused); + ret = knot_zone_sign_update(update, NULL, &ctx); } else { - ret = knot_zone_sign(update, NULL, &ctx, &unused); + ret = knot_zone_sign(update, NULL, &ctx); } } +end: + if (log_plan) { + const char *msg_valid = incremental ? "incremental " : ""; + if (ret != KNOT_EOK) { + log_validation_error(update, msg_valid, ret, false); + if (conf->cache.srv_dbus_event & DBUS_EVENT_ZONE_INVALID) { + dbus_emit_zone_invalid(update->zone->name, 0); + } + } else if (update->validation_hint.warning != KNOT_EOK) { + log_validation_error(update, msg_valid, update->validation_hint.warning, true); + if (conf->cache.srv_dbus_event & DBUS_EVENT_ZONE_INVALID) { + dbus_emit_zone_invalid(update->zone->name, update->validation_hint.remaining_secs); + } + } else { + log_zone_info(update->zone->name, "DNSSEC, %svalidation successful, checked RRSIGs %zu", + msg_valid, ctx.stats->rrsig_count); + } + + conf_val_t val = conf_zone_get(conf, C_DNSSEC_VALIDATION, update->zone->name); + bool configured = conf_bool(&val); + bool bogus = (ret != KNOT_EOK); + bool running = (update->zone->contents == update->new_cont); + bool may_expire = zone_is_slave(conf, update->zone); + knot_time_t expire = (ctx.stats != NULL ? ctx.stats->expire : 0); + assert(bogus || knot_time_geq(expire, ctx.now)); + + if (running && bogus && may_expire) { + zone_events_schedule_now(update->zone, ZONE_EVENT_EXPIRE); + } + if (configured && !bogus) { + if (!incremental) { + zone_events_schedule_at(update->zone, ZONE_EVENT_VALIDATE, 0); // cancel previously planned re-check when fully re-checked + } + zone_events_schedule_at(update->zone, ZONE_EVENT_VALIDATE, // this works for incremental verify as well, re-planning on later + knot_time_add(expire, 1)); // is a NOOP, sooner is proper + } + } + kdnssec_ctx_deinit(&ctx); + return ret; } diff --git a/src/knot/dnssec/zone-events.h b/src/knot/dnssec/zone-events.h index 462f87a..2f75614 100644 --- a/src/knot/dnssec/zone-events.h +++ b/src/knot/dnssec/zone-events.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -16,10 +16,7 @@ #pragma once -#include <time.h> - -#include "knot/zone/zone.h" -#include "knot/updates/changesets.h" +#include "contrib/time.h" #include "knot/updates/zone-update.h" #include "knot/dnssec/context.h" @@ -54,17 +51,6 @@ typedef struct { } zone_sign_reschedule_t; /*! - * \brief Generate/rollover keys in keystore as needed. - * - * \param kctx Pointers to the keytore, policy, etc. - * \param zone_name Zone name. - * - * \return Error code, KNOT_EOK if successful. - */ -int knot_dnssec_sign_process_events(const kdnssec_ctx_t *kctx, - const knot_dname_t *zone_name); - -/*! * \brief DNSSEC re-sign zone, store new records into changeset. Valid signatures * and NSEC(3) records will not be changed. * @@ -130,7 +116,9 @@ knot_time_t knot_dnssec_failover_delay(const kdnssec_ctx_t *ctx); * \param conf Knot configuration. * \param now If not zero: adjust "now" to this timestamp. * \param incremental Try to validate incrementally. + * \param log_plan Log the result and plan subsequent validation event. * * \return KNOT_E* */ -int knot_dnssec_validate_zone(zone_update_t *update, conf_t *conf, knot_time_t now, bool incremental); +int knot_dnssec_validate_zone(zone_update_t *update, conf_t *conf, + knot_time_t now, bool incremental, bool log_plan); diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c index 62f809e..3cd2420 100644 --- a/src/knot/dnssec/zone-sign.c +++ b/src/knot/dnssec/zone-sign.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 @@ -209,7 +209,6 @@ bool rrsig_covers_type(const knot_rrset_t *rrsig, uint16_t type) * \param skip_crypto All RRSIGs in this node have been verified, just check validity. * \param changeset Changeset to be updated. * \param update Zone update to be updated. Exactly one of "changeset" and "update" must be NULL! - * \param expires_at Earliest RRSIG expiration. * * \return Error code, KNOT_EOK if successful. */ @@ -218,8 +217,7 @@ static int add_missing_rrsigs(const knot_rrset_t *covered, zone_sign_ctx_t *sign_ctx, bool skip_crypto, changeset_t *changeset, - zone_update_t *update, - knot_time_t *expires_at) + zone_update_t *update) { assert(!knot_rrset_empty(covered)); assert(sign_ctx); @@ -258,11 +256,14 @@ static int add_missing_rrsigs(const knot_rrset_t *covered, sign_ctx->dnssec_ctx, refresh, skip_crypto, NULL, &valid_at)) { knot_rdata_t *valid_rr = knot_rdataset_at(&rrsigs->rrs, valid_at); result = knot_rdataset_remove(&to_remove.rrs, valid_rr, NULL); - note_earliest_expiration(valid_rr, sign_ctx->dnssec_ctx->now, expires_at); + knot_spin_lock(&sign_ctx->dnssec_ctx->stats->lock); + note_earliest_expiration(valid_rr, sign_ctx->dnssec_ctx->now, + &sign_ctx->dnssec_ctx->stats->expire); + knot_spin_unlock(&sign_ctx->dnssec_ctx->stats->lock); continue; } result = knot_sign_rrset(&to_add, covered, key->key, sign_ctx->sign_ctxs[i], - sign_ctx->dnssec_ctx, NULL, expires_at); + sign_ctx->dnssec_ctx, NULL); } if (!knot_rrset_empty(&to_remove) && result == KNOT_EOK) { @@ -306,9 +307,10 @@ static bool key_used(bool ksk, bool zsk, uint16_t type, int knot_validate_rrsigs(const knot_rrset_t *covered, const knot_rrset_t *rrsigs, zone_sign_ctx_t *sign_ctx, - bool skip_crypto) + bool skip_crypto, + knot_time_t *valid_until) { - if (covered == NULL || rrsigs == NULL || sign_ctx == NULL) { + if (covered == NULL || rrsigs == NULL || sign_ctx == NULL || valid_until == NULL) { return KNOT_EINVAL; } @@ -327,7 +329,14 @@ int knot_validate_rrsigs(const knot_rrset_t *covered, if (valid_signature_exists(covered, rrsigs, key->key, sign_ctx->sign_ctxs[i], sign_ctx->dnssec_ctx, 0, skip_crypto, val_inval_map, &valid_at)) { valid_exists = true; + knot_rdata_t *valid_rr = knot_rdataset_at(&rrsigs->rrs, valid_at); + note_earliest_expiration(valid_rr, sign_ctx->dnssec_ctx->now, valid_until); } + + knot_spin_lock(&sign_ctx->dnssec_ctx->stats->lock); + sign_ctx->dnssec_ctx->stats->rrsig_count++; + sign_ctx->dnssec_ctx->stats->expire = knot_time_min(sign_ctx->dnssec_ctx->stats->expire, *valid_until); + knot_spin_unlock(&sign_ctx->dnssec_ctx->stats->lock); } for (int i = 0; i < rrsigs->rrs.count; i++) { @@ -398,7 +407,7 @@ static int force_resign_rrset(const knot_rrset_t *covered, } } - return add_missing_rrsigs(covered, NULL, sign_ctx, false, changeset, NULL, NULL); + return add_missing_rrsigs(covered, NULL, sign_ctx, false, changeset, NULL); } /*! @@ -409,7 +418,6 @@ static int force_resign_rrset(const knot_rrset_t *covered, * \param sign_ctx Local zone signing context. * \param skip_crypto All RRSIGs in this node have been verified, just check validity. * \param changeset Changeset to be updated. - * \param expires_at Current earliest expiration, will be updated. * * \return Error code, KNOT_EOK if successful. */ @@ -417,12 +425,11 @@ static int resign_rrset(const knot_rrset_t *covered, const knot_rrset_t *rrsigs, zone_sign_ctx_t *sign_ctx, bool skip_crypto, - changeset_t *changeset, - knot_time_t *expires_at) + changeset_t *changeset) { assert(!knot_rrset_empty(covered)); - return add_missing_rrsigs(covered, rrsigs, sign_ctx, skip_crypto, changeset, NULL, expires_at); + return add_missing_rrsigs(covered, rrsigs, sign_ctx, skip_crypto, changeset, NULL); } static int remove_standalone_rrsigs(const zone_node_t *node, @@ -463,14 +470,13 @@ static int remove_standalone_rrsigs(const zone_node_t *node, * \param node Node to be signed. * \param sign_ctx Local zone signing context. * \param changeset Changeset to be updated. - * \param expires_at Current earliest expiration, will be updated. + * \param hint Out: if DNSSEC validation failed, hint why and where. * * \return Error code, KNOT_EOK if successful. */ static int sign_node_rrsets(const zone_node_t *node, zone_sign_ctx_t *sign_ctx, changeset_t *changeset, - knot_time_t *expires_at, dnssec_validation_hint_t *hint) { assert(node); @@ -499,17 +505,25 @@ static int sign_node_rrsets(const zone_node_t *node, } if (sign_ctx->dnssec_ctx->validation_mode) { - result = knot_validate_rrsigs(&rrset, &rrsigs, sign_ctx, skip_crypto); + knot_time_t until = 0; + result = knot_validate_rrsigs(&rrset, &rrsigs, sign_ctx, skip_crypto, &until); + knot_time_t diff = knot_time_diff(until, sign_ctx->dnssec_ctx->now); if (result != KNOT_EOK) { hint->node = node->owner; hint->rrtype = rrset.type; + } else if (diff < sign_ctx->dnssec_ctx->policy->rrsig_refresh_before) { + hint->node = node->owner; + hint->rrtype = rrset.type; + hint->warning = KNOT_ESOON_EXPIRE; + assert(until > 0); + hint->remaining_secs = MAX(0, diff); } } else if (sign_ctx->dnssec_ctx->rrsig_drop_existing) { result = force_resign_rrset(&rrset, &rrsigs, sign_ctx, changeset); } else { result = resign_rrset(&rrset, &rrsigs, sign_ctx, skip_crypto, - changeset, expires_at); + changeset); } } @@ -526,7 +540,6 @@ typedef struct { zone_tree_t *tree; zone_sign_ctx_t *sign_ctx; changeset_t changeset; - knot_time_t expires_at; dnssec_validation_hint_t *hint; size_t num_threads; size_t thread_index; @@ -557,11 +570,7 @@ static int sign_node(zone_node_t *node, void *data) return KNOT_EOK; } - int result = sign_node_rrsets(node, args->sign_ctx, - &args->changeset, &args->expires_at, - args->hint); - - return result; + return sign_node_rrsets(node, args->sign_ctx, &args->changeset, args->hint); } static void *tree_sign_thread(void *_arg) @@ -585,7 +594,6 @@ static int set_signed(zone_node_t *node, _unused_ void *data) * \param zone_keys Zone keys. * \param policy DNSSEC policy. * \param update Zone update structure to be updated. - * \param expires_at Expiration time of the oldest signature in zone. * * \return Error code, KNOT_EOK if successful. */ @@ -593,8 +601,7 @@ static int zone_tree_sign(zone_tree_t *tree, size_t num_threads, zone_keyset_t *zone_keys, const kdnssec_ctx_t *dnssec_ctx, - zone_update_t *update, - knot_time_t *expires_at) + zone_update_t *update) { assert(zone_keys || dnssec_ctx->validation_mode); assert(dnssec_ctx); @@ -603,7 +610,6 @@ static int zone_tree_sign(zone_tree_t *tree, int ret = KNOT_EOK; node_sign_args_t args[num_threads]; memset(args, 0, sizeof(args)); - *expires_at = knot_time_plus(dnssec_ctx->now, dnssec_ctx->policy->rrsig_lifetime); // init context structures for (size_t i = 0; i < num_threads; i++) { @@ -619,7 +625,6 @@ static int zone_tree_sign(zone_tree_t *tree, if (ret != KNOT_EOK) { break; } - args[i].expires_at = 0; args[i].hint = &update->validation_hint; args[i].num_threads = num_threads; args[i].thread_index = i; @@ -662,7 +667,6 @@ static int zone_tree_sign(zone_tree_t *tree, ret = args[i].errcode; if (ret == KNOT_EOK && !dnssec_ctx->validation_mode) { ret = zone_update_apply_changeset(update, &args[i].changeset); // _fix not needed - *expires_at = knot_time_min(*expires_at, args[i].expires_at); } } } @@ -701,10 +705,9 @@ static int rrset_add_zone_ds(knot_rrset_t *rrset, zone_key_t *zone_key, dnssec_k int knot_zone_sign(zone_update_t *update, zone_keyset_t *zone_keys, - const kdnssec_ctx_t *dnssec_ctx, - knot_time_t *expire_at) + const kdnssec_ctx_t *dnssec_ctx) { - if (!update || !dnssec_ctx || !expire_at || + if (!update || !dnssec_ctx || dnssec_ctx->policy->signing_threads < 1 || (zone_keys == NULL && !dnssec_ctx->validation_mode)) { return KNOT_EINVAL; @@ -712,16 +715,14 @@ int knot_zone_sign(zone_update_t *update, int result; - knot_time_t normal_expire = 0; result = zone_tree_sign(update->new_cont->nodes, dnssec_ctx->policy->signing_threads, - zone_keys, dnssec_ctx, update, &normal_expire); + zone_keys, dnssec_ctx, update); if (result != KNOT_EOK) { return result; } - knot_time_t nsec3_expire = 0; result = zone_tree_sign(update->new_cont->nsec3_nodes, dnssec_ctx->policy->signing_threads, - zone_keys, dnssec_ctx, update, &nsec3_expire); + zone_keys, dnssec_ctx, update); if (result != KNOT_EOK) { return result; } @@ -732,8 +733,6 @@ int knot_zone_sign(zone_update_t *update, result = zone_tree_apply(whole ? update->new_cont->nsec3_nodes : update->a_ctx->nsec3_ptrs, set_signed, NULL); } - *expire_at = knot_time_min(normal_expire, nsec3_expire); - return result; } @@ -948,7 +947,7 @@ bool knot_zone_sign_use_key(const zone_key_t *key, const knot_rrset_t *covered) } } -static int sign_in_changeset(zone_node_t *node, uint16_t rrtype, knot_rrset_t *rrsigs, +static int sign_in_changeset(zone_node_t *node, uint16_t rrtype, zone_sign_ctx_t *sign_ctx, int ret_prev, bool skip_crypto, zone_update_t *up) { @@ -959,7 +958,8 @@ static int sign_in_changeset(zone_node_t *node, uint16_t rrtype, knot_rrset_t *r if (knot_rrset_empty(&rr)) { return KNOT_EOK; } - return add_missing_rrsigs(&rr, rrsigs, sign_ctx, skip_crypto, NULL, up, NULL); + knot_rrset_t rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG); + return add_missing_rrsigs(&rr, &rrsigs, sign_ctx, skip_crypto, NULL, up); } int knot_zone_sign_nsecs_in_changeset(const zone_keyset_t *zone_keys, @@ -982,10 +982,9 @@ int knot_zone_sign_nsecs_in_changeset(const zone_keyset_t *zone_keys, zone_node_t *n = zone_tree_it_val(&it); bool skip_crypto = (n->flags & NODE_FLAGS_RRSIGS_VALID) && !dnssec_ctx->keytag_conflict; - knot_rrset_t rrsigs = node_rrset(n, KNOT_RRTYPE_RRSIG); - ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC, &rrsigs, sign_ctx, ret, skip_crypto, update); - ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC3, &rrsigs, sign_ctx, ret, skip_crypto, update); - ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC3PARAM, &rrsigs, sign_ctx, ret, skip_crypto, update); + ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC, sign_ctx, ret, skip_crypto, update); + ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC3, sign_ctx, ret, skip_crypto, update); + ret = sign_in_changeset(n, KNOT_RRTYPE_NSEC3PARAM, sign_ctx, ret, skip_crypto, update); if (ret == KNOT_EOK) { n->flags |= NODE_FLAGS_RRSIGS_VALID; // non-NSEC RRSIGs had been validated in knot_dnssec_sign_update() @@ -1023,10 +1022,9 @@ bool knot_zone_sign_rr_should_be_signed(const zone_node_t *node, int knot_zone_sign_update(zone_update_t *update, zone_keyset_t *zone_keys, - const kdnssec_ctx_t *dnssec_ctx, - knot_time_t *expire_at) + const kdnssec_ctx_t *dnssec_ctx) { - if (update == NULL || dnssec_ctx == NULL || expire_at == NULL || + if (update == NULL || dnssec_ctx == NULL || dnssec_ctx->policy->signing_threads < 1 || (zone_keys == NULL && !dnssec_ctx->validation_mode)) { return KNOT_EINVAL; @@ -1038,16 +1036,16 @@ int knot_zone_sign_update(zone_update_t *update, * If so, we have to sign the whole zone. */ const bool full_sign = apex_dnssec_changed(update); if (full_sign) { - ret = knot_zone_sign(update, zone_keys, dnssec_ctx, expire_at); + ret = knot_zone_sign(update, zone_keys, dnssec_ctx); } else { ret = zone_tree_sign(update->a_ctx->node_ptrs, dnssec_ctx->policy->signing_threads, - zone_keys, dnssec_ctx, update, expire_at); + zone_keys, dnssec_ctx, update); if (ret == KNOT_EOK) { ret = zone_tree_apply(update->a_ctx->node_ptrs, set_signed, NULL); } if (ret == KNOT_EOK && dnssec_ctx->validation_mode) { ret = zone_tree_sign(update->a_ctx->nsec3_ptrs, dnssec_ctx->policy->signing_threads, - zone_keys, dnssec_ctx, update, expire_at); + zone_keys, dnssec_ctx, update); } if (ret == KNOT_EOK && dnssec_ctx->validation_mode) { ret = zone_tree_apply(update->a_ctx->nsec3_ptrs, set_signed, NULL); diff --git a/src/knot/dnssec/zone-sign.h b/src/knot/dnssec/zone-sign.h index ba6e2b2..480bcf9 100644 --- a/src/knot/dnssec/zone-sign.h +++ b/src/knot/dnssec/zone-sign.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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,13 +81,15 @@ keyptr_dynarray_t knot_zone_sign_get_cdnskeys(const kdnssec_ctx_t *ctx, * \param rrsigs RRSIG with signatures. * \param sign_ctx Signing context (with keys == NULL) * \param skip_crypto Crypto operations might be skipped as they had been successful earlier. + * \param valid_until End of soonest RRSIG validity. * * \return KNOT_E* */ int knot_validate_rrsigs(const knot_rrset_t *covered, const knot_rrset_t *rrsigs, zone_sign_ctx_t *sign_ctx, - bool skip_crypto); + bool skip_crypto, + knot_time_t *valid_until); /*! * \brief Update zone signatures and store performed changes in update. @@ -97,14 +99,12 @@ int knot_validate_rrsigs(const knot_rrset_t *covered, * \param update Zone Update containing the zone and to be updated with new DNSKEYs and RRSIGs. * \param zone_keys Zone keys. * \param dnssec_ctx DNSSEC context. - * \param expire_at Time, when the oldest signature in the zone expires. * * \return Error code, KNOT_EOK if successful. */ int knot_zone_sign(zone_update_t *update, zone_keyset_t *zone_keys, - const kdnssec_ctx_t *dnssec_ctx, - knot_time_t *expire_at); + const kdnssec_ctx_t *dnssec_ctx); /*! * \brief Sign NSEC/NSEC3 nodes in changeset and update the changeset. @@ -138,14 +138,12 @@ bool knot_zone_sign_rr_should_be_signed(const zone_node_t *node, * \param update Zone Update structure. * \param zone_keys Zone keys. * \param dnssec_ctx DNSSEC context. - * \param expire_at Time, when the oldest signature in the update expires. * * \return Error code, KNOT_EOK if successful. */ int knot_zone_sign_update(zone_update_t *update, zone_keyset_t *zone_keys, - const kdnssec_ctx_t *dnssec_ctx, - knot_time_t *expire_at); + const kdnssec_ctx_t *dnssec_ctx); /*! * \brief Force re-sign of a RRSet in zone apex. diff --git a/src/knot/events/events.c b/src/knot/events/events.c index f0de68e..6224006 100644 --- a/src/knot/events/events.c +++ b/src/knot/events/events.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 @@ -46,6 +46,7 @@ static const event_info_t EVENT_INFO[] = { { ZONE_EVENT_BACKUP, event_backup, "backup/restore" }, { ZONE_EVENT_NOTIFY, event_notify, "notify" }, { ZONE_EVENT_DNSSEC, event_dnssec, "re-sign" }, + { ZONE_EVENT_VALIDATE, event_validate, "DNSSEC-validate" }, { ZONE_EVENT_UFREEZE, event_ufreeze, "update-freeze" }, { ZONE_EVENT_UTHAW, event_uthaw, "update-thaw" }, { ZONE_EVENT_DS_CHECK, event_ds_check, "DS-check" }, diff --git a/src/knot/events/events.h b/src/knot/events/events.h index a32d195..cf1b362 100644 --- a/src/knot/events/events.h +++ b/src/knot/events/events.h @@ -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 @@ -38,6 +38,7 @@ typedef enum zone_event_type { ZONE_EVENT_BACKUP, ZONE_EVENT_NOTIFY, ZONE_EVENT_DNSSEC, + ZONE_EVENT_VALIDATE, ZONE_EVENT_UFREEZE, ZONE_EVENT_UTHAW, ZONE_EVENT_DS_CHECK, diff --git a/src/knot/events/handlers.h b/src/knot/events/handlers.h index d74fd8a..45b7b45 100644 --- a/src/knot/events/handlers.h +++ b/src/knot/events/handlers.h @@ -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 @@ -39,6 +39,8 @@ int event_dnssec(conf_t *conf, zone_t *zone); /*! \brief NOT A HANDLER, just a helper function to reschedule based on reschedule_t */ void event_dnssec_reschedule(conf_t *conf, zone_t *zone, const zone_sign_reschedule_t *refresh, bool zone_changed); +/*! \brief Validate the wole zone's DNSSEC. */ +int event_validate(conf_t *conf, zone_t *zone); /*! \brief Freeze those events causing zone contents change. */ int event_ufreeze(conf_t *conf, zone_t *zone); /*! \brief Unfreeze zone updates. */ diff --git a/src/knot/events/handlers/dnskey_sync.c b/src/knot/events/handlers/dnskey_sync.c index c6d80ff..018f66b 100644 --- a/src/knot/events/handlers/dnskey_sync.c +++ b/src/knot/events/handlers/dnskey_sync.c @@ -26,8 +26,8 @@ #define DNSKEY_SYNC_LOG(priority, zone, remote, flags, fmt, ...) \ ns_log(priority, zone, LOG_OPERATION_DNSKEY_SYNC, LOG_DIRECTION_OUT, &(remote)->addr, \ - ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \ - ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, fmt, ## __VA_ARGS__) + flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, \ + 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 be7621f..761b800 100644 --- a/src/knot/events/handlers/ds_push.c +++ b/src/knot/events/handlers/ds_push.c @@ -38,8 +38,8 @@ 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)->addr, \ - ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \ - ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, fmt, ## __VA_ARGS__) + flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, \ + fmt, ## __VA_ARGS__) static const knot_rdata_t remove_cds = { 5, { 0, 0, 0, 0, 0 } }; @@ -53,7 +53,7 @@ static int ds_push_begin(knot_layer_t *layer, void *params) static int parent_soa_produce(struct ds_push_data *data, knot_pkt_t *pkt) { assert(data->parent_query[0] != '\0'); - data->parent_query = knot_wire_next_label(data->parent_query, NULL); + data->parent_query = knot_dname_next_label(data->parent_query); int ret = knot_pkt_put_question(pkt, data->parent_query, KNOT_CLASS_IN, KNOT_RRTYPE_SOA); if (ret != KNOT_EOK) { diff --git a/src/knot/events/handlers/notify.c b/src/knot/events/handlers/notify.c index 9dae70a..9a8b8df 100644 --- a/src/knot/events/handlers/notify.c +++ b/src/knot/events/handlers/notify.c @@ -84,8 +84,8 @@ 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)->addr, \ - ((flags) & KNOT_REQUESTOR_QUIC) ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP, \ - ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, fmt, ## __VA_ARGS__) + flags2proto(flags), ((flags) & KNOT_REQUESTOR_REUSED), (remote)->key.name, \ + 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 8099df6..6c22707 100644 --- a/src/knot/events/handlers/refresh.c +++ b/src/knot/events/handlers/refresh.c @@ -68,24 +68,24 @@ * \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->addr, 0, false, (data)->remote->key.name, msg) #define REFRESH_LOG_PROTO(priority, data, msg...) \ ns_log(priority, (data)->zone->name, LOG_OPERATION_REFRESH, LOG_DIRECTION_NONE, \ - &(data)->remote->addr, PROTO(data), (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) + &(data)->remote->addr, flags2proto((data)->layer->flags), \ + (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) #define AXFRIN_LOG(priority, data, msg...) \ ns_log(priority, (data)->zone->name, LOG_OPERATION_AXFR, LOG_DIRECTION_IN, \ - &(data)->remote->addr, PROTO(data), (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) + &(data)->remote->addr, flags2proto((data)->layer->flags), \ + (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) #define IXFRIN_LOG(priority, data, msg...) \ ns_log(priority, (data)->zone->name, LOG_OPERATION_IXFR, LOG_DIRECTION_IN, \ - &(data)->remote->addr, PROTO(data), (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) + &(data)->remote->addr, flags2proto((data)->layer->flags), \ + (data)->layer->flags & KNOT_REQUESTOR_REUSED, (data)->remote->key.name, msg) enum state { REFRESH_STATE_INVALID = 0, @@ -123,7 +123,7 @@ struct refresh_data { int ret; //!< Error code. enum state state; //!< Event processing state. - enum xfr_type xfr_type; //!< Transer type (mostly IXFR versus AXFR). + enum xfr_type xfr_type; //!< Transfer type (mostly IXFR versus AXFR). bool axfr_style_ixfr; //!< Master responded with AXFR-style-IXFR. knot_rrset_t *initial_soa_copy; //!< Copy of the received initial SOA. struct xfr_stats stats; //!< Transfer statistics. @@ -1200,14 +1200,33 @@ static int transfer_consume(knot_layer_t *layer, knot_pkt_t *pkt) // Transfer completed if (next == KNOT_STATE_DONE) { // Log transfer even if we still can fail + uint32_t serial; + switch (data->xfr_type) { + case XFR_TYPE_AXFR: + serial = zone_contents_serial(data->axfr.zone); + break; + case XFR_TYPE_IXFR: + serial = knot_soa_serial(data->ixfr.final_soa->rrs.rdata); + break; + case XFR_TYPE_UPTODATE: + if (slave_zone_serial(data->zone, data->conf, &serial) == KNOT_EOK) { + break; + } + // FALLTHROUGH + default: + serial = 0; + } + char serial_log[32]; + (void)snprintf(serial_log, sizeof(serial_log), + " remote serial %u,", serial); xfr_log_finished(data->zone->name, data->xfr_type == XFR_TYPE_IXFR || data->xfr_type == XFR_TYPE_UPTODATE ? LOG_OPERATION_IXFR : LOG_OPERATION_AXFR, LOG_DIRECTION_IN, &data->remote->addr, - (layer->flags & KNOT_REQUESTOR_QUIC ? - KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_TCP), - data->remote->key.name, &data->stats); + flags2proto(layer->flags), + data->remote->key.name, + serial_log, &data->stats); /* * TODO: Move finialization into finish diff --git a/src/knot/events/handlers/update.c b/src/knot/events/handlers/update.c index b4092a3..2c6758c 100644 --- a/src/knot/events/handlers/update.c +++ b/src/knot/events/handlers/update.c @@ -28,6 +28,9 @@ #include "knot/zone/zone.h" #include "libdnssec/random.h" #include "libknot/libknot.h" +#include "libknot/quic/quic_conn.h" +#include "libknot/quic/quic.h" +#include "libknot/quic/tls.h" #include "contrib/net.h" #include "contrib/time.h" @@ -51,6 +54,33 @@ static void init_qdata_from_request(knotd_qdata_t *qdata, qdata->extra->zone = zone; } +#ifdef ENABLE_QUIC +static int ddnsq_alloc_reply(knot_quic_reply_t *r) +{ + r->out_payload->iov_len = KNOT_WIRE_MAX_PKTSIZE; + + return KNOT_EOK; +} + +static int ddnsq_send_reply(knot_quic_reply_t *r) +{ + int fd = *(int *)r->sock; + int ret = net_dgram_send(fd, r->out_payload->iov_base, r->out_payload->iov_len, r->ip_rem); + if (ret < 0) { + return knot_map_errno(); + } else if (ret == r->out_payload->iov_len) { + return KNOT_EOK; + } else { + return KNOT_EAGAIN; + } +} + +static void ddnsq_free_reply(knot_quic_reply_t *r) +{ + r->out_payload->iov_len = 0; +} +#endif // ENABLE_QUIC + static int check_prereqs(knot_request_t *request, zone_update_t *update, knotd_qdata_t *qdata) @@ -65,6 +95,15 @@ static int check_prereqs(knot_request_t *request, return ret; } + ret = ddns_precheck_update(request->query, update, &rcode); + if (ret != KNOT_EOK) { + UPDATE_LOG(LOG_WARNING, qdata, "broken update format (%s)", + knot_strerror(ret)); + assert(rcode != KNOT_RCODE_NOERROR); + knot_wire_set_rcode(request->resp->wire, rcode); + return ret; + } + return KNOT_EOK; } @@ -104,6 +143,7 @@ static int process_bulk(zone_t *zone, list_t *requests, zone_update_t *up) knot_request_t *req = node->d; // Init qdata structure for logging (unique per-request). knotd_qdata_params_t params = { + .proto = flags2proto(req->flags), .remote = &req->remote }; knotd_qdata_t qdata; @@ -118,6 +158,8 @@ static int process_bulk(zone_t *zone, list_t *requests, zone_update_t *up) ret = process_single_update(req, up, &qdata); if (ret != KNOT_EOK) { + log_zone_error(zone->name, "DDNS, dropping %zu updates in a bulk", + list_size(requests)); return ret; } } @@ -338,6 +380,39 @@ static void send_update_response(conf_t *conf, zone_t *zone, knot_request_t *req (void)process_query_sign_response(req->resp, &qdata); } + if (net_is_stream(req->fd) && req->tls_req_ctx.conn != NULL) { + (void)knot_tls_send_dns(req->tls_req_ctx.conn, + req->resp->wire, req->resp->size); + knot_tls_conn_block(req->tls_req_ctx.conn, false); + } +#ifdef ENABLE_QUIC + else if (req->quic_conn != NULL) { + assert(!net_is_stream(req->fd)); + uint8_t op_buf[KNOT_WIRE_MAX_PKTSIZE]; + struct iovec out_payload = { .iov_base = op_buf, .iov_len = sizeof(op_buf) }; + knot_quic_reply_t rpl = { + .ip_rem = &req->remote, + .ip_loc = &req->source, + .in_payload = NULL, + .out_payload = &out_payload, + .sock = &req->fd, + .alloc_reply = ddnsq_alloc_reply, + .send_reply = ddnsq_send_reply, + .free_reply = ddnsq_free_reply + }; + + void *succ = knot_quic_stream_add_data(req->quic_conn, req->quic_stream, + req->resp->wire, req->resp->size); + if (succ != NULL) { // else ENOMEM + (void)knot_quic_send(req->quic_conn->quic_table, req->quic_conn, + &rpl, 4, KNOT_QUIC_SEND_IGNORE_BLOCKED); + } + knot_quic_conn_block(req->quic_conn, false); + } else // NOTE ties to 'if' below +#else + assert(req->quic_conn == NULL); +#endif // ENABLE_QUIC + if (net_is_stream(req->fd)) { net_dns_tcp_send(req->fd, req->resp->wire, req->resp->size, conf->cache.srv_tcp_remote_io_timeout, NULL); @@ -348,22 +423,13 @@ static void send_update_response(conf_t *conf, zone_t *zone, knot_request_t *req } } -static void free_request(knot_request_t *req) -{ - close(req->fd); - knot_pkt_free(req->query); - knot_pkt_free(req->resp); - dnssec_binary_free(&req->sign.tsig_key.secret); - free(req); -} - static void send_update_responses(conf_t *conf, zone_t *zone, list_t *updates) { ptrnode_t *node, *nxt; WALK_LIST_DELSAFE(node, nxt, *updates) { knot_request_t *req = node->d; send_update_response(conf, zone, req); - free_request(req); + knot_request_free(req, NULL); } ptrlist_free(updates, NULL); } diff --git a/src/knot/events/handlers/validate.c b/src/knot/events/handlers/validate.c new file mode 100644 index 0000000..e410b4f --- /dev/null +++ b/src/knot/events/handlers/validate.c @@ -0,0 +1,34 @@ +/* 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 "knot/common/log.h" +#include "knot/conf/conf.h" +#include "knot/dnssec/zone-events.h" +#include "knot/zone/zone.h" + +int event_validate(conf_t *conf, zone_t *zone) +{ + knot_time_t now = knot_time(); + zone_update_t fake_upd = { + .zone = zone, + .new_cont = zone->contents, + // .validation_hint is zeroed + }; + + log_zone_info(zone->name, "DNSSEC, re-validating zone fully"); + + return knot_dnssec_validate_zone(&fake_upd, conf, now, false, true); +} diff --git a/src/knot/events/replan.c b/src/knot/events/replan.c index ed03fe1..29a07ea 100644 --- a/src/knot/events/replan.c +++ b/src/knot/events/replan.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 @@ -108,6 +108,9 @@ void replan_from_timers(conf_t *conf, zone_t *zone) if (zone->contents == NULL && zone->timers.last_refresh_ok) { // zone disappeared w/o expiry refresh = now; } + if (refresh == 0) { // sanitize in case of concurrent purge event + refresh = now; + } assert(refresh > 0); } diff --git a/src/knot/include/module.h b/src/knot/include/module.h index 15a9077..9998e48 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-" @@ -354,7 +354,7 @@ knotd_conf_t knotd_conf_check_item(knotd_conf_check_args_t *args, const yp_name_t *item_name); /*! - * \brief Checks if address is in at least one of given ranges. + * Checks if address is in at least one of given ranges. * * \param[in] range * \param[in] addr @@ -393,11 +393,13 @@ 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. */ typedef enum { - KNOTD_QUERY_FLAG_COOKIE = 1 << 0, /*!< Valid DNS Cookie indication. */ + KNOTD_QUERY_FLAG_COOKIE = 1 << 0, /*!< Valid DNS Cookie indication. */ + KNOTD_QUERY_FLAG_AUTHORIZED = 1 << 1, /*!< Successfully authorized operation. */ } knotd_query_flag_t; /*! Query processing data context parameters. */ @@ -411,6 +413,7 @@ typedef struct { void *server; /*!< Server object private item. */ const struct knot_xdp_msg *xdp_msg; /*!< Possible XDP message context. */ struct knot_quic_conn *quic_conn; /*!< QUIC connection context. */ + struct knot_tls_conn *tls_conn; /*!< TLS connection context. */ int64_t quic_stream; /*!< QUIC stream ID inside quic_conn. */ uint32_t measured_rtt; /*!< Measured RTT in usecs: QUIC or TCP-XDP. */ } knotd_qdata_params_t; @@ -493,6 +496,7 @@ knot_rrset_t knotd_qdata_zone_apex_rrset(const knotd_qdata_t *qdata, uint16_t ty * \param[in] qdata Query data. * \param[in] zone_name Optional zone name, the current one otherwise. * \param[in] node_name Optional node name, apex otherwise. + * \param[in] type RRSet type. * \param[out] out Destination rrset to store the output to. * * \return Error code (KNOT_ENOZONE, KNOT_EEMPTYZONE, KNOT_ENONODE), KNOT_EOK if success. @@ -501,6 +505,12 @@ int knotd_qdata_zone_rrset(const knotd_qdata_t *qdata, const knot_dname_t *zone_ const knot_dname_t *node_name, uint16_t type, knot_rrset_t *out); +/*! Transport protocol processing states. */ +typedef enum { + KNOTD_PROTO_STATE_PASS = 0, /*!< Process normally. */ + KNOTD_PROTO_STATE_BLOCK = 1, /*!< Block the packet/connection. */ +} knotd_proto_state_t; + /*! General query processing states. */ typedef enum { KNOTD_STATE_NOOP = 0, /*!< No response. */ @@ -509,7 +519,7 @@ typedef enum { KNOTD_STATE_FINAL = 6, /*!< Finished and finalized (QNAME, EDNS, TSIG). */ } knotd_state_t; -/*! brief Internet query processing states. */ +/*! Internet query processing states. */ typedef enum { KNOTD_IN_STATE_BEGIN, /*!< Begin name resolution. */ KNOTD_IN_STATE_NODATA, /*!< Positive result with NO data. */ @@ -523,15 +533,29 @@ typedef enum { /*! Query module processing stages. */ typedef enum { - KNOTD_STAGE_BEGIN = 0, /*!< Before query processing. */ - KNOTD_STAGE_PREANSWER, /*!< Before section processing. */ - KNOTD_STAGE_ANSWER, /*!< Answer section processing. */ - KNOTD_STAGE_AUTHORITY, /*!< Authority section processing. */ - KNOTD_STAGE_ADDITIONAL, /*!< Additional section processing. */ - KNOTD_STAGE_END, /*!< After query processing. */ + KNOTD_STAGE_PROTO_BEGIN = 0, /*!< Start of transport protocol processing. */ + KNOTD_STAGE_BEGIN, /*!< Before query processing. */ + KNOTD_STAGE_PREANSWER, /*!< Before section processing. */ + KNOTD_STAGE_ANSWER, /*!< Answer section processing. */ + KNOTD_STAGE_AUTHORITY, /*!< Authority section processing. */ + KNOTD_STAGE_ADDITIONAL, /*!< Additional section processing. */ + KNOTD_STAGE_END, /*!< After query processing. */ + KNOTD_STAGE_PROTO_END, /*!< End of transport protocol processing. */ } knotd_stage_t; /*! + * Transport protocol processing hook. + * + * \param[in] state Current processing state. + * \param[in] params Processing parameters. + * \param[in] mod Module context. + * + * \return Next processing state. + */ +typedef knotd_proto_state_t (*knotd_mod_proto_hook_f) + (knotd_proto_state_t state, knotd_qdata_params_t *params, knotd_mod_t *mod); + +/*! * General processing hook. * * \param[in] state Current processing state. @@ -558,6 +582,17 @@ typedef knotd_in_state_t (*knotd_mod_in_hook_f) (knotd_in_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod); /*! + * Registers transport protocol processing module hook. + * + * \param[in] mod Module context. + * \param[in] stage Processing stage (KNOTD_STAGE_PROTO_BEGIN or KNOTD_STAGE_PROTO_END). + * \param[in] hook Module hook. + * + * \return Error code, KNOT_EOK if success. + */ +int knotd_mod_proto_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_proto_hook_f hook); + +/*! * Registers general processing module hook. * * \param[in] mod Module context. diff --git a/src/knot/modules/cookies/cookies.c b/src/knot/modules/cookies/cookies.c index 34c4b22..48eca7d 100644 --- a/src/knot/modules/cookies/cookies.c +++ b/src/knot/modules/cookies/cookies.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -20,19 +20,10 @@ #include "knot/include/module.h" #include "libknot/libknot.h" +#include "contrib/atomic.h" #include "contrib/string.h" #include "libdnssec/random.h" -#ifdef HAVE_ATOMIC -#define ATOMIC_SET(dst, val) __atomic_store_n(&(dst), (val), __ATOMIC_RELAXED) -#define ATOMIC_GET(src) __atomic_load_n(&(src), __ATOMIC_RELAXED) -#define ATOMIC_ADD(dst, val) __atomic_add_fetch(&(dst), (val), __ATOMIC_RELAXED) -#else -#define ATOMIC_SET(dst, val) ((dst) = (val)) -#define ATOMIC_GET(src) (src) -#define ATOMIC_ADD(dst, val) ((dst) += (val)) -#endif - #define BADCOOKIE_CTR_INIT 1 #define MOD_SECRET_LIFETIME "\x0F""secret-lifetime" @@ -59,13 +50,13 @@ int cookies_conf_check(knotd_conf_check_args_t *args) typedef struct { struct { - uint64_t variable; + knot_atomic_uint64_t variable; uint64_t constant; } secret; pthread_t update_secret; uint32_t secret_lifetime; uint32_t badcookie_slip; - uint16_t badcookie_ctr; // Counter for BADCOOKIE answers. + knot_atomic_uint16_t badcookie_ctr; // Counter for BADCOOKIE answers. } cookies_ctx_t; static void update_ctr(cookies_ctx_t *ctx) @@ -285,7 +276,7 @@ int cookies_load(knotd_mod_t *mod) } } -#ifndef HAVE_ATOMIC +#ifndef KNOT_HAVE_ATOMIC knotd_mod_log(mod, LOG_WARNING, "the module might work slightly wrong on this platform"); ctx->badcookie_slip = 1; #endif diff --git a/src/knot/modules/probe/probe.c b/src/knot/modules/probe/probe.c index 9207308..3e0a646 100644 --- a/src/knot/modules/probe/probe.c +++ b/src/knot/modules/probe/probe.c @@ -19,18 +19,11 @@ #include "knot/conf/schema.h" #include "knot/include/module.h" +#include "contrib/atomic.h" #include "contrib/string.h" #include "contrib/time.h" #include "libknot/libknot.h" -#ifdef HAVE_ATOMIC -#define ATOMIC_SET(dst, val) __atomic_store_n(&(dst), (val), __ATOMIC_RELAXED) -#define ATOMIC_GET(src) __atomic_load_n(&(src), __ATOMIC_RELAXED) -#else -#define ATOMIC_SET(dst, val) ((dst) = (val)) -#define ATOMIC_GET(src) (src) -#endif - #define MOD_PATH "\x04""path" #define MOD_CHANNELS "\x08""channels" #define MOD_MAX_RATE "\x08""max-rate" @@ -45,7 +38,7 @@ const yp_item_t probe_conf[] = { typedef struct { knot_probe_t **probes; size_t probe_count; - uint64_t *last_times; + knot_atomic_uint64_t *last_times; uint64_t min_diff_ns; char *path; } probe_ctx_t; @@ -139,7 +132,7 @@ int probe_load(knotd_mod_t *mod) return KNOT_ENOMEM; } - ctx->last_times = calloc(ctx->probe_count, sizeof(uint64_t)); + ctx->last_times = calloc(ctx->probe_count, sizeof(*ctx->last_times)); if (ctx->last_times == NULL) { free_probe_ctx(ctx); return KNOT_ENOMEM; diff --git a/src/knot/modules/rrl/Makefile.inc b/src/knot/modules/rrl/Makefile.inc index d82edf9..fbb9eb0 100644 --- a/src/knot/modules/rrl/Makefile.inc +++ b/src/knot/modules/rrl/Makefile.inc @@ -1,15 +1,20 @@ knot_modules_rrl_la_SOURCES = knot/modules/rrl/rrl.c \ knot/modules/rrl/functions.c \ - knot/modules/rrl/functions.h + knot/modules/rrl/functions.h \ + knot/modules/rrl/kru-generic.c \ + knot/modules/rrl/kru-avx2.c \ + knot/modules/rrl/kru.h +noinst_HEADERS = knot/modules/rrl/kru.inc.c EXTRA_DIST += knot/modules/rrl/rrl.rst if STATIC_MODULE_rrl libknotd_la_SOURCES += $(knot_modules_rrl_la_SOURCES) +libknotd_la_LIBADD += $(math_LIBS) endif if SHARED_MODULE_rrl knot_modules_rrl_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS) knot_modules_rrl_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) -knot_modules_rrl_la_LIBADD = $(libcontrib_LIBS) +knot_modules_rrl_la_LIBADD = $(libcontrib_LIBS) $(math_LIBS) pkglib_LTLIBRARIES += knot/modules/rrl.la endif diff --git a/src/knot/modules/rrl/functions.c b/src/knot/modules/rrl/functions.c index df35394..01d89cb 100644 --- a/src/knot/modules/rrl/functions.c +++ b/src/knot/modules/rrl/functions.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 @@ -14,516 +14,223 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <assert.h> +#include <stdatomic.h> #include <time.h> #include "knot/modules/rrl/functions.h" +#include "knot/modules/rrl/kru.h" +#include "contrib/macros.h" #include "contrib/musl/inet_ntop.h" -#include "contrib/openbsd/strlcat.h" #include "contrib/sockaddr.h" #include "contrib/time.h" -#include "libdnssec/error.h" #include "libdnssec/random.h" -/* Hopscotch defines. */ -#define HOP_LEN (sizeof(unsigned)*8) -/* Limits (class, ipv6 remote, dname) */ -#define RRL_CLSBLK_MAXLEN (1 + 8 + 255) -/* CIDR block prefix lengths for v4/v6 */ -#define RRL_V4_PREFIX_LEN 3 /* /24 */ -#define RRL_V6_PREFIX_LEN 7 /* /56 */ -/* Defaults */ -#define RRL_SSTART 2 /* 1/Nth of the rate for slow start */ -#define RRL_PSIZE_LARGE 1024 -#define RRL_CAPACITY 4 /* Window size in seconds */ -#define RRL_LOCK_GRANULARITY 32 /* Last digit granularity */ - -/* Classification */ -enum { - CLS_NULL = 0 << 0, /* Empty bucket. */ - CLS_NORMAL = 1 << 0, /* Normal response. */ - CLS_ERROR = 1 << 1, /* Error response. */ - CLS_NXDOMAIN = 1 << 2, /* NXDOMAIN (special case of error). */ - CLS_EMPTY = 1 << 3, /* Empty response. */ - CLS_LARGE = 1 << 4, /* Response size over threshold (1024k). */ - CLS_WILDCARD = 1 << 5, /* Wildcard query. */ - CLS_ANY = 1 << 6, /* ANY query (spec. class). */ - CLS_DNSSEC = 1 << 7 /* DNSSEC related RR query (spec. class) */ -}; +// CIDR block prefix lengths for v4/v6 (hardcoded also in unit tests). +#define RRL_V4_PREFIXES (uint8_t[]) { 18, 20, 24, 32 } +#define RRL_V4_RATE_MULT (kru_price_t[]) { 768, 256, 32, 1 } -/* Classification string. */ -struct cls_name { - int code; - const char *name; -}; +#define RRL_V6_PREFIXES (uint8_t[]) { 32, 48, 56, 64, 128 } +#define RRL_V6_RATE_MULT (kru_price_t[]) { 64, 4, 3, 2, 1 } -static const struct cls_name rrl_cls_names[] = { - { CLS_NORMAL, "POSITIVE" }, - { CLS_ERROR, "ERROR" }, - { CLS_NXDOMAIN, "NXDOMAIN"}, - { CLS_EMPTY, "EMPTY"}, - { CLS_LARGE, "LARGE"}, - { CLS_WILDCARD, "WILDCARD"}, - { CLS_ANY, "ANY"}, - { CLS_DNSSEC, "DNSSEC"}, - { CLS_NULL, "NULL"}, - { CLS_NULL, NULL} -}; +#define RRL_V4_PREFIXES_CNT (sizeof(RRL_V4_PREFIXES) / sizeof(*RRL_V4_PREFIXES)) +#define RRL_V6_PREFIXES_CNT (sizeof(RRL_V6_PREFIXES) / sizeof(*RRL_V6_PREFIXES)) +#define RRL_MAX_PREFIXES_CNT ((RRL_V4_PREFIXES_CNT > RRL_V6_PREFIXES_CNT) ? RRL_V4_PREFIXES_CNT : RRL_V6_PREFIXES_CNT) -static inline const char *rrl_clsstr(int code) -{ - for (const struct cls_name *c = rrl_cls_names; c->name; c++) { - if (c->code == code) { - return c->name; - } - } +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +#endif - return "unknown class"; -} +#define RRL_LIMIT_KOEF 1/2 // Avoid probabilistic rounding wherever possible. -/* Bucket flags. */ -enum { - RRL_BF_NULL = 0 << 0, /* No flags. */ - RRL_BF_SSTART = 1 << 0, /* Bucket in slow-start after collision. */ - RRL_BF_ELIMIT = 1 << 1 /* Bucket is rate-limited. */ +struct rrl_table { + kru_price_t v4_prices[RRL_V4_PREFIXES_CNT]; + kru_price_t v6_prices[RRL_V6_PREFIXES_CNT]; + uint32_t log_period; + bool rw_mode; + _Atomic uint32_t log_time; + _Alignas(64) uint8_t kru[]; }; -static uint8_t rrl_clsid(rrl_req_t *p) -{ - /* Check error code */ - int ret = CLS_NULL; - switch (knot_wire_get_rcode(p->wire)) { - case KNOT_RCODE_NOERROR: ret = CLS_NORMAL; break; - case KNOT_RCODE_NXDOMAIN: return CLS_NXDOMAIN; break; - default: return CLS_ERROR; break; - } - - /* Check if answered from a qname */ - if (ret == CLS_NORMAL && p->flags & RRL_REQ_WILDCARD) { - return CLS_WILDCARD; - } - - /* Check query type for spec. classes. */ - if (p->query) { - switch(knot_pkt_qtype(p->query)) { - case KNOT_RRTYPE_ANY: /* ANY spec. class */ - return CLS_ANY; - break; - case KNOT_RRTYPE_DNSKEY: - case KNOT_RRTYPE_RRSIG: - case KNOT_RRTYPE_DS: /* DNSSEC-related RR class. */ - return CLS_DNSSEC; - break; - default: - break; - } - } - - /* Check packet size for threshold. */ - if (p->len >= RRL_PSIZE_LARGE) { - return CLS_LARGE; - } - - /* Check ancount */ - if (knot_wire_get_ancount(p->wire) == 0) { - return CLS_EMPTY; - } - - return ret; -} - -static int rrl_clsname(uint8_t *dst, size_t maxlen, uint8_t cls, rrl_req_t *req, - const knot_dname_t *name) +static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage *ss) { - if (name == NULL) { - /* Fallback for errors etc. */ - name = (const knot_dname_t *)"\x00"; - } - - switch (cls) { - case CLS_ERROR: /* Could be a non-existent zone or garbage. */ - case CLS_NXDOMAIN: /* Queries to non-existent names in zone. */ - case CLS_WILDCARD: /* Queries to names covered by a wildcard. */ - break; - default: - /* Use QNAME */ - if (req->query) { - name = knot_pkt_qname(req->query); - } - break; - } + assert(ss); - /* Write to wire */ - return knot_dname_to_wire(dst, name, maxlen); -} - -static int rrl_classify(uint8_t *dst, size_t maxlen, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *name) -{ - /* Class */ - uint8_t cls = rrl_clsid(req); - *dst = cls; - int blklen = sizeof(cls); - - /* Address (in network byteorder, adjust masks). */ - uint64_t netblk = 0; - if (remote->ss_family == AF_INET6) { - struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote; - memcpy(&netblk, &ipv6->sin6_addr, RRL_V6_PREFIX_LEN); - } else { - struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote; - memcpy(&netblk, &ipv4->sin_addr, RRL_V4_PREFIX_LEN); - } - memcpy(dst + blklen, &netblk, sizeof(netblk)); - blklen += sizeof(netblk); - - /* Name */ - int ret = rrl_clsname(dst + blklen, maxlen - blklen, cls, req, name); - if (ret < 0) { - return ret; - } - uint8_t len = ret; - blklen += len; - - return blklen; -} - -static int bucket_free(rrl_item_t *bucket, uint32_t now) -{ - return bucket->cls == CLS_NULL || (bucket->time + 1 < now); -} - -static int bucket_match(rrl_item_t *bucket, rrl_item_t *match) -{ - return bucket->cls == match->cls && - bucket->netblk == match->netblk && - bucket->qname == match->qname; -} - -static int find_free(rrl_table_t *tbl, unsigned id, uint32_t now) -{ - for (int i = id; i < tbl->size; i++) { - if (bucket_free(&tbl->arr[i], now)) { - return i - id; - } - } - for (int i = 0; i < id; i++) { - if (bucket_free(&tbl->arr[i], now)) { - return i + (tbl->size - id); - } - } - - /* this happens if table is full... force vacate current elm */ - return id; -} - -static inline unsigned find_match(rrl_table_t *tbl, uint32_t id, rrl_item_t *m) -{ - unsigned new_id = 0; - unsigned hop = 0; - unsigned match_bitmap = tbl->arr[id].hop; - while (match_bitmap != 0) { - hop = __builtin_ctz(match_bitmap); /* offset of next potential match */ - new_id = (id + hop) % tbl->size; - if (bucket_match(&tbl->arr[new_id], m)) { - return hop; - } else { - match_bitmap &= ~(1 << hop); /* clear potential match */ - } - } - - return HOP_LEN + 1; -} - -static inline unsigned reduce_dist(rrl_table_t *tbl, unsigned id, unsigned dist, unsigned *free_id) -{ - unsigned rd = HOP_LEN - 1; - while (rd > 0) { - unsigned vacate_id = (tbl->size + *free_id - rd) % tbl->size; /* bucket to be vacated */ - if (tbl->arr[vacate_id].hop != 0) { - unsigned hop = __builtin_ctz(tbl->arr[vacate_id].hop); /* offset of first valid bucket */ - if (hop < rd) { /* only offsets in <vacate_id, free_id> are interesting */ - unsigned new_id = (vacate_id + hop) % tbl->size; /* this item will be displaced to [free_id] */ - unsigned keep_hop = tbl->arr[*free_id].hop; /* unpredictable padding */ - memcpy(tbl->arr + *free_id, tbl->arr + new_id, sizeof(rrl_item_t)); - tbl->arr[*free_id].hop = keep_hop; - tbl->arr[new_id].cls = CLS_NULL; - tbl->arr[vacate_id].hop &= ~(1 << hop); - tbl->arr[vacate_id].hop |= 1 << rd; - *free_id = new_id; - return dist - (rd - hop); - } - } - --rd; - } - - assert(rd == 0); /* this happens with p=1/fact(HOP_LEN) */ - *free_id = id; - dist = 0; /* force vacate initial element */ - return dist; -} - -static void subnet_tostr(char *dst, size_t maxlen, const struct sockaddr_storage *ss) -{ const void *addr; - const char *suffix; if (ss->ss_family == AF_INET6) { addr = &((struct sockaddr_in6 *)ss)->sin6_addr; - suffix = "/56"; } else { addr = &((struct sockaddr_in *)ss)->sin_addr; - suffix = "/24"; } - if (knot_inet_ntop(ss->ss_family, addr, dst, maxlen) != NULL) { - strlcat(dst, suffix, maxlen); - } else { + if (knot_inet_ntop(ss->ss_family, addr, dst, maxlen) == NULL) { dst[0] = '\0'; } } -static void rrl_log_state(knotd_mod_t *mod, const struct sockaddr_storage *ss, - uint16_t flags, uint8_t cls, const knot_dname_t *qname) +static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, + const uint8_t prefix, bool rate) { - if (mod == NULL || ss == NULL) { + if (mod == NULL) { return; } char addr_str[SOCKADDR_STRLEN]; - subnet_tostr(addr_str, sizeof(addr_str), ss); - - const char *what = "leaves"; - if (flags & RRL_BF_ELIMIT) { - what = "enters"; - } + addr_tostr(addr_str, sizeof(addr_str), ss); - knot_dname_txt_storage_t buf; - char *qname_str = knot_dname_to_str(buf, qname, sizeof(buf)); - if (qname_str == NULL) { - qname_str = "?"; - } - - knotd_mod_log(mod, LOG_NOTICE, "address/subnet %s, class %s, qname %s, %s limiting", - addr_str, rrl_clsstr(cls), qname_str, what); + knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d by %s", + addr_str, prefix, rate ? "rate" : "time"); } -static void rrl_lock(rrl_table_t *tbl, int lk_id) +rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, + bool rw_mode, uint32_t log_period) { - assert(lk_id > -1); - pthread_mutex_lock(tbl->lk + lk_id); -} - -static void rrl_unlock(rrl_table_t *tbl, int lk_id) -{ - assert(lk_id > -1); - pthread_mutex_unlock(tbl->lk + lk_id); -} - -static int rrl_setlocks(rrl_table_t *tbl, uint32_t granularity) -{ - assert(!tbl->lk); /* Cannot change while locks are used. */ - assert(granularity <= tbl->size / 10); /* Due to int. division err. */ - - if (pthread_mutex_init(&tbl->ll, NULL) < 0) { - return KNOT_ENOMEM; + if (size == 0 || instant_limit == 0 || rate_limit == 0) { + return NULL; } - /* Alloc new locks. */ - tbl->lk = malloc(granularity * sizeof(pthread_mutex_t)); - if (!tbl->lk) { - return KNOT_ENOMEM; - } - memset(tbl->lk, 0, granularity * sizeof(pthread_mutex_t)); + size--; + size_t capacity_log = 1; + while (size >>= 1) capacity_log++; - /* Initialize. */ - for (size_t i = 0; i < granularity; ++i) { - if (pthread_mutex_init(tbl->lk + i, NULL) < 0) { - break; - } - ++tbl->lk_count; + rrl_table_t *rrl; + size_t rrl_size = offsetof(struct rrl_table, kru) + KRU.get_size(capacity_log); + if (posix_memalign((void **)&rrl, 64, rrl_size) != 0) { + return NULL; } + memset(rrl, 0, rrl_size); - /* Incomplete initialization */ - if (tbl->lk_count != granularity) { - for (size_t i = 0; i < tbl->lk_count; ++i) { - pthread_mutex_destroy(tbl->lk + i); - } - free(tbl->lk); - tbl->lk_count = 0; - return KNOT_ERROR; + assert(rate_limit <= 1000ll * instant_limit); // Ensured by config check. + kru_price_t base_price = KRU_LIMIT / instant_limit; + const kru_price_t max_decay = (uint64_t)base_price * rate_limit / 1000; + if (!rw_mode) { + base_price = base_price * RRL_LIMIT_KOEF; } - return KNOT_EOK; -} - -rrl_table_t *rrl_create(size_t size, uint32_t rate) -{ - if (size == 0) { + if (!KRU.initialize((struct kru *)rrl->kru, capacity_log, max_decay)) { + free(rrl); return NULL; } - const size_t tbl_len = sizeof(rrl_table_t) + size * sizeof(rrl_item_t); - rrl_table_t *tbl = calloc(1, tbl_len); - if (!tbl) { - return NULL; + for (size_t i = 0; i < RRL_V4_PREFIXES_CNT; i++) { + rrl->v4_prices[i] = base_price / RRL_V4_RATE_MULT[i]; } - tbl->size = size; - tbl->rate = rate; - if (dnssec_random_buffer((uint8_t *)&tbl->key, sizeof(tbl->key)) != DNSSEC_EOK) { - free(tbl); - return NULL; + for (size_t i = 0; i < RRL_V6_PREFIXES_CNT; i++) { + rrl->v6_prices[i] = base_price / RRL_V6_RATE_MULT[i]; } - if (rrl_setlocks(tbl, RRL_LOCK_GRANULARITY) != KNOT_EOK) { - free(tbl); - return NULL; - } + rrl->rw_mode = rw_mode; + rrl->log_period = log_period; - return tbl; -} + struct timespec now_ts; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); + uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; + rrl->log_time = now - log_period; -static knot_dname_t *buf_qname(uint8_t *buf) -{ - return buf + sizeof(uint8_t) + sizeof(uint64_t); + return rrl; } -/*! \brief Get bucket for current combination of parameters. */ -static rrl_item_t *rrl_hash(rrl_table_t *tbl, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *zone, uint32_t stamp, - int *lock, uint8_t *buf, size_t buf_len) +int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod) { - int len = rrl_classify(buf, buf_len, remote, req, zone); - if (len < 0) { - return NULL; - } + assert(rrl); + assert(remote); - uint32_t id = SipHash24(&tbl->key, buf, len) % tbl->size; - - /* Lock for lookup. */ - pthread_mutex_lock(&tbl->ll); - - /* Find an exact match in <id, id + HOP_LEN). */ - knot_dname_t *qname = buf_qname(buf); - uint64_t netblk; - memcpy(&netblk, buf + sizeof(uint8_t), sizeof(netblk)); - rrl_item_t match = { - .hop = 0, - .netblk = netblk, - .ntok = tbl->rate * RRL_CAPACITY, - .cls = buf[0], - .flags = RRL_BF_NULL, - .qname = SipHash24(&tbl->key, qname, knot_dname_size(qname)), - .time = stamp - }; - - unsigned dist = find_match(tbl, id, &match); - if (dist > HOP_LEN) { /* not an exact match, find free element [f] */ - dist = find_free(tbl, id, stamp); - } + struct timespec now_ts; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); + uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; - /* Reduce distance to fit <id, id + HOP_LEN) */ - unsigned free_id = (id + dist) % tbl->size; - while (dist >= HOP_LEN) { - dist = reduce_dist(tbl, id, dist, &free_id); - } + uint16_t load = 0; + uint8_t prefix = 0; + _Alignas(16) uint8_t key[16] = { 0 }; + if (remote->ss_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote; + memcpy(key, &ipv6->sin6_addr, 16); - /* Assign granular lock and unlock lookup. */ - *lock = free_id % tbl->lk_count; - rrl_lock(tbl, *lock); - pthread_mutex_unlock(&tbl->ll); - - /* found free bucket which is in <id, id + HOP_LEN) */ - tbl->arr[id].hop |= (1 << dist); - rrl_item_t *bucket = &tbl->arr[free_id]; - assert(free_id == (id + dist) % tbl->size); - - /* Inspect bucket state. */ - unsigned hop = bucket->hop; - if (bucket->cls == CLS_NULL) { - memcpy(bucket, &match, sizeof(rrl_item_t)); - bucket->hop = hop; + if (rrl->rw_mode) { + prefix = KRU.limited_multi_prefix_or( + (struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES, + rrl->v6_prices, RRL_V6_PREFIXES_CNT, NULL); + } else { + load = KRU.load_multi_prefix_max( + (struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES, + NULL, RRL_V6_PREFIXES_CNT, &prefix); + } + } else { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote; + memcpy(key, &ipv4->sin_addr, 4); + + if (rrl->rw_mode) { + prefix = KRU.limited_multi_prefix_or( + (struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES, + rrl->v4_prices, RRL_V4_PREFIXES_CNT, NULL); + } else { + load = KRU.load_multi_prefix_max( + (struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES, + NULL, RRL_V4_PREFIXES_CNT, &prefix); + } } - /* Check for collisions. */ - if (!bucket_match(bucket, &match)) { - if (!(bucket->flags & RRL_BF_SSTART)) { - memcpy(bucket, &match, sizeof(rrl_item_t)); - bucket->hop = hop; - bucket->ntok = tbl->rate + tbl->rate / RRL_SSTART; - bucket->flags |= RRL_BF_SSTART; + + if (rrl->rw_mode) { + if (prefix == 0) { + return KNOT_EOK; } + } else { + if (load <= (1 << 16) * RRL_LIMIT_KOEF) { + return KNOT_EOK; + } + } + + uint32_t log_time_orig = atomic_load_explicit(&rrl->log_time, memory_order_relaxed); + if (rrl->log_period && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) { + do { + if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now, + memory_order_relaxed, memory_order_relaxed)) { + rrl_log_limited(mod, remote, prefix, rrl->rw_mode); + break; + } + } while (now - log_time_orig + 1024 >= rrl->log_period + 1024); } - return bucket; + return KNOT_ELIMIT; } -int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *zone, knotd_mod_t *mod) +void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value) { - if (!rrl || !req || !remote) { - return KNOT_EINVAL; - } + assert(rrl); + assert(remote); + assert(!rrl->rw_mode); - uint8_t buf[RRL_CLSBLK_MAXLEN]; + struct timespec now_ts; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); + uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; - /* Calculate hash and fetch */ - int ret = KNOT_EOK; - int lock = -1; - uint32_t now = time_now().tv_sec; - rrl_item_t *bucket = rrl_hash(rrl, remote, req, zone, now, &lock, buf, sizeof(buf)); - if (!bucket) { - if (lock > -1) { - rrl_unlock(rrl, lock); - } - return KNOT_ERROR; - } - - /* Calculate rate for dT */ - uint32_t dt = now - bucket->time; - if (dt > RRL_CAPACITY) { - dt = RRL_CAPACITY; - } - /* Visit bucket. */ - bucket->time = now; - if (dt > 0) { /* Window moved. */ - - /* Check state change. */ - if ((bucket->ntok > 0 || dt > 1) && (bucket->flags & RRL_BF_ELIMIT)) { - bucket->flags &= ~RRL_BF_ELIMIT; - rrl_log_state(mod, remote, bucket->flags, bucket->cls, - knot_pkt_qname(req->query)); - } + _Alignas(16) uint8_t key[16] = { 0 }; + if (remote->ss_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote; + memcpy(key, &ipv6->sin6_addr, 16); - /* Add new tokens. */ - uint32_t dn = rrl->rate * dt; - if (bucket->flags & RRL_BF_SSTART) { /* Bucket in slow-start. */ - bucket->flags &= ~RRL_BF_SSTART; - } - bucket->ntok += dn; - if (bucket->ntok > RRL_CAPACITY * rrl->rate) { - bucket->ntok = RRL_CAPACITY * rrl->rate; + kru_price_t prices[RRL_V6_PREFIXES_CNT]; + for (size_t i = 0; i < RRL_V6_PREFIXES_CNT; i++) { + prices[i] = MIN(value * rrl->v6_prices[i], (kru_price_t)-1LL); } - } - /* Last item taken. */ - if (bucket->ntok == 1 && !(bucket->flags & RRL_BF_ELIMIT)) { - bucket->flags |= RRL_BF_ELIMIT; - rrl_log_state(mod, remote, bucket->flags, bucket->cls, - knot_pkt_qname(req->query)); - } + (void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now, + 1, key, RRL_V6_PREFIXES, prices, + RRL_V6_PREFIXES_CNT, NULL); + } else { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote; + memcpy(key, &ipv4->sin_addr, 4); - /* Decay current bucket. */ - if (bucket->ntok > 0) { - --bucket->ntok; - } else if (bucket->ntok == 0) { - ret = KNOT_ELIMIT; - } + kru_price_t prices[RRL_V4_PREFIXES_CNT]; + for (size_t i = 0; i < RRL_V4_PREFIXES_CNT; i++) { + prices[i] = MIN(value * rrl->v4_prices[i], (kru_price_t)-1LL); + } - if (lock > -1) { - rrl_unlock(rrl, lock); + (void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now, + 0, key, RRL_V4_PREFIXES, prices, + RRL_V4_PREFIXES_CNT, NULL); } - return ret; } bool rrl_slip_roll(int n_slip) @@ -540,15 +247,5 @@ bool rrl_slip_roll(int n_slip) void rrl_destroy(rrl_table_t *rrl) { - if (rrl) { - if (rrl->lk_count > 0) { - pthread_mutex_destroy(&rrl->ll); - } - for (size_t i = 0; i < rrl->lk_count; ++i) { - pthread_mutex_destroy(rrl->lk + i); - } - free(rrl->lk); - } - free(rrl); } diff --git a/src/knot/modules/rrl/functions.h b/src/knot/modules/rrl/functions.h index 0f09234..0941c83 100644 --- a/src/knot/modules/rrl/functions.h +++ b/src/knot/modules/rrl/functions.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 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 @@ -17,95 +17,63 @@ #pragma once #include <stdint.h> -#include <pthread.h> #include <sys/socket.h> -#include "libknot/libknot.h" #include "knot/include/module.h" -#include "contrib/openbsd/siphash.h" -/*! - * \brief RRL hash bucket. - */ -typedef struct { - unsigned hop; /* Hop bitmap. */ - uint64_t netblk; /* Prefix associated. */ - uint16_t ntok; /* Tokens available. */ - uint8_t cls; /* Bucket class. */ - uint8_t flags; /* Flags. */ - uint32_t qname; /* imputed(QNAME) hash. */ - uint32_t time; /* Timestamp. */ -} rrl_item_t; +typedef struct rrl_table rrl_table_t; /*! - * \brief RRL hash bucket table. + * \brief Create a RRL table. * - * Table is fixed size, so collisions may occur and are dealt with - * in a way, that hashbucket rate is reset and enters slow-start for 1 dt. - * When a bucket is in a slow-start mode, it cannot reset again for the time - * period. + * \param size Fixed table size. + * \param instant_limit Instant limit. + * \param rate_limit Rate limit. + * \param rw_mode If disabled, RW operation is divided into R and W operations. + * \param log_period If nonzero, maximum logging period (in milliseconds). * - * To avoid lock contention, N locks are created and distributed amongst buckets. - * As of now lock K for bucket N is calculated as K = N % (num_buckets). - */ - -typedef struct { - SIPHASH_KEY key; /* Siphash key. */ - uint32_t rate; /* Configured RRL limit. */ - pthread_mutex_t ll; - pthread_mutex_t *lk; /* Table locks. */ - unsigned lk_count; /* Table lock count (granularity). */ - size_t size; /* Number of buckets. */ - rrl_item_t arr[]; /* Buckets. */ -} rrl_table_t; - -/*! \brief RRL request flags. */ -typedef enum { - RRL_REQ_NOFLAG = 0 << 0, /*!< No flags. */ - RRL_REQ_WILDCARD = 1 << 1 /*!< Query to wildcard name. */ -} rrl_req_flag_t; - -/*! - * \brief RRL request descriptor. - */ -typedef struct { - const uint8_t *wire; - uint16_t len; - rrl_req_flag_t flags; - knot_pkt_t *query; -} rrl_req_t; - -/*! - * \brief Create a RRL table. - * \param size Fixed hashtable size (reasonable large prime is recommended). - * \param rate Rate (in pkts/sec). * \return created table or NULL. */ -rrl_table_t *rrl_create(size_t size, uint32_t rate); +rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, + bool rw_mode, uint32_t log_period); /*! * \brief Query the RRL table for accept or deny, when the rate limit is reached. * + * \note This function is common to both RW and non-RW modes! + * * \param rrl RRL table. * \param remote Source address. - * \param req RRL request (containing resp., flags and question). - * \param zone Zone name related to the response (or NULL). * \param mod Query module (needed for logging). + * * \retval KNOT_EOK if passed. * \retval KNOT_ELIMIT when the limit is reached. */ -int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *zone, knotd_mod_t *mod); +int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod); + +/*! + * \brief Update the RRL table. + * + * \note This function is only for the non-RW mode! + * + * \param rrl RRL table. + * \param remote Source address. + * \param value Value with which the table is updated. + */ +void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value); /*! * \brief Roll a dice whether answer slips or not. + * * \param n_slip Number represents every Nth answer that is slipped. + * * \return true or false */ bool rrl_slip_roll(int n_slip); /*! * \brief Destroy RRL table. + * * \param rrl RRL table. */ void rrl_destroy(rrl_table_t *rrl); diff --git a/src/knot/modules/rrl/kru-avx2.c b/src/knot/modules/rrl/kru-avx2.c new file mode 100644 index 0000000..183ae44 --- /dev/null +++ b/src/knot/modules/rrl/kru-avx2.c @@ -0,0 +1,66 @@ +/* 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/>. + */ + +// Checked with clang 5 (2017) and gcc 6 (2016). +// For other cases we'll rather keep just the generic implementation. +#if defined(__x86_64__) && (__clang_major__ >= 5 || __GNUC__ >= 6) + +// This file has code for new-ish x86 (2015+ usually, Atom 2021+) - AES + AVX2 +#if __clang_major__ >= 12 + #pragma clang attribute push (__attribute__((target("arch=x86-64-v3,aes"))), \ + apply_to = function) +#elif __clang__ + #pragma clang attribute push (__attribute__((target("avx2,aes"))), \ + apply_to = function) +#else + #pragma GCC push_options + #if __GNUC__ >= 11 + #pragma GCC target("arch=x86-64-v3,aes") + // try harder for auto-vectorization, etc. + #pragma GCC optimize("O3") + #else + #pragma GCC target("avx2,aes") + #endif +#endif + +#define USE_AES 1 +#define USE_AVX2 1 +#define USE_SSE41 1 + +#include "./kru.inc.c" +const struct kru_api KRU_AVX2 = KRU_API_INITIALIZER; + +#ifdef __clang__ + #pragma clang attribute pop +#else + #pragma GCC pop_options +#endif + +__attribute__((constructor)) +static void detect_CPU_avx2(void) +{ + // Checking just AES+AVX2 will most likely be OK even if we used arch=x86-64-v3 + if (__builtin_cpu_supports("aes") && __builtin_cpu_supports("avx2")) { + KRU = KRU_AVX2; + } +} + +#else + +#include "./kru.h" +const struct kru_api KRU_AVX2 = {NULL}; + +#endif diff --git a/src/knot/modules/rrl/kru-generic.c b/src/knot/modules/rrl/kru-generic.c new file mode 100644 index 0000000..71ffdd4 --- /dev/null +++ b/src/knot/modules/rrl/kru-generic.c @@ -0,0 +1,20 @@ +/* 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 "./kru.inc.c" + +const struct kru_api KRU_GENERIC = KRU_API_INITIALIZER; +struct kru_api KRU = KRU_API_INITIALIZER; // generic version is the default diff --git a/src/knot/modules/rrl/kru.h b/src/knot/modules/rrl/kru.h new file mode 100644 index 0000000..7eef6c0 --- /dev/null +++ b/src/knot/modules/rrl/kru.h @@ -0,0 +1,90 @@ +/* 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 <stddef.h> +#include <stdint.h> + +#define ALIGNED_CPU_CACHE _Alignas(64) + +// An unsigned integral type used for prices, blocking occurs when sum of prices overflows. +// Greater than 16-bit type enables randomized fractional incrementing as the internal counters are still 16-bit. +// Exponential decay always uses randomized rounding on 32 bits. +typedef uint32_t kru_price_t; + +#define KRU_PRICE_BITS (8 * sizeof(kru_price_t)) + +// maximal allowed sum of prices without limiting +#define KRU_LIMIT (((kru_price_t)-1ll) - (1ll << (KRU_PRICE_BITS - 16)) + 1) + +struct kru; + +/// Usage: KRU.limited(...) +struct kru_api { + /// Initialize a new KRU structure that can track roughly 2^capacity_log limited keys. + /// + /// The kru parameter should point to a zeroed preallocated memory + /// of size returned by get_size aligned to 64-bytes; + /// deallocate the memory to destroy KRU. + /// RAM: the current parametrization will use roughly 8 bytes * 2^capacity_log. + /// + /// The number of non-limited keys is basically arbitrary, + /// but the total sum of prices per tick (for queries returning false) + /// should not get over roughly 2^(capacity_log + 15). + /// Note that the _multi variants increase these totals + /// by tracking multiple keys in a single query. + /// + /// Returns false if kru is NULL or other failure occurs. + bool (*initialize)(struct kru *kru, int capacity_log, kru_price_t max_decay); + + /// Calculate size of the KRU structure. + size_t (*get_size)(int capacity_log); + + /// Determine if a key should get limited (and update the KRU). + /// key needs to be aligned to a multiple of 16 bytes. + bool (*limited)(struct kru *kru, uint32_t time_now, uint8_t key[static const 16], kru_price_t price); + + /// Multiple queries. Returns OR of answers. Updates KRU only if no query is blocked (and possibly on race). + bool (*limited_multi_or)(struct kru *kru, uint32_t time_now, uint8_t **keys, kru_price_t *prices, size_t queries_cnt); + + /// Same as previous but without short-circuit evaluation; for time measurement purposes. + bool (*limited_multi_or_nobreak)(struct kru *kru, uint32_t time_now, uint8_t ** keys, kru_price_t *prices, size_t queries_cnt); + + /// Multiple queries based on different prefixes of a single key. + /// Returns a prefix (value in prefixes) on which the key is blocked, or zero if all queries passed. + /// Updates KRU only if no query is blocked, unless a race condition occurs -- + /// in such a case all longer prefixes might have been updated. + /// The key of i-th query consists of prefixes[i] bits of key, prefixes[i], and namespace. + /// If zero is returned, *max_load_out (unless NULL) is set to + /// the maximum of final values of the involved counters normalized to the limit 2^16. + uint8_t (*limited_multi_prefix_or)(struct kru *kru, uint32_t time_now, + uint8_t namespace, uint8_t key[static 16], uint8_t *prefixes, kru_price_t *prices, size_t queries_cnt, uint16_t *max_load_out); + + /// Multiple queries based on different prefixes of a single key. + /// Returns the maximum of final values of the involved counters normalized to the limit 2^16 + /// and stores the corresponding prefix (value in prefixes) to *prefix_out (unless NULL). + /// Set prices to NULL to skip updating; otherwise, KRU is always updated, using maximal allowed value on overflow. + /// The key of i-th query consists of prefixes[i] bits of key, prefixes[i], and namespace. + uint16_t (*load_multi_prefix_max)(struct kru *kru, uint32_t time_now, + uint8_t namespace, uint8_t key[static 16], uint8_t *prefixes, kru_price_t *prices, size_t queries_cnt, uint8_t *prefix_out); +}; + +// The functions are stored this way to make it easier to switch +// implementation based on detected CPU. +extern struct kru_api KRU; +extern const struct kru_api KRU_GENERIC, KRU_AVX2; diff --git a/src/knot/modules/rrl/kru.inc.c b/src/knot/modules/rrl/kru.inc.c new file mode 100644 index 0000000..49e359e --- /dev/null +++ b/src/knot/modules/rrl/kru.inc.c @@ -0,0 +1,615 @@ +/* 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/>. + */ + +/* +KRU estimates recently pricey inputs + +Authors of the simple agorithm (without aging, multi-choice, etc.): + Metwally, D. Agrawal, and A. E. Abbadi. + Efficient computation of frequent and top-k elements in data streams. + In International Conference on Database Theory, 2005. + +With TABLE_COUNT > 1 we're improving reliability by utilizing the property that +longest buckets (cache-lines) get very much shortened, already by providing two choices: + https://en.wikipedia.org/wiki/2-choice_hashing + +The point is to answer point-queries that estimate if the item has been heavily used recently. +To give more weight to recent usage, we use aging via exponential decay (simple to compute). +That has applications for garbage collection of cache and various limiting scenario +(excessive rate, traffic, CPU, maybe RAM). + +### Choosing parameters + +Size (`loads_bits` = log2 length): + - The KRU takes 64 bytes * length * TABLE_COUNT + some small constants. + As TABLE_COUNT == 2 and loads_bits = capacity_log >> 4, we get capacity * 8 Bytes. + - The length should probably be at least something like the square of the number of utilized CPUs. + But this most likely won't be a limiting factor. +*/ + +#include <stdlib.h> +#include <assert.h> +#include <stdatomic.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <math.h> + +#include "./kru.h" +#include "contrib/macros.h" +#include "libdnssec/error.h" +#include "libdnssec/random.h" +typedef uint64_t hash_t; +#if USE_AES + /// 4-8 rounds should be an OK choice, most likely. + #define AES_ROUNDS 4 +#else + #include "contrib/openbsd/siphash.h" + + /// 1,3 should be OK choice, probably. + enum { + SIPHASH_RC = 1, + SIPHASH_RF = 3, + }; +#endif + +#if USE_AVX2 || USE_SSE41 || USE_AES + #include <immintrin.h> + #include <x86intrin.h> +#endif + +/// Block of loads sharing the same time, so that we're more space-efficient. +/// It's exactly a single cache line. +struct load_cl { + ALIGNED_CPU_CACHE + _Atomic uint32_t time; + #define LOADS_LEN 15 + uint16_t ids[LOADS_LEN]; + uint16_t loads[LOADS_LEN]; +}; +static_assert(64 == sizeof(struct load_cl), "bad size of struct load_cl"); + +/// Parametrization for speed of decay. +struct decay_config { + /// Bit shift per tick, fractional + double shift_bits; + + /// Ticks to get zero loads + uint32_t max_ticks; + + uint32_t mult_cache[32]; +}; + +struct kru { +#if USE_AES + /// Hashing secret. Random but shared by all users of the table. + /// Let's not make it too large, so that header fits into 64 Bytes. + _Alignas(32) char hash_key[48]; +#else + /// Hashing secret. Random but shared by all users of the table. + SIPHASH_KEY hash_key; +#endif + struct decay_config decay; + + /// Length of `loads_cls`, stored as binary logarithm. + uint32_t loads_bits; + + #define TABLE_COUNT 2 + /// These are read-write. Each struct has exactly one cache line. + struct load_cl load_cls[][TABLE_COUNT]; +}; + +inline static uint64_t rand_bits(unsigned int bits) +{ + static _Thread_local uint64_t state = 3723796604792068981ull; + const uint64_t prime1 = 11737314301796036329ull; + const uint64_t prime2 = 3107264277052274849ull; + state = prime1 * state + prime2; + //return state & ((1 << bits) - 1); + return state >> (64 - bits); +} + +static inline void decay_initialize(struct decay_config *decay, kru_price_t max_decay) +{ + decay->shift_bits = log2(KRU_LIMIT - 1) - log2(KRU_LIMIT - 1 - max_decay); + decay->max_ticks = 18 / decay->shift_bits; + + decay->mult_cache[0] = 0; // not used + for (size_t ticks = 1; ticks < sizeof(decay->mult_cache) / sizeof(*decay->mult_cache); ticks++) { + decay->mult_cache[ticks] = exp2(32 - decay->shift_bits * ticks) + 0.5; + } +} + +/// Catch up the time drift with configurably slower decay. +static inline void update_time(struct load_cl *l, const uint32_t time_now, + const struct decay_config *decay) +{ + uint32_t ticks; + uint32_t time_last = atomic_load_explicit(&l->time, memory_order_relaxed); + do { + ticks = time_now - time_last; + if (__builtin_expect(!ticks, true)) // we optimize for time not advancing + return; + // We accept some desynchronization of time_now (e.g. from different threads). + if (ticks > (uint32_t)-1024) + return; + } while (!atomic_compare_exchange_weak_explicit(&l->time, &time_last, time_now, memory_order_relaxed, memory_order_relaxed)); + + // If we passed here, we have acquired a time difference we are responsibe for. + + // Don't bother with complex computations if lots of ticks have passed. (little to no speed-up) + if (ticks > decay->max_ticks) { + memset(l->loads, 0, sizeof(l->loads)); + return; + } + + uint32_t mult; + if (__builtin_expect(ticks < sizeof(decay->mult_cache) / sizeof(*decay->mult_cache), 1)) { + mult = decay->mult_cache[ticks]; + } else { + mult = exp2(32 - decay->shift_bits * ticks) + 0.5; + } + + for (int i = 0; i < LOADS_LEN; ++i) { + // We perform decay for the acquired time difference; decays from different threads are commutative. + _Atomic uint16_t *load_at = (_Atomic uint16_t *)&l->loads[i]; + uint16_t l1, load_orig = atomic_load_explicit(load_at, memory_order_relaxed); + const uint16_t rnd = rand_bits(16); + do { + uint64_t m = (((uint64_t)load_orig << 16)) * mult; + m = (m >> 32) + ((m >> 31) & 1); + l1 = (m >> 16) + (rnd < (uint16_t)m); + } while (!atomic_compare_exchange_weak_explicit(load_at, &load_orig, l1, memory_order_relaxed, memory_order_relaxed)); + } +} + +/// Convert capacity_log to loads_bits +static inline int32_t capacity2loads(int capacity_log) +{ + static_assert(LOADS_LEN == 15 && TABLE_COUNT == 2, ""); + // So, the pair of cache lines hold up to 2*15 elements. + // Let's say that we can reliably store 16 = 1 << (1+3). + // (probably more but certainly not 1 << 5) + const int shift = 1 + 3; + int loads_bits = capacity_log - shift; + // Let's behave reasonably for weird capacity_log values. + return loads_bits > 0 ? loads_bits : 1; +} + +static size_t kru_get_size(int capacity_log) +{ + uint32_t loads_bits = capacity2loads(capacity_log); + if (8 * sizeof(hash_t) < TABLE_COUNT * loads_bits + + 8 * sizeof(((struct kru *)0)->load_cls[0]->ids[0])) { + assert(false); + return 0; + } + + return offsetof(struct kru, load_cls) + + sizeof(struct load_cl) * TABLE_COUNT * (1 << loads_bits); +} + +static bool kru_initialize(struct kru *kru, int capacity_log, kru_price_t max_decay) +{ + if (!kru) { + return false; + } + + uint32_t loads_bits = capacity2loads(capacity_log); + if (8 * sizeof(hash_t) < TABLE_COUNT * loads_bits + + 8 * sizeof(((struct kru *)0)->load_cls[0]->ids[0])) { + assert(false); + return false; + } + + kru->loads_bits = loads_bits; + + if (dnssec_random_buffer((uint8_t *)&kru->hash_key, sizeof(kru->hash_key)) != DNSSEC_EOK) { + return false; + } + + decay_initialize(&kru->decay, max_decay); + + return true; +} + +struct query_ctx { + struct load_cl *l[TABLE_COUNT]; + uint32_t time_now; + kru_price_t price; + uint16_t price16, limit16; + uint16_t id; + uint16_t final_load_value; // set by kru_limited_update if not blocked + uint16_t *load; +}; + +/// Phase 1/3 of a query -- hash, prefetch, ctx init. Based on one 16-byte key. +static inline void kru_limited_prefetch(struct kru *kru, uint32_t time_now, uint8_t key[static 16], kru_price_t price, struct query_ctx *ctx) +{ + // Obtain hash of *buf. + hash_t hash; +#if !USE_AES + hash = SipHash(&kru->hash_key, SIPHASH_RC, SIPHASH_RF, key, 16); +#else + { + __m128i h; /// hashing state + h = _mm_load_si128((__m128i *)key); + // Now do the the hashing itself. + __m128i *aes_key = (void*)kru->hash_key; + for (int i = 0; i < AES_ROUNDS; ++i) { + int key_id = i % (sizeof(kru->hash_key) / sizeof(__m128i)); + h = _mm_aesenc_si128(h, _mm_load_si128(&aes_key[key_id])); + } + memcpy(&hash, &h, sizeof(hash)); + } +#endif + + // Choose the cache-lines to operate on + const uint32_t loads_mask = (1 << kru->loads_bits) - 1; + // Fetch the two cache-lines in parallel before we really touch them. + for (int li = 0; li < TABLE_COUNT; ++li) { + struct load_cl * const l = &kru->load_cls[hash & loads_mask][li]; + __builtin_prefetch(l, 0); // hope for read-only access + hash >>= kru->loads_bits; + ctx->l[li] = l; + } + + ctx->time_now = time_now; + ctx->price = price; + ctx->id = hash; +} + +/// Phase 1/3 of a query -- hash, prefetch, ctx init. Based on a bit prefix of one 16-byte key. +static inline void kru_limited_prefetch_prefix(struct kru *kru, uint32_t time_now, uint8_t namespace, uint8_t key[static 16], uint8_t prefix, kru_price_t price, struct query_ctx *ctx) +{ + // Obtain hash of *buf. + hash_t hash; + +#if !USE_AES + { + const int rc = SIPHASH_RC, rf = SIPHASH_RF; + + // Hash prefix of key, prefix size, and namespace together. + SIPHASH_CTX hctx; + SipHash_Init(&hctx, &kru->hash_key); + SipHash_Update(&hctx, rc, rf, &namespace, sizeof(namespace)); + SipHash_Update(&hctx, rc, rf, &prefix, sizeof(prefix)); + SipHash_Update(&hctx, rc, rf, key, prefix / 8); + if (prefix % 8) { + const uint8_t masked_byte = key[prefix / 8] & (0xFF00 >> (prefix % 8)); + SipHash_Update(&hctx, rc, rf, &masked_byte, 1); + } + hash = SipHash_End(&hctx, rc, rf); + } +#else + { + + __m128i h; /// hashing state + h = _mm_load_si128((__m128i *)key); + + { // Keep only the prefix. + const uint8_t p = prefix; + + // Prefix mask (1...0) -> little endian byte array (0x00 ... 0x00 0xFF ... 0xFF). + __m128i mask = _mm_set_epi64x( + (p < 64 ? (p == 0 ? 0 : (uint64_t)-1 << (64 - p)) : (uint64_t)-1), // higher 64 bits (1...) -> second half of byte array (... 0xFF) + (p <= 64 ? 0 : (uint64_t)-1 << (128 - p))); // lower 64 bits (...0) -> first half of byte array (0x00 ...) + + // Swap mask endianness (0x11 ... 0x11 0x00 ... 0x00). + mask = _mm_shuffle_epi8(mask, + _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); + + // Apply mask. + h = _mm_and_si128(h, mask); + } + + // Now do the the hashing itself. + __m128i *aes_key = (void*)kru->hash_key; + { + // Mix namespace and prefix size into the first aes key. + __m128i aes_key1 = _mm_insert_epi16(_mm_load_si128(aes_key), (namespace << 8) | prefix, 0); + h = _mm_aesenc_si128(h, aes_key1); + } + for (int j = 1; j < AES_ROUNDS; ++j) { + int key_id = j % (sizeof(kru->hash_key) / sizeof(__m128i)); + h = _mm_aesenc_si128(h, _mm_load_si128(&aes_key[key_id])); + } + memcpy(&hash, &h, sizeof(hash)); + } +#endif + + // Choose the cache-lines to operate on + const uint32_t loads_mask = (1 << kru->loads_bits) - 1; + // Fetch the two cache-lines in parallel before we really touch them. + for (int li = 0; li < TABLE_COUNT; ++li) { + struct load_cl * const l = &kru->load_cls[hash & loads_mask][li]; + __builtin_prefetch(l, 0); // hope for read-only access + hash >>= kru->loads_bits; + ctx->l[li] = l; + } + + ctx->time_now = time_now; + ctx->price = price; + ctx->id = hash; +} + +/// Phase 2/3 of a query -- returns answer with no state modification (except update_time). +static inline bool kru_limited_fetch(struct kru *kru, struct query_ctx *ctx) +{ + // Compute 16-bit limit and price. + // For 32-bit prices we assume that a 16-bit load value corresponds + // to the 32-bit value extended by low-significant ones and the limit is 2^32 (without ones). + // The 16-bit price is thus rounded up for the comparison with limit, + // but rounded probabilistically for rising the load. + { + const int fract_bits = 8 * sizeof(ctx->price) - 16; + const kru_price_t price = ctx->price; + const kru_price_t fract = price & ((((kru_price_t)1) << fract_bits) - 1); + + ctx->price16 = price >> fract_bits; + ctx->limit16 = -ctx->price16; + + if ((fract_bits > 0) && (fract > 0)) { + ctx->price16 += (rand_bits(fract_bits) < fract); + ctx->limit16--; + } + } + + for (int li = 0; li < TABLE_COUNT; ++li) { + update_time(ctx->l[li], ctx->time_now, &kru->decay); + } + + const uint16_t id = ctx->id; + + // Find matching element. Matching 16 bits in addition to loads_bits. + ctx->load = NULL; +#if !USE_AVX2 + for (int li = 0; li < TABLE_COUNT; ++li) + for (int i = 0; i < LOADS_LEN; ++i) + if (ctx->l[li]->ids[i] == id) { + ctx->load = &ctx->l[li]->loads[i]; + goto load_found; + } +#else + const __m256i id_v = _mm256_set1_epi16(id); + for (int li = 0; li < TABLE_COUNT; ++li) { + static_assert(LOADS_LEN == 15 && sizeof(ctx->l[li]->ids[0]) == 2, ""); + // unfortunately we can't use aligned load here + __m256i ids_v = _mm256_loadu_si256((__m256i *)((uint16_t *)ctx->l[li]->ids - 1)); + __m256i match_mask = _mm256_cmpeq_epi16(ids_v, id_v); + if (_mm256_testz_si256(match_mask, match_mask)) + continue; // no match of id + int index = _bit_scan_reverse(_mm256_movemask_epi8(match_mask)) / 2 - 1; + // there's a small possibility that we hit equality only on the -1 index + if (index >= 0) { + ctx->load = &ctx->l[li]->loads[index]; + goto load_found; + } + } +#endif + + ctx->final_load_value = 0; + return false; + +load_found:; + ctx->final_load_value = *ctx->load; + return (ctx->final_load_value >= ctx->limit16); +} + +/// Phase 3/3 of a query -- state update, return value overrides previous answer in case of race. +/// Not needed if blocked by fetch phase. If overflow_update is activated, false is always returned. +static inline bool kru_limited_update(struct kru *kru, struct query_ctx *ctx, bool overflow_update) +{ + _Atomic uint16_t *load_at; + if (!ctx->load) { + // No match, so find position of the smallest load. + int min_li = 0; + int min_i = 0; +#if !USE_SSE41 + for (int li = 0; li < TABLE_COUNT; ++li) + for (int i = 0; i < LOADS_LEN; ++i) + if (ctx->l[li]->loads[i] < ctx->l[min_li]->loads[min_i]) { + min_li = li; + min_i = i; + } +#else + int min_val = 0; + for (int li = 0; li < TABLE_COUNT; ++li) { + // BEWARE: we're relying on the exact memory layout of struct load_cl, + // where the .loads array take 15 16-bit values at the very end. + static_assert((offsetof(struct load_cl, loads) - 2) % 16 == 0, + "bad alignment of struct load_cl::loads"); + static_assert(LOADS_LEN == 15 && sizeof(ctx->l[li]->loads[0]) == 2, ""); + __m128i *l_v = (__m128i *)((uint16_t *)ctx->l[li]->loads - 1); + __m128i l0 = _mm_load_si128(l_v); + __m128i l1 = _mm_load_si128(l_v + 1); + // We want to avoid the first item in l0, so we maximize it. + // (but this function takes a signed integer, so -1 is the maximum) + l0 = _mm_insert_epi16(l0, -1, 0); + + // Only one instruction can find minimum and its position, + // and it works on 8x uint16_t. + __m128i mp0 = _mm_minpos_epu16(l0); + __m128i mp1 = _mm_minpos_epu16(l1); + int min0 = _mm_extract_epi16(mp0, 0); + int min1 = _mm_extract_epi16(mp1, 0); + int min01, min_ix; + if (min0 < min1) { + min01 = min0; + min_ix = _mm_extract_epi16(mp0, 1); + } else { + min01 = min1; + min_ix = 8 + _mm_extract_epi16(mp1, 1); + } + + if (li == 0 || min_val > min01) { + min_li = li; + min_i = min_ix; + min_val = min01; + } + } + // now, min_i (and min_ix) is offset by one due to alignment of .loads + if (min_i != 0) // zero is very unlikely + --min_i; +#endif + + ctx->l[min_li]->ids[min_i] = ctx->id; + load_at = (_Atomic uint16_t *)&ctx->l[min_li]->loads[min_i]; + } else { + load_at = (_Atomic uint16_t *)ctx->load; + } + + static_assert(ATOMIC_CHAR16_T_LOCK_FREE == 2, "insufficient atomics"); + const uint16_t price = ctx->price16; + const uint16_t limit = ctx->limit16; + uint16_t load_orig = atomic_load_explicit(load_at, memory_order_relaxed); + uint16_t load_new; + do { + if (load_orig >= limit) { + if (overflow_update) { + load_new = -1; + } else { + return true; + } + } else { + load_new = load_orig + price; + } + } while (!atomic_compare_exchange_weak_explicit(load_at, &load_orig, load_new, memory_order_relaxed, memory_order_relaxed)); + + ctx->final_load_value = load_new; + return false; +} + +static bool kru_limited_multi_or(struct kru *kru, uint32_t time_now, uint8_t **keys, kru_price_t *prices, size_t queries_cnt) +{ + struct query_ctx ctx[queries_cnt]; + + for (size_t i = 0; i < queries_cnt; i++) { + kru_limited_prefetch(kru, time_now, keys[i], prices[i], ctx + i); + } + for (size_t i = 0; i < queries_cnt; i++) { + if (kru_limited_fetch(kru, ctx + i)) + return true; + } + bool ret = false; + + for (size_t i = 0; i < queries_cnt; i++) { + ret |= kru_limited_update(kru, ctx + i, false); + } + + return ret; +} + +static bool kru_limited_multi_or_nobreak(struct kru *kru, uint32_t time_now, uint8_t **keys, kru_price_t *prices, size_t queries_cnt) +{ + struct query_ctx ctx[queries_cnt]; + bool ret = false; + + for (size_t i = 0; i < queries_cnt; i++) { + kru_limited_prefetch(kru, time_now, keys[i], prices[i], ctx + i); + } + for (size_t i = 0; i < queries_cnt; i++) { + if (kru_limited_fetch(kru, ctx + i)) + ret = true; + } + if (ret) return true; + + for (size_t i = 0; i < queries_cnt; i++) { + if (kru_limited_update(kru, ctx + i, false)) + ret = true; + } + + return ret; +} + +static uint8_t kru_limited_multi_prefix_or(struct kru *kru, uint32_t time_now, uint8_t namespace, + uint8_t key[static 16], uint8_t *prefixes, kru_price_t *prices, size_t queries_cnt, uint16_t *max_load_out) +{ + struct query_ctx ctx[queries_cnt]; + + for (size_t i = 0; i < queries_cnt; i++) { + kru_limited_prefetch_prefix(kru, time_now, namespace, key, prefixes[i], prices[i], ctx + i); + } + + for (size_t i = 0; i < queries_cnt; i++) { + if (kru_limited_fetch(kru, ctx + i)) + return prefixes[i]; + } + + for (int i = queries_cnt - 1; i >= 0; i--) { + if (kru_limited_update(kru, ctx + i, false)) + return prefixes[i]; + } + + if (max_load_out) { + *max_load_out = 0; + for (size_t i = 0; i < queries_cnt; i++) { + *max_load_out = MAX(*max_load_out, ctx[i].final_load_value); + } + } + + return 0; +} + +static uint16_t kru_load_multi_prefix_max(struct kru *kru, uint32_t time_now, uint8_t namespace, + uint8_t key[static 16], uint8_t *prefixes, kru_price_t *prices, size_t queries_cnt, uint8_t *prefix_out) +{ + struct query_ctx ctx[queries_cnt]; + + for (size_t i = 0; i < queries_cnt; i++) { + kru_limited_prefetch_prefix(kru, time_now, namespace, key, prefixes[i], (prices ? prices[i] : 0), ctx + i); + } + + for (size_t i = 0; i < queries_cnt; i++) { + kru_limited_fetch(kru, ctx + i); + } + + if (prices) { + for (int i = queries_cnt - 1; i >= 0; i--) { + kru_limited_update(kru, ctx + i, true); + } + } + + uint8_t prefix = 0; + uint16_t max_load = 0; + for (size_t i = 0; i < queries_cnt; i++) { + if (max_load < ctx[i].final_load_value) { + max_load = ctx[i].final_load_value; + prefix = prefixes[i]; + } + } + if (prefix_out) { + *prefix_out = prefix; + } + + return max_load; +} + +/// Update limiting and return true iff it hit the limit instead. +static bool kru_limited(struct kru *kru, uint32_t time_now, uint8_t key[static 16], kru_price_t price) +{ + return kru_limited_multi_or(kru, time_now, &key, &price, 1); +} + +#define KRU_API_INITIALIZER { \ + .get_size = kru_get_size, \ + .initialize = kru_initialize, \ + .limited = kru_limited, \ + .limited_multi_or = kru_limited_multi_or, \ + .limited_multi_or_nobreak = kru_limited_multi_or_nobreak, \ + .limited_multi_prefix_or = kru_limited_multi_prefix_or, \ + .load_multi_prefix_max = kru_load_multi_prefix_max, \ +} diff --git a/src/knot/modules/rrl/rrl.c b/src/knot/modules/rrl/rrl.c index 64f6cbf..d1ec7b5 100644 --- a/src/knot/modules/rrl/rrl.c +++ b/src/knot/modules/rrl/rrl.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 @@ -14,28 +14,47 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#include "contrib/time.h" #include "knot/include/module.h" -#include "knot/nameserver/process_query.h" // Dependency on qdata->extra! #include "knot/modules/rrl/functions.h" +#include "knot/modules/rrl/kru.h" #define MOD_RATE_LIMIT "\x0A""rate-limit" +#define MOD_INST_LIMIT "\x0D""instant-limit" +#define MOD_T_RATE_LIMIT "\x0F""time-rate-limit" +#define MOD_T_INST_LIMIT "\x12""time-instant-limit" #define MOD_SLIP "\x04""slip" #define MOD_TBL_SIZE "\x0A""table-size" #define MOD_WHITELIST "\x09""whitelist" +#define MOD_LOG_PERIOD "\x0A""log-period" +#define MOD_DRY_RUN "\x07""dry-run" const yp_item_t rrl_conf[] = { - { MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 1, INT32_MAX } }, - { MOD_SLIP, YP_TINT, YP_VINT = { 0, 100, 1 } }, - { MOD_TBL_SIZE, YP_TINT, YP_VINT = { 1, INT32_MAX, 393241 } }, - { MOD_WHITELIST, YP_TNET, YP_VNONE, YP_FMULTI }, + { MOD_INST_LIMIT, YP_TINT, YP_VINT = { 1, (1ll << 32) / 768 - 1, 50 } }, + { MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 0, ((1ll << 32) / 768 - 1) * 1000, 20 } }, + { MOD_T_INST_LIMIT, YP_TINT, YP_VINT = { 1, 1000000, 5000 } }, + { MOD_T_RATE_LIMIT, YP_TINT, YP_VINT = { 0, 1000000000, 4000 } }, + { MOD_SLIP, YP_TINT, YP_VINT = { 0, 100, 1 } }, + { MOD_TBL_SIZE, YP_TINT, YP_VINT = { 1, INT32_MAX, 524288 } }, + { MOD_WHITELIST, YP_TNET, YP_VNONE, YP_FMULTI }, + { MOD_LOG_PERIOD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0 } }, + { MOD_DRY_RUN, YP_TBOOL, YP_VNONE }, { NULL } }; int rrl_conf_check(knotd_conf_check_args_t *args) { - knotd_conf_t limit = knotd_conf_check_item(args, MOD_RATE_LIMIT); - if (limit.count == 0) { - args->err_str = "no rate limit specified"; + knotd_conf_t rate_limit = knotd_conf_check_item(args, MOD_RATE_LIMIT); + knotd_conf_t inst_limit = knotd_conf_check_item(args, MOD_INST_LIMIT); + if (rate_limit.single.integer > 1000ll * inst_limit.single.integer) { + args->err_str = "rate limit is higher than 1000 times instant rate limit"; + return KNOT_EINVAL; + } + + knotd_conf_t t_rate_limit = knotd_conf_check_item(args, MOD_T_RATE_LIMIT); + knotd_conf_t t_inst_limit = knotd_conf_check_item(args, MOD_T_INST_LIMIT); + if (t_rate_limit.single.integer > 1000ll * t_inst_limit.single.integer) { + args->err_str = "time rate limit is higher than 1000 times time instant limit"; return KNOT_EINVAL; } @@ -43,35 +62,78 @@ int rrl_conf_check(knotd_conf_check_args_t *args) } typedef struct { - rrl_table_t *rrl; + ALIGNED_CPU_CACHE // Ensures that one thread context occupies one cache line. + struct timespec start_time; // Start time of the measurement. + bool whitelist_checked; // Indication whether whitelist check took place. + bool skip; // Skip the rest of the module callbacks. +} thrd_ctx_t; + +typedef struct { + rrl_table_t *rate_table; + rrl_table_t *time_table; + thrd_ctx_t *thrd_ctx; int slip; + bool dry_run; knotd_conf_t whitelist; } rrl_ctx_t; -static const knot_dname_t *name_from_rrsig(const knot_rrset_t *rr) +static uint32_t time_diff_us(const struct timespec *begin, const struct timespec *end) +{ + struct timespec result = time_diff(begin, end); + return (result.tv_sec * 1000000) + (result.tv_nsec / 1000); +} + +static knotd_proto_state_t protolimit_start(knotd_proto_state_t state, + knotd_qdata_params_t *params, + knotd_mod_t *mod) { - if (rr == NULL) { - return NULL; + rrl_ctx_t *ctx = knotd_mod_ctx(mod); + thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id]; + thrd->skip = false; + + // Check if a whitelisted client. + thrd->whitelist_checked = true; + if (knotd_conf_addr_range_match(&ctx->whitelist, params->remote)) { + thrd->skip = true; + return state; } - if (rr->type != KNOT_RRTYPE_RRSIG) { - return NULL; + + // UDP time limiting not implemented (source address can be forged). + if (params->proto == KNOTD_QUERY_PROTO_UDP) { + return state; } - // This is a signature. - return knot_rrsig_signer_name(rr->rrs.rdata); + // Check if the packet is limited. + if (rrl_query(ctx->time_table, params->remote, mod) != KNOT_EOK) { + thrd->skip = true; + knotd_mod_stats_incr(mod, params->thread_id, 2, 0, 1); + return ctx->dry_run ? state : KNOTD_PROTO_STATE_BLOCK; + } else { + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thrd->start_time); + return state; // Not limited. + } } -static const knot_dname_t *name_from_authrr(const knot_rrset_t *rr) +static knotd_proto_state_t protolimit_end(knotd_proto_state_t state, + knotd_qdata_params_t *params, + knotd_mod_t *mod) { - if (rr == NULL) { - return NULL; + rrl_ctx_t *ctx = knotd_mod_ctx(mod); + thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id]; + + if (thrd->skip || params->proto == KNOTD_QUERY_PROTO_UDP) { + return state; } - if (rr->type != KNOT_RRTYPE_NS && rr->type != KNOT_RRTYPE_SOA) { - return NULL; + + // Update the time table. + struct timespec end_time; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time); + uint64_t diff = time_diff_us(&thrd->start_time, &end_time); + if (diff > 0) { // Zero KRU update is NOOP. + rrl_update(ctx->time_table, params->remote, diff); } - // This is a valid authority RR. - return rr->owner; + return state; } static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt, @@ -80,57 +142,36 @@ static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt, assert(pkt && qdata && mod); rrl_ctx_t *ctx = knotd_mod_ctx(mod); + thrd_ctx_t *thrd = &ctx->thrd_ctx[qdata->params->thread_id]; - // Rate limit is applied to pure UDP only. - if (qdata->params->proto != KNOTD_QUERY_PROTO_UDP) { + if (thrd->skip) { return state; } - // Rate limit is not applied to responses with a valid cookie. - if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) { + // Don't limit authorized operations. + if (qdata->params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) { + thrd->skip = true; return state; } - // Exempt clients. - if (knotd_conf_addr_range_match(&ctx->whitelist, knotd_qdata_remote_addr(qdata))) { + // Rate limit is applied to UDP only. + if (qdata->params->proto != KNOTD_QUERY_PROTO_UDP) { return state; } - rrl_req_t req = { - .wire = pkt->wire, - .query = qdata->query - }; - - if (!EMPTY_LIST(qdata->extra->wildcards)) { - req.flags = RRL_REQ_WILDCARD; - } - - // Take the zone name if known. - const knot_dname_t *zone_name = knotd_qdata_zone_name(qdata); - - // Take the signer name as zone name if there is an RRSIG. - if (zone_name == NULL) { - const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER); - for (int i = 0; i < ans->count; i++) { - zone_name = name_from_rrsig(knot_pkt_rr(ans, i)); - if (zone_name != NULL) { - break; - } - } + // Check for whitelisted client IF PER-ZONE module (no proto callbacks). + if (!thrd->whitelist_checked && + knotd_conf_addr_range_match(&ctx->whitelist, qdata->params->remote)) { + thrd->skip = true; + return state; } - // Take the NS or SOA owner name if there is no RRSIG. - if (zone_name == NULL) { - const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY); - for (int i = 0; i < auth->count; i++) { - zone_name = name_from_authrr(knot_pkt_rr(auth, i)); - if (zone_name != NULL) { - break; - } - } + // Rate limit is not applied to responses with a valid cookie. + if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) { + return state; } - if (rrl_query(ctx->rrl, knotd_qdata_remote_addr(qdata), &req, zone_name, mod) == KNOT_EOK) { + if (rrl_query(ctx->rate_table, knotd_qdata_remote_addr(qdata), mod) == KNOT_EOK) { // Rate limiting not applied. return state; } @@ -139,11 +180,11 @@ static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt, // Slip the answer. knotd_mod_stats_incr(mod, qdata->params->thread_id, 0, 0, 1); qdata->err_truncated = true; - return KNOTD_STATE_FAIL; + return ctx->dry_run ? state : KNOTD_STATE_FAIL; } else { // Drop the answer. knotd_mod_stats_incr(mod, qdata->params->thread_id, 1, 0, 1); - return KNOTD_STATE_NOOP; + return ctx->dry_run ? state : KNOTD_STATE_NOOP; } } @@ -151,58 +192,96 @@ static void ctx_free(rrl_ctx_t *ctx) { assert(ctx); - rrl_destroy(ctx->rrl); + free(ctx->thrd_ctx); + rrl_destroy(ctx->rate_table); + rrl_destroy(ctx->time_table); + knotd_conf_free(&ctx->whitelist); free(ctx); } int rrl_load(knotd_mod_t *mod) { - // Create RRL context. rrl_ctx_t *ctx = calloc(1, sizeof(rrl_ctx_t)); if (ctx == NULL) { return KNOT_ENOMEM; } - // Create table. - uint32_t rate = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer; - size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer; - ctx->rrl = rrl_create(size, rate); - if (ctx->rrl == NULL) { + ctx->dry_run = knotd_conf_mod(mod, MOD_DRY_RUN).single.boolean; + ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST); + + ctx->thrd_ctx = calloc(knotd_mod_threads(mod), sizeof(*ctx->thrd_ctx)); + if (ctx->thrd_ctx == NULL) { ctx_free(ctx); return KNOT_ENOMEM; } - // Get slip. - ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer; + size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer; + uint32_t log_period = knotd_conf_mod(mod, MOD_LOG_PERIOD).single.integer; + + uint32_t rate_limit = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer; + if (rate_limit > 0) { + uint32_t inst_limit = knotd_conf_mod(mod, MOD_INST_LIMIT).single.integer; + ctx->rate_table = rrl_create(size, inst_limit, rate_limit, true, log_period); + if (ctx->rate_table == NULL) { + ctx_free(ctx); + return KNOT_ENOMEM; + } + ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer; + } - // Get whitelist. - ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST); + uint32_t time_limit = knotd_conf_mod(mod, MOD_T_RATE_LIMIT).single.integer; + if (time_limit > 0) { + uint32_t inst_limit = knotd_conf_mod(mod, MOD_T_INST_LIMIT).single.integer; + ctx->time_table = rrl_create(size, inst_limit, time_limit, false, log_period); + if (ctx->time_table == NULL) { + ctx_free(ctx); + return KNOT_ENOMEM; + } + } - // Set up statistics counters. int ret = knotd_mod_stats_add(mod, "slipped", 1, NULL); if (ret != KNOT_EOK) { ctx_free(ctx); return ret; } - ret = knotd_mod_stats_add(mod, "dropped", 1, NULL); if (ret != KNOT_EOK) { ctx_free(ctx); return ret; } + ret = knotd_mod_stats_add(mod, "dropped-time", 1, NULL); + if (ret != KNOT_EOK) { + ctx_free(ctx); + return ret; + } + + /* The explicit reference of the AVX2 variant ensures the optimized + * code isn't removed by linker if linking statically. + * Check: nm ./src/.libs/knotd | grep KRU_ + * https://stackoverflow.com/a/28663156/587396 + */ + knotd_mod_log(mod, LOG_DEBUG, "using %s implementation", + KRU.limited == KRU_AVX2.limited ? "optimized" : "generic"); knotd_mod_ctx_set(mod, ctx); - return knotd_mod_hook(mod, KNOTD_STAGE_END, ratelimit_apply); + if (rate_limit > 0) { + knotd_mod_hook(mod, KNOTD_STAGE_BEGIN, ratelimit_apply); + } + + if (time_limit > 0) { + // Note that these two callbacks aren't executed IF PER-ZONE module! + knotd_mod_proto_hook(mod, KNOTD_STAGE_PROTO_BEGIN, protolimit_start); + knotd_mod_proto_hook(mod, KNOTD_STAGE_PROTO_END, protolimit_end); + } + + return KNOT_EOK; } void rrl_unload(knotd_mod_t *mod) { - rrl_ctx_t *ctx = knotd_mod_ctx(mod); - - knotd_conf_free(&ctx->whitelist); - ctx_free(ctx); + ctx_free(knotd_mod_ctx(mod)); } -KNOTD_MOD_API(rrl, KNOTD_MOD_FLAG_SCOPE_ANY, +KNOTD_MOD_API(rrl, KNOTD_MOD_FLAG_SCOPE_ANY | KNOTD_MOD_FLAG_OPT_CONF, rrl_load, rrl_unload, rrl_conf, rrl_conf_check); diff --git a/src/knot/modules/rrl/rrl.rst b/src/knot/modules/rrl/rrl.rst index 0daae16..85c8d35 100644 --- a/src/knot/modules/rrl/rrl.rst +++ b/src/knot/modules/rrl/rrl.rst @@ -4,37 +4,52 @@ ================================ Response rate limiting (RRL) is a method to combat DNS reflection amplification -attacks. These attacks rely on the fact that source address of a UDP query +attacks. These attacks rely on the fact that the source address of a UDP query can be forged, and without a worldwide deployment of `BCP38 <https://tools.ietf.org/html/bcp38>`_, such a forgery cannot be prevented. An attacker can use a DNS server (or multiple servers) as an amplification -source and can flood a victim with a large number of unsolicited DNS responses. -The RRL lowers the amplification factor of these attacks by sending some of -the responses as truncated or by dropping them altogether. +source to flood a victim with a large number of unsolicited DNS responses. +RRL lowers the amplification factor of these attacks by sending some +responses as truncated or by dropping them altogether. + +This module can also help protect the server from excessive utilization by +limiting incoming packets (including handshakes) based on consumed time. +If a packet is time rate limited, it's dropped. This function works with +all supported non-UDP transport protocols and cannot be configured per zone. .. NOTE:: - The module introduces two statistics counters. The number of slipped and - dropped responses. + This module introduces three statistics counters: + + - ``slipped`` – The number of slipped UDP responses. + - ``dropped`` – The number of dropped UDP responses due to the rate limit. + - ``dropped-time`` – The number of dropped non-UDP packets due to the time rate limit. .. NOTE:: If the :ref:`Cookies<mod-cookies>` module is active, RRL is not applied - for responses with a valid DNS cookie. + to UDP responses with a valid DNS cookie. Example ------- -You can enable RRL by setting the module globally or per zone. +You can enable RRL by setting the module globally :: - mod-rrl: - - id: default - rate-limit: 200 # Allow 200 resp/s for each flow - slip: 2 # Approximately every other response slips - template: - id: default - global-module: mod-rrl/default # Enable RRL globally + global-module: mod-rrl # Default module configuration + +or per zone + +:: + + mod-rrl: + - id: custom + rate-limit: 200 + + zone: + - domain: example.com + module: mod-rrl/custom # Custom module configuration Module reference ---------------- @@ -44,9 +59,14 @@ Module reference mod-rrl: - id: STR rate-limit: INT + instant-limit: INT slip: INT + time-rate-limit: INT + time-instant-limit: INT table-size: INT whitelist: ADDR[/INT] | ADDR-ADDR | STR ... + log-period: INT + dry-run: BOOL .. _mod-rrl_id: @@ -60,30 +80,42 @@ A module identifier. rate-limit .......... -Rate limiting is based on the token bucket scheme. A rate basically -represents a number of tokens available each second. Each response is -processed and classified (based on several discriminators, e.g. -source netblock, query type, zone name, rcode, etc.). Classified responses are -then hashed and assigned to a bucket containing number of available -tokens, timestamp and metadata. When available tokens are exhausted, -response is dropped or sent as truncated (see :ref:`mod-rrl_slip`). -Number of available tokens is recalculated each second. +Maximal allowed number of UDP queries per second from a single IPv6 or IPv4 address. -*Required* +Rate limiting is performed for the whole address and several chosen prefixes. +The limits of prefixes are constant multiples of :ref:`mod-rrl_rate-limit`. -.. _mod-rrl_table-size: +The specific prefixes and multipliers, which might be adjusted in the future, are +for IPv6 /128: 1, /64: 2, /56: 3, /48: 4, /32: 64; +for IPv4 /32: 1, /24: 32, /20: 256, /18: 768. -table-size -.......... +With each host/network, a counter of unrestricted responses is associated; +if the counter would exceed its capacity, it is not incremented and the response is restricted. +Counters use exponential decay for lowering their values, +i.e. they are lowered by a constant fraction of their value each millisecond. +The specified rate limit is reached, when the number of queries is the same every millisecond; +sending many queries once a second or even a larger timespan leads to a more strict limiting. + +*Default:* ``20`` + +.. _mod-rrl_instant-limit: + +instant-limit +............. + +Maximal allowed number of queries at a single point in time from a single IPv6 or IPv4 address. +The limits for prefixes use the same multipliers as for :ref:`mod-rrl_rate-limit`. -Size of the hash table in a number of buckets. The larger the hash table, the lesser -the probability of a hash collision, but at the expense of additional memory costs. -Each bucket is estimated roughly to 32 bytes. The size should be selected as -a reasonably large prime due to better hash function distribution properties. -Hash table is internally chained and works well up to a fill rate of 90 %, general -rule of thumb is to select a prime near 1.2 * maximum_qps. +This limit is reached when many queries come from a new host/network, +or after a longer time of inactivity. -*Default:* ``393241`` +The :ref:`mod-rrl_instant-limit` sets the actual capacity of each counter of responses, +and together with the :ref:`mod-rrl_rate-limit` they set the fraction by which the counter +is periodically lowered. +The :ref:`mod-rrl_instant-limit` may be at least :ref:`mod-rrl_rate-limit` **/ 1000**, at which point the +counters are zeroed each millisecond. + +*Default:* ``50`` .. _mod-rrl_slip: @@ -121,6 +153,49 @@ noting, that some responses can't be truncated (e.g. SERVFAIL). *Default:* ``1`` +.. _mod-rrl_time-rate-limit: + +time-rate-limit +............... + +This limit works similarly to :ref:`mod-rrl_rate-limit` but considers the time +consumed (in microseconds) by the remote over non-UDP transport protocols. + +*Default:* ``4000`` (microseconds) + +.. _mod-rrl_time-instant-limit: + +time-instant-limit +.................. + +This limit works similarly to :ref:`mod-rrl_instant-limit` but considers the time +consumed (in microseconds) by the remote over non-UDP transport protocols. + +*Default:* ``5000`` (microseconds) + +.. _mod-rrl_table-size: + +table-size +.......... + +Maximal number of stored hosts/networks with their counters. +The data structure tries to store only the most frequent sources, +so it is safe to set it according to the expected maximal number of limited ones. + +Use `1.4 * maximum_qps / rate-limit`, +where `maximum_qps` is the number of queries which can be handled by the server per second. +There is at most `maximum_qps / rate-limit` limited hosts; +larger networks have higher limits and so require only a fraction of the value (handled by the `1.4` multiplier). +The value will be rounded up to the nearest power of two. + +The same table size is used for both counting-based and time-based limiting; +the maximum number of time-limited hosts is expected to be lower, so it's not typically needed to be considered. +There is at most `1 000 000 * #cpus / time-rate-limit` of them. + +The memory occupied by one table structure is `8 * table-size B`. + +*Default:* ``524288`` + .. _mod-rrl_whitelist: whitelist @@ -131,3 +206,31 @@ or network ranges to exempt from rate limiting. Empty list means that no incoming connection will be white-listed. *Default:* not set + +.. _mod-rrl_log-period: + +log-period +.......... + +Minimal time in milliseconds between two log messages, +or zero to disable logging. + +If a response is limited, the address and the prefix on which it was blocked is logged +and logging is disabled for the `log-period` milliseconds. +As long as limiting is needed, one source is logged each period +and sources with more blocked queries have greater probability to be chosen. + +The approach is used by counting-based and time-based limiting separately, +so you can expect one message per `log-period` from each of them. + +*Default:* ``0`` (disabled) + +.. _mod-rrl_dry-run: + +dry-run +....... + +If enabled, the module doesn't alter any response. Only query classification +is performed with possible statistics counter incrementation. + +*Default:* ``off`` diff --git a/src/knot/modules/stats/stats.c b/src/knot/modules/stats/stats.c index 26262ac..c5b797b 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 8acf1aa..71cf87a 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/axfr.c b/src/knot/nameserver/axfr.c index dcd62e9..cd9ee32 100644 --- a/src/knot/nameserver/axfr.c +++ b/src/knot/nameserver/axfr.c @@ -121,14 +121,14 @@ static void axfr_answer_finished(knotd_qdata_t *qdata, knot_pkt_t *pkt, int stat xfr_stats_add(&xfr->stats, pkt->size); xfr_stats_end(&xfr->stats); xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_AXFR, LOG_DIRECTION_OUT, - REMOTE(qdata), PROTO(qdata), KEY(qdata), &xfr->stats); + REMOTE(qdata), PROTO(qdata), KEY(qdata), "", &xfr->stats); break; default: break; } } -static int axfr_query_check(knotd_qdata_t *qdata) +static knot_layer_state_t axfr_query_check(knotd_qdata_t *qdata) { NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH); NS_NEED_AUTH(qdata, ACL_ACTION_TRANSFER); @@ -187,7 +187,7 @@ static int axfr_query_init(knotd_qdata_t *qdata) return KNOT_EOK; } -int axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +knot_layer_state_t axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { if (pkt == NULL || qdata == NULL) { return KNOT_STATE_FAIL; diff --git a/src/knot/nameserver/axfr.h b/src/knot/nameserver/axfr.h index 81fcad8..25c5f1c 100644 --- a/src/knot/nameserver/axfr.h +++ b/src/knot/nameserver/axfr.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 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 @@ -21,7 +21,5 @@ /*! * \brief Process an AXFR query message. - * - * \return KNOT_STATE_* processing states */ -int axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); +knot_layer_state_t axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c index 51bde97..034fd26 100644 --- a/src/knot/nameserver/internet.c +++ b/src/knot/nameserver/internet.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 @@ -286,7 +286,7 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr, return ret; } -static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, knotd_qdata_t *qdata) +static knotd_in_state_t follow_cname(knot_pkt_t *pkt, uint16_t rrtype, knotd_qdata_t *qdata) { /* CNAME chain processing limit. */ if (++qdata->extra->cname_chain > CNAME_CHAIN_MAX) { @@ -369,7 +369,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, knotd_qdata_t *qdata) return KNOTD_IN_STATE_FOLLOW; } -static int name_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) +static knotd_in_state_t name_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) { uint16_t qtype = knot_pkt_qtype(pkt); @@ -406,7 +406,7 @@ static int name_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) } } -static int name_not_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) +static knotd_in_state_t name_not_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) { /* Name is covered by wildcard. */ if (qdata->extra->encloser->flags & NODE_FLAGS_WILDCARD_CHILD) { @@ -419,7 +419,7 @@ static int name_not_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) assert(qdata->extra->node != NULL); /* Follow expanded wildcard. */ - int next_state = name_found(pkt, qdata); + knotd_in_state_t next_state = name_found(pkt, qdata); /* Put to wildcard node list. */ if (wildcard_has_visited(qdata, wildcard_node)) { @@ -456,7 +456,8 @@ static int name_not_found(knot_pkt_t *pkt, knotd_qdata_t *qdata) return KNOTD_IN_STATE_MISS; } -static int solve_name(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata) +static knotd_in_state_t solve_name(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata) { int ret = zone_contents_find_dname(qdata->extra->contents, qdata->name, &qdata->extra->node, &qdata->extra->encloser, @@ -475,7 +476,8 @@ static int solve_name(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata) } } -static int solve_answer(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void *ctx) +static knotd_in_state_t solve_answer(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { int old_state = state; @@ -506,7 +508,8 @@ static int solve_answer(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void * return state; } -static int solve_answer_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void *ctx) +static knotd_in_state_t solve_answer_dnssec(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { /* RFC4035, section 3.1 RRSIGs for RRs in ANSWER are mandatory. */ int ret = nsec_append_rrsigs(pkt, qdata, false); @@ -517,7 +520,8 @@ static int solve_answer_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, } } -static int solve_authority(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void *ctx) +static knotd_in_state_t solve_authority(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { int ret = KNOT_ERROR; const zone_contents_t *zone_contents = qdata->extra->contents; @@ -554,7 +558,8 @@ static int solve_authority(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, voi } } -static int solve_authority_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void *ctx) +static knotd_in_state_t solve_authority_dnssec(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { int ret = KNOT_ERROR; @@ -591,8 +596,8 @@ static int solve_authority_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qda } } -static int solve_additional(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, - void *ctx) +static knotd_in_state_t solve_additional(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { int ret = KNOT_EOK, rrset_count = pkt->rrset_count; @@ -621,7 +626,8 @@ static int solve_additional(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, } } -static int solve_additional_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qdata, void *ctx) +static knotd_in_state_t solve_additional_dnssec(knotd_in_state_t state, knot_pkt_t *pkt, + knotd_qdata_t *qdata, void *ctx) { /* RFC4035, section 3.1 RRSIGs for RRs in ADDITIONAL are optional. */ int ret = nsec_append_rrsigs(pkt, qdata, true); @@ -641,9 +647,9 @@ static int solve_additional_dnssec(int state, knot_pkt_t *pkt, knotd_qdata_t *qd return KNOT_STATE_FAIL; \ } -static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +static knot_layer_state_t answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { - int state = KNOTD_IN_STATE_BEGIN; + knotd_in_state_t state = KNOTD_IN_STATE_BEGIN; struct query_plan *plan = qdata->extra->zone->query_plan; struct query_step *step; @@ -652,7 +658,8 @@ static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) /* Resolve PREANSWER. */ if (plan != NULL) { WALK_LIST(step, plan->stage[KNOTD_STAGE_PREANSWER]) { - SOLVE_STEP(step->process, state, step->ctx); + assert(step->type == QUERY_HOOK_TYPE_IN); + SOLVE_STEP(step->in_hook, state, step->ctx); } } @@ -664,7 +671,8 @@ static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) } if (plan != NULL) { WALK_LIST(step, plan->stage[KNOTD_STAGE_ANSWER]) { - SOLVE_STEP(step->process, state, step->ctx); + assert(step->type == QUERY_HOOK_TYPE_IN); + SOLVE_STEP(step->in_hook, state, step->ctx); } } @@ -676,7 +684,8 @@ static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) } if (plan != NULL) { WALK_LIST(step, plan->stage[KNOTD_STAGE_AUTHORITY]) { - SOLVE_STEP(step->process, state, step->ctx); + assert(step->type == QUERY_HOOK_TYPE_IN); + SOLVE_STEP(step->in_hook, state, step->ctx); } } @@ -688,7 +697,8 @@ static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) } if (plan != NULL) { WALK_LIST(step, plan->stage[KNOTD_STAGE_ADDITIONAL]) { - SOLVE_STEP(step->process, state, step->ctx); + assert(step->type == QUERY_HOOK_TYPE_IN); + SOLVE_STEP(step->in_hook, state, step->ctx); } } @@ -698,7 +708,7 @@ static int answer_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) return KNOT_STATE_DONE; } -int internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +knot_layer_state_t internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { if (pkt == NULL || qdata == NULL) { return KNOT_STATE_FAIL; diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h index 52afe62..bb6927d 100644 --- a/src/knot/nameserver/internet.h +++ b/src/knot/nameserver/internet.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -25,11 +25,8 @@ /*! * \brief Answer query from an IN class zone. - * - * \retval KNOT_STATE_FAIL if it encountered an error. - * \retval KNOT_STATE_DONE if finished. */ -int internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); +knot_layer_state_t internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); /*! \brief Require given QUERY TYPE or return error code. */ #define NS_NEED_QTYPE(qdata, qtype_want, error_rcode) \ @@ -68,12 +65,6 @@ int internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); if (!process_query_acl_check(conf(), (action), (qdata)) || \ process_query_verify(qdata) != KNOT_EOK) { \ return KNOT_STATE_FAIL; \ - } - -/*! \brief Require the zone not to be frozen. */ -#define NS_NEED_NOT_FROZEN(qdata) \ - if ((qdata)->extra->zone->events.ufrozen) { \ - (qdata)->rcode = KNOT_RCODE_REFUSED; \ - (qdata)->rcode_ede = KNOT_EDNS_EDE_NOT_READY; \ - return KNOT_STATE_FAIL; \ + } else { \ + qdata->params->flags |= KNOTD_QUERY_FLAG_AUTHORIZED; \ } diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c index b57759c..c0e17ea 100644 --- a/src/knot/nameserver/ixfr.c +++ b/src/knot/nameserver/ixfr.c @@ -130,7 +130,7 @@ static int ixfr_load_chsets(journal_read_t **journal_read, zone_t *zone, return journal_read_begin(zone_journal(zone), false, serial_from, journal_read); } -static int ixfr_query_check(knotd_qdata_t *qdata) +static knot_layer_state_t ixfr_query_check(knotd_qdata_t *qdata) { NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH); NS_NEED_AUTH(qdata, ACL_ACTION_TRANSFER); @@ -174,7 +174,7 @@ static void ixfr_answer_finished(knotd_qdata_t *qdata, knot_pkt_t *pkt, int stat xfr_stats_add(&xfr->stats, pkt->size); xfr_stats_end(&xfr->stats); xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_IXFR, LOG_DIRECTION_OUT, - REMOTE(qdata), PROTO(qdata), KEY(qdata), &xfr->stats); + REMOTE(qdata), PROTO(qdata), KEY(qdata), "", &xfr->stats); break; default: break; @@ -245,13 +245,13 @@ static int ixfr_answer_init(knotd_qdata_t *qdata, uint32_t *serial_from) return KNOT_EOK; } -static int ixfr_answer_soa(knot_pkt_t *pkt, knotd_qdata_t *qdata) +static knot_layer_state_t ixfr_answer_soa(knot_pkt_t *pkt, knotd_qdata_t *qdata) { assert(pkt); assert(qdata); /* Check query. */ - int state = ixfr_query_check(qdata); + knot_layer_state_t state = ixfr_query_check(qdata); if (state == KNOT_STATE_FAIL) { return state; /* Malformed query. */ } @@ -277,7 +277,7 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, knotd_qdata_t *qdata) return KNOT_STATE_DONE; } -int ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +knot_layer_state_t ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { if (pkt == NULL || qdata == NULL) { return KNOT_STATE_FAIL; diff --git a/src/knot/nameserver/ixfr.h b/src/knot/nameserver/ixfr.h index 3012be1..91ce750 100644 --- a/src/knot/nameserver/ixfr.h +++ b/src/knot/nameserver/ixfr.h @@ -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 @@ -55,9 +55,5 @@ struct ixfr_proc { /*! * \brief IXFR query processing module. - * - * \retval PRODUCE if it has an answer, but not yet finished. - * \retval FAIL if it encountered an error. - * \retval DONE if finished. */ -int ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); +knot_layer_state_t ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); diff --git a/src/knot/nameserver/log.h b/src/knot/nameserver/log.h index 5a5d5f0..373d65a 100644 --- a/src/knot/nameserver/log.h +++ b/src/knot/nameserver/log.h @@ -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/notify.c b/src/knot/nameserver/notify.c index eaa305a..5b1593f 100644 --- a/src/knot/nameserver/notify.c +++ b/src/knot/nameserver/notify.c @@ -29,7 +29,7 @@ LOG_DIRECTION_IN, (qdata)->params->remote, (qdata)->params->proto, \ false, (qdata)->sign.tsig_key.name, fmt) -static int notify_check_query(knotd_qdata_t *qdata) +static knot_layer_state_t notify_check_query(knotd_qdata_t *qdata) { NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH); NS_NEED_AUTH(qdata, ACL_ACTION_NOTIFY); @@ -39,14 +39,14 @@ static int notify_check_query(knotd_qdata_t *qdata) return KNOT_STATE_DONE; } -int notify_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +knot_layer_state_t notify_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { if (pkt == NULL || qdata == NULL) { return KNOT_STATE_FAIL; } /* Validate notification query. */ - int state = notify_check_query(qdata); + knot_layer_state_t state = notify_check_query(qdata); if (state == KNOT_STATE_FAIL) { switch (qdata->rcode) { case KNOT_RCODE_NOTAUTH: /* Not authorized, already logged. */ diff --git a/src/knot/nameserver/notify.h b/src/knot/nameserver/notify.h index d0bff14..0e60b58 100644 --- a/src/knot/nameserver/notify.h +++ b/src/knot/nameserver/notify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -21,8 +21,5 @@ /*! * \brief Answer IN class zone NOTIFY message (RFC1996). - * - * \retval FAIL if it encountered an error. - * \retval DONE if finished. */ -int notify_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); +knot_layer_state_t notify_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c index 71944b1..b67566c 100644 --- a/src/knot/nameserver/nsec_proofs.c +++ b/src/knot/nameserver/nsec_proofs.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -121,11 +121,11 @@ static const knot_dname_t *get_next_closer(const knot_dname_t *closest_encloser, size_t ce_labels = knot_dname_labels(closest_encloser, NULL); size_t qname_labels = knot_dname_labels(name, NULL); for (int i = 0; i < (qname_labels - ce_labels - 1); ++i) { - name = knot_wire_next_label(name, NULL); + name = knot_dname_next_label(name); } // the common labels should match - assert(knot_dname_is_equal(knot_wire_next_label(name, NULL), closest_encloser)); + assert(knot_dname_is_equal(knot_dname_next_label(name), closest_encloser)); return name; } diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c index 00ec001..beced84 100644 --- a/src/knot/nameserver/process_query.c +++ b/src/knot/nameserver/process_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 @@ -30,9 +30,9 @@ #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/quic_conn.h" +#include "libknot/quic/tls_common.h" +#include "libknot/quic/tls.h" #include "contrib/base64.h" #include "contrib/macros.h" #include "contrib/mempattern.h" @@ -147,7 +147,7 @@ static int process_query_in(knot_layer_t *ctx, knot_pkt_t *pkt) /*! * \brief Create a response for a given query in the INTERNET class. */ -static int query_internet(knot_pkt_t *pkt, knot_layer_t *ctx) +static knot_layer_state_t query_internet(knot_pkt_t *pkt, knot_layer_t *ctx) { knotd_qdata_t *data = QUERY_DATA(ctx); @@ -167,7 +167,7 @@ static int query_internet(knot_pkt_t *pkt, knot_layer_t *ctx) /*! * \brief Create a response for a given query in the CHAOS class. */ -static int query_chaos(knot_pkt_t *pkt, knot_layer_t *ctx) +static knot_layer_state_t query_chaos(knot_pkt_t *pkt, knot_layer_t *ctx) { knotd_qdata_t *data = QUERY_DATA(ctx); @@ -203,7 +203,7 @@ static zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb) * records are only present in a parent zone. */ if (qtype == KNOT_RRTYPE_DS) { - const knot_dname_t *parent = knot_wire_next_label(qname, NULL); + const knot_dname_t *parent = knot_dname_next_label(qname); zone = knot_zonedb_find_suffix(zonedb, parent); /* If zone does not exist, search for its parent zone, this will later result to NODATA answer. */ @@ -364,6 +364,7 @@ static int answer_edns_put(knot_pkt_t *resp, knotd_qdata_t *qdata) if (knot_pkt_edns_option(qdata->query, KNOT_EDNS_OPTION_EXPIRE) != NULL && qdata->extra->contents != NULL && !qdata->extra->zone->is_catalog_flag) { int64_t timer = qdata->extra->zone->timers.next_expire; + timer = knot_time_min(timer, qdata->extra->contents->dnssec_expire); // NOOP if zero timer = (timer == 0 ? zone_soa_expire(qdata->extra->zone) : timer - time(NULL)); timer = MAX(timer, 0); uint32_t timer_be; @@ -389,8 +390,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, @@ -506,7 +507,7 @@ static void set_rcode_to_packet(knot_pkt_t *pkt, knotd_qdata_t *qdata) knot_wire_set_rcode(pkt->wire, KNOT_EDNS_RCODE_LO(qdata->rcode)); } -static int process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) +static knot_layer_state_t process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) { assert(ctx && pkt); @@ -552,7 +553,8 @@ static int process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) #define PROCESS_BEGIN(plan, step, next_state, qdata) \ if (plan != NULL) { \ WALK_LIST(step, plan->stage[KNOTD_STAGE_BEGIN]) { \ - next_state = step->process(next_state, pkt, qdata, step->ctx); \ + assert(step->type == QUERY_HOOK_TYPE_GENERAL); \ + next_state = step->general_hook(next_state, pkt, qdata, step->ctx); \ if (next_state == KNOT_STATE_FAIL) { \ goto finish; \ } \ @@ -562,7 +564,8 @@ static int process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) #define PROCESS_END(plan, step, next_state, qdata) \ if (plan != NULL) { \ WALK_LIST(step, plan->stage[KNOTD_STAGE_END]) { \ - next_state = step->process(next_state, pkt, qdata, step->ctx); \ + assert(step->type == QUERY_HOOK_TYPE_GENERAL); \ + next_state = step->general_hook(next_state, pkt, qdata, step->ctx); \ if (next_state == KNOT_STATE_FAIL) { \ next_state = process_query_err(ctx, pkt); \ } \ @@ -705,43 +708,53 @@ bool process_query_acl_check(conf_t *conf, acl_action_t action, bool automatic = false; bool allowed = false; + struct gnutls_session_int *tls_session; + switch (qdata->params->proto) { + case KNOTD_QUERY_PROTO_QUIC: tls_session = qdata->params->quic_conn->tls_session; break; + case KNOTD_QUERY_PROTO_TLS: tls_session = qdata->params->tls_conn->session; break; + default: tls_session = NULL; + } + if (action != ACL_ACTION_UPDATE) { // ACL_ACTION_QUERY is used for SOA/refresh query. assert(action == ACL_ACTION_QUERY || action == ACL_ACTION_NOTIFY || action == ACL_ACTION_TRANSFER); 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); + allowed = rmt_allowed(conf, &rmts, query_source, &tsig, 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, 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_tls_pin(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", allowed ? "allowed" : "denied", (act != NULL) ? act->name : "query", - addr_str, + addr_str, proto_str, (key_name[0] != '\0') ? ", key " : "", (key_name[0] != '\0') ? key_name : "", - (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) ? ", QUIC" : "", (pin_size > 0) ? " cert-key " : "", (pin_size > 0) ? pin_size : 0, (pin_size > 0) ? (const char *)pin : "", @@ -994,6 +1007,30 @@ int process_query_put_rr(knot_pkt_t *pkt, knotd_qdata_t *qdata, return ret; } +knotd_proto_state_t process_query_proto(knotd_qdata_params_t *params, + const knotd_stage_t stage) +{ + assert(params); + assert(stage == KNOTD_STAGE_PROTO_BEGIN || stage == KNOTD_STAGE_PROTO_END); + + knotd_proto_state_t state = KNOTD_PROTO_STATE_PASS; + + rcu_read_lock(); + + struct query_plan *plan = conf()->query_plan; + if (plan != NULL) { + struct query_step *step; + WALK_LIST(step, plan->stage[stage]) { + assert(step->type == QUERY_HOOK_TYPE_PROTO); + state = step->proto_hook(state, params, step->ctx); + } + } + + rcu_read_unlock(); + + return state; +} + /*! \brief Module implementation. */ const knot_layer_api_t *process_query_layer(void) { diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h index 42e66be..c005b58 100644 --- a/src/knot/nameserver/process_query.h +++ b/src/knot/nameserver/process_query.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 @@ -106,3 +106,14 @@ int process_query_sign_response(knot_pkt_t *pkt, knotd_qdata_t *qdata); int process_query_put_rr(knot_pkt_t *pkt, knotd_qdata_t *qdata, const knot_rrset_t *rr, const knot_rrset_t *rrsigs, uint16_t compr_hint, uint32_t flags); + +/*! + * \brief Processes all global module protocol callbacks at given stage. + * + * \param params Query processing parameters. + * \param stage Processing stage (KNOTD_STAGE_PROTO_BEGIN or KNOTD_STAGE_PROTO_END). + * + * \return Resulting state. + */ +knotd_proto_state_t process_query_proto(knotd_qdata_params_t *params, + const knotd_stage_t stage); diff --git a/src/knot/nameserver/query_module.c b/src/knot/nameserver/query_module.c index f02ee1b..0708b17 100644 --- a/src/knot/nameserver/query_module.c +++ b/src/knot/nameserver/query_module.c @@ -32,17 +32,6 @@ #include "knot/nameserver/query_module.h" #include "knot/nameserver/process_query.h" -#ifdef HAVE_ATOMIC - #define ATOMIC_ADD(dst, val) __atomic_add_fetch(&(dst), (val), __ATOMIC_RELAXED) - #define ATOMIC_SUB(dst, val) __atomic_sub_fetch(&(dst), (val), __ATOMIC_RELAXED) - #define ATOMIC_SET(dst, val) __atomic_store_n(&(dst), (val), __ATOMIC_RELAXED) -#else - #warning "Statistics data can be inaccurate" - #define ATOMIC_ADD(dst, val) ((dst) += (val)) - #define ATOMIC_SUB(dst, val) ((dst) -= (val)) - #define ATOMIC_SET(dst, val) ((dst) = (val)) -#endif - _public_ int knotd_conf_check_ref(knotd_conf_check_args_t *args) { @@ -79,30 +68,31 @@ void query_plan_free(struct query_plan *plan) free(plan); } -static struct query_step *make_step(query_step_process_f process, void *ctx) +int query_plan_step(struct query_plan *plan, knotd_stage_t stage, + query_hook_type_t type, void *hook, void *ctx) { - struct query_step *step = calloc(1, sizeof(struct query_step)); + struct query_step *step = calloc(1, sizeof(*step)); if (step == NULL) { - return NULL; + return KNOT_ENOMEM; } - step->process = process; + step->type = type; + step->general_hook = hook; step->ctx = ctx; - return step; + add_tail(&plan->stage[stage], &step->node); + + return KNOT_EOK; } -int query_plan_step(struct query_plan *plan, knotd_stage_t stage, - query_step_process_f process, void *ctx) +_public_ +int knotd_mod_proto_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_proto_hook_f hook) { - struct query_step *step = make_step(process, ctx); - if (step == NULL) { - return KNOT_ENOMEM; + if (stage != KNOTD_STAGE_PROTO_BEGIN && stage != KNOTD_STAGE_PROTO_END) { + return KNOT_EINVAL; } - add_tail(&plan->stage[stage], &step->node); - - return KNOT_EOK; + return query_plan_step(mod->plan, stage, QUERY_HOOK_TYPE_PROTO, hook, mod); } _public_ @@ -112,17 +102,18 @@ int knotd_mod_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_hook_f hook) return KNOT_EINVAL; } - return query_plan_step(mod->plan, stage, hook, mod); + return query_plan_step(mod->plan, stage, QUERY_HOOK_TYPE_GENERAL, hook, mod); } _public_ int knotd_mod_in_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_in_hook_f hook) { - if (stage == KNOTD_STAGE_BEGIN || stage == KNOTD_STAGE_END) { + if (stage != KNOTD_STAGE_PREANSWER && stage != KNOTD_STAGE_ANSWER && + stage != KNOTD_STAGE_AUTHORITY && stage != KNOTD_STAGE_ADDITIONAL) { return KNOT_EINVAL; } - return query_plan_step(mod->plan, stage, hook, mod); + return query_plan_step(mod->plan, stage, QUERY_HOOK_TYPE_IN, hook, mod); } knotd_mod_t *query_module_open(conf_t *conf, server_t *server, conf_mod_id_t *mod_id, @@ -313,8 +304,8 @@ int knotd_mod_stats_add(knotd_mod_t *mod, const char *ctr_name, uint32_t idx_cou stats += mod->stats_count; for (unsigned i = 0; i < threads; i++) { - uint64_t *new_vals = realloc(mod->stats_vals[i], - (offset + idx_count) * sizeof(*new_vals)); + knot_atomic_uint64_t *new_vals = realloc(mod->stats_vals[i], + (offset + idx_count) * sizeof(*new_vals)); if (new_vals == NULL) { knotd_mod_stats_free(mod); return KNOT_ENOMEM; @@ -627,6 +618,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/query_module.h b/src/knot/nameserver/query_module.h index 5cc905b..d403b25 100644 --- a/src/knot/nameserver/query_module.h +++ b/src/knot/nameserver/query_module.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 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 @@ -22,24 +22,27 @@ #include "knot/dnssec/zone-keys.h" #include "knot/include/module.h" #include "knot/server/server.h" +#include "contrib/atomic.h" #include "contrib/ucw/lists.h" -#ifdef HAVE_ATOMIC - #define ATOMIC_GET(src) __atomic_load_n(&(src), __ATOMIC_RELAXED) -#else - #define ATOMIC_GET(src) (src) -#endif +#define KNOTD_STAGES (KNOTD_STAGE_PROTO_END + 1) -#define KNOTD_STAGES (KNOTD_STAGE_END + 1) +typedef enum { + QUERY_HOOK_TYPE_PROTO, + QUERY_HOOK_TYPE_GENERAL, + QUERY_HOOK_TYPE_IN, +} query_hook_type_t; -typedef unsigned (*query_step_process_f) - (unsigned state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod); - -/*! \brief Single processing step in query processing. */ +/*! \brief Single processing step in query/module processing. */ struct query_step { node_t node; + query_hook_type_t type; + union { + knotd_mod_proto_hook_f proto_hook; + knotd_mod_hook_f general_hook; + knotd_mod_in_hook_f in_hook; + }; void *ctx; - query_step_process_f process; }; /*! Query plan represents a sequence of steps needed for query processing @@ -58,7 +61,7 @@ void query_plan_free(struct query_plan *plan); /*! \brief Plan another step for given stage. */ int query_plan_step(struct query_plan *plan, knotd_stage_t stage, - query_step_process_f process, void *ctx); + query_hook_type_t type, void *hook, void *ctx); /*! \brief Open query module identified by name. */ knotd_mod_t *query_module_open(conf_t *conf, server_t *server, conf_mod_id_t *mod_id, @@ -91,7 +94,7 @@ struct knotd_mod { zone_keyset_t *keyset; zone_sign_ctx_t *sign_ctx; mod_ctr_t *stats_info; - uint64_t **stats_vals; + knot_atomic_uint64_t **stats_vals; uint32_t stats_count; void *ctx; }; diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c index 1168c94..ee884fd 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 @@ -22,12 +22,23 @@ #include "knot/query/requestor.h" #include "contrib/sockaddr.h" #include "libknot/libknot.h" +#include "libknot/quic/quic_conn.h" +#include "libknot/quic/tls.h" static int update_enqueue(zone_t *zone, knotd_qdata_t *qdata) { assert(zone); assert(qdata); + pthread_mutex_lock(&zone->ddns_lock); + if (zone->events.ufrozen && zone->ddns_queue_size >= 8) { + pthread_mutex_unlock(&zone->ddns_lock); + qdata->rcode = KNOT_RCODE_REFUSED; + qdata->rcode_ede = KNOT_EDNS_EDE_NOT_READY; + return KNOT_ELIMIT; + } + pthread_mutex_unlock(&zone->ddns_lock); + /* Create serialized request. */ knot_request_t *req = calloc(1, sizeof(*req)); if (req == NULL) { @@ -63,6 +74,22 @@ static int update_enqueue(zone_t *zone, knotd_qdata_t *qdata) assert(req->sign.tsig_key.algorithm == knot_tsig_rdata_alg(req->query->tsig_rr)); } +#ifdef ENABLE_QUIC + if (qdata->params->quic_conn != NULL) { + req->flags |= KNOT_REQUEST_QUIC; + req->quic_conn = qdata->params->quic_conn; + knot_quic_conn_block(req->quic_conn, true); + assert(qdata->params->quic_stream >= 0); + req->quic_stream = qdata->params->quic_stream; + } else +#endif // ENABLE_QUIC + if (qdata->params->tls_conn != NULL) { + req->flags |= KNOT_REQUEST_TLS; + req->tls_req_ctx.conn = qdata->params->tls_conn; + req->tls_req_ctx.conn->fd_clones_count++; + knot_tls_conn_block(req->tls_req_ctx.conn, true); + } + pthread_mutex_lock(&zone->ddns_lock); /* Enqueue created request. */ @@ -77,10 +104,10 @@ static int update_enqueue(zone_t *zone, knotd_qdata_t *qdata) return KNOT_EOK; } -int update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) +knot_layer_state_t update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) { /* DDNS over XDP not supported. */ - if (qdata->params->xdp_msg != NULL || qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) { + if (qdata->params->xdp_msg != NULL) { qdata->rcode = KNOT_RCODE_SERVFAIL; return KNOT_STATE_FAIL; } @@ -95,8 +122,6 @@ int update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata) NS_NEED_AUTH(qdata, ACL_ACTION_UPDATE); /* Check expiration. */ NS_NEED_ZONE_CONTENTS(qdata); - /* Check frozen zone. */ - NS_NEED_NOT_FROZEN(qdata); /* Store update into DDNS queue. */ int ret = update_enqueue((zone_t *)qdata->extra->zone, qdata); diff --git a/src/knot/nameserver/update.h b/src/knot/nameserver/update.h index 609acd9..430b323 100644 --- a/src/knot/nameserver/update.h +++ b/src/knot/nameserver/update.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 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 @@ -21,7 +21,5 @@ /*! * \brief UPDATE query processing module. - * - * \return KNOT_STATE_* processing states */ -int update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); +knot_layer_state_t update_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata); diff --git a/src/knot/nameserver/xfr.h b/src/knot/nameserver/xfr.h index e6d06b6..fe8a7a8 100644 --- a/src/knot/nameserver/xfr.h +++ b/src/knot/nameserver/xfr.h @@ -38,12 +38,12 @@ static inline void xfr_log_finished(const knot_dname_t *zone, log_operation_t op, log_direction_t dir, const struct sockaddr_storage *remote, knotd_query_proto_t proto, const knot_dname_t *key_name, - const struct xfr_stats *stats) + const char *serial_log, const struct xfr_stats *stats) { ns_log(LOG_INFO, zone, op, dir, remote, proto, false, key_name, - "%sfinished, %0.2f seconds, %u messages, %u bytes", + "%sfinished,%s %0.2f seconds, %u messages, %u bytes", (proto == KNOTD_QUERY_PROTO_QUIC && dir == LOG_DIRECTION_OUT ? "buffering " : ""), - time_diff_ms(&stats->begin, &stats->end) / 1000.0, + serial_log, time_diff_ms(&stats->begin, &stats->end) / 1000.0, stats->messages, stats->bytes); } diff --git a/src/knot/query/quic-requestor.c b/src/knot/query/quic-requestor.c index 62008f9..7492efd 100644 --- a/src/knot/query/quic-requestor.c +++ b/src/knot/query/quic-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 @@ -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 @@ -63,6 +62,7 @@ static int quic_exchange(knot_quic_conn_t *conn, knot_quic_reply_t *r, int timeo int64_t quic_timeout_ms = knot_quic_conn_next_timeout(conn); quic_timeout_ms = MIN(quic_timeout_ms, timeout_remain); quic_timeout_ms = MIN(quic_timeout_ms, timeout_ms / 2); + quic_timeout_ms = MAX(quic_timeout_ms, 1); r->in_payload->iov_len = QUIC_BUF_SIZE; @@ -156,7 +156,7 @@ int knot_qreq_connect(struct knot_quic_reply **out, int fd, struct sockaddr_storage *remote, struct sockaddr_storage *local, - const struct knot_quic_creds *local_creds, + const struct knot_creds *local_creds, const uint8_t *peer_pin, uint8_t peer_pin_len, bool *reused_fd, @@ -179,8 +179,7 @@ int knot_qreq_connect(struct knot_quic_reply **out, r->send_reply = qr_send_reply; r->free_reply = qr_free_reply; - struct knot_quic_creds *creds = knot_quic_init_creds_peer(local_creds, - peer_pin, peer_pin_len); + struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_pin, peer_pin_len); if (creds == NULL) { free(r); return KNOT_ENOMEM; @@ -190,7 +189,7 @@ int knot_qreq_connect(struct knot_quic_reply **out, knot_quic_table_t *table = knot_quic_table_new(1, QUIC_BUF_SIZE, QUIC_BUF_SIZE, 0, creds); if (table == NULL) { - knot_quic_free_creds(creds); + knot_creds_free(creds); free(r); return KNOT_ENOMEM; } @@ -294,7 +293,7 @@ void knot_qreq_close(struct knot_quic_reply *r, bool send_close) knot_quic_table_rem(conn, table); knot_quic_cleanup(&conn, 1); if (table != NULL) { - knot_quic_free_creds(table->creds); + knot_creds_free(table->creds); } knot_quic_table_free(table); free(r); diff --git a/src/knot/query/quic-requestor.h b/src/knot/query/quic-requestor.h index b5f479e..083254d 100644 --- a/src/knot/query/quic-requestor.h +++ b/src/knot/query/quic-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 @@ -17,15 +17,13 @@ #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, struct sockaddr_storage *remote, struct sockaddr_storage *local, - const struct knot_quic_creds *local_creds, + const struct knot_creds *local_creds, const uint8_t *peer_pin, uint8_t peer_pin_len, bool *reused_fd, diff --git a/src/knot/query/requestor.c b/src/knot/query/requestor.c index 436f009..125c036 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); @@ -192,7 +215,7 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm, const struct sockaddr_storage *remote, const struct sockaddr_storage *source, knot_pkt_t *query, - const struct knot_quic_creds *creds, + const struct knot_creds *creds, const query_edns_data_t *edns, const knot_tsig_key_t *tsig_key, const uint8_t *pin, @@ -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); } @@ -243,12 +266,15 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm, knot_request_t *knot_request_make(knot_mm_t *mm, const conf_remote_t *remote, knot_pkt_t *query, - const struct knot_quic_creds *creds, + const struct knot_creds *creds, const query_edns_data_t *edns, 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, @@ -262,15 +288,25 @@ void knot_request_free(knot_request_t *request, knot_mm_t *mm) return; } - if (request->quic_ctx != NULL) { + if (use_quic(request)) { #ifdef ENABLE_QUIC - knot_qreq_close(request->quic_ctx, true); + if (request->quic_ctx != NULL) { + knot_qreq_close(request->quic_ctx, true); + } + // NOTE synthetic DDNSoQ request is NOOP here #else assert(0); #endif // ENABLE_QUIC + } else if (use_tls(request) && request->tls_req_ctx.conn != NULL) { + knot_tls_req_ctx_deinit(&request->tls_req_ctx); + } else { + assert(request->quic_ctx == NULL); + assert(request->quic_conn == NULL); + assert(request->tls_req_ctx.ctx == NULL); + assert(request->tls_req_ctx.conn == NULL); } - 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, @@ -282,6 +318,7 @@ void knot_request_free(knot_request_t *request, knot_mm_t *mm) } knot_pkt_free(request->query); knot_pkt_free(request->resp); + dnssec_binary_free(&request->sign.tsig_key.secret); tsig_cleanup(&request->tsig); mm_free(mm, request); @@ -351,7 +388,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; } @@ -376,6 +413,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 65da1ed..241be04 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,7 +57,14 @@ typedef struct { /*! \brief Request data (socket, payload, response, TSIG and endpoints). */ typedef struct { int fd; - struct knot_quic_reply *quic_ctx; + 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; @@ -67,11 +74,22 @@ typedef struct { knot_sign_context_t sign; /*!< Required for async. DDNS processing. */ - const struct knot_quic_creds *creds; + const struct knot_creds *creds; size_t pin_len; 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. * @@ -92,7 +110,7 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm, const struct sockaddr_storage *remote, const struct sockaddr_storage *source, knot_pkt_t *query, - const struct knot_quic_creds *creds, + const struct knot_creds *creds, const query_edns_data_t *edns, const knot_tsig_key_t *tsig_key, const uint8_t *pin, @@ -108,7 +126,7 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm, knot_request_t *knot_request_make(knot_mm_t *mm, const conf_remote_t *remote, knot_pkt_t *query, - const struct knot_quic_creds *creds, + const struct knot_creds *creds, const query_edns_data_t *edns, knot_request_flag_t flags); diff --git a/src/knot/query/tls-requestor.c b/src/knot/query/tls-requestor.c new file mode 100644 index 0000000..e1a2e8e --- /dev/null +++ b/src/knot/query/tls-requestor.c @@ -0,0 +1,59 @@ +/* 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_creds *local_creds, + const uint8_t *peer_pin, uint8_t peer_pin_len, + int io_timeout_ms) +{ + struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_pin, peer_pin_len); + if (creds == NULL) { + return KNOT_ENOMEM; + } + + // Use HS = 4x IO timeout, as the RMT IO timeout is usually high. + ctx->ctx = knot_tls_ctx_new(creds, io_timeout_ms, 4 * io_timeout_ms, false); + if (ctx->ctx == NULL) { + knot_creds_free(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) { + if (ctx->ctx != NULL) { + knot_creds_free(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 0000000..535bbe8 --- /dev/null +++ b/src/knot/query/tls-requestor.h @@ -0,0 +1,51 @@ +/* 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; + +/*! + * \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_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.c b/src/knot/server/handler.c index ec7377a..bcd0ec5 100644 --- a/src/knot/server/handler.c +++ b/src/knot/server/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 @@ -97,7 +97,7 @@ static void handle_quic_stream(knot_quic_conn_t *conn, int64_t stream_id, struct } void handle_quic_streams(knot_quic_conn_t *conn, knotd_qdata_params_t *params, - knot_layer_t *layer, void *msg) + knot_layer_t *layer) { uint8_t ans_buf[KNOT_WIRE_MAX_PKTSIZE]; @@ -108,14 +108,7 @@ void handle_quic_streams(knot_quic_conn_t *conn, knotd_qdata_params_t *params, assert(stream->inbufs != NULL); assert(stream->inbufs->n_inbufs > 0); struct iovec *inbufs = stream->inbufs->inbufs; - if (msg) { -#ifdef ENABLE_XDP - params_xdp_update(params, KNOTD_QUERY_PROTO_QUIC, msg, - knot_quic_conn_rtt(conn), conn); -#endif // ENABLE_XDP - } else { - params_update(params, knot_quic_conn_rtt(conn), conn); - } + params_update_quic(params, knot_quic_conn_rtt(conn), conn, stream_id); // NOTE: only the first msg in the stream is used, the rest is dropped. handle_quic_stream(conn, stream_id, &inbufs[0], layer, params, ans_buf, sizeof(ans_buf)); diff --git a/src/knot/server/handler.h b/src/knot/server/handler.h index 0f29988..4b63a0c 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 @@ -20,6 +20,7 @@ #include "knot/query/layer.h" #include "knot/server/server.h" #include "libknot/xdp/tcp_iobuf.h" +#include "libknot/quic/tls.h" #ifdef ENABLE_QUIC #include "libknot/quic/quic.h" @@ -44,18 +45,33 @@ inline static knotd_qdata_params_t params_init(knotd_query_proto_t proto, .remote = (const struct sockaddr_storage *)remote, .local = (const struct sockaddr_storage *)local, .socket = sock, + .thread_id = thread_id, .server = server, - .thread_id = thread_id + .quic_stream = -1, }; return params; } -inline static void params_update(knotd_qdata_params_t *params, uint32_t rtt, - struct knot_quic_conn *conn) +inline static void params_update_tcp(knotd_qdata_params_t *params, uint32_t rtt) { params->measured_rtt = rtt; +} + +#ifdef ENABLE_QUIC +inline static void params_update_quic(knotd_qdata_params_t *params, uint32_t rtt, + knot_quic_conn_t *conn, int64_t stream_id) +{ params->quic_conn = conn; + params->quic_stream = stream_id; + params->measured_rtt = rtt; +} +#endif // ENABLE_QUIC + +inline static void params_update_tls(knotd_qdata_params_t *params, + knot_tls_conn_t *conn) +{ + params->tls_conn = conn; } #ifdef ENABLE_XDP @@ -64,8 +80,9 @@ inline static knotd_qdata_params_t params_xdp_init(int sock, server_t *server, { knotd_qdata_params_t params = { .socket = sock, + .thread_id = thread_id, .server = server, - .thread_id = thread_id + .quic_stream = -1, }; return params; @@ -73,16 +90,12 @@ inline static knotd_qdata_params_t params_xdp_init(int sock, server_t *server, inline static void params_xdp_update(knotd_qdata_params_t *params, knotd_query_proto_t proto, - struct knot_xdp_msg *msg, - uint32_t rtt, - struct knot_quic_conn *conn) + struct knot_xdp_msg *msg) { params->proto = proto; params->remote = (struct sockaddr_storage *)&msg->ip_from; params->local = (struct sockaddr_storage *)&msg->ip_to; params->xdp_msg = msg; - params->measured_rtt = rtt; - params->quic_conn = conn; } #endif // ENABLE_XDP @@ -107,7 +120,7 @@ void handle_udp_reply(knotd_qdata_params_t *params, knot_layer_t *layer, #ifdef ENABLE_QUIC void handle_quic_streams(knot_quic_conn_t *conn, knotd_qdata_params_t *params, - knot_layer_t *layer, void *msg); + knot_layer_t *layer); #endif // ENABLE_QUIC void log_swept(knot_sweep_stats_t *stats, bool tcp); diff --git a/src/knot/server/quic-handler.c b/src/knot/server/quic-handler.c index 5010213..663167e 100644 --- a/src/knot/server/quic-handler.c +++ b/src/knot/server/quic-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 @@ -24,6 +24,7 @@ #include "contrib/macros.h" #include "contrib/net.h" #include "knot/common/log.h" +#include "knot/nameserver/process_query.h" #include "knot/server/handler.h" #include "knot/server/quic-handler.h" #include "knot/server/server.h" @@ -92,16 +93,22 @@ void quic_handler(knotd_qdata_params_t *params, knot_layer_t *layer, rpl.out_payload->iov_len = 0; // prevent send attempt if uq_alloc_reply is not called at all + if (process_query_proto(params, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + return; + } + knot_quic_conn_t *conn = NULL; (void)knot_quic_handle(table, &rpl, idle_close, &conn); if (conn != NULL) { - handle_quic_streams(conn, params, layer, NULL); + handle_quic_streams(conn, params, layer); (void)knot_quic_send(table, conn, &rpl, QUIC_MAX_SEND_PER_RECV, 0); knot_quic_cleanup(&conn, 1); } + + (void)process_query_proto(params, KNOTD_STAGE_PROTO_END); } knot_quic_table_t *quic_make_table(struct server *server) diff --git a/src/knot/server/server.c b/src/knot/server/server.c index dd0b025..0626337 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" @@ -65,6 +66,8 @@ #define SESSION_TICKET_POOL_TIMEOUT (24 * 3600) +#define QUIC_LOG "QUIC/TLS, " + /*! \brief Minimal send/receive buffer sizes. */ enum { UDP_MIN_RCVSIZE = 4096, @@ -236,14 +239,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++; } @@ -252,14 +255,11 @@ static size_t quic_rmt_count(conf_t *conf) return count; } +#ifdef ENABLE_XDP static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, bool route_check, bool udp, bool tcp, uint16_t quic, unsigned *thread_id_start, - bool extra_frames) + const knot_xdp_config_t *xdp_config) { -#ifndef ENABLE_XDP - assert(0); - return NULL; -#else conf_xdp_iface_t iface; int ret = conf_xdp_iface(addr, &iface); if (ret != KNOT_EOK) { @@ -296,18 +296,17 @@ static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, bool route_ xdp_flags |= KNOT_XDP_FILTER_ROUTE; } - knot_xdp_config_t xdp_config = { .extra_frames = extra_frames }; for (int i = 0; i < iface.queues; i++) { knot_xdp_load_bpf_t mode = (i == 0 ? KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER); ret = knot_xdp_init(new_if->xdp_sockets + i, iface.name, i, - xdp_flags, iface.port, quic, mode, &xdp_config); + xdp_flags, iface.port, quic, mode, xdp_config); if (ret == -EBUSY && i == 0) { log_notice("XDP interface %s@%u is busy, retrying initialization", iface.name, iface.port); ret = knot_xdp_init(new_if->xdp_sockets + i, iface.name, i, xdp_flags, iface.port, quic, - KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD, &xdp_config); + KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD, xdp_config); } if (ret != KNOT_EOK) { log_warning("failed to initialize XDP interface %s@%u, queue %d (%s)", @@ -345,8 +344,8 @@ static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, bool route_ } return new_if; -#endif } +#endif /*! * \brief Create and initialize new interface. @@ -363,7 +362,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) { @@ -380,14 +379,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; } @@ -459,7 +458,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"); @@ -561,7 +560,6 @@ static void log_sock_conf(conf_t *conf) } } -#ifdef ENABLE_QUIC static int check_file(char *path, char *role) { if (path == NULL) { @@ -576,29 +574,29 @@ static int check_file(char *path, char *role) err_str = "invalid file"; } else if (!S_ISREG(st.st_mode)) { err_str = "not a file"; + } else if ((st.st_mode & S_IRUSR) == 0) { + err_str = "not readable"; } else { return KNOT_EOK; } - log_error("QUIC, %s file '%s' (%s)", role, path, err_str); + log_error(QUIC_LOG "%s file '%s' (%s)", role, path, err_str); return KNOT_EINVAL; } -#endif // ENABLE_QUIC -static int init_creds(server_t *server, conf_t *conf) +static int init_creds(conf_t *conf, server_t *server) { -#ifdef ENABLE_QUIC char *cert_file = conf_tls(conf, C_CERT_FILE); char *key_file = conf_tls(conf, C_KEY_FILE); int ret = check_file(cert_file, "certificate"); if (ret != KNOT_EOK) { - return ret; + goto failed; } ret = check_file(key_file, "key"); if (ret != KNOT_EOK) { - return ret; + goto failed; } if (cert_file == NULL) { @@ -606,35 +604,45 @@ 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_LOG "failed to create directory '%s'", kasp_dir); free(kasp_dir); - return ret; + goto failed; } 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_LOG "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); + + uint8_t prev_pin[128]; + size_t prev_pin_len = server_cert_pin(server, prev_pin, sizeof(prev_pin)); + if (server->quic_creds == NULL) { - log_error("QUIC, failed to initialize server credentials with key '%s'", - key_file); - free(key_file); - return KNOT_ERROR; + server->quic_creds = knot_creds_init(key_file, cert_file); + if (server->quic_creds == NULL) { + log_error(QUIC_LOG "failed to initialize server credentials"); + ret = KNOT_ERROR; + goto failed; + } + } else { + ret = knot_creds_update(server->quic_creds, key_file, cert_file); + if (ret != KNOT_EOK) { + goto failed; + } } - free(key_file); - 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); + size_t pin_len = server_cert_pin(server, pin, sizeof(pin)); + if (pin_len > 0 && (pin_len != prev_pin_len || memcmp(pin, prev_pin, pin_len) != 0)) { + log_info(QUIC_LOG "certificate public key %.*s", (int)pin_len, pin); } - return KNOT_EOK; -#else - return KNOT_ERROR; -#endif // ENABLE_QUIC + ret = KNOT_EOK; +failed: + free(key_file); + free(cert_file); + + return ret; } /*! \brief Initialize bound sockets according to configuration. */ @@ -646,11 +654,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"); @@ -676,7 +686,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"); @@ -720,32 +730,52 @@ static int configure_sockets(conf_t *conf, server_t *s) free(rundir); return KNOT_ERROR; } - new_if->quic = true; + new_if->tls = true; memcpy(&newlist[real_nifs++], new_if, sizeof(*newlist)); free(new_if); 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. */ - bool xdp_udp = conf->cache.xdp_udp; - bool xdp_tcp = conf->cache.xdp_tcp; - uint16_t xdp_quic = conf->cache.xdp_quic; - bool route_check = conf->cache.xdp_route_check; +#ifdef ENABLE_XDP + knot_xdp_config_t xdp_config = { + .ring_size = conf->cache.xdp_ring_size, + .busy_poll_budget = conf->cache.xdp_busypoll_budget, + .busy_poll_timeout = conf->cache.xdp_busypoll_timeout, + }; unsigned thread_id = s->handlers[IO_UDP].handler.unit->size + s->handlers[IO_TCP].handler.unit->size; - conf_val_t extra_frames_val = conf_get(conf, C_XDP, C_EXTRA_FRAMES); - bool extra_frames = conf_bool(&extra_frames_val); while (lisxdp_val.code == KNOT_EOK) { struct sockaddr_storage addr = conf_addr(&lisxdp_val, NULL); char addr_str[SOCKADDR_STRLEN] = { 0 }; sockaddr_tostr(addr_str, sizeof(addr_str), &addr); log_info("binding to XDP interface %s", addr_str); - iface_t *new_if = server_init_xdp_iface(&addr, route_check, xdp_udp, - xdp_tcp, xdp_quic, &thread_id, - extra_frames); + iface_t *new_if = server_init_xdp_iface(&addr, conf->cache.xdp_route_check, + conf->cache.xdp_udp, conf->cache.xdp_tcp, + conf->cache.xdp_quic, &thread_id, + &xdp_config); if (new_if == NULL) { server_deinit_iface_list(newlist, nifs); return KNOT_ERROR; @@ -755,13 +785,16 @@ static int configure_sockets(conf_t *conf, server_t *s) conf_val_next(&lisxdp_val); } +#endif + assert(real_nifs <= nifs); nifs = real_nifs; /* QUIC credentials initialization. */ - s->quic_active = xdp_quic > 0 || convent_quic > 0 || quic_rmt_count(conf) > 0; - if (s->quic_active) { - if (init_creds(s, conf) != KNOT_EOK) { + 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(conf, s) != KNOT_EOK) { server_deinit_iface_list(newlist, nifs); return KNOT_ERROR; } @@ -810,6 +843,8 @@ int server_init(server_t *server, int bg_workers) return ret; } + pthread_rwlock_init(&server->ctl_lock, NULL); + zone_backups_init(&server->backup_ctxs); char *catalog_dir = conf_db(conf(), C_CATALOG_DB); @@ -864,6 +899,9 @@ void server_deinit(server_t *server) /* Free remaining events. */ evsched_deinit(&server->sched); + /* Deinit locks. */ + pthread_rwlock_destroy(&server->ctl_lock); + /* Free catalog zone context. */ catalog_update_clear(&server->catalog_upd); catalog_update_deinit(&server->catalog_upd); @@ -885,9 +923,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 + knot_creds_free(server->quic_creds); } static int server_init_handler(server_t *server, int index, int thread_count, @@ -1058,9 +1094,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; @@ -1075,7 +1112,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; @@ -1090,7 +1129,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; @@ -1101,13 +1142,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; @@ -1137,6 +1197,9 @@ static void warn_server_reconfigure(conf_t *conf, server_t *server) static bool warn_xdp_tcp = true; static bool warn_xdp_quic = true; static bool warn_route_check = true; + static bool warn_ring_size = true; + static bool warn_busypoll_budget = true; + static bool warn_busypoll_timeout = true; static bool warn_rmt_pool_limit = true; if (warn_tcp_reuseport && conf->cache.srv_tcp_reuseport != conf_get_bool(conf, C_SRV, C_TCP_REUSEPORT)) { @@ -1165,7 +1228,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; } @@ -1195,6 +1258,21 @@ static void warn_server_reconfigure(conf_t *conf, server_t *server) warn_route_check = false; } + if (warn_ring_size && conf->cache.xdp_ring_size != conf_get_int(conf, C_XDP, C_RING_SIZE)) { + log_warning(msg, &C_RING_SIZE[1]); + warn_ring_size = false; + } + + if (warn_busypoll_budget && conf->cache.xdp_busypoll_budget != conf_get_int(conf, C_XDP, C_BUSYPOLL_BUDGET)) { + log_warning(msg, &C_BUSYPOLL_BUDGET[1]); + warn_busypoll_budget = false; + } + + if (warn_busypoll_timeout && conf->cache.xdp_busypoll_timeout != conf_get_int(conf, C_XDP, C_BUSYPOLL_TIMEOUT)) { + log_warning(msg, &C_BUSYPOLL_TIMEOUT[1]); + warn_busypoll_timeout = false; + } + if (warn_rmt_pool_limit && global_conn_pool != NULL && global_conn_pool->capacity != conf_get_int(conf, C_SRV, C_RMT_POOL_LIMIT)) { log_warning(msg, &C_RMT_POOL_LIMIT[1]); @@ -1395,7 +1473,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 = @@ -1456,6 +1534,12 @@ int server_reconfigure(conf_t *conf, server_t *server) if (conf_lmdb_readers(conf) > CONF_MAX_DB_READERS) { log_warning("config, exceeded number of database readers"); } + } else { + /* Reconfigure TLS credentials. */ + if ((ret = init_creds(conf, server)) != KNOT_EOK) { + log_error("failed to reconfigure server credentials (%s)", + knot_strerror(ret)); + } } /* Reconfigure journal DB. */ @@ -1515,14 +1599,13 @@ 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 && - knot_quic_creds_cert(server->quic_creds, &cert) == KNOT_EOK && + knot_creds_cert(server->quic_creds, &cert) == KNOT_EOK && gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, bin_pin, &bin_pin_size) == GNUTLS_E_SUCCESS) { pin_size = knot_base64_encode(bin_pin, bin_pin_size, out, out_size); @@ -1530,7 +1613,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 e72e6de..fae06d6 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 @@ -16,8 +16,7 @@ #pragma once -#include <stdatomic.h> - +#include "contrib/atomic.h" #include "knot/conf/conf.h" #include "knot/catalog/catalog_update.h" #include "knot/common/evsched.h" @@ -32,7 +31,7 @@ struct server; struct knot_xdp_socket; -struct knot_quic_creds; +struct knot_creds; /*! * \brief I/O handler structure. @@ -75,7 +74,7 @@ typedef struct { unsigned fd_xdp_count; unsigned xdp_first_thread_id; bool anyaddr; - bool quic; + bool tls; struct knot_xdp_socket **xdp_sockets; struct sockaddr_storage addr; } iface_t; @@ -120,16 +119,20 @@ typedef struct server { iface_t *ifaces; size_t n_ifaces; bool quic_active; + bool tls_active; + + /*! \brief Mutex protecting simultaneous access from concurrent CTL threads. */ + pthread_rwlock_t ctl_lock; /*! \brief Pending changes to catalog member zones, update indication. */ catalog_update_t catalog_upd; - atomic_bool catalog_upd_signal; + knot_atomic_bool catalog_upd_signal; /*! \brief Context of pending zones' backup. */ zone_backup_ctxs_t backup_ctxs; /*! \brief Crendentials context for QUIC. */ - struct knot_quic_creds *quic_creds; + struct knot_creds *quic_creds; } server_t; /*! diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c index 305b2f4..7af2f58 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,11 +78,22 @@ 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. */ -static fdset_sweep_state_t tcp_sweep(fdset_t *set, int fd, _unused_ void *data) +static fdset_sweep_state_t tcp_sweep(fdset_t *set, int idx, _unused_ void *data) { + const int fd = fdset_get_fd(set, idx); assert(set && fd >= 0); /* Best-effort, name and shame. */ @@ -92,6 +105,8 @@ static fdset_sweep_state_t tcp_sweep(fdset_t *set, int fd, _unused_ void *data) log_notice("TCP, terminated inactive client, address %s", addr_str); } + free_tls_ctx(set, idx); + return FDSET_SWEEP; } @@ -107,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->quic) { // Ignore XDP and QUIC interfaces. - assert(i->fd_xdp_count > 0 || i->quic); + if (i->fd_xdp_count > 0 || i->fd_tcp_count == 0) { // Ignore XDP and QUIC interfaces. continue; } @@ -133,31 +147,44 @@ 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, - const sockaddr_t *local, struct iovec *rx, struct iovec *tx) +static int tcp_handle(tcp_context_t *tcp, knotd_qdata_params_t *params, + 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); - 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 (params->tls_conn != NULL) { + int ret = knot_tls_handshake(params->tls_conn, true); + switch (ret) { + case KNOT_EAGAIN: // Unfinished handshake, continue later. + return KNOT_EOK; + case KNOT_EOK: // Finished handshake, continue with receiving message. + recv = knot_tls_recv_dns(params->tls_conn, rx->iov_base, rx->iov_len); + break; + default: // E.g. handshake timeout. + return ret; + } + } else { + recv = net_dns_tcp_recv(params->socket, rx->iov_base, rx->iov_len, tcp->io_timeout); + } if (recv > 0) { rx->iov_len = recv; } else { - tcp_log_error(params.remote, "receive", recv); + tcp_log_error(params->remote, "receive", recv); return KNOT_EOF; } - handle_query(¶ms, &tcp->layer, rx, NULL); + handle_query(params, &tcp->layer, rx, NULL); /* Resolve until NOOP or finished. */ knot_pkt_t *ans = knot_pkt_new(tx->iov_base, tx->iov_len, tcp->layer.mm); @@ -165,10 +192,15 @@ 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 (params->tls_conn != NULL) { + sent = knot_tls_send_dns(params->tls_conn, ans->wire, ans->size); + } else { + sent = net_dns_tcp_send(params->socket, ans->wire, ans->size, + tcp->io_timeout, NULL); + } if (sent != ans->size) { - tcp_log_error(params.remote, "send", sent); + tcp_log_error(params->remote, "send", sent); handle_finish(&tcp->layer); return KNOT_EOF; } @@ -222,12 +254,42 @@ 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]); + knotd_qdata_params_t params = params_init(iface->tls ? KNOTD_QUERY_PROTO_TLS + : KNOTD_QUERY_PROTO_TCP, + remote, local, fd, tcp->server, + tcp->thread_id); + + // NOTE there is no way to avoid calling accept() on unwanted connections: + // - it's not possible to read out the remote IP beforehand + // - there is no way to pull it out of the queue + // So we just accept() those connection (possibly going ahead with the handshake) + // and close it immediately. + if (process_query_proto(¶ms, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + return KNOT_EDENIED; // results in closing connection + } + + /* Establish a TLS session. */ + if (iface->tls) { + assert(tcp->tls_ctx != NULL); + knot_tls_conn_t *tls_conn = *fdset_ctx2(&tcp->set, i); + if (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; + } + params_update_tls(¶ms, tls_conn); + } + + int ret = tcp_handle(tcp, ¶ms, &tcp->iov[0], &tcp->iov[1]); if (ret == KNOT_EOK) { /* Update socket activity timer. */ (void)fdset_set_watchdog(&tcp->set, i, tcp->idle_timeout); } + (void)process_query_proto(¶ms, KNOTD_STAGE_PROTO_END); + return ret; } @@ -273,6 +335,7 @@ static void tcp_wait_for_events(tcp_context_t *tcp) /* Evaluate. */ if (should_close) { + free_tls_ctx(set, idx); fdset_it_remove(&it); } } @@ -325,13 +388,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. */ } @@ -341,6 +406,17 @@ int tcp_master(dthread_t *thread) update_sweep_timer(&next_sweep); update_tcp_conf(&tcp); + /* Initialize TLS context. */ + if (tls) { + // Set the HS timeout to 8x the RMT IO one as the HS duration can be up to 4*roundtrip. + tcp.tls_ctx = knot_tls_ctx_new(handler->server->quic_creds, + tcp.io_timeout, 8 * tcp.io_timeout, true); + if (tcp.tls_ctx == NULL) { + ret = KNOT_ENOMEM; + goto finish; + } + } + for (;;) { /* Check for cancellation. */ if (dt_is_cancelled(thread)) { @@ -359,6 +435,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 e54ce2c..c5166f9 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 @@ -73,9 +73,15 @@ typedef struct { static void udp_handler(udp_context_t *udp, knotd_qdata_params_t *params, struct iovec *rx, struct iovec *tx) { + if (process_query_proto(params, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + return; + } + // Prepare a reply. struct sockaddr_storage proxied_remote; handle_udp_reply(params, &udp->layer, rx, tx, &proxied_remote); + + (void)process_query_proto(params, KNOTD_STAGE_PROTO_END); } typedef struct { @@ -153,7 +159,7 @@ void cmsg_handle(const struct msghdr *rx, struct msghdr *tx, } struct cmsghdr *cmsg = CMSG_FIRSTHDR(tx); - if (iface->quic) { + if (iface->tls) { *p_ecn = NULL; while (cmsg != NULL) { cmsg_handle_ecn(p_ecn, cmsg); @@ -246,9 +252,9 @@ static void udp_msg_handle(udp_context_t *ctx, const iface_t *iface, void *d) /* Process received pkt. */ knotd_qdata_params_t params = params_init( - iface->quic ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_UDP, + iface->tls ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_UDP, &rq->addr, local, rq->fd, ctx->server, ctx->thread_id); - if (iface->quic) { + if (iface->tls) { #ifdef ENABLE_QUIC quic_handler(¶ms, &ctx->layer, ctx->quic_idle_close, ctx->quic_table, &rq->iov[RX], &rq->msg[TX], p_ecn); @@ -357,9 +363,9 @@ static void udp_mmsg_handle(udp_context_t *ctx, const iface_t *iface, void *d) const sockaddr_t *local = local_addr(&ctx->local, iface); knotd_qdata_params_t params = params_init( - iface->quic ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_UDP, + iface->tls ? KNOTD_QUERY_PROTO_QUIC : KNOTD_QUERY_PROTO_UDP, &rq->addrs[i], local, rq->fd, ctx->server, ctx->thread_id); - if (iface->quic) { + if (iface->tls) { #ifdef ENABLE_QUIC quic_handler(¶ms, &ctx->layer, ctx->quic_idle_close, ctx->quic_table, rx->msg_iov, tx, p_ecn); @@ -432,7 +438,7 @@ static int xdp_mmsg_recv(_unused_ int fd, void *d) static void xdp_mmsg_handle(udp_context_t *ctx, _unused_ const iface_t *iface, void *d) { - assert(!iface->quic); + assert(!iface->tls); xdp_handle_msgs(d, &ctx->layer, ctx->server, ctx->thread_id); } @@ -482,7 +488,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 +513,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 @@ -520,7 +525,7 @@ static unsigned udp_set_ifaces(const server_t *server, size_t n_ifaces, fdset_t if (ret < 0) { return 0; } - if (i->quic) { + if (i->tls) { *quic = true; } } diff --git a/src/knot/server/xdp-handler.c b/src/knot/server/xdp-handler.c index ae8512d..403bb70 100644 --- a/src/knot/server/xdp-handler.c +++ b/src/knot/server/xdp-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 @@ -20,6 +20,7 @@ #include <stdlib.h> #include <urcu.h> +#include "knot/nameserver/process_query.h" #include "knot/server/handler.h" #include "knot/server/quic-handler.h" #include "knot/server/xdp-handler.h" @@ -196,6 +197,12 @@ static void handle_udp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, continue; } + params_xdp_update(params, KNOTD_QUERY_PROTO_UDP, msg_recv); + + if (process_query_proto(params, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + continue; + } + // Try to allocate a buffer for a reply. if (knot_xdp_reply_alloc(ctx->sock, msg_recv, msg_send) != KNOT_EOK) { if (log_enabled_debug()) { @@ -206,36 +213,50 @@ static void handle_udp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, ctx->msg_udp_count++; // Prepare a reply. - params_xdp_update(params, KNOTD_QUERY_PROTO_UDP, msg_recv, 0, NULL); handle_udp_reply(params, layer, &msg_recv->payload, &msg_send->payload, &proxied_remote); + + (void)process_query_proto(params, KNOTD_STAGE_PROTO_END); } } static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, knotd_qdata_params_t *params) { - int ret = knot_tcp_recv(ctx->relays, ctx->msg_recv, ctx->msg_recv_count, - ctx->tcp_table, ctx->syn_table, XDP_TCP_IGNORE_NONE); - if (ret != KNOT_EOK) { - if (log_enabled_debug()) { - log_debug("TCP/XDP, failed to process some packets (%s)", knot_strerror(ret)); - } - return; - } else if (knot_tcp_relay_empty(&ctx->relays[0])) { // no TCP traffic - return; - } uint8_t ans_buf[KNOT_WIRE_MAX_PKTSIZE]; for (uint32_t i = 0; i < ctx->msg_recv_count; i++) { + knot_xdp_msg_t *msg_recv = &ctx->msg_recv[i]; knot_tcp_relay_t *rl = &ctx->relays[i]; + if (!(msg_recv->flags & KNOT_XDP_MSG_TCP)) { + continue; + } + + params_xdp_update(params, KNOTD_QUERY_PROTO_TCP, msg_recv); + + if (process_query_proto(params, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + continue; + } + + int ret = knot_tcp_recv(rl, msg_recv, ctx->tcp_table, + ctx->syn_table, XDP_TCP_IGNORE_NONE); + if (ret != KNOT_EOK) { + if (log_enabled_debug()) { + log_debug("TCP/XDP, failed to process some packets (%s)", + knot_strerror(ret)); + } + continue; + } else if (knot_tcp_relay_empty(rl)) { + continue; + } + + params_update_tcp(params, rl->conn->establish_rtt); + // Process all complete DNS queries in one TCP stream. for (size_t j = 0; rl->inbf != NULL && j < rl->inbf->n_inbufs; j++) { // Consume the query. - params_xdp_update(params, KNOTD_QUERY_PROTO_TCP, ctx->msg_recv, - rl->conn->establish_rtt, NULL); struct iovec *inbufs = rl->inbf->inbufs; handle_query(params, layer, &inbufs[j], NULL); @@ -253,6 +274,8 @@ static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, handle_finish(layer); } + + (void)process_query_proto(params, KNOTD_STAGE_PROTO_END); } } @@ -274,6 +297,12 @@ static void handle_quic(xdp_handle_ctx_t *ctx, knot_layer_t *layer, continue; } + params_xdp_update(params, KNOTD_QUERY_PROTO_QUIC, msg_recv); + + if (process_query_proto(params, KNOTD_STAGE_PROTO_BEGIN) == KNOTD_PROTO_STATE_BLOCK) { + continue; + } + knot_quic_reply_t *reply = &ctx->quic_replies[i]; knot_xdp_msg_t *msg_out = &ctx->msg_send_udp[i]; @@ -289,7 +318,9 @@ static void handle_quic(xdp_handle_ctx_t *ctx, knot_layer_t *layer, &ctx->quic_relays[i]); knot_quic_conn_t *conn = ctx->quic_relays[i]; - handle_quic_streams(conn, params, layer, &ctx->msg_recv[i]); + handle_quic_streams(conn, params, layer); + + (void)process_query_proto(params, KNOTD_STAGE_PROTO_END); } #else (void)(ctx); diff --git a/src/knot/updates/acl.c b/src/knot/updates/acl.c index d297747..456e8bb 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_tls_pin(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_tls_pin(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 88d7de5..5072a29 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/knot/updates/ddns.c b/src/knot/updates/ddns.c index 5ab858f..47e3fc5 100644 --- a/src/knot/updates/ddns.c +++ b/src/knot/updates/ddns.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 @@ -266,21 +266,41 @@ static bool node_contains_rr(const zone_node_t *node, /*!< \brief Returns true if CNAME is in this node. */ static bool adding_to_cname(const knot_dname_t *owner, + zone_update_t *update, const zone_node_t *node) { if (node == NULL) { - // Node did not exist before update. + // Node did not exist before update, juch check DNAMEs above. + + while ((owner = knot_dname_next_label(owner)) != NULL && + (node = zone_update_get_node(update, owner)) == NULL); + + for ( ; node != NULL; node = node->parent) { + knot_rrset_t dname = node_rrset(node, KNOT_RRTYPE_DNAME); + if (!knot_rrset_empty(&dname)) { + // DNAME above + return true; + } + } + return false; } knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME); - if (knot_rrset_empty(&cname)) { - // Node did not contain CNAME before update. - return false; + if (!knot_rrset_empty(&cname)) { + // CNAME present + return true; } - // CNAME present - return true; + while ((node = node->parent) != NULL) { + knot_rrset_t dname = node_rrset(node, KNOT_RRTYPE_DNAME); + if (!knot_rrset_empty(&dname)) { + // DNAME above + return true; + } + } + + return false; } /*!< \brief Used to ignore SOA deletions and SOAs with lower serial than zone. */ @@ -315,12 +335,29 @@ static int add_rr_to_changeset(const knot_rrset_t *rr, zone_update_t *update) return zone_update_add(update, rr); } -/*!< \brief Processes CNAME addition (replace or ignore) */ +int node_empty_cb(zone_node_t *node, _unused_ void *ctx) +{ + return node_empty(node) ? KNOT_EOK : KNOT_ESEMCHECK; +} + +bool subtree_empty(zone_contents_t *zone, const zone_node_t *node) +{ + if (node == NULL) { + return true; + } + int ret = zone_tree_sub_apply(zone->nodes, node->owner, true, node_empty_cb, NULL); + return (ret == KNOT_EOK); +} + +/*!< \brief Processes CNAME/DNAME addition (replace or ignore) */ static int process_add_cname(const zone_node_t *node, const knot_rrset_t *rr, + uint16_t type, zone_update_t *update) { - knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME); + assert(type == KNOT_RRTYPE_CNAME || type == KNOT_RRTYPE_DNAME); + + knot_rrset_t cname = node_rrset(node, type); if (!knot_rrset_empty(&cname)) { // If they are identical, ignore. if (knot_rrset_equal(&cname, rr, true)) { @@ -333,40 +370,24 @@ static int process_add_cname(const zone_node_t *node, } return add_rr_to_changeset(rr, update); - } else if (!node_empty(node)) { + } else if (type == KNOT_RRTYPE_CNAME && !node_empty(node)) { // Other occupied node => ignore. return KNOT_EOK; + } else if (type == KNOT_RRTYPE_DNAME && !subtree_empty(update->new_cont, node)) { + // Equivalent to above, ignore. + return KNOT_EOK; + } else if (type == KNOT_RRTYPE_DNAME && node_rrtype_exists(node, KNOT_RRTYPE_CNAME)) { + // RFC 6672 §5.2. + return KNOT_EOK; + } else if (type == KNOT_RRTYPE_CNAME && adding_to_cname(rr->owner, update, node)) { + // DNAME exists above CNAME, ignore. + return KNOT_EOK; } else { // Can add. return add_rr_to_changeset(rr, update); } } -/*!< \brief Processes NSEC3PARAM addition (ignore when not removed, or non-apex) */ -static int process_add_nsec3param(const zone_node_t *node, - const knot_rrset_t *rr, - zone_update_t *update) -{ - if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) { - // Ignore non-apex additions - char *owner = knot_dname_to_str_alloc(rr->owner); - log_warning("DDNS, refusing to add NSEC3PARAM to non-apex " - "node '%s'", owner); - free(owner); - return KNOT_EDENIED; - } - knot_rrset_t param = node_rrset(node, KNOT_RRTYPE_NSEC3PARAM); - if (knot_rrset_empty(¶m)) { - return add_rr_to_changeset(rr, update); - } - - char *owner = knot_dname_to_str_alloc(rr->owner); - log_warning("DDNS, refusing to add second NSEC3PARAM to node '%s'", owner); - free(owner); - - return KNOT_EOK; -} - /*! * \brief Processes SOA addition (ignore when non-apex), lower serials * dropped before. @@ -395,7 +416,7 @@ static int process_add_normal(const zone_node_t *node, const knot_rrset_t *rr, zone_update_t *update) { - if (adding_to_cname(rr->owner, node)) { + if (adding_to_cname(rr->owner, update, node)) { // Adding RR to CNAME node, ignore. return KNOT_EOK; } @@ -415,11 +436,10 @@ static int process_add(const knot_rrset_t *rr, { switch(rr->type) { case KNOT_RRTYPE_CNAME: - return process_add_cname(node, rr, update); + case KNOT_RRTYPE_DNAME: + return process_add_cname(node, rr, rr->type, update); case KNOT_RRTYPE_SOA: return process_add_soa(node, rr, update); - case KNOT_RRTYPE_NSEC3PARAM: - return process_add_nsec3param(node, rr, update); default: return process_add_normal(node, rr, update); } @@ -531,49 +551,32 @@ static int process_remove(const knot_rrset_t *rr, } } -/*!< \brief Checks whether addition has not violated DNAME rules. */ -static bool sem_check(const knot_rrset_t *rr, const zone_node_t *zone_node, - zone_update_t *update) -{ - const zone_node_t *added_node = zone_contents_find_node(update->new_cont, rr->owner); - - // we do this sem check AFTER adding the RR, so the node must exist - assert(added_node != NULL); - - for (const zone_node_t *parent = added_node->parent; - parent != NULL; parent = parent->parent) { - if (node_rrtype_exists(parent, KNOT_RRTYPE_DNAME)) { - // Parent has DNAME RRSet, refuse update - return false; - } - } - - if (rr->type != KNOT_RRTYPE_DNAME || zone_node == NULL) { - return true; - } - - // Check that we have not created node with DNAME children. - if (zone_node->children > 0) { - // Updated node has children and DNAME was added, refuse update - return false; - } - - return true; -} - /*!< \brief Checks whether we can accept this RR. */ static int check_update(const knot_rrset_t *rrset, const knot_pkt_t *query, - uint16_t *rcode) + const zone_contents_t *zone, uint16_t *rcode) { /* Accept both subdomain and dname match. */ const knot_dname_t *owner = rrset->owner; const knot_dname_t *qname = knot_pkt_qname(query); + assert(knot_dname_is_equal(qname, zone->apex->owner)); const int in_bailiwick = knot_dname_in_bailiwick(owner, qname); if (in_bailiwick < 0) { *rcode = KNOT_RCODE_NOTZONE; return KNOT_EOUTOFZONE; } + if (rrset->type == KNOT_RRTYPE_NSEC3PARAM) { + if (!knot_dname_is_equal(rrset->owner, zone->apex->owner)) { + log_warning("DDNS, refusing to add NSEC3PARAM to non-apex node"); + *rcode = KNOT_RCODE_REFUSED; + return KNOT_EDENIED; + } else if (node_rrtype_exists(zone->apex, rrset->type)) { + log_warning("DDNS, refusing to add second NSEC3PARAM to zone apex"); + *rcode = KNOT_RCODE_REFUSED; + return KNOT_EDENIED; + } + } + if (rrset->rclass == knot_pkt_qclass(query)) { if (knot_rrtype_is_metatype(rrset->type)) { *rcode = KNOT_RCODE_FORMERR; @@ -605,13 +608,7 @@ static int process_rr(const knot_rrset_t *rr, zone_update_t *update) const zone_node_t *node = zone_update_get_node(update, rr->owner); if (is_addition(rr)) { - int ret = process_add(rr, node, update); - if (ret == KNOT_EOK) { - if (!sem_check(rr, node, update)) { - return KNOT_EDENIED; - } - } - return ret; + return process_add(rr, node, update); } else if (is_removal(rr)) { return process_remove(rr, node, update); } else { @@ -660,6 +657,27 @@ int ddns_process_prereqs(const knot_pkt_t *query, zone_update_t *update, return ret; } +int ddns_precheck_update(const knot_pkt_t *query, zone_update_t *update, + uint16_t *rcode) +{ + if (query == NULL || rcode == NULL || update == NULL) { + return KNOT_EINVAL; + } + + // Check all RRs in the authority section. + const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY); + const knot_rrset_t *authority_rr = (authority->count > 0) ? knot_pkt_rr(authority, 0) : NULL; + for (uint16_t i = 0; i < authority->count; ++i) { + int ret = check_update(&authority_rr[i], query, update->new_cont, rcode); + if (ret != KNOT_EOK) { + assert(*rcode != KNOT_RCODE_NOERROR); + return ret; + } + } + + return KNOT_EOK; +} + int ddns_process_update(const knot_pkt_t *query, zone_update_t *update, uint16_t *rcode) { @@ -677,18 +695,11 @@ int ddns_process_update(const knot_pkt_t *query, zone_update_t *update, const knot_rrset_t *authority_rr = (authority->count > 0) ? knot_pkt_rr(authority, 0) : NULL; for (uint16_t i = 0; i < authority->count; ++i) { const knot_rrset_t *rr = &authority_rr[i]; - // Check if RR is correct. - int ret = check_update(rr, query, rcode); - if (ret != KNOT_EOK) { - assert(*rcode != KNOT_RCODE_NOERROR); - return ret; - } - if (skip_soa(rr, sn_old)) { continue; } - ret = process_rr(rr, update); + int ret = process_rr(rr, update); if (ret != KNOT_EOK) { *rcode = ret_to_rcode(ret); return ret; diff --git a/src/knot/updates/ddns.h b/src/knot/updates/ddns.h index d5661f5..40f1b37 100644 --- a/src/knot/updates/ddns.h +++ b/src/knot/updates/ddns.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 @@ -32,6 +32,18 @@ int ddns_process_prereqs(const knot_pkt_t *query, zone_update_t *update, uint16_t *rcode); /*! + * \brief Performs a pre-check of the update'S sanity. + * + * \param query DNS message containing the update. + * \param update Zone to be checked. + * \param rcode Returned DNS RCODE. + * + * \return KNOT_E* + */ +int ddns_precheck_update(const knot_pkt_t *query, zone_update_t *update, + uint16_t *rcode); + +/*! * \brief Processes DNS update and creates a changeset out of it. Zone is left * intact. * diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index 73c9558..4b65a2c 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-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 @@ -19,8 +19,8 @@ #include <urcu.h> #include "knot/catalog/interpret.h" +#include "knot/common/dbus.h" #include "knot/common/log.h" -#include "knot/common/systemd.h" #include "knot/dnssec/zone-events.h" #include "knot/server/server.h" #include "knot/updates/zone-update.h" @@ -942,26 +942,10 @@ int zone_update_commit(conf_t *conf, zone_update_t *update) val = conf_zone_get(conf, C_DNSSEC_VALIDATION, update->zone->name); if (conf_bool(&val)) { bool incr_valid = update->flags & UPDATE_INCREMENTAL; - const char *msg_valid = incr_valid ? "incremental " : ""; - - ret = knot_dnssec_validate_zone(update, conf, 0, incr_valid); + ret = knot_dnssec_validate_zone(update, conf, 0, incr_valid, true); if (ret != KNOT_EOK) { - log_zone_error(update->zone->name, "DNSSEC, %svalidation failed (%s)", - msg_valid, knot_strerror(ret)); - char type_str[16]; - knot_dname_txt_storage_t name_str; - if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL && - knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) { - log_zone_error(update->zone->name, "DNSSEC, validation hint: %s %s", - name_str, type_str); - } discard_adds_tree(update); - if (conf->cache.srv_dbus_event & DBUS_EVENT_ZONE_INVALID) { - systemd_emit_zone_invalid(update->zone->name); - } return ret; - } else { - log_zone_info(update->zone->name, "DNSSEC, %svalidation successful", msg_valid); } } @@ -1023,8 +1007,8 @@ int zone_update_commit(conf_t *conf, zone_update_t *update) } if (conf->cache.srv_dbus_event & DBUS_EVENT_ZONE_UPDATED) { - systemd_emit_zone_updated(update->zone->name, - zone_contents_serial(update->zone->contents)); + dbus_emit_zone_updated(update->zone->name, + zone_contents_serial(update->zone->contents)); } memset(update, 0, sizeof(*update)); diff --git a/src/knot/updates/zone-update.h b/src/knot/updates/zone-update.h index 0499d72..814d0ec 100644 --- a/src/knot/updates/zone-update.h +++ b/src/knot/updates/zone-update.h @@ -25,7 +25,9 @@ typedef struct { knot_dname_storage_t next; const knot_dname_t *node; + uint32_t remaining_secs; uint16_t rrtype; + int warning; } dnssec_validation_hint_t; /*! \brief Structure for zone contents updating / querying. */ diff --git a/src/knot/zone/backup.c b/src/knot/zone/backup.c index 36fd577..5c3038a 100644 --- a/src/knot/zone/backup.c +++ b/src/knot/zone/backup.c @@ -85,6 +85,7 @@ int zone_backup_init(bool restore_mode, knot_backup_params_t filters, bool force ctx->restore_mode = restore_mode; ctx->backup_params = filters; ctx->in_backup = 0; // Just to be sure. + ctx->arch_match = true; ctx->forced = forced; ctx->backup_format = BACKUP_VERSION; ctx->backup_global = false; @@ -105,6 +106,11 @@ int zone_backup_init(bool restore_mode, knot_backup_params_t filters, bool force // For restore, check that there are all required data components in the backup. if (restore_mode) { + if (!ctx->arch_match && filters & BACKUP_PARAM_DB) { + free(ctx); + return KNOT_ECPUCOMPAT; + } + // '+kaspdb' in backup provides data also for '+keysonly' restore. knot_backup_params_t available = ctx->in_backup | ((bool)(ctx->in_backup & BACKUP_PARAM_KASPDB) * BACKUP_PARAM_KEYSONLY); @@ -369,7 +375,7 @@ static int backup_keystore(conf_t *conf, zone_t *zone, zone_backup_ctx_t *ctx) return ret; } if (backend_type == KEYSTORE_BACKEND_PKCS11) { - log_zone_notice(zone->name, "private keys from PKCS #11 aren't subject of backup/restore"); + log_zone_notice(zone->name, "private keys from PKCS #11 are not subject of backup/restore"); (void)dnssec_keystore_deinit(from); return KNOT_EOK; } diff --git a/src/knot/zone/backup.h b/src/knot/zone/backup.h index 9f2660d..ad0c954 100644 --- a/src/knot/zone/backup.h +++ b/src/knot/zone/backup.h @@ -57,6 +57,11 @@ typedef enum { BACKUP_PARAM_TIMERS | BACKUP_PARAM_KASPDB | \ BACKUP_PARAM_CATALOG) +/*! \bref Backup components using LMDB databases. */ +#define BACKUP_PARAM_DB (BACKUP_PARAM_JOURNAL | BACKUP_PARAM_TIMERS | \ + BACKUP_PARAM_KASPDB | BACKUP_PARAM_KEYSONLY | \ + BACKUP_PARAM_CATALOG) + typedef struct { const char *name; knot_backup_params_t param; @@ -70,6 +75,7 @@ typedef struct zone_backup_ctx { bool forced; // if true, the force flag has been set knot_backup_params_t backup_params; // bit-mapped list of backup components knot_backup_params_t in_backup; // bit-mapped list of components available in backup + bool arch_match; // match of the system and the backup architectures bool backup_global; // perform global backup for all zones ssize_t readers; // when decremented to 0, all zones done, free this context pthread_mutex_t readers_mutex; // mutex covering readers counter diff --git a/src/knot/zone/backup_dir.c b/src/knot/zone/backup_dir.c index 7bf9fd5..b382258 100644 --- a/src/knot/zone/backup_dir.c +++ b/src/knot/zone/backup_dir.c @@ -28,11 +28,21 @@ #include "contrib/getline.h" #include "knot/common/log.h" +#ifdef ENDIANITY_LITTLE + #define ENDIAN_STR "LE" +#else + #define ENDIAN_STR "BE" +#endif +#define _STR(x) #x +#define STR(x) _STR(x) +#define KNOT_ARCH STR(__LONG_WIDTH__) ENDIAN_STR + #define LABEL_FILE "knot_backup.label" #define LOCK_FILE "lock.knot_backup" #define LABEL_FILE_HEAD "label: Knot DNS Backup\n" #define LABEL_FILE_FORMAT "backup_format: %d\n" +#define LABEL_FILE_ARCH "architecture: " #define LABEL_FILE_PARAMS "parameters: " #define LABEL_FILE_BACKUPDIR "backupdir " #define LABEL_FILE_TIME_FORMAT "%Y-%m-%d %H:%M:%S %Z" @@ -49,6 +59,7 @@ static const char *label_file_name = LABEL_FILE; static const char *lock_file_name = LOCK_FILE; static const char *label_file_head = LABEL_FILE_HEAD; +static const char *label_file_arch = KNOT_ARCH; static void get_full_path(zone_backup_ctx_t *ctx, const char *filename, char *full_path, size_t full_path_size) @@ -125,6 +136,9 @@ static int make_label_file(zone_backup_ctx_t *ctx) localtime_r(&now, &tm); strftime(finished_time, sizeof(finished_time), LABEL_FILE_TIME_FORMAT, &tm); + int lmdb_major, lmdb_minor, lmdb_patch; + (void)mdb_version(&lmdb_major, &lmdb_minor, &lmdb_patch); + // Print the label contents. char params_str[PARAMS_MAX_LENGTH]; print_params(params_str, ctx->backup_params); @@ -135,10 +149,14 @@ static int make_label_file(zone_backup_ctx_t *ctx) "started_time: %s\n" "finished_time: %s\n" "knot_version: %s\n" + "lmdb_version: %d.%d.%d\n" + LABEL_FILE_ARCH "%s\n" LABEL_FILE_PARAMS "%s+" LABEL_FILE_BACKUPDIR "%s\n" "zone_count: %d\n", label_file_head, - ctx->backup_format, ident, started_time, finished_time, PACKAGE_VERSION, + ctx->backup_format, ident, started_time, finished_time, + PACKAGE_VERSION, + lmdb_major, lmdb_minor, lmdb_patch, label_file_arch, params_str, ctx->backup_dir, ctx->zone_count); @@ -199,6 +217,7 @@ static int get_backup_format(zone_backup_ctx_t *ctx) unsigned int remain = 3; // Bit-mapped "punch card" for lines to get data from. while (remain > 0 && knot_getline(&line, &line_size, file) != -1) { int value; + char str[8]; if (sscanf(line, LABEL_FILE_FORMAT, &value) != 0) { if (value >= BACKUP_FORMAT_TERM) { ret = KNOT_ENOTSUP; @@ -212,6 +231,11 @@ static int get_backup_format(zone_backup_ctx_t *ctx) continue; } } + if (sscanf(line, LABEL_FILE_ARCH "%7s\n", str) != 0 && + strcmp(str, label_file_arch) != 0) { + ctx->arch_match = false; + continue; + } if (strncmp(line, LABEL_FILE_PARAMS, sizeof(LABEL_FILE_PARAMS) - 1) == 0) { ctx->in_backup = parse_params(line + sizeof(LABEL_FILE_PARAMS) - 1); remain &= ~2; @@ -240,6 +264,22 @@ int backupdir_init(zone_backup_ctx_t *ctx) return KNOT_ENOTDIR; } } else { + if (ctx->forced) { + if (stat(ctx->backup_dir, &sb) == 0) { + int ret2 = remove_path(ctx->backup_dir, S_ISDIR(sb.st_mode)); + if (ret2 != KNOT_EOK) { + return ret2; + } + } else if (errno != ENOENT) { + return knot_map_errno(); + } else if (lstat(ctx->backup_dir, &sb) == 0 && S_ISLNK(sb.st_mode)) { + // Stale symlink. + if (unlink(ctx->backup_dir) != 0) { + return knot_map_errno(); + } + } // Omitting lstat() failure check, make_dir() will do it. + } + ret = make_dir(ctx->backup_dir, S_IRWXU | S_IRWXG, true); if (ret != KNOT_EOK) { return ret; diff --git a/src/knot/zone/contents.c b/src/knot/zone/contents.c index cba13e8..262136b 100644 --- a/src/knot/zone/contents.c +++ b/src/knot/zone/contents.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -106,6 +106,12 @@ static zone_node_t *get_nsec3_node(const zone_contents_t *zone, return zone_tree_get(zone->nsec3_nodes, name); } +// UBSAN type punning workaround +static zone_node_t *node_new_for_contents_wrap(const uint8_t *owner, void *contents) +{ + return node_new_for_contents(owner, contents); +} + static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n) { if (knot_rrset_empty(rr)) { @@ -114,7 +120,7 @@ static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n if (*n == NULL) { int ret = zone_tree_add_node(zone_contents_tree_for_rr(z, rr), z->apex, rr->owner, - (zone_tree_new_node_cb_t)node_new_for_contents, z, n); + node_new_for_contents_wrap, z, n); if (ret != KNOT_EOK) { return ret; } diff --git a/src/knot/zone/contents.h b/src/knot/zone/contents.h index 8f1f160..f344c97 100644 --- a/src/knot/zone/contents.h +++ b/src/knot/zone/contents.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -16,6 +16,7 @@ #pragma once +#include "contrib/time.h" #include "libdnssec/nsec.h" #include "libknot/rrtype/nsec3param.h" #include "knot/zone/node.h" @@ -38,6 +39,7 @@ typedef struct zone_contents { size_t size; uint32_t max_ttl; bool dnssec; + knot_time_t dnssec_expire; } zone_contents_t; /*! diff --git a/src/knot/zone/digest.c b/src/knot/zone/digest.c index b961f15..a4d50ce 100644 --- a/src/knot/zone/digest.c +++ b/src/knot/zone/digest.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 @@ -60,7 +60,7 @@ static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx } } - size_t buf_req = knot_rrset_size(rrset); + size_t buf_req = knot_rrset_size_estimate(rrset); if (buf_req > ctx->buf_size) { uint8_t *newbuf = realloc(ctx->buf, buf_req); if (newbuf == NULL) { @@ -71,7 +71,7 @@ static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx } int ret = knot_rrset_to_wire_extra(rrset, ctx->buf, ctx->buf_size, 0, - NULL, KNOT_PF_ORIGTTL | KNOT_PF_BUFENOUGH); + NULL, KNOT_PF_ORIGTTL); // cleanup apex RRSIGs mess if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) { diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c index 92ad29c..3d085d8 100644 --- a/src/knot/zone/semantic-check.c +++ b/src/knot/zone/semantic-check.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 @@ -29,6 +29,8 @@ static const char *error_messages[SEM_ERR_UNKNOWN + 1] = { [SEM_ERR_SOA_NONE] = "missing SOA at the zone apex", + [SEM_ERR_SOA_MULTIPLE] = + "multiple SOA records", [SEM_ERR_CNAME_EXTRA_RECORDS] = "another record exists beside CNAME", @@ -516,7 +518,7 @@ static sem_error_t err_dnssec2sem(int ret, uint16_t rrtype, char *info, size_t l static int verify_dnssec(zone_contents_t *zone, sem_handler_t *handler, time_t time) { zone_update_t fake_up = { .new_cont = zone, }; - int ret = knot_dnssec_validate_zone(&fake_up, NULL, time, false); + int ret = knot_dnssec_validate_zone(&fake_up, NULL, time, false, false); if (fake_up.validation_hint.node != NULL) { // validation found an issue char info[64] = ""; sem_error_t err = err_dnssec2sem(ret, fake_up.validation_hint.rrtype, info, sizeof(info)); diff --git a/src/knot/zone/semantic-check.h b/src/knot/zone/semantic-check.h index 230c709..a0b1d21 100644 --- a/src/knot/zone/semantic-check.h +++ b/src/knot/zone/semantic-check.h @@ -35,6 +35,7 @@ typedef enum { typedef enum { // Mandatory checks. SEM_ERR_SOA_NONE, + SEM_ERR_SOA_MULTIPLE, SEM_ERR_CNAME_EXTRA_RECORDS, SEM_ERR_CNAME_MULTIPLE, diff --git a/src/knot/zone/zone-tree.c b/src/knot/zone/zone-tree.c index 87dde18..c3f79e7 100644 --- a/src/knot/zone/zone-tree.c +++ b/src/knot/zone/zone-tree.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 @@ -241,7 +241,7 @@ int zone_tree_add_node(zone_tree_t *tree, zone_node_t *apex, const knot_dname_t return ret; } zone_node_t *parent = NULL; - ret = zone_tree_add_node(tree, apex, knot_wire_next_label(dname, NULL), new_cb, new_cb_ctx, &parent); + ret = zone_tree_add_node(tree, apex, knot_dname_next_label(dname), new_cb, new_cb_ctx, &parent); if (ret != KNOT_EOK) { return ret; } diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c index 7c84202..06a8a90 100644 --- a/src/knot/zone/zone.c +++ b/src/knot/zone/zone.c @@ -180,6 +180,7 @@ zone_t* zone_new(const knot_dname_t *name) zone->ddns_queue_size = 0; init_list(&zone->ddns_queue); + pthread_mutex_init(&zone->cu_lock, NULL); knot_sem_init(&zone->cow_lock, 1); // Preferred master lock @@ -222,6 +223,7 @@ void zone_free(zone_t **zone_ptr) free_ddns_queue(zone); pthread_mutex_destroy(&zone->ddns_lock); + pthread_mutex_destroy(&zone->cu_lock); knot_sem_destroy(&zone->cow_lock); /* Control update. */ @@ -273,6 +275,12 @@ void zone_reset(conf_t *conf, zone_t *zone) } \ } +// UBSAN type punning workaround +static bool dname_cmp_sweep_wrap(const uint8_t *zone, void *data) +{ + return knot_dname_cmp((const knot_dname_t *)zone, (const knot_dname_t *)data) != 0; +} + int selective_zone_purge(conf_t *conf, zone_t *zone, purge_flag_t params) { if (conf == NULL || zone == NULL) { @@ -291,7 +299,7 @@ int selective_zone_purge(conf_t *conf, zone_t *zone, purge_flag_t params) zone_timers_sanitize(conf, zone); zone->zonefile.bootstrap_cnt = 0; ret = zone_timers_sweep(&zone->server->timerdb, - (sweep_cb)knot_dname_cmp, zone->name); + dname_cmp_sweep_wrap, zone->name); RETURN_IF_FAILED("timers", KNOT_ENOENT); } diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index 0527c85..a6046de 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.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 @@ -16,6 +16,7 @@ #pragma once +#include "contrib/atomic.h" #include "contrib/semaphore.h" #include "knot/catalog/catalog_update.h" #include "knot/conf/conf.h" @@ -114,6 +115,7 @@ typedef struct zone /*! \brief Control update context. */ struct zone_update *control_update; + pthread_mutex_t cu_lock; /*! \brief Ensue one COW transaction on zone's trees at a time. */ knot_sem_t cow_lock; @@ -122,7 +124,7 @@ typedef struct zone struct server *server; /*! \brief Zone backup context (NULL unless backup pending). */ - struct zone_backup_ctx *backup_ctx; + knot_atomic_ptr_t backup_ctx; /*! \brief Catalog-generate feature. */ knot_dname_t *catalog_gen; diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c index d8acd0b..52d9b0f 100644 --- a/src/knot/zone/zonedb-load.c +++ b/src/knot/zone/zonedb-load.c @@ -510,7 +510,7 @@ static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server, reload_t mod if (forw == NULL) { knot_dname_txt_storage_t forw_str; (void)knot_dname_to_str(forw_str, forw_name, sizeof(forw_str)); - log_zone_warning(z->name, "zone to reverse %s doesn't exist", + log_zone_warning(z->name, "zone to reverse %s does not exist", forw_str); } else { z->reverse_from = forw; @@ -587,6 +587,12 @@ catalog_only: } } +// UBSAN type punning workaround +static void zone_contents_deep_free_wrap(void *contents) +{ + zone_contents_deep_free((zone_contents_t *)contents); +} + void zonedb_reload(conf_t *conf, server_t *server, reload_t mode) { if (conf == NULL || server == NULL) { @@ -625,7 +631,7 @@ void zonedb_reload(conf_t *conf, server_t *server, reload_t mode) /* Wait for readers to finish reading old zone database. */ synchronize_rcu(); - ptrlist_free_custom(&contents_tofree, NULL, (ptrlist_free_cb)zone_contents_deep_free); + ptrlist_free_custom(&contents_tofree, NULL, zone_contents_deep_free_wrap); /* Remove old zone DB. */ remove_old_zonedb(conf, db_old, server, mode); diff --git a/src/knot/zone/zonedb.c b/src/knot/zone/zonedb.c index 98cade5..b0388c7 100644 --- a/src/knot/zone/zonedb.c +++ b/src/knot/zone/zonedb.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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 @@ -153,7 +153,7 @@ zone_t *knot_zonedb_find_suffix(knot_zonedb_t *db, const knot_dname_t *zone_name return NULL; } - zone_name = knot_wire_next_label(zone_name, NULL); + zone_name = knot_dname_next_label(zone_name); } } diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c index ed075b6..9d0bf97 100644 --- a/src/knot/zone/zonefile.c +++ b/src/knot/zone/zonefile.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 @@ -81,12 +81,6 @@ int zcreator_step(zcreator_t *zc, const knot_rrset_t *rr) return KNOT_EINVAL; } - if (rr->type == KNOT_RRTYPE_SOA && - node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_SOA)) { - // Ignore extra SOA - return KNOT_EOK; - } - zone_node_t *node = NULL; int ret = zone_contents_add_rr(zc->z, rr, &node); if (ret != KNOT_EOK) { @@ -219,10 +213,11 @@ zone_contents_t *zonefile_load(zloader_t *loader) goto fail; } - if (!node_rrtype_exists(loader->creator->z->apex, KNOT_RRTYPE_SOA)) { + knot_rdataset_t *soa = node_rdataset(zc->z->apex, KNOT_RRTYPE_SOA); + if (soa == NULL || soa->count != 1) { + sem_error_t code = (soa == NULL) ? SEM_ERR_SOA_NONE : SEM_ERR_SOA_MULTIPLE; loader->err_handler->error = true; - loader->err_handler->cb(loader->err_handler, zc->z, NULL, - SEM_ERR_SOA_NONE, NULL); + loader->err_handler->cb(loader->err_handler, zc->z, NULL, code, NULL); goto fail; } diff --git a/src/libdnssec/key/algorithm.c b/src/libdnssec/key/algorithm.c index a9bc3ee..d242442 100644 --- a/src/libdnssec/key/algorithm.c +++ b/src/libdnssec/key/algorithm.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -93,11 +93,9 @@ gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec) return GNUTLS_PK_RSA; case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: - return GNUTLS_PK_EC; -#ifdef HAVE_ED25519 + return GNUTLS_PK_ECDSA; case DNSSEC_KEY_ALGORITHM_ED25519: return GNUTLS_PK_EDDSA_ED25519; -#endif #ifdef HAVE_ED448 case DNSSEC_KEY_ALGORITHM_ED448: return GNUTLS_PK_EDDSA_ED448; @@ -119,11 +117,7 @@ bool dnssec_algorithm_reproducible(dnssec_key_algorithm_t algorithm, bool enable return true; // those are always reproducible case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: -#ifdef HAVE_GNUTLS_REPRODUCIBLE - return enabled; // Reproducible only if GnuTLS supports && enabled -#else - return false; -#endif + return enabled; // reproducible only if GnuTLS supports && enabled default: return false; } diff --git a/src/libdnssec/key/convert.c b/src/libdnssec/key/convert.c index 56168f7..d06c25e 100644 --- a/src/libdnssec/key/convert.c +++ b/src/libdnssec/key/convert.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -104,20 +104,16 @@ static size_t ecdsa_curve_point_size(gnutls_ecc_curve_t curve) } } -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static size_t eddsa_curve_point_size(gnutls_ecc_curve_t curve) { switch (curve) { -#ifdef HAVE_ED25519 case GNUTLS_ECC_CURVE_ED25519: return 32; -#endif #ifdef HAVE_ED448 case GNUTLS_ECC_CURVE_ED448: return 57; #endif default: return 0; } } -#endif /*! * Convert ECDSA public key to DNSSEC format. @@ -157,7 +153,6 @@ static int ecdsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) /*! * Convert EDDSA public key to DNSSEC format. */ -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static int eddsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) { assert(key); @@ -187,7 +182,6 @@ static int eddsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) return DNSSEC_EOK; } -#endif /* -- crypto to DNSSEC ------------------------------------------------------*/ @@ -248,20 +242,16 @@ static gnutls_ecc_curve_t ecdsa_curve_from_rdata_size(size_t rdata_size) /*! * Get EDDSA curve based on DNSKEY RDATA size. */ -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static gnutls_ecc_curve_t eddsa_curve_from_rdata_size(size_t rdata_size) { switch (rdata_size) { -#ifdef HAVE_ED25519 case 32: return GNUTLS_ECC_CURVE_ED25519; -#endif #ifdef HAVE_ED448 case 57: return GNUTLS_ECC_CURVE_ED448; #endif default: return GNUTLS_ECC_CURVE_INVALID; } } -#endif /*! * Convert ECDSA key in DNSSEC format to crypto key. @@ -296,7 +286,6 @@ static int ecdsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t k /*! * Convert EDDSA key in DNSSEC format to crypto key. */ -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static int eddsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) { assert(rdata); @@ -320,7 +309,6 @@ static int eddsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t k return DNSSEC_EOK; } -#endif /* -- internal API --------------------------------------------------------- */ @@ -339,10 +327,8 @@ int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata) switch ((gnutls_pk_algorithm_t)algorithm) { case GNUTLS_PK_RSA: return rsa_pubkey_to_rdata(key, rdata); - case GNUTLS_PK_EC: return ecdsa_pubkey_to_rdata(key, rdata); -#ifdef HAVE_ED25519 + case GNUTLS_PK_ECDSA: return ecdsa_pubkey_to_rdata(key, rdata); case GNUTLS_PK_EDDSA_ED25519: return eddsa_pubkey_to_rdata(key, rdata); -#endif #ifdef HAVE_ED448 case GNUTLS_PK_EDDSA_ED448: return eddsa_pubkey_to_rdata(key, rdata); #endif @@ -363,10 +349,8 @@ int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata, switch(gnutls_alg) { case GNUTLS_PK_RSA: return rsa_rdata_to_pubkey(rdata, key); - case GNUTLS_PK_EC: return ecdsa_rdata_to_pubkey(rdata, key); -#ifdef HAVE_ED25519 + case GNUTLS_PK_ECDSA: return ecdsa_rdata_to_pubkey(rdata, key); case GNUTLS_PK_EDDSA_ED25519: return eddsa_rdata_to_pubkey(rdata, key); -#endif #ifdef HAVE_ED448 case GNUTLS_PK_EDDSA_ED448: return eddsa_rdata_to_pubkey(rdata, key); #endif diff --git a/src/libdnssec/pem.c b/src/libdnssec/pem.c index fa463f6..41fd855 100644 --- a/src/libdnssec/pem.c +++ b/src/libdnssec/pem.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -130,29 +130,10 @@ int dnssec_pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem) static int privkey_export_x509(gnutls_privkey_t key, gnutls_x509_privkey_t *_key) { -#ifdef HAVE_EXPORT_X509 if (gnutls_privkey_export_x509(key, _key) != GNUTLS_E_SUCCESS) { return DNSSEC_KEY_EXPORT_ERROR; } -#else // Needed for GnuTLS < 3.4.0 (CentOS 7) - struct privkey { // Extracted needed items only! - gnutls_privkey_type_t type; - gnutls_pk_algorithm_t pk_algorithm; - gnutls_x509_privkey_t x509; - }; - struct privkey *pkey = (struct privkey *)key; - assert(pkey->type == GNUTLS_PRIVKEY_X509); - - if (gnutls_x509_privkey_init(_key) != GNUTLS_E_SUCCESS) { - return DNSSEC_KEY_EXPORT_ERROR; - } - - if (gnutls_x509_privkey_cpy(*_key, pkey->x509) != GNUTLS_E_SUCCESS) { - gnutls_x509_privkey_deinit(*_key); - return DNSSEC_KEY_EXPORT_ERROR; - } -#endif return DNSSEC_EOK; } diff --git a/src/libdnssec/sign/sign.c b/src/libdnssec/sign/sign.c index 3a7bcba..727f650 100644 --- a/src/libdnssec/sign/sign.c +++ b/src/libdnssec/sign/sign.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -202,34 +202,6 @@ static const algorithm_functions_t *get_functions(const dnssec_key_t *key) } } -#ifndef HAVE_SIGN_DATA2 -/*! - * Get digest algorithm used with a given key. - */ -static gnutls_digest_algorithm_t get_digest_algorithm(const dnssec_key_t *key) -{ - uint8_t algorithm = dnssec_key_get_algorithm(key); - - switch ((dnssec_key_algorithm_t)algorithm) { - case DNSSEC_KEY_ALGORITHM_RSA_SHA1: - case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: - return GNUTLS_DIG_SHA1; - case DNSSEC_KEY_ALGORITHM_RSA_SHA256: - case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: - return GNUTLS_DIG_SHA256; - case DNSSEC_KEY_ALGORITHM_RSA_SHA512: - return GNUTLS_DIG_SHA512; - case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: - return GNUTLS_DIG_SHA384; - case DNSSEC_KEY_ALGORITHM_ED25519: - case DNSSEC_KEY_ALGORITHM_ED448: - return GNUTLS_DIG_SHA512; - default: - return GNUTLS_DIG_UNKNOWN; - } -} -#endif - static gnutls_sign_algorithm_t algo_dnssec2gnutls(dnssec_key_algorithm_t algorithm) { switch (algorithm) { @@ -244,10 +216,8 @@ static gnutls_sign_algorithm_t algo_dnssec2gnutls(dnssec_key_algorithm_t algorit return GNUTLS_SIGN_RSA_SHA512; case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: return GNUTLS_SIGN_ECDSA_SHA384; -#ifdef HAVE_ED25519 case DNSSEC_KEY_ALGORITHM_ED25519: return GNUTLS_SIGN_EDDSA_ED25519; -#endif #ifdef HAVE_ED448 case DNSSEC_KEY_ALGORITHM_ED448: return GNUTLS_SIGN_EDDSA_ED448; @@ -356,24 +326,15 @@ int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_sign_flags_t flags, dnssec_ }; unsigned gnutls_flags = 0; -#ifdef HAVE_GNUTLS_REPRODUCIBLE if (flags & DNSSEC_SIGN_REPRODUCIBLE) { gnutls_flags |= GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE; } -#endif assert(ctx->key->private_key); _cleanup_datum_ gnutls_datum_t raw = { 0 }; -#ifdef HAVE_SIGN_DATA2 int result = gnutls_privkey_sign_data2(ctx->key->private_key, ctx->sign_algorithm, gnutls_flags, &data, &raw); -#else - gnutls_digest_algorithm_t digest_algorithm = get_digest_algorithm(ctx->key); - int result = gnutls_privkey_sign_data(ctx->key->private_key, - digest_algorithm, - gnutls_flags, &data, &raw); -#endif if (result < 0) { return DNSSEC_SIGN_ERROR; } diff --git a/src/libdnssec/version.h b/src/libdnssec/version.h index e72e2bd..cd5bad2 100644 --- a/src/libdnssec/version.h +++ b/src/libdnssec/version.h @@ -17,8 +17,8 @@ #pragma once #define DNSSEC_VERSION_MAJOR 3 -#define DNSSEC_VERSION_MINOR 3 -#define DNSSEC_VERSION_PATCH 0x09 +#define DNSSEC_VERSION_MINOR 4 +#define DNSSEC_VERSION_PATCH 0x00 #define DNSSEC_VERSION_HEX ((DNSSEC_VERSION_MAJOR << 16) | \ (DNSSEC_VERSION_MINOR << 8) | \ diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc index f62d836..d09ff55 100755 --- a/src/libknot/Makefile.inc +++ b/src/libknot/Makefile.inc @@ -37,6 +37,8 @@ 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 \ libknot/rrset-dump.h \ @@ -78,6 +80,8 @@ 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 \ libknot/rrset.c \ diff --git a/src/libknot/attribute.h b/src/libknot/attribute.h index 525aef3..ce464fc 100644 --- a/src/libknot/attribute.h +++ b/src/libknot/attribute.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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,17 +35,19 @@ /*! \brief GNU C function attributes. */ #if __GNUC__ >= 3 -#define _pure_ __attribute__ ((pure)) -#define _const_ __attribute__ ((const)) -#define _noreturn_ __attribute__ ((noreturn)) -#define _malloc_ __attribute__ ((malloc)) -#define _mustcheck_ __attribute__ ((warn_unused_result)) +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _noreturn_ __attribute__ ((noreturn)) +#define _malloc_ __attribute__ ((malloc)) +#define _mustcheck_ __attribute__ ((warn_unused_result)) +#define _nonnull_(...) __attribute__ ((nonnull(__VA_ARGS__))) #else #define _pure_ #define _const_ #define _noreturn_ #define _malloc_ #define _mustcheck_ +#define _nonnull_ #endif /*! @} */ diff --git a/src/libknot/control/control.c b/src/libknot/control/control.c index 671896f..8cddd5d 100644 --- a/src/libknot/control/control.c +++ b/src/libknot/control/control.c @@ -36,9 +36,6 @@ #define CTL_BUFF_SIZE (256 * 1024) #endif -/*! Listen backlog size. */ -#define DEFAULT_LISTEN_BACKLOG 5 - /*! Default socket operations timeout in milliseconds. */ #define DEFAULT_TIMEOUT (30 * 1000) @@ -166,6 +163,18 @@ knot_ctl_t* knot_ctl_alloc(void) } _public_ +knot_ctl_t* knot_ctl_clone(knot_ctl_t *ctx) +{ + knot_ctl_t *res = knot_ctl_alloc(); + if (res != NULL) { + res->timeout = ctx->timeout; + res->sock = ctx->sock; + ctx->sock = -1; + } + return res; +} + +_public_ void knot_ctl_free(knot_ctl_t *ctx) { if (ctx == NULL) { @@ -194,13 +203,7 @@ void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms) } _public_ -int knot_ctl_bind(knot_ctl_t *ctx, const char *path) -{ - return knot_ctl_bind2(ctx, path, DEFAULT_LISTEN_BACKLOG); -} - -_public_ -int knot_ctl_bind2(knot_ctl_t *ctx, const char *path, unsigned backlog) +int knot_ctl_bind(knot_ctl_t *ctx, const char *path, unsigned backlog) { if (ctx == NULL || path == NULL) { return KNOT_EINVAL; diff --git a/src/libknot/control/control.h b/src/libknot/control/control.h index 8ab1e10..f110e3f 100644 --- a/src/libknot/control/control.h +++ b/src/libknot/control/control.h @@ -65,6 +65,18 @@ typedef struct knot_ctl knot_ctl_t; knot_ctl_t* knot_ctl_alloc(void); /*! + * \brief Allocates a control context based on an existing one. + * + * \param[in,out] ctx Original control context. + * + * \note The listen_sock is kept at the original, the current sock is taken + * by the clone and RESET in the original! + * + * \return Control context or NULL. + */ +knot_ctl_t* knot_ctl_clone(knot_ctl_t *ctx); + +/*! * Deallocates a control context. * * \param[in] ctx Control context. @@ -86,17 +98,13 @@ void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms); * * \note Server operation. * - * \param[in] ctx Control context. - * \param[in] path Control UNIX socket path. + * \param[in] ctx Control context. + * \param[in] path Control UNIX socket path. + * \param[in] backlog Socket listen backlog size. * * \return Error code, KNOT_EOK if successful. */ -int knot_ctl_bind(knot_ctl_t *ctx, const char *path); - -/*! - * Same as knot_ctl_bind() with socket backlog specification. - */ -int knot_ctl_bind2(knot_ctl_t *ctx, const char *path, unsigned backlog); +int knot_ctl_bind(knot_ctl_t *ctx, const char *path, unsigned backlog); /*! * Unbinds a control socket. diff --git a/src/libknot/dname.c b/src/libknot/dname.c index 31b8a5f..d166f8d 100644 --- a/src/libknot/dname.c +++ b/src/libknot/dname.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 @@ -62,11 +62,11 @@ static int dname_align(const uint8_t **d1, uint8_t d1_labels, assert(d1 && d2); for (unsigned j = d1_labels; j < d2_labels; ++j) { - *d2 = knot_wire_next_label(*d2, NULL); + *d2 = knot_dname_next_label(*d2); } for (unsigned j = d2_labels; j < d1_labels; ++j) { - *d1 = knot_wire_next_label(*d1, NULL); + *d1 = knot_dname_next_label(*d1); } return (d1_labels < d2_labels) ? d1_labels : d2_labels; @@ -190,7 +190,7 @@ _public_ int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src, size_t maxlen, const uint8_t *pkt) { - if (dst == NULL || src == NULL) { + if (dst == NULL || src == NULL || pkt == NULL) { return KNOT_EINVAL; } @@ -533,7 +533,7 @@ size_t knot_dname_size(const knot_dname_t *name) _public_ size_t knot_dname_realsize(const knot_dname_t *name, const uint8_t *pkt) { - if (name == NULL) { + if (name == NULL || pkt == NULL) { return 0; } @@ -573,8 +573,8 @@ size_t knot_dname_matched_labels(const knot_dname_t *d1, const knot_dname_t *d2) } /* Next label. */ - d1 = knot_wire_next_label(d1, NULL); - d2 = knot_wire_next_label(d2, NULL); + d1 = knot_dname_next_label(d1); + d2 = knot_dname_next_label(d2); --common; } @@ -596,7 +596,7 @@ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned label } size_t prefix_lbs = dname_lbs - labels; - size_t prefix_len = knot_dname_prefixlen(name, prefix_lbs, NULL); + size_t prefix_len = knot_dname_prefixlen(name, prefix_lbs); size_t suffix_len = knot_dname_size(suffix); if (prefix_len == 0 || suffix_len == 0) { return NULL; @@ -614,7 +614,7 @@ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned label while (prefix_lbs > 0) { memcpy(dst, name, *name + 1); dst += *name + 1; - name = knot_wire_next_label(name, NULL); + name = knot_dname_next_label(name); --prefix_lbs; } @@ -622,7 +622,7 @@ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned label while (*suffix != '\0') { memcpy(dst, suffix, *suffix + 1); dst += *suffix + 1; - suffix = knot_wire_next_label(suffix, NULL); + suffix = knot_dname_next_label(suffix); } *dst = '\0'; @@ -684,8 +684,8 @@ inline static bool dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2 while (*d1 != '\0' || *d2 != '\0') { if (label_is_equal(d1, d2, no_case)) { - d1 = knot_wire_next_label(d1, NULL); - d2 = knot_wire_next_label(d2, NULL); + d1 = knot_dname_next_label(d1); + d2 = knot_dname_next_label(d2); } else { return false; } @@ -707,7 +707,7 @@ bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2) } _public_ -size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt) +size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels) { if (name == NULL) { return 0; @@ -718,13 +718,10 @@ size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t return 0; } - /* Seek first real label occurrence. */ - name = knot_wire_seek_label(name, pkt); - size_t len = 0; while (*name != '\0') { len += *name + 1; - name = knot_wire_next_label(name, pkt); + name = knot_dname_next_label(name); if (--nlabels == 0) { /* Count N first labels only. */ break; } @@ -743,7 +740,8 @@ size_t knot_dname_labels(const uint8_t *name, const uint8_t *pkt) size_t count = 0; while (*name != '\0') { ++count; - name = knot_wire_next_label(name, pkt); + name = (pkt == NULL) ? knot_dname_next_label(name) : + knot_wire_next_label(name, pkt); if (name == NULL) { return 0; } @@ -794,7 +792,7 @@ int knot_dname_in_bailiwick(const knot_dname_t *name, const knot_dname_t *bailiw } for (int i = 0; i < label_diff; ++i) { - name = knot_wire_next_label(name, NULL); + name = knot_dname_next_label(name); } return knot_dname_is_equal(name, bailiwick) ? label_diff : KNOT_EOUTOFZONE; diff --git a/src/libknot/dname.h b/src/libknot/dname.h index 5733de9..6d72f90 100644 --- a/src/libknot/dname.h +++ b/src/libknot/dname.h @@ -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 @@ -48,7 +48,7 @@ typedef char knot_dname_txt_storage_t[KNOT_DNAME_TXT_MAXLEN + 1]; * * \param name Name on the wire. * \param endp Name boundary. - * \param pkt Wire. + * \param pkt Wire (can be NULL if not compressed). * * \retval (compressed) size of the domain name. * \retval KNOT_EINVAL @@ -195,7 +195,7 @@ size_t knot_dname_size(const knot_dname_t *name); * \brief Returns full size of the given domain name (expanded compression ptrs). * * \param name Domain name to get the size of. - * \param pkt Related packet (or NULL if unpacked) + * \param pkt Related packet. * * \retval size of the domain name. * \retval 0 if invalid argument. @@ -295,12 +295,11 @@ bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2); * * \param name Domain name. * \param nlabels First N labels. - * \param pkt Related packet (or NULL if not compressed). * * \return Length of the prefix. */ _pure_ -size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt); +size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels); /*! * \brief Return number of labels in the domain name. diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h index 6ec4a94..3ee326c 100644 --- a/src/libknot/errcode.h +++ b/src/libknot/errcode.h @@ -110,6 +110,7 @@ enum knot_error { KNOT_EBADCERTKEY, KNOT_EFACCES, KNOT_EBACKUPDATA, + KNOT_ECPUCOMPAT, KNOT_GENERAL_ERROR = -900, @@ -121,6 +122,7 @@ enum knot_error { KNOT_NET_EADDR, KNOT_NET_ESOCKET, KNOT_NET_ECONNECT, + KNOT_NET_EHSHAKE, KNOT_NET_ESEND, KNOT_NET_ERECV, KNOT_NET_ETIMEOUT, @@ -176,6 +178,7 @@ enum knot_error { KNOT_NO_PUBLIC_KEY, KNOT_NO_PRIVATE_KEY, KNOT_NO_READY_KEY, + KNOT_ESOON_EXPIRE, KNOT_DNSSEC_EKEYTAG_LIMIT, KNOT_DNSSEC_EXTRA_NSEC, diff --git a/src/libknot/error.c b/src/libknot/error.c index ae9b973..59f0a7a 100644 --- a/src/libknot/error.c +++ b/src/libknot/error.c @@ -109,6 +109,7 @@ static const struct error errors[] = { { KNOT_EBADCERTKEY, "unknown certificate key" }, { KNOT_EFACCES, "file permission denied" }, { KNOT_EBACKUPDATA, "requested data not in backup" }, + { KNOT_ECPUCOMPAT, "incompatible CPU architecture" }, { KNOT_GENERAL_ERROR, "unknown general error" }, @@ -120,6 +121,7 @@ static const struct error errors[] = { { KNOT_NET_EADDR, "bad address or host name" }, { KNOT_NET_ESOCKET, "can't create socket" }, { KNOT_NET_ECONNECT, "can't connect" }, + { KNOT_NET_EHSHAKE, "handshake failed" }, { KNOT_NET_ESEND, "can't send data" }, { KNOT_NET_ERECV, "can't receive data" }, { KNOT_NET_ETIMEOUT, "network timeout" }, @@ -175,6 +177,7 @@ static const struct error errors[] = { { KNOT_NO_PUBLIC_KEY, "no public key" }, { KNOT_NO_PRIVATE_KEY, "no private key" }, { KNOT_NO_READY_KEY, "no key ready for submission" }, + { KNOT_ESOON_EXPIRE, "oncoming RRSIG expiration" }, { KNOT_DNSSEC_EKEYTAG_LIMIT, "many keys with equal keytag" }, { KNOT_DNSSEC_EXTRA_NSEC, "superfluous NSEC(3)" }, diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h index 383f55e..da69c8c 100644 --- a/src/libknot/packet/pkt.h +++ b/src/libknot/packet/pkt.h @@ -52,7 +52,6 @@ enum { KNOT_PF_NOCANON = 1 << 5, /*!< Don't canonicalize rrsets during parsing. */ KNOT_PF_ORIGTTL = 1 << 6, /*!< Write RRSIGs with their original TTL. */ KNOT_PF_SOAMINTTL = 1 << 7, /*!< Write SOA with its minimum-ttl as TTL. */ - KNOT_PF_BUFENOUGH = 1 << 8, /*!< The output buffer is big enough for the output. */ }; typedef struct knot_pkt knot_pkt_t; diff --git a/src/libknot/packet/rrset-wire.c b/src/libknot/packet/rrset-wire.c index ef3a068..1d4f78e 100644 --- a/src/libknot/packet/rrset-wire.c +++ b/src/libknot/packet/rrset-wire.c @@ -73,6 +73,7 @@ static bool dname_equal_wire(const knot_dname_t *d1, const knot_dname_t *d2, { assert(d1); assert(d2); + assert(wire); d2 = knot_wire_seek_label(d2, wire); @@ -80,7 +81,7 @@ static bool dname_equal_wire(const knot_dname_t *d1, const knot_dname_t *d2, if (!label_is_equal(d1, d2)) { return false; } - d1 = knot_wire_next_label(d1, NULL); + d1 = knot_dname_next_label(d1); d2 = knot_wire_next_label(d2, wire); } @@ -170,7 +171,7 @@ static int write_rdata_naptr_header(const uint8_t **src, size_t *src_avail, written += (len); \ } -#define CHECK_NEXT_LABEL(res) \ +#define CHECK_WIRE_NEXT_LABEL(res) \ if (res == NULL) { return KNOT_EINVAL; } /*! @@ -201,7 +202,7 @@ static int compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max int suffix_labels = compr->suffix.labels; while (suffix_labels > name_labels) { suffix = knot_wire_next_label(suffix, compr->wire); - CHECK_NEXT_LABEL(suffix); + CHECK_WIRE_NEXT_LABEL(suffix); --suffix_labels; } @@ -210,8 +211,7 @@ static int compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max uint16_t written = 0; while (name_labels > suffix_labels) { WRITE_LABEL(dst, written, dname, max, (*dname + 1)); - dname = knot_wire_next_label(dname, NULL); - CHECK_NEXT_LABEL(dname); + dname = knot_dname_next_label(dname); --name_labels; } @@ -221,10 +221,9 @@ static int compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max const knot_dname_t *compr_ptr = suffix; while (dname[0] != '\0') { // Next labels. - const knot_dname_t *next_dname = knot_wire_next_label(dname, NULL); - CHECK_NEXT_LABEL(next_dname); + const knot_dname_t *next_dname = knot_dname_next_label(dname); const knot_dname_t *next_suffix = knot_wire_next_label(suffix, compr->wire); - CHECK_NEXT_LABEL(next_suffix); + CHECK_WIRE_NEXT_LABEL(next_suffix); // Two labels match, extend suffix length. if (!label_is_equal(dname, suffix)) { @@ -495,7 +494,7 @@ static int write_rr(const knot_rrset_t *rrset, uint16_t rrset_index, _public_ int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire, - uint16_t max_size, uint16_t rotate, + uint32_t max_size, uint16_t rotate, knot_compr_t *compr, uint16_t flags) { if (rrset == NULL || wire == NULL) { @@ -511,11 +510,6 @@ int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire, uint8_t *write = wire; size_t capacity = max_size; - // FIXME remove this and make the max_size parameter uint32_t in next major libknot release! - if ((flags & KNOT_PF_BUFENOUGH)) { - capacity = SIZE_MAX; - } - uint16_t count = rrset->rrs.count; knot_rdata_t *rdata = rotate > 1 ? knot_rdataset_at(&rrset->rrs, rotate - 1) : rrset->rrs.rdata; for (int i = rotate; i < count + rotate; i++) { diff --git a/src/libknot/packet/rrset-wire.h b/src/libknot/packet/rrset-wire.h index 3be0cba..3247df8 100644 --- a/src/libknot/packet/rrset-wire.h +++ b/src/libknot/packet/rrset-wire.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -41,12 +41,12 @@ * \return Output size, negative number on error (KNOT_E*). */ int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire, - uint16_t max_size, uint16_t rotate, + uint32_t max_size, uint16_t rotate, knot_compr_t *compr, uint16_t flags); /*! \brief Same as knot_rrset_to_wire_extra but without rrset rotation and flags. */ static inline int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, - uint16_t max_size, knot_compr_t *compr) + uint32_t max_size, knot_compr_t *compr) { return knot_rrset_to_wire_extra(rrset, wire, max_size, 0, compr, 0); } diff --git a/src/libknot/packet/wire.h b/src/libknot/packet/wire.h index 630cd83..cff4b21 100644 --- a/src/libknot/packet/wire.h +++ b/src/libknot/packet/wire.h @@ -1025,13 +1025,11 @@ static inline uint16_t knot_wire_get_pointer(const uint8_t *pos) return (knot_wire_read_u16(pos) - KNOT_WIRE_PTR_BASE); // Return offset. } -_pure_ _mustcheck_ +_pure_ _mustcheck_ _nonnull_(2) static inline const uint8_t *knot_wire_seek_label(const uint8_t *lp, const uint8_t *wire) { + assert(wire); while (knot_wire_is_pointer(lp)) { - if (!wire) { - return NULL; - } const uint8_t *new_lp = wire + knot_wire_get_pointer(lp); if (new_lp >= lp) { assert(0); @@ -1042,12 +1040,21 @@ static inline const uint8_t *knot_wire_seek_label(const uint8_t *lp, const uint8 return lp; } -_pure_ _mustcheck_ +_pure_ _mustcheck_ _nonnull_(1, 2) static inline const uint8_t *knot_wire_next_label(const uint8_t *lp, const uint8_t *wire) { - if (!lp || !lp[0]) /* No label after final label. */ - return NULL; + assert(lp); + assert(lp[0] > 0); // Not a terminal label. return knot_wire_seek_label(lp + (lp[0] + sizeof(uint8_t)), wire); } +_pure_ _mustcheck_ _nonnull_(1) +static inline const uint8_t *knot_dname_next_label(const uint8_t *lp) +{ + assert(lp); + assert(lp[0] > 0); // Not a terminal label. + assert(!knot_wire_is_pointer(lp)); + return lp + (lp[0] + sizeof(uint8_t)); +} + /*! @} */ diff --git a/src/libknot/quic/quic.c b/src/libknot/quic/quic.c index f9d1d1d..4eb84c3 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; @@ -153,223 +139,6 @@ session_free: return ret; } -static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time, - const gnutls_datum_t *key, - const gnutls_datum_t *data) -{ - return 0; -} - -static void tls_session_ticket_key_free(gnutls_datum_t *ticket) -{ - gnutls_memset(ticket->data, 0, ticket->size); - gnutls_free(ticket->data); -} - -static int self_key(gnutls_x509_privkey_t *privkey, const char *key_file) -{ - gnutls_datum_t data = { 0 }; - - int ret = gnutls_x509_privkey_init(privkey); - if (ret != GNUTLS_E_SUCCESS) { - return ret; - } - - int fd = open(key_file, O_RDONLY); - if (fd != -1) { - struct stat stat; - if (fstat(fd, &stat) != 0 || - (data.data = gnutls_malloc(stat.st_size)) == NULL || - read(fd, data.data, stat.st_size) != stat.st_size) { - ret = GNUTLS_E_KEYFILE_ERROR; - goto finish; - } - - data.size = stat.st_size; - ret = gnutls_x509_privkey_import_pkcs8(*privkey, &data, GNUTLS_X509_FMT_PEM, - NULL, GNUTLS_PKCS_PLAIN); - if (ret != GNUTLS_E_SUCCESS) { - goto finish; - } - } else { - ret = gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_EDDSA_ED25519, - GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_ED25519), 0); - if (ret != GNUTLS_E_SUCCESS) { - goto finish; - } - - ret = gnutls_x509_privkey_export2_pkcs8(*privkey, GNUTLS_X509_FMT_PEM, NULL, - GNUTLS_PKCS_PLAIN, &data); - if (ret != GNUTLS_E_SUCCESS || - (fd = open(key_file, O_WRONLY | O_CREAT, 0600)) == -1 || - write(fd, data.data, data.size) != data.size) { - ret = GNUTLS_E_KEYFILE_ERROR; - goto finish; - } - } - -finish: - close(fd); - gnutls_free(data.data); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_x509_privkey_deinit(*privkey); - *privkey = NULL; - } - return ret; -} - -static int self_signed_cert(gnutls_certificate_credentials_t tls_cert, - const char *key_file) -{ - gnutls_x509_privkey_t privkey = NULL; - gnutls_x509_crt_t cert = NULL; - - char *hostname = sockaddr_hostname(); - if (hostname == NULL) { - return GNUTLS_E_MEMORY_ERROR; - } - - int ret; - uint8_t serial[16]; - gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial)); - // Clear the left-most bit to be a positive number (two's complement form). - serial[0] &= 0x7F; - -#define CHK(cmd) if ((ret = (cmd)) != GNUTLS_E_SUCCESS) { goto finish; } -#define NOW_DAYS(days) (time(NULL) + 24 * 3600 * (days)) - - CHK(self_key(&privkey, key_file)); - - CHK(gnutls_x509_crt_init(&cert)); - CHK(gnutls_x509_crt_set_version(cert, 3)); - CHK(gnutls_x509_crt_set_serial(cert, serial, sizeof(serial))); - CHK(gnutls_x509_crt_set_activation_time(cert, NOW_DAYS(-1))); - CHK(gnutls_x509_crt_set_expiration_time(cert, NOW_DAYS(10 * 365))); - CHK(gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, - hostname, strlen(hostname))); - CHK(gnutls_x509_crt_set_key(cert, privkey)); - CHK(gnutls_x509_crt_sign2(cert, cert, privkey, GNUTLS_DIG_SHA512, 0)); - - ret = gnutls_certificate_set_x509_key(tls_cert, &cert, 1, privkey); - -finish: - free(hostname); - gnutls_x509_crt_deinit(cert); - gnutls_x509_privkey_deinit(privkey); - - return ret; -} - -_public_ -struct knot_quic_creds *knot_quic_init_creds(const char *cert_file, - const char *key_file) -{ - knot_quic_creds_t *creds = calloc(1, sizeof(*creds)); - if (creds == NULL) { - return NULL; - } - - int ret = gnutls_certificate_allocate_credentials(&creds->tls_cert); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - ret = gnutls_anti_replay_init(&creds->tls_anti_replay); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func); - gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL); - - if (cert_file != NULL) { - ret = gnutls_certificate_set_x509_key_file(creds->tls_cert, - cert_file, key_file, - GNUTLS_X509_FMT_PEM); - } else { - ret = self_signed_cert(creds->tls_cert, key_file); - } - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - return creds; -fail: - knot_quic_free_creds(creds); - return NULL; -} - -_public_ -struct knot_quic_creds *knot_quic_init_creds_peer(const struct knot_quic_creds *local_creds, - const uint8_t *peer_pin, - uint8_t peer_pin_len) -{ - knot_quic_creds_t *creds = calloc(1, sizeof(*creds) + peer_pin_len); - if (creds == NULL) { - return NULL; - } - - if (local_creds != NULL) { - creds->peer = true; - creds->tls_cert = local_creds->tls_cert; - } else { - int ret = gnutls_certificate_allocate_credentials(&creds->tls_cert); - if (ret != GNUTLS_E_SUCCESS) { - free(creds); - return NULL; - } - } - - if (peer_pin_len > 0 && peer_pin != NULL) { - memcpy(creds->peer_pin, peer_pin, peer_pin_len); - creds->peer_pin_len = peer_pin_len; - } - - return creds; -} - -_public_ -int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_int **cert) -{ - if (creds == NULL || cert == NULL) { - return KNOT_EINVAL; - } - - gnutls_x509_crt_t *certs; - unsigned cert_count; - int ret = gnutls_certificate_get_x509_crt(creds->tls_cert, 0, &certs, &cert_count); - if (ret == GNUTLS_E_SUCCESS) { - if (cert_count == 0) { - gnutls_x509_crt_deinit(*certs); - return KNOT_ENOENT; - } - *cert = *certs; - free(certs); - } - return ret; -} - -_public_ -void knot_quic_free_creds(struct knot_quic_creds *creds) -{ - if (creds == NULL) { - return; - } - - if (!creds->peer && creds->tls_cert != NULL) { - gnutls_certificate_free_credentials(creds->tls_cert); - } - gnutls_anti_replay_deinit(creds->tls_anti_replay); - if (creds->tls_ticket_key.data != NULL) { - tls_session_ticket_key_free(&creds->tls_ticket_key); - } - free(creds); -} - static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { return ((knot_quic_conn_t *)conn_ref->user_data)->conn; @@ -377,51 +146,31 @@ 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_tls_session(&conn->tls_session, conn->quic_table->creds, + conn->quic_table->priority, "\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; @@ -477,54 +226,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; @@ -602,18 +303,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_tls_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) { @@ -945,6 +636,10 @@ int knot_quic_handle(knot_quic_table_t *table, knot_quic_reply_t *reply, goto finish; } + if (conn != NULL && (conn->flags & KNOT_QUIC_CONN_BLOCKED)) { + return KNOT_EOK; + } + ngtcp2_path path; path.remote.addr = (struct sockaddr *)reply->ip_rem; path.remote.addrlen = addr_len((struct sockaddr_in6 *)reply->ip_rem); @@ -1249,6 +944,8 @@ int knot_quic_send(knot_quic_table_t *quic_table, knot_quic_conn_t *conn, return KNOT_EINVAL; } else if (reply->handle_ret < 0) { return reply->handle_ret; + } else if ((conn->flags & KNOT_QUIC_CONN_BLOCKED) && !(flags & KNOT_QUIC_SEND_IGNORE_BLOCKED)) { + return KNOT_EOK; } else if (reply->handle_ret > 0) { return send_special(quic_table, reply, conn); } else if (conn == NULL) { diff --git a/src/libknot/quic/quic.h b/src/libknot/quic/quic.h index 29a02e0..b4acb33 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 @@ -29,20 +29,18 @@ #include <netinet/in.h> #include "libknot/quic/quic_conn.h" - -#define KNOT_QUIC_PIN_LEN 32 +#include "libknot/quic/tls_common.h" #define KNOT_QUIC_HANDLE_RET_CLOSE 2000 // RFC 9250 #define KNOT_QUIC_ERR_EXCESSIVE_LOAD 0x4 -struct gnutls_x509_crt_int; -struct knot_quic_creds; struct knot_quic_session; typedef enum { KNOT_QUIC_SEND_IGNORE_LASTBYTE = (1 << 0), + KNOT_QUIC_SEND_IGNORE_BLOCKED = (1 << 1), } knot_quic_send_flag_t; typedef struct knot_quic_reply { @@ -87,45 +85,6 @@ struct knot_quic_session *knot_quic_session_save(knot_quic_conn_t *conn); int knot_quic_session_load(knot_quic_conn_t *conn, struct knot_quic_session *session); /*! - * \brief Init server TLS certificate for DoQ. - * - * \param cert_file X509 certificate PEM file path/name (NULL if auto-generated). - * \param key_file Key PEM file path/name. - * - * \return Initialized creds. - */ -struct knot_quic_creds *knot_quic_init_creds(const char *cert_file, - const char *key_file); - -/*! - * \brief Init peer TLS certificate for DoQ. - * - * \param local_creds Local credentials if server. - * \param peer_pin Optional peer certificate pin to check. - * \param peer_pin_len Length of the peer pin. Set 0 if not specified. - * - * \return Initialized creds. - */ -struct knot_quic_creds *knot_quic_init_creds_peer(const struct knot_quic_creds *local_creds, - const uint8_t *peer_pin, - uint8_t peer_pin_len); - -/*! - * \brief Gets the certificate from credentials. - * - * \param creds TLS credentials. - * \param cert Output certificate. - * - * \return KNOT_E* - */ -int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_int **cert); - -/*! - * \brief Deinit server TLS certificate for DoQ. - */ -void knot_quic_free_creds(struct knot_quic_creds *creds); - -/*! * \brief Returns timeout value for the connection. */ uint64_t quic_conn_get_timeout(knot_quic_conn_t *conn); @@ -156,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/quic_conn.c b/src/libknot/quic/quic_conn.c index 6616573..1a3b9df 100644 --- a/src/libknot/quic/quic_conn.c +++ b/src/libknot/quic/quic_conn.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 @@ -29,6 +29,7 @@ #include "libdnssec/random.h" #include "libknot/attribute.h" #include "libknot/error.h" +#include "libknot/quic/tls_common.h" #include "libknot/quic/quic.h" #include "libknot/xdp/tcp_iobuf.h" #include "libknot/wire.h" @@ -45,7 +46,7 @@ static int cmp_expiry_heap_nodes(void *c1, void *c2) _public_ knot_quic_table_t *knot_quic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs, - size_t udp_payload, struct knot_quic_creds *creds) + size_t udp_payload, struct knot_creds *creds) { size_t table_size = max_conns * BUCKETS_PER_CONNS; @@ -61,9 +62,17 @@ knot_quic_table_t *knot_quic_table_new(size_t max_conns, size_t max_ibufs, size_ res->obufs_max = max_obufs; res->udp_payload_limit = udp_payload; + int ret = gnutls_priority_init2(&res->priority, KNOT_TLS_PRIORITIES, NULL, + GNUTLS_PRIORITY_INIT_DEF_APPEND); + if (ret != GNUTLS_E_SUCCESS) { + free(res); + return NULL; + } + res->expiry_heap = malloc(sizeof(struct heap)); if (res->expiry_heap == NULL || !heap_init(res->expiry_heap, cmp_expiry_heap_nodes, 0)) { free(res->expiry_heap); + gnutls_priority_deinit(res->priority); free(res); return NULL; } @@ -92,6 +101,7 @@ void knot_quic_table_free(knot_quic_table_t *table) assert(table->ibufs_size == 0); assert(table->obufs_size == 0); + gnutls_priority_deinit(table->priority); heap_deinit(table->expiry_heap); free(table->expiry_heap); free(table); @@ -118,7 +128,9 @@ void knot_quic_table_sweep(knot_quic_table_t *table, struct knot_quic_reply *swe while (!EMPTY_HEAP(table->expiry_heap)) { knot_quic_conn_t *c = *(knot_quic_conn_t **)HHEAD(table->expiry_heap); - if (table->usage > table->max_conns) { + if ((c->flags & KNOT_QUIC_CONN_BLOCKED)) { + break; // highly inprobable + } else if (table->usage > table->max_conns) { knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_CONN); send_excessive_load(c, sweep_reply, table); knot_quic_table_rem(c, table); @@ -476,7 +488,7 @@ uint8_t *knot_quic_stream_add_data(knot_quic_conn_t *conn, int64_t stream_id, add_tail((list_t *)&s->outbufs, (node_t *)obuf); s->obufs_size += obuf->len; conn->obufs_size += obuf->len; - conn->quic_table->obufs_size += obuf->len; + ATOMIC_ADD(conn->quic_table->obufs_size, obuf->len); return obuf->buf + prefix; } @@ -497,7 +509,7 @@ void knot_quic_stream_ack_data(knot_quic_conn_t *conn, int64_t stream_id, assert(HEAD(*obs) != first); // help CLANG analyzer understand what rem_node did and that further usage of HEAD(*obs) is safe s->obufs_size -= first->len; conn->obufs_size -= first->len; - conn->quic_table->obufs_size -= first->len; + ATOMIC_SUB(conn->quic_table->obufs_size, first->len); s->first_offset += first->len; free(first); if (s->unsent_obuf == first) { @@ -556,6 +568,19 @@ void knot_quic_stream_mark_sent(knot_quic_conn_t *conn, int64_t stream_id, } _public_ +void knot_quic_conn_block(knot_quic_conn_t *conn, bool block) +{ + if (block) { + conn->flags |= KNOT_QUIC_CONN_BLOCKED; + conn->next_expiry = UINT64_MAX; + conn_heap_reschedule(conn, conn->quic_table); + } else { + conn->flags &= ~KNOT_QUIC_CONN_BLOCKED; + quic_conn_mark_used(conn, conn->quic_table); + } +} + +_public_ void knot_quic_cleanup(knot_quic_conn_t *conns[], size_t n_conns) { for (size_t i = 0; i < n_conns; i++) { diff --git a/src/libknot/quic/quic_conn.h b/src/libknot/quic/quic_conn.h index 64ead51..49e0631 100644 --- a/src/libknot/quic/quic_conn.h +++ b/src/libknot/quic/quic_conn.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,10 +29,13 @@ #include <stdint.h> #include <sys/uio.h> +#include "contrib/atomic.h" + #define MAX_STREAMS_PER_CONN 10 // this limits the number of un-finished streams per conn (i.e. if response has been recvd with FIN, it doesn't count) +struct gnutls_priority_st; struct ngtcp2_cid; // declaration taken from wherever in ngtcp2 -struct knot_quic_creds; +struct knot_creds; struct knot_quic_reply; struct knot_sweep_stats; @@ -70,6 +73,7 @@ typedef struct { typedef enum { KNOT_QUIC_CONN_HANDSHAKE_DONE = (1 << 0), KNOT_QUIC_CONN_SESSION_TAKEN = (1 << 1), + KNOT_QUIC_CONN_BLOCKED = (1 << 2), } knot_quic_conn_flag_t; typedef struct knot_quic_conn { @@ -111,12 +115,13 @@ typedef struct knot_quic_table { size_t ibufs_max; size_t obufs_max; size_t ibufs_size; - size_t obufs_size; + knot_atomic_size_t obufs_size; size_t udp_payload_limit; // for simplicity not distinguishing IPv4/6 void (*log_cb)(const char *); const char *qlog_dir; uint64_t hash_secret[4]; - struct knot_quic_creds *creds; + struct knot_creds *creds; + struct gnutls_priority_st *priority; struct heap *expiry_heap; knot_quic_cid_t *conns[]; } knot_quic_table_t; @@ -133,7 +138,7 @@ typedef struct knot_quic_table { * \return Allocated table, or NULL. */ knot_quic_table_t *knot_quic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs, - size_t udp_payload, struct knot_quic_creds *creds); + size_t udp_payload, struct knot_creds *creds); /*! * \brief Free QUIC table including its contents. @@ -306,6 +311,11 @@ void knot_quic_stream_mark_sent(knot_quic_conn_t *conn, int64_t stream_id, size_t amount_sent); /*! + * \brief (Un)block the connection for incoming/outgoing traffic and sweep. + */ +void knot_quic_conn_block(knot_quic_conn_t *conn, bool block); + +/*! * \brief Free rest of resources of closed conns. * * \param conns Array with recently used conns (possibly NULLs). diff --git a/src/libknot/quic/tls.c b/src/libknot/quic/tls.c new file mode 100644 index 0000000..01172df --- /dev/null +++ b/src/libknot/quic/tls.c @@ -0,0 +1,262 @@ +/* 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 <assert.h> +#include <gnutls/crypto.h> +#include <gnutls/gnutls.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include "libknot/quic/tls.h" + +#include "contrib/macros.h" +#include "contrib/time.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "libknot/quic/tls_common.h" + +_public_ +knot_tls_ctx_t *knot_tls_ctx_new(struct knot_creds *creds, unsigned io_timeout, + unsigned hs_timeout, bool server) +{ + knot_tls_ctx_t *res = calloc(1, sizeof(*res)); + if (res == NULL) { + return NULL; + } + + res->creds = creds; + res->handshake_timeout = hs_timeout; + res->io_timeout = io_timeout; + res->server = server; + + int ret = gnutls_priority_init2(&res->priority, KNOT_TLS_PRIORITIES, NULL, + GNUTLS_PRIORITY_INIT_DEF_APPEND); + if (ret != GNUTLS_E_SUCCESS) { + free(res); + return NULL; + } + + return res; +} + +_public_ +void knot_tls_ctx_free(knot_tls_ctx_t *ctx) +{ + if (ctx != NULL) { + gnutls_priority_deinit(ctx->priority); + free(ctx); + } +} + +_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_tls_session(&res->session, ctx->creds, ctx->priority, + "\x03""dot", false, ctx->server); + if (ret != KNOT_EOK) { + goto fail; + } + + gnutls_transport_set_int(res->session, sock_fd); // Use internal recv/send/poll. + gnutls_handshake_set_timeout(res->session, ctx->handshake_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 && conn->fd_clones_count-- < 1) { + gnutls_deinit(conn->session); + free(conn); + } +} + +_public_ +int knot_tls_handshake(knot_tls_conn_t *conn, bool oneshot) +{ + if (conn->flags & (KNOT_TLS_CONN_HANDSHAKE_DONE | KNOT_TLS_CONN_BLOCKED)) { + return KNOT_EOK; + } + + /* Check if NB socket is writeable. */ + int opt; + socklen_t opt_len = sizeof(opt); + int ret = getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len); + if (ret < 0 || opt == ECONNREFUSED) { + return KNOT_NET_ECONNECT; + } + + gnutls_record_set_timeout(conn->session, conn->ctx->io_timeout); + do { + ret = gnutls_handshake(conn->session); + } while (!oneshot && ret < 0 && gnutls_error_is_fatal(ret) == 0); + + switch (ret) { + case GNUTLS_E_SUCCESS: + conn->flags |= KNOT_TLS_CONN_HANDSHAKE_DONE; + return knot_tls_pin_check(conn->session, conn->ctx->creds); + case GNUTLS_E_TIMEDOUT: + return KNOT_NET_ETIMEOUT; + default: + if (gnutls_error_is_fatal(ret) == 0) { + return KNOT_EAGAIN; + } else { + return KNOT_NET_EHSHAKE; + } + } +} + +#define TIMEOUT_CTX_INIT \ + struct timespec begin, end; \ + if (*timeout_ptr > 0) { \ + clock_gettime(CLOCK_MONOTONIC, &begin); \ + } + +#define TIMEOUT_CTX_UPDATE \ + if (*timeout_ptr > 0) { \ + clock_gettime(CLOCK_MONOTONIC, &end); \ + int running_ms = time_diff_ms(&begin, &end); \ + *timeout_ptr = MAX(*timeout_ptr - running_ms, 0); \ + } + +static ssize_t recv_data(knot_tls_conn_t *conn, void *data, size_t size, int *timeout_ptr) +{ + gnutls_record_set_timeout(conn->session, *timeout_ptr); + + size_t total = 0; + ssize_t res; + while (total < size) { + TIMEOUT_CTX_INIT + res = gnutls_record_recv(conn->session, data + total, size - total); + if (res > 0) { + total += res; + } else if (res == 0) { + return KNOT_ECONNRESET; + } else if (gnutls_error_is_fatal(res) != 0) { + return KNOT_NET_ERECV; + } + TIMEOUT_CTX_UPDATE + gnutls_record_set_timeout(conn->session, *timeout_ptr); + } + + assert(total == size); + return size; +} + +_public_ +ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size) +{ + if (conn == NULL || data == NULL) { + return KNOT_EINVAL; + } + + if (conn->flags & KNOT_TLS_CONN_BLOCKED) { + return 0; + } + + ssize_t ret = knot_tls_handshake(conn, false); + if (ret != KNOT_EOK) { + return ret; + } + + int timeout = conn->ctx->io_timeout; + + uint16_t msg_len; + ret = recv_data(conn, &msg_len, sizeof(msg_len), &timeout); + if (ret != sizeof(msg_len)) { + return ret; + } + + msg_len = ntohs(msg_len); + if (size < msg_len) { + return KNOT_ESPACE; + } + + ret = recv_data(conn, data, msg_len, &timeout); + if (ret != size) { + return ret; + } + + return msg_len; +} + +_public_ +ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size) +{ + if (conn == NULL || data == NULL || size > UINT16_MAX) { + return KNOT_EINVAL; + } + + ssize_t res = knot_tls_handshake(conn, false); + if (res != KNOT_EOK) { + return res; + } + + // Enable data buffering. + gnutls_record_cork(conn->session); + + uint16_t msg_len = htons(size); + res = gnutls_record_send(conn->session, &msg_len, sizeof(msg_len)); + if (res != sizeof(msg_len)) { + return KNOT_NET_ESEND; + } + + res = gnutls_record_send(conn->session, data, size); + if (res != size) { + return KNOT_NET_ESEND; + } + + int timeout = conn->ctx->io_timeout, *timeout_ptr = &timeout; + gnutls_record_set_timeout(conn->session, timeout); + + // Send the buffered data. + while (gnutls_record_check_corked(conn->session) > 0) { + TIMEOUT_CTX_INIT + int ret = gnutls_record_uncork(conn->session, 0); + if (ret < 0 && gnutls_error_is_fatal(ret) != 0) { + return ret == GNUTLS_E_TIMEDOUT ? KNOT_ETIMEOUT : + KNOT_NET_ESEND; + } + TIMEOUT_CTX_UPDATE + gnutls_record_set_timeout(conn->session, timeout); + } + + return size; +} + +_public_ +void knot_tls_conn_block(knot_tls_conn_t *conn, bool block) +{ + if (block) { + conn->flags |= KNOT_TLS_CONN_BLOCKED; + } else { + conn->flags &= ~KNOT_TLS_CONN_BLOCKED; + } +} diff --git a/src/libknot/quic/tls.h b/src/libknot/quic/tls.h new file mode 100644 index 0000000..7801ca8 --- /dev/null +++ b/src/libknot/quic/tls.h @@ -0,0 +1,135 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Pure TLS functionality. + * + * \addtogroup quic + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <sys/types.h> + +struct gnutls_priority_st; + +typedef enum { + KNOT_TLS_CONN_HANDSHAKE_DONE = (1 << 0), + KNOT_TLS_CONN_SESSION_TAKEN = (1 << 1), // unused, to be implemeted later + KNOT_TLS_CONN_BLOCKED = (1 << 2), +} knot_tls_conn_flag_t; + +typedef struct knot_tls_ctx { + struct knot_creds *creds; + struct gnutls_priority_st *priority; + 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; + unsigned fd_clones_count; + knot_tls_conn_flag_t flags; +} knot_tls_conn_t; + +/*! + * \brief Initialize DoT answering context. + * + * \param creds Certificate credentials. + * \param io_timeout Connections' IO-timeout (in milliseconds). + * \param hs_timeout Handshake timeout (in milliseconds). + * \param server Server context (otherwise client). + * + * \return Initialized context or NULL. + */ +knot_tls_ctx_t *knot_tls_ctx_new(struct knot_creds *creds, unsigned io_timeout, + unsigned hs_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. + * + * \param conn DoT connection. + * \param oneshot If set, don't wait untill the handshake is finished. + * + * \retval KNOT_EOK Handshake successfully finished. + * \retval KNOT_EGAIN Handshake not finished, call me again. + * \retval KNOT_NET_EHSHAKE Handshake error. + * \retval KNOT_NET_ECONNECT Socket not connected. + */ +int knot_tls_handshake(knot_tls_conn_t *conn, bool oneshot); + +/*! + * \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); + +/*! + * \brief Set or unset the conection's BLOCKED flag. + */ +void knot_tls_conn_block(knot_tls_conn_t *conn, bool block); + +/*! @} */ diff --git a/src/libknot/quic/tls_common.c b/src/libknot/quic/tls_common.c new file mode 100644 index 0000000..d1647d8 --- /dev/null +++ b/src/libknot/quic/tls_common.c @@ -0,0 +1,472 @@ +/* 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 <fcntl.h> +#include <gnutls/crypto.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "libknot/quic/tls_common.h" + +#include "contrib/atomic.h" +#include "contrib/sockaddr.h" +#include "contrib/string.h" +#include "libknot/attribute.h" +#include "libknot/error.h" + +typedef struct knot_creds { + knot_atomic_ptr_t cert_creds; // Current credentials. + gnutls_certificate_credentials_t cert_creds_prev; // Previous credentials (for pending connections). + 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_creds_t; + +static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time, + const gnutls_datum_t *key, + const gnutls_datum_t *data) +{ + return 0; +} + +static void tls_session_ticket_key_free(gnutls_datum_t *ticket) +{ + memzero(ticket->data, ticket->size); + gnutls_free(ticket->data); +} + +static int self_key(gnutls_x509_privkey_t *privkey, const char *key_file) +{ + gnutls_datum_t data = { 0 }; + + int ret = gnutls_x509_privkey_init(privkey); + if (ret != GNUTLS_E_SUCCESS) { + return ret; + } + + int fd = open(key_file, O_RDONLY); + if (fd != -1) { + struct stat stat; + if (fstat(fd, &stat) != 0 || + (data.data = gnutls_malloc(stat.st_size)) == NULL || + read(fd, data.data, stat.st_size) != stat.st_size) { + ret = GNUTLS_E_KEYFILE_ERROR; + goto finish; + } + + data.size = stat.st_size; + ret = gnutls_x509_privkey_import_pkcs8(*privkey, &data, GNUTLS_X509_FMT_PEM, + NULL, GNUTLS_PKCS_PLAIN); + if (ret != GNUTLS_E_SUCCESS) { + goto finish; + } + } else { + ret = gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_EDDSA_ED25519, + GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_ED25519), 0); + if (ret != GNUTLS_E_SUCCESS) { + goto finish; + } + + ret = gnutls_x509_privkey_export2_pkcs8(*privkey, GNUTLS_X509_FMT_PEM, NULL, + GNUTLS_PKCS_PLAIN, &data); + if (ret != GNUTLS_E_SUCCESS || + (fd = open(key_file, O_WRONLY | O_CREAT, 0600)) == -1 || + write(fd, data.data, data.size) != data.size) { + ret = GNUTLS_E_KEYFILE_ERROR; + goto finish; + } + } + +finish: + if (fd > -1) { + close(fd); + } + gnutls_free(data.data); + if (ret != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(*privkey); + *privkey = NULL; + } + return ret; +} + +static int self_signed_cert(gnutls_certificate_credentials_t tls_cert, + const char *key_file) +{ + gnutls_x509_privkey_t privkey = NULL; + gnutls_x509_crt_t cert = NULL; + + char *hostname = sockaddr_hostname(); + if (hostname == NULL) { + return GNUTLS_E_MEMORY_ERROR; + } + + int ret; + uint8_t serial[16]; + gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial)); + // Clear the left-most bit to be a positive number (two's complement form). + serial[0] &= 0x7F; + +#define CHK(cmd) if ((ret = (cmd)) != GNUTLS_E_SUCCESS) { goto finish; } +#define NOW_DAYS(days) (time(NULL) + 24 * 3600 * (days)) + + CHK(self_key(&privkey, key_file)); + + CHK(gnutls_x509_crt_init(&cert)); + CHK(gnutls_x509_crt_set_version(cert, 3)); + CHK(gnutls_x509_crt_set_serial(cert, serial, sizeof(serial))); + CHK(gnutls_x509_crt_set_activation_time(cert, NOW_DAYS(-1))); + CHK(gnutls_x509_crt_set_expiration_time(cert, NOW_DAYS(10 * 365))); + CHK(gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, + hostname, strlen(hostname))); + CHK(gnutls_x509_crt_set_key(cert, privkey)); + CHK(gnutls_x509_crt_sign2(cert, cert, privkey, GNUTLS_DIG_SHA512, 0)); + + ret = gnutls_certificate_set_x509_key(tls_cert, &cert, 1, privkey); + +finish: + free(hostname); + gnutls_x509_crt_deinit(cert); + gnutls_x509_privkey_deinit(privkey); + + return ret; +} + +_public_ +struct knot_creds *knot_creds_init(const char *key_file, const char *cert_file) +{ + knot_creds_t *creds = calloc(1, sizeof(*creds)); + if (creds == NULL) { + return NULL; + } + + int ret = knot_creds_update(creds, key_file, cert_file); + if (ret != KNOT_EOK) { + goto fail; + } + + ret = gnutls_anti_replay_init(&creds->tls_anti_replay); + if (ret != GNUTLS_E_SUCCESS) { + goto fail; + } + gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func); + gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL); + + ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key); + if (ret != GNUTLS_E_SUCCESS) { + goto fail; + } + + return creds; +fail: + knot_creds_free(creds); + return NULL; +} + +_public_ +struct knot_creds *knot_creds_init_peer(const struct knot_creds *local_creds, + const uint8_t *peer_pin, + uint8_t peer_pin_len) +{ + knot_creds_t *creds = calloc(1, sizeof(*creds) + peer_pin_len); + if (creds == NULL) { + return NULL; + } + + if (local_creds != NULL) { + creds->peer = true; + creds->cert_creds = ATOMIC_GET(local_creds->cert_creds); + } else { + gnutls_certificate_credentials_t new_creds; + int ret = gnutls_certificate_allocate_credentials(&new_creds); + if (ret != GNUTLS_E_SUCCESS) { + free(creds); + return NULL; + } + creds->cert_creds = new_creds; + } + + if (peer_pin_len > 0 && peer_pin != NULL) { + memcpy(creds->peer_pin, peer_pin, peer_pin_len); + creds->peer_pin_len = peer_pin_len; + } + + return creds; +} + +static int creds_cert(gnutls_certificate_credentials_t creds, + struct gnutls_x509_crt_int **cert) +{ + gnutls_x509_crt_t *certs; + unsigned cert_count; + int ret = gnutls_certificate_get_x509_crt(creds, 0, &certs, &cert_count); + if (ret == GNUTLS_E_SUCCESS) { + if (cert_count == 0) { + gnutls_x509_crt_deinit(*certs); + return KNOT_ENOENT; + } + *cert = *certs; + free(certs); + return KNOT_EOK; + } + return KNOT_ERROR; +} + +static int creds_changed(gnutls_certificate_credentials_t creds, + gnutls_certificate_credentials_t prev, + bool self_cert, bool *changed) +{ + if (creds == NULL || prev == NULL) { + *changed = true; + return KNOT_EOK; + } + + gnutls_x509_crt_t cert = NULL, cert_prev = NULL; + + int ret = creds_cert(creds, &cert); + if (ret != KNOT_EOK) { + goto failed; + } + ret = creds_cert(prev, &cert_prev); + if (ret != KNOT_EOK) { + goto failed; + } + + if (self_cert) { + uint8_t pin[KNOT_TLS_PIN_LEN], pin_prev[KNOT_TLS_PIN_LEN]; + size_t pin_size = sizeof(pin), pin_prev_size = sizeof(pin_prev); + + ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, + pin, &pin_size); + if (ret != KNOT_EOK) { + goto failed; + } + ret = gnutls_x509_crt_get_key_id(cert_prev, GNUTLS_KEYID_USE_SHA256, + pin_prev, &pin_prev_size); + if (ret != KNOT_EOK) { + goto failed; + } + + *changed = (pin_size != pin_prev_size) || + memcmp(pin, pin_prev, pin_size) != 0; + } else { + *changed = (gnutls_x509_crt_equals(cert, cert_prev) == 0); + } + + ret = KNOT_EOK; +failed: + gnutls_x509_crt_deinit(cert); + gnutls_x509_crt_deinit(cert_prev); + + return ret; +} + +_public_ +int knot_creds_update(struct knot_creds *creds, const char *key_file, const char *cert_file) +{ + if (creds == NULL || key_file == NULL) { + return KNOT_EINVAL; + } + + gnutls_certificate_credentials_t new_creds; + int ret = gnutls_certificate_allocate_credentials(&new_creds); + if (ret != GNUTLS_E_SUCCESS) { + return KNOT_ENOMEM; + } + + if (cert_file != NULL) { + ret = gnutls_certificate_set_x509_key_file(new_creds, + cert_file, key_file, + GNUTLS_X509_FMT_PEM); + } else { + ret = self_signed_cert(new_creds, key_file); + } + if (ret != GNUTLS_E_SUCCESS) { + gnutls_certificate_free_credentials(new_creds); + return KNOT_EFILE; + } + + bool changed = false; + ret = creds_changed(new_creds, ATOMIC_GET(creds->cert_creds), + cert_file == NULL, &changed); + if (ret != KNOT_EOK) { + gnutls_certificate_free_credentials(new_creds); + return ret; + } + + if (changed) { + if (creds->cert_creds_prev != NULL) { + gnutls_certificate_free_credentials(creds->cert_creds_prev); + } + creds->cert_creds_prev = ATOMIC_XCHG(creds->cert_creds, new_creds); + } else { + gnutls_certificate_free_credentials(new_creds); + } + + return KNOT_EOK; +} + +_public_ +int knot_creds_cert(struct knot_creds *creds, struct gnutls_x509_crt_int **cert) +{ + if (creds == NULL || cert == NULL) { + return KNOT_EINVAL; + } + + return creds_cert(ATOMIC_GET(creds->cert_creds), cert); +} + +_public_ +void knot_creds_free(struct knot_creds *creds) +{ + if (creds == NULL) { + return; + } + + if (!creds->peer && creds->cert_creds != NULL) { + gnutls_certificate_free_credentials(creds->cert_creds); + if (creds->cert_creds_prev != NULL) { + gnutls_certificate_free_credentials(creds->cert_creds_prev); + } + } + gnutls_anti_replay_deinit(creds->tls_anti_replay); + if (creds->tls_ticket_key.data != NULL) { + tls_session_ticket_key_free(&creds->tls_ticket_key); + } + free(creds); +} + +_public_ +int knot_tls_session(struct gnutls_session_int **session, + struct knot_creds *creds, + struct gnutls_priority_st *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 flags = GNUTLS_NO_SIGNAL; + if (early_data) { + flags |= GNUTLS_ENABLE_EARLY_DATA; +#ifdef ENABLE_QUIC // Next flags aren't available in older GnuTLS versions. + flags |= GNUTLS_NO_AUTO_SEND_TICKET | GNUTLS_NO_END_OF_EARLY_DATA; +#endif + } + + int ret = gnutls_init(session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | 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(*session, priority); + } + 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, + ATOMIC_GET(creds->cert_creds)); + } + if (ret != GNUTLS_E_SUCCESS) { + gnutls_deinit(*session); + *session = NULL; + } + return ret == GNUTLS_E_SUCCESS ? KNOT_EOK : KNOT_ERROR; +} + +_public_ +void knot_tls_pin(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_tls_pin_check(struct gnutls_session_int *session, + struct knot_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_tls_pin(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 new file mode 100644 index 0000000..934f256 --- /dev/null +++ b/src/libknot/quic/tls_common.h @@ -0,0 +1,134 @@ +/* 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/>. + */ + +/*! + * \file + * + * \brief Credentials handling common to QUIC and TLS. + * + * \addtogroup quic + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#define KNOT_TLS_PIN_LEN 32 +#define KNOT_TLS_PRIORITIES "-VERS-ALL:+VERS-TLS1.3:" \ + "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:" \ + "+GROUP-SECP384R1:+GROUP-SECP521R1" + +struct gnutls_priority_st; +struct gnutls_session_int; +struct gnutls_x509_crt_int; +struct knot_creds; + +/*! + * \brief Init server TLS key and certificate for DoQ. + * + * \param key_file Key PEM file path/name. + * \param cert_file X509 certificate PEM file path/name (NULL if auto-generated). + * + * \return Initialized creds. + */ +struct knot_creds *knot_creds_init(const char *key_file, const char *cert_file); + +/*! + * \brief Init peer TLS key and certificate for DoQ. + * + * \param local_creds Local credentials if server. + * \param peer_pin Optional peer certificate pin to check. + * \param peer_pin_len Length of the peer pin. Set 0 if not specified. + * + * \return Initialized creds. + */ +struct knot_creds *knot_creds_init_peer(const struct knot_creds *local_creds, + const uint8_t *peer_pin, + uint8_t peer_pin_len); + +/*! + * \brief Load new server TLS key and certificate for DoQ. + * + * \param creds Server credentials where key/cert pair will be updated. + * \param key_file Key PEM file path/name. + * \param cert_file X509 certificate PEM file path/name (NULL if auto-generated). + * + * \return KNOT_E* + */ +int knot_creds_update(struct knot_creds *creds, const char *key_file, const char *cert_file); + +/*! + * \brief Gets the certificate from credentials. + * + * \param creds TLS credentials. + * \param cert Output certificate. + * + * \return KNOT_E* + */ +int knot_creds_cert(struct knot_creds *creds, struct gnutls_x509_crt_int **cert); + +/*! + * \brief Deinit server TLS certificate for DoQ. + */ +void knot_creds_free(struct knot_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_tls_session(struct gnutls_session_int **session, + struct knot_creds *creds, + struct gnutls_priority_st *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_tls_pin(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_tls_pin_check(struct gnutls_session_int *session, + struct knot_creds *creds); + +/*! @} */ diff --git a/src/libknot/rrset-dump.c b/src/libknot/rrset-dump.c index ea406ca..9fac99d 100644 --- a/src/libknot/rrset-dump.c +++ b/src/libknot/rrset-dump.c @@ -508,10 +508,46 @@ static void wire_len_data_encode_to_str(rrset_dump_params_t *p, } } -static void wire_data_omit(rrset_dump_params_t *p) +static void wire_data_omit(rrset_dump_params_t *p, + const size_t len_len, + const bool print_len) { CHECK_PRET + size_t in_len; + + // First len_len bytes are data length. + CHECK_INMAX(len_len) + + // Read data length. + switch (len_len) { + case 0: + in_len = p->in_max; + break; + case 2: + in_len = knot_wire_read_u16(p->in); + break; + default: + p->ret = -1; + return; + } + + // If required print data length. + if (print_len == true && len_len != 0) { + assert(len_len == 2); + wire_num16_to_str(p); + CHECK_PRET + + // If something follows, print one space character. + if (in_len > 0) { + dump_string(p, " "); + CHECK_PRET + } + } else { + p->in += len_len; + p->in_max -= len_len; + } + const char *omit_message = "[omitted]"; const size_t omlen = strlen(omit_message); @@ -527,8 +563,8 @@ static void wire_data_omit(rrset_dump_params_t *p) STRING_TERMINATION - p->in += p->in_max; - p->in_max = 0; + p->in += in_len; + p->in_max -= in_len; } static void wire_dnskey_to_tag(rrset_dump_params_t *p) @@ -1735,6 +1771,10 @@ static void dnskey_info(const uint8_t *rdata, #define DUMP_TYPE wire_type_to_str(p); CHECK_RET(p); #define DUMP_HEX wire_data_encode_to_str(p, &hex_encode, \ &hex_encode_alloc); CHECK_RET(p); +#define DUMP_OMIT wire_data_omit(p, 0, false); CHECK_RET(p); +#define DUMP_HEX_OMIT if (p->style->hide_crypto) { DUMP_OMIT; } \ + else if (p->style->wrap) { WRAP_INIT; DUMP_HEX; WRAP_END; } \ + else { DUMP_HEX; } #define DUMP_BASE64 wire_data_encode_to_str(p, &knot_base64_encode, \ &knot_base64_encode_alloc); CHECK_RET(p); #define DUMP_HASH wire_len_data_encode_to_str(p, &knot_base32hex_encode, \ @@ -1743,9 +1783,9 @@ static void dnskey_info(const uint8_t *rdata, 1, false, "-"); CHECK_RET(p); #define DUMP_TSIG_DGST wire_len_data_encode_to_str(p, &knot_base64_encode, \ 2, true, ""); CHECK_RET(p); +#define DUMP_TSIG_OMIT wire_data_omit(p, 2, true); CHECK_RET(p); #define DUMP_TSIG_DATA wire_len_data_encode_to_str(p, &num48_encode, \ 2, true, ""); CHECK_RET(p); -#define DUMP_OMIT wire_data_omit(p); CHECK_RET(p); #define DUMP_KEY_OMIT wire_dnskey_to_tag(p); CHECK_RET(p); #define DUMP_TEXT wire_text_to_str1(p, true, false); CHECK_RET(p); #define DUMP_LONG_TEXT wire_text_to_str(p, p->in_max, NULL, true, false); CHECK_RET(p); @@ -1923,16 +1963,17 @@ static int dump_naptr(DUMP_PARAMS) static int dump_cert(DUMP_PARAMS) { - if (p->style->wrap) { - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; WRAP_INIT; + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + + if (p->style->hide_crypto) { + DUMP_OMIT; + } else if (p->style->wrap) { + WRAP_INIT; DUMP_BASE64; WRAP_END; } else { - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; DUMP_BASE64; } @@ -1982,34 +2023,19 @@ static int dump_apl(DUMP_PARAMS) static int dump_ds(DUMP_PARAMS) { - if (p->style->wrap) { - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; WRAP_INIT; - DUMP_HEX; - WRAP_END; - } else { - DUMP_NUM16; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_HEX; - } + DUMP_NUM16; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX_OMIT; DUMP_END; } static int dump_sshfp(DUMP_PARAMS) { - if (p->style->wrap) { - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; WRAP_INIT; - DUMP_HEX; - WRAP_END; - } else { - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_HEX; - } + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX_OMIT; DUMP_END; } @@ -2067,7 +2093,9 @@ static int dump_nsec(DUMP_PARAMS) static int dump_dhcid(DUMP_PARAMS) { - if (p->style->wrap) { + if (p->style->hide_crypto) { + DUMP_OMIT; + } else if (p->style->wrap) { WRAP_INIT; DUMP_BASE64; WRAP_END; @@ -2112,18 +2140,10 @@ static int dump_nsec3param(DUMP_PARAMS) static int dump_tlsa(DUMP_PARAMS) { - if (p->style->wrap) { - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; WRAP_INIT; - DUMP_HEX; - WRAP_END; - } else { - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_HEX; - } + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX_OMIT; DUMP_END; } @@ -2139,18 +2159,10 @@ static int dump_csync(DUMP_PARAMS) static int dump_zonemd(DUMP_PARAMS) { - if (p->style->wrap) { - DUMP_NUM32; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; WRAP_INIT; - DUMP_HEX; - WRAP_END; - } else { - DUMP_NUM32; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_NUM8; DUMP_SPACE; - DUMP_HEX; - } + DUMP_NUM32; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_NUM8; DUMP_SPACE; + DUMP_HEX_OMIT; DUMP_END; } @@ -2184,7 +2196,11 @@ static int dump_tsig(DUMP_PARAMS) DUMP_DNAME; DUMP_SPACE; DUMP_NUM48; DUMP_SPACE; DUMP_NUM16; DUMP_SPACE; WRAP_INIT; - DUMP_TSIG_DGST; WRAP_LINE; + if (p->style->hide_crypto) { + DUMP_TSIG_OMIT; WRAP_LINE; + } else { + DUMP_TSIG_DGST; WRAP_LINE; + } DUMP_NUM16; DUMP_SPACE; DUMP_TSIG_RCODE; DUMP_SPACE; DUMP_TSIG_DATA; @@ -2193,7 +2209,11 @@ static int dump_tsig(DUMP_PARAMS) DUMP_DNAME; DUMP_SPACE; DUMP_NUM48; DUMP_SPACE; DUMP_NUM16; DUMP_SPACE; - DUMP_TSIG_DGST; DUMP_SPACE; + if (p->style->hide_crypto) { + DUMP_TSIG_OMIT; DUMP_SPACE; + } else { + DUMP_TSIG_DGST; DUMP_SPACE; + } DUMP_NUM16; DUMP_SPACE; DUMP_TSIG_RCODE; DUMP_SPACE; DUMP_TSIG_DATA; diff --git a/src/libknot/rrtype/tsig.c b/src/libknot/rrtype/tsig.c index 83f8436..0002963 100644 --- a/src/libknot/rrtype/tsig.c +++ b/src/libknot/rrtype/tsig.c @@ -245,7 +245,11 @@ int knot_tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t len, _public_ const knot_dname_t *knot_tsig_rdata_alg_name(const knot_rrset_t *tsig) { - return knot_rdataset_at(&tsig->rrs, 0)->data; + const knot_rdata_t *rr_data = knot_rdataset_at(&tsig->rrs, 0); + if (!rr_data) { + return NULL; + } + return rr_data->data; } _public_ diff --git a/src/libknot/version.h b/src/libknot/version.h index 38e0b74..8e21f49 100644 --- a/src/libknot/version.h +++ b/src/libknot/version.h @@ -17,8 +17,8 @@ #pragma once #define KNOT_VERSION_MAJOR 3 -#define KNOT_VERSION_MINOR 3 -#define KNOT_VERSION_PATCH 0x09 +#define KNOT_VERSION_MINOR 4 +#define KNOT_VERSION_PATCH 0x00 #define KNOT_VERSION_HEX ((KNOT_VERSION_MAJOR << 16) | \ (KNOT_VERSION_MINOR << 8) | \ diff --git a/src/libknot/xdp/Makefile.in b/src/libknot/xdp/Makefile.in index 8aa77ce..f065f3e 100644 --- a/src/libknot/xdp/Makefile.in +++ b/src/libknot/xdp/Makefile.in @@ -270,6 +270,8 @@ infodir = @infodir@ install_sh = @install_sh@ libbpf_CFLAGS = @libbpf_CFLAGS@ libbpf_LIBS = @libbpf_LIBS@ +libdbus_CFLAGS = @libdbus_CFLAGS@ +libdbus_LIBS = @libdbus_LIBS@ libdir = @libdir@ libdnssec_SONAME = @libdnssec_SONAME@ libdnssec_SOVERSION = @libdnssec_SOVERSION@ @@ -281,8 +283,6 @@ libfstrm_CFLAGS = @libfstrm_CFLAGS@ libfstrm_LIBS = @libfstrm_LIBS@ libidn2_CFLAGS = @libidn2_CFLAGS@ libidn2_LIBS = @libidn2_LIBS@ -libidn_CFLAGS = @libidn_CFLAGS@ -libidn_LIBS = @libidn_LIBS@ libknot_SONAME = @libknot_SONAME@ libknot_SOVERSION = @libknot_SOVERSION@ libknot_VERSION_INFO = @libknot_VERSION_INFO@ @@ -300,7 +300,6 @@ libprotobuf_c_CFLAGS = @libprotobuf_c_CFLAGS@ libprotobuf_c_LIBS = @libprotobuf_c_LIBS@ liburcu_CFLAGS = @liburcu_CFLAGS@ liburcu_LIBS = @liburcu_LIBS@ -liburcu_PKGCONFIG = @liburcu_PKGCONFIG@ libxdp_CFLAGS = @libxdp_CFLAGS@ libxdp_LIBS = @libxdp_LIBS@ libzscanner_SONAME = @libzscanner_SONAME@ diff --git a/src/libknot/xdp/bpf-user.h b/src/libknot/xdp/bpf-user.h index 37aac61..b76c9d6 100644 --- a/src/libknot/xdp/bpf-user.h +++ b/src/libknot/xdp/bpf-user.h @@ -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 @@ -60,8 +60,10 @@ struct kxsk_umem { /*! The memory frames. */ struct umem_frame *frames; + /*! Size of RX and TX rings. */ + uint16_t ring_size; /*! The number of free frames (for TX). */ - uint32_t tx_free_count; + uint16_t tx_free_count; /*! Stack of indices of the free frames (for TX). */ uint16_t tx_free_indices[]; }; @@ -82,15 +84,15 @@ struct knot_xdp_socket { /*! If non-NULL, it's a mocked socket with this send function. */ int (*send_mock)(struct knot_xdp_socket *, const knot_xdp_msg_t[], uint32_t, uint32_t *); - /*! The kernel has to be woken up by a syscall indication. */ - bool kernel_needs_wakeup; - /*! The limit of frame size. */ unsigned frame_limit; /*! Mapping of interface indices to VLAN tags. */ uint16_t *vlan_map; uint16_t vlan_map_max; + + /*! Enabled preferred busy polling. */ + bool busy_poll; }; /*! diff --git a/src/libknot/xdp/tcp.c b/src/libknot/xdp/tcp.c index eae73a9..d219db9 100644 --- a/src/libknot/xdp/tcp.c +++ b/src/libknot/xdp/tcp.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 @@ -287,172 +287,162 @@ static void conn_update(knot_tcp_conn_t *conn, const knot_xdp_msg_t *msg) } _public_ -int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count, +int knot_tcp_recv(knot_tcp_relay_t *relay, knot_xdp_msg_t *msg, knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, knot_tcp_ignore_t ignore) { - if (msg_count == 0) { - return KNOT_EOK; - } - if (relays == NULL || msgs == NULL || tcp_table == NULL) { + if (relay == NULL || msg == NULL || tcp_table == NULL) { return KNOT_EINVAL; } - memset(relays, 0, msg_count * sizeof(*relays)); + memset(relay, 0, sizeof(*relay)); - knot_tcp_relay_t *relay = relays; int ret = KNOT_EOK; - for (knot_xdp_msg_t *msg = msgs; msg != msgs + msg_count && ret == KNOT_EOK; msg++) { - if (!(msg->flags & KNOT_XDP_MSG_TCP)) { - continue; - } + if (!(msg->flags & KNOT_XDP_MSG_TCP)) { + return KNOT_EOK; + } - uint64_t conn_hash = 0; - knot_tcp_conn_t **pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, - &conn_hash, tcp_table); - knot_tcp_conn_t *conn = *pconn; - bool seq_ack_match = check_seq_ack(msg, conn); - if (seq_ack_match) { - assert(conn->mss != 0); - conn_update(conn, msg); - - rem_align_pointers(conn, tcp_table); - rem_node(tcp_conn_node(conn)); - add_tail(tcp_table_timeout(tcp_table), tcp_conn_node(conn)); - - if (msg->flags & KNOT_XDP_MSG_ACK) { - conn->acked = msg->ackno; - knot_tcp_outbufs_ack(&conn->outbufs, msg->ackno, &tcp_table->outbufs_total); - } + uint64_t conn_hash = 0; + knot_tcp_conn_t **pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, + &conn_hash, tcp_table); + knot_tcp_conn_t *conn = *pconn; + bool seq_ack_match = check_seq_ack(msg, conn); + if (seq_ack_match) { + assert(conn->mss != 0); + conn_update(conn, msg); + + rem_align_pointers(conn, tcp_table); + rem_node(tcp_conn_node(conn)); + add_tail(tcp_table_timeout(tcp_table), tcp_conn_node(conn)); + + if (msg->flags & KNOT_XDP_MSG_ACK) { + conn->acked = msg->ackno; + knot_tcp_outbufs_ack(&conn->outbufs, msg->ackno, &tcp_table->outbufs_total); } + } - relay->msg = msg; - relay->conn = conn; + relay->msg = msg; + relay->conn = conn; - // process incoming data - if (seq_ack_match && (msg->flags & KNOT_XDP_MSG_ACK) && msg->payload.iov_len > 0) { - if (!(ignore & XDP_TCP_IGNORE_DATA_ACK)) { - relay->auto_answer = KNOT_XDP_MSG_ACK; - } - ret = knot_tcp_inbufs_upd(&conn->inbuf, msg->payload, false, - &relay->inbf, &tcp_table->inbufs_total); - if (ret != KNOT_EOK) { - break; - } - if (conn->inbuf.iov_len > 0 && tcp_table->next_ibuf == NULL) { - tcp_table->next_ibuf = conn; - } + // process incoming data + if (seq_ack_match && (msg->flags & KNOT_XDP_MSG_ACK) && msg->payload.iov_len > 0) { + if (!(ignore & XDP_TCP_IGNORE_DATA_ACK)) { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } + ret = knot_tcp_inbufs_upd(&conn->inbuf, msg->payload, false, + &relay->inbf, &tcp_table->inbufs_total); + if (ret != KNOT_EOK) { + return ret; + } + if (conn->inbuf.iov_len > 0 && tcp_table->next_ibuf == NULL) { + tcp_table->next_ibuf = conn; } + } - // process TCP connection state - switch (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK | - KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST)) { - case KNOT_XDP_MSG_SYN: - case (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK): - if (conn == NULL) { - bool synack = (msg->flags & KNOT_XDP_MSG_ACK); - - knot_tcp_table_t *add_table = tcp_table; - if (syn_table != NULL) { - if (synack) { - break; // creating conn based on SYN+ACK is only for kxdpgun, disallow in knotd - } - add_table = syn_table; - if (*tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table) != NULL) { - break; - } - } + // process TCP connection state + switch (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK | + KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST)) { + case KNOT_XDP_MSG_SYN: + case (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK): + if (conn == NULL) { + bool synack = (msg->flags & KNOT_XDP_MSG_ACK); - ret = tcp_table_add(msg, conn_hash, add_table, &relay->conn); - if (ret == KNOT_EOK) { - relay->action = synack ? XDP_TCP_ESTABLISH : XDP_TCP_SYN; - if (!(ignore & XDP_TCP_IGNORE_ESTABLISH)) { - relay->auto_answer = synack ? KNOT_XDP_MSG_ACK : (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK); - } - - conn = relay->conn; - conn->state = synack ? XDP_TCP_NORMAL: XDP_TCP_ESTABLISHING; - conn->mss = MAX(msg->mss, 536); // minimal MSS, most importantly not zero! - conn->window_scale = msg->win_scale; - conn_update(conn, msg); - if (!synack) { - conn->acked = dnssec_random_uint32_t(); - conn->ackno = conn->acked; - } + knot_tcp_table_t *add_table = tcp_table; + if (syn_table != NULL) { + if (synack) { + break; // creating conn based on SYN+ACK is only for kxdpgun, disallow in knotd + } + add_table = syn_table; + if (*tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table) != NULL) { + break; } - } else { - relay->auto_answer = KNOT_XDP_MSG_ACK; } - break; - case KNOT_XDP_MSG_ACK: - if (!seq_ack_match) { - if (syn_table != NULL && msg->payload.iov_len == 0 && conn == NULL && - (pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table)) != NULL && - (conn = *pconn) != NULL && check_seq_ack(msg, conn)) { - // move conn from syn_table to tcp_table - tcp_table_remove(pconn, syn_table); - tcp_table_insert(conn, conn_hash, tcp_table); - relay->conn = conn; - relay->action = XDP_TCP_ESTABLISH; - conn->state = XDP_TCP_NORMAL; - conn_update(conn, msg); + + ret = tcp_table_add(msg, conn_hash, add_table, &relay->conn); + if (ret == KNOT_EOK) { + relay->action = synack ? XDP_TCP_ESTABLISH : XDP_TCP_SYN; + if (!(ignore & XDP_TCP_IGNORE_ESTABLISH)) { + relay->auto_answer = synack ? KNOT_XDP_MSG_ACK : (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK); } - } else { - switch (conn->state) { - case XDP_TCP_NORMAL: - case XDP_TCP_CLOSING1: // just a mess, ignore - break; - case XDP_TCP_ESTABLISHING: - conn->state = XDP_TCP_NORMAL; - relay->action = XDP_TCP_ESTABLISH; - break; - case XDP_TCP_CLOSING2: - if (msg->payload.iov_len == 0) { // otherwise ignore close - tcp_table_remove(pconn, tcp_table); - relay->answer = XDP_TCP_FREE; - } - break; + + conn = relay->conn; + conn->state = synack ? XDP_TCP_NORMAL: XDP_TCP_ESTABLISHING; + conn->mss = MAX(msg->mss, 536); // minimal MSS, most importantly not zero! + conn->window_scale = msg->win_scale; + conn_update(conn, msg); + if (!synack) { + conn->acked = dnssec_random_uint32_t(); + conn->ackno = conn->acked; } } - break; - case (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK): - if (ignore & XDP_TCP_IGNORE_FIN) { - break; + } else { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } + break; + case KNOT_XDP_MSG_ACK: + if (!seq_ack_match) { + if (syn_table != NULL && msg->payload.iov_len == 0 && conn == NULL && + (pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table)) != NULL && + (conn = *pconn) != NULL && check_seq_ack(msg, conn)) { + // move conn from syn_table to tcp_table + tcp_table_remove(pconn, syn_table); + tcp_table_insert(conn, conn_hash, tcp_table); + relay->conn = conn; + relay->action = XDP_TCP_ESTABLISH; + conn->state = XDP_TCP_NORMAL; + conn_update(conn, msg); } - if (!seq_ack_match) { - if (conn != NULL) { - relay->auto_answer = KNOT_XDP_MSG_RST; - relay->auto_seqno = msg->ackno; - } // else ignore. It would be better and possible, but no big value for the price of CPU. - } else { - if (conn->state == XDP_TCP_CLOSING1) { - relay->action = XDP_TCP_CLOSE; - relay->auto_answer = KNOT_XDP_MSG_ACK; - relay->answer = XDP_TCP_FREE; + } else { + switch (conn->state) { + case XDP_TCP_NORMAL: + case XDP_TCP_CLOSING1: // just a mess, ignore + break; + case XDP_TCP_ESTABLISHING: + conn->state = XDP_TCP_NORMAL; + relay->action = XDP_TCP_ESTABLISH; + break; + case XDP_TCP_CLOSING2: + if (msg->payload.iov_len == 0) { // otherwise ignore close tcp_table_remove(pconn, tcp_table); - } else if (msg->payload.iov_len == 0) { // otherwise ignore FIN - relay->action = XDP_TCP_CLOSE; - relay->auto_answer = KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK; - conn->state = XDP_TCP_CLOSING2; + relay->answer = XDP_TCP_FREE; } + break; } + } + break; + case (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK): + if (ignore & XDP_TCP_IGNORE_FIN) { break; - case KNOT_XDP_MSG_RST: - if (conn != NULL && msg->seqno == conn->seqno) { - relay->action = XDP_TCP_RESET; - tcp_table_remove(pconn, tcp_table); - relay->answer = XDP_TCP_FREE; - } else if (conn != NULL) { + } + if (!seq_ack_match) { + if (conn != NULL) { + relay->auto_answer = KNOT_XDP_MSG_RST; + relay->auto_seqno = msg->ackno; + } // else ignore. It would be better and possible, but no big value for the price of CPU. + } else { + if (conn->state == XDP_TCP_CLOSING1) { + relay->action = XDP_TCP_CLOSE; relay->auto_answer = KNOT_XDP_MSG_ACK; + relay->answer = XDP_TCP_FREE; + tcp_table_remove(pconn, tcp_table); + } else if (msg->payload.iov_len == 0) { // otherwise ignore FIN + relay->action = XDP_TCP_CLOSE; + relay->auto_answer = KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK; + conn->state = XDP_TCP_CLOSING2; } - break; - default: - break; } - - if (!knot_tcp_relay_empty(relay)) { - relay++; + break; + case KNOT_XDP_MSG_RST: + if (conn != NULL && msg->seqno == conn->seqno) { + relay->action = XDP_TCP_RESET; + tcp_table_remove(pconn, tcp_table); + relay->answer = XDP_TCP_FREE; + } else if (conn != NULL) { + relay->auto_answer = KNOT_XDP_MSG_ACK; } + break; + default: + break; } return ret; diff --git a/src/libknot/xdp/tcp.h b/src/libknot/xdp/tcp.h index 09fe652..39a30fd 100644 --- a/src/libknot/xdp/tcp.h +++ b/src/libknot/xdp/tcp.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 @@ -148,18 +148,19 @@ knot_tcp_table_t *knot_tcp_table_new(size_t size, knot_tcp_table_t *secret_share void knot_tcp_table_free(knot_tcp_table_t *table); /*! - * \brief Process received packets, prepare automatic responses (e.g. ACK), pick incoming data. + * \brief Process received packet, prepare automatic response (e.g. ACK), pick incoming data. * - * \param relays Out: relays to be filled with message/connection details. - * \param msgs Packets received by knot_xdp_recv(). - * \param msg_count Number of received packets. + * \param relay Out: relay to be filled with message/connection details. + * \param msg Packet received by knot_xdp_recv(). * \param tcp_table Table of TCP connections. * \param syn_table Optional: extra table for handling partially established connections. * \param ignore Ignore specific TCP packets indication. * + * \note resulting relay might be knot_tcp_relay_empty() + * * \return KNOT_E* */ -int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count, +int knot_tcp_recv(knot_tcp_relay_t *relay, knot_xdp_msg_t *msg, knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, knot_tcp_ignore_t ignore); diff --git a/src/libknot/xdp/xdp.c b/src/libknot/xdp/xdp.c index 8286884..132f5c4 100644 --- a/src/libknot/xdp/xdp.c +++ b/src/libknot/xdp/xdp.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 @@ -38,63 +38,68 @@ #include "contrib/net.h" #define FRAME_SIZE 2048 - -#define FRAME_COUNT_TX 2048 -#define FRAME_COUNT_RX 2048 -#define FRAME_COUNT (FRAME_COUNT_TX + FRAME_COUNT_RX) - -#define RING_LEN_TX FRAME_COUNT_TX -#define RING_LEN_CQ FRAME_COUNT_TX -#define RING_LEN_RX FRAME_COUNT_RX -/* It's recommended that the FQ ring size >= HW RX ring size + AF_XDP RX ring size. */ -#define RING_LEN_FQ (2 * FRAME_COUNT_RX) - -#define ALLOC_RETRY_NUM 15 -#define ALLOC_RETRY_DELAY 20 // In nanoseconds. - -/* With recent compilers we statically check #defines for settings that - * get refused by AF_XDP drivers (in current versions, at least). */ -#if (__STDC_VERSION__ >= 201112L) -#define IS_POWER_OF_2(n) (((n) & (n - 1)) == 0) -_Static_assert((FRAME_SIZE == 4096 || FRAME_SIZE == 2048) - && IS_POWER_OF_2(RING_LEN_TX) && IS_POWER_OF_2(RING_LEN_RX) - && IS_POWER_OF_2(RING_LEN_CQ) && IS_POWER_OF_2(RING_LEN_FQ) - && FRAME_COUNT_TX <= (1 << 16) /* see tx_free_indices */ - , "Incorrect #define combination for AF_XDP."); -#endif +#define DEFAULT_RING_SIZE 2048 +#define RETRY_DELAY 20 // In nanoseconds. struct umem_frame { uint8_t bytes[FRAME_SIZE]; }; -static int configure_xsk_umem(struct kxsk_umem **out_umem, bool extra_frames) +static bool valid_config(const knot_xdp_config_t *config) +{ + if (FRAME_SIZE != 2048 && FRAME_SIZE != 4096) { + return false; + } + + if (config == NULL) { + return true; + } + + if ((config->ring_size & (config->ring_size - 1)) != 0) { + return false; + } + + return true; +} + +static uint32_t ring_size(const knot_xdp_config_t *config) +{ + return config != NULL ? config->ring_size : DEFAULT_RING_SIZE; +} + +static int configure_xsk_umem(struct kxsk_umem **out_umem, uint32_t ring_size) { /* Allocate memory and call driver to create the UMEM. */ struct kxsk_umem *umem = calloc(1, offsetof(struct kxsk_umem, tx_free_indices) - + sizeof(umem->tx_free_indices[0]) * FRAME_COUNT_TX); + + sizeof(umem->tx_free_indices[0]) * ring_size); if (umem == NULL) { return KNOT_ENOMEM; } + umem->ring_size = ring_size; - size_t frame_count = FRAME_COUNT + (extra_frames ? FRAME_COUNT_RX : 0); + /* It's recommended that the FQ ring size >= HW RX ring size + AF_XDP RX ring size. + * However, the performance is better if FQ size == AF_XDP RX size. */ + const uint32_t FQ_SIZE = umem->ring_size; + const uint32_t CQ_SIZE = umem->ring_size; + const uint32_t FRAMES = FQ_SIZE + CQ_SIZE; int ret = posix_memalign((void **)&umem->frames, getpagesize(), - FRAME_SIZE * frame_count); + FRAME_SIZE * FRAMES); if (ret != 0) { free(umem); return KNOT_ENOMEM; } - const struct xsk_umem_config config = { - .fill_size = RING_LEN_FQ, - .comp_size = RING_LEN_CQ, + const struct xsk_umem_config umem_config = { + .fill_size = FQ_SIZE, + .comp_size = CQ_SIZE, .frame_size = FRAME_SIZE, .frame_headroom = KNOT_XDP_PKT_ALIGNMENT, }; - ret = xsk_umem__create(&umem->umem, umem->frames, FRAME_SIZE * frame_count, - &umem->fq, &umem->cq, &config); + ret = xsk_umem__create(&umem->umem, umem->frames, FRAME_SIZE * FRAMES, + &umem->fq, &umem->cq, &umem_config); if (ret != KNOT_EOK) { free(umem->frames); free(umem); @@ -103,23 +108,23 @@ static int configure_xsk_umem(struct kxsk_umem **out_umem, bool extra_frames) *out_umem = umem; /* Designate the starting chunk of buffers for TX, and put them onto the stack. */ - umem->tx_free_count = FRAME_COUNT_TX; - for (uint32_t i = 0; i < FRAME_COUNT_TX; ++i) { + umem->tx_free_count = CQ_SIZE; + for (uint32_t i = 0; i < CQ_SIZE; ++i) { umem->tx_free_indices[i] = i; } /* Designate the rest of buffers for RX, and pass them to the driver. */ uint32_t idx = 0; - ret = xsk_ring_prod__reserve(&umem->fq, frame_count - FRAME_COUNT_TX, &idx); - if (ret != frame_count - FRAME_COUNT_TX) { + ret = xsk_ring_prod__reserve(&umem->fq, FQ_SIZE, &idx); + if (ret != FQ_SIZE) { assert(0); return KNOT_ERROR; } assert(idx == 0); - for (uint32_t i = FRAME_COUNT_TX; i < frame_count; ++i) { + for (uint32_t i = CQ_SIZE; i < CQ_SIZE + FQ_SIZE; ++i) { *xsk_ring_prod__fill_addr(&umem->fq, idx++) = i * FRAME_SIZE; } - xsk_ring_prod__submit(&umem->fq, frame_count - FRAME_COUNT_TX); + xsk_ring_prod__submit(&umem->fq, FQ_SIZE); return KNOT_EOK; } @@ -131,6 +136,33 @@ static void deconfigure_xsk_umem(struct kxsk_umem *umem) free(umem); } +static int enable_busypoll(int socket, unsigned timeout_us, unsigned budget) +{ +#if defined(SO_PREFER_BUSY_POLL) && defined(SO_BUSY_POLL_BUDGET) + int opt_val = 1; + if (setsockopt(socket, SOL_SOCKET, SO_PREFER_BUSY_POLL, + &opt_val, sizeof(opt_val)) != 0) { + return knot_map_errno(); + } + + opt_val = timeout_us; + if (setsockopt(socket, SOL_SOCKET, SO_BUSY_POLL, + &opt_val, sizeof(opt_val)) != 0) { + return knot_map_errno(); + } + + opt_val = budget; + if (setsockopt(socket, SOL_SOCKET, SO_BUSY_POLL_BUDGET, + &opt_val, sizeof(opt_val)) != 0) { + return knot_map_errno(); + } + + return KNOT_EOK; +#else + return KNOT_ENOTSUP; +#endif +} + static int configure_xsk_socket(struct kxsk_umem *umem, const struct kxsk_iface *iface, knot_xdp_socket_t **out_sock, @@ -143,14 +175,14 @@ static int configure_xsk_socket(struct kxsk_umem *umem, xsk_info->iface = iface; xsk_info->umem = umem; - uint16_t bind_flags = 0; + uint16_t bind_flags = XDP_USE_NEED_WAKEUP; if (config != NULL && config->force_copy) { bind_flags |= XDP_COPY; } const struct xsk_socket_config sock_conf = { - .tx_size = RING_LEN_TX, - .rx_size = RING_LEN_RX, + .tx_size = umem->ring_size, + .rx_size = umem->ring_size, .libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, .bind_flags = bind_flags, }; @@ -163,6 +195,17 @@ static int configure_xsk_socket(struct kxsk_umem *umem, return ret; } + if (config != NULL && config->busy_poll_budget > 0) { + ret = enable_busypoll(xsk_socket__fd(xsk_info->xsk), + config->busy_poll_timeout, config->busy_poll_budget); + if (ret != KNOT_EOK) { + xsk_socket__delete(xsk_info->xsk); + free(xsk_info); + return ret; + } + xsk_info->busy_poll = true; + } + *out_sock = xsk_info; return KNOT_EOK; } @@ -172,7 +215,7 @@ int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue, knot_xdp_filter_flag_t flags, uint16_t udp_port, uint16_t quic_port, knot_xdp_load_bpf_t load_bpf, const knot_xdp_config_t *xdp_config) { - if (socket == NULL || if_name == NULL || + if (socket == NULL || if_name == NULL || !valid_config(xdp_config) || (udp_port == quic_port && (flags & KNOT_XDP_FILTER_UDP) && (flags & KNOT_XDP_FILTER_QUIC)) || (flags & (KNOT_XDP_FILTER_UDP | KNOT_XDP_FILTER_TCP | KNOT_XDP_FILTER_QUIC)) == 0) { return KNOT_EINVAL; @@ -187,7 +230,7 @@ int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue, /* Initialize shared packet_buffer for umem usage. */ struct kxsk_umem *umem = NULL; - ret = configure_xsk_umem(&umem, xdp_config->extra_frames); + ret = configure_xsk_umem(&umem, ring_size(xdp_config)); if (ret != KNOT_EOK) { kxsk_iface_free(iface); return ret; @@ -266,7 +309,7 @@ static void tx_free_relative(struct kxsk_umem *umem, uint64_t addr_relative) { /* The address may not point to *start* of buffer, but `/` solves that. */ uint64_t index = addr_relative / FRAME_SIZE; - assert(index < FRAME_COUNT); + assert(index < umem->ring_size); umem->tx_free_indices[umem->tx_free_count++] = index; } @@ -285,7 +328,7 @@ void knot_xdp_send_prepare(knot_xdp_socket_t *socket) if (completed == 0) { return; } - assert(umem->tx_free_count + completed <= FRAME_COUNT_TX); + assert(umem->tx_free_count + completed <= umem->ring_size); for (uint32_t i = 0; i < completed; ++i) { uint64_t addr_relative = *xsk_ring_cons__comp_addr(cq, idx++); @@ -301,12 +344,13 @@ static struct umem_frame *alloc_tx_frame(knot_xdp_socket_t *socket) return malloc(sizeof(struct umem_frame)); } - const struct timespec delay = { .tv_nsec = ALLOC_RETRY_DELAY }; struct kxsk_umem *umem = socket->umem; - for (int i = 0; unlikely(umem->tx_free_count == 0); i++) { - if (i == ALLOC_RETRY_NUM) { - return NULL; + const struct timespec delay = { .tv_nsec = RETRY_DELAY }; + while (unlikely(umem->tx_free_count == 0)) { + if (socket->busy_poll || xsk_ring_prod__needs_wakeup(&socket->tx)) { + (void)sendto(xsk_socket__fd(socket->xsk), NULL, 0, + MSG_DONTWAIT, NULL, 0); } nanosleep(&delay, NULL); knot_xdp_send_prepare(socket); @@ -381,9 +425,7 @@ int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], } if (unlikely(socket->send_mock != NULL)) { int ret = socket->send_mock(socket, msgs, count, sent); - for (uint32_t i = 0; i < count; ++i) { - free_unsent(socket, &msgs[i]); - } + knot_xdp_send_free(socket, msgs, count); return ret; } @@ -393,12 +435,13 @@ int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], * and the API doesn't allow "cancelling reservations". * Therefore we handle `socket->tx.cached_prod` by hand. */ - if (xsk_prod_nb_free(&socket->tx, count) < count) { - /* This situation was sometimes observed in the emulated XDP mode. */ - for (uint32_t i = 0; i < count; ++i) { - free_unsent(socket, &msgs[i]); + const struct timespec delay = { .tv_nsec = RETRY_DELAY }; + while (unlikely(xsk_prod_nb_free(&socket->tx, count) < count)) { + if (socket->busy_poll || xsk_ring_prod__needs_wakeup(&socket->tx)) { + (void)sendto(xsk_socket__fd(socket->xsk), NULL, 0, + MSG_DONTWAIT, NULL, 0); } - return KNOT_ENOBUFS; + nanosleep(&delay, NULL); } uint32_t idx = socket->tx.cached_prod; @@ -425,7 +468,6 @@ int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[], assert(*sent <= count); socket->tx.cached_prod = idx; xsk_ring_prod__submit(&socket->tx, *sent); - socket->kernel_needs_wakeup = true; return KNOT_EOK; } @@ -446,34 +488,19 @@ int knot_xdp_send_finish(knot_xdp_socket_t *socket) return KNOT_EINVAL; } - /* Trigger sending queued packets. */ - if (!socket->kernel_needs_wakeup) { + if (!socket->busy_poll && !xsk_ring_prod__needs_wakeup(&socket->tx)) { return KNOT_EOK; } int ret = sendto(xsk_socket__fd(socket->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); - const bool is_ok = (ret >= 0); - // List of "safe" errors taken from - // https://github.com/torvalds/linux/blame/master/samples/bpf/xdpsock_user.c - const bool is_again = !is_ok && (errno == ENOBUFS || errno == EAGAIN - || errno == EBUSY || errno == ENETDOWN); - // Some of the !is_ok cases are a little unclear - what to do about the syscall, - // including how caller of _sendmsg_finish() should react. - if (is_ok || !is_again) { - socket->kernel_needs_wakeup = false; - } - if (is_again) { - return KNOT_EAGAIN; - } else if (is_ok) { + if (ret >= 0) { return KNOT_EOK; + } else if (errno == ENOBUFS || errno == EAGAIN || errno == EBUSY || + errno == ENETDOWN) { + return KNOT_EAGAIN; } else { return -errno; } - /* This syscall might be avoided with a newer kernel feature (>= 5.4): - https://www.kernel.org/doc/html/latest/networking/af_xdp.html#xdp-use-need-wakeup-bind-flag - Unfortunately it's not easy to continue supporting older kernels - when using this feature on newer ones. - */ } _public_ @@ -518,7 +545,7 @@ int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[], static uint8_t *msg_uframe_ptr(const knot_xdp_msg_t *msg) { - return NULL + ((msg->payload.iov_base - NULL) & ~(FRAME_SIZE - 1)); + return (uint8_t *)((uintptr_t)msg->payload.iov_base & ~(FRAME_SIZE - 1)); } _public_ @@ -529,30 +556,32 @@ void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[] return; } - const struct timespec delay = { .tv_nsec = ALLOC_RETRY_DELAY }; - struct kxsk_umem *const umem = socket->umem; struct xsk_ring_prod *const fq = &umem->fq; uint32_t idx = 0; - uint32_t reserved = xsk_ring_prod__reserve(fq, count, &idx); - for (int i = 0; unlikely(reserved < count); i++) { - if (i == ALLOC_RETRY_NUM) { - return; + const struct timespec delay = { .tv_nsec = RETRY_DELAY }; + while (unlikely(xsk_ring_prod__reserve(fq, count, &idx) != count)) { + if (socket->busy_poll || xsk_ring_prod__needs_wakeup(fq)) { + (void)recvfrom(xsk_socket__fd(socket->xsk), NULL, 0, + MSG_DONTWAIT, NULL, NULL); } nanosleep(&delay, NULL); - reserved = xsk_ring_prod__reserve(fq, count, &idx); } - for (uint32_t i = 0; i < reserved; ++i) { + for (uint32_t i = 0; i < count; ++i) { uint8_t *uframe_p = msg_uframe_ptr(&msgs[i]); uint64_t offset = uframe_p - umem->frames->bytes; *xsk_ring_prod__fill_addr(fq, idx++) = offset; } - xsk_ring_prod__submit(fq, reserved); + xsk_ring_prod__submit(fq, count); + // recvfrom() here slightly worsens the performance, poll is called later anyway. } +// The number of busy frames +#define RING_BUSY(ring) ((*(ring)->producer - *(ring)->consumer) & (ring)->mask) + _public_ void knot_xdp_socket_info(const knot_xdp_socket_t *socket, FILE *file) { @@ -560,10 +589,6 @@ void knot_xdp_socket_info(const knot_xdp_socket_t *socket, FILE *file) return; } - // The number of busy frames - #define RING_BUSY(ring) \ - ((*(ring)->producer - *(ring)->consumer) & (ring)->mask) - #define RING_PRINFO(name, ring) \ fprintf(file, "Ring %s: size %4d, busy %4d (prod %4d, cons %4d)\n", \ name, (unsigned)(ring)->size, \ @@ -571,11 +596,11 @@ void knot_xdp_socket_info(const knot_xdp_socket_t *socket, FILE *file) (unsigned)*(ring)->producer, (unsigned)*(ring)->consumer) const int rx_busyf = RING_BUSY(&socket->umem->fq) + RING_BUSY(&socket->rx); - fprintf(file, "\nLOST RX frames: %4d", (int)(FRAME_COUNT_RX - rx_busyf)); + fprintf(file, "\nLOST RX frames: %4d", (int)(socket->umem->ring_size - rx_busyf)); const int tx_busyf = RING_BUSY(&socket->umem->cq) + RING_BUSY(&socket->tx); const int tx_freef = socket->umem->tx_free_count; - fprintf(file, "\nLOST TX frames: %4d\n", (int)(FRAME_COUNT_TX - tx_busyf - tx_freef)); + fprintf(file, "\nLOST TX frames: %4d\n", (int)(socket->umem->ring_size - tx_busyf - tx_freef)); RING_PRINFO("FQ", &socket->umem->fq); RING_PRINFO("RX", &socket->rx); @@ -583,3 +608,39 @@ void knot_xdp_socket_info(const knot_xdp_socket_t *socket, FILE *file) RING_PRINFO("CQ", &socket->umem->cq); fprintf(file, "TX free frames: %4d\n", tx_freef); } + +_public_ +int knot_xdp_socket_stats(knot_xdp_socket_t *socket, knot_xdp_stats_t *stats) +{ + if (socket == NULL || stats == NULL) { + return KNOT_EINVAL; + } + + memset(stats, 0, sizeof(*stats)); + + stats->if_name = socket->iface->if_name; + stats->if_index = socket->iface->if_index; + stats->if_queue = socket->iface->if_queue; + + struct xdp_statistics xdp_stats; + socklen_t optlen = sizeof(xdp_stats); + + int fd = knot_xdp_socket_fd(socket); + int ret = getsockopt(fd, SOL_XDP, XDP_STATISTICS, &xdp_stats, &optlen); + if (ret != 0) { + return knot_map_errno(); + } else if (optlen != sizeof(xdp_stats)) { + return KNOT_EINVAL; + } + + size_t common_size = MIN(sizeof(xdp_stats), sizeof(stats->socket)); + memcpy(&stats->socket, &xdp_stats, common_size); + + stats->rings.tx_busy = socket->umem->ring_size - socket->umem->tx_free_count; + stats->rings.fq_fill = RING_BUSY(&socket->umem->fq); + stats->rings.rx_fill = RING_BUSY(&socket->rx); + stats->rings.tx_fill = RING_BUSY(&socket->tx); + stats->rings.cq_fill = RING_BUSY(&socket->umem->cq); + + return KNOT_EOK; +} diff --git a/src/libknot/xdp/xdp.h b/src/libknot/xdp/xdp.h index 6c8bb1e..5944d44 100644 --- a/src/libknot/xdp/xdp.h +++ b/src/libknot/xdp/xdp.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 @@ -53,14 +53,54 @@ typedef struct knot_xdp_socket knot_xdp_socket_t; /*! \brief Configuration of XDP socket. */ struct knot_xdp_config { - bool force_generic; /*!< Use generic XDP mode (avoid driver/hadrware implementation). */ - bool force_copy; /*!< Force copying packet data between kernel and user-space (avoid zero-copy). */ - bool extra_frames; /*!< Extra FQ frames. */ + uint16_t ring_size; /*!< Size of RX and TX rings (must be power of 2). */ + bool force_generic; /*!< Use generic XDP mode (avoid driver/hardware implementation). */ + bool force_copy; /*!< Force copying packet data between kernel and user-space (avoid zero-copy). */ + unsigned busy_poll_timeout; /*!< Preferred busy poll budget (0 means disabled). */ + unsigned busy_poll_budget; /*!< Preferred busy poll timeout (in microseconds) . */ }; /*! \brief Configuration of XDP socket. */ typedef struct knot_xdp_config knot_xdp_config_t; +/*! \brief Various statistics of an XDP socket (optimally kernel >=5.9). */ +typedef struct { + /*! Interface name. */ + const char *if_name; + /*! Interface name index (derived from ifname). */ + int if_index; + /*! Network card queue id. */ + unsigned if_queue; + /*! Counters (xdp_statistics) retrieved from the kernel via XDP_STATISTICS. */ + struct { + /*! Dropped for other reasons. */ + uint64_t rx_dropped; + /*! Dropped due to invalid descriptor. */ + uint64_t rx_invalid; + /*! Dropped due to invalid descriptor. */ + uint64_t tx_invalid; + /*! Dropped due to rx ring being full. */ + uint64_t rx_full; + /*! Failed to retrieve item from fill ring. */ + uint64_t fq_empty; + /*! Failed to retrieve item from tx ring. */ + uint64_t tx_empty; + } socket; + /*! States of rings of the XDP socket. */ + struct { + /*! Busy TX buffers. */ + uint16_t tx_busy; + /*! Free buffers to consume from FQ ring. */ + uint16_t fq_fill; + /*! Pending buffers in TX ring. */ + uint16_t rx_fill; + /*! Pending buffers in RX ring. */ + uint16_t tx_fill; + /*! Pending buffers in CQ ring. */ + uint16_t cq_fill; + } rings; +} knot_xdp_stats_t; + /*! * \brief Initialize XDP socket. * @@ -196,4 +236,14 @@ void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[] */ void knot_xdp_socket_info(const knot_xdp_socket_t *socket, FILE *file); +/*! + * \brief Gets various statistics of the XDP socket. + * + * \param socket XDP socket. + * \param stats Output structure. + * + * \return KNOT_E* + */ +int knot_xdp_socket_stats(knot_xdp_socket_t *socket, knot_xdp_stats_t *stats); + /*! @} */ diff --git a/src/libknot/yparser/ypschema.h b/src/libknot/yparser/ypschema.h index 57ced72..7fca93e 100644 --- a/src/libknot/yparser/ypschema.h +++ b/src/libknot/yparser/ypschema.h @@ -130,6 +130,8 @@ typedef union { int64_t dflt; /*! Possible unit type. */ yp_style_t unit; + /*! Alternative default value. */ + int64_t dflt_alt; } i; /*! Boolean variables. */ struct { diff --git a/src/libknot/yparser/yptrafo.c b/src/libknot/yparser/yptrafo.c index 60b3717..764a5d1 100644 --- a/src/libknot/yparser/yptrafo.c +++ b/src/libknot/yparser/yptrafo.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 @@ -31,25 +31,31 @@ #include "contrib/wire_ctx.h" enum { - UNIT_BYTE = 'B', - UNIT_KILO = 'K', - UNIT_MEGA = 'M', - UNIT_GIGA = 'G', - UNIT_SEC = 's', - UNIT_MIN = 'm', - UNIT_HOUR = 'h', - UNIT_DAY = 'd' + UNIT_BYTE = 'B', + UNIT_KILO = 'K', + UNIT_MEGA = 'M', + UNIT_GIGA = 'G', + UNIT_SEC = 's', + UNIT_MIN = 'm', + UNIT_HOUR = 'h', + UNIT_DAY = 'd', + UNIT_WEEK = 'w', + UNIT_MONTH = 'M', + UNIT_YEAR = 'y', }; enum { - MULTI_BYTE = 1, - MULTI_KILO = 1024, - MULTI_MEGA = 1024 * 1024, - MULTI_GIGA = 1024 * 1024 * 1024, - MULTI_SEC = 1, - MULTI_MIN = 60, - MULTI_HOUR = 3600, - MULTI_DAY = 24 * 3600 + MULTI_BYTE = 1, + MULTI_KILO = 1024, + MULTI_MEGA = 1024 * 1024, + MULTI_GIGA = 1024 * 1024 * 1024, + MULTI_SEC = 1, + MULTI_MIN = 60, + MULTI_HOUR = 3600, + MULTI_DAY = 24 * 3600, + MULTI_WEEK = MULTI_DAY * 7, + MULTI_MONTH = MULTI_DAY * 30, + MULTI_YEAR = MULTI_DAY * 365, }; static wire_ctx_t copy_in( @@ -186,6 +192,15 @@ static int remove_unit( case UNIT_DAY: multiplier = MULTI_DAY; break; + case UNIT_WEEK: + multiplier = MULTI_WEEK; + break; + case UNIT_MONTH: + multiplier = MULTI_MONTH; + break; + case UNIT_YEAR: + multiplier = MULTI_YEAR; + break; default: return KNOT_EINVAL; } @@ -295,9 +310,18 @@ static void add_unit( } else if (*number < MULTI_DAY) { multiplier = MULTI_HOUR; new_unit = UNIT_HOUR; - } else { + } else if (*number < MULTI_WEEK) { multiplier = MULTI_DAY; new_unit = UNIT_DAY; + } else if (*number < MULTI_MONTH) { + multiplier = MULTI_WEEK; + new_unit = UNIT_WEEK; + } else if (*number < MULTI_YEAR) { + multiplier = MULTI_MONTH; + new_unit = UNIT_MONTH; + } else { + multiplier = MULTI_YEAR; + new_unit = UNIT_YEAR; } } diff --git a/src/libzscanner/version.h b/src/libzscanner/version.h index f24b3a9..05ab471 100644 --- a/src/libzscanner/version.h +++ b/src/libzscanner/version.h @@ -17,8 +17,8 @@ #pragma once #define ZSCANNER_VERSION_MAJOR 3 -#define ZSCANNER_VERSION_MINOR 3 -#define ZSCANNER_VERSION_PATCH 0x09 +#define ZSCANNER_VERSION_MINOR 4 +#define ZSCANNER_VERSION_PATCH 0x00 #define ZSCANNER_VERSION_HEX ((ZSCANNER_VERSION_MAJOR << 16) | \ (ZSCANNER_VERSION_MINOR << 8) | \ diff --git a/src/utils/Makefile.inc b/src/utils/Makefile.inc index 3097050..1f11282 100644 --- a/src/utils/Makefile.inc +++ b/src/utils/Makefile.inc @@ -103,7 +103,11 @@ kxdpgun_SOURCES = \ utils/kxdpgun/ip_route.h \ utils/kxdpgun/load_queries.c \ utils/kxdpgun/load_queries.h \ - utils/kxdpgun/main.c + utils/kxdpgun/main.c \ + utils/kxdpgun/main.h \ + utils/kxdpgun/stats.c \ + utils/kxdpgun/stats.h + kxdpgun_CPPFLAGS = $(libknotus_la_CPPFLAGS) $(libmnl_CFLAGS) kxdpgun_LDADD = libknot.la $(libcontrib_LIBS) $(libmnl_LIBS) $(pthread_LIBS) diff --git a/src/utils/common/msg.h b/src/utils/common/msg.h index d2ed57e..fbd6c8e 100644 --- a/src/utils/common/msg.h +++ b/src/utils/common/msg.h @@ -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 @@ -23,10 +23,10 @@ #define WARNING_ ";; WARNING: " #define DEBUG_ ";; DEBUG: " -#define ERR(msg, ...) { fprintf(stderr, ERROR_ msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define INFO(msg, ...) { fprintf(stdout, INFO_ msg "\n", ##__VA_ARGS__); fflush(stdout); } -#define WARN(msg, ...) { fprintf(stderr, WARNING_ msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define DBG(msg, ...) { msg_debug(DEBUG_ msg "\n", ##__VA_ARGS__); fflush(stdout); } +#define ERR(msg, ...) do { fprintf(stderr, ERROR_ msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define INFO(msg, ...) do { fprintf(stdout, INFO_ msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) +#define WARN(msg, ...) do { fprintf(stderr, WARNING_ msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define DBG(msg, ...) do { msg_debug(DEBUG_ msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) /*! \brief Enable/disable debugging. */ int msg_enable_debug(int val); @@ -37,6 +37,6 @@ int msg_debug(const char *fmt, ...); /*! \brief Debug message for null input. */ #define DBG_NULL DBG("%s: null parameter", __func__) -#define ERR2(msg, ...) { fprintf(stderr, "error: " msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define WARN2(msg, ...) { fprintf(stderr, "warning: " msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define INFO2(msg, ...) { fprintf(stdout, msg "\n", ##__VA_ARGS__); fflush(stdout); } +#define ERR2(msg, ...) do { fprintf(stderr, "error: " msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define WARN2(msg, ...) do { fprintf(stderr, "warning: " msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define INFO2(msg, ...) do { fprintf(stdout, msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c index eed14ee..8ea7b59 100644 --- a/src/utils/common/netio.c +++ b/src/utils/common/netio.c @@ -32,6 +32,7 @@ #include "utils/common/msg.h" #include "utils/common/tls.h" #include "libknot/libknot.h" +#include "libknot/quic/tls_common.h" #include "contrib/net.h" #include "contrib/proxyv2/proxyv2.h" #include "contrib/sockaddr.h" @@ -521,8 +522,8 @@ int net_connect(net_t *net) #endif //LIBNGHTTP2 { // Establish TLS connection. - ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, NULL, - net_get_remote(net)); + ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, + KNOT_TLS_PRIORITIES, net_get_remote(net)); if (ret != 0) { net_close(net); return ret; @@ -546,7 +547,7 @@ int net_connect(net_t *net) return ret; } ret = tls_ctx_setup_remote_endpoint(&net->tls, - &doq_alpn, 1, QUIC_PRIORITY, net_get_remote(net)); + &doq_alpn, 1, KNOT_TLS_PRIORITIES, net_get_remote(net)); if (ret != 0) { net_close(net); return ret; diff --git a/src/utils/common/params.c b/src/utils/common/params.c index d16af4c..fe5a854 100644 --- a/src/utils/common/params.c +++ b/src/utils/common/params.c @@ -21,7 +21,7 @@ #include <sys/socket.h> #ifdef LIBIDN -#include LIBIDN_HEADER +#include <idn2.h> #endif #include "utils/common/params.h" diff --git a/src/utils/common/params.h b/src/utils/common/params.h index 8b7565e..bb071aa 100644 --- a/src/utils/common/params.h +++ b/src/utils/common/params.h @@ -22,6 +22,7 @@ #include <stdio.h> #include "libknot/libknot.h" +#include "contrib/string.h" #include "contrib/ucw/lists.h" #define DEFAULT_IPV4_NAME "127.0.0.1" @@ -31,7 +32,7 @@ #define DEFAULT_DNS_QUIC_PORT "853" #define DEFAULT_DNS_TLS_PORT "853" #define DEFAULT_UDP_SIZE 512 -#define DEFAULT_EDNS_SIZE 4096 +#define DEFAULT_EDNS_SIZE 1232 #define MAX_PACKET_SIZE 65535 #define SEP_CHARS "\n\t " @@ -118,9 +119,15 @@ typedef struct { param_handle_f handler; } param_t; -inline static void print_version(const char *program_name) +inline static void print_version(const char *prog_name, bool verbose) { - printf("%s (Knot DNS), version %s\n", program_name, PACKAGE_VERSION); + if (prog_name != NULL) { + printf("%s, ", prog_name); + } + printf("Knot DNS %s\n", PACKAGE_VERSION); + if (verbose) { + printf("\n%s\n", configure_summary); + } } /*! diff --git a/src/utils/common/quic.h b/src/utils/common/quic.h index fd70d27..2b860c3 100644 --- a/src/utils/common/quic.h +++ b/src/utils/common/quic.h @@ -35,10 +35,6 @@ void quic_params_clean(quic_params_t *params); #include "utils/common/tls.h" -#define QUIC_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3" -#define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:+GROUP-SECP384R1:+GROUP-SECP521R1" -#define QUIC_PRIORITY "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_GROUPS - typedef enum { CLOSED, // Initialized CONNECTED, // RTT-0 diff --git a/src/utils/common/tls.c b/src/utils/common/tls.c index 276ae16..4c9a588 100644 --- a/src/utils/common/tls.c +++ b/src/utils/common/tls.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 @@ -398,7 +398,7 @@ int tls_certificate_verification(tls_ctx_t *ctx) }; size_t data_count = (ctx->params->hostname != NULL) ? 2 : 1; if (data_count == 1) { - WARN("TLS, no hostname provided, will not verify certificate owner") + WARN("TLS, no hostname provided, will not verify certificate owner"); } unsigned int status; @@ -533,7 +533,8 @@ int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn, } if (priority != NULL) { - ret = gnutls_priority_set_direct(ctx->session, priority, NULL); + ret = gnutls_set_default_priority_append(ctx->session, priority, + NULL, 0); } else { ret = gnutls_set_default_priority(ctx->session); } diff --git a/src/utils/kcatalogprint/main.c b/src/utils/kcatalogprint/main.c index 0172347..85e50b6 100644 --- a/src/utils/kcatalogprint/main.c +++ b/src/utils/kcatalogprint/main.c @@ -108,7 +108,7 @@ int main(int argc, char *argv[]) { "catalog", required_argument, NULL, 'a' }, { "member", required_argument, NULL, 'm' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:a:m:hV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:a:m:hV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); @@ -155,13 +155,9 @@ int main(int argc, char *argv[]) } } - // Backward compatibility. if (argc - optind > 0) { - WARN2("obsolete parameter specified"); - if (util_conf_init_justdb("catalog-db", argv[optind]) != KNOT_EOK) { - goto failure; - } - optind++; + print_help(); + goto failure; } if (util_conf_init_default(true) != KNOT_EOK) { diff --git a/src/utils/kdig/kdig_params.c b/src/utils/kdig/kdig_params.c index c8fd83f..8566848 100644 --- a/src/utils/kdig/kdig_params.c +++ b/src/utils/kdig/kdig_params.c @@ -1690,7 +1690,7 @@ query_t *query_create(const char *owner, const query_t *conf) query->style.style.now = knot_time(); query->idn = true; query->nsid = false; - query->edns = -1; + query->edns = 0; query->cc.len = 0; query->sc.len = 0; query->badcookie = BADCOOKIE_RETRY_MAX; @@ -2517,12 +2517,7 @@ static int parse_opt1(const char *opt, const char *value, kdig_params_t *params, *index += add; break; case 'V': - if (len > 1) { - ERR("invalid option -%s", opt); - return KNOT_ENOTSUP; - } - - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, len > 1); params->stop = true; break; case 'x': @@ -2598,8 +2593,8 @@ static int parse_opt1(const char *opt, const char *value, kdig_params_t *params, if (strcmp(opt, "-help") == 0) { print_help(); params->stop = true; - } else if (strcmp(opt, "-version") == 0) { - print_version(PROGRAM_NAME); + } else if (strncmp(opt, "-version", 8) == 0) { + print_version(PROGRAM_NAME, strlen(opt) > 9); params->stop = true; } else { ERR("invalid option: -%s", opt); diff --git a/src/utils/keymgr/bind_privkey.c b/src/utils/keymgr/bind_privkey.c index 9ab895c..bbb61a5 100644 --- a/src/utils/keymgr/bind_privkey.c +++ b/src/utils/keymgr/bind_privkey.c @@ -281,9 +281,7 @@ static int rsa_params_to_pem(const bind_privkey_t *params, dnssec_binary_t *pem) static gnutls_ecc_curve_t choose_ecdsa_curve(size_t pubkey_size) { switch (pubkey_size) { -#ifdef HAVE_ED25519 case 32: return GNUTLS_ECC_CURVE_ED25519; -#endif #ifdef HAVE_ED448 case 57: return GNUTLS_ECC_CURVE_ED448; #endif @@ -334,7 +332,6 @@ static int ecdsa_params_to_pem(dnssec_key_t *dnskey, const bind_privkey_t *param return dnssec_pem_from_x509(key, pem); } -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static void eddsa_extract_public_params(dnssec_key_t *key, gnutls_ecc_curve_t *curve, gnutls_datum_t *x) { @@ -371,7 +368,6 @@ static int eddsa_params_to_pem(dnssec_key_t *dnskey, const bind_privkey_t *param return dnssec_pem_from_x509(key, pem); } -#endif int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary_t *pem) { @@ -385,15 +381,11 @@ int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: return ecdsa_params_to_pem(key, params, pem); -#ifdef HAVE_ED25519 case DNSSEC_KEY_ALGORITHM_ED25519: -#endif #ifdef HAVE_ED448 case DNSSEC_KEY_ALGORITHM_ED448: #endif -#if defined(HAVE_ED25519) || defined(HAVE_ED448) return eddsa_params_to_pem(key, params, pem); -#endif default: return DNSSEC_INVALID_KEY_ALGORITHM; } diff --git a/src/utils/keymgr/main.c b/src/utils/keymgr/main.c index b46aaa0..999b5c5 100644 --- a/src/utils/keymgr/main.c +++ b/src/utils/keymgr/main.c @@ -333,11 +333,10 @@ int main(int argc, char *argv[]) { "tsig", required_argument, NULL, 't' }, { "extended", no_argument, NULL, 'e' }, { "list", no_argument, NULL, 'l' }, - { "brief", no_argument, NULL, 'b' }, // Legacy. { "mono", no_argument, NULL, 'x' }, { "color", no_argument, NULL, 'X' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { "json", no_argument, NULL, 'j' }, { NULL } }; @@ -358,7 +357,7 @@ int main(int argc, char *argv[]) list_params.color = isatty(STDOUT_FILENO); int opt = 0, parm = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:t:ejlbxXhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:t:ejlxXhV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -394,9 +393,6 @@ int main(int argc, char *argv[]) case 'l': just_list = true; break; - case 'b': - WARN2("option '--brief' is deprecated and enabled by default"); - break; case 'x': list_params.color = false; break; @@ -407,7 +403,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); diff --git a/src/utils/keymgr/offline_ksk.c b/src/utils/keymgr/offline_ksk.c index b4260b9..05b2d2b 100644 --- a/src/utils/keymgr/offline_ksk.c +++ b/src/utils/keymgr/offline_ksk.c @@ -37,6 +37,8 @@ static int pregenerate_once(kdnssec_ctx_t *ctx, knot_time_t *next) { zone_sign_reschedule_t resch = { 0 }; + memset(ctx->stats, 0, sizeof(*ctx->stats)); + // generate ZSKs int ret = knot_dnssec_key_rollover(ctx, KEY_ROLL_ALLOW_ZSK_ROLL | KEY_ROLL_PRESERVE_FUTURE, &resch); if (ret != KNOT_EOK) { @@ -245,6 +247,9 @@ static int ksr_once(kdnssec_ctx_t *ctx, char **buf, size_t *buf_size, knot_time_ { knot_rrset_t *dnskey = NULL; zone_keyset_t keyset = { 0 }; + + memset(ctx->stats, 0, sizeof(*ctx->stats)); + int ret = load_dnskey_rrset(ctx, &dnskey, &keyset); if (ret != KNOT_EOK) { goto done; @@ -322,10 +327,10 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no zone_keyset_t keyset = { 0 }; char *buf = NULL; size_t buf_size = 4096; - knot_time_t rrsigs_expire = 0; ctx->now = now; ctx->policy->dnskey_ttl = zsk->ttl; + memset(ctx->stats, 0, sizeof(*ctx->stats)); knot_timediff_t rrsig_refresh = ctx->policy->rrsig_refresh_before; if (rrsig_refresh == UINT32_MAX) { // not setting rrsig-refresh prohibited by documentation, but we need to do something @@ -352,7 +357,7 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no // no check if the KSK used for signing (in keyset) is contained in DNSKEY record being signed (in KSR) ! for (int i = 0; i < keyset.count; i++) { - ret = key_records_sign(&keyset.keys[i], &r, ctx, &rrsigs_expire); + ret = key_records_sign(&keyset.keys[i], &r, ctx); if (ret != KNOT_EOK) { goto done; } @@ -362,7 +367,7 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no print_header("SignedKeyResponse "KSR_SKR_VER, ctx->now, buf); *next_sign = knot_time_min( knot_get_next_zone_key_event(&keyset), - knot_time_add(rrsigs_expire, -rrsig_refresh) + knot_time_add(ctx->stats->expire, -rrsig_refresh) ); } @@ -446,6 +451,7 @@ static void skr_import_header(zs_scanner_t *sc) // trailing header without timestamp next_timestamp = 0; } + knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp; // delete possibly existing conflicting offline records ctx->ret = kasp_db_delete_offline_records( @@ -454,16 +460,11 @@ static void skr_import_header(zs_scanner_t *sc) // store previous SKR if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) { - ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp); + ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts); if (ctx->ret != KNOT_EOK) { return; } - if (next_timestamp > 0) { - ctx->ret = key_records_verify(&ctx->r, ctx->kctx, next_timestamp - 1); - if (ctx->ret != KNOT_EOK) { - return; - } - } + ctx->ret = kasp_db_store_offline_records(ctx->kctx->kasp_db, ctx->timestamp, &ctx->r); key_records_clear_rdatasets(&ctx->r); @@ -490,20 +491,14 @@ static void skr_validate_header(zs_scanner_t *sc) // trailing header without timestamp next_timestamp = 0; } + knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp; if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) { - int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp); + int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts); if (ret != KNOT_EOK) { // ctx->ret untouched ERR2("invalid SignedKeyResponse for %"KNOT_TIME_PRINTF" (%s)", ctx->timestamp, knot_strerror(ret)); } - if (next_timestamp > 0) { - ret = key_records_verify(&ctx->r, ctx->kctx, next_timestamp - 1); - if (ret != KNOT_EOK) { // ctx->ret untouched - ERR2("invalid SignedKeyResponse for %"KNOT_TIME_PRINTF" (%s)", - next_timestamp - 1, knot_strerror(ret)); - } - } key_records_clear_rdatasets(&ctx->r); } diff --git a/src/utils/khost/khost_params.c b/src/utils/khost/khost_params.c index 1423e09..de95d9b 100644 --- a/src/utils/khost/khost_params.c +++ b/src/utils/khost/khost_params.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -251,14 +251,14 @@ int khost_parse(kdig_params_t *params, int argc, char *argv[]) // Long options. struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; // Command line options processing. int opt = 0; - while ((opt = getopt_long(argc, argv, "46adhrsTvVwc:t:R:W:", opts, NULL)) + while ((opt = getopt_long(argc, argv, "46adhrsTvV::wc:t:R:W:", opts, NULL)) != -1) { switch (opt) { case '4': @@ -294,7 +294,7 @@ int khost_parse(kdig_params_t *params, int argc, char *argv[]) conf->style.show_footer = true; break; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); params->stop = false; return KNOT_EOK; case 'w': diff --git a/src/utils/kjournalprint/main.c b/src/utils/kjournalprint/main.c index 3ba0019..1d633dd 100644 --- a/src/utils/kjournalprint/main.c +++ b/src/utils/kjournalprint/main.c @@ -342,11 +342,10 @@ int main(int argc, char *argv[]) { "zone-list", no_argument, NULL, 'z' }, { "check", no_argument, NULL, 'H' }, { "debug", no_argument, NULL, 'd' }, - { "no-color", no_argument, NULL, 'n' }, { "mono", no_argument, NULL, 'x' }, { "color", no_argument, NULL, 'X' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -354,7 +353,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:l:s:zHdnxXhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:l:s:zHdxXhV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -393,7 +392,6 @@ int main(int argc, char *argv[]) case 'd': params.debug = true; break; - case 'n': case 'x': params.color = false; break; @@ -404,7 +402,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); @@ -412,15 +410,6 @@ int main(int argc, char *argv[]) } } - // Backward compatibility. - if ((justlist && (argc - optind > 0)) || (!justlist && (argc - optind > 1))) { - WARN2("obsolete parameter specified"); - if (util_conf_init_justdb("journal-db", argv[optind]) != KNOT_EOK) { - goto failure; - } - optind++; - } - signal_ctx.color = params.color; if (util_conf_init_default(true) != KNOT_EOK) { diff --git a/src/utils/knotc/commands.c b/src/utils/knotc/commands.c index c2c25a2..5cc1a14 100644 --- a/src/utils/knotc/commands.c +++ b/src/utils/knotc/commands.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 @@ -51,6 +51,7 @@ #define CMD_ZONE_BACKUP "zone-backup" #define CMD_ZONE_RESTORE "zone-restore" #define CMD_ZONE_SIGN "zone-sign" +#define CMD_ZONE_VALIDATE "zone-validate" #define CMD_ZONE_KEYS_LOAD "zone-keys-load" #define CMD_ZONE_KEY_ROLL "zone-key-rollover" #define CMD_ZONE_KSK_SBM "zone-ksk-submitted" @@ -99,7 +100,7 @@ static int check_args(cmd_args_t *args, int min, int max) { if (max == 0 && args->argc > 0) { - log_error("command doesn't take arguments"); + log_error("command does not take arguments"); return KNOT_EINVAL; } else if (min == max && args->argc != min) { log_error("command requires %i arguments", min); @@ -272,6 +273,7 @@ static void format_data(cmd_args_t *args, knot_ctl_type_t data_type, case CTL_ZONE_BACKUP: case CTL_ZONE_RESTORE: case CTL_ZONE_SIGN: + case CTL_ZONE_VALIDATE: case CTL_ZONE_KEYS_LOAD: case CTL_ZONE_KEY_ROLL: case CTL_ZONE_KSK_SBM: @@ -411,6 +413,7 @@ static void format_block(ctl_cmd_t cmd, bool failed, bool empty) case CTL_ZONE_BACKUP: case CTL_ZONE_RESTORE: case CTL_ZONE_SIGN: + case CTL_ZONE_VALIDATE: case CTL_ZONE_KEYS_LOAD: case CTL_ZONE_KEY_ROLL: case CTL_ZONE_KSK_SBM: @@ -1294,6 +1297,7 @@ const cmd_desc_t cmd_table[] = { { CMD_ZONE_BACKUP, cmd_zone_filter_ctl, CTL_ZONE_BACKUP, CMD_FOPT_ZONE }, { CMD_ZONE_RESTORE, cmd_zone_filter_ctl, CTL_ZONE_RESTORE, CMD_FOPT_ZONE }, { CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN, CMD_FOPT_ZONE }, + { CMD_ZONE_VALIDATE, cmd_zone_ctl, CTL_ZONE_VALIDATE, CMD_FOPT_ZONE }, { CMD_ZONE_KEYS_LOAD, cmd_zone_ctl, CTL_ZONE_KEYS_LOAD, CMD_FOPT_ZONE }, { CMD_ZONE_KEY_ROLL, cmd_zone_key_roll_ctl, CTL_ZONE_KEY_ROLL, CMD_FREQ_ZONE }, { CMD_ZONE_KSK_SBM, cmd_zone_ctl, CTL_ZONE_KSK_SBM, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, @@ -1347,6 +1351,7 @@ static const cmd_help_t cmd_help_table[] = { { CMD_ZONE_BACKUP, "[<zone>...] [<filter>...] +backupdir <dir>", "Backup zone data and metadata. (#)" }, { CMD_ZONE_RESTORE, "[<zone>...] [<filter>...] +backupdir <dir>", "Restore zone data and metadata. (#)" }, { CMD_ZONE_SIGN, "[<zone>...]", "Re-sign the automatically signed zone. (#)" }, + { CMD_ZONE_VALIDATE, "[<zone>...]", "Trigger a DNSSEC validation of the zone. (#)" }, { CMD_ZONE_KEYS_LOAD, "[<zone>...]", "Re-load keys from KASP database, sign the zone. (#)" }, { CMD_ZONE_KEY_ROLL, " <zone> ksk|zsk", "Trigger immediate key rollover. (#)" }, { CMD_ZONE_KSK_SBM, " <zone>...", "When KSK submission, confirm parent's DS presence. (#)" }, diff --git a/src/utils/knotc/main.c b/src/utils/knotc/main.c index dad3671..274ab6d 100644 --- a/src/utils/knotc/main.c +++ b/src/utils/knotc/main.c @@ -82,7 +82,7 @@ int main(int argc, char **argv) { "color", no_argument, NULL, 'X' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -97,7 +97,7 @@ int main(int argc, char **argv) /* Parse command line arguments */ int opt = 0; - while ((opt = getopt_long(argc, argv, "+c:C:m:s:t:befxXvhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "+c:C:m:s:t:befxXvhV::", opts, NULL)) != -1) { switch (opt) { case 'c': params.orig_config = optarg; @@ -147,7 +147,7 @@ int main(int argc, char **argv) print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); diff --git a/src/utils/knotd/main.c b/src/utils/knotd/main.c index d4ebd53..e863296 100644 --- a/src/utils/knotd/main.c +++ b/src/utils/knotd/main.c @@ -38,15 +38,39 @@ #include "knot/conf/conf.h" #include "knot/conf/migration.h" #include "knot/conf/module.h" +#include "knot/common/dbus.h" #include "knot/common/log.h" #include "knot/common/process.h" #include "knot/common/stats.h" #include "knot/common/systemd.h" #include "knot/server/server.h" #include "knot/server/tcp-handler.h" +#include "utils/common/params.h" #define PROGRAM_NAME "knotd" +typedef enum { + CONCURRENT_EMPTY = 0, // fresh cctx without a thread. + CONCURRENT_ASSIGNED, // cctx assigned to process a command. + CONCURRENT_RUNNING, // ctl command is being processed in the thread. + CONCURRENT_IDLE, // command has been processed, waiting for a new one. + CONCURRENT_KILLED, // cctx cleanup has started. + CONCURRENT_FINISHED, // after having been killed, the thread is being joined. +} concurrent_ctl_state_t; + +typedef struct { + concurrent_ctl_state_t state; + pthread_mutex_t mutex; // Protects .state. + pthread_cond_t cond; + knot_ctl_t *ctl; + server_t *server; + pthread_t thread; + sigset_t sigmask; + int ret; + int thread_idx; + bool exclusive; +} concurrent_ctl_ctx_t; + /* Signal flags. */ static volatile bool sig_req_stop = false; static volatile bool sig_req_reload = false; @@ -161,13 +185,14 @@ static void setup_signals(void) sigdelset(&all, SIGBUS); sigdelset(&all, SIGFPE); sigdelset(&all, SIGSEGV); - pthread_sigmask(SIG_SETMASK, &all, NULL); /* Setup handlers. */ struct sigaction action = { .sa_handler = handle_signal }; for (const struct signal *s = SIGNALS; s->signum > 0; s++) { sigaction(s->signum, &action, NULL); } + + pthread_sigmask(SIG_SETMASK, &all, NULL); } /*! \brief Unblock server control signals. */ @@ -185,6 +210,24 @@ static void enable_signals(void) pthread_sigmask(SIG_UNBLOCK, &mask, NULL); } +/*! \brief Create a control thread with correct signals setting. */ +static void create_thread_sigmask(pthread_t *thr, void *(*fcn)(void*), void *ctx, + sigset_t *out_mask) +{ + /* Block all blockable signals. */ + sigset_t mask; + sigfillset(&mask); + sigdelset(&mask, SIGBUS); + sigdelset(&mask, SIGFPE); + sigdelset(&mask, SIGILL); + sigdelset(&mask, SIGSEGV); + pthread_sigmask(SIG_SETMASK, &mask, out_mask); + + pthread_create(thr, NULL, fcn, ctx); + + pthread_sigmask(SIG_SETMASK, out_mask, NULL); +} + /*! \brief Drop POSIX 1003.1e capabilities. */ static void drop_capabilities(void) { @@ -224,6 +267,7 @@ static void check_loaded(server_t *server) return; } + rcu_read_lock(); knot_zonedb_iter_t *it = knot_zonedb_iter_begin(server->zone_db); while (!knot_zonedb_iter_finished(it)) { zone_t *zone = (zone_t *)knot_zonedb_iter_val(it); @@ -234,9 +278,162 @@ static void check_loaded(server_t *server) knot_zonedb_iter_next(it); } knot_zonedb_iter_free(it); + rcu_read_unlock(); finished = true; - systemd_emit_running(true); + dbus_emit_running(true); +} + +static void *ctl_process_thread(void *arg); + +/*! + * Try to find an empty ctl processing context and if successful, + * prepare to lauch the incomming command processing in it. + * + * \param[in] concurrent_ctxs Configured concurrent control contexts. + * \param[in] n_ctxs Number of configured concurrent control contexts. + * \param[in] ctl Control context. + * + * \return Assigned concurrent control context, or NULL. + */ + +static concurrent_ctl_ctx_t *find_free_ctx(concurrent_ctl_ctx_t *concurrent_ctxs, + size_t n_ctxs, knot_ctl_t *ctl) +{ + concurrent_ctl_ctx_t *res = NULL; + for (size_t i = 0; i < n_ctxs && res == NULL; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->exclusive) { + while (cctx->state != CONCURRENT_IDLE) { + pthread_cond_wait(&cctx->cond, &cctx->mutex); + } + knot_ctl_free(cctx->ctl); + cctx->ctl = knot_ctl_clone(ctl); + if (cctx->ctl == NULL) { + cctx->exclusive = false; + pthread_mutex_unlock(&cctx->mutex); + break; + } + cctx->state = CONCURRENT_ASSIGNED; + res = cctx; + pthread_cond_broadcast(&cctx->cond); + } + pthread_mutex_unlock(&cctx->mutex); + } + for (size_t i = 0; i < n_ctxs && res == NULL; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + switch (cctx->state) { + case CONCURRENT_EMPTY: + create_thread_sigmask(&cctx->thread, ctl_process_thread, cctx, &cctx->sigmask); + break; + case CONCURRENT_IDLE: + knot_ctl_free(cctx->ctl); + pthread_cond_broadcast(&cctx->cond); + break; + default: + pthread_mutex_unlock(&cctx->mutex); + continue; + } + cctx->ctl = knot_ctl_clone(ctl); + if (cctx->ctl != NULL) { + cctx->state = CONCURRENT_ASSIGNED; + res = cctx; + } + pthread_mutex_unlock(&cctx->mutex); + } + return res; +} + +static void init_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs, server_t *server) +{ + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_init(&cctx->mutex, NULL); + pthread_cond_init(&cctx->cond, NULL); + cctx->server = server; + cctx->thread_idx = i + 1; + } +} + +static int cleanup_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs) +{ + int ret = KNOT_EOK; + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->state == CONCURRENT_IDLE) { + knot_ctl_free(cctx->ctl); + cctx->ctl = NULL; + if (cctx->ret == KNOT_CTL_ESTOP) { + ret = cctx->ret; + } + } + pthread_mutex_unlock(&cctx->mutex); + } + return ret; +} + +static void finalize_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs) +{ + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->state == CONCURRENT_EMPTY) { + pthread_mutex_unlock(&cctx->mutex); + pthread_mutex_destroy(&cctx->mutex); + pthread_cond_destroy(&cctx->cond); + continue; + } + + cctx->state = CONCURRENT_KILLED; + pthread_cond_broadcast(&cctx->cond); + pthread_mutex_unlock(&cctx->mutex); + (void)pthread_join(cctx->thread, NULL); + + assert(cctx->state == CONCURRENT_FINISHED); + knot_ctl_free(cctx->ctl); + pthread_mutex_destroy(&cctx->mutex); + pthread_cond_destroy(&cctx->cond); + } +} + +static void *ctl_process_thread(void *arg) +{ + concurrent_ctl_ctx_t *ctx = arg; + rcu_register_thread(); + setup_signals(); // in fact, this blocks common signals so that they + // arrive to main thread instead of this one + + pthread_mutex_lock(&ctx->mutex); + while (ctx->state != CONCURRENT_KILLED) { + if (ctx->state != CONCURRENT_ASSIGNED) { + pthread_cond_wait(&ctx->cond, &ctx->mutex); + continue; + } + ctx->state = CONCURRENT_RUNNING; + bool exclusive = ctx->exclusive; + pthread_mutex_unlock(&ctx->mutex); + + // Not IDLE, ctx can be read without locking. + int ret = ctl_process(ctx->ctl, ctx->server, ctx->thread_idx, &exclusive); + + pthread_mutex_lock(&ctx->mutex); + ctx->ret = ret; + ctx->exclusive = exclusive; + if (ctx->state == CONCURRENT_RUNNING) { // not KILLED + ctx->state = CONCURRENT_IDLE; + pthread_cond_broadcast(&ctx->cond); + } + } + + knot_ctl_close(ctx->ctl); + + ctx->state = CONCURRENT_FINISHED; + pthread_mutex_unlock(&ctx->mutex); + rcu_unregister_thread(); + return NULL; } /*! \brief Event loop listening for signals and remote commands. */ @@ -274,7 +471,7 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, /* Bind the control socket. */ uint16_t backlog = conf_get_int(conf(), C_CTL, C_BACKLOG); - int ret = knot_ctl_bind2(ctl, listen, backlog); + int ret = knot_ctl_bind(ctl, listen, backlog); if (ret != KNOT_EOK) { knot_ctl_free(ctl); log_fatal("control, failed to bind socket '%s' (%s)", @@ -286,6 +483,10 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, enable_signals(); + concurrent_ctl_ctx_t concurrent_ctxs[CTL_MAX_CONCURRENT] = { 0 }; + init_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT, server); + bool main_thread_exclusive = false; + /* Notify systemd about successful start. */ systemd_ready_notify(); if (daemonize) { @@ -299,15 +500,19 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, /* Interrupts. */ if (sig_req_reload && !sig_req_stop) { sig_req_reload = false; + pthread_rwlock_wrlock(&server->ctl_lock); server_reload(server, RELOAD_FULL); + pthread_rwlock_unlock(&server->ctl_lock); } if (sig_req_zones_reload && !sig_req_stop) { sig_req_zones_reload = false; reload_t mode = server->catalog_upd_signal ? RELOAD_CATALOG : RELOAD_ZONES; + pthread_rwlock_wrlock(&server->ctl_lock); server->catalog_upd_signal = false; server_update_zones(conf(), server, mode); + pthread_rwlock_unlock(&server->ctl_lock); } - if (sig_req_stop) { + if (sig_req_stop || cleanup_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT) == KNOT_CTL_ESTOP) { break; } @@ -325,15 +530,20 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, continue; } - ret = ctl_process(ctl, server); - knot_ctl_close(ctl); - if (ret == KNOT_CTL_ESTOP) { - break; + if (main_thread_exclusive || + find_free_ctx(concurrent_ctxs, CTL_MAX_CONCURRENT, ctl) == NULL) { + ret = ctl_process(ctl, server, 0, &main_thread_exclusive); + knot_ctl_close(ctl); + if (ret == KNOT_CTL_ESTOP) { + break; + } } } + finalize_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT); + if (conf()->cache.srv_dbus_event & DBUS_EVENT_RUNNING) { - systemd_emit_running(false); + dbus_emit_running(false); } /* Unbind the control socket. */ @@ -363,11 +573,6 @@ static void print_help(void) CONF_MAPSIZE, RUN_DIR "/knot.sock"); } -static void print_version(void) -{ - printf("%s (Knot DNS), version %s\n", PROGRAM_NAME, PACKAGE_VERSION); -} - static int set_config(const char *confdb, const char *config, size_t max_conf_size) { if (config != NULL && confdb != NULL) { @@ -440,7 +645,7 @@ int main(int argc, char **argv) { "daemonize", optional_argument, NULL, 'd' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -449,7 +654,7 @@ int main(int argc, char **argv) /* Parse command line arguments. */ int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:m:s:dvhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:m:s:dvhV::", opts, NULL)) != -1) { switch (opt) { case 'c': config = optarg; @@ -481,7 +686,7 @@ int main(int argc, char **argv) print_help(); return EXIT_SUCCESS; case 'V': - print_version(); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); @@ -570,14 +775,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (conf()->cache.srv_dbus_event != DBUS_EVENT_NONE) { - ret = systemd_dbus_open(); - if (ret != KNOT_EOK) { - log_error("d-bus: failed to open system bus (%s)", - knot_strerror(ret)); - } else { - log_info("d-bus: connected to system bus"); - } + /* Connect to the system D-bus. */ + if (conf()->cache.srv_dbus_event != DBUS_EVENT_NONE && + dbus_open() == KNOT_EOK) { int64_t delay = conf_get_int(conf(), C_SRV, C_DBUS_INIT_DELAY); sleep(delay); } @@ -595,7 +795,7 @@ int main(int argc, char **argv) server_wait(&server); server_deinit(&server); conf_free(conf()); - systemd_dbus_close(); + dbus_close(); log_close(); dnssec_crypto_cleanup(); return EXIT_FAILURE; @@ -636,7 +836,7 @@ int main(int argc, char **argv) rcu_unregister_thread(); pid_cleanup(); conf_free(conf()); - systemd_dbus_close(); + dbus_close(); log_close(); dnssec_crypto_cleanup(); return EXIT_FAILURE; @@ -660,7 +860,7 @@ int main(int argc, char **argv) /* Unhook from RCU. */ rcu_unregister_thread(); - systemd_dbus_close(); + dbus_close(); log_info("shutting down"); log_close(); diff --git a/src/utils/knsec3hash/knsec3hash.c b/src/utils/knsec3hash/knsec3hash.c index a7bac97..a18498d 100644 --- a/src/utils/knsec3hash/knsec3hash.c +++ b/src/utils/knsec3hash/knsec3hash.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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,7 +36,7 @@ */ static void print_help(void) { - printf("Usage: " PROGRAM_NAME " <salt> <algorithm> <iterations> <domain-name>\n"); + printf("Usage: " PROGRAM_NAME " [-h | -V] <salt> <algorithm> <iterations> <domain-name>\n"); printf("Example: " PROGRAM_NAME " c01dcafe 1 10 knot-dns.cz\n"); printf("Alternative usage: "PROGRAM_NAME " <algorithm> <flags> <iterations> <salt> <domain-name>\n"); printf("Example: " PROGRAM_NAME " 1 0 10 c01dcafe knot-dns.cz\n"); @@ -103,19 +103,19 @@ static bool parse_nsec3_params(dnssec_nsec3_params_t *params, const char *salt_s int main(int argc, char *argv[]) { struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; int opt = 0; - while ((opt = getopt_long(argc, argv, "hV", options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "hV::", options, NULL)) != -1) { switch(opt) { case 'h': print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); diff --git a/src/utils/knsupdate/knsupdate_exec.c b/src/utils/knsupdate/knsupdate_exec.c index e201711..d716ffb 100644 --- a/src/utils/knsupdate/knsupdate_exec.c +++ b/src/utils/knsupdate/knsupdate_exec.c @@ -452,15 +452,23 @@ static int pkt_sendrecv(knsupdate_params_t *params) return -1; } + ret = net_init_crypto(&net, ¶ms->tls_params, NULL, ¶ms->quic_params); + if (ret != 0) { + ERR("failed to initialize crypto context (%s)", knot_strerror(ret)); + net_clean(&net); + return -1; + } + ret = net_connect(&net); - DBG("%s: send_msg = %d", __func__, net.sockfd); if (ret != KNOT_EOK) { + ERR("failed to connect (%s)", knot_strerror(ret)); net_clean(&net); return -1; } ret = net_send(&net, params->query->wire, params->query->size); if (ret != KNOT_EOK) { + ERR("failed to send update (%s)", knot_strerror(ret)); net_close(&net); net_clean(&net); return -1; @@ -471,8 +479,8 @@ static int pkt_sendrecv(knsupdate_params_t *params) /* Wait for reception. */ int rb = net_receive(&net, params->answer->wire, params->answer->max_size); - DBG("%s: receive_msg = %d", __func__, rb); if (rb <= 0) { + ERR("failed to receive response (%s)", knot_strerror(rb)); net_close(&net); net_clean(&net); return -1; diff --git a/src/utils/knsupdate/knsupdate_params.c b/src/utils/knsupdate/knsupdate_params.c index f9fa41f..5aaf808 100644 --- a/src/utils/knsupdate/knsupdate_params.c +++ b/src/utils/knsupdate/knsupdate_params.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 @@ -26,6 +26,7 @@ #include "utils/common/netio.h" #include "libknot/libknot.h" #include "libknot/tsig.h" +#include "contrib/base64.h" #include "contrib/mempattern.h" #include "contrib/strtonum.h" #include "contrib/ucw/mempool.h" @@ -90,6 +91,8 @@ static int knsupdate_init(knsupdate_params_t *params) init_list(¶ms->update_list); init_list(¶ms->prereq_list); + tls_params_init(¶ms->tls_params); + /* Initialize memory context. */ mm_ctx_mempool(¶ms->mm, MM_DEFAULT_BLKSIZE); @@ -142,6 +145,9 @@ void knsupdate_clean(knsupdate_params_t *params) knot_pkt_free(params->answer); knot_tsig_key_deinit(¶ms->tsig_key); + tls_params_clean(¶ms->tls_params); + quic_params_clean(¶ms->quic_params); + /* Clean up the structure. */ mp_delete(params->mm.ctx); memset(params, 0, sizeof(*params)); @@ -172,9 +178,31 @@ void knsupdate_reset(knsupdate_params_t *params) static void print_help(void) { - printf("Usage: %s [-d] [-v] [-k keyfile | -y [hmac:]name:key]\n" - " [-p port] [-t timeout] [-r retries] [filename]\n", - PROGRAM_NAME); + printf("Usage:\n" + " %s [-T] [options] [filename]\n" + " %s [-S | -Q] [tls_options] [options] [filename]\n" + "\n" + "Options:\n" + " -T, --tcp Use TCP protocol.\n" + " -S, --tls Use TLS protocol.\n" + " -Q, --quic Use QUIC protocol.\n" + " -p, --port <num> Remote port.\n" + " -r, --retry <num> Number of retries over UDP.\n" + " -t, --timeout <num> Update timeout.\n" + " -y, --tsig <str> TSIG key in the form [alg:]name:key.\n" + " -k, --tsigfile <path> Path to a TSIG key file.\n" + " -d, --debug Debug mode output.\n" + " -h, --help Print the program help.\n" + " -V, --version Print the program version.\n" + "\n" + "QUIC/TLS options:\n" + " -H, --hostname <str> Remote hostname validation.\n" + " -P, --pin <base64> Certificate key PIN.\n" + " -A, --ca [<path>] Path to a CA file.\n" + " -E, --certfile <path> Path to a client certificate file.\n" + " -K, --keyfile <path> Path to a client key file.\n" + " -s, --sni <str> Remote SNI.\n", + PROGRAM_NAME, PROGRAM_NAME); } int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) @@ -188,40 +216,74 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) return ret; } - // Long options. + const char *opts_str = "dhvTSQV::p:r:t:y:k:H:P:A::E:K:s:"; struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "tcp", no_argument, NULL, 'T' }, + { "tls", no_argument, NULL, 'S' }, + { "quic", no_argument, NULL, 'Q' }, + { "version", optional_argument, NULL, 'V' }, + { "port", required_argument, NULL, 'p' }, + { "retry", required_argument, NULL, 'r' }, + { "timeout", required_argument, NULL, 't' }, + { "tsig", required_argument, NULL, 'y' }, + { "tsigfile", required_argument, NULL, 'k' }, + { "hostname", required_argument, NULL, 'H' }, + { "pin", required_argument, NULL, 'P' }, + { "ca", optional_argument, NULL, 'A' }, + { "certfile", required_argument, NULL, 'E' }, + { "keyfile", required_argument, NULL, 'K' }, + { "sni", required_argument, NULL, 's' }, { NULL } }; - /* Command line options processing. */ + bool default_port = true; + int opt = 0; - while ((opt = getopt_long(argc, argv, "dhDvVp:t:r:y:k:", opts, NULL)) - != -1) { + while ((opt = getopt_long(argc, argv, opts_str, opts, NULL)) != -1) { switch (opt) { case 'd': - case 'D': /* Extra debugging. */ msg_enable_debug(1); break; case 'h': print_help(); params->stop = true; return KNOT_EOK; - case 'v': + case 'v': // Compatibility with nsupdate. + case 'T': params->protocol = PROTO_TCP; break; + case 'S': + params->protocol = PROTO_TCP; + + params->tls_params.enable = true; + + if (default_port) { + free(params->server->service); + params->server->service = strdup(DEFAULT_DNS_TLS_PORT); + } + break; + case 'Q': + params->protocol = PROTO_UDP; + + params->tls_params.enable = true; + params->quic_params.enable = true; + + if (default_port) { + free(params->server->service); + params->server->service = strdup(DEFAULT_DNS_QUIC_PORT); + } + break; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); params->stop = true; return KNOT_EOK; case 'p': + assert(optarg); + default_port = false; free(params->server->service); params->server->service = strdup(optarg); - if (!params->server->service) { - ERR("failed to set default port '%s'", optarg); - return KNOT_ENOMEM; - } break; case 'r': ret = str_to_u32(optarg, ¶ms->retries); @@ -241,7 +303,7 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) knot_tsig_key_deinit(¶ms->tsig_key); ret = knot_tsig_key_init_str(¶ms->tsig_key, optarg); if (ret != KNOT_EOK) { - ERR("failed to parse key '%s'", optarg); + ERR("failed to parse TSIG key '%s'", optarg); return ret; } break; @@ -249,9 +311,63 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) knot_tsig_key_deinit(¶ms->tsig_key); ret = knot_tsig_key_init_file(¶ms->tsig_key, optarg); if (ret != KNOT_EOK) { - ERR("failed to parse keyfile '%s'", optarg); + ERR("failed to parse TSIG keyfile '%s'", optarg); + return ret; + } + break; + case 'H': + assert(optarg); + free(params->tls_params.hostname); + params->tls_params.hostname = strdup(optarg); + break; + case 'P': + assert(optarg); + uint8_t pin[64] = { 0 }; + ret = knot_base64_decode((const uint8_t *)optarg, strlen(optarg), pin, sizeof(pin)); + if (ret < 0) { + ERR("invalid certificate pin %s", optarg); return ret; + } else if (ret != CERT_PIN_LEN) { // Check for 256-bit value. + ERR("invalid SHA256 hash length of certificate pin %s", optarg); + return KNOT_EINVAL; } + + uint8_t *item = malloc(1 + ret); // 1 ~ leading data length. + if (item == NULL) { + return KNOT_ENOMEM; + } + item[0] = ret; + memcpy(&item[1], pin, ret); + + if (ptrlist_add(¶ms->tls_params.pins, item, NULL) == NULL) { + return KNOT_ENOMEM; + } + + break; + case 'A': + if (optarg == NULL) { + params->tls_params.system_ca = true; + break; + } + if (ptrlist_add(¶ms->tls_params.ca_files, strdup(optarg), NULL) == NULL) { + ERR("failed to set CA file '%s'", optarg); + return KNOT_ENOMEM; + } + break; + case 'E': + assert(optarg); + free(params->tls_params.certfile); + params->tls_params.certfile = strdup(optarg); + break; + case 'K': + assert(optarg); + free(params->tls_params.keyfile); + params->tls_params.keyfile = strdup(optarg); + break; + case 's': + assert(optarg); + free(params->tls_params.sni); + params->tls_params.sni = strdup(optarg); break; default: print_help(); @@ -259,8 +375,8 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) } } - /* No retries for TCP. */ - if (params->protocol == PROTO_TCP) { + /* Retries only for UDP. */ + if (params->protocol == PROTO_TCP || params->quic_params.enable) { params->retries = 0; } else { /* If wait/tries < 1 s, set 1 second for each try. */ @@ -277,7 +393,7 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) ptrlist_add(¶ms->qfiles, argv[optind], ¶ms->mm); } - return ret; + return KNOT_EOK; } int knsupdate_set_ttl(knsupdate_params_t *params, const uint32_t ttl) diff --git a/src/utils/knsupdate/knsupdate_params.h b/src/utils/knsupdate/knsupdate_params.h index 1933244..fbf30e5 100644 --- a/src/utils/knsupdate/knsupdate_params.h +++ b/src/utils/knsupdate/knsupdate_params.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 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 @@ -20,7 +20,9 @@ #include "utils/common/netio.h" #include "utils/common/params.h" +#include "utils/common/quic.h" #include "utils/common/sign.h" +#include "utils/common/tls.h" #include "libknot/libknot.h" #include "libzscanner/scanner.h" #include "contrib/ucw/lists.h" @@ -63,6 +65,10 @@ typedef struct { style_t style; /*!< Memory context. */ knot_mm_t mm; + /*!< TLS params. */ + tls_params_t tls_params; + /*!< QUIC params. */ + quic_params_t quic_params; } knsupdate_params_t; int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]); diff --git a/src/utils/kxdpgun/load_queries.c b/src/utils/kxdpgun/load_queries.c index 8ecac48..fe7c9ae 100644 --- a/src/utils/kxdpgun/load_queries.c +++ b/src/utils/kxdpgun/load_queries.c @@ -16,9 +16,11 @@ #include <assert.h> #include <errno.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <arpa/inet.h> #include "load_queries.h" #include "libknot/libknot.h" @@ -44,106 +46,181 @@ void free_global_payloads(void) global_payloads = NULL; } -bool load_queries(const char *filename, uint16_t edns_size, uint16_t msgid, size_t maxcount) +typedef struct { + char line[KNOT_DNAME_TXT_MAXLEN + 256]; + char dname_txt[KNOT_DNAME_TXT_MAXLEN + 1]; + uint8_t dname[KNOT_DNAME_MAXLEN]; + char type_txt[128]; + char flags_txt[128]; +} txt_bufs_t; + +typedef struct { + char line[USHRT_MAX]; +} bin_bufs_t; + +static int read_txt(struct pkt_payload **g_payloads_top_p, FILE *f, txt_bufs_t *bufs, + uint16_t edns_size, uint16_t msgid) +{ + assert(g_payloads_top_p != NULL); + struct pkt_payload *g_payloads_top = *g_payloads_top_p; + if (fgets(bufs->line, sizeof(bufs->line), f) == NULL) { + return 0; + } + bufs->flags_txt[0] = '\0'; + int ret = sscanf(bufs->line, "%s%s%s", bufs->dname_txt, bufs->type_txt, + bufs->flags_txt); + if (ret < 2) { + ERR2(ERR_PREFIX "(faulty line): '%.*s'", + (int)strcspn(bufs->line, "\n"), bufs->line); + return KNOT_EINVAL; + } + + void *pret = knot_dname_from_str(bufs->dname, bufs->dname_txt, sizeof(bufs->dname)); + if (pret == NULL) { + ERR2(ERR_PREFIX "(faulty dname): '%s'", bufs->dname_txt); + return KNOT_EINVAL; + } + + uint16_t type; + ret = knot_rrtype_from_string(bufs->type_txt, &type); + if (ret < 0) { + ERR2(ERR_PREFIX "(faulty type): '%s'", bufs->type_txt); + return KNOT_EINVAL; + } + + enum qflags flags = 0; + switch (bufs->flags_txt[0]) { + case '\0': + break; + case 'e': + case 'E': + flags |= QFLAG_EDNS; + break; + case 'd': + case 'D': + flags |= QFLAG_EDNS | QFLAG_DO; + break; + default: + ERR2(ERR_PREFIX "(faulty flag): '%s'", bufs->flags_txt); + return KNOT_EINVAL; + } + + size_t dname_len = knot_dname_size(bufs->dname); + size_t pkt_len = KNOT_WIRE_HEADER_SIZE + 2 * sizeof(uint16_t) + dname_len; + if (flags & QFLAG_EDNS) { + pkt_len += KNOT_EDNS_MIN_SIZE; + } + + struct pkt_payload *pkt = calloc(1, sizeof(*pkt) + pkt_len); + if (pkt == NULL) { + ERR2(ERR_PREFIX "(out of memory)"); + return KNOT_ENOMEM; + } + pkt->len = pkt_len; + memcpy(pkt->payload, &msgid, sizeof(msgid)); + pkt->payload[2] = 0x01; // RD bit + pkt->payload[5] = 0x01; // 1 question + pkt->payload[11] = (flags & QFLAG_EDNS) ? 0x01 : 0x00; + memcpy(pkt->payload + 12, bufs->dname, dname_len); + pkt->payload[dname_len + 12] = type >> 8; + pkt->payload[dname_len + 13] = type & 0xff; + pkt->payload[dname_len + 15] = KNOT_CLASS_IN; + if (flags & QFLAG_EDNS) { + pkt->payload[dname_len + 18] = KNOT_RRTYPE_OPT; + pkt->payload[dname_len + 19] = edns_size >> 8; + pkt->payload[dname_len + 20] = edns_size & 0xff; + pkt->payload[dname_len + 23] = (flags & QFLAG_DO) ? 0x80 : 0x00; + } + + // add pkt to list global_payloads + if (g_payloads_top == NULL) { + global_payloads = pkt; + *g_payloads_top_p = pkt; + } else { + g_payloads_top->next = pkt; + *g_payloads_top_p = pkt; + } + return pkt_len; +} + +static int read_bin(struct pkt_payload **g_payloads_top_p, FILE *f, bin_bufs_t *bufs, + uint16_t msgid) +{ + assert(g_payloads_top_p != NULL); + struct pkt_payload *g_payloads_top = *g_payloads_top_p; + uint16_t size; + if (fread(&size, sizeof(size), 1, f) < 1) { + return 0; + } + size = ntohs(size); + if (fread(bufs->line, size, 1, f) < 1) { + return KNOT_EINVAL; + } + struct pkt_payload *pkt = calloc(1, sizeof(*pkt) + size); + if (pkt == NULL) { + ERR2(ERR_PREFIX "(out of memory)"); + return KNOT_ENOMEM; + } + pkt->len = size; + memcpy(pkt->payload, &msgid, sizeof(msgid)); // Override msgID + memcpy(pkt->payload + 2, bufs->line + 2, size - 2); + + // add pkt to list global_payloads + if (g_payloads_top == NULL) { + global_payloads = pkt; + *g_payloads_top_p = pkt; + } else { + g_payloads_top->next = pkt; + *g_payloads_top_p = pkt; + } + return size; +} + +bool load_queries(const input_t *input, uint16_t edns_size, uint16_t msgid, size_t maxcount) { size_t read = 0; - FILE *f = fopen(filename, "r"); + FILE *f = fopen(input->path, (input->format == BIN) ? "rb" : "r"); if (f == NULL) { - ERR2(ERR_PREFIX "file '%s' (%s)", filename, strerror(errno)); + ERR2(ERR_PREFIX "file '%s' (%s)", input->path, strerror(errno)); return false; } - struct pkt_payload *g_payloads_top = NULL; - struct { - char line[KNOT_DNAME_TXT_MAXLEN + 256]; - char dname_txt[KNOT_DNAME_TXT_MAXLEN + 1]; - uint8_t dname[KNOT_DNAME_MAXLEN]; - char type_txt[128]; - char flags_txt[128]; - } *bufs; - bufs = malloc(sizeof(*bufs)); // avoiding too much stuff on stack + void *bufs = NULL; + switch (input->format) { + case TXT: + bufs = malloc(sizeof(txt_bufs_t)); // avoiding too much stuff on stack + break; + case BIN: + bufs = malloc(sizeof(bin_bufs_t)); + break; + default: + assert(0); + goto fail; + } if (bufs == NULL) { ERR2(ERR_PREFIX "(out of memory)"); goto fail; } + struct pkt_payload *g_payloads_top = NULL; while (read < maxcount) { - if (fgets(bufs->line, sizeof(bufs->line), f) == NULL) { + int ret = 0; + switch (input->format) { + case TXT: + ret = read_txt(&g_payloads_top, f, bufs, edns_size, msgid); break; - } - bufs->flags_txt[0] = '\0'; - int ret = sscanf(bufs->line, "%s%s%s", bufs->dname_txt, bufs->type_txt, bufs->flags_txt); - if (ret < 2) { - ERR2(ERR_PREFIX "(faulty line): '%.*s'", - (int)strcspn(bufs->line, "\n"), bufs->line); - goto fail; - } - - void *pret = knot_dname_from_str(bufs->dname, bufs->dname_txt, sizeof(bufs->dname)); - if (pret == NULL) { - ERR2(ERR_PREFIX "(faulty dname): '%s'", bufs->dname_txt); - goto fail; - } - - uint16_t type; - ret = knot_rrtype_from_string(bufs->type_txt, &type); - if (ret < 0) { - ERR2(ERR_PREFIX "(faulty type): '%s'", bufs->type_txt); - goto fail; - } - - enum qflags flags = 0; - switch (bufs->flags_txt[0]) { - case '\0': - break; - case 'e': - case 'E': - flags |= QFLAG_EDNS; - break; - case 'd': - case 'D': - flags |= QFLAG_EDNS | QFLAG_DO; + case BIN: + ret = read_bin(&g_payloads_top, f, bufs, msgid); break; default: - ERR2(ERR_PREFIX "(faulty flag): '%s'", bufs->flags_txt); - goto fail; - } - - size_t dname_len = knot_dname_size(bufs->dname); - size_t pkt_len = KNOT_WIRE_HEADER_SIZE + 2 * sizeof(uint16_t) + dname_len; - if (flags & QFLAG_EDNS) { - pkt_len += KNOT_EDNS_MIN_SIZE; + ret = -1; + break; } - - struct pkt_payload *pkt = calloc(1, sizeof(struct pkt_payload) + pkt_len); - if (pkt == NULL) { - ERR2(ERR_PREFIX "(out of memory)"); + if (ret == 0) { + break; + } else if (ret < 0) { goto fail; } - pkt->len = pkt_len; - memcpy(pkt->payload, &msgid, sizeof(msgid)); - pkt->payload[2] = 0x01; // RD bit - pkt->payload[5] = 0x01; // 1 question - pkt->payload[11] = (flags & QFLAG_EDNS) ? 0x01 : 0x00; - memcpy(pkt->payload + 12, bufs->dname, dname_len); - pkt->payload[dname_len + 12] = type >> 8; - pkt->payload[dname_len + 13] = type & 0xff; - pkt->payload[dname_len + 15] = KNOT_CLASS_IN; - if (flags & QFLAG_EDNS) { - pkt->payload[dname_len + 18] = KNOT_RRTYPE_OPT; - pkt->payload[dname_len + 19] = edns_size >> 8; - pkt->payload[dname_len + 20] = edns_size & 0xff; - pkt->payload[dname_len + 23] = (flags & QFLAG_DO) ? 0x80 : 0x00; - } - - // add pkt to list global_payloads - if (g_payloads_top == NULL) { - global_payloads = pkt; - g_payloads_top = pkt; - } else { - g_payloads_top->next = pkt; - g_payloads_top = pkt; - } read++; } diff --git a/src/utils/kxdpgun/load_queries.h b/src/utils/kxdpgun/load_queries.h index 3d7bace..09dfee9 100644 --- a/src/utils/kxdpgun/load_queries.h +++ b/src/utils/kxdpgun/load_queries.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 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,16 @@ #include <stdbool.h> #include <stdint.h> +enum input_format { + TXT = 0, + BIN +}; + +typedef struct { + const char *path; + enum input_format format; +} input_t; + struct pkt_payload { struct pkt_payload *next; size_t len; @@ -27,6 +37,6 @@ struct pkt_payload { extern struct pkt_payload *global_payloads; -bool load_queries(const char *filename, uint16_t edns_size, uint16_t msgid, size_t maxcount); +bool load_queries(const input_t *input, uint16_t edns_size, uint16_t msgid, size_t maxcount); void free_global_payloads(void); diff --git a/src/utils/kxdpgun/main.c b/src/utils/kxdpgun/main.c index 523f64f..4e3aa31 100644 --- a/src/utils/kxdpgun/main.c +++ b/src/utils/kxdpgun/main.c @@ -14,12 +14,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#include <arpa/inet.h> #include <assert.h> #include <errno.h> #include <getopt.h> #include <ifaddrs.h> #include <inttypes.h> #include <net/if.h> +#include <netdb.h> +#include <netinet/in.h> #include <poll.h> #include <pthread.h> #include <signal.h> @@ -28,16 +31,11 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <time.h> -#include <unistd.h> -#include <netdb.h> - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <net/if.h> #include <sys/ioctl.h> -#include <sys/socket.h> #include <sys/resource.h> +#include <sys/socket.h> +#include <time.h> +#include <unistd.h> #include "libknot/libknot.h" #include "libknot/xdp.h" @@ -46,101 +44,28 @@ #include <gnutls/gnutls.h> #include "libknot/quic/quic.h" #endif // ENABLE_QUIC -#include "contrib/macros.h" -#include "contrib/mempattern.h" -#include "contrib/openbsd/strlcat.h" +#include "contrib/atomic.h" #include "contrib/openbsd/strlcpy.h" #include "contrib/os.h" #include "contrib/sockaddr.h" #include "contrib/toeplitz.h" -#include "contrib/ucw/mempool.h" #include "utils/common/msg.h" #include "utils/common/params.h" #include "utils/kxdpgun/ip_route.h" #include "utils/kxdpgun/load_queries.h" - -#define PROGRAM_NAME "kxdpgun" -#define SPACE " " - -enum { - KXDPGUN_WAIT, - KXDPGUN_START, - KXDPGUN_STOP, -}; +#include "utils/kxdpgun/main.h" +#include "utils/kxdpgun/stats.h" volatile int xdp_trigger = KXDPGUN_WAIT; -volatile unsigned stats_trigger = 0; +volatile knot_atomic_uint64_t stats_trigger = 0; +volatile knot_atomic_bool stats_switch = STATS_SUM; unsigned global_cpu_aff_start = 0; unsigned global_cpu_aff_step = 1; -#define REMOTE_PORT_DEFAULT 53 -#define REMOTE_PORT_DOQ_DEFAULT 853 -#define LOCAL_PORT_MIN 2000 -#define LOCAL_PORT_MAX 65535 -#define QUIC_THREAD_PORTS 100 - -#define RCODE_MAX (0x0F + 1) - -typedef struct { - size_t collected; - uint64_t duration; - uint64_t qry_sent; - uint64_t synack_recv; - uint64_t ans_recv; - uint64_t finack_recv; - uint64_t rst_recv; - uint64_t size_recv; - uint64_t wire_recv; - uint64_t rcodes_recv[RCODE_MAX]; - pthread_mutex_t mutex; -} kxdpgun_stats_t; - static kxdpgun_stats_t global_stats = { 0 }; -typedef enum { - KXDPGUN_IGNORE_NONE = 0, - KXDPGUN_IGNORE_QUERY = (1 << 0), - KXDPGUN_IGNORE_LASTBYTE = (1 << 1), - KXDPGUN_IGNORE_CLOSE = (1 << 2), - KXDPGUN_REUSE_CONN = (1 << 3), -} xdp_gun_ignore_t; - -typedef struct { - union { - struct sockaddr_in local_ip4; - struct sockaddr_in6 local_ip; - struct sockaddr_storage local_ip_ss; - }; - union { - struct sockaddr_in target_ip4; - struct sockaddr_in6 target_ip; - struct sockaddr_storage target_ip_ss; - }; - char dev[IFNAMSIZ]; - uint64_t qps, duration; - unsigned at_once; - uint16_t msgid; - uint16_t edns_size; - uint16_t vlan_tci; - uint8_t local_mac[6], target_mac[6]; - uint8_t local_ip_range; - bool ipv6; - bool tcp; - bool quic; - bool quic_full_handshake; - const char *qlog_dir; - const char *sending_mode; - xdp_gun_ignore_t ignore1; - knot_tcp_ignore_t ignore2; - uint16_t target_port; - knot_xdp_filter_flag_t flags; - unsigned n_threads, thread_id; - knot_eth_rss_conf_t *rss_conf; - knot_xdp_config_t xdp_config; -} xdp_gun_ctx_t; - const static xdp_gun_ctx_t ctx_defaults = { .dev[0] = '\0', .edns_size = 1232, @@ -150,7 +75,9 @@ const static xdp_gun_ctx_t ctx_defaults = { .sending_mode = "", .target_port = 0, .flags = KNOT_XDP_FILTER_UDP | KNOT_XDP_FILTER_PASS, - .xdp_config = { .extra_frames = true }, + .xdp_config = { .ring_size = 2048 }, + .jw = NULL, + .stats_period = 0, }; static void sigterm_handler(int signo) @@ -163,103 +90,8 @@ static void sigusr_handler(int signo) { assert(signo == SIGUSR1); if (global_stats.collected == 0) { - stats_trigger++; - } -} - -static void clear_stats(kxdpgun_stats_t *st) -{ - pthread_mutex_lock(&st->mutex); - st->duration = 0; - st->qry_sent = 0; - st->synack_recv = 0; - st->ans_recv = 0; - st->finack_recv = 0; - st->rst_recv = 0; - st->size_recv = 0; - st->wire_recv = 0; - st->collected = 0; - memset(st->rcodes_recv, 0, sizeof(st->rcodes_recv)); - pthread_mutex_unlock(&st->mutex); -} - -static size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) -{ - pthread_mutex_lock(&into->mutex); - into->duration = MAX(into->duration, what->duration); - into->qry_sent += what->qry_sent; - into->synack_recv += what->synack_recv; - into->ans_recv += what->ans_recv; - into->finack_recv += what->finack_recv; - into->rst_recv += what->rst_recv; - into->size_recv += what->size_recv; - into->wire_recv += what->wire_recv; - for (int i = 0; i < RCODE_MAX; i++) { - into->rcodes_recv[i] += what->rcodes_recv[i]; + ATOMIC_ADD(stats_trigger, 1); } - size_t res = ++into->collected; - pthread_mutex_unlock(&into->mutex); - return res; -} - -static void print_stats(kxdpgun_stats_t *st, bool tcp, bool quic, bool recv, uint64_t qps) -{ - pthread_mutex_lock(&st->mutex); - -#define ps(counter) ((typeof(counter))((counter) * 1000 / ((float)st->duration / 1000))) -#define pct(counter) ((counter) * 100.0 / st->qry_sent) - - const char *name = tcp ? "SYNs: " : quic ? "initials:" : "queries: "; - printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, st->qry_sent, - ps(st->qry_sent), 100.0 * st->qry_sent / (st->duration / 1000000.0 * qps)); - if (st->qry_sent > 0 && recv) { - if (tcp || quic) { - name = tcp ? "established:" : "handshakes: "; - printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, - st->synack_recv, ps(st->synack_recv), pct(st->synack_recv)); - } - printf("total replies: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->ans_recv, ps(st->ans_recv), pct(st->ans_recv)); - if (tcp) { - printf("total closed: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->finack_recv, ps(st->finack_recv), pct(st->finack_recv)); - } - if (st->rst_recv > 0) { - printf("total reset: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->rst_recv, ps(st->rst_recv), pct(st->rst_recv)); - } - printf("average DNS reply size: %"PRIu64" B\n", - st->ans_recv > 0 ? st->size_recv / st->ans_recv : 0); - printf("average Ethernet reply rate: %"PRIu64" bps (%.2f Mbps)\n", - ps(st->wire_recv * 8), ps((float)st->wire_recv * 8 / (1000 * 1000))); - - for (int i = 0; i < RCODE_MAX; i++) { - if (st->rcodes_recv[i] > 0) { - const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, i); - const char *rcname = rcode == NULL ? "unknown" : rcode->name; - int space = MAX(9 - strlen(rcname), 0); - printf("responded %s: %.*s%"PRIu64"\n", - rcname, space, " ", st->rcodes_recv[i]); - } - } - } - printf("duration: %"PRIu64" s\n", (st->duration / (1000 * 1000))); - - pthread_mutex_unlock(&st->mutex); -} - -inline static void timer_start(struct timespec *timesp) -{ - clock_gettime(CLOCK_MONOTONIC, timesp); -} - -inline static uint64_t timer_end(struct timespec *timesp) -{ - struct timespec end; - clock_gettime(CLOCK_MONOTONIC, &end); - uint64_t res = (end.tv_sec - timesp->tv_sec) * (uint64_t)1000000; - res += ((int64_t)end.tv_nsec - timesp->tv_nsec) / 1000; - return res; } static unsigned addr_bits(bool ipv6) @@ -267,7 +99,8 @@ static unsigned addr_bits(bool ipv6) return ipv6 ? 128 : 32; } -static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, uint64_t increment) +static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, + uint64_t increment) { memcpy(&dst->sin_addr, &src->sin_addr, sizeof(dst->sin_addr)); if (increment > 0) { @@ -275,7 +108,8 @@ static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, } } -static void shuffle_sockaddr6(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, uint64_t increment) +static void shuffle_sockaddr6(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, + uint64_t increment) { memcpy(&dst->sin6_addr, &src->sin6_addr, sizeof(dst->sin6_addr)); if (increment > 0) { @@ -293,7 +127,8 @@ static void shuffle_sockaddr(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, if (src->sin6_family == AF_INET6) { shuffle_sockaddr6(dst, src, increment); } else { - shuffle_sockaddr4((struct sockaddr_in *)dst, (struct sockaddr_in *)src, increment); + shuffle_sockaddr4((struct sockaddr_in *)dst, (struct sockaddr_in *)src, + increment); } } @@ -311,7 +146,8 @@ static void next_payload(struct pkt_payload **payload, int increment) } } -static void put_dns_payload(struct iovec *put_into, bool zero_copy, xdp_gun_ctx_t *ctx, struct pkt_payload **payl) +static void put_dns_payload(struct iovec *put_into, bool zero_copy, xdp_gun_ctx_t *ctx, + struct pkt_payload **payl) { if (zero_copy) { put_into->iov_base = (*payl)->payload; @@ -472,19 +308,41 @@ static void quic_free_cb(knot_quic_reply_t *rpl) } #endif // ENABLE_QUIC +static uint64_t timestamp_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec; +} + +static void timer_start(struct timespec *out) +{ + clock_gettime(CLOCK_MONOTONIC, out); +} + +static uint64_t timer_end_ns(const struct timespec *start) +{ + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + uint64_t res = (end.tv_sec - start->tv_sec) * (uint64_t)1000000000; + res += end.tv_nsec - start->tv_nsec; + return res; +} + void *xdp_gun_thread(void *_ctx) { xdp_gun_ctx_t *ctx = _ctx; struct knot_xdp_socket *xsk = NULL; - struct timespec timer; knot_xdp_msg_t pkts[ctx->at_once]; - uint64_t errors = 0, lost = 0, duration = 0; - kxdpgun_stats_t local_stats = { 0 }; + uint64_t duration_us = 0; + struct timespec timer; + kxdpgun_stats_t local_stats = { 0 }; // cumulative stats of past periods excluding the current + kxdpgun_stats_t periodic_stats = { 0 }; // stats for the current period (see -S option) unsigned stats_triggered = 0; knot_tcp_table_t *tcp_table = NULL; #ifdef ENABLE_QUIC knot_quic_table_t *quic_table = NULL; - struct knot_quic_creds *quic_creds = NULL; + struct knot_creds *quic_creds = NULL; list_t quic_sessions; init_list(&quic_sessions); #endif // ENABLE_QUIC @@ -501,12 +359,13 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->quic) { #ifdef ENABLE_QUIC - quic_creds = knot_quic_init_creds_peer(NULL, NULL, 0); + quic_creds = knot_creds_init_peer(NULL, NULL, 0); if (quic_creds == NULL) { ERR2("failed to initialize QUIC context"); goto cleanup; } - quic_table = knot_quic_table_new(ctx->qps * 100, SIZE_MAX, SIZE_MAX, 1232, quic_creds); + quic_table = knot_quic_table_new(ctx->qps * 100, SIZE_MAX, SIZE_MAX, + 1232, quic_creds); if (quic_table == NULL) { ERR2("failed to allocate QUIC connection table"); goto cleanup; @@ -517,12 +376,12 @@ void *xdp_gun_thread(void *_ctx) #endif // ENABLE_QUIC } - knot_xdp_load_bpf_t mode = (ctx->thread_id == 0 ? - KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER); + knot_xdp_load_bpf_t mode = + (ctx->thread_id == 0 ? KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER); /* * This mutex prevents libbpf from logging: * 'libbpf: can't get link by id (5535): Resource temporarily unavailable' - */ + */ pthread_mutex_lock(&global_stats.mutex); int ret = knot_xdp_init(&xsk, ctx->dev, ctx->thread_id, ctx->flags, LOCAL_PORT_MIN, LOCAL_PORT_MIN, mode, &ctx->xdp_config); @@ -534,13 +393,7 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->thread_id == 0) { - INFO2("using interface %s, XDP threads %u, IPv%c/%s%s%s, %s mode", - ctx->dev, ctx->n_threads, (ctx->ipv6 ? '6' : '4'), - (ctx->tcp ? "TCP" : ctx->quic ? "QUIC" : "UDP"), - (ctx->sending_mode[0] != '\0' ? " mode " : ""), - (ctx->sending_mode[0] != '\0' ? ctx->sending_mode : ""), - (knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL ? - "native" : "emulated")); + STATS_HDR(ctx); } struct pollfd pfd = { knot_xdp_socket_fd(xsk), POLLIN, 0 }; @@ -576,7 +429,7 @@ void *xdp_gun_thread(void *_ctx) ctx->target_ip.sin6_port = htobe16(ctx->target_port); knot_sweep_stats_t sweep_stats = { 0 }; - uint16_t local_ports[QUIC_THREAD_PORTS]; + uint16_t local_ports[QUIC_THREAD_PORTS] = { 0 }; uint16_t port = LOCAL_PORT_MIN; for (int i = 0; ctx->quic && i < QUIC_THREAD_PORTS; ++i) { local_ports[i] = adjust_port(ctx, port); @@ -586,24 +439,25 @@ void *xdp_gun_thread(void *_ctx) size_t local_ports_it = 0; #endif // ENABLE_QUIC + local_stats.since = periodic_stats.since = timestamp_ns(); timer_start(&timer); + ctx->stats_start_us = local_stats.since / 1000; - while (duration < ctx->duration + extra_wait) { - + while (duration_us < ctx->duration + extra_wait) { // sending part - if (duration < ctx->duration) { + if (duration_us < ctx->duration) { while (1) { knot_xdp_send_prepare(xsk); unsigned alloced = alloc_pkts(pkts, xsk, ctx, tick); if (alloced < ctx->at_once) { - lost += ctx->at_once - alloced; + periodic_stats.lost += ctx->at_once - alloced; if (alloced == 0) { break; } } if (ctx->tcp) { - for (int i = 0; i < alloced; i++) { + for (uint32_t i = 0; i < alloced; i++) { pkts[i].payload.iov_len = 0; if (!EMPTY_LIST(reuse_conns)) { @@ -621,7 +475,7 @@ void *xdp_gun_thread(void *_ctx) } if (ret == KNOT_EOK) { pkts[i].flags &= ~KNOT_XDP_MSG_SYN; // skip sending respective packet - local_stats.qry_sent++; + periodic_stats.qry_sent++; } free(rl); } @@ -670,14 +524,14 @@ void *xdp_gun_thread(void *_ctx) (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE) ? KNOT_QUIC_SEND_IGNORE_LASTBYTE : 0); } if (ret == KNOT_EOK) { - local_stats.qry_sent++; + periodic_stats.qry_sent++; } } (void)knot_xdp_send_finish(xsk); #endif // ENABLE_QUIC break; } else { - for (int i = 0; i < alloced; i++) { + for (uint32_t i = 0; i < alloced; i++) { put_dns_payload(&pkts[i].payload, false, ctx, &payload_ptr); } @@ -685,9 +539,9 @@ void *xdp_gun_thread(void *_ctx) uint32_t really_sent = 0; if (knot_xdp_send(xsk, pkts, alloced, &really_sent) != KNOT_EOK) { - lost += alloced; + periodic_stats.lost += alloced; } - local_stats.qry_sent += really_sent; + periodic_stats.qry_sent += really_sent; (void)knot_xdp_send_finish(xsk); break; @@ -699,7 +553,7 @@ void *xdp_gun_thread(void *_ctx) while (1) { ret = poll(&pfd, 1, 0); if (ret < 0) { - errors++; + periodic_stats.errors++; break; } if (!pfd.revents) { @@ -714,18 +568,19 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->tcp) { knot_tcp_relay_t relays[recvd]; - ret = knot_tcp_recv(relays, pkts, recvd, tcp_table, NULL, ctx->ignore2); - if (ret != KNOT_EOK) { - errors++; - break; - } for (size_t i = 0; i < recvd; i++) { knot_tcp_relay_t *rl = &relays[i]; + ret = knot_tcp_recv(rl, &pkts[i], tcp_table, NULL, ctx->ignore2); + if (ret != KNOT_EOK) { + periodic_stats.errors++; + continue; + } + struct iovec payl; switch (rl->action) { case XDP_TCP_ESTABLISH: - local_stats.synack_recv++; + periodic_stats.synack_recv++; if (ctx->ignore1 & KXDPGUN_IGNORE_QUERY) { break; } @@ -734,20 +589,20 @@ void *xdp_gun_thread(void *_ctx) (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE), payl.iov_base, payl.iov_len); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } break; case XDP_TCP_CLOSE: - local_stats.finack_recv++; + periodic_stats.finack_recv++; break; case XDP_TCP_RESET: - local_stats.rst_recv++; + periodic_stats.rst_recv++; break; default: break; } for (size_t j = 0; rl->inbf != NULL && j < rl->inbf->n_inbufs; j++) { - if (check_dns_payload(&rl->inbf->inbufs[j], ctx, &local_stats)) { + if (check_dns_payload(&rl->inbf->inbufs[j], ctx, &periodic_stats)) { if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE)) { rl->answer = XDP_TCP_CLOSE; } else if ((ctx->ignore1 & KXDPGUN_REUSE_CONN)) { @@ -763,7 +618,7 @@ void *xdp_gun_thread(void *_ctx) ret = knot_tcp_send(xsk, relays, recvd, ctx->at_once); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } (void)knot_xdp_send_finish(xsk); @@ -781,11 +636,11 @@ void *xdp_gun_thread(void *_ctx) ret = knot_quic_handle(quic_table, &quic_reply, 5000000000L, &conn); if (ret == KNOT_ECONN) { - local_stats.rst_recv++; + periodic_stats.rst_recv++; knot_quic_cleanup(&conn, 1); continue; } else if (ret != 0) { - errors++; + periodic_stats.errors++; knot_quic_cleanup(&conn, 1); break; } @@ -805,7 +660,7 @@ void *xdp_gun_thread(void *_ctx) if ((conn->flags & KNOT_QUIC_CONN_HANDSHAKE_DONE) && conn->streams_count == -1) { conn->streams_count = 1; - local_stats.synack_recv++; + periodic_stats.synack_recv++; if ((ctx->ignore1 & KXDPGUN_IGNORE_QUERY)) { knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); @@ -822,14 +677,14 @@ void *xdp_gun_thread(void *_ctx) if ((ctx->ignore2 & XDP_TCP_IGNORE_ESTABLISH)) { knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); - local_stats.synack_recv++; + periodic_stats.synack_recv++; continue; } int64_t s0id; knot_quic_stream_t *stream0 = knot_quic_stream_get_process(conn, &s0id); if (stream0 != NULL && stream0->inbufs != NULL && stream0->inbufs->n_inbufs > 0) { - check_dns_payload(&stream0->inbufs->inbufs[0], ctx, &local_stats); + check_dns_payload(&stream0->inbufs->inbufs[0], ctx, &periodic_stats); stream0->inbufs->n_inbufs = 0; // signal that data have been read out if ((ctx->ignore2 & XDP_TCP_IGNORE_DATA_ACK)) { @@ -837,7 +692,9 @@ void *xdp_gun_thread(void *_ctx) knot_quic_cleanup(&conn, 1); continue; } else if ((ctx->ignore1 & KXDPGUN_REUSE_CONN)) { - if (conn->streams_count > 1) { // keep the number of outstanding streams below MAX_STREAMS_PER_CONN, while preserving at least one at all times + /* keep the number of outstanding streams below MAX_STREAMS_PER_CONN, + * while preserving at least one at all times */ + if (conn->streams_count > 1) { knot_quic_conn_stream_free(conn, conn->streams_first * 4); } ptrlist_add(&reuse_conns, conn, NULL); @@ -846,30 +703,31 @@ void *xdp_gun_thread(void *_ctx) ret = knot_quic_send(quic_table, conn, &quic_reply, 4, (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE) ? KNOT_QUIC_SEND_IGNORE_LASTBYTE : 0); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } - if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE) && (conn->flags & KNOT_QUIC_CONN_SESSION_TAKEN) && - stream0 != NULL && stream0->inbufs != NULL && stream0->inbufs->n_inbufs == 0) { + if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE) + && (conn->flags & KNOT_QUIC_CONN_SESSION_TAKEN) + && stream0 != NULL && stream0->inbufs != NULL + && stream0->inbufs->n_inbufs == 0) { assert(!(ctx->ignore2 & XDP_TCP_IGNORE_DATA_ACK)); quic_reply.handle_ret = KNOT_QUIC_HANDLE_RET_CLOSE; ret = knot_quic_send(quic_table, conn, &quic_reply, 1, 0); knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } } } (void)knot_xdp_send_finish(xsk); #endif // ENABLE_QUIC } else { - for (int i = 0; i < recvd; i++) { - (void)check_dns_payload(&pkts[i].payload, ctx, - &local_stats); + for (uint32_t i = 0; i < recvd; i++) { + check_dns_payload(&pkts[i].payload, ctx, &periodic_stats); } } - local_stats.wire_recv += wire; + periodic_stats.wire_recv += wire; knot_xdp_recv_finish(xsk, pkts, recvd); pfd.revents = 0; } @@ -882,47 +740,59 @@ void *xdp_gun_thread(void *_ctx) #endif // ENABLE_QUIC // speed and signal part - uint64_t dura_exp = (local_stats.qry_sent * 1000000) / ctx->qps; - duration = timer_end(&timer); - if (xdp_trigger == KXDPGUN_STOP && ctx->duration > duration) { - ctx->duration = duration; + uint64_t duration_ns = timer_end_ns(&timer); + duration_us = duration_ns / 1000; + uint64_t dura_exp = ((local_stats.qry_sent + periodic_stats.qry_sent) * 1000000) / ctx->qps; + if (ctx->thread_id == 0 && ctx->stats_period != 0 && global_stats.collected == 0 + && (duration_ns - (periodic_stats.since - local_stats.since)) >= ctx->stats_period) { + ATOMIC_SET(stats_switch, STATS_PERIODIC); + ATOMIC_ADD(stats_trigger, 1); } - if (stats_trigger > stats_triggered) { - assert(stats_trigger == stats_triggered + 1); - stats_triggered++; - local_stats.duration = duration; - size_t collected = collect_stats(&global_stats, &local_stats); + if (xdp_trigger == KXDPGUN_STOP && ctx->duration > duration_us) { + ctx->duration = duration_us; + } + uint64_t tmp_stats_trigger = ATOMIC_GET(stats_trigger); + if (duration_us < ctx->duration && tmp_stats_trigger > stats_triggered) { + bool tmp_stats_switch = ATOMIC_GET(stats_switch); + stats_triggered = tmp_stats_trigger; + + local_stats.until = periodic_stats.until = local_stats.since + duration_ns; + kxdpgun_stats_t cumulative_stats = periodic_stats; + if (tmp_stats_switch == STATS_PERIODIC) { + collect_periodic_stats(&local_stats, &periodic_stats); + clear_stats(&periodic_stats); + periodic_stats.since = local_stats.since + duration_ns; + } else { + collect_periodic_stats(&cumulative_stats, &local_stats); + cumulative_stats.since = local_stats.since; + } + + size_t collected = collect_stats(&global_stats, &cumulative_stats); + assert(collected <= ctx->n_threads); if (collected == ctx->n_threads) { - print_stats(&global_stats, ctx->tcp, ctx->quic, - !(ctx->flags & KNOT_XDP_FILTER_DROP), - ctx->qps * ctx->n_threads); + STATS_FMT(ctx, &global_stats, tmp_stats_switch); + if (!JSON_MODE(*ctx)) { + puts(STATS_SECTION_SEP); + } clear_stats(&global_stats); + ATOMIC_SET(stats_switch, STATS_SUM); } } - if (dura_exp > duration) { - usleep(dura_exp - duration); + if (dura_exp > duration_us) { + usleep(dura_exp - duration_us); } - if (duration > ctx->duration) { + if (duration_us > ctx->duration) { usleep(1000); } tick++; } + periodic_stats.until = local_stats.since + timer_end_ns(&timer) - extra_wait * 1000; + collect_periodic_stats(&local_stats, &periodic_stats); + + STATS_THRD(ctx, &local_stats); - char recv_str[40] = "", lost_str[40] = "", err_str[40] = ""; - if (!(ctx->flags & KNOT_XDP_FILTER_DROP)) { - (void)snprintf(recv_str, sizeof(recv_str), ", received %"PRIu64, local_stats.ans_recv); - } - if (lost > 0) { - (void)snprintf(lost_str, sizeof(lost_str), ", lost %"PRIu64, lost); - } - if (errors > 0) { - (void)snprintf(err_str, sizeof(err_str), ", errors %"PRIu64, errors); - } - INFO2("thread#%02u: sent %"PRIu64"%s%s%s", - ctx->thread_id, local_stats.qry_sent, recv_str, lost_str, err_str); - local_stats.duration = ctx->duration; collect_stats(&global_stats, &local_stats); cleanup: @@ -943,7 +813,7 @@ cleanup: WALK_LIST_DELSAFE(n, nxt, quic_sessions) { knot_quic_session_load(NULL, n); } - knot_quic_free_creds(quic_creds); + knot_creds_free(quic_creds); #endif // ENABLE_QUIC return NULL; @@ -1118,30 +988,33 @@ static void print_help(void) printf("Usage: %s [options] -i <queries_file> <dest_ip>\n" "\n" "Options:\n" - " -t, --duration <sec> "SPACE"Duration of traffic generation.\n" - " "SPACE" (default is %"PRIu64" seconds)\n" - " -T, --tcp[=debug_mode] "SPACE"Send queries over TCP.\n" - " -U, --quic[=debug_mode] "SPACE"Send queries over QUIC.\n" - " -Q, --qps <qps> "SPACE"Number of queries-per-second (approximately) to be sent.\n" - " "SPACE" (default is %"PRIu64" qps)\n" - " -b, --batch <size> "SPACE"Send queries in a batch of defined size.\n" - " "SPACE" (default is %d for UDP, %d for TCP)\n" - " -r, --drop "SPACE"Drop incoming responses (disables response statistics).\n" - " -p, --port <port> "SPACE"Remote destination port.\n" - " "SPACE" (default is %d for UDP/TCP, %u for QUIC)\n" - " -F, --affinity <spec> "SPACE"CPU affinity in the format [<cpu_start>][s<cpu_step>].\n" - " "SPACE" (default is %s)\n" - " -i, --infile <file> "SPACE"Path to a file with query templates.\n" - " -I, --interface <ifname> "SPACE"Override auto-detected interface for outgoing communication.\n" - " -l, --local <ip[/prefix]>"SPACE"Override auto-detected source IP address or subnet.\n" - " -L, --local-mac <MAC> "SPACE"Override auto-detected local MAC address.\n" - " -R, --remote-mac <MAC> "SPACE"Override auto-detected remote MAC address.\n" - " -v, --vlan <id> "SPACE"Add VLAN 802.1Q header with the given id.\n" - " -e, --edns-size <size> "SPACE"EDNS UDP payload size, range 512-4096 (default 1232)\n" - " -m, --mode <mode> "SPACE"Set XDP mode (auto, copy, generic).\n" - " -G, --qlog <path> "SPACE"Output directory for qlog (useful for QUIC only).\n" - " -h, --help "SPACE"Print the program help.\n" - " -V, --version "SPACE"Print the program version.\n" + " -t, --duration <sec> "SPACE"Duration of traffic generation.\n" + " "SPACE" (default is %"PRIu64" seconds)\n" + " -T, --tcp[=debug_mode] "SPACE"Send queries over TCP.\n" + " -U, --quic[=debug_mode] "SPACE"Send queries over QUIC.\n" + " -Q, --qps <qps> "SPACE"Number of queries-per-second (approximately) to be sent.\n" + " "SPACE" (default is %"PRIu64" qps)\n" + " -b, --batch <size> "SPACE"Send queries in a batch of defined size.\n" + " "SPACE" (default is %d for UDP, %d for TCP)\n" + " -r, --drop "SPACE"Drop incoming responses (disables response statistics).\n" + " -p, --port <port> "SPACE"Remote destination port.\n" + " "SPACE" (default is %d for UDP/TCP, %u for QUIC)\n" + " -F, --affinity <spec> "SPACE"CPU affinity in the format [<cpu_start>][s<cpu_step>].\n" + " "SPACE" (default is %s)\n" + " -I, --interface <ifname> "SPACE"Override auto-detected interface for outgoing communication.\n" + " -i, --infile <file> "SPACE"Path to a file with query templates.\n" + " -B, --binary "SPACE"Specify that input file is in binary format (<length:2><wire:length>).\n" + " -l, --local <ip[/prefix]> "SPACE"Override auto-detected source IP address or subnet.\n" + " -L, --local-mac <MAC> "SPACE"Override auto-detected local MAC address.\n" + " -R, --remote-mac <MAC> "SPACE"Override auto-detected remote MAC address.\n" + " -v, --vlan <id> "SPACE"Add VLAN 802.1Q header with the given id.\n" + " -e, --edns-size <size> "SPACE"EDNS UDP payload size, range 512-4096 (default 1232)\n" + " -m, --mode <mode> "SPACE"Set XDP mode (auto, copy, generic).\n" + " -G, --qlog <path> "SPACE"Output directory for qlog (useful for QUIC only).\n" + " -j, --json "SPACE"Output statistics in json.\n" + " -S, --stats-period <period>"SPACE"Enable periodic statistics printout in milliseconds.\n" + " -h, --help "SPACE"Print the program help.\n" + " -V, --version "SPACE"Print the program version.\n" "\n" "Parameters:\n" " <dest_ip> "SPACE"IPv4 or IPv6 address of the remote destination.\n", @@ -1240,40 +1113,45 @@ static int set_mode(const char *arg, knot_xdp_config_t *config) static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) { + const char *opts_str = "hV::t:Q:b:rp:T::U::F:I:i:Bl:L:R:v:e:m:G:jS:"; struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "duration", required_argument, NULL, 't' }, - { "qps", required_argument, NULL, 'Q' }, - { "batch", required_argument, NULL, 'b' }, - { "drop", no_argument, NULL, 'r' }, - { "port", required_argument, NULL, 'p' }, - { "tcp", optional_argument, NULL, 'T' }, - { "quic", optional_argument, NULL, 'U' }, - { "affinity", required_argument, NULL, 'F' }, - { "interface", required_argument, NULL, 'I' }, - { "local", required_argument, NULL, 'l' }, - { "infile", required_argument, NULL, 'i' }, - { "local-mac", required_argument, NULL, 'L' }, - { "remote-mac", required_argument, NULL, 'R' }, - { "vlan", required_argument, NULL, 'v' }, - { "edns-size", required_argument, NULL, 'e' }, - { "mode", required_argument, NULL, 'm' }, - { "qlog", required_argument, NULL, 'G' }, - { NULL } + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, + { "duration", required_argument, NULL, 't' }, + { "qps", required_argument, NULL, 'Q' }, + { "batch", required_argument, NULL, 'b' }, + { "drop", no_argument, NULL, 'r' }, + { "port", required_argument, NULL, 'p' }, + { "tcp", optional_argument, NULL, 'T' }, + { "quic", optional_argument, NULL, 'U' }, + { "affinity", required_argument, NULL, 'F' }, + { "interface", required_argument, NULL, 'I' }, + { "infile", required_argument, NULL, 'i' }, + { "binary", no_argument, NULL, 'B' }, + { "local", required_argument, NULL, 'l' }, + { "local-mac", required_argument, NULL, 'L' }, + { "remote-mac", required_argument, NULL, 'R' }, + { "vlan", required_argument, NULL, 'v' }, + { "edns-size", required_argument, NULL, 'e' }, + { "mode", required_argument, NULL, 'm' }, + { "qlog", required_argument, NULL, 'G' }, + { "json", no_argument, NULL, 'j' }, + { "stats-period", required_argument, NULL, 'S' }, + { 0 } }; int opt = 0, arg; bool default_at_once = true; double argf; - char *argcp, *local_ip = NULL, *filename = NULL; - while ((opt = getopt_long(argc, argv, "hVt:Q:b:rp:T::U::F:I:l:i:L:R:v:e:m:G:", opts, NULL)) != -1) { + char *argcp, *local_ip = NULL; + input_t input = { .format = TXT }; + while ((opt = getopt_long(argc, argv, opts_str, opts, NULL)) != -1) { switch (opt) { case 'h': print_help(); exit(EXIT_SUCCESS); case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); exit(EXIT_SUCCESS); case 't': assert(optarg); @@ -1366,12 +1244,15 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) case 'I': strlcpy(ctx->dev, optarg, IFNAMSIZ); break; + case 'i': + input.path = optarg; + break; + case 'B': + input.format = BIN; + break; case 'l': local_ip = optarg; break; - case 'i': - filename = optarg; - break; case 'L': if (mac_sscan(optarg, ctx->local_mac) != KNOT_EOK) { ERR2("invalid local MAC address '%s'", optarg); @@ -1415,17 +1296,33 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) case 'G': ctx->qlog_dir = optarg; break; + case 'S': + assert(optarg); + arg = atoi(optarg); + if (arg > 0) { + ctx->stats_period = arg * 1000000; // convert to ns + } else { + ERR2("period must be a positive integer\n"); + return false; + } + break; + case 'j': + if ((ctx->jw = jsonw_new(stdout, JSON_INDENT)) == NULL) { + ERR2("failed to use JSON"); + return false; + } + break; default: print_help(); return false; } } - if (filename == NULL) { + if (input.path == NULL) { print_help(); return false; } size_t qcount = ctx->duration / 1000000 * ctx->qps; - if (!load_queries(filename, ctx->edns_size, ctx->msgid, qcount)) { + if (!load_queries(&input, ctx->edns_size, ctx->msgid, qcount)) { return false; } if (global_payloads == NULL || argc - optind != 1) { @@ -1452,25 +1349,29 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) int main(int argc, char *argv[]) { + int ecode = EXIT_FAILURE; + xdp_gun_ctx_t ctx = ctx_defaults, *thread_ctxs = NULL; ctx.msgid = time(NULL) % UINT16_MAX; + ctx.runid = timestamp_ns() / 1000; + ctx.argv = argv; pthread_t *threads = NULL; if (!get_opts(argc, argv, &ctx)) { - free_global_payloads(); - return EXIT_FAILURE; + goto err; + } + + if (JSON_MODE(ctx)) { + jsonw_list(ctx.jw, NULL); // wrap the json in a list, for syntactic correctness } thread_ctxs = calloc(ctx.n_threads, sizeof(*thread_ctxs)); threads = calloc(ctx.n_threads, sizeof(*threads)); if (thread_ctxs == NULL || threads == NULL) { ERR2("out of memory"); - free(thread_ctxs); - free(threads); - free_global_payloads(); - return EXIT_FAILURE; + goto err; } - for (int i = 0; i < ctx.n_threads; i++) { + for (uint32_t i = 0; i < ctx.n_threads; i++) { thread_ctxs[i] = ctx; thread_ctxs[i].thread_id = i; } @@ -1482,8 +1383,7 @@ int main(int argc, char *argv[]) cur_limit.rlim_max != min_limit.rlim_max) { int ret = setrlimit(RLIMIT_MEMLOCK, &min_limit); if (ret != 0) { - WARN2("unable to increase RLIMIT_MEMLOCK: %s", - strerror(errno)); + WARN2("unable to increase RLIMIT_MEMLOCK: %s", strerror(errno)); } } } @@ -1509,22 +1409,30 @@ int main(int argc, char *argv[]) usleep(20000); } usleep(1000000); - xdp_trigger = KXDPGUN_START; usleep(1000000); for (size_t i = 0; i < ctx.n_threads; i++) { pthread_join(threads[i], NULL); } - if (global_stats.duration > 0 && global_stats.qry_sent > 0) { - print_stats(&global_stats, ctx.tcp, ctx.quic, !(ctx.flags & KNOT_XDP_FILTER_DROP), ctx.qps * ctx.n_threads); + if (DURATION_US(global_stats) > 0 && global_stats.qry_sent > 0) { + if (!JSON_MODE(ctx)) { + puts(STATS_SECTION_SEP); + } + STATS_FMT(&ctx, &global_stats, STATS_SUM); } pthread_mutex_destroy(&global_stats.mutex); + ecode = EXIT_SUCCESS; + +err: free(ctx.rss_conf); free(thread_ctxs); free(threads); free_global_payloads(); - - return EXIT_SUCCESS; + if (JSON_MODE(ctx)) { + jsonw_end(ctx.jw); + jsonw_free(&ctx.jw); + } + return ecode; } diff --git a/src/utils/kxdpgun/main.h b/src/utils/kxdpgun/main.h new file mode 100644 index 0000000..d87aee8 --- /dev/null +++ b/src/utils/kxdpgun/main.h @@ -0,0 +1,87 @@ +/* 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 <net/if.h> +#include <netinet/in.h> +#include <stdbool.h> + +#include "contrib/json.h" +#include "libknot/xdp/eth.h" +#include "libknot/xdp/tcp.h" + +#define PROGRAM_NAME "kxdpgun" +#define SPACE " " + +#define REMOTE_PORT_DEFAULT 53 +#define REMOTE_PORT_DOQ_DEFAULT 853 +#define LOCAL_PORT_MIN 2000 +#define LOCAL_PORT_MAX 65535 +#define QUIC_THREAD_PORTS 100 + +enum { + KXDPGUN_WAIT, + KXDPGUN_START, + KXDPGUN_STOP, +}; + +typedef enum { + KXDPGUN_IGNORE_NONE = 0, + KXDPGUN_IGNORE_QUERY = (1 << 0), + KXDPGUN_IGNORE_LASTBYTE = (1 << 1), + KXDPGUN_IGNORE_CLOSE = (1 << 2), + KXDPGUN_REUSE_CONN = (1 << 3), +} xdp_gun_ignore_t; + +typedef struct xdp_gun_ctx { + union { + struct sockaddr_in local_ip4; + struct sockaddr_in6 local_ip; + struct sockaddr_storage local_ip_ss; + }; + union { + struct sockaddr_in target_ip4; + struct sockaddr_in6 target_ip; + struct sockaddr_storage target_ip_ss; + }; + char dev[IFNAMSIZ]; + uint64_t qps, duration; + uint64_t runid; + uint64_t stats_start_us; + uint32_t stats_period; // 0 means no periodic stats + unsigned at_once; + uint16_t msgid; + uint16_t edns_size; + uint16_t vlan_tci; + uint8_t local_mac[6], target_mac[6]; + uint8_t local_ip_range; + bool ipv6; + bool tcp; + bool quic; + bool quic_full_handshake; + const char *qlog_dir; + const char *sending_mode; + xdp_gun_ignore_t ignore1; + knot_tcp_ignore_t ignore2; + uint16_t target_port; + knot_xdp_filter_flag_t flags; + unsigned n_threads, thread_id; + knot_eth_rss_conf_t *rss_conf; + jsonw_t *jw; + char **argv; + knot_xdp_config_t xdp_config; +} xdp_gun_ctx_t; diff --git a/src/utils/kxdpgun/stats.c b/src/utils/kxdpgun/stats.c new file mode 100644 index 0000000..f1e4f43 --- /dev/null +++ b/src/utils/kxdpgun/stats.c @@ -0,0 +1,292 @@ +/* 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 <assert.h> +#include <limits.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/macros.h" +#include "libknot/codes.h" +#include "libknot/lookup.h" +#include "utils/common/msg.h" +#include "utils/kxdpgun/main.h" +#include "utils/kxdpgun/stats.h" + +pthread_mutex_t stdout_mtx = PTHREAD_MUTEX_INITIALIZER; + +void clear_stats(kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&st->mutex); + st->since = 0; + st->until = 0; + st->qry_sent = 0; + st->synack_recv = 0; + st->ans_recv = 0; + st->finack_recv = 0; + st->rst_recv = 0; + st->size_recv = 0; + st->wire_recv = 0; + st->collected = 0; + st->lost = 0; + st->errors = 0; + memset(st->rcodes_recv, 0, sizeof(st->rcodes_recv)); + pthread_mutex_unlock(&st->mutex); +} + +size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) +{ + pthread_mutex_lock(&into->mutex); + into->since = what->since; + collect_periodic_stats(into, what); + size_t res = ++into->collected; + pthread_mutex_unlock(&into->mutex); + return res; +} + +void collect_periodic_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) +{ + into->until = what->until; + into->qry_sent += what->qry_sent; + into->synack_recv += what->synack_recv; + into->ans_recv += what->ans_recv; + into->finack_recv += what->finack_recv; + into->rst_recv += what->rst_recv; + into->size_recv += what->size_recv; + into->wire_recv += what->wire_recv; + into->lost += what->lost; + into->errors += what->errors; + for (int i = 0; i < RCODE_MAX; i++) { + into->rcodes_recv[i] += what->rcodes_recv[i]; + } +} + +void plain_stats_header(const xdp_gun_ctx_t *ctx) +{ + INFO2("using interface %s, XDP threads %u, IPv%c/%s%s%s, %s mode", ctx->dev, ctx->n_threads, + (ctx->ipv6 ? '6' : '4'), + (ctx->tcp ? "TCP" : ctx->quic ? "QUIC" : "UDP"), + (ctx->sending_mode[0] != '\0' ? " mode " : ""), + (ctx->sending_mode[0] != '\0' ? ctx->sending_mode : ""), + (knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL ? "native" : "emulated")); + puts(STATS_SECTION_SEP); +} + +/* see: + * - https://github.com/DNS-OARC/dns-metrics/blob/main/dns-metrics.schema.json + * - https://github.com/DNS-OARC/dns-metrics/issues/16#issuecomment-2139462920 + */ +void json_stats_header(const xdp_gun_ctx_t *ctx) +{ + jsonw_t *w = ctx->jw; + + jsonw_object(w, NULL); + { + jsonw_ulong(w, "runid", ctx->runid); + jsonw_str(w, "type", "header"); + jsonw_int(w, "schema_version", STATS_SCHEMA_VERSION); + jsonw_str(w, "generator", PROGRAM_NAME); + jsonw_str(w, "generator_version", PACKAGE_VERSION); + + jsonw_list(w, "generator_params"); + { + for (char **it = ctx->argv; *it != NULL; ++it) { + jsonw_str(w, NULL, *it); + } + } + jsonw_end(w); + + jsonw_ulong(w, "time_units_per_sec", 1000000000); + if (ctx->stats_period > 0) { + jsonw_double(w, "stats_interval", ctx->stats_period / 1000.0); + } + // TODO: timeout + + // mirror the info given by the plaintext printout + jsonw_object(w, "additional_info"); + { + jsonw_str(w, "interface", ctx->dev); + jsonw_int(w, "xdp_threads", ctx->n_threads); + jsonw_int(w, "ip_version", ctx->ipv6 ? 6 : 4); + jsonw_str(w, "transport_layer_proto", ctx->tcp ? "TCP" : (ctx->quic ? "QUIC" : "UDP")); + jsonw_object(w, "mode_info"); + { + if (ctx->sending_mode[0] != '\0') { + jsonw_str(w, "debug", ctx->sending_mode); + } + jsonw_str(w, "mode", knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL + ? "native" + : "emulated"); + } + jsonw_end(w); + } + jsonw_end(w); + } + jsonw_end(w); +} + +void plain_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&stdout_mtx); + + char recv_str[40] = "", lost_str[40] = "", err_str[40] = ""; + if (!(ctx->flags & KNOT_XDP_FILTER_DROP)) { + (void)snprintf(recv_str, sizeof(recv_str), ", received %"PRIu64, st->ans_recv); + } + if (st->lost > 0) { + (void)snprintf(lost_str, sizeof(lost_str), ", lost %"PRIu64, st->lost); + } + if (st->errors > 0) { + (void)snprintf(err_str, sizeof(err_str), ", errors %"PRIu64, st->errors); + } + INFO2("thread#%02u: sent %"PRIu64"%s%s%s", + ctx->thread_id, st->qry_sent, recv_str, lost_str, err_str); + + pthread_mutex_unlock(&stdout_mtx); +} + +void json_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&stdout_mtx); + + jsonw_t *w = ctx->jw; + + jsonw_object(ctx->jw, NULL); + { + jsonw_str(w, "type", "thread_summary"); + jsonw_ulong(w, "runid", ctx->runid); + jsonw_ulong(w, "subid", ctx->thread_id); + jsonw_ulong(w, "qry_sent", st->qry_sent); + jsonw_ulong(w, "ans_recv", st->ans_recv); + jsonw_ulong(w, "lost", st->lost); + jsonw_ulong(w, "errors", st->errors); + } + jsonw_end(ctx->jw); + + pthread_mutex_unlock(&stdout_mtx); +} + +void plain_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt) +{ + pthread_mutex_lock(&st->mutex); + + printf("%s metrics:\n", (stt == STATS_SUM) ? "cumulative" : "periodic"); + + bool recv = !(ctx->flags & KNOT_XDP_FILTER_DROP); + uint64_t duration = DURATION_US(*st); + double rel_start_us = (st->since / 1000.0) - ctx->stats_start_us ; + double rel_end_us = rel_start_us + duration; + +#define ps(counter) ((typeof(counter))((counter) * 1000 / ((float)duration / 1000))) +#define pct(counter) ((counter) * 100.0 / st->qry_sent) + + const char *name = ctx->tcp ? "SYNs: " : ctx->quic ? "initials:" : "queries: "; + printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, st->qry_sent, + ps(st->qry_sent), 100.0 * st->qry_sent / (duration / 1000000.0 * ctx->qps * ctx->n_threads)); + if (st->qry_sent > 0 && recv) { + if (ctx->tcp || ctx->quic) { + name = ctx->tcp ? "established:" : "handshakes: "; + printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, + st->synack_recv, ps(st->synack_recv), pct(st->synack_recv)); + } + printf("total replies: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->ans_recv, ps(st->ans_recv), pct(st->ans_recv)); + if (ctx->tcp) { + printf("total closed: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->finack_recv, ps(st->finack_recv), pct(st->finack_recv)); + } + if (st->rst_recv > 0) { + printf("total reset: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->rst_recv, ps(st->rst_recv), pct(st->rst_recv)); + } + printf("average DNS reply size: %"PRIu64" B\n", + st->ans_recv > 0 ? st->size_recv / st->ans_recv : 0); + printf("average Ethernet reply rate: %"PRIu64" bps (%.2f Mbps)\n", + ps(st->wire_recv * 8), ps((float)st->wire_recv * 8 / (1000 * 1000))); + + for (int i = 0; i < RCODE_MAX; i++) { + if (st->rcodes_recv[i] > 0) { + const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, i); + const char *rcname = rcode == NULL ? "unknown" : rcode->name; + int space = MAX(9 - strlen(rcname), 0); + printf("responded %s: %.*s%"PRIu64"\n", + rcname, space, " ", st->rcodes_recv[i]); + } + } + } + if (stt == STATS_SUM) { + printf("duration: %.4f s\n", duration / 1000000.0); + } else { + printf("since: %.4fs until: %.4fs\n", rel_start_us / 1000000, rel_end_us / 1000000); + } + + pthread_mutex_unlock(&st->mutex); +} + +/* see https://github.com/DNS-OARC/dns-metrics/blob/main/dns-metrics.schema.json + * and https://github.com/DNS-OARC/dns-metrics/issues/16#issuecomment-2139462920 */ +void json_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt) +{ + assert(stt == STATS_PERIODIC || stt == STATS_SUM); + + jsonw_t *w = ctx->jw; + + pthread_mutex_lock(&st->mutex); + + jsonw_object(w, NULL); + { + jsonw_ulong(w, "runid", ctx->runid); + jsonw_str(w, "type", (stt == STATS_PERIODIC) ? "stats_periodic" : "stats_sum"); + jsonw_ulong(w, "since", st->since); + jsonw_ulong(w, "until", st->until); + jsonw_ulong(w, "queries", st->qry_sent); + jsonw_ulong(w, "responses", st->ans_recv); + + jsonw_object(w, "response_rcodes"); + { + for (size_t i = 0; i < RCODE_MAX; ++i) { + if (st->rcodes_recv[i] > 0) { + const knot_lookup_t *rc = knot_lookup_by_id(knot_rcode_names, i); + jsonw_ulong(w, (rc == NULL) ? "unknown" : rc->name, st->rcodes_recv[i]); + } + } + } + jsonw_end(w); + + jsonw_object(w, "conn_info"); + { + jsonw_str(w, "type", ctx->tcp ? "tcp" : (ctx->quic ? "quic_conn" : "udp")); + + // TODO: + // packets_sent + // packets_recieved + + jsonw_ulong(w, "socket_errors", st->errors); + if (ctx->tcp || ctx->quic) { + jsonw_ulong(w, "handshakes", st->synack_recv); + // TODO: handshakes_failed + if (ctx->quic) { + // TODO: conn_resumption + } + } + } + jsonw_end(w); + } + jsonw_end(w); + + pthread_mutex_unlock(&st->mutex); +} diff --git a/src/utils/kxdpgun/stats.h b/src/utils/kxdpgun/stats.h new file mode 100644 index 0000000..2c62ee3 --- /dev/null +++ b/src/utils/kxdpgun/stats.h @@ -0,0 +1,78 @@ +/* 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 <stddef.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdbool.h> + +#include "utils/kxdpgun/main.h" + +#define RCODE_MAX (0x0F + 1) + +#define STATS_SECTION_SEP "--------------------------------------------------------------" + +#define JSON_INDENT " " +#define STATS_SCHEMA_VERSION 20240530 + +#define DURATION_US(st) (((st).until - (st).since) / 1000) +#define DURATION_NS(st) ((st).until - (st).since) + +#define JSON_MODE(ctx) ((ctx).jw != NULL) + +#define STATS_HDR(ctx) ((JSON_MODE(*(ctx)) ? json_stats_header : plain_stats_header)((ctx))) +#define STATS_THRD(ctx, stats) \ + ((JSON_MODE(*ctx) ? json_thrd_summary : plain_thrd_summary)((ctx), (stats))) +#define STATS_FMT(ctx, stats, stats_type) \ + ((JSON_MODE(*(ctx)) ? json_stats : plain_stats)((ctx), (stats), (stats_type))) + +typedef struct { + size_t collected; + uint64_t since, until; // nanosecs UNIX + uint64_t qry_sent; + uint64_t synack_recv; + uint64_t ans_recv; + uint64_t finack_recv; + uint64_t rst_recv; + uint64_t size_recv; + uint64_t wire_recv; + uint64_t errors; + uint64_t lost; + uint64_t rcodes_recv[RCODE_MAX]; + pthread_mutex_t mutex; +} kxdpgun_stats_t; + +typedef enum { + STATS_PERIODIC, + STATS_SUM, +} stats_type_t; + +void clear_stats(kxdpgun_stats_t *st); +size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what); +void collect_periodic_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what); + +void plain_stats_header(const xdp_gun_ctx_t *ctx); +void json_stats_header(const xdp_gun_ctx_t *ctx); + +void plain_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st); +void json_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st); + +void plain_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt); +void json_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt); + +extern pthread_mutex_t stdout_mtx; diff --git a/src/utils/kzonecheck/main.c b/src/utils/kzonecheck/main.c index b602cc9..e8c5868 100644 --- a/src/utils/kzonecheck/main.c +++ b/src/utils/kzonecheck/main.c @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) { "print", no_argument, NULL, 'p' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) /* Parse command line arguments */ int opt = 0; - while ((opt = getopt_long(argc, argv, "o:t:d:zpvVh", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "o:t:d:zpvV::h", opts, NULL)) != -1) { switch (opt) { case 'o': origin = optarg; @@ -105,7 +105,7 @@ int main(int argc, char *argv[]) print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; case 'd': optional = str2bool(optarg) ? SEMCHECK_DNSSEC_ON : SEMCHECK_DNSSEC_OFF; diff --git a/src/utils/kzonesign/main.c b/src/utils/kzonesign/main.c index e70abb6..641acfc 100644 --- a/src/utils/kzonesign/main.c +++ b/src/utils/kzonesign/main.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 @@ -117,7 +117,7 @@ static int zonesign(sign_params_t *params) goto fail; } - ret = knot_dnssec_validate_zone(&up, conf(), params->timestamp, false); + ret = knot_dnssec_validate_zone(&up, conf(), params->timestamp, false, false); if (ret != KNOT_EOK) { ERR2("DNSSEC validation failed (%s)", knot_strerror(ret)); char type_str[16]; @@ -204,7 +204,7 @@ int main(int argc, char *argv[]) { "verify" , no_argument, NULL, 'v' }, { "time", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -212,7 +212,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:o:rvt:hV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:o:rvt:hV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -245,7 +245,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); |