Commit fff3431c authored by Jan Hák's avatar Jan Hák Committed by Daniel Salzman
Browse files

kdig: add +json (RFC 8427) option

parent 0ba75bdc
Pipeline #104253 passed with stages
in 5 minutes and 54 seconds
......@@ -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,
......
......@@ -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*,
......
......@@ -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);
......
......@@ -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.
*/
......
......@@ -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';
......
/* 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.
......
/* 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;
}
......
......@@ -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;
}
......
/* 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;
}
......
......@@ -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,
......
/* 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.
......
......@@ -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. */
......
......@@ -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;
}