summaryrefslogtreecommitdiffstats
path: root/src/utils/kdig
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
commitf449f278dd3c70e479a035f50a9bb817a9b433ba (patch)
tree8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /src/utils/kdig
parentInitial commit. (diff)
downloadknot-f449f278dd3c70e479a035f50a9bb817a9b433ba.tar.xz
knot-f449f278dd3c70e479a035f50a9bb817a9b433ba.zip
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/utils/kdig/kdig_exec.c1309
-rw-r--r--src/utils/kdig/kdig_exec.h21
-rw-r--r--src/utils/kdig/kdig_main.c45
-rw-r--r--src/utils/kdig/kdig_params.c2740
-rw-r--r--src/utils/kdig/kdig_params.h187
5 files changed, 4302 insertions, 0 deletions
diff --git a/src/utils/kdig/kdig_exec.c b/src/utils/kdig/kdig_exec.c
new file mode 100644
index 0000000..391e674
--- /dev/null
+++ b/src/utils/kdig/kdig_exec.c
@@ -0,0 +1,1309 @@
+/* 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
+ 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 <stdlib.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include "utils/kdig/kdig_exec.h"
+#include "utils/common/exec.h"
+#include "utils/common/msg.h"
+#include "utils/common/netio.h"
+#include "utils/common/sign.h"
+#include "libknot/libknot.h"
+#include "contrib/json.h"
+#include "contrib/sockaddr.h"
+#include "contrib/time.h"
+#include "contrib/ucw/lists.h"
+
+#if USE_DNSTAP
+# include "contrib/dnstap/convert.h"
+# include "contrib/dnstap/message.h"
+# include "contrib/dnstap/writer.h"
+
+static int write_dnstap(dt_writer_t *writer,
+ const bool is_query,
+ const uint8_t *wire,
+ const size_t wire_len,
+ net_t *net,
+ const struct timespec *mtime)
+{
+ Dnstap__Message msg;
+ Dnstap__Message__Type msg_type;
+ int ret;
+ int protocol = 0;
+
+ if (writer == NULL) {
+ return KNOT_EOK;
+ }
+
+ if (net->local == NULL) {
+ net_set_local_info(net);
+ }
+
+ msg_type = is_query ? DNSTAP__MESSAGE__TYPE__TOOL_QUERY :
+ DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE;
+
+ if (net->socktype == SOCK_DGRAM) {
+ protocol = IPPROTO_UDP;
+ } else if (net->socktype == SOCK_STREAM) {
+ protocol = IPPROTO_TCP;
+ }
+
+ ret = dt_message_fill(&msg, msg_type, net->local_info->ai_addr,
+ net->srv->ai_addr, protocol,
+ wire, wire_len, mtime);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return dt_writer_write(writer, (const ProtobufCMessage *)&msg);
+}
+
+static float get_query_time(const Dnstap__Dnstap *frame)
+{
+ if (!frame->message->has_query_time_sec ||
+ !frame->message->has_query_time_nsec ||
+ !frame->message->has_response_time_sec ||
+ !frame->message->has_response_time_sec) {
+ return 0;
+ }
+
+ struct timespec from = {
+ .tv_sec = frame->message->query_time_sec,
+ .tv_nsec = frame->message->query_time_nsec
+ };
+
+ struct timespec to = {
+ .tv_sec = frame->message->response_time_sec,
+ .tv_nsec = frame->message->response_time_nsec
+ };
+
+ return time_diff_ms(&from, &to);
+}
+
+static void fill_remote_addr(net_t *net, Dnstap__Message *message, bool is_initiator)
+{
+ if (!message->has_socket_family || !message->has_socket_protocol) {
+ return;
+ }
+
+ if ((message->response_address.data == NULL && is_initiator) ||
+ message->query_address.data == NULL) {
+ return;
+ }
+
+ struct sockaddr_storage ss = { 0 };
+ int family = dt_family_decode(message->socket_family);
+ int proto = dt_protocol_decode(message->socket_protocol);
+ int sock_type = 0;
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ sock_type = SOCK_STREAM;
+ break;
+ case IPPROTO_UDP:
+ sock_type = SOCK_DGRAM;
+ break;
+ default:
+ break;
+ }
+
+ ProtobufCBinaryData *addr = NULL;
+ uint32_t port = 0;
+ if (is_initiator) {
+ addr = &message->response_address;
+ port = message->response_port;
+ } else {
+ addr = &message->query_address;
+ port = message->query_port;
+ }
+
+ sockaddr_set_raw(&ss, family, addr->data, addr->len);
+ sockaddr_port_set(&ss, port);
+
+ get_addr_str(&ss, sock_type, &net->remote_str);
+}
+
+static int process_dnstap(const query_t *query)
+{
+ dt_reader_t *reader = query->dt_reader;
+
+ if (query->dt_reader == NULL) {
+ return -1;
+ }
+
+ bool first_message = true;
+
+ for (;;) {
+ Dnstap__Dnstap *frame = NULL;
+ Dnstap__Message *message = NULL;
+ ProtobufCBinaryData *wire = NULL;
+ bool is_query;
+ bool is_initiator;
+
+ // Read next message.
+ int ret = dt_reader_read(reader, &frame);
+ if (ret == KNOT_EOF) {
+ break;
+ } else if (ret != KNOT_EOK) {
+ ERR("can't read dnstap message");
+ break;
+ }
+
+ // Check for dnstap message.
+ if (frame->type == DNSTAP__DNSTAP__TYPE__MESSAGE) {
+ message = frame->message;
+ } else {
+ WARN("ignoring non-dnstap message");
+ dt_reader_free_frame(reader, &frame);
+ continue;
+ }
+
+ // Check for the type of dnstap message.
+ if (message->has_response_message) {
+ wire = &message->response_message;
+ is_query = false;
+ } else if (message->has_query_message) {
+ wire = &message->query_message;
+ is_query = true;
+ } else {
+ WARN("dnstap frame contains no message");
+ dt_reader_free_frame(reader, &frame);
+ continue;
+ }
+
+ // Ignore query message if requested.
+ if (is_query && !query->style.show_query) {
+ dt_reader_free_frame(reader, &frame);
+ continue;
+ }
+
+ // Get the message role.
+ is_initiator = dt_message_role_is_initiator(message->type);
+
+ // Create dns packet based on dnstap wire data.
+ knot_pkt_t *pkt = knot_pkt_new(wire->data, wire->len, NULL);
+ if (pkt == NULL) {
+ ERR("can't allocate packet");
+ dt_reader_free_frame(reader, &frame);
+ break;
+ }
+
+ // Parse packet and reconstruct required data.
+ ret = knot_pkt_parse(pkt, KNOT_PF_NOCANON);
+ if (ret == KNOT_EOK || ret == KNOT_ETRAIL) {
+ time_t timestamp = 0;
+ float query_time = 0.0;
+ net_t net_ctx = { 0 };
+
+ if (ret == KNOT_ETRAIL) {
+ WARN("malformed message (%s)", knot_strerror(ret));
+ }
+
+ if (is_query) {
+ if (message->has_query_time_sec) {
+ timestamp = message->query_time_sec;
+ }
+ } else {
+ if (message->has_response_time_sec) {
+ timestamp = message->response_time_sec;
+ }
+ query_time = get_query_time(frame);
+ }
+
+ // Prepare connection information string.
+ fill_remote_addr(&net_ctx, message, is_initiator);
+
+ if (first_message) {
+ first_message = false;
+ } else {
+ printf("\n");
+ }
+
+ print_packet(pkt, &net_ctx, pkt->size, query_time, timestamp,
+ is_query ^ is_initiator, &query->style);
+
+ net_clean(&net_ctx);
+ } else {
+ ERR("can't print dnstap message");
+ }
+
+ knot_pkt_free(pkt);
+ dt_reader_free_frame(reader, &frame);
+ }
+
+ return 0;
+}
+#endif // USE_DNSTAP
+
+static int add_query_edns(knot_pkt_t *packet, const query_t *query, uint16_t max_size)
+{
+ /* Initialize OPT RR. */
+ knot_rrset_t opt_rr;
+ int ret = knot_edns_init(&opt_rr, max_size, 0,
+ query->edns > -1 ? query->edns : 0, &packet->mm);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (query->flags.do_flag) {
+ knot_edns_set_do(&opt_rr);
+ }
+
+ /* Append NSID. */
+ if (query->nsid) {
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_NSID,
+ 0, NULL, &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+ }
+
+ /* Append EDNS-client-subnet. */
+ if (query->subnet.family != AF_UNSPEC) {
+ uint16_t size = knot_edns_client_subnet_size(&query->subnet);
+ uint8_t data[size];
+
+ ret = knot_edns_client_subnet_write(data, size, &query->subnet);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_CLIENT_SUBNET,
+ size, data, &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+ }
+
+ /* Append a cookie option if present. */
+ if (query->cc.len > 0) {
+ uint16_t size = knot_edns_cookie_size(&query->cc, &query->sc);
+ uint8_t data[size];
+
+ ret = knot_edns_cookie_write(data, size, &query->cc, &query->sc);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_COOKIE,
+ size, data, &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+ }
+
+ /* Append EDNS Padding. */
+ int padding = query->padding;
+ if (padding != -3 && query->alignment > 0) {
+ padding = knot_edns_alignment_size(packet->size,
+ knot_rrset_size(&opt_rr),
+ query->alignment);
+ } else if (query->padding == -2 || (query->padding == -1 && query->tls.enable)) {
+ padding = knot_pkt_default_padding_size(packet, &opt_rr);
+ }
+ if (padding > -1) {
+ uint8_t zeros[padding];
+ memset(zeros, 0, sizeof(zeros));
+
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_PADDING,
+ padding, zeros, &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+ }
+
+ /* Append custom EDNS options. */
+ node_t *node;
+ WALK_LIST(node, query->edns_opts) {
+ ednsopt_t *opt = (ednsopt_t *)node;
+ ret = knot_edns_add_option(&opt_rr, opt->code, opt->length,
+ opt->data, &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ return ret;
+ }
+ }
+
+ /* Add prepared OPT to packet. */
+ ret = knot_pkt_put(packet, KNOT_COMPR_HINT_NONE, &opt_rr, KNOT_PF_FREE);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(&opt_rr, &packet->mm);
+ }
+
+ return ret;
+}
+
+static bool do_padding(const query_t *query)
+{
+ return (query->padding != -3) && // Disabled padding.
+ (query->padding > -1 || query->alignment > 0 || // Explicit padding.
+ query->padding == -2 || // Default padding.
+ (query->padding == -1 && query->tls.enable)); // TLS automatic.
+}
+
+static bool use_edns(const query_t *query)
+{
+ return query->edns > -1 || query->udp_size > -1 || query->nsid ||
+ query->flags.do_flag || query->subnet.family != AF_UNSPEC ||
+ query->cc.len > 0 || do_padding(query) ||
+ !ednsopt_list_empty(&query->edns_opts);
+}
+
+static knot_pkt_t *create_query_packet(const query_t *query)
+{
+ // Set packet buffer size.
+ uint16_t max_size;
+ if (query->udp_size < 0) {
+ if (use_edns(query)) {
+ max_size = DEFAULT_EDNS_SIZE;
+ } else {
+ max_size = DEFAULT_UDP_SIZE;
+ }
+ } else {
+ max_size = query->udp_size;
+ }
+
+ // Create packet skeleton.
+ knot_pkt_t *packet = create_empty_packet(max_size);
+ if (packet == NULL) {
+ return NULL;
+ }
+
+ // Set ID = 0 for packet send over HTTPS
+ // Due HTTP cache it is convenient to set the query ID to 0 - GET messages has same header then
+#if defined(LIBNGHTTP2) || defined(ENABLE_QUIC)
+ if (query->https.enable || query->quic.enable) {
+ knot_wire_set_id(packet->wire, 0);
+ }
+#endif
+
+ // Set flags to wireformat.
+ if (query->flags.aa_flag) {
+ knot_wire_set_aa(packet->wire);
+ }
+ if (query->flags.tc_flag) {
+ knot_wire_set_tc(packet->wire);
+ }
+ if (query->flags.rd_flag) {
+ knot_wire_set_rd(packet->wire);
+ }
+ if (query->flags.ra_flag) {
+ knot_wire_set_ra(packet->wire);
+ }
+ if (query->flags.z_flag) {
+ knot_wire_set_z(packet->wire);
+ }
+ if (query->flags.ad_flag) {
+ knot_wire_set_ad(packet->wire);
+ }
+ if (query->flags.cd_flag) {
+ knot_wire_set_cd(packet->wire);
+ }
+
+ // Set NOTIFY opcode.
+ if (query->notify) {
+ knot_wire_set_opcode(packet->wire, KNOT_OPCODE_NOTIFY);
+ }
+
+ // Set packet question if available.
+ knot_dname_t *qname = NULL;
+ if (query->owner != NULL) {
+ qname = knot_dname_from_str_alloc(query->owner);
+ if (qname == NULL) {
+ ERR("'%s' is not a valid domain name", query->owner);
+ knot_pkt_free(packet);
+ return NULL;
+ }
+
+ int ret = knot_pkt_put_question(packet, qname, query->class_num,
+ query->type_num);
+ if (ret != KNOT_EOK) {
+ knot_dname_free(qname, NULL);
+ knot_pkt_free(packet);
+ return NULL;
+ }
+ }
+
+ // For IXFR query or NOTIFY query with SOA serial, add a proper section.
+ if (query->serial >= 0) {
+ if (query->notify) {
+ knot_pkt_begin(packet, KNOT_ANSWER);
+ } else {
+ knot_pkt_begin(packet, KNOT_AUTHORITY);
+ }
+
+ // SOA rdata in wireformat.
+ uint8_t wire[22] = { 0x0 };
+
+ // Create rrset with SOA record.
+ knot_rrset_t *soa = knot_rrset_new(qname,
+ KNOT_RRTYPE_SOA,
+ query->class_num,
+ 0,
+ &packet->mm);
+ knot_dname_free(qname, NULL);
+ if (soa == NULL) {
+ knot_pkt_free(packet);
+ return NULL;
+ }
+
+ // Fill in blank SOA rdata to rrset.
+ int ret = knot_rrset_add_rdata(soa, wire, sizeof(wire), &packet->mm);
+ if (ret != KNOT_EOK) {
+ knot_rrset_free(soa, &packet->mm);
+ knot_pkt_free(packet);
+ return NULL;
+ }
+
+ // Set SOA serial.
+ knot_soa_serial_set(soa->rrs.rdata, query->serial);
+
+ ret = knot_pkt_put(packet, KNOT_COMPR_HINT_NONE, soa, KNOT_PF_FREE);
+ if (ret != KNOT_EOK) {
+ knot_rrset_free(soa, &packet->mm);
+ knot_pkt_free(packet);
+ return NULL;
+ }
+
+ free(soa);
+ } else {
+ knot_dname_free(qname, NULL);
+ }
+
+ // Begin additional section
+ knot_pkt_begin(packet, KNOT_ADDITIONAL);
+
+ // Create EDNS section if required.
+ if (use_edns(query)) {
+ int ret = add_query_edns(packet, query, max_size);
+ if (ret != KNOT_EOK) {
+ ERR("can't set up EDNS section");
+ knot_pkt_free(packet);
+ return NULL;
+ }
+ }
+
+ return packet;
+}
+
+static bool check_reply_id(const knot_pkt_t *reply,
+ const knot_pkt_t *query)
+{
+ uint16_t query_id = knot_wire_get_id(query->wire);
+ uint16_t reply_id = knot_wire_get_id(reply->wire);
+
+ if (reply_id != query_id) {
+ WARN("reply ID (%u) is different from query ID (%u)",
+ reply_id, query_id);
+ return false;
+ }
+
+ return true;
+}
+
+static void check_reply_qr(const knot_pkt_t *reply)
+{
+ if (!knot_wire_get_qr(reply->wire)) {
+ WARN("response QR bit not set");
+ }
+}
+
+static void check_reply_question(const knot_pkt_t *reply,
+ const knot_pkt_t *query)
+{
+ if (knot_wire_get_qdcount(reply->wire) < 1) {
+ WARN("response doesn't have question section");
+ return;
+ }
+
+ if (!knot_dname_is_equal(knot_pkt_wire_qname(reply), knot_pkt_wire_qname(query)) ||
+ knot_pkt_qclass(reply) != knot_pkt_qclass(query) ||
+ knot_pkt_qtype(reply) != knot_pkt_qtype(query)) {
+ WARN("query/response question sections are different");
+ return;
+ }
+}
+
+static int64_t first_serial_check(const knot_pkt_t *reply, const knot_pkt_t *query)
+{
+ const knot_pktsection_t *answer = knot_pkt_section(reply, KNOT_ANSWER);
+
+ if (answer->count <= 0) {
+ return -1;
+ }
+
+ const knot_rrset_t *first = knot_pkt_rr(answer, 0);
+
+ if (first->type != KNOT_RRTYPE_SOA) {
+ return -1;
+ } else {
+ if (!knot_dname_is_case_equal(first->owner, knot_pkt_qname(query))) {
+ WARN("leading SOA owner not matching the requested zone name");
+ }
+
+ return knot_soa_serial(first->rrs.rdata);
+ }
+}
+
+static bool finished_xfr(const uint32_t serial, const knot_pkt_t *reply,
+ const knot_pkt_t *query, const size_t msg_count, bool is_ixfr)
+{
+ const knot_pktsection_t *answer = knot_pkt_section(reply, KNOT_ANSWER);
+
+ if (answer->count <= 0) {
+ return false;
+ }
+
+ const knot_rrset_t *last = knot_pkt_rr(answer, answer->count - 1);
+
+ if (last->type != KNOT_RRTYPE_SOA) {
+ return false;
+ } else if (answer->count == 1 && msg_count == 1) {
+ return is_ixfr;
+ } else {
+ if (!knot_dname_is_case_equal(last->owner, knot_pkt_qname(query))) {
+ WARN("final SOA owner not matching the requested zone name");
+ }
+
+ return knot_soa_serial(last->rrs.rdata) == serial;
+ }
+}
+
+static int sign_query(knot_pkt_t *pkt, const query_t *query, sign_context_t *ctx)
+{
+ if (query->tsig_key.name == NULL) {
+ return KNOT_EOK;
+ }
+
+ int ret = sign_context_init_tsig(ctx, &query->tsig_key);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = sign_packet(pkt, ctx);
+ if (ret != KNOT_EOK) {
+ sign_context_deinit(ctx);
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+static void net_close_keepopen(net_t *net, const query_t *query)
+{
+ if (!query->keepopen) {
+ net_close(net);
+ }
+}
+
+static int process_query_packet(const knot_pkt_t *query,
+ net_t *net,
+ const query_t *query_ctx,
+ const bool ignore_tc,
+ const sign_context_t *sign_ctx,
+ const style_t *style)
+{
+ struct timespec t_start, t_query, t_query_full, t_end, t_end_full;
+ time_t timestamp;
+ knot_pkt_t *reply = NULL;
+ uint8_t in[MAX_PACKET_SIZE];
+ int in_len;
+ int ret;
+
+ // Get start query time.
+ timestamp = time(NULL);
+ t_start = time_now();
+
+ // Connect to the server if not already connected.
+ if (net->sockfd < 0) {
+ ret = net_connect(net);
+ if (ret != KNOT_EOK) {
+ return -1;
+ }
+ }
+
+ // Send query packet.
+ ret = net_send(net, query->wire, query->size);
+ if (ret != KNOT_EOK) {
+ net_close(net);
+ return -1;
+ }
+
+ // Get stop query time and start reply time.
+ t_query = time_now();
+ t_query_full = time_diff(&t_start, &t_query);
+ t_query_full.tv_sec += timestamp;
+
+#if USE_DNSTAP
+ // Make the dnstap copy of the query.
+ write_dnstap(query_ctx->dt_writer, true, query->wire, query->size,
+ net, &t_query_full);
+#endif // USE_DNSTAP
+
+ // Print query packet if required.
+ if (style->show_query && style->format != FORMAT_JSON) {
+ // Create copy of query packet for parsing.
+ knot_pkt_t *q = knot_pkt_new(query->wire, query->size, NULL);
+ if (q != NULL) {
+ if (knot_pkt_parse(q, KNOT_PF_NOCANON) == KNOT_EOK) {
+ print_packet(q, net, query->size,
+ time_diff_ms(&t_start, &t_query),
+ timestamp, false, style);
+ } else {
+ ERR("can't print query packet");
+ }
+ knot_pkt_free(q);
+ } else {
+ ERR("can't print query packet");
+ }
+
+ printf("\n");
+ }
+
+ // Loop over incoming messages, unless reply id is correct or timeout.
+ while (true) {
+ reply = NULL;
+
+ // Receive a reply message.
+ in_len = net_receive(net, in, sizeof(in));
+ if (in_len <= 0) {
+ goto fail;
+ }
+
+ // Get stop reply time.
+ t_end = time_now();
+ t_end_full = time_diff(&t_start, &t_end);
+ t_end_full.tv_sec += timestamp;
+
+#if USE_DNSTAP
+ // Make the dnstap copy of the response.
+ write_dnstap(query_ctx->dt_writer, false, in, in_len, net,
+ &t_end_full);
+#endif // USE_DNSTAP
+
+ // Create reply packet structure to fill up.
+ reply = knot_pkt_new(in, in_len, NULL);
+ if (reply == NULL) {
+ ERR("internal error (%s)", knot_strerror(KNOT_ENOMEM));
+ goto fail;
+ }
+
+ // Parse reply to the packet structure.
+ ret = knot_pkt_parse(reply, KNOT_PF_NOCANON);
+ if (ret == KNOT_ETRAIL) {
+ WARN("malformed reply packet (%s)", knot_strerror(ret));
+ } else if (ret != KNOT_EOK) {
+ ERR("malformed reply packet from %s", net->remote_str);
+ goto fail;
+ }
+
+ // Compare reply header id.
+ if (check_reply_id(reply, query)) {
+ break;
+ // Check for timeout.
+ } else if (time_diff_ms(&t_query, &t_end) > 1000 * net->wait) {
+ goto fail;
+ }
+
+ knot_pkt_free(reply);
+ }
+
+ // Check for TC bit and repeat query with TCP if required.
+ if (knot_wire_get_tc(reply->wire) != 0 &&
+ ignore_tc == false && net->socktype == SOCK_DGRAM) {
+ printf("\n");
+ WARN("truncated reply from %s, retrying over TCP\n",
+ net->remote_str);
+ knot_pkt_free(reply);
+ net_close_keepopen(net, query_ctx);
+
+ net->socktype = SOCK_STREAM;
+
+ return process_query_packet(query, net, query_ctx, true,
+ sign_ctx, style);
+ }
+
+ // Check for question sections equality.
+ check_reply_question(reply, query);
+
+ // Check QR bit
+ check_reply_qr(reply);
+
+ // Print reply packet.
+ if (style->format != FORMAT_JSON) {
+ print_packet(reply, net, in_len, time_diff_ms(&t_start, &t_end),
+ timestamp, true, style);
+ } else {
+ knot_pkt_t *q = knot_pkt_new(query->wire, query->size, NULL);
+ (void)knot_pkt_parse(q, KNOT_PF_NOCANON);
+ print_packets_json(q, reply, net, timestamp, style);
+ knot_pkt_free(q);
+ }
+
+ // Verify signature if a key was specified.
+ if (sign_ctx->digest != NULL) {
+ ret = verify_packet(reply, sign_ctx);
+ if (ret != KNOT_EOK) {
+ WARN("reply verification for %s (%s)",
+ net->remote_str, knot_strerror(ret));
+ }
+ }
+
+ // Check for BADCOOKIE RCODE and repeat query with the new cookie if required.
+ if (knot_pkt_ext_rcode(reply) == KNOT_RCODE_BADCOOKIE && query_ctx->badcookie > 0) {
+ printf("\n");
+ WARN("bad cookie from %s, retrying with the received one\n",
+ net->remote_str);
+ net_close_keepopen(net, query_ctx);
+
+ // Prepare new query context.
+ query_t new_ctx = *query_ctx;
+
+ uint8_t *opt = knot_pkt_edns_option(reply, KNOT_EDNS_OPTION_COOKIE);
+ if (opt == NULL) {
+ ERR("bad cookie, missing EDNS section");
+ goto fail;
+ }
+
+ const uint8_t *data = knot_edns_opt_get_data(opt);
+ uint16_t data_len = knot_edns_opt_get_length(opt);
+ ret = knot_edns_cookie_parse(&new_ctx.cc, &new_ctx.sc, data, data_len);
+ if (ret != KNOT_EOK) {
+ ERR("bad cookie, missing EDNS cookie option");
+ goto fail;
+ }
+ knot_pkt_free(reply);
+
+ // Restore the original client cookie.
+ new_ctx.cc = query_ctx->cc;
+
+ new_ctx.badcookie--;
+
+ knot_pkt_t *new_query = create_query_packet(&new_ctx);
+ ret = process_query_packet(new_query, net, &new_ctx, ignore_tc,
+ sign_ctx, style);
+ knot_pkt_free(new_query);
+
+ return ret;
+ }
+
+ knot_pkt_free(reply);
+ net_close_keepopen(net, query_ctx);
+
+ return 0;
+
+fail:
+ if (style->format == FORMAT_JSON) {
+ knot_pkt_t *q = knot_pkt_new(query->wire, query->size, NULL);
+ (void)knot_pkt_parse(q, KNOT_PF_NOCANON);
+ print_packets_json(q, reply, net, timestamp, style);
+ knot_pkt_free(q);
+ }
+
+ knot_pkt_free(reply);
+ net_close(net);
+
+ return -1;
+}
+
+static int process_query(const query_t *query, net_t *net)
+{
+ node_t *server;
+ knot_pkt_t *out_packet;
+ int ret;
+
+ // Create query packet.
+ out_packet = create_query_packet(query);
+ if (out_packet == NULL) {
+ ERR("can't create query packet");
+ return -1;
+ }
+
+ // Sign the query.
+ sign_context_t sign_ctx = { 0 };
+ ret = sign_query(out_packet, query, &sign_ctx);
+ if (ret != KNOT_EOK) {
+ ERR("can't sign the packet (%s)", knot_strerror(ret));
+ return -1;
+ }
+
+ // Reuse previous connection if available.
+ if (net->sockfd >= 0) {
+ DBG("Querying for owner(%s), class(%u), type(%u), reused connection",
+ query->owner, query->class_num, query->type_num);
+
+ ret = process_query_packet(out_packet, net, query, query->ignore_tc,
+ &sign_ctx, &query->style);
+ goto finish;
+ }
+
+ // Get connection parameters.
+ int socktype = get_socktype(query->protocol, query->type_num);
+ int flags = query->fastopen ? NET_FLAGS_FASTOPEN : NET_FLAGS_NONE;
+
+ // Loop over server list to process query.
+ WALK_LIST(server, query->servers) {
+ srv_info_t *remote = (srv_info_t *)server;
+ int iptype = get_iptype(query->ip, remote);
+
+ DBG("Querying for owner(%s), class(%u), type(%u), server(%s), "
+ "port(%s), protocol(%s)", query->owner, query->class_num,
+ query->type_num, remote->name, remote->service,
+ get_sockname(socktype));
+
+ // Loop over the number of retries.
+ for (size_t i = 0; i <= query->retries; i++) {
+ // Initialize network structure for current server.
+ ret = net_init(query->local, remote, iptype, socktype,
+ query->wait, flags, &query->tls,
+ &query->https, &query->quic,
+ (struct sockaddr *)&query->proxy.src,
+ (struct sockaddr *)&query->proxy.dst,
+ net);
+ if (ret != KNOT_EOK) {
+ if (ret == KNOT_NET_EADDR) {
+ // Requested address family not available.
+ goto next_server;
+ }
+ continue;
+ }
+
+ // Loop over all resolved addresses for remote.
+ while (net->srv != NULL) {
+ ret = process_query_packet(out_packet, net,
+ query,
+ query->ignore_tc,
+ &sign_ctx,
+ &query->style);
+ // If error try next resolved address.
+ if (ret != 0) {
+ net->srv = net->srv->ai_next;
+ if (net->srv != NULL && query->style.show_query) {
+ printf("\n");
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ // Success.
+ if (ret == 0) {
+ goto finish;
+ }
+
+ if (i < query->retries) {
+ DBG("retrying server %s@%s(%s)",
+ remote->name, remote->service,
+ get_sockname(socktype));
+
+ if (query->style.show_query) {
+ printf("\n");
+ }
+ }
+
+ net_clean(net);
+ }
+
+ ERR("failed to query server %s@%s(%s)",
+ remote->name, remote->service, get_sockname(socktype));
+
+ // If not last server, print separation.
+ if (server->next->next && query->style.show_query) {
+ printf("\n");
+ }
+next_server:
+ continue;
+ }
+finish:
+ if (!query->keepopen || net->sockfd < 0) {
+ net_clean(net);
+ }
+ sign_context_deinit(&sign_ctx);
+ knot_pkt_free(out_packet);
+
+ if (ret == KNOT_NET_EADDR) {
+ WARN("no servers to query");
+ }
+
+ return ret;
+}
+
+static int process_xfr_packet(const knot_pkt_t *query,
+ net_t *net,
+ const query_t *query_ctx,
+ const sign_context_t *sign_ctx,
+ const style_t *style)
+{
+ struct timespec t_start, t_query, t_query_full, t_end, t_end_full;
+ time_t timestamp;
+ knot_pkt_t *reply = NULL;
+ uint8_t in[MAX_PACKET_SIZE];
+ int in_len;
+ int ret;
+ int64_t serial = 0;
+ size_t total_len = 0;
+ size_t msg_count = 0;
+ size_t rr_count = 0;
+ jsonw_t *w = NULL;
+
+ // Get start query time.
+ timestamp = time(NULL);
+ t_start = time_now();
+
+ // Connect to the server if not already connected.
+ if (net->sockfd < 0) {
+ ret = net_connect(net);
+ if (ret != KNOT_EOK) {
+ return -1;
+ }
+ }
+
+ // Send query packet.
+ ret = net_send(net, query->wire, query->size);
+ if (ret != KNOT_EOK) {
+ net_close(net);
+ return -1;
+ }
+
+ // Get stop query time and start reply time.
+ t_query = time_now();
+ t_query_full = time_diff(&t_start, &t_query);
+ t_query_full.tv_sec += timestamp;
+
+#if USE_DNSTAP
+ // Make the dnstap copy of the query.
+ write_dnstap(query_ctx->dt_writer, true, query->wire, query->size,
+ net, &t_query_full);
+#endif // USE_DNSTAP
+
+ // Print query packet if required.
+ if (style->show_query && style->format != FORMAT_JSON) {
+ // Create copy of query packet for parsing.
+ knot_pkt_t *q = knot_pkt_new(query->wire, query->size, NULL);
+ if (q != NULL) {
+ if (knot_pkt_parse(q, KNOT_PF_NOCANON) == KNOT_EOK) {
+ print_packet(q, net, query->size,
+ time_diff_ms(&t_start, &t_query),
+ timestamp, false, style);
+ } else {
+ ERR("can't print query packet");
+ }
+ knot_pkt_free(q);
+ } else {
+ ERR("can't print query packet");
+ }
+
+ printf("\n");
+ }
+
+ // Loop over reply messages unless first and last SOA serials differ.
+ while (true) {
+ reply = NULL;
+
+ // Receive a reply message.
+ in_len = net_receive(net, in, sizeof(in));
+ if (in_len <= 0) {
+ goto fail;
+ }
+
+ // Get stop message time.
+ t_end = time_now();
+ t_end_full = time_diff(&t_start, &t_end);
+ t_end_full.tv_sec += timestamp;
+
+#if USE_DNSTAP
+ // Make the dnstap copy of the response.
+ write_dnstap(query_ctx->dt_writer, false, in, in_len, net,
+ &t_end_full);
+#endif // USE_DNSTAP
+
+ // Create reply packet structure to fill up.
+ reply = knot_pkt_new(in, in_len, NULL);
+ if (reply == NULL) {
+ ERR("internal error (%s)", knot_strerror(KNOT_ENOMEM));
+ goto fail;
+ }
+
+ // Parse reply to the packet structure.
+ ret = knot_pkt_parse(reply, KNOT_PF_NOCANON);
+ if (ret == KNOT_ETRAIL) {
+ WARN("malformed reply packet (%s)", knot_strerror(ret));
+ } else if (ret != KNOT_EOK) {
+ ERR("malformed reply packet from %s", net->remote_str);
+ goto fail;
+ }
+
+ // Compare reply header id.
+ if (check_reply_id(reply, query) == false) {
+ ERR("reply ID mismatch from %s", net->remote_str);
+ goto fail;
+ }
+
+ // Print leading transfer information.
+ if (msg_count == 0) {
+ if (style->format != FORMAT_JSON) {
+ print_header_xfr(query, style);
+ } else {
+ knot_pkt_t *q = knot_pkt_new(query->wire, query->size, NULL);
+ (void)knot_pkt_parse(q, KNOT_PF_NOCANON);
+ w = print_header_xfr_json(q, timestamp, style);
+ knot_pkt_free(q);
+ }
+ }
+
+ // Check for error reply.
+ if (knot_pkt_ext_rcode(reply) != KNOT_RCODE_NOERROR) {
+ ERR("server replied with error '%s'",
+ knot_pkt_ext_rcode_name(reply));
+ goto fail;
+ }
+
+ // The first message has a special treatment.
+ if (msg_count == 0) {
+ // Verify 1. signature if a key was specified.
+ if (sign_ctx->digest != NULL) {
+ ret = verify_packet(reply, sign_ctx);
+ if (ret != KNOT_EOK) {
+ style_t tsig_style = {
+ .format = style->format,
+ .style = style->style,
+ .show_tsig = true
+ };
+ if (style->format != FORMAT_JSON) {
+ print_data_xfr(reply, &tsig_style);
+ }
+
+ ERR("reply verification for %s (%s)",
+ net->remote_str, knot_strerror(ret));
+ goto fail;
+ }
+ }
+
+ // Read first SOA serial.
+ serial = first_serial_check(reply, query);
+
+ if (serial < 0) {
+ ERR("first answer record from %s isn't SOA",
+ net->remote_str);
+ goto fail;
+ }
+
+ // Check for question sections equality.
+ check_reply_question(reply, query);
+
+ // Check QR bit
+ check_reply_qr(reply);
+ }
+
+ msg_count++;
+ rr_count += knot_wire_get_ancount(reply->wire);
+ total_len += in_len;
+
+ // Print reply packet.
+ if (style->format != FORMAT_JSON) {
+ print_data_xfr(reply, style);
+ } else {
+ print_data_xfr_json(w, reply, timestamp);
+ }
+
+ // Check for finished transfer.
+ if (finished_xfr(serial, reply, query, msg_count, query_ctx->serial != -1)) {
+ knot_pkt_free(reply);
+ break;
+ }
+
+ knot_pkt_free(reply);
+ reply = NULL;
+ }
+
+ // Print full transfer information.
+ t_end = time_now();
+ if (style->format != FORMAT_JSON) {
+ print_footer_xfr(total_len, msg_count, rr_count, net,
+ time_diff_ms(&t_query, &t_end), timestamp, style);
+ } else {
+ print_footer_xfr_json(&w, style);
+ }
+
+ net_close_keepopen(net, query_ctx);
+
+ return 0;
+
+fail:
+ // Print partial transfer information.
+ t_end = time_now();
+ if (style->format != FORMAT_JSON) {
+ print_footer_xfr(total_len, msg_count, rr_count, net,
+ time_diff_ms(&t_query, &t_end), timestamp, style);
+ } else {
+ print_data_xfr_json(w, reply, timestamp);
+ print_footer_xfr_json(&w, style);
+ }
+
+ knot_pkt_free(reply);
+ net_close(net);
+ free(w);
+
+ return -1;
+}
+
+static int process_xfr(const query_t *query, net_t *net)
+{
+ knot_pkt_t *out_packet;
+ int ret;
+
+ // Create query packet.
+ out_packet = create_query_packet(query);
+ if (out_packet == NULL) {
+ ERR("can't create query packet");
+ return -1;
+ }
+
+ // Sign the query.
+ sign_context_t sign_ctx = { 0 };
+ ret = sign_query(out_packet, query, &sign_ctx);
+ if (ret != KNOT_EOK) {
+ ERR("can't sign the packet (%s)", knot_strerror(ret));
+ return -1;
+ }
+
+ // Reuse previous connection if available.
+ if (net->sockfd >= 0) {
+ DBG("Querying for owner(%s), class(%u), type(%u), reused connection",
+ query->owner, query->class_num, query->type_num);
+
+ ret = process_xfr_packet(out_packet, net, query,
+ &sign_ctx, &query->style);
+ goto finish;
+ }
+
+ // Get connection parameters.
+ int socktype = get_socktype(query->protocol, query->type_num);
+ int flags = query->fastopen ? NET_FLAGS_FASTOPEN : NET_FLAGS_NONE;
+
+ // Use the first nameserver from the list.
+ srv_info_t *remote = HEAD(query->servers);
+ int iptype = get_iptype(query->ip, remote);
+
+ DBG("Querying for owner(%s), class(%u), type(%u), server(%s), "
+ "port(%s), protocol(%s)", query->owner, query->class_num,
+ query->type_num, remote->name, remote->service,
+ get_sockname(socktype));
+
+ // Initialize network structure.
+ ret = net_init(query->local, remote, iptype, socktype, query->wait,
+ flags, &query->tls, &query->https, &query->quic,
+ (struct sockaddr *)&query->proxy.src,
+ (struct sockaddr *)&query->proxy.dst,
+ net);
+ if (ret != KNOT_EOK) {
+ sign_context_deinit(&sign_ctx);
+ knot_pkt_free(out_packet);
+ return -1;
+ }
+
+ // Loop over all resolved addresses for remote.
+ while (net->srv != NULL) {
+ ret = process_xfr_packet(out_packet, net,
+ query,
+ &sign_ctx,
+ &query->style);
+ // If error try next resolved address.
+ if (ret != 0) {
+ net->srv = (net->srv)->ai_next;
+ continue;
+ }
+
+ break;
+ }
+
+ if (ret != 0) {
+ ERR("failed to query server %s@%s(%s)",
+ remote->name, remote->service, get_sockname(socktype));
+ }
+finish:
+ if (!query->keepopen || net->sockfd < 0) {
+ net_clean(net);
+ }
+ sign_context_deinit(&sign_ctx);
+ knot_pkt_free(out_packet);
+
+ return ret;
+}
+
+int kdig_exec(const kdig_params_t *params)
+{
+ node_t *n;
+ net_t net = { .sockfd = -1 };
+
+ if (params == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ bool success = true;
+
+ // Loop over query list.
+ WALK_LIST(n, params->queries) {
+ query_t *query = (query_t *)n;
+
+ int ret = -1;
+ switch (query->operation) {
+ case OPERATION_QUERY:
+ ret = process_query(query, &net);
+ break;
+ case OPERATION_XFR:
+ ret = process_xfr(query, &net);
+ break;
+#if USE_DNSTAP
+ case OPERATION_LIST_DNSTAP:
+ ret = process_dnstap(query);
+ break;
+#endif // USE_DNSTAP
+ default:
+ ERR("unsupported operation");
+ break;
+ }
+
+ // All operations must succeed.
+ if (ret != 0) {
+ success = false;
+ }
+
+ // If not last query, print separation.
+ if (n->next->next && params->config->style.format == FORMAT_FULL) {
+ printf("\n");
+ }
+ }
+
+ if (net.sockfd >= 0) {
+ net_close(&net);
+ net_clean(&net);
+ }
+
+ return success ? KNOT_EOK : KNOT_ERROR;
+}
diff --git a/src/utils/kdig/kdig_exec.h b/src/utils/kdig/kdig_exec.h
new file mode 100644
index 0000000..99167ce
--- /dev/null
+++ b/src/utils/kdig/kdig_exec.h
@@ -0,0 +1,21 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "utils/kdig/kdig_params.h"
+
+int kdig_exec(const kdig_params_t *params);
diff --git a/src/utils/kdig/kdig_main.c b/src/utils/kdig/kdig_main.c
new file mode 100644
index 0000000..534d50e
--- /dev/null
+++ b/src/utils/kdig/kdig_main.c
@@ -0,0 +1,45 @@
+/* Copyright (C) 2018 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 <stdlib.h>
+
+#include "libdnssec/crypto.h"
+#include "utils/kdig/kdig_params.h"
+#include "utils/kdig/kdig_exec.h"
+#include "libknot/libknot.h"
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_SUCCESS;
+
+ tzset();
+
+ kdig_params_t params;
+ if (kdig_parse(&params, argc, argv) == KNOT_EOK) {
+ if (!params.stop) {
+ dnssec_crypto_init();
+ if (kdig_exec(&params) != KNOT_EOK) {
+ ret = EXIT_FAILURE;
+ }
+ dnssec_crypto_cleanup();
+ }
+ } else {
+ ret = EXIT_FAILURE;
+ }
+
+ kdig_clean(&params);
+ return ret;
+}
diff --git a/src/utils/kdig/kdig_params.c b/src/utils/kdig/kdig_params.c
new file mode 100644
index 0000000..fce9405
--- /dev/null
+++ b/src/utils/kdig/kdig_params.c
@@ -0,0 +1,2740 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "utils/kdig/kdig_params.h"
+#include "utils/common/cert.h"
+#include "utils/common/hex.h"
+#include "utils/common/msg.h"
+#include "utils/common/netio.h"
+#include "utils/common/params.h"
+#include "utils/common/resolv.h"
+#include "libknot/descriptor.h"
+#include "libknot/libknot.h"
+#include "contrib/base64.h"
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+#include "contrib/strtonum.h"
+#include "contrib/time.h"
+#include "contrib/ucw/lists.h"
+#include "libdnssec/error.h"
+#include "libdnssec/random.h"
+
+#define PROGRAM_NAME "kdig"
+
+#define DEFAULT_RETRIES_DIG 2
+#define DEFAULT_TIMEOUT_DIG 5
+#define DEFAULT_ALIGNMENT_SIZE 128
+#define DEFAULT_TLS_OCSP_STAPLING (7 * 24 * 3600)
+
+#define BADCOOKIE_RETRY_MAX 10
+
+static const flags_t DEFAULT_FLAGS_DIG = {
+ .aa_flag = false,
+ .tc_flag = false,
+ .rd_flag = true,
+ .ra_flag = false,
+ .z_flag = false,
+ .ad_flag = true,
+ .cd_flag = false,
+ .do_flag = false
+};
+
+static const style_t DEFAULT_STYLE_DIG = {
+ .format = FORMAT_FULL,
+ .style = {
+ .wrap = false,
+ .show_class = true,
+ .show_ttl = true,
+ .verbose = false,
+ .original_ttl = false,
+ .empty_ttl = false,
+ .human_ttl = false,
+ .human_timestamp = true,
+ .generic = false,
+ .ascii_to_idn = name_to_idn
+ },
+ .show_query = false,
+ .show_header = true,
+ .show_section = true,
+ .show_edns = true,
+ .show_question = true,
+ .show_answer = true,
+ .show_authority = true,
+ .show_additional = true,
+ .show_tsig = true,
+ .show_footer = true
+};
+
+static int opt_multiline(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.wrap = true;
+ q->style.format = FORMAT_FULL;
+ q->style.show_header = true;
+ q->style.show_edns = true;
+ q->style.show_footer = true;
+ q->style.style.verbose = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nomultiline(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.wrap = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_short(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_DIG;
+ q->style.show_header = false;
+ q->style.show_edns = false;
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_noshort(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_FULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_generic(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.generic = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nogeneric(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.generic = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_aaflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.aa_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noaaflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.aa_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tcflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.tc_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_notcflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.tc_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_rdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.rd_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nordflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.rd_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_raflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ra_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noraflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ra_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_zflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.z_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nozflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.z_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_adflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ad_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noadflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.ad_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_cdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.cd_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocdflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.cd_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_doflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.do_flag = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nodoflag(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->flags.do_flag = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_all(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = true;
+ q->style.show_section = true;
+ q->style.show_edns = true;
+ q->style.show_question = true;
+ q->style.show_answer = true;
+ q->style.show_authority = true;
+ q->style.show_additional = true;
+ q->style.show_tsig = true;
+ q->style.show_footer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noall(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = false;
+ q->style.show_section = false;
+ q->style.show_edns = false;
+ q->style.show_query = false;
+ q->style.show_question = false;
+ q->style.show_answer = false;
+ q->style.show_authority = false;
+ q->style.show_additional = false;
+ q->style.show_tsig = false;
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_qr(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_query = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noqr(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_query = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_header(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noheader(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_header = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_comments(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_section = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocomments(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_section = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_opt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_opttext(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns_opt_text = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noopttext(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_edns_opt_text = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_question(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_question = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noquestion(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_question = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_answer(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_answer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noanswer(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_answer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_authority(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_authority = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noauthority(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_authority = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_additional(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_additional = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noadditional(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_additional = false;
+ q->style.show_edns = false;
+ q->style.show_tsig = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tsig(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_tsig = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_notsig(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_tsig = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_stats(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_footer = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nostats(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.show_footer = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_class(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_class = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noclass(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_class = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_ttl(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_ttl = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nottl(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.show_ttl = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_ignore(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->ignore_tc = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_noignore(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->ignore_tc = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_crypto(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.hide_crypto = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_nocrypto(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.style.hide_crypto = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_tcp(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->protocol = PROTO_TCP;
+
+ return KNOT_EOK;
+}
+
+static int opt_notcp(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->protocol = PROTO_UDP;
+ return opt_ignore(arg, query);
+}
+
+static int opt_fastopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->fastopen = true;
+
+ return opt_tcp(arg, query);
+}
+
+static int opt_nofastopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->fastopen = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_keepopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->keepopen = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nokeepopen(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->keepopen = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.enable = true;
+ return opt_tcp(arg, query);
+}
+
+static int opt_notls(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ tls_params_clean(&q->tls);
+ tls_params_init(&q->tls);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_ca(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->tls.system_ca = true;
+ return opt_tls(arg, query);
+ } else {
+ if (ptrlist_add(&q->tls.ca_files, strdup(arg), NULL) == NULL) {
+ return KNOT_ENOMEM;
+ }
+ return opt_tls(arg, query);
+ }
+}
+
+static int opt_notls_ca(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.system_ca = false;
+
+ ptrnode_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->tls.ca_files) {
+ free(node->d);
+ }
+ ptrlist_free(&q->tls.ca_files, NULL);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_pin(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ uint8_t pin[64] = { 0 };
+
+ int ret = knot_base64_decode((const uint8_t *)arg, strlen(arg), pin, sizeof(pin));
+ if (ret < 0) {
+ ERR("invalid +tls-pin=%s", arg);
+ return ret;
+ } else if (ret != CERT_PIN_LEN) { // Check for 256-bit value.
+ ERR("invalid sha256 hash length +tls-pin=%s", arg);
+ 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(&q->tls.pins, item, NULL) == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_pin(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ptrnode_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->tls.pins) {
+ free(node->d);
+ }
+ ptrlist_free(&q->tls.pins, NULL);
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_hostname(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.hostname);
+ q->tls.hostname = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_hostname(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.hostname);
+ q->tls.hostname = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_sni(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.sni);
+ q->tls.sni = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_sni(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.sni);
+ q->tls.sni = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_keyfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.keyfile);
+ q->tls.keyfile = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_keyfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.keyfile);
+ q->tls.keyfile = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_certfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.certfile);
+ q->tls.certfile = strdup(arg);
+
+ return opt_tls(arg, query);
+}
+
+static int opt_notls_certfile(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ free(q->tls.certfile);
+ q->tls.certfile = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_tls_ocsp_stapling(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->tls.ocsp_stapling = DEFAULT_TLS_OCSP_STAPLING;
+ return opt_tls(arg, query);
+ } else {
+ uint32_t num = 0;
+ if (str_to_u32(arg, &num) != KNOT_EOK || num == 0) {
+ ERR("invalid +tls-ocsp-stapling=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->tls.ocsp_stapling = 3600 * num;
+ return opt_tls(arg, query);
+ }
+}
+
+static int opt_notls_ocsp_stapling(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->tls.ocsp_stapling = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_https(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.enable = true;
+
+ if (arg != NULL) {
+ char *resource = strstr(arg, "://");
+ if (resource == NULL) {
+ resource = (char *)arg;
+ } else {
+ resource += 3; // strlen("://")
+ if (*resource == '\0') {
+ ERR("invalid +https=%s", arg);
+ return KNOT_EINVAL;
+ }
+ }
+
+ char *tmp_path = strchr(resource, '/');
+ if (tmp_path) {
+ free(q->https.path);
+ q->https.path = strdup(tmp_path);
+
+ if (tmp_path != resource) {
+ free(q->tls.hostname);
+ q->tls.hostname = strndup(resource, (size_t)(tmp_path - resource));
+ }
+ return opt_tls(arg, query);
+ } else {
+ return opt_tls_hostname(arg, query);
+ }
+
+ }
+
+ return opt_tls(arg, query);
+
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_nohttps(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ https_params_clean(&q->https);
+
+ return opt_notls(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_https_get(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.method = GET;
+
+ return opt_https(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //LIBNGHTTP2
+}
+
+static int opt_nohttps_get(const char *arg, void *query)
+{
+#ifdef LIBNGHTTP2
+ query_t *q = query;
+
+ q->https.method = POST;
+
+ return KNOT_EOK;
+#else
+ return KNOT_ENOTSUP;
+#endif
+}
+
+static int opt_quic(const char *arg, void *query)
+{
+#ifdef ENABLE_QUIC
+ query_t *q = query;
+
+ q->quic.enable = true;
+
+ opt_tls(arg, query);
+ opt_notcp(arg, query);
+
+ return KNOT_EOK;
+#else
+ return KNOT_ENOTSUP;
+#endif //ENABLE_QUIC
+}
+
+static int opt_noquic(const char *arg, void *query)
+{
+#ifdef ENABLE_QUIC
+ query_t *q = query;
+
+ quic_params_clean(&q->quic);
+
+ return opt_notls(arg, query);
+#else
+ return KNOT_ENOTSUP;
+#endif //ENABLE_QUIC
+}
+
+static int opt_nsid(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->nsid = true;
+
+ return KNOT_EOK;
+}
+
+static int opt_nonsid(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->nsid = false;
+
+ return KNOT_EOK;
+}
+
+static int opt_bufsize(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK) {
+ ERR("invalid +bufsize=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ // Disable EDNS if zero bufsize.
+ if (num == 0) {
+ q->udp_size = -1;
+ } else if (num < KNOT_WIRE_HEADER_SIZE) {
+ q->udp_size = KNOT_WIRE_HEADER_SIZE;
+ } else {
+ q->udp_size = num;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nobufsize(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->udp_size = -1;
+
+ return KNOT_EOK;
+}
+
+static int opt_cookie(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg != NULL) {
+ uint8_t *input = NULL;
+ size_t input_len;
+
+ int ret = hex_decode(arg, &input, &input_len);
+ if (ret != KNOT_EOK) {
+ ERR("invalid +cookie=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ if (input_len < KNOT_EDNS_COOKIE_CLNT_SIZE) {
+ ERR("too short client +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ q->cc.len = KNOT_EDNS_COOKIE_CLNT_SIZE;
+ memcpy(q->cc.data, input, q->cc.len);
+
+ input_len -= q->cc.len;
+ if (input_len > 0) {
+ if (input_len < KNOT_EDNS_COOKIE_SRVR_MIN_SIZE) {
+ ERR("too short server +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ if (input_len > KNOT_EDNS_COOKIE_SRVR_MAX_SIZE) {
+ ERR("too long server +cookie=%s", arg);
+ free(input);
+ return KNOT_EINVAL;
+ }
+ q->sc.len = input_len;
+ memcpy(q->sc.data, input + q->cc.len, q->sc.len);
+ }
+
+ free(input);
+ } else {
+ q->cc.len = KNOT_EDNS_COOKIE_CLNT_SIZE;
+
+ int ret = dnssec_random_buffer(q->cc.data, q->cc.len);
+ if (ret != DNSSEC_EOK) {
+ return knot_error_from_libdnssec(ret);
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nocookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->cc.len = 0;
+ q->sc.len = 0;
+ return KNOT_EOK;
+}
+
+static int opt_badcookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->badcookie = BADCOOKIE_RETRY_MAX;
+ return KNOT_EOK;
+}
+
+static int opt_nobadcookie(const char *arg, void *query)
+{
+ query_t *q = query;
+ q->badcookie = 0;
+ return KNOT_EOK;
+}
+
+static int opt_padding(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->padding = -2;
+ return KNOT_EOK;
+ } else {
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK) {
+ ERR("invalid +padding=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->padding = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_nopadding(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->padding = -3;
+
+ return KNOT_EOK;
+}
+
+static int opt_alignment(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->alignment = DEFAULT_ALIGNMENT_SIZE;
+ return KNOT_EOK;
+ } else {
+ uint16_t num = 0;
+ if (str_to_u16(arg, &num) != KNOT_EOK || num < 2) {
+ ERR("invalid +alignment=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->alignment = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_noalignment(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->alignment = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_subnet(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ char *sep = NULL;
+ const size_t arg_len = strlen(arg);
+ const char *arg_end = arg + arg_len;
+ size_t addr_len = 0;
+
+ // Separate address and network mask.
+ if ((sep = strchr(arg, '/')) != NULL) {
+ addr_len = sep - arg;
+ } else {
+ addr_len = arg_len;
+ }
+
+ // Check IP address.
+
+ struct sockaddr_storage ss = { 0 };
+ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST };
+ struct addrinfo *ai = NULL;
+
+ char *addr_str = strndup(arg, addr_len);
+ if (getaddrinfo(addr_str, NULL, &hints, &ai) != 0) {
+ free(addr_str);
+ ERR("invalid address +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ memcpy(&ss, ai->ai_addr, ai->ai_addrlen);
+ freeaddrinfo(ai);
+ free(addr_str);
+
+ if (knot_edns_client_subnet_set_addr(&q->subnet, &ss) != KNOT_EOK) {
+ ERR("invalid address +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ // Parse network mask.
+ const char *mask = arg;
+ if (mask + addr_len < arg_end) {
+ mask += addr_len + 1;
+ uint8_t num = 0;
+ if (str_to_u8(mask, &num) != KNOT_EOK || num > q->subnet.source_len) {
+ ERR("invalid network mask +subnet=%s", arg);
+ return KNOT_EINVAL;
+ }
+ q->subnet.source_len = num;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_nosubnet(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->subnet.family = AF_UNSPEC;
+
+ return KNOT_EOK;
+}
+
+static int opt_edns(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (arg == NULL) {
+ q->edns = 0;
+ return KNOT_EOK;
+ } else {
+ uint8_t num = 0;
+ if (str_to_u8(arg, &num) != KNOT_EOK) {
+ ERR("invalid +edns=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ q->edns = num;
+ return KNOT_EOK;
+ }
+}
+
+static int opt_noedns(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->edns = -1;
+ opt_nodoflag(arg, query);
+ opt_nonsid(arg, query);
+ opt_nobufsize(arg, query);
+ opt_nocookie(arg, query);
+ opt_nopadding(arg, query);
+ opt_noalignment(arg, query);
+ opt_nosubnet(arg, query);
+
+ return KNOT_EOK;
+}
+
+static int opt_timeout(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (params_parse_wait(arg, &q->wait) != KNOT_EOK) {
+ ERR("invalid +timeout=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_notimeout(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ (void)params_parse_wait("0", &q->wait);
+
+ return KNOT_EOK;
+}
+
+static int opt_retry(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ if (str_to_u32(arg, &q->retries) != KNOT_EOK) {
+ ERR("invalid +retry=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int opt_noretry(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->retries = 0;
+
+ return KNOT_EOK;
+}
+
+static int opt_expire(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *opt = ednsopt_create(KNOT_EDNS_OPTION_EXPIRE, 0, NULL);
+ add_tail(&q->edns_opts, &opt->n);
+
+ return KNOT_EOK;
+}
+
+static int opt_noexpire(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *node, *nxt;
+ WALK_LIST_DELSAFE(node, nxt, q->edns_opts) {
+ ednsopt_t *opt = node;
+ if (opt->code == KNOT_EDNS_OPTION_EXPIRE) {
+ rem_node(&opt->n);
+ ednsopt_free(opt);
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_ednsopt(const char *arg, ednsopt_t **opt_ptr)
+{
+ errno = 0;
+ char *end = NULL;
+ unsigned long code = strtoul(arg, &end, 10);
+ if (errno != 0 || arg == end || code > UINT16_MAX) {
+ return KNOT_EINVAL;
+ }
+
+ size_t length = 0;
+ uint8_t *data = NULL;
+ if (end[0] == ':') {
+ if (end[1] != '\0') {
+ int ret = hex_decode(end + 1, &data, &length);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ if (length > UINT16_MAX) {
+ free(data);
+ return KNOT_ERANGE;
+ }
+ }
+ } else if (end[0] != '\0') {
+ return KNOT_EINVAL;
+ }
+
+ ednsopt_t *opt = ednsopt_create(code, length, data);
+ if (opt == NULL) {
+ free(data);
+ return KNOT_ENOMEM;
+ }
+
+ *opt_ptr = opt;
+ return KNOT_EOK;
+}
+
+static int opt_ednsopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_t *opt = NULL;
+ int ret = parse_ednsopt(arg, &opt);
+ if (ret != KNOT_EOK) {
+ ERR("invalid +ednsopt=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ add_tail(&q->edns_opts, &opt->n);
+
+ return KNOT_EOK;
+}
+
+static int opt_noednsopt(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ ednsopt_list_deinit(&q->edns_opts);
+
+ return KNOT_EOK;
+}
+
+static int opt_noidn(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->idn = false;
+ q->style.style.ascii_to_idn = NULL;
+
+ return KNOT_EOK;
+}
+
+static int opt_json(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_JSON;
+
+ return KNOT_EOK;
+}
+
+static int opt_nojson(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->style.format = FORMAT_FULL;
+
+ return KNOT_EOK;
+}
+
+static int parse_addr(struct sockaddr_storage *addr, const char *arg, const char *def_port)
+{
+ srv_info_t *info = parse_nameserver(arg, def_port);
+ if (info == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct addrinfo *addr_info = NULL;
+ int ret = getaddrinfo(info->name, info->service, NULL, &addr_info);
+ srv_info_free(info);
+ if (ret != 0) {
+ return KNOT_EINVAL;
+ }
+
+ memcpy(addr, addr_info->ai_addr, addr_info->ai_addrlen);
+ freeaddrinfo(addr_info);
+
+ return KNOT_EOK;
+}
+
+static int opt_proxy(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ const char *sep = strchr(arg, '-');
+ if (sep == NULL || sep == arg || *(sep + 1) == '\0') {
+ ERR("invalid specification +proxy=%s", arg);
+ return KNOT_EINVAL;
+ }
+
+ char *src = strndup(arg, sep - arg);
+ int ret = parse_addr(&q->proxy.src, src, "0");
+ if (ret != KNOT_EOK) {
+ ERR("invalid proxy source address '%s'", src);
+ free(src);
+ return KNOT_EINVAL;
+ }
+
+ const char *dst = sep + 1;
+ ret = parse_addr(&q->proxy.dst, dst, "53");
+ if (ret != KNOT_EOK) {
+ ERR("invalid proxy destination address '%s'", dst);
+ free(src);
+ return KNOT_EINVAL;
+ }
+
+ if (q->proxy.src.ss_family != q->proxy.dst.ss_family) {
+ ERR("proxy address family mismatch '%s' versus '%s'", src, dst);
+ free(src);
+ return KNOT_EINVAL;
+ }
+ free(src);
+
+ return KNOT_EOK;
+}
+
+static int opt_noproxy(const char *arg, void *query)
+{
+ query_t *q = query;
+
+ q->proxy.src.ss_family = 0;
+ q->proxy.dst.ss_family = 0;
+
+ return KNOT_EOK;
+}
+
+static const param_t kdig_opts2[] = {
+ { "multiline", ARG_NONE, opt_multiline },
+ { "nomultiline", ARG_NONE, opt_nomultiline },
+
+ { "short", ARG_NONE, opt_short },
+ { "noshort", ARG_NONE, opt_noshort },
+
+ { "generic", ARG_NONE, opt_generic },
+ { "nogeneric", ARG_NONE, opt_nogeneric },
+
+ { "aaflag", ARG_NONE, opt_aaflag },
+ { "noaaflag", ARG_NONE, opt_noaaflag },
+
+ { "tcflag", ARG_NONE, opt_tcflag },
+ { "notcflag", ARG_NONE, opt_notcflag },
+
+ { "rdflag", ARG_NONE, opt_rdflag },
+ { "nordflag", ARG_NONE, opt_nordflag },
+
+ { "recurse", ARG_NONE, opt_rdflag },
+ { "norecurse", ARG_NONE, opt_nordflag },
+
+ { "raflag", ARG_NONE, opt_raflag },
+ { "noraflag", ARG_NONE, opt_noraflag },
+
+ { "zflag", ARG_NONE, opt_zflag },
+ { "nozflag", ARG_NONE, opt_nozflag },
+
+ { "adflag", ARG_NONE, opt_adflag },
+ { "noadflag", ARG_NONE, opt_noadflag },
+
+ { "cdflag", ARG_NONE, opt_cdflag },
+ { "nocdflag", ARG_NONE, opt_nocdflag },
+
+ { "dnssec", ARG_NONE, opt_doflag },
+ { "nodnssec", ARG_NONE, opt_nodoflag },
+
+ { "all", ARG_NONE, opt_all },
+ { "noall", ARG_NONE, opt_noall },
+
+ { "qr", ARG_NONE, opt_qr },
+ { "noqr", ARG_NONE, opt_noqr },
+
+ { "header", ARG_NONE, opt_header },
+ { "noheader", ARG_NONE, opt_noheader },
+
+ { "comments", ARG_NONE, opt_comments },
+ { "nocomments", ARG_NONE, opt_nocomments },
+
+ { "opt", ARG_NONE, opt_opt },
+ { "noopt", ARG_NONE, opt_noopt },
+
+ { "opttext", ARG_NONE, opt_opttext },
+ { "noopttext", ARG_NONE, opt_noopttext },
+
+ { "question", ARG_NONE, opt_question },
+ { "noquestion", ARG_NONE, opt_noquestion },
+
+ { "answer", ARG_NONE, opt_answer },
+ { "noanswer", ARG_NONE, opt_noanswer },
+
+ { "authority", ARG_NONE, opt_authority },
+ { "noauthority", ARG_NONE, opt_noauthority },
+
+ { "additional", ARG_NONE, opt_additional },
+ { "noadditional", ARG_NONE, opt_noadditional },
+
+ { "tsig", ARG_NONE, opt_tsig },
+ { "notsig", ARG_NONE, opt_notsig },
+
+ { "stats", ARG_NONE, opt_stats },
+ { "nostats", ARG_NONE, opt_nostats },
+
+ { "class", ARG_NONE, opt_class },
+ { "noclass", ARG_NONE, opt_noclass },
+
+ { "ttl", ARG_NONE, opt_ttl },
+ { "nottl", ARG_NONE, opt_nottl },
+
+ { "crypto", ARG_NONE, opt_crypto },
+ { "nocrypto", ARG_NONE, opt_nocrypto },
+
+ { "tcp", ARG_NONE, opt_tcp },
+ { "notcp", ARG_NONE, opt_notcp },
+
+ { "fastopen", ARG_NONE, opt_fastopen },
+ { "nofastopen", ARG_NONE, opt_nofastopen },
+
+ { "ignore", ARG_NONE, opt_ignore },
+ { "noignore", ARG_NONE, opt_noignore },
+
+ { "keepopen", ARG_NONE, opt_keepopen },
+ { "nokeepopen", ARG_NONE, opt_nokeepopen },
+
+ { "tls", ARG_NONE, opt_tls },
+ { "notls", ARG_NONE, opt_notls },
+
+ { "tls-ca", ARG_OPTIONAL, opt_tls_ca },
+ { "notls-ca", ARG_NONE, opt_notls_ca },
+
+ { "tls-pin", ARG_REQUIRED, opt_tls_pin },
+ { "notls-pin", ARG_NONE, opt_notls_pin },
+
+ { "tls-hostname", ARG_REQUIRED, opt_tls_hostname },
+ { "notls-hostname", ARG_NONE, opt_notls_hostname },
+
+ { "tls-sni", ARG_REQUIRED, opt_tls_sni },
+ { "notls-sni", ARG_NONE, opt_notls_sni },
+
+ { "tls-keyfile", ARG_REQUIRED, opt_tls_keyfile },
+ { "notls-keyfile", ARG_NONE, opt_notls_keyfile },
+
+ { "tls-certfile", ARG_REQUIRED, opt_tls_certfile },
+ { "notls-certfile", ARG_NONE, opt_notls_certfile },
+
+ { "tls-ocsp-stapling", ARG_OPTIONAL, opt_tls_ocsp_stapling },
+ { "notls-ocsp-stapling", ARG_NONE, opt_notls_ocsp_stapling },
+
+ { "https", ARG_OPTIONAL, opt_https },
+ { "nohttps", ARG_NONE, opt_nohttps },
+
+ { "https-get", ARG_NONE, opt_https_get },
+ { "nohttps-get", ARG_NONE, opt_nohttps_get },
+
+ { "quic", ARG_NONE, opt_quic },
+ { "noquic", ARG_NONE, opt_noquic },
+
+ { "nsid", ARG_NONE, opt_nsid },
+ { "nonsid", ARG_NONE, opt_nonsid },
+
+ { "bufsize", ARG_REQUIRED, opt_bufsize },
+ { "nobufsize", ARG_NONE, opt_nobufsize },
+
+ { "padding", ARG_OPTIONAL, opt_padding },
+ { "nopadding", ARG_NONE, opt_nopadding },
+
+ { "alignment", ARG_OPTIONAL, opt_alignment },
+ { "noalignment", ARG_NONE, opt_noalignment },
+
+ { "subnet", ARG_REQUIRED, opt_subnet },
+ { "nosubnet", ARG_NONE, opt_nosubnet },
+
+ { "proxy", ARG_REQUIRED, opt_proxy },
+ { "noproxy", ARG_NONE, opt_noproxy },
+
+ // Obsolete aliases.
+ { "client", ARG_REQUIRED, opt_subnet },
+ { "noclient", ARG_NONE, opt_nosubnet },
+
+ { "edns", ARG_OPTIONAL, opt_edns },
+ { "noedns", ARG_NONE, opt_noedns },
+
+ { "timeout", ARG_REQUIRED, opt_timeout },
+ { "notimeout", ARG_NONE, opt_notimeout },
+
+ { "retry", ARG_REQUIRED, opt_retry },
+ { "noretry", ARG_NONE, opt_noretry },
+
+ { "expire", ARG_NONE, opt_expire },
+ { "noexpire", ARG_NONE, opt_noexpire },
+
+ { "cookie", ARG_OPTIONAL, opt_cookie },
+ { "nocookie", ARG_NONE, opt_nocookie },
+
+ { "badcookie", ARG_NONE, opt_badcookie },
+ { "nobadcookie", ARG_NONE, opt_nobadcookie },
+
+ { "ednsopt", ARG_REQUIRED, opt_ednsopt },
+ { "noednsopt", ARG_NONE, opt_noednsopt },
+
+ { "json", ARG_NONE, opt_json },
+ { "nojson", ARG_NONE, opt_nojson },
+
+ /* "idn" doesn't work since it must be called before query creation. */
+ { "noidn", ARG_NONE, opt_noidn },
+
+ { NULL }
+};
+
+query_t *query_create(const char *owner, const query_t *conf)
+{
+ // Create output structure.
+ query_t *query = calloc(1, sizeof(query_t));
+
+ if (query == NULL) {
+ DBG_NULL;
+ return NULL;
+ }
+
+ // Initialization with defaults or with reference query.
+ if (conf == NULL) {
+ query->conf = NULL;
+ query->local = NULL;
+ query->operation = OPERATION_QUERY;
+ query->ip = IP_ALL;
+ query->protocol = PROTO_ALL;
+ query->fastopen = false;
+ query->port = strdup("");
+ query->udp_size = -1;
+ query->retries = DEFAULT_RETRIES_DIG;
+ query->wait = DEFAULT_TIMEOUT_DIG;
+ query->ignore_tc = false;
+ query->class_num = -1;
+ query->type_num = -1;
+ query->serial = -1;
+ query->notify = false;
+ query->flags = DEFAULT_FLAGS_DIG;
+ query->style = DEFAULT_STYLE_DIG;
+ query->style.style.now = knot_time();
+ query->idn = true;
+ query->nsid = false;
+ query->edns = -1;
+ query->cc.len = 0;
+ query->sc.len = 0;
+ query->badcookie = BADCOOKIE_RETRY_MAX;
+ query->padding = -1;
+ query->alignment = 0;
+ tls_params_init(&query->tls);
+ //query->tsig_key
+ query->subnet.family = AF_UNSPEC;
+ ednsopt_list_init(&query->edns_opts);
+#if USE_DNSTAP
+ query->dt_reader = NULL;
+ query->dt_writer = NULL;
+#endif // USE_DNSTAP
+ } else {
+ *query = *conf;
+ query->conf = conf;
+ if (conf->local != NULL) {
+ query->local = srv_info_create(conf->local->name,
+ conf->local->service);
+ if (query->local == NULL) {
+ query_free(query);
+ return NULL;
+ }
+ } else {
+ query->local = NULL;
+ }
+ query->port = strdup(conf->port);
+ tls_params_copy(&query->tls, &conf->tls);
+ https_params_copy(&query->https, &conf->https);
+ quic_params_copy(&query->quic, &conf->quic);
+ if (conf->tsig_key.name != NULL) {
+ int ret = knot_tsig_key_copy(&query->tsig_key,
+ &conf->tsig_key);
+ if (ret != KNOT_EOK) {
+ query_free(query);
+ return NULL;
+ }
+ }
+
+ int ret = ednsopt_list_dup(&query->edns_opts, &conf->edns_opts);
+ if (ret != KNOT_EOK) {
+ query_free(query);
+ return NULL;
+ }
+
+#if USE_DNSTAP
+ query->dt_reader = conf->dt_reader;
+ query->dt_writer = conf->dt_writer;
+#endif // USE_DNSTAP
+ }
+
+ // Initialize list of servers.
+ init_list(&query->servers);
+
+ // Set the query owner if any.
+ if (owner != NULL) {
+ if ((query->owner = strdup(owner)) == NULL) {
+ query_free(query);
+ return NULL;
+ }
+ }
+
+ // Check dynamic allocation.
+ if (query->port == NULL) {
+ query_free(query);
+ return NULL;
+ }
+
+ return query;
+}
+
+void query_free(query_t *query)
+{
+ node_t *n, *nxt;
+
+ if (query == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // Cleanup servers.
+ WALK_LIST_DELSAFE(n, nxt, query->servers) {
+ srv_info_free((srv_info_t *)n);
+ }
+
+ // Cleanup local address.
+ if (query->local != NULL) {
+ srv_info_free(query->local);
+ }
+
+ tls_params_clean(&query->tls);
+ https_params_clean(&query->https);
+ quic_params_clean(&query->quic);
+
+ // Cleanup signing key.
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ // Cleanup EDNS options.
+ ednsopt_list_deinit(&query->edns_opts);
+
+#if USE_DNSTAP
+ if (query->dt_reader != NULL) {
+ dt_reader_free(query->dt_reader);
+ }
+ if (query->dt_writer != NULL) {
+ // Global writer can be shared!
+ if (query->conf == NULL ||
+ query->conf->dt_writer != query->dt_writer) {
+ dt_writer_free(query->dt_writer);
+ }
+ }
+#endif // USE_DNSTAP
+
+ free(query->owner);
+ free(query->port);
+ free(query);
+}
+
+ednsopt_t *ednsopt_create(uint16_t code, uint16_t length, uint8_t *data)
+{
+ ednsopt_t *opt = calloc(1, sizeof(*opt));
+ if (opt == NULL) {
+ return NULL;
+ }
+
+ opt->code = code;
+ opt->length = length;
+ opt->data = data;
+
+ return opt;
+}
+
+ednsopt_t *ednsopt_dup(const ednsopt_t *opt)
+{
+ ednsopt_t *dup = calloc(1, sizeof(*opt));
+ if (dup == NULL) {
+ return NULL;
+ }
+
+ dup->code = opt->code;
+ dup->length = opt->length;
+ dup->data = memdup(opt->data, opt->length);
+ if (dup->data == NULL) {
+ free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+void ednsopt_free(ednsopt_t *opt)
+{
+ if (opt == NULL) {
+ return;
+ }
+
+ free(opt->data);
+ free(opt);
+}
+
+void ednsopt_list_init(list_t *list)
+{
+ init_list(list);
+}
+
+void ednsopt_list_deinit(list_t *list)
+{
+ node_t *n, *next;
+ WALK_LIST_DELSAFE(n, next, *list) {
+ ednsopt_t *opt = (ednsopt_t *)n;
+ ednsopt_free(opt);
+ }
+
+ init_list(list);
+}
+
+int ednsopt_list_dup(list_t *dest, const list_t *src)
+{
+ list_t backup = *dest;
+ init_list(dest);
+
+ node_t *n;
+ WALK_LIST(n, *src) {
+ ednsopt_t *opt = (ednsopt_t *)n;
+ ednsopt_t *dup = ednsopt_dup(opt);
+ if (dup == NULL) {
+ ednsopt_list_deinit(dest);
+ *dest = backup;
+ return KNOT_ENOMEM;
+ }
+
+ add_tail(dest, &dup->n);
+ }
+
+ return KNOT_EOK;
+}
+
+bool ednsopt_list_empty(const list_t *list)
+{
+ return EMPTY_LIST(*list);
+}
+
+int kdig_init(kdig_params_t *params)
+{
+ if (params == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ memset(params, 0, sizeof(*params));
+
+ params->stop = false;
+
+ // Initialize list of queries.
+ init_list(&params->queries);
+
+ // Create config query.
+ if ((params->config = query_create(NULL, NULL)) == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+void kdig_clean(kdig_params_t *params)
+{
+ node_t *n, *nxt;
+
+ if (params == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // Clean up queries.
+ WALK_LIST_DELSAFE(n, nxt, params->queries) {
+ query_free((query_t *)n);
+ }
+
+ // Clean up config.
+ query_free(params->config);
+
+ // Clean up the structure.
+ memset(params, 0, sizeof(*params));
+}
+
+static int parse_class(const char *value, query_t *query)
+{
+ uint16_t rclass;
+
+ if (params_parse_class(value, &rclass) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ query->class_num = rclass;
+
+ return KNOT_EOK;
+}
+
+static int parse_keyfile(const char *value, query_t *query)
+{
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ if (knot_tsig_key_init_file(&query->tsig_key, value) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_local(const char *value, query_t *query)
+{
+ srv_info_t *local = parse_nameserver(value, "0");
+ if (local == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (query->local != NULL) {
+ srv_info_free(query->local);
+ }
+
+ query->local = local;
+
+ return KNOT_EOK;
+}
+
+static int parse_name(const char *value, list_t *queries, const query_t *conf)
+{
+ query_t *query = NULL;
+ char *ascii_name = (char *)value;
+ char *fqd_name = NULL;
+
+ if (value != NULL) {
+ if (conf->idn) {
+ ascii_name = name_from_idn(value);
+ if (ascii_name == NULL) {
+ return KNOT_EINVAL;
+ }
+ }
+
+ // If name is not FQDN, append trailing dot.
+ fqd_name = get_fqd_name(ascii_name);
+
+ if (conf->idn) {
+ free(ascii_name);
+ }
+ }
+
+ // Create new query.
+ query = query_create(fqd_name, conf);
+
+ free(fqd_name);
+
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Add new query to the queries.
+ add_tail(queries, (node_t *)query);
+
+ return KNOT_EOK;
+}
+
+static int parse_port(const char *value, query_t *query)
+{
+ char **port;
+
+ // Set current server port (last or query default).
+ if (list_size(&query->servers) > 0) {
+ srv_info_t *server = TAIL(query->servers);
+ port = &(server->service);
+ } else {
+ port = &(query->port);
+ }
+
+ char *new_port = strdup(value);
+
+ if (new_port == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Deallocate old string.
+ free(*port);
+
+ *port = new_port;
+
+ return KNOT_EOK;
+}
+
+static int parse_reverse(const char *value, list_t *queries, const query_t *conf)
+{
+ query_t *query = NULL;
+
+ // Create reverse name.
+ char *reverse = get_reverse_name(value);
+
+ if (reverse == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Create reverse query for given address.
+ query = query_create(reverse, conf);
+
+ free(reverse);
+
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Set type for reverse query.
+ query->type_num = KNOT_RRTYPE_PTR;
+
+ // Add new query to the queries.
+ add_tail(queries, (node_t *)query);
+
+ return KNOT_EOK;
+}
+
+static int parse_server(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ if (params_parse_server(value, &query->servers, query->port) != KNOT_EOK) {
+ ERR("invalid server @%s", value);
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_tsig(const char *value, query_t *query)
+{
+ knot_tsig_key_deinit(&query->tsig_key);
+
+ if (knot_tsig_key_init_str(&query->tsig_key, value) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_type(const char *value, query_t *query)
+{
+ uint16_t rtype;
+ int64_t serial;
+ bool notify;
+
+ if (params_parse_type(value, &rtype, &serial, &notify) != KNOT_EOK) {
+ return KNOT_EINVAL;
+ }
+
+ query->type_num = rtype;
+ query->serial = serial;
+ query->notify = notify;
+
+ // If NOTIFY, reset default RD flag.
+ if (query->notify) {
+ query->flags.rd_flag = false;
+ }
+
+ return KNOT_EOK;
+}
+
+#if USE_DNSTAP
+static int parse_dnstap_output(const char *value, query_t *query)
+{
+ if (query->dt_writer != NULL) {
+ if (query->conf == NULL ||
+ query->conf->dt_writer != query->dt_writer) {
+ dt_writer_free(query->dt_writer);
+ }
+ }
+
+ query->dt_writer = dt_writer_create(value, "kdig " PACKAGE_VERSION);
+ if (query->dt_writer == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_dnstap_input(const char *value, query_t *query)
+{
+ // Just in case, shouldn't happen.
+ if (query->dt_reader != NULL) {
+ dt_reader_free(query->dt_reader);
+ }
+
+ query->dt_reader = dt_reader_create(value);
+ if (query->dt_reader == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+#endif // USE_DNSTAP
+
+static void complete_servers(query_t *query, const query_t *conf)
+{
+ node_t *n;
+ char *def_port;
+
+ // Decide which default port use.
+ if (strlen(query->port) > 0) {
+ def_port = query->port;
+ } else if (strlen(conf->port) > 0) {
+ def_port = conf->port;
+ } else if (query->https.enable) {
+ def_port = DEFAULT_DNS_HTTPS_PORT;
+ } else if (query->quic.enable) {
+ def_port = DEFAULT_DNS_QUIC_PORT;
+ } else if (query->tls.enable) {
+ def_port = DEFAULT_DNS_TLS_PORT;
+ } else {
+ def_port = DEFAULT_DNS_PORT;
+ }
+
+ // Complete specified nameservers if any.
+ if (list_size(&query->servers) > 0) {
+ WALK_LIST(n, query->servers) {
+ srv_info_t *s = (srv_info_t *)n;
+
+ // If the port isn't specified yet use the default one.
+ if (strlen(s->service) == 0) {
+ free(s->service);
+ s->service = strdup(def_port);
+ if (s->service == NULL) {
+ WARN("can't set port %s", def_port);
+ return;
+ }
+ }
+
+ // Use server name as hostname for TLS if necessary.
+ if (query->tls.enable && query->tls.hostname == NULL &&
+ (query->tls.system_ca || !EMPTY_LIST(query->tls.ca_files))) {
+ query->tls.hostname = strdup(s->name);
+ }
+ }
+ // Use servers from config if any.
+ } else if (list_size(&conf->servers) > 0) {
+ WALK_LIST(n, conf->servers) {
+ srv_info_t *s = (srv_info_t *)n;
+ char *port = def_port;
+
+ // If the port is already specified, use it.
+ if (strlen(s->service) > 0) {
+ port = s->service;
+ }
+
+ srv_info_t *server = srv_info_create(s->name, port);
+ if (server == NULL) {
+ WARN("can't set nameserver %s port %s",
+ s->name, s->service);
+ return;
+ }
+ add_tail(&query->servers, (node_t *)server);
+
+ // Use server name as hostname for TLS if necessary.
+ if (query->tls.enable && query->tls.hostname == NULL &&
+ (query->tls.system_ca || !EMPTY_LIST(query->tls.ca_files))) {
+ query->tls.hostname = strdup(s->name);
+ }
+ }
+ // Use system specific.
+ } else {
+ get_nameservers(&query->servers, def_port);
+ }
+}
+
+static bool compare_servers(list_t *s1, list_t *s2)
+{
+ if (list_size(s1) != list_size(s2)) {
+ return false;
+ }
+
+ node_t *n1, *n2 = HEAD(*s2);
+ WALK_LIST(n1, *s1) {
+ srv_info_t *i1 = (srv_info_t *)n1, *i2 = (srv_info_t *)n2;
+ if (strcmp(i1->service, i2->service) != 0 ||
+ strcmp(i1->name, i2->name) != 0)
+ {
+ return false;
+ }
+ n2 = n2->next;
+ }
+ return true;
+}
+
+void complete_queries(list_t *queries, const query_t *conf)
+{
+ node_t *n;
+
+ if (queries == NULL || conf == NULL) {
+ DBG_NULL;
+ return;
+ }
+
+ // If there is no query, add default query: NS to ".".
+ if (list_size(queries) == 0) {
+ query_t *q = query_create(".", conf);
+ if (q == NULL) {
+ WARN("can't create query . NS IN");
+ return;
+ }
+ q->class_num = KNOT_CLASS_IN;
+ q->type_num = KNOT_RRTYPE_NS;
+ add_tail(queries, (node_t *)q);
+ }
+
+ WALK_LIST(n, *queries) {
+ query_t *q = (query_t *)n;
+ query_t *q_prev = (HEAD(*queries) != n) ? (query_t *)n->prev : NULL;
+
+ // Fill class number if missing.
+ if (q->class_num < 0) {
+ if (conf->class_num >= 0) {
+ q->class_num = conf->class_num;
+ } else {
+ q->class_num = KNOT_CLASS_IN;
+ }
+ }
+
+ // Fill type number if missing.
+ if (q->type_num < 0) {
+ if (conf->type_num >= 0) {
+ q->type_num = conf->type_num;
+ q->serial = conf->serial;
+ } else {
+ q->type_num = KNOT_RRTYPE_A;
+ }
+ }
+
+ // Set zone transfer if any.
+ if (q->type_num == KNOT_RRTYPE_AXFR ||
+ q->type_num == KNOT_RRTYPE_IXFR) {
+ q->operation = OPERATION_XFR;
+ }
+
+ // No retries for TCP.
+ if (q->protocol == PROTO_TCP) {
+ q->retries = 0;
+ }
+
+ // Complete nameservers list.
+ complete_servers(q, conf);
+
+ // Check if using previous connection makes sense.
+ if (q_prev != NULL && q_prev->keepopen &&
+ (get_socktype(q->protocol, q->type_num) !=
+ get_socktype(q_prev->protocol, q_prev->type_num) ||
+ q->https.enable != q_prev->https.enable ||
+ q->tls.enable != q_prev->tls.enable ||
+ strcmp(q->port, q_prev->port) != 0 ||
+ !compare_servers(&q->servers, &q_prev->servers)))
+ {
+ WARN("connection parameters mismatch for query (%s), "
+ "ignoring keepopen", q->owner);
+ q_prev->keepopen = false;
+ }
+ }
+}
+
+static void print_help(void)
+{
+ printf("Usage: %s [-4] [-6] [-d] [-b address] [-c class] [-p port]\n"
+ " [-q name] [-t type] [-x address] [-k keyfile]\n"
+ " [-y [algo:]keyname:key] [-E tapfile] [-G tapfile]\n"
+ " name [type] [class] [@server]\n"
+ "\n"
+ " +[no]multiline Wrap long records to more lines.\n"
+ " +[no]short Show record data only.\n"
+ " +[no]generic Use generic representation format.\n"
+ " +[no]aaflag Set AA flag.\n"
+ " +[no]tcflag Set TC flag.\n"
+ " +[no]rdflag Set RD flag.\n"
+ " +[no]recurse Same as +[no]rdflag\n"
+ " +[no]raflag Set RA flag.\n"
+ " +[no]zflag Set zero flag bit.\n"
+ " +[no]adflag Set AD flag.\n"
+ " +[no]cdflag Set CD flag.\n"
+ " +[no]dnssec Set DO flag.\n"
+ " +[no]all Show all packet sections.\n"
+ " +[no]qr Show query packet.\n"
+ " +[no]header Show packet header.\n"
+ " +[no]comments Show commented section names.\n"
+ " +[no]opt Show EDNS pseudosection.\n"
+ " +[no]opttext Try to show unknown EDNS options as text.\n"
+ " +[no]question Show question section.\n"
+ " +[no]answer Show answer section.\n"
+ " +[no]authority Show authority section.\n"
+ " +[no]additional Show additional section.\n"
+ " +[no]tsig Show TSIG pseudosection.\n"
+ " +[no]stats Show trailing packet statistics.\n"
+ " +[no]class Show DNS class.\n"
+ " +[no]ttl Show TTL value.\n"
+ " +[no]crypto Show binary parts of RRSIGs and DNSKEYs.\n"
+ " +[no]tcp Use TCP protocol.\n"
+ " +[no]fastopen Use TCP Fast Open.\n"
+ " +[no]ignore Don't use TCP automatically if truncated.\n"
+ " +[no]keepopen Don't close the TCP connection to be reused.\n"
+ " +[no]tls Use TLS with Opportunistic privacy profile.\n"
+ " +[no]tls-ca[=FILE] Use TLS with Out-Of-Band privacy profile.\n"
+ " +[no]tls-pin=BASE64 Use TLS with pinned certificate.\n"
+ " +[no]tls-hostname=STR Use TLS with remote server hostname.\n"
+ " +[no]tls-sni=STR Use TLS with Server Name Indication.\n"
+ " +[no]tls-keyfile=FILE Use TLS with a client keyfile.\n"
+ " +[no]tls-certfile=FILE Use TLS with a client certfile.\n"
+ " +[no]tls-ocsp-stapling[=H] Use TLS with a valid stapled OCSP response for the\n"
+ " server certificate (%u or specify hours).\n"
+#ifdef LIBNGHTTP2
+ " +[no]https[=URL] Use HTTPS protocol. It's also possible to specify\n"
+ " URL as [authority][/path] where query will be sent.\n"
+ " +[no]https-get Use HTTPS protocol with GET method instead of POST.\n"
+#endif
+#ifdef ENABLE_QUIC
+ " +[no]quic Use QUIC protocol.\n"
+#endif
+ " +[no]nsid Request NSID.\n"
+ " +[no]bufsize=B Set EDNS buffer size.\n"
+ " +[no]padding[=N] Pad with EDNS(0) (default or specify size).\n"
+ " +[no]alignment[=N] Pad with EDNS(0) to blocksize (%u or specify size).\n"
+ " +[no]subnet=SUBN Set EDNS(0) client subnet addr/prefix.\n"
+ " +[no]edns[=N] Use EDNS(=version).\n"
+ " +[no]timeout=T Set wait for reply interval in seconds.\n"
+ " +[no]retry=N Set number of retries.\n"
+ " +[no]expire Set the EXPIRE EDNS option.\n"
+ " +[no]cookie[=HEX] Attach EDNS(0) cookie to the query.\n"
+ " +[no]badcookie Repeat a query with the correct cookie.\n"
+ " +[no]ednsopt=CODE[:HEX] Set custom EDNS option.\n"
+ " +[no]proxy=SADDR-DADDR Add PROXYv2 header with src and dest addresses.\n"
+ " +[no]json Use JSON for output encoding (RFC 8427).\n"
+ " +noidn Disable IDN transformation.\n"
+ "\n"
+ " -h, --help Print the program help.\n"
+ " -V, --version Print the program version.\n",
+ PROGRAM_NAME, DEFAULT_TLS_OCSP_STAPLING / 3600, DEFAULT_ALIGNMENT_SIZE);
+}
+
+static int parse_opt1(const char *opt, const char *value, kdig_params_t *params,
+ int *index)
+{
+ const char *val = value;
+ size_t len = strlen(opt);
+ int add = 1;
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // If there is no space between option and argument.
+ if (len > 1) {
+ val = opt + 1;
+ add = 0;
+ }
+
+ switch (opt[0]) {
+ case '4':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ query->ip = IP_4;
+ break;
+ case '6':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ query->ip = IP_6;
+ break;
+ case 'b':
+ if (val == NULL) {
+ ERR("missing address");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_local(val, query) != KNOT_EOK) {
+ ERR("bad address %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'd':
+ msg_enable_debug(1);
+ break;
+ case 'h':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ print_help();
+ params->stop = true;
+ break;
+ case 'c':
+ if (val == NULL) {
+ ERR("missing class");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_class(val, query) != KNOT_EOK) {
+ ERR("bad class %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'k':
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_keyfile(val, query) != KNOT_EOK) {
+ ERR("bad keyfile %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'p':
+ if (val == NULL) {
+ ERR("missing port");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_port(val, query) != KNOT_EOK) {
+ ERR("bad port %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'q':
+ // Allow empty QNAME.
+ if (parse_name(val, &params->queries, params->config)
+ != KNOT_EOK) {
+ ERR("bad query name %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 't':
+ if (val == NULL) {
+ ERR("missing type");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_type(val, query) != KNOT_EOK) {
+ ERR("bad type %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'V':
+ if (len > 1) {
+ ERR("invalid option -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ print_version(PROGRAM_NAME);
+ params->stop = true;
+ break;
+ case 'x':
+ if (val == NULL) {
+ ERR("missing address");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_reverse(val, &params->queries, params->config)
+ != KNOT_EOK) {
+ ERR("bad reverse name %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'y':
+ if (val == NULL) {
+ ERR("missing key");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_tsig(val, query) != KNOT_EOK) {
+ ERR("bad key %s", value);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+ break;
+ case 'E':
+#if USE_DNSTAP
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ if (parse_dnstap_output(val, query) != KNOT_EOK) {
+ ERR("unable to open dnstap output file %s", val);
+ return KNOT_EINVAL;
+ }
+ *index += add;
+#else
+ ERR("no dnstap support but -E specified");
+ return KNOT_EINVAL;
+#endif // USE_DNSTAP
+ break;
+ case 'G':
+#if USE_DNSTAP
+ if (val == NULL) {
+ ERR("missing filename");
+ return KNOT_EINVAL;
+ }
+
+ query = query_create(NULL, params->config);
+ if (query == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ if (parse_dnstap_input(val, query) != KNOT_EOK) {
+ ERR("unable to open dnstap input file %s", val);
+ query_free(query);
+ return KNOT_EINVAL;
+ }
+
+ query->operation = OPERATION_LIST_DNSTAP;
+ add_tail(&params->queries, (node_t *)query);
+
+ *index += add;
+#else
+ ERR("no dnstap support but -G specified");
+ return KNOT_EINVAL;
+#endif // USE_DNSTAP
+ break;
+ case '-':
+ if (strcmp(opt, "-help") == 0) {
+ print_help();
+ params->stop = true;
+ } else if (strcmp(opt, "-version") == 0) {
+ print_version(PROGRAM_NAME);
+ params->stop = true;
+ } else {
+ ERR("invalid option: -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+ break;
+ default:
+ ERR("invalid option: -%s", opt);
+ return KNOT_ENOTSUP;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_opt2(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // Get option name.
+ const char *arg_sep = "=";
+ size_t opt_len = strcspn(value, arg_sep);
+ if (opt_len < 1) {
+ ERR("invalid option: +%s", value);
+ return KNOT_ENOTSUP;
+ }
+
+ // Get option argument if any.
+ const char *arg = NULL;
+ const char *rest = value + opt_len;
+ if (strlen(rest) > 0) {
+ arg = rest + strspn(rest, arg_sep);
+ }
+
+ // Check if the given option is supported.
+ bool unique;
+ int ret = best_param(value, opt_len, kdig_opts2, &unique);
+ if (ret < 0) {
+ ERR("invalid option: +%s", value);
+ return KNOT_ENOTSUP;
+ } else if (!unique) {
+ ERR("ambiguous option: +%s", value);
+ return KNOT_ENOTSUP;
+ }
+
+ // Check argument presence.
+ switch (kdig_opts2[ret].arg) {
+ case ARG_NONE:
+ if (arg != NULL && *arg != '\0') {
+ WARN("superfluous option argument: +%s", value);
+ }
+ break;
+ case ARG_REQUIRED:
+ if (arg == NULL) {
+ ERR("missing argument: +%s", value);
+ return KNOT_EFEWDATA;
+ }
+ // FALLTHROUGH
+ case ARG_OPTIONAL:
+ if (arg != NULL && *arg == '\0') {
+ ERR("empty argument: +%s", value);
+ return KNOT_EFEWDATA;
+ }
+ break;
+ }
+
+ // Call option handler.
+ return kdig_opts2[ret].handler(arg, query);
+}
+
+static int parse_token(const char *value, kdig_params_t *params)
+{
+ query_t *query;
+
+ // Set current query (last or config).
+ if (list_size(&params->queries) > 0) {
+ query = TAIL(params->queries);
+ } else {
+ query = params->config;
+ }
+
+ // Try to guess the meaning of the token.
+ if (strlen(value) == 0) {
+ ERR("invalid empty parameter");
+ } else if (parse_type(value, query) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else if (parse_class(value, query) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else if (parse_name(value, &params->queries, params->config) == KNOT_EOK) {
+ return KNOT_EOK;
+ } else {
+ ERR("invalid parameter: %s", value);
+ }
+
+ return KNOT_EINVAL;
+}
+
+int kdig_parse(kdig_params_t *params, int argc, char *argv[])
+{
+ if (params == NULL || argv == NULL) {
+ DBG_NULL;
+ return KNOT_EINVAL;
+ }
+
+ // Initialize parameters.
+ if (kdig_init(params) != KNOT_EOK) {
+ return KNOT_ERROR;
+ }
+
+#ifdef LIBIDN
+ // Set up localization.
+ if (setlocale(LC_CTYPE, "") == NULL) {
+ WARN("can't setlocale, disabling IDN");
+ params->config->idn = false;
+ params->config->style.style.ascii_to_idn = NULL;
+ }
+#endif
+
+ // Command line parameters processing.
+ for (int i = 1; i < argc; i++) {
+ int ret = KNOT_ERROR;
+
+ // Process parameter.
+ switch (argv[i][0]) {
+ case '@':
+ ret = parse_server(argv[i] + 1, params);
+ break;
+ case '-':
+ ret = parse_opt1(argv[i] + 1, argv[i + 1], params, &i);
+ break;
+ case '+':
+ ret = parse_opt2(argv[i] + 1, params);
+ break;
+ default:
+ ret = parse_token(argv[i], params);
+ break;
+ }
+
+ // Check return.
+ switch (ret) {
+ case KNOT_EOK:
+ if (params->stop) {
+ return KNOT_EOK;
+ }
+ break;
+ case KNOT_ENOTSUP:
+ print_help();
+ default: // Fall through.
+ return ret;
+ }
+ }
+
+ // Complete missing data in queries based on defaults.
+ complete_queries(&params->queries, params->config);
+
+ return KNOT_EOK;
+}
diff --git a/src/utils/kdig/kdig_params.h b/src/utils/kdig/kdig_params.h
new file mode 100644
index 0000000..03aafe3
--- /dev/null
+++ b/src/utils/kdig/kdig_params.h
@@ -0,0 +1,187 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "utils/common/params.h"
+#include "utils/common/exec.h"
+#include "utils/common/https.h"
+#include "utils/common/quic.h"
+#include "utils/common/sign.h"
+#include "libknot/libknot.h"
+#include "contrib/sockaddr.h"
+
+#if USE_DNSTAP
+# include "contrib/dnstap/reader.h"
+# include "contrib/dnstap/writer.h"
+#endif // USE_DNSTAP
+
+/*! \brief Operation mode of kdig. */
+typedef enum {
+ /*!< Standard 1-message query/reply. */
+ OPERATION_QUERY,
+ /*!< Zone transfer (AXFR or IXFR). */
+ OPERATION_XFR,
+ /*!< Dump dnstap file. */
+ OPERATION_LIST_DNSTAP,
+} operation_t;
+
+/*! \brief DNS header and EDNS flags. */
+typedef struct {
+ /*!< Authoritative answer flag. */
+ bool aa_flag;
+ /*!< Truncated flag. */
+ bool tc_flag;
+ /*!< Recursion desired flag. */
+ bool rd_flag;
+ /*!< Recursion available flag. */
+ bool ra_flag;
+ /*!< Z flag. */
+ bool z_flag;
+ /*!< Authenticated data flag. */
+ bool ad_flag;
+ /*!< Checking disabled flag. */
+ bool cd_flag;
+ /*!< DNSSEC OK flag. */
+ bool do_flag;
+} flags_t;
+
+/*! \brief Basic parameters for DNS query. */
+typedef struct query query_t; // Forward declaration due to configuration.
+struct query {
+ /*!< List node (for list container). */
+ node_t n;
+ /*!< Reference to global config. */
+ const query_t *conf;
+ /*!< Name to query on. */
+ char *owner;
+ /*!< List of nameservers to query to. */
+ list_t servers;
+ /*!< Local interface (optional). */
+ srv_info_t *local;
+ /*!< Operation mode. */
+ operation_t operation;
+ /*!< Version of ip protocol to use. */
+ ip_t ip;
+ /*!< Protocol type (TCP, UDP) to use. */
+ protocol_t protocol;
+ /*!< Use TCP Fast Open. */
+ bool fastopen;
+ /*!< Keep TCP connection open. */
+ bool keepopen;
+ /*!< Port/service to connect to. */
+ char *port;
+ /*!< UDP buffer size (16unsigned + -1 uninitialized). */
+ int32_t udp_size;
+ /*!< Number of UDP retries. */
+ uint32_t retries;
+ /*!< Wait for network response in seconds (-1 means forever). */
+ int32_t wait;
+ /*!< Ignore truncated response. */
+ bool ignore_tc;
+ /*!< Class number (16unsigned + -1 uninitialized). */
+ int32_t class_num;
+ /*!< Type number (16unsigned + -1 uninitialized). */
+ int32_t type_num;
+ /*!< SOA serial for IXFR and NOTIFY (32unsigned + -1 uninitialized). */
+ int64_t serial;
+ /*!< NOTIFY query. */
+ bool notify;
+ /*!< Header flags. */
+ flags_t flags;
+ /*!< Output settings. */
+ style_t style;
+ /*!< IDN conversion. */
+ bool idn;
+ /*!< Query for NSID. */
+ bool nsid;
+ /*!< EDNS version (8unsigned + -1 uninitialized). */
+ int16_t edns;
+ /*!< EDNS client cookie. */
+ knot_edns_cookie_t cc;
+ /*!< EDNS server cookie. */
+ knot_edns_cookie_t sc;
+ /*!< Repeat query after BADCOOKIE. */
+ int badcookie;
+ /*!< EDNS0 padding (16unsigned + -1 ~ uninitialized, -2 ~ default, -3 ~ none). */
+ int32_t padding;
+ /*!< Query alignment with EDNS0 padding (0 ~ uninitialized). */
+ uint16_t alignment;
+ /*!< TLS parameters. */
+ tls_params_t tls;
+ /*!< HTTPS parameters. */
+ https_params_t https;
+ /*!< QUIC parameters. */
+ quic_params_t quic;
+ /*!< Transaction signature. */
+ knot_tsig_key_t tsig_key;
+ /*!< EDNS client subnet. */
+ knot_edns_client_subnet_t subnet;
+ /*!< Lits of custom EDNS options. */
+ list_t edns_opts;
+ /*!< PROXYv2 source and destination address. */
+ struct {
+ struct sockaddr_storage src;
+ struct sockaddr_storage dst;
+ } proxy;
+#if USE_DNSTAP
+ /*!< Context for dnstap reader input. */
+ dt_reader_t *dt_reader;
+ /*!< Context for dnstap writer output. */
+ dt_writer_t *dt_writer;
+#endif // USE_DNSTAP
+};
+
+/*! \brief EDNS option data. */
+typedef struct {
+ /*! List node (for list container). */
+ node_t n;
+ /*!< OPTION-CODE field. */
+ uint16_t code;
+ /*!< OPTION-LENGTH field. */
+ uint16_t length;
+ /*!< OPTION-DATA field. */
+ uint8_t *data;
+} ednsopt_t;
+
+/*! \brief Settings for kdig. */
+typedef struct {
+ /*!< Stop processing - just print help, version,... */
+ bool stop;
+ /*!< List of DNS queries to process. */
+ list_t queries;
+ /*!< Default settings for queries. */
+ query_t *config;
+} kdig_params_t;
+
+query_t *query_create(const char *owner, const query_t *config);
+void query_free(query_t *query);
+void complete_queries(list_t *queries, const query_t *conf);
+
+ednsopt_t *ednsopt_create(uint16_t code, uint16_t length, uint8_t *data);
+ednsopt_t *ednsopt_dup(const ednsopt_t *opt);
+void ednsopt_free(ednsopt_t *opt);
+
+void ednsopt_list_init(list_t *list);
+void ednsopt_list_deinit(list_t *list);
+int ednsopt_list_dup(list_t *dst, const list_t *src);
+bool ednsopt_list_empty(const list_t *list);
+
+int kdig_init(kdig_params_t *params);
+int kdig_parse(kdig_params_t *params, int argc, char *argv[]);
+void kdig_clean(kdig_params_t *params);