xref: /netbsd-src/external/mpl/bind/dist/bin/tools/dnstap-read.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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