summaryrefslogtreecommitdiffstats
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/Makefile.inc8
-rw-r--r--src/utils/common/msg.h16
-rw-r--r--src/utils/common/netio.c7
-rw-r--r--src/utils/common/params.c2
-rw-r--r--src/utils/common/params.h13
-rw-r--r--src/utils/common/quic.h4
-rw-r--r--src/utils/common/tls.c7
-rw-r--r--src/utils/kcatalogprint/main.c14
-rw-r--r--src/utils/kdig/kdig_params.c13
-rw-r--r--src/utils/keymgr/bind_privkey.c8
-rw-r--r--src/utils/keymgr/keystore.c395
-rw-r--r--src/utils/keymgr/keystore.h24
-rw-r--r--src/utils/keymgr/main.c49
-rw-r--r--src/utils/keymgr/offline_ksk.c33
-rw-r--r--src/utils/khost/khost_params.c10
-rw-r--r--src/utils/kjournalprint/main.c17
-rw-r--r--src/utils/knotc/commands.c9
-rw-r--r--src/utils/knotc/main.c6
-rw-r--r--src/utils/knotd/main.c256
-rw-r--r--src/utils/knsec3hash/knsec3hash.c12
-rw-r--r--src/utils/knsupdate/knsupdate_exec.c12
-rw-r--r--src/utils/knsupdate/knsupdate_params.c160
-rw-r--r--src/utils/knsupdate/knsupdate_params.h8
-rw-r--r--src/utils/kxdpgun/load_queries.c241
-rw-r--r--src/utils/kxdpgun/load_queries.h14
-rw-r--r--src/utils/kxdpgun/main.c594
-rw-r--r--src/utils/kxdpgun/main.h87
-rw-r--r--src/utils/kxdpgun/stats.c292
-rw-r--r--src/utils/kxdpgun/stats.h78
-rw-r--r--src/utils/kzonecheck/main.c10
-rw-r--r--src/utils/kzonecheck/zone_check.c4
-rw-r--r--src/utils/kzonecheck/zone_check.h2
-rw-r--r--src/utils/kzonesign/main.c10
33 files changed, 1815 insertions, 600 deletions
diff --git a/src/utils/Makefile.inc b/src/utils/Makefile.inc
index b39b10d..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)
@@ -159,6 +163,8 @@ keymgr_SOURCES = \
utils/keymgr/bind_privkey.h \
utils/keymgr/functions.c \
utils/keymgr/functions.h \
+ utils/keymgr/keystore.c \
+ utils/keymgr/keystore.h \
utils/keymgr/offline_ksk.c \
utils/keymgr/offline_ksk.h \
utils/keymgr/main.c
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/keystore.c b/src/utils/keymgr/keystore.c
new file mode 100644
index 0000000..a4a0937
--- /dev/null
+++ b/src/utils/keymgr/keystore.c
@@ -0,0 +1,395 @@
+/* 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 <stdio.h>
+
+#include "utils/keymgr/keystore.h"
+
+#include "contrib/color.h"
+#include "contrib/spinlock.h"
+#include "contrib/time.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/key/privkey.h"
+#include "libdnssec/random.h"
+#include "libdnssec/sample_keys.h"
+#include "libdnssec/sign.h"
+#include "libknot/errcode.h"
+#include "knot/conf/conf.h"
+#include "knot/dnssec/kasp/kasp_zone.h"
+#include "knot/server/dthreads.h"
+#include "utils/common/msg.h"
+
+#define DFLT_ID "-"
+
+#define TEST_FORMAT "%-18s %9s %9s %9s %9s\n"
+#define BENCH_FORMAT "%-18s %9"
+#define BENCH_TIME 3000
+
+static const key_parameters_t *KEYS[] = {
+ &SAMPLE_RSA_KEY,
+ &SAMPLE_ECDSA_KEY,
+ &SAMPLE_ED25519_KEY,
+ &SAMPLE_ED448_KEY,
+};
+static const int KEYS_COUNT = sizeof(KEYS) / sizeof(*KEYS);
+
+static int create_dnskeys(dnssec_keystore_t *keystore, const char *id,
+ dnssec_key_algorithm_t algorithm,
+ dnssec_key_t **test_key_ptr, dnssec_key_t **ref_key_ptr)
+{
+ dnssec_key_t *test_key = NULL;
+ if (dnssec_key_new(&test_key) != DNSSEC_EOK ||
+ dnssec_key_set_algorithm(test_key, algorithm) != DNSSEC_EOK ||
+ dnssec_keystore_get_private(keystore, id, test_key) != DNSSEC_EOK) {
+ dnssec_key_free(test_key);
+ return KNOT_ERROR;
+ }
+
+ dnssec_binary_t rdata;
+ dnssec_key_t *ref_key = NULL;
+ if (dnssec_key_new(&ref_key) != DNSSEC_EOK ||
+ dnssec_key_get_rdata(test_key, &rdata) != DNSSEC_EOK ||
+ dnssec_key_set_rdata(ref_key, &rdata) != DNSSEC_EOK) {
+ dnssec_key_free(test_key);
+ dnssec_key_free(ref_key);
+ return KNOT_ERROR;
+ }
+
+ *test_key_ptr = test_key;
+ *ref_key_ptr = ref_key;
+
+ return KNOT_EOK;
+}
+
+static int test_sign(dnssec_key_t *test_key, dnssec_key_t *ref_key)
+{
+ static const dnssec_binary_t input = {
+ .data = (uint8_t *)"WuSEFCiFEKDTKuErihBW76q7p70dHuCfS6c1ffCK6ST",
+ .size = 43
+ };
+
+ dnssec_binary_t sign = { 0 };
+
+ dnssec_sign_ctx_t *ctx = NULL;
+ if (dnssec_sign_new(&ctx, test_key) != DNSSEC_EOK ||
+ dnssec_sign_add(ctx, &input) != DNSSEC_EOK ||
+ dnssec_sign_write(ctx, DNSSEC_SIGN_NORMAL, &sign) != DNSSEC_EOK) {
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+ return KNOT_ERROR;
+ }
+
+ if (dnssec_sign_init(ctx) != DNSSEC_EOK ||
+ dnssec_sign_add(ctx, &input) != DNSSEC_EOK ||
+ dnssec_sign_verify(ctx, false, &sign) != DNSSEC_EOK) {
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+ return KNOT_ERROR;
+ }
+
+ dnssec_sign_free(ctx);
+ ctx = NULL;
+
+ if (dnssec_sign_new(&ctx, ref_key) != DNSSEC_EOK ||
+ dnssec_sign_add(ctx, &input) != DNSSEC_EOK ||
+ dnssec_sign_verify(ctx, false, &sign) != DNSSEC_EOK) {
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+ return KNOT_ERROR;
+ }
+
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+
+ return KNOT_EOK;
+}
+
+static int test_key_use(dnssec_keystore_t *store, const char *keyid,
+ dnssec_key_algorithm_t algorithm)
+{
+ dnssec_key_t *test_key = NULL;
+ dnssec_key_t *ref_key = NULL;
+
+ if (create_dnskeys(store, keyid, algorithm, &test_key, &ref_key) != KNOT_EOK) {
+ return KNOT_ERROR;
+ }
+
+ if (test_sign(test_key, ref_key) != KNOT_EOK) {
+ dnssec_key_free(test_key);
+ dnssec_key_free(ref_key);
+ return KNOT_ERROR;
+ }
+
+ dnssec_key_free(test_key);
+ dnssec_key_free(ref_key);
+
+ return KNOT_EOK;
+}
+
+static void test_algorithm(dnssec_keystore_t *store,
+ const key_parameters_t *params)
+{
+ struct {
+ bool generate;
+ bool import;
+ bool remove;
+ bool use;
+ } res = { 0 };
+
+ char *id = NULL;
+ int ret = dnssec_keystore_generate(store, params->algorithm,
+ params->bit_size, NULL, &id);
+ if (ret == DNSSEC_EOK) {
+ res.generate = true;
+
+ ret = test_key_use(store, id, params->algorithm);
+ res.use = (ret == KNOT_EOK);
+
+ ret = dnssec_keystore_remove(store, id);
+ res.remove = (ret == DNSSEC_EOK);
+ free(id);
+ }
+
+ ret = dnssec_keystore_import(store, &params->pem, &id);
+ if (ret == DNSSEC_EOK) {
+ res.import = true;
+
+ ret = test_key_use(store, id, params->algorithm);
+ if (res.generate) {
+ res.use &= (ret == KNOT_EOK);
+ } else {
+ res.use = (ret == KNOT_EOK);
+ }
+
+ ret = dnssec_keystore_remove(store, id);
+ if (res.generate) {
+ res.remove &= (ret == DNSSEC_EOK);
+ } else {
+ res.remove = (ret == DNSSEC_EOK);
+ }
+ free(id);
+ }
+
+ const knot_lookup_t *alg_info = knot_lookup_by_id(knot_dnssec_alg_names,
+ params->algorithm);
+ assert(alg_info);
+
+ printf(TEST_FORMAT,
+ alg_info->name,
+ res.generate ? "yes" : "no",
+ res.import ? "yes" : "no",
+ res.remove ? "yes" : "no",
+ res.use ? "yes" : "no");
+}
+
+static int init_keystore(dnssec_keystore_t **store, const char *keystore_id,
+ unsigned threads)
+{
+ size_t len = strlen(keystore_id) + 1;
+ conf_val_t id = conf_rawid_get(conf(), C_KEYSTORE, C_ID,
+ (const uint8_t *)keystore_id, len);
+ if (id.code != KNOT_EOK && strcmp(keystore_id, DFLT_ID) != 0) {
+ ERR2("keystore '%s' not configured", keystore_id);
+ return id.code;
+ }
+ id.blob = (const uint8_t *)keystore_id;
+ id.blob_len = len;
+
+ unsigned backend;
+ bool key_label;
+
+ int ret = zone_init_keystore(conf(), NULL, &id, store, &backend, &key_label);
+ if (ret != KNOT_EOK) {
+ ERR2("failed to open '%s' keystore (%s)", keystore_id, knot_strerror(ret));
+ return ret;
+ }
+
+ if (strcmp(keystore_id, DFLT_ID) == 0) {
+ printf("Keystore default");
+ } else {
+ printf("Keystore id '%s'", keystore_id);
+ };
+ printf(", type %s", (backend == KEYSTORE_BACKEND_PEM ? "PEM" : "PKCS #11"));
+ if (threads > 0) {
+ printf(", threads %u", threads);
+ }
+ printf("\n\n");
+
+ return KNOT_EOK;
+}
+
+int keymgr_keystore_test(const char *keystore_id, keymgr_list_params_t *params)
+{
+ dnssec_keystore_t *store = NULL;
+
+ int ret = init_keystore(&store, keystore_id, 0);
+ if (ret != KNOT_EOK) {
+ goto done;
+ }
+
+ const bool c = params->color;
+ printf("%s" TEST_FORMAT "%s",
+ COL_UNDR(c),
+ "Algorithm", "Generate", "Import", "Remove", "Use",
+ COL_RST(c));
+ for (int i = 0; i < KEYS_COUNT; i++) {
+ test_algorithm(store, KEYS[i]);
+ }
+done:
+ dnssec_keystore_deinit(store);
+
+ return ret;
+}
+
+struct result {
+ unsigned long signs;
+ unsigned long time;
+};
+
+typedef struct bench_ctx {
+ dnssec_keystore_t *store;
+ const key_parameters_t *params;
+ struct result *results;
+ knot_spin_t lock;
+} bench_ctx_t;
+
+static int bench(dthread_t *dt)
+{
+ assert(dt != NULL && dt->data != NULL);
+
+ bench_ctx_t *data = dt->data;
+ dnssec_keystore_t *store = data->store;
+ const key_parameters_t *params = data->params;
+ struct result *result = data->results + dt_get_id(dt);
+
+ result->time = 0;
+ result->signs = 0;
+
+ char *id = NULL;
+ dnssec_key_t *test_key = NULL;
+ knot_spin_lock(&data->lock);
+ int ret = dnssec_keystore_generate(store, params->algorithm,
+ params->bit_size, NULL, &id);
+ if (ret != DNSSEC_EOK ||
+ dnssec_key_new(&test_key) != DNSSEC_EOK ||
+ dnssec_key_set_algorithm(test_key, params->algorithm) != DNSSEC_EOK ||
+ dnssec_keystore_get_private(store, id, test_key) != DNSSEC_EOK) {
+ goto finish;
+ }
+ knot_spin_unlock(&data->lock);
+
+ uint8_t input_data[64];
+ dnssec_binary_t input = {
+ .data = input_data,
+ .size = sizeof(input_data)
+ };
+ (void)dnssec_random_binary(&input);
+
+ struct timespec start_ts, end_ts;
+ clock_gettime(CLOCK_MONOTONIC, &start_ts);
+
+ while (result->time < BENCH_TIME) {
+ dnssec_binary_t sign = { 0 };
+ dnssec_sign_ctx_t *ctx = NULL;
+ if (dnssec_sign_new(&ctx, test_key) != DNSSEC_EOK ||
+ dnssec_sign_add(ctx, &input) != DNSSEC_EOK ||
+ dnssec_sign_write(ctx, DNSSEC_SIGN_NORMAL, &sign) != DNSSEC_EOK) {
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+ result->time = 0;
+ goto finish;
+ }
+ memcpy(input.data, sign.data, MIN(input.size, sign.size));
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+
+ clock_gettime(CLOCK_MONOTONIC, &end_ts);
+ result->time = time_diff_ms(&start_ts, &end_ts);
+ result->signs++;
+ }
+
+finish:
+ knot_spin_unlock(&data->lock);
+ dnssec_key_free(test_key);
+ (void)dnssec_keystore_remove(store, id);
+ free(id);
+
+ return KNOT_EOK;
+}
+
+int keymgr_keystore_bench(const char *keystore_id, keymgr_list_params_t *params,
+ uint16_t threads)
+{
+ dnssec_keystore_t *store = NULL;
+
+ int ret = init_keystore(&store, keystore_id, threads);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ const bool c = params->color;
+ printf("%s" BENCH_FORMAT"s\n" "%s",
+ COL_UNDR(c),
+ "Algorithm", "Sigs/sec",
+ COL_RST(c));
+
+ for (int i = 0; i < KEYS_COUNT; i++) {
+ struct result results[threads];
+ bench_ctx_t d = {
+ .store = store,
+ .params = KEYS[i],
+ .results = results
+ };
+ knot_spin_init(&d.lock);
+
+ dt_unit_t *pool = dt_create(threads, bench, NULL, &d);
+ if (pool == NULL ||
+ dt_start(pool) != KNOT_EOK ||
+ dt_join(pool) != KNOT_EOK) {
+ dt_delete(&pool);
+ knot_spin_destroy(&d.lock);
+ dnssec_keystore_deinit(store);
+ return KNOT_ERROR;
+ }
+ dt_delete(&pool);
+ knot_spin_destroy(&d.lock);
+
+ double result_f = 0.5; // 0.5 to ensure correct rounding
+ for (struct result *it = d.results; it < d.results + threads; ++it) {
+ if (it->time == 0) {
+ result_f = 0.;
+ break;
+ }
+ result_f += it->signs * 1000. / it->time;
+ }
+
+ const knot_lookup_t *alg_info = knot_lookup_by_id(
+ knot_dnssec_alg_names, KEYS[i]->algorithm);
+ assert(alg_info);
+
+ const unsigned result = (unsigned)result_f;
+ if (result > 0) {
+ printf(BENCH_FORMAT"u\n", alg_info->name, result);
+ } else {
+ printf(BENCH_FORMAT"s\n", alg_info->name, "n/a");
+ }
+ }
+
+ dnssec_keystore_deinit(store);
+
+ return KNOT_EOK;
+}
diff --git a/src/utils/keymgr/keystore.h b/src/utils/keymgr/keystore.h
new file mode 100644
index 0000000..6adb5df
--- /dev/null
+++ b/src/utils/keymgr/keystore.h
@@ -0,0 +1,24 @@
+/* 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 "utils/keymgr/functions.h"
+
+int keymgr_keystore_test(const char *keystore_id, keymgr_list_params_t *params);
+
+int keymgr_keystore_bench(const char *keystore_id, keymgr_list_params_t *params,
+ uint16_t threads);
diff --git a/src/utils/keymgr/main.c b/src/utils/keymgr/main.c
index 355fd3a..999b5c5 100644
--- a/src/utils/keymgr/main.c
+++ b/src/utils/keymgr/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
@@ -15,17 +15,20 @@
*/
#include <getopt.h>
+#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include "contrib/strtonum.h"
#include "knot/dnssec/zone-keys.h"
+#include "libdnssec/crypto.h"
#include "libknot/libknot.h"
#include "utils/common/msg.h"
#include "utils/common/params.h"
#include "utils/common/signal.h"
#include "utils/common/util_conf.h"
#include "utils/keymgr/functions.h"
+#include "utils/keymgr/keystore.h"
#include "utils/keymgr/offline_ksk.h"
#define PROGRAM_NAME "keymgr"
@@ -36,6 +39,7 @@ static void print_help(void)
{
printf("Usage:\n"
" %s [-c | -C | -D <path>] [options] <zone_name> <command>\n"
+ " %s [-c | -C | -D <path>] [options] <keystore_id> <command>\n"
" %s [-c | -C | -D <path>] [-j] -l\n"
" %s -t <tsig_name> [<algorithm> [<bits>]]\n"
"\n"
@@ -87,6 +91,13 @@ static void print_help(void)
" set Set existing key's timing attribute.\n"
" (syntax: set <key_spec> <attribute_name>=<value>...)\n"
"\n"
+ "Keystore commands:\n"
+ " keystore_test Conduct some tests on the specified keystore.\n"
+ " Use a configured keystore id or '-' for the default.\n"
+ " keystore_bench Conduct a signing benchmark for each supported algorithm.\n"
+ " Use a configured keystore id or '-' for the default.\n"
+ " (syntax: keystore_bench [<num_threads>])\n"
+ "\n"
"Commands related to Offline KSK feature:\n"
" pregenerate Pre-generate ZSKs for later rollovers with offline KSK.\n"
" (syntax: pregenerate [<from>] <to>)\n"
@@ -115,7 +126,8 @@ static void print_help(void)
" ksk Whether the generated/imported key shall be Key Signing Key.\n"
" created/publish/ready/active/retire/remove The timestamp of the key\n"
" lifetime event (e.g. published=+1d active=1499770874)\n",
- PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR);
+ PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, CONF_DEFAULT_FILE,
+ CONF_DEFAULT_DBDIR);
}
static int key_command(int argc, char *argv[], int opt_ind, knot_lmdb_db_t *kaspdb,
@@ -129,7 +141,8 @@ static int key_command(int argc, char *argv[], int opt_ind, knot_lmdb_db_t *kasp
argc -= opt_ind;
argv += opt_ind;
- knot_dname_t *zone_name = knot_dname_from_str_alloc(argv[0]);
+ const char *id_str = argv[0];
+ knot_dname_t *zone_name = knot_dname_from_str_alloc(id_str);
if (zone_name == NULL) {
return KNOT_ENOMEM;
}
@@ -275,6 +288,20 @@ static int key_command(int argc, char *argv[], int opt_ind, knot_lmdb_db_t *kasp
} else if (strcmp(argv[1], "import-skr") == 0) {
CHECK_MISSING_ARG("Input file not specified");
ret = keymgr_import_skr(&kctx, argv[2]);
+ } else if (strcmp(argv[1], "keystore-test") == 0) {
+ ret = keymgr_keystore_test(id_str, list_params);
+ print_ok_on_succes = false;
+ } else if (strcmp(argv[1], "keystore-bench") == 0) {
+ uint16_t threads = 1;
+ if (argc > 2) {
+ ret = str_to_u16(argv[2], &threads);
+ }
+ if (ret == KNOT_EOK && threads > 0) {
+ ret = keymgr_keystore_bench(id_str, list_params, threads);
+ } else {
+ ret = KNOT_EINVAL;
+ }
+ print_ok_on_succes = false;
} else {
ERR2("invalid command '%s'", argv[1]);
goto main_end;
@@ -306,19 +333,22 @@ 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 }
};
tzset();
+ dnssec_crypto_init();
+
signal_ctx.close_db = &kaspdb;
signal_init_std();
+ struct sigaction sigact = { .sa_handler = SIG_IGN };
+ sigaction(SIGALRM, &sigact, NULL);
int ret;
bool just_list = false;
@@ -327,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) {
@@ -363,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;
@@ -376,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();
@@ -409,8 +436,10 @@ int main(int argc, char *argv[])
success:
util_conf_deinit();
+ dnssec_crypto_cleanup();
return EXIT_SUCCESS;
failure:
util_conf_deinit();
+ dnssec_crypto_cleanup();
return EXIT_FAILURE;
}
diff --git a/src/utils/keymgr/offline_ksk.c b/src/utils/keymgr/offline_ksk.c
index 253199f..05b2d2b 100644
--- a/src/utils/keymgr/offline_ksk.c
+++ b/src/utils/keymgr/offline_ksk.c
@@ -37,8 +37,10 @@ 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, &resch);
+ int ret = knot_dnssec_key_rollover(ctx, KEY_ROLL_ALLOW_ZSK_ROLL | KEY_ROLL_PRESERVE_FUTURE, &resch);
if (ret != KNOT_EOK) {
ERR2("key rollover failed");
return ret;
@@ -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, &params->tls_params, NULL, &params->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(&params->update_list);
init_list(&params->prereq_list);
+ tls_params_init(&params->tls_params);
+
/* Initialize memory context. */
mm_ctx_mempool(&params->mm, MM_DEFAULT_BLKSIZE);
@@ -142,6 +145,9 @@ void knsupdate_clean(knsupdate_params_t *params)
knot_pkt_free(params->answer);
knot_tsig_key_deinit(&params->tsig_key);
+ tls_params_clean(&params->tls_params);
+ quic_params_clean(&params->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, &params->retries);
@@ -241,7 +303,7 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[])
knot_tsig_key_deinit(&params->tsig_key);
ret = knot_tsig_key_init_str(&params->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(&params->tsig_key);
ret = knot_tsig_key_init_file(&params->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(&params->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(&params->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(&params->qfiles, argv[optind], &params->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 8f4d402..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;
- struct timespec timer;
+ struct knot_xdp_socket *xsk = NULL;
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
@@ -496,20 +354,21 @@ void *xdp_gun_thread(void *_ctx)
tcp_table = knot_tcp_table_new(ctx->qps, NULL);
if (tcp_table == NULL) {
ERR2("failed to allocate TCP connection table");
- return NULL;
+ goto cleanup;
}
}
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");
- return NULL;
+ 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");
- return NULL;
+ goto cleanup;
}
quic_table->qlog_dir = ctx->qlog_dir;
#else
@@ -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);
@@ -530,18 +389,11 @@ void *xdp_gun_thread(void *_ctx)
if (ret != KNOT_EOK) {
ERR2("failed to initialize XDP socket#%u on interface %s (%s)",
ctx->thread_id, ctx->dev, knot_strerror(ret));
- knot_tcp_table_free(tcp_table);
- return NULL;
+ goto cleanup;
}
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 };
@@ -577,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);
@@ -587,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)) {
@@ -622,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);
}
@@ -671,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);
}
@@ -686,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;
@@ -700,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) {
@@ -715,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;
}
@@ -735,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)) {
@@ -764,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);
@@ -782,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;
}
@@ -806,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);
@@ -823,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)) {
@@ -838,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);
@@ -847,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;
}
@@ -883,34 +740,62 @@ 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);
+ collect_stats(&global_stats, &local_stats);
+
+cleanup:
knot_xdp_deinit(xsk);
if (ctx->tcp) {
@@ -928,24 +813,9 @@ void *xdp_gun_thread(void *_ctx)
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
- 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);
-
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 5fb4c73..e8c5868 100644
--- a/src/utils/kzonecheck/main.c
+++ b/src/utils/kzonecheck/main.c
@@ -23,6 +23,7 @@
#include "libknot/libknot.h"
#include "knot/common/log.h"
#include "knot/zone/semantic-check.h"
+#include "knot/zone/zone-load.h"
#include "utils/common/msg.h"
#include "utils/common/params.h"
#include "utils/kzonecheck/zone_check.h"
@@ -80,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 }
};
@@ -89,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;
@@ -104,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;
@@ -169,7 +170,8 @@ int main(int argc, char *argv[])
log_levels_add(LOG_TARGET_STDOUT, LOG_SOURCE_ANY, LOG_UPTO(LOG_DEBUG));
}
- int ret = zone_check(filename, zone, zonemd, optional, (time_t)check_time, print);
+ int ret = zone_check(filename, zone, zonemd, DEFAULT_TTL, optional,
+ (time_t)check_time, print);
log_close();
if (ret == KNOT_EOK) {
if (verbose && !print) {
diff --git a/src/utils/kzonecheck/zone_check.c b/src/utils/kzonecheck/zone_check.c
index 2ea63b8..46c8a8e 100644
--- a/src/utils/kzonecheck/zone_check.c
+++ b/src/utils/kzonecheck/zone_check.c
@@ -64,14 +64,14 @@ static void print_statistics(err_handler_stats_t *stats)
}
int zone_check(const char *zone_file, const knot_dname_t *zone_name, bool zonemd,
- semcheck_optional_t optional, time_t time, bool print)
+ uint32_t dflt_ttl, semcheck_optional_t optional, time_t time, bool print)
{
err_handler_stats_t stats = {
.handler = { .cb = err_callback },
};
zloader_t zl;
- int ret = zonefile_open(&zl, zone_file, zone_name, optional, time);
+ int ret = zonefile_open(&zl, zone_file, zone_name, dflt_ttl, optional, time);
switch (ret) {
case KNOT_EOK:
break;
diff --git a/src/utils/kzonecheck/zone_check.h b/src/utils/kzonecheck/zone_check.h
index 206c27e..6a7afdf 100644
--- a/src/utils/kzonecheck/zone_check.h
+++ b/src/utils/kzonecheck/zone_check.h
@@ -20,4 +20,4 @@
#include "libknot/libknot.h"
int zone_check(const char *zone_file, const knot_dname_t *zone_name, bool zonemd,
- semcheck_optional_t optional, time_t time, bool print);
+ uint32_t dflt_ttl, semcheck_optional_t optional, time_t time, bool print);
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();