diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..000bb2c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +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 diff --git a/Cargo.lock b/Cargo.lock index b5fe1bb..71b0497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,19 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 4 + [[package]] name = "addr2line" -version = "0.14.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ansi_term" @@ -24,6 +26,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -37,15 +50,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.56" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -53,181 +66,237 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.13.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" -version = "1.2.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] -name = "bumpalo" -version = "3.7.0" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] -name = "byteorder" -version = "1.4.3" +name = "cc" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] -name = "cc" -version = "1.0.67" +name = "cfg-if" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "data-encoding" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] -name = "core-foundation" -version = "0.9.1" +name = "datetime" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ - "core-foundation-sys", "libc", + "redox_syscall 0.1.57", + "winapi", ] [[package]] -name = "core-foundation-sys" -version = "0.8.2" +name = "diff" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] -name = "ctor" -version = "0.1.20" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ + "proc-macro2", "quote", "syn", ] [[package]] -name = "datetime" -version = "0.5.1" +name = "dog" +version = "0.4.1-pre" +dependencies = [ + "ansi_term", + "atty", + "datetime", + "futures", + "getopts", + "hickory-resolver", + "ipconfig", + "json", + "log", + "pretty_assertions", + "rand", + "tokio", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fcb4df22ae812fa2f6d5e3b577247584cc67fce06ad0779168d1dd41cbcce3" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "libc", - "redox_syscall 0.1.57", - "winapi", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "diff" -version = "0.1.12" +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.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] -name = "dns" -version = "0.2.0-pre" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "base64", - "byteorder", - "log", - "mutagen", - "pretty_assertions", - "unic-idna", + "percent-encoding", ] [[package]] -name = "dns-transport" -version = "0.2.0-pre" +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "cfg-if", - "dns", - "httparse", - "log", - "native-tls", - "rustls", - "webpki", - "webpki-roots", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "dog" -version = "0.2.0-pre" +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "ansi_term", - "atty", - "datetime", - "dns", - "dns-transport", - "getopts", - "ipconfig", - "json", - "log", - "pretty_assertions", - "rand", + "futures-core", + "futures-sink", ] [[package]] -name = "failure" -version = "0.1.8" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ - "backtrace", - "failure_derive", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "failure_derive" -version = "0.1.8" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +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", - "synstructure", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "futures-sink" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] -name = "foreign-types-shared" -version = "0.1.1" +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 = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] [[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", @@ -236,269 +305,459 @@ dependencies = [ [[package]] name = "gimli" -version = "0.23.0" +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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[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" +name = "hickory-proto" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +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", + "rustls", + "rustls-pemfile", + "thiserror", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", +] [[package]] -name = "ipconfig" -version = "0.2.2" +name = "hickory-resolver" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ - "socket2", - "widestring", - "winapi", - "winreg", + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "rustls", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls", + "tracing", ] [[package]] -name = "itoa" -version = "0.4.7" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] [[package]] -name = "js-sys" -version = "0.3.49" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ - "wasm-bindgen", + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "json" -version = "0.12.4" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "icu_normalizer" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] [[package]] -name = "libc" -version = "0.2.91" +name = "icu_normalizer_data" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] -name = "log" -version = "0.4.14" +name = "icu_properties" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ - "cfg-if", + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", ] [[package]] -name = "matches" -version = "0.1.8" +name = "icu_properties_data" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "icu_provider" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ - "adler", - "autocfg", + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "mutagen" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "mutagen-core", - "mutagen-transform", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "mutagen-core" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "failure", - "json", - "lazy_static", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "mutagen-transform" -version = "0.2.0" -source = "git+https://github.com/llogiq/mutagen#933bbaf4edaa22f6237b2201dff2940ff7f4193c" +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ - "mutagen-core", - "proc-macro2", + "equivalent", + "hashbrown", ] [[package]] -name = "native-tls" -version = "0.2.7" +name = "io-uring" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "lazy_static", + "bitflags", + "cfg-if", "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", ] [[package]] -name = "object" -version = "0.23.0" +name = "ipconfig" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] [[package]] -name = "once_cell" -version = "1.7.2" +name = "ipnet" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "libc" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] -name = "openssl" -version = "0.10.33" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", + "autocfg", + "scopeguard", ] [[package]] -name = "openssl-probe" +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] [[package]] -name = "openssl-src" -version = "111.15.0+1.1.1k" +name = "memchr" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a5f6ae2ac04393b217ea9f700cd04fa9bf3d93fae2872069f3d15d908af70a" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "cc", + "adler2", ] [[package]] -name = "openssl-sys" -version = "0.9.61" +name = "mio" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "autocfg", - "cc", "libc", - "openssl-src", - "pkg-config", - "vcpkg", + "wasi", + "windows-sys 0.59.0", ] [[package]] -name = "output_vt100" -version = "0.1.2" +name = "object" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ - "winapi", + "memchr", +] + +[[package]] +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 = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.17", + "smallvec", + "windows-targets 0.52.6", ] [[package]] -name = "pkg-config" -version = "0.3.19" +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] [[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 = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "ansi_term", - "ctor", "diff", - "output_vt100", + "yansi", ] [[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,22 +765,13 @@ 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" @@ -530,119 +780,110 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "resolv-conf" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" [[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", "libc", - "once_cell", - "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustls" -version = "0.19.1" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "base64", "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] -name = "ryu" -version = "1.0.5" +name = "rustls-pemfile" +version = "1.0.4" 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" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "lazy_static", - "winapi", + "base64", ] [[package]] -name = "sct" -version = "0.6.1" +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", ] [[package]] -name = "security-framework" -version = "2.2.0" +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 = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", + "ring", + "untrusted", ] [[package]] -name = "security-framework-sys" -version = "2.2.0" +name = "serde" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ - "core-foundation-sys", - "libc", + "serde_core", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.125" +name = "serde_core" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -650,319 +891,544 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.64" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ - "itoa", - "ryu", - "serde", + "libc", ] +[[package]] +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 = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[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 = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "1.0.65" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "synstructure" -version = "0.12.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", - "unicode-xid", ] [[package]] -name = "tempfile" -version = "3.2.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "cfg-if", - "libc", - "rand", - "redox_syscall 0.2.5", - "remove_dir_all", - "winapi", + "thiserror-impl", ] [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "unic-char-range", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unic-char-range" -version = "0.9.0" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] -name = "unic-common" -version = "0.9.0" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] [[package]] -name = "unic-idna" -version = "0.9.0" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621e9cf526f2094d2c2ced579766458a92f8f422d6bb934c503ba1a95823a62d" -dependencies = [ - "matches", - "unic-idna-mapping", - "unic-idna-punycode", - "unic-normal", - "unic-ucd-bidi", - "unic-ucd-normal", - "unic-ucd-version", -] +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "unic-idna-mapping" -version = "0.9.0" +name = "tokio" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de70fd4e5331537347a50a0dbc938efb1f127c9f6e5efec980fc90585aa1343" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "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-idna-punycode" -version = "0.9.0" +name = "tokio-macros" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06feaedcbf9f1fc259144d833c0d630b8b15207b0486ab817d29258bc89f2f8a" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "unic-normal" -version = "0.9.0" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09d64d33589a94628bc2aeb037f35c2e25f3f049c7348b5aa5580b48e6bba62" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "unic-ucd-normal", + "rustls", + "tokio", ] [[package]] -name = "unic-ucd-bidi" -version = "0.9.0" +name = "tokio-util" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[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]] -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" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "vcpkg" -version = "0.2.11" +name = "url" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" +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 = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[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" +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 = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +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-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.72" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", + "windows-targets 0.52.6", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.72" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "windows-targets 0.52.6", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.72" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "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 = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[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_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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[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_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_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_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 = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.72" +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 = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] -name = "web-sys" -version = "0.3.49" +name = "yoke" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ - "js-sys", - "wasm-bindgen", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "webpki" -version = "0.21.4" +name = "yoke-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ - "ring", - "untrusted", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "webpki-roots" -version = "0.21.1" +name = "zerocopy" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "webpki", + "zerocopy-derive", ] [[package]] -name = "widestring" -version = "0.4.3" +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] [[package]] -name = "winapi" -version = "0.3.9" +name = "zerofrom-derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "zerovec" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] [[package]] -name = "winreg" -version = "0.6.2" +name = "zerovec-derive" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ - "winapi", + "proc-macro2", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 0e6a3d0..f3ae48b 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.4.1-pre" [[bin]] @@ -20,16 +20,12 @@ path = "src/main.rs" doctest = false -[workspace] -members = [ - "dns", - "dns-transport", -] - - # 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] @@ -39,10 +35,8 @@ panic = "abort" [dependencies] - -# dns stuff -dns = { path = "./dns" } -dns-transport = { path = "./dns-transport" } +hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dns-over-https-rustls"] } +tokio = { version = "1", features = ["full"] } # command-line ansi_term = "0.12" @@ -55,26 +49,18 @@ rand = "0.8" # json output json = "0.12" +# async utilities +futures = "0.3" + # logging 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" - -[features] -default = ["with_idna", "with_tls", "with_https", "with_nativetls"] -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"] +pretty_assertions = "1.4" diff --git a/Justfile b/Justfile index beca6b8..73ee12c 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 := "" @@ -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 @@ -48,58 +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 - - -#------------------------# -# 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 # -#---------# - -# 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 #-----------------------# @@ -126,12 +82,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 @@ -154,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/ #-----------# diff --git a/README.md b/README.md index 1721b71..ba5f1d6 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,6 @@ It has colourful output, understands normal command-line argument syntax, suppor -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 @@ -60,9 +54,21 @@ 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 + + -?, --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`. + --- ## Installation @@ -93,10 +99,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. @@ -133,24 +136,6 @@ Note that this will expose your IP address. 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. -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: - -- `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`) - -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: - - $ 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 @@ -165,4 +150,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/build.rs b/build.rs index f941aeb..49ab017 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() } @@ -114,42 +114,15 @@ 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_TLS") { - s.push("-tls"); - } - if ! feature_enabled("WITH_HTTPS") { - s.push("-https"); - } - s.join(", ") -} /// Formats the current date as an ISO 8601 string. 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.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 +] 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/dns-transport/Cargo.toml b/dns-transport/Cargo.toml deleted file mode 100644 index 67552c3..0000000 --- a/dns-transport/Cargo.toml +++ /dev/null @@ -1,42 +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 -native-tls = { version = "0.2", optional = true } - -# http response parsing -httparse = { version = "1.3", optional = true } - -rustls = { version = "0.19", optional = true } - -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/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 ae140d8..0000000 --- a/dns-transport/src/error.rs +++ /dev/null @@ -1,79 +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, - - /// 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")] - RustlsInvalidDnsNameError(webpki::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) - } -} - -#[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 { - fn from(inner: webpki::InvalidDNSNameError) -> Self { - Self::RustlsInvalidDnsNameError(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 6a3d324..0000000 --- a/dns-transport/src/https.rs +++ /dev/null @@ -1,127 +0,0 @@ -#![cfg_attr(not(feature = "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. -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() -} - -use tls_stream::TlsStream; - -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 = Self::stream(&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 959dbc9..0000000 --- a/dns-transport/src/tls.rs +++ /dev/null @@ -1,81 +0,0 @@ -#![cfg_attr(not(feature = "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 -/// 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 { - - #[cfg(feature = "with_tls")] - 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"); - - Self::stream(domain, port)? - } - else { - Self::stream(&*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) - } - - #[cfg(not(feature = "with_tls"))] - fn send(&self, request: &Request) -> Result { - unreachable!("TLS feature disabled") - } -} - -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 be6443a..0000000 --- a/dns-transport/src/tls_stream.rs +++ /dev/null @@ -1,69 +0,0 @@ -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> { - use std::sync::Arc; - - let mut config = rustls::ClientConfig::new(); - - config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - - let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain)?; - - let conn = rustls::ClientSession::new(&Arc::new(config), dns_name); - - let sock = TcpStream::connect((domain, port))?; - let tls = rustls::StreamOwned::new(conn, sock); - - Ok(tls) -} - -pub trait TlsStream { - 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-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 0dc7ba5..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.3" - -# printing of certain packets -base64 = "0.13" - -# 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 = "0.7" - -[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/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/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 908fb4d..0000000 --- a/dns/src/record/mod.rs +++ /dev/null @@ -1,238 +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 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), - - /// 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, - - /// 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); - - RecordType::Other(UnknownQtype::from(type_number)) - } -} - - -impl RecordType { - - /// 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); - - 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::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/openpgpkey.rs b/dns/src/record/openpgpkey.rs deleted file mode 100644 index 6fccb45..0000000 --- a/dns/src/record/openpgpkey.rs +++ /dev/null @@ -1,91 +0,0 @@ -use log::*; - -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 { - base64::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/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 46489e2..0000000 --- a/dns/src/strings.rs +++ /dev/null @@ -1,325 +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. - 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 ({}) decoding string", offset); - 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); - 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 2559ab3..0000000 --- a/dns/src/wire.rs +++ /dev/null @@ -1,445 +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::(if self.additional.is_some() { 1 } else { 0 })?; // 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 {}x query from response", query_count); - 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); - 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); - 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); - 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 -> {:?}", qtype); - - Ok(Self { qtype, qclass, qname }) - } -} - - -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::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/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 diff --git a/man/dog.1.md b/man/dog.1.md index 34f5f89..729a451 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -1,4 +1,4 @@ -% dog(1) v0.1.0 +% dog(1) 0.4.1-pre @@ -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,13 +97,12 @@ 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. -`--time` -: Print how long the response took to arrive. + META OPTIONS @@ -125,96 +111,27 @@ META OPTIONS `--help` : Displays an overview of the command-line options. -`--version` +`-V`, `--version` : Displays the version of dog being invoked. - -ENVIRONMENT VARIABLES -===================== - -dog responds to the following environment variables: - -## `DOG_DEBUG` - -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`’. +`-l`, `--list` +: List known DNS record types. 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 - -`HINFO` -: system information and, sometimes, forbidden request explanations - -`LOC` -: location information +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`. -`MX` -: e-mail server addresses -`NAPTR` -: DDDS rules - -`NS` -: domain name servers - -`OPT` -: extensions to the DNS protocol - -`PTR` -: pointers to canonical names, usually for reverse lookups - -`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. +ENVIRONMENT VARIABLES +===================== -`ad` -: Sets the `AD` (Authentic Data) bit in the query. +dog responds to the following environment variables: -`bufsize=NUM` -: Sets the UDP payload size field in the OPT field in the query. This has no effect if EDNS is diabled. +## `DOG_DEBUG` -`cd` -: Sets the `CD` (Checking Disabled) bit in the query. +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`’. EXIT STATUSES @@ -232,9 +149,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 ====== @@ -242,4 +156,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 9535722..6b7918c 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -7,33 +7,34 @@ 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, - pub authority: Style, - pub additional: 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, - pub eui48: Style, - pub eui64: Style, - pub hinfo: Style, - pub loc: Style, + /// The style for MX records. pub mx: Style, + /// The style for NS records. pub ns: Style, - pub naptr: Style, - pub openpgpkey: Style, - pub opt: Style, + /// The style for PTR records. pub ptr: Style, - pub sshfp: Style, + /// The style for SOA records. pub soa: Style, + /// The style for SRV records. pub srv: Style, - pub tlsa: Style, + /// The style for TXT records. pub txt: Style, - pub uri: Style, - pub unknown: Style, + /// The style for unknown record types. + pub default: Style, } impl Colours { @@ -43,32 +44,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(), - naptr: Green.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/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 9d39727..e9aac62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,24 +22,33 @@ #![deny(unsafe_code)] use log::*; +use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::config::{ResolverConfig, ResolverOpts, NameServerConfig, Protocol}; +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 connect; mod hints; mod logger; mod output; -mod requests; -mod resolve; mod table; -mod txid; mod options; use self::options::*; +use futures::future::join_all; /// 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,16 +62,15 @@ 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)); + 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 { @@ -75,15 +83,23 @@ 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); } + 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); @@ -96,15 +112,42 @@ 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. -fn run(Options { requests, format, measure_time }: Options) -> i32 { +/// +/// # Arguments +/// +/// * `options` - The command-line options. +/// +/// # Returns +/// +/// * The process exit code. +async fn run(Options { requests, format, verbose }: Options) -> i32 { use std::time::Instant; - - let should_show_opt = requests.edns.should_show(); + use std::net::{IpAddr, SocketAddr}; let mut responses = Vec::new(); - let timer = if measure_time { Some(Instant::now()) } else { None }; + let timer = if verbose { Some(Instant::now()) } else { None }; let mut errored = false; @@ -122,75 +165,224 @@ 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; + // 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(), + _ => { + // 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(); + 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; + } + _ => {} + } + } - 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); + 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 = 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 = 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()) }; + } - match result { - Ok(mut response) => { - if response.flags.error_code.is_some() && i != request_list_len - 1 { - continue; + 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; + } } - - 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); + 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.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(); + futures.push(async move { + let elapsed = query_timer.elapsed(); + let result = resolver_clone.lookup(&domain_str, qtype).await; + (domain_str, qtype, result, elapsed) + }); + } + } + + // 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_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", + TransportType::TCP => "TCP", + TransportType::TLS => "TLS", + TransportType::HTTPS => "HTTPS", + }); + 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 { + Ok(response) => { + if verbose { + format.print(vec![response], None); + } + else { responses.push(response); - break; } - Err(e) => { + } + 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; - break; } } } } - let duration = timer.map(|t| t.elapsed()); - if format.print(responses, duration) { - if errored { - exits::NETWORK_ERROR + if !verbose { + let duration = timer.map(|t| t.elapsed()); + if format.print(responses, duration) { + if errored { + exits::NETWORK_ERROR + } + else { + exits::SUCCESS + } } else { - exits::SUCCESS + exits::NO_SHORT_RESULTS } } else { - exits::NO_SHORT_RESULTS - } -} - - -/// 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(all(not(feature = "with_tls"), not(feature = "with_rustls_tls")))] - 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); - } + let duration = timer.map(|t| t.elapsed()); + if let Some(duration) = duration { + let duration_ms = duration.as_secs_f64() * 1000.0; + println!("Ran in {:.2}ms", duration_ms); + } - #[cfg(all(not(feature = "with_https"), not(feature = "with_rustls_https")))] - 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); + if errored { + exits::NETWORK_ERROR + } + else { + exits::SUCCESS + } } } @@ -210,7 +402,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 bea218c..3283aa2 100644 --- a/src/options.rs +++ b/src/options.rs @@ -2,17 +2,13 @@ use std::ffi::OsStr; use std::fmt; +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. @@ -20,15 +16,37 @@ 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, + /// Whether to display verbose information. + pub verbose: bool, /// How to format the output data. 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, +} + +/// 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 @@ -68,13 +86,14 @@ 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"); + 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) { + let matches = match opts.parse(args.into_iter().collect::>()) { Ok(m) => m, Err(e) => return OptionsResult::InvalidOptionsFormat(e), }; @@ -87,8 +106,22 @@ 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) { + 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() { OptionsResult::Help(HelpReason::NoDomains, uc) @@ -104,72 +137,72 @@ impl Options { } } - fn deduce(matches: getopts::Matches) -> Result { - let measure_time = matches.opt_present("time"); + /// Deduce the options from the command-line matches. + fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { + let verbose = matches.opt_present("verbose"); let format = OutputFormat::deduce(&matches); - let requests = RequestGenerator::deduce(matches)?; + let requests = Requests::deduce(matches, transport_type)?; - Ok(Self { requests, measure_time, format }) + Ok(Self { requests, verbose, format }) } } -impl RequestGenerator { - 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)?; +impl Requests { + /// Deduce the requests from the command-line matches. + fn deduce(matches: getopts::Matches, transport_type: Option) -> Result { + let inputs = Inputs::deduce(matches, transport_type)?; - 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, + + /// 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, +} + + impl Inputs { - fn deduce(matches: getopts::Matches) -> Result { + /// Deduce the inputs from the command-line matches. + 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.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); - } - } - + /// 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)?; + self.add_domain(&domain); } for record_name in matches.opt_strs("type") { - if record_name.eq_ignore_ascii_case("OPT") { - return Err(OptionsError::QueryTypeOPT); + if record_name.eq_ignore_ascii_case("ANY") { + self.add_any_record_types(); } - else if let Some(record_type) = RecordType::from_type_name(&record_name) { + else 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)); } @@ -179,103 +212,90 @@ impl Inputs { 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(()) } + /// 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('@') { - 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); + if argument.eq_ignore_ascii_case("ANY") { + self.add_any_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) { + else 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 { 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); + } } } 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(()) - } - } - + /// Load the fallback values for the inputs. 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())) - } + /// 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); } + /// Add a nameserver to the list of nameservers to use. 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); + 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; + self.record_types.extend_from_slice(&[ + RecordType::A, + RecordType::AAAA, + RecordType::CAA, + RecordType::CNAME, + RecordType::DNSKEY, + RecordType::DS, + RecordType::MX, + RecordType::NS, + RecordType::SOA, + RecordType::SRV, + RecordType::SSHFP, + RecordType::TLSA, + RecordType::TXT, + RecordType::RRSIG, + ]); } } +/// 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, @@ -289,65 +309,29 @@ 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 - } -} - - -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 +/// Returns the reverse lookup domain for an IP address. +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 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); @@ -366,6 +350,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, @@ -381,6 +366,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 } @@ -388,61 +374,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 { @@ -461,6 +392,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, @@ -479,46 +413,80 @@ pub enum HelpReason { /// Something wrong with the combination of options the user has picked. #[derive(PartialEq, Debug)] pub enum OptionsError { - InvalidDomain(String), - InvalidEDNS(String), + /// The query type is invalid. 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"), + } } } +/// 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 { use super::*; use pretty_assertions::assert_eq; - use dns::record::UnknownQtype; impl Inputs { fn fallbacks() -> Self { 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 ], + any_query: false, + transport_type: None, + nameservers: vec![], } } } @@ -558,6 +526,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" ]), @@ -573,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 @@ -583,7 +559,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() }); } @@ -592,7 +568,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() }); } @@ -601,7 +577,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() }); @@ -611,94 +587,51 @@ 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_other_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()) ], - .. 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()) ], + nameservers: vec![ "1.1.1.1".to_string() ], .. 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()) ], + nameservers: vec![ "1.1.1.1".to_string() ], .. 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()) ], + nameservers: vec![ "1.1.1.1".to_string() ], .. Inputs::fallbacks() }); } @@ -707,87 +640,33 @@ 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()) ], + nameservers: vec![ "1.1.1.1".to_string() ], .. 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 }; @@ -808,36 +687,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" ]), @@ -850,86 +701,21 @@ mod test { 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)); - } + // reverse lookup tests + /// Verifies that IPv4 addresses are correctly converted to in-addr.arpa domains #[test] - fn opt_arg() { - assert_eq!(Options::getopts(&[ "-t", "OPT", "lookup.dog" ]), - OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT)); + 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 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); + 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"); } } diff --git a/src/output.rs b/src/output.rs index b776e53..98508dc 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,13 +1,12 @@ //! Text and JSON output. -use std::fmt; use std::time::Duration; use std::env; +use std::io::{self, BufWriter, Write}; -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 +77,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 +88,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 +96,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); @@ -134,28 +122,32 @@ impl OutputFormat { } } Self::Text(uc, tf) => { - let mut table = Table::new(uc.palette(), tf); - - for response in responses { - if let Some(rcode) = response.flags.error_code { - 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); + 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(); } - - for a in response.additionals { - table.add_row(a, Section::Additional); + 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()); } } - table.print(duration); - } + if let Some(duration) = duration { + println!("Ran in {}ms", duration.as_millis()); + } + } } true @@ -163,17 +155,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); @@ -187,117 +178,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::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 @@ -338,400 +220,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::TLSA => "TLSA".into(), - RecordType::TXT => "TXT".into(), - RecordType::URI => "URI".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::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::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", - #[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")] - 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(), - #[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")] - 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 26ff0f6..f6fc6fa 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,107 +47,91 @@ 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. + pub 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. + #[allow(dead_code)] + pub fn print(self, duration: Option) { + print!("{}", self.render()); if let Some(dur) = duration { println!("Ran in {}ms", dur.as_millis()); } } + /// Returns a coloured string for a record type. 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::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()), } } + /// Returns the maximum length of a qtype 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) } + /// Returns the maximum length of a qname string. 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) } + /// 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() + 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(" "), - 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 - } - } -} diff --git a/src/usage.txt b/src/usage.txt index 29934f1..1aab030 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -1,38 +1,31 @@ -\4mUsage:\0m - \1mdog\0m \1;33m[OPTIONS]\0m [--] \32m\0m +Usage: + dog [options] [--] -\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 +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) -\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) +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 -\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 -\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, --verbose Print verbose information + -V, --version Print version information + -?, --help Print list of command-line options + -l, --list List known DNS record types 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)