Home | History | Annotate | Line # | Download | only in tools
      1 /*	$NetBSD: dnstap-read.c,v 1.11 2025/01/26 16:25:10 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*
     17  * Portions of this code were adapted from dnstap-ldns:
     18  *
     19  * Copyright (c) 2014 by Farsight Security, Inc.
     20  *
     21  * Licensed under the Apache License, Version 2.0 (the "License");
     22  * you may not use this file except in compliance with the License.
     23  * You may obtain a copy of the License at
     24  *
     25  *    http://www.apache.org/licenses/LICENSE-2.0
     26  *
     27  * Unless required by applicable law or agreed to in writing, software
     28  * distributed under the License is distributed on an "AS IS" BASIS,
     29  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     30  * See the License for the specific language governing permissions and
     31  * limitations under the License.
     32  */
     33 
     34 #include <inttypes.h>
     35 #include <stdbool.h>
     36 #include <stdlib.h>
     37 #include <unistd.h>
     38 
     39 #include <protobuf-c/protobuf-c.h>
     40 
     41 #include <isc/attributes.h>
     42 #include <isc/buffer.h>
     43 #include <isc/commandline.h>
     44 #include <isc/hex.h>
     45 #include <isc/mem.h>
     46 #include <isc/result.h>
     47 #include <isc/string.h>
     48 #include <isc/util.h>
     49 
     50 #include <dns/dnstap.h>
     51 #include <dns/fixedname.h>
     52 #include <dns/masterdump.h>
     53 #include <dns/message.h>
     54 #include <dns/name.h>
     55 
     56 #include "dnstap.pb-c.h"
     57 
     58 isc_mem_t *mctx = NULL;
     59 bool memrecord = false;
     60 bool printmessage = false;
     61 bool hexmessage = false;
     62 bool yaml = false;
     63 bool timestampmillis = false;
     64 
     65 const char *program = "dnstap-read";
     66 
     67 #define CHECKM(op, msg)                                               \
     68 	do {                                                          \
     69 		result = (op);                                        \
     70 		if (result != ISC_R_SUCCESS) {                        \
     71 			fprintf(stderr, "%s: %s: %s\n", program, msg, \
     72 				isc_result_totext(result));           \
     73 			goto cleanup;                                 \
     74 		}                                                     \
     75 	} while (0)
     76 
     77 noreturn static void
     78 fatal(const char *format, ...);
     79 
     80 static void
     81 fatal(const char *format, ...) {
     82 	va_list args;
     83 
     84 	fprintf(stderr, "%s: fatal: ", program);
     85 	va_start(args, format);
     86 	vfprintf(stderr, format, args);
     87 	va_end(args);
     88 	fprintf(stderr, "\n");
     89 	_exit(EXIT_FAILURE);
     90 }
     91 
     92 static void
     93 usage(void) {
     94 	fprintf(stderr, "dnstap-read [-mpxy] [filename]\n");
     95 	fprintf(stderr, "\t-m\ttrace memory allocations\n");
     96 	fprintf(stderr, "\t-p\tprint the full DNS message\n");
     97 	fprintf(stderr,
     98 		"\t-t\tprint long timestamps with millisecond precision\n");
     99 	fprintf(stderr, "\t-x\tuse hex format to print DNS message\n");
    100 	fprintf(stderr, "\t-y\tprint YAML format (implies -p)\n");
    101 }
    102 
    103 static void
    104 print_dtdata(dns_dtdata_t *dt) {
    105 	isc_result_t result;
    106 	isc_buffer_t *b = NULL;
    107 
    108 	isc_buffer_allocate(mctx, &b, 2048);
    109 	if (b == NULL) {
    110 		fatal("out of memory");
    111 	}
    112 
    113 	CHECKM(dns_dt_datatotext(dt, &b), "dns_dt_datatotext");
    114 	printf("%.*s\n", (int)isc_buffer_usedlength(b),
    115 	       (char *)isc_buffer_base(b));
    116 
    117 cleanup:
    118 	if (b != NULL) {
    119 		isc_buffer_free(&b);
    120 	}
    121 }
    122 
    123 static void
    124 print_hex(dns_dtdata_t *dt) {
    125 	isc_buffer_t *b = NULL;
    126 	isc_result_t result;
    127 	size_t textlen;
    128 
    129 	if (dt->msg == NULL) {
    130 		return;
    131 	}
    132 
    133 	textlen = (dt->msgdata.length * 2) + 1;
    134 	isc_buffer_allocate(mctx, &b, textlen);
    135 	if (b == NULL) {
    136 		fatal("out of memory");
    137 	}
    138 
    139 	result = isc_hex_totext(&dt->msgdata, 0, "", b);
    140 	CHECKM(result, "isc_hex_totext");
    141 
    142 	printf("%.*s\n", (int)isc_buffer_usedlength(b),
    143 	       (char *)isc_buffer_base(b));
    144 
    145 cleanup:
    146 	if (b != NULL) {
    147 		isc_buffer_free(&b);
    148 	}
    149 }
    150 
    151 static void
    152 print_packet(dns_dtdata_t *dt, const dns_master_style_t *style) {
    153 	isc_buffer_t *b = NULL;
    154 	isc_result_t result;
    155 
    156 	if (dt->msg != NULL) {
    157 		size_t textlen = 2048;
    158 
    159 		isc_buffer_allocate(mctx, &b, textlen);
    160 		if (b == NULL) {
    161 			fatal("out of memory");
    162 		}
    163 
    164 		for (;;) {
    165 			isc_buffer_reserve(b, textlen);
    166 			if (b == NULL) {
    167 				fatal("out of memory");
    168 			}
    169 
    170 			result = dns_message_totext(dt->msg, style, 0, b);
    171 			if (result == ISC_R_NOSPACE) {
    172 				isc_buffer_clear(b);
    173 				textlen *= 2;
    174 				continue;
    175 			} else if (result == ISC_R_SUCCESS) {
    176 				printf("%.*s", (int)isc_buffer_usedlength(b),
    177 				       (char *)isc_buffer_base(b));
    178 				isc_buffer_free(&b);
    179 			} else {
    180 				isc_buffer_free(&b);
    181 				CHECKM(result, "dns_message_totext");
    182 			}
    183 			break;
    184 		}
    185 	}
    186 
    187 cleanup:
    188 	if (b != NULL) {
    189 		isc_buffer_free(&b);
    190 	}
    191 }
    192 
    193 static void
    194 print_yaml(dns_dtdata_t *dt) {
    195 	Dnstap__Dnstap *frame = dt->frame;
    196 	Dnstap__Message *m = frame->message;
    197 	const ProtobufCEnumValue *ftype, *mtype;
    198 	static bool first = true;
    199 
    200 	ftype = protobuf_c_enum_descriptor_get_value(
    201 		&dnstap__dnstap__type__descriptor, frame->type);
    202 	if (ftype == NULL) {
    203 		return;
    204 	}
    205 
    206 	if (!first) {
    207 		printf("---\n");
    208 	} else {
    209 		first = false;
    210 	}
    211 
    212 	printf("type: %s\n", ftype->name);
    213 
    214 	if (frame->has_identity) {
    215 		printf("identity: %.*s\n", (int)frame->identity.len,
    216 		       frame->identity.data);
    217 	}
    218 
    219 	if (frame->has_version) {
    220 		printf("version: %.*s\n", (int)frame->version.len,
    221 		       frame->version.data);
    222 	}
    223 
    224 	if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) {
    225 		return;
    226 	}
    227 
    228 	printf("message:\n");
    229 
    230 	mtype = protobuf_c_enum_descriptor_get_value(
    231 		&dnstap__message__type__descriptor, m->type);
    232 	if (mtype == NULL) {
    233 		return;
    234 	}
    235 
    236 	printf("  type: %s\n", mtype->name);
    237 
    238 	if (!isc_time_isepoch(&dt->qtime)) {
    239 		char buf[100];
    240 		if (timestampmillis) {
    241 			isc_time_formatISO8601ms(&dt->qtime, buf, sizeof(buf));
    242 		} else {
    243 			isc_time_formatISO8601(&dt->qtime, buf, sizeof(buf));
    244 		}
    245 		printf("  query_time: !!timestamp %s\n", buf);
    246 	}
    247 
    248 	if (!isc_time_isepoch(&dt->rtime)) {
    249 		char buf[100];
    250 		if (timestampmillis) {
    251 			isc_time_formatISO8601ms(&dt->rtime, buf, sizeof(buf));
    252 		} else {
    253 			isc_time_formatISO8601(&dt->rtime, buf, sizeof(buf));
    254 		}
    255 		printf("  response_time: !!timestamp %s\n", buf);
    256 	}
    257 
    258 	if (dt->msgdata.base != NULL) {
    259 		printf("  message_size: %zub\n", (size_t)dt->msgdata.length);
    260 	} else {
    261 		printf("  message_size: 0b\n");
    262 	}
    263 
    264 	if (m->has_socket_family) {
    265 		const ProtobufCEnumValue *type =
    266 			protobuf_c_enum_descriptor_get_value(
    267 				&dnstap__socket_family__descriptor,
    268 				m->socket_family);
    269 		if (type != NULL) {
    270 			printf("  socket_family: %s\n", type->name);
    271 		}
    272 	}
    273 
    274 	printf("  socket_protocol: %s\n",
    275 	       dt->transport == DNS_TRANSPORT_UDP ? "UDP" : "TCP");
    276 
    277 	if (m->has_query_address) {
    278 		ProtobufCBinaryData *ip = &m->query_address;
    279 		char buf[100];
    280 
    281 		(void)inet_ntop(ip->len == 4 ? AF_INET : AF_INET6, ip->data,
    282 				buf, sizeof(buf));
    283 		printf("  query_address: \"%s\"\n", buf);
    284 	}
    285 
    286 	if (m->has_response_address) {
    287 		ProtobufCBinaryData *ip = &m->response_address;
    288 		char buf[100];
    289 
    290 		(void)inet_ntop(ip->len == 4 ? AF_INET : AF_INET6, ip->data,
    291 				buf, sizeof(buf));
    292 		printf("  response_address: \"%s\"\n", buf);
    293 	}
    294 
    295 	if (m->has_query_port) {
    296 		printf("  query_port: %u\n", m->query_port);
    297 	}
    298 
    299 	if (m->has_response_port) {
    300 		printf("  response_port: %u\n", m->response_port);
    301 	}
    302 
    303 	if (m->has_query_zone) {
    304 		isc_result_t result;
    305 		dns_fixedname_t fn;
    306 		dns_name_t *name;
    307 		isc_buffer_t b;
    308 
    309 		name = dns_fixedname_initname(&fn);
    310 
    311 		isc_buffer_init(&b, m->query_zone.data, m->query_zone.len);
    312 		isc_buffer_add(&b, m->query_zone.len);
    313 
    314 		result = dns_name_fromwire(name, &b, DNS_DECOMPRESS_NEVER,
    315 					   NULL);
    316 		if (result == ISC_R_SUCCESS) {
    317 			printf("  query_zone: ");
    318 			dns_name_print(name, stdout);
    319 			printf("\n");
    320 		}
    321 	}
    322 
    323 	if (dt->msg != NULL) {
    324 		dt->msg->indent.count = 2;
    325 		dt->msg->indent.string = "  ";
    326 		printf("  %s:\n", ((dt->type & DNS_DTTYPE_QUERY) != 0)
    327 					  ? "query_message_data"
    328 					  : "response_message_data");
    329 
    330 		print_packet(dt, &dns_master_style_yaml);
    331 
    332 		printf("  %s: |\n", ((dt->type & DNS_DTTYPE_QUERY) != 0)
    333 					    ? "query_message"
    334 					    : "response_message");
    335 		print_packet(dt, &dns_master_style_indent);
    336 	}
    337 }
    338 
    339 int
    340 main(int argc, char *argv[]) {
    341 	isc_result_t result;
    342 	dns_message_t *message = NULL;
    343 	dns_dtdata_t *dt = NULL;
    344 	dns_dthandle_t *handle = NULL;
    345 	int rv = 0, ch;
    346 
    347 	while ((ch = isc_commandline_parse(argc, argv, "mptxy")) != -1) {
    348 		switch (ch) {
    349 		case 'm':
    350 			isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
    351 			memrecord = true;
    352 			break;
    353 		case 'p':
    354 			printmessage = true;
    355 			break;
    356 		case 't':
    357 			timestampmillis = true;
    358 			break;
    359 		case 'x':
    360 			hexmessage = true;
    361 			break;
    362 		case 'y':
    363 			yaml = true;
    364 			break;
    365 		default:
    366 			usage();
    367 			exit(EXIT_FAILURE);
    368 		}
    369 	}
    370 
    371 	argc -= isc_commandline_index;
    372 	argv += isc_commandline_index;
    373 
    374 	if (argc < 1) {
    375 		fatal("no file specified");
    376 	}
    377 
    378 	isc_mem_create(&mctx);
    379 
    380 	CHECKM(dns_dt_open(argv[0], dns_dtmode_file, mctx, &handle),
    381 	       "dns_dt_openfile");
    382 
    383 	for (;;) {
    384 		isc_region_t input;
    385 		uint8_t *data;
    386 		size_t datalen;
    387 
    388 		result = dns_dt_getframe(handle, &data, &datalen);
    389 		if (result == ISC_R_NOMORE) {
    390 			break;
    391 		} else {
    392 			CHECKM(result, "dns_dt_getframe");
    393 		}
    394 
    395 		input.base = data;
    396 		input.length = datalen;
    397 
    398 		result = dns_dt_parse(mctx, &input, &dt);
    399 		if (result != ISC_R_SUCCESS) {
    400 			continue;
    401 		}
    402 
    403 		if (yaml) {
    404 			print_yaml(dt);
    405 		} else if (hexmessage) {
    406 			print_dtdata(dt);
    407 			print_hex(dt);
    408 		} else if (printmessage) {
    409 			print_dtdata(dt);
    410 			print_packet(dt, &dns_master_style_debug);
    411 		} else {
    412 			print_dtdata(dt);
    413 		}
    414 
    415 		dns_dtdata_free(&dt);
    416 	}
    417 
    418 cleanup:
    419 	if (dt != NULL) {
    420 		dns_dtdata_free(&dt);
    421 	}
    422 	if (handle != NULL) {
    423 		dns_dt_close(&handle);
    424 	}
    425 	if (message != NULL) {
    426 		dns_message_detach(&message);
    427 	}
    428 	isc_mem_destroy(&mctx);
    429 
    430 	exit(rv);
    431 }
    432