1/*	$NetBSD: dnstap-read.c,v 1.9 2024/02/21 22:51:41 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
38#include <protobuf-c/protobuf-c.h>
39
40#include <isc/attributes.h>
41#include <isc/buffer.h>
42#include <isc/commandline.h>
43#include <isc/hex.h>
44#include <isc/mem.h>
45#include <isc/print.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
58isc_mem_t *mctx = NULL;
59bool memrecord = false;
60bool printmessage = false;
61bool hexmessage = false;
62bool yaml = false;
63
64const char *program = "dnstap-read";
65
66#define CHECKM(op, msg)                                               \
67	do {                                                          \
68		result = (op);                                        \
69		if (result != ISC_R_SUCCESS) {                        \
70			fprintf(stderr, "%s: %s: %s\n", program, msg, \
71				isc_result_totext(result));           \
72			goto cleanup;                                 \
73		}                                                     \
74	} while (0)
75
76noreturn static void
77fatal(const char *format, ...);
78
79static void
80fatal(const char *format, ...) {
81	va_list args;
82
83	fprintf(stderr, "%s: fatal: ", program);
84	va_start(args, format);
85	vfprintf(stderr, format, args);
86	va_end(args);
87	fprintf(stderr, "\n");
88	exit(1);
89}
90
91static void
92usage(void) {
93	fprintf(stderr, "dnstap-read [-mpxy] [filename]\n");
94	fprintf(stderr, "\t-m\ttrace memory allocations\n");
95	fprintf(stderr, "\t-p\tprint the full DNS message\n");
96	fprintf(stderr, "\t-x\tuse hex format to print DNS message\n");
97	fprintf(stderr, "\t-y\tprint YAML format (implies -p)\n");
98}
99
100static void
101print_dtdata(dns_dtdata_t *dt) {
102	isc_result_t result;
103	isc_buffer_t *b = NULL;
104
105	isc_buffer_allocate(mctx, &b, 2048);
106	if (b == NULL) {
107		fatal("out of memory");
108	}
109
110	CHECKM(dns_dt_datatotext(dt, &b), "dns_dt_datatotext");
111	printf("%.*s\n", (int)isc_buffer_usedlength(b),
112	       (char *)isc_buffer_base(b));
113
114cleanup:
115	if (b != NULL) {
116		isc_buffer_free(&b);
117	}
118}
119
120static void
121print_hex(dns_dtdata_t *dt) {
122	isc_buffer_t *b = NULL;
123	isc_result_t result;
124	size_t textlen;
125
126	if (dt->msg == NULL) {
127		return;
128	}
129
130	textlen = (dt->msgdata.length * 2) + 1;
131	isc_buffer_allocate(mctx, &b, textlen);
132	if (b == NULL) {
133		fatal("out of memory");
134	}
135
136	result = isc_hex_totext(&dt->msgdata, 0, "", b);
137	CHECKM(result, "isc_hex_totext");
138
139	printf("%.*s\n", (int)isc_buffer_usedlength(b),
140	       (char *)isc_buffer_base(b));
141
142cleanup:
143	if (b != NULL) {
144		isc_buffer_free(&b);
145	}
146}
147
148static void
149print_packet(dns_dtdata_t *dt, const dns_master_style_t *style) {
150	isc_buffer_t *b = NULL;
151	isc_result_t result;
152
153	if (dt->msg != NULL) {
154		size_t textlen = 2048;
155
156		isc_buffer_allocate(mctx, &b, textlen);
157		if (b == NULL) {
158			fatal("out of memory");
159		}
160
161		for (;;) {
162			isc_buffer_reserve(&b, textlen);
163			if (b == NULL) {
164				fatal("out of memory");
165			}
166
167			result = dns_message_totext(dt->msg, style, 0, b);
168			if (result == ISC_R_NOSPACE) {
169				isc_buffer_clear(b);
170				textlen *= 2;
171				continue;
172			} else if (result == ISC_R_SUCCESS) {
173				printf("%.*s", (int)isc_buffer_usedlength(b),
174				       (char *)isc_buffer_base(b));
175				isc_buffer_free(&b);
176			} else {
177				isc_buffer_free(&b);
178				CHECKM(result, "dns_message_totext");
179			}
180			break;
181		}
182	}
183
184cleanup:
185	if (b != NULL) {
186		isc_buffer_free(&b);
187	}
188}
189
190static void
191print_yaml(dns_dtdata_t *dt) {
192	Dnstap__Dnstap *frame = dt->frame;
193	Dnstap__Message *m = frame->message;
194	const ProtobufCEnumValue *ftype, *mtype;
195	static bool first = true;
196
197	ftype = protobuf_c_enum_descriptor_get_value(
198		&dnstap__dnstap__type__descriptor, frame->type);
199	if (ftype == NULL) {
200		return;
201	}
202
203	if (!first) {
204		printf("---\n");
205	} else {
206		first = false;
207	}
208
209	printf("type: %s\n", ftype->name);
210
211	if (frame->has_identity) {
212		printf("identity: %.*s\n", (int)frame->identity.len,
213		       frame->identity.data);
214	}
215
216	if (frame->has_version) {
217		printf("version: %.*s\n", (int)frame->version.len,
218		       frame->version.data);
219	}
220
221	if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) {
222		return;
223	}
224
225	printf("message:\n");
226
227	mtype = protobuf_c_enum_descriptor_get_value(
228		&dnstap__message__type__descriptor, m->type);
229	if (mtype == NULL) {
230		return;
231	}
232
233	printf("  type: %s\n", mtype->name);
234
235	if (!isc_time_isepoch(&dt->qtime)) {
236		char buf[100];
237		isc_time_formatISO8601(&dt->qtime, buf, sizeof(buf));
238		printf("  query_time: !!timestamp %s\n", buf);
239	}
240
241	if (!isc_time_isepoch(&dt->rtime)) {
242		char buf[100];
243		isc_time_formatISO8601(&dt->rtime, buf, sizeof(buf));
244		printf("  response_time: !!timestamp %s\n", buf);
245	}
246
247	if (dt->msgdata.base != NULL) {
248		printf("  message_size: %zub\n", (size_t)dt->msgdata.length);
249	} else {
250		printf("  message_size: 0b\n");
251	}
252
253	if (m->has_socket_family) {
254		const ProtobufCEnumValue *type =
255			protobuf_c_enum_descriptor_get_value(
256				&dnstap__socket_family__descriptor,
257				m->socket_family);
258		if (type != NULL) {
259			printf("  socket_family: %s\n", type->name);
260		}
261	}
262
263	printf("  socket_protocol: %s\n", dt->tcp ? "TCP" : "UDP");
264
265	if (m->has_query_address) {
266		ProtobufCBinaryData *ip = &m->query_address;
267		char buf[100];
268
269		(void)inet_ntop(ip->len == 4 ? AF_INET : AF_INET6, ip->data,
270				buf, sizeof(buf));
271		printf("  query_address: \"%s\"\n", buf);
272	}
273
274	if (m->has_response_address) {
275		ProtobufCBinaryData *ip = &m->response_address;
276		char buf[100];
277
278		(void)inet_ntop(ip->len == 4 ? AF_INET : AF_INET6, ip->data,
279				buf, sizeof(buf));
280		printf("  response_address: \"%s\"\n", buf);
281	}
282
283	if (m->has_query_port) {
284		printf("  query_port: %u\n", m->query_port);
285	}
286
287	if (m->has_response_port) {
288		printf("  response_port: %u\n", m->response_port);
289	}
290
291	if (m->has_query_zone) {
292		isc_result_t result;
293		dns_fixedname_t fn;
294		dns_name_t *name;
295		isc_buffer_t b;
296		dns_decompress_t dctx;
297
298		name = dns_fixedname_initname(&fn);
299
300		isc_buffer_init(&b, m->query_zone.data, m->query_zone.len);
301		isc_buffer_add(&b, m->query_zone.len);
302
303		dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
304		result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
305		if (result == ISC_R_SUCCESS) {
306			printf("  query_zone: ");
307			dns_name_print(name, stdout);
308			printf("\n");
309		}
310	}
311
312	if (dt->msg != NULL) {
313		dt->msg->indent.count = 2;
314		dt->msg->indent.string = "  ";
315		printf("  %s:\n", ((dt->type & DNS_DTTYPE_QUERY) != 0)
316					  ? "query_message_data"
317					  : "response_message_data");
318
319		print_packet(dt, &dns_master_style_yaml);
320
321		printf("  %s: |\n", ((dt->type & DNS_DTTYPE_QUERY) != 0)
322					    ? "query_message"
323					    : "response_message");
324		print_packet(dt, &dns_master_style_indent);
325	}
326}
327
328int
329main(int argc, char *argv[]) {
330	isc_result_t result;
331	dns_message_t *message = NULL;
332	dns_dtdata_t *dt = NULL;
333	dns_dthandle_t *handle = NULL;
334	int rv = 0, ch;
335
336	while ((ch = isc_commandline_parse(argc, argv, "mpxy")) != -1) {
337		switch (ch) {
338		case 'm':
339			isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
340			memrecord = true;
341			break;
342		case 'p':
343			printmessage = true;
344			break;
345		case 'x':
346			hexmessage = true;
347			break;
348		case 'y':
349			yaml = true;
350			break;
351		default:
352			usage();
353			exit(1);
354		}
355	}
356
357	argc -= isc_commandline_index;
358	argv += isc_commandline_index;
359
360	if (argc < 1) {
361		fatal("no file specified");
362	}
363
364	isc_mem_create(&mctx);
365
366	CHECKM(dns_dt_open(argv[0], dns_dtmode_file, mctx, &handle),
367	       "dns_dt_openfile");
368
369	for (;;) {
370		isc_region_t input;
371		uint8_t *data;
372		size_t datalen;
373
374		result = dns_dt_getframe(handle, &data, &datalen);
375		if (result == ISC_R_NOMORE) {
376			break;
377		} else {
378			CHECKM(result, "dns_dt_getframe");
379		}
380
381		input.base = data;
382		input.length = datalen;
383
384		result = dns_dt_parse(mctx, &input, &dt);
385		if (result != ISC_R_SUCCESS) {
386			continue;
387		}
388
389		if (yaml) {
390			print_yaml(dt);
391		} else if (hexmessage) {
392			print_dtdata(dt);
393			print_hex(dt);
394		} else if (printmessage) {
395			print_dtdata(dt);
396			print_packet(dt, &dns_master_style_debug);
397		} else {
398			print_dtdata(dt);
399		}
400
401		dns_dtdata_free(&dt);
402	}
403
404cleanup:
405	if (dt != NULL) {
406		dns_dtdata_free(&dt);
407	}
408	if (handle != NULL) {
409		dns_dt_close(&handle);
410	}
411	if (message != NULL) {
412		dns_message_detach(&message);
413	}
414	isc_mem_destroy(&mctx);
415
416	exit(rv);
417}
418