├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── build.rs ├── dnstap.pb └── dnstap.proto └── src ├── bin ├── dnstap-dump │ └── main.rs ├── dnstap-inject │ └── main.rs ├── dnstap-replay │ ├── dnstap_handler.rs │ ├── frame_handler.rs │ ├── http_handler.rs │ ├── main.rs │ ├── metrics.rs │ └── monitor_handler.rs └── fmt-dns-message │ └── main.rs ├── framestreams_codec.rs ├── lib.rs ├── proxyv2.rs └── util.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install build dependencies 20 | run: sudo apt-get -y install protobuf-compiler 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle" 45 | version = "1.0.4" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 48 | 49 | [[package]] 50 | name = "anstyle-parse" 51 | version = "0.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 54 | dependencies = [ 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-query" 60 | version = "1.0.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 63 | dependencies = [ 64 | "windows-sys 0.52.0", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle-wincon" 69 | version = "3.0.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 72 | dependencies = [ 73 | "anstyle", 74 | "windows-sys 0.52.0", 75 | ] 76 | 77 | [[package]] 78 | name = "anyhow" 79 | version = "1.0.79" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 82 | 83 | [[package]] 84 | name = "async-channel" 85 | version = "2.1.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" 88 | dependencies = [ 89 | "concurrent-queue", 90 | "event-listener", 91 | "event-listener-strategy", 92 | "futures-core", 93 | "pin-project-lite", 94 | ] 95 | 96 | [[package]] 97 | name = "async-stream" 98 | version = "0.3.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 101 | dependencies = [ 102 | "async-stream-impl", 103 | "futures-core", 104 | "pin-project-lite", 105 | ] 106 | 107 | [[package]] 108 | name = "async-stream-impl" 109 | version = "0.3.5" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 112 | dependencies = [ 113 | "proc-macro2", 114 | "quote", 115 | "syn 2.0.48", 116 | ] 117 | 118 | [[package]] 119 | name = "atty" 120 | version = "0.2.14" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 123 | dependencies = [ 124 | "hermit-abi 0.1.19", 125 | "libc", 126 | "winapi", 127 | ] 128 | 129 | [[package]] 130 | name = "autocfg" 131 | version = "1.1.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 134 | 135 | [[package]] 136 | name = "backtrace" 137 | version = "0.3.69" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 140 | dependencies = [ 141 | "addr2line", 142 | "cc", 143 | "cfg-if", 144 | "libc", 145 | "miniz_oxide", 146 | "object", 147 | "rustc-demangle", 148 | ] 149 | 150 | [[package]] 151 | name = "bitflags" 152 | version = "1.3.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 155 | 156 | [[package]] 157 | name = "bitflags" 158 | version = "2.4.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 161 | 162 | [[package]] 163 | name = "bytes" 164 | version = "1.5.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 167 | 168 | [[package]] 169 | name = "cc" 170 | version = "1.0.83" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 173 | dependencies = [ 174 | "libc", 175 | ] 176 | 177 | [[package]] 178 | name = "cfg-if" 179 | version = "1.0.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 182 | 183 | [[package]] 184 | name = "clap" 185 | version = "4.4.16" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" 188 | dependencies = [ 189 | "clap_builder", 190 | "clap_derive", 191 | ] 192 | 193 | [[package]] 194 | name = "clap_builder" 195 | version = "4.4.16" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" 198 | dependencies = [ 199 | "anstream", 200 | "anstyle", 201 | "clap_lex", 202 | "strsim", 203 | ] 204 | 205 | [[package]] 206 | name = "clap_derive" 207 | version = "4.4.7" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 210 | dependencies = [ 211 | "heck", 212 | "proc-macro2", 213 | "quote", 214 | "syn 2.0.48", 215 | ] 216 | 217 | [[package]] 218 | name = "clap_lex" 219 | version = "0.6.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 222 | 223 | [[package]] 224 | name = "colorchoice" 225 | version = "1.0.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 228 | 229 | [[package]] 230 | name = "concurrent-queue" 231 | version = "2.4.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" 234 | dependencies = [ 235 | "crossbeam-utils", 236 | ] 237 | 238 | [[package]] 239 | name = "crossbeam-utils" 240 | version = "0.8.16" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 243 | dependencies = [ 244 | "cfg-if", 245 | ] 246 | 247 | [[package]] 248 | name = "deranged" 249 | version = "0.3.11" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 252 | dependencies = [ 253 | "powerfmt", 254 | ] 255 | 256 | [[package]] 257 | name = "dnstap-utils" 258 | version = "0.5.0" 259 | dependencies = [ 260 | "anyhow", 261 | "async-channel", 262 | "async-stream", 263 | "bytes", 264 | "clap", 265 | "domain", 266 | "futures", 267 | "futures-util", 268 | "heck", 269 | "hex", 270 | "hyper", 271 | "inotify", 272 | "ip_network", 273 | "ip_network_table", 274 | "lazy_static", 275 | "libc", 276 | "log", 277 | "prometheus", 278 | "prometheus-static-metric", 279 | "prost", 280 | "prost-build", 281 | "stderrlog", 282 | "thiserror", 283 | "time", 284 | "tokio", 285 | "tokio-stream", 286 | "tokio-util", 287 | ] 288 | 289 | [[package]] 290 | name = "domain" 291 | version = "0.9.3" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "e853e3f6d4c6e52a4d73a94c1810c66ad71958fbe24934a7119b447f425aed76" 294 | dependencies = [ 295 | "bytes", 296 | "octseq", 297 | "rand", 298 | "time", 299 | ] 300 | 301 | [[package]] 302 | name = "either" 303 | version = "1.9.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 306 | 307 | [[package]] 308 | name = "errno" 309 | version = "0.3.8" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 312 | dependencies = [ 313 | "libc", 314 | "windows-sys 0.52.0", 315 | ] 316 | 317 | [[package]] 318 | name = "event-listener" 319 | version = "4.0.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" 322 | dependencies = [ 323 | "concurrent-queue", 324 | "parking", 325 | "pin-project-lite", 326 | ] 327 | 328 | [[package]] 329 | name = "event-listener-strategy" 330 | version = "0.4.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" 333 | dependencies = [ 334 | "event-listener", 335 | "pin-project-lite", 336 | ] 337 | 338 | [[package]] 339 | name = "fastrand" 340 | version = "2.0.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 343 | 344 | [[package]] 345 | name = "fixedbitset" 346 | version = "0.4.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 349 | 350 | [[package]] 351 | name = "fnv" 352 | version = "1.0.7" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 355 | 356 | [[package]] 357 | name = "futures" 358 | version = "0.3.30" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 361 | dependencies = [ 362 | "futures-channel", 363 | "futures-core", 364 | "futures-executor", 365 | "futures-io", 366 | "futures-sink", 367 | "futures-task", 368 | "futures-util", 369 | ] 370 | 371 | [[package]] 372 | name = "futures-channel" 373 | version = "0.3.30" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 376 | dependencies = [ 377 | "futures-core", 378 | "futures-sink", 379 | ] 380 | 381 | [[package]] 382 | name = "futures-core" 383 | version = "0.3.30" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 386 | 387 | [[package]] 388 | name = "futures-executor" 389 | version = "0.3.30" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 392 | dependencies = [ 393 | "futures-core", 394 | "futures-task", 395 | "futures-util", 396 | ] 397 | 398 | [[package]] 399 | name = "futures-io" 400 | version = "0.3.30" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 403 | 404 | [[package]] 405 | name = "futures-macro" 406 | version = "0.3.30" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 409 | dependencies = [ 410 | "proc-macro2", 411 | "quote", 412 | "syn 2.0.48", 413 | ] 414 | 415 | [[package]] 416 | name = "futures-sink" 417 | version = "0.3.30" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 420 | 421 | [[package]] 422 | name = "futures-task" 423 | version = "0.3.30" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 426 | 427 | [[package]] 428 | name = "futures-util" 429 | version = "0.3.30" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 432 | dependencies = [ 433 | "futures-channel", 434 | "futures-core", 435 | "futures-io", 436 | "futures-macro", 437 | "futures-sink", 438 | "futures-task", 439 | "memchr", 440 | "pin-project-lite", 441 | "pin-utils", 442 | "slab", 443 | ] 444 | 445 | [[package]] 446 | name = "getrandom" 447 | version = "0.2.11" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 450 | dependencies = [ 451 | "cfg-if", 452 | "libc", 453 | "wasi", 454 | ] 455 | 456 | [[package]] 457 | name = "gimli" 458 | version = "0.28.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 461 | 462 | [[package]] 463 | name = "hashbrown" 464 | version = "0.12.3" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 467 | 468 | [[package]] 469 | name = "heck" 470 | version = "0.4.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 473 | 474 | [[package]] 475 | name = "hermit-abi" 476 | version = "0.1.19" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 479 | dependencies = [ 480 | "libc", 481 | ] 482 | 483 | [[package]] 484 | name = "hermit-abi" 485 | version = "0.3.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 488 | 489 | [[package]] 490 | name = "hex" 491 | version = "0.4.3" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 494 | 495 | [[package]] 496 | name = "home" 497 | version = "0.5.5" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" 500 | dependencies = [ 501 | "windows-sys 0.48.0", 502 | ] 503 | 504 | [[package]] 505 | name = "http" 506 | version = "0.2.11" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 509 | dependencies = [ 510 | "bytes", 511 | "fnv", 512 | "itoa", 513 | ] 514 | 515 | [[package]] 516 | name = "http-body" 517 | version = "0.4.6" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 520 | dependencies = [ 521 | "bytes", 522 | "http", 523 | "pin-project-lite", 524 | ] 525 | 526 | [[package]] 527 | name = "httparse" 528 | version = "1.8.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 531 | 532 | [[package]] 533 | name = "httpdate" 534 | version = "1.0.3" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 537 | 538 | [[package]] 539 | name = "hyper" 540 | version = "0.14.28" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" 543 | dependencies = [ 544 | "bytes", 545 | "futures-channel", 546 | "futures-core", 547 | "futures-util", 548 | "http", 549 | "http-body", 550 | "httparse", 551 | "httpdate", 552 | "itoa", 553 | "pin-project-lite", 554 | "socket2", 555 | "tokio", 556 | "tower-service", 557 | "tracing", 558 | "want", 559 | ] 560 | 561 | [[package]] 562 | name = "indexmap" 563 | version = "1.9.3" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 566 | dependencies = [ 567 | "autocfg", 568 | "hashbrown", 569 | ] 570 | 571 | [[package]] 572 | name = "inotify" 573 | version = "0.10.2" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" 576 | dependencies = [ 577 | "bitflags 1.3.2", 578 | "futures-core", 579 | "inotify-sys", 580 | "libc", 581 | "tokio", 582 | ] 583 | 584 | [[package]] 585 | name = "inotify-sys" 586 | version = "0.1.5" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 589 | dependencies = [ 590 | "libc", 591 | ] 592 | 593 | [[package]] 594 | name = "ip_network" 595 | version = "0.4.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" 598 | 599 | [[package]] 600 | name = "ip_network_table" 601 | version = "0.2.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" 604 | dependencies = [ 605 | "ip_network", 606 | "ip_network_table-deps-treebitmap", 607 | ] 608 | 609 | [[package]] 610 | name = "ip_network_table-deps-treebitmap" 611 | version = "0.5.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" 614 | 615 | [[package]] 616 | name = "itertools" 617 | version = "0.10.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 620 | dependencies = [ 621 | "either", 622 | ] 623 | 624 | [[package]] 625 | name = "itoa" 626 | version = "1.0.10" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 629 | 630 | [[package]] 631 | name = "lazy_static" 632 | version = "1.4.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 635 | 636 | [[package]] 637 | name = "libc" 638 | version = "0.2.152" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" 641 | 642 | [[package]] 643 | name = "linux-raw-sys" 644 | version = "0.4.12" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" 647 | 648 | [[package]] 649 | name = "lock_api" 650 | version = "0.4.11" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 653 | dependencies = [ 654 | "autocfg", 655 | "scopeguard", 656 | ] 657 | 658 | [[package]] 659 | name = "log" 660 | version = "0.4.20" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 663 | 664 | [[package]] 665 | name = "memchr" 666 | version = "2.7.1" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 669 | 670 | [[package]] 671 | name = "miniz_oxide" 672 | version = "0.7.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 675 | dependencies = [ 676 | "adler", 677 | ] 678 | 679 | [[package]] 680 | name = "mio" 681 | version = "0.8.10" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" 684 | dependencies = [ 685 | "libc", 686 | "wasi", 687 | "windows-sys 0.48.0", 688 | ] 689 | 690 | [[package]] 691 | name = "multimap" 692 | version = "0.8.3" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 695 | 696 | [[package]] 697 | name = "num_cpus" 698 | version = "1.16.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 701 | dependencies = [ 702 | "hermit-abi 0.3.2", 703 | "libc", 704 | ] 705 | 706 | [[package]] 707 | name = "object" 708 | version = "0.32.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 711 | dependencies = [ 712 | "memchr", 713 | ] 714 | 715 | [[package]] 716 | name = "octseq" 717 | version = "0.3.2" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d92b38a4aabbacf619b8083841713216e7668178422decfe06bbc70643024c5d" 720 | dependencies = [ 721 | "bytes", 722 | ] 723 | 724 | [[package]] 725 | name = "once_cell" 726 | version = "1.19.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 729 | 730 | [[package]] 731 | name = "parking" 732 | version = "2.2.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" 735 | 736 | [[package]] 737 | name = "parking_lot" 738 | version = "0.12.1" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 741 | dependencies = [ 742 | "lock_api", 743 | "parking_lot_core", 744 | ] 745 | 746 | [[package]] 747 | name = "parking_lot_core" 748 | version = "0.9.9" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 751 | dependencies = [ 752 | "cfg-if", 753 | "libc", 754 | "redox_syscall 0.4.1", 755 | "smallvec", 756 | "windows-targets 0.48.5", 757 | ] 758 | 759 | [[package]] 760 | name = "petgraph" 761 | version = "0.6.3" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" 764 | dependencies = [ 765 | "fixedbitset", 766 | "indexmap", 767 | ] 768 | 769 | [[package]] 770 | name = "pin-project-lite" 771 | version = "0.2.13" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 774 | 775 | [[package]] 776 | name = "pin-utils" 777 | version = "0.1.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 780 | 781 | [[package]] 782 | name = "powerfmt" 783 | version = "0.2.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 786 | 787 | [[package]] 788 | name = "ppv-lite86" 789 | version = "0.2.17" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 792 | 793 | [[package]] 794 | name = "prettyplease" 795 | version = "0.2.16" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" 798 | dependencies = [ 799 | "proc-macro2", 800 | "syn 2.0.48", 801 | ] 802 | 803 | [[package]] 804 | name = "proc-macro2" 805 | version = "1.0.76" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" 808 | dependencies = [ 809 | "unicode-ident", 810 | ] 811 | 812 | [[package]] 813 | name = "prometheus" 814 | version = "0.13.3" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" 817 | dependencies = [ 818 | "cfg-if", 819 | "fnv", 820 | "lazy_static", 821 | "memchr", 822 | "parking_lot", 823 | "protobuf", 824 | "thiserror", 825 | ] 826 | 827 | [[package]] 828 | name = "prometheus-static-metric" 829 | version = "0.5.1" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "f8f30cdb09c39930b8fa5e0f23cbb895ab3f766b187403a0ba0956fc1ef4f0e5" 832 | dependencies = [ 833 | "lazy_static", 834 | "proc-macro2", 835 | "quote", 836 | "syn 1.0.109", 837 | ] 838 | 839 | [[package]] 840 | name = "prost" 841 | version = "0.12.3" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" 844 | dependencies = [ 845 | "bytes", 846 | "prost-derive", 847 | ] 848 | 849 | [[package]] 850 | name = "prost-build" 851 | version = "0.12.3" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" 854 | dependencies = [ 855 | "bytes", 856 | "heck", 857 | "itertools", 858 | "log", 859 | "multimap", 860 | "once_cell", 861 | "petgraph", 862 | "prettyplease", 863 | "prost", 864 | "prost-types", 865 | "regex", 866 | "syn 2.0.48", 867 | "tempfile", 868 | "which", 869 | ] 870 | 871 | [[package]] 872 | name = "prost-derive" 873 | version = "0.12.3" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" 876 | dependencies = [ 877 | "anyhow", 878 | "itertools", 879 | "proc-macro2", 880 | "quote", 881 | "syn 2.0.48", 882 | ] 883 | 884 | [[package]] 885 | name = "prost-types" 886 | version = "0.12.3" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" 889 | dependencies = [ 890 | "prost", 891 | ] 892 | 893 | [[package]] 894 | name = "protobuf" 895 | version = "2.28.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" 898 | 899 | [[package]] 900 | name = "quote" 901 | version = "1.0.35" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 904 | dependencies = [ 905 | "proc-macro2", 906 | ] 907 | 908 | [[package]] 909 | name = "rand" 910 | version = "0.8.5" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 913 | dependencies = [ 914 | "libc", 915 | "rand_chacha", 916 | "rand_core", 917 | ] 918 | 919 | [[package]] 920 | name = "rand_chacha" 921 | version = "0.3.1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 924 | dependencies = [ 925 | "ppv-lite86", 926 | "rand_core", 927 | ] 928 | 929 | [[package]] 930 | name = "rand_core" 931 | version = "0.6.4" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 934 | dependencies = [ 935 | "getrandom", 936 | ] 937 | 938 | [[package]] 939 | name = "redox_syscall" 940 | version = "0.3.5" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 943 | dependencies = [ 944 | "bitflags 1.3.2", 945 | ] 946 | 947 | [[package]] 948 | name = "redox_syscall" 949 | version = "0.4.1" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 952 | dependencies = [ 953 | "bitflags 1.3.2", 954 | ] 955 | 956 | [[package]] 957 | name = "regex" 958 | version = "1.10.2" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 961 | dependencies = [ 962 | "aho-corasick", 963 | "memchr", 964 | "regex-automata", 965 | "regex-syntax", 966 | ] 967 | 968 | [[package]] 969 | name = "regex-automata" 970 | version = "0.4.3" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 973 | dependencies = [ 974 | "aho-corasick", 975 | "memchr", 976 | "regex-syntax", 977 | ] 978 | 979 | [[package]] 980 | name = "regex-syntax" 981 | version = "0.8.2" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 984 | 985 | [[package]] 986 | name = "rustc-demangle" 987 | version = "0.1.23" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 990 | 991 | [[package]] 992 | name = "rustix" 993 | version = "0.38.30" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" 996 | dependencies = [ 997 | "bitflags 2.4.1", 998 | "errno", 999 | "libc", 1000 | "linux-raw-sys", 1001 | "windows-sys 0.52.0", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "scopeguard" 1006 | version = "1.2.0" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1009 | 1010 | [[package]] 1011 | name = "serde" 1012 | version = "1.0.195" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" 1015 | dependencies = [ 1016 | "serde_derive", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "serde_derive" 1021 | version = "1.0.195" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" 1024 | dependencies = [ 1025 | "proc-macro2", 1026 | "quote", 1027 | "syn 2.0.48", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "signal-hook-registry" 1032 | version = "1.4.1" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1035 | dependencies = [ 1036 | "libc", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "slab" 1041 | version = "0.4.9" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1044 | dependencies = [ 1045 | "autocfg", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "smallvec" 1050 | version = "1.11.2" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1053 | 1054 | [[package]] 1055 | name = "socket2" 1056 | version = "0.5.5" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1059 | dependencies = [ 1060 | "libc", 1061 | "windows-sys 0.48.0", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "stderrlog" 1066 | version = "0.5.4" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "69a26bbf6de627d389164afa9783739b56746c6c72c4ed16539f4ff54170327b" 1069 | dependencies = [ 1070 | "atty", 1071 | "log", 1072 | "termcolor", 1073 | "thread_local", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "strsim" 1078 | version = "0.10.0" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1081 | 1082 | [[package]] 1083 | name = "syn" 1084 | version = "1.0.109" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1087 | dependencies = [ 1088 | "proc-macro2", 1089 | "quote", 1090 | "unicode-ident", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "syn" 1095 | version = "2.0.48" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 1098 | dependencies = [ 1099 | "proc-macro2", 1100 | "quote", 1101 | "unicode-ident", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "tempfile" 1106 | version = "3.8.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 1109 | dependencies = [ 1110 | "cfg-if", 1111 | "fastrand", 1112 | "redox_syscall 0.3.5", 1113 | "rustix", 1114 | "windows-sys 0.48.0", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "termcolor" 1119 | version = "1.1.3" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1122 | dependencies = [ 1123 | "winapi-util", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "thiserror" 1128 | version = "1.0.56" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" 1131 | dependencies = [ 1132 | "thiserror-impl", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "thiserror-impl" 1137 | version = "1.0.56" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" 1140 | dependencies = [ 1141 | "proc-macro2", 1142 | "quote", 1143 | "syn 2.0.48", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "thread_local" 1148 | version = "1.1.7" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1151 | dependencies = [ 1152 | "cfg-if", 1153 | "once_cell", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "time" 1158 | version = "0.3.31" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" 1161 | dependencies = [ 1162 | "deranged", 1163 | "itoa", 1164 | "powerfmt", 1165 | "serde", 1166 | "time-core", 1167 | "time-macros", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "time-core" 1172 | version = "0.1.2" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1175 | 1176 | [[package]] 1177 | name = "time-macros" 1178 | version = "0.2.16" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" 1181 | dependencies = [ 1182 | "time-core", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "tokio" 1187 | version = "1.35.1" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" 1190 | dependencies = [ 1191 | "backtrace", 1192 | "bytes", 1193 | "libc", 1194 | "mio", 1195 | "num_cpus", 1196 | "parking_lot", 1197 | "pin-project-lite", 1198 | "signal-hook-registry", 1199 | "socket2", 1200 | "tokio-macros", 1201 | "windows-sys 0.48.0", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "tokio-macros" 1206 | version = "2.2.0" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1209 | dependencies = [ 1210 | "proc-macro2", 1211 | "quote", 1212 | "syn 2.0.48", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "tokio-stream" 1217 | version = "0.1.14" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 1220 | dependencies = [ 1221 | "futures-core", 1222 | "pin-project-lite", 1223 | "tokio", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "tokio-util" 1228 | version = "0.7.10" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1231 | dependencies = [ 1232 | "bytes", 1233 | "futures-core", 1234 | "futures-sink", 1235 | "pin-project-lite", 1236 | "tokio", 1237 | "tracing", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "tower-service" 1242 | version = "0.3.2" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1245 | 1246 | [[package]] 1247 | name = "tracing" 1248 | version = "0.1.40" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1251 | dependencies = [ 1252 | "pin-project-lite", 1253 | "tracing-core", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "tracing-core" 1258 | version = "0.1.32" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1261 | dependencies = [ 1262 | "once_cell", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "try-lock" 1267 | version = "0.2.5" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1270 | 1271 | [[package]] 1272 | name = "unicode-ident" 1273 | version = "1.0.12" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1276 | 1277 | [[package]] 1278 | name = "utf8parse" 1279 | version = "0.2.1" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1282 | 1283 | [[package]] 1284 | name = "want" 1285 | version = "0.3.1" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1288 | dependencies = [ 1289 | "try-lock", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "wasi" 1294 | version = "0.11.0+wasi-snapshot-preview1" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1297 | 1298 | [[package]] 1299 | name = "which" 1300 | version = "4.4.2" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1303 | dependencies = [ 1304 | "either", 1305 | "home", 1306 | "once_cell", 1307 | "rustix", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "winapi" 1312 | version = "0.3.9" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1315 | dependencies = [ 1316 | "winapi-i686-pc-windows-gnu", 1317 | "winapi-x86_64-pc-windows-gnu", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "winapi-i686-pc-windows-gnu" 1322 | version = "0.4.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1325 | 1326 | [[package]] 1327 | name = "winapi-util" 1328 | version = "0.1.5" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1331 | dependencies = [ 1332 | "winapi", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "winapi-x86_64-pc-windows-gnu" 1337 | version = "0.4.0" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1340 | 1341 | [[package]] 1342 | name = "windows-sys" 1343 | version = "0.48.0" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1346 | dependencies = [ 1347 | "windows-targets 0.48.5", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "windows-sys" 1352 | version = "0.52.0" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1355 | dependencies = [ 1356 | "windows-targets 0.52.0", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "windows-targets" 1361 | version = "0.48.5" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1364 | dependencies = [ 1365 | "windows_aarch64_gnullvm 0.48.5", 1366 | "windows_aarch64_msvc 0.48.5", 1367 | "windows_i686_gnu 0.48.5", 1368 | "windows_i686_msvc 0.48.5", 1369 | "windows_x86_64_gnu 0.48.5", 1370 | "windows_x86_64_gnullvm 0.48.5", 1371 | "windows_x86_64_msvc 0.48.5", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "windows-targets" 1376 | version = "0.52.0" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1379 | dependencies = [ 1380 | "windows_aarch64_gnullvm 0.52.0", 1381 | "windows_aarch64_msvc 0.52.0", 1382 | "windows_i686_gnu 0.52.0", 1383 | "windows_i686_msvc 0.52.0", 1384 | "windows_x86_64_gnu 0.52.0", 1385 | "windows_x86_64_gnullvm 0.52.0", 1386 | "windows_x86_64_msvc 0.52.0", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "windows_aarch64_gnullvm" 1391 | version = "0.48.5" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1394 | 1395 | [[package]] 1396 | name = "windows_aarch64_gnullvm" 1397 | version = "0.52.0" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1400 | 1401 | [[package]] 1402 | name = "windows_aarch64_msvc" 1403 | version = "0.48.5" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1406 | 1407 | [[package]] 1408 | name = "windows_aarch64_msvc" 1409 | version = "0.52.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1412 | 1413 | [[package]] 1414 | name = "windows_i686_gnu" 1415 | version = "0.48.5" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1418 | 1419 | [[package]] 1420 | name = "windows_i686_gnu" 1421 | version = "0.52.0" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1424 | 1425 | [[package]] 1426 | name = "windows_i686_msvc" 1427 | version = "0.48.5" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1430 | 1431 | [[package]] 1432 | name = "windows_i686_msvc" 1433 | version = "0.52.0" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1436 | 1437 | [[package]] 1438 | name = "windows_x86_64_gnu" 1439 | version = "0.48.5" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1442 | 1443 | [[package]] 1444 | name = "windows_x86_64_gnu" 1445 | version = "0.52.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1448 | 1449 | [[package]] 1450 | name = "windows_x86_64_gnullvm" 1451 | version = "0.48.5" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1454 | 1455 | [[package]] 1456 | name = "windows_x86_64_gnullvm" 1457 | version = "0.52.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1460 | 1461 | [[package]] 1462 | name = "windows_x86_64_msvc" 1463 | version = "0.48.5" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1466 | 1467 | [[package]] 1468 | name = "windows_x86_64_msvc" 1469 | version = "0.52.0" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1472 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dnstap-utils" 3 | version = "0.5.0" 4 | description = "dnstap utilities" 5 | authors = ["Fastly"] 6 | keywords = ["dns", "dnstap"] 7 | categories = ["command-line-utilities"] 8 | edition = "2021" 9 | repository = "https://github.com/fastly/dnstap-utils" 10 | license = "Apache-2.0" 11 | include = ["src/**/*", "LICENSE", "README.md", "dnstap.pb/dnstap.proto", "build.rs"] 12 | 13 | [dependencies] 14 | anyhow = "1.0.79" 15 | async-channel = "2.1.1" 16 | async-stream = "0.3.5" 17 | bytes = "1.5.0" 18 | clap = { version = "4.4.16", features = ["derive"] } 19 | domain = "0.9.3" 20 | futures = "0.3.30" 21 | futures-util = "0.3.30" 22 | heck = "0.4.1" 23 | hex = "0.4.3" 24 | hyper = { version = "0.14.28", features = ["server", "stream", "http1", "tcp"] } 25 | inotify = "0.10.2" 26 | ip_network = "0.4.1" 27 | ip_network_table = "0.2.0" 28 | lazy_static = "1.4.0" 29 | libc = "0.2.152" 30 | log = "0.4.20" 31 | prometheus = "0.13.3" 32 | prometheus-static-metric = "0.5.1" 33 | prost = "0.12.3" 34 | stderrlog = { version = "0.5.4", default-features = false } 35 | thiserror = "1.0.56" 36 | time = { version = "0.3.31", features = ["formatting", "macros"] } 37 | tokio = { version = "1.35.1", features = ["full"] } 38 | tokio-stream = "0.1.14" 39 | tokio-util = { version = "0.7.10", features = ["codec", "io"] } 40 | 41 | [build-dependencies] 42 | prost-build = "0.12.3" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2021-2022 Fastly, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dnstap-utils 2 | 3 | A collection of [dnstap] utilities implemented using the Rust 4 | programming language. 5 | 6 | [dnstap]: https://dnstap.info/ 7 | 8 | ## `dnstap-replay` 9 | 10 | `dnstap-replay` is a dnstap collection server which receives dnstap 11 | messages from one or more DNS nameservers and replays them against a 12 | target nameserver. The responses from the target nameserver are 13 | compared against the originally logged response messages and any 14 | ***mismatches*** or other errors are made available in dnstap format 15 | via an HTTP endpoint for later analysis. 16 | 17 | ### `dnstap-replay`: dnstap message requirements 18 | 19 | `dnstap-replay` was designed for testing authoritative nameservers. The 20 | only type of dnstap log payload that `dnstap-replay` supports is the 21 | `Message/AUTH_RESPONSE` type. Any other dnstap log payload types will be 22 | silently ignored by `dnstap-replay`. 23 | 24 | The following fields are ***required*** to be set in the dnstap log 25 | payload: 26 | 27 | * `query_address` 28 | * `query_port` 29 | * `query_message` 30 | * `response_message` 31 | 32 | Typically, dnstap `Message/*_RESPONSE` log payloads do not include both 33 | the `query_message` and `response_message` fields on the assumption that 34 | the query message will be logged separately by a `Message/*_QUERY` log 35 | payload. However, this presents a problem for the replay-and-comparison 36 | phase in `dnstap-replay` because it is not entirely trivial to derive 37 | the original DNS query message given only the DNS response message. In 38 | some cases it may be impossible to recover the original query, for 39 | instance if the query is not a validly formatted DNS message. 40 | 41 | For the Knot DNS server, [support was added in version 3.1.4] to add a 42 | configuration option `responses-with-queries` to the `dnstap` module 43 | that logs ***both*** query and response messages together in the 44 | `Message/AUTH_RESPONSE` log payload type. The `mod-dnstap` configuration 45 | stanza in `knot.conf` would need to look like the following to produce 46 | dnstap output with the fields needed by `dnstap-replay`: 47 | 48 | ``` 49 | mod-dnstap: 50 | - id: "default" 51 | sink: "[...]" 52 | log-queries: off 53 | log-responses: on 54 | responses-with-queries: on 55 | ``` 56 | 57 | [support was added in version 3.1.4]: https://gitlab.nic.cz/knot/knot-dns/-/issues/764 58 | 59 | ### `dnstap-replay`: PROXY support in target nameserver 60 | 61 | `dnstap-replay` was originally designed for testing nameservers that may 62 | have source IP address dependent behavior or configuration. When a 63 | dnstap-originated DNS query message is replayed by `dnstap-replay`, the 64 | target nameserver sees the source IP address of the machine running 65 | `dnstap-replay` on the UDP packets containing the replayed query 66 | messages. This may elicit varying DNS response message content from the 67 | target nameserver. 68 | 69 | In order to avoid this problem, `dnstap-replay` can use the haproxy 70 | [PROXY] protocol to prepend the original source address and source port 71 | as logged in the `query_address` and `query_port` dnstap message fields 72 | to the outgoing DNS query message sent to the target nameserver. This 73 | requires support in the target nameserver. Currently, at least 74 | [dnsdist], [PowerDNS Authoritative Nameserver], [PowerDNS Recursor], and 75 | [Knot DNS] have support for the PROXY header. 76 | 77 | To enable this functionality in `dnstap-replay`, add the `--proxy` 78 | option to the command-line parameters. 79 | 80 | Support for PROXYv2 as a connection target was added in Knot DNS version 81 | 3.2.2, which adds a configuration option [`proxy_allowlist`] that lists 82 | the IP addresses that are allowed to initiate queries with the PROXYv2 83 | header. It is enabled by placing the option in the `server` 84 | configuration stanza, for instance: 85 | 86 | ``` 87 | server: 88 | […] 89 | proxy-allowlist: 127.0.0.0/8 90 | ``` 91 | 92 | [PROXY]: https://www.haproxy.org/download/2.5/doc/proxy-protocol.txt 93 | [dnsdist]: https://blog.powerdns.com/2021/05/11/dnsdist-1-6-0-released/ 94 | [PowerDNS Authoritative Nameserver]: https://github.com/PowerDNS/pdns/pull/10660 95 | [PowerDNS Recursor]: https://github.com/PowerDNS/pdns/pull/8874 96 | [Knot DNS]: https://gitlab.nic.cz/knot/knot-dns/-/merge_requests/1468 97 | [`proxy_allowlist`]: https://www.knot-dns.cz/docs/3.2/html/reference.html?highlight=proxy#proxy-allowlist 98 | 99 | ### `dnstap-replay`: HTTP server 100 | 101 | `dnstap-replay` includes a built-in HTTP server to export [Prometheus 102 | metrics] which are available at the `/metrics` HTTP endpoint. 103 | 104 | When `dnstap-replay` sends a DNS query to the target nameserver and the 105 | response from the target nameserver does not exactly match the 106 | originally logged response message, a log message containing the 107 | mismatched response message is generated and buffered in memory and can 108 | be retrieved from the `/errors` HTTP endpoint. This endpoint drains the 109 | error buffer and provides the output in Frame Streams format containing 110 | dnstap payloads. 111 | 112 | The dnstap log messages exported via the `/errors` endpoint are the 113 | originally logged dnstap messages received by `dnstap-replay`, with the 114 | [dnstap `extra` field] populated with a serialized version of the error 115 | encountered by `dnstap-replay`. This preserves the original DNS response 116 | message as well as the DNS response message sent by the target 117 | nameserver, which allows for byte-for-byte analysis of the mismatch. 118 | 119 | A separate `/timeouts` endpoint is available which can be used to 120 | retrieve dnstap log messages that resulted in timeouts when re-querying 121 | the target nameserver. The format used is the same as the `/errors` 122 | endpoint. 123 | 124 | [Prometheus metrics]: https://github.com/fastly/dnstap-utils/blob/main/src/bin/dnstap-replay/metrics.rs 125 | [dnstap `extra` field]: https://github.com/dnstap/dnstap.pb/blob/9bafb5b59dacc48a6ff6a839e419e540f1201c42/dnstap.proto#L37-L40 126 | 127 | ### `dnstap-replay`: Command-line example 128 | 129 | `dnstap-replay` requires the `--dns`, `--http`, and `--unix` arguments 130 | to be provided. 131 | 132 | The `--dns` argument specifies the IP address and port of the target 133 | nameserver which will receive replayed DNS queries. 134 | 135 | The `--http` argument specifies the IP address and port for the built-in 136 | HTTP server. 137 | 138 | The `--unix` argument specifies the filesystem path to bind the dnstap 139 | Unix socket to. 140 | 141 | Additionally, there are command-line options `--channel-capacity` and 142 | `--channel-error-capacity` which allow tuning of internal buffer 143 | sizes. 144 | 145 | For example, the following command-line invocation will listen on the 146 | filesystem path `/run/dnstap.sock` for incoming dnstap connections from 147 | the DNS server(s) that will send dnstap log data and on the TCP socket 148 | 127.0.0.1:53080 for incoming HTTP connections. Replayed DNS queries will 149 | be sent to the target nameserver which should be configured to listen on 150 | 127.0.0.1:53053. 151 | 152 | ``` 153 | $ dnstap-replay --dns 127.0.0.1:53053 --http 127.0.0.1:53080 --unix /run/dnstap.sock 154 | ``` 155 | 156 | The Prometheus metrics endpoint can be accessed at 157 | `http://127.0.0.1:53080/metrics`. 158 | 159 | The Frame Streams "errors" endpoint can be accessed at 160 | `http://127.0.0.1:53080/errors`. 161 | 162 | The Frame Streams "timeouts" endpoint can be accessad at 163 | `http://127.0.0.1:53080/timeouts`. 164 | 165 | ## `dnstap-dump` 166 | 167 | `dnstap-dump` is a utility which dumps a Frame Streams formatted dnstap 168 | file to YAML. The output format is very similar to the format generated 169 | by the [`dnstap-ldns`] utility. 170 | 171 | It has support for decoding the `extra` field in dnstap error payloads 172 | produced by `dnstap-replay`, and it also dumps DNS wire messages in 173 | hex-encoded wire format as well as in dig-style output. 174 | 175 | [`dnstap-ldns`]: https://github.com/dnstap/dnstap-ldns 176 | 177 | ## `dnstap-inject` 178 | 179 | `dnstap-inject` is a utility which reads a Frame Streams formatted 180 | dnstap file, extracts messages which contain both a `query_address` and 181 | `query_message` field, and re-sends the query message to a DNS server. 182 | It can optionally prepend a PROXYv2 header to the query sent to the DNS 183 | server (if supported by the server) in order to exercise source address 184 | dependent behavior. 185 | 186 | ## `fmt-dns-message` 187 | 188 | `fmt-dns-message` is a utility which converts a hex-encoded wire format 189 | DNS message to dig-style output using the [NLnet Labs `domain` crate]. 190 | 191 | [NLnet Labs `domain` crate]: https://github.com/NLnetLabs/domain 192 | 193 | ## License 194 | 195 | `dnstap-utils` is distributed under the terms of the [Apache-2.0] 196 | license. See the [LICENSE] and [NOTICE] files for details. 197 | 198 | [Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0 199 | [LICENSE]: https://github.com/fastly/dnstap-utils/blob/main/LICENSE 200 | [NOTICE]: https://github.com/fastly/dnstap-utils/blob/main/NOTICE 201 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Fastly, Inc. 2 | 3 | fn main() -> std::io::Result<()> { 4 | prost_build::compile_protos(&["dnstap.pb/dnstap.proto"], &["dnstap.pb/"])?; 5 | Ok(()) 6 | } 7 | -------------------------------------------------------------------------------- /dnstap.pb/dnstap.proto: -------------------------------------------------------------------------------- 1 | // dnstap: flexible, structured event replication format for DNS software 2 | // 3 | // This file contains the protobuf schemas for the "dnstap" structured event 4 | // replication format for DNS software. 5 | 6 | // Written in 2013-2014 by Farsight Security, Inc. 7 | // 8 | // To the extent possible under law, the author(s) have dedicated all 9 | // copyright and related and neighboring rights to this file to the public 10 | // domain worldwide. This file is distributed without any warranty. 11 | // 12 | // You should have received a copy of the CC0 Public Domain Dedication along 13 | // with this file. If not, see: 14 | // 15 | // . 16 | 17 | syntax = "proto2"; 18 | package dnstap; 19 | 20 | // "Dnstap": this is the top-level dnstap type, which is a "union" type that 21 | // contains other kinds of dnstap payloads, although currently only one type 22 | // of dnstap payload is defined. 23 | // See: https://developers.google.com/protocol-buffers/docs/techniques#union 24 | message Dnstap { 25 | // DNS server identity. 26 | // If enabled, this is the identity string of the DNS server which generated 27 | // this message. Typically this would be the same string as returned by an 28 | // "NSID" (RFC 5001) query. 29 | optional bytes identity = 1; 30 | 31 | // DNS server version. 32 | // If enabled, this is the version string of the DNS server which generated 33 | // this message. Typically this would be the same string as returned by a 34 | // "version.bind" query. 35 | optional bytes version = 2; 36 | 37 | // Extra data for this payload. 38 | // This field can be used for adding an arbitrary byte-string annotation to 39 | // the payload. No encoding or interpretation is applied or enforced. 40 | optional bytes extra = 3; 41 | 42 | // Identifies which field below is filled in. 43 | enum Type { 44 | MESSAGE = 1; 45 | } 46 | required Type type = 15; 47 | 48 | // One of the following will be filled in. 49 | optional Message message = 14; 50 | } 51 | 52 | // SocketFamily: the network protocol family of a socket. This specifies how 53 | // to interpret "network address" fields. 54 | enum SocketFamily { 55 | INET = 1; // IPv4 (RFC 791) 56 | INET6 = 2; // IPv6 (RFC 2460) 57 | } 58 | 59 | // SocketProtocol: the protocol used to transport a DNS message. 60 | enum SocketProtocol { 61 | UDP = 1; // DNS over UDP transport (RFC 1035 section 4.2.1) 62 | TCP = 2; // DNS over TCP transport (RFC 1035 section 4.2.2) 63 | DOT = 3; // DNS over TLS (RFC 7858) 64 | DOH = 4; // DNS over HTTPS (RFC 8484) 65 | DNSCryptUDP = 5; // DNSCrypt over UDP (https://dnscrypt.info/protocol) 66 | DNSCryptTCP = 6; // DNSCrypt over TCP (https://dnscrypt.info/protocol) 67 | } 68 | 69 | // Policy: information about any name server operator policy 70 | // applied to the processing of a DNS message. 71 | message Policy { 72 | 73 | // Match: what aspect of the message or message exchange 74 | // triggered the application of the Policy. 75 | enum Match { 76 | QNAME = 1; // Name in question section of query 77 | CLIENT_IP = 2; // Client IP address 78 | RESPONSE_IP = 3; // Address in A/AAAA RRSet 79 | NS_NAME = 4; // Authoritative name server, by name 80 | NS_IP = 5; // Authoritative name server, by IP address 81 | } 82 | 83 | // The Action taken to implement the Policy. 84 | enum Action { 85 | NXDOMAIN = 1; // Respond with NXDOMAIN 86 | NODATA = 2; // Respond with empty answer section 87 | PASS = 3; // Do not alter the response (passthrough) 88 | DROP = 4; // Do not respond. 89 | TRUNCATE = 5; // Truncate UDP response, forcing TCP retry 90 | LOCAL_DATA = 6; // Respond with local data from policy 91 | } 92 | 93 | // type: the type of policy applied, e.g. "RPZ" for a 94 | // policy from a Response Policy Zone. 95 | optional string type = 1; 96 | 97 | // rule: the rule matched by the message. 98 | // 99 | // In a RPZ context, this is the owner name of the rule in 100 | // the Reponse Policy Zone in wire format. 101 | optional bytes rule = 2; 102 | 103 | // action: the policy action taken in response to the 104 | // rule match. 105 | optional Action action = 3; 106 | 107 | // match: the feature of the message exchange which matched the rule. 108 | optional Match match = 4; 109 | 110 | // The matched value. Format depends on the matched feature . 111 | optional bytes value = 5; 112 | } 113 | 114 | // Message: a wire-format (RFC 1035 section 4) DNS message and associated 115 | // metadata. Applications generating "Message" payloads should follow 116 | // certain requirements based on the MessageType, see below. 117 | message Message { 118 | 119 | // There are eight types of "Message" defined that correspond to the 120 | // four arrows in the following diagram, slightly modified from RFC 1035 121 | // section 2: 122 | 123 | // +---------+ +----------+ +--------+ 124 | // | | query | | query | | 125 | // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. | 126 | // | Resolver| | Server | | Name | 127 | // | |<-SR--------CR-| |<-RR----AR-| Server | 128 | // +---------+ response | | response | | 129 | // +----------+ +--------+ 130 | 131 | // Each arrow has two Type values each, one for each "end" of each arrow, 132 | // because these are considered to be distinct events. Each end of each 133 | // arrow on the diagram above has been marked with a two-letter Type 134 | // mnemonic. Clockwise from upper left, these mnemonic values are: 135 | // 136 | // SQ: STUB_QUERY 137 | // CQ: CLIENT_QUERY 138 | // RQ: RESOLVER_QUERY 139 | // AQ: AUTH_QUERY 140 | // AR: AUTH_RESPONSE 141 | // RR: RESOLVER_RESPONSE 142 | // CR: CLIENT_RESPONSE 143 | // SR: STUB_RESPONSE 144 | 145 | // Two additional types of "Message" have been defined for the 146 | // "forwarding" case where an upstream DNS server is responsible for 147 | // further recursion. These are not shown on the diagram above, but have 148 | // the following mnemonic values: 149 | 150 | // FQ: FORWARDER_QUERY 151 | // FR: FORWARDER_RESPONSE 152 | 153 | // The "Message" Type values are defined below. 154 | 155 | enum Type { 156 | // AUTH_QUERY is a DNS query message received from a resolver by an 157 | // authoritative name server, from the perspective of the authoritative 158 | // name server. 159 | AUTH_QUERY = 1; 160 | 161 | // AUTH_RESPONSE is a DNS response message sent from an authoritative 162 | // name server to a resolver, from the perspective of the authoritative 163 | // name server. 164 | AUTH_RESPONSE = 2; 165 | 166 | // RESOLVER_QUERY is a DNS query message sent from a resolver to an 167 | // authoritative name server, from the perspective of the resolver. 168 | // Resolvers typically clear the RD (recursion desired) bit when 169 | // sending queries. 170 | RESOLVER_QUERY = 3; 171 | 172 | // RESOLVER_RESPONSE is a DNS response message received from an 173 | // authoritative name server by a resolver, from the perspective of 174 | // the resolver. 175 | RESOLVER_RESPONSE = 4; 176 | 177 | // CLIENT_QUERY is a DNS query message sent from a client to a DNS 178 | // server which is expected to perform further recursion, from the 179 | // perspective of the DNS server. The client may be a stub resolver or 180 | // forwarder or some other type of software which typically sets the RD 181 | // (recursion desired) bit when querying the DNS server. The DNS server 182 | // may be a simple forwarding proxy or it may be a full recursive 183 | // resolver. 184 | CLIENT_QUERY = 5; 185 | 186 | // CLIENT_RESPONSE is a DNS response message sent from a DNS server to 187 | // a client, from the perspective of the DNS server. The DNS server 188 | // typically sets the RA (recursion available) bit when responding. 189 | CLIENT_RESPONSE = 6; 190 | 191 | // FORWARDER_QUERY is a DNS query message sent from a downstream DNS 192 | // server to an upstream DNS server which is expected to perform 193 | // further recursion, from the perspective of the downstream DNS 194 | // server. 195 | FORWARDER_QUERY = 7; 196 | 197 | // FORWARDER_RESPONSE is a DNS response message sent from an upstream 198 | // DNS server performing recursion to a downstream DNS server, from the 199 | // perspective of the downstream DNS server. 200 | FORWARDER_RESPONSE = 8; 201 | 202 | // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS 203 | // server, from the perspective of the stub resolver. 204 | STUB_QUERY = 9; 205 | 206 | // STUB_RESPONSE is a DNS response message sent from a DNS server to a 207 | // stub resolver, from the perspective of the stub resolver. 208 | STUB_RESPONSE = 10; 209 | 210 | // TOOL_QUERY is a DNS query message sent from a DNS software tool to a 211 | // DNS server, from the perspective of the tool. 212 | TOOL_QUERY = 11; 213 | 214 | // TOOL_RESPONSE is a DNS response message received by a DNS software 215 | // tool from a DNS server, from the perspective of the tool. 216 | TOOL_RESPONSE = 12; 217 | 218 | // UPDATE_QUERY is a DNS update query message received from a resolver 219 | // by an authoritative name server, from the perspective of the 220 | // authoritative name server. 221 | UPDATE_QUERY = 13; 222 | 223 | // UPDATE_RESPONSE is a DNS update response message sent from an 224 | // authoritative name server to a resolver, from the perspective of the 225 | // authoritative name server. 226 | UPDATE_RESPONSE = 14; 227 | } 228 | 229 | // One of the Type values described above. 230 | required Type type = 1; 231 | 232 | // One of the SocketFamily values described above. 233 | optional SocketFamily socket_family = 2; 234 | 235 | // One of the SocketProtocol values described above. 236 | optional SocketProtocol socket_protocol = 3; 237 | 238 | // The network address of the message initiator. 239 | // For SocketFamily INET, this field is 4 octets (IPv4 address). 240 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). 241 | optional bytes query_address = 4; 242 | 243 | // The network address of the message responder. 244 | // For SocketFamily INET, this field is 4 octets (IPv4 address). 245 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). 246 | optional bytes response_address = 5; 247 | 248 | // The transport port of the message initiator. 249 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. 250 | optional uint32 query_port = 6; 251 | 252 | // The transport port of the message responder. 253 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. 254 | optional uint32 response_port = 7; 255 | 256 | // The time at which the DNS query message was sent or received, depending 257 | // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. 258 | // This is the number of seconds since the UNIX epoch. 259 | optional uint64 query_time_sec = 8; 260 | 261 | // The time at which the DNS query message was sent or received. 262 | // This is the seconds fraction, expressed as a count of nanoseconds. 263 | optional fixed32 query_time_nsec = 9; 264 | 265 | // The initiator's original wire-format DNS query message, verbatim. 266 | optional bytes query_message = 10; 267 | 268 | // The "zone" or "bailiwick" pertaining to the DNS query message. 269 | // This is a wire-format DNS domain name. 270 | optional bytes query_zone = 11; 271 | 272 | // The time at which the DNS response message was sent or received, 273 | // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or 274 | // CLIENT_RESPONSE. 275 | // This is the number of seconds since the UNIX epoch. 276 | optional uint64 response_time_sec = 12; 277 | 278 | // The time at which the DNS response message was sent or received. 279 | // This is the seconds fraction, expressed as a count of nanoseconds. 280 | optional fixed32 response_time_nsec = 13; 281 | 282 | // The responder's original wire-format DNS response message, verbatim. 283 | optional bytes response_message = 14; 284 | 285 | // Operator policy applied to the processing of this message, if any. 286 | optional Policy policy = 15; 287 | } 288 | 289 | // All fields except for 'type' in the Message schema are optional. 290 | // It is recommended that at least the following fields be filled in for 291 | // particular types of Messages. 292 | 293 | // AUTH_QUERY: 294 | // socket_family, socket_protocol 295 | // query_address, query_port 296 | // query_message 297 | // query_time_sec, query_time_nsec 298 | 299 | // AUTH_RESPONSE: 300 | // socket_family, socket_protocol 301 | // query_address, query_port 302 | // query_time_sec, query_time_nsec 303 | // response_message 304 | // response_time_sec, response_time_nsec 305 | 306 | // RESOLVER_QUERY: 307 | // socket_family, socket_protocol 308 | // query_message 309 | // query_time_sec, query_time_nsec 310 | // query_zone 311 | // response_address, response_port 312 | 313 | // RESOLVER_RESPONSE: 314 | // socket_family, socket_protocol 315 | // query_time_sec, query_time_nsec 316 | // query_zone 317 | // response_address, response_port 318 | // response_message 319 | // response_time_sec, response_time_nsec 320 | 321 | // CLIENT_QUERY: 322 | // socket_family, socket_protocol 323 | // query_message 324 | // query_time_sec, query_time_nsec 325 | 326 | // CLIENT_RESPONSE: 327 | // socket_family, socket_protocol 328 | // query_time_sec, query_time_nsec 329 | // response_message 330 | // response_time_sec, response_time_nsec 331 | -------------------------------------------------------------------------------- /src/bin/dnstap-dump/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | use anyhow::{bail, Result}; 4 | use clap::{Parser, ValueHint}; 5 | use heck::ToShoutySnekCase; 6 | use prost::Message; 7 | use std::fmt::Debug; 8 | use std::io::Write; 9 | use std::path::PathBuf; 10 | use tokio::fs::File; 11 | use tokio_stream::StreamExt; 12 | use tokio_util::codec::Framed; 13 | 14 | use dnstap_utils::dnstap; 15 | use dnstap_utils::framestreams_codec::{Frame, FrameStreamsCodec}; 16 | use dnstap_utils::util::deserialize_dnstap_handler_error; 17 | use dnstap_utils::util::fmt_dns_message; 18 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr; 19 | use dnstap_utils::util::unix_epoch_timestamp_to_string; 20 | use dnstap_utils::util::DnstapHandlerError; 21 | 22 | #[derive(Parser, Debug)] 23 | struct Opts { 24 | /// Read dnstap data from file 25 | #[clap(short = 'r', 26 | long = "read", 27 | name = "FILE", 28 | value_parser, 29 | value_hint = ValueHint::FilePath) 30 | ] 31 | file: PathBuf, 32 | } 33 | 34 | #[tokio::main] 35 | async fn main() -> Result<()> { 36 | // Workaround for https://github.com/rust-lang/rust/issues/62569 37 | if cfg!(unix) { 38 | unsafe { 39 | libc::signal(libc::SIGPIPE, libc::SIG_DFL); 40 | } 41 | } 42 | 43 | let opts = Opts::parse(); 44 | let file = File::open(opts.file).await?; 45 | let mut framed = Framed::new(file, FrameStreamsCodec {}); 46 | 47 | while let Some(frame) = framed.next().await { 48 | match frame { 49 | Ok(frame) => match frame { 50 | Frame::ControlReady(_) => { 51 | bail!("Protocol error: READY frame not allowed here"); 52 | } 53 | Frame::ControlAccept(_) => { 54 | bail!("Protocol error: ACCEPT frame not allowed here"); 55 | } 56 | Frame::ControlStart(_) => { 57 | // XXX: Validate the content type embedded in the Start control frame payload. 58 | } 59 | Frame::ControlStop => { 60 | return Ok(()); 61 | } 62 | Frame::ControlFinish => { 63 | bail!("Protocol error: FINISH frame not allowed here"); 64 | } 65 | Frame::ControlUnknown(_) => { 66 | bail!("Protocol error: Unknown control frame"); 67 | } 68 | Frame::Data(mut payload) => match dnstap::Dnstap::decode(&mut payload) { 69 | Ok(d) => { 70 | let mut s = String::with_capacity(2048); 71 | fmt_dnstap(&mut s, d); 72 | s.push_str("---\n"); 73 | std::io::stdout().write_all(s.as_bytes()).unwrap(); 74 | } 75 | Err(e) => { 76 | bail!( 77 | "Protocol error: Decoding dnstap protobuf message: {}, payload: {}", 78 | e, 79 | hex::encode(&payload) 80 | ); 81 | } 82 | }, 83 | }, 84 | Err(e) => { 85 | bail!("Protocol error: {}", e); 86 | } 87 | } 88 | } 89 | 90 | Ok(()) 91 | } 92 | 93 | fn fmt_dnstap(s: &mut String, d: dnstap::Dnstap) { 94 | if let Ok(dtype) = dnstap::dnstap::Type::try_from(d.r#type) { 95 | s.push_str("type: "); 96 | s.push_str(&format!("{:?}", dtype).TO_SHOUTY_SNEK_CASE()); 97 | s.push('\n'); 98 | } 99 | 100 | if let Some(identity) = &d.identity { 101 | s.push_str("identity: \""); 102 | s.push_str(&String::from_utf8_lossy(identity)); 103 | s.push_str("\"\n"); 104 | } 105 | 106 | if let Some(version) = &d.version { 107 | s.push_str("version: \""); 108 | s.push_str(&String::from_utf8_lossy(version)); 109 | s.push_str("\"\n"); 110 | } 111 | 112 | if let Some(extra) = &d.extra { 113 | s.push_str("extra:\n"); 114 | 115 | s.push_str(" bytes: \""); 116 | s.push_str(&hex::encode(extra)); 117 | s.push_str("\"\n"); 118 | 119 | s.push_str(" type: "); 120 | 121 | // Attempt to deserialize the dnstap payload's 'extra' field as if it were a serialized 122 | // DnstapHandler error. The 'extra' field in dnstap payloads "can be used for adding an 123 | // arbitrary byte-string annotation to the payload. No encoding or interpretation is 124 | // applied or enforced.", according to the dnstap protobuf definition. 125 | // 126 | // The custom serialization of DnstapHandler errors has a unique prefix which allows them 127 | // to be distinguished from other uses of the 'extra' field. 128 | match deserialize_dnstap_handler_error(extra) { 129 | Ok(dhe) => match dhe { 130 | DnstapHandlerError::Mismatch(mismatch_dns_bytes, _, _) => { 131 | s.push_str("DRDH_MISMATCH\n"); 132 | s.push_str(" mismatch_bytes: \""); 133 | s.push_str(&hex::encode(&mismatch_dns_bytes)); 134 | s.push_str("\"\n"); 135 | s.push_str(" mismatch_formatted: |\n"); 136 | fmt_dns_message(s, " ", &mismatch_dns_bytes); 137 | } 138 | DnstapHandlerError::Timeout => { 139 | s.push_str("DRDH_TIMEOUT\n"); 140 | } 141 | DnstapHandlerError::MissingField => { 142 | s.push_str("DRDH_MISSING_FIELD\n"); 143 | } 144 | }, 145 | Err(_) => { 146 | s.push_str("DRDH_EXTRA_PAYLOAD_PARSE_ERROR\n"); 147 | } 148 | } 149 | } 150 | 151 | if let Some(msg) = &d.message { 152 | fmt_dnstap_message(s, msg); 153 | } 154 | } 155 | 156 | fn fmt_dnstap_message(s: &mut String, msg: &dnstap::Message) { 157 | s.push_str("message:\n"); 158 | 159 | s.push_str(" type: "); 160 | s.push_str(&format!("{:?}", msg.r#type()).TO_SHOUTY_SNEK_CASE()); 161 | s.push('\n'); 162 | 163 | if let Some(query_time_sec) = msg.query_time_sec { 164 | if let Ok(dt) = unix_epoch_timestamp_to_string(query_time_sec, msg.query_time_nsec) { 165 | s.push_str(" query_time: !!timestamp "); 166 | s.push_str(&dt); 167 | s.push('\n'); 168 | } 169 | } 170 | 171 | if let Some(response_time_sec) = msg.response_time_sec { 172 | if let Ok(dt) = unix_epoch_timestamp_to_string(response_time_sec, msg.response_time_nsec) { 173 | s.push_str(" response_time: !!timestamp "); 174 | s.push_str(&dt); 175 | s.push('\n'); 176 | } 177 | } 178 | 179 | if msg.socket_family.is_some() { 180 | s.push_str(" socket_family: "); 181 | s.push_str(&format!("{:?}", msg.socket_family()).TO_SHOUTY_SNEK_CASE()); 182 | s.push('\n'); 183 | } 184 | 185 | if msg.socket_protocol.is_some() { 186 | s.push_str(" socket_protocol: "); 187 | s.push_str(&format!("{:?}", msg.socket_protocol()).TO_SHOUTY_SNEK_CASE()); 188 | s.push('\n'); 189 | } 190 | 191 | if let Some(query_address) = &msg.query_address { 192 | if let Ok(query_address) = try_from_u8_slice_for_ipaddr(query_address) { 193 | s.push_str(" query_address: \""); 194 | s.push_str(&query_address.to_string()); 195 | s.push_str("\"\n"); 196 | } 197 | } 198 | 199 | if let Some(response_address) = &msg.response_address { 200 | if let Ok(response_address) = try_from_u8_slice_for_ipaddr(response_address) { 201 | s.push_str(" response_address: \""); 202 | s.push_str(&response_address.to_string()); 203 | s.push_str("\"\n"); 204 | } 205 | } 206 | 207 | if let Some(query_port) = &msg.query_port { 208 | s.push_str(" query_port: "); 209 | s.push_str(&query_port.to_string()); 210 | s.push('\n'); 211 | } 212 | 213 | if let Some(response_port) = &msg.response_port { 214 | s.push_str(" response_port: "); 215 | s.push_str(&response_port.to_string()); 216 | s.push('\n'); 217 | } 218 | 219 | if let Some(query_message) = &msg.query_message { 220 | s.push_str(" query_message_bytes: \""); 221 | s.push_str(&hex::encode(query_message)); 222 | s.push_str("\"\n"); 223 | 224 | s.push_str(" query_message_formatted: |\n"); 225 | fmt_dns_message(s, " ", query_message); 226 | } 227 | 228 | if let Some(response_message) = &msg.response_message { 229 | s.push_str(" response_message_bytes: \""); 230 | s.push_str(&hex::encode(response_message)); 231 | s.push_str("\"\n"); 232 | 233 | s.push_str(" response_message_formatted: |\n"); 234 | fmt_dns_message(s, " ", response_message); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/bin/dnstap-inject/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | use anyhow::{bail, Result}; 4 | use bytes::{BufMut, BytesMut}; 5 | use clap::{ArgAction, Parser, ValueHint}; 6 | use log::*; 7 | use prost::Message; 8 | use std::fmt::Debug; 9 | use std::net::{IpAddr, SocketAddr}; 10 | use std::path::PathBuf; 11 | use std::time::Duration; 12 | use tokio::fs::File; 13 | use tokio::net::UdpSocket; 14 | use tokio::time::timeout; 15 | use tokio_stream::StreamExt; 16 | use tokio_util::codec::Framed; 17 | 18 | use dnstap_utils::dnstap; 19 | use dnstap_utils::framestreams_codec::{Frame, FrameStreamsCodec}; 20 | use dnstap_utils::proxyv2; 21 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr; 22 | 23 | /// Duration to wait for a response from the DNS server under test. 24 | const DNS_QUERY_TIMEOUT: Duration = Duration::from_millis(500); 25 | 26 | #[derive(Parser, Debug)] 27 | struct Opts { 28 | /// Read dnstap data from file 29 | #[clap(short = 'r', 30 | long = "read", 31 | name = "FILE", 32 | value_parser, 33 | value_hint = ValueHint::FilePath) 34 | ] 35 | file: PathBuf, 36 | 37 | /// UDP DNS server and port to send queries to 38 | #[clap(long, name = "DNS IP:PORT")] 39 | dns: SocketAddr, 40 | 41 | /// Whether to add PROXY v2 header to re-sent DNS queries 42 | #[clap(long)] 43 | proxy: bool, 44 | 45 | /// Increase verbosity level 46 | #[clap(short, long, action = ArgAction::Count)] 47 | verbose: u8, 48 | } 49 | 50 | #[tokio::main] 51 | async fn main() -> Result<()> { 52 | // Workaround for https://github.com/rust-lang/rust/issues/62569 53 | if cfg!(unix) { 54 | unsafe { 55 | libc::signal(libc::SIGPIPE, libc::SIG_DFL); 56 | } 57 | } 58 | 59 | let opts = Opts::parse(); 60 | 61 | stderrlog::new() 62 | .verbosity(opts.verbose as usize) 63 | .module(module_path!()) 64 | .init() 65 | .unwrap(); 66 | 67 | let file = File::open(opts.file).await?; 68 | let socket = setup_socket(&opts.dns).await?; 69 | 70 | let mut framed = Framed::new(file, FrameStreamsCodec {}); 71 | while let Some(frame) = framed.next().await { 72 | match frame { 73 | Ok(frame) => match frame { 74 | Frame::ControlReady(_) => { 75 | bail!("Protocol error: READY frame not allowed here"); 76 | } 77 | Frame::ControlAccept(_) => { 78 | bail!("Protocol error: ACCEPT frame not allowed here"); 79 | } 80 | Frame::ControlStart(_) => { 81 | // XXX: Validate the content type embedded in the Start control frame payload. 82 | } 83 | Frame::ControlStop => { 84 | return Ok(()); 85 | } 86 | Frame::ControlFinish => { 87 | bail!("Protocol error: FINISH frame not allowed here"); 88 | } 89 | Frame::ControlUnknown(_) => { 90 | bail!("Protocol error: Unknown control frame"); 91 | } 92 | Frame::Data(mut payload) => match dnstap::Dnstap::decode(&mut payload) { 93 | Ok(d) => { 94 | process_dnstap_frame(&socket, opts.proxy, d).await?; 95 | } 96 | Err(e) => { 97 | bail!( 98 | "Protocol error: Decoding dnstap protobuf message: {}, payload: {}", 99 | e, 100 | hex::encode(&payload) 101 | ); 102 | } 103 | }, 104 | }, 105 | Err(e) => { 106 | bail!("Protocol error: {}", e); 107 | } 108 | } 109 | } 110 | 111 | Ok(()) 112 | } 113 | 114 | async fn setup_socket(server_addr: &SocketAddr) -> Result { 115 | let local_addr: SocketAddr = if server_addr.is_ipv4() { 116 | "0.0.0.0:0" 117 | } else { 118 | "[::]:0" 119 | } 120 | .parse()?; 121 | 122 | let socket = UdpSocket::bind(local_addr).await?; 123 | socket.connect(server_addr).await?; 124 | debug!("Connected socket to DNS server: {:?}", &socket); 125 | Ok(socket) 126 | } 127 | 128 | async fn process_dnstap_frame(socket: &UdpSocket, proxy: bool, d: dnstap::Dnstap) -> Result<()> { 129 | if let Ok(dtype) = dnstap::dnstap::Type::try_from(d.r#type) { 130 | if dtype == dnstap::dnstap::Type::Message { 131 | if let Some(msg) = &d.message { 132 | process_dnstap_message(socket, proxy, msg).await?; 133 | } 134 | } 135 | } 136 | 137 | Ok(()) 138 | } 139 | 140 | async fn process_dnstap_message( 141 | socket: &UdpSocket, 142 | proxy: bool, 143 | msg: &dnstap::Message, 144 | ) -> Result<()> { 145 | if let Some(query_address) = &msg.query_address { 146 | if let Ok(query_address) = try_from_u8_slice_for_ipaddr(query_address) { 147 | if msg.query_message.is_some() { 148 | send_query(socket, proxy, &query_address, msg).await?; 149 | } 150 | } 151 | } 152 | Ok(()) 153 | } 154 | 155 | async fn send_query( 156 | socket: &UdpSocket, 157 | proxy: bool, 158 | query_address: &IpAddr, 159 | msg: &dnstap::Message, 160 | ) -> Result<()> { 161 | if let Some(query_message) = &msg.query_message { 162 | // Buffer to received UDP response messages from the DNS server under test. 163 | let mut recv_buf: [u8; 4096] = [0; 4096]; 164 | 165 | // Create a buffer for containing the original DNS query message, optionally with a PROXY v2 166 | // header prepended. 167 | let mut buf = BytesMut::with_capacity(1024); 168 | 169 | if proxy { 170 | proxyv2::add_proxy_payload(&mut buf, msg, query_address, None)?; 171 | } 172 | 173 | // Add the original DNS query message. 174 | buf.put_slice(query_message); 175 | 176 | // Freeze the buffer since it no longer needs to be mutated. 177 | let buf = buf.freeze(); 178 | 179 | // Send the constructed query message to the DNS server under test. 180 | trace!("Sending DNS query: {}", hex::encode(query_message)); 181 | socket.send(&buf).await?; 182 | 183 | // Receive the DNS response message from the DNS server under test, or wait for the DNS 184 | // query timeout to expire. 185 | match timeout(DNS_QUERY_TIMEOUT, socket.recv(&mut recv_buf)).await { 186 | Ok(res) => match res { 187 | Ok(n_bytes) => { 188 | let received_message = &recv_buf[..n_bytes]; 189 | trace!("Received DNS response: {}", hex::encode(received_message)); 190 | } 191 | Err(e) => { 192 | error!("Error while receiving response: {}", e); 193 | } 194 | }, 195 | Err(e) => { 196 | error!("Timeout: {}", e); 197 | } 198 | } 199 | } 200 | 201 | Ok(()) 202 | } 203 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/dnstap_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | use anyhow::{bail, Result}; 4 | use bytes::{BufMut, Bytes, BytesMut}; 5 | use ip_network_table::IpNetworkTable; 6 | use log::*; 7 | use std::net::SocketAddr; 8 | use std::sync::atomic::{AtomicBool, Ordering}; 9 | use std::sync::Arc; 10 | use std::time::Duration; 11 | use thiserror::Error; 12 | use tokio::net::UdpSocket; 13 | use tokio::time::timeout; 14 | 15 | use dnstap_utils::dnstap; 16 | use dnstap_utils::proxyv2; 17 | use dnstap_utils::util::dns_message_is_truncated; 18 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr; 19 | use dnstap_utils::util::DnstapHandlerError; 20 | 21 | use crate::{Channels, Opts}; 22 | 23 | /// Duration for [`DnstapHandler`]'s to wait for a response from the DNS server under test. 24 | const DNS_QUERY_TIMEOUT: Duration = Duration::from_millis(5000); 25 | 26 | /// Maximum UDP response message size that can be received from the DNS server under test. 27 | const DNS_RESPONSE_BUFFER_SIZE: usize = 4096; 28 | 29 | /// Process the dnstap protobuf payloads and re-send them to the DNS server under test. 30 | /// 31 | /// Decoded protobuf payloads are received over a channel from a [`crate::FrameHandler`]. The 32 | /// original address of the DNS client that sent the DNS query is used to construct a PROXY v2 33 | /// header which is prepended to the DNS query which is sent to the DNS server specified in the 34 | /// configuration. The response that the DNS server sends is then compared to the original DNS 35 | /// response message included in the dnstap payload and logged if they differ. 36 | /// 37 | /// This requires two specializations from the DNS server that we receive dnstap logging data and 38 | /// the DNS server that we re-send DNS queries to: 39 | /// 40 | /// 1. The DNS server that sends the dnstap payloads needs to produce `AUTH_RESPONSE` messages that 41 | /// include both the `query_message` and `response_message` fields. It is optional to fill out 42 | /// both of these fields, and DNS servers typically only fill out the `response_message` field 43 | /// for `AUTH_RESPONSE` dnstap payloads. 44 | /// 2. The DNS server under test needs to understand the PROXY v2 header which the 45 | /// [`DnstapHandler`] prepends to the DNS query. An unmodified DNS server will not recognize the 46 | /// prepended DNS queries that the [`DnstapHandler`] sends and will likely respond with the DNS 47 | /// `FORMERR` or `NOTIMP` RCODEs. 48 | pub struct DnstapHandler { 49 | /// Server options. 50 | opts: Opts, 51 | 52 | /// Server channels. 53 | channels: Channels, 54 | 55 | /// Networks to ignore queries from. 56 | ignore_query_nets: Option>, 57 | 58 | /// The result of the status monitor check. Controls whether mismatches should be emitted into 59 | /// the errors channel (if true) or suppressed (if false). 60 | match_status: Arc, 61 | 62 | /// Per-handler DNS client socket to use to send DNS queries to the DNS server under test. This 63 | /// is an [`Option`] rather than a [`UdpSocket`] because in the case of a timeout 64 | /// the socket will need to be closed and reopened. 65 | socket: Option, 66 | 67 | /// Buffer to received UDP response messages from the DNS server under test. 68 | recv_buf: [u8; DNS_RESPONSE_BUFFER_SIZE], 69 | } 70 | 71 | #[derive(Error, Debug)] 72 | enum DnstapHandlerInternalError { 73 | #[error("Non-UDP dnstap payload was discarded")] 74 | DiscardNonUdp, 75 | } 76 | 77 | impl DnstapHandler { 78 | /// Create a new [`DnstapHandler`] that receives decoded dnstap protobuf payloads from the 79 | /// `channel_receiver` channel, synthesizes new DNS client queries from the received dnstap 80 | /// payloads, and sends them to the DNS server under test. 81 | /// 82 | /// Server options are specified in `opts`, such as: 83 | /// 84 | /// * `opts.dns`: The DNS server address/port to send DNS queries to. 85 | /// * `opts.dscp`: The DSCP value to use for re-sent DNS queries. 86 | /// * `opts.proxy`: Whether to add PROXY v2 header to re-sent DNS queries. 87 | /// * `opts.ignore_tc`: Whether to ignore UDP responses with the TC bit set. 88 | /// 89 | /// The `match_status` variable is a shared flag used to suppress mismatches. It needs to be 90 | /// externally set to `true` to enable the generation of mismatch output and metrics. 91 | /// 92 | /// Responses received from the DNS server under test that don't match the original response in 93 | /// the dnstap payload will be sent via the channel `channel_error_sender` if `match_status` is 94 | /// `true`. 95 | /// 96 | /// If a DNS timeout occurs when re-querying the DNS server under test, the dnstap payload will 97 | /// be sent via the channel `channel_timeout_sender`. 98 | pub async fn new( 99 | opts: &Opts, 100 | channels: &Channels, 101 | match_status: Arc, 102 | ) -> Result { 103 | // Create the IpNetworkTable of networks to ignore queries from. 104 | let ignore_query_nets = if !opts.ignore_query_net.is_empty() { 105 | let mut table = IpNetworkTable::new(); 106 | for net in &opts.ignore_query_net { 107 | table.insert(*net, true); 108 | } 109 | Some(table) 110 | } else { 111 | None 112 | }; 113 | 114 | let mut handler = DnstapHandler { 115 | opts: opts.clone(), 116 | channels: channels.clone(), 117 | ignore_query_nets, 118 | match_status, 119 | socket: None, 120 | recv_buf: [0; DNS_RESPONSE_BUFFER_SIZE], 121 | }; 122 | 123 | // Setup the private UDP client socket for this handler. 124 | handler.maybe_setup_socket().await?; 125 | 126 | Ok(handler) 127 | } 128 | 129 | /// Create, bind, and connect the UDP client socket if needed. 130 | async fn maybe_setup_socket(&mut self) -> Result<()> { 131 | if self.socket.is_none() { 132 | // Determine whether to create an IPv4 or IPv6 client socket. 133 | let local_address: SocketAddr = if self.opts.dns.is_ipv4() { 134 | "0.0.0.0:0" 135 | } else { 136 | "[::]:0" 137 | } 138 | .parse()?; 139 | 140 | // Bind the socket. 141 | let socket = UdpSocket::bind(local_address).await?; 142 | 143 | // Set the DSCP value. 144 | if let Some(dscp) = self.opts.dscp { 145 | set_udp_dscp(&socket, dscp)?; 146 | } 147 | 148 | // Connect the socket to the DNS server under test. 149 | socket.connect(&self.opts.dns).await?; 150 | 151 | debug!("Connected socket to DNS server: {:?}", &socket); 152 | 153 | // Store the socket for use by the main processing loop. 154 | self.socket = Some(socket); 155 | } 156 | Ok(()) 157 | } 158 | 159 | /// Close and reopen the UDP client socket. 160 | async fn restart_socket(&mut self) -> Result<()> { 161 | // Drop the old socket. 162 | self.socket = None; 163 | 164 | // Setup the private UDP client socket for this handler again. 165 | self.maybe_setup_socket().await?; 166 | 167 | Ok(()) 168 | } 169 | 170 | /// Receive dnstap protobuf payloads from a [`crate::FrameHandler`] and perform further 171 | /// processing. 172 | pub async fn run(&mut self) -> Result<()> { 173 | while let Ok(d) = self.channels.receiver.recv().await { 174 | // Check if the UDP client socket needs to be re-created. 175 | self.maybe_setup_socket().await?; 176 | 177 | // Actually process the dnstap payload. 178 | self.process_dnstap(d).await? 179 | } 180 | Ok(()) 181 | } 182 | 183 | /// Process the outer dnstap container and if it contains a dnstap "Message" object of type 184 | /// `AUTH_RESPONSE`, perform further processing on it. 185 | async fn process_dnstap(&mut self, mut d: dnstap::Dnstap) -> Result<()> { 186 | // Currently only "Message" objects are defined. 187 | if dnstap::dnstap::Type::try_from(d.r#type) != Ok(dnstap::dnstap::Type::Message) { 188 | return Ok(()); 189 | } 190 | 191 | let msg = match &d.message { 192 | Some(msg) => msg, 193 | None => return Ok(()), 194 | }; 195 | 196 | // Check if this is an `AUTH_RESPONSE` type dnstap "Message" object. 197 | if dnstap::message::Type::try_from(msg.r#type) != Ok(dnstap::message::Type::AuthResponse) { 198 | return Ok(()); 199 | } 200 | 201 | // Perform further processing on this message. On timeout, log the error and close and 202 | // reopen the UDP client socket. 203 | match self.process_dnstap_message(msg).await { 204 | Ok(_) => { 205 | crate::metrics::DNSTAP_PAYLOADS.success.inc(); 206 | } 207 | Err(e) => { 208 | crate::metrics::DNSTAP_PAYLOADS.error.inc(); 209 | 210 | if let Some(e) = e.downcast_ref::() { 211 | // Serialize the [`DnstapHandlerError`] instance and export it via the dnstap 212 | // object's `extra` field. 213 | d.extra = Some(e.serialize().to_vec()); 214 | 215 | match e { 216 | DnstapHandlerError::Mismatch(_, _, _) => { 217 | // Send to the errors channel. 218 | self.send_error(d); 219 | 220 | crate::metrics::DNS_COMPARISONS.mismatched.inc(); 221 | } 222 | 223 | DnstapHandlerError::Timeout => { 224 | // Send to the timeouts channel. 225 | self.send_timeout(d); 226 | 227 | crate::metrics::DNS_QUERIES.timeout.inc(); 228 | 229 | // In the case of a DNS query timeout, we can't tell the difference 230 | // between a message that has genuinely been lost and one that might 231 | // still be processed by the DNS server under test and returned to us 232 | // on a subsequent socket read. If the latter happens, the lockstep 233 | // synchronization between a sent DNS query and a received DNS response 234 | // will be lost and every subsequent comparison between originally 235 | // logged response message and corresponding response message received 236 | // from the DNS server under test will fail because the wrong messages 237 | // are being compared. 238 | // 239 | // This kind of failure mode could be worked around by implementing a 240 | // state table for the outbound DNS queries sent to the DNS server 241 | // under test but this requires parsing some portions of the DNS header 242 | // and the question section as well as garbage collection of the state 243 | // table to avoid filling it with timed out queries. The easier thing 244 | // to do is to close the socket and open a new one. 245 | self.restart_socket().await?; 246 | } 247 | 248 | DnstapHandlerError::MissingField => { 249 | // Send to the errors channel. 250 | self.send_error(d); 251 | 252 | // Metric increment already handled above. 253 | } 254 | } 255 | } else if let Some(e) = e.downcast_ref::() { 256 | match e { 257 | DnstapHandlerInternalError::DiscardNonUdp => { 258 | crate::metrics::DNSTAP_HANDLER_INTERNAL_ERRORS 259 | .discard_non_udp 260 | .inc(); 261 | } 262 | } 263 | } 264 | } 265 | } 266 | 267 | Ok(()) 268 | } 269 | 270 | /// Process a dnstap "Message" object. 271 | async fn process_dnstap_message(&mut self, msg: &dnstap::Message) -> Result<()> { 272 | // Check if we have a connected UDP socket to send DNS queries to. 273 | let socket = match &self.socket { 274 | Some(socket) => socket, 275 | None => { 276 | bail!("No connected socket to send DNS queries"); 277 | } 278 | }; 279 | 280 | // Check if the original DNS query message was sent over UDP. If not, when the query is 281 | // re-sent over UDP it may elicit different behavior compared to the original transport. 282 | match &msg.socket_protocol { 283 | Some(socket_protocol) => { 284 | if dnstap::SocketProtocol::try_from(*socket_protocol) 285 | != Ok(dnstap::SocketProtocol::Udp) 286 | { 287 | bail!(DnstapHandlerInternalError::DiscardNonUdp); 288 | } 289 | } 290 | None => bail!(DnstapHandlerError::MissingField), 291 | }; 292 | 293 | // Extract the `query_message` field. This is the original DNS query message sent to the 294 | // DNS server that logged the dnstap message. 295 | let query_message = match &msg.query_message { 296 | Some(msg) => msg, 297 | None => return Ok(()), 298 | }; 299 | 300 | // Extract the `response_message` field. This is the original DNS response message sent by 301 | // the DNS server that logged the dnstap message to the original client. 302 | let response_message = match &msg.response_message { 303 | Some(msg) => msg, 304 | None => return Ok(()), 305 | }; 306 | 307 | // Extract the `query_address` field and convert it to an [`IpAddr`]. This is the IP 308 | // address of the original client that sent the DNS query to the DNS server that logged the 309 | // dnstap message. 310 | let query_address = match &msg.query_address { 311 | Some(addr) => try_from_u8_slice_for_ipaddr(addr)?, 312 | None => bail!(DnstapHandlerError::MissingField), 313 | }; 314 | 315 | // Check whether the query address matches any network in the table of networks to ignore 316 | // queries from. 317 | if let Some(table) = &self.ignore_query_nets { 318 | if table.longest_match(query_address).is_some() { 319 | crate::metrics::DNS_COMPARISONS.query_net_ignored.inc(); 320 | return Ok(()); 321 | }; 322 | }; 323 | 324 | // Create a buffer for containing the original DNS client message, optionally with a PROXY 325 | // v2 header prepended. The PROXY v2 payload that we generate will be small (<100 bytes) 326 | // and DNS query messages are restricted by the protocol to a maximum size of 512 bytes. 327 | let mut buf = BytesMut::with_capacity(1024); 328 | 329 | // Add the PROXY v2 payload, if the dnstap handler has been configured to do so. 330 | if self.opts.proxy { 331 | // Check if the dnstap handler has also been configured to add the timespec TLV to the 332 | // PROXY v2 payload. 333 | let timespec = if self.opts.proxy_timespec { 334 | Some(proxyv2::Timespec { 335 | seconds: msg.response_time_sec(), 336 | nanoseconds: msg.response_time_nsec(), 337 | }) 338 | } else { 339 | None 340 | }; 341 | 342 | proxyv2::add_proxy_payload(&mut buf, msg, &query_address, timespec)?; 343 | } 344 | 345 | // Add the original DNS query message. 346 | buf.put_slice(query_message); 347 | 348 | // Freeze the buffer since it no longer needs to be mutated. 349 | let buf = buf.freeze(); 350 | 351 | // Send the constructed query message to the DNS server under test. 352 | trace!("Sending DNS query: {}", hex::encode(&buf)); 353 | socket.send(&buf).await?; 354 | 355 | // Receive the DNS response message from the DNS server under test, or wait for the DNS 356 | // query timeout to expire. 357 | match timeout(DNS_QUERY_TIMEOUT, socket.recv(&mut self.recv_buf)).await { 358 | Ok(res) => match res { 359 | Ok(n_bytes) => { 360 | // A DNS response message was successfully received. 361 | crate::metrics::DNS_QUERIES.success.inc(); 362 | 363 | let received_message = &self.recv_buf[..n_bytes]; 364 | trace!("Received DNS response: {}", hex::encode(received_message)); 365 | 366 | // Check if matching is enabled. 367 | if self.match_status.load(Ordering::Relaxed) { 368 | // Check if the DNS response message received from the DNS server under test is 369 | // identical to the original DNS response message recorded in the dnstap 370 | // message. 371 | if response_message == received_message { 372 | // Match. 373 | crate::metrics::DNS_COMPARISONS.matched.inc(); 374 | } else if self.opts.ignore_tc 375 | && (dns_message_is_truncated(response_message) 376 | || dns_message_is_truncated(received_message)) 377 | { 378 | // Either the original DNS response message or the DNS response message 379 | // received from the DNS server under test was truncated, and the 380 | // option to ignore TC=1 responses was enabled. 381 | crate::metrics::DNS_COMPARISONS.udp_tc_ignored.inc(); 382 | } else { 383 | // Mismatch. 384 | bail!(DnstapHandlerError::Mismatch( 385 | Bytes::copy_from_slice(received_message), 386 | hex::encode(received_message), 387 | hex::encode(response_message), 388 | )); 389 | } 390 | } else { 391 | crate::metrics::DNS_COMPARISONS.suppressed.inc(); 392 | } 393 | } 394 | Err(e) => { 395 | crate::metrics::DNS_QUERIES.error.inc(); 396 | bail!(e); 397 | } 398 | }, 399 | Err(_) => { 400 | bail!(DnstapHandlerError::Timeout); 401 | } 402 | } 403 | 404 | Ok(()) 405 | } 406 | 407 | fn send_error(&self, d: dnstap::Dnstap) { 408 | match self.channels.error_sender.try_send(d) { 409 | Ok(_) => { 410 | crate::metrics::CHANNEL_ERROR_TX.success.inc(); 411 | } 412 | Err(_) => { 413 | crate::metrics::CHANNEL_ERROR_TX.error.inc(); 414 | } 415 | } 416 | } 417 | 418 | fn send_timeout(&self, d: dnstap::Dnstap) { 419 | match self.channels.timeout_sender.try_send(d) { 420 | Ok(_) => { 421 | crate::metrics::CHANNEL_TIMEOUT_TX.success.inc(); 422 | } 423 | Err(_) => { 424 | crate::metrics::CHANNEL_TIMEOUT_TX.error.inc(); 425 | } 426 | } 427 | } 428 | } 429 | 430 | /// Utility function that sets the DSCP value on a UDP socket. 431 | #[cfg(unix)] 432 | fn set_udp_dscp(s: &UdpSocket, dscp: u8) -> Result<()> { 433 | use std::os::unix::io::AsRawFd; 434 | 435 | let raw_fd = s.as_raw_fd(); 436 | let optval: libc::c_int = (dscp << 2).into(); 437 | 438 | let ret = match s.local_addr()? { 439 | SocketAddr::V4(_) => unsafe { 440 | libc::setsockopt( 441 | raw_fd, 442 | libc::IPPROTO_IP, 443 | libc::IP_TOS, 444 | &optval as *const _ as *const libc::c_void, 445 | std::mem::size_of_val(&optval) as libc::socklen_t, 446 | ) 447 | }, 448 | SocketAddr::V6(_) => unsafe { 449 | libc::setsockopt( 450 | raw_fd, 451 | libc::IPPROTO_IPV6, 452 | libc::IPV6_TCLASS, 453 | &optval as *const _ as *const libc::c_void, 454 | std::mem::size_of_val(&optval) as libc::socklen_t, 455 | ) 456 | }, 457 | }; 458 | 459 | match ret { 460 | 0 => Ok(()), 461 | _ => bail!( 462 | "Failed to set DSCP value {} on socket fd {}: {}", 463 | dscp, 464 | raw_fd, 465 | std::io::Error::last_os_error() 466 | ), 467 | } 468 | } 469 | 470 | #[cfg(not(unix))] 471 | fn set_udp_dscp(_s: &UdpSocket, _dscp: u8) -> Result<()> { 472 | bail!("Cannot set DSCP values on this platform"); 473 | } 474 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/frame_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Fastly, Inc. 2 | 3 | use anyhow::bail; 4 | use anyhow::Result; 5 | use async_channel::Sender; 6 | use futures::SinkExt; 7 | use log::*; 8 | use prost::Message; 9 | use std::os::unix::io::AsRawFd; 10 | use tokio::net::UnixStream; 11 | use tokio_stream::StreamExt; 12 | use tokio_util::codec::Framed; 13 | 14 | use dnstap_utils::dnstap; 15 | use dnstap_utils::framestreams_codec::{self, Frame, FrameStreamsCodec}; 16 | 17 | /// Per-connection FrameStreams protocol handler. Reads delimited frames from the Unix socket 18 | /// stream, decodes the protobuf payload, and then sends the protobuf object over a channel to a 19 | /// [`crate::DnstapHandler`] for further processing. 20 | pub struct FrameHandler { 21 | /// The send side of the async channel, used by [`FrameHandler`]'s to send decoded dnstap 22 | /// protobuf messages to the [`crate::DnstapHandler`]'s. 23 | channel_sender: Sender, 24 | 25 | /// The Unix stream to read frames from. 26 | stream: UnixStream, 27 | 28 | /// Identifying description of the connected `stream`. 29 | stream_descr: String, 30 | 31 | /// Counter of the number of bytes processed by this [`FrameHandler`]. 32 | count_data_bytes: usize, 33 | 34 | /// Counter of the number of frames processed by this [`FrameHandler`]. 35 | count_data_frames: usize, 36 | } 37 | 38 | impl FrameHandler { 39 | /// Create a new [`FrameHandler`] that reads from `stream` and writes decoded protobuf messages 40 | /// to `channel_sender`. 41 | pub fn new(stream: UnixStream, channel_sender: Sender) -> Self { 42 | let stream_descr = format!("fd {}", stream.as_raw_fd()); 43 | 44 | FrameHandler { 45 | stream, 46 | stream_descr, 47 | channel_sender, 48 | count_data_bytes: 0, 49 | count_data_frames: 0, 50 | } 51 | } 52 | 53 | /// Set up the FrameStreams connection and processing the incoming data frames. 54 | pub async fn run(&mut self) -> Result<()> { 55 | info!( 56 | "Accepted new Frame Streams connection on {}", 57 | self.stream_descr 58 | ); 59 | 60 | // Initialize the FrameStreams codec on the connected stream. 61 | let mut framed = Framed::with_capacity( 62 | &mut self.stream, 63 | FrameStreamsCodec {}, 64 | framestreams_codec::FRAME_LENGTH_MAX, 65 | ); 66 | 67 | // Process each frame from the connection. 68 | while let Some(frame) = framed.next().await { 69 | match frame { 70 | Ok(frame) => { 71 | match frame { 72 | Frame::ControlReady(payload) => { 73 | // Ready: This is the first control frame received from the sender. 74 | // Send the Accept control frame. 75 | // 76 | // XXX: We mirror the content type(s) specified in the Ready control 77 | // frame payload into the Accept control frame payload. Instead we 78 | // should select a specific content type from the sender's list. 79 | framed.send(Frame::ControlAccept(payload)).await?; 80 | } 81 | Frame::ControlAccept(_) => { 82 | // Accept: This is the control frame that the receiver sends in 83 | // response to the Ready frame. It is a protocol violation for a sender 84 | // to send an Accept control frame. 85 | bail!( 86 | "{}: Protocol error: Sender sent ACCEPT frame", 87 | self.stream_descr 88 | ); 89 | } 90 | Frame::ControlStart(payload) => { 91 | // Start: This is the control frame that the sender sends in response 92 | // to the Accept frame and indicates it will begin sending data frames 93 | // of the type specified in the Start control frame payload. 94 | // 95 | // XXX: We should probably do something with the content type that the 96 | // sender specifies in the Start control frame payload. 97 | trace!( 98 | "{}: START payload: {}", 99 | self.stream_descr, 100 | hex::encode(&payload) 101 | ); 102 | } 103 | Frame::ControlStop => { 104 | // Stop: This is the control frame that the sender sends when it is 105 | // done sending Data frames. Send the Finish frame acknowledging 106 | // shutdown of the stream. 107 | info!( 108 | "{}: STOP received, processed {} data frames, {} data bytes", 109 | self.stream_descr, self.count_data_frames, self.count_data_bytes, 110 | ); 111 | framed.send(Frame::ControlFinish).await?; 112 | 113 | // Shut the [`FrameHandler`] down. 114 | return Ok(()); 115 | } 116 | Frame::ControlFinish => { 117 | // Protocol violation for a receiver to receive a Finish control frame. 118 | bail!( 119 | "{}: Protocol error: Sender sent FINISH frame", 120 | self.stream_descr 121 | ); 122 | } 123 | Frame::ControlUnknown(_) => { 124 | bail!( 125 | "{}: Protocol error: Sender sent unknown control frame", 126 | self.stream_descr 127 | ); 128 | } 129 | Frame::Data(mut payload) => { 130 | // Accounting. 131 | crate::metrics::DATA_FRAMES.inc(); 132 | crate::metrics::DATA_BYTES.inc_by(payload.len() as u64); 133 | self.count_data_bytes += payload.len(); 134 | self.count_data_frames += 1; 135 | 136 | // Decode the protobuf message. 137 | match dnstap::Dnstap::decode(&mut payload) { 138 | // The message was successfully parsed, send it to a 139 | // [`DnstapHandler`] for further processing. 140 | Ok(d) => match self.channel_sender.send(d).await { 141 | Ok(_) => {} 142 | Err(e) => { 143 | bail!("{}: Unable to send dnstap protobuf message to channel: {}", 144 | self.stream_descr, e); 145 | } 146 | }, 147 | // The payload failed to parse. 148 | Err(e) => { 149 | bail!( 150 | "{}: Protocol error: Decoding dnstap protobuf message: {}, payload: {}", 151 | self.stream_descr, 152 | e, 153 | hex::encode(&payload) 154 | ); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | Err(e) => { 161 | bail!("{}: Protocol error: {}", self.stream_descr, e); 162 | } 163 | } 164 | } 165 | 166 | Ok(()) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/http_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2022 Fastly, Inc. 2 | 3 | use anyhow::Result; 4 | use async_channel::Receiver; 5 | use async_stream::stream; 6 | use bytes::{BufMut, BytesMut}; 7 | use hyper::header::CONTENT_TYPE; 8 | use hyper::service::{make_service_fn, service_fn}; 9 | use hyper::{Body, Request, Response}; 10 | use hyper::{Method, StatusCode}; 11 | use log::*; 12 | use prometheus::core::{AtomicU64, GenericCounter}; 13 | use prometheus::Encoder as PrometheusEncoder; 14 | use prometheus::TextEncoder; 15 | use prost::Message; 16 | use std::convert::Infallible; 17 | use std::net::SocketAddr; 18 | use tokio_util::codec::Encoder as CodecEncoder; 19 | 20 | use dnstap_utils::dnstap; 21 | use dnstap_utils::framestreams_codec::{self, Frame, FrameStreamsCodec}; 22 | 23 | use crate::Channels; 24 | 25 | /// Process HTTP requests. 26 | pub struct HttpHandler { 27 | /// HTTP server socket to listen on. 28 | http_address: SocketAddr, 29 | 30 | /// Server channels. 31 | channels: Channels, 32 | } 33 | 34 | /// Structure for encapsulating a channel together with the metric to be incremented when a payload 35 | /// is successfully read from the channel. Used below by the HTTP endpoints that read from the 36 | /// error and timeout channels. 37 | struct HttpChannel { 38 | receiver: Receiver, 39 | success_metric: &'static GenericCounter, 40 | } 41 | 42 | impl HttpHandler { 43 | /// Create a new [`HttpHandler`] that listens on `http_address`. For the `/errors` 44 | /// endpoint, error dnstap payloads will be retrieved from `channel_error_receiver`. 45 | pub fn new(http_address: SocketAddr, channels: &Channels) -> Self { 46 | HttpHandler { 47 | http_address, 48 | channels: channels.clone(), 49 | } 50 | } 51 | 52 | /// Run the HTTP server. 53 | pub async fn run(&self) -> Result<()> { 54 | // Clone the channels for the outer closure. 55 | let channel_error = self.channels.error_receiver.clone(); 56 | let channel_timeout = self.channels.timeout_receiver.clone(); 57 | 58 | let make_svc = make_service_fn(move |_| { 59 | // Clone the channels again for the inner closure. 60 | let channel_error = channel_error.clone(); 61 | let channel_timeout = channel_timeout.clone(); 62 | 63 | async move { 64 | Ok::<_, Infallible>(service_fn(move |req| { 65 | let channel_error = HttpChannel { 66 | receiver: channel_error.clone(), 67 | success_metric: &crate::metrics::CHANNEL_ERROR_RX.success, 68 | }; 69 | let channel_timeout = HttpChannel { 70 | receiver: channel_timeout.clone(), 71 | success_metric: &crate::metrics::CHANNEL_TIMEOUT_RX.success, 72 | }; 73 | 74 | async move { http_service(req, channel_error, channel_timeout).await } 75 | })) 76 | } 77 | }); 78 | 79 | // Bind to the HTTP server address. 80 | let server = hyper::server::Server::try_bind(&self.http_address)?.serve(make_svc); 81 | info!("HTTP server listening on http://{}", &self.http_address); 82 | 83 | Ok(server.await?) 84 | } 85 | } 86 | 87 | /// Route HTTP requests based on method/path. 88 | async fn http_service( 89 | req: Request, 90 | channel_error: HttpChannel, 91 | channel_timeout: HttpChannel, 92 | ) -> Result> { 93 | match (req.method(), req.uri().path()) { 94 | // Handle the `/metrics` endpoint. 95 | (&Method::GET, "/metrics") => get_metrics_response(), 96 | 97 | // Handle the `/errors` endpoint. 98 | (&Method::GET, "/errors") => get_channel_response(channel_error), 99 | 100 | // Handle the `/timeouts` endpoint. 101 | (&Method::GET, "/timeouts") => get_channel_response(channel_timeout), 102 | 103 | // Default 404 Not Found response. 104 | _ => { 105 | let mut not_found = Response::default(); 106 | *not_found.status_mut() = StatusCode::NOT_FOUND; 107 | Ok(not_found) 108 | } 109 | } 110 | } 111 | 112 | /// Handle requests for the Prometheus metrics endpoint. 113 | fn get_metrics_response() -> Result> { 114 | let encoder = TextEncoder::new(); 115 | 116 | let metric_families = prometheus::gather(); 117 | let mut buffer = vec![]; 118 | encoder.encode(&metric_families, &mut buffer).unwrap(); 119 | 120 | let response = Response::builder() 121 | .status(200) 122 | .header(CONTENT_TYPE, encoder.format_type()) 123 | .body(Body::from(buffer)) 124 | .unwrap(); 125 | 126 | Ok(response) 127 | } 128 | 129 | /// Handle requests for the endpoints that return a Frame Streams formatted log file of dnstap 130 | /// payloads from a channel. 131 | fn get_channel_response(channel: HttpChannel) -> Result> { 132 | Ok(Response::new(Body::wrap_stream(dnstap_receiver_to_stream( 133 | channel, 134 | )))) 135 | } 136 | 137 | /// Read dnstap payloads from the [`async_channel::Receiver`] embedded in an `HttpChannel`, 138 | /// serialize them using the unidirectional Frame Streams encoding, and yield them to a 139 | /// [`tokio_stream::Stream`]. Increments the success counter contained in the `HttpChannel`. 140 | fn dnstap_receiver_to_stream( 141 | channel: HttpChannel, 142 | ) -> impl tokio_stream::Stream> { 143 | let mut f = FrameStreamsCodec {}; 144 | 145 | stream! { 146 | // Write the Start frame with the dnstap content type to the beginning of the stream. 147 | let mut buf = BytesMut::with_capacity(64); 148 | f.encode( 149 | Frame::ControlStart(framestreams_codec::encode_content_type_payload( 150 | b"protobuf:dnstap.Dnstap", 151 | )), 152 | &mut buf, 153 | )?; 154 | yield Ok(buf); 155 | 156 | // Get each dnstap payload from the channel and write it to the stream. 157 | loop { 158 | match channel.receiver.try_recv() { 159 | Ok(d) => { 160 | // Accounting. 161 | channel.success_metric.inc(); 162 | 163 | // Get the length of the serialized protobuf. 164 | let len = d.encoded_len(); 165 | 166 | // Create a [`BytesMut`] of the exact size needed for this data frame. 167 | let mut buf = BytesMut::with_capacity(4 + len); 168 | 169 | // Write the length of the protobuf to the beginning of the data frame. 170 | buf.put_u32(len as u32); 171 | 172 | // Serialize the protobuf and write it to the data frame. 173 | d.encode(&mut buf).unwrap(); 174 | 175 | yield Ok(buf); 176 | } 177 | Err(_) => { 178 | // Write the Stop frame to the end of the stream. 179 | let mut buf = BytesMut::with_capacity(64); 180 | f.encode(Frame::ControlStop, &mut buf)?; 181 | yield Ok(buf); 182 | 183 | // No more frames. 184 | break; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 Fastly, Inc. 2 | 3 | use anyhow::Result; 4 | use async_channel::{bounded, Receiver, Sender}; 5 | use clap::{ArgAction, Parser, ValueHint}; 6 | use ip_network::IpNetwork; 7 | use log::*; 8 | use std::net::SocketAddr; 9 | use std::path::PathBuf; 10 | use std::sync::atomic::{AtomicBool, Ordering}; 11 | use std::sync::Arc; 12 | use tokio::net::UnixListener; 13 | 14 | /// Prometheus metrics. 15 | pub mod metrics; 16 | 17 | /// The dnstap payload processing handler. 18 | mod dnstap_handler; 19 | use dnstap_handler::*; 20 | 21 | /// The Frame Streams connection processing handler. 22 | mod frame_handler; 23 | use frame_handler::*; 24 | 25 | /// The HTTP server for stats and reporting. 26 | mod http_handler; 27 | use http_handler::*; 28 | 29 | /// The status file monitoring handler. 30 | mod monitor_handler; 31 | use monitor_handler::*; 32 | 33 | /// The generated protobuf definitions for the dnstap protocol. 34 | use dnstap_utils::dnstap; 35 | 36 | /// Server configuration and state. 37 | struct Server { 38 | /// Command-line options. 39 | opts: Opts, 40 | 41 | /// Channels. 42 | channels: Channels, 43 | } 44 | 45 | #[derive(Clone)] 46 | pub struct Channels { 47 | /// The send side of the async channel used by [`FrameHandler`]'s to send decoded dnstap 48 | /// protobuf messages to the [`DnstapHandler`]'s. 49 | sender: Sender, 50 | 51 | /// The receive side of the async channel used by [`DnstapHandler`]'s to receive decoded 52 | /// dnstap messages from the [`FrameHandler`]'s. 53 | receiver: Receiver, 54 | 55 | /// The send side of the async channel used by [`DnstapHandler`]'s to send error dnstap 56 | /// protobuf messages to the [`HttpHandler`]. 57 | error_sender: Sender, 58 | 59 | /// The receive side of the async channel used by [`DnstapHandler`]'s to send error dnstap 60 | /// protobuf messages to the [`HttpHandler`]. 61 | error_receiver: Receiver, 62 | 63 | /// The send side of the async channel used by [`DnstapHandler`]'s to send timeout dnstap 64 | /// protobuf messages to the [`HttpHandler`]. 65 | timeout_sender: Sender, 66 | 67 | /// The receive side of the async channel used by [`DnstapHandler`]'s to send timeout dnstap 68 | /// protobuf messages to the [`HttpHandler`]. 69 | timeout_receiver: Receiver, 70 | } 71 | 72 | /// Command-line arguments. 73 | #[derive(Parser, Clone)] 74 | pub struct Opts { 75 | /// Capacity of async channel for handler payload distribution 76 | #[clap(long, default_value = "10000")] 77 | channel_capacity: usize, 78 | 79 | /// Capacity of async channel for /errors endpoint buffer 80 | #[clap(long, default_value = "100000")] 81 | channel_error_capacity: usize, 82 | 83 | /// Capacity of async channel for /timeouts endpoint buffer 84 | #[clap(long, default_value = "100000")] 85 | channel_timeout_capacity: usize, 86 | 87 | /// UDP DNS server and port to send queries to 88 | #[clap(long, name = "DNS IP:PORT")] 89 | dns: SocketAddr, 90 | 91 | /// DSCP value to set on outgoing queries 92 | #[clap(long, 93 | name = "DSCP code point", 94 | value_parser = clap::value_parser!(u8).range(0..63))] 95 | dscp: Option, 96 | 97 | /// HTTP server socket to listen on for stats and reporting 98 | #[clap(long, name = "HTTP IP:PORT")] 99 | http: SocketAddr, 100 | 101 | /// Whether to ignore UDP responses with the TC bit set 102 | #[clap(long)] 103 | ignore_tc: bool, 104 | 105 | /// Ignore queries from this IPv4 or IPv6 network 106 | #[clap(long, value_parser = clap::value_parser!(IpNetwork))] 107 | ignore_query_net: Vec, 108 | 109 | /// Number of UDP client sockets to use to send queries to DNS server 110 | #[clap(long, default_value = "10")] 111 | num_sockets: usize, 112 | 113 | /// Whether to add PROXY v2 header to re-sent DNS queries 114 | #[clap(long)] 115 | proxy: bool, 116 | 117 | /// Whether to add timespec TLV to PROXY v2 header 118 | #[clap(long)] 119 | proxy_timespec: bool, 120 | 121 | /// Time to delay after status files match 122 | #[clap(long, name = "MILLISECONDS", default_value = "5000", required = false)] 123 | match_status_delay: u64, 124 | 125 | /// Match status files to compare 126 | #[clap(long = "match-status-files", 127 | name = "STATUS-FILE", 128 | required = false, 129 | num_args(2..), 130 | value_parser, 131 | value_hint = ValueHint::FilePath) 132 | ] 133 | status_files: Vec, 134 | 135 | /// Unix socket path to listen on 136 | #[clap(long, name = "PATH")] 137 | unix: String, 138 | 139 | /// Increase verbosity level 140 | #[clap(short, long, action = ArgAction::Count)] 141 | verbose: u8, 142 | } 143 | 144 | impl Server { 145 | /// Create a new [`Server`] and prepare its state. 146 | pub fn new(opts: &Opts) -> Self { 147 | // Create the channel for connecting [`FrameHandler`]'s and [`DnstapHandler`]'s. 148 | let (sender, receiver) = bounded(opts.channel_capacity); 149 | 150 | // Create the error channel for connecting [`DnstapHandler`]'s and the [`HttpHandler`]. 151 | let (error_sender, error_receiver) = bounded(opts.channel_error_capacity); 152 | 153 | // Create the timeout channel for connecting [`DnstapHandler`]'s and the [`HttpHandler`]. 154 | let (timeout_sender, timeout_receiver) = bounded(opts.channel_timeout_capacity); 155 | 156 | Server { 157 | opts: opts.clone(), 158 | channels: Channels { 159 | sender, 160 | receiver, 161 | error_sender, 162 | error_receiver, 163 | timeout_sender, 164 | timeout_receiver, 165 | }, 166 | } 167 | } 168 | 169 | /// Run the server. Binds a Unix socket listener to the filesystem and listens for incoming 170 | /// connections. Each incoming connection is handled by a newly spawned [`FrameHandler`]. 171 | /// 172 | /// Also starts up the [`HttpHandler`] and the number of [`DnstapHandler`]'s specified by the 173 | /// configuration. Each [`DnstapHandler`] creates its own UDP query socket for querying the 174 | /// configured DNS server. 175 | async fn run(&mut self) -> Result<()> { 176 | let match_status = Arc::new(AtomicBool::new(false)); 177 | 178 | // Start up the [`MonitorHandler']. 179 | if !self.opts.status_files.is_empty() { 180 | let match_status_mh = match_status.clone(); 181 | let mut monitor_handler = 182 | MonitorHandler::new(&self.opts.status_files, self.opts.match_status_delay)?; 183 | tokio::spawn(async move { 184 | if let Err(err) = monitor_handler.run(match_status_mh).await { 185 | error!("Monitor handler error: {}", err); 186 | } 187 | }); 188 | } else { 189 | match_status.store(true, Ordering::Relaxed); 190 | crate::metrics::MATCH_STATUS.set(1); 191 | } 192 | 193 | // Start up the [`HttpHandler`]. 194 | let http_handler = HttpHandler::new(self.opts.http, &self.channels); 195 | tokio::spawn(async move { 196 | if let Err(err) = http_handler.run().await { 197 | error!("Hyper HTTP server error: {}", err); 198 | } 199 | }); 200 | 201 | // Start up the [`DnstapHandler`]'s. 202 | for _ in 0..self.opts.num_sockets { 203 | let match_status_dh = match_status.clone(); 204 | 205 | // Create a new [`DnstapHandler`] and give it a cloned channel receiver. 206 | let mut dnstap_handler = 207 | DnstapHandler::new(&self.opts, &self.channels, match_status_dh).await?; 208 | 209 | // Spawn a new task to run the [`DnstapHandler`]. 210 | tokio::spawn(async move { 211 | if let Err(err) = dnstap_handler.run().await { 212 | error!("DnstapHandler error: {}", err); 213 | } 214 | }); 215 | } 216 | info!( 217 | "Sending DNS queries to server {} using {} UDP query sockets", 218 | &self.opts.dns, self.opts.num_sockets, 219 | ); 220 | if self.opts.proxy { 221 | info!("Sending DNS queries with PROXY v2 header"); 222 | } 223 | if let Some(dscp) = self.opts.dscp { 224 | info!("Sending DNS queries with DSCP value {}", dscp); 225 | } 226 | 227 | // Bind to the configured Unix socket. Remove the socket file if it exists. 228 | let _ = std::fs::remove_file(&self.opts.unix); 229 | let listener = UnixListener::bind(&self.opts.unix)?; 230 | info!("Listening on Unix socket path {}", &self.opts.unix); 231 | 232 | // Accept incoming connections on the FrameStreams socket. 233 | loop { 234 | match listener.accept().await { 235 | Ok((stream, _addr)) => { 236 | // Create a [`FrameHandler`] for this connection. 237 | let mut frame_handler = FrameHandler::new(stream, self.channels.sender.clone()); 238 | 239 | // Spawn a new task to run the [`FrameHandler`]. 240 | tokio::spawn(async move { 241 | if let Err(err) = frame_handler.run().await { 242 | warn!("FrameHandler error: {}", err); 243 | } 244 | }); 245 | } 246 | Err(err) => { 247 | warn!("Accept error: {}", err); 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | fn main() -> Result<()> { 255 | let opts = Opts::parse(); 256 | 257 | stderrlog::new() 258 | .verbosity(opts.verbose as usize) 259 | .module(module_path!()) 260 | .init() 261 | .unwrap(); 262 | 263 | metrics::initialize_metrics(); 264 | 265 | let mut server = Server::new(&opts); 266 | 267 | // Start the Tokio runtime. 268 | tokio::runtime::Builder::new_multi_thread() 269 | .worker_threads(2) 270 | .enable_all() 271 | .build() 272 | .unwrap() 273 | .block_on(async { server.run().await }) 274 | } 275 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/metrics.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2022 Fastly, Inc. 2 | 3 | use lazy_static::{initialize, lazy_static}; 4 | use prometheus::{ 5 | opts, register_int_counter, register_int_counter_vec, register_int_gauge, IntCounter, IntGauge, 6 | }; 7 | use prometheus_static_metric::{make_static_metric, register_static_int_counter_vec}; 8 | 9 | make_static_metric! { 10 | pub struct ChannelErrorRxVec: IntCounter { 11 | "result" => { 12 | success, 13 | }, 14 | } 15 | 16 | pub struct ChannelErrorTxVec: IntCounter { 17 | "result" => { 18 | success, 19 | error, 20 | }, 21 | } 22 | 23 | pub struct ChannelTimeoutRxVec: IntCounter { 24 | "result" => { 25 | success, 26 | }, 27 | } 28 | 29 | pub struct ChannelTimeoutTxVec: IntCounter { 30 | "result" => { 31 | success, 32 | error, 33 | }, 34 | } 35 | 36 | pub struct DnsComparisonsVec: IntCounter { 37 | "result" => { 38 | matched, 39 | mismatched, 40 | suppressed, 41 | udp_tc_ignored, 42 | query_net_ignored, 43 | }, 44 | } 45 | 46 | pub struct DnsQueriesVec: IntCounter { 47 | "result" => { 48 | success, 49 | error, 50 | timeout, 51 | }, 52 | } 53 | 54 | pub struct DnstapPayloadsVec: IntCounter { 55 | "result" => { 56 | success, 57 | error, 58 | }, 59 | } 60 | 61 | pub struct DnstapHandlerInternalErrorsVec: IntCounter { 62 | "result" => { 63 | discard_non_udp, 64 | }, 65 | } 66 | } 67 | 68 | lazy_static! { 69 | pub static ref CHANNEL_ERROR_RX: ChannelErrorRxVec = register_static_int_counter_vec!( 70 | ChannelErrorRxVec, 71 | "dnstap_replay_channel_error_rx_total", 72 | "Number of error channel receives performed.", 73 | &["result"] 74 | ) 75 | .unwrap(); 76 | pub static ref CHANNEL_ERROR_TX: ChannelErrorTxVec = register_static_int_counter_vec!( 77 | ChannelErrorTxVec, 78 | "dnstap_replay_channel_error_tx_total", 79 | "Number of error channel sends performed.", 80 | &["result"] 81 | ) 82 | .unwrap(); 83 | pub static ref CHANNEL_TIMEOUT_RX: ChannelTimeoutRxVec = register_static_int_counter_vec!( 84 | ChannelTimeoutRxVec, 85 | "dnstap_replay_channel_timeout_rx_total", 86 | "Number of timeout channel receives performed.", 87 | &["result"] 88 | ) 89 | .unwrap(); 90 | pub static ref CHANNEL_TIMEOUT_TX: ChannelTimeoutTxVec = register_static_int_counter_vec!( 91 | ChannelTimeoutTxVec, 92 | "dnstap_replay_channel_timeout_tx_total", 93 | "Number of timeout channel sends performed.", 94 | &["result"] 95 | ) 96 | .unwrap(); 97 | pub static ref DATA_BYTES: IntCounter = register_int_counter!(opts!( 98 | "dnstap_replay_data_bytes_total", 99 | "Number of Frame Streams data frame bytes processed." 100 | )) 101 | .unwrap(); 102 | pub static ref DATA_FRAMES: IntCounter = register_int_counter!(opts!( 103 | "dnstap_replay_data_frames_total", 104 | "Number of Frame Streams data frames processed." 105 | )) 106 | .unwrap(); 107 | pub static ref DNS_COMPARISONS: DnsComparisonsVec = register_static_int_counter_vec!( 108 | DnsComparisonsVec, 109 | "dnstap_replay_dns_comparisons_total", 110 | "Number of DNS comparison operations performed.", 111 | &["result"] 112 | ) 113 | .unwrap(); 114 | pub static ref DNS_QUERIES: DnsQueriesVec = register_static_int_counter_vec!( 115 | DnsQueriesVec, 116 | "dnstap_replay_dns_queries_total", 117 | "Number of DNS re-query operations performed.", 118 | &["result"] 119 | ) 120 | .unwrap(); 121 | pub static ref DNSTAP_PAYLOADS: DnstapPayloadsVec = register_static_int_counter_vec!( 122 | DnstapPayloadsVec, 123 | "dnstap_replay_dnstap_payloads_total", 124 | "Number of dnstap payloads processed.", 125 | &["result"] 126 | ) 127 | .unwrap(); 128 | pub static ref DNSTAP_HANDLER_INTERNAL_ERRORS: DnstapHandlerInternalErrorsVec = 129 | register_static_int_counter_vec!( 130 | DnstapHandlerInternalErrorsVec, 131 | "dnstap_replay_dnstap_handler_internal_errors_total", 132 | "Number of internal errors encountered by dnstap handler.", 133 | &["result"] 134 | ) 135 | .unwrap(); 136 | pub static ref MATCH_STATUS: IntGauge = register_int_gauge!(opts!( 137 | "dnstap_replay_match_status", 138 | "Whether match comparisons are enabled (1) or suppressed (0)." 139 | )) 140 | .unwrap(); 141 | } 142 | 143 | pub fn initialize_metrics() { 144 | initialize(&CHANNEL_ERROR_RX); 145 | initialize(&CHANNEL_ERROR_TX); 146 | initialize(&CHANNEL_TIMEOUT_RX); 147 | initialize(&CHANNEL_TIMEOUT_TX); 148 | initialize(&DATA_BYTES); 149 | initialize(&DATA_FRAMES); 150 | initialize(&DNS_COMPARISONS); 151 | initialize(&DNS_QUERIES); 152 | initialize(&DNSTAP_PAYLOADS); 153 | initialize(&DNSTAP_HANDLER_INTERNAL_ERRORS); 154 | } 155 | -------------------------------------------------------------------------------- /src/bin/dnstap-replay/monitor_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2022 Fastly, Inc. 2 | 3 | use anyhow::{bail, Context, Result}; 4 | use futures_util::StreamExt; 5 | use inotify::{Event, Inotify, WatchDescriptor, WatchMask}; 6 | use log::*; 7 | use std::ffi::OsString; 8 | use std::path::Path; 9 | use std::path::PathBuf; 10 | use std::sync::atomic::{AtomicBool, Ordering}; 11 | use std::sync::Arc; 12 | use tokio::time::{sleep, Duration}; 13 | 14 | /// Monitor status files for changes and detect when all status files are identical. 15 | pub struct MonitorHandler { 16 | inotify: Option, 17 | monitors: Vec, 18 | delay: u64, 19 | } 20 | 21 | impl MonitorHandler { 22 | /// Create a new [`MonitorHandler`] that watches the set of status files specified in 23 | /// `monitor_files`. 24 | pub fn new(monitor_files: &[PathBuf], delay: u64) -> Result { 25 | let inotify = Inotify::init().context("Failed to initialize inotify")?; 26 | 27 | let mut monitor = MonitorHandler { 28 | inotify: Some(inotify), 29 | monitors: vec![], 30 | delay, 31 | }; 32 | 33 | for path in monitor_files { 34 | monitor.add_watch(path)?; 35 | } 36 | 37 | Ok(monitor) 38 | } 39 | 40 | /// Perform status file monitoring. 41 | pub async fn run(&mut self, status: Arc) -> Result<()> { 42 | let mut buffer = [0; 1024]; 43 | let inotify = std::mem::take(&mut self.inotify); 44 | let mut stream = inotify.unwrap().into_event_stream(&mut buffer)?; 45 | 46 | // Perform an initial check of the current status files, if any. Since inotify is 47 | // event-driven, events won't be received for pre-existing status files. 48 | let res = self.check().await; 49 | 50 | // Update the match status flag and metric. 51 | status.store(res, Ordering::Relaxed); 52 | crate::metrics::MATCH_STATUS.set(res as i64); 53 | 54 | while let Some(event_or_error) = stream.next().await { 55 | match event_or_error { 56 | Ok(event) => { 57 | // Handle this inotify event. 58 | self.handle_event(event); 59 | 60 | // And then re-check the status files. 61 | let res = self.check().await; 62 | 63 | // Update the match status flag and metric. 64 | status.store(res, Ordering::Relaxed); 65 | crate::metrics::MATCH_STATUS.set(res as i64); 66 | } 67 | Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue, 68 | _ => { 69 | panic!("Error while reading inotify events"); 70 | } 71 | } 72 | } 73 | 74 | Ok(()) 75 | } 76 | 77 | /// Add a file path to the set of status files to monitor. 78 | fn add_watch>(&mut self, watch_path: P) -> Result<()> { 79 | info!("Monitoring '{}' for changes", watch_path.as_ref().display()); 80 | let mut monitor = FileMonitor::new(self.inotify.as_mut().unwrap(), watch_path)?; 81 | monitor.update(); 82 | self.monitors.push(monitor); 83 | Ok(()) 84 | } 85 | 86 | /// Check if all of the status files being monitored have identical contents. 87 | async fn check(&self) -> bool { 88 | let res = match self.monitors.first() { 89 | Some(first) => self.monitors.iter().all(|item| item == first), 90 | None => false, 91 | }; 92 | // Sleep for the configured match delay. This should be set to a large enough value to 93 | // allow pending dnstap payloads to be drained and suppressed. 94 | if res && self.delay > 0 { 95 | sleep(Duration::from_millis(self.delay)).await; 96 | } 97 | debug!("Monitor status: {}", res); 98 | res 99 | } 100 | 101 | /// Handle an inotify event. 102 | fn handle_event(&mut self, event: Event) { 103 | debug!("Handling inotify update: {:?}", &event); 104 | if let Some(name) = event.name { 105 | for m in &mut self.monitors { 106 | // Look up which of the monitors have an [`inotify::WatchDescriptor`] and base 107 | // filename that corresponds to the inotify event being processed. 108 | if m.wd == event.wd && name == m.base_name { 109 | // If a monitor was found, the file that it represents has been updated and its 110 | // contents should be reloaded. 111 | m.update(); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | /// Monitor a small file using inotify. Contains an [`inotify::WatchDescriptor`] which corresponds 119 | /// to the directory containing the file, and caches the file's contents. 120 | #[derive(Debug)] 121 | struct FileMonitor { 122 | // The final directory component of the status file being watched, e.g. "status.txt". 123 | pub base_name: PathBuf, 124 | 125 | // The full path to the file being watched, e.g. "/run/directory/status.txt". 126 | pub full_name: PathBuf, 127 | 128 | // The WatchDescriptor returned by inotify for the directory being watched. 129 | pub wd: WatchDescriptor, 130 | 131 | // The current contents of the status file being watched. 132 | pub contents: Option, 133 | } 134 | 135 | impl Eq for FileMonitor {} 136 | 137 | impl PartialEq for FileMonitor { 138 | fn eq(&self, other: &Self) -> bool { 139 | if self.contents.is_some() && other.contents.is_some() { 140 | self.contents == other.contents 141 | } else { 142 | false 143 | } 144 | } 145 | } 146 | 147 | impl FileMonitor { 148 | /// Create a new [`FileMonitor`] that watches a given filesystem path and add the directory 149 | /// that contains it to an [`Inotify`] to be watched. 150 | pub fn new>(inotify: &mut Inotify, watch_path: P) -> Result { 151 | // Figure out the canonical name of the directory that contains the filesystem path. 152 | let watch_dir = parent_directory_to_monitor_from_filename(&watch_path)?; 153 | 154 | // Extract the final directory component of `watch_path`. 155 | let base_name = match watch_path.as_ref().file_name() { 156 | Some(path) => PathBuf::from(path), 157 | None => bail!( 158 | "Unable to extract base filename from '{}'", 159 | watch_path.as_ref().display() 160 | ), 161 | }; 162 | 163 | // Construct the full, canonicalized path name to the file path being monitored. 164 | let full_name = watch_dir.join(&base_name); 165 | 166 | // Watch the directory using inotify. 167 | let wd = inotify.watches().add( 168 | &watch_dir, 169 | WatchMask::CLOSE_WRITE 170 | | WatchMask::DELETE 171 | | WatchMask::MOVED_TO 172 | | WatchMask::MOVED_FROM, 173 | )?; 174 | 175 | let mut fm = FileMonitor { 176 | base_name, 177 | full_name, 178 | wd, 179 | contents: None, 180 | }; 181 | 182 | // Perform an initial read of the file contents, if it already exists. 183 | fm.update(); 184 | 185 | Ok(fm) 186 | } 187 | 188 | /// Read and cache the contents of the file being monitored, if it exists. 189 | pub fn update(&mut self) { 190 | self.contents = std::fs::read_to_string(&self.full_name).ok(); 191 | } 192 | } 193 | 194 | fn parent_directory_to_monitor_from_filename>(path: P) -> Result { 195 | if path.as_ref().is_dir() { 196 | bail!( 197 | "Path '{}' is a directory, not a file", 198 | path.as_ref().display() 199 | ); 200 | } 201 | match path.as_ref().parent() { 202 | Some(p) => Ok(p.canonicalize()?), 203 | None => bail!(format!( 204 | "Unable to find parent of '{}'", 205 | path.as_ref().display() 206 | )), 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/bin/fmt-dns-message/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Fastly, Inc. 2 | 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use std::fmt::Debug; 6 | use std::io::Write; 7 | 8 | use dnstap_utils::util::fmt_dns_message; 9 | 10 | #[derive(Parser, Debug)] 11 | struct Opts { 12 | /// Hex-encoded DNS message data to decode 13 | hex_msg_bytes: String, 14 | } 15 | 16 | fn main() -> Result<()> { 17 | let mut opts = Opts::parse(); 18 | 19 | // Strip whitespace characters from the command-line input data. 20 | opts.hex_msg_bytes.retain(|c| !c.is_whitespace()); 21 | 22 | // Decode the hex-encoded input data into a binary, wire-format DNS message. 23 | let raw_msg_bytes = hex::decode(&opts.hex_msg_bytes)?; 24 | 25 | // Format the wire-format DNS message to a string. 26 | let mut fmt_buffer = String::with_capacity(2048); 27 | fmt_dns_message(&mut fmt_buffer, "", &raw_msg_bytes); 28 | 29 | // Write the formatted DNS message to stdout. 30 | std::io::stdout().write_all(fmt_buffer.as_bytes())?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/framestreams_codec.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Fastly, Inc. 2 | 3 | use bytes::{Buf, BufMut, BytesMut}; 4 | use tokio_util::codec::{Decoder, Encoder}; 5 | 6 | // Implementation defined limits. 7 | 8 | pub const CONTROL_FRAME_LENGTH_MAX: usize = 512; 9 | pub const FRAME_LENGTH_MAX: usize = 128 * 1024; 10 | 11 | // Protocol constants. 12 | 13 | pub const FRAMESTREAMS_CONTROL_ACCEPT: u32 = 0x01; 14 | pub const FRAMESTREAMS_CONTROL_START: u32 = 0x02; 15 | pub const FRAMESTREAMS_CONTROL_STOP: u32 = 0x03; 16 | pub const FRAMESTREAMS_CONTROL_READY: u32 = 0x04; 17 | pub const FRAMESTREAMS_CONTROL_FINISH: u32 = 0x05; 18 | 19 | pub const FRAMESTREAMS_CONTROL_FIELD_CONTENT_TYPE: u32 = 0x01; 20 | 21 | pub const FRAMESTREAMS_ESCAPE_SEQUENCE: u32 = 0x00; 22 | 23 | #[derive(Debug)] 24 | pub enum Frame { 25 | ControlReady(BytesMut), 26 | ControlAccept(BytesMut), 27 | ControlStart(BytesMut), 28 | ControlStop, 29 | ControlFinish, 30 | ControlUnknown(BytesMut), 31 | Data(BytesMut), 32 | } 33 | 34 | pub struct FrameStreamsCodec {} 35 | 36 | impl Encoder for FrameStreamsCodec { 37 | type Error = std::io::Error; 38 | 39 | fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> { 40 | match frame { 41 | Frame::ControlReady(payload) => { 42 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE); 43 | dst.put_u32(4); 44 | dst.put_u32(FRAMESTREAMS_CONTROL_READY); 45 | dst.put(payload); 46 | } 47 | Frame::ControlAccept(payload) => { 48 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE); 49 | dst.put_u32(4 + payload.len() as u32); 50 | dst.put_u32(FRAMESTREAMS_CONTROL_ACCEPT); 51 | dst.put(payload); 52 | } 53 | Frame::ControlStart(payload) => { 54 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE); 55 | dst.put_u32(4 + payload.len() as u32); 56 | dst.put_u32(FRAMESTREAMS_CONTROL_START); 57 | dst.put(payload); 58 | } 59 | Frame::ControlStop => { 60 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE); 61 | dst.put_u32(4); 62 | dst.put_u32(FRAMESTREAMS_CONTROL_STOP); 63 | } 64 | Frame::ControlFinish => { 65 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE); 66 | dst.put_u32(4); 67 | dst.put_u32(FRAMESTREAMS_CONTROL_FINISH); 68 | } 69 | Frame::ControlUnknown(_) => todo!(), 70 | Frame::Data(payload) => { 71 | dst.put_u32(payload.len() as u32); 72 | dst.put(payload); 73 | } 74 | } 75 | Ok(()) 76 | } 77 | } 78 | 79 | impl Decoder for FrameStreamsCodec { 80 | type Item = Frame; 81 | type Error = std::io::Error; 82 | 83 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 84 | // Check if there is enough data to read the frame length. 85 | if src.len() < 4 { 86 | return Ok(None); 87 | } 88 | 89 | // Read the frame length. 90 | let mut len_frame_bytes = [0u8; 4]; 91 | len_frame_bytes.copy_from_slice(&src[..4]); 92 | let len_frame = u32::from_be_bytes(len_frame_bytes) as usize; 93 | 94 | // Enforce the maximum frame size. 95 | if len_frame > FRAME_LENGTH_MAX { 96 | return Err(std::io::Error::new( 97 | std::io::ErrorKind::InvalidData, 98 | format!( 99 | "Frame of length {} is too large for this implementation", 100 | len_frame 101 | ), 102 | )); 103 | } 104 | 105 | // Check if this is a control frame. 106 | if len_frame == FRAMESTREAMS_ESCAPE_SEQUENCE as usize { 107 | // Check if there is enough data to read the control frame escape (4 bytes) + the 108 | // control frame length (4 bytes). 109 | if src.len() < 4 + 4 { 110 | return Ok(None); 111 | } 112 | 113 | // Read the control frame length. 114 | let mut len_control_bytes = [0u8; 4]; 115 | len_control_bytes.copy_from_slice(&src[4..8]); 116 | let len_control = u32::from_be_bytes(len_control_bytes) as usize; 117 | 118 | // Check that the control frame length is large enough. The minimum control frame 119 | // length is 4 bytes (to encode the control frame types that have no payload). 120 | if len_control < 4 { 121 | return Err(std::io::Error::new( 122 | std::io::ErrorKind::InvalidData, 123 | format!("Control frame of length {} is too small", len_control), 124 | )); 125 | } 126 | 127 | // Check that the control frame length is not too large. 128 | if len_control > CONTROL_FRAME_LENGTH_MAX { 129 | return Err(std::io::Error::new( 130 | std::io::ErrorKind::InvalidData, 131 | format!("Control frame of length {} is too large", len_control), 132 | )); 133 | } 134 | 135 | // Check if there is enough data to read the full control frame sequence of the control 136 | // frame escape (4 bytes) + the control frame length (4 bytes) + the actual length of 137 | // the control frame. 138 | if src.len() < 4 + 4 + len_control { 139 | return Ok(None); 140 | } 141 | 142 | // Detach the control frame escape (4 bytes) and the control frame length (4 bytes). 143 | src.advance(4 + 4); 144 | 145 | // Read the control frame type. This is the first 4 bytes of the control frame. 146 | let control_frame_type = src.get_u32(); 147 | 148 | // Detach the control frame payload. This is the remainder of the control frame after 149 | // the control frame type. 150 | let payload = src.split_to(len_control - 4); 151 | 152 | Ok(Some(match control_frame_type { 153 | FRAMESTREAMS_CONTROL_READY => Frame::ControlReady(payload), 154 | FRAMESTREAMS_CONTROL_ACCEPT => Frame::ControlAccept(payload), 155 | FRAMESTREAMS_CONTROL_START => Frame::ControlStart(payload), 156 | FRAMESTREAMS_CONTROL_STOP => Frame::ControlStop, 157 | FRAMESTREAMS_CONTROL_FINISH => Frame::ControlFinish, 158 | _ => Frame::ControlUnknown(payload), 159 | })) 160 | } else { 161 | // This is a data frame. 162 | 163 | // Check if there is enough data to read the frame length plus the data frame. 164 | if src.len() < 4 + len_frame { 165 | // Inform the Framed that more data is needed. 166 | return Ok(None); 167 | } 168 | 169 | // Detach the frame length. 170 | src.advance(4); 171 | 172 | // Detach the data frame. 173 | let data = src.split_to(len_frame); 174 | 175 | // Return the bytes of the frame. 176 | Ok(Some(Frame::Data(data))) 177 | } 178 | } 179 | } 180 | 181 | /// Helper function for encoding the "content type" payload used in the Frame Streams Ready, 182 | /// Accept, and Start control frames. 183 | pub fn encode_content_type_payload(content_type: &[u8]) -> BytesMut { 184 | let mut buf = BytesMut::with_capacity(4 + 4 + content_type.len()); 185 | buf.put_u32(FRAMESTREAMS_CONTROL_FIELD_CONTENT_TYPE); 186 | buf.put_u32(content_type.len() as u32); 187 | buf.put(content_type); 188 | buf 189 | } 190 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | pub mod dnstap { 4 | #![allow(clippy::module_inception)] 5 | #![allow(rustdoc::bare_urls)] 6 | include!(concat!(env!("OUT_DIR"), "/dnstap.rs")); 7 | } 8 | 9 | pub mod framestreams_codec; 10 | 11 | pub mod proxyv2; 12 | pub mod util; 13 | -------------------------------------------------------------------------------- /src/proxyv2.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | use anyhow::{bail, Result}; 4 | use bytes::{BufMut, BytesMut}; 5 | use log::*; 6 | use std::net::IpAddr; 7 | 8 | use crate::{dnstap, util::DnstapHandlerError}; 9 | 10 | #[derive(Debug)] 11 | pub struct Timespec { 12 | pub seconds: u64, 13 | pub nanoseconds: u32, 14 | } 15 | 16 | pub fn add_proxy_payload( 17 | buf: &mut BytesMut, 18 | msg: &dnstap::Message, 19 | query_address: &IpAddr, 20 | timespec: Option, 21 | ) -> Result<()> { 22 | // Codepoint in the range between `PP2_TYPE_MIN_CUSTOM` (0xE0) and `PP2_TYPE_MAX_CUSTOM` 23 | // (0xEF). This range is "reserved for application-specific data and will never be used by the 24 | // PROXY Protocol." 25 | const PP2_TYPE_CUSTOM_TIMESPEC: u8 = 0xEA; 26 | 27 | // u8 `type` (1 byte) 28 | // u8 `length_hi` (1 byte) 29 | // u8 `length_lo` (1 byte) 30 | // u64 `query_time_sec` (8 bytes) 31 | // u32 `query_time_nsec` (4 bytes) 32 | const PP2_CUSTOM_TIMESPEC_SIZE: u16 = 3 + 8 + 4; 33 | 34 | // Calculate the number of bytes following the address block needed for the following TLV(s). 35 | // This is zero if the timespec TLV is not going to be written into the PROXY v2 payload. 36 | let needed_tlv_size = if timespec.is_some() { 37 | PP2_CUSTOM_TIMESPEC_SIZE 38 | } else { 39 | 0 40 | }; 41 | 42 | // Extract the `query_port` field. 43 | let query_port = match &msg.query_port { 44 | Some(port) => *port as u16, 45 | None => bail!(DnstapHandlerError::MissingField), 46 | }; 47 | 48 | // Add the PROXY v2 signature. 49 | buf.put(&b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"[..]); 50 | 51 | // Add the PROXY version (2) and command (1), PROXY. 52 | buf.put_u8(0x21); 53 | 54 | // Add the PROXY v2 address block. 55 | match query_address { 56 | IpAddr::V4(addr) => { 57 | // UDP-over-IPv4: protocol constant 0x12. 58 | buf.put_u8(0x12); 59 | 60 | // Size of the UDP-over-IPv4 address block: 12 bytes. There are two IPv4 addresses 61 | // (4 bytes each) and two UDP port numbers (2 bytes each). 62 | // 63 | // Also add the size of the TLV(s) after the address block. 64 | buf.put_u16(12 + needed_tlv_size); 65 | 66 | // Original IPv4 source address. 67 | buf.put_slice(&addr.octets()); 68 | 69 | // Original IPv4 destination address. Use 0.0.0.0 since it doesn't matter and the 70 | // dnstap message payload may not have it. 71 | buf.put_u32(0); 72 | } 73 | IpAddr::V6(addr) => { 74 | // UDP-over-IPv6: protocol constant 0x22. 75 | buf.put_u8(0x22); 76 | 77 | // Size of the UDP-over-IPv6 address block: 36 bytes. There are two IPv6 addresses 78 | // (16 bytes each) and two UDP port numbers (2 bytes each). 79 | // 80 | // Also add the size of the TLV(s) after the address block. 81 | buf.put_u16(36 + needed_tlv_size); 82 | 83 | // Original IPv6 source address. 84 | buf.put_slice(&addr.octets()); 85 | 86 | // Original IPv6 destination address. Use :: since it doesn't matter and the dnstap 87 | // message payload may not have it. 88 | buf.put_u128(0); 89 | } 90 | }; 91 | 92 | // Original UDP source port. 93 | buf.put_u16(query_port); 94 | 95 | // Original UDP destination port. Use 53 since it doesn't matter and the dnstap message 96 | // payload may not have it. 97 | buf.put_u16(53); 98 | 99 | if let Some(timespec) = timespec { 100 | trace!("Sending PROXY v2 custom TLV: {timespec:?}"); 101 | 102 | // Timespec TLV: type field. 103 | buf.put_u8(PP2_TYPE_CUSTOM_TIMESPEC); 104 | 105 | // Timespec TLV: length field. This is split into a "high byte" and "low byte". The length is 106 | // of the following value (8 bytes u64 + 4 bytes u32). 107 | buf.put_u8(0); 108 | buf.put_u8(12); 109 | 110 | // Timespec TLV: value field. Intentionally encode using little endian byte order. 111 | buf.put_u64_le(timespec.seconds); 112 | buf.put_u32_le(timespec.nanoseconds); 113 | } 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Fastly, Inc. 2 | 3 | use anyhow::{bail, Result}; 4 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 5 | use domain::base::opt::AllOptData; 6 | use std::convert::TryFrom; 7 | use std::net::IpAddr; 8 | use std::time::Duration; 9 | use thiserror::Error; 10 | use time::OffsetDateTime; 11 | 12 | /// Utility function to convert a Unix epoch timestamp specified as a (u64, u32) tuple into a human 13 | /// readable timestamp. 14 | 15 | const TIME_FORMAT: &[time::format_description::FormatItem<'_>] = 16 | time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); 17 | 18 | pub fn unix_epoch_timestamp_to_string(sec: u64, nsec: Option) -> Result { 19 | let mut s = String::new(); 20 | 21 | let sec = i64::try_from(sec)?; 22 | let dt = OffsetDateTime::from_unix_timestamp(sec)?; 23 | s.push_str(&dt.format(&TIME_FORMAT)?); 24 | 25 | if let Some(nsec) = nsec { 26 | s.push_str(&format!(".{:09}", nsec)); 27 | } 28 | 29 | Ok(s) 30 | } 31 | 32 | /// Utility function that converts a slice of bytes into an [`IpAddr`]. Slices of length 4 are 33 | /// converted to IPv4 addresses and slices of length 16 are converted to IPv6 addresses. All other 34 | /// slice lengths are invalid. This is how IP addresses are encoded in dnstap protobuf messages. 35 | pub fn try_from_u8_slice_for_ipaddr(value: &[u8]) -> Result { 36 | match value.len() { 37 | 4 => Ok(IpAddr::from(<[u8; 4]>::try_from(value)?)), 38 | 16 => Ok(IpAddr::from(<[u8; 16]>::try_from(value)?)), 39 | _ => bail!( 40 | "Cannot decode an IP address from a {} byte field", 41 | value.len() 42 | ), 43 | } 44 | } 45 | 46 | #[derive(Debug, Error, PartialEq)] 47 | pub enum DnstapHandlerError { 48 | #[error("Mismatch between logged dnstap response and re-queried DNS response, expecting {1} but received {2}")] 49 | Mismatch(Bytes, String, String), 50 | 51 | #[error("Timeout sending DNS query")] 52 | Timeout, 53 | 54 | #[error("dnstap payload is missing a required field")] 55 | MissingField, 56 | } 57 | 58 | const DNSTAP_HANDLER_ERROR_PREFIX: &[u8] = b"dnstap-replay/DnstapHandlerError\x00"; 59 | 60 | impl DnstapHandlerError { 61 | pub fn serialize(&self) -> Bytes { 62 | match self { 63 | DnstapHandlerError::Mismatch(mismatch, _, _) => { 64 | let mut b = 65 | BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4 + mismatch.len()); 66 | b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX); 67 | b.put_u32(1); 68 | b.extend_from_slice(mismatch); 69 | b 70 | } 71 | DnstapHandlerError::Timeout => { 72 | let mut b = BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4); 73 | b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX); 74 | b.put_u32(2); 75 | b 76 | } 77 | DnstapHandlerError::MissingField => { 78 | let mut b = BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4); 79 | b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX); 80 | b.put_u32(3); 81 | b 82 | } 83 | } 84 | .freeze() 85 | } 86 | } 87 | 88 | pub fn deserialize_dnstap_handler_error(input: &[u8]) -> Result { 89 | if input.len() < DNSTAP_HANDLER_ERROR_PREFIX.len() { 90 | bail!("Input buffer is too small"); 91 | } 92 | 93 | let mut buf = Bytes::copy_from_slice(input); 94 | 95 | let prefix = buf.copy_to_bytes(DNSTAP_HANDLER_ERROR_PREFIX.len()); 96 | if prefix != DNSTAP_HANDLER_ERROR_PREFIX { 97 | bail!("DnstapHandlerError prefix not present"); 98 | } 99 | 100 | if buf.remaining() < 4 { 101 | bail!("DnstapHandlerError type not present"); 102 | } 103 | 104 | match buf.get_u32() { 105 | 1 => Ok(DnstapHandlerError::Mismatch( 106 | buf.split_off(0), 107 | String::from(""), 108 | String::from(""), 109 | )), 110 | 2 => Ok(DnstapHandlerError::Timeout), 111 | 3 => Ok(DnstapHandlerError::MissingField), 112 | _ => { 113 | bail!("Unknown DnstapHandlerError type"); 114 | } 115 | } 116 | } 117 | 118 | #[test] 119 | fn test_deserialize_dnstap_handler_error() { 120 | // Test the extreme boundary condition. This is a minimally sized serialized 121 | // DnstapHandlerError::Mismatch. 122 | assert_eq!( 123 | deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x01") 124 | .unwrap(), 125 | DnstapHandlerError::Mismatch(Bytes::from(&b""[..]), String::from(""), String::from("")) 126 | ); 127 | 128 | // This is the smallest serialized DnstapHandlerError::Mismatch with a single byte payload. 129 | assert_eq!( 130 | deserialize_dnstap_handler_error( 131 | b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x01\x42" 132 | ) 133 | .unwrap(), 134 | DnstapHandlerError::Mismatch( 135 | Bytes::from(&b"\x42"[..]), 136 | String::from(""), 137 | String::from("") 138 | ) 139 | ); 140 | 141 | // The only way to serialize a DnstapHandlerError::Timeout. 142 | assert_eq!( 143 | deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x02") 144 | .unwrap(), 145 | DnstapHandlerError::Timeout 146 | ); 147 | 148 | // The only way to serialize a DnstapHandlerError::MissingField. 149 | assert_eq!( 150 | deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x03") 151 | .unwrap(), 152 | DnstapHandlerError::MissingField 153 | ); 154 | } 155 | 156 | pub fn fmt_dns_message(s: &mut String, prefix: &str, raw_msg_bytes: &[u8]) { 157 | use domain::base::iana::rtype::Rtype; 158 | use domain::base::Message; 159 | use domain::rdata::AllRecordData; 160 | 161 | let msg = match Message::from_octets(raw_msg_bytes) { 162 | Ok(msg) => msg, 163 | Err(err) => { 164 | s.push_str(prefix); 165 | s.push_str(";; PARSE ERROR: "); 166 | s.push_str(&err.to_string()); 167 | s.push('\n'); 168 | return; 169 | } 170 | }; 171 | 172 | let hdr = msg.header(); 173 | 174 | // opcode 175 | s.push_str(prefix); 176 | s.push_str(";; ->>HEADER<<- opcode: "); 177 | s.push_str(&hdr.opcode().to_string()); 178 | 179 | // rcode 180 | s.push_str(", rcode: "); 181 | s.push_str(&hdr.rcode().to_string()); 182 | 183 | // id 184 | s.push_str(", id: "); 185 | s.push_str(&hdr.id().to_string()); 186 | s.push('\n'); 187 | 188 | // flags 189 | s.push_str(prefix); 190 | s.push_str(";; flags: "); 191 | if hdr.qr() { 192 | s.push_str("qr "); 193 | } 194 | if hdr.aa() { 195 | s.push_str("aa "); 196 | } 197 | if hdr.tc() { 198 | s.push_str("tc "); 199 | } 200 | if hdr.rd() { 201 | s.push_str("rd "); 202 | } 203 | if hdr.ra() { 204 | s.push_str("ra "); 205 | } 206 | if hdr.ad() { 207 | s.push_str("ad "); 208 | } 209 | if hdr.cd() { 210 | s.push_str("cd "); 211 | } 212 | 213 | // header counts 214 | let hdr_counts = msg.header_counts(); 215 | s.push_str("; QUERY: "); 216 | s.push_str(&hdr_counts.qdcount().to_string()); 217 | s.push_str(", ANSWER: "); 218 | s.push_str(&hdr_counts.ancount().to_string()); 219 | s.push_str(", AUTHORITY: "); 220 | s.push_str(&hdr_counts.nscount().to_string()); 221 | s.push_str(", ADDITIONAL: "); 222 | s.push_str(&hdr_counts.adcount().to_string()); 223 | s.push_str("\n\n"); 224 | 225 | if let Ok(sections) = msg.sections() { 226 | s.push_str(prefix); 227 | s.push_str(";; QUESTION SECTION:\n"); 228 | for question in sections.0.flatten() { 229 | s.push_str(prefix); 230 | s.push(';'); 231 | s.push_str(&question.qname().to_string()); 232 | s.push_str(". "); 233 | s.push_str(&question.qclass().to_string()); 234 | s.push(' '); 235 | s.push_str(&question.qtype().to_string()); 236 | s.push('\n') 237 | } 238 | s.push('\n'); 239 | 240 | s.push_str(prefix); 241 | s.push_str(";; ANSWER SECTION:\n"); 242 | for record in sections.1.limit_to::>().flatten() { 243 | s.push_str(prefix); 244 | s.push_str(&record.to_string()); 245 | s.push('\n') 246 | } 247 | s.push('\n'); 248 | 249 | s.push_str(prefix); 250 | s.push_str(";; AUTHORITY SECTION:\n"); 251 | for record in sections.2.limit_to::>().flatten() { 252 | s.push_str(prefix); 253 | s.push_str(&record.to_string()); 254 | s.push('\n') 255 | } 256 | s.push('\n'); 257 | 258 | s.push_str(prefix); 259 | s.push_str(";; ADDITIONAL SECTION:\n"); 260 | for record in sections.3.limit_to::>().flatten() { 261 | if record.rtype() == Rtype::Opt { 262 | continue; 263 | } 264 | s.push_str(prefix); 265 | s.push_str(&record.to_string()); 266 | s.push('\n') 267 | } 268 | 269 | if let Some(optrec) = msg.opt() { 270 | s.push('\n'); 271 | s.push_str(prefix); 272 | s.push_str(";; OPT PSEUDOSECTION:\n"); 273 | s.push_str(prefix); 274 | s.push_str("; EDNS: version "); 275 | s.push_str(&optrec.version().to_string()); 276 | s.push_str("; flags: "); 277 | if optrec.dnssec_ok() { 278 | s.push_str("do "); 279 | } 280 | s.push_str("; udp: "); 281 | s.push_str(&optrec.udp_payload_size().to_string()); 282 | s.push('\n'); 283 | 284 | for opt in optrec.opt().iter::>().flatten() { 285 | s.push_str(prefix); 286 | match opt { 287 | AllOptData::Nsid(nsid) => { 288 | s.push_str("; NSID: "); 289 | s.push_str(&nsid.to_string()); 290 | if let Ok(nsid_data) = hex::decode(&nsid.to_string()) { 291 | if let Ok(nsid_str) = std::str::from_utf8(&nsid_data) { 292 | s.push_str(" (\""); 293 | s.push_str(nsid_str); 294 | s.push_str("\")"); 295 | } 296 | } 297 | } 298 | AllOptData::Dau(data) => { 299 | s.push_str("; DAU:"); 300 | for alg in &data { 301 | s.push(' '); 302 | s.push_str(&alg.to_string()); 303 | } 304 | } 305 | AllOptData::Dhu(data) => { 306 | s.push_str("; DHU:"); 307 | for alg in &data { 308 | s.push(' '); 309 | s.push_str(&alg.to_string()); 310 | } 311 | } 312 | AllOptData::N3u(data) => { 313 | s.push_str("; N3U:"); 314 | for alg in &data { 315 | s.push(' '); 316 | s.push_str(&alg.to_string()); 317 | } 318 | } 319 | AllOptData::Expire(expire) => { 320 | s.push_str("; EXPIRE: "); 321 | if let Some(expire_data) = expire.expire() { 322 | s.push_str(&expire_data.to_string()); 323 | s.push_str(" seconds"); 324 | } 325 | } 326 | AllOptData::TcpKeepalive(data) => { 327 | s.push_str("; TCP-KEEPALIVE"); 328 | if let Some(timeout) = data.timeout() { 329 | s.push_str(&format!(": {:?}", Duration::from(timeout))); 330 | } 331 | } 332 | AllOptData::Padding(padding) => { 333 | s.push_str("; PADDING: ["); 334 | s.push_str(&padding.as_slice().len().to_string()); 335 | s.push_str(" bytes]"); 336 | } 337 | AllOptData::ClientSubnet(subnet) => { 338 | s.push_str("; CLIENT-SUBNET:\n"); 339 | s.push_str(prefix); 340 | s.push_str("; NETWORK ADDRESS: "); 341 | s.push_str(&subnet.addr().to_string()); 342 | s.push('\n'); 343 | s.push_str(prefix); 344 | s.push_str("; SOURCE PREFIX-LENGTH: "); 345 | s.push_str(&subnet.source_prefix_len().to_string()); 346 | s.push('\n'); 347 | s.push_str(prefix); 348 | s.push_str("; SCOPE PREFIX-LENGTH: "); 349 | s.push_str(&subnet.scope_prefix_len().to_string()); 350 | } 351 | AllOptData::Cookie(data) => { 352 | s.push_str("; COOKIE: "); 353 | s.push_str(&hex::encode(data.client())); 354 | } 355 | AllOptData::Chain(data) => { 356 | s.push_str("; CHAIN: "); 357 | s.push_str(&data.start().to_string()); 358 | } 359 | AllOptData::KeyTag(data) => { 360 | s.push_str("; KEY-TAG:"); 361 | for keytag in &data { 362 | s.push(' '); 363 | s.push_str(&keytag.to_string()); 364 | } 365 | } 366 | AllOptData::ExtendedError(data) => { 367 | s.push_str("; EXTENDED-DNS-ERROR:\n"); 368 | s.push_str(prefix); 369 | s.push_str("; INFO-CODE: ("); 370 | s.push_str(&data.code().to_int().to_string()); 371 | s.push_str(") "); 372 | s.push_str(&data.code().to_string()); 373 | s.push('\n'); 374 | if let Some(Ok(text_str)) = data.text() { 375 | s.push_str(prefix); 376 | s.push_str("; EXTRA-TEXT: \""); 377 | s.push_str(text_str); 378 | s.push('"'); 379 | } 380 | } 381 | AllOptData::Other(data) => { 382 | s.push_str("; OPT="); 383 | s.push_str(&data.code().to_int().to_string()); 384 | s.push(':'); 385 | s.push_str(&hex::encode(data.data()).to_uppercase()); 386 | } 387 | _ => { 388 | s.push_str("; Other unknown EDNS option, giving up."); 389 | } 390 | } 391 | s.push('\n'); 392 | } 393 | } 394 | } 395 | } 396 | 397 | pub fn dns_message_is_truncated(raw_msg_bytes: &[u8]) -> bool { 398 | // Only check a message if the complete header (12 octets) was received. 399 | if raw_msg_bytes.len() >= 12 { 400 | let msg = domain::base::Header::for_message_slice(raw_msg_bytes); 401 | return msg.tc(); 402 | } 403 | false 404 | } 405 | 406 | #[test] 407 | fn test_dns_message_is_truncated() { 408 | // Too few bytes to be a complete DNS header. 409 | assert!(!dns_message_is_truncated(&hex::decode("1234").unwrap())); 410 | 411 | // Real DNS header with the TC bit set. 412 | assert!(dns_message_is_truncated( 413 | &hex::decode("b84587000001000000000001").unwrap() 414 | )); 415 | 416 | // Real DNS header with the TC bit unset. 417 | assert!(!dns_message_is_truncated( 418 | &hex::decode("b84585000001000000010001").unwrap() 419 | )); 420 | } 421 | --------------------------------------------------------------------------------