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,
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*,
#include "contrib/json.h"
+#include "contrib/string.h"
+
#define MAX_DEPTH 8
enum {
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);
#include <stdio.h>
#include <stdbool.h>
+#include <stdint.h>
/*!
* Simple pretty JSON writer.
*/
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.
*/
}
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;
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';
-/* 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
#pragma once
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*!
* \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.
-/* 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
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;
}
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;
}
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;
}
-/* 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
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;
}
#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" },
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.
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) {
}
}
+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,
-/* 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
#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.
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.
/*!< 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. */
#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"
{
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;
#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) {
// 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.
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.
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.
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);
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) {
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);
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)
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);
#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) {
// 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) {
// 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.
.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));
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)) {
// 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);
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;
}
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 },
{ "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 },
" +[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"