diff options
Diffstat (limited to 'src/utils')
29 files changed, 1343 insertions, 584 deletions
diff --git a/src/utils/Makefile.inc b/src/utils/Makefile.inc index 3097050..1f11282 100644 --- a/src/utils/Makefile.inc +++ b/src/utils/Makefile.inc @@ -103,7 +103,11 @@ kxdpgun_SOURCES = \ utils/kxdpgun/ip_route.h \ utils/kxdpgun/load_queries.c \ utils/kxdpgun/load_queries.h \ - utils/kxdpgun/main.c + utils/kxdpgun/main.c \ + utils/kxdpgun/main.h \ + utils/kxdpgun/stats.c \ + utils/kxdpgun/stats.h + kxdpgun_CPPFLAGS = $(libknotus_la_CPPFLAGS) $(libmnl_CFLAGS) kxdpgun_LDADD = libknot.la $(libcontrib_LIBS) $(libmnl_LIBS) $(pthread_LIBS) diff --git a/src/utils/common/msg.h b/src/utils/common/msg.h index d2ed57e..fbd6c8e 100644 --- a/src/utils/common/msg.h +++ b/src/utils/common/msg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,10 +23,10 @@ #define WARNING_ ";; WARNING: " #define DEBUG_ ";; DEBUG: " -#define ERR(msg, ...) { fprintf(stderr, ERROR_ msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define INFO(msg, ...) { fprintf(stdout, INFO_ msg "\n", ##__VA_ARGS__); fflush(stdout); } -#define WARN(msg, ...) { fprintf(stderr, WARNING_ msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define DBG(msg, ...) { msg_debug(DEBUG_ msg "\n", ##__VA_ARGS__); fflush(stdout); } +#define ERR(msg, ...) do { fprintf(stderr, ERROR_ msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define INFO(msg, ...) do { fprintf(stdout, INFO_ msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) +#define WARN(msg, ...) do { fprintf(stderr, WARNING_ msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define DBG(msg, ...) do { msg_debug(DEBUG_ msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) /*! \brief Enable/disable debugging. */ int msg_enable_debug(int val); @@ -37,6 +37,6 @@ int msg_debug(const char *fmt, ...); /*! \brief Debug message for null input. */ #define DBG_NULL DBG("%s: null parameter", __func__) -#define ERR2(msg, ...) { fprintf(stderr, "error: " msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define WARN2(msg, ...) { fprintf(stderr, "warning: " msg "\n", ##__VA_ARGS__); fflush(stderr); } -#define INFO2(msg, ...) { fprintf(stdout, msg "\n", ##__VA_ARGS__); fflush(stdout); } +#define ERR2(msg, ...) do { fprintf(stderr, "error: " msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define WARN2(msg, ...) do { fprintf(stderr, "warning: " msg "\n", ##__VA_ARGS__); fflush(stderr); } while (0) +#define INFO2(msg, ...) do { fprintf(stdout, msg "\n", ##__VA_ARGS__); fflush(stdout); } while (0) diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c index eed14ee..8ea7b59 100644 --- a/src/utils/common/netio.c +++ b/src/utils/common/netio.c @@ -32,6 +32,7 @@ #include "utils/common/msg.h" #include "utils/common/tls.h" #include "libknot/libknot.h" +#include "libknot/quic/tls_common.h" #include "contrib/net.h" #include "contrib/proxyv2/proxyv2.h" #include "contrib/sockaddr.h" @@ -521,8 +522,8 @@ int net_connect(net_t *net) #endif //LIBNGHTTP2 { // Establish TLS connection. - ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, NULL, - net_get_remote(net)); + ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, + KNOT_TLS_PRIORITIES, net_get_remote(net)); if (ret != 0) { net_close(net); return ret; @@ -546,7 +547,7 @@ int net_connect(net_t *net) return ret; } ret = tls_ctx_setup_remote_endpoint(&net->tls, - &doq_alpn, 1, QUIC_PRIORITY, net_get_remote(net)); + &doq_alpn, 1, KNOT_TLS_PRIORITIES, net_get_remote(net)); if (ret != 0) { net_close(net); return ret; diff --git a/src/utils/common/params.c b/src/utils/common/params.c index d16af4c..fe5a854 100644 --- a/src/utils/common/params.c +++ b/src/utils/common/params.c @@ -21,7 +21,7 @@ #include <sys/socket.h> #ifdef LIBIDN -#include LIBIDN_HEADER +#include <idn2.h> #endif #include "utils/common/params.h" diff --git a/src/utils/common/params.h b/src/utils/common/params.h index 8b7565e..bb071aa 100644 --- a/src/utils/common/params.h +++ b/src/utils/common/params.h @@ -22,6 +22,7 @@ #include <stdio.h> #include "libknot/libknot.h" +#include "contrib/string.h" #include "contrib/ucw/lists.h" #define DEFAULT_IPV4_NAME "127.0.0.1" @@ -31,7 +32,7 @@ #define DEFAULT_DNS_QUIC_PORT "853" #define DEFAULT_DNS_TLS_PORT "853" #define DEFAULT_UDP_SIZE 512 -#define DEFAULT_EDNS_SIZE 4096 +#define DEFAULT_EDNS_SIZE 1232 #define MAX_PACKET_SIZE 65535 #define SEP_CHARS "\n\t " @@ -118,9 +119,15 @@ typedef struct { param_handle_f handler; } param_t; -inline static void print_version(const char *program_name) +inline static void print_version(const char *prog_name, bool verbose) { - printf("%s (Knot DNS), version %s\n", program_name, PACKAGE_VERSION); + if (prog_name != NULL) { + printf("%s, ", prog_name); + } + printf("Knot DNS %s\n", PACKAGE_VERSION); + if (verbose) { + printf("\n%s\n", configure_summary); + } } /*! diff --git a/src/utils/common/quic.h b/src/utils/common/quic.h index fd70d27..2b860c3 100644 --- a/src/utils/common/quic.h +++ b/src/utils/common/quic.h @@ -35,10 +35,6 @@ void quic_params_clean(quic_params_t *params); #include "utils/common/tls.h" -#define QUIC_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3" -#define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:+GROUP-SECP384R1:+GROUP-SECP521R1" -#define QUIC_PRIORITY "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_GROUPS - typedef enum { CLOSED, // Initialized CONNECTED, // RTT-0 diff --git a/src/utils/common/tls.c b/src/utils/common/tls.c index 276ae16..4c9a588 100644 --- a/src/utils/common/tls.c +++ b/src/utils/common/tls.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -398,7 +398,7 @@ int tls_certificate_verification(tls_ctx_t *ctx) }; size_t data_count = (ctx->params->hostname != NULL) ? 2 : 1; if (data_count == 1) { - WARN("TLS, no hostname provided, will not verify certificate owner") + WARN("TLS, no hostname provided, will not verify certificate owner"); } unsigned int status; @@ -533,7 +533,8 @@ int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn, } if (priority != NULL) { - ret = gnutls_priority_set_direct(ctx->session, priority, NULL); + ret = gnutls_set_default_priority_append(ctx->session, priority, + NULL, 0); } else { ret = gnutls_set_default_priority(ctx->session); } diff --git a/src/utils/kcatalogprint/main.c b/src/utils/kcatalogprint/main.c index 0172347..85e50b6 100644 --- a/src/utils/kcatalogprint/main.c +++ b/src/utils/kcatalogprint/main.c @@ -108,7 +108,7 @@ int main(int argc, char *argv[]) { "catalog", required_argument, NULL, 'a' }, { "member", required_argument, NULL, 'm' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:a:m:hV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:a:m:hV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); @@ -155,13 +155,9 @@ int main(int argc, char *argv[]) } } - // Backward compatibility. if (argc - optind > 0) { - WARN2("obsolete parameter specified"); - if (util_conf_init_justdb("catalog-db", argv[optind]) != KNOT_EOK) { - goto failure; - } - optind++; + print_help(); + goto failure; } if (util_conf_init_default(true) != KNOT_EOK) { diff --git a/src/utils/kdig/kdig_params.c b/src/utils/kdig/kdig_params.c index c8fd83f..8566848 100644 --- a/src/utils/kdig/kdig_params.c +++ b/src/utils/kdig/kdig_params.c @@ -1690,7 +1690,7 @@ query_t *query_create(const char *owner, const query_t *conf) query->style.style.now = knot_time(); query->idn = true; query->nsid = false; - query->edns = -1; + query->edns = 0; query->cc.len = 0; query->sc.len = 0; query->badcookie = BADCOOKIE_RETRY_MAX; @@ -2517,12 +2517,7 @@ static int parse_opt1(const char *opt, const char *value, kdig_params_t *params, *index += add; break; case 'V': - if (len > 1) { - ERR("invalid option -%s", opt); - return KNOT_ENOTSUP; - } - - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, len > 1); params->stop = true; break; case 'x': @@ -2598,8 +2593,8 @@ static int parse_opt1(const char *opt, const char *value, kdig_params_t *params, if (strcmp(opt, "-help") == 0) { print_help(); params->stop = true; - } else if (strcmp(opt, "-version") == 0) { - print_version(PROGRAM_NAME); + } else if (strncmp(opt, "-version", 8) == 0) { + print_version(PROGRAM_NAME, strlen(opt) > 9); params->stop = true; } else { ERR("invalid option: -%s", opt); diff --git a/src/utils/keymgr/bind_privkey.c b/src/utils/keymgr/bind_privkey.c index 9ab895c..bbb61a5 100644 --- a/src/utils/keymgr/bind_privkey.c +++ b/src/utils/keymgr/bind_privkey.c @@ -281,9 +281,7 @@ static int rsa_params_to_pem(const bind_privkey_t *params, dnssec_binary_t *pem) static gnutls_ecc_curve_t choose_ecdsa_curve(size_t pubkey_size) { switch (pubkey_size) { -#ifdef HAVE_ED25519 case 32: return GNUTLS_ECC_CURVE_ED25519; -#endif #ifdef HAVE_ED448 case 57: return GNUTLS_ECC_CURVE_ED448; #endif @@ -334,7 +332,6 @@ static int ecdsa_params_to_pem(dnssec_key_t *dnskey, const bind_privkey_t *param return dnssec_pem_from_x509(key, pem); } -#if defined(HAVE_ED25519) || defined(HAVE_ED448) static void eddsa_extract_public_params(dnssec_key_t *key, gnutls_ecc_curve_t *curve, gnutls_datum_t *x) { @@ -371,7 +368,6 @@ static int eddsa_params_to_pem(dnssec_key_t *dnskey, const bind_privkey_t *param return dnssec_pem_from_x509(key, pem); } -#endif int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary_t *pem) { @@ -385,15 +381,11 @@ int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: return ecdsa_params_to_pem(key, params, pem); -#ifdef HAVE_ED25519 case DNSSEC_KEY_ALGORITHM_ED25519: -#endif #ifdef HAVE_ED448 case DNSSEC_KEY_ALGORITHM_ED448: #endif -#if defined(HAVE_ED25519) || defined(HAVE_ED448) return eddsa_params_to_pem(key, params, pem); -#endif default: return DNSSEC_INVALID_KEY_ALGORITHM; } diff --git a/src/utils/keymgr/main.c b/src/utils/keymgr/main.c index b46aaa0..999b5c5 100644 --- a/src/utils/keymgr/main.c +++ b/src/utils/keymgr/main.c @@ -333,11 +333,10 @@ int main(int argc, char *argv[]) { "tsig", required_argument, NULL, 't' }, { "extended", no_argument, NULL, 'e' }, { "list", no_argument, NULL, 'l' }, - { "brief", no_argument, NULL, 'b' }, // Legacy. { "mono", no_argument, NULL, 'x' }, { "color", no_argument, NULL, 'X' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { "json", no_argument, NULL, 'j' }, { NULL } }; @@ -358,7 +357,7 @@ int main(int argc, char *argv[]) list_params.color = isatty(STDOUT_FILENO); int opt = 0, parm = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:t:ejlbxXhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:t:ejlxXhV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -394,9 +393,6 @@ int main(int argc, char *argv[]) case 'l': just_list = true; break; - case 'b': - WARN2("option '--brief' is deprecated and enabled by default"); - break; case 'x': list_params.color = false; break; @@ -407,7 +403,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); diff --git a/src/utils/keymgr/offline_ksk.c b/src/utils/keymgr/offline_ksk.c index b4260b9..05b2d2b 100644 --- a/src/utils/keymgr/offline_ksk.c +++ b/src/utils/keymgr/offline_ksk.c @@ -37,6 +37,8 @@ static int pregenerate_once(kdnssec_ctx_t *ctx, knot_time_t *next) { zone_sign_reschedule_t resch = { 0 }; + memset(ctx->stats, 0, sizeof(*ctx->stats)); + // generate ZSKs int ret = knot_dnssec_key_rollover(ctx, KEY_ROLL_ALLOW_ZSK_ROLL | KEY_ROLL_PRESERVE_FUTURE, &resch); if (ret != KNOT_EOK) { @@ -245,6 +247,9 @@ static int ksr_once(kdnssec_ctx_t *ctx, char **buf, size_t *buf_size, knot_time_ { knot_rrset_t *dnskey = NULL; zone_keyset_t keyset = { 0 }; + + memset(ctx->stats, 0, sizeof(*ctx->stats)); + int ret = load_dnskey_rrset(ctx, &dnskey, &keyset); if (ret != KNOT_EOK) { goto done; @@ -322,10 +327,10 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no zone_keyset_t keyset = { 0 }; char *buf = NULL; size_t buf_size = 4096; - knot_time_t rrsigs_expire = 0; ctx->now = now; ctx->policy->dnskey_ttl = zsk->ttl; + memset(ctx->stats, 0, sizeof(*ctx->stats)); knot_timediff_t rrsig_refresh = ctx->policy->rrsig_refresh_before; if (rrsig_refresh == UINT32_MAX) { // not setting rrsig-refresh prohibited by documentation, but we need to do something @@ -352,7 +357,7 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no // no check if the KSK used for signing (in keyset) is contained in DNSKEY record being signed (in KSR) ! for (int i = 0; i < keyset.count; i++) { - ret = key_records_sign(&keyset.keys[i], &r, ctx, &rrsigs_expire); + ret = key_records_sign(&keyset.keys[i], &r, ctx); if (ret != KNOT_EOK) { goto done; } @@ -362,7 +367,7 @@ static int ksr_sign_dnskey(kdnssec_ctx_t *ctx, knot_rrset_t *zsk, knot_time_t no print_header("SignedKeyResponse "KSR_SKR_VER, ctx->now, buf); *next_sign = knot_time_min( knot_get_next_zone_key_event(&keyset), - knot_time_add(rrsigs_expire, -rrsig_refresh) + knot_time_add(ctx->stats->expire, -rrsig_refresh) ); } @@ -446,6 +451,7 @@ static void skr_import_header(zs_scanner_t *sc) // trailing header without timestamp next_timestamp = 0; } + knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp; // delete possibly existing conflicting offline records ctx->ret = kasp_db_delete_offline_records( @@ -454,16 +460,11 @@ static void skr_import_header(zs_scanner_t *sc) // store previous SKR if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) { - ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp); + ctx->ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts); if (ctx->ret != KNOT_EOK) { return; } - if (next_timestamp > 0) { - ctx->ret = key_records_verify(&ctx->r, ctx->kctx, next_timestamp - 1); - if (ctx->ret != KNOT_EOK) { - return; - } - } + ctx->ret = kasp_db_store_offline_records(ctx->kctx->kasp_db, ctx->timestamp, &ctx->r); key_records_clear_rdatasets(&ctx->r); @@ -490,20 +491,14 @@ static void skr_validate_header(zs_scanner_t *sc) // trailing header without timestamp next_timestamp = 0; } + knot_time_t validity_ts = next_timestamp != 0 ? next_timestamp : ctx->timestamp; if (ctx->timestamp > 0 && ctx->ret == KNOT_EOK) { - int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp); + int ret = key_records_verify(&ctx->r, ctx->kctx, ctx->timestamp, validity_ts); if (ret != KNOT_EOK) { // ctx->ret untouched ERR2("invalid SignedKeyResponse for %"KNOT_TIME_PRINTF" (%s)", ctx->timestamp, knot_strerror(ret)); } - if (next_timestamp > 0) { - ret = key_records_verify(&ctx->r, ctx->kctx, next_timestamp - 1); - if (ret != KNOT_EOK) { // ctx->ret untouched - ERR2("invalid SignedKeyResponse for %"KNOT_TIME_PRINTF" (%s)", - next_timestamp - 1, knot_strerror(ret)); - } - } key_records_clear_rdatasets(&ctx->r); } diff --git a/src/utils/khost/khost_params.c b/src/utils/khost/khost_params.c index 1423e09..de95d9b 100644 --- a/src/utils/khost/khost_params.c +++ b/src/utils/khost/khost_params.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -251,14 +251,14 @@ int khost_parse(kdig_params_t *params, int argc, char *argv[]) // Long options. struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; // Command line options processing. int opt = 0; - while ((opt = getopt_long(argc, argv, "46adhrsTvVwc:t:R:W:", opts, NULL)) + while ((opt = getopt_long(argc, argv, "46adhrsTvV::wc:t:R:W:", opts, NULL)) != -1) { switch (opt) { case '4': @@ -294,7 +294,7 @@ int khost_parse(kdig_params_t *params, int argc, char *argv[]) conf->style.show_footer = true; break; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); params->stop = false; return KNOT_EOK; case 'w': diff --git a/src/utils/kjournalprint/main.c b/src/utils/kjournalprint/main.c index 3ba0019..1d633dd 100644 --- a/src/utils/kjournalprint/main.c +++ b/src/utils/kjournalprint/main.c @@ -342,11 +342,10 @@ int main(int argc, char *argv[]) { "zone-list", no_argument, NULL, 'z' }, { "check", no_argument, NULL, 'H' }, { "debug", no_argument, NULL, 'd' }, - { "no-color", no_argument, NULL, 'n' }, { "mono", no_argument, NULL, 'x' }, { "color", no_argument, NULL, 'X' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -354,7 +353,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:D:l:s:zHdnxXhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:D:l:s:zHdxXhV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -393,7 +392,6 @@ int main(int argc, char *argv[]) case 'd': params.debug = true; break; - case 'n': case 'x': params.color = false; break; @@ -404,7 +402,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); @@ -412,15 +410,6 @@ int main(int argc, char *argv[]) } } - // Backward compatibility. - if ((justlist && (argc - optind > 0)) || (!justlist && (argc - optind > 1))) { - WARN2("obsolete parameter specified"); - if (util_conf_init_justdb("journal-db", argv[optind]) != KNOT_EOK) { - goto failure; - } - optind++; - } - signal_ctx.color = params.color; if (util_conf_init_default(true) != KNOT_EOK) { diff --git a/src/utils/knotc/commands.c b/src/utils/knotc/commands.c index c2c25a2..5cc1a14 100644 --- a/src/utils/knotc/commands.c +++ b/src/utils/knotc/commands.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,6 +51,7 @@ #define CMD_ZONE_BACKUP "zone-backup" #define CMD_ZONE_RESTORE "zone-restore" #define CMD_ZONE_SIGN "zone-sign" +#define CMD_ZONE_VALIDATE "zone-validate" #define CMD_ZONE_KEYS_LOAD "zone-keys-load" #define CMD_ZONE_KEY_ROLL "zone-key-rollover" #define CMD_ZONE_KSK_SBM "zone-ksk-submitted" @@ -99,7 +100,7 @@ static int check_args(cmd_args_t *args, int min, int max) { if (max == 0 && args->argc > 0) { - log_error("command doesn't take arguments"); + log_error("command does not take arguments"); return KNOT_EINVAL; } else if (min == max && args->argc != min) { log_error("command requires %i arguments", min); @@ -272,6 +273,7 @@ static void format_data(cmd_args_t *args, knot_ctl_type_t data_type, case CTL_ZONE_BACKUP: case CTL_ZONE_RESTORE: case CTL_ZONE_SIGN: + case CTL_ZONE_VALIDATE: case CTL_ZONE_KEYS_LOAD: case CTL_ZONE_KEY_ROLL: case CTL_ZONE_KSK_SBM: @@ -411,6 +413,7 @@ static void format_block(ctl_cmd_t cmd, bool failed, bool empty) case CTL_ZONE_BACKUP: case CTL_ZONE_RESTORE: case CTL_ZONE_SIGN: + case CTL_ZONE_VALIDATE: case CTL_ZONE_KEYS_LOAD: case CTL_ZONE_KEY_ROLL: case CTL_ZONE_KSK_SBM: @@ -1294,6 +1297,7 @@ const cmd_desc_t cmd_table[] = { { CMD_ZONE_BACKUP, cmd_zone_filter_ctl, CTL_ZONE_BACKUP, CMD_FOPT_ZONE }, { CMD_ZONE_RESTORE, cmd_zone_filter_ctl, CTL_ZONE_RESTORE, CMD_FOPT_ZONE }, { CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN, CMD_FOPT_ZONE }, + { CMD_ZONE_VALIDATE, cmd_zone_ctl, CTL_ZONE_VALIDATE, CMD_FOPT_ZONE }, { CMD_ZONE_KEYS_LOAD, cmd_zone_ctl, CTL_ZONE_KEYS_LOAD, CMD_FOPT_ZONE }, { CMD_ZONE_KEY_ROLL, cmd_zone_key_roll_ctl, CTL_ZONE_KEY_ROLL, CMD_FREQ_ZONE }, { CMD_ZONE_KSK_SBM, cmd_zone_ctl, CTL_ZONE_KSK_SBM, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, @@ -1347,6 +1351,7 @@ static const cmd_help_t cmd_help_table[] = { { CMD_ZONE_BACKUP, "[<zone>...] [<filter>...] +backupdir <dir>", "Backup zone data and metadata. (#)" }, { CMD_ZONE_RESTORE, "[<zone>...] [<filter>...] +backupdir <dir>", "Restore zone data and metadata. (#)" }, { CMD_ZONE_SIGN, "[<zone>...]", "Re-sign the automatically signed zone. (#)" }, + { CMD_ZONE_VALIDATE, "[<zone>...]", "Trigger a DNSSEC validation of the zone. (#)" }, { CMD_ZONE_KEYS_LOAD, "[<zone>...]", "Re-load keys from KASP database, sign the zone. (#)" }, { CMD_ZONE_KEY_ROLL, " <zone> ksk|zsk", "Trigger immediate key rollover. (#)" }, { CMD_ZONE_KSK_SBM, " <zone>...", "When KSK submission, confirm parent's DS presence. (#)" }, diff --git a/src/utils/knotc/main.c b/src/utils/knotc/main.c index dad3671..274ab6d 100644 --- a/src/utils/knotc/main.c +++ b/src/utils/knotc/main.c @@ -82,7 +82,7 @@ int main(int argc, char **argv) { "color", no_argument, NULL, 'X' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -97,7 +97,7 @@ int main(int argc, char **argv) /* Parse command line arguments */ int opt = 0; - while ((opt = getopt_long(argc, argv, "+c:C:m:s:t:befxXvhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "+c:C:m:s:t:befxXvhV::", opts, NULL)) != -1) { switch (opt) { case 'c': params.orig_config = optarg; @@ -147,7 +147,7 @@ int main(int argc, char **argv) print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); diff --git a/src/utils/knotd/main.c b/src/utils/knotd/main.c index d4ebd53..e863296 100644 --- a/src/utils/knotd/main.c +++ b/src/utils/knotd/main.c @@ -38,15 +38,39 @@ #include "knot/conf/conf.h" #include "knot/conf/migration.h" #include "knot/conf/module.h" +#include "knot/common/dbus.h" #include "knot/common/log.h" #include "knot/common/process.h" #include "knot/common/stats.h" #include "knot/common/systemd.h" #include "knot/server/server.h" #include "knot/server/tcp-handler.h" +#include "utils/common/params.h" #define PROGRAM_NAME "knotd" +typedef enum { + CONCURRENT_EMPTY = 0, // fresh cctx without a thread. + CONCURRENT_ASSIGNED, // cctx assigned to process a command. + CONCURRENT_RUNNING, // ctl command is being processed in the thread. + CONCURRENT_IDLE, // command has been processed, waiting for a new one. + CONCURRENT_KILLED, // cctx cleanup has started. + CONCURRENT_FINISHED, // after having been killed, the thread is being joined. +} concurrent_ctl_state_t; + +typedef struct { + concurrent_ctl_state_t state; + pthread_mutex_t mutex; // Protects .state. + pthread_cond_t cond; + knot_ctl_t *ctl; + server_t *server; + pthread_t thread; + sigset_t sigmask; + int ret; + int thread_idx; + bool exclusive; +} concurrent_ctl_ctx_t; + /* Signal flags. */ static volatile bool sig_req_stop = false; static volatile bool sig_req_reload = false; @@ -161,13 +185,14 @@ static void setup_signals(void) sigdelset(&all, SIGBUS); sigdelset(&all, SIGFPE); sigdelset(&all, SIGSEGV); - pthread_sigmask(SIG_SETMASK, &all, NULL); /* Setup handlers. */ struct sigaction action = { .sa_handler = handle_signal }; for (const struct signal *s = SIGNALS; s->signum > 0; s++) { sigaction(s->signum, &action, NULL); } + + pthread_sigmask(SIG_SETMASK, &all, NULL); } /*! \brief Unblock server control signals. */ @@ -185,6 +210,24 @@ static void enable_signals(void) pthread_sigmask(SIG_UNBLOCK, &mask, NULL); } +/*! \brief Create a control thread with correct signals setting. */ +static void create_thread_sigmask(pthread_t *thr, void *(*fcn)(void*), void *ctx, + sigset_t *out_mask) +{ + /* Block all blockable signals. */ + sigset_t mask; + sigfillset(&mask); + sigdelset(&mask, SIGBUS); + sigdelset(&mask, SIGFPE); + sigdelset(&mask, SIGILL); + sigdelset(&mask, SIGSEGV); + pthread_sigmask(SIG_SETMASK, &mask, out_mask); + + pthread_create(thr, NULL, fcn, ctx); + + pthread_sigmask(SIG_SETMASK, out_mask, NULL); +} + /*! \brief Drop POSIX 1003.1e capabilities. */ static void drop_capabilities(void) { @@ -224,6 +267,7 @@ static void check_loaded(server_t *server) return; } + rcu_read_lock(); knot_zonedb_iter_t *it = knot_zonedb_iter_begin(server->zone_db); while (!knot_zonedb_iter_finished(it)) { zone_t *zone = (zone_t *)knot_zonedb_iter_val(it); @@ -234,9 +278,162 @@ static void check_loaded(server_t *server) knot_zonedb_iter_next(it); } knot_zonedb_iter_free(it); + rcu_read_unlock(); finished = true; - systemd_emit_running(true); + dbus_emit_running(true); +} + +static void *ctl_process_thread(void *arg); + +/*! + * Try to find an empty ctl processing context and if successful, + * prepare to lauch the incomming command processing in it. + * + * \param[in] concurrent_ctxs Configured concurrent control contexts. + * \param[in] n_ctxs Number of configured concurrent control contexts. + * \param[in] ctl Control context. + * + * \return Assigned concurrent control context, or NULL. + */ + +static concurrent_ctl_ctx_t *find_free_ctx(concurrent_ctl_ctx_t *concurrent_ctxs, + size_t n_ctxs, knot_ctl_t *ctl) +{ + concurrent_ctl_ctx_t *res = NULL; + for (size_t i = 0; i < n_ctxs && res == NULL; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->exclusive) { + while (cctx->state != CONCURRENT_IDLE) { + pthread_cond_wait(&cctx->cond, &cctx->mutex); + } + knot_ctl_free(cctx->ctl); + cctx->ctl = knot_ctl_clone(ctl); + if (cctx->ctl == NULL) { + cctx->exclusive = false; + pthread_mutex_unlock(&cctx->mutex); + break; + } + cctx->state = CONCURRENT_ASSIGNED; + res = cctx; + pthread_cond_broadcast(&cctx->cond); + } + pthread_mutex_unlock(&cctx->mutex); + } + for (size_t i = 0; i < n_ctxs && res == NULL; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + switch (cctx->state) { + case CONCURRENT_EMPTY: + create_thread_sigmask(&cctx->thread, ctl_process_thread, cctx, &cctx->sigmask); + break; + case CONCURRENT_IDLE: + knot_ctl_free(cctx->ctl); + pthread_cond_broadcast(&cctx->cond); + break; + default: + pthread_mutex_unlock(&cctx->mutex); + continue; + } + cctx->ctl = knot_ctl_clone(ctl); + if (cctx->ctl != NULL) { + cctx->state = CONCURRENT_ASSIGNED; + res = cctx; + } + pthread_mutex_unlock(&cctx->mutex); + } + return res; +} + +static void init_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs, server_t *server) +{ + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_init(&cctx->mutex, NULL); + pthread_cond_init(&cctx->cond, NULL); + cctx->server = server; + cctx->thread_idx = i + 1; + } +} + +static int cleanup_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs) +{ + int ret = KNOT_EOK; + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->state == CONCURRENT_IDLE) { + knot_ctl_free(cctx->ctl); + cctx->ctl = NULL; + if (cctx->ret == KNOT_CTL_ESTOP) { + ret = cctx->ret; + } + } + pthread_mutex_unlock(&cctx->mutex); + } + return ret; +} + +static void finalize_ctxs(concurrent_ctl_ctx_t *concurrent_ctxs, size_t n_ctxs) +{ + for (size_t i = 0; i < n_ctxs; i++) { + concurrent_ctl_ctx_t *cctx = &concurrent_ctxs[i]; + pthread_mutex_lock(&cctx->mutex); + if (cctx->state == CONCURRENT_EMPTY) { + pthread_mutex_unlock(&cctx->mutex); + pthread_mutex_destroy(&cctx->mutex); + pthread_cond_destroy(&cctx->cond); + continue; + } + + cctx->state = CONCURRENT_KILLED; + pthread_cond_broadcast(&cctx->cond); + pthread_mutex_unlock(&cctx->mutex); + (void)pthread_join(cctx->thread, NULL); + + assert(cctx->state == CONCURRENT_FINISHED); + knot_ctl_free(cctx->ctl); + pthread_mutex_destroy(&cctx->mutex); + pthread_cond_destroy(&cctx->cond); + } +} + +static void *ctl_process_thread(void *arg) +{ + concurrent_ctl_ctx_t *ctx = arg; + rcu_register_thread(); + setup_signals(); // in fact, this blocks common signals so that they + // arrive to main thread instead of this one + + pthread_mutex_lock(&ctx->mutex); + while (ctx->state != CONCURRENT_KILLED) { + if (ctx->state != CONCURRENT_ASSIGNED) { + pthread_cond_wait(&ctx->cond, &ctx->mutex); + continue; + } + ctx->state = CONCURRENT_RUNNING; + bool exclusive = ctx->exclusive; + pthread_mutex_unlock(&ctx->mutex); + + // Not IDLE, ctx can be read without locking. + int ret = ctl_process(ctx->ctl, ctx->server, ctx->thread_idx, &exclusive); + + pthread_mutex_lock(&ctx->mutex); + ctx->ret = ret; + ctx->exclusive = exclusive; + if (ctx->state == CONCURRENT_RUNNING) { // not KILLED + ctx->state = CONCURRENT_IDLE; + pthread_cond_broadcast(&ctx->cond); + } + } + + knot_ctl_close(ctx->ctl); + + ctx->state = CONCURRENT_FINISHED; + pthread_mutex_unlock(&ctx->mutex); + rcu_unregister_thread(); + return NULL; } /*! \brief Event loop listening for signals and remote commands. */ @@ -274,7 +471,7 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, /* Bind the control socket. */ uint16_t backlog = conf_get_int(conf(), C_CTL, C_BACKLOG); - int ret = knot_ctl_bind2(ctl, listen, backlog); + int ret = knot_ctl_bind(ctl, listen, backlog); if (ret != KNOT_EOK) { knot_ctl_free(ctl); log_fatal("control, failed to bind socket '%s' (%s)", @@ -286,6 +483,10 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, enable_signals(); + concurrent_ctl_ctx_t concurrent_ctxs[CTL_MAX_CONCURRENT] = { 0 }; + init_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT, server); + bool main_thread_exclusive = false; + /* Notify systemd about successful start. */ systemd_ready_notify(); if (daemonize) { @@ -299,15 +500,19 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, /* Interrupts. */ if (sig_req_reload && !sig_req_stop) { sig_req_reload = false; + pthread_rwlock_wrlock(&server->ctl_lock); server_reload(server, RELOAD_FULL); + pthread_rwlock_unlock(&server->ctl_lock); } if (sig_req_zones_reload && !sig_req_stop) { sig_req_zones_reload = false; reload_t mode = server->catalog_upd_signal ? RELOAD_CATALOG : RELOAD_ZONES; + pthread_rwlock_wrlock(&server->ctl_lock); server->catalog_upd_signal = false; server_update_zones(conf(), server, mode); + pthread_rwlock_unlock(&server->ctl_lock); } - if (sig_req_stop) { + if (sig_req_stop || cleanup_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT) == KNOT_CTL_ESTOP) { break; } @@ -325,15 +530,20 @@ static void event_loop(server_t *server, const char *socket, bool daemonize, continue; } - ret = ctl_process(ctl, server); - knot_ctl_close(ctl); - if (ret == KNOT_CTL_ESTOP) { - break; + if (main_thread_exclusive || + find_free_ctx(concurrent_ctxs, CTL_MAX_CONCURRENT, ctl) == NULL) { + ret = ctl_process(ctl, server, 0, &main_thread_exclusive); + knot_ctl_close(ctl); + if (ret == KNOT_CTL_ESTOP) { + break; + } } } + finalize_ctxs(concurrent_ctxs, CTL_MAX_CONCURRENT); + if (conf()->cache.srv_dbus_event & DBUS_EVENT_RUNNING) { - systemd_emit_running(false); + dbus_emit_running(false); } /* Unbind the control socket. */ @@ -363,11 +573,6 @@ static void print_help(void) CONF_MAPSIZE, RUN_DIR "/knot.sock"); } -static void print_version(void) -{ - printf("%s (Knot DNS), version %s\n", PROGRAM_NAME, PACKAGE_VERSION); -} - static int set_config(const char *confdb, const char *config, size_t max_conf_size) { if (config != NULL && confdb != NULL) { @@ -440,7 +645,7 @@ int main(int argc, char **argv) { "daemonize", optional_argument, NULL, 'd' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -449,7 +654,7 @@ int main(int argc, char **argv) /* Parse command line arguments. */ int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:m:s:dvhV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:m:s:dvhV::", opts, NULL)) != -1) { switch (opt) { case 'c': config = optarg; @@ -481,7 +686,7 @@ int main(int argc, char **argv) print_help(); return EXIT_SUCCESS; case 'V': - print_version(); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); @@ -570,14 +775,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (conf()->cache.srv_dbus_event != DBUS_EVENT_NONE) { - ret = systemd_dbus_open(); - if (ret != KNOT_EOK) { - log_error("d-bus: failed to open system bus (%s)", - knot_strerror(ret)); - } else { - log_info("d-bus: connected to system bus"); - } + /* Connect to the system D-bus. */ + if (conf()->cache.srv_dbus_event != DBUS_EVENT_NONE && + dbus_open() == KNOT_EOK) { int64_t delay = conf_get_int(conf(), C_SRV, C_DBUS_INIT_DELAY); sleep(delay); } @@ -595,7 +795,7 @@ int main(int argc, char **argv) server_wait(&server); server_deinit(&server); conf_free(conf()); - systemd_dbus_close(); + dbus_close(); log_close(); dnssec_crypto_cleanup(); return EXIT_FAILURE; @@ -636,7 +836,7 @@ int main(int argc, char **argv) rcu_unregister_thread(); pid_cleanup(); conf_free(conf()); - systemd_dbus_close(); + dbus_close(); log_close(); dnssec_crypto_cleanup(); return EXIT_FAILURE; @@ -660,7 +860,7 @@ int main(int argc, char **argv) /* Unhook from RCU. */ rcu_unregister_thread(); - systemd_dbus_close(); + dbus_close(); log_info("shutting down"); log_close(); diff --git a/src/utils/knsec3hash/knsec3hash.c b/src/utils/knsec3hash/knsec3hash.c index a7bac97..a18498d 100644 --- a/src/utils/knsec3hash/knsec3hash.c +++ b/src/utils/knsec3hash/knsec3hash.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,7 +36,7 @@ */ static void print_help(void) { - printf("Usage: " PROGRAM_NAME " <salt> <algorithm> <iterations> <domain-name>\n"); + printf("Usage: " PROGRAM_NAME " [-h | -V] <salt> <algorithm> <iterations> <domain-name>\n"); printf("Example: " PROGRAM_NAME " c01dcafe 1 10 knot-dns.cz\n"); printf("Alternative usage: "PROGRAM_NAME " <algorithm> <flags> <iterations> <salt> <domain-name>\n"); printf("Example: " PROGRAM_NAME " 1 0 10 c01dcafe knot-dns.cz\n"); @@ -103,19 +103,19 @@ static bool parse_nsec3_params(dnssec_nsec3_params_t *params, const char *salt_s int main(int argc, char *argv[]) { struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; int opt = 0; - while ((opt = getopt_long(argc, argv, "hV", options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "hV::", options, NULL)) != -1) { switch(opt) { case 'h': print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; default: print_help(); diff --git a/src/utils/knsupdate/knsupdate_exec.c b/src/utils/knsupdate/knsupdate_exec.c index e201711..d716ffb 100644 --- a/src/utils/knsupdate/knsupdate_exec.c +++ b/src/utils/knsupdate/knsupdate_exec.c @@ -452,15 +452,23 @@ static int pkt_sendrecv(knsupdate_params_t *params) return -1; } + ret = net_init_crypto(&net, ¶ms->tls_params, NULL, ¶ms->quic_params); + if (ret != 0) { + ERR("failed to initialize crypto context (%s)", knot_strerror(ret)); + net_clean(&net); + return -1; + } + ret = net_connect(&net); - DBG("%s: send_msg = %d", __func__, net.sockfd); if (ret != KNOT_EOK) { + ERR("failed to connect (%s)", knot_strerror(ret)); net_clean(&net); return -1; } ret = net_send(&net, params->query->wire, params->query->size); if (ret != KNOT_EOK) { + ERR("failed to send update (%s)", knot_strerror(ret)); net_close(&net); net_clean(&net); return -1; @@ -471,8 +479,8 @@ static int pkt_sendrecv(knsupdate_params_t *params) /* Wait for reception. */ int rb = net_receive(&net, params->answer->wire, params->answer->max_size); - DBG("%s: receive_msg = %d", __func__, rb); if (rb <= 0) { + ERR("failed to receive response (%s)", knot_strerror(rb)); net_close(&net); net_clean(&net); return -1; diff --git a/src/utils/knsupdate/knsupdate_params.c b/src/utils/knsupdate/knsupdate_params.c index f9fa41f..5aaf808 100644 --- a/src/utils/knsupdate/knsupdate_params.c +++ b/src/utils/knsupdate/knsupdate_params.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ #include "utils/common/netio.h" #include "libknot/libknot.h" #include "libknot/tsig.h" +#include "contrib/base64.h" #include "contrib/mempattern.h" #include "contrib/strtonum.h" #include "contrib/ucw/mempool.h" @@ -90,6 +91,8 @@ static int knsupdate_init(knsupdate_params_t *params) init_list(¶ms->update_list); init_list(¶ms->prereq_list); + tls_params_init(¶ms->tls_params); + /* Initialize memory context. */ mm_ctx_mempool(¶ms->mm, MM_DEFAULT_BLKSIZE); @@ -142,6 +145,9 @@ void knsupdate_clean(knsupdate_params_t *params) knot_pkt_free(params->answer); knot_tsig_key_deinit(¶ms->tsig_key); + tls_params_clean(¶ms->tls_params); + quic_params_clean(¶ms->quic_params); + /* Clean up the structure. */ mp_delete(params->mm.ctx); memset(params, 0, sizeof(*params)); @@ -172,9 +178,31 @@ void knsupdate_reset(knsupdate_params_t *params) static void print_help(void) { - printf("Usage: %s [-d] [-v] [-k keyfile | -y [hmac:]name:key]\n" - " [-p port] [-t timeout] [-r retries] [filename]\n", - PROGRAM_NAME); + printf("Usage:\n" + " %s [-T] [options] [filename]\n" + " %s [-S | -Q] [tls_options] [options] [filename]\n" + "\n" + "Options:\n" + " -T, --tcp Use TCP protocol.\n" + " -S, --tls Use TLS protocol.\n" + " -Q, --quic Use QUIC protocol.\n" + " -p, --port <num> Remote port.\n" + " -r, --retry <num> Number of retries over UDP.\n" + " -t, --timeout <num> Update timeout.\n" + " -y, --tsig <str> TSIG key in the form [alg:]name:key.\n" + " -k, --tsigfile <path> Path to a TSIG key file.\n" + " -d, --debug Debug mode output.\n" + " -h, --help Print the program help.\n" + " -V, --version Print the program version.\n" + "\n" + "QUIC/TLS options:\n" + " -H, --hostname <str> Remote hostname validation.\n" + " -P, --pin <base64> Certificate key PIN.\n" + " -A, --ca [<path>] Path to a CA file.\n" + " -E, --certfile <path> Path to a client certificate file.\n" + " -K, --keyfile <path> Path to a client key file.\n" + " -s, --sni <str> Remote SNI.\n", + PROGRAM_NAME, PROGRAM_NAME); } int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) @@ -188,40 +216,74 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) return ret; } - // Long options. + const char *opts_str = "dhvTSQV::p:r:t:y:k:H:P:A::E:K:s:"; struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "tcp", no_argument, NULL, 'T' }, + { "tls", no_argument, NULL, 'S' }, + { "quic", no_argument, NULL, 'Q' }, + { "version", optional_argument, NULL, 'V' }, + { "port", required_argument, NULL, 'p' }, + { "retry", required_argument, NULL, 'r' }, + { "timeout", required_argument, NULL, 't' }, + { "tsig", required_argument, NULL, 'y' }, + { "tsigfile", required_argument, NULL, 'k' }, + { "hostname", required_argument, NULL, 'H' }, + { "pin", required_argument, NULL, 'P' }, + { "ca", optional_argument, NULL, 'A' }, + { "certfile", required_argument, NULL, 'E' }, + { "keyfile", required_argument, NULL, 'K' }, + { "sni", required_argument, NULL, 's' }, { NULL } }; - /* Command line options processing. */ + bool default_port = true; + int opt = 0; - while ((opt = getopt_long(argc, argv, "dhDvVp:t:r:y:k:", opts, NULL)) - != -1) { + while ((opt = getopt_long(argc, argv, opts_str, opts, NULL)) != -1) { switch (opt) { case 'd': - case 'D': /* Extra debugging. */ msg_enable_debug(1); break; case 'h': print_help(); params->stop = true; return KNOT_EOK; - case 'v': + case 'v': // Compatibility with nsupdate. + case 'T': params->protocol = PROTO_TCP; break; + case 'S': + params->protocol = PROTO_TCP; + + params->tls_params.enable = true; + + if (default_port) { + free(params->server->service); + params->server->service = strdup(DEFAULT_DNS_TLS_PORT); + } + break; + case 'Q': + params->protocol = PROTO_UDP; + + params->tls_params.enable = true; + params->quic_params.enable = true; + + if (default_port) { + free(params->server->service); + params->server->service = strdup(DEFAULT_DNS_QUIC_PORT); + } + break; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); params->stop = true; return KNOT_EOK; case 'p': + assert(optarg); + default_port = false; free(params->server->service); params->server->service = strdup(optarg); - if (!params->server->service) { - ERR("failed to set default port '%s'", optarg); - return KNOT_ENOMEM; - } break; case 'r': ret = str_to_u32(optarg, ¶ms->retries); @@ -241,7 +303,7 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) knot_tsig_key_deinit(¶ms->tsig_key); ret = knot_tsig_key_init_str(¶ms->tsig_key, optarg); if (ret != KNOT_EOK) { - ERR("failed to parse key '%s'", optarg); + ERR("failed to parse TSIG key '%s'", optarg); return ret; } break; @@ -249,9 +311,63 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) knot_tsig_key_deinit(¶ms->tsig_key); ret = knot_tsig_key_init_file(¶ms->tsig_key, optarg); if (ret != KNOT_EOK) { - ERR("failed to parse keyfile '%s'", optarg); + ERR("failed to parse TSIG keyfile '%s'", optarg); + return ret; + } + break; + case 'H': + assert(optarg); + free(params->tls_params.hostname); + params->tls_params.hostname = strdup(optarg); + break; + case 'P': + assert(optarg); + uint8_t pin[64] = { 0 }; + ret = knot_base64_decode((const uint8_t *)optarg, strlen(optarg), pin, sizeof(pin)); + if (ret < 0) { + ERR("invalid certificate pin %s", optarg); return ret; + } else if (ret != CERT_PIN_LEN) { // Check for 256-bit value. + ERR("invalid SHA256 hash length of certificate pin %s", optarg); + return KNOT_EINVAL; } + + uint8_t *item = malloc(1 + ret); // 1 ~ leading data length. + if (item == NULL) { + return KNOT_ENOMEM; + } + item[0] = ret; + memcpy(&item[1], pin, ret); + + if (ptrlist_add(¶ms->tls_params.pins, item, NULL) == NULL) { + return KNOT_ENOMEM; + } + + break; + case 'A': + if (optarg == NULL) { + params->tls_params.system_ca = true; + break; + } + if (ptrlist_add(¶ms->tls_params.ca_files, strdup(optarg), NULL) == NULL) { + ERR("failed to set CA file '%s'", optarg); + return KNOT_ENOMEM; + } + break; + case 'E': + assert(optarg); + free(params->tls_params.certfile); + params->tls_params.certfile = strdup(optarg); + break; + case 'K': + assert(optarg); + free(params->tls_params.keyfile); + params->tls_params.keyfile = strdup(optarg); + break; + case 's': + assert(optarg); + free(params->tls_params.sni); + params->tls_params.sni = strdup(optarg); break; default: print_help(); @@ -259,8 +375,8 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) } } - /* No retries for TCP. */ - if (params->protocol == PROTO_TCP) { + /* Retries only for UDP. */ + if (params->protocol == PROTO_TCP || params->quic_params.enable) { params->retries = 0; } else { /* If wait/tries < 1 s, set 1 second for each try. */ @@ -277,7 +393,7 @@ int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]) ptrlist_add(¶ms->qfiles, argv[optind], ¶ms->mm); } - return ret; + return KNOT_EOK; } int knsupdate_set_ttl(knsupdate_params_t *params, const uint32_t ttl) diff --git a/src/utils/knsupdate/knsupdate_params.h b/src/utils/knsupdate/knsupdate_params.h index 1933244..fbf30e5 100644 --- a/src/utils/knsupdate/knsupdate_params.h +++ b/src/utils/knsupdate/knsupdate_params.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,9 @@ #include "utils/common/netio.h" #include "utils/common/params.h" +#include "utils/common/quic.h" #include "utils/common/sign.h" +#include "utils/common/tls.h" #include "libknot/libknot.h" #include "libzscanner/scanner.h" #include "contrib/ucw/lists.h" @@ -63,6 +65,10 @@ typedef struct { style_t style; /*!< Memory context. */ knot_mm_t mm; + /*!< TLS params. */ + tls_params_t tls_params; + /*!< QUIC params. */ + quic_params_t quic_params; } knsupdate_params_t; int knsupdate_parse(knsupdate_params_t *params, int argc, char *argv[]); diff --git a/src/utils/kxdpgun/load_queries.c b/src/utils/kxdpgun/load_queries.c index 8ecac48..fe7c9ae 100644 --- a/src/utils/kxdpgun/load_queries.c +++ b/src/utils/kxdpgun/load_queries.c @@ -16,9 +16,11 @@ #include <assert.h> #include <errno.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <arpa/inet.h> #include "load_queries.h" #include "libknot/libknot.h" @@ -44,106 +46,181 @@ void free_global_payloads(void) global_payloads = NULL; } -bool load_queries(const char *filename, uint16_t edns_size, uint16_t msgid, size_t maxcount) +typedef struct { + char line[KNOT_DNAME_TXT_MAXLEN + 256]; + char dname_txt[KNOT_DNAME_TXT_MAXLEN + 1]; + uint8_t dname[KNOT_DNAME_MAXLEN]; + char type_txt[128]; + char flags_txt[128]; +} txt_bufs_t; + +typedef struct { + char line[USHRT_MAX]; +} bin_bufs_t; + +static int read_txt(struct pkt_payload **g_payloads_top_p, FILE *f, txt_bufs_t *bufs, + uint16_t edns_size, uint16_t msgid) +{ + assert(g_payloads_top_p != NULL); + struct pkt_payload *g_payloads_top = *g_payloads_top_p; + if (fgets(bufs->line, sizeof(bufs->line), f) == NULL) { + return 0; + } + bufs->flags_txt[0] = '\0'; + int ret = sscanf(bufs->line, "%s%s%s", bufs->dname_txt, bufs->type_txt, + bufs->flags_txt); + if (ret < 2) { + ERR2(ERR_PREFIX "(faulty line): '%.*s'", + (int)strcspn(bufs->line, "\n"), bufs->line); + return KNOT_EINVAL; + } + + void *pret = knot_dname_from_str(bufs->dname, bufs->dname_txt, sizeof(bufs->dname)); + if (pret == NULL) { + ERR2(ERR_PREFIX "(faulty dname): '%s'", bufs->dname_txt); + return KNOT_EINVAL; + } + + uint16_t type; + ret = knot_rrtype_from_string(bufs->type_txt, &type); + if (ret < 0) { + ERR2(ERR_PREFIX "(faulty type): '%s'", bufs->type_txt); + return KNOT_EINVAL; + } + + enum qflags flags = 0; + switch (bufs->flags_txt[0]) { + case '\0': + break; + case 'e': + case 'E': + flags |= QFLAG_EDNS; + break; + case 'd': + case 'D': + flags |= QFLAG_EDNS | QFLAG_DO; + break; + default: + ERR2(ERR_PREFIX "(faulty flag): '%s'", bufs->flags_txt); + return KNOT_EINVAL; + } + + size_t dname_len = knot_dname_size(bufs->dname); + size_t pkt_len = KNOT_WIRE_HEADER_SIZE + 2 * sizeof(uint16_t) + dname_len; + if (flags & QFLAG_EDNS) { + pkt_len += KNOT_EDNS_MIN_SIZE; + } + + struct pkt_payload *pkt = calloc(1, sizeof(*pkt) + pkt_len); + if (pkt == NULL) { + ERR2(ERR_PREFIX "(out of memory)"); + return KNOT_ENOMEM; + } + pkt->len = pkt_len; + memcpy(pkt->payload, &msgid, sizeof(msgid)); + pkt->payload[2] = 0x01; // RD bit + pkt->payload[5] = 0x01; // 1 question + pkt->payload[11] = (flags & QFLAG_EDNS) ? 0x01 : 0x00; + memcpy(pkt->payload + 12, bufs->dname, dname_len); + pkt->payload[dname_len + 12] = type >> 8; + pkt->payload[dname_len + 13] = type & 0xff; + pkt->payload[dname_len + 15] = KNOT_CLASS_IN; + if (flags & QFLAG_EDNS) { + pkt->payload[dname_len + 18] = KNOT_RRTYPE_OPT; + pkt->payload[dname_len + 19] = edns_size >> 8; + pkt->payload[dname_len + 20] = edns_size & 0xff; + pkt->payload[dname_len + 23] = (flags & QFLAG_DO) ? 0x80 : 0x00; + } + + // add pkt to list global_payloads + if (g_payloads_top == NULL) { + global_payloads = pkt; + *g_payloads_top_p = pkt; + } else { + g_payloads_top->next = pkt; + *g_payloads_top_p = pkt; + } + return pkt_len; +} + +static int read_bin(struct pkt_payload **g_payloads_top_p, FILE *f, bin_bufs_t *bufs, + uint16_t msgid) +{ + assert(g_payloads_top_p != NULL); + struct pkt_payload *g_payloads_top = *g_payloads_top_p; + uint16_t size; + if (fread(&size, sizeof(size), 1, f) < 1) { + return 0; + } + size = ntohs(size); + if (fread(bufs->line, size, 1, f) < 1) { + return KNOT_EINVAL; + } + struct pkt_payload *pkt = calloc(1, sizeof(*pkt) + size); + if (pkt == NULL) { + ERR2(ERR_PREFIX "(out of memory)"); + return KNOT_ENOMEM; + } + pkt->len = size; + memcpy(pkt->payload, &msgid, sizeof(msgid)); // Override msgID + memcpy(pkt->payload + 2, bufs->line + 2, size - 2); + + // add pkt to list global_payloads + if (g_payloads_top == NULL) { + global_payloads = pkt; + *g_payloads_top_p = pkt; + } else { + g_payloads_top->next = pkt; + *g_payloads_top_p = pkt; + } + return size; +} + +bool load_queries(const input_t *input, uint16_t edns_size, uint16_t msgid, size_t maxcount) { size_t read = 0; - FILE *f = fopen(filename, "r"); + FILE *f = fopen(input->path, (input->format == BIN) ? "rb" : "r"); if (f == NULL) { - ERR2(ERR_PREFIX "file '%s' (%s)", filename, strerror(errno)); + ERR2(ERR_PREFIX "file '%s' (%s)", input->path, strerror(errno)); return false; } - struct pkt_payload *g_payloads_top = NULL; - struct { - char line[KNOT_DNAME_TXT_MAXLEN + 256]; - char dname_txt[KNOT_DNAME_TXT_MAXLEN + 1]; - uint8_t dname[KNOT_DNAME_MAXLEN]; - char type_txt[128]; - char flags_txt[128]; - } *bufs; - bufs = malloc(sizeof(*bufs)); // avoiding too much stuff on stack + void *bufs = NULL; + switch (input->format) { + case TXT: + bufs = malloc(sizeof(txt_bufs_t)); // avoiding too much stuff on stack + break; + case BIN: + bufs = malloc(sizeof(bin_bufs_t)); + break; + default: + assert(0); + goto fail; + } if (bufs == NULL) { ERR2(ERR_PREFIX "(out of memory)"); goto fail; } + struct pkt_payload *g_payloads_top = NULL; while (read < maxcount) { - if (fgets(bufs->line, sizeof(bufs->line), f) == NULL) { + int ret = 0; + switch (input->format) { + case TXT: + ret = read_txt(&g_payloads_top, f, bufs, edns_size, msgid); break; - } - bufs->flags_txt[0] = '\0'; - int ret = sscanf(bufs->line, "%s%s%s", bufs->dname_txt, bufs->type_txt, bufs->flags_txt); - if (ret < 2) { - ERR2(ERR_PREFIX "(faulty line): '%.*s'", - (int)strcspn(bufs->line, "\n"), bufs->line); - goto fail; - } - - void *pret = knot_dname_from_str(bufs->dname, bufs->dname_txt, sizeof(bufs->dname)); - if (pret == NULL) { - ERR2(ERR_PREFIX "(faulty dname): '%s'", bufs->dname_txt); - goto fail; - } - - uint16_t type; - ret = knot_rrtype_from_string(bufs->type_txt, &type); - if (ret < 0) { - ERR2(ERR_PREFIX "(faulty type): '%s'", bufs->type_txt); - goto fail; - } - - enum qflags flags = 0; - switch (bufs->flags_txt[0]) { - case '\0': - break; - case 'e': - case 'E': - flags |= QFLAG_EDNS; - break; - case 'd': - case 'D': - flags |= QFLAG_EDNS | QFLAG_DO; + case BIN: + ret = read_bin(&g_payloads_top, f, bufs, msgid); break; default: - ERR2(ERR_PREFIX "(faulty flag): '%s'", bufs->flags_txt); - goto fail; - } - - size_t dname_len = knot_dname_size(bufs->dname); - size_t pkt_len = KNOT_WIRE_HEADER_SIZE + 2 * sizeof(uint16_t) + dname_len; - if (flags & QFLAG_EDNS) { - pkt_len += KNOT_EDNS_MIN_SIZE; + ret = -1; + break; } - - struct pkt_payload *pkt = calloc(1, sizeof(struct pkt_payload) + pkt_len); - if (pkt == NULL) { - ERR2(ERR_PREFIX "(out of memory)"); + if (ret == 0) { + break; + } else if (ret < 0) { goto fail; } - pkt->len = pkt_len; - memcpy(pkt->payload, &msgid, sizeof(msgid)); - pkt->payload[2] = 0x01; // RD bit - pkt->payload[5] = 0x01; // 1 question - pkt->payload[11] = (flags & QFLAG_EDNS) ? 0x01 : 0x00; - memcpy(pkt->payload + 12, bufs->dname, dname_len); - pkt->payload[dname_len + 12] = type >> 8; - pkt->payload[dname_len + 13] = type & 0xff; - pkt->payload[dname_len + 15] = KNOT_CLASS_IN; - if (flags & QFLAG_EDNS) { - pkt->payload[dname_len + 18] = KNOT_RRTYPE_OPT; - pkt->payload[dname_len + 19] = edns_size >> 8; - pkt->payload[dname_len + 20] = edns_size & 0xff; - pkt->payload[dname_len + 23] = (flags & QFLAG_DO) ? 0x80 : 0x00; - } - - // add pkt to list global_payloads - if (g_payloads_top == NULL) { - global_payloads = pkt; - g_payloads_top = pkt; - } else { - g_payloads_top->next = pkt; - g_payloads_top = pkt; - } read++; } diff --git a/src/utils/kxdpgun/load_queries.h b/src/utils/kxdpgun/load_queries.h index 3d7bace..09dfee9 100644 --- a/src/utils/kxdpgun/load_queries.h +++ b/src/utils/kxdpgun/load_queries.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,16 @@ #include <stdbool.h> #include <stdint.h> +enum input_format { + TXT = 0, + BIN +}; + +typedef struct { + const char *path; + enum input_format format; +} input_t; + struct pkt_payload { struct pkt_payload *next; size_t len; @@ -27,6 +37,6 @@ struct pkt_payload { extern struct pkt_payload *global_payloads; -bool load_queries(const char *filename, uint16_t edns_size, uint16_t msgid, size_t maxcount); +bool load_queries(const input_t *input, uint16_t edns_size, uint16_t msgid, size_t maxcount); void free_global_payloads(void); diff --git a/src/utils/kxdpgun/main.c b/src/utils/kxdpgun/main.c index 523f64f..4e3aa31 100644 --- a/src/utils/kxdpgun/main.c +++ b/src/utils/kxdpgun/main.c @@ -14,12 +14,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#include <arpa/inet.h> #include <assert.h> #include <errno.h> #include <getopt.h> #include <ifaddrs.h> #include <inttypes.h> #include <net/if.h> +#include <netdb.h> +#include <netinet/in.h> #include <poll.h> #include <pthread.h> #include <signal.h> @@ -28,16 +31,11 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <time.h> -#include <unistd.h> -#include <netdb.h> - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <net/if.h> #include <sys/ioctl.h> -#include <sys/socket.h> #include <sys/resource.h> +#include <sys/socket.h> +#include <time.h> +#include <unistd.h> #include "libknot/libknot.h" #include "libknot/xdp.h" @@ -46,101 +44,28 @@ #include <gnutls/gnutls.h> #include "libknot/quic/quic.h" #endif // ENABLE_QUIC -#include "contrib/macros.h" -#include "contrib/mempattern.h" -#include "contrib/openbsd/strlcat.h" +#include "contrib/atomic.h" #include "contrib/openbsd/strlcpy.h" #include "contrib/os.h" #include "contrib/sockaddr.h" #include "contrib/toeplitz.h" -#include "contrib/ucw/mempool.h" #include "utils/common/msg.h" #include "utils/common/params.h" #include "utils/kxdpgun/ip_route.h" #include "utils/kxdpgun/load_queries.h" - -#define PROGRAM_NAME "kxdpgun" -#define SPACE " " - -enum { - KXDPGUN_WAIT, - KXDPGUN_START, - KXDPGUN_STOP, -}; +#include "utils/kxdpgun/main.h" +#include "utils/kxdpgun/stats.h" volatile int xdp_trigger = KXDPGUN_WAIT; -volatile unsigned stats_trigger = 0; +volatile knot_atomic_uint64_t stats_trigger = 0; +volatile knot_atomic_bool stats_switch = STATS_SUM; unsigned global_cpu_aff_start = 0; unsigned global_cpu_aff_step = 1; -#define REMOTE_PORT_DEFAULT 53 -#define REMOTE_PORT_DOQ_DEFAULT 853 -#define LOCAL_PORT_MIN 2000 -#define LOCAL_PORT_MAX 65535 -#define QUIC_THREAD_PORTS 100 - -#define RCODE_MAX (0x0F + 1) - -typedef struct { - size_t collected; - uint64_t duration; - uint64_t qry_sent; - uint64_t synack_recv; - uint64_t ans_recv; - uint64_t finack_recv; - uint64_t rst_recv; - uint64_t size_recv; - uint64_t wire_recv; - uint64_t rcodes_recv[RCODE_MAX]; - pthread_mutex_t mutex; -} kxdpgun_stats_t; - static kxdpgun_stats_t global_stats = { 0 }; -typedef enum { - KXDPGUN_IGNORE_NONE = 0, - KXDPGUN_IGNORE_QUERY = (1 << 0), - KXDPGUN_IGNORE_LASTBYTE = (1 << 1), - KXDPGUN_IGNORE_CLOSE = (1 << 2), - KXDPGUN_REUSE_CONN = (1 << 3), -} xdp_gun_ignore_t; - -typedef struct { - union { - struct sockaddr_in local_ip4; - struct sockaddr_in6 local_ip; - struct sockaddr_storage local_ip_ss; - }; - union { - struct sockaddr_in target_ip4; - struct sockaddr_in6 target_ip; - struct sockaddr_storage target_ip_ss; - }; - char dev[IFNAMSIZ]; - uint64_t qps, duration; - unsigned at_once; - uint16_t msgid; - uint16_t edns_size; - uint16_t vlan_tci; - uint8_t local_mac[6], target_mac[6]; - uint8_t local_ip_range; - bool ipv6; - bool tcp; - bool quic; - bool quic_full_handshake; - const char *qlog_dir; - const char *sending_mode; - xdp_gun_ignore_t ignore1; - knot_tcp_ignore_t ignore2; - uint16_t target_port; - knot_xdp_filter_flag_t flags; - unsigned n_threads, thread_id; - knot_eth_rss_conf_t *rss_conf; - knot_xdp_config_t xdp_config; -} xdp_gun_ctx_t; - const static xdp_gun_ctx_t ctx_defaults = { .dev[0] = '\0', .edns_size = 1232, @@ -150,7 +75,9 @@ const static xdp_gun_ctx_t ctx_defaults = { .sending_mode = "", .target_port = 0, .flags = KNOT_XDP_FILTER_UDP | KNOT_XDP_FILTER_PASS, - .xdp_config = { .extra_frames = true }, + .xdp_config = { .ring_size = 2048 }, + .jw = NULL, + .stats_period = 0, }; static void sigterm_handler(int signo) @@ -163,103 +90,8 @@ static void sigusr_handler(int signo) { assert(signo == SIGUSR1); if (global_stats.collected == 0) { - stats_trigger++; - } -} - -static void clear_stats(kxdpgun_stats_t *st) -{ - pthread_mutex_lock(&st->mutex); - st->duration = 0; - st->qry_sent = 0; - st->synack_recv = 0; - st->ans_recv = 0; - st->finack_recv = 0; - st->rst_recv = 0; - st->size_recv = 0; - st->wire_recv = 0; - st->collected = 0; - memset(st->rcodes_recv, 0, sizeof(st->rcodes_recv)); - pthread_mutex_unlock(&st->mutex); -} - -static size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) -{ - pthread_mutex_lock(&into->mutex); - into->duration = MAX(into->duration, what->duration); - into->qry_sent += what->qry_sent; - into->synack_recv += what->synack_recv; - into->ans_recv += what->ans_recv; - into->finack_recv += what->finack_recv; - into->rst_recv += what->rst_recv; - into->size_recv += what->size_recv; - into->wire_recv += what->wire_recv; - for (int i = 0; i < RCODE_MAX; i++) { - into->rcodes_recv[i] += what->rcodes_recv[i]; + ATOMIC_ADD(stats_trigger, 1); } - size_t res = ++into->collected; - pthread_mutex_unlock(&into->mutex); - return res; -} - -static void print_stats(kxdpgun_stats_t *st, bool tcp, bool quic, bool recv, uint64_t qps) -{ - pthread_mutex_lock(&st->mutex); - -#define ps(counter) ((typeof(counter))((counter) * 1000 / ((float)st->duration / 1000))) -#define pct(counter) ((counter) * 100.0 / st->qry_sent) - - const char *name = tcp ? "SYNs: " : quic ? "initials:" : "queries: "; - printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, st->qry_sent, - ps(st->qry_sent), 100.0 * st->qry_sent / (st->duration / 1000000.0 * qps)); - if (st->qry_sent > 0 && recv) { - if (tcp || quic) { - name = tcp ? "established:" : "handshakes: "; - printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, - st->synack_recv, ps(st->synack_recv), pct(st->synack_recv)); - } - printf("total replies: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->ans_recv, ps(st->ans_recv), pct(st->ans_recv)); - if (tcp) { - printf("total closed: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->finack_recv, ps(st->finack_recv), pct(st->finack_recv)); - } - if (st->rst_recv > 0) { - printf("total reset: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", - st->rst_recv, ps(st->rst_recv), pct(st->rst_recv)); - } - printf("average DNS reply size: %"PRIu64" B\n", - st->ans_recv > 0 ? st->size_recv / st->ans_recv : 0); - printf("average Ethernet reply rate: %"PRIu64" bps (%.2f Mbps)\n", - ps(st->wire_recv * 8), ps((float)st->wire_recv * 8 / (1000 * 1000))); - - for (int i = 0; i < RCODE_MAX; i++) { - if (st->rcodes_recv[i] > 0) { - const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, i); - const char *rcname = rcode == NULL ? "unknown" : rcode->name; - int space = MAX(9 - strlen(rcname), 0); - printf("responded %s: %.*s%"PRIu64"\n", - rcname, space, " ", st->rcodes_recv[i]); - } - } - } - printf("duration: %"PRIu64" s\n", (st->duration / (1000 * 1000))); - - pthread_mutex_unlock(&st->mutex); -} - -inline static void timer_start(struct timespec *timesp) -{ - clock_gettime(CLOCK_MONOTONIC, timesp); -} - -inline static uint64_t timer_end(struct timespec *timesp) -{ - struct timespec end; - clock_gettime(CLOCK_MONOTONIC, &end); - uint64_t res = (end.tv_sec - timesp->tv_sec) * (uint64_t)1000000; - res += ((int64_t)end.tv_nsec - timesp->tv_nsec) / 1000; - return res; } static unsigned addr_bits(bool ipv6) @@ -267,7 +99,8 @@ static unsigned addr_bits(bool ipv6) return ipv6 ? 128 : 32; } -static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, uint64_t increment) +static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, + uint64_t increment) { memcpy(&dst->sin_addr, &src->sin_addr, sizeof(dst->sin_addr)); if (increment > 0) { @@ -275,7 +108,8 @@ static void shuffle_sockaddr4(struct sockaddr_in *dst, struct sockaddr_in *src, } } -static void shuffle_sockaddr6(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, uint64_t increment) +static void shuffle_sockaddr6(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, + uint64_t increment) { memcpy(&dst->sin6_addr, &src->sin6_addr, sizeof(dst->sin6_addr)); if (increment > 0) { @@ -293,7 +127,8 @@ static void shuffle_sockaddr(struct sockaddr_in6 *dst, struct sockaddr_in6 *src, if (src->sin6_family == AF_INET6) { shuffle_sockaddr6(dst, src, increment); } else { - shuffle_sockaddr4((struct sockaddr_in *)dst, (struct sockaddr_in *)src, increment); + shuffle_sockaddr4((struct sockaddr_in *)dst, (struct sockaddr_in *)src, + increment); } } @@ -311,7 +146,8 @@ static void next_payload(struct pkt_payload **payload, int increment) } } -static void put_dns_payload(struct iovec *put_into, bool zero_copy, xdp_gun_ctx_t *ctx, struct pkt_payload **payl) +static void put_dns_payload(struct iovec *put_into, bool zero_copy, xdp_gun_ctx_t *ctx, + struct pkt_payload **payl) { if (zero_copy) { put_into->iov_base = (*payl)->payload; @@ -472,19 +308,41 @@ static void quic_free_cb(knot_quic_reply_t *rpl) } #endif // ENABLE_QUIC +static uint64_t timestamp_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec; +} + +static void timer_start(struct timespec *out) +{ + clock_gettime(CLOCK_MONOTONIC, out); +} + +static uint64_t timer_end_ns(const struct timespec *start) +{ + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + uint64_t res = (end.tv_sec - start->tv_sec) * (uint64_t)1000000000; + res += end.tv_nsec - start->tv_nsec; + return res; +} + void *xdp_gun_thread(void *_ctx) { xdp_gun_ctx_t *ctx = _ctx; struct knot_xdp_socket *xsk = NULL; - struct timespec timer; knot_xdp_msg_t pkts[ctx->at_once]; - uint64_t errors = 0, lost = 0, duration = 0; - kxdpgun_stats_t local_stats = { 0 }; + uint64_t duration_us = 0; + struct timespec timer; + kxdpgun_stats_t local_stats = { 0 }; // cumulative stats of past periods excluding the current + kxdpgun_stats_t periodic_stats = { 0 }; // stats for the current period (see -S option) unsigned stats_triggered = 0; knot_tcp_table_t *tcp_table = NULL; #ifdef ENABLE_QUIC knot_quic_table_t *quic_table = NULL; - struct knot_quic_creds *quic_creds = NULL; + struct knot_creds *quic_creds = NULL; list_t quic_sessions; init_list(&quic_sessions); #endif // ENABLE_QUIC @@ -501,12 +359,13 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->quic) { #ifdef ENABLE_QUIC - quic_creds = knot_quic_init_creds_peer(NULL, NULL, 0); + quic_creds = knot_creds_init_peer(NULL, NULL, 0); if (quic_creds == NULL) { ERR2("failed to initialize QUIC context"); goto cleanup; } - quic_table = knot_quic_table_new(ctx->qps * 100, SIZE_MAX, SIZE_MAX, 1232, quic_creds); + quic_table = knot_quic_table_new(ctx->qps * 100, SIZE_MAX, SIZE_MAX, + 1232, quic_creds); if (quic_table == NULL) { ERR2("failed to allocate QUIC connection table"); goto cleanup; @@ -517,12 +376,12 @@ void *xdp_gun_thread(void *_ctx) #endif // ENABLE_QUIC } - knot_xdp_load_bpf_t mode = (ctx->thread_id == 0 ? - KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER); + knot_xdp_load_bpf_t mode = + (ctx->thread_id == 0 ? KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER); /* * This mutex prevents libbpf from logging: * 'libbpf: can't get link by id (5535): Resource temporarily unavailable' - */ + */ pthread_mutex_lock(&global_stats.mutex); int ret = knot_xdp_init(&xsk, ctx->dev, ctx->thread_id, ctx->flags, LOCAL_PORT_MIN, LOCAL_PORT_MIN, mode, &ctx->xdp_config); @@ -534,13 +393,7 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->thread_id == 0) { - INFO2("using interface %s, XDP threads %u, IPv%c/%s%s%s, %s mode", - ctx->dev, ctx->n_threads, (ctx->ipv6 ? '6' : '4'), - (ctx->tcp ? "TCP" : ctx->quic ? "QUIC" : "UDP"), - (ctx->sending_mode[0] != '\0' ? " mode " : ""), - (ctx->sending_mode[0] != '\0' ? ctx->sending_mode : ""), - (knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL ? - "native" : "emulated")); + STATS_HDR(ctx); } struct pollfd pfd = { knot_xdp_socket_fd(xsk), POLLIN, 0 }; @@ -576,7 +429,7 @@ void *xdp_gun_thread(void *_ctx) ctx->target_ip.sin6_port = htobe16(ctx->target_port); knot_sweep_stats_t sweep_stats = { 0 }; - uint16_t local_ports[QUIC_THREAD_PORTS]; + uint16_t local_ports[QUIC_THREAD_PORTS] = { 0 }; uint16_t port = LOCAL_PORT_MIN; for (int i = 0; ctx->quic && i < QUIC_THREAD_PORTS; ++i) { local_ports[i] = adjust_port(ctx, port); @@ -586,24 +439,25 @@ void *xdp_gun_thread(void *_ctx) size_t local_ports_it = 0; #endif // ENABLE_QUIC + local_stats.since = periodic_stats.since = timestamp_ns(); timer_start(&timer); + ctx->stats_start_us = local_stats.since / 1000; - while (duration < ctx->duration + extra_wait) { - + while (duration_us < ctx->duration + extra_wait) { // sending part - if (duration < ctx->duration) { + if (duration_us < ctx->duration) { while (1) { knot_xdp_send_prepare(xsk); unsigned alloced = alloc_pkts(pkts, xsk, ctx, tick); if (alloced < ctx->at_once) { - lost += ctx->at_once - alloced; + periodic_stats.lost += ctx->at_once - alloced; if (alloced == 0) { break; } } if (ctx->tcp) { - for (int i = 0; i < alloced; i++) { + for (uint32_t i = 0; i < alloced; i++) { pkts[i].payload.iov_len = 0; if (!EMPTY_LIST(reuse_conns)) { @@ -621,7 +475,7 @@ void *xdp_gun_thread(void *_ctx) } if (ret == KNOT_EOK) { pkts[i].flags &= ~KNOT_XDP_MSG_SYN; // skip sending respective packet - local_stats.qry_sent++; + periodic_stats.qry_sent++; } free(rl); } @@ -670,14 +524,14 @@ void *xdp_gun_thread(void *_ctx) (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE) ? KNOT_QUIC_SEND_IGNORE_LASTBYTE : 0); } if (ret == KNOT_EOK) { - local_stats.qry_sent++; + periodic_stats.qry_sent++; } } (void)knot_xdp_send_finish(xsk); #endif // ENABLE_QUIC break; } else { - for (int i = 0; i < alloced; i++) { + for (uint32_t i = 0; i < alloced; i++) { put_dns_payload(&pkts[i].payload, false, ctx, &payload_ptr); } @@ -685,9 +539,9 @@ void *xdp_gun_thread(void *_ctx) uint32_t really_sent = 0; if (knot_xdp_send(xsk, pkts, alloced, &really_sent) != KNOT_EOK) { - lost += alloced; + periodic_stats.lost += alloced; } - local_stats.qry_sent += really_sent; + periodic_stats.qry_sent += really_sent; (void)knot_xdp_send_finish(xsk); break; @@ -699,7 +553,7 @@ void *xdp_gun_thread(void *_ctx) while (1) { ret = poll(&pfd, 1, 0); if (ret < 0) { - errors++; + periodic_stats.errors++; break; } if (!pfd.revents) { @@ -714,18 +568,19 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->tcp) { knot_tcp_relay_t relays[recvd]; - ret = knot_tcp_recv(relays, pkts, recvd, tcp_table, NULL, ctx->ignore2); - if (ret != KNOT_EOK) { - errors++; - break; - } for (size_t i = 0; i < recvd; i++) { knot_tcp_relay_t *rl = &relays[i]; + ret = knot_tcp_recv(rl, &pkts[i], tcp_table, NULL, ctx->ignore2); + if (ret != KNOT_EOK) { + periodic_stats.errors++; + continue; + } + struct iovec payl; switch (rl->action) { case XDP_TCP_ESTABLISH: - local_stats.synack_recv++; + periodic_stats.synack_recv++; if (ctx->ignore1 & KXDPGUN_IGNORE_QUERY) { break; } @@ -734,20 +589,20 @@ void *xdp_gun_thread(void *_ctx) (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE), payl.iov_base, payl.iov_len); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } break; case XDP_TCP_CLOSE: - local_stats.finack_recv++; + periodic_stats.finack_recv++; break; case XDP_TCP_RESET: - local_stats.rst_recv++; + periodic_stats.rst_recv++; break; default: break; } for (size_t j = 0; rl->inbf != NULL && j < rl->inbf->n_inbufs; j++) { - if (check_dns_payload(&rl->inbf->inbufs[j], ctx, &local_stats)) { + if (check_dns_payload(&rl->inbf->inbufs[j], ctx, &periodic_stats)) { if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE)) { rl->answer = XDP_TCP_CLOSE; } else if ((ctx->ignore1 & KXDPGUN_REUSE_CONN)) { @@ -763,7 +618,7 @@ void *xdp_gun_thread(void *_ctx) ret = knot_tcp_send(xsk, relays, recvd, ctx->at_once); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } (void)knot_xdp_send_finish(xsk); @@ -781,11 +636,11 @@ void *xdp_gun_thread(void *_ctx) ret = knot_quic_handle(quic_table, &quic_reply, 5000000000L, &conn); if (ret == KNOT_ECONN) { - local_stats.rst_recv++; + periodic_stats.rst_recv++; knot_quic_cleanup(&conn, 1); continue; } else if (ret != 0) { - errors++; + periodic_stats.errors++; knot_quic_cleanup(&conn, 1); break; } @@ -805,7 +660,7 @@ void *xdp_gun_thread(void *_ctx) if ((conn->flags & KNOT_QUIC_CONN_HANDSHAKE_DONE) && conn->streams_count == -1) { conn->streams_count = 1; - local_stats.synack_recv++; + periodic_stats.synack_recv++; if ((ctx->ignore1 & KXDPGUN_IGNORE_QUERY)) { knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); @@ -822,14 +677,14 @@ void *xdp_gun_thread(void *_ctx) if ((ctx->ignore2 & XDP_TCP_IGNORE_ESTABLISH)) { knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); - local_stats.synack_recv++; + periodic_stats.synack_recv++; continue; } int64_t s0id; knot_quic_stream_t *stream0 = knot_quic_stream_get_process(conn, &s0id); if (stream0 != NULL && stream0->inbufs != NULL && stream0->inbufs->n_inbufs > 0) { - check_dns_payload(&stream0->inbufs->inbufs[0], ctx, &local_stats); + check_dns_payload(&stream0->inbufs->inbufs[0], ctx, &periodic_stats); stream0->inbufs->n_inbufs = 0; // signal that data have been read out if ((ctx->ignore2 & XDP_TCP_IGNORE_DATA_ACK)) { @@ -837,7 +692,9 @@ void *xdp_gun_thread(void *_ctx) knot_quic_cleanup(&conn, 1); continue; } else if ((ctx->ignore1 & KXDPGUN_REUSE_CONN)) { - if (conn->streams_count > 1) { // keep the number of outstanding streams below MAX_STREAMS_PER_CONN, while preserving at least one at all times + /* keep the number of outstanding streams below MAX_STREAMS_PER_CONN, + * while preserving at least one at all times */ + if (conn->streams_count > 1) { knot_quic_conn_stream_free(conn, conn->streams_first * 4); } ptrlist_add(&reuse_conns, conn, NULL); @@ -846,30 +703,31 @@ void *xdp_gun_thread(void *_ctx) ret = knot_quic_send(quic_table, conn, &quic_reply, 4, (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE) ? KNOT_QUIC_SEND_IGNORE_LASTBYTE : 0); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } - if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE) && (conn->flags & KNOT_QUIC_CONN_SESSION_TAKEN) && - stream0 != NULL && stream0->inbufs != NULL && stream0->inbufs->n_inbufs == 0) { + if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE) + && (conn->flags & KNOT_QUIC_CONN_SESSION_TAKEN) + && stream0 != NULL && stream0->inbufs != NULL + && stream0->inbufs->n_inbufs == 0) { assert(!(ctx->ignore2 & XDP_TCP_IGNORE_DATA_ACK)); quic_reply.handle_ret = KNOT_QUIC_HANDLE_RET_CLOSE; ret = knot_quic_send(quic_table, conn, &quic_reply, 1, 0); knot_quic_table_rem(conn, quic_table); knot_quic_cleanup(&conn, 1); if (ret != KNOT_EOK) { - errors++; + periodic_stats.errors++; } } } (void)knot_xdp_send_finish(xsk); #endif // ENABLE_QUIC } else { - for (int i = 0; i < recvd; i++) { - (void)check_dns_payload(&pkts[i].payload, ctx, - &local_stats); + for (uint32_t i = 0; i < recvd; i++) { + check_dns_payload(&pkts[i].payload, ctx, &periodic_stats); } } - local_stats.wire_recv += wire; + periodic_stats.wire_recv += wire; knot_xdp_recv_finish(xsk, pkts, recvd); pfd.revents = 0; } @@ -882,47 +740,59 @@ void *xdp_gun_thread(void *_ctx) #endif // ENABLE_QUIC // speed and signal part - uint64_t dura_exp = (local_stats.qry_sent * 1000000) / ctx->qps; - duration = timer_end(&timer); - if (xdp_trigger == KXDPGUN_STOP && ctx->duration > duration) { - ctx->duration = duration; + uint64_t duration_ns = timer_end_ns(&timer); + duration_us = duration_ns / 1000; + uint64_t dura_exp = ((local_stats.qry_sent + periodic_stats.qry_sent) * 1000000) / ctx->qps; + if (ctx->thread_id == 0 && ctx->stats_period != 0 && global_stats.collected == 0 + && (duration_ns - (periodic_stats.since - local_stats.since)) >= ctx->stats_period) { + ATOMIC_SET(stats_switch, STATS_PERIODIC); + ATOMIC_ADD(stats_trigger, 1); } - if (stats_trigger > stats_triggered) { - assert(stats_trigger == stats_triggered + 1); - stats_triggered++; - local_stats.duration = duration; - size_t collected = collect_stats(&global_stats, &local_stats); + if (xdp_trigger == KXDPGUN_STOP && ctx->duration > duration_us) { + ctx->duration = duration_us; + } + uint64_t tmp_stats_trigger = ATOMIC_GET(stats_trigger); + if (duration_us < ctx->duration && tmp_stats_trigger > stats_triggered) { + bool tmp_stats_switch = ATOMIC_GET(stats_switch); + stats_triggered = tmp_stats_trigger; + + local_stats.until = periodic_stats.until = local_stats.since + duration_ns; + kxdpgun_stats_t cumulative_stats = periodic_stats; + if (tmp_stats_switch == STATS_PERIODIC) { + collect_periodic_stats(&local_stats, &periodic_stats); + clear_stats(&periodic_stats); + periodic_stats.since = local_stats.since + duration_ns; + } else { + collect_periodic_stats(&cumulative_stats, &local_stats); + cumulative_stats.since = local_stats.since; + } + + size_t collected = collect_stats(&global_stats, &cumulative_stats); + assert(collected <= ctx->n_threads); if (collected == ctx->n_threads) { - print_stats(&global_stats, ctx->tcp, ctx->quic, - !(ctx->flags & KNOT_XDP_FILTER_DROP), - ctx->qps * ctx->n_threads); + STATS_FMT(ctx, &global_stats, tmp_stats_switch); + if (!JSON_MODE(*ctx)) { + puts(STATS_SECTION_SEP); + } clear_stats(&global_stats); + ATOMIC_SET(stats_switch, STATS_SUM); } } - if (dura_exp > duration) { - usleep(dura_exp - duration); + if (dura_exp > duration_us) { + usleep(dura_exp - duration_us); } - if (duration > ctx->duration) { + if (duration_us > ctx->duration) { usleep(1000); } tick++; } + periodic_stats.until = local_stats.since + timer_end_ns(&timer) - extra_wait * 1000; + collect_periodic_stats(&local_stats, &periodic_stats); + + STATS_THRD(ctx, &local_stats); - char recv_str[40] = "", lost_str[40] = "", err_str[40] = ""; - if (!(ctx->flags & KNOT_XDP_FILTER_DROP)) { - (void)snprintf(recv_str, sizeof(recv_str), ", received %"PRIu64, local_stats.ans_recv); - } - if (lost > 0) { - (void)snprintf(lost_str, sizeof(lost_str), ", lost %"PRIu64, lost); - } - if (errors > 0) { - (void)snprintf(err_str, sizeof(err_str), ", errors %"PRIu64, errors); - } - INFO2("thread#%02u: sent %"PRIu64"%s%s%s", - ctx->thread_id, local_stats.qry_sent, recv_str, lost_str, err_str); - local_stats.duration = ctx->duration; collect_stats(&global_stats, &local_stats); cleanup: @@ -943,7 +813,7 @@ cleanup: WALK_LIST_DELSAFE(n, nxt, quic_sessions) { knot_quic_session_load(NULL, n); } - knot_quic_free_creds(quic_creds); + knot_creds_free(quic_creds); #endif // ENABLE_QUIC return NULL; @@ -1118,30 +988,33 @@ static void print_help(void) printf("Usage: %s [options] -i <queries_file> <dest_ip>\n" "\n" "Options:\n" - " -t, --duration <sec> "SPACE"Duration of traffic generation.\n" - " "SPACE" (default is %"PRIu64" seconds)\n" - " -T, --tcp[=debug_mode] "SPACE"Send queries over TCP.\n" - " -U, --quic[=debug_mode] "SPACE"Send queries over QUIC.\n" - " -Q, --qps <qps> "SPACE"Number of queries-per-second (approximately) to be sent.\n" - " "SPACE" (default is %"PRIu64" qps)\n" - " -b, --batch <size> "SPACE"Send queries in a batch of defined size.\n" - " "SPACE" (default is %d for UDP, %d for TCP)\n" - " -r, --drop "SPACE"Drop incoming responses (disables response statistics).\n" - " -p, --port <port> "SPACE"Remote destination port.\n" - " "SPACE" (default is %d for UDP/TCP, %u for QUIC)\n" - " -F, --affinity <spec> "SPACE"CPU affinity in the format [<cpu_start>][s<cpu_step>].\n" - " "SPACE" (default is %s)\n" - " -i, --infile <file> "SPACE"Path to a file with query templates.\n" - " -I, --interface <ifname> "SPACE"Override auto-detected interface for outgoing communication.\n" - " -l, --local <ip[/prefix]>"SPACE"Override auto-detected source IP address or subnet.\n" - " -L, --local-mac <MAC> "SPACE"Override auto-detected local MAC address.\n" - " -R, --remote-mac <MAC> "SPACE"Override auto-detected remote MAC address.\n" - " -v, --vlan <id> "SPACE"Add VLAN 802.1Q header with the given id.\n" - " -e, --edns-size <size> "SPACE"EDNS UDP payload size, range 512-4096 (default 1232)\n" - " -m, --mode <mode> "SPACE"Set XDP mode (auto, copy, generic).\n" - " -G, --qlog <path> "SPACE"Output directory for qlog (useful for QUIC only).\n" - " -h, --help "SPACE"Print the program help.\n" - " -V, --version "SPACE"Print the program version.\n" + " -t, --duration <sec> "SPACE"Duration of traffic generation.\n" + " "SPACE" (default is %"PRIu64" seconds)\n" + " -T, --tcp[=debug_mode] "SPACE"Send queries over TCP.\n" + " -U, --quic[=debug_mode] "SPACE"Send queries over QUIC.\n" + " -Q, --qps <qps> "SPACE"Number of queries-per-second (approximately) to be sent.\n" + " "SPACE" (default is %"PRIu64" qps)\n" + " -b, --batch <size> "SPACE"Send queries in a batch of defined size.\n" + " "SPACE" (default is %d for UDP, %d for TCP)\n" + " -r, --drop "SPACE"Drop incoming responses (disables response statistics).\n" + " -p, --port <port> "SPACE"Remote destination port.\n" + " "SPACE" (default is %d for UDP/TCP, %u for QUIC)\n" + " -F, --affinity <spec> "SPACE"CPU affinity in the format [<cpu_start>][s<cpu_step>].\n" + " "SPACE" (default is %s)\n" + " -I, --interface <ifname> "SPACE"Override auto-detected interface for outgoing communication.\n" + " -i, --infile <file> "SPACE"Path to a file with query templates.\n" + " -B, --binary "SPACE"Specify that input file is in binary format (<length:2><wire:length>).\n" + " -l, --local <ip[/prefix]> "SPACE"Override auto-detected source IP address or subnet.\n" + " -L, --local-mac <MAC> "SPACE"Override auto-detected local MAC address.\n" + " -R, --remote-mac <MAC> "SPACE"Override auto-detected remote MAC address.\n" + " -v, --vlan <id> "SPACE"Add VLAN 802.1Q header with the given id.\n" + " -e, --edns-size <size> "SPACE"EDNS UDP payload size, range 512-4096 (default 1232)\n" + " -m, --mode <mode> "SPACE"Set XDP mode (auto, copy, generic).\n" + " -G, --qlog <path> "SPACE"Output directory for qlog (useful for QUIC only).\n" + " -j, --json "SPACE"Output statistics in json.\n" + " -S, --stats-period <period>"SPACE"Enable periodic statistics printout in milliseconds.\n" + " -h, --help "SPACE"Print the program help.\n" + " -V, --version "SPACE"Print the program version.\n" "\n" "Parameters:\n" " <dest_ip> "SPACE"IPv4 or IPv6 address of the remote destination.\n", @@ -1240,40 +1113,45 @@ static int set_mode(const char *arg, knot_xdp_config_t *config) static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) { + const char *opts_str = "hV::t:Q:b:rp:T::U::F:I:i:Bl:L:R:v:e:m:G:jS:"; struct option opts[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "duration", required_argument, NULL, 't' }, - { "qps", required_argument, NULL, 'Q' }, - { "batch", required_argument, NULL, 'b' }, - { "drop", no_argument, NULL, 'r' }, - { "port", required_argument, NULL, 'p' }, - { "tcp", optional_argument, NULL, 'T' }, - { "quic", optional_argument, NULL, 'U' }, - { "affinity", required_argument, NULL, 'F' }, - { "interface", required_argument, NULL, 'I' }, - { "local", required_argument, NULL, 'l' }, - { "infile", required_argument, NULL, 'i' }, - { "local-mac", required_argument, NULL, 'L' }, - { "remote-mac", required_argument, NULL, 'R' }, - { "vlan", required_argument, NULL, 'v' }, - { "edns-size", required_argument, NULL, 'e' }, - { "mode", required_argument, NULL, 'm' }, - { "qlog", required_argument, NULL, 'G' }, - { NULL } + { "help", no_argument, NULL, 'h' }, + { "version", optional_argument, NULL, 'V' }, + { "duration", required_argument, NULL, 't' }, + { "qps", required_argument, NULL, 'Q' }, + { "batch", required_argument, NULL, 'b' }, + { "drop", no_argument, NULL, 'r' }, + { "port", required_argument, NULL, 'p' }, + { "tcp", optional_argument, NULL, 'T' }, + { "quic", optional_argument, NULL, 'U' }, + { "affinity", required_argument, NULL, 'F' }, + { "interface", required_argument, NULL, 'I' }, + { "infile", required_argument, NULL, 'i' }, + { "binary", no_argument, NULL, 'B' }, + { "local", required_argument, NULL, 'l' }, + { "local-mac", required_argument, NULL, 'L' }, + { "remote-mac", required_argument, NULL, 'R' }, + { "vlan", required_argument, NULL, 'v' }, + { "edns-size", required_argument, NULL, 'e' }, + { "mode", required_argument, NULL, 'm' }, + { "qlog", required_argument, NULL, 'G' }, + { "json", no_argument, NULL, 'j' }, + { "stats-period", required_argument, NULL, 'S' }, + { 0 } }; int opt = 0, arg; bool default_at_once = true; double argf; - char *argcp, *local_ip = NULL, *filename = NULL; - while ((opt = getopt_long(argc, argv, "hVt:Q:b:rp:T::U::F:I:l:i:L:R:v:e:m:G:", opts, NULL)) != -1) { + char *argcp, *local_ip = NULL; + input_t input = { .format = TXT }; + while ((opt = getopt_long(argc, argv, opts_str, opts, NULL)) != -1) { switch (opt) { case 'h': print_help(); exit(EXIT_SUCCESS); case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); exit(EXIT_SUCCESS); case 't': assert(optarg); @@ -1366,12 +1244,15 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) case 'I': strlcpy(ctx->dev, optarg, IFNAMSIZ); break; + case 'i': + input.path = optarg; + break; + case 'B': + input.format = BIN; + break; case 'l': local_ip = optarg; break; - case 'i': - filename = optarg; - break; case 'L': if (mac_sscan(optarg, ctx->local_mac) != KNOT_EOK) { ERR2("invalid local MAC address '%s'", optarg); @@ -1415,17 +1296,33 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) case 'G': ctx->qlog_dir = optarg; break; + case 'S': + assert(optarg); + arg = atoi(optarg); + if (arg > 0) { + ctx->stats_period = arg * 1000000; // convert to ns + } else { + ERR2("period must be a positive integer\n"); + return false; + } + break; + case 'j': + if ((ctx->jw = jsonw_new(stdout, JSON_INDENT)) == NULL) { + ERR2("failed to use JSON"); + return false; + } + break; default: print_help(); return false; } } - if (filename == NULL) { + if (input.path == NULL) { print_help(); return false; } size_t qcount = ctx->duration / 1000000 * ctx->qps; - if (!load_queries(filename, ctx->edns_size, ctx->msgid, qcount)) { + if (!load_queries(&input, ctx->edns_size, ctx->msgid, qcount)) { return false; } if (global_payloads == NULL || argc - optind != 1) { @@ -1452,25 +1349,29 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) int main(int argc, char *argv[]) { + int ecode = EXIT_FAILURE; + xdp_gun_ctx_t ctx = ctx_defaults, *thread_ctxs = NULL; ctx.msgid = time(NULL) % UINT16_MAX; + ctx.runid = timestamp_ns() / 1000; + ctx.argv = argv; pthread_t *threads = NULL; if (!get_opts(argc, argv, &ctx)) { - free_global_payloads(); - return EXIT_FAILURE; + goto err; + } + + if (JSON_MODE(ctx)) { + jsonw_list(ctx.jw, NULL); // wrap the json in a list, for syntactic correctness } thread_ctxs = calloc(ctx.n_threads, sizeof(*thread_ctxs)); threads = calloc(ctx.n_threads, sizeof(*threads)); if (thread_ctxs == NULL || threads == NULL) { ERR2("out of memory"); - free(thread_ctxs); - free(threads); - free_global_payloads(); - return EXIT_FAILURE; + goto err; } - for (int i = 0; i < ctx.n_threads; i++) { + for (uint32_t i = 0; i < ctx.n_threads; i++) { thread_ctxs[i] = ctx; thread_ctxs[i].thread_id = i; } @@ -1482,8 +1383,7 @@ int main(int argc, char *argv[]) cur_limit.rlim_max != min_limit.rlim_max) { int ret = setrlimit(RLIMIT_MEMLOCK, &min_limit); if (ret != 0) { - WARN2("unable to increase RLIMIT_MEMLOCK: %s", - strerror(errno)); + WARN2("unable to increase RLIMIT_MEMLOCK: %s", strerror(errno)); } } } @@ -1509,22 +1409,30 @@ int main(int argc, char *argv[]) usleep(20000); } usleep(1000000); - xdp_trigger = KXDPGUN_START; usleep(1000000); for (size_t i = 0; i < ctx.n_threads; i++) { pthread_join(threads[i], NULL); } - if (global_stats.duration > 0 && global_stats.qry_sent > 0) { - print_stats(&global_stats, ctx.tcp, ctx.quic, !(ctx.flags & KNOT_XDP_FILTER_DROP), ctx.qps * ctx.n_threads); + if (DURATION_US(global_stats) > 0 && global_stats.qry_sent > 0) { + if (!JSON_MODE(ctx)) { + puts(STATS_SECTION_SEP); + } + STATS_FMT(&ctx, &global_stats, STATS_SUM); } pthread_mutex_destroy(&global_stats.mutex); + ecode = EXIT_SUCCESS; + +err: free(ctx.rss_conf); free(thread_ctxs); free(threads); free_global_payloads(); - - return EXIT_SUCCESS; + if (JSON_MODE(ctx)) { + jsonw_end(ctx.jw); + jsonw_free(&ctx.jw); + } + return ecode; } diff --git a/src/utils/kxdpgun/main.h b/src/utils/kxdpgun/main.h new file mode 100644 index 0000000..d87aee8 --- /dev/null +++ b/src/utils/kxdpgun/main.h @@ -0,0 +1,87 @@ +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <net/if.h> +#include <netinet/in.h> +#include <stdbool.h> + +#include "contrib/json.h" +#include "libknot/xdp/eth.h" +#include "libknot/xdp/tcp.h" + +#define PROGRAM_NAME "kxdpgun" +#define SPACE " " + +#define REMOTE_PORT_DEFAULT 53 +#define REMOTE_PORT_DOQ_DEFAULT 853 +#define LOCAL_PORT_MIN 2000 +#define LOCAL_PORT_MAX 65535 +#define QUIC_THREAD_PORTS 100 + +enum { + KXDPGUN_WAIT, + KXDPGUN_START, + KXDPGUN_STOP, +}; + +typedef enum { + KXDPGUN_IGNORE_NONE = 0, + KXDPGUN_IGNORE_QUERY = (1 << 0), + KXDPGUN_IGNORE_LASTBYTE = (1 << 1), + KXDPGUN_IGNORE_CLOSE = (1 << 2), + KXDPGUN_REUSE_CONN = (1 << 3), +} xdp_gun_ignore_t; + +typedef struct xdp_gun_ctx { + union { + struct sockaddr_in local_ip4; + struct sockaddr_in6 local_ip; + struct sockaddr_storage local_ip_ss; + }; + union { + struct sockaddr_in target_ip4; + struct sockaddr_in6 target_ip; + struct sockaddr_storage target_ip_ss; + }; + char dev[IFNAMSIZ]; + uint64_t qps, duration; + uint64_t runid; + uint64_t stats_start_us; + uint32_t stats_period; // 0 means no periodic stats + unsigned at_once; + uint16_t msgid; + uint16_t edns_size; + uint16_t vlan_tci; + uint8_t local_mac[6], target_mac[6]; + uint8_t local_ip_range; + bool ipv6; + bool tcp; + bool quic; + bool quic_full_handshake; + const char *qlog_dir; + const char *sending_mode; + xdp_gun_ignore_t ignore1; + knot_tcp_ignore_t ignore2; + uint16_t target_port; + knot_xdp_filter_flag_t flags; + unsigned n_threads, thread_id; + knot_eth_rss_conf_t *rss_conf; + jsonw_t *jw; + char **argv; + knot_xdp_config_t xdp_config; +} xdp_gun_ctx_t; diff --git a/src/utils/kxdpgun/stats.c b/src/utils/kxdpgun/stats.c new file mode 100644 index 0000000..f1e4f43 --- /dev/null +++ b/src/utils/kxdpgun/stats.c @@ -0,0 +1,292 @@ +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <limits.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/macros.h" +#include "libknot/codes.h" +#include "libknot/lookup.h" +#include "utils/common/msg.h" +#include "utils/kxdpgun/main.h" +#include "utils/kxdpgun/stats.h" + +pthread_mutex_t stdout_mtx = PTHREAD_MUTEX_INITIALIZER; + +void clear_stats(kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&st->mutex); + st->since = 0; + st->until = 0; + st->qry_sent = 0; + st->synack_recv = 0; + st->ans_recv = 0; + st->finack_recv = 0; + st->rst_recv = 0; + st->size_recv = 0; + st->wire_recv = 0; + st->collected = 0; + st->lost = 0; + st->errors = 0; + memset(st->rcodes_recv, 0, sizeof(st->rcodes_recv)); + pthread_mutex_unlock(&st->mutex); +} + +size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) +{ + pthread_mutex_lock(&into->mutex); + into->since = what->since; + collect_periodic_stats(into, what); + size_t res = ++into->collected; + pthread_mutex_unlock(&into->mutex); + return res; +} + +void collect_periodic_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what) +{ + into->until = what->until; + into->qry_sent += what->qry_sent; + into->synack_recv += what->synack_recv; + into->ans_recv += what->ans_recv; + into->finack_recv += what->finack_recv; + into->rst_recv += what->rst_recv; + into->size_recv += what->size_recv; + into->wire_recv += what->wire_recv; + into->lost += what->lost; + into->errors += what->errors; + for (int i = 0; i < RCODE_MAX; i++) { + into->rcodes_recv[i] += what->rcodes_recv[i]; + } +} + +void plain_stats_header(const xdp_gun_ctx_t *ctx) +{ + INFO2("using interface %s, XDP threads %u, IPv%c/%s%s%s, %s mode", ctx->dev, ctx->n_threads, + (ctx->ipv6 ? '6' : '4'), + (ctx->tcp ? "TCP" : ctx->quic ? "QUIC" : "UDP"), + (ctx->sending_mode[0] != '\0' ? " mode " : ""), + (ctx->sending_mode[0] != '\0' ? ctx->sending_mode : ""), + (knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL ? "native" : "emulated")); + puts(STATS_SECTION_SEP); +} + +/* see: + * - https://github.com/DNS-OARC/dns-metrics/blob/main/dns-metrics.schema.json + * - https://github.com/DNS-OARC/dns-metrics/issues/16#issuecomment-2139462920 + */ +void json_stats_header(const xdp_gun_ctx_t *ctx) +{ + jsonw_t *w = ctx->jw; + + jsonw_object(w, NULL); + { + jsonw_ulong(w, "runid", ctx->runid); + jsonw_str(w, "type", "header"); + jsonw_int(w, "schema_version", STATS_SCHEMA_VERSION); + jsonw_str(w, "generator", PROGRAM_NAME); + jsonw_str(w, "generator_version", PACKAGE_VERSION); + + jsonw_list(w, "generator_params"); + { + for (char **it = ctx->argv; *it != NULL; ++it) { + jsonw_str(w, NULL, *it); + } + } + jsonw_end(w); + + jsonw_ulong(w, "time_units_per_sec", 1000000000); + if (ctx->stats_period > 0) { + jsonw_double(w, "stats_interval", ctx->stats_period / 1000.0); + } + // TODO: timeout + + // mirror the info given by the plaintext printout + jsonw_object(w, "additional_info"); + { + jsonw_str(w, "interface", ctx->dev); + jsonw_int(w, "xdp_threads", ctx->n_threads); + jsonw_int(w, "ip_version", ctx->ipv6 ? 6 : 4); + jsonw_str(w, "transport_layer_proto", ctx->tcp ? "TCP" : (ctx->quic ? "QUIC" : "UDP")); + jsonw_object(w, "mode_info"); + { + if (ctx->sending_mode[0] != '\0') { + jsonw_str(w, "debug", ctx->sending_mode); + } + jsonw_str(w, "mode", knot_eth_xdp_mode(if_nametoindex(ctx->dev)) == KNOT_XDP_MODE_FULL + ? "native" + : "emulated"); + } + jsonw_end(w); + } + jsonw_end(w); + } + jsonw_end(w); +} + +void plain_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&stdout_mtx); + + char recv_str[40] = "", lost_str[40] = "", err_str[40] = ""; + if (!(ctx->flags & KNOT_XDP_FILTER_DROP)) { + (void)snprintf(recv_str, sizeof(recv_str), ", received %"PRIu64, st->ans_recv); + } + if (st->lost > 0) { + (void)snprintf(lost_str, sizeof(lost_str), ", lost %"PRIu64, st->lost); + } + if (st->errors > 0) { + (void)snprintf(err_str, sizeof(err_str), ", errors %"PRIu64, st->errors); + } + INFO2("thread#%02u: sent %"PRIu64"%s%s%s", + ctx->thread_id, st->qry_sent, recv_str, lost_str, err_str); + + pthread_mutex_unlock(&stdout_mtx); +} + +void json_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st) +{ + pthread_mutex_lock(&stdout_mtx); + + jsonw_t *w = ctx->jw; + + jsonw_object(ctx->jw, NULL); + { + jsonw_str(w, "type", "thread_summary"); + jsonw_ulong(w, "runid", ctx->runid); + jsonw_ulong(w, "subid", ctx->thread_id); + jsonw_ulong(w, "qry_sent", st->qry_sent); + jsonw_ulong(w, "ans_recv", st->ans_recv); + jsonw_ulong(w, "lost", st->lost); + jsonw_ulong(w, "errors", st->errors); + } + jsonw_end(ctx->jw); + + pthread_mutex_unlock(&stdout_mtx); +} + +void plain_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt) +{ + pthread_mutex_lock(&st->mutex); + + printf("%s metrics:\n", (stt == STATS_SUM) ? "cumulative" : "periodic"); + + bool recv = !(ctx->flags & KNOT_XDP_FILTER_DROP); + uint64_t duration = DURATION_US(*st); + double rel_start_us = (st->since / 1000.0) - ctx->stats_start_us ; + double rel_end_us = rel_start_us + duration; + +#define ps(counter) ((typeof(counter))((counter) * 1000 / ((float)duration / 1000))) +#define pct(counter) ((counter) * 100.0 / st->qry_sent) + + const char *name = ctx->tcp ? "SYNs: " : ctx->quic ? "initials:" : "queries: "; + printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, st->qry_sent, + ps(st->qry_sent), 100.0 * st->qry_sent / (duration / 1000000.0 * ctx->qps * ctx->n_threads)); + if (st->qry_sent > 0 && recv) { + if (ctx->tcp || ctx->quic) { + name = ctx->tcp ? "established:" : "handshakes: "; + printf("total %s %"PRIu64" (%"PRIu64" pps) (%f%%)\n", name, + st->synack_recv, ps(st->synack_recv), pct(st->synack_recv)); + } + printf("total replies: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->ans_recv, ps(st->ans_recv), pct(st->ans_recv)); + if (ctx->tcp) { + printf("total closed: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->finack_recv, ps(st->finack_recv), pct(st->finack_recv)); + } + if (st->rst_recv > 0) { + printf("total reset: %"PRIu64" (%"PRIu64" pps) (%f%%)\n", + st->rst_recv, ps(st->rst_recv), pct(st->rst_recv)); + } + printf("average DNS reply size: %"PRIu64" B\n", + st->ans_recv > 0 ? st->size_recv / st->ans_recv : 0); + printf("average Ethernet reply rate: %"PRIu64" bps (%.2f Mbps)\n", + ps(st->wire_recv * 8), ps((float)st->wire_recv * 8 / (1000 * 1000))); + + for (int i = 0; i < RCODE_MAX; i++) { + if (st->rcodes_recv[i] > 0) { + const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, i); + const char *rcname = rcode == NULL ? "unknown" : rcode->name; + int space = MAX(9 - strlen(rcname), 0); + printf("responded %s: %.*s%"PRIu64"\n", + rcname, space, " ", st->rcodes_recv[i]); + } + } + } + if (stt == STATS_SUM) { + printf("duration: %.4f s\n", duration / 1000000.0); + } else { + printf("since: %.4fs until: %.4fs\n", rel_start_us / 1000000, rel_end_us / 1000000); + } + + pthread_mutex_unlock(&st->mutex); +} + +/* see https://github.com/DNS-OARC/dns-metrics/blob/main/dns-metrics.schema.json + * and https://github.com/DNS-OARC/dns-metrics/issues/16#issuecomment-2139462920 */ +void json_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt) +{ + assert(stt == STATS_PERIODIC || stt == STATS_SUM); + + jsonw_t *w = ctx->jw; + + pthread_mutex_lock(&st->mutex); + + jsonw_object(w, NULL); + { + jsonw_ulong(w, "runid", ctx->runid); + jsonw_str(w, "type", (stt == STATS_PERIODIC) ? "stats_periodic" : "stats_sum"); + jsonw_ulong(w, "since", st->since); + jsonw_ulong(w, "until", st->until); + jsonw_ulong(w, "queries", st->qry_sent); + jsonw_ulong(w, "responses", st->ans_recv); + + jsonw_object(w, "response_rcodes"); + { + for (size_t i = 0; i < RCODE_MAX; ++i) { + if (st->rcodes_recv[i] > 0) { + const knot_lookup_t *rc = knot_lookup_by_id(knot_rcode_names, i); + jsonw_ulong(w, (rc == NULL) ? "unknown" : rc->name, st->rcodes_recv[i]); + } + } + } + jsonw_end(w); + + jsonw_object(w, "conn_info"); + { + jsonw_str(w, "type", ctx->tcp ? "tcp" : (ctx->quic ? "quic_conn" : "udp")); + + // TODO: + // packets_sent + // packets_recieved + + jsonw_ulong(w, "socket_errors", st->errors); + if (ctx->tcp || ctx->quic) { + jsonw_ulong(w, "handshakes", st->synack_recv); + // TODO: handshakes_failed + if (ctx->quic) { + // TODO: conn_resumption + } + } + } + jsonw_end(w); + } + jsonw_end(w); + + pthread_mutex_unlock(&st->mutex); +} diff --git a/src/utils/kxdpgun/stats.h b/src/utils/kxdpgun/stats.h new file mode 100644 index 0000000..2c62ee3 --- /dev/null +++ b/src/utils/kxdpgun/stats.h @@ -0,0 +1,78 @@ +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stddef.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdbool.h> + +#include "utils/kxdpgun/main.h" + +#define RCODE_MAX (0x0F + 1) + +#define STATS_SECTION_SEP "--------------------------------------------------------------" + +#define JSON_INDENT " " +#define STATS_SCHEMA_VERSION 20240530 + +#define DURATION_US(st) (((st).until - (st).since) / 1000) +#define DURATION_NS(st) ((st).until - (st).since) + +#define JSON_MODE(ctx) ((ctx).jw != NULL) + +#define STATS_HDR(ctx) ((JSON_MODE(*(ctx)) ? json_stats_header : plain_stats_header)((ctx))) +#define STATS_THRD(ctx, stats) \ + ((JSON_MODE(*ctx) ? json_thrd_summary : plain_thrd_summary)((ctx), (stats))) +#define STATS_FMT(ctx, stats, stats_type) \ + ((JSON_MODE(*(ctx)) ? json_stats : plain_stats)((ctx), (stats), (stats_type))) + +typedef struct { + size_t collected; + uint64_t since, until; // nanosecs UNIX + uint64_t qry_sent; + uint64_t synack_recv; + uint64_t ans_recv; + uint64_t finack_recv; + uint64_t rst_recv; + uint64_t size_recv; + uint64_t wire_recv; + uint64_t errors; + uint64_t lost; + uint64_t rcodes_recv[RCODE_MAX]; + pthread_mutex_t mutex; +} kxdpgun_stats_t; + +typedef enum { + STATS_PERIODIC, + STATS_SUM, +} stats_type_t; + +void clear_stats(kxdpgun_stats_t *st); +size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what); +void collect_periodic_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what); + +void plain_stats_header(const xdp_gun_ctx_t *ctx); +void json_stats_header(const xdp_gun_ctx_t *ctx); + +void plain_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st); +void json_thrd_summary(const xdp_gun_ctx_t *ctx, const kxdpgun_stats_t *st); + +void plain_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt); +void json_stats(const xdp_gun_ctx_t *ctx, kxdpgun_stats_t *st, stats_type_t stt); + +extern pthread_mutex_t stdout_mtx; diff --git a/src/utils/kzonecheck/main.c b/src/utils/kzonecheck/main.c index b602cc9..e8c5868 100644 --- a/src/utils/kzonecheck/main.c +++ b/src/utils/kzonecheck/main.c @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) { "print", no_argument, NULL, 'p' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) /* Parse command line arguments */ int opt = 0; - while ((opt = getopt_long(argc, argv, "o:t:d:zpvVh", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "o:t:d:zpvV::h", opts, NULL)) != -1) { switch (opt) { case 'o': origin = optarg; @@ -105,7 +105,7 @@ int main(int argc, char *argv[]) print_help(); return EXIT_SUCCESS; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); return EXIT_SUCCESS; case 'd': optional = str2bool(optarg) ? SEMCHECK_DNSSEC_ON : SEMCHECK_DNSSEC_OFF; diff --git a/src/utils/kzonesign/main.c b/src/utils/kzonesign/main.c index e70abb6..641acfc 100644 --- a/src/utils/kzonesign/main.c +++ b/src/utils/kzonesign/main.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -117,7 +117,7 @@ static int zonesign(sign_params_t *params) goto fail; } - ret = knot_dnssec_validate_zone(&up, conf(), params->timestamp, false); + ret = knot_dnssec_validate_zone(&up, conf(), params->timestamp, false, false); if (ret != KNOT_EOK) { ERR2("DNSSEC validation failed (%s)", knot_strerror(ret)); char type_str[16]; @@ -204,7 +204,7 @@ int main(int argc, char *argv[]) { "verify" , no_argument, NULL, 'v' }, { "time", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, + { "version", optional_argument, NULL, 'V' }, { NULL } }; @@ -212,7 +212,7 @@ int main(int argc, char *argv[]) signal_init_std(); int opt = 0; - while ((opt = getopt_long(argc, argv, "c:C:o:rvt:hV", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:o:rvt:hV::", opts, NULL)) != -1) { switch (opt) { case 'c': if (util_conf_init_file(optarg) != KNOT_EOK) { @@ -245,7 +245,7 @@ int main(int argc, char *argv[]) print_help(); goto success; case 'V': - print_version(PROGRAM_NAME); + print_version(PROGRAM_NAME, optarg != NULL); goto success; default: print_help(); |