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