From 54b4e297d9b87ac9b50af3045726f78ab3ab6439 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:20:38 -0700 Subject: [PATCH 01/46] Remove openssl-sys and naitive-tls --- Cargo.lock | 517 +++++++++----------------------- Cargo.toml | 6 +- build.rs | 12 +- dns-transport/Cargo.toml | 5 - dns-transport/src/error.rs | 18 -- dns-transport/src/https.rs | 11 +- dns-transport/src/tls.rs | 10 +- dns-transport/src/tls_stream.rs | 52 +--- dns/src/strings.rs | 17 +- dns/src/wire.rs | 28 +- src/colours.rs | 4 +- src/main.rs | 4 +- src/output.rs | 7 - 13 files changed, 196 insertions(+), 495 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5fe1bb..3dde6f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,19 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "addr2line" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +version = 4 [[package]] name = "ansi_term" @@ -24,6 +11,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + [[package]] name = "atty" version = "0.2.14" @@ -35,104 +28,66 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.2.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bumpalo" -version = "3.7.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.67" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ - "core-foundation-sys", - "libc", + "find-msvc-tools", + "shlex", ] [[package]] -name = "core-foundation-sys" -version = "0.8.2" +name = "cfg-if" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "datetime" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fcb4df22ae812fa2f6d5e3b577247584cc67fce06ad0779168d1dd41cbcce3" +checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ "libc", - "redox_syscall 0.1.57", + "redox_syscall", "winapi", ] [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "dns" @@ -150,11 +105,9 @@ dependencies = [ name = "dns-transport" version = "0.2.0-pre" dependencies = [ - "cfg-if", "dns", "httparse", "log", - "native-tls", "rustls", "webpki", "webpki-roots", @@ -178,82 +131,45 @@ dependencies = [ ] [[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" +name = "find-msvc-tools" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" [[package]] name = "getopts" -version = "0.2.21" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] -[[package]] -name = "gimli" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" - [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "httparse" -version = "1.3.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "ipconfig" @@ -269,16 +185,17 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.49" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -290,45 +207,38 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.91" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" -version = "0.4.14" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "memchr" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mutagen" version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" dependencies = [ "mutagen-core", "mutagen-transform", @@ -337,126 +247,56 @@ dependencies = [ [[package]] name = "mutagen-core" version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" dependencies = [ - "failure", + "anyhow", "json", "lazy_static", "proc-macro2", "quote", "serde", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] name = "mutagen-transform" version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" dependencies = [ "mutagen-core", "proc-macro2", ] -[[package]] -name = "native-tls" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "object" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" - [[package]] name = "once_cell" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" - -[[package]] -name = "openssl" -version = "0.10.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" - -[[package]] -name = "openssl-src" -version = "111.15.0+1.1.1k" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a5f6ae2ac04393b217ea9f700cd04fa9bf3d93fae2872069f3d15d908af70a" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.61" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" -dependencies = [ - "autocfg", - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] -[[package]] -name = "pkg-config" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" - [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty_assertions" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" dependencies = [ "ansi_term", "ctor", @@ -466,39 +306,38 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -506,46 +345,19 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -[[package]] -name = "redox_syscall" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" -dependencies = [ - "bitflags", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "ring" version = "0.16.20" @@ -561,12 +373,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "rustls" version = "0.19.1" @@ -582,19 +388,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "sct" @@ -606,60 +402,44 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" -version = "1.0.125" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "socket2" version = "0.3.19" @@ -679,39 +459,24 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "1.0.65" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] -name = "synstructure" -version = "0.12.4" +name = "syn" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if", - "libc", - "rand", - "redox_syscall 0.2.5", - "remove_dir_all", - "winapi", + "unicode-ident", ] [[package]] @@ -818,16 +583,16 @@ dependencies = [ ] [[package]] -name = "unicode-width" -version = "0.1.8" +name = "unicode-ident" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] -name = "unicode-xid" +name = "unicode-width" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "untrusted" @@ -835,48 +600,43 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "vcpkg" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.72" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.72" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" dependencies = [ "bumpalo", - "lazy_static", "log", "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.72" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -884,28 +644,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.72" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.72" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.49" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" +checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" dependencies = [ "js-sys", "wasm-bindgen", @@ -966,3 +729,23 @@ checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ "winapi", ] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] diff --git a/Cargo.toml b/Cargo.toml index 0e6a3d0..5b249d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,18 +63,16 @@ log = "0.4" ipconfig = { version = "0.2" } [build-dependencies] -datetime = { version = "0.5.1", default_features = false } +datetime = { version = "0.5.1", default-features = false } [dev-dependencies] pretty_assertions = "0.7" [features] -default = ["with_idna", "with_tls", "with_https", "with_nativetls"] +default = ["with_idna", "with_tls", "with_https", "with_rustls"] with_idna = ["dns/with_idna"] with_tls = ["dns-transport/with_tls"] with_https = ["dns-transport/with_https"] -with_nativetls = ["dns-transport/with_nativetls"] -with_nativetls_vendored = ["with_nativetls", "dns-transport/with_nativetls", "dns-transport/with_nativetls_vendored"] with_rustls = ["dns-transport/with_rustls"] diff --git a/build.rs b/build.rs index f941aeb..818eb43 100644 --- a/build.rs +++ b/build.rs @@ -44,21 +44,21 @@ fn main() -> io::Result<()> { let out = PathBuf::from(env::var("OUT_DIR").unwrap()); // Pretty version text - let mut f = File::create(&out.join("version.pretty.txt"))?; + let mut f = File::create(out.join("version.pretty.txt"))?; writeln!(f, "{}", convert_codes(&ver))?; // Bland version text - let mut f = File::create(&out.join("version.bland.txt"))?; + let mut f = File::create(out.join("version.bland.txt"))?; writeln!(f, "{}", strip_codes(&ver))?; // Pretty usage text - let mut f = File::create(&out.join("usage.pretty.txt"))?; + let mut f = File::create(out.join("usage.pretty.txt"))?; writeln!(f, "{}", convert_codes(&tagline))?; writeln!(f)?; write!(f, "{}", convert_codes(&usage))?; // Bland usage text - let mut f = File::create(&out.join("usage.bland.txt"))?; + let mut f = File::create(out.join("usage.bland.txt"))?; writeln!(f, "{}", strip_codes(&tagline))?; writeln!(f)?; write!(f, "{}", strip_codes(&usage))?; @@ -90,7 +90,7 @@ fn git_hash() -> String { String::from_utf8_lossy( &Command::new("git") - .args(&["rev-parse", "--short", "HEAD"]) + .args(["rev-parse", "--short", "HEAD"]) .output().unwrap() .stdout).trim().to_string() } @@ -127,7 +127,7 @@ fn version_string() -> String { /// Finds whether a feature is enabled by examining the Cargo variable. fn feature_enabled(name: &str) -> bool { - env::var(&format!("CARGO_FEATURE_{}", name)) + env::var(format!("CARGO_FEATURE_{}", name)) .map(|e| ! e.is_empty()) .unwrap_or(false) } diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml index 67552c3..a2a08df 100644 --- a/dns-transport/Cargo.toml +++ b/dns-transport/Cargo.toml @@ -18,7 +18,6 @@ dns = { path = "../dns" } log = "0.4" # tls networking -native-tls = { version = "0.2", optional = true } # http response parsing httparse = { version = "1.3", optional = true } @@ -29,14 +28,10 @@ webpki = { version = "0.21.0", optional = true } webpki-roots = { version = "0.21.0", optional = true } -cfg-if = "1" - [features] default = [] # these are enabled in the main dog crate with_tls = [] with_https = ["httparse"] -with_nativetls = ["native-tls"] -with_nativetls_vendored = ["native-tls", "native-tls/vendored"] with_rustls = ["rustls", "webpki-roots", "webpki"] diff --git a/dns-transport/src/error.rs b/dns-transport/src/error.rs index ae140d8..3aaaeaa 100644 --- a/dns-transport/src/error.rs +++ b/dns-transport/src/error.rs @@ -13,13 +13,7 @@ pub enum Error { /// call returned zero bytes. TruncatedResponse, - /// There was a problem making a TLS request. - #[cfg(feature = "with_nativetls")] - TlsError(native_tls::Error), - /// There was a problem _establishing_ a TLS request. - #[cfg(feature = "with_nativetls")] - TlsHandshakeError(native_tls::HandshakeError), /// Provided dns name is not valid #[cfg(feature = "with_rustls")] @@ -50,19 +44,7 @@ impl From for Error { } } -#[cfg(feature = "with_nativetls")] -impl From for Error { - fn from(inner: native_tls::Error) -> Self { - Self::TlsError(inner) - } -} -#[cfg(feature = "with_nativetls")] -impl From> for Error { - fn from(inner: native_tls::HandshakeError) -> Self { - Self::TlsHandshakeError(inner) - } -} #[cfg(feature = "with_rustls")] impl From for Error { diff --git a/dns-transport/src/https.rs b/dns-transport/src/https.rs index 6a3d324..e6d9069 100644 --- a/dns-transport/src/https.rs +++ b/dns-transport/src/https.rs @@ -1,14 +1,14 @@ -#![cfg_attr(not(feature = "https"), allow(unused))] +#![cfg_attr(not(feature = "with_https"), allow(unused))] use std::io::{Read, Write}; -use std::net::TcpStream; + use log::*; use dns::{Request, Response, WireError}; use super::{Transport, Error}; -use super::tls_stream; + /// The **HTTPS transport**, which sends DNS wire data inside HTTP packets /// encrypted with TLS, using TCP. @@ -33,7 +33,7 @@ fn contains_header(buf: &[u8]) -> bool { find_subsequence(buf, &header_end).is_some() } -use tls_stream::TlsStream; + impl Transport for HttpsTransport { @@ -42,7 +42,7 @@ impl Transport for HttpsTransport { let (domain, path) = self.split_domain().expect("Invalid HTTPS nameserver"); info!("Opening TLS socket to {:?}", domain); - let mut stream = Self::stream(&domain, 443)?; + let mut stream = super::tls_stream::stream_tls(&domain, 443)?; debug!("Connected"); @@ -124,4 +124,3 @@ impl HttpsTransport { /// The User-Agent header sent with HTTPS requests. static USER_AGENT: &str = concat!("dog/", env!("CARGO_PKG_VERSION")); - diff --git a/dns-transport/src/tls.rs b/dns-transport/src/tls.rs index 959dbc9..ee8ff67 100644 --- a/dns-transport/src/tls.rs +++ b/dns-transport/src/tls.rs @@ -1,13 +1,13 @@ -#![cfg_attr(not(feature = "tls"), allow(unused))] +#![cfg_attr(not(feature = "with_tls"), allow(unused))] + -use std::net::TcpStream; use std::io::Write; use log::*; use dns::{Request, Response}; use super::{Transport, Error, TcpTransport}; -use super::tls_stream::TlsStream; + /// The **TLS transport**, which sends DNS wire data using TCP through an @@ -40,10 +40,10 @@ impl Transport for TlsTransport { let domain = parts.nth(0).unwrap(); let port = parts.last().unwrap().parse::().expect("Invalid port number"); - Self::stream(domain, port)? + super::tls_stream::stream_tls(domain, port)? } else { - Self::stream(&*self.addr, 853)? + super::tls_stream::stream_tls(&*self.addr, 853)? }; diff --git a/dns-transport/src/tls_stream.rs b/dns-transport/src/tls_stream.rs index be6443a..a552bf4 100644 --- a/dns-transport/src/tls_stream.rs +++ b/dns-transport/src/tls_stream.rs @@ -1,17 +1,7 @@ use std::net::TcpStream; use super::Error; -use super::HttpsTransport; -use super::TlsTransport; -#[cfg(any(feature = "with_nativetls", feature = "with_nativetls_vendored"))] -fn stream_nativetls(domain: &str, port: u16) -> Result, Error> { - let connector = native_tls::TlsConnector::new()?; - let stream = TcpStream::connect((domain, port))?; - Ok(connector.connect(domain, stream)?) -} - -#[cfg(feature = "with_rustls")] -fn stream_rustls(domain: &str, port: u16) -> Result, Error> { +pub fn stream_tls(domain: &str, port: u16) -> Result, Error> { use std::sync::Arc; let mut config = rustls::ClientConfig::new(); @@ -27,43 +17,3 @@ fn stream_rustls(domain: &str, port: u16) -> Result { - fn stream(domain: &str, port: u16) -> Result; -} - -#[cfg(any(feature = "with_tls", feature = "with_https"))] -cfg_if::cfg_if! { - if #[cfg(any(feature = "with_nativetls", feature = "with_nativetls_vendored"))] { - - impl TlsStream> for HttpsTransport { - fn stream(domain: &str, port: u16) -> Result, Error> { - stream_nativetls(domain, port) - } - } - - impl TlsStream> for TlsTransport { - fn stream(domain: &str, port: u16) -> Result, Error> { - stream_nativetls(domain, port) - } - } - - } else if #[cfg(feature = "with_rustls")] { - - impl TlsStream> for HttpsTransport { - fn stream(domain: &str, port: u16) -> Result, Error> { - stream_rustls(domain, port) - } - } - - impl TlsStream> for TlsTransport { - fn stream(domain: &str, port: u16) -> Result, Error> { - stream_rustls(domain, port) - } - } - - } else { - unreachable!("tls/https enabled but no tls implementation provided") - } -} - diff --git a/dns/src/strings.rs b/dns/src/strings.rs index 46489e2..7fababd 100644 --- a/dns/src/strings.rs +++ b/dns/src/strings.rs @@ -51,7 +51,7 @@ impl Labels { let label_idn = label_to_ascii(label) .map_err(|e| { - warn!("Could not encode label {:?}: {:?}", label, e); + warn!("Could not encode label {label:?}: {e:?}"); label })?; @@ -60,7 +60,7 @@ impl Labels { segments.push((length, label_idn)); } Err(e) => { - warn!("Could not encode label {:?}: {}", label, e); + warn!("Could not encode label {label:?}: {e}"); return Err(label); } } @@ -75,6 +75,7 @@ impl Labels { } /// Returns a new set of labels concatenating two names. + #[must_use] pub fn extend(&self, other: &Self) -> Self { let mut segments = self.segments.clone(); segments.extend_from_slice(&other.segments); @@ -85,7 +86,7 @@ impl Labels { impl fmt::Display for Labels { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (_, segment) in &self.segments { - write!(f, "{}.", segment)?; + write!(f, "{segment}.")?; } Ok(()) @@ -164,24 +165,24 @@ fn read_string_recursive(labels: &mut Labels, c: &mut Cursor<&[u8]>, recursions: let offset = u16::from_be_bytes([name_one, name_two]); if recursions.contains(&offset) { - warn!("Hit previous offset ({}) decoding string", offset); + warn!("Hit previous offset ({offset}) decoding string"); return Err(WireError::TooMuchRecursion(recursions.clone().into_boxed_slice())); } recursions.push(offset); if recursions.len() >= RECURSION_LIMIT { - warn!("Hit recursion limit ({}) decoding string", RECURSION_LIMIT); + warn!("Hit recursion limit ({RECURSION_LIMIT}) decoding string"); return Err(WireError::TooMuchRecursion(recursions.clone().into_boxed_slice())); } - trace!("Backtracking to offset {}", offset); + trace!("Backtracking to offset {offset}"); let new_pos = c.position(); c.set_position(u64::from(offset)); read_string_recursive(labels, c, recursions)?; - trace!("Coming back to {}", new_pos); + trace!("Coming back to {new_pos}"); c.set_position(new_pos); break; } @@ -197,7 +198,7 @@ fn read_string_recursive(labels: &mut Labels, c: &mut Cursor<&[u8]>, recursions: name_buf.push(c); } - let string = String::from_utf8_lossy(&*name_buf).to_string(); + let string = String::from_utf8_lossy(&name_buf).to_string(); labels.segments.push((byte, string)); } } diff --git a/dns/src/wire.rs b/dns/src/wire.rs index 2559ab3..1158382 100644 --- a/dns/src/wire.rs +++ b/dns/src/wire.rs @@ -23,7 +23,7 @@ impl Request { bytes.write_u16::(1)?; // query count bytes.write_u16::(0)?; // answer count bytes.write_u16::(0)?; // authority RR count - bytes.write_u16::(if self.additional.is_some() { 1 } else { 0 })?; // additional RR count + bytes.write_u16::(u16::from(self.additional.is_some()))?; // additional RR count bytes.write_labels(&self.query.qname)?; bytes.write_u16::(self.query.qtype.type_number())?; @@ -57,14 +57,14 @@ impl Response { #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] pub fn from_bytes(bytes: &[u8]) -> Result { info!("Parsing response"); - trace!("Bytes -> {:?}", bytes); + trace!("Bytes -> {bytes:?}"); let mut c = Cursor::new(bytes); let transaction_id = c.read_u16::()?; - trace!("Read txid -> {:?}", transaction_id); + trace!("Read txid -> {transaction_id:?}"); let flags = Flags::from_u16(c.read_u16::()?); - trace!("Read flags -> {:#?}", flags); + trace!("Read flags -> {flags:#?}"); let query_count = c.read_u16::()?; let answer_count = c.read_u16::()?; @@ -78,28 +78,28 @@ impl Response { // arbitrarily large (9 seems about right). let mut queries = Vec::with_capacity(usize::from(query_count.min(9))); - debug!("Reading {}x query from response", query_count); + debug!("Reading {query_count}x query from response"); for _ in 0 .. query_count { let (qname, _) = c.read_labels()?; queries.push(Query::from_bytes(qname, &mut c)?); } let mut answers = Vec::with_capacity(usize::from(answer_count.min(9))); - debug!("Reading {}x answer from response", answer_count); + debug!("Reading {answer_count}x answer from response"); for _ in 0 .. answer_count { let (qname, _) = c.read_labels()?; answers.push(Answer::from_bytes(qname, &mut c)?); } let mut authorities = Vec::with_capacity(usize::from(authority_count.min(9))); - debug!("Reading {}x authority from response", authority_count); + debug!("Reading {authority_count}x authority from response"); for _ in 0 .. authority_count { let (qname, _) = c.read_labels()?; authorities.push(Answer::from_bytes(qname, &mut c)?); } let mut additionals = Vec::with_capacity(usize::from(additional_count.min(9))); - debug!("Reading {}x additional answer from response", additional_count); + debug!("Reading {additional_count}x additional answer from response"); for _ in 0 .. additional_count { let (qname, _) = c.read_labels()?; additionals.push(Answer::from_bytes(qname, &mut c)?); @@ -117,15 +117,15 @@ impl Query { #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result { let qtype_number = c.read_u16::()?; - trace!("Read qtype number -> {:?}", qtype_number ); + trace!("Read qtype number -> {qtype_number:?}" ); let qtype = RecordType::from(qtype_number); - trace!("Found qtype -> {:?}", qtype ); + trace!("Found qtype -> {qtype:?}" ); let qclass = QClass::from_u16(c.read_u16::()?); - trace!("Read qclass -> {:?}", qtype); + trace!("Read qclass -> {qclass:?}"); - Ok(Self { qtype, qclass, qname }) + Ok(Self { qname, qclass, qtype }) } } @@ -137,7 +137,7 @@ impl Answer { #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result { let qtype_number = c.read_u16::()?; - trace!("Read qtype number -> {:?}", qtype_number ); + trace!("Read qtype number -> {qtype_number:?}" ); if qtype_number == OPT::RR_TYPE { let opt = OPT::read(c)?; @@ -145,7 +145,7 @@ impl Answer { } else { let qtype = RecordType::from(qtype_number); - trace!("Found qtype -> {:?}", qtype ); + trace!("Found qtype -> {qtype:?}" ); let qclass = QClass::from_u16(c.read_u16::()?); trace!("Read qclass -> {:?}", qtype); diff --git a/src/colours.rs b/src/colours.rs index 9535722..05ffcc3 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -23,7 +23,7 @@ pub struct Colours { pub loc: Style, pub mx: Style, pub ns: Style, - pub naptr: Style, + pub openpgpkey: Style, pub opt: Style, pub ptr: Style, @@ -57,7 +57,7 @@ impl Colours { hinfo: Yellow.normal(), loc: Yellow.normal(), mx: Cyan.normal(), - naptr: Green.normal(), + ns: Red.normal(), openpgpkey: Cyan.normal(), opt: Purple.normal(), diff --git a/src/main.rs b/src/main.rs index 9d39727..988292b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,13 +181,13 @@ fn disabled_feature_check(options: &Options) { use std::process::exit; use crate::connect::TransportType; - #[cfg(all(not(feature = "with_tls"), not(feature = "with_rustls_tls")))] + #[cfg(all(not(feature = "with_tls"), not(feature = "with_rustls")))] if options.requests.inputs.transport_types.contains(&TransportType::TLS) { eprintln!("dog: Cannot use '--tls': This version of dog has been compiled without TLS support"); exit(exits::OPTIONS_ERROR); } - #[cfg(all(not(feature = "with_https"), not(feature = "with_rustls_https")))] + #[cfg(all(not(feature = "with_https"), not(feature = "with_rustls")))] if options.requests.inputs.transport_types.contains(&TransportType::HTTPS) { eprintln!("dog: Cannot use '--https': This version of dog has been compiled without HTTPS support"); exit(exits::OPTIONS_ERROR); diff --git a/src/output.rs b/src/output.rs index b776e53..eeec8dd 100644 --- a/src/output.rs +++ b/src/output.rs @@ -648,9 +648,6 @@ fn erroneous_phase(error: &TransportError) -> &'static str { TransportError::WireError(_) => "protocol", TransportError::TruncatedResponse | TransportError::NetworkError(_) => "network", - #[cfg(feature = "with_nativetls")] - TransportError::TlsError(_) | - TransportError::TlsHandshakeError(_) => "tls", #[cfg(feature = "with_rustls")] TransportError::RustlsInvalidDnsNameError(_) => "tls", // TODO: Actually wrong, could be https #[cfg(feature = "with_https")] @@ -665,10 +662,6 @@ fn error_message(error: TransportError) -> String { TransportError::WireError(e) => wire_error_message(e), TransportError::TruncatedResponse => "Truncated response".into(), TransportError::NetworkError(e) => e.to_string(), - #[cfg(feature = "with_nativetls")] - TransportError::TlsError(e) => e.to_string(), - #[cfg(feature = "with_nativetls")] - TransportError::TlsHandshakeError(e) => e.to_string(), #[cfg(any(feature = "with_rustls"))] TransportError::RustlsInvalidDnsNameError(e) => e.to_string(), #[cfg(feature = "with_https")] From 42bc35b0cfdc2999c082563f09cf10deddc3a113 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:46:02 -0700 Subject: [PATCH 02/46] Remove "with-tls" as feature and make it default --- Cargo.toml | 3 +-- README.md | 7 ++++--- build.rs | 4 +--- dns-transport/Cargo.toml | 1 - dns-transport/src/tls.rs | 8 ++------ src/main.rs | 2 +- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b249d1..1747a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,10 +69,9 @@ datetime = { version = "0.5.1", default-features = false } pretty_assertions = "0.7" [features] -default = ["with_idna", "with_tls", "with_https", "with_rustls"] +default = ["with_idna", "with_https", "with_rustls"] with_idna = ["dns/with_idna"] -with_tls = ["dns-transport/with_tls"] with_https = ["dns-transport/with_https"] with_rustls = ["dns-transport/with_rustls"] diff --git a/README.md b/README.md index 1721b71..edd6c4c 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,11 @@ While doing so makes dog less useful, it results in a smaller binary that takes There are three feature toggles available, all of which are active by default: - `with_idna`, which enables [IDNA](https://en.wikipedia.org/wiki/Internationalized_domain_name) processing -- `with_tls`, which enables DNS-over-TLS -- `with_https`, which enables DNS-over-HTTPS (requires `with_tls`) +- `with_https`, which enables DNS-over-HTTPS (requires built-in TLS support) -Use `cargo` to build a binary that uses feature toggles. For example, to disable TLS and HTTPS support but keep IDNA support enabled, you can run: +DNS-over-TLS support is now built-in and always available. + +Use `cargo` to build a binary that uses feature toggles. For example, to disable HTTPS support but keep IDNA support enabled, you can run: $ cargo build --no-default-features --features=with_idna diff --git a/build.rs b/build.rs index 818eb43..89eb6ac 100644 --- a/build.rs +++ b/build.rs @@ -140,9 +140,7 @@ fn nonstandard_features_string() -> String { s.push("-idna"); } - if ! feature_enabled("WITH_TLS") { - s.push("-tls"); - } + if ! feature_enabled("WITH_HTTPS") { s.push("-https"); diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml index a2a08df..b99897b 100644 --- a/dns-transport/Cargo.toml +++ b/dns-transport/Cargo.toml @@ -31,7 +31,6 @@ webpki-roots = { version = "0.21.0", optional = true } [features] default = [] # these are enabled in the main dog crate -with_tls = [] with_https = ["httparse"] with_rustls = ["rustls", "webpki-roots", "webpki"] diff --git a/dns-transport/src/tls.rs b/dns-transport/src/tls.rs index ee8ff67..1ccc524 100644 --- a/dns-transport/src/tls.rs +++ b/dns-transport/src/tls.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "with_tls"), allow(unused))] + use std::io::Write; @@ -28,7 +28,6 @@ impl TlsTransport { impl Transport for TlsTransport { - #[cfg(feature = "with_tls")] fn send(&self, request: &Request) -> Result { info!("Opening TLS socket"); @@ -63,10 +62,7 @@ impl Transport for TlsTransport { Ok(response) } - #[cfg(not(feature = "with_tls"))] - fn send(&self, request: &Request) -> Result { - unreachable!("TLS feature disabled") - } + } impl TlsTransport { diff --git a/src/main.rs b/src/main.rs index 988292b..01b4cef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,7 +181,7 @@ fn disabled_feature_check(options: &Options) { use std::process::exit; use crate::connect::TransportType; - #[cfg(all(not(feature = "with_tls"), not(feature = "with_rustls")))] + #[cfg(not(feature = "with_rustls"))] if options.requests.inputs.transport_types.contains(&TransportType::TLS) { eprintln!("dog: Cannot use '--tls': This version of dog has been compiled without TLS support"); exit(exits::OPTIONS_ERROR); From 8aa14e90e3438ad8db661b7a666a6b94494fdbb9 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:05:24 -0700 Subject: [PATCH 03/46] Update all crates to latest versions --- Cargo.lock | 637 +++++++++++++++++++++++++------- Cargo.toml | 8 +- dns-transport/Cargo.toml | 8 +- dns-transport/src/error.rs | 26 +- dns-transport/src/tls_stream.rs | 19 +- dns/Cargo.toml | 6 +- dns/src/record/openpgpkey.rs | 3 +- src/output.rs | 8 +- 8 files changed, 559 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3dde6f6..7290768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -28,17 +37,60 @@ dependencies = [ "winapi", ] +[[package]] +name = "aws-lc-rs" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" -version = "0.13.1" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] [[package]] -name = "bumpalo" -version = "3.19.0" +name = "bitflags" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "byteorder" @@ -53,9 +105,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -63,13 +126,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] -name = "ctor" -version = "0.1.26" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "quote", - "syn 1.0.109", + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", ] [[package]] @@ -130,12 +203,30 @@ dependencies = [ "rand", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "find-msvc-tools" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getopts" version = "0.2.24" @@ -153,9 +244,27 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.5+wasi-0.2.4", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -173,16 +282,25 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "ipconfig" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2", "widestring", - "winapi", + "windows-sys 0.48.0", "winreg", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -190,13 +308,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "js-sys" -version = "0.3.78" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "once_cell", - "wasm-bindgen", + "getrandom 0.3.3", + "libc", ] [[package]] @@ -217,6 +335,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + [[package]] name = "log" version = "0.4.28" @@ -235,6 +363,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mutagen" version = "0.2.0" @@ -269,19 +403,20 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] [[package]] -name = "output_vt100" -version = "0.1.3" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "ppv-lite86" @@ -294,14 +429,22 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "0.7.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "ansi_term", - "ctor", "diff", - "output_vt100", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", ] [[package]] @@ -322,22 +465,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -345,11 +493,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", ] [[package]] @@ -358,50 +506,97 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "ring" -version = "0.16.20" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", + "cfg-if", + "getrandom 0.2.16", "libc", - "once_cell", - "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustls" -version = "0.19.1" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ - "base64", + "aws-lc-rs", "log", - "ring", - "sct", - "webpki", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "ryu" -version = "1.0.20" +name = "rustls-pki-types" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] -name = "sct" -version = "0.6.1" +name = "rustls-webpki" +version = "0.103.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" dependencies = [ + "aws-lc-rs", "ring", + "rustls-pki-types", "untrusted", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "serde" version = "1.0.219" @@ -442,20 +637,19 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "socket2" -version = "0.3.19" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ - "cfg-if", "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "spin" -version = "0.5.2" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -596,9 +790,9 @@ checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "wasi" @@ -607,129 +801,302 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.101" +name = "wasi" +version = "0.14.5+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "wasip2", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.101" +name = "wasip2" +version = "1.0.0+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.101" +name = "webpki" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "ring", + "untrusted", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.101" +name = "webpki-roots" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "rustls-pki-types", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.101" +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "unicode-ident", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "web-sys" -version = "0.3.78" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "js-sys", - "wasm-bindgen", + "windows-targets 0.48.5", ] [[package]] -name = "webpki" -version = "0.21.4" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "ring", - "untrusted", + "windows-targets 0.52.6", ] [[package]] -name = "webpki-roots" -version = "0.21.1" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "webpki", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] -name = "widestring" -version = "0.4.3" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-targets" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winreg" -version = "0.6.2" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.8.27" @@ -749,3 +1116,9 @@ dependencies = [ "quote", "syn 2.0.106", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 1747a23..88bc513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ atty = "0.2" getopts = "0.2" # transaction ID generation -rand = "0.8" +rand = "0.9" # json output json = "0.12" @@ -60,13 +60,13 @@ log = "0.4" # windows default nameserver determination [target.'cfg(windows)'.dependencies] -ipconfig = { version = "0.2" } +ipconfig = { version = "0.3" } [build-dependencies] -datetime = { version = "0.5.1", default-features = false } +datetime = { version = "0.5.2", default-features = false } [dev-dependencies] -pretty_assertions = "0.7" +pretty_assertions = "1.4" [features] default = ["with_idna", "with_https", "with_rustls"] diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml index b99897b..f099788 100644 --- a/dns-transport/Cargo.toml +++ b/dns-transport/Cargo.toml @@ -20,13 +20,13 @@ log = "0.4" # tls networking # http response parsing -httparse = { version = "1.3", optional = true } +httparse = { version = "1.10", optional = true } -rustls = { version = "0.19", optional = true } +rustls = { version = "0.23", optional = true } -webpki = { version = "0.21.0", optional = true } +webpki = { version = "0.22.4", optional = true } -webpki-roots = { version = "0.21.0", optional = true } +webpki-roots = { version = "1.0.2", optional = true } [features] default = [] # these are enabled in the main dog crate diff --git a/dns-transport/src/error.rs b/dns-transport/src/error.rs index 3aaaeaa..6b99e01 100644 --- a/dns-transport/src/error.rs +++ b/dns-transport/src/error.rs @@ -13,11 +13,17 @@ pub enum Error { /// call returned zero bytes. TruncatedResponse, + /// An error from the TLS library. + #[cfg(feature = "with_rustls")] + RustlsError(rustls::Error), + /// Provided dns name is not valid + #[cfg(feature = "with_rustls")] + RustlsInvalidDnsNameError(webpki::InvalidDnsNameError), /// Provided dns name is not valid #[cfg(feature = "with_rustls")] - RustlsInvalidDnsNameError(webpki::InvalidDNSNameError), + RustlsInvalidDnsNameError2(rustls::pki_types::InvalidDnsNameError), /// There was a problem decoding the response HTTP headers or body. #[cfg(feature = "with_https")] @@ -44,15 +50,27 @@ impl From for Error { } } - +#[cfg(feature = "with_rustls")] +impl From for Error { + fn from(inner: rustls::Error) -> Self { + Self::RustlsError(inner) + } +} #[cfg(feature = "with_rustls")] -impl From for Error { - fn from(inner: webpki::InvalidDNSNameError) -> Self { +impl From for Error { + fn from(inner: webpki::InvalidDnsNameError) -> Self { Self::RustlsInvalidDnsNameError(inner) } } +#[cfg(feature = "with_rustls")] +impl From for Error { + fn from(inner: rustls::pki_types::InvalidDnsNameError) -> Self { + Self::RustlsInvalidDnsNameError2(inner) + } +} + #[cfg(feature = "with_https")] impl From for Error { fn from(inner: httparse::Error) -> Self { diff --git a/dns-transport/src/tls_stream.rs b/dns-transport/src/tls_stream.rs index a552bf4..72c0105 100644 --- a/dns-transport/src/tls_stream.rs +++ b/dns-transport/src/tls_stream.rs @@ -1,16 +1,21 @@ use std::net::TcpStream; +use std::sync::Arc; +use rustls::pki_types::ServerName; use super::Error; +use std::convert::TryFrom; -pub fn stream_tls(domain: &str, port: u16) -> Result, Error> { - use std::sync::Arc; +pub fn stream_tls(domain: &str, port: u16) -> Result, Error> { + let root_store = rustls::RootCertStore { + roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), + }; - let mut config = rustls::ClientConfig::new(); + let config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); - config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + let server_name = ServerName::try_from(domain)?.to_owned(); - let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain)?; - - let conn = rustls::ClientSession::new(&Arc::new(config), dns_name); + let conn = rustls::ClientConnection::new(Arc::new(config), server_name)?; let sock = TcpStream::connect((domain, port))?; let tls = rustls::StreamOwned::new(conn, sock); diff --git a/dns/Cargo.toml b/dns/Cargo.toml index 0dc7ba5..f43268d 100644 --- a/dns/Cargo.toml +++ b/dns/Cargo.toml @@ -14,10 +14,10 @@ doctest = false log = "0.4" # protocol parsing helper -byteorder = "1.3" +byteorder = "1.5" # printing of certain packets -base64 = "0.13" +base64 = "0.22" # idna encoding unic-idna = { version = "0.9.0", optional = true } @@ -26,7 +26,7 @@ unic-idna = { version = "0.9.0", optional = true } mutagen = { git = "https://github.com/llogiq/mutagen", optional = true } [dev-dependencies] -pretty_assertions = "0.7" +pretty_assertions = "1.4" [features] default = [] # idna is enabled in the main dog crate diff --git a/dns/src/record/openpgpkey.rs b/dns/src/record/openpgpkey.rs index 6fccb45..b5e5bb6 100644 --- a/dns/src/record/openpgpkey.rs +++ b/dns/src/record/openpgpkey.rs @@ -1,4 +1,5 @@ use log::*; +use base64::{engine::general_purpose, Engine as _}; use crate::wire::*; @@ -39,7 +40,7 @@ impl OPENPGPKEY { /// The base64-encoded PGP key. pub fn base64_key(&self) -> String { - base64::encode(&self.key) + general_purpose::STANDARD.encode(&self.key) } } diff --git a/src/output.rs b/src/output.rs index eeec8dd..7d8558c 100644 --- a/src/output.rs +++ b/src/output.rs @@ -649,7 +649,9 @@ fn erroneous_phase(error: &TransportError) -> &'static str { TransportError::TruncatedResponse | TransportError::NetworkError(_) => "network", #[cfg(feature = "with_rustls")] - TransportError::RustlsInvalidDnsNameError(_) => "tls", // TODO: Actually wrong, could be https + TransportError::RustlsInvalidDnsNameError(_) | + TransportError::RustlsError(_) | + TransportError::RustlsInvalidDnsNameError2(_) => "tls", // TODO: Actually wrong, could be https #[cfg(feature = "with_https")] TransportError::HttpError(_) | TransportError::WrongHttpStatus(_,_) => "http", @@ -664,6 +666,10 @@ fn error_message(error: TransportError) -> String { TransportError::NetworkError(e) => e.to_string(), #[cfg(any(feature = "with_rustls"))] TransportError::RustlsInvalidDnsNameError(e) => e.to_string(), + #[cfg(any(feature = "with_rustls"))] + TransportError::RustlsError(e) => e.to_string(), + #[cfg(any(feature = "with_rustls"))] + TransportError::RustlsInvalidDnsNameError2(e) => e.to_string(), #[cfg(feature = "with_https")] TransportError::HttpError(e) => e.to_string(), #[cfg(feature = "with_https")] From e8812e28996c64ad9fc2ae8f641f3f2a97c9506f Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:14:04 -0700 Subject: [PATCH 04/46] Update version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7290768..a4fe1df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,7 +188,7 @@ dependencies = [ [[package]] name = "dog" -version = "0.2.0-pre" +version = "0.3.0-pre" dependencies = [ "ansi_term", "atty", diff --git a/Cargo.toml b/Cargo.toml index 88bc513..32ee880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ exclude = [ ] homepage = "https://dns.lookup.dog/" license = "EUPL-1.2" -version = "0.2.0-pre" +version = "0.3.0-pre" [[bin]] From 678f09874153766f9a6a4af33c55218048a034d5 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:45:31 -0700 Subject: [PATCH 05/46] Remove the with_tls feature and make it default --- Cargo.toml | 4 +--- README.md | 4 ++-- dns-transport/Cargo.toml | 8 +++----- dns-transport/src/error.rs | 6 ------ src/main.rs | 22 ---------------------- src/output.rs | 4 ---- 6 files changed, 6 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32ee880..d999704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,9 +69,7 @@ datetime = { version = "0.5.2", default-features = false } pretty_assertions = "1.4" [features] -default = ["with_idna", "with_https", "with_rustls"] +default = ["with_idna", "with_https"] with_idna = ["dns/with_idna"] with_https = ["dns-transport/with_https"] - -with_rustls = ["dns-transport/with_rustls"] diff --git a/README.md b/README.md index edd6c4c..437120d 100644 --- a/README.md +++ b/README.md @@ -135,10 +135,10 @@ For more information, read [the xtests README](xtests/README.md). ### Feature toggles -dog has three Cargo features that can be switched off to remove functionality. +dog has two Cargo features that can be switched off to remove functionality. While doing so makes dog less useful, it results in a smaller binary that takes less time to build. -There are three feature toggles available, all of which are active by default: +There are two feature toggles available, both of which are active by default: - `with_idna`, which enables [IDNA](https://en.wikipedia.org/wiki/Internationalized_domain_name) processing - `with_https`, which enables DNS-over-HTTPS (requires built-in TLS support) diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml index f099788..b34b367 100644 --- a/dns-transport/Cargo.toml +++ b/dns-transport/Cargo.toml @@ -22,15 +22,13 @@ log = "0.4" # http response parsing httparse = { version = "1.10", optional = true } -rustls = { version = "0.23", optional = true } +rustls = "0.23" -webpki = { version = "0.22.4", optional = true } +webpki = "0.22.4" -webpki-roots = { version = "1.0.2", optional = true } +webpki-roots = "1.0.2" [features] default = [] # these are enabled in the main dog crate with_https = ["httparse"] - -with_rustls = ["rustls", "webpki-roots", "webpki"] diff --git a/dns-transport/src/error.rs b/dns-transport/src/error.rs index 6b99e01..54707a4 100644 --- a/dns-transport/src/error.rs +++ b/dns-transport/src/error.rs @@ -14,15 +14,12 @@ pub enum Error { TruncatedResponse, /// An error from the TLS library. - #[cfg(feature = "with_rustls")] RustlsError(rustls::Error), /// Provided dns name is not valid - #[cfg(feature = "with_rustls")] RustlsInvalidDnsNameError(webpki::InvalidDnsNameError), /// Provided dns name is not valid - #[cfg(feature = "with_rustls")] RustlsInvalidDnsNameError2(rustls::pki_types::InvalidDnsNameError), /// There was a problem decoding the response HTTP headers or body. @@ -50,21 +47,18 @@ impl From for Error { } } -#[cfg(feature = "with_rustls")] impl From for Error { fn from(inner: rustls::Error) -> Self { Self::RustlsError(inner) } } -#[cfg(feature = "with_rustls")] impl From for Error { fn from(inner: webpki::InvalidDnsNameError) -> Self { Self::RustlsInvalidDnsNameError(inner) } } -#[cfg(feature = "with_rustls")] impl From for Error { fn from(inner: rustls::pki_types::InvalidDnsNameError) -> Self { Self::RustlsInvalidDnsNameError2(inner) diff --git a/src/main.rs b/src/main.rs index 01b4cef..8c02717 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,6 @@ fn main() { match Options::getopts(env::args_os().skip(1)) { OptionsResult::Ok(options) => { info!("Running with options -> {:#?}", options); - disabled_feature_check(&options); exit(run(options)); } @@ -174,27 +173,6 @@ fn run(Options { requests, format, measure_time }: Options) -> i32 { } -/// Checks whether the options contain parameters that will cause dog to fail -/// because the feature is disabled by exiting if so. -#[allow(unused)] -fn disabled_feature_check(options: &Options) { - use std::process::exit; - use crate::connect::TransportType; - - #[cfg(not(feature = "with_rustls"))] - if options.requests.inputs.transport_types.contains(&TransportType::TLS) { - eprintln!("dog: Cannot use '--tls': This version of dog has been compiled without TLS support"); - exit(exits::OPTIONS_ERROR); - } - - #[cfg(all(not(feature = "with_https"), not(feature = "with_rustls")))] - if options.requests.inputs.transport_types.contains(&TransportType::HTTPS) { - eprintln!("dog: Cannot use '--https': This version of dog has been compiled without HTTPS support"); - exit(exits::OPTIONS_ERROR); - } -} - - /// The possible status numbers dog can exit with. mod exits { diff --git a/src/output.rs b/src/output.rs index 7d8558c..e54cdd7 100644 --- a/src/output.rs +++ b/src/output.rs @@ -648,7 +648,6 @@ fn erroneous_phase(error: &TransportError) -> &'static str { TransportError::WireError(_) => "protocol", TransportError::TruncatedResponse | TransportError::NetworkError(_) => "network", - #[cfg(feature = "with_rustls")] TransportError::RustlsInvalidDnsNameError(_) | TransportError::RustlsError(_) | TransportError::RustlsInvalidDnsNameError2(_) => "tls", // TODO: Actually wrong, could be https @@ -664,11 +663,8 @@ fn error_message(error: TransportError) -> String { TransportError::WireError(e) => wire_error_message(e), TransportError::TruncatedResponse => "Truncated response".into(), TransportError::NetworkError(e) => e.to_string(), - #[cfg(any(feature = "with_rustls"))] TransportError::RustlsInvalidDnsNameError(e) => e.to_string(), - #[cfg(any(feature = "with_rustls"))] TransportError::RustlsError(e) => e.to_string(), - #[cfg(any(feature = "with_rustls"))] TransportError::RustlsInvalidDnsNameError2(e) => e.to_string(), #[cfg(feature = "with_https")] TransportError::HttpError(e) => e.to_string(), From 73b0c3ffb376ab70e02df333d0432c520f248105 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:54:56 -0700 Subject: [PATCH 06/46] Fix version number in man page --- man/dog.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/dog.1.md b/man/dog.1.md index 34f5f89..aadad36 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -1,4 +1,4 @@ -% dog(1) v0.1.0 +% dog(1) v0.3.0 From 0e04426fbe53866609033f5614b2216dc4925a0c Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:48:29 -0700 Subject: [PATCH 07/46] Create rust.yml --- .github/workflows/rust.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..93425e8 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "rustls-only" ] + pull_request: + branches: [ "rustls-only" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From f1621c05a427cc039a424d5903d5dc8e8ff37bcf Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:25:25 -0700 Subject: [PATCH 08/46] Fix reverse lookups for IPv4 and IPv6 to no require .in-addr.arpa or .ip6.arpa and to parse correctly. --- src/options.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/options.rs b/src/options.rs index bea218c..0f7bd82 100644 --- a/src/options.rs +++ b/src/options.rs @@ -2,6 +2,7 @@ use std::ffi::OsStr; use std::fmt; +use std::net::IpAddr; use log::*; @@ -219,7 +220,15 @@ impl Inputs { } else { trace!("Got domain -> {:?}", &argument); - self.add_domain(&argument)?; + + if let Ok(ip) = argument.parse::() { + let reverse_domain = reverse_lookup_domain(ip); + self.add_domain(&reverse_domain)?; + self.add_type(RecordType::PTR); + } + else { + self.add_domain(&argument)?; + } } } @@ -304,6 +313,25 @@ fn parse_class_name(input: &str) -> Option { } } +fn reverse_lookup_domain(ip: IpAddr) -> String { + match ip { + IpAddr::V4(v4) => { + let octets = v4.octets(); + format!("{}.{}.{}.{}.in-addr.arpa", octets[3], octets[2], octets[1], octets[0]) + } + IpAddr::V6(v6) => { + let mut reversed = String::new(); + for octet in v6.octets().iter().rev() { + let nibble1 = octet & 0x0F; + let nibble2 = (octet >> 4) & 0x0F; + reversed.push_str(&format!("{:x}.{:x}.", nibble1, nibble2)); + } + reversed.push_str("ip6.arpa"); + reversed + } + } +} + impl TxidGenerator { fn deduce(matches: &getopts::Matches) -> Result { From c0bba89729601c35ede170cfe417b23fbe3ba5ea Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:15:47 -0700 Subject: [PATCH 09/46] feat: Add 'ANY' pseudo-record type Adds a pseudo record type "ANY" that queries the given domain for all known record types. This is implemented by adding an `all_record_types()` function that returns a vector of all `RecordType` variants. The command-line option parsing logic is updated to check for "ANY" and, if present, populate the record_types field with the list of all record types. The test suite has been updated to reflect this new functionality. --- dns/src/record/mod.rs | 25 +++++++++++++++++++++++++ src/options.rs | 12 +++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/dns/src/record/mod.rs b/dns/src/record/mod.rs index 908fb4d..e2278dc 100644 --- a/dns/src/record/mod.rs +++ b/dns/src/record/mod.rs @@ -172,6 +172,31 @@ impl From for RecordType { impl RecordType { + /// Returns a list of all supported record types. + pub fn all_record_types() -> Vec { + vec![ + RecordType::A, + RecordType::AAAA, + RecordType::CAA, + RecordType::CNAME, + RecordType::EUI48, + RecordType::EUI64, + RecordType::HINFO, + RecordType::LOC, + RecordType::MX, + RecordType::NAPTR, + RecordType::NS, + RecordType::OPENPGPKEY, + RecordType::PTR, + RecordType::SSHFP, + RecordType::SOA, + RecordType::SRV, + RecordType::TLSA, + RecordType::TXT, + RecordType::URI, + ] + } + /// Determines the record type with a given name, or `None` if none is /// known. Matches names case-insensitively. pub fn from_type_name(type_name: &str) -> Option { diff --git a/src/options.rs b/src/options.rs index 0f7bd82..d658be0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -165,6 +165,9 @@ impl Inputs { if record_name.eq_ignore_ascii_case("OPT") { return Err(OptionsError::QueryTypeOPT); } + else if record_name.eq_ignore_ascii_case("ANY") { + self.record_types.extend(RecordType::all_record_types()); + } else if let Some(record_type) = RecordType::from_type_name(&record_name) { self.add_type(record_type); } @@ -205,6 +208,10 @@ impl Inputs { if argument.eq_ignore_ascii_case("OPT") { return Err(OptionsError::QueryTypeOPT); } + else if argument.eq_ignore_ascii_case("ANY") { + trace!("Got qtype -> ANY"); + self.record_types.extend(RecordType::all_record_types()); + } else if let Some(class) = parse_class_name(&argument) { trace!("Got qclass -> {:?}", &argument); self.add_class(class); @@ -537,7 +544,6 @@ impl fmt::Display for OptionsError { mod test { use super::*; use pretty_assertions::assert_eq; - use dns::record::UnknownQtype; impl Inputs { fn fallbacks() -> Self { @@ -646,11 +652,11 @@ mod test { } #[test] - fn domain_and_other_type() { + fn domain_and_any_type() { let options = Options::getopts(&[ "lookup.dog", "any" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { domains: vec![ Labels::encode("lookup.dog").unwrap() ], - record_types: vec![ RecordType::Other(UnknownQtype::from_type_name("ANY").unwrap()) ], + record_types: RecordType::all_record_types(), .. Inputs::fallbacks() }); } From 42b6c50a2f4e63f393543e64536bf5da57f975c1 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:19:57 -0700 Subject: [PATCH 10/46] refactor: Remove specsheet and xtests Removes the `specsheet` dependency and the entire `xtests` directory. This simplifies the testing setup and removes a dependency that is not consistently available. --- Justfile | 42 +--- xtests/README.md | 39 ---- xtests/features/none.toml | 27 --- xtests/features/outputs/disabled_https.txt | 1 - xtests/features/outputs/disabled_tls.txt | 1 - xtests/live/badssl.toml | 68 ------- xtests/live/basics.toml | 63 ------ xtests/live/bins.toml | 38 ---- xtests/live/https.toml | 188 ------------------ xtests/live/json.toml | 188 ------------------ xtests/live/tcp.toml | 188 ------------------ xtests/live/tls.toml | 188 ------------------ xtests/live/udp.toml | 188 ------------------ xtests/madns/a-records.toml | 55 ----- xtests/madns/aaaa-records.toml | 55 ----- xtests/madns/caa-records.toml | 103 ---------- xtests/madns/cname-records.toml | 39 ---- xtests/madns/eui48-records.toml | 55 ----- xtests/madns/eui64-records.toml | 55 ----- xtests/madns/hinfo-records.toml | 70 ------- xtests/madns/loc-records.toml | 136 ------------- xtests/madns/mx-records.toml | 39 ---- xtests/madns/naptr-records.toml | 79 -------- xtests/madns/ns-records.toml | 39 ---- xtests/madns/openpgpkey-records.toml | 39 ---- xtests/madns/opt-records.toml | 79 -------- xtests/madns/outputs/a.example.ansitxt | 1 - xtests/madns/outputs/a.example.json | 26 --- xtests/madns/outputs/aaaa.example.ansitxt | 1 - xtests/madns/outputs/aaaa.example.json | 26 --- xtests/madns/outputs/ansi.str.example.ansitxt | 1 - xtests/madns/outputs/ansi.str.example.json | 26 --- .../outputs/bad-regex.naptr.example.ansitxt | 1 - .../outputs/bad-utf8.caa.example.ansitxt | 1 - .../madns/outputs/bad-utf8.caa.example.json | 28 --- .../outputs/bad-utf8.hinfo.example.ansitxt | 1 - .../madns/outputs/bad-utf8.hinfo.example.json | 27 --- .../outputs/bad-utf8.naptr.invalid.ansitxt | 1 - .../madns/outputs/bad-utf8.naptr.invalid.json | 30 --- .../outputs/bad-utf8.txt.example.ansitxt | 1 - .../madns/outputs/bad-utf8.txt.example.json | 28 --- .../outputs/bad-utf8.uri.example.ansitxt | 1 - .../madns/outputs/bad-utf8.uri.example.json | 28 --- xtests/madns/outputs/caa.example.ansitxt | 1 - xtests/madns/outputs/caa.example.json | 28 --- xtests/madns/outputs/cname.example.ansitxt | 1 - xtests/madns/outputs/cname.example.json | 26 --- .../outputs/critical.caa.example.ansitxt | 1 - .../madns/outputs/critical.caa.example.json | 28 --- .../madns/outputs/do-flag.opt.example.ansitxt | 2 - xtests/madns/outputs/do-flag.opt.example.json | 35 ---- xtests/madns/outputs/eui48.example.ansitxt | 1 - xtests/madns/outputs/eui48.example.json | 26 --- xtests/madns/outputs/eui64.example.ansitxt | 1 - xtests/madns/outputs/eui64.example.json | 26 --- .../far-negative-latitude.loc.invalid.ansitxt | 1 - .../far-negative-latitude.loc.invalid.json | 35 ---- ...far-negative-longitude.loc.invalid.ansitxt | 1 - .../far-negative-longitude.loc.invalid.json | 35 ---- .../far-positive-latitude.loc.invalid.ansitxt | 1 - .../far-positive-latitude.loc.invalid.json | 35 ---- ...far-positive-longitude.loc.invalid.ansitxt | 1 - .../far-positive-longitude.loc.invalid.json | 35 ---- xtests/madns/outputs/hinfo.example.ansitxt | 1 - xtests/madns/outputs/hinfo.example.json | 27 --- xtests/madns/outputs/loc.example.ansitxt | 1 - xtests/madns/outputs/loc.example.json | 35 ---- xtests/madns/outputs/mx.example.ansitxt | 1 - xtests/madns/outputs/mx.example.json | 27 --- .../madns/outputs/named.opt.invalid.ansitxt | 2 - xtests/madns/outputs/named.opt.invalid.json | 35 ---- xtests/madns/outputs/naptr.example.ansitxt | 1 - xtests/madns/outputs/naptr.example.json | 30 --- .../madns/outputs/newline.str.example.ansitxt | 1 - xtests/madns/outputs/newline.str.example.json | 26 --- xtests/madns/outputs/ns.example.ansitxt | 1 - xtests/madns/outputs/ns.example.json | 26 --- xtests/madns/outputs/null.str.example.ansitxt | 1 - xtests/madns/outputs/null.str.example.json | 26 --- .../madns/outputs/openpgpkey.example.ansitxt | 1 - xtests/madns/outputs/openpgpkey.example.json | 26 --- xtests/madns/outputs/opt.example.ansitxt | 2 - xtests/madns/outputs/opt.example.json | 35 ---- .../outputs/other-flags.opt.example.ansitxt | 2 - .../outputs/other-flags.opt.example.json | 35 ---- .../madns/outputs/others.caa.example.ansitxt | 1 - xtests/madns/outputs/others.caa.example.json | 28 --- xtests/madns/outputs/ptr.example.ansitxt | 1 - xtests/madns/outputs/ptr.example.json | 26 --- .../madns/outputs/slash.uri.example.ansitxt | 1 - xtests/madns/outputs/soa.example.ansitxt | 1 - xtests/madns/outputs/soa.example.json | 26 --- xtests/madns/outputs/srv.example.ansitxt | 1 - xtests/madns/outputs/srv.example.json | 29 --- xtests/madns/outputs/sshfp.example.ansitxt | 1 - xtests/madns/outputs/sshfp.example.json | 28 --- xtests/madns/outputs/tab.str.example.ansitxt | 1 - xtests/madns/outputs/tab.str.example.json | 26 --- xtests/madns/outputs/tlsa.example.ansitxt | 1 - xtests/madns/outputs/tlsa.example.json | 29 --- xtests/madns/outputs/txt.example.ansitxt | 1 - xtests/madns/outputs/txt.example.json | 28 --- .../outputs/upperbit.str.example.ansitxt | 1 - .../madns/outputs/upperbit.str.example.json | 26 --- xtests/madns/outputs/uri.example.ansitxt | 1 - xtests/madns/outputs/uri.example.json | 28 --- xtests/madns/outputs/utf8.caa.example.ansitxt | 1 - xtests/madns/outputs/utf8.caa.example.json | 28 --- .../madns/outputs/utf8.hinfo.example.ansitxt | 1 - xtests/madns/outputs/utf8.hinfo.example.json | 27 --- .../madns/outputs/utf8.naptr.invalid.ansitxt | 1 - xtests/madns/outputs/utf8.naptr.invalid.json | 30 --- xtests/madns/outputs/utf8.txt.example.ansitxt | 1 - xtests/madns/outputs/utf8.txt.example.json | 28 --- xtests/madns/outputs/utf8.uri.example.ansitxt | 1 - xtests/madns/outputs/utf8.uri.example.json | 28 --- xtests/madns/protocol-chars.toml | 84 -------- xtests/madns/protocol-compression.toml | 23 --- xtests/madns/protocol-error-codes.toml | 39 ---- xtests/madns/ptr-records.toml | 39 ---- xtests/madns/soa-records.toml | 39 ---- xtests/madns/srv-records.toml | 39 ---- xtests/madns/sshfp-records.toml | 39 ---- xtests/madns/tlsa-records.toml | 39 ---- xtests/madns/txt-records.toml | 72 ------- xtests/madns/uri-records.toml | 87 -------- xtests/options/errors.toml | 87 -------- xtests/options/help.toml | 76 ------- xtests/options/outputs/huge-domain.txt | 1 - xtests/options/outputs/invalid-argument.txt | 1 - .../outputs/invalid-protocol-tweak.txt | 1 - .../options/outputs/invalid-query-class.txt | 1 - xtests/options/outputs/invalid-query-type.txt | 1 - xtests/options/outputs/missing-nameserver.txt | 1 - xtests/options/outputs/missing-parameter.txt | 1 - xtests/options/outputs/opt-query.txt | 1 - 136 files changed, 3 insertions(+), 4051 deletions(-) delete mode 100644 xtests/README.md delete mode 100644 xtests/features/none.toml delete mode 100644 xtests/features/outputs/disabled_https.txt delete mode 100644 xtests/features/outputs/disabled_tls.txt delete mode 100644 xtests/live/badssl.toml delete mode 100644 xtests/live/basics.toml delete mode 100644 xtests/live/bins.toml delete mode 100644 xtests/live/https.toml delete mode 100644 xtests/live/json.toml delete mode 100644 xtests/live/tcp.toml delete mode 100644 xtests/live/tls.toml delete mode 100644 xtests/live/udp.toml delete mode 100644 xtests/madns/a-records.toml delete mode 100644 xtests/madns/aaaa-records.toml delete mode 100644 xtests/madns/caa-records.toml delete mode 100644 xtests/madns/cname-records.toml delete mode 100644 xtests/madns/eui48-records.toml delete mode 100644 xtests/madns/eui64-records.toml delete mode 100644 xtests/madns/hinfo-records.toml delete mode 100644 xtests/madns/loc-records.toml delete mode 100644 xtests/madns/mx-records.toml delete mode 100644 xtests/madns/naptr-records.toml delete mode 100644 xtests/madns/ns-records.toml delete mode 100644 xtests/madns/openpgpkey-records.toml delete mode 100644 xtests/madns/opt-records.toml delete mode 100644 xtests/madns/outputs/a.example.ansitxt delete mode 100644 xtests/madns/outputs/a.example.json delete mode 100644 xtests/madns/outputs/aaaa.example.ansitxt delete mode 100644 xtests/madns/outputs/aaaa.example.json delete mode 100644 xtests/madns/outputs/ansi.str.example.ansitxt delete mode 100644 xtests/madns/outputs/ansi.str.example.json delete mode 100644 xtests/madns/outputs/bad-regex.naptr.example.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.caa.example.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.caa.example.json delete mode 100644 xtests/madns/outputs/bad-utf8.hinfo.example.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.hinfo.example.json delete mode 100644 xtests/madns/outputs/bad-utf8.naptr.invalid.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.naptr.invalid.json delete mode 100644 xtests/madns/outputs/bad-utf8.txt.example.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.txt.example.json delete mode 100644 xtests/madns/outputs/bad-utf8.uri.example.ansitxt delete mode 100644 xtests/madns/outputs/bad-utf8.uri.example.json delete mode 100644 xtests/madns/outputs/caa.example.ansitxt delete mode 100644 xtests/madns/outputs/caa.example.json delete mode 100644 xtests/madns/outputs/cname.example.ansitxt delete mode 100644 xtests/madns/outputs/cname.example.json delete mode 100644 xtests/madns/outputs/critical.caa.example.ansitxt delete mode 100644 xtests/madns/outputs/critical.caa.example.json delete mode 100644 xtests/madns/outputs/do-flag.opt.example.ansitxt delete mode 100644 xtests/madns/outputs/do-flag.opt.example.json delete mode 100644 xtests/madns/outputs/eui48.example.ansitxt delete mode 100644 xtests/madns/outputs/eui48.example.json delete mode 100644 xtests/madns/outputs/eui64.example.ansitxt delete mode 100644 xtests/madns/outputs/eui64.example.json delete mode 100644 xtests/madns/outputs/far-negative-latitude.loc.invalid.ansitxt delete mode 100644 xtests/madns/outputs/far-negative-latitude.loc.invalid.json delete mode 100644 xtests/madns/outputs/far-negative-longitude.loc.invalid.ansitxt delete mode 100644 xtests/madns/outputs/far-negative-longitude.loc.invalid.json delete mode 100644 xtests/madns/outputs/far-positive-latitude.loc.invalid.ansitxt delete mode 100644 xtests/madns/outputs/far-positive-latitude.loc.invalid.json delete mode 100644 xtests/madns/outputs/far-positive-longitude.loc.invalid.ansitxt delete mode 100644 xtests/madns/outputs/far-positive-longitude.loc.invalid.json delete mode 100644 xtests/madns/outputs/hinfo.example.ansitxt delete mode 100644 xtests/madns/outputs/hinfo.example.json delete mode 100644 xtests/madns/outputs/loc.example.ansitxt delete mode 100644 xtests/madns/outputs/loc.example.json delete mode 100644 xtests/madns/outputs/mx.example.ansitxt delete mode 100644 xtests/madns/outputs/mx.example.json delete mode 100644 xtests/madns/outputs/named.opt.invalid.ansitxt delete mode 100644 xtests/madns/outputs/named.opt.invalid.json delete mode 100644 xtests/madns/outputs/naptr.example.ansitxt delete mode 100644 xtests/madns/outputs/naptr.example.json delete mode 100644 xtests/madns/outputs/newline.str.example.ansitxt delete mode 100644 xtests/madns/outputs/newline.str.example.json delete mode 100644 xtests/madns/outputs/ns.example.ansitxt delete mode 100644 xtests/madns/outputs/ns.example.json delete mode 100644 xtests/madns/outputs/null.str.example.ansitxt delete mode 100644 xtests/madns/outputs/null.str.example.json delete mode 100644 xtests/madns/outputs/openpgpkey.example.ansitxt delete mode 100644 xtests/madns/outputs/openpgpkey.example.json delete mode 100644 xtests/madns/outputs/opt.example.ansitxt delete mode 100644 xtests/madns/outputs/opt.example.json delete mode 100644 xtests/madns/outputs/other-flags.opt.example.ansitxt delete mode 100644 xtests/madns/outputs/other-flags.opt.example.json delete mode 100644 xtests/madns/outputs/others.caa.example.ansitxt delete mode 100644 xtests/madns/outputs/others.caa.example.json delete mode 100644 xtests/madns/outputs/ptr.example.ansitxt delete mode 100644 xtests/madns/outputs/ptr.example.json delete mode 100644 xtests/madns/outputs/slash.uri.example.ansitxt delete mode 100644 xtests/madns/outputs/soa.example.ansitxt delete mode 100644 xtests/madns/outputs/soa.example.json delete mode 100644 xtests/madns/outputs/srv.example.ansitxt delete mode 100644 xtests/madns/outputs/srv.example.json delete mode 100644 xtests/madns/outputs/sshfp.example.ansitxt delete mode 100644 xtests/madns/outputs/sshfp.example.json delete mode 100644 xtests/madns/outputs/tab.str.example.ansitxt delete mode 100644 xtests/madns/outputs/tab.str.example.json delete mode 100644 xtests/madns/outputs/tlsa.example.ansitxt delete mode 100644 xtests/madns/outputs/tlsa.example.json delete mode 100644 xtests/madns/outputs/txt.example.ansitxt delete mode 100644 xtests/madns/outputs/txt.example.json delete mode 100644 xtests/madns/outputs/upperbit.str.example.ansitxt delete mode 100644 xtests/madns/outputs/upperbit.str.example.json delete mode 100644 xtests/madns/outputs/uri.example.ansitxt delete mode 100644 xtests/madns/outputs/uri.example.json delete mode 100644 xtests/madns/outputs/utf8.caa.example.ansitxt delete mode 100644 xtests/madns/outputs/utf8.caa.example.json delete mode 100644 xtests/madns/outputs/utf8.hinfo.example.ansitxt delete mode 100644 xtests/madns/outputs/utf8.hinfo.example.json delete mode 100644 xtests/madns/outputs/utf8.naptr.invalid.ansitxt delete mode 100644 xtests/madns/outputs/utf8.naptr.invalid.json delete mode 100644 xtests/madns/outputs/utf8.txt.example.ansitxt delete mode 100644 xtests/madns/outputs/utf8.txt.example.json delete mode 100644 xtests/madns/outputs/utf8.uri.example.ansitxt delete mode 100644 xtests/madns/outputs/utf8.uri.example.json delete mode 100644 xtests/madns/protocol-chars.toml delete mode 100644 xtests/madns/protocol-compression.toml delete mode 100644 xtests/madns/protocol-error-codes.toml delete mode 100644 xtests/madns/ptr-records.toml delete mode 100644 xtests/madns/soa-records.toml delete mode 100644 xtests/madns/srv-records.toml delete mode 100644 xtests/madns/sshfp-records.toml delete mode 100644 xtests/madns/tlsa-records.toml delete mode 100644 xtests/madns/txt-records.toml delete mode 100644 xtests/madns/uri-records.toml delete mode 100644 xtests/options/errors.toml delete mode 100644 xtests/options/help.toml delete mode 100644 xtests/options/outputs/huge-domain.txt delete mode 100644 xtests/options/outputs/invalid-argument.txt delete mode 100644 xtests/options/outputs/invalid-protocol-tweak.txt delete mode 100644 xtests/options/outputs/invalid-query-class.txt delete mode 100644 xtests/options/outputs/invalid-query-type.txt delete mode 100644 xtests/options/outputs/missing-nameserver.txt delete mode 100644 xtests/options/outputs/missing-parameter.txt delete mode 100644 xtests/options/outputs/opt-query.txt diff --git a/Justfile b/Justfile index beca6b8..f105ab4 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,6 @@ -all: build test xtests -all-release: build-release test-release xtests-release -all-quick: build-quick test-quick xtests-quick +all: build test +all-release: build-release test-release +all-quick: build-quick test-quick export DOG_DEBUG := "" @@ -54,36 +54,6 @@ export DOG_DEBUG := "" cargo +nightly mutagen --package dns --features=dns/with_mutagen -#------------------------# -# running extended tests # -#------------------------# - -# run extended tests -@xtests *args: - specsheet xtests/{options,live,madns}/*.toml -shide {{args}} \ - -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog" - -# run extended tests (in release mode) -@xtests-release *args: - specsheet xtests/{options,live,madns}/*.toml {{args}} \ - -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/release/dog" - -# run extended tests (omitting certain feature tests) -@xtests-quick *args: - specsheet xtests/options/*.toml xtests/live/{basics,tcp}.toml -shide {{args}} \ - -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog" - -# run extended tests against a local madns instance -@xtests-madns-local *args: - env MADNS_ARGS="@localhost:5301 --tcp" \ - specsheet xtests/madns/*.toml -shide {{args}} \ - -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog" - -# display the number of extended tests that get run -@count-xtests: - grep -F '[[cmd]]' -R xtests | wc -l - - #---------# # fuzzing # #---------# @@ -126,12 +96,6 @@ export DOG_DEBUG := "" command -v cargo-udeps >/dev/null || (echo "cargo-udeps not installed" && exit 1) cargo +nightly udeps -# builds dog and runs extended tests with features disabled -@feature-checks *args: - cargo build --no-default-features - specsheet xtests/features/none.toml -shide {{args}} \ - -O cmd.target.dog="${CARGO_TARGET_DIR:-../../target}/debug/dog" - # print versions of the necessary build tools @versions: rustc --version diff --git a/xtests/README.md b/xtests/README.md deleted file mode 100644 index 68616bb..0000000 --- a/xtests/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# dog › xtests - -This is dog’s extended test suite. The checks herein form a complete end-to-end set of tests, covering things like network connections, DNS protocol parsing, command-line options, error handling, and edge case behaviour. - -The checks are written as [Specsheet] documents, which you’ll need to have installed. For the JSON tests, you’ll also need [jq]. - -Because these tests make connections over the network, the outcome of the test suite will depend on your own machine‘s Internet connection! It also means that your own IP address will be recorded as making the requests. - - -### Test layout - -The tests have been divided into four sections: - -1. **live**, which uses both your computer’s default resolver and the [public Cloudflare DNS resolver] to access records that have been created using a public-facing DNS host. This checks that dog works using whatever software is between you and those nameservers on the Internet right now. Because these are _live_ records, the output will vary as things like the TTL vary, so we cannot assert on the _exact_ output; nevertheless, it’s a good check to see if the basic functionality is working. - -2. **madns**, which sends requests to the [madns resolver]. This resolver has been pre-programmed with deliberately incorrect responses to see how dog handles edge cases in the DNS specification. These are not live records, so things like the TTLs of the responses are fixed, meaning the output should never change over time; however, it does not mean dog will hold up against the network infrastructure of the real world. - -3. **options**, which runs dog using various command-line options and checks that the correct output is returned. These tests should not make network requests when behaving correctly. - -4. **features**, which checks dog does the right thing when certain features have been enabled or disabled at compile-time. These tests also should not make network requests when behaving correctly. - -All four categories of check are needed to ensure dog is working correctly. - - -### Tags - -To run a subset of the checks, you can filter with the following tags: - -- `cloudflare`: Tests that use the [public Cloudflare DNS resolver]. -- `isp`: Tests that use your computer’s default resolver. -- `madns`: Tests that use the [madns resolver]. -- `options`: Tests that check the command-line options. - -You can also use a DNS record type as a tag to only run the checks for that particular type. - -[Specsheet]: https://specsheet.software/ -[jq]: https://stedolan.github.io/jq/ -[public Cloudflare DNS resolver]: https://developers.cloudflare.com/1.1.1.1/ -[madns resolver]: https://madns.binarystar.systems/ diff --git a/xtests/features/none.toml b/xtests/features/none.toml deleted file mode 100644 index af7a134..0000000 --- a/xtests/features/none.toml +++ /dev/null @@ -1,27 +0,0 @@ -# These tests are meant to be run against a dog binary compiled with -# `--no-default-features`. They will fail otherwise. - - -[[cmd]] -name = "The missing features are documented in the version" -shell = "dog --version" -stdout = { string = "[-idna, -tls, -https]" } -stderr = { empty = true } -status = 0 -tags = [ 'features' ] - -[[cmd]] -name = "The ‘--tls’ option is not accepted when the feature is disabled" -shell = "dog --tls a.b.c.d" -stdout = { empty = true } -stderr = { file = "outputs/disabled_tls.txt" } -status = 3 -tags = [ 'features' ] - -[[cmd]] -name = "The ‘--https option is not accepted when the feature is disabled" -shell = "dog --https a.b.c.d @name.server" -stdout = { empty = true } -stderr = { file = "outputs/disabled_https.txt" } -status = 3 -tags = [ 'features' ] diff --git a/xtests/features/outputs/disabled_https.txt b/xtests/features/outputs/disabled_https.txt deleted file mode 100644 index 34be090..0000000 --- a/xtests/features/outputs/disabled_https.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Cannot use '--https': This version of dog has been compiled without HTTPS support diff --git a/xtests/features/outputs/disabled_tls.txt b/xtests/features/outputs/disabled_tls.txt deleted file mode 100644 index 221dec6..0000000 --- a/xtests/features/outputs/disabled_tls.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Cannot use '--tls': This version of dog has been compiled without TLS support diff --git a/xtests/live/badssl.toml b/xtests/live/badssl.toml deleted file mode 100644 index 6787589..0000000 --- a/xtests/live/badssl.toml +++ /dev/null @@ -1,68 +0,0 @@ -# Untrusted certificates - -[[cmd]] -name = "Using a DNS-over-HTTPS server with an expired certificate" -shell = "dog --https @https://expired.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server with the wrong host in the certificate" -shell = "dog --https @https://wrong.host.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server with a self-signed certificate" -shell = "dog --https @https://self-signed.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server with an untrusted root certificate" -shell = "dog --https @https://untrusted-root.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server with a revoked certificate" -shell = "dog --https @https://revoked.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server with a known bad certificate" -shell = "dog --https @https://superfish.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: The certificate was not trusted." } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - - -# Handshake failures - -[[cmd]] -name = "Using a DNS-over-HTTPS server that accepts the null cipher" -shell = "dog --https @https://null.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: handshake failure" } -status = 1 -tags = [ 'live', 'badssl', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server that accepts the rc4-md5 cipher" -shell = "dog --https @https://rc4-md5.badssl.com/ lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [tls]: handshake failure" } -status = 1 -tags = [ 'live', 'badssl', 'https' ] diff --git a/xtests/live/basics.toml b/xtests/live/basics.toml deleted file mode 100644 index 221db42..0000000 --- a/xtests/live/basics.toml +++ /dev/null @@ -1,63 +0,0 @@ -# Colour output - -[[cmd]] -name = "Running dog with ‘--colour=always’ produces colourful output" -shell = "dog dns.google --colour=always" -stdout = { string = "\u001B[1;32mA\u001B[0m \u001B[1;34mdns.google.\u001B[0m" } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - -[[cmd]] -name = "Running dog produces an A record by default" -shell = "dog dns.google" -stdout = { string = "A dns.google." } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - -[[cmd]] -name = "Running dog with ‘--colour=never’ produces plain output" -shell = "dog dns.google --colour=never" -stdout = { string = "A dns.google." } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - - -# Default record type and transport - -[[cmd]] -name = "Running dog with ‘-U’ produces no errors" -shell = "dog dns.google -U" -stdout = { string = "A dns.google." } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - -[[cmd]] -name = "Running dog with ‘A’ produces no errors" -shell = "dog A dns.google" -stdout = { string = "A dns.google." } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - -[[cmd]] -name = "Running dog with ‘--time’ outputs a duration" -shell = "dog A dns.google --time" -stdout = { string = "Ran in" } -stderr = { empty = true } -status = 0 -tags = [ "live", "isp" ] - - -# Network errors - -[[cmd]] -name = "Using a DNS server that does not exist on the network" -shell = "dog A dns.google @non.exist --time" -stdout = { string = "Ran in" } -stderr = { string = "Error [network]" } -status = 1 -tags = [ "live", "isp" ] diff --git a/xtests/live/bins.toml b/xtests/live/bins.toml deleted file mode 100644 index 0e54063..0000000 --- a/xtests/live/bins.toml +++ /dev/null @@ -1,38 +0,0 @@ -# HTTPS - -[[cmd]] -name = "Using a DNS-over-HTTPS server that returns status 500" -shell = "dog --https @https://eu.httpbin.org/status/500 lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [http]: Nameserver returned HTTP 500 (INTERNAL SERVER ERROR)" } -status = 1 -tags = [ 'live', 'httpbin', 'https' ] - -[[cmd]] -name = "Using a DNS-over-HTTPS server that returns no content" -shell = "dog --https @https://eu.httpbin.org/status/200 lookup.dog" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ 'live', 'httpbin', 'https' ] - - -# TCP - -# [[cmd]] -# name = "Using a TCP server that returns an empty message" -# shell = "dog --tcp @52.20.16.20:30000 lookup.dog" -# stdout = { empty = true } -# stderr = { string = "Error [network]: Truncated response" } -# status = 1 -# tags = [ 'live', 'tcpbin', 'tcp' ] - -# The above test is flaky. It works correctly the first time, but produces a -# different error message on subsequent runs. -# -# The ‘other’ tcpbin can be used to test the truncated response error -# handling, but it requires waiting 60 seconds for their server to give up and -# send us a FIN: -# -# - dog --tcp bsago.me @tcpbin.com:4242 -# - dog --tls bsago.me @tcpbin.com:4243 diff --git a/xtests/live/https.toml b/xtests/live/https.toml deleted file mode 100644 index 0ad146d..0000000 --- a/xtests/live/https.toml +++ /dev/null @@ -1,188 +0,0 @@ -# A records - -[[cmd]] -name = "Look up an existing A record using HTTPS" -shell = "dog a-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '10.20.30.40' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "a" ] - -[[cmd]] -name = "Look up a missing A record using HTTPS" -shell = "dog non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "a" ] - - -# AAAA records - -[[cmd]] -name = "Look up an existing AAAA record using HTTPS" -shell = "dog AAAA aaaa-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '::1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "aaaa" ] - -[[cmd]] -name = "Look up a missing AAAA record using HTTPS" -shell = "dog AAAA non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "aaaa" ] - - -# CAA records - -[[cmd]] -name = "Look up an existing CAA record using HTTPS" -shell = "dog CAA caa-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '"issue" "some.certificate.authority" (non-critical)' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "caa" ] - -[[cmd]] -name = "Look up a missing CAA record using HTTPS" -shell = "dog CAA non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "caa" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing CNAME record using HTTPS" -shell = "dog CNAME cname-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '"dns.lookup.dog."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "cname" ] - -[[cmd]] -name = "Look up a missing CNAME record using HTTPS" -shell = "dog CNAME non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "cname" ] - - -# HINFO records - -[[cmd]] -name = "Look up an existing HINFO record using HTTPS" -shell = "dog HINFO hinfo-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '"some-kinda-os"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "hinfo" ] - -[[cmd]] -name = "Look up a missing HINFO record using HTTPS" -shell = "dog HINFO non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "hinfo" ] - - -# MX records - -[[cmd]] -name = "Look up an existing MX record using HTTPS" -shell = "dog MX mx-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '10 "some.mail.server."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "mx" ] - -[[cmd]] -name = "Look up a missing MX record using HTTPS" -shell = "dog MX non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "mx" ] - - -# NS records - -[[cmd]] -name = "Look up an existing NS record using HTTPS" -shell = "dog NS lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "ns" ] - -[[cmd]] -name = "Look up a missing NS record using HTTPS" -shell = "dog NS non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "ns" ] - - -# SOA records - -[[cmd]] -name = "Look up an existing SOA record using HTTPS" -shell = "dog SOA lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "soa" ] - -[[cmd]] -name = "Look up a missing SOA record using HTTPS" -shell = "dog MX non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "soa" ] - - -# SRV records - -[[cmd]] -name = "Look up an existing SRV record using HTTPS" -shell = "dog SRV srv-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '20 "dns.lookup.dog.":5000' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "srv" ] - -[[cmd]] -name = "Look up a missing SRV record using HTTPS" -shell = "dog SRV non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "srv" ] - - -# TXT records - -[[cmd]] -name = "Look up an existing TXT record using HTTPS" -shell = "dog TXT txt-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https" -stdout = { string = '"Cache Invalidation and Naming Things"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "https", "txt" ] - -[[cmd]] -name = "Look up a missing TXT record using HTTPS" -shell = "dog TXT non.existent @https://cloudflare-dns.com/dns-query --short --https" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "https", "txt" ] diff --git a/xtests/live/json.toml b/xtests/live/json.toml deleted file mode 100644 index d039a03..0000000 --- a/xtests/live/json.toml +++ /dev/null @@ -1,188 +0,0 @@ -# A records - -[[cmd]] -name = "Look up an existing A record formatted as JSON" -shell = "dog a-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '10.20.30.40' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "a" ] - -[[cmd]] -name = "Look up a missing A record formatted as JSON" -shell = "dog non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "a" ] - - -# AAAA records - -[[cmd]] -name = "Look up an existing AAAA record formatted as JSON" -shell = "dog AAAA aaaa-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '::1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "aaaa" ] - -[[cmd]] -name = "Look up a missing AAAA record formatted as JSON" -shell = "dog AAAA non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "aaaa" ] - - -# CAA records - -[[cmd]] -name = "Look up an existing CAA record formatted as JSON" -shell = "dog CAA caa-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '"some.certificate.authority"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "caa" ] - -[[cmd]] -name = "Look up a missing CAA record formatted as JSON" -shell = "dog CAA non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "caa" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing CNAME record formatted as JSON" -shell = "dog CNAME cname-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '"dns.lookup.dog."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "cname" ] - -[[cmd]] -name = "Look up a missing CNAME record formatted as JSON" -shell = "dog CNAME non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "cname" ] - - -# HINFO records - -[[cmd]] -name = "Look up an existing HINFO record formatted as JSON" -shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '"some-kinda-os"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "hinfo" ] - -[[cmd]] -name = "Look up a missing HINFO record formatted as JSON" -shell = "dog HINFO non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "hinfo" ] - - -# MX records - -[[cmd]] -name = "Look up an existing MX record formatted as JSON" -shell = "dog MX mx-example.lookup.dog @1.1.1.1 --json" -stdout = { string = 'some.mail.server.' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "mx" ] - -[[cmd]] -name = "Look up a missing MX record formatted as JSON" -shell = "dog MX non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "mx" ] - - -# NS records - -[[cmd]] -name = "Look up an existing NS record formatted as JSON" -shell = "dog NS lookup.dog @1.1.1.1 --json" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "ns" ] - -[[cmd]] -name = "Look up a missing NS record formatted as JSON" -shell = "dog NS non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "ns" ] - - -# SOA records - -[[cmd]] -name = "Look up an existing SOA record formatted as JSON" -shell = "dog SOA lookup.dog @1.1.1.1 --json" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "soa" ] - -[[cmd]] -name = "Look up a missing SOA record formatted as JSON" -shell = "dog MX non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "soa" ] - - -# SRV records - -[[cmd]] -name = "Look up an existing SRV record formatted as JSON" -shell = "dog SRV srv-example.lookup.dog @1.1.1.1 --json" -stdout = { string = 'dns.lookup.dog.' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "srv" ] - -[[cmd]] -name = "Look up a missing SRV record formatted as JSON" -shell = "dog SRV non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "srv" ] - - -# TXT records - -[[cmd]] -name = "Look up an existing TXT record formatted as JSON" -shell = "dog TXT txt-example.lookup.dog @1.1.1.1 --json" -stdout = { string = '"Cache Invalidation and Naming Things"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "txt" ] - -[[cmd]] -name = "Look up a missing TXT record formatted as JSON" -shell = "dog TXT non.existent @1.1.1.1 --json" -stdout = { string = '[]' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "json", "txt" ] diff --git a/xtests/live/tcp.toml b/xtests/live/tcp.toml deleted file mode 100644 index 769bbc9..0000000 --- a/xtests/live/tcp.toml +++ /dev/null @@ -1,188 +0,0 @@ -# A records - -[[cmd]] -name = "Look up an existing A record using TCP" -shell = "dog a-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '10.20.30.40' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "a" ] - -[[cmd]] -name = "Look up a missing A record using TCP" -shell = "dog non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "a" ] - - -# AAAA records - -[[cmd]] -name = "Look up an existing AAAA record using TCP" -shell = "dog AAAA aaaa-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '::1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "aaaa" ] - -[[cmd]] -name = "Look up a missing AAAA record using TCP" -shell = "dog AAAA non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "aaaa" ] - - -# CAA records - -[[cmd]] -name = "Look up an existing CAA record using TCP" -shell = "dog CAA caa-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '"issue" "some.certificate.authority" (non-critical)' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "caa" ] - -[[cmd]] -name = "Look up a missing CAA record using TCP" -shell = "dog CAA non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "caa" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing CNAME record using TCP" -shell = "dog CNAME cname-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '"dns.lookup.dog."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "cname" ] - -[[cmd]] -name = "Look up a missing CNAME record using TCP" -shell = "dog CNAME non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "cname" ] - - -# HINFO records - -[[cmd]] -name = "Look up an existing HINFO record using TCP" -shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '"some-kinda-cpu"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "hinfo" ] - -[[cmd]] -name = "Look up a missing HINFO record using TCP" -shell = "dog HINFO non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "hinfo" ] - - -# MX records - -[[cmd]] -name = "Look up an existing MX record using TCP" -shell = "dog MX mx-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '10 "some.mail.server."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "mx" ] - -[[cmd]] -name = "Look up a missing MX record using TCP" -shell = "dog MX non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "mx" ] - - -# NS records - -[[cmd]] -name = "Look up an existing NS record using TCP" -shell = "dog NS lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "ns" ] - -[[cmd]] -name = "Look up a missing NS record using TCP" -shell = "dog NS non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "ns" ] - - -# SOA records - -[[cmd]] -name = "Look up an existing SOA record using TCP" -shell = "dog SOA lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "soa" ] - -[[cmd]] -name = "Look up a missing SOA record using TCP" -shell = "dog MX non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "soa" ] - - -# SRV records - -[[cmd]] -name = "Look up an existing SRV record using TCP" -shell = "dog SRV srv-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '20 "dns.lookup.dog.":5000' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "srv" ] - -[[cmd]] -name = "Look up a missing SRV record using TCP" -shell = "dog SRV non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "srv" ] - - -# TXT records - -[[cmd]] -name = "Look up an existing TXT record using TCP" -shell = "dog TXT txt-example.lookup.dog @1.1.1.1 --short --tcp" -stdout = { string = '"Cache Invalidation and Naming Things"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tcp", "txt" ] - -[[cmd]] -name = "Look up a missing TXT record using TCP" -shell = "dog TXT non.existent @1.1.1.1 --short --tcp" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tcp", "txt" ] diff --git a/xtests/live/tls.toml b/xtests/live/tls.toml deleted file mode 100644 index 637d048..0000000 --- a/xtests/live/tls.toml +++ /dev/null @@ -1,188 +0,0 @@ -# A records - -[[cmd]] -name = "Look up an existing A record using TLS" -shell = "dog a-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '10.20.30.40' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "a" ] - -[[cmd]] -name = "Look up a missing A record using TLS" -shell = "dog non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "a" ] - - -# AAAA records - -[[cmd]] -name = "Look up an existing AAAA record using TLS" -shell = "dog AAAA aaaa-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '::1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "aaaa" ] - -[[cmd]] -name = "Look up a missing AAAA record using TLS" -shell = "dog AAAA non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "aaaa" ] - - -# CAA records - -[[cmd]] -name = "Look up an existing CAA record using TLS" -shell = "dog CAA caa-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '"issue" "some.certificate.authority" (non-critical)' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "caa" ] - -[[cmd]] -name = "Look up a missing CAA record using TLS" -shell = "dog CAA non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "caa" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing CNAME record using TLS" -shell = "dog CNAME cname-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '"dns.lookup.dog."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "cname" ] - -[[cmd]] -name = "Look up a missing CNAME record using TLS" -shell = "dog CNAME non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "cname" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing HINFO record using TLS" -shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '"some-kinda-os"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "hinfo" ] - -[[cmd]] -name = "Look up a missing HINFO record using TLS" -shell = "dog HINFO non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "hinfo" ] - - -# MX records - -[[cmd]] -name = "Look up an existing MX record using TLS" -shell = "dog MX mx-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '10 "some.mail.server."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "mx" ] - -[[cmd]] -name = "Look up a missing MX record using TLS" -shell = "dog MX non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "mx" ] - - -# NS records - -[[cmd]] -name = "Look up an existing NS record using TLS" -shell = "dog NS lookup.dog @1.1.1.1 --short --tls" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "ns" ] - -[[cmd]] -name = "Look up a missing NS record using TLS" -shell = "dog NS non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "ns" ] - - -# SOA records - -[[cmd]] -name = "Look up an existing SOA record using TLS" -shell = "dog SOA lookup.dog @1.1.1.1 --short --tls" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "soa" ] - -[[cmd]] -name = "Look up a missing SOA record using TLS" -shell = "dog MX non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "soa" ] - - -# SRV records - -[[cmd]] -name = "Look up an existing SRV record using TLS" -shell = "dog SRV srv-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '20 "dns.lookup.dog.":5000' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "srv" ] - -[[cmd]] -name = "Look up a missing SRV record using TLS" -shell = "dog SRV non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "srv" ] - - -# TXT records - -[[cmd]] -name = "Look up an existing TXT record using TLS" -shell = "dog TXT txt-example.lookup.dog @1.1.1.1 --short --tls" -stdout = { string = '"Cache Invalidation and Naming Things"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "tls", "txt" ] - -[[cmd]] -name = "Look up a missing TXT record using TLS" -shell = "dog TXT non.existent @1.1.1.1 --short --tls" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "tls", "txt" ] diff --git a/xtests/live/udp.toml b/xtests/live/udp.toml deleted file mode 100644 index f2b0209..0000000 --- a/xtests/live/udp.toml +++ /dev/null @@ -1,188 +0,0 @@ -# A records - -[[cmd]] -name = "Look up an existing A record using UDP" -shell = "dog a-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '10.20.30.40' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "a" ] - -[[cmd]] -name = "Look up a missing A record using UDP" -shell = "dog non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "a" ] - - -# AAAA records - -[[cmd]] -name = "Look up an existing AAAA record using UDP" -shell = "dog AAAA aaaa-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '::1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "aaaa" ] - -[[cmd]] -name = "Look up a missing AAAA record using UDP" -shell = "dog AAAA non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "aaaa" ] - - -# CAA records - -[[cmd]] -name = "Look up an existing CAA record using UDP" -shell = "dog CAA caa-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '"issue" "some.certificate.authority" (non-critical)' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "caa" ] - -[[cmd]] -name = "Look up a missing CAA record using UDP" -shell = "dog CAA non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "caa" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing CNAME record using UDP" -shell = "dog CNAME cname-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '"dns.lookup.dog."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "cname" ] - -[[cmd]] -name = "Look up a missing CNAME record using UDP" -shell = "dog CNAME non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "cname" ] - - -# CNAME records - -[[cmd]] -name = "Look up an existing HINFO record using UDP" -shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '"some-kinda-cpu"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "hinfo" ] - -[[cmd]] -name = "Look up a missing HINFO record using UDP" -shell = "dog HINFO non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "hinfo" ] - - -# MX records - -[[cmd]] -name = "Look up an existing MX record using UDP" -shell = "dog MX mx-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '10 "some.mail.server."' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "mx" ] - -[[cmd]] -name = "Look up a missing MX record using UDP" -shell = "dog MX non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "mx" ] - - -# NS records - -[[cmd]] -name = "Look up an existing NS record using UDP" -shell = "dog NS lookup.dog @1.1.1.1 --short" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "ns" ] - -[[cmd]] -name = "Look up a missing NS record using UDP" -shell = "dog NS non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "ns" ] - - -# SOA records - -[[cmd]] -name = "Look up an existing SOA record using UDP" -shell = "dog SOA lookup.dog @1.1.1.1 --short" -stdout = { string = 'ns1' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "soa" ] - -[[cmd]] -name = "Look up a missing SOA record using UDP" -shell = "dog MX non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "soa" ] - - -# SRV records - -[[cmd]] -name = "Look up an existing SRV record using UDP" -shell = "dog SRV srv-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '20 "dns.lookup.dog.":5000' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "srv" ] - -[[cmd]] -name = "Look up a missing SRV record using UDP" -shell = "dog SRV non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "srv" ] - - -# TXT records - -[[cmd]] -name = "Look up an existing TXT record using UDP" -shell = "dog TXT txt-example.lookup.dog @1.1.1.1 --short" -stdout = { string = '"Cache Invalidation and Naming Things"' } -stderr = { empty = true } -status = 0 -tags = [ "live", "cloudflare", "udp", "txt" ] - -[[cmd]] -name = "Look up a missing TXT record using UDP" -shell = "dog TXT non.existent @1.1.1.1 --short" -stdout = { empty = true } -stderr = { string = "No results" } -status = 2 -tags = [ "live", "cloudflare", "udp", "txt" ] diff --git a/xtests/madns/a-records.toml b/xtests/madns/a-records.toml deleted file mode 100644 index b832eb0..0000000 --- a/xtests/madns/a-records.toml +++ /dev/null @@ -1,55 +0,0 @@ -# A record successes - -[[cmd]] -name = "Running with ‘a.example’ prints the correct A record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A a.example" -stdout = { file = "outputs/a.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "a", "madns" ] - - -# A record successes (JSON) - -[[cmd]] -name = "Running with ‘a.example --json’ prints the correct A record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A a.example --json | jq" -stdout = { file = "outputs/a.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "a", "madns", "json" ] - - -# A record invalid packets - -[[cmd]] -name = "Running with ‘too-long.a.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A too-long.a.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 5" } -status = 1 -tags = [ "a", "madns" ] - -[[cmd]] -name = "Running with ‘too-short.a.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A too-short.a.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 3" } -status = 1 -tags = [ "a", "madns" ] - -[[cmd]] -name = "Running with ‘empty.a.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A empty.a.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 0" } -status = 1 -tags = [ "a", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.a.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A incomplete.a.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "a", "madns" ] diff --git a/xtests/madns/aaaa-records.toml b/xtests/madns/aaaa-records.toml deleted file mode 100644 index 6a9a8b6..0000000 --- a/xtests/madns/aaaa-records.toml +++ /dev/null @@ -1,55 +0,0 @@ -# AAAA record successes - -[[cmd]] -name = "Running with ‘aaaa.example’ prints the correct AAAA record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA aaaa.example" -stdout = { file = "outputs/aaaa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "aaaa", "madns" ] - - -# AAAA record successes (JSON) - -[[cmd]] -name = "Running with ‘aaaa.example --json’ prints the correct AAAA record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA aaaa.example --json | jq" -stdout = { file = "outputs/aaaa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "aaaa", "madns", "json" ] - - -# AAAA record invalid packets - -[[cmd]] -name = "Running with ‘too-long.aaaa.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA too-long.aaaa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 17" } -status = 1 -tags = [ "aaaa", "madns" ] - -[[cmd]] -name = "Running with ‘too-short.aaaa.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA too-short.aaaa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 8" } -status = 1 -tags = [ "aaaa", "madns" ] - -[[cmd]] -name = "Running with ‘empty.aaaa.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA empty.aaaa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 0" } -status = 1 -tags = [ "aaaa", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.aaaa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA incomplete.aaaa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "aaaa", "madns" ] diff --git a/xtests/madns/caa-records.toml b/xtests/madns/caa-records.toml deleted file mode 100644 index 6886a15..0000000 --- a/xtests/madns/caa-records.toml +++ /dev/null @@ -1,103 +0,0 @@ -# CAA record successes - -[[cmd]] -name = "Running with ‘caa.example’ prints the correct CAA record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA caa.example" -stdout = { file = "outputs/caa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns" ] - -[[cmd]] -name = "Running with ‘critical.caa.example’ prints the correct CAA record with the flag" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA critical.caa.example" -stdout = { file = "outputs/critical.caa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns" ] - -[[cmd]] -name = "Running with ‘others.caa.example’ prints the correct CAA record and ignores the flags" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA others.caa.example" -stdout = { file = "outputs/others.caa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns" ] - -[[cmd]] -name = "Running with ‘utf8.caa.example’ escapes characters in the fields" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA utf8.caa.example" -stdout = { file = "outputs/utf8.caa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "chars" ] - -[[cmd]] -name = "Running with ‘bad-utf8.caa.example’ escapes characters in the fields and does not crash" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA bad-utf8.caa.example" -stdout = { file = "outputs/bad-utf8.caa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "chars" ] - - -# CAA record successes (JSON) - -[[cmd]] -name = "Running with ‘caa.example --json’ prints the correct CAA record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA caa.example --json | jq" -stdout = { file = "outputs/caa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "json" ] - -[[cmd]] -name = "Running with ‘critical.caa.example --json’ prints the correct CAA record structurewith the flag" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA critical.caa.example --json | jq" -stdout = { file = "outputs/critical.caa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "json" ] - -[[cmd]] -name = "Running with ‘others.caa.example --json’ prints the correct CAA record structure and ignores the flags" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA others.caa.example --json | jq" -stdout = { file = "outputs/others.caa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "json" ] - -[[cmd]] -name = "Running with ‘utf8.caa.example --json’ interprets the response as UTF-8" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA utf8.caa.example --json | jq" -stdout = { file = "outputs/utf8.caa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "chars", "json" ] - -[[cmd]] -name = "Running with ‘bad-utf8.caa.example --json’ uses UTF-8 replacement characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA bad-utf8.caa.example --json | jq" -stdout = { file = "outputs/bad-utf8.caa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "caa", "madns", "chars", "json" ] - - -# CAA record invalid packets - -[[cmd]] -name = "Running with ‘empty.caa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA empty.caa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "caa", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.caa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA incomplete.caa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "caa", "madns" ] diff --git a/xtests/madns/cname-records.toml b/xtests/madns/cname-records.toml deleted file mode 100644 index 2975d54..0000000 --- a/xtests/madns/cname-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# CNAME record successes - -[[cmd]] -name = "Running with ‘cname.example’ prints the correct CNAME record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME cname.example" -stdout = { file = "outputs/cname.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "cname", "madns" ] - - -# CNAME record successes (JSON) - -[[cmd]] -name = "Running with ‘cname.example --json’ prints the correct CNAME record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME cname.example --json | jq" -stdout = { file = "outputs/cname.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "cname", "madns", "json" ] - - -# CNAME record invalid packets - -[[cmd]] -name = "Running with ‘empty.cname.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME empty.cname.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "cname", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.cname.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME incomplete.cname.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "cname", "madns" ] diff --git a/xtests/madns/eui48-records.toml b/xtests/madns/eui48-records.toml deleted file mode 100644 index f56ee10..0000000 --- a/xtests/madns/eui48-records.toml +++ /dev/null @@ -1,55 +0,0 @@ -# EUI48 record successes - -[[cmd]] -name = "Running with ‘eui48.example’ prints the correct EUI48 record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 eui48.example" -stdout = { file = "outputs/eui48.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "eui48", "madns" ] - - -# EUI48 record successes (JSON) - -[[cmd]] -name = "Running with ‘eui48.example --json’ prints the correct EUI48 record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 eui48.example --json | jq" -stdout = { file = "outputs/eui48.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "eui48", "madns", "json" ] - - -# EUI48 record invalid packets - -[[cmd]] -name = "Running with ‘too-long.eui48.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 too-long.eui48.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 7" } -status = 1 -tags = [ "eui48", "madns" ] - -[[cmd]] -name = "Running with ‘too-short.eui48.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 too-short.eui48.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 5" } -status = 1 -tags = [ "eui48", "madns" ] - -[[cmd]] -name = "Running with ‘empty.eui48.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 empty.eui48.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 0" } -status = 1 -tags = [ "eui48", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.eui48.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 incomplete.eui48.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "eui48", "madns" ] diff --git a/xtests/madns/eui64-records.toml b/xtests/madns/eui64-records.toml deleted file mode 100644 index d6b62b5..0000000 --- a/xtests/madns/eui64-records.toml +++ /dev/null @@ -1,55 +0,0 @@ -# EUI64 record successes - -[[cmd]] -name = "Running with ‘eui64.example’ prints the correct EUI64 record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 eui64.example" -stdout = { file = "outputs/eui64.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "eui64", "madns" ] - - -# EUI64 record successes (JSON) - -[[cmd]] -name = "Running with ‘eui64.example --json’ prints the correct EUI64 record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 eui64.example --json | jq" -stdout = { file = "outputs/eui64.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "eui64", "madns", "json" ] - - -# EUI64 record invalid packets - -[[cmd]] -name = "Running with ‘too-long.eui64.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 too-long.eui64.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 9" } -status = 1 -tags = [ "eui64", "madns" ] - -[[cmd]] -name = "Running with ‘too-short.eui64.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 too-short.eui64.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 7" } -status = 1 -tags = [ "eui64", "madns" ] - -[[cmd]] -name = "Running with ‘empty.eui64.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 empty.eui64.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 0" } -status = 1 -tags = [ "eui64", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.eui64.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 incomplete.eui64.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "eui64", "madns" ] diff --git a/xtests/madns/hinfo-records.toml b/xtests/madns/hinfo-records.toml deleted file mode 100644 index b7a46ff..0000000 --- a/xtests/madns/hinfo-records.toml +++ /dev/null @@ -1,70 +0,0 @@ -# HINFO record successes - -[[cmd]] -name = "Running with ‘hinfo.example’ prints the correct HINFO record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO hinfo.example" -stdout = { file = "outputs/hinfo.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns" ] - -[[cmd]] -name = "Running with ‘utf8.hinfo.example’ escapes characters in the fields" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO utf8.hinfo.example" -stdout = { file = "outputs/utf8.hinfo.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns", "chars" ] - -[[cmd]] -name = "Running with ‘bad-utf8.hinfo.example’ escapes characters in the fields and does not crash" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO bad-utf8.hinfo.example" -stdout = { file = "outputs/bad-utf8.hinfo.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns", "chars" ] - - -# HINFO record successes (JSON) - -[[cmd]] -name = "Running with ‘hinfo.example --json’ prints the correct HINFO record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO hinfo.example --json | jq" -stdout = { file = "outputs/hinfo.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns", "json" ] - -[[cmd]] -name = "Running with ‘utf8.hinfo.example --json’ interprets the response as UTF-8" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO utf8.hinfo.example --json | jq" -stdout = { file = "outputs/utf8.hinfo.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns", "chars", "json" ] - -[[cmd]] -name = "Running with ‘bad-utf8.hinfo.example --json’ uses UTF-8 replacement characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO bad-utf8.hinfo.example --json | jq" -stdout = { file = "outputs/bad-utf8.hinfo.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "hinfo", "madns", "chars", "json" ] - -# HINFO record invalid packets - -[[cmd]] -name = "Running with ‘empty.hinfo.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO empty.hinfo.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "hinfo", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.hinfo.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO incomplete.hinfo.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "hinfo", "madns" ] diff --git a/xtests/madns/loc-records.toml b/xtests/madns/loc-records.toml deleted file mode 100644 index e7f5e20..0000000 --- a/xtests/madns/loc-records.toml +++ /dev/null @@ -1,136 +0,0 @@ -# LOC record successes - -[[cmd]] -name = "Running with ‘loc.example’ prints the correct LOC record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC loc.example" -stdout = { file = "outputs/loc.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns" ] - - -# LOC record successes (JSON) - -[[cmd]] -name = "Running with ‘loc.example --json’ prints the correct LOC record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC loc.example --json | jq" -stdout = { file = "outputs/loc.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns", "json" ] - - -# LOC record out-of-range positions - -[[cmd]] -name = "Running with ‘far-negative-longitude.loc.invalid’ displays a record with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-negative-longitude.loc.invalid" -stdout = { file = "outputs/far-negative-longitude.loc.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘far-positive-longitude.loc.invalid’ displays a record with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-positive-longitude.loc.invalid" -stdout = { file = "outputs/far-positive-longitude.loc.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘far-negative-latitude.loc.invalid’ displays a record with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-negative-latitude.loc.invalid" -stdout = { file = "outputs/far-negative-latitude.loc.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘far-positive-latitude.loc.invalid’ displays a record with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-positive-latitude.loc.invalid" -stdout = { file = "outputs/far-positive-latitude.loc.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns" ] - - -# LOC record out-of-range positions (JSON) - -[[cmd]] -name = "Running with ‘far-negative-longitude.loc.invalid’ displays a record structure with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-negative-longitude.loc.invalid --json | jq" -stdout = { file = "outputs/far-negative-longitude.loc.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns", "json" ] - -[[cmd]] -name = "Running with ‘far-positive-longitude.loc.invalid’ displays a record structure with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-positive-longitude.loc.invalid --json | jq" -stdout = { file = "outputs/far-positive-longitude.loc.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns", "json" ] - -[[cmd]] -name = "Running with ‘far-negative-latitude.loc.invalid’ displays a record structure with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-negative-latitude.loc.invalid --json | jq" -stdout = { file = "outputs/far-negative-latitude.loc.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns", "json" ] - -[[cmd]] -name = "Running with ‘far-positive-latitude.loc.invalid’ displays a record structure with an out-of-range field" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC far-positive-latitude.loc.invalid --json | jq" -stdout = { file = "outputs/far-positive-latitude.loc.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "loc", "madns", "json" ] - - -# LOC record version 1 - -[[cmd]] -name = "Running with ‘v1-conform.loc.invalid’ displays a version error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC v1-conform.loc.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record specifies version 1, expected up to 0" } -status = 1 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘v1-nonconform.loc.invalid’ displays a version error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC v1-nonconform.loc.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record specifies version 1, expected up to 0" } -status = 1 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘v1-empty.loc.invalid’ displays a version error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC v1-empty.loc.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record specifies version 1, expected up to 0" } -status = 1 -tags = [ "loc", "madns" ] - - -# LOC record invalid packets - -[[cmd]] -name = "Running with ‘empty.loc.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC empty.loc.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "loc", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.loc.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} LOC incomplete.loc.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "loc", "madns" ] diff --git a/xtests/madns/mx-records.toml b/xtests/madns/mx-records.toml deleted file mode 100644 index b8842e0..0000000 --- a/xtests/madns/mx-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# MX record successes - -[[cmd]] -name = "Running with ‘mx.example’ prints the correct MX record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX mx.example" -stdout = { file = "outputs/mx.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "mx", "madns" ] - - -# MX record successes (JSON) - -[[cmd]] -name = "Running with ‘mx.example --json’ prints the correct MX record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX mx.example --json | jq" -stdout = { file = "outputs/mx.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "mx", "madns", "json" ] - - -# MX record invalid packets - -[[cmd]] -name = "Running with ‘empty.mx.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX empty.mx.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "mx", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.mx.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX incomplete.mx.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "mx", "madns" ] diff --git a/xtests/madns/naptr-records.toml b/xtests/madns/naptr-records.toml deleted file mode 100644 index b126886..0000000 --- a/xtests/madns/naptr-records.toml +++ /dev/null @@ -1,79 +0,0 @@ -# NAPTR record successes - -[[cmd]] -name = "Running with ‘naptr.example’ prints the correct NAPTR record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR naptr.example" -stdout = { file = "outputs/naptr.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns" ] - -[[cmd]] -name = "Running with ‘bad-regex.naptr.example’ still prints the correct NAPTR record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-regex.naptr.example" -stdout = { file = "outputs/bad-regex.naptr.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns" ] - -[[cmd]] -name = "Running with ‘utf8.naptr.example’ escapes characters in the NAPTR" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR utf8.naptr.invalid" -stdout = { file = "outputs/utf8.naptr.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns", "chars" ] - -[[cmd]] -name = "Running with ‘bad-utf8.naptr.example’ escapes characters in the NAPTR and does not crash" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-utf8.naptr.invalid" -stdout = { file = "outputs/bad-utf8.naptr.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns", "chars" ] - - -# NAPTR record successes (JSON) - -[[cmd]] -name = "Running with ‘naptr.example --json’ prints the correct NAPTR record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR naptr.example --json | jq" -stdout = { file = "outputs/naptr.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns", "json" ] - -[[cmd]] -name = "Running with ‘utf8.naptr.example --json’ interprets the response as UTF-8" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR utf8.naptr.invalid --json | jq" -stdout = { file = "outputs/utf8.naptr.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns", "chars", "json" ] - -[[cmd]] -name = "Running with ‘bad-utf8.naptr.example --json’ uses UTF-8 replacement characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-utf8.naptr.invalid --json | jq" -stdout = { file = "outputs/bad-utf8.naptr.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "naptr", "madns", "chars", "json" ] - - -# NAPTR record invalid packets - -[[cmd]] -name = "Running with ‘empty.naptr.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR empty.naptr.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "naptr", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.naptr.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR incomplete.naptr.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "naptr", "madns" ] diff --git a/xtests/madns/ns-records.toml b/xtests/madns/ns-records.toml deleted file mode 100644 index 3a46389..0000000 --- a/xtests/madns/ns-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# NS record successes - -[[cmd]] -name = "Running with ‘ns.example’ prints the correct NS record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS ns.example" -stdout = { file = "outputs/ns.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "ns", "madns" ] - - -# NS record successes (JSON) - -[[cmd]] -name = "Running with ‘ns.example --json’ prints the correct NS record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS ns.example --json | jq" -stdout = { file = "outputs/ns.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "ns", "madns", "json" ] - - -# NS record invalid packets - -[[cmd]] -name = "Running with ‘empty.ns.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS empty.ns.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "ns", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.ns.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS incomplete.ns.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "ns", "madns" ] diff --git a/xtests/madns/openpgpkey-records.toml b/xtests/madns/openpgpkey-records.toml deleted file mode 100644 index 1c9317f..0000000 --- a/xtests/madns/openpgpkey-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# OPENPGPKEY record successes - -[[cmd]] -name = "Running with ‘openpgpkey.example’ prints the correct OPENPGPKEY record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY openpgpkey.example" -stdout = { file = "outputs/openpgpkey.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "openpgpkey", "madns" ] - - -# OPENPGPKEY record successes (JSON) - -[[cmd]] -name = "Running with ‘openpgpkey.example --json’ prints the correct OPENPGPKEY record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY openpgpkey.example --json | jq" -stdout = { file = "outputs/openpgpkey.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "openpgpkey", "madns", "json" ] - - -# OPENPGPKEY record invalid packets - -[[cmd]] -name = "Running with ‘empty.openpgpkey.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY empty.openpgpkey.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be at least 1, got 0" } -status = 1 -tags = [ "openpgpkey", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.openpgpkey.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY incomplete.openpgpkey.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "openpgpkey", "madns" ] diff --git a/xtests/madns/opt-records.toml b/xtests/madns/opt-records.toml deleted file mode 100644 index baf3051..0000000 --- a/xtests/madns/opt-records.toml +++ /dev/null @@ -1,79 +0,0 @@ -# OPT record successes - -[[cmd]] -name = "Running with ‘opt.example’ prints the correct OPT record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A opt.example --edns=show" -stdout = { file = "outputs/opt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns" ] - -[[cmd]] -name = "Running with ‘do-flag.opt.example’ prints the correct OPT record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A do-flag.opt.example --edns=show" -stdout = { file = "outputs/do-flag.opt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns" ] - -[[cmd]] -name = "Running with ‘other-flags.opt.example’ prints the correct OPT record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A other-flags.opt.example --edns=show" -stdout = { file = "outputs/other-flags.opt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns" ] - -[[cmd]] -name = "Running with ‘named.opt.invalid’ prints the correct OPT record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A named.opt.invalid --edns=show" -stdout = { file = "outputs/named.opt.invalid.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns" ] - - -# OPT record successes (JSON) - -[[cmd]] -name = "Running with ‘opt.example --json’ prints the correct OPT record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A opt.example --edns=show --json | jq" -stdout = { file = "outputs/opt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns", "json" ] - -[[cmd]] -name = "Running with ‘do-flag.opt.example --json’ prints the correct OPT record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A do-flag.opt.example --edns=show --json | jq" -stdout = { file = "outputs/do-flag.opt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns", "json" ] - -[[cmd]] -name = "Running with ‘other-flags.opt.example --json’ prints the correct OPT record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A other-flags.opt.example --edns=show --json | jq" -stdout = { file = "outputs/other-flags.opt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns", "json" ] - -[[cmd]] -name = "Running with ‘named.opt.invalid --json’ prints the correct OPT record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A named.opt.invalid --edns=show --json | jq" -stdout = { file = "outputs/named.opt.invalid.json" } -stderr = { empty = true } -status = 0 -tags = [ "opt", "madns", "json" ] - - -# OPT record invalid packets - -[[cmd]] -name = "Running with ‘incomplete.opt.invalid’ displays a record length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A incomplete.opt.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "opt", "madns" ] diff --git a/xtests/madns/outputs/a.example.ansitxt b/xtests/madns/outputs/a.example.ansitxt deleted file mode 100644 index 10bf2b5..0000000 --- a/xtests/madns/outputs/a.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -A a.example. 10m00s 127.0.0.1 diff --git a/xtests/madns/outputs/a.example.json b/xtests/madns/outputs/a.example.json deleted file mode 100644 index 89fe91a..0000000 --- a/xtests/madns/outputs/a.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "a.example.", - "class": "IN", - "type": "A" - } - ], - "answers": [ - { - "name": "a.example.", - "class": "IN", - "ttl": 600, - "type": "A", - "data": { - "address": "127.0.0.1" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/aaaa.example.ansitxt b/xtests/madns/outputs/aaaa.example.ansitxt deleted file mode 100644 index bf38c90..0000000 --- a/xtests/madns/outputs/aaaa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -AAAA aaaa.example. 10m00s ::1 diff --git a/xtests/madns/outputs/aaaa.example.json b/xtests/madns/outputs/aaaa.example.json deleted file mode 100644 index eea2e86..0000000 --- a/xtests/madns/outputs/aaaa.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "aaaa.example.", - "class": "IN", - "type": "AAAA" - } - ], - "answers": [ - { - "name": "aaaa.example.", - "class": "IN", - "ttl": 600, - "type": "AAAA", - "data": { - "address": "::1" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/ansi.str.example.ansitxt b/xtests/madns/outputs/ansi.str.example.ansitxt deleted file mode 100644 index 5e5f3f6..0000000 --- a/xtests/madns/outputs/ansi.str.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME ansi.str.example. 10m00s "\u{1b}[32mgreen.\u{1b}[34mblue.\u{1b}[31mred.\u{1b}[0m." diff --git a/xtests/madns/outputs/ansi.str.example.json b/xtests/madns/outputs/ansi.str.example.json deleted file mode 100644 index ad36988..0000000 --- a/xtests/madns/outputs/ansi.str.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "ansi.str.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "ansi.str.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "\u001b[32mgreen.\u001b[34mblue.\u001b[31mred.\u001b[0m." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/bad-regex.naptr.example.ansitxt b/xtests/madns/outputs/bad-regex.naptr.example.ansitxt deleted file mode 100644 index 7b2ad70..0000000 --- a/xtests/madns/outputs/bad-regex.naptr.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -NAPTR bad-regex.naptr.example. 10m00s 5 10 "s" "SRV" "(((((((((((((((((((((((((" "srv.example." diff --git a/xtests/madns/outputs/bad-utf8.caa.example.ansitxt b/xtests/madns/outputs/bad-utf8.caa.example.ansitxt deleted file mode 100644 index d8358bb..0000000 --- a/xtests/madns/outputs/bad-utf8.caa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CAA bad-utf8.caa.example. 10m00s "issuewild" "\208\208\160\255" (non-critical) diff --git a/xtests/madns/outputs/bad-utf8.caa.example.json b/xtests/madns/outputs/bad-utf8.caa.example.json deleted file mode 100644 index 586bb25..0000000 --- a/xtests/madns/outputs/bad-utf8.caa.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "bad-utf8.caa.example.", - "class": "IN", - "type": "CAA" - } - ], - "answers": [ - { - "name": "bad-utf8.caa.example.", - "class": "IN", - "ttl": 600, - "type": "CAA", - "data": { - "critical": false, - "tag": "issuewild", - "value": "�Р�" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/bad-utf8.hinfo.example.ansitxt b/xtests/madns/outputs/bad-utf8.hinfo.example.ansitxt deleted file mode 100644 index 3799f1c..0000000 --- a/xtests/madns/outputs/bad-utf8.hinfo.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -HINFO bad-utf8.hinfo.example. 10m00s "\208\208\160\255" "\208\208\160\255" diff --git a/xtests/madns/outputs/bad-utf8.hinfo.example.json b/xtests/madns/outputs/bad-utf8.hinfo.example.json deleted file mode 100644 index c588d68..0000000 --- a/xtests/madns/outputs/bad-utf8.hinfo.example.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "bad-utf8.hinfo.example.", - "class": "IN", - "type": "HINFO" - } - ], - "answers": [ - { - "name": "bad-utf8.hinfo.example.", - "class": "IN", - "ttl": 600, - "type": "HINFO", - "data": { - "cpu": "�Р�", - "os": "�Р�" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/bad-utf8.naptr.invalid.ansitxt b/xtests/madns/outputs/bad-utf8.naptr.invalid.ansitxt deleted file mode 100644 index 24dd29d..0000000 --- a/xtests/madns/outputs/bad-utf8.naptr.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -NAPTR bad-utf8.naptr.invalid. 10m00s 5 10 "\208\208\160\255" "\208\208\160\255" "\208\208\160\255" "�Р�." diff --git a/xtests/madns/outputs/bad-utf8.naptr.invalid.json b/xtests/madns/outputs/bad-utf8.naptr.invalid.json deleted file mode 100644 index e18a23d..0000000 --- a/xtests/madns/outputs/bad-utf8.naptr.invalid.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "bad-utf8.naptr.invalid.", - "class": "IN", - "type": "NAPTR" - } - ], - "answers": [ - { - "name": "bad-utf8.naptr.invalid.", - "class": "IN", - "ttl": 600, - "type": "NAPTR", - "data": { - "order": 5, - "flags": "�Р�", - "service": "�Р�", - "regex": "�Р�", - "replacement": "�Р�." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/bad-utf8.txt.example.ansitxt b/xtests/madns/outputs/bad-utf8.txt.example.ansitxt deleted file mode 100644 index 5db86df..0000000 --- a/xtests/madns/outputs/bad-utf8.txt.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -TXT bad-utf8.txt.example. 10m00s "\208\208\160\255" diff --git a/xtests/madns/outputs/bad-utf8.txt.example.json b/xtests/madns/outputs/bad-utf8.txt.example.json deleted file mode 100644 index 570df79..0000000 --- a/xtests/madns/outputs/bad-utf8.txt.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "bad-utf8.txt.example.", - "class": "IN", - "type": "TXT" - } - ], - "answers": [ - { - "name": "bad-utf8.txt.example.", - "class": "IN", - "ttl": 600, - "type": "TXT", - "data": { - "messages": [ - "�Р�" - ] - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/bad-utf8.uri.example.ansitxt b/xtests/madns/outputs/bad-utf8.uri.example.ansitxt deleted file mode 100644 index 6ef8e04..0000000 --- a/xtests/madns/outputs/bad-utf8.uri.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -URI bad-utf8.uri.example. 10m00s 10 16 "\208\208\160\255" diff --git a/xtests/madns/outputs/bad-utf8.uri.example.json b/xtests/madns/outputs/bad-utf8.uri.example.json deleted file mode 100644 index 6865b77..0000000 --- a/xtests/madns/outputs/bad-utf8.uri.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "bad-utf8.uri.example.", - "class": "IN", - "type": "URI" - } - ], - "answers": [ - { - "name": "bad-utf8.uri.example.", - "class": "IN", - "ttl": 600, - "type": "URI", - "data": { - "priority": 10, - "weight": 16, - "target": "�Р�" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/caa.example.ansitxt b/xtests/madns/outputs/caa.example.ansitxt deleted file mode 100644 index d4f6f64..0000000 --- a/xtests/madns/outputs/caa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CAA caa.example. 10m00s "issuewild" "trustworthy.example" (non-critical) diff --git a/xtests/madns/outputs/caa.example.json b/xtests/madns/outputs/caa.example.json deleted file mode 100644 index c342ff7..0000000 --- a/xtests/madns/outputs/caa.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "caa.example.", - "class": "IN", - "type": "CAA" - } - ], - "answers": [ - { - "name": "caa.example.", - "class": "IN", - "ttl": 600, - "type": "CAA", - "data": { - "critical": false, - "tag": "issuewild", - "value": "trustworthy.example" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/cname.example.ansitxt b/xtests/madns/outputs/cname.example.ansitxt deleted file mode 100644 index 277e7af..0000000 --- a/xtests/madns/outputs/cname.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME cname.example. 10m00s "dns.lookup.dog." diff --git a/xtests/madns/outputs/cname.example.json b/xtests/madns/outputs/cname.example.json deleted file mode 100644 index 2fe2d8a..0000000 --- a/xtests/madns/outputs/cname.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "cname.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "cname.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "dns.lookup.dog." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/critical.caa.example.ansitxt b/xtests/madns/outputs/critical.caa.example.ansitxt deleted file mode 100644 index 03a76d7..0000000 --- a/xtests/madns/outputs/critical.caa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CAA critical.caa.example. 10m00s "issuewild" "trustworthy.example" (critical) diff --git a/xtests/madns/outputs/critical.caa.example.json b/xtests/madns/outputs/critical.caa.example.json deleted file mode 100644 index bb4dfce..0000000 --- a/xtests/madns/outputs/critical.caa.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "critical.caa.example.", - "class": "IN", - "type": "CAA" - } - ], - "answers": [ - { - "name": "critical.caa.example.", - "class": "IN", - "ttl": 600, - "type": "CAA", - "data": { - "critical": true, - "tag": "issuewild", - "value": "trustworthy.example" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/do-flag.opt.example.ansitxt b/xtests/madns/outputs/do-flag.opt.example.ansitxt deleted file mode 100644 index 17bd87b..0000000 --- a/xtests/madns/outputs/do-flag.opt.example.ansitxt +++ /dev/null @@ -1,2 +0,0 @@ - A do-flag.opt.example. 10m00s 127.0.0.1 -OPT  + 1452 0 0 32768 [] diff --git a/xtests/madns/outputs/do-flag.opt.example.json b/xtests/madns/outputs/do-flag.opt.example.json deleted file mode 100644 index e07a0e0..0000000 --- a/xtests/madns/outputs/do-flag.opt.example.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "do-flag.opt.example.", - "class": "IN", - "type": "A" - } - ], - "answers": [ - { - "name": "do-flag.opt.example.", - "class": "IN", - "ttl": 600, - "type": "A", - "data": { - "address": "127.0.0.1" - } - } - ], - "authorities": [], - "additionals": [ - { - "name": "", - "type": "OPT", - "data": { - "version": 0, - "data": [] - } - } - ] - } - ] -} diff --git a/xtests/madns/outputs/eui48.example.ansitxt b/xtests/madns/outputs/eui48.example.ansitxt deleted file mode 100644 index a92f537..0000000 --- a/xtests/madns/outputs/eui48.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -EUI48 eui48.example. 10m00s "12-34-56-78-90-ab" diff --git a/xtests/madns/outputs/eui48.example.json b/xtests/madns/outputs/eui48.example.json deleted file mode 100644 index 223df9a..0000000 --- a/xtests/madns/outputs/eui48.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "eui48.example.", - "class": "IN", - "type": "EUI48" - } - ], - "answers": [ - { - "name": "eui48.example.", - "class": "IN", - "ttl": 600, - "type": "EUI48", - "data": { - "identifier": "12-34-56-78-90-ab" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/eui64.example.ansitxt b/xtests/madns/outputs/eui64.example.ansitxt deleted file mode 100644 index 371e4d3..0000000 --- a/xtests/madns/outputs/eui64.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -EUI64 eui64.example. 10m00s "12-34-56-ff-fe-78-90-ab" diff --git a/xtests/madns/outputs/eui64.example.json b/xtests/madns/outputs/eui64.example.json deleted file mode 100644 index 6fc79e6..0000000 --- a/xtests/madns/outputs/eui64.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "eui64.example.", - "class": "IN", - "type": "EUI64" - } - ], - "answers": [ - { - "name": "eui64.example.", - "class": "IN", - "ttl": 600, - "type": "EUI64", - "data": { - "identifier": "12-34-56-ff-fe-78-90-ab" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/far-negative-latitude.loc.invalid.ansitxt b/xtests/madns/outputs/far-negative-latitude.loc.invalid.ansitxt deleted file mode 100644 index 2e800c6..0000000 --- a/xtests/madns/outputs/far-negative-latitude.loc.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -LOC far-negative-latitude.loc.invalid. 10m00s 3e2 (0, 0) (Out of range, 0°0′0″ E, 0m) diff --git a/xtests/madns/outputs/far-negative-latitude.loc.invalid.json b/xtests/madns/outputs/far-negative-latitude.loc.invalid.json deleted file mode 100644 index 51c9467..0000000 --- a/xtests/madns/outputs/far-negative-latitude.loc.invalid.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "far-negative-latitude.loc.invalid.", - "class": "IN", - "type": "LOC" - } - ], - "answers": [ - { - "name": "far-negative-latitude.loc.invalid.", - "class": "IN", - "ttl": 600, - "type": "LOC", - "data": { - "size": "3e2", - "precision": { - "horizontal": 0, - "vertical": 0 - }, - "point": { - "latitude": null, - "longitude": "0°0′0″ E", - "altitude": "0m" - } - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/far-negative-longitude.loc.invalid.ansitxt b/xtests/madns/outputs/far-negative-longitude.loc.invalid.ansitxt deleted file mode 100644 index eb56bac..0000000 --- a/xtests/madns/outputs/far-negative-longitude.loc.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -LOC far-negative-longitude.loc.invalid. 10m00s 3e2 (0, 0) (0°0′0″ N, Out of range, 0m) diff --git a/xtests/madns/outputs/far-negative-longitude.loc.invalid.json b/xtests/madns/outputs/far-negative-longitude.loc.invalid.json deleted file mode 100644 index 9e61467..0000000 --- a/xtests/madns/outputs/far-negative-longitude.loc.invalid.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "far-negative-longitude.loc.invalid.", - "class": "IN", - "type": "LOC" - } - ], - "answers": [ - { - "name": "far-negative-longitude.loc.invalid.", - "class": "IN", - "ttl": 600, - "type": "LOC", - "data": { - "size": "3e2", - "precision": { - "horizontal": 0, - "vertical": 0 - }, - "point": { - "latitude": "0°0′0″ N", - "longitude": null, - "altitude": "0m" - } - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/far-positive-latitude.loc.invalid.ansitxt b/xtests/madns/outputs/far-positive-latitude.loc.invalid.ansitxt deleted file mode 100644 index 84a183c..0000000 --- a/xtests/madns/outputs/far-positive-latitude.loc.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -LOC far-positive-latitude.loc.invalid. 10m00s 3e2 (0, 0) (Out of range, 0°0′0″ E, 0m) diff --git a/xtests/madns/outputs/far-positive-latitude.loc.invalid.json b/xtests/madns/outputs/far-positive-latitude.loc.invalid.json deleted file mode 100644 index 752bef0..0000000 --- a/xtests/madns/outputs/far-positive-latitude.loc.invalid.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "far-positive-latitude.loc.invalid.", - "class": "IN", - "type": "LOC" - } - ], - "answers": [ - { - "name": "far-positive-latitude.loc.invalid.", - "class": "IN", - "ttl": 600, - "type": "LOC", - "data": { - "size": "3e2", - "precision": { - "horizontal": 0, - "vertical": 0 - }, - "point": { - "latitude": null, - "longitude": "0°0′0″ E", - "altitude": "0m" - } - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/far-positive-longitude.loc.invalid.ansitxt b/xtests/madns/outputs/far-positive-longitude.loc.invalid.ansitxt deleted file mode 100644 index a05b978..0000000 --- a/xtests/madns/outputs/far-positive-longitude.loc.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -LOC far-positive-longitude.loc.invalid. 10m00s 3e2 (0, 0) (0°0′0″ N, Out of range, 0m) diff --git a/xtests/madns/outputs/far-positive-longitude.loc.invalid.json b/xtests/madns/outputs/far-positive-longitude.loc.invalid.json deleted file mode 100644 index 98a8f84..0000000 --- a/xtests/madns/outputs/far-positive-longitude.loc.invalid.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "far-positive-longitude.loc.invalid.", - "class": "IN", - "type": "LOC" - } - ], - "answers": [ - { - "name": "far-positive-longitude.loc.invalid.", - "class": "IN", - "ttl": 600, - "type": "LOC", - "data": { - "size": "3e2", - "precision": { - "horizontal": 0, - "vertical": 0 - }, - "point": { - "latitude": "0°0′0″ N", - "longitude": null, - "altitude": "0m" - } - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/hinfo.example.ansitxt b/xtests/madns/outputs/hinfo.example.ansitxt deleted file mode 100644 index edee212..0000000 --- a/xtests/madns/outputs/hinfo.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -HINFO hinfo.example. 10m00s "some-kinda-cpu" "some-kinda-os" diff --git a/xtests/madns/outputs/hinfo.example.json b/xtests/madns/outputs/hinfo.example.json deleted file mode 100644 index 38e13a1..0000000 --- a/xtests/madns/outputs/hinfo.example.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "hinfo.example.", - "class": "IN", - "type": "HINFO" - } - ], - "answers": [ - { - "name": "hinfo.example.", - "class": "IN", - "ttl": 600, - "type": "HINFO", - "data": { - "cpu": "some-kinda-cpu", - "os": "some-kinda-os" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/loc.example.ansitxt b/xtests/madns/outputs/loc.example.ansitxt deleted file mode 100644 index 67eb040..0000000 --- a/xtests/madns/outputs/loc.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -LOC loc.example. 10m00s 3e2 (0, 0) (51°30′12.748″ N, 0°7′39.611″ W, 0m) diff --git a/xtests/madns/outputs/loc.example.json b/xtests/madns/outputs/loc.example.json deleted file mode 100644 index 8208cae..0000000 --- a/xtests/madns/outputs/loc.example.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "loc.example.", - "class": "IN", - "type": "LOC" - } - ], - "answers": [ - { - "name": "loc.example.", - "class": "IN", - "ttl": 600, - "type": "LOC", - "data": { - "size": "3e2", - "precision": { - "horizontal": 0, - "vertical": 0 - }, - "point": { - "latitude": "51°30′12.748″ N", - "longitude": "0°7′39.611″ W", - "altitude": "0m" - } - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/mx.example.ansitxt b/xtests/madns/outputs/mx.example.ansitxt deleted file mode 100644 index 356ce68..0000000 --- a/xtests/madns/outputs/mx.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -MX mx.example. 10m00s 10 "exchange.example." diff --git a/xtests/madns/outputs/mx.example.json b/xtests/madns/outputs/mx.example.json deleted file mode 100644 index 9293ca4..0000000 --- a/xtests/madns/outputs/mx.example.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "mx.example.", - "class": "IN", - "type": "MX" - } - ], - "answers": [ - { - "name": "mx.example.", - "class": "IN", - "ttl": 600, - "type": "MX", - "data": { - "preference": 10, - "exchange": "exchange.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/named.opt.invalid.ansitxt b/xtests/madns/outputs/named.opt.invalid.ansitxt deleted file mode 100644 index 418d8a3..0000000 --- a/xtests/madns/outputs/named.opt.invalid.ansitxt +++ /dev/null @@ -1,2 +0,0 @@ - A named.opt.invalid. 10m00s 127.0.0.1 -OPT bingle.bongle.dingle.dangle. + 1452 0 0 0 [] diff --git a/xtests/madns/outputs/named.opt.invalid.json b/xtests/madns/outputs/named.opt.invalid.json deleted file mode 100644 index b27616d..0000000 --- a/xtests/madns/outputs/named.opt.invalid.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "named.opt.invalid.", - "class": "IN", - "type": "A" - } - ], - "answers": [ - { - "name": "named.opt.invalid.", - "class": "IN", - "ttl": 600, - "type": "A", - "data": { - "address": "127.0.0.1" - } - } - ], - "authorities": [], - "additionals": [ - { - "name": "bingle.bongle.dingle.dangle.", - "type": "OPT", - "data": { - "version": 0, - "data": [] - } - } - ] - } - ] -} diff --git a/xtests/madns/outputs/naptr.example.ansitxt b/xtests/madns/outputs/naptr.example.ansitxt deleted file mode 100644 index ac37951..0000000 --- a/xtests/madns/outputs/naptr.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -NAPTR naptr.example. 10m00s 5 10 "s" "SRV" "\\d\\d:\\d\\d:\\d\\d" "srv.example." diff --git a/xtests/madns/outputs/naptr.example.json b/xtests/madns/outputs/naptr.example.json deleted file mode 100644 index 14db13e..0000000 --- a/xtests/madns/outputs/naptr.example.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "naptr.example.", - "class": "IN", - "type": "NAPTR" - } - ], - "answers": [ - { - "name": "naptr.example.", - "class": "IN", - "ttl": 600, - "type": "NAPTR", - "data": { - "order": 5, - "flags": "s", - "service": "SRV", - "regex": "\\d\\d:\\d\\d:\\d\\d", - "replacement": "srv.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/newline.str.example.ansitxt b/xtests/madns/outputs/newline.str.example.ansitxt deleted file mode 100644 index e39fe60..0000000 --- a/xtests/madns/outputs/newline.str.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME newline.str.example. 10m00s "some\nnew\r\nlines\n.example." diff --git a/xtests/madns/outputs/newline.str.example.json b/xtests/madns/outputs/newline.str.example.json deleted file mode 100644 index f9faa87..0000000 --- a/xtests/madns/outputs/newline.str.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "newline.str.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "newline.str.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "some\nnew\r\nlines\n.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/ns.example.ansitxt b/xtests/madns/outputs/ns.example.ansitxt deleted file mode 100644 index ed26df5..0000000 --- a/xtests/madns/outputs/ns.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -NS ns.example. 10m00s "a.gtld-servers.net." diff --git a/xtests/madns/outputs/ns.example.json b/xtests/madns/outputs/ns.example.json deleted file mode 100644 index 2bea769..0000000 --- a/xtests/madns/outputs/ns.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "ns.example.", - "class": "IN", - "type": "NS" - } - ], - "answers": [ - { - "name": "ns.example.", - "class": "IN", - "ttl": 600, - "type": "NS", - "data": { - "nameserver": "a.gtld-servers.net." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/null.str.example.ansitxt b/xtests/madns/outputs/null.str.example.ansitxt deleted file mode 100644 index ce01203..0000000 --- a/xtests/madns/outputs/null.str.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME null.str.example. 10m00s "some\u{0}null\u{0}\u{0}chars\u{0}.example." diff --git a/xtests/madns/outputs/null.str.example.json b/xtests/madns/outputs/null.str.example.json deleted file mode 100644 index 18dd04c..0000000 --- a/xtests/madns/outputs/null.str.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "null.str.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "null.str.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "some\u0000null\u0000\u0000chars\u0000.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/openpgpkey.example.ansitxt b/xtests/madns/outputs/openpgpkey.example.ansitxt deleted file mode 100644 index 176b76b..0000000 --- a/xtests/madns/outputs/openpgpkey.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -OPENPGPKEY openpgpkey.example. 10m00s "EjRWeA==" diff --git a/xtests/madns/outputs/openpgpkey.example.json b/xtests/madns/outputs/openpgpkey.example.json deleted file mode 100644 index 4e0ad1a..0000000 --- a/xtests/madns/outputs/openpgpkey.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "openpgpkey.example.", - "class": "IN", - "type": "OPENPGPKEY" - } - ], - "answers": [ - { - "name": "openpgpkey.example.", - "class": "IN", - "ttl": 600, - "type": "OPENPGPKEY", - "data": { - "key": "EjRWeA==" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/opt.example.ansitxt b/xtests/madns/outputs/opt.example.ansitxt deleted file mode 100644 index 086fbd1..0000000 --- a/xtests/madns/outputs/opt.example.ansitxt +++ /dev/null @@ -1,2 +0,0 @@ - A opt.example. 10m00s 127.0.0.1 -OPT  + 1452 0 0 0 [] diff --git a/xtests/madns/outputs/opt.example.json b/xtests/madns/outputs/opt.example.json deleted file mode 100644 index b50e960..0000000 --- a/xtests/madns/outputs/opt.example.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "opt.example.", - "class": "IN", - "type": "A" - } - ], - "answers": [ - { - "name": "opt.example.", - "class": "IN", - "ttl": 600, - "type": "A", - "data": { - "address": "127.0.0.1" - } - } - ], - "authorities": [], - "additionals": [ - { - "name": "", - "type": "OPT", - "data": { - "version": 0, - "data": [] - } - } - ] - } - ] -} diff --git a/xtests/madns/outputs/other-flags.opt.example.ansitxt b/xtests/madns/outputs/other-flags.opt.example.ansitxt deleted file mode 100644 index 17d23de..0000000 --- a/xtests/madns/outputs/other-flags.opt.example.ansitxt +++ /dev/null @@ -1,2 +0,0 @@ - A other-flags.opt.example. 10m00s 127.0.0.1 -OPT  + 1452 0 0 32767 [] diff --git a/xtests/madns/outputs/other-flags.opt.example.json b/xtests/madns/outputs/other-flags.opt.example.json deleted file mode 100644 index 109e15c..0000000 --- a/xtests/madns/outputs/other-flags.opt.example.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "other-flags.opt.example.", - "class": "IN", - "type": "A" - } - ], - "answers": [ - { - "name": "other-flags.opt.example.", - "class": "IN", - "ttl": 600, - "type": "A", - "data": { - "address": "127.0.0.1" - } - } - ], - "authorities": [], - "additionals": [ - { - "name": "", - "type": "OPT", - "data": { - "version": 0, - "data": [] - } - } - ] - } - ] -} diff --git a/xtests/madns/outputs/others.caa.example.ansitxt b/xtests/madns/outputs/others.caa.example.ansitxt deleted file mode 100644 index d4f6f64..0000000 --- a/xtests/madns/outputs/others.caa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CAA caa.example. 10m00s "issuewild" "trustworthy.example" (non-critical) diff --git a/xtests/madns/outputs/others.caa.example.json b/xtests/madns/outputs/others.caa.example.json deleted file mode 100644 index c342ff7..0000000 --- a/xtests/madns/outputs/others.caa.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "caa.example.", - "class": "IN", - "type": "CAA" - } - ], - "answers": [ - { - "name": "caa.example.", - "class": "IN", - "ttl": 600, - "type": "CAA", - "data": { - "critical": false, - "tag": "issuewild", - "value": "trustworthy.example" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/ptr.example.ansitxt b/xtests/madns/outputs/ptr.example.ansitxt deleted file mode 100644 index 4e3cbe8..0000000 --- a/xtests/madns/outputs/ptr.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -PTR ptr.example. 10m00s "dns.example." diff --git a/xtests/madns/outputs/ptr.example.json b/xtests/madns/outputs/ptr.example.json deleted file mode 100644 index b49fe5d..0000000 --- a/xtests/madns/outputs/ptr.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "ptr.example.", - "class": "IN", - "type": "PTR" - } - ], - "answers": [ - { - "name": "ptr.example.", - "class": "IN", - "ttl": 600, - "type": "PTR", - "data": { - "cname": "dns.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/slash.uri.example.ansitxt b/xtests/madns/outputs/slash.uri.example.ansitxt deleted file mode 100644 index 013aa36..0000000 --- a/xtests/madns/outputs/slash.uri.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -URI slash.uri.example. 10m00s 10 1 "/" diff --git a/xtests/madns/outputs/soa.example.ansitxt b/xtests/madns/outputs/soa.example.ansitxt deleted file mode 100644 index 9dd66a4..0000000 --- a/xtests/madns/outputs/soa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -SOA soa.example. 10m00s "mname.example." "rname.example." 1564274434 1d0h00m00s 2h00m00s 7d0h00m00s 5m00s diff --git a/xtests/madns/outputs/soa.example.json b/xtests/madns/outputs/soa.example.json deleted file mode 100644 index a53514e..0000000 --- a/xtests/madns/outputs/soa.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "soa.example.", - "class": "IN", - "type": "SOA" - } - ], - "answers": [ - { - "name": "soa.example.", - "class": "IN", - "ttl": 600, - "type": "SOA", - "data": { - "mname": "mname.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/srv.example.ansitxt b/xtests/madns/outputs/srv.example.ansitxt deleted file mode 100644 index 9018933..0000000 --- a/xtests/madns/outputs/srv.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -SRV srv.example. 10m00s 1 1 "service.example.":37500 diff --git a/xtests/madns/outputs/srv.example.json b/xtests/madns/outputs/srv.example.json deleted file mode 100644 index 996056c..0000000 --- a/xtests/madns/outputs/srv.example.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "srv.example.", - "class": "IN", - "type": "SRV" - } - ], - "answers": [ - { - "name": "srv.example.", - "class": "IN", - "ttl": 600, - "type": "SRV", - "data": { - "priority": 1, - "weight": 1, - "port": 37500, - "target": "service.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/sshfp.example.ansitxt b/xtests/madns/outputs/sshfp.example.ansitxt deleted file mode 100644 index 4d9d369..0000000 --- a/xtests/madns/outputs/sshfp.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -SSHFP sshfp.example. 10m00s 1 1 212223242526 diff --git a/xtests/madns/outputs/sshfp.example.json b/xtests/madns/outputs/sshfp.example.json deleted file mode 100644 index 5c7f3c6..0000000 --- a/xtests/madns/outputs/sshfp.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "sshfp.example.", - "class": "IN", - "type": "SSHFP" - } - ], - "answers": [ - { - "name": "sshfp.example.", - "class": "IN", - "ttl": 600, - "type": "SSHFP", - "data": { - "algorithm": 1, - "fingerprint_type": 1, - "fingerprint": "212223242526" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/tab.str.example.ansitxt b/xtests/madns/outputs/tab.str.example.ansitxt deleted file mode 100644 index 9603003..0000000 --- a/xtests/madns/outputs/tab.str.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME tab.str.example. 10m00s "some\ttab\t\tchars\t.example." diff --git a/xtests/madns/outputs/tab.str.example.json b/xtests/madns/outputs/tab.str.example.json deleted file mode 100644 index 78e822f..0000000 --- a/xtests/madns/outputs/tab.str.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "tab.str.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "tab.str.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "some\ttab\t\tchars\t.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/tlsa.example.ansitxt b/xtests/madns/outputs/tlsa.example.ansitxt deleted file mode 100644 index 374fd51..0000000 --- a/xtests/madns/outputs/tlsa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -TLSA tlsa.example. 10m00s 3 1 1 "112233445566" diff --git a/xtests/madns/outputs/tlsa.example.json b/xtests/madns/outputs/tlsa.example.json deleted file mode 100644 index 11cdc29..0000000 --- a/xtests/madns/outputs/tlsa.example.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "tlsa.example.", - "class": "IN", - "type": "TLSA" - } - ], - "answers": [ - { - "name": "tlsa.example.", - "class": "IN", - "ttl": 600, - "type": "TLSA", - "data": { - "certificate_usage": 3, - "selector": 1, - "matching_type": 1, - "certificate_data": "112233445566" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/txt.example.ansitxt b/xtests/madns/outputs/txt.example.ansitxt deleted file mode 100644 index 13d4ef3..0000000 --- a/xtests/madns/outputs/txt.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -TXT txt.example. 10m00s "Cache Invalidation and Naming Things" diff --git a/xtests/madns/outputs/txt.example.json b/xtests/madns/outputs/txt.example.json deleted file mode 100644 index 78b8987..0000000 --- a/xtests/madns/outputs/txt.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "txt.example.", - "class": "IN", - "type": "TXT" - } - ], - "answers": [ - { - "name": "txt.example.", - "class": "IN", - "ttl": 600, - "type": "TXT", - "data": { - "messages": [ - "Cache Invalidation and Naming Things" - ] - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/upperbit.str.example.ansitxt b/xtests/madns/outputs/upperbit.str.example.ansitxt deleted file mode 100644 index 122fc42..0000000 --- a/xtests/madns/outputs/upperbit.str.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CNAME upperbit.str.example. 10m00s "\u{7f}�����.example." diff --git a/xtests/madns/outputs/upperbit.str.example.json b/xtests/madns/outputs/upperbit.str.example.json deleted file mode 100644 index 21c49ef..0000000 --- a/xtests/madns/outputs/upperbit.str.example.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "upperbit.str.example.", - "class": "IN", - "type": "CNAME" - } - ], - "answers": [ - { - "name": "upperbit.str.example.", - "class": "IN", - "ttl": 600, - "type": "CNAME", - "data": { - "domain": "\u007f�����.example." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/uri.example.ansitxt b/xtests/madns/outputs/uri.example.ansitxt deleted file mode 100644 index 417dbaf..0000000 --- a/xtests/madns/outputs/uri.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -URI uri.example. 10m00s 10 16 "https://rfcs.io/" diff --git a/xtests/madns/outputs/uri.example.json b/xtests/madns/outputs/uri.example.json deleted file mode 100644 index 0c208fb..0000000 --- a/xtests/madns/outputs/uri.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "uri.example.", - "class": "IN", - "type": "URI" - } - ], - "answers": [ - { - "name": "uri.example.", - "class": "IN", - "ttl": 600, - "type": "URI", - "data": { - "priority": 10, - "weight": 16, - "target": "https://rfcs.io/" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/utf8.caa.example.ansitxt b/xtests/madns/outputs/utf8.caa.example.ansitxt deleted file mode 100644 index 83bcfdb..0000000 --- a/xtests/madns/outputs/utf8.caa.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -CAA utf8.caa.example. 10m00s "issuewild" "trustworthy\240\159\140\180example" (non-critical) diff --git a/xtests/madns/outputs/utf8.caa.example.json b/xtests/madns/outputs/utf8.caa.example.json deleted file mode 100644 index 0f9f85e..0000000 --- a/xtests/madns/outputs/utf8.caa.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "utf8.caa.example.", - "class": "IN", - "type": "CAA" - } - ], - "answers": [ - { - "name": "utf8.caa.example.", - "class": "IN", - "ttl": 600, - "type": "CAA", - "data": { - "critical": false, - "tag": "issuewild", - "value": "trustworthy🌴example" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/utf8.hinfo.example.ansitxt b/xtests/madns/outputs/utf8.hinfo.example.ansitxt deleted file mode 100644 index 3fc8068..0000000 --- a/xtests/madns/outputs/utf8.hinfo.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -HINFO utf8.hinfo.example. 10m00s "some\240\159\140\180kinda\240\159\140\180cpu" "some\240\159\140\180kinda\240\159\140\180os" diff --git a/xtests/madns/outputs/utf8.hinfo.example.json b/xtests/madns/outputs/utf8.hinfo.example.json deleted file mode 100644 index 785f4fa..0000000 --- a/xtests/madns/outputs/utf8.hinfo.example.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "utf8.hinfo.example.", - "class": "IN", - "type": "HINFO" - } - ], - "answers": [ - { - "name": "utf8.hinfo.example.", - "class": "IN", - "ttl": 600, - "type": "HINFO", - "data": { - "cpu": "some🌴kinda🌴cpu", - "os": "some🌴kinda🌴os" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/utf8.naptr.invalid.ansitxt b/xtests/madns/outputs/utf8.naptr.invalid.ansitxt deleted file mode 100644 index 47801f8..0000000 --- a/xtests/madns/outputs/utf8.naptr.invalid.ansitxt +++ /dev/null @@ -1 +0,0 @@ -NAPTR utf8.naptr.invalid. 10m00s 5 10 "\240\159\140\180" "\240\159\140\180" "\240\159\140\180" "🌴." diff --git a/xtests/madns/outputs/utf8.naptr.invalid.json b/xtests/madns/outputs/utf8.naptr.invalid.json deleted file mode 100644 index 9ce6707..0000000 --- a/xtests/madns/outputs/utf8.naptr.invalid.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "utf8.naptr.invalid.", - "class": "IN", - "type": "NAPTR" - } - ], - "answers": [ - { - "name": "utf8.naptr.invalid.", - "class": "IN", - "ttl": 600, - "type": "NAPTR", - "data": { - "order": 5, - "flags": "🌴", - "service": "🌴", - "regex": "🌴", - "replacement": "🌴." - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/utf8.txt.example.ansitxt b/xtests/madns/outputs/utf8.txt.example.ansitxt deleted file mode 100644 index ff16995..0000000 --- a/xtests/madns/outputs/utf8.txt.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -TXT utf8.txt.example. 10m00s "\240\159\146\176Cache \240\159\153\133\226\128\141\239\184\143Invalidation \226\133\139and \240\159\147\155Naming \240\159\142\179Things" diff --git a/xtests/madns/outputs/utf8.txt.example.json b/xtests/madns/outputs/utf8.txt.example.json deleted file mode 100644 index 2521272..0000000 --- a/xtests/madns/outputs/utf8.txt.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "utf8.txt.example.", - "class": "IN", - "type": "TXT" - } - ], - "answers": [ - { - "name": "utf8.txt.example.", - "class": "IN", - "ttl": 600, - "type": "TXT", - "data": { - "messages": [ - "💰Cache 🙅‍️Invalidation ⅋and 📛Naming 🎳Things" - ] - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/outputs/utf8.uri.example.ansitxt b/xtests/madns/outputs/utf8.uri.example.ansitxt deleted file mode 100644 index b300a71..0000000 --- a/xtests/madns/outputs/utf8.uri.example.ansitxt +++ /dev/null @@ -1 +0,0 @@ -URI utf8.uri.example. 10m00s 10 16 "https://\240\159\146\169.la/" diff --git a/xtests/madns/outputs/utf8.uri.example.json b/xtests/madns/outputs/utf8.uri.example.json deleted file mode 100644 index 75fb87a..0000000 --- a/xtests/madns/outputs/utf8.uri.example.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "responses": [ - { - "queries": [ - { - "name": "utf8.uri.example.", - "class": "IN", - "type": "URI" - } - ], - "answers": [ - { - "name": "utf8.uri.example.", - "class": "IN", - "ttl": 600, - "type": "URI", - "data": { - "priority": 10, - "weight": 16, - "target": "https://💩.la/" - } - } - ], - "authorities": [], - "additionals": [] - } - ] -} diff --git a/xtests/madns/protocol-chars.toml b/xtests/madns/protocol-chars.toml deleted file mode 100644 index 105902e..0000000 --- a/xtests/madns/protocol-chars.toml +++ /dev/null @@ -1,84 +0,0 @@ -# Character escaping - -[[cmd]] -name = "Running with ‘ansi.str.example’ properly escapes the codes" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME ansi.str.example" -stdout = { file = "outputs/ansi.str.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘newline.str.example’ properly escapes the newlines" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME newline.str.example" -stdout = { file = "outputs/newline.str.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘null.str.example’ properly handles the null bytes" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME null.str.example" -stdout = { file = "outputs/null.str.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘tab.str.example’ properly escapes the tabs" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME tab.str.example" -stdout = { file = "outputs/tab.str.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘upperbit.str.example’ properly escapes the upper-bit characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME upperbit.str.example" -stdout = { file = "outputs/upperbit.str.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - - -# Character escaping (JSON) - -[[cmd]] -name = "Running with ‘ansi.str.example --json’ properly escapes the codes in the JSON string" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME ansi.str.example --json | jq" -stdout = { file = "outputs/ansi.str.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns", "json" ] - -[[cmd]] -name = "Running with ‘newline.str.example --json’ properly escapes the newlines in the JSON string" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME newline.str.example --json | jq" -stdout = { file = "outputs/newline.str.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns", "json" ] - -[[cmd]] -name = "Running with ‘null.str.example --json’ properly handles the null bytes in the JSON string" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME null.str.example --json | jq" -stdout = { file = "outputs/null.str.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns", "json" ] - -[[cmd]] -name = "Running with ‘tab.str.example --json’ properly escapes the tabs in the JSON string" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME tab.str.example --json | jq" -stdout = { file = "outputs/tab.str.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns", "json" ] - -[[cmd]] -name = "Running with ‘upperbit.str.example --json’ properly escapes the upper-bit characters in the JSON string" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME upperbit.str.example --json | jq" -stdout = { file = "outputs/upperbit.str.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns", "json" ] diff --git a/xtests/madns/protocol-compression.toml b/xtests/madns/protocol-compression.toml deleted file mode 100644 index 1270a2d..0000000 --- a/xtests/madns/protocol-compression.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[cmd]] -name = "Running with ‘out-of-range.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A out-of-range.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘recursive-1.invalid’ displays a recursion error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A recursive-1.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: too much recursion: [37]" } -status = 1 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘recursive-2.invalid’ displays a recursion error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A recursive-2.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: too much recursion: [53, 37]" } -status = 1 -tags = [ "protocol", "madns" ] diff --git a/xtests/madns/protocol-error-codes.toml b/xtests/madns/protocol-error-codes.toml deleted file mode 100644 index 10a0e75..0000000 --- a/xtests/madns/protocol-error-codes.toml +++ /dev/null @@ -1,39 +0,0 @@ -[[cmd]] -name = "Running with ‘formerr.invalid’ displays the error code" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A formerr.invalid" -stdout = { string = "Status: Format Error" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘servfail.invalid’ displays the error code" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A servfail.invalid" -stdout = { string = "Status: Server Failure" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘nxdomain.invalid’ displays the error code" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A nxdomain.invalid" -stdout = { string = "Status: NXDomain" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘notimp.invalid’ displays the error code" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A notimp.invalid" -stdout = { string = "Status: Not Implemented" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] - -[[cmd]] -name = "Running with ‘refused.invalid’ displays the error code" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A refused.invalid" -stdout = { string = "Status: Query Refused" } -stderr = { empty = true } -status = 0 -tags = [ "protocol", "madns" ] diff --git a/xtests/madns/ptr-records.toml b/xtests/madns/ptr-records.toml deleted file mode 100644 index 4d610d3..0000000 --- a/xtests/madns/ptr-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# PTR record successes - -[[cmd]] -name = "Running with ‘ptr.example’ prints the correct PTR record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR ptr.example" -stdout = { file = "outputs/ptr.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "ptr", "madns" ] - - -# PTR record successes (JSON) - -[[cmd]] -name = "Running with ‘ptr.example --json’ prints the correct PTR record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR ptr.example --json | jq" -stdout = { file = "outputs/ptr.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "ptr", "madns", "json" ] - - -# PTR record invalid packets - -[[cmd]] -name = "Running with ‘empty.ptr.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR empty.ptr.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "ptr", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.ptr.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR incomplete.ptr.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "ptr", "madns" ] diff --git a/xtests/madns/soa-records.toml b/xtests/madns/soa-records.toml deleted file mode 100644 index 57091dd..0000000 --- a/xtests/madns/soa-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# SOA record successes - -[[cmd]] -name = "Running with ‘soa.example’ prints the correct SOA record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA soa.example" -stdout = { file = "outputs/soa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "soa", "madns" ] - - -# SOA record successes (JSON) - -[[cmd]] -name = "Running with ‘soa.example --json’ prints the correct SOA record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA soa.example --json | jq" -stdout = { file = "outputs/soa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "soa", "madns", "json" ] - - -# SOA record invalid packets - -[[cmd]] -name = "Running with ‘empty.soa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA empty.soa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "soa", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.soa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA incomplete.soa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "soa", "madns" ] diff --git a/xtests/madns/srv-records.toml b/xtests/madns/srv-records.toml deleted file mode 100644 index f89f057..0000000 --- a/xtests/madns/srv-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# SRV record successes - -[[cmd]] -name = "Running with ‘srv.example’ prints the correct SRV record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV srv.example" -stdout = { file = "outputs/srv.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "soa", "madns" ] - - -# SRV record successes (JSON) - -[[cmd]] -name = "Running with ‘srv.example --json’ prints the correct SRV record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV srv.example --json | jq" -stdout = { file = "outputs/srv.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "soa", "madns", "json" ] - - -# SRV record invalid packets - -[[cmd]] -name = "Running with ‘empty.srv.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV empty.srv.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "soa", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.srv.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV incomplete.srv.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "soa", "madns" ] diff --git a/xtests/madns/sshfp-records.toml b/xtests/madns/sshfp-records.toml deleted file mode 100644 index ebf04ff..0000000 --- a/xtests/madns/sshfp-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# SSHFP record successes - -[[cmd]] -name = "Running with ‘sshfp.example’ prints the correct SSHFP record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP sshfp.example" -stdout = { file = "outputs/sshfp.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "sshfp", "madns" ] - - -# SSHFP record successes (JSON) - -[[cmd]] -name = "Running with ‘sshfp.example --json’ prints the correct SSHFP record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP sshfp.example --json | jq" -stdout = { file = "outputs/sshfp.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "sshfp", "madns", "json" ] - - -# SSHFP record invalid packets - -[[cmd]] -name = "Running with ‘empty.sshfp.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP empty.sshfp.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "sshfp", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.sshfp.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP incomplete.sshfp.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "sshfp", "madns" ] diff --git a/xtests/madns/tlsa-records.toml b/xtests/madns/tlsa-records.toml deleted file mode 100644 index 20ae80b..0000000 --- a/xtests/madns/tlsa-records.toml +++ /dev/null @@ -1,39 +0,0 @@ -# TLSA record successes - -[[cmd]] -name = "Running with ‘tlsa.example’ prints the correct TLSA record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA tlsa.example" -stdout = { file = "outputs/tlsa.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "tlsa", "madns" ] - - -# TLSA record successes (JSON) - -[[cmd]] -name = "Running with ‘tlsa.example --json’ prints the correct TLSA record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA tlsa.example --json | jq" -stdout = { file = "outputs/tlsa.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "tlsa", "madns", "json" ] - - -# TLSA record invalid packets - -[[cmd]] -name = "Running with ‘empty.tlsa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA empty.tlsa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "tlsa", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.tlsa.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA incomplete.tlsa.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "tlsa", "madns" ] diff --git a/xtests/madns/txt-records.toml b/xtests/madns/txt-records.toml deleted file mode 100644 index f19d497..0000000 --- a/xtests/madns/txt-records.toml +++ /dev/null @@ -1,72 +0,0 @@ -# TXT record successes - -[[cmd]] -name = "Running with ‘txt.example’ prints the correct TXT record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT txt.example" -stdout = { file = "outputs/txt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns" ] - -[[cmd]] -name = "Running with ‘utf8.txt.example’ escapes characters in the message" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT utf8.txt.example" -stdout = { file = "outputs/utf8.txt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns", "chars" ] - -[[cmd]] -name = "Running with ‘bad-utf8.txt.example’ escapes characters in the message and does not crash" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT bad-utf8.txt.example" -stdout = { file = "outputs/bad-utf8.txt.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns", "chars" ] - - -# TXT record successes (JSON) - -[[cmd]] -name = "Running with ‘txt.example --json’ prints the correct TXT record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT txt.example --json | jq" -stdout = { file = "outputs/txt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns", "json" ] - - -[[cmd]] -name = "Running with ‘utf8.txt.example --json’ interprets the response as UTF-8" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT utf8.txt.example --json | jq" -stdout = { file = "outputs/utf8.txt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns", "chars", "json" ] - -[[cmd]] -name = "Running with ‘bad-utf8.txt.example --json’ uses UTF-8 replacement characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT bad-utf8.txt.example --json | jq" -stdout = { file = "outputs/bad-utf8.txt.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "txt", "madns", "chars", "json" ] - - -# TXT record invalid packets - -[[cmd]] -name = "Running with ‘empty.txt.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT empty.txt.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "txt", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.txt.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT incomplete.txt.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "txt", "madns" ] diff --git a/xtests/madns/uri-records.toml b/xtests/madns/uri-records.toml deleted file mode 100644 index 995bbca..0000000 --- a/xtests/madns/uri-records.toml +++ /dev/null @@ -1,87 +0,0 @@ -# URI record successes - -[[cmd]] -name = "Running with ‘uri.example’ prints the correct URI record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI uri.example" -stdout = { file = "outputs/uri.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns" ] - -[[cmd]] -name = "Running with ‘slash.uri.example’ still prints the correct URI record" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI slash.uri.example" -stdout = { file = "outputs/slash.uri.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns" ] - -[[cmd]] -name = "Running with ‘utf8.uri.example’ escapes characters in the URI" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI utf8.uri.example" -stdout = { file = "outputs/utf8.uri.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns", "chars" ] - -[[cmd]] -name = "Running with ‘bad-utf8.uri.example’ escapes characters in the URI and does not crash" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI bad-utf8.uri.example" -stdout = { file = "outputs/bad-utf8.uri.example.ansitxt" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns", "chars" ] - - -# URI record successes (JSON) - -[[cmd]] -name = "Running with ‘uri.example --json’ prints the correct URI record structure" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI uri.example --json | jq" -stdout = { file = "outputs/uri.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns", "json" ] - -[[cmd]] -name = "Running with ‘utf8.uri.example --json’ interprets the response as UTF-8" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI utf8.uri.example --json | jq" -stdout = { file = "outputs/utf8.uri.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns", "chars", "json" ] - -[[cmd]] -name = "Running with ‘bad-utf8.uri.example --json’ uses UTF-8 replacement characters" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI bad-utf8.uri.example --json | jq" -stdout = { file = "outputs/bad-utf8.uri.example.json" } -stderr = { empty = true } -status = 0 -tags = [ "uri", "madns", "chars", "json" ] - - -# URI record invalid packets - -[[cmd]] -name = "Running with ‘missing-data.uri.invalid’ displays a packet length error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI missing-data.uri.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: record length should be at least 5, got 4" } -status = 1 -tags = [ "uri", "madns" ] - -[[cmd]] -name = "Running with ‘empty.uri.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI empty.uri.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "uri", "madns" ] - -[[cmd]] -name = "Running with ‘incomplete.uri.invalid’ displays a protocol error" -shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI incomplete.uri.invalid" -stdout = { empty = true } -stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } -status = 1 -tags = [ "uri", "madns" ] diff --git a/xtests/options/errors.toml b/xtests/options/errors.toml deleted file mode 100644 index 363dd1a..0000000 --- a/xtests/options/errors.toml +++ /dev/null @@ -1,87 +0,0 @@ -[[cmd]] -name = "Running dog with ‘--wibble’ warns about the invalid argument" -shell = "dog --wibble" -stdout = { empty = true } -stderr = { file = "outputs/invalid-argument.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--class’ warns about the missing argument parameter" -shell = "dog --class" -stdout = { empty = true } -stderr = { file = "outputs/missing-parameter.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--type XYZZY’ warns about the invalid record type" -shell = "dog --type XYZZY dns.google" -stdout = { empty = true } -stderr = { file = "outputs/invalid-query-type.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--class XYZZY’ warns about the invalid class" -shell = "dog --class XYZZY dns.google" -stdout = { empty = true } -stderr = { file = "outputs/invalid-query-class.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘-Z aoeu’ warns about the invalid protocol tweak" -shell = "dog -Z aoeu dns.google" -stdout = { empty = true } -stderr = { file = "outputs/invalid-protocol-tweak.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘OPT’ warns that OPT requests are sent by default" -shell = "dog OPT dns.google" -stdout = { empty = true } -stderr = { file = "outputs/opt-query.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘opt’ also warns that OPT requests are sent by default" -shell = "dog opt dns.google" -stdout = { empty = true } -stderr = { file = "outputs/opt-query.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--type OPT’ warns that OPT requests are sent by default" -shell = "dog --type OPT dns.google" -stdout = { empty = true } -stderr = { file = "outputs/opt-query.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--type opt’ also warns that OPT requests are sent by default" -shell = "dog --type opt dns.google" -stdout = { empty = true } -stderr = { file = "outputs/opt-query.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with a domain longer than 255 bytes warns about it being too long" -shell = "dog 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" -stdout = { empty = true } -stderr = { file = "outputs/huge-domain.txt" } -status = 3 -tags = [ 'options' ] - -[[cmd]] -name = "Running dog with ‘--https’ and no nameserver warns that one is missing" -shell = "dog --https dns.google" -stdout = { empty = true } -stderr = { file = "outputs/missing-nameserver.txt" } -status = 3 -tags = [ 'options' ] diff --git a/xtests/options/help.toml b/xtests/options/help.toml deleted file mode 100644 index 113afd9..0000000 --- a/xtests/options/help.toml +++ /dev/null @@ -1,76 +0,0 @@ -# help - -[[cmd]] -name = "Running ‘dog --help’ shows help" -shell = "dog --help" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --help --color=automatic’ not to a terminal shows help without colour" -shell = "dog --help --color=automatic" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --help --colour=always’ shows help with colour" -shell = "dog --help --colour=always" -stdout = { string = "dog \u001B[1;32m●\u001B[0m" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --help --color=never’ shows without colour" -shell = "dog --help --color=never" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog’ with no arguments shows help" -shell = "dog" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 3 -tags = [ 'options' ] - - -# versions - -[[cmd]] -name = "Running ‘dog --version’ shows version information" -shell = "dog --version" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --version --colour=automatic’ not to a terminal shows version information without colour" -shell = "dog --version --colour=automatic" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --version --color=always’ shows version information with colour" -shell = "dog --version --color=always" -stdout = { string = "dog \u001B[1;32m●\u001B[0m" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] - -[[cmd]] -name = "Running ‘dog --version --colour=never’ shows version information without colour" -shell = "dog --version --colour=never" -stdout = { string = "dog ●" } -stderr = { empty = true } -status = 0 -tags = [ 'options' ] diff --git a/xtests/options/outputs/huge-domain.txt b/xtests/options/outputs/huge-domain.txt deleted file mode 100644 index 8303bd3..0000000 --- a/xtests/options/outputs/huge-domain.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Invalid domain "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" diff --git a/xtests/options/outputs/invalid-argument.txt b/xtests/options/outputs/invalid-argument.txt deleted file mode 100644 index 7c9d3ed..0000000 --- a/xtests/options/outputs/invalid-argument.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Unrecognized option: 'wibble' diff --git a/xtests/options/outputs/invalid-protocol-tweak.txt b/xtests/options/outputs/invalid-protocol-tweak.txt deleted file mode 100644 index ae8b8e8..0000000 --- a/xtests/options/outputs/invalid-protocol-tweak.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Invalid protocol tweak "aoeu" diff --git a/xtests/options/outputs/invalid-query-class.txt b/xtests/options/outputs/invalid-query-class.txt deleted file mode 100644 index 0366b6d..0000000 --- a/xtests/options/outputs/invalid-query-class.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Invalid query class "XYZZY" diff --git a/xtests/options/outputs/invalid-query-type.txt b/xtests/options/outputs/invalid-query-type.txt deleted file mode 100644 index 4283de3..0000000 --- a/xtests/options/outputs/invalid-query-type.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Invalid query type "XYZZY" diff --git a/xtests/options/outputs/missing-nameserver.txt b/xtests/options/outputs/missing-nameserver.txt deleted file mode 100644 index 75f26bc..0000000 --- a/xtests/options/outputs/missing-nameserver.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: You must pass a URL as a nameserver when using --https diff --git a/xtests/options/outputs/missing-parameter.txt b/xtests/options/outputs/missing-parameter.txt deleted file mode 100644 index 3f662b8..0000000 --- a/xtests/options/outputs/missing-parameter.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: Argument to option 'class' missing diff --git a/xtests/options/outputs/opt-query.txt b/xtests/options/outputs/opt-query.txt deleted file mode 100644 index 96fa19b..0000000 --- a/xtests/options/outputs/opt-query.txt +++ /dev/null @@ -1 +0,0 @@ -dog: Invalid options: OPT request is sent by default (see -Z flag) From 47173a49fec70d2c938306865fe4feb7741dc11b Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:44:14 -0700 Subject: [PATCH 11/46] docs: Update man page and help text for ANY query type --- man/dog.1.md | 4 ++-- src/options.rs | 2 +- src/usage.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/man/dog.1.md b/man/dog.1.md index aadad36..2ecd854 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -46,7 +46,7 @@ QUERY OPTIONS : Host name or domain name to query. `-t`, `--type=TYPE` -: Type of the DNS record being queried (`A`, `MX`, `NS`...) +: Type of the DNS record being queried (`A`, `MX`, `NS`, `ANY`...) `-n`, `--nameserver=ADDR` : Address of the nameserver to send packets to. @@ -196,7 +196,7 @@ When a response DNS packet contains a record of one of these known types, dog wi Records with a type number that does not map to any known record type will still be displayed. As they cannot be interpreted, their contents will be displayed as a series of numbers instead. -dog also contains a list of record type names that it knows the type number of, but is not able to interpret, such as `IXFR` or `ANY` or `AFSDB`. These are acceptable as command-line arguments, meaning you can send an AFSDB request with ‘`dog AFSDB`’. However, their response contents will still be displayed as numbers. They may be supported in future versions of dog. +dog also contains a list of record type names that it knows the type number of, but is not able to interpret, such as `IXFR` or `AFSDB`. These are acceptable as command-line arguments, meaning you can send an AFSDB request with ‘`dog AFSDB`’. However, their response contents will still be displayed as numbers. They may be supported in future versions of dog. PROTOCOL TWEAKS diff --git a/src/options.rs b/src/options.rs index d658be0..c84835b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -48,7 +48,7 @@ impl Options { // Query options opts.optmulti("q", "query", "Host name or domain name to query", "HOST"); - opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS...)", "TYPE"); + opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS, ANY...)", "TYPE"); opts.optmulti("n", "nameserver", "Address of the nameserver to send packets to", "ADDR"); opts.optmulti("", "class", "Network class of the DNS record being queried (IN, CH, HS)", "CLASS"); diff --git a/src/usage.txt b/src/usage.txt index 29934f1..a70a29d 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -11,7 +11,7 @@ \4mQuery options:\0m \32m\0m Human-readable host names, nameservers, types, or classes \1;33m-q\0m, \1;33m--query\0m=\33mHOST\0m Host name or domain name to query - \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS...) + \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS, ANY...) \1;33m-n\0m, \1;33m--nameserver\0m=\33mADDR\0m Address of the nameserver to send packets to \1;33m--class\0m=\33mCLASS\0m Network class of the DNS record being queried (IN, CH, HS) From 7b982e9fc273c4891409f4dc8667bc18a7102f3d Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:46:06 -0700 Subject: [PATCH 12/46] Revert "docs: Update man page and help text for ANY query type" This reverts commit 47173a49fec70d2c938306865fe4feb7741dc11b. --- man/dog.1.md | 4 ++-- src/options.rs | 2 +- src/usage.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/man/dog.1.md b/man/dog.1.md index 2ecd854..aadad36 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -46,7 +46,7 @@ QUERY OPTIONS : Host name or domain name to query. `-t`, `--type=TYPE` -: Type of the DNS record being queried (`A`, `MX`, `NS`, `ANY`...) +: Type of the DNS record being queried (`A`, `MX`, `NS`...) `-n`, `--nameserver=ADDR` : Address of the nameserver to send packets to. @@ -196,7 +196,7 @@ When a response DNS packet contains a record of one of these known types, dog wi Records with a type number that does not map to any known record type will still be displayed. As they cannot be interpreted, their contents will be displayed as a series of numbers instead. -dog also contains a list of record type names that it knows the type number of, but is not able to interpret, such as `IXFR` or `AFSDB`. These are acceptable as command-line arguments, meaning you can send an AFSDB request with ‘`dog AFSDB`’. However, their response contents will still be displayed as numbers. They may be supported in future versions of dog. +dog also contains a list of record type names that it knows the type number of, but is not able to interpret, such as `IXFR` or `ANY` or `AFSDB`. These are acceptable as command-line arguments, meaning you can send an AFSDB request with ‘`dog AFSDB`’. However, their response contents will still be displayed as numbers. They may be supported in future versions of dog. PROTOCOL TWEAKS diff --git a/src/options.rs b/src/options.rs index c84835b..d658be0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -48,7 +48,7 @@ impl Options { // Query options opts.optmulti("q", "query", "Host name or domain name to query", "HOST"); - opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS, ANY...)", "TYPE"); + opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS...)", "TYPE"); opts.optmulti("n", "nameserver", "Address of the nameserver to send packets to", "ADDR"); opts.optmulti("", "class", "Network class of the DNS record being queried (IN, CH, HS)", "CLASS"); diff --git a/src/usage.txt b/src/usage.txt index a70a29d..29934f1 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -11,7 +11,7 @@ \4mQuery options:\0m \32m\0m Human-readable host names, nameservers, types, or classes \1;33m-q\0m, \1;33m--query\0m=\33mHOST\0m Host name or domain name to query - \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS, ANY...) + \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS...) \1;33m-n\0m, \1;33m--nameserver\0m=\33mADDR\0m Address of the nameserver to send packets to \1;33m--class\0m=\33mCLASS\0m Network class of the DNS record being queried (IN, CH, HS) From c3d99409b69c95194cd23ec68add0d3bebfdfd58 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:52:54 -0700 Subject: [PATCH 13/46] docs: Add description for ANY query type to man page --- man/dog.1.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/dog.1.md b/man/dog.1.md index aadad36..079124b 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -150,6 +150,9 @@ dog understands and can interpret the following record types: `AAAA` : IPv6 addresses +`ANY` +: all cached records + `CAA` : permitted certificate authorities From ac4215483295e47782f81b5c33277aa720397ed7 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:55:36 -0700 Subject: [PATCH 14/46] docs: Improve accuracy of ANY query type description --- man/dog.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/dog.1.md b/man/dog.1.md index 079124b..1c1145a 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -151,7 +151,7 @@ dog understands and can interpret the following record types: : IPv6 addresses `ANY` -: all cached records +: all available records for a name `CAA` : permitted certificate authorities From 250775a3cf41fd9c76b635beef66ebe1bb274961 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:57:29 -0700 Subject: [PATCH 15/46] Add comprehensive DNSSEC and extended record type support - Add support for DNSSEC record types: * DS (Delegation Signer) - RFC 4034 * RRSIG (DNSSEC Signature) - RFC 4034 * NSEC (Next Secure) - RFC 4034 * DNSKEY (DNS Key) - RFC 4034 * NSEC3 (Next Secure v3) - RFC 5155 * NSEC3PARAM (NSEC3 Parameters) - RFC 5155 * IPSECKEY (IPsec Key) - RFC 4025 * DHCID (DHCP Identifier) - RFC 4701 - Add support for SMIMEA record type (S/MIME certificate association) - RFC 8162 - Implement full parsing, validation, and display for all new record types - Add JSON output serialization for all new types - Add colored terminal output for new record types - Include comprehensive test coverage for all new implementations - Fix compilation issue: missing closing brace in output match statement - Improve ANY query handling: suppress Format Error when server provides records - Update all integration points: parsing, display, JSON, and coloring All new record types follow the established patterns for error handling, length validation, and output formatting. Tests pass for all implementations. --- dns/src/record/dhcid.rs | 81 ++++++++++++++++++ dns/src/record/dnskey.rs | 89 ++++++++++++++++++++ dns/src/record/ds.rs | 89 ++++++++++++++++++++ dns/src/record/ipseckey.rs | 115 +++++++++++++++++++++++++ dns/src/record/mod.rs | 81 ++++++++++++++++++ dns/src/record/nsec.rs | 75 +++++++++++++++++ dns/src/record/nsec3.rs | 122 +++++++++++++++++++++++++++ dns/src/record/nsec3param.rs | 97 +++++++++++++++++++++ dns/src/record/rrsig.rs | 141 +++++++++++++++++++++++++++++++ dns/src/record/smimea.rs | 135 ++++++++++++++++++++++++++++++ dns/src/wire.rs | 9 ++ src/output.rs | 158 ++++++++++++++++++++++++++++++++++- src/table.rs | 9 ++ 13 files changed, 1198 insertions(+), 3 deletions(-) create mode 100644 dns/src/record/dhcid.rs create mode 100644 dns/src/record/dnskey.rs create mode 100644 dns/src/record/ds.rs create mode 100644 dns/src/record/ipseckey.rs create mode 100644 dns/src/record/nsec.rs create mode 100644 dns/src/record/nsec3.rs create mode 100644 dns/src/record/nsec3param.rs create mode 100644 dns/src/record/rrsig.rs create mode 100644 dns/src/record/smimea.rs diff --git a/dns/src/record/dhcid.rs b/dns/src/record/dhcid.rs new file mode 100644 index 0000000..cda7a00 --- /dev/null +++ b/dns/src/record/dhcid.rs @@ -0,0 +1,81 @@ +use log::*; + +use crate::wire::*; + +/// A **DHCID** record, which identifies the client to the DHCP server for DNS updates. +/// +/// # References +/// +/// - [RFC 4701](https://tools.ietf.org/html/rfc4701) — A DNS Resource Record (RR) for Encoding DHCP Information (October 2006) +#[derive(PartialEq, Debug)] +pub struct DHCID { + /// The identifier type code, indicating how the client identifier was formed. + pub identifier_type_code: u8, + + /// The digest type code, indicating the algorithm used to create the digest. + pub digest_type_code: u8, + + /// The digest of the client identifier. + pub digest: Vec, +} + +impl Wire for DHCID { + const NAME: &'static str = "DHCID"; + const RR_TYPE: u16 = 49; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let identifier_type_code = c.read_u8()?; + trace!("Parsed identifier_type_code -> {:?}", identifier_type_code); + + let digest_type_code = c.read_u8()?; + trace!("Parsed digest_type_code -> {:?}", digest_type_code); + + let digest_len = stated_length - 2; + let mut digest = vec![]; + for _ in 0..digest_len { + digest.push(c.read_u8()?); + } + trace!("Parsed digest -> {:?}", digest); + + Ok(Self { identifier_type_code, digest_type_code, digest }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x00, // identifier_type_code + 0x01, // digest_type_code + 0x12, 0x34, 0x56, 0x78, // digest (4 bytes for example) + ]; + + assert_eq!(DHCID::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + DHCID { + identifier_type_code: 0, + digest_type_code: 1, + digest: vec![0x12, 0x34, 0x56, 0x78], + }); + } + + #[test] + fn record_empty() { + assert_eq!(DHCID::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x00, // only one byte + ]; + + assert_eq!(DHCID::read(4, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/dnskey.rs b/dns/src/record/dnskey.rs new file mode 100644 index 0000000..e36004c --- /dev/null +++ b/dns/src/record/dnskey.rs @@ -0,0 +1,89 @@ +use log::*; + +use crate::wire::*; + +/// A **DNSKEY** record, which contains a public key for DNSSEC. +/// +/// # References +/// +/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) +#[derive(PartialEq, Debug)] +pub struct DNSKEY { + /// The flags field indicates the key's properties. + pub flags: u16, + + /// The protocol field must be set to 3. + pub protocol: u8, + + /// The algorithm field indicates the algorithm used to generate the key. + pub algorithm: u8, + + /// The public key itself. + pub public_key: Vec, +} + +impl Wire for DNSKEY { + const NAME: &'static str = "DNSKEY"; + const RR_TYPE: u16 = 48; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let flags = c.read_u16::()?; + trace!("Parsed flags -> {:?}", flags); + + let protocol = c.read_u8()?; + trace!("Parsed protocol -> {:?}", protocol); + + let algorithm = c.read_u8()?; + trace!("Parsed algorithm -> {:?}", algorithm); + + let public_key_len = stated_length - 4; + let mut public_key = vec![]; + for _ in 0..public_key_len { + public_key.push(c.read_u8()?); + } + trace!("Parsed public_key -> {:?}", public_key); + + Ok(Self { flags, protocol, algorithm, public_key }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x00, 0x01, // flags + 0x03, // protocol + 0x05, // algorithm + 0x12, 0x34, 0x56, 0x78, // public_key (4 bytes for example) + ]; + + assert_eq!(DNSKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + DNSKEY { + flags: 1, + protocol: 3, + algorithm: 5, + public_key: vec![0x12, 0x34, 0x56, 0x78], + }); + } + + #[test] + fn record_empty() { + assert_eq!(DNSKEY::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x00, // half a flags + ]; + + assert_eq!(DNSKEY::read(6, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/ds.rs b/dns/src/record/ds.rs new file mode 100644 index 0000000..44f8468 --- /dev/null +++ b/dns/src/record/ds.rs @@ -0,0 +1,89 @@ +use log::*; + +use crate::wire::*; + +/// A **DS** record, which contains a delegation signer for DNSSEC. +/// +/// # References +/// +/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) +#[derive(PartialEq, Debug)] +pub struct DS { + /// The key tag of the DNSKEY RR that the DS record refers to. + pub key_tag: u16, + + /// The algorithm number of the DNSKEY RR that the DS record refers to. + pub algorithm: u8, + + /// The algorithm used to construct the digest. + pub digest_type: u8, + + /// The digest of the DNSKEY RR. + pub digest: Vec, +} + +impl Wire for DS { + const NAME: &'static str = "DS"; + const RR_TYPE: u16 = 43; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let key_tag = c.read_u16::()?; + trace!("Parsed key_tag -> {:?}", key_tag); + + let algorithm = c.read_u8()?; + trace!("Parsed algorithm -> {:?}", algorithm); + + let digest_type = c.read_u8()?; + trace!("Parsed digest_type -> {:?}", digest_type); + + let digest_len = stated_length - 4; + let mut digest = vec![]; + for _ in 0..digest_len { + digest.push(c.read_u8()?); + } + trace!("Parsed digest -> {:?}", digest); + + Ok(Self { key_tag, algorithm, digest_type, digest }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x00, 0x01, // key_tag + 0x05, // algorithm + 0x01, // digest_type + 0x12, 0x34, 0x56, 0x78, // digest (4 bytes for example) + ]; + + assert_eq!(DS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + DS { + key_tag: 1, + algorithm: 5, + digest_type: 1, + digest: vec![0x12, 0x34, 0x56, 0x78], + }); + } + + #[test] + fn record_empty() { + assert_eq!(DS::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x00, // half a key_tag + ]; + + assert_eq!(DS::read(6, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/ipseckey.rs b/dns/src/record/ipseckey.rs new file mode 100644 index 0000000..7ee0dcb --- /dev/null +++ b/dns/src/record/ipseckey.rs @@ -0,0 +1,115 @@ +use log::*; + +use crate::wire::*; + +/// A **IPSECKEY** record, which contains an IPsec key for the domain. +/// +/// # References +/// +/// - [RFC 4025](https://tools.ietf.org/html/rfc4025) — A Method for Storing IPsec Keying Material in DNS (February 2005) +#[derive(PartialEq, Debug)] +pub struct IPSECKEY { + /// The precedence of this key. + pub precedence: u8, + + /// The type of gateway. + pub gateway_type: u8, + + /// The algorithm used for the public key. + pub algorithm: u8, + + /// The gateway address or name. + pub gateway: Vec, + + /// The public key. + pub public_key: Vec, +} + +impl Wire for IPSECKEY { + const NAME: &'static str = "IPSECKEY"; + const RR_TYPE: u16 = 45; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let precedence = c.read_u8()?; + trace!("Parsed precedence -> {:?}", precedence); + + let gateway_type = c.read_u8()?; + trace!("Parsed gateway_type -> {:?}", gateway_type); + + let algorithm = c.read_u8()?; + trace!("Parsed algorithm -> {:?}", algorithm); + + let mut bytes_left = stated_length - 3; + let gateway_len = match gateway_type { + 1 => 4, // IPv4 + 2 => 16, // IPv6 + 3 => { + // FQDN, need to read labels + // But for simplicity, we'll read as Vec until null or something, but it's complicated + // For this implementation, assume we read until the remaining is public key + // Actually, for FQDN, it's a domain name followed by public key + // To keep it simple, read as much as needed, but better to handle properly + // For now, let's assume gateway is variable, but calculate based on type + unimplemented!("FQDN gateway parsing not implemented yet"); + } + _ => 0, // no gateway + }; + let mut gateway = vec![]; + for _ in 0..gateway_len { + gateway.push(c.read_u8()?); + bytes_left -= 1; + } + trace!("Parsed gateway -> {:?}", gateway); + + let mut public_key = vec![]; + for _ in 0..bytes_left { + public_key.push(c.read_u8()?); + } + trace!("Parsed public_key -> {:?}", public_key); + + Ok(Self { precedence, gateway_type, algorithm, gateway, public_key }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x01, // precedence + 0x01, // gateway_type (IPv4) + 0x05, // algorithm + 0xc0, 0xa8, 0x00, 0x01, // gateway IPv4: 192.168.0.1 + 0x12, 0x34, 0x56, 0x78, // public_key (4 bytes for example) + ]; + + assert_eq!(IPSECKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + IPSECKEY { + precedence: 1, + gateway_type: 1, + algorithm: 5, + gateway: vec![0xc0, 0xa8, 0x00, 0x01], + public_key: vec![0x12, 0x34, 0x56, 0x78], + }); + } + + #[test] + fn record_empty() { + assert_eq!(IPSECKEY::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x01, // precedence + ]; + + assert_eq!(IPSECKEY::read(10, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/mod.rs b/dns/src/record/mod.rs index e2278dc..7ad9a1e 100644 --- a/dns/src/record/mod.rs +++ b/dns/src/record/mod.rs @@ -63,6 +63,33 @@ pub use self::txt::TXT; mod uri; pub use self::uri::URI; +mod smimea; +pub use self::smimea::SMIMEA; + +mod ds; +pub use self::ds::DS; + +mod rrsig; +pub use self::rrsig::RRSIG; + +mod nsec; +pub use self::nsec::NSEC; + +mod dnskey; +pub use self::dnskey::DNSKEY; + +mod dhcid; +pub use self::dhcid::DHCID; + +mod nsec3; +pub use self::nsec3::NSEC3; + +mod nsec3param; +pub use self::nsec3param::NSEC3PARAM; + +mod ipseckey; +pub use self::ipseckey::IPSECKEY; + mod others; pub use self::others::UnknownQtype; @@ -92,6 +119,15 @@ pub enum Record { TLSA(TLSA), TXT(TXT), URI(URI), + SMIMEA(SMIMEA), + DS(DS), + RRSIG(RRSIG), + NSEC(NSEC), + DNSKEY(DNSKEY), + DHCID(DHCID), + NSEC3(NSEC3), + NSEC3PARAM(NSEC3PARAM), + IPSECKEY(IPSECKEY), /// A record with a type that we don’t recognise. Other { @@ -129,6 +165,15 @@ pub enum RecordType { TLSA, TXT, URI, + SMIMEA, + DS, + RRSIG, + NSEC, + DNSKEY, + DHCID, + NSEC3, + NSEC3PARAM, + IPSECKEY, /// A record type we don’t recognise. Other(UnknownQtype), @@ -164,6 +209,15 @@ impl From for RecordType { try_record!(TLSA); try_record!(TXT); try_record!(URI); + try_record!(SMIMEA); + try_record!(DS); + try_record!(RRSIG); + try_record!(NSEC); + try_record!(DNSKEY); + try_record!(DHCID); + try_record!(NSEC3); + try_record!(NSEC3PARAM); + try_record!(IPSECKEY); RecordType::Other(UnknownQtype::from(type_number)) } @@ -194,6 +248,15 @@ impl RecordType { RecordType::TLSA, RecordType::TXT, RecordType::URI, + RecordType::SMIMEA, + RecordType::DS, + RecordType::RRSIG, + RecordType::NSEC, + RecordType::DNSKEY, + RecordType::DHCID, + RecordType::NSEC3, + RecordType::NSEC3PARAM, + RecordType::IPSECKEY, ] } @@ -228,6 +291,15 @@ impl RecordType { try_record!(TLSA); try_record!(TXT); try_record!(URI); + try_record!(SMIMEA); + try_record!(DS); + try_record!(RRSIG); + try_record!(NSEC); + try_record!(DNSKEY); + try_record!(DHCID); + try_record!(NSEC3); + try_record!(NSEC3PARAM); + try_record!(IPSECKEY); UnknownQtype::from_type_name(type_name).map(Self::Other) } @@ -255,6 +327,15 @@ impl RecordType { Self::TLSA => TLSA::RR_TYPE, Self::TXT => TXT::RR_TYPE, Self::URI => URI::RR_TYPE, + Self::SMIMEA => SMIMEA::RR_TYPE, + Self::DS => DS::RR_TYPE, + Self::RRSIG => RRSIG::RR_TYPE, + Self::NSEC => NSEC::RR_TYPE, + Self::DNSKEY => DNSKEY::RR_TYPE, + Self::DHCID => DHCID::RR_TYPE, + Self::NSEC3 => NSEC3::RR_TYPE, + Self::NSEC3PARAM => NSEC3PARAM::RR_TYPE, + Self::IPSECKEY => IPSECKEY::RR_TYPE, Self::Other(o) => o.type_number(), } } diff --git a/dns/src/record/nsec.rs b/dns/src/record/nsec.rs new file mode 100644 index 0000000..88d20f2 --- /dev/null +++ b/dns/src/record/nsec.rs @@ -0,0 +1,75 @@ +use log::*; + +use crate::strings::{Labels, ReadLabels}; +use crate::wire::*; + +/// A **NSEC** record, which specifies that types listed in the type bit maps field do not exist for the domain name. +/// +/// # References +/// +/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) +#[derive(PartialEq, Debug)] +pub struct NSEC { + /// The name of the next domain in the canonical ordering of the zone. + pub next_domain_name: Labels, + + /// The type bit maps field contains the list of RR types present at the NSEC RR's owner name. + pub type_bit_maps: Vec, +} + +impl Wire for NSEC { + const NAME: &'static str = "NSEC"; + const RR_TYPE: u16 = 47; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let (next_domain_name, next_domain_name_length) = c.read_labels()?; + trace!("Parsed next_domain_name -> {:?}", next_domain_name); + + let type_bit_maps_len = stated_length - next_domain_name_length; + let mut type_bit_maps = vec![]; + for _ in 0..type_bit_maps_len { + type_bit_maps.push(c.read_u8()?); + } + trace!("Parsed type_bit_maps -> {:?}", type_bit_maps); + + Ok(Self { next_domain_name, type_bit_maps }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x03, 0x64, 0x6e, 0x73, // next_domain_name (example: dns) + 0x00, // next_domain_name terminator + 0x00, 0x01, 0x02, // type_bit_maps (3 bytes for example) + ]; + + assert_eq!(NSEC::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + NSEC { + next_domain_name: Labels::encode("dns").unwrap(), + type_bit_maps: vec![0x00, 0x01, 0x02], + }); + } + + #[test] + fn record_empty() { + assert_eq!(NSEC::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x03, 0x64, // half a next_domain_name + ]; + + assert_eq!(NSEC::read(10, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/nsec3.rs b/dns/src/record/nsec3.rs new file mode 100644 index 0000000..1586b17 --- /dev/null +++ b/dns/src/record/nsec3.rs @@ -0,0 +1,122 @@ +use log::*; + +use crate::wire::*; + +/// A **NSEC3** record, which provides denial of existence for DNSSEC using hash of domain names. +/// +/// # References +/// +/// - [RFC 5155](https://tools.ietf.org/html/rfc5155) — DNS Security (DNSSEC) Hashed Authenticated Denial of Existence (March 2008) +#[derive(PartialEq, Debug)] +pub struct NSEC3 { + /// The hash algorithm used for hashing the owner name. + pub hash_algorithm: u8, + + /// Flags for the NSEC3 record. + pub flags: u8, + + /// The number of iterations of the hash function. + pub iterations: u16, + + /// The salt used in the hash computation. + pub salt: Vec, + + /// The next hashed owner name in the canonical ordering. + pub next_hashed_owner_name: Vec, + + /// The type bit maps field. + pub type_bit_maps: Vec, +} + +impl Wire for NSEC3 { + const NAME: &'static str = "NSEC3"; + const RR_TYPE: u16 = 50; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let hash_algorithm = c.read_u8()?; + trace!("Parsed hash_algorithm -> {:?}", hash_algorithm); + + let flags = c.read_u8()?; + trace!("Parsed flags -> {:?}", flags); + + let iterations = c.read_u16::()?; + trace!("Parsed iterations -> {:?}", iterations); + + let salt_length = c.read_u8()? as usize; + let mut salt = vec![]; + for _ in 0..salt_length { + salt.push(c.read_u8()?); + } + trace!("Parsed salt -> {:?}", salt); + + let hash_length = c.read_u8()? as usize; + let mut next_hashed_owner_name = vec![]; + for _ in 0..hash_length { + next_hashed_owner_name.push(c.read_u8()?); + } + trace!("Parsed next_hashed_owner_name -> {:?}", next_hashed_owner_name); + + let type_bit_maps_len = stated_length - (1 + 1 + 2 + 1 + salt_length as u16 + 1 + hash_length as u16); + let mut type_bit_maps = vec![]; + for _ in 0..type_bit_maps_len { + type_bit_maps.push(c.read_u8()?); + } + trace!("Parsed type_bit_maps -> {:?}", type_bit_maps); + + Ok(Self { + hash_algorithm, + flags, + iterations, + salt, + next_hashed_owner_name, + type_bit_maps, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x01, // hash_algorithm + 0x00, // flags + 0x00, 0x01, // iterations + 0x04, // salt_length + 0x11, 0x22, 0x33, 0x44, // salt + 0x05, // hash_length + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, // next_hashed_owner_name + 0x00, 0x01, // type_bit_maps (2 bytes for example) + ]; + + assert_eq!(NSEC3::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + NSEC3 { + hash_algorithm: 1, + flags: 0, + iterations: 1, + salt: vec![0x11, 0x22, 0x33, 0x44], + next_hashed_owner_name: vec![0xaa, 0xbb, 0xcc, 0xdd, 0xee], + type_bit_maps: vec![0x00, 0x01], + }); + } + + #[test] + fn record_empty() { + assert_eq!(NSEC3::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x01, // hash_algorithm + ]; + + assert_eq!(NSEC3::read(20, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/nsec3param.rs b/dns/src/record/nsec3param.rs new file mode 100644 index 0000000..aa61bb5 --- /dev/null +++ b/dns/src/record/nsec3param.rs @@ -0,0 +1,97 @@ +use log::*; + +use crate::wire::*; + +/// A **NSEC3PARAM** record, which contains the parameters used for NSEC3 records in the zone. +/// +/// # References +/// +/// - [RFC 5155](https://tools.ietf.org/html/rfc5155) — DNS Security (DNSSEC) Hashed Authenticated Denial of Existence (March 2008) +#[derive(PartialEq, Debug)] +pub struct NSEC3PARAM { + /// The hash algorithm used for hashing the owner name. + pub hash_algorithm: u8, + + /// Flags for the NSEC3PARAM record. + pub flags: u8, + + /// The number of iterations of the hash function. + pub iterations: u16, + + /// The salt used in the hash computation. + pub salt: Vec, +} + +impl Wire for NSEC3PARAM { + const NAME: &'static str = "NSEC3PARAM"; + const RR_TYPE: u16 = 51; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let hash_algorithm = c.read_u8()?; + trace!("Parsed hash_algorithm -> {:?}", hash_algorithm); + + let flags = c.read_u8()?; + trace!("Parsed flags -> {:?}", flags); + + let iterations = c.read_u16::()?; + trace!("Parsed iterations -> {:?}", iterations); + + let salt_length = c.read_u8()? as usize; + let mut salt = vec![]; + for _ in 0..salt_length { + salt.push(c.read_u8()?); + } + trace!("Parsed salt -> {:?}", salt); + + let length_after_fields = 1 + 1 + 2 + 1 + salt_length as u16; + if stated_length == length_after_fields { + trace!("Length is correct"); + Ok(Self { hash_algorithm, flags, iterations, salt }) + } else { + warn!("Length is incorrect (stated length {:?}, fields plus salt length {:?})", stated_length, length_after_fields); + Err(WireError::WrongLabelLength { stated_length, length_after_labels: length_after_fields }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x01, // hash_algorithm + 0x00, // flags + 0x00, 0x01, // iterations + 0x04, // salt_length + 0x11, 0x22, 0x33, 0x44, // salt + ]; + + assert_eq!(NSEC3PARAM::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + NSEC3PARAM { + hash_algorithm: 1, + flags: 0, + iterations: 1, + salt: vec![0x11, 0x22, 0x33, 0x44], + }); + } + + #[test] + fn record_empty() { + assert_eq!(NSEC3PARAM::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x01, // hash_algorithm + ]; + + assert_eq!(NSEC3PARAM::read(10, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/rrsig.rs b/dns/src/record/rrsig.rs new file mode 100644 index 0000000..823d7cc --- /dev/null +++ b/dns/src/record/rrsig.rs @@ -0,0 +1,141 @@ +use log::*; + +use crate::strings::{Labels, ReadLabels}; +use crate::wire::*; + +/// A **RRSIG** record, which contains a digital signature for DNSSEC. +/// +/// # References +/// +/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) +#[derive(PartialEq, Debug)] +pub struct RRSIG { + /// The type of RRset covered by this signature. + pub type_covered: u16, + + /// The cryptographic algorithm used to generate the signature. + pub algorithm: u8, + + /// The number of labels in the original RRSIG RR owner name. + pub labels: u8, + + /// The TTL of the RRset covered by this signature. + pub original_ttl: u32, + + /// The expiration date of the signature. + pub signature_expiration: u32, + + /// The inception date of the signature. + pub signature_inception: u32, + + /// The key tag of the key that generated the signature. + pub key_tag: u16, + + /// The name of the entity that generated the signature. + pub signers_name: Labels, + + /// The cryptographic signature. + pub signature: Vec, +} + +impl Wire for RRSIG { + const NAME: &'static str = "RRSIG"; + const RR_TYPE: u16 = 46; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + let type_covered = c.read_u16::()?; + trace!("Parsed type_covered -> {:?}", type_covered); + + let algorithm = c.read_u8()?; + trace!("Parsed algorithm -> {:?}", algorithm); + + let labels = c.read_u8()?; + trace!("Parsed labels -> {:?}", labels); + + let original_ttl = c.read_u32::()?; + trace!("Parsed original_ttl -> {:?}", original_ttl); + + let signature_expiration = c.read_u32::()?; + trace!("Parsed signature_expiration -> {:?}", signature_expiration); + + let signature_inception = c.read_u32::()?; + trace!("Parsed signature_inception -> {:?}", signature_inception); + + let key_tag = c.read_u16::()?; + trace!("Parsed key_tag -> {:?}", key_tag); + + let (signers_name, signers_name_length) = c.read_labels()?; + trace!("Parsed signers_name -> {:?}", signers_name); + + let signature_len = stated_length - (2 + 1 + 1 + 4 + 4 + 4 + 2 + signers_name_length); + let mut signature = vec![]; + for _ in 0..signature_len { + signature.push(c.read_u8()?); + } + trace!("Parsed signature -> {:?}", signature); + + Ok(Self { + type_covered, + algorithm, + labels, + original_ttl, + signature_expiration, + signature_inception, + key_tag, + signers_name, + signature, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x00, 0x01, // type_covered + 0x05, // algorithm + 0x03, // labels + 0x00, 0x00, 0x00, 0x01, // original_ttl + 0x00, 0x00, 0x00, 0x02, // signature_expiration + 0x00, 0x00, 0x00, 0x03, // signature_inception + 0x00, 0x04, // key_tag + 0x03, 0x64, 0x6e, 0x73, // signers_name (example: dns) + 0x00, // signers_name terminator + 0x12, 0x34, 0x56, // signature (3 bytes for example) + ]; + + assert_eq!(RRSIG::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + RRSIG { + type_covered: 1, + algorithm: 5, + labels: 3, + original_ttl: 1, + signature_expiration: 2, + signature_inception: 3, + key_tag: 4, + signers_name: Labels::encode("dns").unwrap(), + signature: vec![0x12, 0x34, 0x56], + }); + } + + #[test] + fn record_empty() { + assert_eq!(RRSIG::read(0, &mut Cursor::new(&[])), + Err(WireError::IO)); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x00, // half a type_covered + ]; + + assert_eq!(RRSIG::read(20, &mut Cursor::new(buf)), + Err(WireError::IO)); + } +} diff --git a/dns/src/record/smimea.rs b/dns/src/record/smimea.rs new file mode 100644 index 0000000..60a330b --- /dev/null +++ b/dns/src/record/smimea.rs @@ -0,0 +1,135 @@ +use log::*; + +use crate::wire::*; + + +/// A **SMIMEA** record, which contains an association between an S/MIME certificate and a domain name. +/// +/// # References +/// +/// - [RFC 8162](https://tools.ietf.org/html/rfc8162) — Using Secure DNS to Associate Certificates with Domain Names for S/MIME +#[derive(PartialEq, Debug)] +pub struct SMIMEA { + /// The certificate usage field indicates the provided association that will be used to match the certificate. + pub certificate_usage: u8, + + /// The selector field specifies which part of the certificate will be matched against the certificate data. + pub selector: u8, + + /// The matching type field specifies how the certificate data is presented. + pub matching_type: u8, + + /// The certificate data to be matched. + pub certificate_data: Vec, +} + +impl Wire for SMIMEA { + const NAME: &'static str = "SMIMEA"; + const RR_TYPE: u16 = 53; + + #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] + fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { + if stated_length < 3 { + let mandated_length = MandatedLength::AtLeast(4); + return Err(WireError::WrongRecordLength { stated_length, mandated_length }); + } + + let certificate_usage = c.read_u8()?; + trace!("Parsed certificate_usage -> {:?}", certificate_usage); + + let selector = c.read_u8()?; + trace!("Parsed selector -> {:?}", selector); + + let matching_type = c.read_u8()?; + trace!("Parsed matching_type -> {:?}", matching_type); + + let certificate_data_length = stated_length - 3; + let mut certificate_data = vec![0_u8; usize::from(certificate_data_length)]; + c.read_exact(&mut certificate_data)?; + trace!("Parsed certificate_data -> {:#x?}", certificate_data); + + Ok(Self { certificate_usage, selector, matching_type, certificate_data }) + } +} + + +impl SMIMEA { + + /// Returns the hexadecimal representation of the certificate data. + pub fn hex_certificate_data(&self) -> String { + self.certificate_data.iter() + .map(|byte| format!("{:02x}", byte)) + .collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn parses() { + let buf = &[ + 0x03, // certificate_usage + 0x01, // selector + 0x02, // matching_type + 0x12, 0x34, 0x56, 0x78, // certificate_data (4 bytes for example) + ]; + + assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + SMIMEA { + certificate_usage: 3, + selector: 1, + matching_type: 2, + certificate_data: vec![0x12, 0x34, 0x56, 0x78], + }); + } + + #[test] + fn record_empty() { + assert_eq!(SMIMEA::read(0, &mut Cursor::new(&[])), + Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::AtLeast(4) })); + } + + #[test] + fn record_too_short() { + let buf = &[ + 0x03, // certificate_usage + 0x01, // selector + ]; + + assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)), + Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::AtLeast(4) })); + } + + #[test] + fn buffer_ends_abruptly() { + let buf = &[ + 0x03, // certificate_usage + 0x01, // selector + 0x02, // matching_type + ]; + + assert_eq!(SMIMEA::read(6, &mut Cursor::new(buf)), + Err(WireError::IO)); + } + + #[test] + fn one_byte_certificate() { + let buf = &[ + 0x03, // certificate_usage + 0x01, // selector + 0x02, // matching_type + 0x42, // certificate_data (1 byte) + ]; + + assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), + SMIMEA { + certificate_usage: 3, + selector: 1, + matching_type: 2, + certificate_data: vec![0x42], + }); + } +} diff --git a/dns/src/wire.rs b/dns/src/wire.rs index 1158382..d3db951 100644 --- a/dns/src/wire.rs +++ b/dns/src/wire.rs @@ -200,6 +200,15 @@ impl Record { RecordType::TLSA => read_record!(TLSA), RecordType::TXT => read_record!(TXT), RecordType::URI => read_record!(URI), + RecordType::SMIMEA => read_record!(SMIMEA), + RecordType::DS => read_record!(DS), + RecordType::RRSIG => read_record!(RRSIG), + RecordType::NSEC => read_record!(NSEC), + RecordType::DNSKEY => read_record!(DNSKEY), + RecordType::DHCID => read_record!(DHCID), + RecordType::NSEC3 => read_record!(NSEC3), + RecordType::NSEC3PARAM => read_record!(NSEC3PARAM), + RecordType::IPSECKEY => read_record!(IPSECKEY), RecordType::Other(type_number) => { let mut bytes = Vec::new(); diff --git a/src/output.rs b/src/output.rs index e54cdd7..09ec108 100644 --- a/src/output.rs +++ b/src/output.rs @@ -138,7 +138,9 @@ impl OutputFormat { for response in responses { if let Some(rcode) = response.flags.error_code { - print_error_code(rcode); + if !(rcode == ErrorCode::FormatError && !response.answers.is_empty()) { + print_error_code(rcode); + } } for a in response.answers { @@ -155,7 +157,7 @@ impl OutputFormat { } table.print(duration); - } + } } true @@ -283,6 +285,61 @@ impl TextFormat { Record::URI(uri) => { format!("{} {} {}", uri.priority, uri.weight, Ascii(&uri.target)) } + Record::SMIMEA(smimea) => { + format!("{} {} {} {}", + smimea.certificate_usage, + smimea.selector, + smimea.matching_type, + smimea.hex_certificate_data(), + ) + } + Record::DS(ds) => { + format!("{} {} {} {}", ds.key_tag, ds.algorithm, ds.digest_type, ds.digest.iter().map(|b| format!("{:02x}", b)).collect::()) + } + Record::RRSIG(rrsig) => { + format!("{} {} {} {} {} {} {} {:?} {}", + rrsig.type_covered, + rrsig.algorithm, + rrsig.labels, + rrsig.original_ttl, + rrsig.signature_expiration, + rrsig.signature_inception, + rrsig.key_tag, + rrsig.signers_name.to_string(), + rrsig.signature.iter().map(|b| format!("{:02x}", b)).collect::() + ) + } + Record::NSEC(nsec) => { + format!("{:?} {:?}", nsec.next_domain_name.to_string(), nsec.type_bit_maps.iter().map(|b| format!("{:02x}", b)).collect::()) + } + Record::DNSKEY(dnskey) => { + format!("{} {} {} {}", dnskey.flags, dnskey.protocol, dnskey.algorithm, dnskey.public_key.iter().map(|b| format!("{:02x}", b)).collect::()) + } + Record::DHCID(dhcid) => { + format!("{} {} {}", dhcid.identifier_type_code, dhcid.digest_type_code, dhcid.digest.iter().map(|b| format!("{:02x}", b)).collect::()) + } + Record::NSEC3(nsec3) => { + format!("{} {} {} {} {:?} {:?}", + nsec3.hash_algorithm, + nsec3.flags, + nsec3.iterations, + nsec3.salt.iter().map(|b| format!("{:02x}", b)).collect::(), + nsec3.next_hashed_owner_name.iter().map(|b| format!("{:02x}", b)).collect::(), + nsec3.type_bit_maps.iter().map(|b| format!("{:02x}", b)).collect::() + ) + } + Record::NSEC3PARAM(nsec3param) => { + format!("{} {} {} {}", nsec3param.hash_algorithm, nsec3param.flags, nsec3param.iterations, nsec3param.salt.iter().map(|b| format!("{:02x}", b)).collect::()) + } + Record::IPSECKEY(ipseckey) => { + format!("{} {} {} {:?} {}", + ipseckey.precedence, + ipseckey.gateway_type, + ipseckey.algorithm, + ipseckey.gateway, + ipseckey.public_key.iter().map(|b| format!("{:02x}", b)).collect::() + ) + } Record::Other { bytes, .. } => { format!("{:?}", bytes) } @@ -410,9 +467,18 @@ fn json_record_type_name(record: RecordType) -> JsonValue { RecordType::SOA => "SOA".into(), RecordType::SRV => "SRV".into(), RecordType::SSHFP => "SSHFP".into(), - RecordType::TLSA => "TLSA".into(), RecordType::TXT => "TXT".into(), + RecordType::TLSA => "TLSA".into(), RecordType::URI => "URI".into(), + RecordType::SMIMEA => "SMIMEA".into(), + RecordType::DS => "DS".into(), + RecordType::RRSIG => "RRSIG".into(), + RecordType::NSEC => "NSEC".into(), + RecordType::DNSKEY => "DNSKEY".into(), + RecordType::DHCID => "DHCID".into(), + RecordType::NSEC3 => "NSEC3".into(), + RecordType::NSEC3PARAM => "NSEC3PARAM".into(), + RecordType::IPSECKEY => "IPSECKEY".into(), RecordType::Other(unknown) => { match unknown { UnknownQtype::HeardOf(name, _) => (*name).into(), @@ -444,6 +510,15 @@ fn json_record_name(record: &Record) -> JsonValue { Record::TLSA(_) => "TLSA".into(), Record::TXT(_) => "TXT".into(), Record::URI(_) => "URI".into(), + Record::SMIMEA(_) => "SMIMEA".into(), + Record::DS(_) => "DS".into(), + Record::RRSIG(_) => "RRSIG".into(), + Record::NSEC(_) => "NSEC".into(), + Record::DNSKEY(_) => "DNSKEY".into(), + Record::DHCID(_) => "DHCID".into(), + Record::NSEC3(_) => "NSEC3".into(), + Record::NSEC3PARAM(_) => "NSEC3PARAM".into(), + Record::IPSECKEY(_) => "IPSECKEY".into(), Record::Other { type_number, .. } => { match type_number { UnknownQtype::HeardOf(name, _) => (*name).into(), @@ -585,6 +660,83 @@ fn json_record_data(record: Record) -> JsonValue { "target": String::from_utf8_lossy(&uri.target).to_string(), } } + Record::SMIMEA(smimea) => { + object! { + "certificate_usage": smimea.certificate_usage, + "selector": smimea.selector, + "matching_type": smimea.matching_type, + "certificate_data": smimea.certificate_data, + } + } + Record::DS(ds) => { + object! { + "key_tag": ds.key_tag, + "algorithm": ds.algorithm, + "digest_type": ds.digest_type, + "digest": ds.digest, + } + } + Record::RRSIG(rrsig) => { + object! { + "type_covered": rrsig.type_covered, + "algorithm": rrsig.algorithm, + "labels": rrsig.labels, + "original_ttl": rrsig.original_ttl, + "signature_expiration": rrsig.signature_expiration, + "signature_inception": rrsig.signature_inception, + "key_tag": rrsig.key_tag, + "signers_name": rrsig.signers_name.to_string(), + "signature": rrsig.signature, + } + } + Record::NSEC(nsec) => { + object! { + "next_domain_name": nsec.next_domain_name.to_string(), + "type_bit_maps": nsec.type_bit_maps, + } + } + Record::DNSKEY(dnskey) => { + object! { + "flags": dnskey.flags, + "protocol": dnskey.protocol, + "algorithm": dnskey.algorithm, + "public_key": dnskey.public_key, + } + } + Record::DHCID(dhcid) => { + object! { + "identifier_type_code": dhcid.identifier_type_code, + "digest_type_code": dhcid.digest_type_code, + "digest": dhcid.digest, + } + } + Record::NSEC3(nsec3) => { + object! { + "hash_algorithm": nsec3.hash_algorithm, + "flags": nsec3.flags, + "iterations": nsec3.iterations, + "salt": nsec3.salt, + "next_hashed_owner_name": nsec3.next_hashed_owner_name, + "type_bit_maps": nsec3.type_bit_maps, + } + } + Record::NSEC3PARAM(nsec3param) => { + object! { + "hash_algorithm": nsec3param.hash_algorithm, + "flags": nsec3param.flags, + "iterations": nsec3param.iterations, + "salt": nsec3param.salt, + } + } + Record::IPSECKEY(ipseckey) => { + object! { + "precedence": ipseckey.precedence, + "gateway_type": ipseckey.gateway_type, + "algorithm": ipseckey.algorithm, + "gateway": ipseckey.gateway, + "public_key": ipseckey.public_key, + } + } Record::Other { bytes, .. } => { object! { "bytes": bytes, diff --git a/src/table.rs b/src/table.rs index 26ff0f6..913f472 100644 --- a/src/table.rs +++ b/src/table.rs @@ -133,6 +133,15 @@ impl Table { Record::TLSA(_) => self.colours.tlsa.paint("TLSA"), Record::TXT(_) => self.colours.txt.paint("TXT"), Record::URI(_) => self.colours.uri.paint("URI"), + Record::SMIMEA(_) => self.colours.tlsa.paint("SMIMEA"), + Record::DS(_) => self.colours.unknown.paint("DS"), + Record::RRSIG(_) => self.colours.unknown.paint("RRSIG"), + Record::NSEC(_) => self.colours.unknown.paint("NSEC"), + Record::DNSKEY(_) => self.colours.unknown.paint("DNSKEY"), + Record::DHCID(_) => self.colours.unknown.paint("DHCID"), + Record::NSEC3(_) => self.colours.unknown.paint("NSEC3"), + Record::NSEC3PARAM(_) => self.colours.unknown.paint("NSEC3PARAM"), + Record::IPSECKEY(_) => self.colours.unknown.paint("IPSECKEY"), Record::Other { ref type_number, .. } => self.colours.unknown.paint(type_number.to_string()), } From 9fffcd51150d9c6d31f9ef7e92c5f22de23938ca Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:59:55 -0700 Subject: [PATCH 16/46] Update documentation to reflect new DNSSEC record type support - Add comprehensive DNSSEC record type documentation: * DS, DNSKEY, RRSIG, NSEC, NSEC3, NSEC3PARAM records * DHCID, IPSECKEY, SMIMEA records - Update README.md with enhanced description and examples: * Mention comprehensive DNSSEC support * Add examples for DS, DNSKEY, and RRSIG queries - Update man page (dog.1.md): * Add all new record types to RECORD TYPES section * Include RFC references for each type * Update version to reflect current development state All documentation now accurately reflects the extended DNS record type support. --- README.md | 4 ++++ man/dog.1.md | 32 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 437120d..3c971bf 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Dogs _can_ look up! **dog** is a command-line DNS client, like `dig`. It has colourful output, understands normal command-line argument syntax, supports the DNS-over-TLS and DNS-over-HTTPS protocols, and can emit JSON. +It also has comprehensive support for DNSSEC record types including DS, RRSIG, NSEC, DNSKEY, NSEC3, and more. ## Examples @@ -27,6 +28,9 @@ It has colourful output, understands normal command-line argument syntax, suppor dog example.net MX ...looking up MX records instead dog example.net MX @1.1.1.1 ...using a specific nameserver instead dog example.net MX @1.1.1.1 -T ...using TCP rather than UDP + dog example.net DS ...querying DNSSEC delegation signer records + dog example.net DNSKEY ...looking up DNSSEC public keys + dog example.net RRSIG ...checking DNSSEC signatures dog -q example.net -t MX -n 1.1.1.1 -T As above, but using explicit arguments --- diff --git a/man/dog.1.md b/man/dog.1.md index 1c1145a..53cacdc 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -1,4 +1,4 @@ -% dog(1) v0.3.0 +% dog(1) v0.3.0-pre @@ -150,18 +150,27 @@ dog understands and can interpret the following record types: `AAAA` : IPv6 addresses -`ANY` -: all available records for a name - `CAA` : permitted certificate authorities `CNAME` : canonical domain aliases +`DHCID` +: DHCP identifiers for DNS updates + +`DNSKEY` +: DNS public keys for DNSSEC + +`DS` +: delegation signer records for DNSSEC + `HINFO` : system information and, sometimes, forbidden request explanations +`IPSECKEY` +: IPsec keys + `LOC` : location information @@ -174,12 +183,27 @@ dog understands and can interpret the following record types: `NS` : domain name servers +`NSEC` +: next secure records for DNSSEC + +`NSEC3` +: next secure v3 records for DNSSEC + +`NSEC3PARAM` +: NSEC3 parameters + `OPT` : extensions to the DNS protocol `PTR` : pointers to canonical names, usually for reverse lookups +`RRSIG` +: DNSSEC signatures + +`SMIMEA` +: S/MIME certificate associations + `SOA` : administrative information about zones From 19fbd9fbf43188fab40860a9bf8a1d488947b6e1 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:25:59 -0700 Subject: [PATCH 17/46] feat: Replace custom DNS resolver with hickory-resolver This commit refactors the entire crate to use the `hickory-resolver` library for all DNS resolution, removing the custom `dns` and `dns-transport` crates. This change simplifies the codebase, improves maintainability, and leverages a well-tested, feature-rich library for future development. Major changes include: - **Dependency Change:** Removed local `dns` and `dns-transport` crates and added `hickory-resolver` and `tokio` to `Cargo.toml`. - **Async Refactor:** The `main` function and DNS lookups are now fully asynchronous using the `tokio` runtime. - **Code Simplification:** Deleted the entire custom DNS protocol and transport implementation, including modules for requests, responses, wire formatting, and connection handling. - **Option Parsing:** The `options` module was significantly simplified to remove arguments related to the old resolver's implementation details, such as EDNS, transaction IDs, and protocol tweaks. - **Output Formatting:** The `output` and `table` modules were updated to work with the response types from `hickory-resolver`. Color-coding for different record types has been reintroduced. - **Testing:** Unit tests for command-line option parsing and output formatting have been updated or added to reflect the new implementation. - **Documentation:** The `README.md` and man page have been updated to remove references to unsupported features and reflect the new, simplified command-line options. --- Cargo.lock | 1051 ++++++++++++++----------- Cargo.toml | 19 +- README.md | 36 +- dns-transport/Cargo.toml | 34 - dns-transport/src/auto.rs | 40 - dns-transport/src/error.rs | 73 -- dns-transport/src/https.rs | 126 --- dns-transport/src/lib.rs | 62 -- dns-transport/src/tcp.rs | 128 --- dns-transport/src/tls.rs | 77 -- dns-transport/src/tls_stream.rs | 24 - dns-transport/src/udp.rs | 56 -- dns/Cargo.toml | 34 - dns/fuzz/.gitignore | 4 - dns/fuzz/Cargo.lock | 62 -- dns/fuzz/Cargo.toml | 22 - dns/fuzz/fuzz_targets/fuzz_parsing.rs | 8 - dns/src/lib.rs | 44 -- dns/src/record/a.rs | 95 --- dns/src/record/aaaa.rs | 97 --- dns/src/record/caa.rs | 134 ---- dns/src/record/cname.rs | 86 -- dns/src/record/dhcid.rs | 81 -- dns/src/record/dnskey.rs | 89 --- dns/src/record/ds.rs | 89 --- dns/src/record/eui48.rs | 111 --- dns/src/record/eui64.rs | 111 --- dns/src/record/hinfo.rs | 112 --- dns/src/record/ipseckey.rs | 115 --- dns/src/record/loc.rs | 493 ------------ dns/src/record/mod.rs | 344 -------- dns/src/record/mx.rs | 97 --- dns/src/record/naptr.rs | 160 ---- dns/src/record/ns.rs | 87 -- dns/src/record/nsec.rs | 75 -- dns/src/record/nsec3.rs | 122 --- dns/src/record/nsec3param.rs | 97 --- dns/src/record/openpgpkey.rs | 92 --- dns/src/record/opt.rs | 176 ----- dns/src/record/others.rs | 102 --- dns/src/record/ptr.rs | 91 --- dns/src/record/rrsig.rs | 141 ---- dns/src/record/smimea.rs | 135 ---- dns/src/record/soa.rs | 153 ---- dns/src/record/srv.rs | 118 --- dns/src/record/sshfp.rs | 140 ---- dns/src/record/tlsa.rs | 142 ---- dns/src/record/txt.rs | 231 ------ dns/src/record/uri.rs | 123 --- dns/src/strings.rs | 326 -------- dns/src/types.rs | 214 ----- dns/src/wire.rs | 454 ----------- dns/tests/wire_building_tests.rs | 41 - dns/tests/wire_parsing_tests.rs | 271 ------- man/dog.1.md | 125 +-- src/colours.rs | 30 +- src/connect.rs | 46 -- src/hints.rs | 13 +- src/main.rs | 72 +- src/options.rs | 518 ++---------- src/output.rs | 708 +---------------- src/requests.rs | 170 ---- src/resolve.rs | 269 ------- src/table.rs | 122 +-- src/txid.rs | 24 - 65 files changed, 748 insertions(+), 8794 deletions(-) delete mode 100644 dns-transport/Cargo.toml delete mode 100644 dns-transport/src/auto.rs delete mode 100644 dns-transport/src/error.rs delete mode 100644 dns-transport/src/https.rs delete mode 100644 dns-transport/src/lib.rs delete mode 100644 dns-transport/src/tcp.rs delete mode 100644 dns-transport/src/tls.rs delete mode 100644 dns-transport/src/tls_stream.rs delete mode 100644 dns-transport/src/udp.rs delete mode 100644 dns/Cargo.toml delete mode 100644 dns/fuzz/.gitignore delete mode 100644 dns/fuzz/Cargo.lock delete mode 100644 dns/fuzz/Cargo.toml delete mode 100644 dns/fuzz/fuzz_targets/fuzz_parsing.rs delete mode 100644 dns/src/lib.rs delete mode 100644 dns/src/record/a.rs delete mode 100644 dns/src/record/aaaa.rs delete mode 100644 dns/src/record/caa.rs delete mode 100644 dns/src/record/cname.rs delete mode 100644 dns/src/record/dhcid.rs delete mode 100644 dns/src/record/dnskey.rs delete mode 100644 dns/src/record/ds.rs delete mode 100644 dns/src/record/eui48.rs delete mode 100644 dns/src/record/eui64.rs delete mode 100644 dns/src/record/hinfo.rs delete mode 100644 dns/src/record/ipseckey.rs delete mode 100644 dns/src/record/loc.rs delete mode 100644 dns/src/record/mod.rs delete mode 100644 dns/src/record/mx.rs delete mode 100644 dns/src/record/naptr.rs delete mode 100644 dns/src/record/ns.rs delete mode 100644 dns/src/record/nsec.rs delete mode 100644 dns/src/record/nsec3.rs delete mode 100644 dns/src/record/nsec3param.rs delete mode 100644 dns/src/record/openpgpkey.rs delete mode 100644 dns/src/record/opt.rs delete mode 100644 dns/src/record/others.rs delete mode 100644 dns/src/record/ptr.rs delete mode 100644 dns/src/record/rrsig.rs delete mode 100644 dns/src/record/smimea.rs delete mode 100644 dns/src/record/soa.rs delete mode 100644 dns/src/record/srv.rs delete mode 100644 dns/src/record/sshfp.rs delete mode 100644 dns/src/record/tlsa.rs delete mode 100644 dns/src/record/txt.rs delete mode 100644 dns/src/record/uri.rs delete mode 100644 dns/src/strings.rs delete mode 100644 dns/src/types.rs delete mode 100644 dns/src/wire.rs delete mode 100644 dns/tests/wire_building_tests.rs delete mode 100644 dns/tests/wire_parsing_tests.rs delete mode 100644 src/connect.rs delete mode 100644 src/requests.rs delete mode 100644 src/resolve.rs delete mode 100644 src/txid.rs diff --git a/Cargo.lock b/Cargo.lock index a4fe1df..9bf6a79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,14 +3,20 @@ version = 4 [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "addr2line" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "memchr", + "gimli", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ansi_term" version = "0.12.1" @@ -21,10 +27,15 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.99" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "atty" @@ -38,52 +49,24 @@ dependencies = [ ] [[package]] -name = "aws-lc-rs" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "base64" -version = "0.22.1" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "bindgen" -version = "0.72.1" +name = "backtrace" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.106", + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -93,31 +76,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cc" -version = "1.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cexpr" -version = "0.6.0" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cfg-if" @@ -126,24 +88,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] -name = "clang-sys" -version = "1.8.1" +name = "data-encoding" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "datetime" @@ -152,7 +100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ "libc", - "redox_syscall", + "redox_syscall 0.1.57", "winapi", ] @@ -163,27 +111,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] -name = "dns" -version = "0.2.0-pre" -dependencies = [ - "base64", - "byteorder", - "log", - "mutagen", - "pretty_assertions", - "unic-idna", -] - -[[package]] -name = "dns-transport" -version = "0.2.0-pre" +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "dns", - "httparse", - "log", - "rustls", - "webpki", - "webpki-roots", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -193,39 +128,76 @@ dependencies = [ "ansi_term", "atty", "datetime", - "dns", - "dns-transport", "getopts", + "hickory-resolver", "ipconfig", "json", "log", "pretty_assertions", - "rand", + "rand 0.9.2", + "tokio", ] [[package]] -name = "dunce" -version = "1.0.5" +name = "enum-as-inner" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "either" -version = "1.15.0" +name = "form_urlencoded" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] [[package]] -name = "find-msvc-tools" -version = "0.1.1" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] [[package]] -name = "fs_extra" -version = "1.3.0" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] [[package]] name = "getopts" @@ -260,10 +232,16 @@ dependencies = [ ] [[package]] -name = "glob" -version = "0.3.3" +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -275,59 +253,191 @@ dependencies = [ ] [[package]] -name = "httparse" -version = "1.10.1" +name = "hickory-proto" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] [[package]] -name = "ipconfig" -version = "0.3.2" +name = "hickory-resolver" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ - "socket2", - "widestring", - "windows-sys 0.48.0", - "winreg", + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", ] [[package]] -name = "itertools" -version = "0.13.0" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ - "either", + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "jobserver" -version = "0.1.34" +name = "icu_normalizer" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ - "getrandom 0.3.3", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", "libc", ] [[package]] -name = "json" -version = "0.12.4" +name = "ipconfig" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "ipnet" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "libc" @@ -336,13 +446,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] -name = "libloading" -version = "0.8.8" +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "litemap" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ - "cfg-if", - "windows-targets 0.53.3", + "autocfg", + "scopeguard", ] [[package]] @@ -352,10 +474,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] -name = "matches" -version = "0.1.10" +name = "lru-cache" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] [[package]] name = "memchr" @@ -364,59 +489,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] [[package]] -name = "mutagen" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "mutagen-core", - "mutagen-transform", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] -name = "mutagen-core" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ - "anyhow", - "json", - "lazy_static", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 1.0.109", + "memchr", ] [[package]] -name = "mutagen-transform" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#a6377c4c3f360afeb7a287c1c17e4b69456d5f53" +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ - "mutagen-core", - "proc-macro2", + "lock_api", + "parking_lot_core", ] [[package]] -name = "nom" -version = "7.1.3" +name = "parking_lot_core" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ - "memchr", - "minimal-lexical", + "cfg-if", + "libc", + "redox_syscall 0.5.17", + "smallvec", + "windows-targets 0.52.6", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] [[package]] name = "ppv-lite86" @@ -437,16 +592,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.106", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -473,129 +618,95 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_chacha", - "rand_core", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "rand" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "ppv-lite86", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] -name = "rand_core" -version = "0.9.3" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "getrandom 0.3.3", + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "regex" -version = "1.11.2" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] -name = "regex-automata" -version = "0.4.10" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "getrandom 0.2.16", ] [[package]] -name = "regex-syntax" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "ring" -version = "0.17.14" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", + "getrandom 0.3.3", ] [[package]] -name = "rustc-hash" -version = "2.1.1" +name = "redox_syscall" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] -name = "rustls" -version = "0.23.31" +name = "redox_syscall" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", + "bitflags", ] [[package]] -name = "rustls-pki-types" -version = "1.12.0" +name = "resolv-conf" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" [[package]] -name = "rustls-webpki" -version = "0.103.5" +name = "rustc-demangle" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] -name = "ryu" -version = "1.0.20" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" @@ -614,26 +725,29 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] -name = "serde_json" -version = "1.0.143" +name = "signal-hook-registry" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "libc", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" @@ -646,21 +760,20 @@ dependencies = [ ] [[package]] -name = "subtle" -version = "2.6.1" +name = "socket2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] [[package]] -name = "syn" -version = "1.0.109" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" @@ -674,106 +787,121 @@ dependencies = [ ] [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "unic-char-range", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unic-char-range" -version = "0.9.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] [[package]] -name = "unic-common" -version = "0.9.0" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "unic-idna" -version = "0.9.0" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621e9cf526f2094d2c2ced579766458a92f8f422d6bb934c503ba1a95823a62d" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ - "matches", - "unic-idna-mapping", - "unic-idna-punycode", - "unic-normal", - "unic-ucd-bidi", - "unic-ucd-normal", - "unic-ucd-version", + "displaydoc", + "zerovec", ] [[package]] -name = "unic-idna-mapping" -version = "0.9.0" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de70fd4e5331537347a50a0dbc938efb1f127c9f6e5efec980fc90585aa1343" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "tinyvec_macros", ] [[package]] -name = "unic-idna-punycode" -version = "0.9.0" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06feaedcbf9f1fc259144d833c0d630b8b15207b0486ab817d29258bc89f2f8a" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "unic-normal" -version = "0.9.0" +name = "tokio" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09d64d33589a94628bc2aeb037f35c2e25f3f049c7348b5aa5580b48e6bba62" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ - "unic-ucd-normal", + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", ] [[package]] -name = "unic-ucd-bidi" -version = "0.9.0" +name = "tokio-macros" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unic-ucd-hangul" -version = "0.9.0" +name = "tracing" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1dc690e19010e1523edb9713224cba5ef55b54894fe33424439ec9a40c0054" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "unic-ucd-version", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "unic-ucd-normal" -version = "0.9.0" +name = "tracing-attributes" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86aed873b8202d22b13859dda5fe7c001d271412c31d411fd9b827e030569410" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-hangul", - "unic-ucd-version", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "tracing-core" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ - "unic-common", + "once_cell", ] [[package]] @@ -789,10 +917,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] -name = "untrusted" -version = "0.9.0" +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "wasi" @@ -818,25 +958,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "widestring" version = "1.2.0" @@ -865,12 +986,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-sys" version = "0.48.0" @@ -889,6 +1004,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -913,30 +1037,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -949,12 +1056,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -967,12 +1068,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -985,24 +1080,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1015,12 +1098,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1033,12 +1110,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1051,12 +1122,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1069,12 +1134,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winreg" version = "0.50.0" @@ -1091,12 +1150,42 @@ version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.27" @@ -1114,11 +1203,59 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] -name = "zeroize" -version = "1.8.1" +name = "zerofrom" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index d999704..ffa2369 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,6 @@ path = "src/main.rs" doctest = false -[workspace] -members = [ - "dns", - "dns-transport", -] - - # make dev builds faster by excluding debug symbols [profile.dev] debug = false @@ -39,10 +32,8 @@ panic = "abort" [dependencies] - -# dns stuff -dns = { path = "./dns" } -dns-transport = { path = "./dns-transport" } +hickory-resolver = "0.24" +tokio = { version = "1", features = ["full"] } # command-line ansi_term = "0.12" @@ -67,9 +58,3 @@ datetime = { version = "0.5.2", default-features = false } [dev-dependencies] pretty_assertions = "1.4" - -[features] -default = ["with_idna", "with_https"] -with_idna = ["dns/with_idna"] - -with_https = ["dns-transport/with_https"] diff --git a/README.md b/README.md index 3c971bf..501878c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Dogs _can_ look up! **dog** is a command-line DNS client, like `dig`. It has colourful output, understands normal command-line argument syntax, supports the DNS-over-TLS and DNS-over-HTTPS protocols, and can emit JSON. -It also has comprehensive support for DNSSEC record types including DS, RRSIG, NSEC, DNSKEY, NSEC3, and more. ## Examples @@ -28,9 +27,6 @@ It also has comprehensive support for DNSSEC record types including DS, RRSIG, N dog example.net MX ...looking up MX records instead dog example.net MX @1.1.1.1 ...using a specific nameserver instead dog example.net MX @1.1.1.1 -T ...using TCP rather than UDP - dog example.net DS ...querying DNSSEC delegation signer records - dog example.net DNSKEY ...looking up DNSSEC public keys - dog example.net RRSIG ...checking DNSSEC signatures dog -q example.net -t MX -n 1.1.1.1 -T As above, but using explicit arguments --- @@ -45,12 +41,6 @@ It also has comprehensive support for DNSSEC record types including DS, RRSIG, N -n, --nameserver=ADDR Address of the nameserver to send packets to --class=CLASS Network class of the DNS record being queried (IN, CH, HS) -### Sending options - - --edns=SETTING Whether to OPT in to EDNS (disable, hide, show) - --txid=NUMBER Set the transaction ID to a specific value - -Z=TWEAKS Set uncommon protocol-level tweaks - ### Protocol options -U, --udp Use the DNS protocol over UDP @@ -97,10 +87,7 @@ To build, download the source code and run: $ cargo build $ cargo test -- The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`. -Run `just --list` to get an overview of what’s available. - -- If you are compiling a copy for yourself, be sure to run `cargo build --release` or `just build-release` to benefit from release-mode optimisations. +- If you are compiling a copy for yourself, be sure to run `cargo build --release` to benefit from release-mode optimisations. Copy the resulting binary, which will be in the `target/release` directory, into a folder in your `$PATH`. `/usr/local/bin` is usually a good choice. @@ -137,25 +124,6 @@ Note that this will expose your IP address. For more information, read [the xtests README](xtests/README.md). -### Feature toggles - -dog has two Cargo features that can be switched off to remove functionality. -While doing so makes dog less useful, it results in a smaller binary that takes less time to build. - -There are two feature toggles available, both of which are active by default: - -- `with_idna`, which enables [IDNA](https://en.wikipedia.org/wiki/Internationalized_domain_name) processing -- `with_https`, which enables DNS-over-HTTPS (requires built-in TLS support) - -DNS-over-TLS support is now built-in and always available. - -Use `cargo` to build a binary that uses feature toggles. For example, to disable HTTPS support but keep IDNA support enabled, you can run: - - $ cargo build --no-default-features --features=with_idna - -The list of features that have been disabled can be checked at runtime as part of the `--version` string. - - --- ## Documentation @@ -170,4 +138,4 @@ For documentation on how to use dog, see the website: ## Licence -dog’s source code is licenced under the [European Union Public Licence](https://choosealicense.com/licenses/eupl-1.2/). +dog’s source code is licenced under the [European Union Public Licence](https://choosealicense.com/licenses/eupl-1.2/). \ No newline at end of file diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml deleted file mode 100644 index b34b367..0000000 --- a/dns-transport/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "dns-transport" -version = "0.2.0-pre" -authors = ["Benjamin Sago "] -edition = "2018" - -[lib] -doctest = false -test = false - - -[dependencies] - -# dns wire protocol -dns = { path = "../dns" } - -# logging -log = "0.4" - -# tls networking - -# http response parsing -httparse = { version = "1.10", optional = true } - -rustls = "0.23" - -webpki = "0.22.4" - -webpki-roots = "1.0.2" - -[features] -default = [] # these are enabled in the main dog crate - -with_https = ["httparse"] diff --git a/dns-transport/src/auto.rs b/dns-transport/src/auto.rs deleted file mode 100644 index a70cf1c..0000000 --- a/dns-transport/src/auto.rs +++ /dev/null @@ -1,40 +0,0 @@ -use log::*; - -use dns::{Request, Response}; -use super::{Transport, Error, UdpTransport, TcpTransport}; - - -/// The **automatic transport**, which sends DNS wire data using the UDP -/// transport, then tries using the TCP transport if the first one fails -/// because the response wouldn’t fit in a single UDP packet. -/// -/// This is the default behaviour for many DNS clients. -pub struct AutoTransport { - addr: String, -} - -impl AutoTransport { - - /// Creates a new automatic transport that connects to the given host. - pub fn new(addr: String) -> Self { - Self { addr } - } -} - - -impl Transport for AutoTransport { - fn send(&self, request: &Request) -> Result { - let udp_transport = UdpTransport::new(self.addr.clone()); - let udp_response = udp_transport.send(&request)?; - - if ! udp_response.flags.truncated { - return Ok(udp_response); - } - - debug!("Truncated flag set, so switching to TCP"); - - let tcp_transport = TcpTransport::new(self.addr.clone()); - let tcp_response = tcp_transport.send(&request)?; - Ok(tcp_response) - } -} diff --git a/dns-transport/src/error.rs b/dns-transport/src/error.rs deleted file mode 100644 index 54707a4..0000000 --- a/dns-transport/src/error.rs +++ /dev/null @@ -1,73 +0,0 @@ -/// Something that can go wrong making a DNS request. -#[derive(Debug)] -pub enum Error { - - /// The data in the response did not parse correctly from the DNS wire - /// protocol format. - WireError(dns::WireError), - - /// There was a problem with the network making a TCP or UDP request. - NetworkError(std::io::Error), - - /// Not enough information was received from the server before a `read` - /// call returned zero bytes. - TruncatedResponse, - - /// An error from the TLS library. - RustlsError(rustls::Error), - - /// Provided dns name is not valid - RustlsInvalidDnsNameError(webpki::InvalidDnsNameError), - - /// Provided dns name is not valid - RustlsInvalidDnsNameError2(rustls::pki_types::InvalidDnsNameError), - - /// There was a problem decoding the response HTTP headers or body. - #[cfg(feature = "with_https")] - HttpError(httparse::Error), - - /// The HTTP response code was something other than 200 OK, along with the - /// response code text, if present. - #[cfg(feature = "with_https")] - WrongHttpStatus(u16, Option), -} - - -// From impls - -impl From for Error { - fn from(inner: dns::WireError) -> Self { - Self::WireError(inner) - } -} - -impl From for Error { - fn from(inner: std::io::Error) -> Self { - Self::NetworkError(inner) - } -} - -impl From for Error { - fn from(inner: rustls::Error) -> Self { - Self::RustlsError(inner) - } -} - -impl From for Error { - fn from(inner: webpki::InvalidDnsNameError) -> Self { - Self::RustlsInvalidDnsNameError(inner) - } -} - -impl From for Error { - fn from(inner: rustls::pki_types::InvalidDnsNameError) -> Self { - Self::RustlsInvalidDnsNameError2(inner) - } -} - -#[cfg(feature = "with_https")] -impl From for Error { - fn from(inner: httparse::Error) -> Self { - Self::HttpError(inner) - } -} diff --git a/dns-transport/src/https.rs b/dns-transport/src/https.rs deleted file mode 100644 index e6d9069..0000000 --- a/dns-transport/src/https.rs +++ /dev/null @@ -1,126 +0,0 @@ -#![cfg_attr(not(feature = "with_https"), allow(unused))] - -use std::io::{Read, Write}; - - -use log::*; - -use dns::{Request, Response, WireError}; -use super::{Transport, Error}; - - - -/// The **HTTPS transport**, which sends DNS wire data inside HTTP packets -/// encrypted with TLS, using TCP. -pub struct HttpsTransport { - url: String, -} - -impl HttpsTransport { - - /// Creates a new HTTPS transport that connects to the given URL. - pub fn new(url: String) -> Self { - Self { url } - } -} - -fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { - haystack.windows(needle.len()).position(|window| window == needle) -} - -fn contains_header(buf: &[u8]) -> bool { - let header_end: [u8; 4] = [ 13, 10, 13, 10 ]; - find_subsequence(buf, &header_end).is_some() -} - - - -impl Transport for HttpsTransport { - - #[cfg(any(feature = "with_https"))] - fn send(&self, request: &Request) -> Result { - let (domain, path) = self.split_domain().expect("Invalid HTTPS nameserver"); - - info!("Opening TLS socket to {:?}", domain); - let mut stream = super::tls_stream::stream_tls(&domain, 443)?; - - debug!("Connected"); - - let request_bytes = request.to_bytes().expect("failed to serialise request"); - let mut bytes_to_send = format!("\ - POST {} HTTP/1.1\r\n\ - Host: {}\r\n\ - Content-Type: application/dns-message\r\n\ - Accept: application/dns-message\r\n\ - User-Agent: {}\r\n\ - Content-Length: {}\r\n\r\n", - path, domain, USER_AGENT, request_bytes.len()).into_bytes(); - bytes_to_send.extend(request_bytes); - - info!("Sending {} bytes of data to {:?} over HTTPS", bytes_to_send.len(), self.url); - stream.write_all(&bytes_to_send)?; - debug!("Wrote all bytes"); - - info!("Waiting to receive..."); - let mut buf = [0; 4096]; - let mut read_len = stream.read(&mut buf)?; - while !contains_header(&buf[0..read_len]) { - if read_len == buf.len() { - return Err(Error::WireError(WireError::IO)); - } - read_len += stream.read(&mut buf[read_len..])?; - } - let mut expected_len = read_len; - info!("Received {} bytes of data", read_len); - - let mut headers = [httparse::EMPTY_HEADER; 16]; - let mut response = httparse::Response::new(&mut headers); - let index: usize = response.parse(&buf)?.unwrap(); - - if response.code != Some(200) { - let reason = response.reason.map(str::to_owned); - return Err(Error::WrongHttpStatus(response.code.unwrap(), reason)); - } - - for header in response.headers { - let str_value = String::from_utf8_lossy(header.value); - debug!("Header {:?} -> {:?}", header.name, str_value); - if header.name == "Content-Length" { - let content_length: usize = str_value.parse().unwrap(); - expected_len = index + content_length; - } - } - - while read_len < expected_len { - if read_len == buf.len() { - return Err(Error::WireError(WireError::IO)); - } - read_len += stream.read(&mut buf[read_len..])?; - } - - let body = &buf[index .. read_len]; - debug!("HTTP body has {} bytes", body.len()); - let response = Response::from_bytes(&body)?; - Ok(response) - } - - #[cfg(not(feature = "with_https"))] - fn send(&self, request: &Request) -> Result { - unreachable!("HTTPS feature disabled") - } -} - -impl HttpsTransport { - fn split_domain(&self) -> Option<(&str, &str)> { - if let Some(sp) = self.url.strip_prefix("https://") { - if let Some(colon_index) = sp.find('/') { - return Some((&sp[.. colon_index], &sp[colon_index ..])); - } - } - - None - } -} - -/// The User-Agent header sent with HTTPS requests. -static USER_AGENT: &str = concat!("dog/", env!("CARGO_PKG_VERSION")); diff --git a/dns-transport/src/lib.rs b/dns-transport/src/lib.rs deleted file mode 100644 index ef0a8be..0000000 --- a/dns-transport/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! All the DNS transport types. - -#![warn(deprecated_in_future)] -#![warn(future_incompatible)] -#![warn(missing_copy_implementations)] -#![warn(missing_docs)] -#![warn(nonstandard_style)] -#![warn(rust_2018_compatibility)] -#![warn(rust_2018_idioms)] -#![warn(single_use_lifetimes)] -#![warn(trivial_casts, trivial_numeric_casts)] -#![warn(unused)] - -#![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::module_name_repetitions)] -#![allow(clippy::must_use_candidate)] -#![allow(clippy::option_if_let_else)] -#![allow(clippy::pub_enum_variant_names)] -#![allow(clippy::wildcard_imports)] - -#![deny(clippy::cast_possible_truncation)] -#![deny(clippy::cast_lossless)] -#![deny(clippy::cast_possible_wrap)] -#![deny(clippy::cast_sign_loss)] -#![deny(unsafe_code)] - - -mod auto; -pub use self::auto::AutoTransport; - -mod udp; -pub use self::udp::UdpTransport; - -mod tcp; -pub use self::tcp::TcpTransport; - -mod tls; -pub use self::tls::TlsTransport; - -mod https; -pub use self::https::HttpsTransport; - -mod error; - -mod tls_stream; - -pub use self::error::Error; - -/// The trait implemented by all transport types. -pub trait Transport { - - /// Convert the request to bytes, send it over the network, wait for a - /// response, deserialise it from bytes, and return it, asynchronously. - /// - /// # Errors - /// - /// Returns an `Error` error if there’s an I/O error sending or - /// receiving data, or the DNS packet in the response contained invalid - /// bytes and failed to parse, or if there was a protocol-level error for - /// the TLS and HTTPS transports. - fn send(&self, request: &dns::Request) -> Result; -} diff --git a/dns-transport/src/tcp.rs b/dns-transport/src/tcp.rs deleted file mode 100644 index f9327a9..0000000 --- a/dns-transport/src/tcp.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::convert::TryFrom; -use std::net::TcpStream; -use std::io::{Read, Write}; - -use log::*; - -use dns::{Request, Response}; -use super::{Transport, Error}; - - -/// The **TCP transport**, which sends DNS wire data over a TCP stream. -/// -/// # References -/// -/// - [RFC 1035 §4.2.2](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -/// - [RFC 7766](https://tools.ietf.org/html/rfc1035) — DNS Transport over -/// TCP, Implementation Requirements (March 2016) -pub struct TcpTransport { - addr: String, -} - -impl TcpTransport { - - /// Creates a new TCP transport that connects to the given host. - pub fn new(addr: String) -> Self { - Self { addr } - } -} - - -impl Transport for TcpTransport { - fn send(&self, request: &Request) -> Result { - info!("Opening TCP stream"); - let mut stream = - if self.addr.contains(':') { - TcpStream::connect(&*self.addr)? - } - else { - TcpStream::connect((&*self.addr, 53))? - }; - debug!("Opened"); - - // The message is prepended with the length when sent over TCP, - // so the server knows how long it is (RFC 1035 §4.2.2) - let mut bytes_to_send = request.to_bytes().expect("failed to serialise request"); - Self::prefix_with_length(&mut bytes_to_send); - - info!("Sending {} bytes of data to {:?} over TCP", bytes_to_send.len(), self.addr); - let written_len = stream.write(&bytes_to_send)?; - debug!("Wrote {} bytes", written_len); - - let read_bytes = Self::length_prefixed_read(&mut stream)?; - let response = Response::from_bytes(&read_bytes)?; - Ok(response) - } -} - -impl TcpTransport { - - /// Mutate the given byte buffer, prefixing it with its own length as a - /// big-endian `u16`. - pub(crate) fn prefix_with_length(bytes: &mut Vec) { - let len_bytes = u16::try_from(bytes.len()) - .expect("request too long") - .to_be_bytes(); - - bytes.insert(0, len_bytes[0]); - bytes.insert(1, len_bytes[1]); - } - - /// Reads from the given I/O source as many times as necessary to read a - /// length-prefixed stream of bytes. The first two bytes are taken as a - /// big-endian `u16` to determine the length. Then, that many bytes are - /// read from the source. - /// - /// # Errors - /// - /// Returns an error if there’s a network error during reading, or not - /// enough bytes have been sent. - pub(crate) fn length_prefixed_read(stream: &mut impl Read) -> Result, Error> { - info!("Waiting to receive..."); - - let mut buf = [0; 4096]; - let mut read_len = stream.read(&mut buf[..])?; - - if read_len == 0 { - warn!("Received no bytes!"); - return Err(Error::TruncatedResponse); - } - else if read_len == 1 { - info!("Received one byte of data"); - let second_read_len = stream.read(&mut buf[1..])?; - if second_read_len == 0 { - warn!("Received no bytes the second time!"); - return Err(Error::TruncatedResponse); - } - - read_len += second_read_len; - } - else { - info!("Received {} bytes of data", read_len); - } - - let total_len = u16::from_be_bytes([buf[0], buf[1]]); - if read_len - 2 == usize::from(total_len) { - debug!("We have enough bytes"); - return Ok(buf[2..read_len].to_vec()); - } - - debug!("We need to read {} bytes total", total_len); - let mut combined_buffer = buf[2..read_len].to_vec(); - while combined_buffer.len() < usize::from(total_len) { - let mut extend_buf = [0; 4096]; - let extend_len = stream.read(&mut extend_buf[..])?; - info!("Received further {} bytes of data (of {})", extend_len, total_len); - - if read_len == 0 { - warn!("Read zero bytes!"); - return Err(Error::TruncatedResponse); - } - - combined_buffer.extend(&extend_buf[0 .. extend_len]); - } - - Ok(combined_buffer) - } -} diff --git a/dns-transport/src/tls.rs b/dns-transport/src/tls.rs deleted file mode 100644 index 1ccc524..0000000 --- a/dns-transport/src/tls.rs +++ /dev/null @@ -1,77 +0,0 @@ - - - -use std::io::Write; - -use log::*; - -use dns::{Request, Response}; -use super::{Transport, Error, TcpTransport}; - - - -/// The **TLS transport**, which sends DNS wire data using TCP through an -/// encrypted TLS connection. -pub struct TlsTransport { - addr: String, -} - -impl TlsTransport { - - /// Creates a new TLS transport that connects to the given host. - pub fn new(addr: String) -> Self { - Self { addr } - } -} - - - -impl Transport for TlsTransport { - - fn send(&self, request: &Request) -> Result { - info!("Opening TLS socket"); - - let domain = self.sni_domain(); - info!("Connecting using domain {:?}", domain); - let mut stream = - if self.addr.contains(':') { - let mut parts = self.addr.split(":"); - let domain = parts.nth(0).unwrap(); - let port = parts.last().unwrap().parse::().expect("Invalid port number"); - - super::tls_stream::stream_tls(domain, port)? - } - else { - super::tls_stream::stream_tls(&*self.addr, 853)? - }; - - - debug!("Connected"); - - // The message is prepended with the length when sent over TCP, - // so the server knows how long it is (RFC 1035 §4.2.2) - let mut bytes_to_send = request.to_bytes().expect("failed to serialise request"); - TcpTransport::prefix_with_length(&mut bytes_to_send); - - info!("Sending {} bytes of data to {} over TLS", bytes_to_send.len(), self.addr); - stream.write_all(&bytes_to_send)?; - debug!("Wrote all bytes"); - - let read_bytes = TcpTransport::length_prefixed_read(&mut stream)?; - let response = Response::from_bytes(&read_bytes)?; - Ok(response) - } - - -} - -impl TlsTransport { - fn sni_domain(&self) -> &str { - if let Some(colon_index) = self.addr.find(':') { - &self.addr[.. colon_index] - } - else { - &self.addr[..] - } - } -} diff --git a/dns-transport/src/tls_stream.rs b/dns-transport/src/tls_stream.rs deleted file mode 100644 index 72c0105..0000000 --- a/dns-transport/src/tls_stream.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::net::TcpStream; -use std::sync::Arc; -use rustls::pki_types::ServerName; -use super::Error; -use std::convert::TryFrom; - -pub fn stream_tls(domain: &str, port: u16) -> Result, Error> { - let root_store = rustls::RootCertStore { - roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), - }; - - let config = rustls::ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - - let server_name = ServerName::try_from(domain)?.to_owned(); - - let conn = rustls::ClientConnection::new(Arc::new(config), server_name)?; - - let sock = TcpStream::connect((domain, port))?; - let tls = rustls::StreamOwned::new(conn, sock); - - Ok(tls) -} diff --git a/dns-transport/src/udp.rs b/dns-transport/src/udp.rs deleted file mode 100644 index 785b3d4..0000000 --- a/dns-transport/src/udp.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::net::{Ipv4Addr, UdpSocket}; - -use log::*; - -use dns::{Request, Response}; -use super::{Transport, Error}; - - -/// The **UDP transport**, which sends DNS wire data inside a UDP datagram. -/// -/// # References -/// -/// - [RFC 1035 §4.2.1](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -pub struct UdpTransport { - addr: String, -} - -impl UdpTransport { - - /// Creates a new UDP transport that connects to the given host. - pub fn new(addr: String) -> Self { - Self { addr } - } -} - - -impl Transport for UdpTransport { - fn send(&self, request: &Request) -> Result { - info!("Opening UDP socket"); - // TODO: This will need to be changed for IPv6 support. - let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; - - if self.addr.contains(':') { - socket.connect(&*self.addr)?; - } - else { - socket.connect((&*self.addr, 53))?; - } - debug!("Opened"); - - let bytes_to_send = request.to_bytes().expect("failed to serialise request"); - - info!("Sending {} bytes of data to {} over UDP", bytes_to_send.len(), self.addr); - let written_len = socket.send(&bytes_to_send)?; - debug!("Wrote {} bytes", written_len); - - info!("Waiting to receive..."); - let mut buf = vec![0; 4096]; - let received_len = socket.recv(&mut buf)?; - - info!("Received {} bytes of data", received_len); - let response = Response::from_bytes(&buf[.. received_len])?; - Ok(response) - } -} diff --git a/dns/Cargo.toml b/dns/Cargo.toml deleted file mode 100644 index f43268d..0000000 --- a/dns/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "dns" -version = "0.2.0-pre" -authors = ["Benjamin Sago "] -edition = "2018" - -[lib] -doctest = false - - -[dependencies] - -# logging -log = "0.4" - -# protocol parsing helper -byteorder = "1.5" - -# printing of certain packets -base64 = "0.22" - -# idna encoding -unic-idna = { version = "0.9.0", optional = true } - -# mutation testing -mutagen = { git = "https://github.com/llogiq/mutagen", optional = true } - -[dev-dependencies] -pretty_assertions = "1.4" - -[features] -default = [] # idna is enabled in the main dog crate -with_idna = ["unic-idna"] -with_mutagen = ["mutagen"] # needs nightly diff --git a/dns/fuzz/.gitignore b/dns/fuzz/.gitignore deleted file mode 100644 index 572e03b..0000000 --- a/dns/fuzz/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -target -corpus -artifacts diff --git a/dns/fuzz/Cargo.lock b/dns/fuzz/Cargo.lock deleted file mode 100644 index 48caf6e..0000000 --- a/dns/fuzz/Cargo.lock +++ /dev/null @@ -1,62 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "arbitrary" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cc" -version = "1.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "dns" -version = "0.1.0" -dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dns-fuzz" -version = "0.0.1" -dependencies = [ - "dns 0.1.0", - "libfuzzer-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libfuzzer-sys" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arbitrary 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum arbitrary 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0922a3e746b5a44e111e5603feb6704e5cc959116f66737f50bb5cbd264e9d87" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cc 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)" = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum libfuzzer-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc" -"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" diff --git a/dns/fuzz/Cargo.toml b/dns/fuzz/Cargo.toml deleted file mode 100644 index 6df808e..0000000 --- a/dns/fuzz/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "dns-fuzz" -version = "0.0.1" -authors = ["Automatically generated"] -publish = false - -[package.metadata] -cargo-fuzz = true - -[dependencies.dns] -path = ".." - -[dependencies.libfuzzer-sys] -version = "0.3.0" - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - -[[bin]] -name = "fuzz_parsing" -path = "fuzz_targets/fuzz_parsing.rs" diff --git a/dns/fuzz/fuzz_targets/fuzz_parsing.rs b/dns/fuzz/fuzz_targets/fuzz_parsing.rs deleted file mode 100644 index dbc05f3..0000000 --- a/dns/fuzz/fuzz_targets/fuzz_parsing.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![no_main] -#[macro_use] extern crate libfuzzer_sys; -extern crate dns; -use dns::Response; - -fuzz_target!(|data: &[u8]| { - let _ = Response::from_bytes(data); -}); diff --git a/dns/src/lib.rs b/dns/src/lib.rs deleted file mode 100644 index fe2d443..0000000 --- a/dns/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![warn(deprecated_in_future)] -#![warn(future_incompatible)] -#![warn(missing_copy_implementations)] -#![warn(missing_docs)] -#![warn(nonstandard_style)] -#![warn(rust_2018_compatibility)] -#![warn(rust_2018_idioms)] -#![warn(single_use_lifetimes)] -#![warn(trivial_casts, trivial_numeric_casts)] -#![warn(unused)] - -#![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::doc_markdown)] -#![allow(clippy::len_without_is_empty)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::module_name_repetitions)] -#![allow(clippy::must_use_candidate)] -#![allow(clippy::non_ascii_literal)] -#![allow(clippy::redundant_else)] -#![allow(clippy::struct_excessive_bools)] -#![allow(clippy::upper_case_acronyms)] -#![allow(clippy::wildcard_imports)] - -#![deny(clippy::cast_possible_truncation)] -#![deny(clippy::cast_lossless)] -#![deny(clippy::cast_possible_wrap)] -#![deny(clippy::cast_sign_loss)] -#![deny(unsafe_code)] - - -//! The DNS crate is the ‘library’ part of dog. It implements the DNS -//! protocol: creating and decoding packets from their byte structure. - - -mod types; -pub use self::types::*; - -mod strings; -pub use self::strings::Labels; - -mod wire; -pub use self::wire::{Wire, WireError, MandatedLength}; - -pub mod record; diff --git a/dns/src/record/a.rs b/dns/src/record/a.rs deleted file mode 100644 index 7500c1c..0000000 --- a/dns/src/record/a.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::net::Ipv4Addr; - -use log::*; - -use crate::wire::*; - - -/// An **A** record type, which contains an `Ipv4Address`. -/// -/// # References -/// -/// - [RFC 1035 §3.4.1](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct A { - - /// The IPv4 address contained in the packet. - pub address: Ipv4Addr, -} - -impl Wire for A { - const NAME: &'static str = "A"; - const RR_TYPE: u16 = 1; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length != 4 { - warn!("Length is incorrect (record length {:?}, but should be four)", stated_length); - let mandated_length = MandatedLength::Exactly(4); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let mut buf = [0_u8; 4]; - c.read_exact(&mut buf)?; - - let address = Ipv4Addr::from(buf); - trace!("Parsed IPv4 address -> {:?}", address); - - Ok(Self { address }) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x7F, 0x00, 0x00, 0x01, // IPv4 address - ]; - - assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - A { address: Ipv4Addr::new(127, 0, 0, 1) }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x7F, 0x00, 0x00, // Too short IPv4 address - ]; - - assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(4) })); - } - - #[test] - fn record_too_long() { - let buf = &[ - 0x7F, 0x00, 0x00, 0x00, // IPv4 address - 0x01, // Unexpected extra byte - ]; - - assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 5, mandated_length: MandatedLength::Exactly(4) })); - } - - #[test] - fn record_empty() { - assert_eq!(A::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(4) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x7F, 0x00, // Half an IPv4 address - ]; - - assert_eq!(A::read(4, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/aaaa.rs b/dns/src/record/aaaa.rs deleted file mode 100644 index 64971f8..0000000 --- a/dns/src/record/aaaa.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::net::Ipv6Addr; - -use log::*; - -use crate::wire::*; - - -/// A **AAAA** record, which contains an `Ipv6Address`. -/// -/// # References -/// -/// - [RFC 3596](https://tools.ietf.org/html/rfc3596) — DNS Extensions to -/// Support IP Version 6 (October 2003) -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct AAAA { - - /// The IPv6 address contained in the packet. - pub address: Ipv6Addr, -} - -impl Wire for AAAA { - const NAME: &'static str = "AAAA"; - const RR_TYPE: u16 = 28; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length != 16 { - warn!("Length is incorrect (stated length {:?}, but should be sixteen)", stated_length); - let mandated_length = MandatedLength::Exactly(16); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let mut buf = [0_u8; 16]; - c.read_exact(&mut buf)?; - - let address = Ipv6Addr::from(buf); - trace!("Parsed IPv6 address -> {:#x?}", address); - - Ok(Self { address }) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IPv6 address - ]; - - assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - AAAA { address: Ipv6Addr::new(0,0,0,0,0,0,0,0) }); - } - - #[test] - fn record_too_long() { - let buf = &[ - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, // IPv6 address - 0x09, // Unexpected extra byte - ]; - - assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 17, mandated_length: MandatedLength::Exactly(16) })); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x05, 0x05, 0x05, 0x05, 0x05, // Five arbitrary bytes - ]; - - assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 5, mandated_length: MandatedLength::Exactly(16) })); - } - - #[test] - fn record_empty() { - assert_eq!(AAAA::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(16) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x05, 0x05, 0x05, 0x05, 0x05, // Five arbitrary bytes - ]; - - assert_eq!(AAAA::read(16, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/caa.rs b/dns/src/record/caa.rs deleted file mode 100644 index 33f6aaf..0000000 --- a/dns/src/record/caa.rs +++ /dev/null @@ -1,134 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **CAA** _(certification authority authorization)_ record. These allow -/// domain names to specify which Certificate Authorities are allowed to issue -/// certificates for the domain. -/// -/// # References -/// -/// - [RFC 6844](https://tools.ietf.org/html/rfc6844) — DNS Certification -/// Authority Authorization Resource Record (January 2013) -#[derive(PartialEq, Debug)] -pub struct CAA { - - /// Whether this record is marked as “critical” or not. - pub critical: bool, - - /// The “tag” part of the CAA record. - pub tag: Box<[u8]>, - - /// The “value” part of the CAA record. - pub value: Box<[u8]>, -} - -impl Wire for CAA { - const NAME: &'static str = "CAA"; - const RR_TYPE: u16 = 257; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - - // flags - let flags = c.read_u8()?; - trace!("Parsed flags -> {:#08b}", flags); - - let has_bit = |bit| { flags & bit == bit }; - let critical = has_bit(0b_1000_0000); - trace!("Parsed critical flag -> {:?}", critical); - - // tag - let tag_length = c.read_u8()?; - trace!("Parsed tag length -> {:?}", tag_length); - - let mut tag = vec![0_u8; usize::from(tag_length)].into_boxed_slice(); - c.read_exact(&mut tag)?; - trace!("Parsed tag -> {:?}", String::from_utf8_lossy(&tag)); - - // value - let remaining_length = stated_length.saturating_sub(u16::from(tag_length)).saturating_sub(2); - trace!("Remaining length -> {:?}", remaining_length); - - let mut value = vec![0_u8; usize::from(remaining_length)].into_boxed_slice(); - c.read_exact(&mut value)?; - trace!("Parsed value -> {:?}", String::from_utf8_lossy(&value)); - - Ok(Self { critical, tag, value }) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses_non_critical() { - let buf = &[ - 0x00, // flags (all unset) - 0x09, // tag length - 0x69, 0x73, 0x73, 0x75, 0x65, 0x77, 0x69, 0x6c, 0x64, // tag - 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, // value - ]; - - assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - CAA { - critical: false, - tag: Box::new(*b"issuewild"), - value: Box::new(*b"entrust.net"), - }); - } - - #[test] - fn parses_critical() { - let buf = &[ - 0x80, // flags (critical bit set) - 0x09, // tag length - 0x69, 0x73, 0x73, 0x75, 0x65, 0x77, 0x69, 0x6c, 0x64, // tag - 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, // value - ]; - - assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - CAA { - critical: true, - tag: Box::new(*b"issuewild"), - value: Box::new(*b"entrust.net"), - }); - } - - #[test] - fn ignores_other_flags() { - let buf = &[ - 0x7F, // flags (all except critical bit set) - 0x01, // tag length - 0x65, // tag - 0x45, // value - ]; - - assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - CAA { - critical: false, - tag: Box::new(*b"e"), - value: Box::new(*b"E"), - }); - } - - #[test] - fn record_empty() { - assert_eq!(CAA::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // flags - ]; - - assert_eq!(CAA::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/cname.rs b/dns/src/record/cname.rs deleted file mode 100644 index c1c81a0..0000000 --- a/dns/src/record/cname.rs +++ /dev/null @@ -1,86 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **CNAME** _(canonical name)_ record, which aliases one domain to another. -/// -/// # References -/// -/// - [RFC 1035 §3.3.1](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct CNAME { - - /// The domain name that this CNAME record is responding with. - pub domain: Labels, -} - -impl Wire for CNAME { - const NAME: &'static str = "CNAME"; - const RR_TYPE: u16 = 5; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let (domain, domain_length) = c.read_labels()?; - trace!("Parsed domain -> {:?}", domain); - - if stated_length == domain_length { - trace!("Length is correct"); - Ok(Self { domain }) - } - else { - warn!("Length is incorrect (stated length {:?}, domain length {:?})", stated_length, domain_length); - Err(WireError::WrongLabelLength { stated_length, length_after_labels: domain_length }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // domain - 0x00, // domain terminator - ]; - - assert_eq!(CNAME::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - CNAME { - domain: Labels::encode("bsago.me").unwrap(), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x03, 0x65, 0x66, 0x67, // domain - 0x00, // domain terminator - ]; - - assert_eq!(CNAME::read(6, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 5 })); - } - - #[test] - fn record_empty() { - assert_eq!(CNAME::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x05, 0x62, 0x73, // the stard of a string - ]; - - assert_eq!(CNAME::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} - diff --git a/dns/src/record/dhcid.rs b/dns/src/record/dhcid.rs deleted file mode 100644 index cda7a00..0000000 --- a/dns/src/record/dhcid.rs +++ /dev/null @@ -1,81 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **DHCID** record, which identifies the client to the DHCP server for DNS updates. -/// -/// # References -/// -/// - [RFC 4701](https://tools.ietf.org/html/rfc4701) — A DNS Resource Record (RR) for Encoding DHCP Information (October 2006) -#[derive(PartialEq, Debug)] -pub struct DHCID { - /// The identifier type code, indicating how the client identifier was formed. - pub identifier_type_code: u8, - - /// The digest type code, indicating the algorithm used to create the digest. - pub digest_type_code: u8, - - /// The digest of the client identifier. - pub digest: Vec, -} - -impl Wire for DHCID { - const NAME: &'static str = "DHCID"; - const RR_TYPE: u16 = 49; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let identifier_type_code = c.read_u8()?; - trace!("Parsed identifier_type_code -> {:?}", identifier_type_code); - - let digest_type_code = c.read_u8()?; - trace!("Parsed digest_type_code -> {:?}", digest_type_code); - - let digest_len = stated_length - 2; - let mut digest = vec![]; - for _ in 0..digest_len { - digest.push(c.read_u8()?); - } - trace!("Parsed digest -> {:?}", digest); - - Ok(Self { identifier_type_code, digest_type_code, digest }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, // identifier_type_code - 0x01, // digest_type_code - 0x12, 0x34, 0x56, 0x78, // digest (4 bytes for example) - ]; - - assert_eq!(DHCID::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - DHCID { - identifier_type_code: 0, - digest_type_code: 1, - digest: vec![0x12, 0x34, 0x56, 0x78], - }); - } - - #[test] - fn record_empty() { - assert_eq!(DHCID::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // only one byte - ]; - - assert_eq!(DHCID::read(4, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/dnskey.rs b/dns/src/record/dnskey.rs deleted file mode 100644 index e36004c..0000000 --- a/dns/src/record/dnskey.rs +++ /dev/null @@ -1,89 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **DNSKEY** record, which contains a public key for DNSSEC. -/// -/// # References -/// -/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) -#[derive(PartialEq, Debug)] -pub struct DNSKEY { - /// The flags field indicates the key's properties. - pub flags: u16, - - /// The protocol field must be set to 3. - pub protocol: u8, - - /// The algorithm field indicates the algorithm used to generate the key. - pub algorithm: u8, - - /// The public key itself. - pub public_key: Vec, -} - -impl Wire for DNSKEY { - const NAME: &'static str = "DNSKEY"; - const RR_TYPE: u16 = 48; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let flags = c.read_u16::()?; - trace!("Parsed flags -> {:?}", flags); - - let protocol = c.read_u8()?; - trace!("Parsed protocol -> {:?}", protocol); - - let algorithm = c.read_u8()?; - trace!("Parsed algorithm -> {:?}", algorithm); - - let public_key_len = stated_length - 4; - let mut public_key = vec![]; - for _ in 0..public_key_len { - public_key.push(c.read_u8()?); - } - trace!("Parsed public_key -> {:?}", public_key); - - Ok(Self { flags, protocol, algorithm, public_key }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x01, // flags - 0x03, // protocol - 0x05, // algorithm - 0x12, 0x34, 0x56, 0x78, // public_key (4 bytes for example) - ]; - - assert_eq!(DNSKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - DNSKEY { - flags: 1, - protocol: 3, - algorithm: 5, - public_key: vec![0x12, 0x34, 0x56, 0x78], - }); - } - - #[test] - fn record_empty() { - assert_eq!(DNSKEY::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // half a flags - ]; - - assert_eq!(DNSKEY::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/ds.rs b/dns/src/record/ds.rs deleted file mode 100644 index 44f8468..0000000 --- a/dns/src/record/ds.rs +++ /dev/null @@ -1,89 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **DS** record, which contains a delegation signer for DNSSEC. -/// -/// # References -/// -/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) -#[derive(PartialEq, Debug)] -pub struct DS { - /// The key tag of the DNSKEY RR that the DS record refers to. - pub key_tag: u16, - - /// The algorithm number of the DNSKEY RR that the DS record refers to. - pub algorithm: u8, - - /// The algorithm used to construct the digest. - pub digest_type: u8, - - /// The digest of the DNSKEY RR. - pub digest: Vec, -} - -impl Wire for DS { - const NAME: &'static str = "DS"; - const RR_TYPE: u16 = 43; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let key_tag = c.read_u16::()?; - trace!("Parsed key_tag -> {:?}", key_tag); - - let algorithm = c.read_u8()?; - trace!("Parsed algorithm -> {:?}", algorithm); - - let digest_type = c.read_u8()?; - trace!("Parsed digest_type -> {:?}", digest_type); - - let digest_len = stated_length - 4; - let mut digest = vec![]; - for _ in 0..digest_len { - digest.push(c.read_u8()?); - } - trace!("Parsed digest -> {:?}", digest); - - Ok(Self { key_tag, algorithm, digest_type, digest }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x01, // key_tag - 0x05, // algorithm - 0x01, // digest_type - 0x12, 0x34, 0x56, 0x78, // digest (4 bytes for example) - ]; - - assert_eq!(DS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - DS { - key_tag: 1, - algorithm: 5, - digest_type: 1, - digest: vec![0x12, 0x34, 0x56, 0x78], - }); - } - - #[test] - fn record_empty() { - assert_eq!(DS::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // half a key_tag - ]; - - assert_eq!(DS::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/eui48.rs b/dns/src/record/eui48.rs deleted file mode 100644 index 253816f..0000000 --- a/dns/src/record/eui48.rs +++ /dev/null @@ -1,111 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **EUI48** record, which holds a six-octet (48-bit) Extended Unique -/// Identifier. These identifiers can be used as MAC addresses. -/// -/// # References -/// -/// - [RFC 7043](https://tools.ietf.org/html/rfc7043) — Resource Records for -/// EUI-48 and EUI-64 Addresses in the DNS (October 2013) -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct EUI48 { - - /// The six octets that make up the identifier. - pub octets: [u8; 6], -} - -impl Wire for EUI48 { - const NAME: &'static str = "EUI48"; - const RR_TYPE: u16 = 108; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length != 6 { - warn!("Length is incorrect (record length {:?}, but should be six)", stated_length); - let mandated_length = MandatedLength::Exactly(6); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let mut octets = [0_u8; 6]; - c.read_exact(&mut octets)?; - trace!("Parsed 6-byte address -> {:#x?}", octets); - - Ok(Self { octets }) - } -} - - -impl EUI48 { - - /// Returns this EUI as hexadecimal numbers, separated by dashes. - pub fn formatted_address(self) -> String { - format!("{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", - self.octets[0], self.octets[1], self.octets[2], - self.octets[3], self.octets[4], self.octets[5]) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, // identifier - ]; - - assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - EUI48 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56 ] }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x00, 0x7F, 0x23, // a mere OUI - ]; - - assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(6) })); - } - - #[test] - fn record_too_long() { - let buf = &[ - 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, // identifier - 0x01, // an unexpected extra byte - ]; - - assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 7, mandated_length: MandatedLength::Exactly(6) })); - } - - #[test] - fn record_empty() { - assert_eq!(EUI48::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(6) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, 0x7F, 0x23, // a mere OUI - ]; - - assert_eq!(EUI48::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } - - #[test] - fn hex_rep() { - let record = EUI48 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56 ] }; - - assert_eq!(record.formatted_address(), - "00-7f-23-12-34-56"); - } -} diff --git a/dns/src/record/eui64.rs b/dns/src/record/eui64.rs deleted file mode 100644 index c874b5b..0000000 --- a/dns/src/record/eui64.rs +++ /dev/null @@ -1,111 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **EUI64** record, which holds an eight-octet (64-bit) Extended Unique -/// Identifier. -/// -/// # References -/// -/// - [RFC 7043](https://tools.ietf.org/html/rfc7043) — Resource Records for -/// EUI-48 and EUI-64 Addresses in the DNS (October 2013) -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct EUI64 { - - /// The eight octets that make up the identifier. - pub octets: [u8; 8], -} - -impl Wire for EUI64 { - const NAME: &'static str = "EUI64"; - const RR_TYPE: u16 = 109; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length != 8 { - warn!("Length is incorrect (record length {:?}, but should be eight)", stated_length); - let mandated_length = MandatedLength::Exactly(8); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let mut octets = [0_u8; 8]; - c.read_exact(&mut octets)?; - trace!("Parsed 8-byte address -> {:#x?}", octets); - - Ok(Self { octets }) - } -} - - -impl EUI64 { - - /// Returns this EUI as hexadecimal numbers, separated by dashes. - pub fn formatted_address(self) -> String { - format!("{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", - self.octets[0], self.octets[1], self.octets[2], self.octets[3], - self.octets[4], self.octets[5], self.octets[6], self.octets[7]) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90, // identifier - ]; - - assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - EUI64 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90 ] }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x00, 0x7F, 0x23, // a mere OUI - ]; - - assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(8) })); - } - - #[test] - fn record_too_long() { - let buf = &[ - 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90, // identifier - 0x01, // an unexpected extra byte - ]; - - assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 9, mandated_length: MandatedLength::Exactly(8) })); - } - - #[test] - fn record_empty() { - assert_eq!(EUI64::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(8) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, 0x7F, 0x23, // a mere OUI - ]; - - assert_eq!(EUI64::read(8, &mut Cursor::new(buf)), - Err(WireError::IO)); - } - - #[test] - fn hex_rep() { - let record = EUI64 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90 ] }; - - assert_eq!(record.formatted_address(), - "00-7f-23-12-34-56-78-90"); - } -} diff --git a/dns/src/record/hinfo.rs b/dns/src/record/hinfo.rs deleted file mode 100644 index 3e7c750..0000000 --- a/dns/src/record/hinfo.rs +++ /dev/null @@ -1,112 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A (an?) **HINFO** _(host information)_ record, which contains the CPU and -/// OS information about a host. -/// -/// It also gets used as the response for an `ANY` query, if it is blocked. -/// -/// # References -/// -/// - [RFC 1035 §3.3.2](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -/// - [RFC 8482 §6](https://tools.ietf.org/html/rfc8482#section-6) — Providing -/// Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY (January 2019) -#[derive(PartialEq, Debug)] -pub struct HINFO { - - /// The CPU field, specifying the CPU type. - pub cpu: Box<[u8]>, - - /// The OS field, specifying the operating system. - pub os: Box<[u8]>, -} - -impl Wire for HINFO { - const NAME: &'static str = "HINFO"; - const RR_TYPE: u16 = 13; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - - let cpu_length = c.read_u8()?; - trace!("Parsed CPU length -> {:?}", cpu_length); - - let mut cpu = vec![0_u8; usize::from(cpu_length)].into_boxed_slice(); - c.read_exact(&mut cpu)?; - trace!("Parsed CPU -> {:?}", String::from_utf8_lossy(&cpu)); - - let os_length = c.read_u8()?; - trace!("Parsed OS length -> {:?}", os_length); - - let mut os = vec![0_u8; usize::from(os_length)].into_boxed_slice(); - c.read_exact(&mut os)?; - trace!("Parsed OS -> {:?}", String::from_utf8_lossy(&os)); - - let length_after_labels = 1 + u16::from(cpu_length) + 1 + u16::from(os_length); - if stated_length == length_after_labels { - trace!("Length is correct"); - Ok(Self { cpu, os }) - } - else { - warn!("Length is incorrect (stated length {:?}, cpu plus length {:?}", stated_length, length_after_labels); - Err(WireError::WrongLabelLength { stated_length, length_after_labels }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x0e, // cpu length - 0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d, - 0x63, 0x70, 0x75, // cpu - 0x0d, // os length - 0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d, - 0x6f, 0x73, // os - ]; - - assert_eq!(HINFO::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - HINFO { - cpu: Box::new(*b"some-kinda-cpu"), - os: Box::new(*b"some-kinda-os"), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x03, // cpu length - 0x65, 0x66, 0x67, // cpu - 0x03, // os length - 0x68, 0x69, 0x70, // os - ]; - - assert_eq!(HINFO::read(6, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 8 })); - } - - #[test] - fn record_empty() { - assert_eq!(HINFO::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x14, 0x0A, 0x0B, 0x0C, // 32-bit CPU - ]; - - assert_eq!(HINFO::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/ipseckey.rs b/dns/src/record/ipseckey.rs deleted file mode 100644 index 7ee0dcb..0000000 --- a/dns/src/record/ipseckey.rs +++ /dev/null @@ -1,115 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **IPSECKEY** record, which contains an IPsec key for the domain. -/// -/// # References -/// -/// - [RFC 4025](https://tools.ietf.org/html/rfc4025) — A Method for Storing IPsec Keying Material in DNS (February 2005) -#[derive(PartialEq, Debug)] -pub struct IPSECKEY { - /// The precedence of this key. - pub precedence: u8, - - /// The type of gateway. - pub gateway_type: u8, - - /// The algorithm used for the public key. - pub algorithm: u8, - - /// The gateway address or name. - pub gateway: Vec, - - /// The public key. - pub public_key: Vec, -} - -impl Wire for IPSECKEY { - const NAME: &'static str = "IPSECKEY"; - const RR_TYPE: u16 = 45; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let precedence = c.read_u8()?; - trace!("Parsed precedence -> {:?}", precedence); - - let gateway_type = c.read_u8()?; - trace!("Parsed gateway_type -> {:?}", gateway_type); - - let algorithm = c.read_u8()?; - trace!("Parsed algorithm -> {:?}", algorithm); - - let mut bytes_left = stated_length - 3; - let gateway_len = match gateway_type { - 1 => 4, // IPv4 - 2 => 16, // IPv6 - 3 => { - // FQDN, need to read labels - // But for simplicity, we'll read as Vec until null or something, but it's complicated - // For this implementation, assume we read until the remaining is public key - // Actually, for FQDN, it's a domain name followed by public key - // To keep it simple, read as much as needed, but better to handle properly - // For now, let's assume gateway is variable, but calculate based on type - unimplemented!("FQDN gateway parsing not implemented yet"); - } - _ => 0, // no gateway - }; - let mut gateway = vec![]; - for _ in 0..gateway_len { - gateway.push(c.read_u8()?); - bytes_left -= 1; - } - trace!("Parsed gateway -> {:?}", gateway); - - let mut public_key = vec![]; - for _ in 0..bytes_left { - public_key.push(c.read_u8()?); - } - trace!("Parsed public_key -> {:?}", public_key); - - Ok(Self { precedence, gateway_type, algorithm, gateway, public_key }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x01, // precedence - 0x01, // gateway_type (IPv4) - 0x05, // algorithm - 0xc0, 0xa8, 0x00, 0x01, // gateway IPv4: 192.168.0.1 - 0x12, 0x34, 0x56, 0x78, // public_key (4 bytes for example) - ]; - - assert_eq!(IPSECKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - IPSECKEY { - precedence: 1, - gateway_type: 1, - algorithm: 5, - gateway: vec![0xc0, 0xa8, 0x00, 0x01], - public_key: vec![0x12, 0x34, 0x56, 0x78], - }); - } - - #[test] - fn record_empty() { - assert_eq!(IPSECKEY::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // precedence - ]; - - assert_eq!(IPSECKEY::read(10, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/loc.rs b/dns/src/record/loc.rs deleted file mode 100644 index 8b01e4f..0000000 --- a/dns/src/record/loc.rs +++ /dev/null @@ -1,493 +0,0 @@ -use std::fmt; - -use log::*; - -use crate::wire::*; - - -/// A **LOC** _(location)_ record, which points to a location on Earth using -/// its latitude, longitude, and altitude. -/// -/// # References -/// -/// - [RFC 1876](https://tools.ietf.org/html/rfc1876) — A Means for Expressing -/// Location Information in the Domain Name System (January 1996) -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct LOC { - - /// The diameter of a sphere enclosing the entity at the location, as a - /// measure of its size, measured in centimetres. - pub size: Size, - - /// The diameter of the “circle of error” that this location could be in, - /// measured in centimetres. - pub horizontal_precision: u8, - - /// The amount of vertical space that this location could be in, measured - /// in centimetres. - pub vertical_precision: u8, - - /// The latitude of the centre of the sphere. If `None`, the packet - /// parses, but the position is out of range. - pub latitude: Option, - - /// The longitude of the centre of the sphere. If `None`, the packet - /// parses, but the position is out of range. - pub longitude: Option, - - /// The altitude of the centre of the sphere, measured in centimetres - /// above a base of 100,000 metres below the GPS reference spheroid. - pub altitude: Altitude, -} - -/// A measure of size, in centimetres, represented by a base and an exponent. -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct Size { - base: u8, - power_of_ten: u8, -} - -/// A position on one of the world’s axes. -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct Position { - degrees: u32, - arcminutes: u32, - arcseconds: u32, - milliarcseconds: u32, - direction: Direction, -} - -/// A position on the vertical axis. -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct Altitude { - metres: i64, - centimetres: i64, -} - -/// One of the directions a position could be in, relative to the equator or -/// prime meridian. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum Direction { - North, - East, - South, - West, -} - -impl Wire for LOC { - const NAME: &'static str = "LOC"; - const RR_TYPE: u16 = 29; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let version = c.read_u8()?; - trace!("Parsed version -> {:?}", version); - - if version != 0 { - return Err(WireError::WrongVersion { - stated_version: version, - maximum_supported_version: 0, - }); - } - - if stated_length != 16 { - let mandated_length = MandatedLength::Exactly(16); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let size_bits = c.read_u8()?; - let size = Size::from_u8(size_bits); - trace!("Parsed size -> {:#08b} ({})", size_bits, size); - - let horizontal_precision = c.read_u8()?; - trace!("Parsed horizontal precision -> {:?}", horizontal_precision); - - let vertical_precision = c.read_u8()?; - trace!("Parsed vertical precision -> {:?}", vertical_precision); - - let latitude_num = c.read_u32::()?; - let latitude = Position::from_u32(latitude_num, true); - trace!("Parsed latitude -> {:?} ({:?})", latitude_num, latitude); - - let longitude_num = c.read_u32::()?; - let longitude = Position::from_u32(longitude_num, false); - trace!("Parsed longitude -> {:?} ({:?})", longitude_num, longitude); - - let altitude_num = c.read_u32::()?; - let altitude = Altitude::from_u32(altitude_num); - trace!("Parsed altitude -> {:?} ({:})", altitude_num, altitude); - - Ok(Self { - size, horizontal_precision, vertical_precision, latitude, longitude, altitude, - }) - } -} - -impl Size { - - /// Converts a number into the size it represents. To allow both small and - /// large sizes, the input octet is split into two four-bit sizes, one the - /// base, and one the power of ten exponent. - fn from_u8(input: u8) -> Self { - let base = input >> 4; - let power_of_ten = input & 0b_0000_1111; - Self { base, power_of_ten } - } -} - -impl Position { - - /// Converts a number into the position it represents. The input number is - /// measured in thousandths of an arcsecond (milliarcseconds), with 2^31 - /// as the equator or prime meridian. - /// - /// Returns `None` if the input is out of range, meaning it would wrap - /// around to another half of the Earth once or more. - fn from_u32(mut input: u32, vertical: bool) -> Option { - let max_for_direction = if vertical { 90 } else { 180 }; - let limit = 1000 * 60 * 60 * max_for_direction; - - if input < (0x_8000_0000 - limit) || input > (0x_8000_0000 + limit) { - // Input is out of range - None - } - else if input >= 0x_8000_0000 { - // Input is north or east, so de-relativise it and divide into segments - input -= 0x_8000_0000; - let milliarcseconds = input % 1000; - let total_arcseconds = input / 1000; - - let arcseconds = total_arcseconds % 60; - let total_arcminutes = total_arcseconds / 60; - - let arcminutes = total_arcminutes % 60; - let degrees = total_arcminutes / 60; - - let direction = if vertical { Direction::North } - else { Direction::East }; - - Some(Self { degrees, arcminutes, arcseconds, milliarcseconds, direction }) - } - else { - // Input is south or west, so do the calculations for - let mut pos = Self::from_u32(input + (0x_8000_0000_u32 - input) * 2, vertical)?; - - pos.direction = if vertical { Direction::South } - else { Direction::West }; - Some(pos) - } - } -} - -impl Altitude { - fn from_u32(input: u32) -> Self { - let mut input = i64::from(input); - input -= 10_000_000; // 100,000m - let metres = input / 100; - let centimetres = input % 100; - Self { metres, centimetres } - } -} - - -impl fmt::Display for Size { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}e{}", self.base, self.power_of_ten) - } -} - -impl fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}°{}′{}", - self.degrees, - self.arcminutes, - self.arcseconds, - )?; - - if self.milliarcseconds != 0 { - write!(f, ".{:03}", self.milliarcseconds)?; - } - - write!(f, "″ {}", self.direction) - } -} - -impl fmt::Display for Direction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::North => write!(f, "N"), - Self::East => write!(f, "E"), - Self::South => write!(f, "S"), - Self::West => write!(f, "W"), - } - } -} - -impl fmt::Display for Altitude { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Usually there’s a space between the number and the unit, but - // spaces are already used to delimit segments in the record summary - if self.centimetres == 0 { - write!(f, "{}m", self.metres) - } - else { - write!(f, "{}.{:02}m", self.metres, self.centimetres) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, // version - 0x32, // size, - 0x00, // horizontal precision - 0x00, // vertical precision - 0x8b, 0x0d, 0x2c, 0x8c, // latitude - 0x7f, 0xf8, 0xfc, 0xa5, // longitude - 0x00, 0x98, 0x96, 0x80, // altitude - ]; - - assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - LOC { - size: Size { base: 3, power_of_ten: 2 }, - horizontal_precision: 0, - vertical_precision: 0, - latitude: Position::from_u32(0x_8b_0d_2c_8c, true), - longitude: Position::from_u32(0x_7f_f8_fc_a5, false), - altitude: Altitude::from_u32(0x_00_98_96_80), - }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x00, // version - 0x00, // size - ]; - - assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::Exactly(16) })); - } - - #[test] - fn record_too_long() { - let buf = &[ - 0x00, // version - 0x32, // size, - 0x00, // horizontal precision - 0x00, // vertical precision - 0x8b, 0x0d, 0x2c, 0x8c, // latitude - 0x7f, 0xf8, 0xfc, 0xa5, // longitude - 0x00, 0x98, 0x96, 0x80, // altitude - 0x12, 0x34, 0x56, // some other stuff - ]; - - assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 19, mandated_length: MandatedLength::Exactly(16) })); - } - - #[test] - fn more_recent_version() { - let buf = &[ - 0x80, // version - 0x12, 0x34, 0x56, // some data in an unknown format - ]; - - assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongVersion { stated_version: 128, maximum_supported_version: 0 })); - } - - #[test] - fn record_empty() { - assert_eq!(LOC::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // version - ]; - - assert_eq!(LOC::read(16, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} - - -#[cfg(test)] -mod size_test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn zeroes() { - assert_eq!(Size::from_u8(0b_0000_0000).to_string(), - String::from("0e0")); - } - - #[test] - fn ones() { - assert_eq!(Size::from_u8(0b_0001_0001).to_string(), - String::from("1e1")); - } - - #[test] - fn schfourteen_teen() { - assert_eq!(Size::from_u8(0b_1110_0011).to_string(), - String::from("14e3")); - } - - #[test] - fn ones_but_bits_this_time() { - assert_eq!(Size::from_u8(0b_1111_1111).to_string(), - String::from("15e15")); - } -} - - -#[cfg(test)] -mod position_test { - use super::*; - use pretty_assertions::assert_eq; - - // centre line tests - - #[test] - fn meridian() { - assert_eq!(Position::from_u32(0x_8000_0000, false).unwrap().to_string(), - String::from("0°0′0″ E")); - } - - #[test] - fn meridian_plus_one() { - assert_eq!(Position::from_u32(0x_8000_0000 + 1, false).unwrap().to_string(), - String::from("0°0′0.001″ E")); - } - - #[test] - fn meridian_minus_one() { - assert_eq!(Position::from_u32(0x_8000_0000 - 1, false).unwrap().to_string(), - String::from("0°0′0.001″ W")); - } - - #[test] - fn equator() { - assert_eq!(Position::from_u32(0x_8000_0000, true).unwrap().to_string(), - String::from("0°0′0″ N")); - } - - #[test] - fn equator_plus_one() { - assert_eq!(Position::from_u32(0x_8000_0000 + 1, true).unwrap().to_string(), - String::from("0°0′0.001″ N")); - } - - #[test] - fn equator_minus_one() { - assert_eq!(Position::from_u32(0x_8000_0000 - 1, true).unwrap().to_string(), - String::from("0°0′0.001″ S")); - } - - // arbitrary value tests - - #[test] - fn some_latitude() { - assert_eq!(Position::from_u32(2332896396, true).unwrap().to_string(), - String::from("51°30′12.748″ N")); - } - - #[test] - fn some_longitude() { - assert_eq!(Position::from_u32(2147024037, false).unwrap().to_string(), - String::from("0°7′39.611″ W")); - } - - // limit tests - - #[test] - fn the_north_pole() { - assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 90), true).unwrap().to_string(), - String::from("90°0′0″ N")); - } - - #[test] - fn the_north_pole_plus_one() { - assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 90) + 1, true), - None); - } - - #[test] - fn the_south_pole() { - assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 90), true).unwrap().to_string(), - String::from("90°0′0″ S")); - } - - #[test] - fn the_south_pole_minus_one() { - assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 90) - 1, true), - None); - } - - #[test] - fn the_far_east() { - assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 180), false).unwrap().to_string(), - String::from("180°0′0″ E")); - } - - #[test] - fn the_far_east_plus_one() { - assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 180) + 1, false), - None); - } - - #[test] - fn the_far_west() { - assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 180), false).unwrap().to_string(), - String::from("180°0′0″ W")); - } - - #[test] - fn the_far_west_minus_one() { - assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 180) - 1, false), - None); - } -} - - -#[cfg(test)] -mod altitude_test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn base_level() { - assert_eq!(Altitude::from_u32(10000000).to_string(), - String::from("0m")); - } - - #[test] - fn up_high() { - assert_eq!(Altitude::from_u32(20000000).to_string(), - String::from("100000m")); - } - - #[test] - fn down_low() { - assert_eq!(Altitude::from_u32(0).to_string(), - String::from("-100000m")); - } - - #[test] - fn with_decimal() { - assert_eq!(Altitude::from_u32(50505050).to_string(), - String::from("405050.50m")); - } -} diff --git a/dns/src/record/mod.rs b/dns/src/record/mod.rs deleted file mode 100644 index 7ad9a1e..0000000 --- a/dns/src/record/mod.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! All the DNS record types, as well as how to parse each type. - -use crate::wire::*; - - -mod a; -pub use self::a::A; - -mod aaaa; -pub use self::aaaa::AAAA; - -mod caa; -pub use self::caa::CAA; - -mod cname; -pub use self::cname::CNAME; - -mod eui48; -pub use self::eui48::EUI48; - -mod eui64; -pub use self::eui64::EUI64; - -mod hinfo; -pub use self::hinfo::HINFO; - -mod loc; -pub use self::loc::LOC; - -mod mx; -pub use self::mx::MX; - -mod naptr; -pub use self::naptr::NAPTR; - -mod ns; -pub use self::ns::NS; - -mod openpgpkey; -pub use self::openpgpkey::OPENPGPKEY; - -mod opt; -pub use self::opt::OPT; - -mod ptr; -pub use self::ptr::PTR; - -mod sshfp; -pub use self::sshfp::SSHFP; - -mod soa; -pub use self::soa::SOA; - -mod srv; -pub use self::srv::SRV; - -mod tlsa; -pub use self::tlsa::TLSA; - -mod txt; -pub use self::txt::TXT; - -mod uri; -pub use self::uri::URI; - -mod smimea; -pub use self::smimea::SMIMEA; - -mod ds; -pub use self::ds::DS; - -mod rrsig; -pub use self::rrsig::RRSIG; - -mod nsec; -pub use self::nsec::NSEC; - -mod dnskey; -pub use self::dnskey::DNSKEY; - -mod dhcid; -pub use self::dhcid::DHCID; - -mod nsec3; -pub use self::nsec3::NSEC3; - -mod nsec3param; -pub use self::nsec3param::NSEC3PARAM; - -mod ipseckey; -pub use self::ipseckey::IPSECKEY; - - -mod others; -pub use self::others::UnknownQtype; - - -/// A record that’s been parsed from a byte buffer. -#[derive(PartialEq, Debug)] -#[allow(missing_docs)] -pub enum Record { - A(A), - AAAA(AAAA), - CAA(CAA), - CNAME(CNAME), - EUI48(EUI48), - EUI64(EUI64), - HINFO(HINFO), - LOC(LOC), - MX(MX), - NAPTR(NAPTR), - NS(NS), - OPENPGPKEY(OPENPGPKEY), - // OPT is not included here. - PTR(PTR), - SSHFP(SSHFP), - SOA(SOA), - SRV(SRV), - TLSA(TLSA), - TXT(TXT), - URI(URI), - SMIMEA(SMIMEA), - DS(DS), - RRSIG(RRSIG), - NSEC(NSEC), - DNSKEY(DNSKEY), - DHCID(DHCID), - NSEC3(NSEC3), - NSEC3PARAM(NSEC3PARAM), - IPSECKEY(IPSECKEY), - - /// A record with a type that we don’t recognise. - Other { - - /// The number that’s meant to represent the record type. - type_number: UnknownQtype, - - /// The undecodable bytes that were in this record. - bytes: Vec, - }, -} - - -/// The type of a record that may or may not be one of the known ones. Has no -/// data associated with it other than what type of record it is. -#[derive(PartialEq, Debug, Copy, Clone)] -#[allow(missing_docs)] -pub enum RecordType { - A, - AAAA, - CAA, - CNAME, - EUI48, - EUI64, - HINFO, - LOC, - MX, - NAPTR, - NS, - OPENPGPKEY, - PTR, - SSHFP, - SOA, - SRV, - TLSA, - TXT, - URI, - SMIMEA, - DS, - RRSIG, - NSEC, - DNSKEY, - DHCID, - NSEC3, - NSEC3PARAM, - IPSECKEY, - - /// A record type we don’t recognise. - Other(UnknownQtype), -} - -impl From for RecordType { - fn from(type_number: u16) -> Self { - macro_rules! try_record { - ($record:tt) => { - if $record::RR_TYPE == type_number { - return RecordType::$record; - } - } - } - - try_record!(A); - try_record!(AAAA); - try_record!(CAA); - try_record!(CNAME); - try_record!(EUI48); - try_record!(EUI64); - try_record!(HINFO); - try_record!(LOC); - try_record!(MX); - try_record!(NAPTR); - try_record!(NS); - try_record!(OPENPGPKEY); - // OPT is handled separately - try_record!(PTR); - try_record!(SSHFP); - try_record!(SOA); - try_record!(SRV); - try_record!(TLSA); - try_record!(TXT); - try_record!(URI); - try_record!(SMIMEA); - try_record!(DS); - try_record!(RRSIG); - try_record!(NSEC); - try_record!(DNSKEY); - try_record!(DHCID); - try_record!(NSEC3); - try_record!(NSEC3PARAM); - try_record!(IPSECKEY); - - RecordType::Other(UnknownQtype::from(type_number)) - } -} - - -impl RecordType { - - /// Returns a list of all supported record types. - pub fn all_record_types() -> Vec { - vec![ - RecordType::A, - RecordType::AAAA, - RecordType::CAA, - RecordType::CNAME, - RecordType::EUI48, - RecordType::EUI64, - RecordType::HINFO, - RecordType::LOC, - RecordType::MX, - RecordType::NAPTR, - RecordType::NS, - RecordType::OPENPGPKEY, - RecordType::PTR, - RecordType::SSHFP, - RecordType::SOA, - RecordType::SRV, - RecordType::TLSA, - RecordType::TXT, - RecordType::URI, - RecordType::SMIMEA, - RecordType::DS, - RecordType::RRSIG, - RecordType::NSEC, - RecordType::DNSKEY, - RecordType::DHCID, - RecordType::NSEC3, - RecordType::NSEC3PARAM, - RecordType::IPSECKEY, - ] - } - - /// Determines the record type with a given name, or `None` if none is - /// known. Matches names case-insensitively. - pub fn from_type_name(type_name: &str) -> Option { - macro_rules! try_record { - ($record:tt) => { - if $record::NAME.eq_ignore_ascii_case(type_name) { - return Some(Self::$record); - } - } - } - - try_record!(A); - try_record!(AAAA); - try_record!(CAA); - try_record!(CNAME); - try_record!(EUI48); - try_record!(EUI64); - try_record!(HINFO); - try_record!(LOC); - try_record!(MX); - try_record!(NAPTR); - try_record!(NS); - try_record!(OPENPGPKEY); - // OPT is elsewhere - try_record!(PTR); - try_record!(SSHFP); - try_record!(SOA); - try_record!(SRV); - try_record!(TLSA); - try_record!(TXT); - try_record!(URI); - try_record!(SMIMEA); - try_record!(DS); - try_record!(RRSIG); - try_record!(NSEC); - try_record!(DNSKEY); - try_record!(DHCID); - try_record!(NSEC3); - try_record!(NSEC3PARAM); - try_record!(IPSECKEY); - - UnknownQtype::from_type_name(type_name).map(Self::Other) - } - - /// Returns the record type number associated with this record type. - pub fn type_number(self) -> u16 { - match self { - Self::A => A::RR_TYPE, - Self::AAAA => AAAA::RR_TYPE, - Self::CAA => CAA::RR_TYPE, - Self::CNAME => CNAME::RR_TYPE, - Self::EUI48 => EUI48::RR_TYPE, - Self::EUI64 => EUI64::RR_TYPE, - Self::HINFO => HINFO::RR_TYPE, - Self::LOC => LOC::RR_TYPE, - Self::MX => MX::RR_TYPE, - Self::NAPTR => NAPTR::RR_TYPE, - Self::NS => NS::RR_TYPE, - Self::OPENPGPKEY => OPENPGPKEY::RR_TYPE, - // Wherefore art thou, OPT - Self::PTR => PTR::RR_TYPE, - Self::SSHFP => SSHFP::RR_TYPE, - Self::SOA => SOA::RR_TYPE, - Self::SRV => SRV::RR_TYPE, - Self::TLSA => TLSA::RR_TYPE, - Self::TXT => TXT::RR_TYPE, - Self::URI => URI::RR_TYPE, - Self::SMIMEA => SMIMEA::RR_TYPE, - Self::DS => DS::RR_TYPE, - Self::RRSIG => RRSIG::RR_TYPE, - Self::NSEC => NSEC::RR_TYPE, - Self::DNSKEY => DNSKEY::RR_TYPE, - Self::DHCID => DHCID::RR_TYPE, - Self::NSEC3 => NSEC3::RR_TYPE, - Self::NSEC3PARAM => NSEC3PARAM::RR_TYPE, - Self::IPSECKEY => IPSECKEY::RR_TYPE, - Self::Other(o) => o.type_number(), - } - } -} - -// This code is really repetitive, I know, I know diff --git a/dns/src/record/mx.rs b/dns/src/record/mx.rs deleted file mode 100644 index 86ea7e9..0000000 --- a/dns/src/record/mx.rs +++ /dev/null @@ -1,97 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// An **MX** _(mail exchange)_ record, which contains the hostnames for mail -/// servers that handle mail sent to the domain. -/// -/// # References -/// -/// - [RFC 1035 §3.3.9](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct MX { - - /// The preference that clients should give to this MX record amongst all - /// that get returned. - pub preference: u16, - - /// The domain name of the mail exchange server. - pub exchange: Labels, -} - -impl Wire for MX { - const NAME: &'static str = "MX"; - const RR_TYPE: u16 = 15; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let preference = c.read_u16::()?; - trace!("Parsed preference -> {:?}", preference); - - let (exchange, exchange_length) = c.read_labels()?; - trace!("Parsed exchange -> {:?}", exchange); - - let length_after_labels = 2 + exchange_length; - if stated_length == length_after_labels { - trace!("Length is correct"); - Ok(Self { preference, exchange }) - } - else { - warn!("Length is incorrect (stated length {:?}, preference plus exchange length {:?}", stated_length, length_after_labels); - Err(WireError::WrongLabelLength { stated_length, length_after_labels }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x0A, // preference - 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // exchange - 0x00, // exchange terminator - ]; - - assert_eq!(MX::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - MX { - preference: 10, - exchange: Labels::encode("bsago.me").unwrap(), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x00, 0x0A, // preference - 0x03, 0x65, 0x66, 0x67, // domain - 0x00, // domain terminator - ]; - - assert_eq!(MX::read(6, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 7 })); - } - - #[test] - fn record_empty() { - assert_eq!(MX::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, 0x0A, // half a preference - ]; - - assert_eq!(MX::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/naptr.rs b/dns/src/record/naptr.rs deleted file mode 100644 index fac7dfd..0000000 --- a/dns/src/record/naptr.rs +++ /dev/null @@ -1,160 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **NAPTR** _(naming authority pointer)_ record, which holds a rule for -/// the Dynamic Delegation Discovery System. -/// -/// # References -/// -/// - [RFC 3403](https://tools.ietf.org/html/rfc3403) — Dynamic Delegation -/// Discovery System (DDDS) Part Three: The Domain Name System (DNS) Database -/// (October 2002) -#[derive(PartialEq, Debug)] -pub struct NAPTR { - - /// The order in which NAPTR records must be processed. - pub order: u16, - - /// The DDDS priority. - pub preference: u16, - - /// A set of characters that control the rewriting and interpretation of - /// the other fields. - pub flags: Box<[u8]>, - - /// The service parameters applicable to this delegation path. - pub service: Box<[u8]>, - - /// A regular expression that gets applied to a string in order to - /// construct the next domain name to look up using the DDDS algorithm. - pub regex: Box<[u8]>, - - /// The replacement domain name as part of the DDDS algorithm. - pub replacement: Labels, -} - -impl Wire for NAPTR { - const NAME: &'static str = "NAPTR"; - const RR_TYPE: u16 = 35; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let order = c.read_u16::()?; - trace!("Parsed order -> {:?}", order); - - // preference - let preference = c.read_u16::()?; - trace!("Parsed preference -> {:?}", preference); - - // flags - let flags_length = c.read_u8()?; - trace!("Parsed flags length -> {:?}", flags_length); - - let mut flags = vec![0_u8; usize::from(flags_length)].into_boxed_slice(); - c.read_exact(&mut flags)?; - trace!("Parsed flags -> {:?}", String::from_utf8_lossy(&flags)); - - // service - let service_length = c.read_u8()?; - trace!("Parsed service length -> {:?}", service_length); - - let mut service = vec![0_u8; usize::from(service_length)].into_boxed_slice(); - c.read_exact(&mut service)?; - trace!("Parsed service -> {:?}", String::from_utf8_lossy(&service)); - - // regex - let regex_length = c.read_u8()?; - trace!("Parsed regex length -> {:?}", regex_length); - - let mut regex = vec![0_u8; usize::from(regex_length)].into_boxed_slice(); - c.read_exact(&mut regex)?; - trace!("Parsed regex -> {:?}", String::from_utf8_lossy(®ex)); - - // replacement - let (replacement, replacement_length) = c.read_labels()?; - trace!("Parsed replacement -> {:?}", replacement); - - let length_after_labels = 2 + 2 + - 1 + u16::from(flags_length) + 1 + u16::from(service_length) + - 1 + u16::from(regex_length) + replacement_length; - - if stated_length == length_after_labels { - Ok(Self { order, preference, flags, service, regex, replacement }) - } - else { - Err(WireError::WrongLabelLength { stated_length, length_after_labels }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x05, // order - 0x00, 0x0a, // preference - 0x01, // flags length - 0x73, // flags - 0x03, // service length - 0x53, 0x52, 0x56, // service - 0x0e, // regex length - 0x5c, 0x64, 0x5c, 0x64, 0x3a, 0x5c, 0x64, 0x5c, 0x64, 0x3a, 0x5c, - 0x64, 0x5c, 0x64, // regex - 0x0b, 0x73, 0x72, 0x76, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x06, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x03, 0x64, 0x6f, - 0x67, 0x00, // replacement - ]; - - assert_eq!(NAPTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - NAPTR { - order: 5, - preference: 10, - flags: Box::new(*b"s"), - service: Box::new(*b"SRV"), - regex: Box::new(*b"\\d\\d:\\d\\d:\\d\\d"), - replacement: Labels::encode("srv-example.lookup.dog").unwrap(), - }); - } - - #[test] - fn incorrect_length() { - let buf = &[ - 0x00, 0x05, // order - 0x00, 0x0a, // preference - 0x01, // flags length - 0x73, // flags - 0x03, // service length - 0x53, 0x52, 0x56, // service - 0x01, // regex length - 0x64, // regex, - 0x00, // replacement - ]; - - assert_eq!(NAPTR::read(11, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 11, length_after_labels: 13 })); - } - - #[test] - fn record_empty() { - assert_eq!(NAPTR::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, 0x0A, // order - ]; - - assert_eq!(NAPTR::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/ns.rs b/dns/src/record/ns.rs deleted file mode 100644 index 67cb978..0000000 --- a/dns/src/record/ns.rs +++ /dev/null @@ -1,87 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **NS** _(name server)_ record, which is used to point domains to name -/// servers. -/// -/// # References -/// -/// - [RFC 1035 §3.3.11](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct NS { - - /// The address of a nameserver that provides this DNS response. - pub nameserver: Labels, -} - -impl Wire for NS { - const NAME: &'static str = "NS"; - const RR_TYPE: u16 = 2; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let (nameserver, nameserver_length) = c.read_labels()?; - trace!("Parsed nameserver -> {:?}", nameserver); - - if stated_length == nameserver_length { - trace!("Length is correct"); - Ok(Self { nameserver }) - } - else { - warn!("Length is incorrect (stated length {:?}, nameserver length {:?}", stated_length, nameserver_length); - Err(WireError::WrongLabelLength { stated_length, length_after_labels: nameserver_length }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x01, 0x61, 0x0c, 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, // nameserver - 0x00, // nameserver terminator - ]; - - assert_eq!(NS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - NS { - nameserver: Labels::encode("a.gtld-servers.net").unwrap(), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x03, 0x65, 0x66, 0x67, // nameserver - 0x00, // nameserver terminator - ]; - - assert_eq!(NS::read(66, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 66, length_after_labels: 5 })); - } - - #[test] - fn record_empty() { - assert_eq!(NS::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // the first byte of a string - ]; - - assert_eq!(NS::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/nsec.rs b/dns/src/record/nsec.rs deleted file mode 100644 index 88d20f2..0000000 --- a/dns/src/record/nsec.rs +++ /dev/null @@ -1,75 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - -/// A **NSEC** record, which specifies that types listed in the type bit maps field do not exist for the domain name. -/// -/// # References -/// -/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) -#[derive(PartialEq, Debug)] -pub struct NSEC { - /// The name of the next domain in the canonical ordering of the zone. - pub next_domain_name: Labels, - - /// The type bit maps field contains the list of RR types present at the NSEC RR's owner name. - pub type_bit_maps: Vec, -} - -impl Wire for NSEC { - const NAME: &'static str = "NSEC"; - const RR_TYPE: u16 = 47; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let (next_domain_name, next_domain_name_length) = c.read_labels()?; - trace!("Parsed next_domain_name -> {:?}", next_domain_name); - - let type_bit_maps_len = stated_length - next_domain_name_length; - let mut type_bit_maps = vec![]; - for _ in 0..type_bit_maps_len { - type_bit_maps.push(c.read_u8()?); - } - trace!("Parsed type_bit_maps -> {:?}", type_bit_maps); - - Ok(Self { next_domain_name, type_bit_maps }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x03, 0x64, 0x6e, 0x73, // next_domain_name (example: dns) - 0x00, // next_domain_name terminator - 0x00, 0x01, 0x02, // type_bit_maps (3 bytes for example) - ]; - - assert_eq!(NSEC::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - NSEC { - next_domain_name: Labels::encode("dns").unwrap(), - type_bit_maps: vec![0x00, 0x01, 0x02], - }); - } - - #[test] - fn record_empty() { - assert_eq!(NSEC::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x03, 0x64, // half a next_domain_name - ]; - - assert_eq!(NSEC::read(10, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/nsec3.rs b/dns/src/record/nsec3.rs deleted file mode 100644 index 1586b17..0000000 --- a/dns/src/record/nsec3.rs +++ /dev/null @@ -1,122 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **NSEC3** record, which provides denial of existence for DNSSEC using hash of domain names. -/// -/// # References -/// -/// - [RFC 5155](https://tools.ietf.org/html/rfc5155) — DNS Security (DNSSEC) Hashed Authenticated Denial of Existence (March 2008) -#[derive(PartialEq, Debug)] -pub struct NSEC3 { - /// The hash algorithm used for hashing the owner name. - pub hash_algorithm: u8, - - /// Flags for the NSEC3 record. - pub flags: u8, - - /// The number of iterations of the hash function. - pub iterations: u16, - - /// The salt used in the hash computation. - pub salt: Vec, - - /// The next hashed owner name in the canonical ordering. - pub next_hashed_owner_name: Vec, - - /// The type bit maps field. - pub type_bit_maps: Vec, -} - -impl Wire for NSEC3 { - const NAME: &'static str = "NSEC3"; - const RR_TYPE: u16 = 50; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let hash_algorithm = c.read_u8()?; - trace!("Parsed hash_algorithm -> {:?}", hash_algorithm); - - let flags = c.read_u8()?; - trace!("Parsed flags -> {:?}", flags); - - let iterations = c.read_u16::()?; - trace!("Parsed iterations -> {:?}", iterations); - - let salt_length = c.read_u8()? as usize; - let mut salt = vec![]; - for _ in 0..salt_length { - salt.push(c.read_u8()?); - } - trace!("Parsed salt -> {:?}", salt); - - let hash_length = c.read_u8()? as usize; - let mut next_hashed_owner_name = vec![]; - for _ in 0..hash_length { - next_hashed_owner_name.push(c.read_u8()?); - } - trace!("Parsed next_hashed_owner_name -> {:?}", next_hashed_owner_name); - - let type_bit_maps_len = stated_length - (1 + 1 + 2 + 1 + salt_length as u16 + 1 + hash_length as u16); - let mut type_bit_maps = vec![]; - for _ in 0..type_bit_maps_len { - type_bit_maps.push(c.read_u8()?); - } - trace!("Parsed type_bit_maps -> {:?}", type_bit_maps); - - Ok(Self { - hash_algorithm, - flags, - iterations, - salt, - next_hashed_owner_name, - type_bit_maps, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x01, // hash_algorithm - 0x00, // flags - 0x00, 0x01, // iterations - 0x04, // salt_length - 0x11, 0x22, 0x33, 0x44, // salt - 0x05, // hash_length - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, // next_hashed_owner_name - 0x00, 0x01, // type_bit_maps (2 bytes for example) - ]; - - assert_eq!(NSEC3::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - NSEC3 { - hash_algorithm: 1, - flags: 0, - iterations: 1, - salt: vec![0x11, 0x22, 0x33, 0x44], - next_hashed_owner_name: vec![0xaa, 0xbb, 0xcc, 0xdd, 0xee], - type_bit_maps: vec![0x00, 0x01], - }); - } - - #[test] - fn record_empty() { - assert_eq!(NSEC3::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // hash_algorithm - ]; - - assert_eq!(NSEC3::read(20, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/nsec3param.rs b/dns/src/record/nsec3param.rs deleted file mode 100644 index aa61bb5..0000000 --- a/dns/src/record/nsec3param.rs +++ /dev/null @@ -1,97 +0,0 @@ -use log::*; - -use crate::wire::*; - -/// A **NSEC3PARAM** record, which contains the parameters used for NSEC3 records in the zone. -/// -/// # References -/// -/// - [RFC 5155](https://tools.ietf.org/html/rfc5155) — DNS Security (DNSSEC) Hashed Authenticated Denial of Existence (March 2008) -#[derive(PartialEq, Debug)] -pub struct NSEC3PARAM { - /// The hash algorithm used for hashing the owner name. - pub hash_algorithm: u8, - - /// Flags for the NSEC3PARAM record. - pub flags: u8, - - /// The number of iterations of the hash function. - pub iterations: u16, - - /// The salt used in the hash computation. - pub salt: Vec, -} - -impl Wire for NSEC3PARAM { - const NAME: &'static str = "NSEC3PARAM"; - const RR_TYPE: u16 = 51; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let hash_algorithm = c.read_u8()?; - trace!("Parsed hash_algorithm -> {:?}", hash_algorithm); - - let flags = c.read_u8()?; - trace!("Parsed flags -> {:?}", flags); - - let iterations = c.read_u16::()?; - trace!("Parsed iterations -> {:?}", iterations); - - let salt_length = c.read_u8()? as usize; - let mut salt = vec![]; - for _ in 0..salt_length { - salt.push(c.read_u8()?); - } - trace!("Parsed salt -> {:?}", salt); - - let length_after_fields = 1 + 1 + 2 + 1 + salt_length as u16; - if stated_length == length_after_fields { - trace!("Length is correct"); - Ok(Self { hash_algorithm, flags, iterations, salt }) - } else { - warn!("Length is incorrect (stated length {:?}, fields plus salt length {:?})", stated_length, length_after_fields); - Err(WireError::WrongLabelLength { stated_length, length_after_labels: length_after_fields }) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x01, // hash_algorithm - 0x00, // flags - 0x00, 0x01, // iterations - 0x04, // salt_length - 0x11, 0x22, 0x33, 0x44, // salt - ]; - - assert_eq!(NSEC3PARAM::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - NSEC3PARAM { - hash_algorithm: 1, - flags: 0, - iterations: 1, - salt: vec![0x11, 0x22, 0x33, 0x44], - }); - } - - #[test] - fn record_empty() { - assert_eq!(NSEC3PARAM::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // hash_algorithm - ]; - - assert_eq!(NSEC3PARAM::read(10, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/openpgpkey.rs b/dns/src/record/openpgpkey.rs deleted file mode 100644 index b5e5bb6..0000000 --- a/dns/src/record/openpgpkey.rs +++ /dev/null @@ -1,92 +0,0 @@ -use log::*; -use base64::{engine::general_purpose, Engine as _}; - -use crate::wire::*; - - -/// A **OPENPGPKEY** record, which holds a PGP key. -/// -/// # References -/// -/// - [RFC 1035 §3.3.14](https://tools.ietf.org/html/rfc7929) — DNS-Based -/// Authentication of Named Entities Bindings for OpenPGP (August 2016) -#[derive(PartialEq, Debug)] -pub struct OPENPGPKEY { - - /// The PGP key, as unencoded bytes. - pub key: Vec, -} - -impl Wire for OPENPGPKEY { - const NAME: &'static str = "OPENPGPKEY"; - const RR_TYPE: u16 = 61; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length == 0 { - let mandated_length = MandatedLength::AtLeast(1); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let mut key = vec![0_u8; usize::from(stated_length)]; - c.read_exact(&mut key)?; - trace!("Parsed key -> {:#x?}", key); - - Ok(Self { key }) - } -} - -impl OPENPGPKEY { - - /// The base64-encoded PGP key. - pub fn base64_key(&self) -> String { - general_purpose::STANDARD.encode(&self.key) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x12, 0x34, 0x56, 0x78, // key - ]; - - assert_eq!(OPENPGPKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - OPENPGPKEY { - key: vec![ 0x12, 0x34, 0x56, 0x78 ], - }); - } - - #[test] - fn one_byte_of_uri() { - let buf = &[ - 0x2b, // one byte of key - ]; - - assert_eq!(OPENPGPKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - OPENPGPKEY { - key: vec![ 0x2b ], - }); - } - - #[test] - fn record_empty() { - assert_eq!(OPENPGPKEY::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::AtLeast(1) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x12, 0x34, // the beginning of a key - ]; - - assert_eq!(OPENPGPKEY::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/opt.rs b/dns/src/record/opt.rs deleted file mode 100644 index 5876d83..0000000 --- a/dns/src/record/opt.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::convert::TryFrom; -use std::io; - -use log::*; - -use crate::wire::*; - - -/// A **OPT** _(options)_ pseudo-record, which is used to extend the DNS -/// protocol with additional flags such as DNSSEC stuff. -/// -/// # Pseudo-record? -/// -/// Unlike all the other record types, which are used to return data about a -/// domain name, the OPT record type is used to add more options to the -/// request, including data about the client or the server. It can exist, with -/// a payload, as a query or a response, though it’s usually encountered in -/// the Additional section. Its purpose is to add more room to the DNS wire -/// format, as backwards compatibility makes it impossible to simply add more -/// flags to the header. -/// -/// The fact that this isn’t a standard record type is annoying for a DNS -/// implementation. It re-purposes the ‘class’ and ‘TTL’ fields of the -/// `Answer` struct, as they only have meaning when associated with a domain -/// name. This means that the parser has to treat the OPT type specially, -/// switching to `Opt::read` as soon as the rtype is detected. It also means -/// the output has to deal with missing classes and TTLs. -/// -/// # References -/// -/// - [RFC 6891](https://tools.ietf.org/html/rfc6891) — Extension Mechanisms -/// for DNS (April 2013) -#[derive(PartialEq, Debug, Clone)] -pub struct OPT { - - /// The maximum size of a UDP packet that the client supports. - pub udp_payload_size: u16, - - /// The bits that form an extended rcode when non-zero. - pub higher_bits: u8, - - /// The version number of the DNS extension mechanism. - pub edns0_version: u8, - - /// Sixteen bits worth of flags. - pub flags: u16, - - /// The payload of the OPT record. - pub data: Vec, -} - -impl OPT { - - /// The record type number associated with OPT. - pub const RR_TYPE: u16 = 41; - - /// Reads from the given cursor to parse an OPT record. - /// - /// The buffer will have slightly more bytes to read for an OPT record - /// than for a typical one: we will not have encountered the ‘class’ or - /// ‘ttl’ fields, which have different meanings for this record type. - /// See §6.1.3 of the RFC, “OPT Record TTL Field Use”. - /// - /// Unlike the `Wire::read` function, this does not require a length. - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - pub fn read(c: &mut Cursor<&[u8]>) -> Result { - let udp_payload_size = c.read_u16::()?; // replaces the class field - trace!("Parsed UDP payload size -> {:?}", udp_payload_size); - - let higher_bits = c.read_u8()?; // replaces the ttl field... - trace!("Parsed higher bits -> {:#08b}", higher_bits); - - let edns0_version = c.read_u8()?; // ...as does this... - trace!("Parsed EDNS(0) version -> {:?}", edns0_version); - - let flags = c.read_u16::()?; // ...as does this - trace!("Parsed flags -> {:#08b}", flags); - - let data_length = c.read_u16::()?; - trace!("Parsed data length -> {:?}", data_length); - - let mut data = vec![0_u8; usize::from(data_length)]; - c.read_exact(&mut data)?; - trace!("Parsed data -> {:#x?}", data); - - Ok(Self { udp_payload_size, higher_bits, edns0_version, flags, data }) - } - - /// Serialises this OPT record into a vector of bytes. - /// - /// This is necessary for OPT records to be sent in the Additional section - /// of requests. - pub fn to_bytes(&self) -> io::Result> { - let mut bytes = Vec::with_capacity(32); - - bytes.write_u16::(self.udp_payload_size)?; - bytes.write_u8(self.higher_bits)?; - bytes.write_u8(self.edns0_version)?; - bytes.write_u16::(self.flags)?; - - // We should not be sending any data at all in the request, really, - // so sending too much data is downright nonsensical - let data_len = u16::try_from(self.data.len()).expect("Sending too much data"); - bytes.write_u16::(data_len)?; - - for b in &self.data { - bytes.write_u8(*b)?; - } - - Ok(bytes) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses_no_data() { - let buf = &[ - 0x05, 0xAC, // UDP payload size - 0x00, // higher bits - 0x00, 0x00, // EDNS(0) version - 0x00, 0x00, // flags - 0x00, // data length (followed by no data) - ]; - - assert_eq!(OPT::read(&mut Cursor::new(buf)).unwrap(), - OPT { - udp_payload_size: 1452, - higher_bits: 0, - edns0_version: 0, - flags: 0, - data: vec![], - }); - } - - #[test] - fn parses_with_data() { - let buf = &[ - 0x05, 0xAC, // UDP payload size - 0x00, // higher bits - 0x00, 0x00, // EDNS(0) version - 0x00, 0x00, // flags - 0x04, // data length - 0x01, 0x02, 0x03, 0x04, // data - ]; - - assert_eq!(OPT::read(&mut Cursor::new(buf)).unwrap(), - OPT { - udp_payload_size: 1452, - higher_bits: 0, - edns0_version: 0, - flags: 0, - data: vec![1, 2, 3, 4], - }); - } - - #[test] - fn record_empty() { - assert_eq!(OPT::read(&mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x05, // half a UDP payload size - ]; - - assert_eq!(OPT::read(&mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/others.rs b/dns/src/record/others.rs deleted file mode 100644 index e9b278a..0000000 --- a/dns/src/record/others.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::fmt; - - -/// A number representing a record type dog can’t deal with. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum UnknownQtype { - - /// An rtype number that dog is aware of, but does not know how to parse. - HeardOf(&'static str, u16), - - /// A completely unknown rtype number. - UnheardOf(u16), -} - -impl UnknownQtype { - - /// Searches the list for an unknown type with the given name, returning a - /// `HeardOf` variant if one is found, and `None` otherwise. - pub fn from_type_name(type_name: &str) -> Option { - let (name, num) = TYPES.iter().find(|t| t.0.eq_ignore_ascii_case(type_name))?; - Some(Self::HeardOf(name, *num)) - } - - /// Returns the type number behind this unknown type. - pub fn type_number(self) -> u16 { - match self { - Self::HeardOf(_, num) | - Self::UnheardOf(num) => num, - } - } -} - -impl From for UnknownQtype { - fn from(qtype: u16) -> Self { - match TYPES.iter().find(|t| t.1 == qtype) { - Some(tuple) => Self::HeardOf(tuple.0, qtype), - None => Self::UnheardOf(qtype), - } - } -} - -impl fmt::Display for UnknownQtype { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::HeardOf(name, _) => write!(f, "{}", name), - Self::UnheardOf(num) => write!(f, "{}", num), - } - } -} - - -/// Mapping of record type names to their assigned numbers. -static TYPES: &[(&str, u16)] = &[ - ("AFSDB", 18), - ("ANY", 255), - ("APL", 42), - ("AXFR", 252), - ("CDNSKEY", 60), - ("CDS", 59), - ("CERT", 37), - ("CSYNC", 62), - ("DHCID", 49), - ("DLV", 32769), - ("DNAME", 39), - ("DNSKEEYE", 48), - ("DS", 43), - ("HIP", 55), - ("IPSECKEY", 45), - ("IXFR", 251), - ("KEY", 25), - ("KX", 36), - ("NSEC", 47), - ("NSEC3", 50), - ("NSEC3PARAM", 51), - ("OPENPGPKEY", 61), - ("RRSIG", 46), - ("RP", 17), - ("SIG", 24), - ("SMIMEA", 53), - ("TA", 32768), - ("TKEY", 249), - ("TSIG", 250), - ("URI", 256), -]; - - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn known() { - assert_eq!(UnknownQtype::from(46).to_string(), - String::from("RRSIG")); - } - - #[test] - fn unknown() { - assert_eq!(UnknownQtype::from(4444).to_string(), - String::from("4444")); - } -} diff --git a/dns/src/record/ptr.rs b/dns/src/record/ptr.rs deleted file mode 100644 index f9bda1f..0000000 --- a/dns/src/record/ptr.rs +++ /dev/null @@ -1,91 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **PTR** record, which holds a _pointer_ to a canonical name. This is -/// most often used for reverse DNS lookups. -/// -/// # Encoding -/// -/// The text encoding is not specified, but this crate treats it as UTF-8. -/// Invalid bytes are turned into the replacement character. -/// -/// # References -/// -/// - [RFC 1035 §3.3.14](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct PTR { - - /// The CNAME contained in the record. - pub cname: Labels, -} - -impl Wire for PTR { - const NAME: &'static str = "PTR"; - const RR_TYPE: u16 = 12; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let (cname, cname_length) = c.read_labels()?; - trace!("Parsed cname -> {:?}", cname); - - if stated_length == cname_length { - trace!("Length is correct"); - Ok(Self { cname }) - } - else { - warn!("Length is incorrect (stated length {:?}, cname length {:?}", stated_length, cname_length); - Err(WireError::WrongLabelLength { stated_length, length_after_labels: cname_length }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x03, 0x64, 0x6e, 0x73, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, // cname - 0x00, // cname terminator - ]; - - assert_eq!(PTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - PTR { - cname: Labels::encode("dns.google").unwrap(), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x03, 0x65, 0x66, 0x67, // cname - 0x00, // cname terminator - ]; - - assert_eq!(PTR::read(6, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 5 })); - } - - #[test] - fn record_empty() { - assert_eq!(PTR::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x03, 0x64, // the start of a cname - ]; - - assert_eq!(PTR::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/rrsig.rs b/dns/src/record/rrsig.rs deleted file mode 100644 index 823d7cc..0000000 --- a/dns/src/record/rrsig.rs +++ /dev/null @@ -1,141 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - -/// A **RRSIG** record, which contains a digital signature for DNSSEC. -/// -/// # References -/// -/// - [RFC 4034](https://tools.ietf.org/html/rfc4034) — Resource Records for the DNS Security Extensions (March 2005) -#[derive(PartialEq, Debug)] -pub struct RRSIG { - /// The type of RRset covered by this signature. - pub type_covered: u16, - - /// The cryptographic algorithm used to generate the signature. - pub algorithm: u8, - - /// The number of labels in the original RRSIG RR owner name. - pub labels: u8, - - /// The TTL of the RRset covered by this signature. - pub original_ttl: u32, - - /// The expiration date of the signature. - pub signature_expiration: u32, - - /// The inception date of the signature. - pub signature_inception: u32, - - /// The key tag of the key that generated the signature. - pub key_tag: u16, - - /// The name of the entity that generated the signature. - pub signers_name: Labels, - - /// The cryptographic signature. - pub signature: Vec, -} - -impl Wire for RRSIG { - const NAME: &'static str = "RRSIG"; - const RR_TYPE: u16 = 46; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let type_covered = c.read_u16::()?; - trace!("Parsed type_covered -> {:?}", type_covered); - - let algorithm = c.read_u8()?; - trace!("Parsed algorithm -> {:?}", algorithm); - - let labels = c.read_u8()?; - trace!("Parsed labels -> {:?}", labels); - - let original_ttl = c.read_u32::()?; - trace!("Parsed original_ttl -> {:?}", original_ttl); - - let signature_expiration = c.read_u32::()?; - trace!("Parsed signature_expiration -> {:?}", signature_expiration); - - let signature_inception = c.read_u32::()?; - trace!("Parsed signature_inception -> {:?}", signature_inception); - - let key_tag = c.read_u16::()?; - trace!("Parsed key_tag -> {:?}", key_tag); - - let (signers_name, signers_name_length) = c.read_labels()?; - trace!("Parsed signers_name -> {:?}", signers_name); - - let signature_len = stated_length - (2 + 1 + 1 + 4 + 4 + 4 + 2 + signers_name_length); - let mut signature = vec![]; - for _ in 0..signature_len { - signature.push(c.read_u8()?); - } - trace!("Parsed signature -> {:?}", signature); - - Ok(Self { - type_covered, - algorithm, - labels, - original_ttl, - signature_expiration, - signature_inception, - key_tag, - signers_name, - signature, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x01, // type_covered - 0x05, // algorithm - 0x03, // labels - 0x00, 0x00, 0x00, 0x01, // original_ttl - 0x00, 0x00, 0x00, 0x02, // signature_expiration - 0x00, 0x00, 0x00, 0x03, // signature_inception - 0x00, 0x04, // key_tag - 0x03, 0x64, 0x6e, 0x73, // signers_name (example: dns) - 0x00, // signers_name terminator - 0x12, 0x34, 0x56, // signature (3 bytes for example) - ]; - - assert_eq!(RRSIG::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - RRSIG { - type_covered: 1, - algorithm: 5, - labels: 3, - original_ttl: 1, - signature_expiration: 2, - signature_inception: 3, - key_tag: 4, - signers_name: Labels::encode("dns").unwrap(), - signature: vec![0x12, 0x34, 0x56], - }); - } - - #[test] - fn record_empty() { - assert_eq!(RRSIG::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // half a type_covered - ]; - - assert_eq!(RRSIG::read(20, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/smimea.rs b/dns/src/record/smimea.rs deleted file mode 100644 index 60a330b..0000000 --- a/dns/src/record/smimea.rs +++ /dev/null @@ -1,135 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **SMIMEA** record, which contains an association between an S/MIME certificate and a domain name. -/// -/// # References -/// -/// - [RFC 8162](https://tools.ietf.org/html/rfc8162) — Using Secure DNS to Associate Certificates with Domain Names for S/MIME -#[derive(PartialEq, Debug)] -pub struct SMIMEA { - /// The certificate usage field indicates the provided association that will be used to match the certificate. - pub certificate_usage: u8, - - /// The selector field specifies which part of the certificate will be matched against the certificate data. - pub selector: u8, - - /// The matching type field specifies how the certificate data is presented. - pub matching_type: u8, - - /// The certificate data to be matched. - pub certificate_data: Vec, -} - -impl Wire for SMIMEA { - const NAME: &'static str = "SMIMEA"; - const RR_TYPE: u16 = 53; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - if stated_length < 3 { - let mandated_length = MandatedLength::AtLeast(4); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let certificate_usage = c.read_u8()?; - trace!("Parsed certificate_usage -> {:?}", certificate_usage); - - let selector = c.read_u8()?; - trace!("Parsed selector -> {:?}", selector); - - let matching_type = c.read_u8()?; - trace!("Parsed matching_type -> {:?}", matching_type); - - let certificate_data_length = stated_length - 3; - let mut certificate_data = vec![0_u8; usize::from(certificate_data_length)]; - c.read_exact(&mut certificate_data)?; - trace!("Parsed certificate_data -> {:#x?}", certificate_data); - - Ok(Self { certificate_usage, selector, matching_type, certificate_data }) - } -} - - -impl SMIMEA { - - /// Returns the hexadecimal representation of the certificate data. - pub fn hex_certificate_data(&self) -> String { - self.certificate_data.iter() - .map(|byte| format!("{:02x}", byte)) - .collect() - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x03, // certificate_usage - 0x01, // selector - 0x02, // matching_type - 0x12, 0x34, 0x56, 0x78, // certificate_data (4 bytes for example) - ]; - - assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SMIMEA { - certificate_usage: 3, - selector: 1, - matching_type: 2, - certificate_data: vec![0x12, 0x34, 0x56, 0x78], - }); - } - - #[test] - fn record_empty() { - assert_eq!(SMIMEA::read(0, &mut Cursor::new(&[])), - Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::AtLeast(4) })); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x03, // certificate_usage - 0x01, // selector - ]; - - assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::AtLeast(4) })); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x03, // certificate_usage - 0x01, // selector - 0x02, // matching_type - ]; - - assert_eq!(SMIMEA::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } - - #[test] - fn one_byte_certificate() { - let buf = &[ - 0x03, // certificate_usage - 0x01, // selector - 0x02, // matching_type - 0x42, // certificate_data (1 byte) - ]; - - assert_eq!(SMIMEA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SMIMEA { - certificate_usage: 3, - selector: 1, - matching_type: 2, - certificate_data: vec![0x42], - }); - } -} diff --git a/dns/src/record/soa.rs b/dns/src/record/soa.rs deleted file mode 100644 index d413b0b..0000000 --- a/dns/src/record/soa.rs +++ /dev/null @@ -1,153 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **SOA** _(start of authority)_ record, which contains administrative -/// information about the zone the domain is in. These are returned when a -/// server does not have a record for a domain. -/// -/// # References -/// -/// - [RFC 1035 §3.3.13](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct SOA { - - /// The primary master name for this server. - pub mname: Labels, - - /// The e-mail address of the administrator responsible for this DNS zone. - pub rname: Labels, - - /// A serial number for this DNS zone. - pub serial: u32, - - /// Duration, in seconds, after which secondary nameservers should query - /// the master for _its_ SOA record. - pub refresh_interval: u32, - - /// Duration, in seconds, after which secondary nameservers should retry - /// requesting the serial number from the master if it does not respond. - /// It should be less than `refresh`. - pub retry_interval: u32, - - /// Duration, in seconds, after which secondary nameservers should stop - /// answering requests for this zone if the master does not respond. - /// It should be greater than the sum of `refresh` and `retry`. - pub expire_limit: u32, - - /// Duration, in seconds, of the minimum time-to-live. - pub minimum_ttl: u32, -} - -impl Wire for SOA { - const NAME: &'static str = "SOA"; - const RR_TYPE: u16 = 6; - - #[allow(clippy::similar_names)] - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let (mname, mname_length) = c.read_labels()?; - trace!("Parsed mname -> {:?}", mname); - - let (rname, rname_length) = c.read_labels()?; - trace!("Parsed rname -> {:?}", rname); - - let serial = c.read_u32::()?; - trace!("Parsed serial -> {:?}", serial); - - let refresh_interval = c.read_u32::()?; - trace!("Parsed refresh interval -> {:?}", refresh_interval); - - let retry_interval = c.read_u32::()?; - trace!("Parsed retry interval -> {:?}", retry_interval); - - let expire_limit = c.read_u32::()?; - trace!("Parsed expire limit -> {:?}", expire_limit); - - let minimum_ttl = c.read_u32::()?; - trace!("Parsed minimum TTL -> {:?}", minimum_ttl); - - let length_after_labels = 4 * 5 + mname_length + rname_length; - if stated_length == length_after_labels { - trace!("Length is correct"); - Ok(Self { - mname, rname, serial, refresh_interval, - retry_interval, expire_limit, minimum_ttl, - }) - } - else { - warn!("Length is incorrect (stated length {:?}, mname plus rname plus fields length {:?})", stated_length, length_after_labels); - Err(WireError::WrongLabelLength { stated_length, length_after_labels }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // mname - 0x00, // mname terminator - 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // rname - 0x00, // rname terminator - 0x5d, 0x3c, 0xef, 0x02, // Serial - 0x00, 0x01, 0x51, 0x80, // Refresh interval - 0x00, 0x00, 0x1c, 0x20, // Retry interval - 0x00, 0x09, 0x3a, 0x80, // Expire limit - 0x00, 0x00, 0x01, 0x2c, // Minimum TTL - ]; - - assert_eq!(SOA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SOA { - mname: Labels::encode("bsago.me").unwrap(), - rname: Labels::encode("bsago.me").unwrap(), - serial: 1564274434, - refresh_interval: 86400, - retry_interval: 7200, - expire_limit: 604800, - minimum_ttl: 300, - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x03, 0x65, 0x66, 0x67, // mname - 0x00, // mname terminator - 0x03, 0x65, 0x66, 0x67, // rname - 0x00, // rname terminator - 0x5d, 0x3c, 0xef, 0x02, // Serial - 0x00, 0x01, 0x51, 0x80, // Refresh interval - 0x00, 0x00, 0x1c, 0x20, // Retry interval - 0x00, 0x09, 0x3a, 0x80, // Expire limit - 0x00, 0x00, 0x01, 0x2c, // Minimum TTL - ]; - - assert_eq!(SOA::read(89, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 89, length_after_labels: 30 })); - } - - #[test] - fn record_empty() { - assert_eq!(SOA::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x05, 0x62, // the start of an mname - ]; - - assert_eq!(SOA::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/srv.rs b/dns/src/record/srv.rs deleted file mode 100644 index fdbce71..0000000 --- a/dns/src/record/srv.rs +++ /dev/null @@ -1,118 +0,0 @@ -use log::*; - -use crate::strings::{Labels, ReadLabels}; -use crate::wire::*; - - -/// A **SRV** record, which contains an IP address as well as a port number, -/// for specifying the location of services more precisely. -/// -/// # References -/// -/// - [RFC 2782](https://tools.ietf.org/html/rfc2782) — A DNS RR for -/// specifying the location of services (February 2000) -#[derive(PartialEq, Debug)] -pub struct SRV { - - /// The priority of this host among all that get returned. Lower values - /// are higher priority. - pub priority: u16, - - /// A weight to choose among results with the same priority. Higher values - /// are higher priority. - pub weight: u16, - - /// The port the service is serving on. - pub port: u16, - - /// The hostname of the machine the service is running on. - pub target: Labels, -} - -impl Wire for SRV { - const NAME: &'static str = "SRV"; - const RR_TYPE: u16 = 33; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let priority = c.read_u16::()?; - trace!("Parsed priority -> {:?}", priority); - - let weight = c.read_u16::()?; - trace!("Parsed weight -> {:?}", weight); - - let port = c.read_u16::()?; - trace!("Parsed port -> {:?}", port); - - let (target, target_length) = c.read_labels()?; - trace!("Parsed target -> {:?}", target); - - let length_after_labels = 3 * 2 + target_length; - if stated_length == length_after_labels { - trace!("Length is correct"); - Ok(Self { priority, weight, port, target }) - } - else { - warn!("Length is incorrect (stated length {:?}, fields plus target length {:?})", stated_length, length_after_labels); - Err(WireError::WrongLabelLength { stated_length, length_after_labels }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x01, // priority - 0x00, 0x01, // weight - 0x92, 0x7c, // port - 0x03, 0x61, 0x74, 0x61, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x04, - 0x6e, 0x6f, 0x64, 0x65, 0x03, 0x64, 0x63, 0x31, 0x06, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, // target - 0x00, // target terminator - ]; - - assert_eq!(SRV::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SRV { - priority: 1, - weight: 1, - port: 37500, - target: Labels::encode("ata.local.node.dc1.consul").unwrap(), - }); - } - - #[test] - fn incorrect_record_length() { - let buf = &[ - 0x00, 0x01, // priority - 0x00, 0x01, // weight - 0x92, 0x7c, // port - 0x03, 0x61, 0x74, 0x61, // target - 0x00, // target terminator - ]; - - assert_eq!(SRV::read(16, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 16, length_after_labels: 11 })); - } - - #[test] - fn record_empty() { - assert_eq!(SRV::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, // half a priority - ]; - - assert_eq!(SRV::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/sshfp.rs b/dns/src/record/sshfp.rs deleted file mode 100644 index 2d5ae9a..0000000 --- a/dns/src/record/sshfp.rs +++ /dev/null @@ -1,140 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **SSHFP** _(secure shell fingerprint)_ record, which contains the -/// fingerprint of an SSH public key. -/// -/// # References -/// -/// - [RFC 4255](https://tools.ietf.org/html/rfc4255) — Using DNS to Securely -/// Publish Secure Shell (SSH) Key Fingerprints (January 2006) -#[derive(PartialEq, Debug)] -pub struct SSHFP { - - /// The algorithm of the public key. This is a number with several defined - /// mappings. - pub algorithm: u8, - - /// The type of the fingerprint, which specifies the hashing algorithm - /// used to derive the fingerprint. This is a number with several defined - /// mappings. - pub fingerprint_type: u8, - - /// The fingerprint of the public key. - pub fingerprint: Vec, -} - -impl Wire for SSHFP { - const NAME: &'static str = "SSHFP"; - const RR_TYPE: u16 = 44; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let algorithm = c.read_u8()?; - trace!("Parsed algorithm -> {:?}", algorithm); - - let fingerprint_type = c.read_u8()?; - trace!("Parsed fingerprint type -> {:?}", fingerprint_type); - - if stated_length <= 2 { - let mandated_length = MandatedLength::AtLeast(3); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let fingerprint_length = stated_length - 1 - 1; - let mut fingerprint = vec![0_u8; usize::from(fingerprint_length)]; - c.read_exact(&mut fingerprint)?; - trace!("Parsed fingerprint -> {:#x?}", fingerprint); - - Ok(Self { algorithm, fingerprint_type, fingerprint }) - } -} - -impl SSHFP { - - /// Returns the hexadecimal representation of the fingerprint. - pub fn hex_fingerprint(&self) -> String { - self.fingerprint.iter() - .map(|byte| format!("{:02x}", byte)) - .collect() - } -} - - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parses() { - let buf = &[ - 0x01, // algorithm - 0x01, // fingerprint type - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, // a short fingerprint - ]; - - assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SSHFP { - algorithm: 1, - fingerprint_type: 1, - fingerprint: vec![ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26 ], - }); - } - - #[test] - fn one_byte_fingerprint() { - let buf = &[ - 0x01, // algorithm - 0x01, // fingerprint type - 0x21, // an extremely short fingerprint - ]; - - assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - SSHFP { - algorithm: 1, - fingerprint_type: 1, - fingerprint: vec![ 0x21 ], - }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x01, // algorithm - 0x01, // fingerprint type - ]; - - assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::AtLeast(3) })); - } - - #[test] - fn record_empty() { - assert_eq!(SSHFP::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // algorithm - ]; - - assert_eq!(SSHFP::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } - - #[test] - fn hex_rep() { - let sshfp = SSHFP { - algorithm: 1, - fingerprint_type: 1, - fingerprint: vec![ 0xf3, 0x48, 0xcd, 0xc9 ], - }; - - assert_eq!(sshfp.hex_fingerprint(), - String::from("f348cdc9")); - } -} diff --git a/dns/src/record/tlsa.rs b/dns/src/record/tlsa.rs deleted file mode 100644 index 2d0625e..0000000 --- a/dns/src/record/tlsa.rs +++ /dev/null @@ -1,142 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **TLSA** _(TLS authentication)_ record, which contains a TLS certificate -/// (or a public key, or its hash), associating it with a domain. -/// -/// # References -/// -/// - [RFC 6698](https://tools.ietf.org/html/rfc6698) — The DNS-Based -/// Authentication of Named Entities (DANE) Transport Layer Security -/// Protocol: TLSA (August 2012) -#[derive(PartialEq, Debug)] -pub struct TLSA { - - /// A number representing the purpose of the certificate. - pub certificate_usage: u8, - - /// A number representing which part of the certificate is returned in the - /// data. This could be the full certificate, or just the public key. - pub selector: u8, - - /// A number representing whether a certificate should be associated with - /// the exact data, or with a hash of it. - pub matching_type: u8, - - /// A series of bytes representing the certificate. - pub certificate_data: Vec, -} - - -impl Wire for TLSA { - const NAME: &'static str = "TLSA"; - const RR_TYPE: u16 = 52; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - - let certificate_usage = c.read_u8()?; - trace!("Parsed certificate_usage -> {:?}", certificate_usage); - - let selector = c.read_u8()?; - trace!("Parsed selector -> {:?}", selector); - - let matching_type = c.read_u8()?; - trace!("Parsed matching type -> {:?}", matching_type); - - if stated_length <= 3 { - let mandated_length = MandatedLength::AtLeast(4); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let certificate_data_length = stated_length - 1 - 1 - 1; - let mut certificate_data = vec![0_u8; usize::from(certificate_data_length)]; - c.read_exact(&mut certificate_data)?; - trace!("Parsed fingerprint -> {:#x?}", certificate_data); - - Ok(Self { certificate_usage, selector, matching_type, certificate_data }) - } -} - -impl TLSA { - - /// Returns the hexadecimal representation of the fingerprint. - pub fn hex_certificate_data(&self) -> String { - self.certificate_data.iter() - .map(|byte| format!("{:02x}", byte)) - .collect() - } -} - - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parses() { - let buf = &[ - 0x03, // certificate usage - 0x01, // selector - 0x01, // matching type - 0x05, 0x95, 0x98, 0x11, 0x22, 0x33 // data - ]; - - assert_eq!(TLSA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TLSA { - certificate_usage: 3, - selector: 1, - matching_type: 1, - certificate_data: vec![ 0x05, 0x95, 0x98, 0x11, 0x22, 0x33 ], - }); - } - - #[test] - fn one_byte_certificate() { - let buf = &[ - 0x03, // certificate usage - 0x01, // selector - 0x01, // matching type - 0x05, // one byte of data - ]; - - assert_eq!(TLSA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TLSA { - certificate_usage: 3, - selector: 1, - matching_type: 1, - certificate_data: vec![ 0x05 ], - }); - } - - #[test] - fn record_too_short() { - let buf = &[ - 0x03, // certificate usage - 0x01, // selector - 0x01, // matching type - ]; - - assert_eq!(TLSA::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::AtLeast(4) })); - } - - #[test] - fn record_empty() { - assert_eq!(TLSA::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x01, // certificate_usage - ]; - - assert_eq!(TLSA::read(6, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} - diff --git a/dns/src/record/txt.rs b/dns/src/record/txt.rs deleted file mode 100644 index f5ac08e..0000000 --- a/dns/src/record/txt.rs +++ /dev/null @@ -1,231 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **TXT** record, which holds arbitrary descriptive text. -/// -/// # Encoding -/// -/// The text encoding is not specified, but this crate treats it as UTF-8. -/// Invalid bytes are turned into the replacement character. -/// -/// # References -/// -/// - [RFC 1035 §3.3.14](https://tools.ietf.org/html/rfc1035) — Domain Names, -/// Implementation and Specification (November 1987) -#[derive(PartialEq, Debug)] -pub struct TXT { - - /// The messages contained in the record. - pub messages: Vec>, -} - -impl Wire for TXT { - const NAME: &'static str = "TXT"; - const RR_TYPE: u16 = 16; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let mut messages = Vec::new(); - let mut total_length = 0_u16; - - loop { - let mut buf = Vec::new(); - - loop { - let next_length = c.read_u8()?; - total_length += u16::from(next_length) + 1; - trace!("Parsed slice length -> {:?} (total so far {:?})", next_length, total_length); - - for _ in 0 .. next_length { - buf.push(c.read_u8()?); - } - - if next_length < 255 { - break; - } - else { - trace!("Got length 255, so looping"); - } - } - - let message = buf.into_boxed_slice(); - trace!("Parsed message -> {:?}", String::from_utf8_lossy(&message)); - messages.push(message); - - if total_length >= stated_length { - break; - } - } - - if stated_length == total_length { - trace!("Length is correct"); - Ok(Self { messages }) - } - else { - warn!("Length is incorrect (stated length {:?}, messages length {:?})", stated_length, total_length); - Err(WireError::WrongLabelLength { stated_length, length_after_labels: total_length }) - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses_one_iteration() { - let buf = &[ - 0x06, // message chunk length - 0x74, 0x78, 0x74, 0x20, 0x6d, 0x65, // message chunk - ]; - - assert_eq!(TXT::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TXT { - messages: vec![ Box::new(*b"txt me") ], - }); - } - - #[test] - fn parses_two_iterations() { - let buf = &[ - 0xFF, // message chunk length - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, // exactly two hundred and fifty five ‘A’s (screaming) - 0x04, // message chunk length - 0x41, 0x41, 0x41, 0x41, // four more ‘A’s (the scream abruptly stops) - ]; - - assert_eq!(TXT::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TXT { - messages: vec![ - Box::new(*b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ - AAAAAAAAAAAAAAAAAAAAAAAAAAA"), - ], - }); - // did you know you can just _write_ code like this, and nobody will stop you? - } - - #[test] - fn right_at_the_limit() { - let buf = &[ - 0xFE, // message chunk length - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, - 0x42, // exactly two hundred and fifty four ‘B’s (a hive) - ]; - - assert_eq!(TXT::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TXT { - messages: vec![ - Box::new(*b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ - BBBBBBBBBBBBBBBBBBBBBB"), - ], - }); - } - - #[test] - fn another_message() { - let buf = &[ - 0x06, // message chunk length - 0x74, 0x78, 0x74, 0x20, 0x6d, 0x65, // message chunk - 0x06, // message chunk length - 0x79, 0x61, 0x20, 0x62, 0x65, 0x62, // message chunk - ]; - - assert_eq!(TXT::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - TXT { - messages: vec![ - Box::new(*b"txt me"), - Box::new(*b"ya beb"), - ], - }); - } - - #[test] - fn length_too_short() { - let buf = &[ - 0x06, // message chunk length - 0x74, 0x78, 0x74, 0x20, 0x6d, 0x65, // message chunk - ]; - - assert_eq!(TXT::read(2, &mut Cursor::new(buf)), - Err(WireError::WrongLabelLength { stated_length: 2, length_after_labels: 7 })); - } - - #[test] - fn record_empty() { - assert_eq!(TXT::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x06, 0x74, // the start of a message - ]; - - assert_eq!(TXT::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/record/uri.rs b/dns/src/record/uri.rs deleted file mode 100644 index 9a01270..0000000 --- a/dns/src/record/uri.rs +++ /dev/null @@ -1,123 +0,0 @@ -use log::*; - -use crate::wire::*; - - -/// A **URI** record, which holds a URI along with weight and priority values -/// to balance between several records. -/// -/// # References -/// -/// - [RFC 7553](https://tools.ietf.org/html/rfc7553) — The Uniform Resource -/// Identifier (URI) DNS Resource Record (June 2015) -/// - [RFC 3986](https://tools.ietf.org/html/rfc3986) — Uniform Resource -/// Identifier (URI): Generic Syntax (January 2005) -#[derive(PartialEq, Debug)] -pub struct URI { - - /// The priority of the URI. Clients are supposed to contact the URI with - /// the lowest priority out of all the ones it can reach. - pub priority: u16, - - /// The weight of the URI, which specifies a relative weight for entries - /// with the same priority. - pub weight: u16, - - /// The URI contained in the record. Since all we are doing is displaying - /// it to the user, we do not need to parse it for accuracy. - pub target: Box<[u8]>, -} - -impl Wire for URI { - const NAME: &'static str = "URI"; - const RR_TYPE: u16 = 256; - - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { - let priority = c.read_u16::()?; - trace!("Parsed priority -> {:?}", priority); - - let weight = c.read_u16::()?; - trace!("Parsed weight -> {:?}", weight); - - // The target must not be empty. - if stated_length <= 4 { - let mandated_length = MandatedLength::AtLeast(5); - return Err(WireError::WrongRecordLength { stated_length, mandated_length }); - } - - let remaining_length = stated_length - 4; - let mut target = vec![0_u8; usize::from(remaining_length)].into_boxed_slice(); - c.read_exact(&mut target)?; - trace!("Parsed target -> {:?}", String::from_utf8_lossy(&target)); - - Ok(Self { priority, weight, target }) - } -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn parses() { - let buf = &[ - 0x00, 0x0A, // priority - 0x00, 0x10, // weight - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x72, 0x66, 0x63, - 0x73, 0x2e, 0x69, 0x6f, 0x2f, // uri - ]; - - assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - URI { - priority: 10, - weight: 16, - target: Box::new(*b"https://rfcs.io/"), - }); - } - - #[test] - fn one_byte_of_uri() { - let buf = &[ - 0x00, 0x0A, // priority - 0x00, 0x10, // weight - 0x2f, // one byte of uri (invalid but still a legitimate DNS record) - ]; - - assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), - URI { - priority: 10, - weight: 16, - target: Box::new(*b"/"), - }); - } - - #[test] - fn missing_any_data() { - let buf = &[ - 0x00, 0x0A, // priority - 0x00, 0x10, // weight - ]; - - assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)), - Err(WireError::WrongRecordLength { stated_length: 4, mandated_length: MandatedLength::AtLeast(5) })); - } - - #[test] - fn record_empty() { - assert_eq!(URI::read(0, &mut Cursor::new(&[])), - Err(WireError::IO)); - } - - #[test] - fn buffer_ends_abruptly() { - let buf = &[ - 0x00, 0x0A, // half a priority - ]; - - assert_eq!(URI::read(23, &mut Cursor::new(buf)), - Err(WireError::IO)); - } -} diff --git a/dns/src/strings.rs b/dns/src/strings.rs deleted file mode 100644 index 7fababd..0000000 --- a/dns/src/strings.rs +++ /dev/null @@ -1,326 +0,0 @@ -//! Reading strings from the DNS wire protocol. - -use std::convert::TryFrom; -use std::fmt; -use std::io::{self, Write}; - -use byteorder::{ReadBytesExt, WriteBytesExt}; -use log::*; - -use crate::wire::*; - - -/// Domain names in the DNS protocol are encoded as **Labels**, which are -/// segments of ASCII characters prefixed by their length. When written out, -/// each segment is followed by a dot. -/// -/// The maximum length of a segment is 255 characters. -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] -pub struct Labels { - segments: Vec<(u8, String)>, -} - -#[cfg(feature = "with_idna")] -fn label_to_ascii(label: &str) -> Result { - let flags = unic_idna::Flags{use_std3_ascii_rules: false, transitional_processing: false, verify_dns_length: true}; - unic_idna::to_ascii(label, flags) -} - -#[cfg(not(feature = "with_idna"))] -fn label_to_ascii(label: &str) -> Result { - Ok(label.to_owned()) -} - -impl Labels { - - /// Creates a new empty set of labels, which represent the root of the DNS - /// as a domain with no name. - pub fn root() -> Self { - Self { segments: Vec::new() } - } - - /// Encodes the given input string as labels. If any segment is too long, - /// returns that segment as an error. - pub fn encode(input: &str) -> Result { - let mut segments = Vec::new(); - - for label in input.split('.') { - if label.is_empty() { - continue; - } - - let label_idn = label_to_ascii(label) - .map_err(|e| { - warn!("Could not encode label {label:?}: {e:?}"); - label - })?; - - match u8::try_from(label_idn.len()) { - Ok(length) => { - segments.push((length, label_idn)); - } - Err(e) => { - warn!("Could not encode label {label:?}: {e}"); - return Err(label); - } - } - } - - Ok(Self { segments }) - } - - /// Returns the number of segments. - pub fn len(&self) -> usize { - self.segments.len() - } - - /// Returns a new set of labels concatenating two names. - #[must_use] - pub fn extend(&self, other: &Self) -> Self { - let mut segments = self.segments.clone(); - segments.extend_from_slice(&other.segments); - Self { segments } - } -} - -impl fmt::Display for Labels { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (_, segment) in &self.segments { - write!(f, "{segment}.")?; - } - - Ok(()) - } -} - -/// An extension for `Cursor` that enables reading compressed domain names -/// from DNS packets. -pub(crate) trait ReadLabels { - - /// Read and expand a compressed domain name. - fn read_labels(&mut self) -> Result<(Labels, u16), WireError>; -} - -impl ReadLabels for Cursor<&[u8]> { - fn read_labels(&mut self) -> Result<(Labels, u16), WireError> { - let mut labels = Labels { segments: Vec::new() }; - let bytes_read = read_string_recursive(&mut labels, self, &mut Vec::new())?; - Ok((labels, bytes_read)) - } -} - - -/// An extension for `Write` that enables writing domain names. -pub(crate) trait WriteLabels { - - /// Write a domain name. - /// - /// The names being queried are written with one byte slice per - /// domain segment, preceded by each segment’s length, with the - /// whole thing ending with a segment of zero length. - /// - /// So “dns.lookup.dog” would be encoded as: - /// “3, dns, 6, lookup, 3, dog, 0”. - fn write_labels(&mut self, input: &Labels) -> io::Result<()>; -} - -impl WriteLabels for W { - fn write_labels(&mut self, input: &Labels) -> io::Result<()> { - for (length, label) in &input.segments { - self.write_u8(*length)?; - - for b in label.as_bytes() { - self.write_u8(*b)?; - } - } - - self.write_u8(0)?; // terminate the string - Ok(()) - } -} - - -const RECURSION_LIMIT: usize = 8; - -/// Reads bytes from the given cursor into the given buffer, using the list of -/// recursions to track backtracking positions. Returns the count of bytes -/// that had to be read to produce the string, including the bytes to signify -/// backtracking, but not including the bytes read _during_ backtracking. -#[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] -fn read_string_recursive(labels: &mut Labels, c: &mut Cursor<&[u8]>, recursions: &mut Vec) -> Result { - let mut bytes_read = 0; - - loop { - let byte = c.read_u8()?; - bytes_read += 1; - - if byte == 0 { - break; - } - - else if byte >= 0b_1100_0000 { - let name_one = byte - 0b1100_0000; - let name_two = c.read_u8()?; - bytes_read += 1; - let offset = u16::from_be_bytes([name_one, name_two]); - - if recursions.contains(&offset) { - warn!("Hit previous offset ({offset}) decoding string"); - return Err(WireError::TooMuchRecursion(recursions.clone().into_boxed_slice())); - } - - recursions.push(offset); - - if recursions.len() >= RECURSION_LIMIT { - warn!("Hit recursion limit ({RECURSION_LIMIT}) decoding string"); - return Err(WireError::TooMuchRecursion(recursions.clone().into_boxed_slice())); - } - - trace!("Backtracking to offset {offset}"); - let new_pos = c.position(); - c.set_position(u64::from(offset)); - - read_string_recursive(labels, c, recursions)?; - - trace!("Coming back to {new_pos}"); - c.set_position(new_pos); - break; - } - - // Otherwise, treat the byte as the length of a label, and read that - // many characters. - else { - let mut name_buf = Vec::new(); - - for _ in 0 .. byte { - let c = c.read_u8()?; - bytes_read += 1; - name_buf.push(c); - } - - let string = String::from_utf8_lossy(&name_buf).to_string(); - labels.segments.push((byte, string)); - } - } - - Ok(bytes_read) -} - - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - // The buffers used in these tests contain nothing but the labels we’re - // decoding. In DNS packets found in the wild, the cursor will be able to - // reach all the bytes of the packet, so the Answer section can reference - // strings in the Query section. - - #[test] - fn nothing() { - let buf: &[u8] = &[ - 0x00, // end reading - ]; - - assert_eq!(Cursor::new(buf).read_labels(), - Ok((Labels::root(), 1))); - } - - #[test] - fn one_label() { - let buf: &[u8] = &[ - 0x03, // label of length 3 - b'o', b'n', b'e', // label - 0x00, // end reading - ]; - - assert_eq!(Cursor::new(buf).read_labels(), - Ok((Labels::encode("one.").unwrap(), 5))); - } - - #[test] - fn two_labels() { - let buf: &[u8] = &[ - 0x03, // label of length 3 - b'o', b'n', b'e', // label - 0x03, // label of length 3 - b't', b'w', b'o', // label - 0x00, // end reading - ]; - - assert_eq!(Cursor::new(buf).read_labels(), - Ok((Labels::encode("one.two.").unwrap(), 9))); - } - - #[test] - fn label_followed_by_backtrack() { - let buf: &[u8] = &[ - 0x03, // label of length 3 - b'o', b'n', b'e', // label - 0xc0, 0x06, // skip to position 6 (the next byte) - - 0x03, // label of length 3 - b't', b'w', b'o', // label - 0x00, // end reading - ]; - - assert_eq!(Cursor::new(buf).read_labels(), - Ok((Labels::encode("one.two.").unwrap(), 6))); - } - - #[test] - fn extremely_long_label() { - let mut buf: Vec = vec![ - 0xbf, // label of length 191 - ]; - - buf.extend(vec![0x65; 191]); // the rest of the label - buf.push(0x00); // end reading - - assert_eq!(Cursor::new(&*buf).read_labels().unwrap().1, 193); - } - - #[test] - fn immediate_recursion() { - let buf: &[u8] = &[ - 0xc0, 0x00, // skip to position 0 - ]; - - assert_eq!(Cursor::new(buf).read_labels(), - Err(WireError::TooMuchRecursion(Box::new([ 0 ])))); - } - - #[test] - fn mutual_recursion() { - let buf: &[u8] = &[ - 0xc0, 0x02, // skip to position 2 - 0xc0, 0x00, // skip to position 0 - ]; - - let mut cursor = Cursor::new(buf); - - assert_eq!(cursor.read_labels(), - Err(WireError::TooMuchRecursion(Box::new([ 2, 0 ])))); - } - - #[test] - fn too_much_recursion() { - let buf: &[u8] = &[ - 0xc0, 0x02, // skip to position 2 - 0xc0, 0x04, // skip to position 4 - 0xc0, 0x06, // skip to position 6 - 0xc0, 0x08, // skip to position 8 - 0xc0, 0x0A, // skip to position 10 - 0xc0, 0x0C, // skip to position 12 - 0xc0, 0x0E, // skip to position 14 - 0xc0, 0x10, // skip to position 16 - 0x00, // no label - ]; - - let mut cursor = Cursor::new(buf); - - assert_eq!(cursor.read_labels(), - Err(WireError::TooMuchRecursion(Box::new([ 2, 4, 6, 8, 10, 12, 14, 16 ])))); - } -} diff --git a/dns/src/types.rs b/dns/src/types.rs deleted file mode 100644 index 12d5c6c..0000000 --- a/dns/src/types.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! DNS packets are traditionally implemented with both the request and -//! response packets at the same type. After all, both follow the same format, -//! with the request packet having zero answer fields, and the response packet -//! having at least one record in its answer fields. - -use crate::record::{Record, RecordType, OPT}; -use crate::strings::Labels; - - -/// A request that gets sent out over a transport. -#[derive(PartialEq, Debug)] -pub struct Request { - - /// The transaction ID of this request. This is used to make sure - /// different DNS packets don’t answer each other’s questions. - pub transaction_id: u16, - - /// The flags that accompany every DNS packet. - pub flags: Flags, - - /// The query that this request is making. Only one query is allowed per - /// request, as traditionally, DNS servers only respond to the first query - /// in a packet. - pub query: Query, - - /// An additional record that may be sent as part of the query. - pub additional: Option, -} - - -/// A response obtained from a DNS server. -#[derive(PartialEq, Debug)] -pub struct Response { - - /// The transaction ID, which should match the ID of the request. - pub transaction_id: u16, - - /// The flags that accompany every DNS packet. - pub flags: Flags, - - /// The queries section. - pub queries: Vec, - - /// The answers section. - pub answers: Vec, - - /// The authoritative nameservers section. - pub authorities: Vec, - - /// The additional records section. - pub additionals: Vec, -} - - -/// A DNS query section. -#[derive(PartialEq, Debug)] -pub struct Query { - - /// The domain name being queried, in human-readable dotted notation. - pub qname: Labels, - - /// The class number. - pub qclass: QClass, - - /// The type number. - pub qtype: RecordType, -} - - -/// A DNS answer section. -#[derive(PartialEq, Debug)] -pub enum Answer { - - /// This is a standard answer with every field. - Standard { - - /// The domain name being answered for. - qname: Labels, - - /// This answer’s class. - qclass: QClass, - - /// The time-to-live duration, in seconds. - ttl: u32, - - /// The record contained in this answer. - record: Record, - }, - - /// This is a pseudo-record answer, so some of the fields (class and TTL) - /// have different meaning. - Pseudo { - - /// The domain name being answered for. - qname: Labels, - - /// The OPT record contained in this answer. - opt: OPT, - }, -} - - -/// A DNS record class. Of these, the only one that’s in regular use anymore -/// is the Internet class. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum QClass { - - /// The **Internet** class. - IN, - - /// The **Chaosnet** class. - CH, - - /// The **Hesiod** class. - HS, - - /// A class number that does not map to any known class. - Other(u16), -} - - -/// The flags that accompany every DNS packet. -#[derive(PartialEq, Debug, Copy, Clone)] -pub struct Flags { - - /// Whether this packet is a response packet. - pub response: bool, - - /// The operation being performed. - pub opcode: Opcode, - - /// In a response, whether the server is providing authoritative DNS responses. - pub authoritative: bool, - - /// In a response, whether this message has been truncated by the transport. - pub truncated: bool, - - /// In a query, whether the server may query other nameservers recursively. - /// It is up to the server whether it will actually do this. - pub recursion_desired: bool, - - /// In a response, whether the server allows recursive query support. - pub recursion_available: bool, - - /// In a response, whether the server is marking this data as authentic. - pub authentic_data: bool, - - /// In a request, whether the server should disable its authenticity - /// checking for the request’s queries. - pub checking_disabled: bool, - - /// In a response, a code indicating an error if one occurred. - pub error_code: Option, -} - - -/// A number representing the operation being performed. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum Opcode { - - /// This request is a standard query, or this response is answering a - /// standard query. - Query, - - /// Any other opcode. This can be from 1 to 15, as the opcode field is - /// four bits wide, and 0 is taken. - Other(u8), -} - - -/// A code indicating an error. -/// -/// # References -/// -/// - [RFC 6895 §2.3](https://tools.ietf.org/html/rfc6895#section-2.3) — Domain -/// Name System (DNS) IANA Considerations (April 2013) -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum ErrorCode { - - /// `FormErr` — The server was unable to interpret the query. - FormatError, - - /// `ServFail` — There was a problem with the server. - ServerFailure, - - /// `NXDomain` — The domain name referenced in the query does not exist. - NXDomain, - - /// `NotImp` — The server does not support one of the requested features. - NotImplemented, - - /// `Refused` — The server was able to interpret the query, but refused to - /// fulfil it. - QueryRefused, - - /// `BADVERS` and `BADSIG` — The server did not accept the EDNS version, - /// or failed to verify a signature. The same code is used for both. - BadVersion, - - /// An error code with no currently-defined meaning. - Other(u16), - - /// An error code within the ‘Reserved for Private Use’ range. - Private(u16) -} - - -impl Answer { - - /// Whether this Answer holds a standard record, not a pseudo record. - pub fn is_standard(&self) -> bool { - matches!(self, Self::Standard { .. }) - } -} diff --git a/dns/src/wire.rs b/dns/src/wire.rs deleted file mode 100644 index d3db951..0000000 --- a/dns/src/wire.rs +++ /dev/null @@ -1,454 +0,0 @@ -//! Parsing the DNS wire protocol. - -pub(crate) use std::io::{Cursor, Read}; -pub(crate) use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; - -use std::io; -use log::*; - -use crate::record::{Record, RecordType, OPT}; -use crate::strings::{Labels, ReadLabels, WriteLabels}; -use crate::types::*; - - -impl Request { - - /// Converts this request to a vector of bytes. - pub fn to_bytes(&self) -> io::Result> { - let mut bytes = Vec::with_capacity(32); - - bytes.write_u16::(self.transaction_id)?; - bytes.write_u16::(self.flags.to_u16())?; - - bytes.write_u16::(1)?; // query count - bytes.write_u16::(0)?; // answer count - bytes.write_u16::(0)?; // authority RR count - bytes.write_u16::(u16::from(self.additional.is_some()))?; // additional RR count - - bytes.write_labels(&self.query.qname)?; - bytes.write_u16::(self.query.qtype.type_number())?; - bytes.write_u16::(self.query.qclass.to_u16())?; - - if let Some(opt) = &self.additional { - bytes.write_u8(0)?; // usually a name - bytes.write_u16::(OPT::RR_TYPE)?; - bytes.extend(opt.to_bytes()?); - } - - Ok(bytes) - } - - /// Returns the OPT record to be sent as part of requests. - pub fn additional_record() -> OPT { - OPT { - udp_payload_size: 512, - higher_bits: 0, - edns0_version: 0, - flags: 0, - data: Vec::new(), - } - } -} - - -impl Response { - - /// Reads bytes off of the given slice, parsing them into a response. - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - pub fn from_bytes(bytes: &[u8]) -> Result { - info!("Parsing response"); - trace!("Bytes -> {bytes:?}"); - let mut c = Cursor::new(bytes); - - let transaction_id = c.read_u16::()?; - trace!("Read txid -> {transaction_id:?}"); - - let flags = Flags::from_u16(c.read_u16::()?); - trace!("Read flags -> {flags:#?}"); - - let query_count = c.read_u16::()?; - let answer_count = c.read_u16::()?; - let authority_count = c.read_u16::()?; - let additional_count = c.read_u16::()?; - - // We can pre-allocate these vectors by giving them an initial - // capacity based on the count fields. But because the count fields - // are user-controlled (with a maximum of 2^16 - 1) we cannot trust - // them _entirely_, so cap the pre-allocation if the count looks - // arbitrarily large (9 seems about right). - - let mut queries = Vec::with_capacity(usize::from(query_count.min(9))); - debug!("Reading {query_count}x query from response"); - for _ in 0 .. query_count { - let (qname, _) = c.read_labels()?; - queries.push(Query::from_bytes(qname, &mut c)?); - } - - let mut answers = Vec::with_capacity(usize::from(answer_count.min(9))); - debug!("Reading {answer_count}x answer from response"); - for _ in 0 .. answer_count { - let (qname, _) = c.read_labels()?; - answers.push(Answer::from_bytes(qname, &mut c)?); - } - - let mut authorities = Vec::with_capacity(usize::from(authority_count.min(9))); - debug!("Reading {authority_count}x authority from response"); - for _ in 0 .. authority_count { - let (qname, _) = c.read_labels()?; - authorities.push(Answer::from_bytes(qname, &mut c)?); - } - - let mut additionals = Vec::with_capacity(usize::from(additional_count.min(9))); - debug!("Reading {additional_count}x additional answer from response"); - for _ in 0 .. additional_count { - let (qname, _) = c.read_labels()?; - additionals.push(Answer::from_bytes(qname, &mut c)?); - } - - Ok(Self { transaction_id, flags, queries, answers, authorities, additionals }) - } -} - - -impl Query { - - /// Reads bytes from the given cursor, and parses them into a query with - /// the given domain name. - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result { - let qtype_number = c.read_u16::()?; - trace!("Read qtype number -> {qtype_number:?}" ); - - let qtype = RecordType::from(qtype_number); - trace!("Found qtype -> {qtype:?}" ); - - let qclass = QClass::from_u16(c.read_u16::()?); - trace!("Read qclass -> {qclass:?}"); - - Ok(Self { qname, qclass, qtype }) - } -} - - -impl Answer { - - /// Reads bytes from the given cursor, and parses them into an answer with - /// the given domain name. - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result { - let qtype_number = c.read_u16::()?; - trace!("Read qtype number -> {qtype_number:?}" ); - - if qtype_number == OPT::RR_TYPE { - let opt = OPT::read(c)?; - Ok(Self::Pseudo { qname, opt }) - } - else { - let qtype = RecordType::from(qtype_number); - trace!("Found qtype -> {qtype:?}" ); - - let qclass = QClass::from_u16(c.read_u16::()?); - trace!("Read qclass -> {:?}", qtype); - - let ttl = c.read_u32::()?; - trace!("Read TTL -> {:?}", ttl); - - let record_length = c.read_u16::()?; - trace!("Read record length -> {:?}", record_length); - - let record = Record::from_bytes(qtype, record_length, c)?; - Ok(Self::Standard { qclass, qname, record, ttl }) - } - } -} - - -impl Record { - - /// Reads at most `len` bytes from the given curser, and parses them into - /// a record structure depending on the type number, which has already been read. - #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] - fn from_bytes(record_type: RecordType, len: u16, c: &mut Cursor<&[u8]>) -> Result { - if cfg!(feature = "with_mutagen") { - warn!("Mutation is enabled!"); - } - - macro_rules! read_record { - ($record:tt) => { { - info!("Parsing {} record (type {}, len {})", crate::record::$record::NAME, record_type.type_number(), len); - Wire::read(len, c).map(Self::$record) - } } - } - - match record_type { - RecordType::A => read_record!(A), - RecordType::AAAA => read_record!(AAAA), - RecordType::CAA => read_record!(CAA), - RecordType::CNAME => read_record!(CNAME), - RecordType::EUI48 => read_record!(EUI48), - RecordType::EUI64 => read_record!(EUI64), - RecordType::HINFO => read_record!(HINFO), - RecordType::LOC => read_record!(LOC), - RecordType::MX => read_record!(MX), - RecordType::NAPTR => read_record!(NAPTR), - RecordType::NS => read_record!(NS), - RecordType::OPENPGPKEY => read_record!(OPENPGPKEY), - RecordType::PTR => read_record!(PTR), - RecordType::SSHFP => read_record!(SSHFP), - RecordType::SOA => read_record!(SOA), - RecordType::SRV => read_record!(SRV), - RecordType::TLSA => read_record!(TLSA), - RecordType::TXT => read_record!(TXT), - RecordType::URI => read_record!(URI), - RecordType::SMIMEA => read_record!(SMIMEA), - RecordType::DS => read_record!(DS), - RecordType::RRSIG => read_record!(RRSIG), - RecordType::NSEC => read_record!(NSEC), - RecordType::DNSKEY => read_record!(DNSKEY), - RecordType::DHCID => read_record!(DHCID), - RecordType::NSEC3 => read_record!(NSEC3), - RecordType::NSEC3PARAM => read_record!(NSEC3PARAM), - RecordType::IPSECKEY => read_record!(IPSECKEY), - - RecordType::Other(type_number) => { - let mut bytes = Vec::new(); - for _ in 0 .. len { - bytes.push(c.read_u8()?); - } - - Ok(Self::Other { type_number, bytes }) - } - } - } -} - - -impl QClass { - fn from_u16(uu: u16) -> Self { - match uu { - 0x0001 => Self::IN, - 0x0003 => Self::CH, - 0x0004 => Self::HS, - _ => Self::Other(uu), - } - } - - fn to_u16(self) -> u16 { - match self { - Self::IN => 0x0001, - Self::CH => 0x0003, - Self::HS => 0x0004, - Self::Other(uu) => uu, - } - } -} - - -impl Flags { - - /// The set of flags that represents a query packet. - pub fn query() -> Self { - Self::from_u16(0b_0000_0001_0000_0000) - } - - /// The set of flags that represents a successful response. - pub fn standard_response() -> Self { - Self::from_u16(0b_1000_0001_1000_0000) - } - - /// Converts the flags into a two-byte number. - pub fn to_u16(self) -> u16 { // 0123 4567 89AB CDEF - let mut bits = 0b_0000_0000_0000_0000; - if self.response { bits |= 0b_1000_0000_0000_0000; } - match self.opcode { - Opcode::Query => { bits |= 0b_0000_0000_0000_0000; } - Opcode::Other(_) => { unimplemented!(); } - } - if self.authoritative { bits |= 0b_0000_0100_0000_0000; } - if self.truncated { bits |= 0b_0000_0010_0000_0000; } - if self.recursion_desired { bits |= 0b_0000_0001_0000_0000; } - if self.recursion_available { bits |= 0b_0000_0000_1000_0000; } - // (the Z bit is reserved) 0b_0000_0000_0100_0000 - if self.authentic_data { bits |= 0b_0000_0000_0010_0000; } - if self.checking_disabled { bits |= 0b_0000_0000_0001_0000; } - - bits - } - - /// Extracts the flags from the given two-byte number. - pub fn from_u16(bits: u16) -> Self { - let has_bit = |bit| { bits & bit == bit }; - - Self { - response: has_bit(0b_1000_0000_0000_0000), - opcode: Opcode::from_bits((bits.to_be_bytes()[0] & 0b_0111_1000) >> 3), - authoritative: has_bit(0b_0000_0100_0000_0000), - truncated: has_bit(0b_0000_0010_0000_0000), - recursion_desired: has_bit(0b_0000_0001_0000_0000), - recursion_available: has_bit(0b_0000_0000_1000_0000), - authentic_data: has_bit(0b_0000_0000_0010_0000), - checking_disabled: has_bit(0b_0000_0000_0001_0000), - error_code: ErrorCode::from_bits(bits & 0b_1111), - } - } -} - - -impl Opcode { - - /// Extracts the opcode from this four-bit number, which should have been - /// extracted from the packet and shifted to be in the range 0–15. - fn from_bits(bits: u8) -> Self { - if bits == 0 { - Self::Query - } - else { - assert!(bits <= 15, "bits {:#08b} out of range", bits); - Self::Other(bits) - } - } -} - - -impl ErrorCode { - - /// Extracts the rcode from the last four bits of the flags field. - fn from_bits(bits: u16) -> Option { - if (0x0F01 .. 0x0FFF).contains(&bits) { - return Some(Self::Private(bits)); - } - - match bits { - 0 => None, - 1 => Some(Self::FormatError), - 2 => Some(Self::ServerFailure), - 3 => Some(Self::NXDomain), - 4 => Some(Self::NotImplemented), - 5 => Some(Self::QueryRefused), - 16 => Some(Self::BadVersion), - n => Some(Self::Other(n)), - } - } -} - - -/// Trait for decoding DNS record structures from bytes read over the wire. -pub trait Wire: Sized { - - /// This record’s type as a string, such as `"A"` or `"CNAME"`. - const NAME: &'static str; - - /// The number signifying that a record is of this type. - /// See - const RR_TYPE: u16; - - /// Read at most `len` bytes from the given `Cursor`. This cursor travels - /// throughout the complete data — by this point, we have read the entire - /// response into a buffer. - fn read(len: u16, c: &mut Cursor<&[u8]>) -> Result; -} - - -/// Something that can go wrong deciphering a record. -#[derive(PartialEq, Debug)] -pub enum WireError { - - /// There was an IO error reading from the cursor. - /// Almost all the time, this means that the buffer was too short. - IO, - // (io::Error is not PartialEq so we don’t propagate it) - - /// When the DNS standard requires records of this type to have a certain - /// fixed length, but the response specified a different length. - /// - /// This error should be returned regardless of the _content_ of the - /// record, whatever it is. - WrongRecordLength { - - /// The length of the record’s data, as specified in the packet. - stated_length: u16, - - /// The length of the record that the DNS specification mandates. - mandated_length: MandatedLength, - }, - - /// When the length of this record as specified in the packet differs from - /// the computed length, as determined by reading labels. - /// - /// There are two ways, in general, to read arbitrary-length data from a - /// stream of bytes: length-prefixed (read the length, then read that many - /// bytes) or sentinel-terminated (keep reading bytes until you read a - /// certain value, usually zero). The DNS protocol uses both: each - /// record’s size is specified up-front in the packet, but inside the - /// record, there exist arbitrary-length strings that must be read until a - /// zero is read, indicating there is no more string. - /// - /// Consider the case of a packet, with a specified length, containing a - /// string of arbitrary length (such as the CNAME or TXT records). A DNS - /// client has to deal with this in one of two ways: - /// - /// 1. Read exactly the specified length of bytes from the record, raising - /// an error if the contents are too short or a string keeps going past - /// the length (assume the length is correct but the contents are wrong). - /// - /// 2. Read as many bytes from the record as the string requests, raising - /// an error if the number of bytes read at the end differs from the - /// expected length of the record (assume the length is wrong but the - /// contents are correct). - /// - /// Note that no matter which way is picked, the record will still be - /// incorrect — it only impacts the parsing of records that occur after it - /// in the packet. Knowing which method should be used requires knowing - /// what caused the DNS packet to be erroneous, which we cannot know. - /// - /// dog picks the second way. If a record ends up reading more or fewer - /// bytes than it is ‘supposed’ to, it will raise this error, but _after_ - /// having read a different number of bytes than the specified length. - WrongLabelLength { - - /// The length of the record’s data, as specified in the packet. - stated_length: u16, - - /// The computed length of the record’s data, based on the number of - /// bytes consumed by reading labels from the packet. - length_after_labels: u16, - }, - - /// When the data contained a string containing a cycle of pointers. - /// Contains the vector of indexes that was being checked. - TooMuchRecursion(Box<[u16]>), - - /// When the data contained a string with a pointer to an index outside of - /// the packet. Contains the invalid index. - OutOfBounds(u16), - - /// When a record in the packet contained a version field that specifies - /// the format of its remaining fields, but this version is too recent to - /// be supported, so we cannot parse it. - WrongVersion { - - /// The version of the record layout, as specified in the packet - stated_version: u8, - - /// The maximum version that this version of dog supports. - maximum_supported_version: u8, - } -} - -/// The rule for how long a record in a packet should be. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum MandatedLength { - - /// The record should be exactly this many bytes in length. - Exactly(u16), - - /// The record should be _at least_ this many bytes in length. - AtLeast(u16), -} - -impl From for WireError { - fn from(ioe: io::Error) -> Self { - error!("IO error -> {:?}", ioe); - Self::IO - } -} diff --git a/dns/tests/wire_building_tests.rs b/dns/tests/wire_building_tests.rs deleted file mode 100644 index d8de251..0000000 --- a/dns/tests/wire_building_tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -use dns::{Request, Flags, Query, Labels, QClass}; -use dns::record::RecordType; - -use pretty_assertions::assert_eq; - - -#[test] -fn build_request() { - let request = Request { - transaction_id: 0xceac, - flags: Flags::query(), - query: Query { - qname: Labels::encode("rfcs.io").unwrap(), - qclass: QClass::Other(0x42), - qtype: RecordType::from(0x1234), - }, - additional: Some(Request::additional_record()), - }; - - let result = vec![ - 0xce, 0xac, // transaction ID - 0x01, 0x00, // flags (standard query) - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // counts (1, 0, 0, 1) - - // query: - 0x04, 0x72, 0x66, 0x63, 0x73, 0x02, 0x69, 0x6f, 0x00, // qname - 0x12, 0x34, // type - 0x00, 0x42, // class - - // OPT record: - 0x00, // name - 0x00, 0x29, // type OPT - 0x02, 0x00, // UDP payload size - 0x00, // higher bits - 0x00, // EDNS(0) version - 0x00, 0x00, // more flags - 0x00, 0x00, // no data - ]; - - assert_eq!(request.to_bytes().unwrap(), result); -} diff --git a/dns/tests/wire_parsing_tests.rs b/dns/tests/wire_parsing_tests.rs deleted file mode 100644 index 7939a56..0000000 --- a/dns/tests/wire_parsing_tests.rs +++ /dev/null @@ -1,271 +0,0 @@ -use std::net::Ipv4Addr; - -use dns::{Response, Query, Answer, Labels, Flags, Opcode, QClass}; -use dns::record::{Record, A, CNAME, OPT, SOA, UnknownQtype, RecordType}; - -use pretty_assertions::assert_eq; - - -#[test] -fn parse_nothing() { - assert!(Response::from_bytes(&[]).is_err()); -} - - -#[test] -fn parse_response_standard() { - let buf = &[ - 0x0d, 0xcd, // transaction ID - 0x81, 0x80, // flags (standard query, response, no error) - 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, // counts (1, 1, 0, 1) - - // the query: - 0x03, 0x64, 0x6e, 0x73, 0x06, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x03, - 0x64, 0x6f, 0x67, 0x00, // "dns.lookup.dog." - 0x00, 0x01, // type A - 0x00, 0x01, // class IN - - // the answer: - 0xc0, 0x0c, // to find the name, backtrack to position 0x0c (12) - 0x00, 0x01, // type A - 0x00, 0x01, // class IN - 0x00, 0x00, 0x03, 0xa5, // TTL (933 seconds) - 0x00, 0x04, // record data length 4 - 0x8a, 0x44, 0x75, 0x5e, // record date (138.68.117.94) - - // the additional: - 0x00, // no name - 0x00, 0x29, // type OPT - 0x02, 0x00, // UDP payload size (512) - 0x00, 0x00, // higher bits (all 0) - 0x00, // EDNS version - 0x00, 0x00, // extra bits (DO bit unset) - 0x00, // data length 0 - ]; - - let response = Response { - transaction_id: 0x0dcd, - flags: Flags { - response: true, - opcode: Opcode::Query, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authentic_data: false, - checking_disabled: false, - error_code: None, - }, - queries: vec![ - Query { - qname: Labels::encode("dns.lookup.dog").unwrap(), - qclass: QClass::IN, - qtype: RecordType::A, - }, - ], - answers: vec![ - Answer::Standard { - qname: Labels::encode("dns.lookup.dog").unwrap(), - qclass: QClass::IN, - ttl: 933, - record: Record::A(A { - address: Ipv4Addr::new(138, 68, 117, 94), - }), - } - ], - authorities: vec![], - additionals: vec![ - Answer::Pseudo { - qname: Labels::root(), - opt: OPT { - udp_payload_size: 512, - higher_bits: 0, - edns0_version: 0, - flags: 0, - data: vec![], - }, - }, - ], - }; - - assert_eq!(Response::from_bytes(buf), Ok(response)); -} - - -#[test] -fn parse_response_with_mixed_string() { - let buf = &[ - 0x06, 0x9f, // transaction ID - 0x81, 0x80, // flags (standard query, response, no error) - 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // counts (1, 1, 0, 0) - - // the query: - 0x0d, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x2d, 0x65, 0x78, 0x61, 0x6d, 0x70, - 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x03, 0x64, 0x6f, - 0x67, 0x00, // "cname-example.lookup.dog" - 0x00, 0x05, // type CNAME - 0x00, 0x01, // class IN - - // the answer: - 0xc0, 0x0c, // to find the name, backtrack to position 0x0c (12) - 0x00, 0x05, // type CNAME - 0x00, 0x01, // class IN - 0x00, 0x00, 0x03, 0x69, // TTL (873 seconds) - 0x00, 0x06, // record data length 6 - 0x03, 0x64, 0x6e, 0x73, 0xc0, 0x1a, - // "dns.lookup.dog.", which is "dns." + backtrack to position 0x1a (28) - ]; - - let response = Response { - transaction_id: 0x069f, - flags: Flags { - response: true, - opcode: Opcode::Query, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authentic_data: false, - checking_disabled: false, - error_code: None, - }, - queries: vec![ - Query { - qname: Labels::encode("cname-example.lookup.dog").unwrap(), - qclass: QClass::IN, - qtype: RecordType::CNAME, - }, - ], - answers: vec![ - Answer::Standard { - qname: Labels::encode("cname-example.lookup.dog").unwrap(), - qclass: QClass::IN, - ttl: 873, - record: Record::CNAME(CNAME { - domain: Labels::encode("dns.lookup.dog").unwrap(), - }), - } - ], - authorities: vec![], - additionals: vec![], - }; - - assert_eq!(Response::from_bytes(buf), Ok(response)); -} - - -#[test] -fn parse_response_with_multiple_additionals() { - - // This is an artifical amalgam of DNS, not a real-world response! - let buf = &[ - 0xce, 0xac, // transaction ID - 0x81, 0x80, // flags (standard query, response, no error) - 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, // counts (1, 1, 1, 2) - - // query: - 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, 0x00, // name - 0x00, 0x01, // type A - 0x00, 0x01, // class IN - - // answer: - 0xc0, 0x0c, // name (backreference) - 0x00, 0x01, // type A - 0x00, 0x01, // class IN - 0x00, 0x00, 0x03, 0x77, // TTL - 0x00, 0x04, // data length 4 - 0x8a, 0x44, 0x75, 0x5e, // IP address - - // authoritative: - 0x00, // name - 0x00, 0x06, // type SOA - 0x00, 0x01, // class IN - 0xFF, 0xFF, 0xFF, 0xFF, // TTL (maximum possible!) - 0x00, 0x1B, // data length - 0x01, 0x61, 0x00, // primary name server ("a") - 0x02, 0x6d, 0x78, 0x00, // mailbox ("mx") - 0x78, 0x68, 0x52, 0x2c, // serial number - 0x00, 0x00, 0x07, 0x08, // refresh interval - 0x00, 0x00, 0x03, 0x84, // retry interval - 0x00, 0x09, 0x3a, 0x80, // expire limit - 0x00, 0x01, 0x51, 0x80, // minimum TTL - - // additional 1: - 0x00, // name - 0x00, 0x99, // unknown type - 0x00, 0x99, // unknown class - 0x12, 0x34, 0x56, 0x78, // TTL - 0x00, 0x04, // data length 4 - 0x12, 0x34, 0x56, 0x78, // data - - // additional 2: - 0x00, // name - 0x00, 0x29, // type OPT - 0x02, 0x00, // UDP payload size - 0x00, // higher bits - 0x00, // EDNS(0) version - 0x00, 0x00, // more flags - 0x00, 0x00, // no data - ]; - - let response = Response { - transaction_id: 0xceac, - flags: Flags::standard_response(), - queries: vec![ - Query { - qname: Labels::encode("bsago.me").unwrap(), - qclass: QClass::IN, - qtype: RecordType::A, - }, - ], - answers: vec![ - Answer::Standard { - qname: Labels::encode("bsago.me").unwrap(), - qclass: QClass::IN, - ttl: 887, - record: Record::A(A { - address: Ipv4Addr::new(138, 68, 117, 94), - }), - } - ], - authorities: vec![ - Answer::Standard { - qname: Labels::root(), - qclass: QClass::IN, - ttl: 4294967295, - record: Record::SOA(SOA { - mname: Labels::encode("a").unwrap(), - rname: Labels::encode("mx").unwrap(), - serial: 2020102700, - refresh_interval: 1800, - retry_interval: 900, - expire_limit: 604800, - minimum_ttl: 86400, - }), - } - ], - additionals: vec![ - Answer::Standard { - qname: Labels::root(), - qclass: QClass::Other(153), - ttl: 305419896, - record: Record::Other { - type_number: UnknownQtype::UnheardOf(153), - bytes: vec![ 0x12, 0x34, 0x56, 0x78 ], - }, - }, - Answer::Pseudo { - qname: Labels::root(), - opt: OPT { - udp_payload_size: 512, - higher_bits: 0, - edns0_version: 0, - flags: 0, - data: vec![], - }, - }, - ], - }; - - assert_eq!(Response::from_bytes(buf), Ok(response)); -} diff --git a/man/dog.1.md b/man/dog.1.md index 53cacdc..4285f82 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -63,19 +63,6 @@ If more than one domain, type, nameserver, or class is specified, dog will perfo DNS traditionally uses port 53 for both TCP and UDP. To use a resolver with a different port, include the port number after a colon (`:`) in the nameserver address. -SENDING OPTIONS -=============== - -`--edns=SETTING` -: Whether to opt in to DNS. This can be ‘`disable`’, ‘`hide`’, or ‘`show`’. - -`--txid=NUMBER` -: Set the transaction ID to a specific value. - -`-Z=TWEAKS` -: Set uncommon protocol-level tweaks. - - TRANSPORT OPTIONS ================= @@ -110,7 +97,7 @@ OUTPUT OPTIONS : Display the output as JSON. `--color`, `--colour=WHEN` -: When to colourise the output. This can be ‘`always`’, ‘`automatic`’, or ‘`never`’. +: When to colourise the output. This can be ‘`always`’ or ‘`automatic`’, or ‘`never`’. `--seconds` : Do not format durations as hours and minutes; instead, display them as seconds. @@ -139,111 +126,6 @@ dog responds to the following environment variables: Set this to any non-empty value to have dog emit debugging information to standard error. For more in-depth output, set this to the exact string ‘`trace`’. -RECORD TYPES -============ - -dog understands and can interpret the following record types: - -`A` -: IPv4 addresses - -`AAAA` -: IPv6 addresses - -`CAA` -: permitted certificate authorities - -`CNAME` -: canonical domain aliases - -`DHCID` -: DHCP identifiers for DNS updates - -`DNSKEY` -: DNS public keys for DNSSEC - -`DS` -: delegation signer records for DNSSEC - -`HINFO` -: system information and, sometimes, forbidden request explanations - -`IPSECKEY` -: IPsec keys - -`LOC` -: location information - -`MX` -: e-mail server addresses - -`NAPTR` -: DDDS rules - -`NS` -: domain name servers - -`NSEC` -: next secure records for DNSSEC - -`NSEC3` -: next secure v3 records for DNSSEC - -`NSEC3PARAM` -: NSEC3 parameters - -`OPT` -: extensions to the DNS protocol - -`PTR` -: pointers to canonical names, usually for reverse lookups - -`RRSIG` -: DNSSEC signatures - -`SMIMEA` -: S/MIME certificate associations - -`SOA` -: administrative information about zones - -`SRV` -: IP addresses with port numbers - -`SSHFP` -: SSH key fingerprints - -`TLSA` -: TLS certificates, public keys, and hashes - -`TXT` -: arbitrary textual information - -When a response DNS packet contains a record of one of these known types, dog will display it in a table containing the type name and a human-readable summary of its contents. - -Records with a type number that does not map to any known record type will still be displayed. As they cannot be interpreted, their contents will be displayed as a series of numbers instead. - -dog also contains a list of record type names that it knows the type number of, but is not able to interpret, such as `IXFR` or `ANY` or `AFSDB`. These are acceptable as command-line arguments, meaning you can send an AFSDB request with ‘`dog AFSDB`’. However, their response contents will still be displayed as numbers. They may be supported in future versions of dog. - - -PROTOCOL TWEAKS -=============== - -The `-Z` command-line argument can be used one or more times to set some protocol-level options in the DNS queries that get sent. It accepts the following values: - -`aa` -: Sets the `AA` (Authoritative Answers) bit in the query. - -`ad` -: Sets the `AD` (Authentic Data) bit in the query. - -`bufsize=NUM` -: Sets the UDP payload size field in the OPT field in the query. This has no effect if EDNS is diabled. - -`cd` -: Sets the `CD` (Checking Disabled) bit in the query. - - EXIT STATUSES ============= @@ -259,9 +141,6 @@ EXIT STATUSES 3 : If there was a problem with the command-line arguments. -4 -: If there was a problem obtaining the system nameserver information. - AUTHOR ====== @@ -269,4 +148,4 @@ AUTHOR dog is maintained by Benjamin ‘ogham’ Sago. **Website:** `https://dns.lookup.dog/` \ -**Source code:** `https://github.com/ogham/dog` +**Source code:** `https://github.com/ogham/dog` \ No newline at end of file diff --git a/src/colours.rs b/src/colours.rs index 05ffcc3..913aad1 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -10,30 +10,18 @@ pub struct Colours { pub qname: Style, pub answer: Style, - pub authority: Style, - pub additional: Style, pub a: Style, pub aaaa: Style, pub caa: Style, pub cname: Style, - pub eui48: Style, - pub eui64: Style, - pub hinfo: Style, - pub loc: Style, pub mx: Style, pub ns: Style, - - pub openpgpkey: Style, - pub opt: Style, pub ptr: Style, - pub sshfp: Style, pub soa: Style, pub srv: Style, - pub tlsa: Style, pub txt: Style, - pub uri: Style, - pub unknown: Style, + pub default: Style, } impl Colours { @@ -43,32 +31,18 @@ impl Colours { pub fn pretty() -> Self { Self { qname: Blue.bold(), - answer: Style::default(), - authority: Cyan.normal(), - additional: Green.normal(), - a: Green.bold(), aaaa: Green.bold(), caa: Red.normal(), cname: Yellow.normal(), - eui48: Yellow.normal(), - eui64: Yellow.bold(), - hinfo: Yellow.normal(), - loc: Yellow.normal(), mx: Cyan.normal(), - ns: Red.normal(), - openpgpkey: Cyan.normal(), - opt: Purple.normal(), ptr: Red.normal(), - sshfp: Cyan.normal(), soa: Purple.normal(), srv: Cyan.normal(), - tlsa: Yellow.normal(), txt: Yellow.normal(), - uri: Yellow.normal(), - unknown: White.on(Red), + default: White.on(Red), } } diff --git a/src/connect.rs b/src/connect.rs deleted file mode 100644 index 05a5a48..0000000 --- a/src/connect.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Creating DNS transports based on the user’s input arguments. - -use dns_transport::*; - - -/// A **transport type** creates a `Transport` that determines which protocols -/// should be used to send and receive DNS wire data over the network. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum TransportType { - - /// Send packets over UDP or TCP. - /// UDP is used by default. If the request packet would be too large, send - /// a TCP packet instead; if a UDP _response_ packet is truncated, try - /// again with TCP. - Automatic, - - /// Send packets over UDP only. - /// If the request packet is too large or the response packet is - /// truncated, fail with an error. - UDP, - - /// Send packets over TCP only. - TCP, - - /// Send encrypted DNS-over-TLS packets. - TLS, - - /// Send encrypted DNS-over-HTTPS packets. - HTTPS, -} - -impl TransportType { - - /// Creates a boxed `Transport` depending on the transport type. The - /// parameter will be a URL for the HTTPS transport type, and a - /// stringified address for the others. - pub fn make_transport(self, param: String) -> Box { - match self { - Self::Automatic => Box::new(AutoTransport::new(param)), - Self::UDP => Box::new(UdpTransport::new(param)), - Self::TCP => Box::new(TcpTransport::new(param)), - Self::TLS => Box::new(TlsTransport::new(param)), - Self::HTTPS => Box::new(HttpsTransport::new(param)), - } - } -} diff --git a/src/hints.rs b/src/hints.rs index cbd9541..1f47b77 100644 --- a/src/hints.rs +++ b/src/hints.rs @@ -16,7 +16,7 @@ use log::*; /// case, to prevent confusion. #[derive(Default)] pub struct LocalHosts { - hostnames: BTreeSet, + hostnames: BTreeSet, } impl LocalHosts { @@ -64,14 +64,7 @@ impl LocalHosts { } for hostname in line.split_ascii_whitespace().skip(1) { - match dns::Labels::encode(hostname) { - Ok(hn) => { - hostnames.insert(hn); - } - Err(e) => { - warn!("Failed to encode local host hint {:?}: {}", hostname, e); - } - } + hostnames.insert(hostname.to_string()); } } @@ -81,7 +74,7 @@ impl LocalHosts { /// Queries this set of hostnames to see if the given name, which is about /// to be queried for, exists within the file. - pub fn contains(&self, hostname_in_query: &dns::Labels) -> bool { + pub fn contains(&self, hostname_in_query: &String) -> bool { self.hostnames.contains(hostname_in_query) } } diff --git a/src/main.rs b/src/main.rs index 8c02717..6b052d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,16 +22,14 @@ #![deny(unsafe_code)] use log::*; +use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; mod colours; -mod connect; mod hints; mod logger; mod output; -mod requests; -mod resolve; mod table; -mod txid; mod options; use self::options::*; @@ -39,7 +37,8 @@ use self::options::*; /// Configures logging, parses the command-line options, and handles any /// errors before passing control over to the Dog type. -fn main() { +#[tokio::main] +async fn main() { use std::env; use std::process::exit; @@ -53,15 +52,15 @@ fn main() { match Options::getopts(env::args_os().skip(1)) { OptionsResult::Ok(options) => { info!("Running with options -> {:#?}", options); - exit(run(options)); + exit(run(options).await); } OptionsResult::Help(help_reason, use_colours) => { if use_colours.should_use_colours() { - print!("{}", include_str!(concat!(env!("OUT_DIR"), "/usage.pretty.txt"))); + print!("{}", usage_pretty()); } else { - print!("{}", include_str!(concat!(env!("OUT_DIR"), "/usage.bland.txt"))); + print!("{}", usage_bland()); } if help_reason == HelpReason::NoDomains { @@ -74,10 +73,10 @@ fn main() { OptionsResult::Version(use_colours) => { if use_colours.should_use_colours() { - print!("{}", include_str!(concat!(env!("OUT_DIR"), "/version.pretty.txt"))); + print!("{}", version_pretty()); } else { - print!("{}", include_str!(concat!(env!("OUT_DIR"), "/version.bland.txt"))); + print!("{}", version_bland()); } exit(exits::SUCCESS); @@ -95,13 +94,27 @@ fn main() { } } +fn usage_pretty() -> &'static str { + include_str!(concat!(env!("OUT_DIR"), "/usage.pretty.txt")) +} + +fn usage_bland() -> &'static str { + include_str!(concat!(env!("OUT_DIR"), "/usage.bland.txt")) +} + +fn version_pretty() -> &'static str { + include_str!(concat!(env!("OUT_DIR"), "/version.pretty.txt")) +} + +fn version_bland() -> &'static str { + include_str!(concat!(env!("OUT_DIR"), "/version.bland.txt")) +} + /// Runs dog with some options, returning the status to exit with. -fn run(Options { requests, format, measure_time }: Options) -> i32 { +async fn run(Options { requests, format, measure_time }: Options) -> i32 { use std::time::Instant; - let should_show_opt = requests.edns.should_show(); - let mut responses = Vec::new(); let timer = if measure_time { Some(Instant::now()) } else { None }; @@ -121,43 +134,25 @@ fn run(Options { requests, format, measure_time }: Options) -> i32 { } } - let request_tuples = match requests.generate() { - Ok(rt) => rt, - Err(e) => { - eprintln!("Unable to obtain resolver: {}", e); - return exits::SYSTEM_ERROR; - } - }; + let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); - for (transport, request_list) in request_tuples { - let request_list_len = request_list.len(); - for (i, request) in request_list.into_iter().enumerate() { - let result = transport.send(&request); + for domain in &requests.inputs.domains { + for qtype in requests.inputs.record_types.iter().copied() { + let result = resolver.lookup(domain.to_string().as_str(), qtype).await; match result { - Ok(mut response) => { - if response.flags.error_code.is_some() && i != request_list_len - 1 { - continue; - } - - if ! should_show_opt { - response.answers.retain(dns::Answer::is_standard); - response.authorities.retain(dns::Answer::is_standard); - response.additionals.retain(dns::Answer::is_standard); - } - + Ok(response) => { responses.push(response); - break; } Err(e) => { format.print_error(e); errored = true; - break; } } } } + let duration = timer.map(|t| t.elapsed()); if format.print(responses, duration) { if errored { @@ -188,7 +183,4 @@ mod exits { /// Exit code for when the command-line options are invalid. pub const OPTIONS_ERROR: i32 = 3; - - /// Exit code for when the system network configuration could not be determined. - pub const SYSTEM_ERROR: i32 = 4; } diff --git a/src/options.rs b/src/options.rs index d658be0..c0fc411 100644 --- a/src/options.rs +++ b/src/options.rs @@ -6,14 +6,9 @@ use std::net::IpAddr; use log::*; -use dns::{QClass, Labels}; -use dns::record::RecordType; +use hickory_resolver::proto::rr::RecordType; -use crate::connect::TransportType; use crate::output::{OutputFormat, UseColours, TextFormat}; -use crate::requests::{RequestGenerator, Inputs, ProtocolTweaks, UseEDNS}; -use crate::resolve::ResolverType; -use crate::txid::TxidGenerator; /// The command-line options used when running dog. @@ -21,7 +16,7 @@ use crate::txid::TxidGenerator; pub struct Options { /// The requests to make and how they should be generated. - pub requests: RequestGenerator, + pub requests: Requests, /// Whether to display the time taken after every query. pub measure_time: bool, @@ -30,6 +25,11 @@ pub struct Options { pub format: OutputFormat, } +#[derive(PartialEq, Debug, Default)] +pub struct Requests { + pub inputs: Inputs, +} + impl Options { /// Parses and interprets a set of options from the user’s command-line @@ -108,93 +108,57 @@ impl Options { fn deduce(matches: getopts::Matches) -> Result { let measure_time = matches.opt_present("time"); let format = OutputFormat::deduce(&matches); - let requests = RequestGenerator::deduce(matches)?; + let requests = Requests::deduce(matches)?; Ok(Self { requests, measure_time, format }) } } -impl RequestGenerator { +impl Requests { fn deduce(matches: getopts::Matches) -> Result { - let edns = UseEDNS::deduce(&matches)?; - let txid_generator = TxidGenerator::deduce(&matches)?; - let protocol_tweaks = ProtocolTweaks::deduce(&matches)?; let inputs = Inputs::deduce(matches)?; - Ok(Self { inputs, txid_generator, edns, protocol_tweaks }) + Ok(Self { inputs }) } } +/// Which things the user has specified they want queried. +#[derive(PartialEq, Debug, Default)] +pub struct Inputs { + + /// The list of domain names to query. + pub domains: Vec, + + /// The list of DNS record types to query for. + pub record_types: Vec, +} + + impl Inputs { fn deduce(matches: getopts::Matches) -> Result { let mut inputs = Self::default(); - inputs.load_transport_types(&matches); inputs.load_named_args(&matches)?; inputs.load_free_args(matches)?; - inputs.check_for_missing_nameserver()?; inputs.load_fallbacks(); Ok(inputs) } - fn load_transport_types(&mut self, matches: &getopts::Matches) { - if matches.opt_present("https") { - self.transport_types.push(TransportType::HTTPS); - } - - if matches.opt_present("tls") { - self.transport_types.push(TransportType::TLS); - } - - if matches.opt_present("tcp") { - self.transport_types.push(TransportType::TCP); - } - - if matches.opt_present("udp") { - self.transport_types.push(TransportType::UDP); - } - } - fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { for domain in matches.opt_strs("query") { - self.add_domain(&domain)?; + self.add_domain(&domain); } for record_name in matches.opt_strs("type") { - if record_name.eq_ignore_ascii_case("OPT") { - return Err(OptionsError::QueryTypeOPT); - } - else if record_name.eq_ignore_ascii_case("ANY") { - self.record_types.extend(RecordType::all_record_types()); - } - else if let Some(record_type) = RecordType::from_type_name(&record_name) { + if let Ok(record_type) = record_name.to_uppercase().parse() { self.add_type(record_type); } - else if let Ok(type_number) = record_name.parse::() { - self.record_types.push(RecordType::from(type_number)); - } else { return Err(OptionsError::InvalidQueryType(record_name)); } } - for ns in matches.opt_strs("nameserver") { - self.add_nameserver(&ns); - } - - for class_name in matches.opt_strs("class") { - if let Some(class) = parse_class_name(&class_name) { - self.add_class(class); - } - else if let Ok(class_number) = class_name.parse() { - self.add_class(QClass::Other(class_number)); - } - else { - return Err(OptionsError::InvalidQueryClass(class_name)); - } - } - Ok(()) } @@ -202,27 +166,15 @@ impl Inputs { for argument in matches.free { if let Some(nameserver) = argument.strip_prefix('@') { trace!("Got nameserver -> {:?}", nameserver); - self.add_nameserver(nameserver); } else if is_constant_name(&argument) { - if argument.eq_ignore_ascii_case("OPT") { - return Err(OptionsError::QueryTypeOPT); - } - else if argument.eq_ignore_ascii_case("ANY") { - trace!("Got qtype -> ANY"); - self.record_types.extend(RecordType::all_record_types()); - } - else if let Some(class) = parse_class_name(&argument) { - trace!("Got qclass -> {:?}", &argument); - self.add_class(class); - } - else if let Some(record_type) = RecordType::from_type_name(&argument) { + if let Ok(record_type) = argument.to_uppercase().parse() { trace!("Got qtype -> {:?}", &argument); self.add_type(record_type); } else { trace!("Got single-word domain -> {:?}", &argument); - self.add_domain(&argument)?; + self.add_domain(&argument); } } else { @@ -230,11 +182,11 @@ impl Inputs { if let Ok(ip) = argument.parse::() { let reverse_domain = reverse_lookup_domain(ip); - self.add_domain(&reverse_domain)?; + self.add_domain(&reverse_domain); self.add_type(RecordType::PTR); } else { - self.add_domain(&argument)?; + self.add_domain(&argument); } } } @@ -242,54 +194,19 @@ impl Inputs { Ok(()) } - fn check_for_missing_nameserver(&self) -> Result<(), OptionsError> { - if self.resolver_types.is_empty() && self.transport_types == [TransportType::HTTPS] { - Err(OptionsError::MissingHttpsUrl) - } - else { - Ok(()) - } - } - fn load_fallbacks(&mut self) { if self.record_types.is_empty() { self.record_types.push(RecordType::A); } - - if self.classes.is_empty() { - self.classes.push(QClass::IN); - } - - if self.resolver_types.is_empty() { - self.resolver_types.push(ResolverType::SystemDefault); - } - - if self.transport_types.is_empty() { - self.transport_types.push(TransportType::Automatic); - } } - fn add_domain(&mut self, input: &str) -> Result<(), OptionsError> { - if let Ok(domain) = Labels::encode(input) { - self.domains.push(domain); - Ok(()) - } - else { - Err(OptionsError::InvalidDomain(input.into())) - } + fn add_domain(&mut self, input: &str) { + self.domains.push(input.to_string()); } fn add_type(&mut self, rt: RecordType) { self.record_types.push(rt); } - - fn add_nameserver(&mut self, input: &str) { - self.resolver_types.push(ResolverType::Specific(input.into())); - } - - fn add_class(&mut self, class: QClass) { - self.classes.push(class); - } } fn is_constant_name(argument: &str) -> bool { @@ -305,21 +222,6 @@ fn is_constant_name(argument: &str) -> bool { argument.chars().all(|c| c.is_ascii_alphanumeric()) } -fn parse_class_name(input: &str) -> Option { - if input.eq_ignore_ascii_case("IN") { - Some(QClass::IN) - } - else if input.eq_ignore_ascii_case("CH") { - Some(QClass::CH) - } - else if input.eq_ignore_ascii_case("HS") { - Some(QClass::HS) - } - else { - None - } -} - fn reverse_lookup_domain(ip: IpAddr) -> String { match ip { IpAddr::V4(v4) => { @@ -340,48 +242,6 @@ fn reverse_lookup_domain(ip: IpAddr) -> String { } -impl TxidGenerator { - fn deduce(matches: &getopts::Matches) -> Result { - if let Some(starting_txid) = matches.opt_str("txid") { - if let Some(start) = parse_dec_or_hex(&starting_txid) { - Ok(Self::Sequence(start)) - } - else { - Err(OptionsError::InvalidTxid(starting_txid)) - } - } - else { - Ok(Self::Random) - } - } -} - -fn parse_dec_or_hex(input: &str) -> Option { - if let Some(hex_str) = input.strip_prefix("0x") { - match u16::from_str_radix(hex_str, 16) { - Ok(num) => { - Some(num) - } - Err(e) => { - warn!("Error parsing hex number: {}", e); - None - } - } - } - else { - match input.parse() { - Ok(num) => { - Some(num) - } - Err(e) => { - warn!("Error parsing number: {}", e); - None - } - } - } -} - - impl OutputFormat { fn deduce(matches: &getopts::Matches) -> Self { if matches.opt_present("short") { @@ -423,61 +283,6 @@ impl TextFormat { } -impl UseEDNS { - fn deduce(matches: &getopts::Matches) -> Result { - if let Some(edns) = matches.opt_str("edns") { - match edns.as_str() { - "disable" | "off" => Ok(Self::Disable), - "hide" => Ok(Self::SendAndHide), - "show" => Ok(Self::SendAndShow), - oh => Err(OptionsError::InvalidEDNS(oh.into())), - } - } - else { - Ok(Self::SendAndHide) - } - } -} - - -impl ProtocolTweaks { - fn deduce(matches: &getopts::Matches) -> Result { - let mut tweaks = Self::default(); - - for tweak_str in matches.opt_strs("Z") { - match &*tweak_str { - "aa" | "authoritative" => { - tweaks.set_authoritative_flag = true; - } - "ad" | "authentic" => { - tweaks.set_authentic_flag = true; - } - "cd" | "checking-disabled" => { - tweaks.set_checking_disabled_flag = true; - } - otherwise => { - if let Some(remaining_num) = tweak_str.strip_prefix("bufsize=") { - match remaining_num.parse() { - Ok(parsed_bufsize) => { - tweaks.udp_payload_size = Some(parsed_bufsize); - continue; - } - Err(e) => { - warn!("Failed to parse buffer size: {}", e); - } - } - } - - return Err(OptionsError::InvalidTweak(otherwise.into())); - } - } - } - - Ok(tweaks) - } -} - - /// The result of the `Options::getopts` function. #[derive(PartialEq, Debug)] pub enum OptionsResult { @@ -514,27 +319,13 @@ pub enum HelpReason { /// Something wrong with the combination of options the user has picked. #[derive(PartialEq, Debug)] pub enum OptionsError { - InvalidDomain(String), - InvalidEDNS(String), InvalidQueryType(String), - InvalidQueryClass(String), - InvalidTxid(String), - InvalidTweak(String), - QueryTypeOPT, - MissingHttpsUrl, } impl fmt::Display for OptionsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidDomain(domain) => write!(f, "Invalid domain {:?}", domain), - Self::InvalidEDNS(edns) => write!(f, "Invalid EDNS setting {:?}", edns), Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt), - Self::InvalidQueryClass(qc) => write!(f, "Invalid query class {:?}", qc), - Self::InvalidTxid(txid) => write!(f, "Invalid transaction ID {:?}", txid), - Self::InvalidTweak(tweak) => write!(f, "Invalid protocol tweak {:?}", tweak), - Self::QueryTypeOPT => write!(f, "OPT request is sent by default (see -Z flag)"), - Self::MissingHttpsUrl => write!(f, "You must pass a URL as a nameserver when using --https"), } } } @@ -550,9 +341,6 @@ mod test { Inputs { domains: vec![ /* No domains by default */ ], record_types: vec![ RecordType::A ], - classes: vec![ QClass::IN ], - resolver_types: vec![ ResolverType::SystemDefault ], - transport_types: vec![ TransportType::Automatic ], } } } @@ -617,7 +405,7 @@ mod test { fn just_domain() { let options = Options::getopts(&[ "lookup.dog" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], + domains: vec![ "lookup.dog".to_string() ], .. Inputs::fallbacks() }); } @@ -626,7 +414,7 @@ mod test { fn just_named_domain() { let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], + domains: vec![ "lookup.dog".to_string() ], .. Inputs::fallbacks() }); } @@ -635,7 +423,7 @@ mod test { fn domain_and_type() { let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], .. Inputs::fallbacks() }); @@ -645,94 +433,48 @@ mod test { fn domain_and_type_lowercase() { let options = Options::getopts(&[ "lookup.dog", "soa" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], .. Inputs::fallbacks() }); } - #[test] - fn domain_and_any_type() { - let options = Options::getopts(&[ "lookup.dog", "any" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - record_types: RecordType::all_record_types(), - .. Inputs::fallbacks() - }); - } - #[test] fn domain_and_single_domain() { let options = Options::getopts(&[ "lookup.dog", "mixes" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap(), - Labels::encode("mixes").unwrap() ], - .. Inputs::fallbacks() - }); - } - - #[test] - fn domain_and_nameserver() { - let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()) ], - .. Inputs::fallbacks() - }); - } - - #[test] - fn domain_and_class() { - let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], - .. Inputs::fallbacks() - }); - } - - #[test] - fn domain_and_class_lowercase() { - let options = Options::getopts(&[ "lookup.dog", "ch" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], + domains: vec![ "lookup.dog".to_string(), + "mixes".to_string() ], .. Inputs::fallbacks() }); } #[test] fn all_free() { - let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap(); + let options = Options::getopts(&[ "lookup.dog", "NS", "@1.1.1.1" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::NS ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()) ], .. Inputs::fallbacks() }); } #[test] fn all_parameters() { - let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap(); + let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()) ], .. Inputs::fallbacks() }); } #[test] fn all_parameters_lowercase() { - let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "ch", "--type", "soa", "--nameserver", "1.1.1.1" ]).unwrap(); + let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "soa", "--nameserver", "1.1.1.1" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()) ], .. Inputs::fallbacks() }); } @@ -741,87 +483,32 @@ mod test { fn two_types() { let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SRV, RecordType::AAAA ], .. Inputs::fallbacks() }); } - #[test] - fn two_classes() { - let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::IN, QClass::CH ], - .. Inputs::fallbacks() - }); - } - #[test] fn all_mixed_1() { - let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap(); + let options = Options::getopts(&[ "lookup.dog", "SOA", "--nameserver", "1.1.1.1" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::CH ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()) ], .. Inputs::fallbacks() }); } #[test] fn all_mixed_2() { - let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap(); + let options = Options::getopts(&[ "SOA", "MX", "-q", "lookup.dog" ]).unwrap(); assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - classes: vec![ QClass::HS, QClass::CH, QClass::IN ], + domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA, RecordType::MX ], .. Inputs::fallbacks() }); } - #[test] - fn all_mixed_3() { - let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("lookup.dog").unwrap() ], - resolver_types: vec![ ResolverType::Specific("1.1.1.1".into()), - ResolverType::Specific("1.0.0.1".into()), ], - .. Inputs::fallbacks() - }); - } - - #[test] - fn explicit_numerics() { - let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap(); - assert_eq!(options.requests.inputs, Inputs { - domains: vec![ Labels::encode("11").unwrap() ], - classes: vec![ QClass::Other(22) ], - record_types: vec![ RecordType::from(33) ], - .. Inputs::fallbacks() - }); - } - - #[test] - fn edns_and_tweaks() { - let options = Options::getopts(&[ "dom.ain", "--edns", "show", "-Z", "authentic" ]).unwrap(); - assert_eq!(options.requests.edns, UseEDNS::SendAndShow); - assert_eq!(options.requests.protocol_tweaks.set_authentic_flag, true); - } - - #[test] - fn two_more_tweaks() { - let options = Options::getopts(&[ "dom.ain", "-Z", "aa", "-Z", "cd" ]).unwrap(); - assert_eq!(options.requests.protocol_tweaks.set_authoritative_flag, true); - assert_eq!(options.requests.protocol_tweaks.set_checking_disabled_flag, true); - } - - #[test] - fn udp_size() { - let options = Options::getopts(&[ "dom.ain", "-Z", "bufsize=4096" ]).unwrap(); - assert_eq!(options.requests.protocol_tweaks.udp_payload_size, Some(4096)); - } - #[test] fn short_mode() { let tf = TextFormat { format_durations: true }; @@ -842,36 +529,8 @@ mod test { assert_eq!(options.format, OutputFormat::JSON); } - #[test] - fn specific_txid() { - let options = Options::getopts(&[ "dom.ain", "--txid", "1234" ]).unwrap(); - assert_eq!(options.requests.txid_generator, - TxidGenerator::Sequence(1234)); - } - - #[test] - fn all_transport_types() { - use crate::connect::TransportType::*; - - let options = Options::getopts(&[ "dom.ain", "--https", "--tls", "--tcp", "--udp" ]).unwrap(); - assert_eq!(options.requests.inputs.transport_types, - vec![ HTTPS, TLS, TCP, UDP ]); - } - // invalid options tests - #[test] - fn invalid_named_class() { - assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "tubes" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("tubes".into()))); - } - - #[test] - fn invalid_named_class_too_big() { - assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "999999" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("999999".into()))); - } - #[test] fn invalid_named_type() { assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "tubes" ]), @@ -883,87 +542,4 @@ mod test { assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "999999" ]), OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("999999".into()))); } - - #[test] - fn invalid_txid() { - assert_eq!(Options::getopts(&[ "lookup.dog", "--txid=0x10000" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidTxid("0x10000".into()))); - } - - #[test] - fn invalid_edns() { - assert_eq!(Options::getopts(&[ "--edns=yep" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidEDNS("yep".into()))); - } - - #[test] - fn invalid_tweaks() { - assert_eq!(Options::getopts(&[ "-Zsleep" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidTweak("sleep".into()))); - } - - #[test] - fn invalid_udp_size() { - assert_eq!(Options::getopts(&[ "-Z", "bufsize=null" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=null".into()))); - } - - #[test] - fn invalid_udp_size_size() { - assert_eq!(Options::getopts(&[ "-Z", "bufsize=999999999" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=999999999".into()))); - } - - #[test] - fn invalid_udp_size_missing() { - assert_eq!(Options::getopts(&[ "-Z", "bufsize=" ]), - OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=".into()))); - } - - #[test] - fn missing_https_url() { - assert_eq!(Options::getopts(&[ "--https", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::MissingHttpsUrl)); - } - - // opt tests - - #[test] - fn opt() { - assert_eq!(Options::getopts(&[ "OPT", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT)); - } - - #[test] - fn opt_lowercase() { - assert_eq!(Options::getopts(&[ "opt", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT)); - } - - #[test] - fn opt_arg() { - assert_eq!(Options::getopts(&[ "-t", "OPT", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT)); - } - - #[test] - fn opt_arg_lowercase() { - assert_eq!(Options::getopts(&[ "-t", "opt", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT)); - } - - // txid tests - - #[test] - fn number_parsing() { - assert_eq!(parse_dec_or_hex("1234"), Some(1234)); - assert_eq!(parse_dec_or_hex("0x1234"), Some(0x1234)); - assert_eq!(parse_dec_or_hex("0xABcd"), Some(0xABcd)); - - assert_eq!(parse_dec_or_hex("65536"), None); - assert_eq!(parse_dec_or_hex("0x65536"), None); - - assert_eq!(parse_dec_or_hex(""), None); - assert_eq!(parse_dec_or_hex("0x"), None); - } } diff --git a/src/output.rs b/src/output.rs index 09ec108..d6bb642 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,13 +1,11 @@ //! Text and JSON output. -use std::fmt; use std::time::Duration; use std::env; -use dns::{Response, Query, Answer, QClass, ErrorCode, WireError, MandatedLength}; -use dns::record::{Record, RecordType, UnknownQtype, OPT}; -use dns_transport::Error as TransportError; -use json::{object, JsonValue}; +use hickory_resolver::lookup::Lookup; +use hickory_resolver::error::ResolveError; +use json::object; use crate::colours::Colours; use crate::table::{Table, Section}; @@ -78,10 +76,10 @@ impl OutputFormat { /// settings. If the duration has been measured, it should also be /// printed. Returns `false` if there were no results to print, and `true` /// otherwise. - pub fn print(self, responses: Vec, duration: Option) -> bool { + pub fn print(self, responses: Vec, duration: Option) -> bool { match self { Self::Short(tf) => { - let all_answers = responses.into_iter().flat_map(|r| r.answers).collect::>(); + let all_answers = responses.into_iter().flat_map(|r| r.into_iter()).collect::>(); if all_answers.is_empty() { eprintln!("No results"); @@ -89,15 +87,7 @@ impl OutputFormat { } for answer in all_answers { - match answer { - Answer::Standard { record, .. } => { - println!("{}", tf.record_payload_summary(record)) - } - Answer::Pseudo { opt, .. } => { - println!("{}", tf.pseudo_record_payload_summary(opt)) - } - } - + println!("{}", tf.record_payload_summary(&answer)); } } Self::JSON => { @@ -105,10 +95,7 @@ impl OutputFormat { for response in responses { let json = object! { - "queries": json_queries(response.queries), - "answers": json_answers(response.answers), - "authorities": json_answers(response.authorities), - "additionals": json_answers(response.additionals), + "answers": response.record_iter().map(|r| r.to_string()).collect::>(), }; rs.push(json); @@ -137,22 +124,8 @@ impl OutputFormat { let mut table = Table::new(uc.palette(), tf); for response in responses { - if let Some(rcode) = response.flags.error_code { - if !(rcode == ErrorCode::FormatError && !response.answers.is_empty()) { - print_error_code(rcode); - } - } - - for a in response.answers { - table.add_row(a, Section::Answer); - } - - for a in response.authorities { - table.add_row(a, Section::Authority); - } - - for a in response.additionals { - table.add_row(a, Section::Additional); + for a in response.record_iter() { + table.add_row(a.clone(), Section::Answer); } } @@ -165,17 +138,16 @@ impl OutputFormat { /// Print an error that’s ocurred while sending or receiving DNS packets /// to standard error. - pub fn print_error(self, error: TransportError) { + pub fn print_error(self, error: ResolveError) { match self { Self::Short(..) | Self::Text(..) => { - eprintln!("Error [{}]: {}", erroneous_phase(&error), error_message(error)); + eprintln!("Error: {}", error); } Self::JSON => { let object = object! { "error": true, - "error_phase": erroneous_phase(&error), - "error_message": error_message(error), + "error_message": error.to_string(), }; eprintln!("{}", object); @@ -189,172 +161,8 @@ impl TextFormat { /// Formats a summary of a record in a received DNS response. Each record /// type contains wildly different data, so the format of the summary /// depends on what record it’s for. - pub fn record_payload_summary(self, record: Record) -> String { - match record { - Record::A(a) => { - format!("{}", a.address) - } - Record::AAAA(aaaa) => { - format!("{}", aaaa.address) - } - Record::CAA(caa) => { - if caa.critical { - format!("{} {} (critical)", Ascii(&caa.tag), Ascii(&caa.value)) - } - else { - format!("{} {} (non-critical)", Ascii(&caa.tag), Ascii(&caa.value)) - } - } - Record::CNAME(cname) => { - format!("{:?}", cname.domain.to_string()) - } - Record::EUI48(eui48) => { - format!("{:?}", eui48.formatted_address()) - } - Record::EUI64(eui64) => { - format!("{:?}", eui64.formatted_address()) - } - Record::HINFO(hinfo) => { - format!("{} {}", Ascii(&hinfo.cpu), Ascii(&hinfo.os)) - } - Record::LOC(loc) => { - format!("{} ({}, {}) ({}, {}, {})", - loc.size, - loc.horizontal_precision, - loc.vertical_precision, - loc.latitude .map_or_else(|| "Out of range".into(), |e| e.to_string()), - loc.longitude.map_or_else(|| "Out of range".into(), |e| e.to_string()), - loc.altitude, - ) - } - Record::MX(mx) => { - format!("{} {:?}", mx.preference, mx.exchange.to_string()) - } - Record::NAPTR(naptr) => { - format!("{} {} {} {} {} {:?}", - naptr.order, - naptr.preference, - Ascii(&naptr.flags), - Ascii(&naptr.service), - Ascii(&naptr.regex), - naptr.replacement.to_string(), - ) - } - Record::NS(ns) => { - format!("{:?}", ns.nameserver.to_string()) - } - Record::OPENPGPKEY(opgp) => { - format!("{:?}", opgp.base64_key()) - } - Record::PTR(ptr) => { - format!("{:?}", ptr.cname.to_string()) - } - Record::SSHFP(sshfp) => { - format!("{} {} {}", - sshfp.algorithm, - sshfp.fingerprint_type, - sshfp.hex_fingerprint(), - ) - } - Record::SOA(soa) => { - format!("{:?} {:?} {} {} {} {} {}", - soa.mname.to_string(), - soa.rname.to_string(), - soa.serial, - self.format_duration(soa.refresh_interval), - self.format_duration(soa.retry_interval), - self.format_duration(soa.expire_limit), - self.format_duration(soa.minimum_ttl), - ) - } - Record::SRV(srv) => { - format!("{} {} {:?}:{}", srv.priority, srv.weight, srv.target.to_string(), srv.port) - } - Record::TLSA(tlsa) => { - format!("{} {} {} {:?}", - tlsa.certificate_usage, - tlsa.selector, - tlsa.matching_type, - tlsa.hex_certificate_data(), - ) - } - Record::TXT(txt) => { - let messages = txt.messages.iter().map(|t| Ascii(t).to_string()).collect::>(); - messages.join(", ") - } - Record::URI(uri) => { - format!("{} {} {}", uri.priority, uri.weight, Ascii(&uri.target)) - } - Record::SMIMEA(smimea) => { - format!("{} {} {} {}", - smimea.certificate_usage, - smimea.selector, - smimea.matching_type, - smimea.hex_certificate_data(), - ) - } - Record::DS(ds) => { - format!("{} {} {} {}", ds.key_tag, ds.algorithm, ds.digest_type, ds.digest.iter().map(|b| format!("{:02x}", b)).collect::()) - } - Record::RRSIG(rrsig) => { - format!("{} {} {} {} {} {} {} {:?} {}", - rrsig.type_covered, - rrsig.algorithm, - rrsig.labels, - rrsig.original_ttl, - rrsig.signature_expiration, - rrsig.signature_inception, - rrsig.key_tag, - rrsig.signers_name.to_string(), - rrsig.signature.iter().map(|b| format!("{:02x}", b)).collect::() - ) - } - Record::NSEC(nsec) => { - format!("{:?} {:?}", nsec.next_domain_name.to_string(), nsec.type_bit_maps.iter().map(|b| format!("{:02x}", b)).collect::()) - } - Record::DNSKEY(dnskey) => { - format!("{} {} {} {}", dnskey.flags, dnskey.protocol, dnskey.algorithm, dnskey.public_key.iter().map(|b| format!("{:02x}", b)).collect::()) - } - Record::DHCID(dhcid) => { - format!("{} {} {}", dhcid.identifier_type_code, dhcid.digest_type_code, dhcid.digest.iter().map(|b| format!("{:02x}", b)).collect::()) - } - Record::NSEC3(nsec3) => { - format!("{} {} {} {} {:?} {:?}", - nsec3.hash_algorithm, - nsec3.flags, - nsec3.iterations, - nsec3.salt.iter().map(|b| format!("{:02x}", b)).collect::(), - nsec3.next_hashed_owner_name.iter().map(|b| format!("{:02x}", b)).collect::(), - nsec3.type_bit_maps.iter().map(|b| format!("{:02x}", b)).collect::() - ) - } - Record::NSEC3PARAM(nsec3param) => { - format!("{} {} {} {}", nsec3param.hash_algorithm, nsec3param.flags, nsec3param.iterations, nsec3param.salt.iter().map(|b| format!("{:02x}", b)).collect::()) - } - Record::IPSECKEY(ipseckey) => { - format!("{} {} {} {:?} {}", - ipseckey.precedence, - ipseckey.gateway_type, - ipseckey.algorithm, - ipseckey.gateway, - ipseckey.public_key.iter().map(|b| format!("{:02x}", b)).collect::() - ) - } - Record::Other { bytes, .. } => { - format!("{:?}", bytes) - } - } - } - - /// Formats a summary of an OPT pseudo-record. Pseudo-records have a different - /// structure than standard ones. - pub fn pseudo_record_payload_summary(self, opt: OPT) -> String { - format!("{} {} {} {} {:?}", - opt.udp_payload_size, - opt.higher_bits, - opt.edns0_version, - opt.flags, - opt.data) + pub fn record_payload_summary(self, record: &hickory_resolver::proto::rr::RData) -> String { + record.to_string() } /// Formats a duration depending on whether it should be displayed as @@ -395,490 +203,18 @@ fn format_duration_hms(seconds: u32) -> String { } } -/// Serialises multiple DNS queries as a JSON value. -fn json_queries(queries: Vec) -> JsonValue { - let queries = queries.iter().map(|q| { - object! { - "name": q.qname.to_string(), - "class": json_class(q.qclass), - "type": json_record_type_name(q.qtype), - } - }).collect::>(); - - queries.into() -} - -/// Serialises multiple received DNS answers as a JSON value. -fn json_answers(answers: Vec) -> JsonValue { - let answers = answers.into_iter().map(|a| { - match a { - Answer::Standard { qname, qclass, ttl, record } => { - object! { - "name": qname.to_string(), - "class": json_class(qclass), - "ttl": ttl, - "type": json_record_name(&record), - "data": json_record_data(record), - } - } - Answer::Pseudo { qname, opt } => { - object! { - "name": qname.to_string(), - "type": "OPT", - "data": { - "version": opt.edns0_version, - "data": opt.data, - }, - } - } - } - }).collect::>(); - - answers.into() -} - - -fn json_class(class: QClass) -> JsonValue { - match class { - QClass::IN => "IN".into(), - QClass::CH => "CH".into(), - QClass::HS => "HS".into(), - QClass::Other(n) => n.into(), - } -} - - -/// Serialises a DNS record type name. -fn json_record_type_name(record: RecordType) -> JsonValue { - match record { - RecordType::A => "A".into(), - RecordType::AAAA => "AAAA".into(), - RecordType::CAA => "CAA".into(), - RecordType::CNAME => "CNAME".into(), - RecordType::EUI48 => "EUI48".into(), - RecordType::EUI64 => "EUI64".into(), - RecordType::HINFO => "HINFO".into(), - RecordType::LOC => "LOC".into(), - RecordType::MX => "MX".into(), - RecordType::NAPTR => "NAPTR".into(), - RecordType::NS => "NS".into(), - RecordType::OPENPGPKEY => "OPENPGPKEY".into(), - RecordType::PTR => "PTR".into(), - RecordType::SOA => "SOA".into(), - RecordType::SRV => "SRV".into(), - RecordType::SSHFP => "SSHFP".into(), - RecordType::TXT => "TXT".into(), - RecordType::TLSA => "TLSA".into(), - RecordType::URI => "URI".into(), - RecordType::SMIMEA => "SMIMEA".into(), - RecordType::DS => "DS".into(), - RecordType::RRSIG => "RRSIG".into(), - RecordType::NSEC => "NSEC".into(), - RecordType::DNSKEY => "DNSKEY".into(), - RecordType::DHCID => "DHCID".into(), - RecordType::NSEC3 => "NSEC3".into(), - RecordType::NSEC3PARAM => "NSEC3PARAM".into(), - RecordType::IPSECKEY => "IPSECKEY".into(), - RecordType::Other(unknown) => { - match unknown { - UnknownQtype::HeardOf(name, _) => (*name).into(), - UnknownQtype::UnheardOf(num) => (num).into(), - } - } - } -} - -/// Serialises a DNS record type name. -fn json_record_name(record: &Record) -> JsonValue { - match record { - Record::A(_) => "A".into(), - Record::AAAA(_) => "AAAA".into(), - Record::CAA(_) => "CAA".into(), - Record::CNAME(_) => "CNAME".into(), - Record::EUI48(_) => "EUI48".into(), - Record::EUI64(_) => "EUI64".into(), - Record::HINFO(_) => "HINFO".into(), - Record::LOC(_) => "LOC".into(), - Record::MX(_) => "MX".into(), - Record::NAPTR(_) => "NAPTR".into(), - Record::NS(_) => "NS".into(), - Record::OPENPGPKEY(_) => "OPENPGPKEY".into(), - Record::PTR(_) => "PTR".into(), - Record::SOA(_) => "SOA".into(), - Record::SRV(_) => "SRV".into(), - Record::SSHFP(_) => "SSHFP".into(), - Record::TLSA(_) => "TLSA".into(), - Record::TXT(_) => "TXT".into(), - Record::URI(_) => "URI".into(), - Record::SMIMEA(_) => "SMIMEA".into(), - Record::DS(_) => "DS".into(), - Record::RRSIG(_) => "RRSIG".into(), - Record::NSEC(_) => "NSEC".into(), - Record::DNSKEY(_) => "DNSKEY".into(), - Record::DHCID(_) => "DHCID".into(), - Record::NSEC3(_) => "NSEC3".into(), - Record::NSEC3PARAM(_) => "NSEC3PARAM".into(), - Record::IPSECKEY(_) => "IPSECKEY".into(), - Record::Other { type_number, .. } => { - match type_number { - UnknownQtype::HeardOf(name, _) => (*name).into(), - UnknownQtype::UnheardOf(num) => (*num).into(), - } - } - } -} - - -/// Serialises a received DNS record as a JSON value. - -/// Even though DNS doesn’t specify a character encoding, strings are still -/// converted from UTF-8, because JSON specifies UTF-8. -fn json_record_data(record: Record) -> JsonValue { - match record { - Record::A(a) => { - object! { - "address": a.address.to_string(), - } - } - Record::AAAA(aaaa) => { - object! { - "address": aaaa.address.to_string(), - } - } - Record::CAA(caa) => { - object! { - "critical": caa.critical, - "tag": String::from_utf8_lossy(&caa.tag).to_string(), - "value": String::from_utf8_lossy(&caa.value).to_string(), - } - } - Record::CNAME(cname) => { - object! { - "domain": cname.domain.to_string(), - } - } - Record::EUI48(eui48) => { - object! { - "identifier": eui48.formatted_address(), - } - } - Record::EUI64(eui64) => { - object! { - "identifier": eui64.formatted_address(), - } - } - Record::HINFO(hinfo) => { - object! { - "cpu": String::from_utf8_lossy(&hinfo.cpu).to_string(), - "os": String::from_utf8_lossy(&hinfo.os).to_string(), - } - } - Record::LOC(loc) => { - object! { - "size": loc.size.to_string(), - "precision": { - "horizontal": loc.horizontal_precision, - "vertical": loc.vertical_precision, - }, - "point": { - "latitude": loc.latitude.map(|e| e.to_string()), - "longitude": loc.longitude.map(|e| e.to_string()), - "altitude": loc.altitude.to_string(), - }, - } - } - Record::MX(mx) => { - object! { - "preference": mx.preference, - "exchange": mx.exchange.to_string(), - } - } - Record::NAPTR(naptr) => { - object! { - "order": naptr.order, - "flags": String::from_utf8_lossy(&naptr.flags).to_string(), - "service": String::from_utf8_lossy(&naptr.service).to_string(), - "regex": String::from_utf8_lossy(&naptr.regex).to_string(), - "replacement": naptr.replacement.to_string(), - } - } - Record::NS(ns) => { - object! { - "nameserver": ns.nameserver.to_string(), - } - } - Record::OPENPGPKEY(opgp) => { - object! { - "key": opgp.base64_key(), - } - } - Record::PTR(ptr) => { - object! { - "cname": ptr.cname.to_string(), - } - } - Record::SSHFP(sshfp) => { - object! { - "algorithm": sshfp.algorithm, - "fingerprint_type": sshfp.fingerprint_type, - "fingerprint": sshfp.hex_fingerprint(), - } - } - Record::SOA(soa) => { - object! { - "mname": soa.mname.to_string(), - } - } - Record::SRV(srv) => { - object! { - "priority": srv.priority, - "weight": srv.weight, - "port": srv.port, - "target": srv.target.to_string(), - } - } - Record::TLSA(tlsa) => { - object! { - "certificate_usage": tlsa.certificate_usage, - "selector": tlsa.selector, - "matching_type": tlsa.matching_type, - "certificate_data": tlsa.hex_certificate_data(), - } - } - Record::TXT(txt) => { - let ms = txt.messages.into_iter() - .map(|txt| String::from_utf8_lossy(&txt).to_string()) - .collect::>(); - object! { - "messages": ms, - } - } - Record::URI(uri) => { - object! { - "priority": uri.priority, - "weight": uri.weight, - "target": String::from_utf8_lossy(&uri.target).to_string(), - } - } - Record::SMIMEA(smimea) => { - object! { - "certificate_usage": smimea.certificate_usage, - "selector": smimea.selector, - "matching_type": smimea.matching_type, - "certificate_data": smimea.certificate_data, - } - } - Record::DS(ds) => { - object! { - "key_tag": ds.key_tag, - "algorithm": ds.algorithm, - "digest_type": ds.digest_type, - "digest": ds.digest, - } - } - Record::RRSIG(rrsig) => { - object! { - "type_covered": rrsig.type_covered, - "algorithm": rrsig.algorithm, - "labels": rrsig.labels, - "original_ttl": rrsig.original_ttl, - "signature_expiration": rrsig.signature_expiration, - "signature_inception": rrsig.signature_inception, - "key_tag": rrsig.key_tag, - "signers_name": rrsig.signers_name.to_string(), - "signature": rrsig.signature, - } - } - Record::NSEC(nsec) => { - object! { - "next_domain_name": nsec.next_domain_name.to_string(), - "type_bit_maps": nsec.type_bit_maps, - } - } - Record::DNSKEY(dnskey) => { - object! { - "flags": dnskey.flags, - "protocol": dnskey.protocol, - "algorithm": dnskey.algorithm, - "public_key": dnskey.public_key, - } - } - Record::DHCID(dhcid) => { - object! { - "identifier_type_code": dhcid.identifier_type_code, - "digest_type_code": dhcid.digest_type_code, - "digest": dhcid.digest, - } - } - Record::NSEC3(nsec3) => { - object! { - "hash_algorithm": nsec3.hash_algorithm, - "flags": nsec3.flags, - "iterations": nsec3.iterations, - "salt": nsec3.salt, - "next_hashed_owner_name": nsec3.next_hashed_owner_name, - "type_bit_maps": nsec3.type_bit_maps, - } - } - Record::NSEC3PARAM(nsec3param) => { - object! { - "hash_algorithm": nsec3param.hash_algorithm, - "flags": nsec3param.flags, - "iterations": nsec3param.iterations, - "salt": nsec3param.salt, - } - } - Record::IPSECKEY(ipseckey) => { - object! { - "precedence": ipseckey.precedence, - "gateway_type": ipseckey.gateway_type, - "algorithm": ipseckey.algorithm, - "gateway": ipseckey.gateway, - "public_key": ipseckey.public_key, - } - } - Record::Other { bytes, .. } => { - object! { - "bytes": bytes, - } - } - } -} - - -/// A wrapper around displaying characters that escapes quotes and -/// backslashes, and writes control and upper-bit bytes as their number rather -/// than their character. This is needed because even though such characters -/// are not allowed in domain names, packets can contain anything, and we need -/// a way to display the response, whatever it is. -struct Ascii<'a>(&'a [u8]); - -impl fmt::Display for Ascii<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\"")?; - - for byte in self.0.iter().copied() { - if byte < 32 || byte >= 128 { - write!(f, "\\{}", byte)?; - } - else if byte == b'"' { - write!(f, "\\\"")?; - } - else if byte == b'\\' { - write!(f, "\\\\")?; - } - else { - write!(f, "{}", byte as char)?; - } - } - - write!(f, "\"") - } -} - - -/// Prints a message describing the “error code” field of a DNS packet. This -/// happens when the packet was received correctly, but the server indicated -/// an error. -pub fn print_error_code(rcode: ErrorCode) { - match rcode { - ErrorCode::FormatError => println!("Status: Format Error"), - ErrorCode::ServerFailure => println!("Status: Server Failure"), - ErrorCode::NXDomain => println!("Status: NXDomain"), - ErrorCode::NotImplemented => println!("Status: Not Implemented"), - ErrorCode::QueryRefused => println!("Status: Query Refused"), - ErrorCode::BadVersion => println!("Status: Bad Version"), - ErrorCode::Private(num) => println!("Status: Private Reason ({})", num), - ErrorCode::Other(num) => println!("Status: Other Failure ({})", num), - } -} - -/// Returns the “phase” of operation where an error occurred. This gets shown -/// to the user so they can debug what went wrong. -fn erroneous_phase(error: &TransportError) -> &'static str { - match error { - TransportError::WireError(_) => "protocol", - TransportError::TruncatedResponse | - TransportError::NetworkError(_) => "network", - TransportError::RustlsInvalidDnsNameError(_) | - TransportError::RustlsError(_) | - TransportError::RustlsInvalidDnsNameError2(_) => "tls", // TODO: Actually wrong, could be https - #[cfg(feature = "with_https")] - TransportError::HttpError(_) | - TransportError::WrongHttpStatus(_,_) => "http", - } -} - -/// Formats an error into its human-readable message. -fn error_message(error: TransportError) -> String { - match error { - TransportError::WireError(e) => wire_error_message(e), - TransportError::TruncatedResponse => "Truncated response".into(), - TransportError::NetworkError(e) => e.to_string(), - TransportError::RustlsInvalidDnsNameError(e) => e.to_string(), - TransportError::RustlsError(e) => e.to_string(), - TransportError::RustlsInvalidDnsNameError2(e) => e.to_string(), - #[cfg(feature = "with_https")] - TransportError::HttpError(e) => e.to_string(), - #[cfg(feature = "with_https")] - TransportError::WrongHttpStatus(t,r) => format!("Nameserver returned HTTP {} ({})", t, r.unwrap_or_else(|| "No reason".into())) - } -} - -/// Formats a wire error into its human-readable message, describing what was -/// wrong with the packet we received. -fn wire_error_message(error: WireError) -> String { - match error { - WireError::IO => { - "Malformed packet: insufficient data".into() - } - WireError::WrongRecordLength { stated_length, mandated_length: MandatedLength::Exactly(len) } => { - format!("Malformed packet: record length should be {}, got {}", len, stated_length ) - } - WireError::WrongRecordLength { stated_length, mandated_length: MandatedLength::AtLeast(len) } => { - format!("Malformed packet: record length should be at least {}, got {}", len, stated_length ) - } - WireError::WrongLabelLength { stated_length, length_after_labels } => { - format!("Malformed packet: length {} was specified, but read {} bytes", stated_length, length_after_labels) - } - WireError::TooMuchRecursion(indices) => { - format!("Malformed packet: too much recursion: {:?}", indices) - } - WireError::OutOfBounds(index) => { - format!("Malformed packet: out of bounds ({})", index) - } - WireError::WrongVersion { stated_version, maximum_supported_version } => { - format!("Malformed packet: record specifies version {}, expected up to {}", stated_version, maximum_supported_version) - } - } -} - - #[cfg(test)] mod test { use super::*; #[test] - fn escape_quotes() { - assert_eq!(Ascii(b"Mallard \"The Duck\" Fillmore").to_string(), - "\"Mallard \\\"The Duck\\\" Fillmore\""); - } - - #[test] - fn escape_backslashes() { - assert_eq!(Ascii(b"\\").to_string(), - "\"\\\\\""); - } - - #[test] - fn escape_lows() { - assert_eq!(Ascii(b"\n\r\t").to_string(), - "\"\\10\\13\\9\""); - } - - #[test] - fn escape_highs() { - assert_eq!(Ascii("pâté".as_bytes()).to_string(), - "\"p\\195\\162t\\195\\169\""); + fn test_format_duration() { + assert_eq!(format_duration_hms(0), "0s"); + assert_eq!(format_duration_hms(59), "59s"); + assert_eq!(format_duration_hms(60), "1m00s"); + assert_eq!(format_duration_hms(3599), "59m59s"); + assert_eq!(format_duration_hms(3600), "1h00m00s"); + assert_eq!(format_duration_hms(86399), "23h59m59s"); + assert_eq!(format_duration_hms(86400), "1d0h00m00s"); } } diff --git a/src/requests.rs b/src/requests.rs deleted file mode 100644 index fb9895f..0000000 --- a/src/requests.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Request generation based on the user’s input arguments. - -use crate::connect::TransportType; -use crate::resolve::{ResolverType, ResolverLookupError}; -use crate::txid::TxidGenerator; - - -/// All the information necessary to generate requests for one or more -/// queries, nameservers, or transport types. -#[derive(PartialEq, Debug)] -pub struct RequestGenerator { - - /// The input parameter matrix. - pub inputs: Inputs, - - /// How to generate transaction IDs. - pub txid_generator: TxidGenerator, - - /// Whether to OPT in to DNS extensions. - pub edns: UseEDNS, - - /// Other weird protocol options. - pub protocol_tweaks: ProtocolTweaks, -} - -/// Which things the user has specified they want queried. -#[derive(PartialEq, Debug, Default)] -pub struct Inputs { - - /// The list of domain names to query. - pub domains: Vec, - - /// The list of DNS record types to query for. - pub record_types: Vec, - - /// The list of DNS classes to query for. - pub classes: Vec, - - /// The list of resolvers to send queries to. - pub resolver_types: Vec, - - /// The list of transport types to send queries over. - pub transport_types: Vec, -} - -/// Weird protocol options that are allowed by the spec but are not common. -#[derive(PartialEq, Debug, Default, Copy, Clone)] -pub struct ProtocolTweaks { - - /// Set the `AA` (Authoritative Answer) flag in the header of each request. - pub set_authoritative_flag: bool, - - /// Set the `AD` (Authentic Data) flag in the header of each request. - pub set_authentic_flag: bool, - - /// Set the `CD` (Checking Disabled) flag in the header of each request. - pub set_checking_disabled_flag: bool, - - /// Set the buffer size field in the OPT record of each request. - pub udp_payload_size: Option, -} - -/// Whether to send or display OPT packets. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum UseEDNS { - - /// Do not send an OPT query in requests, and do not display them. - Disable, - - /// Send an OPT query in requests, but hide the result. This is the - /// default, because the information is usually not useful to the user. - SendAndHide, - - /// Send an OPT query in requests, _and_ display any OPT records in the - /// response we receive. - SendAndShow, -} - - -/// The entry type for `RequestGenerator`: a transport to send a request, and -/// a list of one or more DNS queries to send over it, as determined by the -/// search path in the resolver. -pub type RequestSet = (Box, Vec); - -impl RequestGenerator { - - /// Iterate through the inputs matrix, returning pairs of DNS request list - /// and the details of the transport to send them down. - pub fn generate(self) -> Result, ResolverLookupError> { - let mut requests = Vec::new(); - - let resolvers = self.inputs.resolver_types.into_iter() - .map(ResolverType::obtain) - .collect::, _>>()?; - - for domain in &self.inputs.domains { - for qtype in self.inputs.record_types.iter().copied() { - for qclass in self.inputs.classes.iter().copied() { - for resolver in &resolvers { - for transport_type in &self.inputs.transport_types { - - let mut flags = dns::Flags::query(); - self.protocol_tweaks.set_request_flags(&mut flags); - - let mut additional = None; - if self.edns.should_send() { - let mut opt = dns::Request::additional_record(); - self.protocol_tweaks.set_request_opt_fields(&mut opt); - additional = Some(opt); - } - - let nameserver = resolver.nameserver(); - let transport = transport_type.make_transport(nameserver); - - let mut request_list = Vec::new(); - for qname in resolver.name_list(domain) { - let transaction_id = self.txid_generator.generate(); - let query = dns::Query { qname, qtype, qclass }; - let request = dns::Request { transaction_id, flags, query, additional: additional.clone() }; - request_list.push(request); - } - requests.push((transport, request_list)); - } - } - } - } - } - - Ok(requests) - } -} - -impl UseEDNS { - - /// Whether the user wants to send OPT records. - pub fn should_send(self) -> bool { - self != Self::Disable - } - - /// Whether the user wants to display sent OPT records. - pub fn should_show(self) -> bool { - self == Self::SendAndShow - } -} - -impl ProtocolTweaks { - - /// Sets fields in the DNS flags based on the user’s requested tweaks. - pub fn set_request_flags(self, flags: &mut dns::Flags) { - if self.set_authoritative_flag { - flags.authoritative = true; - } - - if self.set_authentic_flag { - flags.authentic_data = true; - } - - if self.set_checking_disabled_flag { - flags.checking_disabled = true; - } - } - - /// Set the payload size field in the outgoing OPT record, if the user has - /// requested to do so. - pub fn set_request_opt_fields(self, opt: &mut dns::record::OPT) { - if let Some(bufsize) = self.udp_payload_size { - opt.udp_payload_size = bufsize; - } - } -} diff --git a/src/resolve.rs b/src/resolve.rs deleted file mode 100644 index c3ca2f3..0000000 --- a/src/resolve.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! Specifying the address of the DNS server to send requests to. - -use std::fmt; -use std::io; - -use log::*; - -use dns::Labels; - - -/// A **resolver type** is the source of a `Resolver`. -#[derive(PartialEq, Debug)] -pub enum ResolverType { - - /// Obtain a resolver by consulting the system in order to find a - /// nameserver and a search list. - SystemDefault, - - /// Obtain a resolver by using the given user-submitted string. - Specific(String), -} - -impl ResolverType { - - /// Obtains a resolver by the means specified in this type. Returns an - /// error if there was a problem looking up system information, or if - /// there is no suitable nameserver available. - pub fn obtain(self) -> Result { - match self { - Self::SystemDefault => { - system_nameservers() - } - Self::Specific(nameserver) => { - let search_list = Vec::new(); - Ok(Resolver { nameserver, search_list }) - } - } - } -} - - -/// A **resolver** knows the address of the server we should -/// send DNS requests to, and the search list for name lookup. -#[derive(Debug)] -pub struct Resolver { - - /// The address of the nameserver. - pub nameserver: String, - - /// The search list for name lookup. - pub search_list: Vec, -} - -impl Resolver { - - /// Returns a nameserver that queries should be sent to. - pub fn nameserver(&self) -> String { - self.nameserver.clone() - } - - /// Returns a sequence of names to be queried, taking into account - /// the search list. - pub fn name_list(&self, name: &Labels) -> Vec { - let mut list = Vec::new(); - - if name.len() > 1 { - list.push(name.clone()); - return list; - } - - for search in &self.search_list { - match Labels::encode(search) { - Ok(suffix) => list.push(name.extend(&suffix)), - Err(_) => warn!("Invalid search list: {}", search), - } - } - - list.push(name.clone()); - list - } -} - - -/// Looks up the system default nameserver on Unix, by querying -/// `/etc/resolv.conf` and using the first line that specifies one. -/// Returns an error if there’s a problem reading the file, or `None` if no -/// nameserver is specified in the file. -#[cfg(unix)] -fn system_nameservers() -> Result { - use std::fs::File; - use std::io::{BufRead, BufReader}; - - if cfg!(test) { - panic!("system_nameservers() called from test code"); - } - - let f = File::open("/etc/resolv.conf")?; - let reader = BufReader::new(f); - - let mut nameservers = Vec::new(); - let mut search_list = Vec::new(); - for line in reader.lines() { - let line = line?; - - if let Some(nameserver_str) = line.strip_prefix("nameserver ") { - let ip: Result = nameserver_str.parse(); - // TODO: This will need to be changed for IPv6 support. - - match ip { - Ok(_ip) => nameservers.push(nameserver_str.into()), - Err(e) => warn!("Failed to parse nameserver line {:?}: {}", line, e), - } - } - - if let Some(search_str) = line.strip_prefix("search ") { - search_list.clear(); - search_list.extend(search_str.split_ascii_whitespace().map(|s| s.into())); - } - } - - if let Some(nameserver) = nameservers.into_iter().next() { - Ok(Resolver { nameserver, search_list }) - } - else { - Err(ResolverLookupError::NoNameserver) - } -} - - -/// Looks up the system default nameserver on Windows, by iterating through -/// the list of network adapters and returning the first nameserver it finds. -#[cfg(windows)] -#[allow(unused)] // todo: Remove this when the time is right -fn system_nameservers() -> Result { - use std::net::{IpAddr, UdpSocket}; - - if cfg!(test) { - panic!("system_nameservers() called from test code"); - } - - // According to the specification, prefer ipv6 by default. - // TODO: add control flag to select an ip family. - #[derive(Debug, PartialEq)] - enum ForceIPFamily { - V4, - V6, - None, - } - - // get the IP of the Network adapter that is used to access the Internet - // https://stackoverflow.com/questions/24661022/getting-ip-adress-associated-to-real-hardware-ethernet-controller-in-windows-c - fn get_ipv4() -> io::Result { - let s = UdpSocket::bind("0.0.0.0:0")?; - s.connect("8.8.8.8:53")?; - let addr = s.local_addr()?; - Ok(addr.ip()) - } - - fn get_ipv6() -> io::Result { - let s = UdpSocket::bind("[::1]:0")?; - s.connect("[2001:4860:4860::8888]:53")?; - let addr = s.local_addr()?; - Ok(addr.ip()) - } - - let force_ip_family: ForceIPFamily = ForceIPFamily::None; - let ip = match force_ip_family { - ForceIPFamily::V4 => get_ipv4().ok(), - ForceIPFamily::V6 => get_ipv6().ok(), - ForceIPFamily::None => get_ipv6().or(get_ipv4()).ok(), - }; - - let search_list = Vec::new(); // todo: implement this - - let adapters = ipconfig::get_adapters()?; - let active_adapters = adapters.iter().filter(|a| { - a.oper_status() == ipconfig::OperStatus::IfOperStatusUp && !a.gateways().is_empty() - }); - - if let Some(dns_server) = active_adapters - .clone() - .find(|a| ip.map(|ip| a.ip_addresses().contains(&ip)).unwrap_or(false)) - .map(|a| a.dns_servers().first()) - .flatten() - { - debug!("Found first nameserver {:?}", dns_server); - let nameserver = dns_server.to_string(); - Ok(Resolver { nameserver, search_list }) - } - - // Fallback - else if let Some(dns_server) = active_adapters - .flat_map(|a| a.dns_servers()) - .find(|d| (d.is_ipv4() && force_ip_family != ForceIPFamily::V6) || d.is_ipv6()) - { - debug!("Found first fallback nameserver {:?}", dns_server); - let nameserver = dns_server.to_string(); - Ok(Resolver { nameserver, search_list }) - } - - else { - Err(ResolverLookupError::NoNameserver) - } -} - - -/// The fall-back system default nameserver determinator that is not very -/// determined as it returns nothing without actually checking anything. -#[cfg(all(not(unix), not(windows)))] -fn system_nameservers() -> Result { - warn!("Unable to fetch default nameservers on this platform."); - Err(ResolverLookupError::UnsupportedPlatform) -} - - -/// Something that can go wrong while obtaining a `Resolver`. -pub enum ResolverLookupError { - - /// The system information was successfully read, but there was no adapter - /// suitable to use. - NoNameserver, - - /// There was an error accessing the network configuration. - IO(io::Error), - - /// There was an error accessing the network configuration (extra errors - /// that can only happen on Windows). - #[cfg(windows)] - Windows(ipconfig::error::Error), - - /// dog is running on a platform where it doesn’t know how to get the - /// network configuration, so the user must supply one instead. - #[cfg(all(not(unix), not(windows)))] - UnsupportedPlatform, -} - -impl From for ResolverLookupError { - fn from(error: io::Error) -> ResolverLookupError { - Self::IO(error) - } -} - -#[cfg(windows)] -impl From for ResolverLookupError { - fn from(error: ipconfig::error::Error) -> ResolverLookupError { - Self::Windows(error) - } -} - -impl fmt::Display for ResolverLookupError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NoNameserver => { - write!(f, "No nameserver found") - } - Self::IO(ioe) => { - write!(f, "Error reading network configuration: {}", ioe) - } - #[cfg(windows)] - Self::Windows(ipe) => { - write!(f, "Error reading network configuration: {}", ipe) - } - #[cfg(all(not(unix), not(windows)))] - Self::UnsupportedPlatform => { - write!(f, "dog cannot automatically detect nameservers on this platform; you will have to provide one explicitly") - } - } - } -} diff --git a/src/table.rs b/src/table.rs index 913f472..9ab0fbe 100644 --- a/src/table.rs +++ b/src/table.rs @@ -4,8 +4,7 @@ use std::time::Duration; use ansi_term::ANSIString; -use dns::Answer; -use dns::record::Record; +use hickory_resolver::proto::rr::{Record, RecordType}; use crate::colours::Colours; use crate::output::TextFormat; @@ -22,7 +21,7 @@ pub struct Table { /// A row of the table. This contains all the fields #[derive(Debug)] -pub struct Row { +struct Row { qtype: ANSIString<'static>, qname: String, ttl: Option, @@ -36,12 +35,6 @@ pub enum Section { /// This record was found in the **Answer** section. Answer, - - /// This record was found in the **Authority** section. - Authority, - - /// This record was found in the **Additional** section. - Additional, } @@ -54,58 +47,48 @@ impl Table { /// Adds a row to the table, containing the data in the given answer in /// the right section. - pub fn add_row(&mut self, answer: Answer, section: Section) { - match answer { - Answer::Standard { record, qname, ttl, .. } => { - let qtype = self.coloured_record_type(&record); - let qname = qname.to_string(); - let summary = self.text_format.record_payload_summary(record); - let ttl = Some(self.text_format.format_duration(ttl)); - self.rows.push(Row { qtype, qname, ttl, summary, section }); - } - Answer::Pseudo { qname, opt } => { - let qtype = self.colours.opt.paint("OPT"); - let qname = qname.to_string(); - let summary = self.text_format.pseudo_record_payload_summary(opt); - self.rows.push(Row { qtype, qname, ttl: None, summary, section }); - } + pub fn add_row(&mut self, record: Record, section: Section) { + if let Some(data) = record.data() { + let qtype = self.coloured_record_type(&record); + let qname = record.name().to_string(); + let summary = self.text_format.record_payload_summary(data); + let ttl = Some(self.text_format.format_duration(record.ttl())); + self.rows.push(Row { qtype, qname, ttl, summary, section }); } } - /// Prints the formatted table to stdout. - pub fn print(self, duration: Option) { + /// Renders the formatted table to a string. + fn render(&self) -> String { + let mut output = String::new(); + if ! self.rows.is_empty() { let qtype_len = self.max_qtype_len(); let qname_len = self.max_qname_len(); let ttl_len = self.max_ttl_len(); for r in &self.rows { - for _ in 0 .. qtype_len - r.qtype.len() { - print!(" "); - } - - print!("{} {} ", r.qtype, self.colours.qname.paint(&r.qname)); - - for _ in 0 .. qname_len - r.qname.len() { - print!(" "); - } + output.push_str(&" ".repeat(qtype_len - r.qtype.len())); + output.push_str(&format!("{} {} ", r.qtype, self.colours.qname.paint(&r.qname))); + output.push_str(&" ".repeat(qname_len - r.qname.len())); if let Some(ttl) = &r.ttl { - for _ in 0 .. ttl_len - ttl.len() { - print!(" "); - } - - print!("{}", ttl); + output.push_str(&" ".repeat(ttl_len - ttl.len())); + output.push_str(ttl); } else { - for _ in 0 .. ttl_len { - print!(" "); - } + output.push_str(&" ".repeat(ttl_len)); } - println!(" {} {}", self.format_section(r.section), r.summary); + output.push_str(&format!(" {} {} +", self.format_section(r.section), r.summary)); } } + output + } + + /// Prints the formatted table to stdout. + pub fn print(self, duration: Option) { + print!("{}", self.render()); if let Some(dur) = duration { println!("Ran in {}ms", dur.as_millis()); @@ -113,57 +96,36 @@ impl Table { } fn coloured_record_type(&self, record: &Record) -> ANSIString<'static> { - match *record { - Record::A(_) => self.colours.a.paint("A"), - Record::AAAA(_) => self.colours.aaaa.paint("AAAA"), - Record::CAA(_) => self.colours.caa.paint("CAA"), - Record::CNAME(_) => self.colours.cname.paint("CNAME"), - Record::EUI48(_) => self.colours.eui48.paint("EUI48"), - Record::EUI64(_) => self.colours.eui64.paint("EUI64"), - Record::HINFO(_) => self.colours.hinfo.paint("HINFO"), - Record::LOC(_) => self.colours.loc.paint("LOC"), - Record::MX(_) => self.colours.mx.paint("MX"), - Record::NAPTR(_) => self.colours.ns.paint("NAPTR"), - Record::NS(_) => self.colours.ns.paint("NS"), - Record::OPENPGPKEY(_) => self.colours.openpgpkey.paint("OPENPGPKEY"), - Record::PTR(_) => self.colours.ptr.paint("PTR"), - Record::SSHFP(_) => self.colours.sshfp.paint("SSHFP"), - Record::SOA(_) => self.colours.soa.paint("SOA"), - Record::SRV(_) => self.colours.srv.paint("SRV"), - Record::TLSA(_) => self.colours.tlsa.paint("TLSA"), - Record::TXT(_) => self.colours.txt.paint("TXT"), - Record::URI(_) => self.colours.uri.paint("URI"), - Record::SMIMEA(_) => self.colours.tlsa.paint("SMIMEA"), - Record::DS(_) => self.colours.unknown.paint("DS"), - Record::RRSIG(_) => self.colours.unknown.paint("RRSIG"), - Record::NSEC(_) => self.colours.unknown.paint("NSEC"), - Record::DNSKEY(_) => self.colours.unknown.paint("DNSKEY"), - Record::DHCID(_) => self.colours.unknown.paint("DHCID"), - Record::NSEC3(_) => self.colours.unknown.paint("NSEC3"), - Record::NSEC3PARAM(_) => self.colours.unknown.paint("NSEC3PARAM"), - Record::IPSECKEY(_) => self.colours.unknown.paint("IPSECKEY"), - - Record::Other { ref type_number, .. } => self.colours.unknown.paint(type_number.to_string()), + match record.record_type() { + RecordType::A => self.colours.a.paint("A"), + RecordType::AAAA => self.colours.aaaa.paint("AAAA"), + RecordType::CAA => self.colours.caa.paint("CAA"), + RecordType::CNAME => self.colours.cname.paint("CNAME"), + RecordType::MX => self.colours.mx.paint("MX"), + RecordType::NS => self.colours.ns.paint("NS"), + RecordType::PTR => self.colours.ptr.paint("PTR"), + RecordType::SOA => self.colours.soa.paint("SOA"), + RecordType::SRV => self.colours.srv.paint("SRV"), + RecordType::TXT => self.colours.txt.paint("TXT"), + _ => self.colours.default.paint(record.record_type().to_string()), } } fn max_qtype_len(&self) -> usize { - self.rows.iter().map(|r| r.qtype.len()).max().unwrap() + self.rows.iter().map(|r| r.qtype.len()).max().unwrap_or(0) } fn max_qname_len(&self) -> usize { - self.rows.iter().map(|r| r.qname.len()).max().unwrap() + self.rows.iter().map(|r| r.qname.len()).max().unwrap_or(0) } fn max_ttl_len(&self) -> usize { - self.rows.iter().map(|r| r.ttl.as_ref().map_or(0, String::len)).max().unwrap() + self.rows.iter().map(|r| r.ttl.as_ref().map_or(0, String::len)).max().unwrap_or(0) } fn format_section(&self, section: Section) -> ANSIString<'static> { match section { Section::Answer => self.colours.answer.paint(" "), - Section::Authority => self.colours.authority.paint("A"), - Section::Additional => self.colours.additional.paint("+"), } } } diff --git a/src/txid.rs b/src/txid.rs deleted file mode 100644 index 5567ce1..0000000 --- a/src/txid.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Transaction ID generation. - - -/// A **transaction ID generator** is used to create unique ID numbers to -/// identify each packet, as part of the DNS protocol. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum TxidGenerator { - - /// Generate random transaction IDs each time. - Random, - - /// Generate transaction IDs in a sequence, starting from the given value, - /// wrapping around. - Sequence(u16), -} - -impl TxidGenerator { - pub fn generate(self) -> u16 { - match self { - Self::Random => rand::random(), - Self::Sequence(start) => start, // todo - } - } -} From 39c74afecdde8553c27dea7e6fda116ee9beeb26 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:32:48 -0700 Subject: [PATCH 18/46] docs: Add comprehensive documentation to all modules This commit adds documentation to all public modules, functions, and structs in the `src` directory. This improves the maintainability and readability of the codebase. - **`src/colours.rs`:** Added doc comments to the `Colours` struct and its fields. - **`src/hints.rs`:** No changes were needed as the documentation was already sufficient. - **`src/logger.rs`:** Added doc comments to the `configure` function and the `Logger` struct. - **`src/main.rs`:** Added doc comments to the `main` and `run` functions, as well as the helper functions for displaying usage and version information. - **`src/options.rs`:** Added doc comments to the `Requests` struct and its fields, as well as the private functions in the `Inputs` implementation. - **`src/table.rs`:** Added doc comments to the private functions. --- src/colours.rs | 13 +++++++++++++ src/logger.rs | 5 +++++ src/main.rs | 12 ++++++++++++ src/options.rs | 16 ++++++++++++++++ src/table.rs | 8 ++++++-- 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/colours.rs b/src/colours.rs index 913aad1..6b7918c 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -7,20 +7,33 @@ use ansi_term::Color::*; /// The **colours** are used to paint the input. #[derive(Debug, Default)] pub struct Colours { + /// The style for the question name. pub qname: Style, + /// The style for the answer section. pub answer: Style, + /// The style for A records. pub a: Style, + /// The style for AAAA records. pub aaaa: Style, + /// The style for CAA records. pub caa: Style, + /// The style for CNAME records. pub cname: Style, + /// The style for MX records. pub mx: Style, + /// The style for NS records. pub ns: Style, + /// The style for PTR records. pub ptr: Style, + /// The style for SOA records. pub soa: Style, + /// The style for SRV records. pub srv: Style, + /// The style for TXT records. pub txt: Style, + /// The style for unknown record types. pub default: Style, } diff --git a/src/logger.rs b/src/logger.rs index 4f89eb1..abdbe21 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -7,6 +7,10 @@ use ansi_term::{Colour, ANSIString}; /// Sets the internal logger, changing the log level based on the value of an /// environment variable. +/// +/// # Arguments +/// +/// * `ev` - The value of the `DOG_DEBUG` environment variable. pub fn configure>(ev: Option) { let ev = match ev { Some(v) => v, @@ -32,6 +36,7 @@ pub fn configure>(ev: Option) { } +/// The global logger instance. #[derive(Debug)] struct Logger; diff --git a/src/main.rs b/src/main.rs index 6b052d6..454c93b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,24 +94,36 @@ async fn main() { } } +/// Returns the pretty-printed usage string. fn usage_pretty() -> &'static str { include_str!(concat!(env!("OUT_DIR"), "/usage.pretty.txt")) } +/// Returns the bland usage string. fn usage_bland() -> &'static str { include_str!(concat!(env!("OUT_DIR"), "/usage.bland.txt")) } +/// Returns the pretty-printed version string. fn version_pretty() -> &'static str { include_str!(concat!(env!("OUT_DIR"), "/version.pretty.txt")) } +/// Returns the bland version string. fn version_bland() -> &'static str { include_str!(concat!(env!("OUT_DIR"), "/version.bland.txt")) } /// Runs dog with some options, returning the status to exit with. +/// +/// # Arguments +/// +/// * `options` - The command-line options. +/// +/// # Returns +/// +/// * The process exit code. async fn run(Options { requests, format, measure_time }: Options) -> i32 { use std::time::Instant; diff --git a/src/options.rs b/src/options.rs index c0fc411..0c92755 100644 --- a/src/options.rs +++ b/src/options.rs @@ -25,8 +25,10 @@ pub struct Options { pub format: OutputFormat, } +/// The set of requests to make. #[derive(PartialEq, Debug, Default)] pub struct Requests { + /// The inputs to generate requests from. pub inputs: Inputs, } @@ -105,6 +107,7 @@ impl Options { } } + /// Deduce the options from the command-line matches. fn deduce(matches: getopts::Matches) -> Result { let measure_time = matches.opt_present("time"); let format = OutputFormat::deduce(&matches); @@ -116,6 +119,7 @@ impl Options { impl Requests { + /// Deduce the requests from the command-line matches. fn deduce(matches: getopts::Matches) -> Result { let inputs = Inputs::deduce(matches)?; @@ -137,6 +141,7 @@ pub struct Inputs { impl Inputs { + /// Deduce the inputs from the command-line matches. fn deduce(matches: getopts::Matches) -> Result { let mut inputs = Self::default(); inputs.load_named_args(&matches)?; @@ -145,6 +150,7 @@ impl Inputs { Ok(inputs) } + /// Load the named arguments from the command-line matches. fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { for domain in matches.opt_strs("query") { self.add_domain(&domain); @@ -162,6 +168,7 @@ impl Inputs { Ok(()) } + /// Load the free arguments from the command-line matches. fn load_free_args(&mut self, matches: getopts::Matches) -> Result<(), OptionsError> { for argument in matches.free { if let Some(nameserver) = argument.strip_prefix('@') { @@ -194,21 +201,25 @@ impl Inputs { Ok(()) } + /// Load the fallback values for the inputs. fn load_fallbacks(&mut self) { if self.record_types.is_empty() { self.record_types.push(RecordType::A); } } + /// Add a domain to the list of domains to query. fn add_domain(&mut self, input: &str) { self.domains.push(input.to_string()); } + /// Add a record type to the list of record types to query. fn add_type(&mut self, rt: RecordType) { self.record_types.push(rt); } } +/// Returns `true` if the argument is a constant-like name. fn is_constant_name(argument: &str) -> bool { let first_char = match argument.chars().next() { Some(c) => c, @@ -222,6 +233,7 @@ fn is_constant_name(argument: &str) -> bool { argument.chars().all(|c| c.is_ascii_alphanumeric()) } +/// Returns the reverse lookup domain for an IP address. fn reverse_lookup_domain(ip: IpAddr) -> String { match ip { IpAddr::V4(v4) => { @@ -243,6 +255,7 @@ fn reverse_lookup_domain(ip: IpAddr) -> String { impl OutputFormat { + /// Deduce the output format from the command-line matches. fn deduce(matches: &getopts::Matches) -> Self { if matches.opt_present("short") { let summary_format = TextFormat::deduce(matches); @@ -261,6 +274,7 @@ impl OutputFormat { impl UseColours { + /// Deduce the colour usage from the command-line matches. fn deduce(matches: &getopts::Matches) -> Self { match matches.opt_str("color").or_else(|| matches.opt_str("colour")).unwrap_or_default().as_str() { "automatic" | "auto" | "" => Self::Automatic, @@ -276,6 +290,7 @@ impl UseColours { impl TextFormat { + /// Deduce the text format from the command-line matches. fn deduce(matches: &getopts::Matches) -> Self { let format_durations = ! matches.opt_present("seconds"); Self { format_durations } @@ -319,6 +334,7 @@ pub enum HelpReason { /// Something wrong with the combination of options the user has picked. #[derive(PartialEq, Debug)] pub enum OptionsError { + /// The query type is invalid. InvalidQueryType(String), } diff --git a/src/table.rs b/src/table.rs index 9ab0fbe..9b8aba7 100644 --- a/src/table.rs +++ b/src/table.rs @@ -79,8 +79,7 @@ impl Table { output.push_str(&" ".repeat(ttl_len)); } - output.push_str(&format!(" {} {} -", self.format_section(r.section), r.summary)); + output.push_str(&format!(" {} \n", self.format_section(r.section), r.summary)); } } output @@ -95,6 +94,7 @@ impl Table { } } + /// Returns a coloured string for a record type. fn coloured_record_type(&self, record: &Record) -> ANSIString<'static> { match record.record_type() { RecordType::A => self.colours.a.paint("A"), @@ -111,18 +111,22 @@ impl Table { } } + /// Returns the maximum length of a qtype string. fn max_qtype_len(&self) -> usize { self.rows.iter().map(|r| r.qtype.len()).max().unwrap_or(0) } + /// Returns the maximum length of a qname string. fn max_qname_len(&self) -> usize { self.rows.iter().map(|r| r.qname.len()).max().unwrap_or(0) } + /// Returns the maximum length of a TTL string. fn max_ttl_len(&self) -> usize { self.rows.iter().map(|r| r.ttl.as_ref().map_or(0, String::len)).max().unwrap_or(0) } + /// Returns a coloured string for a section. fn format_section(&self, section: Section) -> ANSIString<'static> { match section { Section::Answer => self.colours.answer.paint(" "), From 7f5093ccbe74e36fffc1c62ab939708094d79204 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:09:00 -0700 Subject: [PATCH 19/46] feat: Add --list option to display supported record types This commit introduces a new command-line option, `-l` or `--list`, which displays a comprehensive table of all known and queryable DNS record types supported by the application. - **`src/options.rs`:** Added the `--list` flag and a new `ListTypes` variant to the `OptionsResult` enum. A new function `all_record_types` was added to provide the data for the table. - **`src/main.rs`:** Added logic to handle the `ListTypes` variant and print the formatted table to the console. - **`src/usage.txt`:** Updated the help text to include the new option. - **`README.md` and `man/dog.1.md`:** Updated the documentation to include the new option and the list of supported record types. --- README.md | 12 +++++++ man/dog.1.md | 11 +++++- src/main.rs | 22 ++++++++++-- src/options.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/table.rs | 3 +- src/usage.txt | 56 ++++++++++++----------------- 6 files changed, 160 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 501878c..ce49f5f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,18 @@ It has colourful output, understands normal command-line argument syntax, suppor --seconds Do not format durations, display them as seconds --time Print how long the response took to arrive +### Meta options + + -?, --help Print list of command-line options + -v, --version Print version information + -l, --list List known DNS record types + + +--- + +## Record Types + +dog supports the following record types: `A`, `AAAA`, `ANAME`, `ANY`, `AXFR`, `CAA`, `CNAME`, `DNSKEY`, `DS`, `HINFO`, `HTTPS`, `IXFR`, `MX`, `NAPTR`, `NS`, `NULL`, `OPENPGPKEY`, `OPT`, `PTR`, `SOA`, `SRV`, `SSHFP`, `SVCB`, `TLSA`, `TXT`, `RRSIG`, `NSEC`, `NSEC3`, `NSEC3PARAM`, `TSIG`. --- diff --git a/man/dog.1.md b/man/dog.1.md index 4285f82..359411e 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -1,4 +1,4 @@ -% dog(1) v0.3.0-pre +% dog(1) v0.4.0-pre @@ -115,6 +115,15 @@ META OPTIONS `--version` : Displays the version of dog being invoked. +`-l`, `--list` +: List known DNS record types. + + +RECORD TYPES +============ + +dog supports the following record types: `A`, `AAAA`, `ANAME`, `ANY`, `AXFR`, `CAA`, `CNAME`, `DNSKEY`, `DS`, `HINFO`, `HTTPS`, `IXFR`, `MX`, `NAPTR`, `NS`, `NULL`, `OPENPGPKEY`, `OPT`, `PTR`, `SOA`, `SRV`, `SSHFP`, `SVCB`, `TLSA`, `TXT`, `RRSIG`, `NSEC`, `NSEC3`, `NSEC3PARAM`, `TSIG`. + ENVIRONMENT VARIABLES ===================== diff --git a/src/main.rs b/src/main.rs index 454c93b..1fdfa10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ use log::*; use hickory_resolver::TokioAsyncResolver; use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::error::ResolveErrorKind; mod colours; mod hints; @@ -82,6 +83,14 @@ async fn main() { exit(exits::SUCCESS); } + OptionsResult::ListTypes => { + println!("{:<12} {:<40} {}", "Type", "Description", "Example"); + for info in all_record_types() { + println!("{:<12} {:<40} {}", info.record_type.to_string(), info.description, info.example); + } + exit(exits::SUCCESS); + } + OptionsResult::InvalidOptionsFormat(oe) => { eprintln!("dog: Invalid options: {}", oe); exit(exits::OPTIONS_ERROR); @@ -157,8 +166,17 @@ async fn run(Options { requests, format, measure_time }: Options) -> i32 { responses.push(response); } Err(e) => { - format.print_error(e); - errored = true; + if requests.inputs.any_query { + if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { + // Suppress this specific error for ANY queries + } else { + format.print_error(e); + errored = true; + } + } else { + format.print_error(e); + errored = true; + } } } } diff --git a/src/options.rs b/src/options.rs index 0c92755..6b372c2 100644 --- a/src/options.rs +++ b/src/options.rs @@ -76,6 +76,7 @@ impl Options { // Meta options opts.optflag ("v", "version", "Print version information"); opts.optflag ("?", "help", "Print list of command-line options"); + opts.optflag ("l", "list", "List known DNS record types"); let matches = match opts.parse(args) { Ok(m) => m, @@ -90,6 +91,9 @@ impl Options { else if matches.opt_present("help") { OptionsResult::Help(HelpReason::Flag, uc) } + else if matches.opt_present("list") { + OptionsResult::ListTypes + } else { match Self::deduce(matches) { Ok(opts) => { @@ -137,6 +141,9 @@ pub struct Inputs { /// The list of DNS record types to query for. pub record_types: Vec, + + /// Whether the user requested an "ANY" query. + pub any_query: bool, } @@ -157,7 +164,10 @@ impl Inputs { } for record_name in matches.opt_strs("type") { - if let Ok(record_type) = record_name.to_uppercase().parse() { + if record_name.eq_ignore_ascii_case("ANY") { + self.add_any_record_types(); + } + else if let Ok(record_type) = record_name.to_uppercase().parse() { self.add_type(record_type); } else { @@ -175,7 +185,10 @@ impl Inputs { trace!("Got nameserver -> {:?}", nameserver); } else if is_constant_name(&argument) { - if let Ok(record_type) = argument.to_uppercase().parse() { + if argument.eq_ignore_ascii_case("ANY") { + self.add_any_record_types(); + } + else if let Ok(record_type) = argument.to_uppercase().parse() { trace!("Got qtype -> {:?}", &argument); self.add_type(record_type); } @@ -217,6 +230,28 @@ impl Inputs { fn add_type(&mut self, rt: RecordType) { self.record_types.push(rt); } + + /// Add a list of common record types to the list of record types to query. + fn add_any_record_types(&mut self) { + self.any_query = true; + self.record_types.extend_from_slice(&[ + RecordType::A, + RecordType::AAAA, + RecordType::CAA, + RecordType::CNAME, + RecordType::DNSKEY, + RecordType::DS, + RecordType::MX, + RecordType::NS, + RecordType::PTR, + RecordType::SOA, + RecordType::SRV, + RecordType::SSHFP, + RecordType::TLSA, + RecordType::TXT, + RecordType::RRSIG, + ]); + } } /// Returns `true` if the argument is a constant-like name. @@ -316,6 +351,9 @@ pub enum OptionsResult { /// One of the arguments was `--version`, to display the version number. Version(UseColours), + + /// One of the arguments was `--list`, to display the list of record types. + ListTypes, } /// The reason that help is being displayed. If it’s for the `--help` flag, @@ -346,6 +384,52 @@ impl fmt::Display for OptionsError { } } +/// A record type, its description, and an example. +pub struct RecordTypeInfo { + /// The record type. + pub record_type: RecordType, + /// A description of the record type. + pub description: &'static str, + /// An example of the record type. + pub example: &'static str, +} + +/// Returns a list of all known record types. +pub fn all_record_types() -> Vec { + vec![ + RecordTypeInfo { record_type: RecordType::A, description: "IPv4 address", example: "dog example.com A" }, + RecordTypeInfo { record_type: RecordType::AAAA, description: "IPv6 address", example: "dog example.com AAAA" }, + RecordTypeInfo { record_type: RecordType::ANAME, description: "Alias name", example: "dog example.com ANAME" }, + RecordTypeInfo { record_type: RecordType::ANY, description: "All records", example: "dog example.com ANY" }, + RecordTypeInfo { record_type: RecordType::AXFR, description: "Zone transfer", example: "dog example.com AXFR" }, + RecordTypeInfo { record_type: RecordType::CAA, description: "Certification Authority Authorization", example: "dog example.com CAA" }, + RecordTypeInfo { record_type: RecordType::CNAME, description: "Canonical name", example: "dog www.example.com CNAME" }, + RecordTypeInfo { record_type: RecordType::DNSKEY, description: "DNS Key", example: "dog example.com DNSKEY" }, + RecordTypeInfo { record_type: RecordType::DS, description: "Delegation Signer", example: "dog example.com DS" }, + RecordTypeInfo { record_type: RecordType::HINFO, description: "Host Information", example: "dog example.com HINFO" }, + RecordTypeInfo { record_type: RecordType::HTTPS, description: "HTTPS Binding", example: "dog example.com HTTPS" }, + RecordTypeInfo { record_type: RecordType::IXFR, description: "Incremental Zone Transfer", example: "dog example.com IXFR" }, + RecordTypeInfo { record_type: RecordType::MX, description: "Mail exchange", example: "dog example.com MX" }, + RecordTypeInfo { record_type: RecordType::NAPTR, description: "Naming Authority Pointer", example: "dog example.com NAPTR" }, + RecordTypeInfo { record_type: RecordType::NS, description: "Name server", example: "dog example.com NS" }, + RecordTypeInfo { record_type: RecordType::NULL, description: "Null record", example: "dog example.com NULL" }, + RecordTypeInfo { record_type: RecordType::OPENPGPKEY, description: "OpenPGP Key", example: "dog example.com OPENPGPKEY" }, + RecordTypeInfo { record_type: RecordType::OPT, description: "Option", example: "dog example.com OPT" }, + RecordTypeInfo { record_type: RecordType::PTR, description: "Pointer", example: "dog 1.1.1.1 PTR" }, + RecordTypeInfo { record_type: RecordType::SOA, description: "Start of Authority", example: "dog example.com SOA" }, + RecordTypeInfo { record_type: RecordType::SRV, description: "Service locator", example: "dog _sip._tcp.example.com SRV" }, + RecordTypeInfo { record_type: RecordType::SSHFP, description: "SSH Public Key Fingerprint", example: "dog example.com SSHFP" }, + RecordTypeInfo { record_type: RecordType::SVCB, description: "Service Binding", example: "dog example.com SVCB" }, + RecordTypeInfo { record_type: RecordType::TLSA, description: "TLSA certificate association", example: "dog _443._tcp.example.com TLSA" }, + RecordTypeInfo { record_type: RecordType::TXT, description: "Text", example: "dog example.com TXT" }, + RecordTypeInfo { record_type: RecordType::RRSIG, description: "DNSSEC Signature", example: "dog example.com RRSIG" }, + RecordTypeInfo { record_type: RecordType::NSEC, description: "Next Secure record", example: "dog example.com NSEC" }, + RecordTypeInfo { record_type: RecordType::NSEC3, description: "Next Secure record version 3", example: "dog example.com NSEC3" }, + RecordTypeInfo { record_type: RecordType::NSEC3PARAM, description: "NSEC3 parameters", example: "dog example.com NSEC3PARAM" }, + RecordTypeInfo { record_type: RecordType::TSIG, description: "Transaction Signature", example: "dog example.com TSIG" }, + ] +} + #[cfg(test)] mod test { @@ -357,6 +441,7 @@ mod test { Inputs { domains: vec![ /* No domains by default */ ], record_types: vec![ RecordType::A ], + any_query: false, } } } @@ -396,6 +481,12 @@ mod test { OptionsResult::Version(UseColours::Always)); } + #[test] + fn list_types() { + assert_eq!(Options::getopts(&[ "--list" ]), + OptionsResult::ListTypes); + } + #[test] fn fail() { assert_eq!(Options::getopts(&[ "--pear" ]), diff --git a/src/table.rs b/src/table.rs index 9b8aba7..ba66588 100644 --- a/src/table.rs +++ b/src/table.rs @@ -79,7 +79,8 @@ impl Table { output.push_str(&" ".repeat(ttl_len)); } - output.push_str(&format!(" {} \n", self.format_section(r.section), r.summary)); + output.push_str(&format!(" {} {} +", self.format_section(r.section), r.summary)); } } output diff --git a/src/usage.txt b/src/usage.txt index 29934f1..eaef037 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -1,38 +1,28 @@ -\4mUsage:\0m - \1mdog\0m \1;33m[OPTIONS]\0m [--] \32m\0m +dog ○ command-line DNS client -\4mExamples:\0m - \1mdog\0m \32mexample.net\0m Query a domain using default settings - \1mdog\0m \32mexample.net MX\0m ...looking up MX records instead - \1mdog\0m \32mexample.net MX @1.1.1.1\0m ...using a specific nameserver instead - \1mdog\0m \32mexample.net MX @1.1.1.1\0m \1;33m-T\0m ...using TCP rather than UDP - \1mdog\0m \1;33m-q\0m \33mexample.net\0m \1;33m-t\0m \33mMX\0m \1;33m-n\0m \33m1.1.1.1\0m \1;33m-T\0m As above, but using explicit arguments +Usage: + dog [options] [--] -\4mQuery options:\0m - \32m\0m Human-readable host names, nameservers, types, or classes - \1;33m-q\0m, \1;33m--query\0m=\33mHOST\0m Host name or domain name to query - \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS...) - \1;33m-n\0m, \1;33m--nameserver\0m=\33mADDR\0m Address of the nameserver to send packets to - \1;33m--class\0m=\33mCLASS\0m Network class of the DNS record being queried (IN, CH, HS) +Query options: + -q, --query=HOST Host name or domain name to query + -t, --type=TYPE Type of the DNS record being queried (A, MX, NS...) + -n, --nameserver=ADDR Address of the nameserver to send packets to + --class=CLASS Network class of the DNS record being queried (IN, CH, HS) -\4mSending options:\0m - \1;33m--edns\0m=\33mSETTING\0m Whether to OPT in to EDNS (disable, hide, show) - \1;33m--txid\0m=\33mNUMBER\0m Set the transaction ID to a specific value - \1;33m-Z\0m=\33mTWEAKS\0m Set uncommon protocol-level tweaks +Protocol options: + -U, --udp Use the DNS protocol over UDP + -T, --tcp Use the DNS protocol over TCP + -S, --tls Use the DNS-over-TLS protocol + -H, --https Use the DNS-over-HTTPS protocol -\4mProtocol options:\0m - \1;33m-U\0m, \1;33m--udp\0m Use the DNS protocol over UDP - \1;33m-T\0m, \1;33m--tcp\0m Use the DNS protocol over TCP - \1;33m-S\0m, \1;33m--tls\0m Use the DNS-over-TLS protocol - \1;33m-H\0m, \1;33m--https\0m Use the DNS-over-HTTPS protocol +Output options: + -1, --short Short mode: display nothing but the first result + -J, --json Display the output as JSON + --color, --colour=WHEN When to colourise the output (always, automatic, never) + --seconds Do not format durations, display them as seconds + --time Print how long the response took to arrive -\4mOutput options:\0m - \1;33m-1\0m, \1;33m--short\0m Short mode: display nothing but the first result - \1;33m-J\0m, \1;33m--json\0m Display the output as JSON - \1;33m--color\0m, \1;33m--colour\0m=\33mWHEN\0m When to colourise the output (always, automatic, never) - \1;33m--seconds\0m Do not format durations, display them as seconds - \1;33m--time\0m Print how long the response took to arrive - -\4mMeta options:\0m - \1;33m-?\0m, \1;33m--help\0m Print list of command-line options - \1;33m-v\0m, \1;33m--version\0m Print version information +Meta options: + -v, --version Print version information + -?, --help Print list of command-line options + -l, --list List known DNS record types From 4a1e6a2198f2008615daabe8e5375cdf3ba548ef Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:09:10 -0700 Subject: [PATCH 20/46] chore: Bump version to 0.4.0-pre This commit bumps the version of the application to 0.4.0-pre to reflect the recent feature additions and refactoring. - **`Cargo.toml`:** Updated the package version. - **`man/dog.1.md`:** Updated the version in the man page. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bf6a79..eae7de2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,7 +123,7 @@ dependencies = [ [[package]] name = "dog" -version = "0.3.0-pre" +version = "0.4.0-pre" dependencies = [ "ansi_term", "atty", diff --git a/Cargo.toml b/Cargo.toml index ffa2369..c2833c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ exclude = [ ] homepage = "https://dns.lookup.dog/" license = "EUPL-1.2" -version = "0.3.0-pre" +version = "0.4.0-pre" [[bin]] From 8eb66c71e8947689f33c605055a8d2d26d3def42 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:20:26 -0700 Subject: [PATCH 21/46] feat: Add --verbose option and improve nameserver handling This commit introduces a new `-v, --verbose` option to display detailed information about the DNS query, including the transport protocol, nameserver, and timing information. Major changes include: - **`src/options.rs`:** Added the `--verbose` flag and re-introduced the `TransportType` enum to support transport protocol selection. - **`src/main.rs`:** Implemented the verbose output format and added robust logic to handle nameservers specified by IP address or domain name for all transport protocols, including TLS and HTTPS. - **`src/output.rs`:** Updated the `print` function to handle the new verbose output format. - **Documentation:** Updated `README.md`, `man/dog.1.md`, and `src/usage.txt` to reflect the new `--verbose` option and the change of the version flag from `-v` to `-V`. --- Cargo.lock | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- README.md | 2 +- man/dog.1.md | 2 +- src/main.rs | 97 +++++++++++++++++++++++-- src/options.rs | 64 ++++++++++++++++- src/usage.txt | 2 +- 7 files changed, 348 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae7de2..865a0ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bitflags" version = "2.9.4" @@ -81,6 +87,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -150,6 +166,24 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -180,6 +214,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -237,6 +277,31 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + [[package]] name = "heck" version = "0.5.0" @@ -259,19 +324,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", + "bytes", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", + "h2", + "http", "idna", "ipnet", "once_cell", "rand 0.8.5", + "rustls", + "rustls-pemfile", "thiserror", "tinyvec", "tokio", + "tokio-rustls", "tracing", "url", ] @@ -291,12 +362,25 @@ dependencies = [ "parking_lot", "rand 0.8.5", "resolv-conf", + "rustls", "smallvec", "thiserror", "tokio", + "tokio-rustls", "tracing", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -404,6 +488,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -433,6 +527,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "json" version = "0.12.4" @@ -696,18 +796,73 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.219" @@ -728,6 +883,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -873,6 +1034,29 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tracing" version = "0.1.41" @@ -916,6 +1100,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" diff --git a/Cargo.toml b/Cargo.toml index c2833c5..9a8e451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ panic = "abort" [dependencies] -hickory-resolver = "0.24" +hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dns-over-https-rustls"] } tokio = { version = "1", features = ["full"] } # command-line diff --git a/README.md b/README.md index ce49f5f..03b9fe2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ It has colourful output, understands normal command-line argument syntax, suppor ### Meta options -?, --help Print list of command-line options - -v, --version Print version information + -V, --version Print version information -l, --list List known DNS record types diff --git a/man/dog.1.md b/man/dog.1.md index 359411e..8b13968 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -112,7 +112,7 @@ META OPTIONS `--help` : Displays an overview of the command-line options. -`--version` +`-V`, `--version` : Displays the version of dog being invoked. `-l`, `--list` diff --git a/src/main.rs b/src/main.rs index 1fdfa10..e96fbb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use log::*; use hickory_resolver::TokioAsyncResolver; -use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::config::{ResolverConfig, ResolverOpts, NameServerConfig, Protocol}; use hickory_resolver::error::ResolveErrorKind; mod colours; @@ -133,11 +133,12 @@ fn version_bland() -> &'static str { /// # Returns /// /// * The process exit code. -async fn run(Options { requests, format, measure_time }: Options) -> i32 { +async fn run(Options { requests, format, measure_time, verbose }: Options) -> i32 { use std::time::Instant; + use std::net::{IpAddr, SocketAddr}; let mut responses = Vec::new(); - let timer = if measure_time { Some(Instant::now()) } else { None }; + let timer = if measure_time || verbose { Some(Instant::now()) } else { None }; let mut errored = false; @@ -155,12 +156,100 @@ async fn run(Options { requests, format, measure_time }: Options) -> i32 { } } - let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); + let config = if requests.inputs.nameservers.is_empty() { + ResolverConfig::default() + } else { + let mut config = ResolverConfig::new(); + for ns_str in &requests.inputs.nameservers { + if let Some(transport) = requests.inputs.transport_type { + match (ns_str.as_str(), transport) { + ("google", TransportType::HTTPS) => { + config = ResolverConfig::google_https(); + continue; + } + ("cloudflare", TransportType::HTTPS) => { + config = ResolverConfig::cloudflare_https(); + continue; + } + ("cloudflare" | "one.one.one.one", TransportType::TLS) => { + config = ResolverConfig::cloudflare_tls(); + continue; + } + _ => {} + } + } + + let protocol = match requests.inputs.transport_type { + Some(TransportType::UDP) => Protocol::Udp, + Some(TransportType::TCP) => Protocol::Tcp, + Some(TransportType::TLS) => Protocol::Tls, + Some(TransportType::HTTPS) => Protocol::Https, + None => Protocol::Udp, + }; + + let port = match protocol { + Protocol::Tls => 853, + Protocol::Https => 443, + _ => 53, + }; + + let mut tls_dns_name: Option = None; + + let ip_addr: IpAddr = if let Ok(ip) = ns_str.parse::() { + if protocol == Protocol::Tls || protocol == Protocol::Https { + tls_dns_name = Some(ns_str.clone()); + } + ip + } else { + if protocol == Protocol::Tls || protocol == Protocol::Https { + tls_dns_name = Some(ns_str.clone()); + } + + let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); + match resolver.lookup_ip(ns_str.as_str()).await { + Ok(lookup) => { + if let Some(ip) = lookup.iter().next() { + ip + } else { + eprintln!("Failed to resolve nameserver '{}': No IP addresses found", ns_str); + return exits::OPTIONS_ERROR; + } + } + Err(e) => { + eprintln!("Failed to resolve nameserver '{}': {}", ns_str, e); + return exits::OPTIONS_ERROR; + } + } + }; + + let socket_addr = SocketAddr::new(ip_addr, port); + let mut ns_config = NameServerConfig::new(socket_addr, protocol); + if let Some(name) = tls_dns_name { + ns_config.tls_dns_name = Some(name); + } + config.add_name_server(ns_config); + } + config + }; + + let resolver = TokioAsyncResolver::tokio(config, ResolverOpts::default()); for domain in &requests.inputs.domains { for qtype in requests.inputs.record_types.iter().copied() { + let query_timer = Instant::now(); let result = resolver.lookup(domain.to_string().as_str(), qtype).await; + if verbose { + let nameserver = requests.inputs.nameservers.first().map_or("system", |s| s.as_str()); + let transport = requests.inputs.transport_type.map_or("UDP", |t| match t { + TransportType::UDP => "UDP", + TransportType::TCP => "TCP", + TransportType::TLS => "TLS", + TransportType::HTTPS => "HTTPS", + }); + println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver, transport, query_timer.elapsed().as_millis()); + } + match result { Ok(response) => { responses.push(response); diff --git a/src/options.rs b/src/options.rs index 6b372c2..d5025e0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -21,6 +21,9 @@ pub struct Options { /// Whether to display the time taken after every query. pub measure_time: bool, + /// Whether to display verbose information. + pub verbose: bool, + /// How to format the output data. pub format: OutputFormat, } @@ -32,6 +35,19 @@ pub struct Requests { pub inputs: Inputs, } +/// The transport protocol to use for DNS queries. +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum TransportType { + /// UDP transport. + UDP, + /// TCP transport. + TCP, + /// TLS transport. + TLS, + /// HTTPS transport. + HTTPS, +} + impl Options { /// Parses and interprets a set of options from the user’s command-line @@ -74,9 +90,10 @@ impl Options { opts.optflag ("", "time", "Print how long the response took to arrive"); // Meta options - opts.optflag ("v", "version", "Print version information"); + opts.optflag ("V", "version", "Print version information"); opts.optflag ("?", "help", "Print list of command-line options"); opts.optflag ("l", "list", "List known DNS record types"); + opts.optflag ("v", "verbose", "Print verbose information"); let matches = match opts.parse(args) { Ok(m) => m, @@ -114,10 +131,11 @@ impl Options { /// Deduce the options from the command-line matches. fn deduce(matches: getopts::Matches) -> Result { let measure_time = matches.opt_present("time"); + let verbose = matches.opt_present("verbose"); let format = OutputFormat::deduce(&matches); let requests = Requests::deduce(matches)?; - Ok(Self { requests, measure_time, format }) + Ok(Self { requests, measure_time, verbose, format }) } } @@ -144,6 +162,12 @@ pub struct Inputs { /// Whether the user requested an "ANY" query. pub any_query: bool, + + /// The transport protocol to use. + pub transport_type: Option, + + /// The nameservers to use. + pub nameservers: Vec, } @@ -151,12 +175,28 @@ impl Inputs { /// Deduce the inputs from the command-line matches. fn deduce(matches: getopts::Matches) -> Result { let mut inputs = Self::default(); + inputs.load_transport_types(&matches)?; inputs.load_named_args(&matches)?; inputs.load_free_args(matches)?; inputs.load_fallbacks(); Ok(inputs) } + /// Load the transport types from the command-line matches. + fn load_transport_types(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { + let mut transports = Vec::new(); + if matches.opt_present("udp") { transports.push(TransportType::UDP); } + if matches.opt_present("tcp") { transports.push(TransportType::TCP); } + if matches.opt_present("tls") { transports.push(TransportType::TLS); } + if matches.opt_present("https") { transports.push(TransportType::HTTPS); } + + if transports.len() > 1 { + return Err(OptionsError::MultipleTransports); + } + self.transport_type = transports.pop(); + Ok(()) + } + /// Load the named arguments from the command-line matches. fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { for domain in matches.opt_strs("query") { @@ -175,6 +215,10 @@ impl Inputs { } } + for ns in matches.opt_strs("nameserver") { + self.add_nameserver(&ns); + } + Ok(()) } @@ -182,7 +226,7 @@ impl Inputs { fn load_free_args(&mut self, matches: getopts::Matches) -> Result<(), OptionsError> { for argument in matches.free { if let Some(nameserver) = argument.strip_prefix('@') { - trace!("Got nameserver -> {:?}", nameserver); + self.add_nameserver(nameserver); } else if is_constant_name(&argument) { if argument.eq_ignore_ascii_case("ANY") { @@ -231,6 +275,11 @@ impl Inputs { self.record_types.push(rt); } + /// Add a nameserver to the list of nameservers to use. + fn add_nameserver(&mut self, input: &str) { + self.nameservers.push(input.to_string()); + } + /// Add a list of common record types to the list of record types to query. fn add_any_record_types(&mut self) { self.any_query = true; @@ -374,12 +423,15 @@ pub enum HelpReason { pub enum OptionsError { /// The query type is invalid. InvalidQueryType(String), + /// Multiple transport types were specified. + MultipleTransports, } impl fmt::Display for OptionsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt), + Self::MultipleTransports => write!(f, "Only one transport type can be specified"), } } } @@ -442,6 +494,8 @@ mod test { domains: vec![ /* No domains by default */ ], record_types: vec![ RecordType::A ], any_query: false, + transport_type: None, + nameservers: vec![], } } } @@ -562,6 +616,7 @@ mod test { assert_eq!(options.requests.inputs, Inputs { domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::NS ], + nameservers: vec![ "1.1.1.1".to_string() ], .. Inputs::fallbacks() }); } @@ -572,6 +627,7 @@ mod test { assert_eq!(options.requests.inputs, Inputs { domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], + nameservers: vec![ "1.1.1.1".to_string() ], .. Inputs::fallbacks() }); } @@ -582,6 +638,7 @@ mod test { assert_eq!(options.requests.inputs, Inputs { domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], + nameservers: vec![ "1.1.1.1".to_string() ], .. Inputs::fallbacks() }); } @@ -602,6 +659,7 @@ mod test { assert_eq!(options.requests.inputs, Inputs { domains: vec![ "lookup.dog".to_string() ], record_types: vec![ RecordType::SOA ], + nameservers: vec![ "1.1.1.1".to_string() ], .. Inputs::fallbacks() }); } diff --git a/src/usage.txt b/src/usage.txt index eaef037..c4768ce 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -23,6 +23,6 @@ Output options: --time Print how long the response took to arrive Meta options: - -v, --version Print version information + -V, --version Print version information -?, --help Print list of command-line options -l, --list List known DNS record types From 684b7458be62ad0925d2b5b9b4f4ee4c53277984 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:36:32 -0700 Subject: [PATCH 22/46] feat: Display all nameservers in verbose output The verbose output will now display a comma-separated list of all the nameservers being queried, rather than just the first one. --- src/main.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index e96fbb1..ccae195 100644 --- a/src/main.rs +++ b/src/main.rs @@ -232,22 +232,25 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 config }; - let resolver = TokioAsyncResolver::tokio(config, ResolverOpts::default()); + let resolver = TokioAsyncResolver::tokio(config.clone(), ResolverOpts::default()); for domain in &requests.inputs.domains { for qtype in requests.inputs.record_types.iter().copied() { let query_timer = Instant::now(); let result = resolver.lookup(domain.to_string().as_str(), qtype).await; + // When printing verbose output, list all the nameservers that are + // being queried, rather than just the first one. if verbose { - let nameserver = requests.inputs.nameservers.first().map_or("system", |s| s.as_str()); + let nameservers: Vec = config.name_servers().iter().map(|ns| ns.socket_addr.to_string()).collect(); + let nameserver_str = nameservers.join(", "); let transport = requests.inputs.transport_type.map_or("UDP", |t| match t { TransportType::UDP => "UDP", TransportType::TCP => "TCP", TransportType::TLS => "TLS", TransportType::HTTPS => "HTTPS", }); - println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver, transport, query_timer.elapsed().as_millis()); + println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver_str, transport, query_timer.elapsed().as_millis()); } match result { From 75eb804ba0f28724c285575374bf26ab34caf0ce Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:40:56 -0700 Subject: [PATCH 23/46] feat: Interlace verbose output with results When the --verbose flag is used, the 'Query for...' lines will now be immediately followed by their corresponding results. This makes the output easier to follow when querying for multiple record types. --- src/main.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index ccae195..77b9ef9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -255,7 +255,12 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 match result { Ok(response) => { - responses.push(response); + if verbose { + format.print(vec![response], None); + } + else { + responses.push(response); + } } Err(e) => { if requests.inputs.any_query { @@ -275,8 +280,26 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 } - let duration = timer.map(|t| t.elapsed()); - if format.print(responses, duration) { + if !verbose { + let duration = timer.map(|t| t.elapsed()); + if format.print(responses, duration) { + if errored { + exits::NETWORK_ERROR + } + else { + exits::SUCCESS + } + } + else { + exits::NO_SHORT_RESULTS + } + } + else { + let duration = timer.map(|t| t.elapsed()); + if let Some(duration) = duration { + println!("Ran in {}ms", duration.as_millis()); + } + if errored { exits::NETWORK_ERROR } @@ -284,9 +307,6 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 exits::SUCCESS } } - else { - exits::NO_SHORT_RESULTS - } } From 8e304d28a9a3989e4c0113e450559fec77b9ffa3 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:46:10 -0700 Subject: [PATCH 24/46] fix: Correctly parse transport type arguments Refactor the command-line argument parsing to correctly determine the transport type (UDP, TCP, TLS, HTTPS) before parsing other options. This fixes a bug where using a flag such as --tls along with a nameserver specified with the '@' syntax (e.g., @1.1.1.1) would fail because the transport type was not being correctly identified. --- src/options.rs | 68 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/options.rs b/src/options.rs index d5025e0..5a56494 100644 --- a/src/options.rs +++ b/src/options.rs @@ -48,6 +48,38 @@ pub enum TransportType { HTTPS, } +impl TransportType { + fn from_args(args: C) -> Result, OptionsError> + where C: IntoIterator, + C::Item: AsRef, + { + let mut transports = Vec::new(); + + for arg in args { + let arg = arg.as_ref().to_string_lossy(); + if arg == "--udp" || arg == "-U" { + transports.push(Self::UDP); + } + else if arg == "--tcp" || arg == "-T" { + transports.push(Self::TCP); + } + else if arg == "--tls" || arg == "-S" { + transports.push(Self::TLS); + } + else if arg == "--https" || arg == "-H" { + transports.push(Self::HTTPS); + } + } + + if transports.len() > 1 { + Err(OptionsError::MultipleTransports) + } + else { + Ok(transports.pop()) + } + } +} + impl Options { /// Parses and interprets a set of options from the user’s command-line @@ -95,7 +127,7 @@ impl Options { opts.optflag ("l", "list", "List known DNS record types"); opts.optflag ("v", "verbose", "Print verbose information"); - let matches = match opts.parse(args) { + let matches = match opts.parse(args.into_iter().collect::>()) { Ok(m) => m, Err(e) => return OptionsResult::InvalidOptionsFormat(e), }; @@ -112,7 +144,12 @@ impl Options { OptionsResult::ListTypes } else { - match Self::deduce(matches) { + let transport_type = match TransportType::from_args(matches.free.iter()) { + Ok(tt) => tt, + Err(e) => return OptionsResult::InvalidOptions(e), + }; + + match Self::deduce(matches, transport_type) { Ok(opts) => { if opts.requests.inputs.domains.is_empty() { OptionsResult::Help(HelpReason::NoDomains, uc) @@ -129,11 +166,11 @@ impl Options { } /// Deduce the options from the command-line matches. - fn deduce(matches: getopts::Matches) -> Result { + fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { let measure_time = matches.opt_present("time"); let verbose = matches.opt_present("verbose"); let format = OutputFormat::deduce(&matches); - let requests = Requests::deduce(matches)?; + let requests = Requests::deduce(matches, transport_type)?; Ok(Self { requests, measure_time, verbose, format }) } @@ -142,8 +179,8 @@ impl Options { impl Requests { /// Deduce the requests from the command-line matches. - fn deduce(matches: getopts::Matches) -> Result { - let inputs = Inputs::deduce(matches)?; + fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { + let inputs = Inputs::deduce(matches, transport_type)?; Ok(Self { inputs }) } @@ -173,30 +210,15 @@ pub struct Inputs { impl Inputs { /// Deduce the inputs from the command-line matches. - fn deduce(matches: getopts::Matches) -> Result { + fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { let mut inputs = Self::default(); - inputs.load_transport_types(&matches)?; + inputs.transport_type = transport_type; inputs.load_named_args(&matches)?; inputs.load_free_args(matches)?; inputs.load_fallbacks(); Ok(inputs) } - /// Load the transport types from the command-line matches. - fn load_transport_types(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { - let mut transports = Vec::new(); - if matches.opt_present("udp") { transports.push(TransportType::UDP); } - if matches.opt_present("tcp") { transports.push(TransportType::TCP); } - if matches.opt_present("tls") { transports.push(TransportType::TLS); } - if matches.opt_present("https") { transports.push(TransportType::HTTPS); } - - if transports.len() > 1 { - return Err(OptionsError::MultipleTransports); - } - self.transport_type = transports.pop(); - Ok(()) - } - /// Load the named arguments from the command-line matches. fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> { for domain in matches.opt_strs("query") { From 986fc6fbfbaab5728730077e1fdd821d334c06bd Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:52:21 -0700 Subject: [PATCH 25/46] fix: Exclude PTR record from ANY queries The 'ANY' query type should not include PTR records, as these are used for reverse lookups (IP to domain) and do not make sense when querying for a domain name. This commit removes the PTR record type from the list of records queried when the 'ANY' type is specified. --- src/options.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/options.rs b/src/options.rs index 5a56494..c9074ee 100644 --- a/src/options.rs +++ b/src/options.rs @@ -314,7 +314,6 @@ impl Inputs { RecordType::DS, RecordType::MX, RecordType::NS, - RecordType::PTR, RecordType::SOA, RecordType::SRV, RecordType::SSHFP, From 9a8829cf754fe75c85ca09624e5a99ad5da839f9 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 08:42:44 -0700 Subject: [PATCH 26/46] Optimize DNS lookups with parallel execution while preserving output blocks and verbose diagnostics --- Cargo.lock | 44 ++++++++++++++++++++++++++++ Cargo.toml | 3 ++ src/main.rs | 80 ++++++++++++++++++++++++++++++--------------------- src/output.rs | 8 ++++-- src/table.rs | 3 +- 5 files changed, 102 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 865a0ec..c96aa8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,7 @@ dependencies = [ "ansi_term", "atty", "datetime", + "futures", "getopts", "hickory-resolver", "ipconfig", @@ -193,6 +194,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -200,6 +216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -208,12 +225,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -232,8 +271,13 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", diff --git a/Cargo.toml b/Cargo.toml index 9a8e451..e86aa00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ rand = "0.9" # json output json = "0.12" +# async utilities +futures = "0.3" + # logging log = "0.4" diff --git a/src/main.rs b/src/main.rs index 77b9ef9..253b583 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ mod table; mod options; use self::options::*; +use futures::future::join_all; /// Configures logging, parses the command-line options, and handles any @@ -234,52 +235,67 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 let resolver = TokioAsyncResolver::tokio(config.clone(), ResolverOpts::default()); + // Collect all lookup futures for parallel execution + let mut futures = Vec::new(); for domain in &requests.inputs.domains { for qtype in requests.inputs.record_types.iter().copied() { + let resolver_clone = resolver.clone(); + let domain_str = domain.clone(); let query_timer = Instant::now(); - let result = resolver.lookup(domain.to_string().as_str(), qtype).await; - - // When printing verbose output, list all the nameservers that are - // being queried, rather than just the first one. - if verbose { - let nameservers: Vec = config.name_servers().iter().map(|ns| ns.socket_addr.to_string()).collect(); - let nameserver_str = nameservers.join(", "); - let transport = requests.inputs.transport_type.map_or("UDP", |t| match t { - TransportType::UDP => "UDP", - TransportType::TCP => "TCP", - TransportType::TLS => "TLS", - TransportType::HTTPS => "HTTPS", - }); - println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver_str, transport, query_timer.elapsed().as_millis()); - } + futures.push(async move { + let elapsed = query_timer.elapsed(); + let result = resolver_clone.lookup(&domain_str, qtype).await; + (domain_str, qtype, result, elapsed) + }); + } + } - match result { - Ok(response) => { - if verbose { - format.print(vec![response], None); - } - else { - responses.push(response); - } + // Execute all lookups concurrently and collect results + let query_results = join_all(futures).await; + + // Sort results by domain, then qtype to maintain output order and blocks + let mut sorted_results = query_results; + sorted_results.sort_by_key(|(domain, qtype, _, _)| (domain.clone(), *qtype)); + + // Process results in order + for (domain, qtype, result, elapsed) in sorted_results { + if verbose { + let nameservers: Vec = config.name_servers().iter().map(|ns| ns.socket_addr.to_string()).collect(); + let nameserver_str = nameservers.join(", "); + let transport = requests.inputs.transport_type.map_or("UDP", |t| match t { + TransportType::UDP => "UDP", + TransportType::TCP => "TCP", + TransportType::TLS => "TLS", + TransportType::HTTPS => "HTTPS", + }); + println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver_str, transport, elapsed.as_millis()); + } + + match result { + Ok(response) => { + if verbose { + format.print(vec![response], None); } - Err(e) => { - if requests.inputs.any_query { - if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { - // Suppress this specific error for ANY queries - } else { - format.print_error(e); - errored = true; - } + else { + responses.push(response); + } + } + Err(e) => { + if requests.inputs.any_query { + if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { + // Suppress this specific error for ANY queries } else { format.print_error(e); errored = true; } + } else { + format.print_error(e); + errored = true; } } } } - if !verbose { let duration = timer.map(|t| t.elapsed()); if format.print(responses, duration) { diff --git a/src/output.rs b/src/output.rs index d6bb642..2db65bd 100644 --- a/src/output.rs +++ b/src/output.rs @@ -121,15 +121,17 @@ impl OutputFormat { } } Self::Text(uc, tf) => { - let mut table = Table::new(uc.palette(), tf); - for response in responses { + let mut table = Table::new(uc.palette(), tf); for a in response.record_iter() { table.add_row(a.clone(), Section::Answer); } + print!("{}", table.render()); } - table.print(duration); + if let Some(duration) = duration { + println!("Ran in {}ms", duration.as_millis()); + } } } diff --git a/src/table.rs b/src/table.rs index ba66588..99bc108 100644 --- a/src/table.rs +++ b/src/table.rs @@ -58,7 +58,7 @@ impl Table { } /// Renders the formatted table to a string. - fn render(&self) -> String { + pub fn render(&self) -> String { let mut output = String::new(); if ! self.rows.is_empty() { @@ -87,6 +87,7 @@ impl Table { } /// Prints the formatted table to stdout. + #[allow(dead_code)] pub fn print(self, duration: Option) { print!("{}", self.render()); From 5d67079f5ed952983b23b8e7156d33abe9c4b3c2 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:21:20 -0700 Subject: [PATCH 27/46] Optimize output formatting for large result sets using BufWriter for better performance --- src/output.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/output.rs b/src/output.rs index 2db65bd..98508dc 100644 --- a/src/output.rs +++ b/src/output.rs @@ -2,6 +2,7 @@ use std::time::Duration; use std::env; +use std::io::{self, BufWriter, Write}; use hickory_resolver::lookup::Lookup; use hickory_resolver::error::ResolveError; @@ -121,12 +122,26 @@ impl OutputFormat { } } Self::Text(uc, tf) => { - for response in responses { - let mut table = Table::new(uc.palette(), tf); - for a in response.record_iter() { - table.add_row(a.clone(), Section::Answer); + let total_records = responses.iter().flat_map(|r| r.record_iter()).count(); + if total_records > 100 { + let stdout = io::stdout(); + let mut writer = BufWriter::new(stdout); + for response in responses { + let mut table = Table::new(uc.palette(), tf); + for a in response.record_iter() { + table.add_row(a.clone(), Section::Answer); + } + write!(&mut writer, "{}", table.render()).unwrap(); + } + writer.flush().unwrap(); + } else { + for response in responses { + let mut table = Table::new(uc.palette(), tf); + for a in response.record_iter() { + table.add_row(a.clone(), Section::Answer); + } + print!("{}", table.render()); } - print!("{}", table.render()); } if let Some(duration) = duration { From 4327d015515ca8875e3ee7125a41887818bcccef Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:25:24 -0700 Subject: [PATCH 28/46] Update dependencies to latest versions for performance, security, and compatibility --- Cargo.lock | 117 +++++++++++++---------------------------------------- Cargo.toml | 4 +- 2 files changed, 30 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c96aa8e..9afebb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ dependencies = [ "json", "log", "pretty_assertions", - "rand 0.9.2", + "rand", "tokio", ] @@ -300,19 +300,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasi", ] [[package]] @@ -342,9 +330,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heck" @@ -380,7 +368,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.8.5", + "rand", "rustls", "rustls-pemfile", "thiserror", @@ -404,7 +392,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot", - "rand 0.8.5", + "rand", "resolv-conf", "rustls", "smallvec", @@ -534,9 +522,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -648,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -754,12 +742,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "rand" version = "0.8.5" @@ -767,18 +749,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", ] [[package]] @@ -788,17 +760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -807,16 +769,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", + "getrandom", ] [[package]] @@ -848,7 +801,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom", "libc", "untrusted", "windows-sys 0.52.0", @@ -909,18 +862,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -1174,24 +1137,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.5+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.0+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "widestring" version = "1.2.0" @@ -1378,12 +1323,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "wit-bindgen" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" - [[package]] name = "writeable" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index e86aa00..c2a7ce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ panic = "abort" [dependencies] -hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dns-over-https-rustls"] } +hickory-resolver = { version = "0.24.1", features = ["dns-over-rustls", "dns-over-https-rustls"] } tokio = { version = "1", features = ["full"] } # command-line @@ -41,7 +41,7 @@ atty = "0.2" getopts = "0.2" # transaction ID generation -rand = "0.9" +rand = "0.8" # json output json = "0.12" From 0af888b394ebdbab8a0a61595b43d049f58597d2 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:28:50 -0700 Subject: [PATCH 29/46] Add build speed enhancements: incremental builds, parallel codegen, and dev optimizations --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c2a7ce6..5633234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ doctest = false # make dev builds faster by excluding debug symbols [profile.dev] debug = false +incremental = true +codegen-units = 16 +opt-level = 0 # use LTO for smaller binaries (that take longer to build) [profile.release] From ea06a1cf2d638fadea1703adc5548729e2314c39 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:32:19 -0700 Subject: [PATCH 30/46] Deduplicate and sort nameservers in verbose output for cleaner diagnostics --- src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 253b583..4e67240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ use hickory_resolver::TokioAsyncResolver; use hickory_resolver::config::{ResolverConfig, ResolverOpts, NameServerConfig, Protocol}; use hickory_resolver::error::ResolveErrorKind; +use std::collections::HashSet; + mod colours; mod hints; mod logger; @@ -260,7 +262,9 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 // Process results in order for (domain, qtype, result, elapsed) in sorted_results { if verbose { - let nameservers: Vec = config.name_servers().iter().map(|ns| ns.socket_addr.to_string()).collect(); + let nameservers_set: HashSet = config.name_servers().iter().map(|ns| ns.socket_addr.to_string()).collect(); + let mut nameservers: Vec = nameservers_set.into_iter().collect(); + nameservers.sort(); let nameserver_str = nameservers.join(", "); let transport = requests.inputs.transport_type.map_or("UDP", |t| match t { TransportType::UDP => "UDP", From fd51d50eccd050f0a50b8f85513f46908041e4b8 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:41:55 -0700 Subject: [PATCH 31/46] Fix query timing in verbose output to show actual DNS query duration --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 4e67240..eddf50b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -272,7 +272,8 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 TransportType::TLS => "TLS", TransportType::HTTPS => "HTTPS", }); - println!("Query for {} {} on {} ({}) finished in {}ms", domain, qtype, nameserver_str, transport, elapsed.as_millis()); + let duration_ms = elapsed.as_secs_f64() * 1000.0; + println!("Query for {} {} on {} ({}) finished in {:.2}ms", domain, qtype, nameserver_str, transport, duration_ms); } match result { From 07bc08c82cf4c77d430aad022380fcfde7410073 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:49:47 -0700 Subject: [PATCH 32/46] Improve verbose output granularity for total runtime to match per-query precision --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index eddf50b..d4572c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -318,7 +318,8 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 else { let duration = timer.map(|t| t.elapsed()); if let Some(duration) = duration { - println!("Ran in {}ms", duration.as_millis()); + let duration_ms = duration.as_secs_f64() * 1000.0; + println!("Ran in {:.2}ms", duration_ms); } if errored { From 5e892533c9e7d3bc4f38472dcf2f715557dc3ea5 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:53:40 -0700 Subject: [PATCH 33/46] Update rust.yml --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 93425e8..000bb2c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ "rustls-only" ] + branches: [ "master" ] pull_request: - branches: [ "rustls-only" ] + branches: [ "master" ] env: CARGO_TERM_COLOR: always From b51c9b8a6038a0d1f6ec4acb573dca5544507914 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:02:30 -0700 Subject: [PATCH 34/46] Fix TLS transport parsing and SNI handling --- src/main.rs | 10 +++++++--- src/options.rs | 14 ++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index d4572c4..ee9fbfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -160,7 +160,11 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 } let config = if requests.inputs.nameservers.is_empty() { - ResolverConfig::default() + match requests.inputs.transport_type { + Some(TransportType::TLS) => ResolverConfig::cloudflare_tls(), + Some(TransportType::HTTPS) => ResolverConfig::google_https(), + _ => ResolverConfig::default(), + } } else { let mut config = ResolverConfig::new(); for ns_str in &requests.inputs.nameservers { @@ -200,12 +204,12 @@ async fn run(Options { requests, format, measure_time, verbose }: Options) -> i3 let ip_addr: IpAddr = if let Ok(ip) = ns_str.parse::() { if protocol == Protocol::Tls || protocol == Protocol::Https { - tls_dns_name = Some(ns_str.clone()); + tls_dns_name = if ns_str == "8.8.8.8" || ns_str == "8.8.4.4" || ns_str == "2001:4860:4860::8888" || ns_str == "2001:4860:4860::8844" { Some("dns.google".to_string()) } else if ns_str == "1.1.1.1" || ns_str == "1.0.0.1" || ns_str == "2606:4700:4700::1111" || ns_str == "2606:4700:4700::1001" { Some("cloudflare-dns.com".to_string()) } else { Some(ns_str.clone()) }; } ip } else { if protocol == Protocol::Tls || protocol == Protocol::Https { - tls_dns_name = Some(ns_str.clone()); + tls_dns_name = if ns_str == "8.8.8.8" || ns_str == "8.8.4.4" || ns_str == "2001:4860:4860::8888" || ns_str == "2001:4860:4860::8844" { Some("dns.google".to_string()) } else if ns_str == "1.1.1.1" || ns_str == "1.0.0.1" || ns_str == "2606:4700:4700::1111" || ns_str == "2606:4700:4700::1001" { Some("cloudflare-dns.com".to_string()) } else { Some(ns_str.clone()) }; } let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); diff --git a/src/options.rs b/src/options.rs index c9074ee..7c5c619 100644 --- a/src/options.rs +++ b/src/options.rs @@ -144,11 +144,17 @@ impl Options { OptionsResult::ListTypes } else { - let transport_type = match TransportType::from_args(matches.free.iter()) { - Ok(tt) => tt, - Err(e) => return OptionsResult::InvalidOptions(e), + let transport_type = if matches.opt_present("udp") { + Some(TransportType::UDP) + } else if matches.opt_present("tcp") { + Some(TransportType::TCP) + } else if matches.opt_present("tls") { + Some(TransportType::TLS) + } else if matches.opt_present("https") { + Some(TransportType::HTTPS) + } else { + None }; - match Self::deduce(matches, transport_type) { Ok(opts) => { if opts.requests.inputs.domains.is_empty() { From 4f4665897f8e91c0ee2030f99447d1fb9dbfbbeb Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:33:33 -0700 Subject: [PATCH 35/46] Remove --time option in favor of --verbose timing - Remove measure_time from Options struct and related logic - Update shell completions for fish, PowerShell, and zsh - Update README, man page, and usage help message - Remove unused from_args function and MultipleTransports error - All tests pass and compile warnings resolved --- README.md | 2 +- completions/dog.fish | 5 +++-- completions/dog.ps1 | 32 +++++++++++++++-------------- completions/dog.zsh | 5 +++-- man/dog.1.md | 3 +-- src/main.rs | 4 ++-- src/options.rs | 48 +++++++------------------------------------- src/usage.txt | 7 ++++++- 8 files changed, 40 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 03b9fe2..ba5f1d6 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ It has colourful output, understands normal command-line argument syntax, suppor -J, --json Display the output as JSON --color, --colour=WHEN When to colourise the output (always, automatic, never) --seconds Do not format durations, display them as seconds - --time Print how long the response took to arrive + ### Meta options diff --git a/completions/dog.fish b/completions/dog.fish index 5b92417..260f5fa 100644 --- a/completions/dog.fish +++ b/completions/dog.fish @@ -1,6 +1,8 @@ # Meta options -complete -c dog -s 'v' -l 'version' -d "Show version of dog" +complete -c dog -s 'V' -l 'version' -d "Show version of dog" complete -c dog -s '?' -l 'help' -d "Show list of command-line options" +complete -c dog -s 'l' -l 'list' -d "List known DNS record types" +complete -c dog -s 'v' -l 'verbose' -d "Print verbose information" # Query options complete -c dog -x -a "(__fish_print_hostnames) A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT IN CH HS" @@ -43,4 +45,3 @@ complete -c dog -l 'colour' -d "When to colourise the output" -x -a " never\t'Never use colours' " complete -c dog -l 'seconds' -d "Do not format durations, display them as seconds" -complete -c dog -l 'time' -d "Print how long the response took to arrive" diff --git a/completions/dog.ps1 b/completions/dog.ps1 index c6a70bf..30117f6 100644 --- a/completions/dog.ps1 +++ b/completions/dog.ps1 @@ -53,7 +53,7 @@ Register-ArgumentCompleter -Native -CommandName 'dog' -ScriptBlock { # if using =, complete including the option name and = $completions = $completions | ForEach-Object { "$previousArg=$_" } } - } + } else { # if not completing option value, offer DNS type values first $completions += $dnsTypeValues @@ -65,20 +65,22 @@ Register-ArgumentCompleter -Native -CommandName 'dog' -ScriptBlock { '-n', '--nameserver', '--class', '--edns', - '--txid', - '-Z', - '-U', '--udp', - '-T', '--tcp', - '-S', '--tls', - '-H', '--https', - '-1', '--short', - '-J', '--json', - '--color', '--colour', - '--seconds', - '--time', - '-?', '--help', - '-v', '--version' - ) | Sort-Object + [string[]]$allOptions = @( + '--txid', + '-Z', + '-U', '--udp', + '-T', '--tcp', + '-S', '--tls', + '-H', '--https', + '-1', '--short', + '-J', '--json', + '--color', '--colour', + '--seconds', + '-?', '--help', + '-V', '--version', + '-v', '--verbose', + '-l', '--list' + ) | Sort-Object $completions += $allOptions } diff --git a/completions/dog.zsh b/completions/dog.zsh index 5a84e7d..c932309 100644 --- a/completions/dog.zsh +++ b/completions/dog.zsh @@ -2,8 +2,10 @@ __dog() { _arguments \ - "(- 1 *)"{-v,--version}"[Show version of dog]" \ + "(- 1 *)"{-V,--version}"[Show version of dog]" \ "(- 1 *)"{-\?,--help}"[Show list of command-line options]" \ + "(- 1 *)"{-l,--list}"[List known DNS record types]" \ + "(- 1 *)"{-v,--verbose}"[Print verbose information]" \ {-q,--query}"[Host name or domain name to query]::_hosts" \ {-t,--type}"[Type of the DNS record being queried]:(record type):(A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT)" \ {-n,--nameserver}"[Address of the nameserver to send packets to]::_hosts;" \ @@ -19,7 +21,6 @@ __dog() { {-J,--json}"[Display the output as JSON]" \ {--color,--colour}"[When to use terminal colours]:(setting):(always automatic never)" \ --seconds"[Do not format durations, display them as seconds]" \ - --time"[Print how long the response took to arrive"] \ '*:filename:_hosts' } diff --git a/man/dog.1.md b/man/dog.1.md index 8b13968..cc879b0 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -102,8 +102,7 @@ OUTPUT OPTIONS `--seconds` : Do not format durations as hours and minutes; instead, display them as seconds. -`--time` -: Print how long the response took to arrive. + META OPTIONS diff --git a/src/main.rs b/src/main.rs index ee9fbfb..fc9824b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,12 +136,12 @@ fn version_bland() -> &'static str { /// # Returns /// /// * The process exit code. -async fn run(Options { requests, format, measure_time, verbose }: Options) -> i32 { +async fn run(Options { requests, format, verbose }: Options) -> i32 { use std::time::Instant; use std::net::{IpAddr, SocketAddr}; let mut responses = Vec::new(); - let timer = if measure_time || verbose { Some(Instant::now()) } else { None }; + let timer = if verbose { Some(Instant::now()) } else { None }; let mut errored = false; diff --git a/src/options.rs b/src/options.rs index 7c5c619..aeed868 100644 --- a/src/options.rs +++ b/src/options.rs @@ -18,9 +18,6 @@ pub struct Options { /// The requests to make and how they should be generated. pub requests: Requests, - /// Whether to display the time taken after every query. - pub measure_time: bool, - /// Whether to display verbose information. pub verbose: bool, @@ -48,37 +45,7 @@ pub enum TransportType { HTTPS, } -impl TransportType { - fn from_args(args: C) -> Result, OptionsError> - where C: IntoIterator, - C::Item: AsRef, - { - let mut transports = Vec::new(); - for arg in args { - let arg = arg.as_ref().to_string_lossy(); - if arg == "--udp" || arg == "-U" { - transports.push(Self::UDP); - } - else if arg == "--tcp" || arg == "-T" { - transports.push(Self::TCP); - } - else if arg == "--tls" || arg == "-S" { - transports.push(Self::TLS); - } - else if arg == "--https" || arg == "-H" { - transports.push(Self::HTTPS); - } - } - - if transports.len() > 1 { - Err(OptionsError::MultipleTransports) - } - else { - Ok(transports.pop()) - } - } -} impl Options { @@ -119,7 +86,6 @@ impl Options { opts.optflag ("J", "json", "Display the output as JSON"); opts.optflag ("", "seconds", "Do not format durations, display them as seconds"); opts.optflag ("1", "short", "Short mode: display nothing but the first result"); - opts.optflag ("", "time", "Print how long the response took to arrive"); // Meta options opts.optflag ("V", "version", "Print version information"); @@ -173,12 +139,11 @@ impl Options { /// Deduce the options from the command-line matches. fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { - let measure_time = matches.opt_present("time"); let verbose = matches.opt_present("verbose"); let format = OutputFormat::deduce(&matches); let requests = Requests::deduce(matches, transport_type)?; - Ok(Self { requests, measure_time, verbose, format }) + Ok(Self { requests, verbose, format }) } } @@ -450,15 +415,14 @@ pub enum HelpReason { pub enum OptionsError { /// The query type is invalid. InvalidQueryType(String), - /// Multiple transport types were specified. - MultipleTransports, + } impl fmt::Display for OptionsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt), - Self::MultipleTransports => write!(f, "Only one transport type can be specified"), + } } } @@ -583,8 +547,10 @@ mod test { #[test] fn an_unrelated_argument() { - assert_eq!(Options::getopts(&[ "--time" ]), - OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic)); + match Options::getopts(&[ "--time" ]) { + OptionsResult::InvalidOptionsFormat(_) => (), + _ => panic!("Expected InvalidOptionsFormat for unknown option"), + } } // query tests diff --git a/src/usage.txt b/src/usage.txt index c4768ce..060e263 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -9,6 +9,11 @@ Query options: -n, --nameserver=ADDR Address of the nameserver to send packets to --class=CLASS Network class of the DNS record being queried (IN, CH, HS) +Sending options: + --edns=SETTING Whether to OPT in to EDNS (disable, hide, show) + --txid=NUMBER Set the transaction ID to a specific value + -Z, Configure uncommon protocol-level tweaks + Protocol options: -U, --udp Use the DNS protocol over UDP -T, --tcp Use the DNS protocol over TCP @@ -20,9 +25,9 @@ Output options: -J, --json Display the output as JSON --color, --colour=WHEN When to colourise the output (always, automatic, never) --seconds Do not format durations, display them as seconds - --time Print how long the response took to arrive Meta options: + -v, --verbose Print verbose information -V, --version Print version information -?, --help Print list of command-line options -l, --list List known DNS record types From c2641e5dcdf597e5ff6860897796c3a6e28de25b Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:48:20 -0700 Subject: [PATCH 36/46] Update default DNS resolver to load system configuration cross-platform - Replace ResolverConfig::default() with manual loading of system DNS servers - On Unix: Parse /etc/resolv.conf to get nameservers - On Windows: Use ipconfig crate to retrieve DNS servers from adapters - Add fallback to Google DNS if no system nameservers found - Add comments explaining the cross-platform logic - Suppress false positive unused import warnings Fixes issue where default resolver didn't use system's DNS server on some platforms. --- src/main.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index fc9824b..dcea5fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,12 @@ use hickory_resolver::config::{ResolverConfig, ResolverOpts, NameServerConfig, P use hickory_resolver::error::ResolveErrorKind; use std::collections::HashSet; +use std::fs; +#[allow(unused_imports)] +use std::net::{IpAddr, SocketAddr}; + +// Windows-specific import for retrieving system DNS servers via ipconfig +#[cfg(windows)] use ipconfig; mod colours; mod hints; @@ -159,11 +165,56 @@ async fn run(Options { requests, format, verbose }: Options) -> i32 { } } + // Load DNS resolver configuration: use system defaults if no custom nameservers provided let config = if requests.inputs.nameservers.is_empty() { match requests.inputs.transport_type { Some(TransportType::TLS) => ResolverConfig::cloudflare_tls(), Some(TransportType::HTTPS) => ResolverConfig::google_https(), - _ => ResolverConfig::default(), + _ => { + // Cross-platform loading of system DNS servers for UDP/TCP transport + let nameservers: Vec = if cfg!(target_os = "windows") { + #[cfg(windows)] + { + // On Windows, use ipconfig to retrieve DNS servers from network adapters + ipconfig::get_adapters() + .unwrap_or_default() + .into_iter() + .flat_map(|adapter| adapter.dns_servers) + .collect() + } + #[cfg(not(windows))] + { + vec![] + } + } else { + // On Unix/Linux, parse /etc/resolv.conf for DNS server entries + match fs::read_to_string("/etc/resolv.conf") { + Ok(content) => { + content.lines() + .filter_map(|line| { + let line = line.trim(); + line.strip_prefix("nameserver ")? + .trim() + .parse::() + .ok() + }) + .collect() + } + Err(_) => vec![], + } + }; + let mut config = ResolverConfig::new(); + for ns in nameservers { + let socket_addr = SocketAddr::new(ns, 53); + let ns_config = NameServerConfig::new(socket_addr, Protocol::Udp); + config.add_name_server(ns_config); + } + if config.name_servers().is_empty() { + ResolverConfig::google() + } else { + config + } + } } } else { let mut config = ResolverConfig::new(); From a84c8797ec9ff3842c74c6e329b9ad43248bc4ce Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:04:07 -0700 Subject: [PATCH 37/46] Remove duplicate title line from usage.txt to fix --help output duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Previously, build.rs added the tagline 'dog ○ command-line DNS client' and usage.txt started with the same, causing the --help text to show the description twice. - Now usage.txt starts with 'Usage:' directly, avoiding the duplication while keeping the colored title from build.rs. --- src/usage.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/usage.txt b/src/usage.txt index 060e263..1aab030 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -1,5 +1,3 @@ -dog ○ command-line DNS client - Usage: dog [options] [--] From acc41781c521c5c9dcb33a633a1b844fa5ccb195 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:27:04 -0700 Subject: [PATCH 38/46] Update version to 0.4.1-pre and remove unused feature checks - Change version in Cargo.toml and Cargo.lock from 0.4.0-pre to 0.4.1-pre - Update man/dog.1.md header to reflect new version - Remove unused feature-checking code from build.rs that added [-idna, -https] to version output - The features did not exist as Cargo features nor gate any code, so they were misleading --- Cargo.lock | 2 +- Cargo.toml | 2 +- build.rs | 31 +++---------------------------- man/dog.1.md | 2 +- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9afebb7..fd8a33e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,7 +139,7 @@ dependencies = [ [[package]] name = "dog" -version = "0.4.0-pre" +version = "0.4.1-pre" dependencies = [ "ansi_term", "atty", diff --git a/Cargo.toml b/Cargo.toml index 5633234..acb95d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ exclude = [ ] homepage = "https://dns.lookup.dog/" license = "EUPL-1.2" -version = "0.4.0-pre" +version = "0.4.1-pre" [[bin]] diff --git a/build.rs b/build.rs index 89eb6ac..49ab017 100644 --- a/build.rs +++ b/build.rs @@ -114,41 +114,16 @@ fn cargo_version() -> String { } /// Returns the version and build parameters string. +/// Previously appended feature indicators (-idna, -https), but these were removed +/// as they did not correspond to any actual Cargo features or gated code. fn version_string() -> String { - let mut ver = cargo_version(); - - let feats = nonstandard_features_string(); - if ! feats.is_empty() { - ver.push_str(&format!(" [{}]", &feats)); - } - - ver -} - -/// Finds whether a feature is enabled by examining the Cargo variable. -fn feature_enabled(name: &str) -> bool { - env::var(format!("CARGO_FEATURE_{}", name)) - .map(|e| ! e.is_empty()) - .unwrap_or(false) + cargo_version() } -/// A comma-separated list of non-standard feature choices. -fn nonstandard_features_string() -> String { - let mut s = Vec::new(); - if ! feature_enabled("WITH_IDNA") { - s.push("-idna"); - } - if ! feature_enabled("WITH_HTTPS") { - s.push("-https"); - } - - s.join(", ") -} - /// Formats the current date as an ISO 8601 string. fn build_date() -> String { diff --git a/man/dog.1.md b/man/dog.1.md index cc879b0..729a451 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -1,4 +1,4 @@ -% dog(1) v0.4.0-pre +% dog(1) 0.4.1-pre From c6e9b31dccd61d6662c0f69734fc740397aae15b Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:31:18 -0700 Subject: [PATCH 39/46] Add install-release recipe to Justfile - Adds a new recipe that installs the release version locally using 'cargo install --path .' - Useful for quickly installing the updated binary after builds --- Justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Justfile b/Justfile index f105ab4..0204b62 100644 --- a/Justfile +++ b/Justfile @@ -18,6 +18,10 @@ export DOG_DEBUG := "" cargo build --release --verbose strip "${CARGO_TARGET_DIR:-target}/release/dog" +# install the dog binary locally (release version) +@install-release: + cargo install --path . + # produce an HTML chart of compilation timings @build-time: cargo +nightly clean From 6c10b0447ee29468efd9800f75b02c44fdffba25 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:34:40 -0700 Subject: [PATCH 40/46] Remove obsolete fuzz and mutation recipes from Justfile - Removed fuzz, fuzz-hex, fuzz-clean recipes that referenced non-existent dns crate - Removed test-mutation recipe that referenced non-existent dns package and features - Project now uses hickory-resolver instead of local dns crate --- Justfile | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Justfile b/Justfile index 0204b62..ea08618 100644 --- a/Justfile +++ b/Justfile @@ -52,28 +52,10 @@ export DOG_DEBUG := "" @test-quick: cargo test --workspace --no-default-features -- --quiet -# run mutation tests -@test-mutation: - cargo +nightly test --package dns --features=dns/with_mutagen -- --quiet - cargo +nightly mutagen --package dns --features=dns/with_mutagen -#---------# -# fuzzing # -#---------# -# run fuzzing on the dns crate -@fuzz: - cargo +nightly fuzz --version - cd dns; cargo +nightly fuzz run fuzz_parsing -- -jobs=`nproc` -workers=`nproc` -runs=69105 -# print out the data that caused crashes during fuzzing as hexadecimal -@fuzz-hex: - for crash in dns/fuzz/artifacts/fuzz_parsing/crash-*; do echo; echo $crash; hexyl $crash; done - -# remove fuzz log files -@fuzz-clean: - rm dns/fuzz/fuzz-*.log #-----------------------# From 82c029424d547024f302d14cdbf7f3f57e45c102 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:41:08 -0700 Subject: [PATCH 41/46] Update all dependencies to latest compatible versions - Updated hickory-resolver to 0.24 (latest stable) - Updated tokio, rand, and other deps to latest within compatibility - Removed obsolete fuzz and mutation recipes - Pinned to stable hickory-resolver to avoid breaking changes in 0.25 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd8a33e..71b0497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.37" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ "find-msvc-tools", "shlex", @@ -175,9 +175,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "fnv" diff --git a/Cargo.toml b/Cargo.toml index acb95d5..f3ae48b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ panic = "abort" [dependencies] -hickory-resolver = { version = "0.24.1", features = ["dns-over-rustls", "dns-over-https-rustls"] } +hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dns-over-https-rustls"] } tokio = { version = "1", features = ["full"] } # command-line From 4ad9de366012dc6b1202a10fc7991e505ac44624 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:46:26 -0700 Subject: [PATCH 42/46] Revert code changes for hickory-resolver 0.24 compatibility - Fix indentation in resolver config matching - Pass &data to record_payload_summary as expected --- src/main.rs | 18 +++++++++--------- src/table.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index dcea5fc..e9aac62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -222,17 +222,17 @@ async fn run(Options { requests, format, verbose }: Options) -> i32 { if let Some(transport) = requests.inputs.transport_type { match (ns_str.as_str(), transport) { ("google", TransportType::HTTPS) => { - config = ResolverConfig::google_https(); - continue; - } + config = ResolverConfig::google_https(); + continue; + } ("cloudflare", TransportType::HTTPS) => { - config = ResolverConfig::cloudflare_https(); - continue; - } + config = ResolverConfig::cloudflare_https(); + continue; + } ("cloudflare" | "one.one.one.one", TransportType::TLS) => { - config = ResolverConfig::cloudflare_tls(); - continue; - } + config = ResolverConfig::cloudflare_tls(); + continue; + } _ => {} } } diff --git a/src/table.rs b/src/table.rs index 99bc108..f6fc6fa 100644 --- a/src/table.rs +++ b/src/table.rs @@ -51,7 +51,7 @@ impl Table { if let Some(data) = record.data() { let qtype = self.coloured_record_type(&record); let qname = record.name().to_string(); - let summary = self.text_format.record_payload_summary(data); + let summary = self.text_format.record_payload_summary(&data); let ttl = Some(self.text_format.format_duration(record.ttl())); self.rows.push(Row { qtype, qname, ttl, summary, section }); } From 4c8ddb6d787e0e8e15c69155d65cfce39663d030 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:33:27 -0700 Subject: [PATCH 43/46] Add Nu shell completions --- completions/dog.nu | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 completions/dog.nu diff --git a/completions/dog.nu b/completions/dog.nu new file mode 100644 index 0000000..7215753 --- /dev/null +++ b/completions/dog.nu @@ -0,0 +1,95 @@ +# Nu shell completions for dog + +# Query options completers +def "nu-complete dog types" [] { + [ + "A" + "AAAA" + "ANAME" + "ANY" + "AXFR" + "CAA" + "CNAME" + "DNSKEY" + "DS" + "HINFO" + "HTTPS" + "IXFR" + "MX" + "NAPTR" + "NS" + "NULL" + "OPENPGPKEY" + "OPT" + "PTR" + "SOA" + "SRV" + "SSHFP" + "SVCB" + "TLSA" + "TXT" + "RRSIG" + "NSEC" + "NSEC3" + "NSEC3PARAM" + "TSIG" + ] | str upcase +} + +def "nu-complete dog classes" [] { + [ + "IN" + "CH" + "HS" + ] | str upcase +} + +def "nu-complete dog edns" [] { + [ + "disable" + "hide" + "show" + ] +} + +def "nu-complete dog tweaks" [] { + [ + "aa" + "ad" + "bufsize=" + "cd" + ] +} + +def "nu-complete dog colors" [] { + [ + "always" + "automatic" + "never" + ] +} + +# Define the external command with completions +extern "dog" [ + --query(-q): string # Host name or domain name to query + --type(-t): string@"nu-complete dog types" # Type of the DNS record being queried + --nameserver(-n): string # Address of the nameserver to send packets to + --class: string@"nu-complete dog classes" # Network class of the DNS record being queried + --edns: string@"nu-complete dog edns" # Whether to OPT in to EDNS + --txid: string # Set the transaction ID to a specific value + -Z: string@"nu-complete dog tweaks" # Set uncommon protocol tweaks + --color: string@"nu-complete dog colors" # When to use terminal colors + --colour: string@"nu-complete dog colors" # When to use terminal colours + --udp(-U) # Use the DNS protocol over UDP + --tcp(-T) # Use the DNS protocol over TCP + --tls(-S) # Use the DNS-over-TLS protocol + --https(-H) # Use the DNS-over-HTTPS protocol + --short(-1) # Short mode: display nothing but the first result + --json(-J) # Display the output as JSON + --seconds # Do not format durations, display them as seconds + --version(-V) # Print version information + --help(-?) # Print list of command-line options + --list(-l) # List known DNS record types + --verbose(-v) # Print verbose information + ...domain: string # The domain to query +] From a69cf9a26523027ca55502b683eb7414eb0be17a Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Sat, 25 Oct 2025 13:03:12 -0700 Subject: [PATCH 44/46] Add man-local recipe to install man pages in ~/.local/man/man1/ --- Justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Justfile b/Justfile index ea08618..73ee12c 100644 --- a/Justfile +++ b/Justfile @@ -104,6 +104,9 @@ export DOG_DEBUG := "" # build and preview the man page @man-preview: man man "${CARGO_TARGET_DIR:-target}/man/dog.1" +@man-local: man + mkdir -p ~/.local/man/man1 + cp "${CARGO_TARGET_DIR:-target}/man/dog.1" ~/.local/man/man1/ #-----------# From 96cf038e9d883f18d91bee8ec768211e1d586f1e Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Mon, 8 Dec 2025 08:05:28 -0800 Subject: [PATCH 45/46] Test: Add unit tests for reverse lookup domain generation --- src/options.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/options.rs b/src/options.rs index aeed868..3283aa2 100644 --- a/src/options.rs +++ b/src/options.rs @@ -700,4 +700,22 @@ mod test { assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "999999" ]), OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("999999".into()))); } + + // reverse lookup tests + + /// Verifies that IPv4 addresses are correctly converted to in-addr.arpa domains + #[test] + fn reverse_lookup_ipv4() { + let ip: IpAddr = "8.8.4.4".parse().unwrap(); + assert_eq!(reverse_lookup_domain(ip), "4.4.8.8.in-addr.arpa"); + } + + /// Verifies that IPv6 addresses are correctly converted to ip6.arpa domains + #[test] + fn reverse_lookup_ipv6() { + let ip: IpAddr = "2001:4860:4860::8888".parse().unwrap(); + // 2001:4860:4860:0000:0000:0000:0000:8888 + // reverse nibbles... + assert_eq!(reverse_lookup_domain(ip), "8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa"); + } } From 421f9a30818c5db28973e04cd5111f2b1e885035 Mon Sep 17 00:00:00 2001 From: Ken Tobias <634380+l1a@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:11:54 -0800 Subject: [PATCH 46/46] chore: Add VS Code workspace file --- dog.code-workspace | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 dog.code-workspace diff --git a/dog.code-workspace b/dog.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/dog.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file