]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
kdig: add +json (RFC 8427) option
authorJan Hák <jan.hak@nic.cz>
Sun, 6 May 2018 20:57:29 +0000 (22:57 +0200)
committerDaniel Salzman <daniel.salzman@nic.cz>
Wed, 14 Sep 2022 10:05:44 +0000 (12:05 +0200)
14 files changed:
doc/man/kdig.1in
doc/man_kdig.rst
src/contrib/json.c
src/contrib/json.h
src/contrib/string.c
src/contrib/string.h
src/knot/catalog/generate.c
src/libdnssec/keystore/pkcs11.c
src/libdnssec/shared/keyid_gnutls.c
src/utils/common/exec.c
src/utils/common/exec.h
src/utils/common/params.h
src/utils/kdig/kdig_exec.c
src/utils/kdig/kdig_params.c

index 6a2f3b1955c4f141a289759c5d42cd9e457a84c1..28cf6a602044d85d9ab008419ffa2405bfcd34d0 100644 (file)
@@ -337,6 +337,9 @@ is an optional hex encoded string to use as EDNS option value. This argument
 can be used multiple times. +noednsopt clears all EDNS options specified by
 +ednsopt.
 .TP
+\fB+\fP[\fBno\fP]\fBjson\fP
+Use JSON for output encoding (RFC 8427).
+.TP
 \fB+noidn\fP
 Disable the IDN transformation to ASCII and vice versa. IDN support depends
 on libidn availability during project building! If used in \fIcommon\-settings\fP,
index 1c37706ae49170f8dfc1b2e4f995e3fae5e18a7b..3cfeb51cc069af2ea944c8c73b040968e5430795 100644 (file)
@@ -316,6 +316,9 @@ Options
   can be used multiple times. +noednsopt clears all EDNS options specified by
   +ednsopt.
 
+**+**\ [\ **no**\ ]\ **json**
+  Use JSON for output encoding (RFC 8427).
+
 **+noidn**
   Disable the IDN transformation to ASCII and vice versa. IDN support depends
   on libidn availability during project building! If used in *common-settings*,
index c46c595580e7b099201d28b6a4e56a36400bd9a6..8b8bda4ea13a139a8bbb0e931502aa0ce58afa21 100644 (file)
@@ -20,6 +20,8 @@
 
 #include "contrib/json.h"
 
+#include "contrib/string.h"
+
 #define MAX_DEPTH 8
 
 enum {
@@ -201,6 +203,17 @@ void jsonw_bool(jsonw_t *w, const char *key, bool value)
        fprintf(w->out, "%s", value ? "true" : "false");
 }
 
+void jsonw_hex(jsonw_t *w, const char *key, const uint8_t *data, size_t len)
+{
+       assert(w);
+
+       char *hex = bin_to_hex(data, len, true);
+       if (hex != NULL) {
+               jsonw_str(w, key, hex);
+       }
+       free(hex);
+}
+
 void jsonw_end(jsonw_t *w)
 {
        assert(w);
index d57b5976f1ab72239252eacfbf1d9a2e7f548195..983b3299bad59be29aeabe4a94ab83817944bacb 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <stdio.h>
 #include <stdbool.h>
+#include <stdint.h>
 
 /*!
  * Simple pretty JSON writer.
@@ -75,6 +76,11 @@ void jsonw_int(jsonw_t *w, const char *key, int value);
  */
 void jsonw_bool(jsonw_t *w, const char *key, bool value);
 
+/*!
+ * Write binary data encoded to HEX as JSON.
+ */
+void jsonw_hex(jsonw_t *w, const char *key, const uint8_t *data, size_t len);
+
 /*!
  * Terminate in-progress object or list.
  */
index b4966adafed74b0204eca5e1f6fbf5207c7aa59a..272116e725dbe9386b448b35803a57e474f9f08b 100644 (file)
@@ -173,10 +173,11 @@ void *memzero(void *s, size_t n)
 }
 
 static const char BIN_TO_HEX[] = {
-       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
 };
 
-char *bin_to_hex(const uint8_t *bin, size_t bin_len)
+char *bin_to_hex(const uint8_t *bin, size_t bin_len, bool upper_case)
 {
        if (bin == NULL) {
                return NULL;
@@ -188,9 +189,10 @@ char *bin_to_hex(const uint8_t *bin, size_t bin_len)
                return NULL;
        }
 
+       unsigned offset = upper_case ? 16 : 0;
        for (size_t i = 0; i < bin_len; i++) {
-               hex[2 * i]     = BIN_TO_HEX[bin[i] >> 4];
-               hex[2 * i + 1] = BIN_TO_HEX[bin[i] & 0x0f];
+               hex[2 * i]     = BIN_TO_HEX[offset + (bin[i] >> 4)];
+               hex[2 * i + 1] = BIN_TO_HEX[offset + (bin[i] & 0x0f)];
        }
        hex[hex_size] = '\0';
 
index 6e524e56b98e0ad1203f9918e495b03fc8d536b8..ad3c990bae0ffd10e5197562c805c7591bb02159 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  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
@@ -20,6 +20,7 @@
 
 #pragma once
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
@@ -95,7 +96,7 @@ void *memzero(void *s, size_t n);
 /*!
  * \brief Convert binary data to hexadecimal string.
  */
-char *bin_to_hex(const uint8_t *bin, size_t bin_len);
+char *bin_to_hex(const uint8_t *bin, size_t bin_len, bool upper_case);
 
 /*!
  * \brief Convert hex encoded string to binary data.
index f1ede167473f869a2c76d52b4b4b13e062d8a3f3..e05442bf4d18e65e6d59de33cc0d09c8bd358713 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  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
@@ -35,7 +35,7 @@ static knot_dname_t *catalog_member_owner(const knot_dname_t *member,
        SipHash24_Update(&hash, &u64time, sizeof(u64time));
        uint64_t hashres = SipHash24_End(&hash);
 
-       char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres));
+       char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres), false);
        if (hexhash == NULL) {
                return NULL;
        }
index 0780fbb2d62825d31232c637b87f50243eb937ca..2b8b6d54ee55c0cdb33171ddf2084f73e3bf5b71 100644 (file)
@@ -214,7 +214,7 @@ static int pkcs11_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm,
                return DNSSEC_KEY_GENERATE_ERROR;
        }
 
-       char *id = bin_to_hex(cka_id.data, cka_id.size);
+       char *id = bin_to_hex(cka_id.data, cka_id.size, false);
        if (id == NULL) {
                return DNSSEC_ENOMEM;
        }
@@ -297,7 +297,7 @@ static int pkcs11_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_p
                return DNSSEC_KEY_IMPORT_ERROR;
        }
 
-       *id_ptr = bin_to_hex(id.data, id.size);
+       *id_ptr = bin_to_hex(id.data, id.size, false);
        if (*id_ptr == NULL) {
                return DNSSEC_ENOMEM;
        }
index 39ab9a695a35acf7e5fbc613adfdf6269dd4e4f0..eee27d31cf46f20df21ed9265a47dfa46b8cc2af 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  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
@@ -70,7 +70,7 @@ static int keyid_hex(gnutls_x509_privkey_t key, gnutls_pubkey_t pubkey, char **i
                return r;
        }
 
-       *id = bin_to_hex(bin.data, bin.size);
+       *id = bin_to_hex(bin.data, bin.size, false);
        if (*id == NULL) {
                return DNSSEC_ENOMEM;
        }
index 75dd3c3170ae84522c864949c69e895d3100278c..3d176f3871c4800751fb7af96141b2691f93bd7a 100644 (file)
@@ -30,6 +30,8 @@
 #include "contrib/ucw/lists.h"
 #include "contrib/wire_ctx.h"
 
+static const char *JSON_INDENT = "  ";
+
 static knot_lookup_t rtypes[] = {
        { KNOT_RRTYPE_A,      "has IPv4 address" },
        { KNOT_RRTYPE_NS,     "nameserver is" },
@@ -534,6 +536,124 @@ static void print_error_host(const knot_pkt_t *packet, const style_t *style)
        free(owner);
 }
 
+static void json_dname(jsonw_t *w, const char *key, const knot_dname_t *dname)
+{
+       knot_dname_txt_storage_t name;
+       if (knot_dname_to_str(name, dname, sizeof(name)) != NULL) {
+               jsonw_str(w, key, name);
+       }
+}
+
+static void json_rdata(jsonw_t *w, const knot_rrset_t *rrset)
+{
+       char type[16];
+       if (knot_rrtype_to_string(rrset->type, type, sizeof(type)) <= 0 ||
+           strncmp(type, "TYPE", 4) == 0) { // Unknown/hex format.
+               return;
+       }
+
+       char key[32] = "rdata";
+       strlcat(key, type, sizeof(key));
+
+       char data[16384];
+       const knot_dump_style_t *style = &KNOT_DUMP_STYLE_DEFAULT;
+       if (knot_rrset_txt_dump_data(rrset, 0, data, sizeof(data), style) > 0) {
+               jsonw_str(w, key, data);
+       }
+}
+
+static void json_print_section(jsonw_t *w, const char *name,
+                               const knot_pktsection_t *section)
+{
+       if (section->count == 0) {
+               return;
+       }
+
+       char str[16];
+
+       jsonw_list(w, name);
+
+       for (int i = 0; i < section->count; i++) {
+               const knot_rrset_t *rr = knot_pkt_rr(section, i);
+               jsonw_object(w, NULL);
+               json_dname(w, "NAME", rr->owner);
+               jsonw_int(w, "TYPE", rr->type);
+               if (knot_rrtype_to_string(rr->type, str, sizeof(str)) > 0) {
+                       jsonw_str(w, "TYPEname", str);
+               }
+               jsonw_int(w, "CLASS", rr->rclass);
+               if (rr->type != KNOT_RRTYPE_OPT && // OPT class meaning is different.
+                   knot_rrclass_to_string(rr->rclass, str, sizeof(str)) > 0) {
+                       jsonw_str(w, "CLASSname", str);
+               }
+               jsonw_int(w, "TTL", rr->ttl);
+               if (rr->type != KNOT_RRTYPE_OPT) { // OPT with HEX rdata.
+                       json_rdata(w, rr);
+               }
+               jsonw_int(w, "RDLENGTH", rr->rrs.rdata->len);
+               if (rr->rrs.rdata->len > 0 ) {
+                       jsonw_hex(w, "RDATAHEX", rr->rrs.rdata->data, rr->rrs.rdata->len);
+               }
+               jsonw_end(w);
+       }
+
+       jsonw_end(w);
+}
+
+static void print_packet_json(jsonw_t *w, const knot_pkt_t *pkt, time_t time)
+{
+       if (pkt == NULL) {
+               return;
+       }
+
+       char str[16];
+
+       struct tm tm;
+       char date[64];
+       localtime_r(&time, &tm);
+       strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S%z", &tm);
+       jsonw_str(w, "dateString", date);
+       jsonw_int(w, "dateSeconds", time);
+
+       jsonw_int(w, "msgLength", pkt->size);
+
+       if (pkt->parsed >= KNOT_WIRE_HEADER_SIZE) {
+               jsonw_int(w, "ID", knot_wire_get_id(pkt->wire));
+               jsonw_int(w, "QR", (bool)knot_wire_get_qr(pkt->wire));
+               jsonw_int(w, "Opcode", knot_wire_get_opcode(pkt->wire));
+               jsonw_int(w, "AA", (bool)knot_wire_get_aa(pkt->wire));
+               jsonw_int(w, "TC", (bool)knot_wire_get_tc(pkt->wire));
+               jsonw_int(w, "RD", (bool)knot_wire_get_rd(pkt->wire));
+               jsonw_int(w, "RA", (bool)knot_wire_get_ra(pkt->wire));
+               jsonw_int(w, "AD", (bool)knot_wire_get_ad(pkt->wire));
+               jsonw_int(w, "CD", (bool)knot_wire_get_cd(pkt->wire));
+               jsonw_int(w, "RCODE", knot_wire_get_rcode(pkt->wire));
+               jsonw_int(w, "QDCOUNT", knot_wire_get_qdcount(pkt->wire));
+               jsonw_int(w, "ANCOUNT", knot_wire_get_ancount(pkt->wire));
+               jsonw_int(w, "NSCOUNT", knot_wire_get_nscount(pkt->wire));
+               jsonw_int(w, "ARCOUNT", knot_wire_get_arcount(pkt->wire));
+       }
+       if (knot_wire_get_qdcount(pkt->wire) == 1) {
+               json_dname(w, "QNAME", knot_pkt_qname(pkt));
+               jsonw_int(w, "QTYPE", knot_pkt_qtype(pkt));
+               if (knot_rrtype_to_string(knot_pkt_qtype(pkt), str, sizeof(str)) > 0) {
+                       jsonw_str(w, "QTYPEname", str);
+               }
+               jsonw_int(w, "QCLASS", knot_pkt_qclass(pkt));
+               if (knot_rrclass_to_string(knot_pkt_qclass(pkt), str, sizeof(str)) > 0) {
+                       jsonw_str(w, "QCLASSname", str);
+               }
+       }
+       if (pkt->rrset_count) {
+               json_print_section(w, "answerRRs", knot_pkt_section(pkt, KNOT_ANSWER));
+               json_print_section(w, "authorityRRs", knot_pkt_section(pkt, KNOT_AUTHORITY));
+               json_print_section(w, "additionalRRs", knot_pkt_section(pkt, KNOT_ADDITIONAL));
+       }
+       if (pkt->parsed < pkt->size) {
+               jsonw_hex(w, "messageOctetsHEX", pkt->wire, pkt->size);
+       }
+}
+
 knot_pkt_t *create_empty_packet(const uint16_t max_size)
 {
        // Create packet skeleton.
@@ -549,6 +669,64 @@ knot_pkt_t *create_empty_packet(const uint16_t max_size)
        return packet;
 }
 
+jsonw_t *print_header_xfr_json(const knot_pkt_t *query,
+                               const time_t     exec_time,
+                               const style_t    *style)
+{
+       if (style == NULL) {
+               DBG_NULL;
+               return NULL;
+       }
+
+       jsonw_t *w = jsonw_new(stdout, JSON_INDENT);
+       if (w == NULL) {
+               return NULL;
+       }
+
+       if (style->show_query) {
+               jsonw_object(w, NULL);
+               jsonw_object(w, "queryMessage");
+               print_packet_json(w, query, exec_time);
+               jsonw_end(w);
+               jsonw_list(w, "responseMessage");
+       } else {
+               jsonw_list(w, NULL);
+       }
+
+       return w;
+}
+
+void print_data_xfr_json(jsonw_t          *w,
+                         const knot_pkt_t *reply,
+                         const time_t     exec_time)
+{
+       if (w == NULL) {
+               DBG_NULL;
+               return;
+       }
+
+       jsonw_object(w, NULL);
+       print_packet_json(w, reply, exec_time);
+       jsonw_end(w);
+}
+
+void print_footer_xfr_json(jsonw_t       **w,
+                           const style_t *style)
+{
+       if (w == NULL || style == NULL) {
+               DBG_NULL;
+               return;
+       }
+
+       jsonw_end(*w); // list (responseMessage)
+       if (style->show_query) {
+               jsonw_end(*w); // object
+       }
+
+       jsonw_free(w);
+       *w = NULL;
+}
+
 void print_header_xfr(const knot_pkt_t *packet, const style_t *style)
 {
        if (style == NULL) {
@@ -629,6 +807,40 @@ void print_footer_xfr(const size_t  total_len,
        }
 }
 
+void print_packets_json(const knot_pkt_t *query,
+                        const knot_pkt_t *reply,
+                        const net_t      *net,
+                        const time_t     exec_time,
+                        const style_t    *style)
+{
+       if (style == NULL) {
+               DBG_NULL;
+               return;
+       }
+
+       jsonw_t *w = jsonw_new(stdout, JSON_INDENT);
+       if (w == NULL) {
+               return;
+       }
+       jsonw_object(w, NULL);
+
+       if (style->show_query) {
+               jsonw_object(w, "queryMessage");
+               print_packet_json(w, query, exec_time);
+               jsonw_end(w);
+               jsonw_object(w, "responseMessage");
+       }
+
+       print_packet_json(w, reply, exec_time);
+
+       if (style->show_query) {
+               jsonw_end(w);
+       }
+
+       jsonw_end(w);
+       jsonw_free(&w);
+}
+
 void print_packet(const knot_pkt_t *packet,
                   const net_t      *net,
                   const size_t     size,
index f96b1b04a0105ad89a30c0bb24cf95d4fb8f928a..359926c04e675b509fdbd4c2e6aebdab55c89dfe 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  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
@@ -21,6 +21,7 @@
 #include "utils/common/netio.h"
 #include "utils/common/params.h"
 #include "libknot/libknot.h"
+#include "contrib/json.h"
 
 /*!
  * \brief Allocates empty packet and sets packet size and random id.
@@ -68,9 +69,58 @@ void print_footer_xfr(const size_t  total_len,
                       const style_t *style);
 
 /*!
- * \brief Prints one response packet.
+ * \brief Prints initial JSON part of XFR output.
  *
- * \param packet       Response packet.
+ * \param query                Query packet.
+ * \param exec_time    Time of the packet creation.
+ * \param style                Style of the output.
+ *
+ * \retval JSON witter if success.
+ * \retval NULL                if error.
+ */
+jsonw_t *print_header_xfr_json(const knot_pkt_t *query,
+                               const time_t     exec_time,
+                               const style_t    *style);
+
+/*!
+ * \brief Prints one XFR reply packet in JSON.
+ *
+ * \param w            JSON writter.
+ * \param reply                Reply packet (possibly one of many).
+ * \param exec_time    Time of the packet creation.
+ */
+void print_data_xfr_json(jsonw_t          *w,
+                         const knot_pkt_t *reply,
+                         const time_t     exec_time);
+
+/*!
+ * \brief Prints trailing JSON part of XFR output.
+ *
+ * \param w            JSON writter.
+ * \param style                Style of the output.
+ */
+void print_footer_xfr_json(jsonw_t       **w,
+                           const style_t *style);
+
+/*!
+ * \brief Prints one or query/reply pair of DNS packets in JSON format.
+ *
+ * \param query                Query DNS packet.
+ * \param reply                Reply DNS packet.
+ * \param net          Connection information.
+ * \param exec_time    Time of the packet creation.
+ * \param style                Style of the output.
+ */
+void print_packets_json(const knot_pkt_t *query,
+                        const knot_pkt_t *reply,
+                        const net_t      *net,
+                        const time_t     exec_time,
+                        const style_t    *style);
+
+/*!
+ * \brief Prints one DNS packet.
+ *
+ * \param packet       DNS packet.
  * \param net          Connection information.
  * \param size         Original packet wire size.
  * \param elapsed      Total elapsed time.
index f39084bc818276b135d4b7b82d1e7ad9c4fc6a73..d70d3e028db5d9fa848c31b5840a7fed875287d5 100644 (file)
@@ -59,7 +59,9 @@ typedef enum {
        /*!< Brief host output. */
        FORMAT_HOST,
        /*!< Brief nsupdate output. */
-       FORMAT_NSUPDATE
+       FORMAT_NSUPDATE,
+       /*!< Machine readable JSON format (RFC 8427). */
+       FORMAT_JSON
 } format_t;
 
 /*! \brief Text output settings. */
index 85f33430487b862a09d33c06d29c2b4bdc2b9f2d..e68069c072996e6e077722d143c8828edc040717 100644 (file)
@@ -25,6 +25,7 @@
 #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"
@@ -626,7 +627,7 @@ static int process_query_packet(const knot_pkt_t      *query,
 {
        struct timespec t_start, t_query, t_query_full, t_end, t_end_full;
        time_t          timestamp;
-       knot_pkt_t      *reply;
+       knot_pkt_t      *reply = NULL;
        uint8_t         in[MAX_PACKET_SIZE];
        int             in_len;
        int             ret;
@@ -662,7 +663,7 @@ static int process_query_packet(const knot_pkt_t      *query,
 #endif // USE_DNSTAP
 
        // Print query packet if required.
-       if (style->show_query) {
+       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) {
@@ -683,11 +684,12 @@ static int process_query_packet(const knot_pkt_t      *query,
 
        // 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) {
-                       net_close(net);
-                       return -1;
+                       goto fail;
                }
 
                // Get stop reply time.
@@ -705,8 +707,7 @@ static int process_query_packet(const knot_pkt_t      *query,
                reply = knot_pkt_new(in, in_len, NULL);
                if (reply == NULL) {
                        ERR("internal error (%s)", knot_strerror(KNOT_ENOMEM));
-                       net_close(net);
-                       return -1;
+                       goto fail;
                }
 
                // Parse reply to the packet structure.
@@ -715,9 +716,7 @@ static int process_query_packet(const knot_pkt_t      *query,
                        WARN("malformed reply packet (%s)", knot_strerror(ret));
                } else if (ret != KNOT_EOK) {
                        ERR("malformed reply packet from %s", net->remote_str);
-                       knot_pkt_free(reply);
-                       net_close(net);
-                       return -1;
+                       goto fail;
                }
 
                // Compare reply header id.
@@ -725,9 +724,7 @@ static int process_query_packet(const knot_pkt_t      *query,
                        break;
                // Check for timeout.
                } else if (time_diff_ms(&t_query, &t_end) > 1000 * net->wait) {
-                       knot_pkt_free(reply);
-                       net_close(net);
-                       return -1;
+                       goto fail;
                }
 
                knot_pkt_free(reply);
@@ -755,8 +752,15 @@ static int process_query_packet(const knot_pkt_t      *query,
        check_reply_qr(reply);
 
        // Print reply packet.
-       print_packet(reply, net, in_len, time_diff_ms(&t_start, &t_end), timestamp,
-                    true, style);
+       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) {
@@ -780,17 +784,15 @@ static int process_query_packet(const knot_pkt_t      *query,
                uint8_t *opt = knot_pkt_edns_option(reply, KNOT_EDNS_OPTION_COOKIE);
                if (opt == NULL) {
                        ERR("bad cookie, missing EDNS section");
-                       knot_pkt_free(reply);
-                       return -1;
+                       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) {
-                       knot_pkt_free(reply);
                        ERR("bad cookie, missing EDNS cookie option");
-                       return -1;
+                       goto fail;
                }
                knot_pkt_free(reply);
 
@@ -811,6 +813,19 @@ static int process_query_packet(const knot_pkt_t      *query,
        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)
@@ -950,6 +965,7 @@ static int process_xfr_packet(const knot_pkt_t      *query,
        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);
@@ -982,7 +998,7 @@ static int process_xfr_packet(const knot_pkt_t      *query,
 #endif // USE_DNSTAP
 
        // Print query packet if required.
-       if (style->show_query) {
+       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) {
@@ -1003,6 +1019,8 @@ static int process_xfr_packet(const knot_pkt_t      *query,
 
        // 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) {
@@ -1044,7 +1062,14 @@ static int process_xfr_packet(const knot_pkt_t      *query,
 
                // Print leading transfer information.
                if (msg_count == 0) {
-                       print_header_xfr(query, style);
+                       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.
@@ -1065,7 +1090,9 @@ static int process_xfr_packet(const knot_pkt_t      *query,
                                                .style = style->style,
                                                .show_tsig = true
                                        };
-                                       print_data_xfr(reply, &tsig_style);
+                                       if (style->format != FORMAT_JSON) {
+                                               print_data_xfr(reply, &tsig_style);
+                                       }
 
                                        ERR("reply verification for %s (%s)",
                                            net->remote_str, knot_strerror(ret));
@@ -1094,7 +1121,11 @@ static int process_xfr_packet(const knot_pkt_t      *query,
                total_len += in_len;
 
                // Print reply packet.
-               print_data_xfr(reply, style);
+               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)) {
@@ -1108,8 +1139,12 @@ static int process_xfr_packet(const knot_pkt_t      *query,
 
        // Print full transfer information.
        t_end = time_now();
-       print_footer_xfr(total_len, msg_count, rr_count, net,
-                        time_diff_ms(&t_query, &t_end), timestamp, style);
+       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);
 
@@ -1118,11 +1153,17 @@ static int process_xfr_packet(const knot_pkt_t      *query,
 fail:
        // Print partial transfer information.
        t_end = time_now();
-       print_footer_xfr(total_len, msg_count, rr_count, net,
-                        time_diff_ms(&t_query, &t_end), timestamp, style);
+       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;
 }
index a67b7964edd1783af1dd30bb47d0a03706d63dbe..159ba35fbeac795d2dbb000e3a06506ad50054c8 100644 (file)
@@ -1346,6 +1346,24 @@ static int opt_noidn(const char *arg, void *query)
        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_EINVAL;
+}
+
 static const param_t kdig_opts2[] = {
        { "multiline",      ARG_NONE,     opt_multiline },
        { "nomultiline",    ARG_NONE,     opt_nomultiline },
@@ -1510,6 +1528,9 @@ static const param_t kdig_opts2[] = {
        { "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 },
 
@@ -2243,6 +2264,7 @@ static void print_help(void)
               "       +[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]json                  Use JSON for output encoding (RFC 8427).\n"
               "       +noidn                     Disable IDN transformation.\n"
               "\n"
               "       -h, --help                 Print the program help.\n"