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