hickory-dns-0.25.2/.cargo_vcs_info.json0000644000000001410000000000100133500ustar { "git": { "sha1": "527c9f470a418cf6b92da902ea0aaa5749963d59" }, "path_in_vcs": "bin" }hickory-dns-0.25.2/Cargo.lock0000644000002510030000000000100113300ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "async-recursion" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "async-trait" version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" dependencies = [ "bindgen 0.69.5", "cc", "cmake", "dunce", "fs_extra", ] [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.12.1", "lazy_static", "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", "syn", "which", ] [[package]] name = "bindgen" version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "windows-link", ] [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cmake" version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[package]] name = "core-foundation" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-as-inner" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generator" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", "log", "rustversion", "windows", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "h3" version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfb059a4f28a66f186ed16ad912d142f490676acba59353831d7cb45a96b0d3" dependencies = [ "bytes", "fastrand", "futures-util", "http", "pin-project-lite", "tokio", ] [[package]] name = "h3-quinn" version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d482318ae94198fc8e3cbb0b7ba3099c865d744e6ec7c62039ca7b6b6c66fbf" dependencies = [ "bytes", "futures", "h3", "quinn", "tokio", "tokio-util", ] [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", ] [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-client" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c466cd63a4217d5b2b8e32f23f58312741ce96e3c84bf7438677d2baff0fc555" dependencies = [ "cfg-if", "data-encoding", "futures-channel", "futures-util", "hickory-proto", "once_cell", "radix_trie", "rand", "thiserror 2.0.12", "tokio", "tracing", ] [[package]] name = "hickory-dns" version = "0.25.2" dependencies = [ "cfg-if", "clap", "futures-executor", "futures-util", "hickory-client", "hickory-proto", "hickory-resolver", "hickory-server", "http", "hyper", "hyper-util", "ipnet", "libc", "metrics", "metrics-exporter-prometheus", "metrics-process", "prometheus-parse", "regex", "reqwest", "rusqlite", "rustls", "rustls-pki-types", "serde", "socket2", "time", "tokio", "tokio-util", "toml", "tracing", "tracing-subscriber", "webpki-roots", ] [[package]] name = "hickory-proto" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ "async-trait", "aws-lc-rs", "bitflags", "bytes", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", "h2", "h3", "h3-quinn", "http", "idna", "ipnet", "once_cell", "pin-project-lite", "quinn", "rand", "ring", "rustls", "rustls-pki-types", "rustls-platform-verifier", "serde", "thiserror 2.0.12", "time", "tinyvec", "tokio", "tokio-rustls", "tracing", "url", "webpki-roots", ] [[package]] name = "hickory-recursor" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9ec7f3d578a10e8f67a30b3668a324b49c2583e47f04b69ebb32fb735fee41" dependencies = [ "async-recursion", "async-trait", "bytes", "cfg-if", "enum-as-inner", "futures-util", "hickory-proto", "hickory-resolver", "ipnet", "lru-cache", "parking_lot", "prefix-trie", "serde", "thiserror 2.0.12", "tokio", "tracing", ] [[package]] name = "hickory-resolver" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", "moka", "once_cell", "parking_lot", "quinn", "rand", "resolv-conf", "rustls", "serde", "smallvec", "thiserror 2.0.12", "tokio", "tokio-rustls", "tracing", "webpki-roots", ] [[package]] name = "hickory-server" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53e5fe811b941c74ee46b8818228bfd2bc2688ba276a0eaeb0f2c95ea3b2585" dependencies = [ "async-trait", "bytes", "cfg-if", "data-encoding", "enum-as-inner", "futures-util", "h2", "h3", "h3-quinn", "hickory-proto", "hickory-recursor", "hickory-resolver", "http", "ipnet", "metrics", "prefix-trie", "rusqlite", "rustls", "serde", "thiserror 2.0.12", "time", "tokio", "tokio-rustls", "tokio-util", "toml", "tracing", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "libc", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core 0.61.0", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2", "widestring", "windows-sys 0.48.0", "winreg", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" dependencies = [ "serde", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.2", "libc", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.48.5", ] [[package]] name = "libproc" version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" dependencies = [ "bindgen 0.70.1", "errno", "libc", ] [[package]] name = "libsqlite3-sys" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loom" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ "cfg-if", "generator", "scoped-tls", "tracing", "tracing-subscriber", ] [[package]] name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ "linked-hash-map", ] [[package]] name = "mach2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "metrics" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" dependencies = [ "ahash", "portable-atomic", ] [[package]] name = "metrics-exporter-prometheus" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" dependencies = [ "base64", "http-body-util", "hyper", "hyper-util", "indexmap", "ipnet", "metrics", "metrics-util", "quanta", "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "metrics-process" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a82c8add4382f29a122fa64fff1891453ed0f6b2867d971e7d60cb8dfa322ff" dependencies = [ "libc", "libproc", "mach2", "metrics", "once_cell", "procfs", "rlimit", "windows", ] [[package]] name = "metrics-util" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown", "metrics", "quanta", "rand", "rand_xoshiro", "sketches-ddsketch", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "moka" version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "loom", "parking_lot", "portable-atomic", "rustc_version", "smallvec", "tagptr", "thiserror 1.0.69", "uuid", ] [[package]] name = "nibble_vec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ "smallvec", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ "critical-section", "portable-atomic", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[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 = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy 0.8.25", ] [[package]] name = "prefix-trie" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cf4c7c25f1dd66c76b451e9041a8cfce26e4ca754934fa7aed8d5a59a01d20" dependencies = [ "ipnet", "num-traits", ] [[package]] name = "prettyplease" version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ "bitflags", "hex", "procfs-core", "rustix", ] [[package]] name = "procfs-core" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ "bitflags", "hex", ] [[package]] name = "prometheus-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" dependencies = [ "chrono", "itertools 0.12.1", "once_cell", "regex", ] [[package]] name = "quanta" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] [[package]] name = "quinn" version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", "cfg_aliases", "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", "rustls", "socket2", "thiserror 2.0.12", "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "aws-lc-rs", "bytes", "getrandom 0.3.2", "rand", "ring", "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", "thiserror 2.0.12", "tinyvec", "tracing", "web-time", ] [[package]] name = "quinn-udp" version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", "windows-sys 0.52.0", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "radix_trie" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ "endian-type", "nibble_vec", ] [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.2", ] [[package]] name = "rand_xoshiro" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ "rand_core", ] [[package]] name = "raw-cpuid" version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "ipnet", "js-sys", "log", "mime", "once_cell", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "resolv-conf" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted 0.9.0", "windows-sys 0.52.0", ] [[package]] name = "rlimit" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" dependencies = [ "libc", ] [[package]] name = "rusqlite" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" dependencies = [ "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", "time", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] [[package]] name = "rustls-platform-verifier" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4937d110d34408e9e5ad30ba0b0ca3b6a8a390f8db3636db60144ac4fa792750" dependencies = [ "core-foundation", "core-foundation-sys", "jni", "log", "once_cell", "rustls", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.52.0", ] [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] name = "sketches-ddsketch" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_write", "winnow", ] [[package]] name = "toml_write" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom 0.3.2", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-root-certs" version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c99403924bc5f23afefc319b8ac67ed0e50669f6e52a413314cccb1fdbc93ba0" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" dependencies = [ "rustls-pki-types", ] [[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix", ] [[package]] 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 = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "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-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.48.0", ] [[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" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ "windows-core 0.58.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ "windows-implement 0.58.0", "windows-interface 0.58.0", "windows-result 0.2.0", "windows-strings 0.1.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", "windows-result 0.3.2", "windows-strings 0.4.0", ] [[package]] name = "windows-implement" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result 0.3.2", "windows-strings 0.3.1", "windows-targets 0.53.0", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result 0.2.0", "windows-targets 0.52.6", ] [[package]] name = "windows-strings" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[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 = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "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 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[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_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" dependencies = [ "memchr", ] [[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 = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive 0.8.25", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerocopy-derive" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] hickory-dns-0.25.2/Cargo.toml0000644000000141020000000000100113500ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.71.1" name = "hickory-dns" version = "0.25.2" authors = ["The contributors to Hickory DNS"] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ Hickory DNS is a safe and secure DNS server with DNSSEC support. Eventually this could be a replacement for BIND9. The DNSSEC support allows for live signing of all records, in it does not currently support records signed offline. The server supports dynamic DNS with SIG0 authenticated requests. Hickory DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries. """ homepage = "https://hickory-dns.org/" documentation = "https://docs.rs/hickory-dns" readme = "README.md" keywords = [ "DNS", "BIND", "dig", "named", "dnssec", ] categories = ["network-programming"] license = "MIT OR Apache-2.0" repository = "https://github.com/hickory-dns/hickory-dns" [package.metadata.cargo-all-features] denylist = [ "__tls", "__https", "__quic", "__h3", "__dnssec", ] max_combination_size = 2 skip_optional_dependencies = true [features] __dnssec = [] __h3 = [] __https = [] __quic = [] __tls = ["dep:rustls"] ascii-art = [] blocklist = ["hickory-server/blocklist"] default = [ "sqlite", "resolver", "rustls-platform-verifier", "ascii-art", ] dnssec-aws-lc-rs = [ "hickory-server/dnssec-aws-lc-rs", "dep:rustls-pki-types", "__dnssec", ] dnssec-ring = [ "hickory-server/dnssec-ring", "dep:rustls-pki-types", "__dnssec", ] h3-aws-lc-rs = [ "hickory-server/h3-aws-lc-rs", "quic-aws-lc-rs", "__h3", ] h3-ring = [ "hickory-server/h3-ring", "quic-ring", "__h3", ] https-aws-lc-rs = [ "hickory-server/https-aws-lc-rs", "tls-aws-lc-rs", "__https", ] https-ring = [ "hickory-server/https-ring", "tls-ring", "__https", ] metrics = [ "hickory-server/metrics", "dep:metrics", "dep:metrics-process", ] prometheus-metrics = [ "metrics", "dep:http", "dep:hyper", "dep:hyper-util", "dep:metrics-exporter-prometheus", "dep:tokio-util", ] quic-aws-lc-rs = [ "hickory-server/quic-aws-lc-rs", "tls-aws-lc-rs", "__quic", ] quic-ring = [ "hickory-server/quic-ring", "tls-ring", "__quic", ] recursor = ["hickory-server/recursor"] resolver = ["hickory-server/resolver"] rustls-platform-verifier = ["hickory-server/rustls-platform-verifier"] sqlite = [ "hickory-server/sqlite", "dep:rusqlite", ] tls-aws-lc-rs = [ "hickory-server/tls-aws-lc-rs", "__tls", ] tls-ring = [ "hickory-server/tls-ring", "__tls", ] webpki-roots = ["hickory-server/webpki-roots"] [lib] name = "hickory_dns" path = "src/lib.rs" [[bin]] name = "hickory-dns" path = "src/hickory-dns.rs" [[test]] name = "integration" path = "tests/integration/main.rs" [[bench]] name = "comparison_benches" path = "benches/comparison_benches.rs" [dependencies.cfg-if] version = "1" [dependencies.clap] version = "4.0" features = [ "cargo", "derive", "help", "std", "suggestions", ] default-features = false [dependencies.futures-util] version = "0.3.5" features = ["std"] default-features = false [dependencies.hickory-client] version = "0.25" default-features = false [dependencies.hickory-proto] version = "0.25" features = ["std"] default-features = false [dependencies.hickory-server] version = "0.25" features = ["toml"] default-features = false [dependencies.http] version = "1.1" optional = true [dependencies.hyper] version = "1.6.0" features = ["server"] optional = true [dependencies.hyper-util] version = "0.1.11" features = [ "server-auto", "server-graceful", "tokio", ] optional = true [dependencies.ipnet] version = "2.3.0" features = ["serde"] default-features = false [dependencies.metrics] version = "0.24.1" optional = true [dependencies.metrics-exporter-prometheus] version = "0.16.2" features = ["http-listener"] optional = true default-features = false [dependencies.metrics-process] version = "2.4.0" optional = true [dependencies.rusqlite] version = "0.35" features = [ "bundled", "time", ] optional = true [dependencies.rustls] version = "0.23.23" features = [ "logging", "std", "tls12", ] optional = true default-features = false [dependencies.rustls-pki-types] version = "1.10" optional = true [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.socket2] version = "0.5" [dependencies.time] version = "0.3" [dependencies.tokio] version = "1.21" features = [ "time", "rt", "signal", ] [dependencies.tokio-util] version = "0.7.9" optional = true [dependencies.toml] version = "0.8.14" [dependencies.tracing] version = "0.1.30" default-features = false [dependencies.tracing-subscriber] version = "0.3" features = [ "env-filter", "fmt", "std", ] default-features = false [dev-dependencies.futures-executor] version = "0.3.5" features = ["std"] default-features = false [dev-dependencies.hickory-proto] version = "0.25" features = [ "std", "testing", "std", ] default-features = false [dev-dependencies.hickory-resolver] version = "0.25" default-features = false [dev-dependencies.prometheus-parse] version = "0.2.5" [dev-dependencies.regex] version = "1.3.4" default-features = false [dev-dependencies.reqwest] version = "0.12.12" default-features = false [dev-dependencies.toml] version = "0.8.14" [dev-dependencies.webpki-roots] version = "0.26" [target."cfg(unix)".dependencies.libc] version = "0.2" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(nightly)"] hickory-dns-0.25.2/Cargo.toml.orig000064400000000000000000000113251046102023000150350ustar 00000000000000[package] name = "hickory-dns" # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Hickory DNS is a safe and secure DNS server with DNSSEC support. Eventually this could be a replacement for BIND9. The DNSSEC support allows for live signing of all records, in it does not currently support records signed offline. The server supports dynamic DNS with SIG0 authenticated requests. Hickory DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries. """ documentation = "https://docs.rs/hickory-dns" readme = "README.md" version.workspace = true authors.workspace = true edition.workspace = true rust-version.workspace = true homepage.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true license.workspace = true [features] default = ["sqlite", "resolver", "rustls-platform-verifier", "ascii-art"] # if enabled, the hickory-dns binary will print ascii-art on start, disable to reduce the binary size ascii-art = [] blocklist = ["hickory-server/blocklist"] recursor = ["hickory-server/recursor"] # Recursive Resolution is Experimental! resolver = ["hickory-server/resolver"] sqlite = ["hickory-server/sqlite", "dep:rusqlite"] prometheus-metrics = ["metrics", "dep:http", "dep:hyper", "dep:hyper-util", "dep:metrics-exporter-prometheus", "dep:tokio-util"] metrics = ["hickory-server/metrics", "dep:metrics", "dep:metrics-process"] tls-aws-lc-rs = ["hickory-server/tls-aws-lc-rs", "__tls"] https-aws-lc-rs = ["hickory-server/https-aws-lc-rs", "tls-aws-lc-rs", "__https"] quic-aws-lc-rs = ["hickory-server/quic-aws-lc-rs", "tls-aws-lc-rs", "__quic"] h3-aws-lc-rs = ["hickory-server/h3-aws-lc-rs", "quic-aws-lc-rs", "__h3"] tls-ring = ["hickory-server/tls-ring", "__tls"] https-ring = ["hickory-server/https-ring", "tls-ring", "__https"] quic-ring = ["hickory-server/quic-ring", "tls-ring", "__quic"] h3-ring = ["hickory-server/h3-ring", "quic-ring", "__h3"] __tls = ["dep:rustls"] __https = [] __quic = [] __h3 = [] dnssec-aws-lc-rs = ["hickory-server/dnssec-aws-lc-rs", "dep:rustls-pki-types", "__dnssec"] dnssec-ring = ["hickory-server/dnssec-ring", "dep:rustls-pki-types", "__dnssec"] __dnssec = [] webpki-roots = ["hickory-server/webpki-roots"] rustls-platform-verifier = ["hickory-server/rustls-platform-verifier"] [[bin]] name = "hickory-dns" path = "src/hickory-dns.rs" [dependencies] # clap features: # - `suggestions` for advanced help with error in cli # - `derive` for clap derive api # - `help` to generate --help cfg-if.workspace = true clap = { workspace = true, default-features = false, features = ["cargo", "derive", "help", "std", "suggestions"] } futures-util = { workspace = true, default-features = false, features = ["std"] } http = { workspace = true, optional = true } hyper = { workspace = true, features = ["server"], optional = true } hyper-util = { workspace = true, features = ["server-auto", "server-graceful", "tokio"], optional = true } ipnet = { workspace = true, features = ["serde"] } # rusqlite is actually only needed for test situations, but we need an optional dependency # here so we can disable it for MSRV tests (rusqlite only supports latest stable) rusqlite = { workspace = true, features = ["bundled", "time"], optional = true } socket2.workspace = true rustls = { workspace = true, optional = true } rustls-pki-types = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } time.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "std"] } tokio = { workspace = true, features = ["time", "rt", "signal"] } tokio-util = { workspace = true, optional = true } toml.workspace = true hickory-client.workspace = true hickory-proto.workspace = true hickory-server = { workspace = true, features = ["toml"] } metrics = { workspace = true, optional = true } metrics-exporter-prometheus = { workspace = true, optional = true } metrics-process = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] libc.workspace = true [dev-dependencies] futures-executor = { workspace = true, default-features = false, features = ["std"] } regex.workspace = true hickory-proto = { workspace = true, features = ["testing", "std"] } hickory-resolver.workspace = true test-support.workspace = true toml.workspace = true webpki-roots.workspace = true # metrics tests reqwest = { workspace = true, default-features = false } prometheus-parse = { workspace = true } [lints] workspace = true [package.metadata.cargo-all-features] skip_optional_dependencies = true max_combination_size = 2 denylist = ["__tls", "__https", "__quic", "__h3", "__dnssec"] hickory-dns-0.25.2/LICENSE-APACHE000064400000000000000000000261401046102023000140730ustar 00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. hickory-dns-0.25.2/LICENSE-MIT000064400000000000000000000021151046102023000135770ustar 00000000000000Copyright (c) 2015 The Hickory DNS Developers Copyright (c) 2017 Google LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hickory-dns-0.25.2/README.md000064400000000000000000000065001046102023000134240ustar 00000000000000# Overview Hickory DNS provides a binary for hosting or forwarding DNS zones. This a named implementation for DNS zone hosting, stub resolvers, and recursive resolvers. It is capable of performing signing all records in the zone for server DNSSEC RRSIG records associated with all records in a zone. There is also a `hickory-dns` binary that can be generated from the library with `cargo install hickory-dns`. Dynamic updates are supported via `SIG0` (an mTLS authentication method is under development). **NOTICE** This project was rebranded from Trust-DNS to Hickory DNS and has been moved to the https://github.com/hickory-dns/hickory-dns organization and repo, this crate/binary has been moved to [hickory-dns](https://crates.io/crates/hickory-dns), from `0.24` and onward, for prior versions see [trust-dns](https://crates.io/crates/trust-dns). ## Features - Dynamic Update with sqlite journaling backend (SIG0) - DNSSEC online signing (with NSEC and NSEC3) - Forwarding stub resolver - ANAME resolution, for zone mapping aliases to A and AAAA records - Additionals section generation for aliasing record types ## Optional protocol support The following DNS protocols are optionally supported: - Enable `dns-over-rustls` for DNS over TLS (DoT) - Enable `dns-over-https-rustls` for DNS over HTTP/2 (DoH) - Enable `dns-over-quic` for DNS over QUIC (DoQ) - Enable `dns-over-h3` for DNS over HTTP/3 (DoH3) ## DNSSEC status The current root key is bundled into the system, and used by default. This gives validation of DNSKEY and DS records back to the root. NSEC and NSEC3 are implemented. Zones will be automatically resigned on any record updates via dynamic DNS. To enable DNSSEC, enable the `dnssec-ring` feature. ## Future goals - Distributed dynamic DNS updates, with consensus - mTLS based authorization for Dynamic Updates - Online NSEC creation for queries - Maybe NSEC5 support ## Running Warning: Hickory DNS is still under development, running in production is not recommended. - Verify the version ```shell ./target/release/hickory-dns --version ``` - Get help ```shell ./target/release/hickory-dns --help ``` - Launch `hickory-dns` server with test config Note that if the `-p` parameter is not passed, the server will run on default DNS ports. There are separate port options for DoT and DoH servers, see `hickory-dns --help` ```shell ./target/release/hickory-dns -c ./tests/test-data/test_configs/example.toml -z ./tests/test-data/test_configs/ -p 24141 ``` - Query the just launched server with `dig` ```shell dig @127.0.0.1 -p 24141 www.example.com ``` ## Minimum Rust Version The current minimum rustc version for this project is `1.70` ## Versioning Hickory DNS does it's best job to follow semver. Hickory DNS will be promoted to 1.0 upon stabilization of the publicly exposed APIs. This does not mean that Hickory DNS will necessarily break on upgrades between 0.x updates. Whenever possible, old APIs will be deprecated with notes on what replaced those deprecations. Hickory DNS will make a best effort to never break software which depends on it due to API changes, though this can not be guaranteed. Deprecated interfaces will be maintained for at minimum one major release after that in which they were deprecated (where possible), with the exception of the upgrade to 1.0 where all deprecated interfaces will be planned to be removed. hickory-dns-0.25.2/benches/bind_conf/example.conf000064400000000000000000000017611046102023000200030ustar 00000000000000/* Hickory DNS bench setup: this is meant to mimic the hickory-server config which is in tests/test-data/test_configs/example.toml */ controls { /* empty */ }; options { pid-file "/tmp/named.pid"; listen-on { 127.0.0.1; }; listen-on-v6 { none; }; recursion no; }; zone "localhost" { type primary; file "../../../tests/test-data/test_configs/default/localhost.zone"; }; zone "0.0.127.in-addr.arpa" { type primary; file "../../../tests/test-data/test_configs/default/127.0.0.1.zone"; }; zone "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" { type primary; file "../../../tests/test-data/test_configs/default/ipv6_1.zone"; }; zone "255.in-addr.arpa" { type primary; file "../../../tests/test-data/test_configs/default/255.zone"; }; zone "0.in-addr.arpa" { type primary; file "../../../tests/test-data/test_configs/default/0.zone"; }; zone "example.com" { type primary; file "../../../tests/test-data/test_configs/example.com.zone"; }; hickory-dns-0.25.2/benches/comparison_benches.rs000064400000000000000000000163251046102023000177710ustar 00000000000000#![cfg(nightly)] #![feature(test)] extern crate test; use std::env; use std::fs::{DirBuilder, File}; use std::future::Future; use std::mem; use std::net::{Ipv4Addr, SocketAddr}; use std::path::Path; use std::process::{Child, Command, Stdio}; use std::str::FromStr; use std::thread; use std::time::Duration; use hickory_proto::runtime::TokioRuntimeProvider; use test::Bencher; use tokio::runtime::Runtime; use hickory_client::client::{Client, ClientHandle}; use hickory_proto::ProtoError; use hickory_proto::op::ResponseCode; use hickory_proto::rr::rdata::A; use hickory_proto::rr::{DNSClass, Name, RData, RecordType}; use hickory_proto::tcp::TcpClientStream; use hickory_proto::udp::UdpClientStream; use hickory_proto::xfer::{DnsMultiplexer, DnsRequestSender}; fn find_test_port() -> u16 { let server = std::net::UdpSocket::bind(("0.0.0.0", 0)).unwrap(); let server_addr = server.local_addr().unwrap(); server_addr.port() } struct NamedProcess { named: Child, } impl Drop for NamedProcess { fn drop(&mut self) { self.named.kill().expect("could not kill process"); self.named.wait().expect("waiting failed"); } } fn wrap_process(named: Child, server_port: u16) -> NamedProcess { let mut started = false; let provider = TokioRuntimeProvider::new(); for _ in 0..20 { let io_loop = Runtime::new().unwrap(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let stream = UdpClientStream::builder(addr, provider.clone()).build(); let client = Client::connect(stream); let (mut client, bg) = io_loop.block_on(client).expect("failed to create client"); io_loop.spawn(bg); let name = Name::from_str("www.example.com.").unwrap(); let response = io_loop.block_on(client.query(name.clone(), DNSClass::IN, RecordType::A)); if response.is_ok() { started = true; break; } else { // wait for the server to start thread::sleep(Duration::from_millis(500)); } } assert!(started, "server did not startup..."); // return handle to child process NamedProcess { named } } /// Returns a NamedProcess (cleans the process up on drop), and a socket addr for connecting /// to the server. fn hickory_process() -> (NamedProcess, u16) { // find a random port to listen on let test_port = find_test_port(); let ws_root = env::var("WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let named_path = env!("CARGO_BIN_EXE_hickory-dns"); let config_path = format!("{}/tests/test-data/test_configs/example.toml", ws_root); let zone_dir = format!("{}/tests/test-data/test_configs", ws_root); File::open(&named_path).expect(&named_path); File::open(&config_path).expect(&config_path); File::open(&zone_dir).expect(&zone_dir); let named = Command::new(&named_path) .stdout(Stdio::null()) .arg("-q") // TODO: need to rethink this one... .arg(&format!("--config={}", config_path)) .arg(&format!("--zonedir={}", zone_dir)) .arg(&format!("--port={}", test_port)) .spawn() .expect("failed to start hickory-dns"); // let process = wrap_process(named, test_port); // return handle to child process (process, test_port) } /// Runs the bench tesk using the specified client fn bench(b: &mut Bencher, stream: F) where F: Future> + 'static + Send + Unpin, S: DnsRequestSender, { let io_loop = Runtime::new().unwrap(); let client = Client::connect(stream); let (mut client, bg) = io_loop.block_on(client).expect("failed to create client"); io_loop.spawn(bg); let name = Name::from_str("www.example.com.").unwrap(); // validate the request let query = client.query(name.clone(), DNSClass::IN, RecordType::A); let response = io_loop.block_on(query).expect("Request failed"); assert_eq!(response.response_code(), ResponseCode::NoError); let record = &response.answers()[0]; if let RData::A(address) = record.data() { assert_eq!(address, &A(Ipv4Addr::LOCALHOST)); } else { unreachable!(); } b.iter(|| { let response = io_loop.block_on(client.query(name.clone(), DNSClass::IN, RecordType::A)); response.unwrap(); }); } #[bench] fn hickory_udp_bench(b: &mut Bencher) { let (named, server_port) = hickory_process(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let stream = UdpClientStream::builder(addr, TokioRuntimeProvider::new()).build(); bench(b, stream); // cleaning up the named process drop(named); } #[bench] #[ignore] fn hickory_udp_bench_prof(b: &mut Bencher) { let server_port = 6363; let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let stream = UdpClientStream::builder(addr, TokioRuntimeProvider::new()).build(); bench(b, stream); } #[bench] fn hickory_tcp_bench(b: &mut Bencher) { let (named, server_port) = hickory_process(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let (stream, sender) = TcpClientStream::new(addr, None, None, TokioRuntimeProvider::new()); let mp = DnsMultiplexer::new(stream, sender, None); bench(b, mp); // cleaning up the named process drop(named); } // downloaded from https://www.isc.org/downloads/file/bind-9-11-0-p1/ // cd bind-9-11-0-p1 // .configure // make // export TDNS_BIND_PATH=${PWD}/bin/named/named fn bind_process() -> (NamedProcess, u16) { let test_port = find_test_port(); let bind_path = env::var("TDNS_BIND_PATH").unwrap_or_else(|_| "".to_owned()); let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let bind_path = format!("{}/sbin/named", bind_path); // create the work directory let working_dir = format!("{}/../target/bind_pwd", server_path); if !Path::new(&working_dir).exists() { DirBuilder::new() .create(&working_dir) .expect("failed to create dir"); } let mut named = Command::new(bind_path) .current_dir(&working_dir) .stderr(Stdio::piped()) .arg("-c") .arg("../../server/benches/bind_conf/example.conf") //.arg("-d").arg("0") .arg("-D") .arg("Hickory DNS cmp bench") .arg("-g") .arg("-p") .arg(&format!("{}", test_port)) .spawn() .expect("failed to start named"); mem::replace(&mut named.stderr, None).unwrap(); let process = wrap_process(named, test_port); (process, test_port) } #[bench] #[ignore] fn bind_udp_bench(b: &mut Bencher) { let (named, server_port) = bind_process(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let stream = UdpClientStream::builder(addr, TokioRuntimeProvider::new()).build(); bench(b, stream); // cleaning up the named process drop(named); } #[bench] #[ignore] fn bind_tcp_bench(b: &mut Bencher) { let (named, server_port) = bind_process(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, server_port)); let (stream, sender) = TcpClientStream::new(addr, None, None, TokioRuntimeProvider::new()); let mp = DnsMultiplexer::new(stream, sender, None); bench(b, mp); // cleaning up the named process drop(named); } hickory-dns-0.25.2/src/dnssec.rs000064400000000000000000000173471046102023000145740ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Configuration types for all security options in hickory-dns use std::path::{Path, PathBuf}; use rustls_pki_types::PrivateKeyDer; use rustls_pki_types::pem::PemObject; use serde::Deserialize; use time::Duration; use tracing::info; use hickory_proto::rr::domain::Name; use hickory_proto::{ ProtoError, dnssec::{Algorithm, SigSigner, SigningKey, rdata::DNSKEY, rdata::KEY, rdata::key::KeyUsage}, rr::domain::IntoName, }; use hickory_server::authority::DnssecAuthority; /// Key pair configuration for DNSSEC keys for signing a zone #[derive(Deserialize, PartialEq, Eq, Debug)] #[serde(deny_unknown_fields)] pub struct KeyConfig { /// file path to the key pub key_path: PathBuf, /// the type of key stored pub algorithm: Algorithm, /// the name to use when signing records, e.g. ns.example.com pub signer_name: Option, pub purpose: KeyPurpose, } impl KeyConfig { /// the signer name for the key, this defaults to the $ORIGIN aka zone name. pub fn signer_name(&self) -> Result, ProtoError> { self.signer_name .as_ref() .map(|name| Name::parse(name, None)) .transpose() } /// set of DNSSEC algorithms to use to sign the zone. enable_dnssec must be true. /// these will be looked up by $file.{key_name}.pem, for backward compatibility /// with previous versions of Hickory DNS, if enable_dnssec is enabled but /// supported_algorithms is not specified, it will default to "RSASHA256" and /// look for the $file.pem for the key. To control key length, or other options /// keys of the specified formats can be generated in PEM format. Instructions /// for custom keys can be found elsewhere. /// /// the currently supported set of supported_algorithms are /// ["RSASHA256", "RSASHA512", "ECDSAP256SHA256", "ECDSAP384SHA384", "ED25519"] /// /// keys are listed in pairs of key_name and algorithm, the search path is the /// same directory has the zone $file: /// keys = [ "my_rsa_2048|RSASHA256", "/path/to/my_ed25519|ED25519" ] pub fn try_into_signer(&self, signer_name: impl IntoName) -> Result { let name = match self.signer_name() { Ok(Some(name)) => name, Ok(None) => signer_name .into_name() .map_err(|e| format!("error loading signer name: {e}"))?, Err(e) => return Err(format!("error loading signer name: {e}")), }; // read the key in let key = key_from_file(&self.key_path, self.algorithm)?; // add the key to the zone // TODO: allow the duration of signatures to be customized let pub_key = key .to_public_key() .map_err(|e| format!("error getting public key: {e}"))?; let signer = SigSigner::dnssec( DNSKEY::from_key(&pub_key), key, name, Duration::weeks(52) .try_into() .map_err(|e| format!("error converting time to std::Duration: {e}"))?, ); signer .test_key() .map_err(|e| format!("key failed test: {e}"))?; Ok(signer) } pub async fn load( &self, authority: &mut impl DnssecAuthority, zone_name: Name, ) -> Result<(), String> { info!( "adding key to zone: {:?}, purpose: {:?}", self.key_path, self.purpose, ); match self.purpose { KeyPurpose::ZoneSigning => { let zone_signer = self .try_into_signer(zone_name) .map_err(|e| format!("failed to load key: {:?} msg: {}", self.key_path, e))?; authority .add_zone_signing_key(zone_signer) .await .map_err(|err| format!("failed to add zone signing key to authority: {err}"))?; } KeyPurpose::ZoneUpdateAuth => { let update_auth_signer = self .try_into_signer(zone_name.clone()) .map_err(|e| format!("failed to load key: {:?} msg: {}", self.key_path, e))?; let public_key = update_auth_signer .key() .to_public_key() .map_err(|err| format!("failed to get public key: {err}"))?; let key = KEY::new_sig0key_with_usage(&public_key, KeyUsage::Host); authority .add_update_auth_key(zone_name, key) .await .map_err(|err| format!("failed to update auth key to authority: {err}"))?; } } Ok(()) } } /// What a key will be used for #[derive(Clone, Copy, Deserialize, PartialEq, Eq, Debug)] pub enum KeyPurpose { /// This key is used to sign a zone /// /// The public key for this must be trusted by a resolver to work. The key must have a private /// portion associated with it. It will be registered as a DNSKEY in the zone. ZoneSigning, /// This key is used for dynamic updates in the zone /// /// This is at least a public_key, and can be used for SIG0 dynamic updates /// /// it will be registered as a KEY record in the zone ZoneUpdateAuth, } pub fn key_from_file(path: &Path, algorithm: Algorithm) -> Result, String> { use std::fs::File; use std::io::Read; use tracing::info; use hickory_proto::dnssec::crypto::signing_key_from_der; info!("reading key: {path:?}"); let mut file = File::open(path).map_err(|e| format!("error opening private key file: {path:?}: {e}"))?; let mut buf = Vec::with_capacity(256); file.read_to_end(&mut buf) .map_err(|e| format!("could not read key from: {path:?}: {e}"))?; let key = match trim_ascii_start(&buf).starts_with(b"-----BEGIN ") { true => PrivateKeyDer::from_pem_slice(&buf) .map_err(|e| format!("could not read pem from {}: {e}", path.display()))?, false => PrivateKeyDer::try_from(&*buf) .map_err(|e| format!("could not read der from {}: {e}", path.display()))?, }; signing_key_from_der(&key, algorithm).map_err(|e| format!("could not decode key: {e}")) } // Copied from std, MSRV 1.80 fn trim_ascii_start(mut bytes: &[u8]) -> &[u8] { // Note: A pattern matching based approach (instead of indexing) allows // making the function const. while let [first, rest @ ..] = bytes { match first.is_ascii_whitespace() { true => bytes = rest, false => return bytes, } } bytes } #[cfg(test)] mod tests { use super::*; #[test] fn pkcs8_pem_key() { // OpenSSL 3 generates PKCS#8-encoded RSA keys by default // `openssl genrsa 2048` key_from_file( Path::new("tests/test-data/rsa-2048-pkcs8.pem"), Algorithm::RSASHA256, ) .expect("failed to read key"); } #[test] fn pkcs1_pem_key() { // OpenSSL 1 used to generate PKCS#1-encoded RSA keys by default // OpenSSL 3 does not anymore, but you can still generate them with ssh-keygen // `ssh-keygen -t rsa -b 2048 -o -a 100 -f test-data/rsa-2048-pkcs1.pem -m PEM` key_from_file( Path::new("tests/test-data/rsa-2048-pkcs1.pem"), Algorithm::RSASHA256, ) .expect("failed to read key"); } } hickory-dns-0.25.2/src/hickory-dns.ascii000064400000000000000000000065111046102023000162020ustar 00000000000000 ------ --------- +---------- ++++++------- ++++++++++++++ +++++++++++++++ +++++++++++++++ ++++++++++++++++ ++++++++++++++++ #### ++++++++++++++++ ######## ####++++++++++++ ########### ################ ############ ################ ############## ################ ############## ################ ############### ################ ###########++++ ################ ######++++++++++ ################ ++++++++++++++++ ############### ++++++++++++++++ ############### +++++++++++++++ ############## +++++++++----- ############# +++++-------- ++++++##### +---------- +++++++++ --------- +++++ ----- ## ### ### ### #### ### ### ### ### ### ### ### ### ### #### ########## ### #### ########## ####### #### #### ############### #### #### #### ### #### #### #### ### ### ### ### ### #### ### ####### #### #### ### ### ### ### ### #### ### ######## ### #### ### #### ### ### ### #### ### ### ### ### ### ### ### ####### ### ### #### ########## ### #### ########## ### ##### ### ### #### ###### ### #### ###### ### ### ### +++++ + + ++++ ####### ++ + +++ + ++ ##### ++ + + +++ ++ +++++ + + +++++ hickory-dns-0.25.2/src/hickory-dns.rs000064400000000000000000000777341046102023000155550ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! The `hickory-dns` binary for running a DNS server //! //! ```text //! Usage: hickory-dns [options] //! hickory-dns (-h | --help | --version) //! //! Options: //! -q, --quiet Disable INFO messages, WARN and ERROR will remain //! -d, --debug Turn on DEBUG messages (default is only INFO) //! -h, --help Show this message //! -v, --version Show the version of hickory-dns //! -c FILE, --config=FILE Path to configuration file, default is /etc/named.toml //! -z DIR, --zonedir=DIR Path to the root directory for all zone files, see also config toml //! -p PORT, --port=PORT Override the listening port //! --tls-port=PORT Override the listening port for TLS connections //! ``` #![recursion_limit = "128"] #[cfg(feature = "metrics")] use std::time::Duration; use std::{ fmt, io::Error, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, path::{Path, PathBuf}, }; use clap::Parser; #[cfg(feature = "metrics")] use metrics::{Counter, Unit, counter, describe_counter, describe_gauge, gauge}; #[cfg(feature = "metrics")] use metrics_process::Collector; use socket2::{Domain, Socket, Type}; use time::OffsetDateTime; #[cfg(unix)] use tokio::signal::unix::{SignalKind, signal}; #[cfg(feature = "metrics")] use tokio::time::sleep; use tokio::{ net::{TcpListener, UdpSocket}, runtime, }; #[cfg(any(feature = "__tls", feature = "__https", feature = "__quic"))] use tracing::warn; use tracing::{Event, Level, Subscriber, error, info}; use tracing_subscriber::{ EnvFilter, fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields, format}, layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt, }; use hickory_dns::Config; #[cfg(all(feature = "metrics", feature = "resolver"))] use hickory_dns::ExternalStoreConfig; #[cfg(feature = "prometheus-metrics")] use hickory_dns::PrometheusServer; #[cfg(feature = "__tls")] use hickory_dns::TlsCertConfig; #[cfg(feature = "metrics")] use hickory_dns::{ServerStoreConfig, ServerZoneConfig, ZoneConfig, ZoneTypeConfig}; use hickory_server::{authority::Catalog, server::ServerFuture}; /// Cli struct for all options managed with clap derive api. #[derive(Debug, Parser)] #[clap(name = "Hickory DNS named server", version, about)] struct Cli { /// Test validation of configuration files #[clap(long = "validate")] validate: bool, /// Number of runtime workers, defaults to the number of CPU cores #[clap(long = "workers")] workers: Option, /// Disable INFO messages, WARN and ERROR will remain #[clap(short = 'q', long = "quiet", conflicts_with = "debug")] quiet: bool, /// Turn on `DEBUG` messages (default is only `INFO`) #[clap(short = 'd', long = "debug", conflicts_with = "quiet")] debug: bool, /// Path to configuration file of named server #[clap( short = 'c', long = "config", default_value = "/etc/named.toml", value_name = "NAME", value_hint=clap::ValueHint::FilePath, )] config: PathBuf, /// Path to the root directory for all zone files, /// see also config toml #[clap(short = 'z', long = "zonedir", value_name = "DIR", value_hint=clap::ValueHint::DirPath)] zonedir: Option, /// Listening port for DNS queries, /// overrides any value in config file #[clap(short = 'p', long = "port", value_name = "PORT")] port: Option, /// Listening port for DNS over TLS queries, /// overrides any value in config file #[cfg(feature = "__tls")] #[clap(long = "tls-port", value_name = "TLS-PORT")] tls_port: Option, /// Listening port for DNS over HTTPS queries, /// overrides any value in config file #[cfg(feature = "__https")] #[clap(long = "https-port", value_name = "HTTPS-PORT")] https_port: Option, /// Listening port for DNS over QUIC queries, /// overrides any value in config file #[cfg(feature = "__quic")] #[clap(long = "quic-port", value_name = "QUIC-PORT")] quic_port: Option, /// Listening socket for Prometheus metrics, /// for remote access configure socket as needed (e.g. 0.0.0.0:9000) /// overrides any value in config file #[cfg(feature = "prometheus-metrics")] #[clap( long = "prometheus-listen-address", value_name = "PROMETHEUS-LISTEN-ADDRESS" )] prometheus_listen_addr: Option, /// Disable TCP protocol, /// overrides any value in config file #[clap(long = "disable-tcp")] disable_tcp: bool, /// Disable UDP protocol, /// overrides any value in config file #[clap(long = "disable-udp")] disable_udp: bool, /// Disable TLS protocol, /// overrides any value in config file #[cfg(feature = "__tls")] #[clap(long = "disable-tls", conflicts_with = "tls_port")] disable_tls: bool, /// Disable HTTPS protocol, /// overrides any value in config file #[cfg(feature = "__https")] #[clap(long = "disable-https", conflicts_with = "https_port")] disable_https: bool, /// Disable QUIC protocol, /// overrides any value in config file #[cfg(feature = "__quic")] #[clap(long = "disable-quic", conflicts_with = "quic_port")] disable_quic: bool, /// Disable Prometheus metrics, /// overrides any value in config file #[cfg(feature = "prometheus-metrics")] #[clap(long = "disable-prometheus", conflicts_with = "prometheus_listen_addr")] disable_prometheus: bool, } /// Main method for running the named server. fn main() -> Result<(), String> { // this is essential for custom formatting the returned error message. // the displayed message of termination impl trait is not pretty. // https://doc.rust-lang.org/stable/src/std/process.rs.html#2439 if let Err(e) = run() { eprintln!("Error: {e}"); std::process::exit(1); } Ok(()) } fn run() -> Result<(), String> { let args = Cli::parse(); // TODO: this should be set after loading config, but it's necessary for initial log lines, no? let level = match (args.quiet, args.debug) { (true, _) => Level::ERROR, (_, true) => Level::DEBUG, _ => Level::INFO, }; // Setup tracing for logging based on input tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().event_format(TdnsFormatter)) .with( EnvFilter::builder() .with_default_directive(level.into()) .from_env() .map_err(|err| { format!("failed to parse environment variable for tracing: {err}") })?, ) .init(); info!("Hickory DNS {} starting...", hickory_client::version()); let mut runtime = runtime::Builder::new_multi_thread(); runtime.enable_all().thread_name("hickory-server-runtime"); if let Some(workers) = args.workers { runtime.worker_threads(workers); } let runtime = runtime .build() .map_err(|err| format!("failed to initialize Tokio runtime: {err}"))?; runtime.block_on(async_run(args)) } async fn async_run(args: Cli) -> Result<(), String> { // Load configuration files let config = args.config.clone(); let config_path = Path::new(&config); info!("loading configuration from: {config_path:?}"); let config = Config::read_config(config_path) .map_err(|err| format!("failed to read config file from {config_path:?}: {err}"))?; let directory_config = config.directory().to_path_buf(); let zonedir = args.zonedir.clone(); let zone_dir: PathBuf = zonedir .as_ref() .map(PathBuf::from) .unwrap_or(directory_config); #[cfg(feature = "prometheus-metrics")] let prometheus_server_opt = if !args.disable_prometheus && !config.disable_prometheus() { let socket_addr = args .prometheus_listen_addr .unwrap_or(config.prometheus_listen_addr()); let listener = build_tcp_listener(socket_addr.ip(), socket_addr.port()).map_err(|err| { format!("failed to bind to Prometheus TCP socket address {socket_addr:?}: {err}") })?; let local_addr = listener .local_addr() .map_err(|err| format!("failed to look up local address: {err}"))?; // Set up Prometheus HTTP server. let server = PrometheusServer::new(listener)?; info!("listening for Prometheus metrics on {local_addr:?}"); Some(server) } else { info!("Prometheus metrics are disabled"); None }; #[cfg(feature = "metrics")] let (process_metrics_collector, config_metrics) = { // setup process metrics (cpu, memory, ...) collection let collector = Collector::default(); collector.describe(); // add metric descriptions let process_metrics_collector = tokio::spawn(async move { loop { sleep(Duration::from_secs(1)).await; collector.collect(); } }); // metrics need to be created after the recorder is registered // calling increment() after registration is not sufficient let config_metrics = ConfigMetrics::new(&config); (process_metrics_collector, config_metrics) }; #[cfg(unix)] let mut signal = signal(SignalKind::terminate()) .map_err(|e| format!("failed to register signal handler: {e}"))?; let mut catalog: Catalog = Catalog::new(); // configure our server based on the config_path for zone in config.zones() { let zone_name = zone .zone() .map_err(|err| format!("failed to read zone name from {config_path:?}: {err}"))?; match zone.load(&zone_dir).await { Ok(authority) => catalog.upsert(zone_name.into(), authority), Err(err) => return Err(format!("could not load zone {zone_name}: {err}")), } #[cfg(feature = "metrics")] config_metrics.increment_zone_metrics(zone); } let v4addr = config .listen_addrs_ipv4() .map_err(|err| format!("failed to parse IPv4 addresses from {config_path:?}: {err}"))?; let v6addr = config .listen_addrs_ipv6() .map_err(|err| format!("failed to parse IPv6 addresses from {config_path:?}: {err}"))?; let mut listen_addrs: Vec = v4addr .into_iter() .map(IpAddr::V4) .chain(v6addr.into_iter().map(IpAddr::V6)) .collect(); let listen_port: u16 = args.port.unwrap_or_else(|| config.listen_port()); if listen_addrs.is_empty() { listen_addrs.push(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); listen_addrs.push(IpAddr::V6(Ipv6Addr::UNSPECIFIED)); } if args.validate { info!("configuration files are validated"); return Ok(()); } let deny_networks = config.deny_networks(); let allow_networks = config.allow_networks(); let tcp_request_timeout = config.tcp_request_timeout(); // now, run the server, based on the config #[cfg_attr(not(feature = "__tls"), allow(unused_mut))] let mut server = ServerFuture::with_access(catalog, deny_networks, allow_networks); if !args.disable_udp && !config.disable_udp() { // load all udp listeners for addr in &listen_addrs { info!("binding UDP to {addr:?}"); let udp_socket = build_udp_socket(*addr, listen_port) .map_err(|err| format!("failed to bind to UDP socket address {addr:?}: {err}"))?; info!( "listening for UDP on {:?}", udp_socket .local_addr() .map_err(|err| format!("failed to lookup local address: {err}"))? ); server.register_socket(udp_socket); } } else { info!("UDP protocol is disabled"); } if !args.disable_tcp && !config.disable_tcp() { // load all tcp listeners for addr in &listen_addrs { info!("binding TCP to {addr:?}"); let tcp_listener = build_tcp_listener(*addr, listen_port) .map_err(|err| format!("failed to bind to TCP socket address {addr:?}: {err}"))?; info!( "listening for TCP on {:?}", tcp_listener .local_addr() .map_err(|err| format!("failed to lookup local address: {err}"))? ); server.register_listener(tcp_listener, tcp_request_timeout); } } else { info!("TCP protocol is disabled"); } #[cfg(feature = "__tls")] if let Some(tls_cert_config) = config.tls_cert() { #[cfg(feature = "__tls")] if !args.disable_tls && !config.disable_tls() { // setup TLS listeners config_tls( args.tls_port, &mut server, &config, tls_cert_config, &zone_dir, &listen_addrs, )?; } else { info!("TLS protocol is disabled"); } #[cfg(feature = "__https")] if !args.disable_https && !config.disable_https() { // setup HTTPS listeners config_https( args.https_port, &mut server, &config, tls_cert_config, &zone_dir, &listen_addrs, )?; } else { info!("HTTPS protocol is disabled"); } #[cfg(feature = "__quic")] if !args.disable_quic && !config.disable_quic() { // setup QUIC listeners config_quic( args.quic_port, &mut server, &config, tls_cert_config, &zone_dir, &listen_addrs, )?; } else { info!("QUIC protocol is disabled"); } } else { info!("TLS certificates are not provided"); info!("TLS related protocols (TLS, HTTPS and QUIC) are disabled") } // Drop privileges on Unix systems if running as root. #[cfg(target_family = "unix")] check_drop_privs( config.user.as_deref().unwrap_or(DEFAULT_USER), config.group.as_deref().unwrap_or(DEFAULT_GROUP), )?; #[cfg(not(target_family = "unix"))] if config.user.is_some() || config.group.is_some() { return Err("dropping privileges is only supported on Unix systems".to_string()); } #[cfg(unix)] { let token = server.shutdown_token().clone(); tokio::spawn(async move { signal.recv().await; token.cancel(); }); } // config complete, starting! banner(); // TODO: how to do threads? should we do a bunch of listener threads and then query threads? // Ideally the processing would be n-threads for receiving, which hand off to m-threads for // request handling. It would generally be the case that n <= m. info!("server starting up, awaiting connections..."); match server.block_until_done().await { Ok(()) => { // we're exiting for some reason... info!("Hickory DNS {} stopping", hickory_client::version()); } Err(e) => { let error_msg = format!( "Hickory DNS {} has encountered an error: {}", hickory_client::version(), e ); error!("{}", error_msg); panic!("{}", error_msg); } }; // Shut down the Prometheus metrics server after the DNS server has gracefully shut down. #[cfg(feature = "prometheus-metrics")] if let Some(server) = prometheus_server_opt { server.stop().await; } #[cfg(feature = "metrics")] process_metrics_collector.abort(); Ok(()) } #[cfg(feature = "__tls")] fn config_tls( tls_port: Option, server: &mut ServerFuture, config: &Config, tls_cert_config: &TlsCertConfig, zone_dir: &Path, listen_addrs: &[IpAddr], ) -> Result<(), String> { let tls_listen_port = tls_port.unwrap_or_else(|| config.tls_listen_port()); if listen_addrs.is_empty() { warn!("a tls certificate was specified, but no TLS addresses configured to listen on"); return Ok(()); } for addr in listen_addrs { let tls_cert_path = &tls_cert_config.path; info!("loading cert for DNS over TLS: {tls_cert_path:?}"); let tls_cert = tls_cert_config.load(zone_dir).map_err(|err| { format!("failed to load tls certificate files from {tls_cert_path:?}: {err}") })?; info!("binding TLS to {addr:?}"); let tls_listener = build_tcp_listener(*addr, tls_listen_port) .map_err(|err| format!("failed to bind to TLS socket address {addr:?}: {err}"))?; info!( "listening for TLS on {:?}", tls_listener .local_addr() .map_err(|err| format!("failed to lookup local address: {err}"))? ); server .register_tls_listener(tls_listener, config.tcp_request_timeout(), tls_cert) .map_err(|err| format!("failed to register TLS listener: {err}"))?; } Ok(()) } #[cfg(feature = "__https")] fn config_https( https_port: Option, server: &mut ServerFuture, config: &Config, tls_cert_config: &TlsCertConfig, zone_dir: &Path, listen_addrs: &[IpAddr], ) -> Result<(), String> { let https_listen_port = https_port.unwrap_or_else(|| config.https_listen_port()); let endpoint_path = config.http_endpoint(); if listen_addrs.is_empty() { warn!("a tls certificate was specified, but no HTTPS addresses configured to listen on"); return Ok(()); } for addr in listen_addrs { let tls_cert_path = &tls_cert_config.path; if let Some(endpoint_name) = &tls_cert_config.endpoint_name { info!("loading cert for DNS over TLS named {endpoint_name} from {tls_cert_path:?}"); } else { info!("loading cert for DNS over TLS from {tls_cert_path:?}"); } // TODO: see about modifying native_tls to impl Clone for Pkcs12 let tls_cert = tls_cert_config.load(zone_dir).map_err(|err| { format!("failed to load tls certificate files from {tls_cert_path:?}: {err}") })?; info!("binding HTTPS to {addr:?}"); let https_listener = build_tcp_listener(*addr, https_listen_port) .map_err(|err| format!("failed to bind to HTTPS socket address {addr:?}: {err}"))?; info!( "listening for HTTPS on {:?}", https_listener .local_addr() .map_err(|err| format!("failed to lookup local address: {err}"))? ); server .register_https_listener( https_listener, config.tcp_request_timeout(), tls_cert, tls_cert_config.endpoint_name.clone(), endpoint_path.into(), ) .map_err(|err| format!("failed to register HTTPS listener: {err}"))?; } Ok(()) } #[cfg(feature = "__quic")] fn config_quic( quic_port: Option, server: &mut ServerFuture, config: &Config, tls_cert_config: &TlsCertConfig, zone_dir: &Path, listen_addrs: &[IpAddr], ) -> Result<(), String> { let quic_listen_port = quic_port.unwrap_or_else(|| config.quic_listen_port()); if listen_addrs.is_empty() { warn!("a tls certificate was specified, but no QUIC addresses configured to listen on"); return Ok(()); } for addr in listen_addrs { let tls_cert_path = &tls_cert_config.path; if let Some(endpoint_name) = &tls_cert_config.endpoint_name { info!("loading cert for DNS over QUIC named {endpoint_name} from {tls_cert_path:?}"); } else { info!("loading cert for DNS over QUIC from {tls_cert_path:?}",); } // TODO: see about modifying native_tls to impl Clone for Pkcs12 let tls_cert = tls_cert_config.load(zone_dir).map_err(|err| { format!("failed to load tls certificate files from {tls_cert_path:?}: {err}") })?; info!("Binding QUIC to {addr:?}"); let quic_listener = build_udp_socket(*addr, quic_listen_port) .map_err(|err| format!("failed to bind to QUIC socket address {addr:?}: {err}"))?; info!( "listening for QUIC on {:?}", quic_listener .local_addr() .map_err(|err| format!("failed to lookup local address: {err}"))? ); server .register_quic_listener( quic_listener, config.tcp_request_timeout(), tls_cert, tls_cert_config.endpoint_name.clone(), ) .map_err(|err| format!("failed to register QUIC listener: {err}"))?; } Ok(()) } fn banner() { #[cfg(feature = "ascii-art")] const HICKORY_DNS_LOGO: &str = include_str!("hickory-dns.ascii"); #[cfg(not(feature = "ascii-art"))] const HICKORY_DNS_LOGO: &str = "Hickory DNS"; info!(""); for line in HICKORY_DNS_LOGO.lines() { info!(" {line}"); } info!(""); } struct TdnsFormatter; impl FormatEvent for TdnsFormatter where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, { fn format_event( &self, ctx: &FmtContext<'_, S, N>, mut writer: format::Writer<'_>, event: &Event<'_>, ) -> fmt::Result { let now = OffsetDateTime::now_utc(); let now_secs = now.unix_timestamp(); // Format values from the event's's metadata: let metadata = event.metadata(); write!( &mut writer, "{}:{}:{}", now_secs, metadata.level(), metadata.target() )?; if let Some(line) = metadata.line() { write!(&mut writer, ":{line}")?; } // Format all the spans in the event's span context. if let Some(scope) = ctx.event_scope() { for span in scope.from_root() { write!(writer, ":{}", span.name())?; let ext = span.extensions(); let fields = &ext .get::>() .expect("will never be `None`"); // Skip formatting the fields if the span had no fields. if !fields.is_empty() { write!(writer, "{{{fields}}}")?; } } } // Write fields on the event write!(writer, ":")?; ctx.field_format().format_fields(writer.by_ref(), event)?; writeln!(writer) } } #[cfg(feature = "metrics")] struct ConfigMetrics { #[cfg(feature = "resolver")] zones_forwarder: Counter, zones_file_primary: Counter, zones_file_secondary: Counter, #[cfg(feature = "sqlite")] zones_sqlite_primary: Counter, #[cfg(feature = "sqlite")] zones_sqlite_secondary: Counter, } #[cfg(feature = "metrics")] impl ConfigMetrics { fn new(config: &Config) -> Self { let hickory_info = gauge!("hickory_info", "version" => hickory_client::version()); describe_gauge!("hickory_info", Unit::Count, "hickory service metadata"); hickory_info.set(1); let hickory_config_info = gauge!("hickory_config_info", "directory" => config.directory().to_string_lossy().to_string(), "disable_https" => config.disable_https().to_string(), "disable_quic" => config.disable_quic().to_string(), "disable_tcp" => config.disable_tcp().to_string(), "disable_tls" => config.disable_tls().to_string(), "disable_udp" => config.disable_udp().to_string(), "allow_networks" => config.allow_networks().len().to_string(), "deny_networks" => config.deny_networks().len().to_string(), "zones" => config.zones().len().to_string() ); describe_gauge!( "hickory_config_info", Unit::Count, "hickory config metadata" ); hickory_config_info.set(1); let zones_total_name = "hickory_zones_total"; let zones_file_primary = counter!(zones_total_name, "store" => "file", "role" => "primary"); let zones_file_secondary = counter!(zones_total_name, "store" => "file", "role" => "secondary"); describe_counter!( zones_total_name, Unit::Count, "number of dns zones in storages" ); #[cfg(feature = "resolver")] let zones_forwarder = counter!(zones_total_name, "store" => "forwarder"); #[cfg(feature = "sqlite")] let (zones_sqlite_primary, zones_sqlite_secondary) = { let zones_primary_sqlite = counter!(zones_total_name, "store" => "sqlite", "role" => "primary"); let zones_secondary_sqlite = counter!(zones_total_name, "store" => "sqlite", "role" => "secondary"); (zones_primary_sqlite, zones_secondary_sqlite) }; Self { #[cfg(feature = "resolver")] zones_forwarder, #[cfg(feature = "sqlite")] zones_sqlite_primary, zones_file_primary, #[cfg(feature = "sqlite")] zones_sqlite_secondary, zones_file_secondary, } } fn increment_zone_metrics(&self, zone: &ZoneConfig) { match &zone.zone_type_config { ZoneTypeConfig::Primary(server_config) => self.increment_stores(server_config, true), ZoneTypeConfig::Secondary(server_config) => self.increment_stores(server_config, false), ZoneTypeConfig::External { stores } => { for store in stores { #[cfg(feature = "resolver")] if let ExternalStoreConfig::Forward(_) = store { self.zones_forwarder.increment(1) } } } } } fn increment_stores(&self, server_config: &ServerZoneConfig, primary: bool) { for store in &server_config.stores { if matches!(store, ServerStoreConfig::File(_)) { if primary { self.zones_file_primary.increment(1) } else { self.zones_file_secondary.increment(1) } } #[cfg(feature = "sqlite")] if matches!(store, ServerStoreConfig::Sqlite(_)) { if primary { self.zones_sqlite_primary.increment(1) } else { self.zones_sqlite_secondary.increment(1) } }; } } } /// Build a TcpListener for a given IP, port pair; IPv6 listeners will not accept v4 connections fn build_tcp_listener(ip: IpAddr, port: u16) -> Result { let sock = if ip.is_ipv4() { Socket::new(Domain::IPV4, Type::STREAM, None)? } else { let s = Socket::new(Domain::IPV6, Type::STREAM, None)?; s.set_only_v6(true)?; s }; sock.set_nonblocking(true)?; let s_addr = SocketAddr::new(ip, port); sock.bind(&s_addr.into())?; // this is a fairly typical backlog value, but we don't have any good data to support it as of yet sock.listen(128)?; TcpListener::from_std(sock.into()) } /// Build a UdpSocket for a given IP, port pair; IPv6 sockets will not accept v4 connections fn build_udp_socket(ip: IpAddr, port: u16) -> Result { let sock = if ip.is_ipv4() { Socket::new(Domain::IPV4, Type::DGRAM, None)? } else { let s = Socket::new(Domain::IPV6, Type::DGRAM, None)?; s.set_only_v6(true)?; s }; sock.set_nonblocking(true)?; let s_addr = SocketAddr::new(ip, port); sock.bind(&s_addr.into())?; UdpSocket::from_std(sock.into()) } /// Drop privileges on Unix systems if running as root. Errors that prevent dropping privileges will /// halt the server. This must be called after binding to low numbered sockets is complete. #[cfg(target_family = "unix")] fn check_drop_privs(user: &str, group: &str) -> Result<(), String> { use libc::{getegid, geteuid, getgid, getgrnam, getpwnam, getuid, setgid, setuid}; use std::ffi::CString; // These calls are guaranteed to succeed in a POSIX-conforming environment. In non-conforming // environments, implementations may return -1 to indicate a process running without an // associated UID/EUID/GID/EGID. In that case, our main block below will not execute as // libc typedefs uid_t and gid_t to u32; -1 will be u32::MAX. // // POSIX reference: IEEE Std 1003.1-1024 getuid, geteuid, getgid, and getegid specifications // https://pubs.opengroup.org/onlinepubs/9799919799/functions/getuid.html // https://pubs.opengroup.org/onlinepubs/9799919799/functions/geteuid.html // https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgid.html // https://pubs.opengroup.org/onlinepubs/9799919799/functions/getegid.html let (uid, gid, euid, egid) = unsafe { (getuid(), getgid(), geteuid(), getegid()) }; if uid == 0 || euid == 0 { info!( "running as root (uid: {uid} gid: {gid} euid: {euid} egid: {egid})...dropping privileges.", ); let Ok(user_cstring) = CString::new(user) else { return Err(format!("unable to create CString for user {user}")); }; let Ok(group_cstring) = CString::new(group) else { return Err(format!( "unable to create CString for group {group}. Exiting." )); }; // These functions must be supplied a NULL-terminated string, which is guaranteed by // std::ffi::CString. Upon success, they will return a pointer to a struct passwd or // struct group, or NULL upon failure. Testing for a NULL return value is mandatory. // // POSIX reference: IEEE Std 1003.1-1024 getpwnam and getgrnam specifications // https://pubs.opengroup.org/onlinepubs/9799919799/functions/getpwnam.html // https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgrnam.html let (user_info, group_info) = unsafe { ( getpwnam(user_cstring.as_ptr()), getgrnam(group_cstring.as_ptr()), ) }; if user_info.is_null() { return Err(format!("unable to lookup user '{user}'. Exiting.")); } if group_info.is_null() { return Err(format!("unable to lookup group '{group}'. Exiting.")); } // These functions must be supplied a gid_t (setgid) and uid_t (setuid), which are // supplied by the passwd and group structs returned by getpwnam and getgrnam. // The structs are tested to be valid by the calls to is_null() above. // // The call to setgid must be completed before the call to setuid is made or the // process will almost certainly lack the privileges necessary to switch its real gid. // // POSIX reference: IEEE Std 1003.1-1024 setgid and setuid specifications // https://pubs.opengroup.org/onlinepubs/9799919799/functions/setgid.html // https://pubs.opengroup.org/onlinepubs/9799919799/functions/setuid.html let (setgid_rc, setuid_rc) = unsafe { (setgid((*group_info).gr_gid), setuid((*user_info).pw_uid)) }; if setgid_rc < 0 { return Err("unable to set gid. Exiting.".into()); } if setuid_rc < 0 { return Err("unable to set uid. Exiting.".into()); } } let (uid, gid, euid, egid) = unsafe { (getuid(), getgid(), geteuid(), getegid()) }; info!("now running as uid: {uid}, gid: {gid} (euid: {euid}, egid: {egid})",); Ok(()) } #[cfg(target_family = "unix")] static DEFAULT_USER: &str = "nobody"; #[cfg(target_family = "unix")] static DEFAULT_GROUP: &str = "nobody"; hickory-dns-0.25.2/src/lib.rs000064400000000000000000000734101046102023000140540ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Configuration module for the server binary, `named`. #[cfg(feature = "__dnssec")] pub mod dnssec; #[cfg(feature = "prometheus-metrics")] use std::net::SocketAddr; #[cfg(feature = "__tls")] use std::{ffi::OsStr, fs}; use std::{ fmt, fs::File, io::Read, net::{AddrParseError, Ipv4Addr, Ipv6Addr}, path::{Path, PathBuf}, str::FromStr, sync::Arc, time::Duration, }; use cfg_if::cfg_if; use ipnet::IpNet; #[cfg(feature = "__tls")] use rustls::{ pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}, server::ResolvesServerCert, sign::{CertifiedKey, SingleCertAndKey}, }; use serde::de::{self, MapAccess, SeqAccess, Visitor}; use serde::{self, Deserialize, Deserializer}; #[cfg(feature = "__tls")] use hickory_proto::rustls::default_provider; use hickory_proto::{ProtoError, rr::Name}; #[cfg(feature = "__dnssec")] use hickory_server::authority::DnssecAuthority; #[cfg(feature = "__dnssec")] use hickory_server::dnssec::NxProofKind; #[cfg(feature = "blocklist")] use hickory_server::store::blocklist::BlocklistAuthority; #[cfg(feature = "blocklist")] use hickory_server::store::blocklist::BlocklistConfig; use hickory_server::store::file::FileConfig; #[cfg(feature = "resolver")] use hickory_server::store::forwarder::ForwardAuthority; #[cfg(feature = "resolver")] use hickory_server::store::forwarder::ForwardConfig; #[cfg(feature = "recursor")] use hickory_server::store::recursor::RecursiveAuthority; #[cfg(feature = "recursor")] use hickory_server::store::recursor::RecursiveConfig; #[cfg(feature = "sqlite")] use hickory_server::store::sqlite::{SqliteAuthority, SqliteConfig}; use hickory_server::{ ConfigError, authority::{AuthorityObject, ZoneType}, store::file::FileAuthority, }; use tracing::{debug, info, warn}; #[cfg(feature = "prometheus-metrics")] mod prometheus_server; #[cfg(feature = "prometheus-metrics")] pub use prometheus_server::PrometheusServer; static DEFAULT_PATH: &str = "/var/named"; // TODO what about windows (do I care? ;) static DEFAULT_PORT: u16 = 53; static DEFAULT_TLS_PORT: u16 = 853; static DEFAULT_HTTPS_PORT: u16 = 443; static DEFAULT_QUIC_PORT: u16 = 853; // https://www.rfc-editor.org/rfc/rfc9250.html#name-reservation-of-a-dedicated- static DEFAULT_H3_PORT: u16 = 443; static DEFAULT_TCP_REQUEST_TIMEOUT: u64 = 5; /// Server configuration #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct Config { /// The list of IPv4 addresses to listen on #[serde(default)] listen_addrs_ipv4: Vec, /// This list of IPv6 addresses to listen on #[serde(default)] listen_addrs_ipv6: Vec, /// Port on which to listen (associated to all IPs) listen_port: Option, /// Secure port to listen on tls_listen_port: Option, /// HTTPS port to listen on https_listen_port: Option, /// QUIC port to listen on quic_listen_port: Option, /// HTTP/3 port to listen on h3_listen_port: Option, /// Prometheus listen address #[cfg(feature = "prometheus-metrics")] prometheus_listen_addr: Option, /// Disable TCP protocol disable_tcp: Option, /// Disable UDP protocol disable_udp: Option, /// Disable TLS protocol disable_tls: Option, /// Disable HTTPS protocol disable_https: Option, /// Disable QUIC protocol disable_quic: Option, /// Disable Prometheus metrics #[cfg(feature = "prometheus-metrics")] disable_prometheus: Option, /// Timeout associated to a request before it is closed. tcp_request_timeout: Option, /// Level at which to log, default is INFO log_level: Option, /// Base configuration directory, i.e. root path for zones directory: Option, /// User to run the server as. /// /// Only supported on Unix-like platforms. If the real or effective UID of the hickory process /// is root, we will attempt to change to this user (or to nobody if no user is specified here.) pub user: Option, /// Group to run the server as. /// /// Only supported on Unix-like platforms. If the real or effective UID of the hickory process /// is root, we will attempt to change to this group (or to nobody if no group is specified here.) pub group: Option, /// List of configurations for zones #[serde(default)] #[serde(deserialize_with = "deserialize_with_file")] zones: Vec, /// Certificate to associate to TLS connections (currently the same is used for HTTPS and TLS) #[cfg(feature = "__tls")] tls_cert: Option, /// The HTTP endpoint where the DNS-over-HTTPS server provides service. Applicable /// to both HTTP/2 and HTTP/3 servers. Typically `/dns-query`. #[cfg(any(feature = "__https", feature = "__h3"))] http_endpoint: Option, /// Networks denied to access the server #[serde(default)] deny_networks: Vec, /// Networks allowed to access the server #[serde(default)] allow_networks: Vec, } impl Config { /// read a Config file from the file specified at path. pub fn read_config(path: &Path) -> Result { let mut file = File::open(path)?; let mut toml = String::new(); file.read_to_string(&mut toml)?; Self::from_toml(&toml) } /// Read a [`Config`] from the given TOML string. pub fn from_toml(toml: &str) -> Result { Ok(toml::from_str(toml)?) } /// set of listening ipv4 addresses (for TCP and UDP) pub fn listen_addrs_ipv4(&self) -> Result, AddrParseError> { self.listen_addrs_ipv4.iter().map(|s| s.parse()).collect() } /// set of listening ipv6 addresses (for TCP and UDP) pub fn listen_addrs_ipv6(&self) -> Result, AddrParseError> { self.listen_addrs_ipv6.iter().map(|s| s.parse()).collect() } /// port on which to listen for connections on specified addresses pub fn listen_port(&self) -> u16 { self.listen_port.unwrap_or(DEFAULT_PORT) } /// port on which to listen for TLS connections pub fn tls_listen_port(&self) -> u16 { self.tls_listen_port.unwrap_or(DEFAULT_TLS_PORT) } /// port on which to listen for HTTPS connections pub fn https_listen_port(&self) -> u16 { self.https_listen_port.unwrap_or(DEFAULT_HTTPS_PORT) } /// port on which to listen for QUIC connections pub fn quic_listen_port(&self) -> u16 { self.quic_listen_port.unwrap_or(DEFAULT_QUIC_PORT) } /// port on which to listen for HTTP/3 connections pub fn h3_listen_port(&self) -> u16 { self.h3_listen_port.unwrap_or(DEFAULT_H3_PORT) } /// prometheus metric endpoint listen address #[cfg(feature = "prometheus-metrics")] pub fn prometheus_listen_addr(&self) -> SocketAddr { self.prometheus_listen_addr .unwrap_or(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9000)) } /// get if TCP protocol should be disabled pub fn disable_tcp(&self) -> bool { self.disable_tcp.unwrap_or_default() } /// get if UDP protocol should be disabled pub fn disable_udp(&self) -> bool { self.disable_udp.unwrap_or_default() } /// get if TLS protocol should be disabled pub fn disable_tls(&self) -> bool { self.disable_tls.unwrap_or_default() } /// get if HTTPS protocol should be disabled pub fn disable_https(&self) -> bool { self.disable_https.unwrap_or_default() } /// get if QUIC protocol should be disabled pub fn disable_quic(&self) -> bool { self.disable_quic.unwrap_or_default() } /// get if Prometheus metrics endpoint should be disabled #[cfg(feature = "prometheus-metrics")] pub fn disable_prometheus(&self) -> bool { self.disable_prometheus.unwrap_or_default() } /// default timeout for all TCP connections before forcibly shutdown pub fn tcp_request_timeout(&self) -> Duration { Duration::from_secs( self.tcp_request_timeout .unwrap_or(DEFAULT_TCP_REQUEST_TIMEOUT), ) } /// specify the log level which should be used, ["Trace", "Debug", "Info", "Warn", "Error"] pub fn log_level(&self) -> tracing::Level { if let Some(level_str) = &self.log_level { tracing::Level::from_str(level_str).unwrap_or(tracing::Level::INFO) } else { tracing::Level::INFO } } /// the path for all zone configurations, defaults to `/var/named` pub fn directory(&self) -> &Path { self.directory .as_ref() .map_or(Path::new(DEFAULT_PATH), Path::new) } /// the set of zones which should be loaded pub fn zones(&self) -> &[ZoneConfig] { &self.zones } /// the tls certificate to use for accepting tls connections pub fn tls_cert(&self) -> Option<&TlsCertConfig> { cfg_if! { if #[cfg(feature = "__tls")] { self.tls_cert.as_ref() } else { None } } } /// the HTTP endpoint from where requests are received #[cfg(any(feature = "__https", feature = "__h3"))] pub fn http_endpoint(&self) -> &str { self.http_endpoint .as_deref() .unwrap_or(hickory_proto::http::DEFAULT_DNS_QUERY_PATH) } /// get the networks denied access to this server pub fn deny_networks(&self) -> &[IpNet] { &self.deny_networks } /// get the networks allowed to connect to this server pub fn allow_networks(&self) -> &[IpNet] { &self.allow_networks } } #[derive(Deserialize, Debug)] struct ZoneConfigWithFile { file: Option, #[serde(flatten)] config: ZoneConfig, } fn deserialize_with_file<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, D::Error: serde::de::Error, { Vec::::deserialize(deserializer)? .into_iter() .map(|ZoneConfigWithFile { file, mut config }| match file { Some(file) => match &mut config.zone_type_config { ZoneTypeConfig::Primary(server_config) | ZoneTypeConfig::Secondary(server_config) => { if server_config .stores .iter() .any(|store| matches!(store, ServerStoreConfig::File(_))) { Err(::custom( "having `file` and `[zones.store]` item with type `file` is ambiguous", )) } else { let store = ServerStoreConfig::File(FileConfig { zone_file_path: file, }); if server_config.stores.len() == 1 && matches!(&server_config.stores[0], ServerStoreConfig::Default) { server_config.stores[0] = store; } else { server_config.stores.push(store); } Ok(config) } } _ => Err(::custom( "cannot use `file` on a zone that is not primary or secondary", )), }, _ => Ok(config), }) .collect::, _>>() } /// Configuration for a zone #[derive(Deserialize, Debug)] pub struct ZoneConfig { /// name of the zone pub zone: String, // TODO: make Domain::Name decodable /// type of the zone #[serde(flatten)] pub zone_type_config: ZoneTypeConfig, } impl ZoneConfig { #[warn(clippy::wildcard_enum_match_arm)] // make sure all cases are handled despite of non_exhaustive pub async fn load(&self, zone_dir: &Path) -> Result>, String> { debug!("loading zone with config: {self:#?}"); let zone_name = self .zone() .map_err(|err| format!("failed to read zone name: {err}"))?; let zone_type = self.zone_type(); // load the zone and insert any configured authorities in the catalog. let mut authorities: Vec> = vec![]; #[cfg(feature = "blocklist")] let handle_blocklist_store = |config| { let zone_name = zone_name.clone(); async move { Result::, String>::Ok(Arc::new( BlocklistAuthority::try_from_config( zone_name.clone(), zone_type, config, Some(zone_dir), ) .await?, )) } }; match &self.zone_type_config { ZoneTypeConfig::Primary(server_config) | ZoneTypeConfig::Secondary(server_config) => { debug!( "loading authorities for {zone_name} with stores {:?}", server_config.stores ); let is_axfr_allowed = server_config.is_axfr_allowed(); for store in &server_config.stores { let authority: Arc = match store { #[cfg(feature = "sqlite")] ServerStoreConfig::Sqlite(config) => { #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))] let mut authority = SqliteAuthority::try_from_config( zone_name.clone(), zone_type, is_axfr_allowed, server_config.is_dnssec_enabled(), Some(zone_dir), config, #[cfg(feature = "__dnssec")] server_config.nx_proof_kind.clone(), ) .await?; #[cfg(feature = "__dnssec")] server_config.load_keys(&mut authority, &zone_name).await?; Arc::new(authority) } ServerStoreConfig::File(config) => { #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))] let mut authority = FileAuthority::try_from_config( zone_name.clone(), zone_type, is_axfr_allowed, Some(zone_dir), config, #[cfg(feature = "__dnssec")] server_config.nx_proof_kind.clone(), )?; #[cfg(feature = "__dnssec")] server_config.load_keys(&mut authority, &zone_name).await?; Arc::new(authority) } _ => return empty_stores_error(), }; authorities.push(authority); } } ZoneTypeConfig::External { stores } => { debug!( "loading authorities for {zone_name} with stores {:?}", stores ); #[cfg_attr( not(any(feature = "blocklist", feature = "resolver")), allow(unreachable_code, unused_variables, clippy::never_loop) )] for store in stores { let authority: Arc = match store { #[cfg(feature = "blocklist")] ExternalStoreConfig::Blocklist(config) => { handle_blocklist_store(config).await? } #[cfg(feature = "resolver")] ExternalStoreConfig::Forward(config) => { let forwarder = ForwardAuthority::builder_tokio(config.clone()) .with_origin(zone_name.clone()) .build()?; Arc::new(forwarder) } #[cfg(feature = "recursor")] ExternalStoreConfig::Recursor(config) => { let recursor = RecursiveAuthority::try_from_config( zone_name.clone(), zone_type, config, Some(zone_dir), ) .await?; Arc::new(recursor) } _ => return empty_stores_error(), }; authorities.push(authority); } } } info!("zone successfully loaded: {}", self.zone()?); Ok(authorities) } // TODO this is a little ugly for the parse, b/c there is no terminal char /// returns the name of the Zone, i.e. the `example.com` of `www.example.com.` pub fn zone(&self) -> Result { Name::parse(&self.zone, Some(&Name::new())) } /// the type of the zone pub fn zone_type(&self) -> ZoneType { match &self.zone_type_config { ZoneTypeConfig::Primary { .. } => ZoneType::Primary, ZoneTypeConfig::Secondary { .. } => ZoneType::Secondary, ZoneTypeConfig::External { .. } => ZoneType::External, } } } fn empty_stores_error() -> Result { Result::Err("empty [[zones.stores]] in config".to_owned()) } #[derive(Deserialize, Debug)] #[serde(tag = "zone_type")] #[serde(deny_unknown_fields)] /// Enumeration over each zone type's configuration. pub enum ZoneTypeConfig { Primary(ServerZoneConfig), Secondary(ServerZoneConfig), External { /// Store configurations. Note: we specify a default handler to get a Vec containing a /// StoreConfig::Default, which is used for authoritative file-based zones and legacy sqlite /// configurations. #[serde(default)] cannot be used, because it will invoke Default for Vec, /// i.e., an empty Vec and we cannot implement Default for StoreConfig and return a Vec. The /// custom visitor is used to handle map (single store) or sequence (chained store) configurations. #[serde(default = "store_config_default")] #[serde(deserialize_with = "store_config_visitor")] stores: Vec, }, } impl ZoneTypeConfig { pub fn as_server(&self) -> Option<&ServerZoneConfig> { match self { Self::Primary(c) | Self::Secondary(c) => Some(c), _ => None, } } } #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct ServerZoneConfig { /// Allow AXFR (TODO: need auth) pub allow_axfr: Option, /// Keys for use by the zone #[cfg(feature = "__dnssec")] #[serde(default)] pub keys: Vec, /// The kind of non-existence proof provided by the nameserver #[cfg(feature = "__dnssec")] pub nx_proof_kind: Option, /// Store configurations. Note: we specify a default handler to get a Vec containing a /// StoreConfig::Default, which is used for authoritative file-based zones and legacy sqlite /// configurations. #[serde(default)] cannot be used, because it will invoke Default for Vec, /// i.e., an empty Vec and we cannot implement Default for StoreConfig and return a Vec. The /// custom visitor is used to handle map (single store) or sequence (chained store) configurations. #[serde(default = "store_config_default")] #[serde(deserialize_with = "store_config_visitor")] pub stores: Vec, } impl ServerZoneConfig { #[cfg(feature = "__dnssec")] async fn load_keys( &self, authority: &mut impl DnssecAuthority, zone_name: &Name, ) -> Result<(), String> { if !self.is_dnssec_enabled() { return Ok(()); } for key_config in &self.keys { key_config.load(authority, zone_name.clone()).await?; } info!("signing zone: {zone_name}"); authority .secure_zone() .await .map_err(|err| format!("failed to sign zone {zone_name}: {err}"))?; Ok(()) } /// path to the zone file, i.e. the base set of original records in the zone /// /// this is only used on first load, if dynamic update is enabled for the zone, then the journal /// file is the actual source of truth for the zone. pub fn file(&self) -> Option<&Path> { self.stores.iter().find_map(|store| match store { ServerStoreConfig::File(file_config) => Some(&*file_config.zone_file_path), #[cfg(feature = "sqlite")] ServerStoreConfig::Sqlite(sqlite_config) => Some(&*sqlite_config.zone_file_path), ServerStoreConfig::Default => None, }) } /// enable AXFR transfers pub fn is_axfr_allowed(&self) -> bool { self.allow_axfr.unwrap_or(false) } /// declare that this zone should be signed, see keys for configuration of the keys for signing pub fn is_dnssec_enabled(&self) -> bool { cfg_if! { if #[cfg(feature = "__dnssec")] { !self.keys.is_empty() } else { false } } } } /// Enumeration over store types for secondary nameservers. #[derive(Deserialize, Debug, Default)] #[serde(tag = "type")] #[serde(rename_all = "lowercase")] #[non_exhaustive] pub enum ServerStoreConfig { /// File based configuration File(FileConfig), /// Sqlite based configuration file #[cfg(feature = "sqlite")] Sqlite(SqliteConfig), /// This is used by the configuration processing code to represent a deprecated or main-block config without an associated store. #[default] Default, } /// Enumeration over store types for external nameservers. #[allow(clippy::large_enum_variant)] #[derive(Deserialize, Debug, Default)] #[serde(rename_all = "lowercase", tag = "type")] #[non_exhaustive] pub enum ExternalStoreConfig { /// Blocklist configuration #[cfg(feature = "blocklist")] Blocklist(BlocklistConfig), /// Forwarding Resolver #[cfg(feature = "resolver")] Forward(ForwardConfig), /// Recursive Resolver #[cfg(feature = "recursor")] Recursor(Box), /// This is used by the configuration processing code to represent a deprecated or main-block config without an associated store. #[default] Default, } /// Create a default value for serde for store config enums. fn store_config_default() -> Vec { vec![Default::default()] } /// Custom serde visitor that can deserialize a map (single configuration store, expressed as a TOML /// table) or sequence (chained configuration stores, expressed as a TOML array of tables.) /// This is used instead of an untagged enum because serde cannot provide variant-specific error /// messages when using an untagged enum. fn store_config_visitor<'de, D, T>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { struct MapOrSequence(std::marker::PhantomData); impl<'de, T: Deserialize<'de>> Visitor<'de> for MapOrSequence { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("map or sequence") } fn visit_seq(self, seq: S) -> Result, S::Error> where S: SeqAccess<'de>, { Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) } fn visit_map(self, map: M) -> Result, M::Error> where M: MapAccess<'de>, { match Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) { Ok(seq) => Ok(vec![seq]), Err(e) => Err(e), } } } deserializer.deserialize_any(MapOrSequence::(Default::default())) } /// Configuration for a TLS certificate #[derive(Deserialize, PartialEq, Eq, Debug)] #[serde(deny_unknown_fields)] #[non_exhaustive] pub struct TlsCertConfig { pub path: PathBuf, pub endpoint_name: Option, pub private_key: PathBuf, } #[cfg(feature = "__tls")] impl TlsCertConfig { /// Load a Certificate from the path (with rustls) pub fn load(&self, zone_dir: &Path) -> Result, String> { if self.path.extension().and_then(OsStr::to_str) != Some("pem") { return Err(format!( "unsupported certificate file format (expected `.pem` extension): {}", self.path.display() )); } let cert_path = zone_dir.join(&self.path); info!( "loading TLS PEM certificate chain from: {}", cert_path.display() ); let cert_chain = CertificateDer::pem_file_iter(&cert_path) .map_err(|e| { format!( "failed to read cert chain from {}: {e}", cert_path.display() ) })? .collect::, _>>() .map_err(|e| { format!( "failed to parse cert chain from {}: {e}", cert_path.display() ) })?; let key_extension = self.private_key.extension(); let key = if key_extension.is_some_and(|ext| ext == "pem") { let key_path = zone_dir.join(&self.private_key); info!("loading TLS PKCS8 key from PEM: {}", key_path.display()); PrivateKeyDer::from_pem_file(&key_path) .map_err(|e| format!("failed to read key from {}: {e}", key_path.display()))? } else if key_extension.is_some_and(|ext| ext == "der" || ext == "key") { let key_path = zone_dir.join(&self.private_key); info!("loading TLS PKCS8 key from DER: {}", key_path.display()); let buf = fs::read(&key_path).map_err(|e| format!("error reading key from file: {e}"))?; PrivateKeyDer::try_from(buf).map_err(|e| format!("error parsing key DER: {e}"))? } else { return Err(format!( "unsupported private key file format (expected `.pem` or `.der` extension): {}", self.private_key.display() )); }; let certified_key = CertifiedKey::from_der(cert_chain, key, &default_provider()) .map_err(|err| format!("failed to read certificate and keys: {err:?}"))?; Ok(Arc::new(SingleCertAndKey::from(certified_key))) } } #[cfg(all(test, any(feature = "resolver", feature = "recursor")))] mod tests { use super::*; #[cfg(feature = "recursor")] #[test] fn example_recursor_config() { toml::from_str::(include_str!( "../../tests/test-data/test_configs/example_recursor.toml" )) .unwrap(); } #[cfg(feature = "resolver")] #[test] fn single_store_config_error_message() { match toml::from_str::( r#"[[zones]] zone = "." zone_type = "External" [zones.stores] ype = "forward""#, ) { Ok(val) => panic!("expected error value; got ok: {val:?}"), Err(e) => assert!(e.to_string().contains("missing field `type`")), } } #[cfg(feature = "resolver")] #[test] fn chained_store_config_error_message() { match toml::from_str::( r#"[[zones]] zone = "." zone_type = "External" [[zones.stores]] type = "forward" [[zones.stores.name_servers]] socket_addr = "8.8.8.8:53" protocol = "udp" trust_negative_responses = false [[zones.stores]] type = "forward" [[zones.stores.name_servers]] socket_addr = "1.1.1.1:53" rotocol = "udp" trust_negative_responses = false"#, ) { Ok(val) => panic!("expected error value; got ok: {val:?}"), Err(e) => assert!(dbg!(e).to_string().contains("unknown field `rotocol`")), } } #[cfg(feature = "resolver")] #[test] fn file_store_zone_file_path() { match toml::from_str::( r#"[[zones]] zone = "localhost" zone_type = "Primary" [zones.stores] type = "file" zone_file_path = "default/localhost.zone""#, ) { Ok(val) => { let ZoneTypeConfig::Primary(config) = &val.zones[0].zone_type_config else { panic!("expected primary zone type"); }; assert_eq!(config.stores.len(), 1); assert!(matches!( &config.stores[0], ServerStoreConfig::File(FileConfig { zone_file_path }) if zone_file_path == Path::new("default/localhost.zone"), )); } Err(e) => panic!("expected successful parse: {e:?}"), } } } hickory-dns-0.25.2/src/prometheus_server.rs000064400000000000000000000070341046102023000170660ustar 00000000000000use std::future::{Ready, ready}; use http::header::CONTENT_TYPE; use hyper::{Request, Response, body::Incoming, service::Service}; use hyper_util::{ rt::{TokioExecutor, TokioIo}, server::{conn::auto::Builder, graceful::GracefulShutdown}, }; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; use tokio::{net::TcpListener, select, task::JoinHandle}; use tokio_util::sync::CancellationToken; use tracing::{debug, error}; /// An HTTP server that repsonds to Prometheus scrape requests. pub struct PrometheusServer { join_handle: JoinHandle<()>, cancellation_token: CancellationToken, } impl PrometheusServer { /// Register a metrics recorder, and start an HTTP server with the provided listener to provide /// metrics to Prometheus. pub fn new(listener: TcpListener) -> Result { // Set up metrics recorder. let handle = PrometheusBuilder::new() .install_recorder() .map_err(|e| format!("failed to install prometheus endpoint {e}"))?; let service = PrometheusService::new(handle); let cancellation_token = CancellationToken::new(); let token_clone = cancellation_token.clone(); let shutdown = GracefulShutdown::new(); let join_handle = tokio::spawn(async move { let builder = Builder::new(TokioExecutor::new()); loop { let stream = select! { result = listener.accept() => { match result { Ok((stream, _)) => stream, Err(error) => { debug!(%error, "error accepting connection"); continue; } } }, _ = cancellation_token.cancelled() => { drop(listener); break; }, }; let io = TokioIo::new(stream); let conn = builder.serve_connection_with_upgrades(io, service.clone()); let conn = shutdown.watch(conn.into_owned()); tokio::spawn(async move { if let Err(error) = conn.await { debug!(%error, "connection error"); } }); } shutdown.shutdown().await; }); Ok(Self { join_handle, cancellation_token: token_clone, }) } /// Stop the Prometheus HTTP server. pub async fn stop(self) { self.cancellation_token.cancel(); if let Err(error) = self.join_handle.await { error!(%error, "Error from Prometheus server task"); } } } #[derive(Clone)] struct PrometheusService { handle: PrometheusHandle, } impl PrometheusService { fn new(handle: PrometheusHandle) -> Self { Self { handle } } } impl Service> for PrometheusService { type Response = Response; type Error = Box; type Future = Ready, Box>>; fn call(&self, _req: Request) -> Self::Future { let response_builder = Response::builder().header(CONTENT_TYPE, "text/plain; version=0.0.4"); match response_builder.body(self.handle.render()) { Ok(response) => ready(Ok(response)), Err(e) => ready(Err(Box::new(e))), } } } hickory-dns-0.25.2/tests/integration/authority_battery/basic.rs000064400000000000000000000573461046102023000230610ustar 00000000000000#![allow(clippy::dbg_macro)] use std::future::Future; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use futures_executor::block_on; use hickory_proto::{ op::{Header, Message, Query, ResponseCode}, rr::{ Name, RData, Record, RecordType, rdata::{A as A4, AAAA}, }, serialize::binary::BinDecodable, xfer::Protocol, }; use hickory_server::{ authority::{AuthLookup, Authority, LookupError, LookupOptions, MessageRequest}, server::RequestInfo, }; const TEST_HEADER: &Header = &Header::new(); pub fn test_a_lookup>(authority: A) { let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); match lookup .into_iter() .next() .expect("A record not found in authority") .data() .as_a() { Some(ip) => assert_eq!(A4::new(127, 0, 0, 1), *ip), _ => panic!("wrong rdata type returned"), } } #[allow(clippy::unreadable_literal)] pub fn test_soa>(authority: A) { let lookup = block_on(authority.soa()).unwrap(); match lookup .into_iter() .next() .expect("SOA record not found in authority") .data() { RData::SOA(soa) => { assert_eq!(Name::from_str("hickory-dns.org.").unwrap(), *soa.mname()); assert_eq!( Name::from_str("root.hickory-dns.org.").unwrap(), *soa.rname() ); assert_eq!(199609203, soa.serial()); assert_eq!(28800, soa.refresh()); assert_eq!(7200, soa.retry()); assert_eq!(604800, soa.expire()); assert_eq!(86400, soa.minimum()); } _ => panic!("wrong rdata type returned"), } } pub fn test_ns>(authority: A) { let lookup = block_on(authority.ns(LookupOptions::default())).unwrap(); match lookup .into_iter() .next() .expect("NS record not found in authority") .data() { RData::NS(name) => assert_eq!(Name::from_str("bbb.example.com.").unwrap(), name.0), _ => panic!("wrong rdata type returned"), } } pub fn test_ns_lookup>(authority: A) { let query = Query::query(Name::from_str("example.com.").unwrap(), RecordType::NS).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = dbg!( lookup .take_additionals() .expect("no additionals in response") ); let ns = lookup .into_iter() .next() .expect("NS record not found in authority") .data() .as_ns() .expect("Not an NS record"); assert_eq!(Name::from_str("bbb.example.com.").unwrap(), ns.0); let a = additionals .into_iter() .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 2), *a); } pub fn test_mx>(authority: A) { let query = Query::query(Name::from_str("example.com.").unwrap(), RecordType::MX).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = dbg!( lookup .take_additionals() .expect("no additionals in response") ); let mx = lookup .into_iter() .next() .expect("MX record not found in authority") .data() .as_mx() .expect("Not an MX record"); assert_eq!( Name::from_str("alias.example.com.").unwrap(), *mx.exchange() ); // assert the A record is in the additionals section let mut additionals = additionals.into_iter(); let cname = additionals .next() .expect("CNAME record not found") .data() .as_cname() .expect("Not an CNAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); let a = additionals .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); let aaaa = additionals .next() .expect("AAAA record not found") .data() .as_aaaa() .expect("Not an AAAA record"); assert_eq!(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1), *aaaa); } pub fn test_mx_to_null>(authority: A) { let query = Query::query( Name::from_str("no-service.example.com.").unwrap(), RecordType::MX, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); // In this case there should be no additional records assert!(lookup.take_additionals().is_none()); let mx = lookup .into_iter() .next() .expect("MX record not found in authority") .data() .as_mx() .expect("Not an MX record"); assert_eq!(Name::from_str(".").unwrap(), *mx.exchange()); } pub fn test_cname>(authority: A) { let query = Query::query( Name::from_str("alias.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let cname = lookup .into_iter() .next() .expect("CNAME record not found in authority") .data() .as_cname() .expect("Not an A record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); } pub fn test_cname_alias>(authority: A) { let query = Query::query(Name::from_str("alias.example.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = lookup .take_additionals() .expect("no additionals in response"); // for cname lookups, we have a cname returned in the answer, the catalog will perform additional lookups let cname = lookup .into_iter() .next() .expect("CNAME record not found in authority") .data() .as_cname() .expect("Not a CNAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); // assert the A record is in the additionals section let a = additionals .into_iter() .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); } pub fn test_cname_chain>(authority: A) { let query = Query::query( Name::from_str("alias-chain.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = lookup .take_additionals() .expect("no additionals in response"); // for cname lookups, we have a cname returned in the answer, the catalog will perform additional lookups let cname = lookup .into_iter() .next() .expect("CNAME record not found in authority") .data() .as_cname() .expect("Not a CNAME record"); assert_eq!(Name::from_str("alias.example.com.").unwrap(), cname.0); // assert the A record is in the additionals section let mut additionals = additionals.into_iter(); let cname = additionals .next() .expect("CNAME record not found") .data() .as_cname() .expect("Not an CNAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); let a = additionals .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); } /// In this the ANAME , should, return A and AAAA records in additional section /// the answer should be the A record pub fn test_aname>(authority: A) { let query = Query::query(Name::from_str("example.com.").unwrap(), RecordType::ANAME).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = lookup .take_additionals() .expect("no additionals from ANAME"); let aname = lookup .into_iter() .next() .expect("ANAME record not found in authority") .data() .as_aname() .expect("Not an ANAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), aname.0); // check that additionals contain the info let a = additionals .iter() .find(|r| r.record_type() == RecordType::A) .map(Record::data) .and_then(RData::as_a) .expect("A not found"); assert_eq!(A4::new(127, 0, 0, 1), *a); let aaaa = additionals .iter() .find(|r| r.record_type() == RecordType::AAAA) .map(Record::data) .and_then(RData::as_aaaa) .expect("AAAA not found"); assert_eq!(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1), *aaaa); } /// In this test the A record that the ANAME resolves to should be returned as the answer, /// /// The additionals should include the ANAME. pub fn test_aname_a_lookup>(authority: A) { let query = Query::query(Name::from_str("example.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = lookup.take_additionals().expect("no additionals for aname"); // the name should match the lookup, not the A records let (name, a) = lookup .into_iter() .next() .map(|r| (r.name(), r.data())) .expect("No A answer"); let a = a.as_a().expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); assert_eq!(Name::from_str("example.com.").unwrap(), *name); // check that additionals contain the info let aname = additionals .into_iter() .next() .expect("ANAME record not found in authority") .data() .as_aname() .expect("Not an ANAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), aname.0); } /// In this test the A record that the ANAME resolves to should be returned as the answer, not at the apex /// /// The additionals should include the ANAME, this one should include the CNAME chain as well. pub fn test_aname_chain>(authority: A) { let query = Query::query( Name::from_str("aname-chain.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = lookup.take_additionals().expect("no additionals"); let (name, a) = lookup .into_iter() .next() .map(|r| (r.name(), r.data())) .expect("Not an A record"); let a = a.as_a().expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); assert_eq!(Name::from_str("aname-chain.example.com.").unwrap(), *name); // the name should match the lookup, not the A records let mut additionals = additionals.into_iter(); let aname = additionals .next() .expect("ANAME record not found in authority") .data() .as_aname() .expect("Not an ANAME record"); assert_eq!(Name::from_str("alias.example.com.").unwrap(), aname.0); let cname = additionals .next() .expect("CNAME record not found") .data() .as_cname() .expect("Not an CNAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); let a = additionals .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); } pub fn test_update_errors>(mut authority: A) { let mut message = Message::default(); message.add_query(Query::new()); let bytes = message.to_vec().unwrap(); let update = MessageRequest::from_bytes(&bytes).unwrap(); // this is expected to fail, i.e. updates are not allowed assert!(block_on(authority.update(&update)).is_err()); } #[allow(clippy::uninlined_format_args)] pub fn test_dots_in_name>(authority: A) { let query = Query::query( Name::from_str("this.has.dots.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!( *lookup .into_iter() .next() .expect("A record not found in authority") .data() .as_a() .expect("wrong rdata type returned"), A4::new(127, 0, 0, 3) ); // the rest should all be NameExists let query = Query::query( Name::from_str("has.dots.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap_err(); assert!(lookup.is_name_exists(), "lookup: {}", lookup); // the rest should all be NameExists let query = Query::query(Name::from_str("dots.example.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap_err(); assert!(lookup.is_name_exists()); // and this should be an NXDOMAIN let query = Query::query( Name::from_str("not.this.has.dots.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap_err(); assert!(lookup.is_nx_domain()); } pub fn test_wildcard>(authority: A) { // check direct lookup let query = Query::query( Name::from_str("*.wildcard.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!( lookup .into_iter() .next() .expect("CNAME record not found in authority") .data() .as_cname() .expect("wrong rdata type returned") .0, Name::from_str("www.example.com.").unwrap() ); // check wildcard lookup let query = Query::query( Name::from_str("www.wildcard.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())) .expect("lookup of www.wildcard.example.com. failed"); assert_eq!( lookup .into_iter() .next() .map(|r| { assert_eq!( *r.name(), Name::from_str("www.wildcard.example.com.").unwrap() ); r }) .expect("CNAME record not found in authority") .data() .as_cname() .expect("wrong rdata type returned") .0, Name::from_str("www.example.com.").unwrap() ); } pub fn test_wildcard_subdomain>(authority: A) { // check wildcard lookup let query = Query::query( Name::from_str("subdomain.www.wildcard.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())) .expect("lookup of subdomain.www.wildcard.example.com. failed"); assert_eq!( lookup .into_iter() .next() .map(|r| { assert_eq!( *r.name(), Name::from_str("subdomain.www.wildcard.example.com.").unwrap() ); r }) .expect("CNAME record not found in authority") .data() .as_cname() .expect("wrong rdata type returned") .0, Name::from_str("www.example.com.").unwrap() ); } pub fn test_wildcard_chain>(authority: A) { // check wildcard lookup let query = Query::query( Name::from_str("www.wildcard.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())) .expect("lookup of www.wildcard.example.com. failed"); // the name should match the lookup, not the A records let additionals = lookup.take_additionals().expect("no additionals"); assert_eq!( lookup .into_iter() .next() .expect("CNAME record not found in authority") .data() .as_cname() .expect("wrong rdata type returned") .0, Name::from_str("www.example.com.").unwrap() ); let mut additionals = additionals.into_iter(); let a = additionals .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); } pub fn test_srv>(authority: A) { let query = Query::query( Name::from_str("server.example.com.").unwrap(), RecordType::SRV, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let mut lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); let additionals = dbg!( lookup .take_additionals() .expect("no additionals in response") ); let srv = lookup .into_iter() .next() .expect("SRV record not found in authority") .data() .as_srv() .expect("Not an SRV record"); assert_eq!(Name::from_str("alias.example.com.").unwrap(), *srv.target()); // assert the A record is in the additionals section let mut additionals = additionals.into_iter(); let cname = additionals .next() .expect("CNAME record not found") .data() .as_cname() .expect("Not an CNAME record"); assert_eq!(Name::from_str("www.example.com.").unwrap(), cname.0); let a = additionals .next() .expect("A record not found") .data() .as_a() .expect("Not an A record"); assert_eq!(A4::new(127, 0, 0, 1), *a); let aaaa = additionals .next() .expect("AAAA record not found") .data() .as_aaaa() .expect("Not an AAAA record"); assert_eq!(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1), *aaaa); } pub fn test_invalid_lookup>(authority: A) { let query = Query::query(Name::from_str("www.google.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); let err = lookup.expect_err("Lookup for www.google.com succeeded"); match err { LookupError::ResponseCode(code) => assert_eq!(code, ResponseCode::Refused), _ => panic!("invalid error enum variant"), } } // test some additional record collections macro_rules! define_basic_test { ($new:expr; $( $f:ident, )*) => { $( #[test] fn $f () { subscribe(); use std::path::Path; let authority = $new(&Path::new("../tests/test-data/test_configs/example.com.zone"), module_path!(), stringify!($f)); crate::authority_battery::basic::$f(authority); } )* } } macro_rules! basic_battery { ($name:ident, $new:expr) => { #[cfg(test)] mod basic { mod $name { use test_support::subscribe; define_basic_test!($new; test_a_lookup, test_soa, test_ns, test_ns_lookup, test_mx, test_mx_to_null, test_cname, test_cname_alias, test_cname_chain, test_aname, test_aname_a_lookup, test_aname_chain, test_update_errors, test_dots_in_name, test_wildcard, test_wildcard_subdomain, test_wildcard_chain, test_srv, test_invalid_lookup, ); } } }; } hickory-dns-0.25.2/tests/integration/authority_battery/dnssec.rs000064400000000000000000000403101046102023000232360ustar 00000000000000#![cfg(feature = "__dnssec")] use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; use std::{future::Future, sync::Arc}; use futures_executor::block_on; use hickory_proto::dnssec::rdata::NSEC; use hickory_proto::{ dnssec::{ Algorithm, Verifier, rdata::{DNSKEY, RRSIG}, verify_nsec, }, op::{Header, Query}, rr::{DNSClass, Name, RData, Record, RecordType}, xfer::{self, Protocol}, }; use hickory_server::{ authority::{AuthLookup, Authority, DnssecAuthority, LookupOptions}, server::RequestInfo, }; const TEST_HEADER: &Header = &Header::new(); pub fn test_a_lookup>(authority: A, keys: &[DNSKEY]) { let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::for_dnssec(true))).unwrap(); let (a_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::A); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&a_records, &rrsig_records, keys); } #[allow(clippy::unreadable_literal)] pub fn test_soa>(authority: A, keys: &[DNSKEY]) { let lookup = block_on(authority.soa_secure(LookupOptions::for_dnssec(true))).unwrap(); let (soa_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::SOA); assert_eq!(soa_records.len(), 1); let soa = soa_records.first().unwrap().data().as_soa().unwrap(); assert_eq!(Name::from_str("hickory-dns.org.").unwrap(), *soa.mname()); assert_eq!( Name::from_str("root.hickory-dns.org.").unwrap(), *soa.rname() ); assert!(199609203 < soa.serial()); // serial should be one or more b/c of the signing process assert_eq!(28800, soa.refresh()); assert_eq!(7200, soa.retry()); assert_eq!(604800, soa.expire()); assert_eq!(86400, soa.minimum()); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&soa_records, &rrsig_records, keys); } pub fn test_ns>(authority: A, keys: &[DNSKEY]) { let lookup = block_on(authority.ns(LookupOptions::for_dnssec(true))).unwrap(); let (ns_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::NS); assert_eq!( ns_records.first().unwrap().data().as_ns().unwrap().0, Name::from_str("bbb.example.com.").unwrap() ); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&ns_records, &rrsig_records, keys); } pub fn test_aname_lookup>(authority: A, keys: &[DNSKEY]) { let query = Query::query( Name::from_str("aname-chain.example.com.").unwrap(), RecordType::A, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::for_dnssec(true))).unwrap(); let (a_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::A); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&a_records, &rrsig_records, keys); } pub fn test_wildcard>(authority: A, keys: &[DNSKEY]) { // check wildcard lookup let query = Query::query( Name::from_str("www.wildcard.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::for_dnssec(true))) .expect("lookup of www.wildcard.example.com. failed"); let (cname_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::CNAME); assert!( cname_records .iter() .all(|r| *r.name() == Name::from_str("www.wildcard.example.com.").unwrap()) ); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&cname_records, &rrsig_records, keys); } pub fn test_wildcard_subdomain>(authority: A, keys: &[DNSKEY]) { // check wildcard lookup let query = Query::query( Name::from_str("subdomain.www.wildcard.example.com.").unwrap(), RecordType::CNAME, ) .into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::for_dnssec(true))) .expect("lookup of subdomain.www.wildcard.example.com. failed"); let (cname_records, other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .partition(|r| r.record_type() == RecordType::CNAME); assert!( cname_records .iter() .all(|r| *r.name() == Name::from_str("subdomain.www.wildcard.example.com.").unwrap()) ); let rrsig_records: Vec<_> = other_records .into_iter() .cloned() .filter_map(|r| Record::::try_from(r).ok()) .collect(); assert!(!rrsig_records.is_empty()); verify(&cname_records, &rrsig_records, keys); } pub fn test_nsec_nodata>(authority: A, keys: &[DNSKEY]) { // this should have a single nsec record that covers the type let name = Name::from_str("www.example.com.").unwrap(); let lookup = block_on(authority.get_nsec_records(&name.clone().into(), LookupOptions::for_dnssec(true))) .unwrap(); let (nsec_records, _other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .cloned() .partition(|r| r.record_type() == RecordType::NSEC); println!("nsec_records: {nsec_records:?}"); // there should only be one, and it should match the www.example.com name assert_eq!(nsec_records.len(), 1); assert_eq!(nsec_records.first().unwrap().name(), &name); let query = Query::query(name, RecordType::TXT); assert!( verify_nsec( &query, &Name::from_str("example.com.").unwrap(), &nsecs(&nsec_records) ) .is_secure() ); } pub fn test_nsec_nxdomain_start>(authority: A, keys: &[DNSKEY]) { // tests between the SOA and first record in the zone, where bbb is the first zone record let name = Name::from_str("aaa.example.com.").unwrap(); let lookup = block_on(authority.get_nsec_records(&name.clone().into(), LookupOptions::for_dnssec(true))) .unwrap(); let (nsec_records, _other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .cloned() .partition(|r| r.record_type() == RecordType::NSEC); println!("nsec_records: {nsec_records:?}"); // there should only be one, and it should match the www.example.com name assert!(!nsec_records.is_empty()); // because the first record is from the SOA, the wildcard isn't necessary // that is `example.com.` -> `bbb.example.com.` proves there is no wildcard. assert_eq!(nsec_records.len(), 1); let query = Query::query(name, RecordType::A); assert!( verify_nsec( &query, &Name::from_str("example.com.").unwrap(), &nsecs(&nsec_records) ) .is_secure() ); } pub fn test_nsec_nxdomain_middle>(authority: A, keys: &[DNSKEY]) { // follows the first record, nsec should cover between ccc and www, where bbb is the first zone record let name = Name::from_str("ccc.example.com.").unwrap(); let lookup = block_on(authority.get_nsec_records(&name.clone().into(), LookupOptions::for_dnssec(true))) .unwrap(); let (nsec_records, _other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .cloned() .partition(|r| r.record_type() == RecordType::NSEC); println!("nsec_records: {nsec_records:?}"); // there should only be one, and it should match the www.example.com name assert!(!nsec_records.is_empty()); // one record covers between the names, the other is for the wildcard proof. assert_eq!(nsec_records.len(), 2); let query = Query::query(name, RecordType::A); assert!( verify_nsec( &query, &Name::from_str("example.com.").unwrap(), &nsecs(&nsec_records) ) .is_secure() ); } pub fn test_nsec_nxdomain_wraps_end>( authority: A, keys: &[DNSKEY], ) { // wraps back to the beginning of the zone, where www is the last zone record let name = Name::from_str("zzz.example.com.").unwrap(); let lookup = block_on(authority.get_nsec_records(&name.clone().into(), LookupOptions::for_dnssec(true))) .unwrap(); let (nsec_records, _other_records): (Vec<_>, Vec<_>) = lookup .into_iter() .cloned() .partition(|r| r.record_type() == RecordType::NSEC); println!("nsec_records: {nsec_records:?}"); // there should only be one, and it should match the www.example.com name assert!(!nsec_records.is_empty()); // one record covers between the names, the other is for the wildcard proof. assert_eq!(nsec_records.len(), 2); let query = Query::query(name, RecordType::A); assert!( verify_nsec( &query, &Name::from_str("example.com.").unwrap(), &nsecs(&nsec_records) ) .is_secure() ); } fn nsecs<'a>(records: impl IntoIterator) -> Vec<(&'a Name, &'a NSEC)> { records .into_iter() .filter_map(|rr| { rr.data() .as_dnssec()? .as_nsec() .map(|data| (rr.name(), data)) }) .collect() } pub fn verify(records: &[&Record], rrsig_records: &[Record], keys: &[DNSKEY]) { let record_name = records.first().unwrap().name(); let record_type = records.first().unwrap().record_type(); println!("record_name: {record_name}, type: {record_type}"); // should be signed with all the keys assert!(keys.iter().all(|key| { rrsig_records .iter() .map(|rrsig| rrsig.data()) .filter(|rrsig| rrsig.algorithm() == key.algorithm()) .filter(|rrsig| rrsig.key_tag() == key.calculate_key_tag().unwrap()) .filter(|rrsig| rrsig.type_covered() == record_type) .any(|rrsig| { key.verify_rrsig(record_name, DNSClass::IN, rrsig, records.iter().copied()) .map_err(|e| println!("failed to verify: {e}")) .is_ok() }) })); } pub fn add_signers(authority: &mut A) -> Vec { use hickory_dns::dnssec::{KeyConfig, KeyPurpose}; let signer_name = Name::from(authority.origin().to_owned()); let mut keys = Vec::::new(); // TODO: support RSA signing with ring #[cfg(feature = "__dnssec")] // rsa { let key_config = KeyConfig { key_path: PathBuf::from("../tests/test-data/test_configs/dnssec/rsa_2048.pk8"), algorithm: Algorithm::RSASHA512, signer_name: Some(signer_name.to_string()), purpose: KeyPurpose::ZoneSigning, }; let signer = key_config .try_into_signer(signer_name.clone()) .expect("failed to read key_config"); keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); block_on(authority.add_zone_signing_key(signer)).expect("failed to add signer to zone"); block_on(authority.secure_zone()).expect("failed to sign zone"); } // // TODO: why are ecdsa tests failing in this context? // // ecdsa_p256 // { // let key_config = KeyConfig { // key_path: "../../tests/test-data/test_configs/dnssec/ecdsa_p256.pem".to_string(), // password: None, // algorithm: Algorithm::ECDSAP256SHA256.to_string(), // signer_name: Some(signer_name.clone().to_string()), // is_zone_signing_key: Some(true), // is_zone_update_auth: Some(false), // }; // let signer = key_config.try_into_signer(signer_name.clone()).expect("failed to read key_config"); // keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); // authority.add_zone_signing_key(signer).expect("failed to add signer to zone"); // authority.secure_zone().expect("failed to sign zone"); // } // // ecdsa_p384 // { // let key_config = KeyConfig { // key_path: "../../tests/test-data/test_configs/dnssec/ecdsa_p384.pem".to_string(), // password: None, // algorithm: Algorithm::ECDSAP384SHA384.to_string(), // signer_name: Some(signer_name.clone().to_string()), // is_zone_signing_key: Some(true), // is_zone_update_auth: Some(false), // }; // let signer = key_config.try_into_signer(signer_name.clone()).expect("failed to read key_config"); // keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); // authority.add_zone_signing_key(signer).expect("failed to add signer to zone"); // authority.secure_zone().expect("failed to sign zone"); // } // ed 25519 #[cfg(feature = "__dnssec")] { let key_config = KeyConfig { key_path: PathBuf::from("../tests/test-data/test_configs/dnssec/ed25519.pk8"), algorithm: Algorithm::ED25519, signer_name: Some(signer_name.to_string()), purpose: KeyPurpose::ZoneSigning, }; let signer = key_config .try_into_signer(signer_name) .expect("failed to read key_config"); keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); block_on(authority.add_zone_signing_key(signer)).expect("failed to add signer to zone"); block_on(authority.secure_zone()).expect("failed to sign zone"); } keys } macro_rules! define_dnssec_test { ($new:expr; $( $f:ident, )*) => { $( #[test] fn $f () { ::test_support::subscribe(); use std::path::Path; let mut authority = $new(&Path::new("../tests/test-data/test_configs/example.com.zone"), module_path!(), stringify!($f)); let keys = crate::authority_battery::dnssec::add_signers(&mut authority); crate::authority_battery::dnssec::$f(authority, &keys); } )* } } macro_rules! dnssec_battery { ($name:ident, $new:expr) => { #[cfg(test)] mod dnssec { mod $name { define_dnssec_test!($new; test_a_lookup, test_soa, test_ns, test_aname_lookup, test_wildcard, test_wildcard_subdomain, test_nsec_nodata, test_nsec_nxdomain_start, test_nsec_nxdomain_middle, test_nsec_nxdomain_wraps_end, ); } } }; } hickory-dns-0.25.2/tests/integration/authority_battery/dynamic_update.rs000064400000000000000000000725201046102023000247550ustar 00000000000000#![cfg(feature = "__dnssec")] use std::{ future::Future, net::{Ipv4Addr, Ipv6Addr, SocketAddr}, path::PathBuf, str::FromStr, }; use futures_executor::block_on; use hickory_dns::dnssec::{KeyConfig, KeyPurpose}; use hickory_proto::{ dnssec::{ Algorithm, PublicKey, SigSigner, Verifier, rdata::{KEY, key::KeyUsage}, }, op::{Header, Message, Query, ResponseCode, update_message}, rr::{ DNSClass, Name, RData, Record, RecordSet, RecordType, rdata::{A as A4, AAAA}, }, serialize::binary::{BinDecodable, BinEncodable}, xfer::Protocol, }; use hickory_server::{ authority::{ AuthLookup, Authority, DnssecAuthority, LookupError, LookupOptions, MessageRequest, UpdateResult, }, server::RequestInfo, }; const TEST_HEADER: &Header = &Header::new(); fn update_authority>( mut message: Message, key: &SigSigner, authority: &mut A, ) -> UpdateResult { message.finalize(key, 1).expect("failed to sign message"); let message = message.to_bytes().unwrap(); let request = MessageRequest::from_bytes(&message).unwrap(); block_on(authority.update(&request)) } pub fn test_create>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("create.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(127, 0, 0, 10))); let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); match lookup .into_iter() .next() .expect("A record not found in authority") .data() { RData::A(ip) => assert_eq!(A4::new(127, 0, 0, 10), *ip), _ => panic!("wrong rdata type returned"), } // trying to create again should error let mut message = update_message::create(record.into(), Name::from_str("example.com.").unwrap(), true); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::YXRRSet ); } } pub fn test_create_multi>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("create-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(100, 10, 100, 11))); let record2 = record2; let mut rrset = RecordSet::from(record.clone()); rrset.insert(record2.clone(), 0); let rrset = rrset; let message = update_message::create(rrset.clone(), Name::from_str("example.com.").unwrap(), true); assert!(update_authority(message, key, &mut authority).expect("create failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); // trying to create again should error let message = update_message::create(rrset, Name::from_str("example.com.").unwrap(), true); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::YXRRSet ); } } pub fn test_append>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("append.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let mut message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); // next append to a non-existent RRset let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), false, true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); // verify record contents let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == record)); // will fail if already set and not the same value. let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); // show that appending the same thing again is ok, but doesn't add any records let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); } } pub fn test_append_multi>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("append-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // next append to a non-existent RRset let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), false, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // will fail if already set and not the same value. let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let mut record3 = record.clone(); record3.set_data(RData::A(A4::new(101, 11, 101, 12))); // build the append set let mut rrset = RecordSet::from(record2.clone()); rrset.insert(record3.clone(), 0); let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 3); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(lookup.iter().any(|rr| *rr == record3)); // show that appending the same thing again is ok, but doesn't add any records // TODO: technically this is a test for the Server, not client... let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 3); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(lookup.iter().any(|rr| *rr == record3)); } } pub fn test_compare_and_swap>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("compare-and-swap.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); let record = record; let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let current = record; let mut new = current.clone(); new.set_data(RData::A(A4::new(101, 11, 101, 11))); let new = new; let message = update_message::compare_and_swap( current.clone().into(), new.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("compare_and_swap failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == new)); assert!(!lookup.iter().any(|rr| *rr == current)); // check the it fails if tried again. let mut not = new.clone(); not.set_data(RData::A(A4::new(102, 12, 102, 12))); let not = not; let message = update_message::compare_and_swap( current.into(), not.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == new)); assert!(!lookup.iter().any(|rr| *rr == not)); } } pub fn test_compare_and_swap_multi>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let mut current = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let current1 = current .new_record(&RData::A(A4::new(100, 10, 100, 10))) .clone(); let current2 = current .new_record(&RData::A(A4::new(100, 10, 100, 11))) .clone(); let current = current; let mut message = update_message::create( current.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut new = RecordSet::with_ttl(current.name().clone(), current.record_type(), current.ttl()); let new1 = new.new_record(&RData::A(A4::new(100, 10, 101, 10))).clone(); let new2 = new.new_record(&RData::A(A4::new(100, 10, 101, 11))).clone(); let new = new; let mut message = update_message::compare_and_swap( current.clone(), new.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("compare_and_swap failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == new1)); assert!(lookup.iter().any(|rr| *rr == new2)); assert!(!lookup.iter().any(|rr| *rr == current1)); assert!(!lookup.iter().any(|rr| *rr == current2)); // check the it fails if tried again. let mut not = new1.clone(); not.set_data(RData::A(A4::new(102, 12, 102, 12))); let not = not; let message = update_message::compare_and_swap( current.clone(), not.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == new1)); assert!(!lookup.iter().any(|rr| *rr == not)); } } pub fn test_delete_by_rdata>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("test-delete-by-rdata.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record1 = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let mut message = update_message::delete_by_rdata( record1.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); // next create to a non-existent RRset let mut message = update_message::create( record1.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let mut record2 = record1.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_by_rdata( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == record1)); } } pub fn test_delete_by_rdata_multi>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("test-delete-by-rdata-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let record1 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 10))) .clone(); let record2 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 11))) .clone(); let record3 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 12))) .clone(); let record4 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 13))) .clone(); let rrset = rrset; // first check the must_exist option let message = update_message::delete_by_rdata( rrset.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); // next create to a non-existent RRset let message = update_message::create(rrset.clone(), Name::from_str("example.com.").unwrap(), true); assert!(update_authority(message, key, &mut authority).expect("create failed")); // append a record let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let record1 = rrset.new_record(record1.data()).clone(); let record3 = rrset.new_record(record3.data()).clone(); let rrset = rrset; let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_by_rdata( rrset.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(!lookup.iter().any(|rr| *rr == record1)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(!lookup.iter().any(|rr| *rr == record3)); assert!(lookup.iter().any(|rr| *rr == record4)); } } pub fn test_delete_rrset>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let message = update_message::delete_rrset( record.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_rrset failed")); // next create to a non-existent RRset let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut record = record.clone(); record.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_rrset( record.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_rrset failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); } } pub fn test_delete_all>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.key().algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let message = update_message::delete_all( record.name().clone(), Name::from_str("example.com.").unwrap(), DNSClass::IN, true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_all failed")); // next create to a non-existent RRset let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut record = record.clone(); record.set_data(RData::AAAA(AAAA::new(1, 2, 3, 4, 5, 6, 7, 8))); let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); // verify record contents let message = update_message::delete_all( record.name().clone(), Name::from_str("example.com.").unwrap(), DNSClass::IN, true, ); assert!(update_authority(message, key, &mut authority).expect("delete_all failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); let query = Query::query(name.clone(), RecordType::AAAA).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); } } pub fn add_auth(authority: &mut A) -> Vec { let update_name = Name::from_str("update") .unwrap() .append_domain(&authority.origin().to_owned().into()) .unwrap(); let mut keys = Vec::::new(); #[cfg(feature = "__dnssec")] { let keys_algorithms = [ ( "../tests/test-data/test_configs/dnssec/rsa_2048.pk8", Algorithm::RSASHA512, ), ( "../tests/test-data/test_configs/dnssec/ecdsa_p256.pk8", Algorithm::ECDSAP256SHA256, ), ( "../tests/test-data/test_configs/dnssec/ecdsa_p384.pk8", Algorithm::ECDSAP384SHA384, ), ]; for (key, algo) in keys_algorithms { let key_config = KeyConfig { key_path: PathBuf::from(key), algorithm: algo, signer_name: Some(update_name.to_string()), purpose: KeyPurpose::ZoneSigning, }; let signer = key_config .try_into_signer(update_name.clone()) .expect("failed to read key_config"); let public_key = signer .key() .to_public_key() .expect("failed to get public key"); let key = KEY::new_sig0key_with_usage(&public_key, KeyUsage::Host); block_on(authority.add_update_auth_key(update_name.clone(), key)) .expect("failed to add signer to zone"); keys.push(signer); } } keys } macro_rules! define_update_test { ($new:expr; $( $f:ident, )*) => { $( #[test] fn $f () { ::test_support::subscribe(); use std::path::Path; let mut authority = $new(&Path::new("../tests/test-data/test_configs/example.com.zone"), module_path!(), stringify!($f)); let keys = crate::authority_battery::dynamic_update::add_auth(&mut authority); crate::authority_battery::dynamic_update::$f(authority, &keys); } )* } } macro_rules! dynamic_update { ($name:ident, $new:expr) => { #[cfg(test)] mod dynamic_update { mod $name { define_update_test!($new; test_create, test_create_multi, test_append, test_append_multi, test_compare_and_swap, test_compare_and_swap_multi, test_delete_by_rdata, test_delete_by_rdata_multi, test_delete_rrset, test_delete_all, ); } } }; } hickory-dns-0.25.2/tests/integration/authority_battery/mod.rs000064400000000000000000000001611046102023000225360ustar 00000000000000#![allow(unused)] #[macro_use] pub mod basic; #[macro_use] pub mod dnssec; #[macro_use] pub mod dynamic_update; hickory-dns-0.25.2/tests/integration/config_tests.rs000064400000000000000000000416761046102023000207040ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use std::env; use std::fs::{File, read_dir}; use std::io::Read; use std::net::{Ipv4Addr, Ipv6Addr}; use std::ops::Range; use std::path::{Path, PathBuf}; use std::time::Duration; use toml::map::Keys; use toml::value::Array; use toml::{Table, Value}; use hickory_dns::{Config, ServerZoneConfig}; use hickory_server::authority::ZoneType; #[test] fn test_read_config() { let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let path: PathBuf = PathBuf::from(server_path).join("tests/test-data/test_configs/example.toml"); if !path.exists() { panic!("can't locate example.toml and other configs: {:?}", path) } println!("reading config"); let config = Config::read_config(&path).unwrap(); assert_eq!(config.listen_port(), 53); assert_eq!(config.listen_addrs_ipv4(), Ok(Vec::::new())); assert_eq!(config.listen_addrs_ipv6(), Ok(Vec::::new())); assert_eq!(config.tcp_request_timeout(), Duration::from_secs(5)); assert_eq!(config.log_level(), tracing::Level::INFO); assert_eq!(config.directory(), Path::new("/var/named")); assert_eq!(config.zones()[0].zone, "localhost"); assert_eq!(config.zones()[0].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 0).file(), Some(Path::new("default/localhost.zone")) ); assert_eq!(config.zones()[1].zone, "0.0.127.in-addr.arpa"); assert_eq!(config.zones()[1].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 1).file(), Some(Path::new("default/127.0.0.1.zone")) ); assert_eq!( config.zones()[2].zone, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" ); assert_eq!(config.zones()[2].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 2).file(), Some(Path::new("default/ipv6_1.zone")) ); assert_eq!(config.zones()[3].zone, "255.in-addr.arpa"); assert_eq!(config.zones()[3].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 3).file(), Some(Path::new("default/255.zone")) ); assert_eq!(config.zones()[4].zone, "0.in-addr.arpa"); assert_eq!(config.zones()[4].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 4).file(), Some(Path::new("default/0.zone")) ); assert_eq!(config.zones()[5].zone, "example.com"); assert_eq!(config.zones()[5].zone_type(), ZoneType::Primary); assert_eq!( server_zone(&config, 5).file(), Some(Path::new("example.com.zone")) ); } #[test] fn test_parse_toml() { let config = Config::from_toml("listen_port = 2053").unwrap(); assert_eq!(config.listen_port(), 2053); let config = Config::from_toml("listen_addrs_ipv4 = [\"0.0.0.0\"]").unwrap(); assert_eq!(config.listen_addrs_ipv4(), Ok(vec![Ipv4Addr::UNSPECIFIED])); let config = Config::from_toml("listen_addrs_ipv4 = [\"0.0.0.0\", \"127.0.0.1\"]").unwrap(); assert_eq!( config.listen_addrs_ipv4(), Ok(vec![Ipv4Addr::UNSPECIFIED, Ipv4Addr::LOCALHOST]) ); let config = Config::from_toml("listen_addrs_ipv6 = [\"::0\"]").unwrap(); assert_eq!(config.listen_addrs_ipv6(), Ok(vec![Ipv6Addr::UNSPECIFIED])); let config = Config::from_toml("listen_addrs_ipv6 = [\"::0\", \"::1\"]").unwrap(); assert_eq!( config.listen_addrs_ipv6(), Ok(vec![ Ipv6Addr::UNSPECIFIED, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), ]) ); let config = Config::from_toml("tcp_request_timeout = 25").unwrap(); assert_eq!(config.tcp_request_timeout(), Duration::from_secs(25)); let config = Config::from_toml("log_level = \"Debug\"").unwrap(); assert_eq!(config.log_level(), tracing::Level::DEBUG); let config = Config::from_toml("directory = \"/dev/null\"").unwrap(); assert_eq!(config.directory(), Path::new("/dev/null")); } #[cfg(feature = "__dnssec")] #[test] fn test_parse_zone_keys() { use hickory_dns::dnssec::KeyPurpose; use hickory_proto::dnssec::Algorithm; use hickory_proto::rr::Name; let config = Config::from_toml( " [[zones]] zone = \"example.com\" zone_type = \"Primary\" [zones.stores] type = \"file\" zone_file_path = \"example.com.zone\" \ [[zones.keys]] key_path = \"/path/to/my_ed25519.pem\" algorithm = \"ED25519\" \ signer_name = \"ns.example.com.\" purpose = \"ZoneUpdateAuth\" [[zones.keys]] key_path = \"/path/to/my_rsa.pem\" algorithm = \ \"RSASHA256\" signer_name = \"ns.example.com.\" purpose = \"ZoneSigning\" ", ) .unwrap(); assert_eq!( server_zone(&config, 0).keys[0].key_path, Path::new("/path/to/my_ed25519.pem") ); assert_eq!( server_zone(&config, 0).keys[0].algorithm, Algorithm::ED25519 ); assert_eq!( server_zone(&config, 0).keys[0] .signer_name() .unwrap() .unwrap(), Name::parse("ns.example.com.", None).unwrap() ); assert_eq!( KeyPurpose::ZoneUpdateAuth, server_zone(&config, 0).keys[0].purpose ); assert_eq!( server_zone(&config, 0).keys[1].key_path, Path::new("/path/to/my_rsa.pem") ); assert_eq!( server_zone(&config, 0).keys[1].algorithm, Algorithm::RSASHA256 ); assert_eq!( server_zone(&config, 0).keys[1] .signer_name() .unwrap() .unwrap(), Name::parse("ns.example.com.", None).unwrap() ); assert_eq!( KeyPurpose::ZoneSigning, server_zone(&config, 0).keys[1].purpose, ); } #[test] #[cfg(feature = "__tls")] fn test_parse_tls() { // defaults let config = Config::from_toml("").unwrap(); assert_eq!(config.tls_listen_port(), 853); assert_eq!(config.tls_cert(), None); let config = Config::from_toml( "tls_cert = { path = \"path/to/some.pkcs12\", endpoint_name = \"ns.example.com\", private_key = \"foo.pem\" } tls_listen_port = 8853 ", ) .unwrap(); assert_eq!(config.tls_listen_port(), 8853); assert_eq!( config.tls_cert().unwrap().path, Path::new("path/to/some.pkcs12") ); } fn test_config(path: &str) { let workspace = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let path = PathBuf::from(workspace) .join("tests/test-data/test_configs") .join(path) .with_extension("toml"); assert!(path.exists(), "does not exist: {}", path.display()); println!("reading: {}", path.display()); Config::read_config(&path).expect("failed to read"); } macro_rules! define_test_config { ($name:ident) => { #[test] fn $name() { test_config(stringify!($name)); } }; } #[cfg(feature = "__dnssec")] define_test_config!(all_supported_dnssec); #[cfg(feature = "blocklist")] define_test_config!(chained_blocklist); #[cfg(feature = "blocklist")] define_test_config!(consulting_blocklist); #[cfg(feature = "__https")] define_test_config!(dns_over_https); #[cfg(feature = "__tls")] define_test_config!(dns_over_tls_rustls_and_openssl); #[cfg(feature = "__tls")] define_test_config!(dns_over_tls); #[cfg(all(feature = "__dnssec", feature = "sqlite"))] define_test_config!(dnssec_with_update); define_test_config!(example); define_test_config!(ipv4_and_ipv6); define_test_config!(ipv4_only); define_test_config!(ipv6_only); #[cfg(feature = "resolver")] define_test_config!(example_forwarder); /// Iterator that yields modified TOML tables with an extra field added, and recurses down the /// table's values. struct TableMutator<'a> { original: &'a Table, yielded_base_case: bool, key_iter: Keys<'a>, nested_table_mutator: Option<(&'a str, Box>)>, nested_array_mutator: Option<(&'a str, Box>)>, } impl<'a> TableMutator<'a> { fn new(table: &'a Table) -> Self { Self { original: table, yielded_base_case: false, key_iter: table.keys(), nested_table_mutator: None, nested_array_mutator: None, } } } impl Iterator for TableMutator<'_> { type Item = Table; fn next(&mut self) -> Option { if !self.yielded_base_case { self.yielded_base_case = true; let mut table = self.original.clone(); table.insert("test_only_invalid_config_key".into(), Value::Integer(1)); return Some(table); } loop { if let Some((key, iter)) = self.nested_table_mutator.as_mut() { if let Some(table) = iter.next() { let mut output = self.original.clone(); output[*key] = Value::Table(table); return Some(output); } else { self.nested_table_mutator = None; } } if let Some((key, iter)) = self.nested_array_mutator.as_mut() { if let Some(array) = iter.next() { let mut output = self.original.clone(); output[*key] = Value::Array(array); return Some(output); } else { self.nested_array_mutator = None; } } if let Some(key) = self.key_iter.next() { match self.original.get(key).unwrap() { Value::String(_) | Value::Integer(_) | Value::Float(_) | Value::Boolean(_) | Value::Datetime(_) => {} Value::Array(array) => { self.nested_array_mutator = Some((key, Box::new(ArrayMutator::new(array)))); } Value::Table(table) => { self.nested_table_mutator = Some((key, Box::new(TableMutator::new(table)))); } } } else { return None; } } } } /// Iterator that yields modified TOML arrays, working with [`TableMutator`], and recurses down the /// array's contents. struct ArrayMutator<'a> { original: &'a Array, index_iter: Range, nested_table_mutator: Option<(usize, Box>)>, nested_array_mutator: Option<(usize, Box>)>, } impl<'a> ArrayMutator<'a> { fn new(array: &'a Array) -> Self { Self { original: array, index_iter: 0..array.len(), nested_table_mutator: None, nested_array_mutator: None, } } } impl Iterator for ArrayMutator<'_> { type Item = Array; fn next(&mut self) -> Option { loop { if let Some((key, iter)) = self.nested_table_mutator.as_mut() { if let Some(table) = iter.next() { let mut output = self.original.clone(); output[*key] = Value::Table(table); return Some(output); } else { self.nested_table_mutator = None; } } if let Some((key, iter)) = self.nested_array_mutator.as_mut() { if let Some(array) = iter.next() { let mut output = self.original.clone(); output[*key] = Value::Array(array); return Some(output); } else { self.nested_array_mutator = None; } } if let Some(index) = self.index_iter.next() { match self.original.get(index).unwrap() { Value::String(_) | Value::Integer(_) | Value::Float(_) | Value::Boolean(_) | Value::Datetime(_) => {} Value::Array(array) => { self.nested_array_mutator = Some((index, Box::new(ArrayMutator::new(array)))); } Value::Table(table) => { self.nested_table_mutator = Some((index, Box::new(TableMutator::new(table)))); } } } else { return None; } } } } /// Check that unknown fields in configuration files are rejected. This uses each example /// configuration file as a seed, and tries adding invalid fields to each table. #[test] fn test_reject_unknown_fields() { let test_configs_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../tests/test-data/test_configs"); for result in read_dir(test_configs_dir).unwrap() { let entry = result.unwrap(); let file_name = entry.file_name().into_string().unwrap(); if !file_name.ends_with(".toml") { continue; } println!("seed file: {file_name}"); let mut file = File::open(entry.path()).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); let value = toml::from_str::(&contents).unwrap(); let config_table = value.as_table().unwrap(); // Skip over configs that can't be read with the current set of features. #[allow(unused_mut)] let mut skip = false; #[cfg(not(feature = "__tls"))] if config_table.contains_key("tls_cert") { println!("skipping due to tls_cert setting"); skip = true; } let zones = config_table.get("zones").unwrap().as_array().unwrap(); for zone in zones { #[cfg(not(feature = "__dnssec"))] if zone.as_table().unwrap().contains_key("keys") { println!("skipping due to keys setting"); skip = true; } let Some(stores) = zone.get("stores") else { continue; }; let vec_stores: Vec; let stores = if !stores.is_array() { vec_stores = vec![stores.clone()]; &vec_stores } else { stores.as_array().unwrap() }; for store in stores { let store = store.as_table().unwrap(); let _store_type = store.get("type").unwrap().as_str().unwrap(); #[cfg(not(feature = "blocklist"))] if _store_type == "blocklist" { println!("skipping due to blocklist store"); skip = true; break; } #[cfg(not(feature = "sqlite"))] if _store_type == "sqlite" { println!("skipping due to sqlite store"); skip = true; break; } #[cfg(not(feature = "resolver"))] if _store_type == "forward" { println!("skipping due to forward store"); skip = true; break; } #[cfg(not(feature = "recursor"))] if _store_type == "recursor" { println!("skipping due to recursor store"); skip = true; break; } } } if skip { continue; } // Confirm the example config file can be read as-is. toml::from_str::(&contents).unwrap(); // Recursively add a key to every table in the configuration file, and confirm that each // modified config file is rejected. for modified_config in TableMutator::new(config_table) { let serialized = toml::to_string(&modified_config).unwrap(); match toml::from_str::(&serialized) { Ok(_) => panic!( "config with spurious key was accepted:\n{}", toml::to_string_pretty(&modified_config).unwrap() ), Err(error) => assert!( error .message() .starts_with("data did not match any variant") || error.message().starts_with("unknown field"), "unexpected error: {error:?} for {modified_config:?}" ), } } } } fn server_zone(config: &Config, index: usize) -> &ServerZoneConfig { config.zones()[index] .zone_type_config .as_server() .expect("expected nameserver") } hickory-dns-0.25.2/tests/integration/forwarder.rs000064400000000000000000000016651046102023000202020ustar 00000000000000#![cfg(feature = "resolver")] use std::str::FromStr; use tokio::runtime::Runtime; use hickory_proto::rr::{Name, RecordType}; use hickory_resolver::name_server::TokioConnectionProvider; use hickory_server::{ authority::{Authority, LookupObject}, store::forwarder::ForwardAuthority, }; use test_support::subscribe; #[test] fn test_lookup() { subscribe(); let runtime = Runtime::new().expect("failed to create Tokio Runtime"); let forwarder = ForwardAuthority::builder(TokioConnectionProvider::default()) .unwrap() .build() .expect("failed to create forwarder"); let lookup = runtime .block_on(forwarder.lookup( &Name::from_str("www.example.com.").unwrap().into(), RecordType::A, Default::default(), )) .unwrap(); assert!( lookup.iter().any(|record| record.data().as_a().is_some()), "no addresses returned!" ); } hickory-dns-0.25.2/tests/integration/in_memory.rs000064400000000000000000000110441046102023000201750ustar 00000000000000use std::str::FromStr; use test_support::subscribe; use tokio::runtime::Runtime; use hickory_proto::rr::{Name, RData, Record, RecordType, rdata::CNAME}; #[cfg(feature = "__dnssec")] use hickory_server::dnssec::NxProofKind; use hickory_server::{ authority::{Authority, ZoneType}, store::in_memory::InMemoryAuthority, }; #[test] fn test_cname_loop() { subscribe(); let runtime = Runtime::new().expect("failed to create Tokio Runtime"); let mut auth = InMemoryAuthority::empty( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ); auth.upsert_mut( Record::from_rdata( Name::from_str("foo.example.com.").unwrap(), 300, RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())), ), 0, ); auth.upsert_mut( Record::from_rdata( Name::from_str("bar.example.com.").unwrap(), 300, RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())), ), 0, ); auth.upsert_mut( Record::from_rdata( Name::from_str("baz.example.com.").unwrap(), 300, RData::CNAME(CNAME(Name::from_str("boz.example.com.").unwrap())), ), 0, ); auth.upsert_mut( Record::from_rdata( Name::from_str("boz.example.com.").unwrap(), 300, RData::CNAME(CNAME(Name::from_str("biz.example.com.").unwrap())), ), 0, ); auth.upsert_mut( Record::from_rdata( Name::from_str("biz.example.com.").unwrap(), 300, RData::CNAME(CNAME(Name::from_str("baz.example.com.").unwrap())), ), 0, ); let mut lookup = runtime .block_on(auth.lookup( &Name::from_str("foo.example.com.").unwrap().into(), RecordType::A, Default::default(), )) .unwrap(); let records: Vec<&Record> = lookup.iter().collect(); assert_eq!(records.len(), 1); let record = records[0]; assert_eq!(record.name(), &Name::from_str("foo.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())) ); assert!( lookup.take_additionals().is_none(), "Should be no additional records." ); let mut lookup = runtime .block_on(auth.lookup( &Name::from_str("bar.example.com.").unwrap().into(), RecordType::A, Default::default(), )) .unwrap(); let records: Vec<&Record> = lookup.iter().collect(); assert_eq!(records.len(), 1); let record = records[0]; assert_eq!(record.name(), &Name::from_str("bar.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())) ); let additionals = lookup .take_additionals() .expect("Should be additional records"); let additionals: Vec<&Record> = additionals.iter().collect(); assert_eq!(additionals.len(), 1); let record = additionals[0]; assert_eq!(record.name(), &Name::from_str("foo.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())) ); let mut lookup = runtime .block_on(auth.lookup( &Name::from_str("baz.example.com.").unwrap().into(), RecordType::A, Default::default(), )) .unwrap(); let records: Vec<&Record> = lookup.iter().collect(); assert_eq!(records.len(), 1); let record = records[0]; assert_eq!(record.name(), &Name::from_str("baz.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("boz.example.com.").unwrap())) ); let additionals = lookup .take_additionals() .expect("Should be additional records"); let additionals: Vec<&Record> = additionals.iter().collect(); assert_eq!(additionals.len(), 2); let record = additionals[0]; assert_eq!(record.name(), &Name::from_str("boz.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("biz.example.com.").unwrap())) ); let record = additionals[1]; assert_eq!(record.name(), &Name::from_str("biz.example.com.").unwrap()); assert_eq!( record.data(), &RData::CNAME(CNAME(Name::from_str("baz.example.com.").unwrap())) ); } hickory-dns-0.25.2/tests/integration/main.rs000064400000000000000000000005351046102023000171260ustar 00000000000000#[macro_use] mod authority_battery; mod config_tests; mod forwarder; mod in_memory; mod named_https_tests; #[cfg(feature = "metrics")] mod named_metrics_tests; mod named_quic_tests; mod named_rustls_tests; mod named_test_rsa_dnssec; mod named_tests; mod server_harness; mod sqlite_tests; mod store_file_tests; mod store_sqlite_tests; mod txt_tests; hickory-dns-0.25.2/tests/integration/named_https_tests.rs000064400000000000000000000056701046102023000217370ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![cfg(not(windows))] #![cfg(feature = "__https")] use std::env; use std::fs::File; use std::io::*; use std::net::*; use std::sync::Arc; use rustls::pki_types::CertificateDer; use rustls::{ClientConfig, RootCertStore}; use tokio::runtime::Runtime; use crate::server_harness::{named_test_harness, query_a}; use hickory_client::client::Client; use hickory_proto::h2::HttpsClientStreamBuilder; use hickory_proto::runtime::TokioRuntimeProvider; use hickory_proto::rustls::default_provider; use hickory_proto::xfer::Protocol; use test_support::subscribe; #[test] fn test_example_https_toml_startup() { subscribe(); const ALPN_H2: &[u8] = b"h2"; named_test_harness("dns_over_https.toml", move |socket_ports| { let mut cert_der = vec![]; let https_port = socket_ports.get_v4(Protocol::Https); let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); println!("using server src path: {server_path}"); File::open(format!( "{server_path}/tests/test-data/test_configs/sec/example.cert" )) .expect("failed to open cert") .read_to_end(&mut cert_der) .expect("failed to read cert"); let mut io_loop = Runtime::new().unwrap(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, https_port.expect("no https_port"))); std::thread::sleep(std::time::Duration::from_secs(1)); // using the mozilla default root store let mut root_store = RootCertStore::empty(); root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); root_store.add(CertificateDer::from(cert_der)).unwrap(); let mut client_config = ClientConfig::builder_with_provider(Arc::new(default_provider())) .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_store) .with_no_client_auth(); client_config.alpn_protocols.push(ALPN_H2.to_vec()); let client_config = Arc::new(client_config); let provider = TokioRuntimeProvider::new(); let https_builder = HttpsClientStreamBuilder::with_client_config(client_config, provider); let mp = https_builder.build(addr, "ns.example.com".to_string(), "/dns-query".to_string()); let client = Client::connect(mp); // ipv4 should succeed let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); // a second request should work... query_a(&mut io_loop, &mut client); }) } hickory-dns-0.25.2/tests/integration/named_metrics_tests.rs000064400000000000000000000421521046102023000222370ustar 00000000000000// Copyright 2015-2025 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::net::{Ipv4Addr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; #[cfg(all(feature = "__dnssec", feature = "sqlite"))] use std::{env, path::Path}; use test_support::subscribe; use hickory_client::client::{Client, ClientHandle}; #[cfg(feature = "__dnssec")] use hickory_proto::dnssec::{ Algorithm, DnssecDnsHandle, SigSigner, SigningKey, TrustAnchors, crypto::RsaSigningKey, rdata::DNSKEY, }; use hickory_proto::op::MessageFinalizer; #[cfg(feature = "__dnssec")] use hickory_proto::rr::Record; use hickory_proto::rr::rdata::A; use hickory_proto::rr::{DNSClass, Name, RData, RecordType}; use hickory_proto::runtime::TokioRuntimeProvider; use hickory_proto::tcp::TcpClientStream; use hickory_proto::xfer::Protocol; use prometheus_parse::{Scrape, Value}; #[cfg(feature = "__dnssec")] use rustls_pki_types::PrivatePkcs8KeyDer; use tokio::runtime::Runtime; use tokio::time::sleep; use crate::server_harness::{ServerProtocol, SocketPorts, named_test_harness}; #[test] fn test_prometheus_endpoint_startup() { subscribe(); named_test_harness("example_forwarder.toml", |socket_ports| { let io_loop = Runtime::new().unwrap(); let metrics = &io_loop.block_on(fetch_parse_check_metrics(&socket_ports)); // check process metrics verify_metric(metrics, "process_cpu_seconds_total", &[], None); verify_metric(metrics, "process_max_fds", &[], None); verify_metric(metrics, "process_open_fds", &[], None); verify_metric(metrics, "process_resident_memory_bytes", &[], None); verify_metric(metrics, "process_start_time_seconds", &[], None); verify_metric(metrics, "process_virtual_memory_bytes", &[], None); #[cfg(not(windows))] { verify_metric(metrics, "process_virtual_memory_max_bytes", &[], None); verify_metric(metrics, "process_threads", &[], None); } // check config metrics let info = [("version", hickory_server::version())]; let config_info = [ ("directory", "/var/named"), ("disable_https", "false"), ("disable_quic", "false"), ("disable_tcp", "false"), ("disable_tls", "false"), ("disable_udp", "false"), ("allow_networks", "0"), // move to separate counter hickory_config_allow_networks_total ? ("deny_networks", "0"), // move to separate counter hickory_config_deny_networks_total ? ("zones", "6"), // redundant ? ]; verify_metric(metrics, "hickory_info", &info, Some(1f64)); verify_metric(metrics, "hickory_config_info", &config_info, Some(1f64)); let store_forwarder = [("store", "forwarder")]; verify_metric(metrics, "hickory_zones_total", &store_forwarder, Some(1f64)); let store_file_primary = [("store", "file"), ("role", "primary")]; let store_file_secondary = [("store", "file"), ("role", "secondary")]; verify_metric( metrics, "hickory_zones_total", &store_file_primary, Some(5f64), ); verify_metric( metrics, "hickory_zones_total", &store_file_secondary, Some(0f64), ); #[cfg(feature = "sqlite")] { let store_sqlite_primary = [("store", "sqlite"), ("role", "primary")]; let store_sqlite_secondary = [("store", "sqlite"), ("role", "secondary")]; verify_metric( metrics, "hickory_zones_total", &store_sqlite_primary, Some(0f64), ); verify_metric( metrics, "hickory_zones_total", &store_sqlite_secondary, Some(0f64), ); } // check store metrics // forwarder store only has QueryStoreMetrics // sqlite store not initialized within example_forwarder.toml let store_file = [("store", "file")]; verify_metric( metrics, "hickory_zone_records_total", &store_file, Some(14f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FILE_SUCCESS, Some(0f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FILE_FAILED, Some(0f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FORWARDER_SUCCESS, Some(0f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FORWARDER_FAILED, Some(0f64), ); // sqlite store is not configured within example_forwarder.toml // therefore StoreMetrics for sqlite are not initialized // currently this feature returns is NotImpl, only functional with sqlite && dnssec feature // empty metrics available for file store as they are part of PersistentStoreMetrics // migrate to Option within PersistentStoreMetrics ? #[cfg(feature = "__dnssec")] { let store_file_added = [("store", "file"), ("operation", "added")]; let store_file_deleted = [("store", "file"), ("operation", "deleted")]; let store_file_updated = [("store", "file"), ("operation", "updated")]; verify_metric( metrics, "hickory_zone_records_modified_total", &store_file_added, Some(0f64), ); verify_metric( metrics, "hickory_zone_records_modified_total", &store_file_deleted, Some(0f64), ); verify_metric( metrics, "hickory_zone_records_modified_total", &store_file_updated, Some(0f64), ); } }) } #[test] fn test_request_response() { subscribe(); named_test_harness("example_forwarder.toml", |socket_ports| { let io_loop = Runtime::new().unwrap(); let metrics = &io_loop.block_on(async { let mut client = create_local_client(&socket_ports, None).await; let response = client .query( Name::from_str("localhost.").unwrap(), DNSClass::IN, RecordType::A, ) .await .unwrap(); if let RData::A(addr) = response.answers()[0].data() { assert_eq!(*addr, A::new(127, 0, 0, 1)); }; fetch_parse_check_metrics(&socket_ports).await }); // check request let request_operations = ["notify", "query", "status", "unknown", "update"]; request_operations.iter().for_each(|op| { let value = if *op == "query" { 1f64 } else { 0f64 }; let op = [("operation", *op)]; verify_metric( metrics, "hickory_request_operations_total", &op, Some(value), ) }); let flags = ["aa", "ad", "cd", "ra", "rd", "tc"]; flags.iter().for_each(|flag| { let value = if *flag == "rd" { 1f64 } else { 0f64 }; let flag = [("flag", *flag)]; verify_metric(metrics, "hickory_request_flags_total", &flag, Some(value)) }); let protocols = ["tcp", "udp"]; protocols.iter().for_each(|proto| { let value = if *proto == "tcp" { 1f64 } else { 0f64 }; let proto = [("protocol", *proto)]; verify_metric( metrics, "hickory_request_protocols_total", &proto, Some(value), ) }); // check response let response_codes = vec![ "bad_alg", "bad_cookie", "bad_key", "bad_mode", "bad_name", "bad_sig", "bad_time", "bad_trunc", "bad_vers", "form_error", "no_error", "not_auth", "not_imp", "not_zone", "nx_domain", "nx_rrset", "refused", "serv_fail", "unknown", "yx_domain", "yx_rrset", ]; response_codes.iter().for_each(|code| { let value = if *code == "no_error" { 1f64 } else { 0f64 }; let code = [("code", *code)]; verify_metric(metrics, "hickory_response_codes_total", &code, Some(value)) }); flags.iter().for_each(|flag| { let value = if ["aa", "rd"].contains(flag) { 1f64 } else { 0f64 }; let flag = [("flag", *flag)]; verify_metric(metrics, "hickory_response_flags_total", &flag, Some(value)) }); // check store lookups verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FILE_SUCCESS, Some(1f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FILE_FAILED, Some(0f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FORWARDER_SUCCESS, Some(0f64), ); verify_metric( metrics, "hickory_zone_record_lookups_total", &STORE_FORWARDER_FAILED, Some(0f64), ); }) } #[test] #[cfg(all(feature = "__dnssec", feature = "sqlite"))] fn test_updates() { subscribe(); named_test_harness("dnssec_with_update_2.toml", |socket_ports| { let io_loop = Runtime::new().unwrap(); let metrics = &io_loop.block_on(async { let rsa_key = include_bytes!("../../../tests/test-data/test_configs/dnssec/rsa_2048.pk8"); let verify_algo = Algorithm::RSASHA256; let verify_key = RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(rsa_key.to_vec()), verify_algo) .unwrap(); let mut trust_anchor = TrustAnchors::empty(); trust_anchor.insert(&verify_key.to_public_key().unwrap()); let origin: Name = Name::parse("example.com.", None).unwrap(); let update_algo = Algorithm::RSASHA512; let update_key = RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(rsa_key.to_vec()), update_algo) .unwrap(); let signer = SigSigner::dnssec( DNSKEY::from_key(&update_key.to_public_key().unwrap()), Box::new(update_key), origin.clone(), time::Duration::weeks(1).try_into().unwrap(), ); let client = create_local_client(&socket_ports, Some(Arc::new(signer))).await; let mut client = DnssecDnsHandle::with_trust_anchor(client, Arc::new(trust_anchor)); let rrset_create = Record::from_rdata( Name::from_str("zzz.example.com").unwrap(), 3600, RData::A(A::from(Ipv4Addr::LOCALHOST)), ); client.create(rrset_create, origin.clone()).await.unwrap(); let record_update = Record::from_rdata( Name::from_str("zzz.example.com").unwrap(), 1800, RData::A(A::from(Ipv4Addr::LOCALHOST)), ); client .append(record_update, origin.clone(), true) .await .unwrap(); let rrset_delete = Record::from_rdata( Name::from_str("zzz.example.com").unwrap(), 3600, RData::A(A::from(Ipv4Addr::LOCALHOST)), ); client .delete_rrset(rrset_delete, origin.clone()) .await .unwrap(); fetch_parse_check_metrics(&socket_ports).await }); verify_metric( metrics, "hickory_request_operations_total", &[("operation", "update")], Some(3f64), ); // check updates lookups verify_metric( metrics, "hickory_zone_records_modified_total", &[("store", "sqlite"), ("operation", "added")], Some(1f64), ); verify_metric( metrics, "hickory_zone_records_modified_total", &[("store", "sqlite"), ("operation", "deleted")], Some(1f64), ); verify_metric( metrics, "hickory_zone_records_modified_total", &[("store", "sqlite"), ("operation", "updated")], Some(1f64), ); }); // Clean up database. let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let server_path = Path::new(&server_path); let database = server_path.join("tests/test-data/test_configs/example.com_dnssec_update_2.jrnl"); std::fs::remove_file(&database).expect("failed to cleanup after test"); } async fn create_local_client( socket_ports: &SocketPorts, signer: Option>, ) -> Client { let dns_port = socket_ports.get_v4(ServerProtocol::Dns(Protocol::Tcp)); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, dns_port.expect("no dns tcp port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, TokioRuntimeProvider::new()); let client = Client::new(stream, sender, signer); let (client, bg) = client.await.expect("connection failed"); tokio::spawn(bg); client } async fn fetch_parse_check_metrics(socket_ports: &SocketPorts) -> Scrape { let prometheus_port = socket_ports.get_v4(ServerProtocol::PrometheusMetrics); let addr = SocketAddr::from(( Ipv4Addr::LOCALHOST, prometheus_port.expect("no prometheus_port"), )); // the collect interval for process metrics is set to 1s // wait to avoid missing the process metrics sleep(Duration::from_secs(1)).await; // fetch from metrics from server let body = reqwest::get(format!("http://{}:{}/", addr.ip(), addr.port())) .await .unwrap() .text() .await .unwrap(); let lines = body.lines().map(|s| Ok(s.to_owned())).collect::>(); let scrape = Scrape::parse(lines.into_iter()).unwrap(); test_metrics_description_present(scrape.clone()); scrape } fn test_metrics_description_present(scrape: Scrape) { let missing = scrape .samples .into_iter() .filter_map(|s| { if !scrape.docs.contains_key(&s.metric) { Some(s.metric) } else { None } }) .collect::>(); assert_eq!( Vec::::new(), missing, "due to missing metric descriptions" ); } fn verify_metric(metrics: &Scrape, name: &str, labels: &[(&str, &str)], value: Option) { let named = metrics .samples .iter() .filter(|s| s.metric == name) .collect::>(); assert!(!named.is_empty(), "failed locating metric: {}", name); let labeled = named .iter() .filter(|s| { labels.len() == labels .iter() .filter(|(k, v)| { let Some(val) = s.labels.get(k) else { return false; }; val == *v }) .count() }) .collect::>(); assert_eq!( labeled.len(), 1, "locating metric: {}, labels: {:?}", name, labels ); let Some(value) = value else { return }; labeled.iter().for_each(|s| match s.value { Value::Counter(v) | Value::Gauge(v) | Value::Untyped(v) => { println!( "checking metric: {}, labels: {}, value: {}", s.metric, s.labels, v ); assert_eq!( v, value, "checking metric: {}, labels: {}, value: {}", s.metric, s.labels, v ) } Value::Histogram(_) | Value::Summary(_) => { panic!("metric type check not supported") } }) } const STORE_FILE_SUCCESS: [(&str, &str); 2] = [("store", "file"), ("success", "true")]; const STORE_FILE_FAILED: [(&str, &str); 2] = [("store", "file"), ("success", "false")]; const STORE_FORWARDER_SUCCESS: [(&str, &str); 2] = [("store", "forwarder"), ("success", "true")]; const STORE_FORWARDER_FAILED: [(&str, &str); 2] = [("store", "forwarder"), ("success", "false")]; hickory-dns-0.25.2/tests/integration/named_quic_tests.rs000064400000000000000000000052131046102023000215270ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![cfg(not(windows))] #![cfg(feature = "__quic")] use std::{env, fs::File, io::*, net::*, sync::Arc}; use rustls::{ClientConfig, RootCertStore, pki_types::CertificateDer}; use tokio::runtime::Runtime; use crate::server_harness::{named_test_harness, query_a}; use hickory_client::client::Client; use hickory_proto::quic::QuicClientStream; use hickory_proto::rustls::default_provider; use hickory_proto::xfer::Protocol; use test_support::subscribe; #[test] fn test_example_quic_toml_startup() { subscribe(); named_test_harness("dns_over_quic.toml", move |socket_ports| { let mut cert_der = vec![]; let quic_port = socket_ports.get_v4(Protocol::Quic); let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); println!("using server src path: {server_path} and quic_port: {quic_port:?}"); File::open(format!( "{server_path}/tests/test-data/test_configs/sec/example.cert" )) .expect("failed to open cert") .read_to_end(&mut cert_der) .expect("failed to read cert"); let mut io_loop = Runtime::new().unwrap(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, quic_port.expect("no quic_port"))); std::thread::sleep(std::time::Duration::from_secs(1)); // using the mozilla default root store let mut root_store = RootCertStore::empty(); root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); root_store.add(CertificateDer::from(cert_der)).unwrap(); let client_config = ClientConfig::builder_with_provider(Arc::new(default_provider())) .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_store) .with_no_client_auth(); let mut quic_builder = QuicClientStream::builder(); quic_builder.crypto_config(client_config); let mp = quic_builder.build(addr, "ns.example.com".to_string()); let client = Client::connect(mp); // ipv4 should succeed let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); // a second request should work... query_a(&mut io_loop, &mut client); }) } hickory-dns-0.25.2/tests/integration/named_rustls_tests.rs000064400000000000000000000063511046102023000221260ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![cfg(not(windows))] #![cfg(feature = "__tls")] use std::env; use std::fs::File; use std::io::*; use std::net::*; use std::sync::Arc; use rustls::pki_types::CertificateDer; use rustls::{ClientConfig, RootCertStore}; use test_support::subscribe; use tokio::runtime::Runtime; use crate::server_harness::{named_test_harness, query_a}; use hickory_client::client::Client; use hickory_proto::runtime::TokioRuntimeProvider; use hickory_proto::rustls::{default_provider, tls_client_connect}; use hickory_proto::xfer::Protocol; #[test] fn test_example_tls_toml_startup() { subscribe(); named_test_harness( "dns_over_tls_rustls_and_openssl.toml", move |socket_ports| { let mut cert_der = vec![]; let tls_port = socket_ports.get_v4(Protocol::Tls); let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); println!("using server src path: {server_path}"); File::open(format!( "{server_path}/tests/test-data/test_configs/sec/example.cert" )) .expect("failed to open cert") .read_to_end(&mut cert_der) .expect("failed to read cert"); let mut io_loop = Runtime::new().unwrap(); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tls_port.expect("no tls_port"))); let mut root_store = RootCertStore::empty(); root_store .add(CertificateDer::from(cert_der)) .expect("bad certificate"); let config = ClientConfig::builder_with_provider(Arc::new(default_provider())) .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_store) .with_no_client_auth(); let config = Arc::new(config); let provider = TokioRuntimeProvider::new(); let (stream, sender) = tls_client_connect( addr, "ns.example.com".to_string(), config.clone(), provider.clone(), ); let client = Client::new(stream, sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should succeed query_a(&mut io_loop, &mut client); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tls_port.expect("no tls_port"))); let (stream, sender) = tls_client_connect(addr, "ns.example.com".to_string(), config, provider); let client = Client::new(stream, sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should succeed query_a(&mut io_loop, &mut client); }, ) } hickory-dns-0.25.2/tests/integration/named_test_rsa_dnssec.rs000064400000000000000000000124671046102023000225400ustar 00000000000000#![cfg(feature = "__dnssec")] #![cfg(not(windows))] use std::env; use std::net::{Ipv4Addr, SocketAddr}; use std::path::Path; use std::sync::Arc; use tokio::runtime::Runtime; use crate::server_harness::{named_test_harness, query_a, query_all_dnssec}; use hickory_client::client::Client; use hickory_dns::dnssec::key_from_file; use hickory_proto::dnssec::{Algorithm, DnssecDnsHandle, TrustAnchors}; use hickory_proto::runtime::{RuntimeProvider, TokioRuntimeProvider, TokioTime}; use hickory_proto::tcp::TcpClientStream; use hickory_proto::xfer::{DnsExchangeBackground, DnsMultiplexer, Protocol}; use test_support::subscribe; #[cfg(feature = "__dnssec")] fn confg_toml() -> &'static str { "all_supported_dnssec.toml" } fn trust_anchor(public_key_path: &Path, algorithm: Algorithm) -> Arc { let key_pair = key_from_file(public_key_path, algorithm).unwrap(); let public_key = key_pair.to_public_key().unwrap(); let mut trust_anchor = TrustAnchors::empty(); trust_anchor.insert(&public_key); Arc::new(trust_anchor) } #[allow(clippy::type_complexity)] async fn standard_tcp_conn( port: u16, provider: P, ) -> ( Client, DnsExchangeBackground>, TokioTime>, ) { let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, port)); let (stream, sender) = TcpClientStream::new(addr, None, None, provider); Client::new(stream, sender, None) .await .expect("new Client failed") } fn generic_test(config_toml: &str, key_path: &str, algorithm: Algorithm) { // TODO: look into the `test-log` crate for enabling logging during tests // use hickory_client::logger; // use tracing::LogLevel; let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let server_path = Path::new(&server_path); let provider = TokioRuntimeProvider::new(); named_test_harness(config_toml, |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); // verify all records are present let client = standard_tcp_conn(tcp_port.expect("no tcp port"), provider.clone()); let (client, bg) = io_loop.block_on(client); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_all_dnssec(&mut io_loop, client, algorithm); // test that request with Dnssec client is successful, i.e. validates chain let trust_anchor = trust_anchor(&server_path.join(key_path), algorithm); let client = standard_tcp_conn(tcp_port.expect("no tcp port"), provider); let (client, bg) = io_loop.block_on(client); hickory_proto::runtime::spawn_bg(&io_loop, bg); let mut client = DnssecDnsHandle::with_trust_anchor(client, trust_anchor); query_a(&mut io_loop, &mut client); }); } #[test] #[cfg(feature = "__dnssec")] fn test_rsa_sha256_pkcs8() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/rsa_2048.pk8", Algorithm::RSASHA256, ); } #[test] #[cfg(feature = "__dnssec")] fn test_rsa_sha512_pkcs8() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/rsa_2048.pk8", Algorithm::RSASHA512, ); } #[test] #[cfg(feature = "__dnssec")] fn test_ecdsa_p256_pkcs8() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/ecdsa_p256.pk8", Algorithm::ECDSAP256SHA256, ); } #[test] #[cfg(feature = "__dnssec")] fn test_ecdsa_p384_pkcs8() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/ecdsa_p384.pk8", Algorithm::ECDSAP384SHA384, ); } #[test] #[cfg(feature = "__dnssec")] fn test_ed25519() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/ed25519.pk8", Algorithm::ED25519, ); } #[test] #[should_panic] #[allow(deprecated)] fn test_rsa_sha1_fails() { subscribe(); generic_test( confg_toml(), "tests/test-data/test_configs/dnssec/rsa_2048.pem", Algorithm::RSASHA1, ); } #[cfg(feature = "__dnssec")] #[cfg(feature = "sqlite")] #[test] fn test_dnssec_restart_with_update_journal() { subscribe(); // TODO: make journal path configurable, it should be in target/tests/... let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); let server_path = Path::new(&server_path); let journal = server_path.join("tests/test-data/test_configs/example.com_dnssec_update.jrnl"); std::fs::remove_file(&journal).ok(); generic_test( "dnssec_with_update.toml", "tests/test-data/test_configs/dnssec/rsa_2048.pk8", Algorithm::RSASHA256, ); // after running the above test, the journal file should exist assert!(journal.exists()); // and all dnssec tests should still pass generic_test( "dnssec_with_update.toml", "tests/test-data/test_configs/dnssec/rsa_2048.pk8", Algorithm::RSASHA256, ); // and journal should still exist assert!(journal.exists()); // cleanup... // TODO: fix journal path so that it doesn't leave the dir dirty... this might make windows an option after that std::fs::remove_file(&journal).expect("failed to cleanup after test"); } hickory-dns-0.25.2/tests/integration/named_tests.rs000064400000000000000000000406171046102023000205150ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::io::Write; use std::net::*; use std::str::FromStr; use tokio::runtime::Runtime; use crate::server_harness::{named_test_harness, query_a, query_a_refused}; use hickory_client::client::{Client, ClientHandle}; use hickory_proto::op::ResponseCode; use hickory_proto::rr::{DNSClass, Name, RecordType}; use hickory_proto::runtime::TokioRuntimeProvider; use hickory_proto::tcp::TcpClientStream; use hickory_proto::udp::UdpClientStream; use hickory_proto::xfer::Protocol; use test_support::subscribe; #[test] fn test_example_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); // just tests that multiple queries work let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); }) } #[test] fn test_ipv4_only_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("ipv4_only.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should succeed query_a(&mut io_loop, &mut client); let addr = SocketAddr::from((Ipv6Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); assert!(io_loop.block_on(client).is_err()); //let (client, bg) = io_loop.block_on(client).expect("client failed to connect"); //hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should fail // FIXME: probably need to send something for proper test... maybe use JoinHandle in tokio 0.2 // assert!(io_loop.block_on(client).is_err()); }) } // TODO: this is commented out b/c at least on macOS, ipv4 will route properly to ipv6 only // listeners over the [::ffff:127.0.0.1] interface // // #[ignore] // #[test] // fn test_ipv6_only_toml_startup() { // named_test_harness("ipv6_only.toml", |socket_ports| { // let mut io_loop = Runtime::new().unwrap(); // let tcp_port = socket_ports.get_v4(Protocol::Tcp); // let addr: SocketAddr = ("127.0.0.1", port).to_socket_addrs().unwrap().next().unwrap(); // let (stream, sender) = TcpClientStream::new(addr); // let client = AsyncClient::new(stream, sender, None); // let mut client = io_loop.block_on(client).unwrap(); // // // ipv4 should fail // assert!(!query(&mut io_loop, client)); // // let addr: SocketAddr = ("::1", port).to_socket_addrs().unwrap().next().unwrap(); // let (stream, sender) = TcpClientStream::new(addr); // let client = AsyncClient::new(stream, sender, None); // let mut client = io_loop.block_on(client).unwrap(); // // // ipv6 should succeed // assert!(query(&mut io_loop, client)); // // assert!(true); // }) // } #[test] fn test_ipv4_and_ipv6_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("ipv4_and_ipv6.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should succeed query_a(&mut io_loop, &mut client); let tcp_port = socket_ports.get_v6(Protocol::Tcp); let addr = SocketAddr::from((Ipv6Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should succeed query_a(&mut io_loop, &mut client); }) } #[test] fn test_nodata_where_name_exists() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example.toml", |socket_ports| { let io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); let msg = io_loop .block_on(client.query( Name::from_str("www.example.com.").unwrap(), DNSClass::IN, RecordType::SRV, )) .unwrap(); assert_eq!(msg.response_code(), ResponseCode::NoError); assert!(msg.answers().is_empty()); }) } #[test] fn test_nxdomain_where_no_name_exists() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example.toml", |socket_ports| { let io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); let msg = io_loop .block_on(client.query( Name::from_str("nxdomain.example.com.").unwrap(), DNSClass::IN, RecordType::SRV, )) .unwrap(); assert_eq!(msg.response_code(), ResponseCode::NXDomain); assert!(msg.answers().is_empty()); }) } #[test] fn test_server_continues_on_bad_data_udp() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let udp_port = socket_ports.get_v4(Protocol::Udp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, udp_port.expect("no udp_port"))); let stream = UdpClientStream::builder(addr, provider.clone()).build(); let client = Client::connect(stream); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); // Send a bad packet, this should get rejected by the server let raw_socket = UdpSocket::bind(SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0)) .expect("couldn't bind raw"); raw_socket .send_to(b"0xDEADBEEF", addr) .expect("raw send failed"); // just tests that multiple queries work let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, udp_port.expect("no udp_port"))); let stream = UdpClientStream::builder(addr, provider).build(); let client = Client::connect(stream); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); }) } #[test] fn test_server_continues_on_bad_data_tcp() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); // Send a bad packet, this should get rejected by the server let mut raw_socket = TcpStream::connect(addr).expect("couldn't bind raw"); raw_socket .write_all(b"0xDEADBEEF") .expect("raw send failed"); // just tests that multiple queries work let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); query_a(&mut io_loop, &mut client); }) } #[test] #[cfg(feature = "resolver")] fn test_forward() { use crate::server_harness::query_message; subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example_forwarder.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); let response = query_message( &mut io_loop, &mut client, Name::from_str("www.example.com.").unwrap(), RecordType::A, ) .unwrap(); assert!( response .answers() .iter() .any(|record| record.data().as_a().is_some()) ); // just tests that multiple queries work let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); let response = query_message( &mut io_loop, &mut client, Name::from_str("www.example.com.").unwrap(), RecordType::A, ) .unwrap(); assert!( response .answers() .iter() .any(|record| record.data().as_a().is_some()) ); assert!(!response.header().authoritative()); }) } #[test] fn test_allow_networks_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example_allow_networks.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should succeed query_a(&mut io_loop, &mut client); let tcp_port = socket_ports.get_v6(Protocol::Tcp); let addr = SocketAddr::from((Ipv6Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should succeed query_a(&mut io_loop, &mut client); }) } #[test] fn test_deny_networks_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example_deny_networks.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should be refused query_a_refused(&mut io_loop, &mut client); let tcp_port = socket_ports.get_v6(Protocol::Tcp); let addr = SocketAddr::from((Ipv6Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should be refused query_a_refused(&mut io_loop, &mut client); }) } #[test] fn test_deny_allow_networks_toml_startup() { subscribe(); let provider = TokioRuntimeProvider::new(); named_test_harness("example_deny_allow_networks.toml", |socket_ports| { let mut io_loop = Runtime::new().unwrap(); let tcp_port = socket_ports.get_v4(Protocol::Tcp); let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv4 should succeed query_a(&mut io_loop, &mut client); let tcp_port = socket_ports.get_v6(Protocol::Tcp); let addr = SocketAddr::from((Ipv6Addr::LOCALHOST, tcp_port.expect("no tcp_port"))); let (stream, sender) = TcpClientStream::new(addr, None, None, provider.clone()); let client = Client::new(Box::new(stream), sender, None); let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect"); hickory_proto::runtime::spawn_bg(&io_loop, bg); // ipv6 should be refused query_a_refused(&mut io_loop, &mut client); }) } hickory-dns-0.25.2/tests/integration/server_harness/mod.rs000064400000000000000000000262551046102023000220210ustar 00000000000000pub mod mut_message_client; use std::{ collections::HashMap, env, io::{BufRead, BufReader, Write, stdout}, net::SocketAddr, panic::{UnwindSafe, catch_unwind}, process::{Command, Stdio}, str::FromStr, sync::*, thread, time::*, }; #[cfg(feature = "__dnssec")] use hickory_client::client::Client; use hickory_client::{ClientError, client::ClientHandle, proto::xfer::DnsResponse}; #[cfg(feature = "__dnssec")] use hickory_proto::dnssec::Algorithm; use hickory_proto::xfer::Protocol; use hickory_proto::{ op::ResponseCode, rr::{DNSClass, Name, RData, RecordType, rdata::A}, }; use regex::Regex; use tokio::runtime::Runtime; use tracing::{info, warn}; #[derive(Debug, Default)] pub struct SocketPort { v4: u16, v6: u16, } #[derive(Debug, Default)] pub struct SocketPorts(HashMap); impl SocketPorts { /// This will overwrite the existing value pub fn put(&mut self, protocol: impl Into, addr: SocketAddr) { let entry = self.0.entry(protocol.into()).or_default(); if addr.is_ipv4() { entry.v4 = addr.port(); } else { entry.v6 = addr.port(); } } /// Assumes there is only one V4 addr for the IP based on the usage in the Server pub fn get_v4(&self, protocol: impl Into) -> Option { self.0 .get(&protocol.into()) .iter() .find_map(|ports| if ports.v4 == 0 { None } else { Some(ports.v4) }) } /// Assumes there is only one V4 addr for the IP based on the usage in the Server #[allow(unused)] pub fn get_v6(&self, protocol: impl Into) -> Option { self.0 .get(&protocol.into()) .iter() .find_map(|ports| if ports.v6 == 0 { None } else { Some(ports.v6) }) } } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ServerProtocol { Dns(Protocol), #[cfg(feature = "metrics")] PrometheusMetrics, } impl From for ServerProtocol { fn from(proto: Protocol) -> Self { Self::Dns(proto) } } #[cfg(feature = "__dnssec")] use self::mut_message_client::MutMessageHandle; fn collect_and_print(read: &mut R, output: &mut String) { output.clear(); read.read_line(output).expect("could not read stdio"); if !output.is_empty() { // uncomment for debugging // println!("SRV: {}", output.trim_end()); } } /// Spins up a Server and handles shutting it down after running the test #[allow(dead_code)] pub fn named_test_harness(toml: &str, test: F) where F: FnOnce(SocketPorts) -> R + UnwindSafe, { let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned()); println!("using server src path: {server_path}"); let mut command = Command::new(env!("CARGO_BIN_EXE_hickory-dns")); command .stdout(Stdio::piped()) .env( "RUST_LOG", "hickory_dns=debug,hickory_client=debug,hickory_proto=debug,hickory_resolver=debug,hickory_server=debug", ) .arg("-d") .arg(format!( "--config={server_path}/tests/test-data/test_configs/{toml}" )) .arg(format!( "--zonedir={server_path}/tests/test-data/test_configs" )) .arg(format!("--port={}", 0)); #[cfg(feature = "__tls")] command.arg(format!("--tls-port={}", 0)); #[cfg(feature = "__https")] command.arg(format!("--https-port={}", 0)); #[cfg(feature = "__quic")] command.arg(format!("--quic-port={}", 0)); #[cfg(feature = "prometheus-metrics")] command.arg(format!("--prometheus-listen-address=127.0.0.1:{}", 0)); println!("named cli options: {command:#?}"); let mut named = command.spawn().expect("failed to start named"); println!("server starting"); let mut named_out = BufReader::new(named.stdout.take().expect("no stdout")); // forced thread killer let named = Arc::new(Mutex::new(named)); let named_killer = Arc::clone(&named); let succeeded = Arc::new(atomic::AtomicBool::new(false)); let succeeded_clone = succeeded.clone(); let killer_join = thread::Builder::new() .name("thread_killer".to_string()) .spawn(move || { let succeeded = succeeded_clone; let kill_named = || { info!("killing named"); let mut named = named_killer.lock().unwrap(); if let Err(error) = named.kill() { warn!(?error, "warning: failed to kill named"); return; } if let Err(error) = named.wait() { warn!(?error, "warning: failed to wait for named"); } }; for _ in 0..30 { thread::sleep(Duration::from_secs(1)); if succeeded.load(atomic::Ordering::Relaxed) { kill_named(); return; } } kill_named(); println!("Thread Killer has been awoken, killing process"); std::process::exit(-1); }) .expect("could not start thread killer"); // These will be collected from the server startup' let mut socket_ports = SocketPorts::default(); // we should get the correct output before 1000 lines... let mut output = String::new(); let mut found = false; let wait_for_start_until = Instant::now() + Duration::from_secs(60); // Search strings for the ports used during testing let addr_regex = Regex::new( r"listening for (UDP|TCP|TLS|HTTPS|QUIC|Prometheus metrics) on ((?:(?:0\.0\.0\.0)|(?:127\.0\.0\.1)|(?:\[::\])):\d+)", ) .unwrap(); while Instant::now() < wait_for_start_until { { if let Some(ret_code) = named .lock() .unwrap() .try_wait() .expect("failed to check status of named") { panic!("named has already exited with code: {}", ret_code); } } collect_and_print(&mut named_out, &mut output); if let Some(addr) = addr_regex.captures(&output) { let proto = addr.get(1).expect("missing protocol").as_str(); let socket_addr = addr.get(2).expect("missing socket addr").as_str(); let socket_addr = SocketAddr::from_str(socket_addr).expect("could not parse socket_addr"); match proto { "UDP" => socket_ports.put(Protocol::Udp, socket_addr), "TCP" => socket_ports.put(Protocol::Tcp, socket_addr), #[cfg(feature = "__tls")] "TLS" => socket_ports.put(Protocol::Tls, socket_addr), #[cfg(feature = "__https")] "HTTPS" => socket_ports.put(Protocol::Https, socket_addr), #[cfg(feature = "__quic")] "QUIC" => socket_ports.put(Protocol::Quic, socket_addr), #[cfg(feature = "metrics")] "Prometheus metrics" => { socket_ports.put(ServerProtocol::PrometheusMetrics, socket_addr) } _ => panic!("unsupported protocol: {proto}"), } } else if output.contains("awaiting connections...") { found = true; break; } } stdout().flush().unwrap(); assert!(found); println!("Test server started. ports: {socket_ports:?}",); // spawn a thread to capture stdout let succeeded_clone = succeeded.clone(); thread::Builder::new() .name("named stdout".into()) .spawn(move || { let succeeded = succeeded_clone; while !succeeded.load(atomic::Ordering::Relaxed) { collect_and_print(&mut named_out, &mut output); if let Some(_ret_code) = named .lock() .unwrap() .try_wait() .expect("failed to check status of named") { // uncomment for debugging: // println!("named exited with code: {}", _ret_code); } } }) .expect("no thread available"); println!("running test..."); let result = catch_unwind(move || test(socket_ports)); println!("test completed"); succeeded.store(true, atomic::Ordering::Relaxed); killer_join.join().expect("join failed"); assert!(result.is_ok(), "test failed"); } pub fn query_message( io_loop: &mut Runtime, client: &mut C, name: Name, record_type: RecordType, ) -> Result { println!("sending request: {name} for: {record_type}"); io_loop.block_on(client.query(name, DNSClass::IN, record_type)) } // This only validates that a query to the server works, it shouldn't be used for more than this. // i.e. more complex checks live with the clients and authorities to validate deeper functionality #[allow(dead_code)] pub fn query_a(io_loop: &mut Runtime, client: &mut C) { let name = Name::from_str("www.example.com.").unwrap(); let response = query_message(io_loop, client, name, RecordType::A).unwrap(); let record = &response.answers()[0]; if let RData::A(address) = record.data() { assert_eq!(address, &A::new(127, 0, 0, 1)) } else { panic!("wrong RDATA") } } // This only validates that a query to the server works, it shouldn't be used for more than this. // i.e. more complex checks live with the clients and authorities to validate deeper functionality #[allow(dead_code)] pub fn query_a_refused(io_loop: &mut Runtime, client: &mut C) { let name = Name::from_str("www.example.com.").unwrap(); let response = query_message(io_loop, client, name, RecordType::A).unwrap(); assert_eq!(response.response_code(), ResponseCode::Refused); } // This only validates that a query to the server works, it shouldn't be used for more than this. // i.e. more complex checks live with the clients and authorities to validate deeper functionality #[allow(dead_code)] #[cfg(feature = "__dnssec")] pub fn query_all_dnssec(io_loop: &mut Runtime, client: Client, algorithm: Algorithm) { use hickory_proto::{ dnssec::{ PublicKey, rdata::{DNSKEY, RRSIG}, }, rr::{Record, RecordData}, }; let name = Name::from_str("example.com.").unwrap(); let mut client = MutMessageHandle::new(client); client.lookup_options.set_dnssec_ok(true); let response = query_message(io_loop, &mut client, name.clone(), RecordType::DNSKEY).unwrap(); let dnskey = response .answers() .iter() .map(Record::data) .filter_map(DNSKEY::try_borrow) .find(|d| d.public_key().algorithm() == algorithm); assert!(dnskey.is_some(), "DNSKEY not found"); let response = query_message(io_loop, &mut client, name, RecordType::DNSKEY).unwrap(); let rrsig = response .answers() .iter() .map(Record::data) .filter_map(RRSIG::try_borrow) .filter(|rrsig| rrsig.algorithm() == algorithm) .find(|rrsig| rrsig.type_covered() == RecordType::DNSKEY); assert!(rrsig.is_some(), "Associated RRSIG not found"); } hickory-dns-0.25.2/tests/integration/server_harness/mut_message_client.rs000064400000000000000000000023611046102023000251010ustar 00000000000000use hickory_client::client::ClientHandle; #[cfg(feature = "__dnssec")] use hickory_proto::op::Edns; use hickory_proto::xfer::{DnsHandle, DnsRequest}; #[cfg(feature = "__dnssec")] use hickory_server::authority::LookupOptions; #[derive(Clone)] pub struct MutMessageHandle { client: C, #[cfg(feature = "__dnssec")] pub lookup_options: LookupOptions, } impl MutMessageHandle { #[allow(dead_code)] pub fn new(client: C) -> Self { MutMessageHandle { client, #[cfg(feature = "__dnssec")] lookup_options: Default::default(), } } } impl DnsHandle for MutMessageHandle { type Response = ::Response; fn is_verifying_dnssec(&self) -> bool { true } #[allow(unused_mut)] fn send + Unpin>(&self, request: R) -> Self::Response { let mut request = request.into(); #[cfg(feature = "__dnssec")] { // mutable block let edns = request.extensions_mut().get_or_insert_with(Edns::new); edns.set_dnssec_ok(true); } println!("sending message"); self.client.send(request) } } hickory-dns-0.25.2/tests/integration/sqlite_tests.rs000064400000000000000000000052421046102023000207250ustar 00000000000000#![cfg(feature = "sqlite")] use std::net::Ipv4Addr; use std::str::FromStr; use rusqlite::*; use hickory_proto::rr::{rdata::A, *}; use hickory_server::store::sqlite::Journal; use hickory_server::store::sqlite::persistence::CURRENT_VERSION; #[test] fn test_new_journal() { let conn = Connection::open_in_memory().expect("could not create in memory DB"); assert_eq!( Journal::new(conn).expect("new Journal").schema_version(), -1 ); } #[test] fn test_init_journal() { let conn = Connection::open_in_memory().expect("could not create in memory DB"); let mut journal = Journal::new(conn).unwrap(); let version = journal.schema_up().unwrap(); assert_eq!(version, CURRENT_VERSION); assert_eq!( Journal::select_schema_version(&journal.conn()).unwrap(), CURRENT_VERSION ); } fn create_test_journal() -> (Record, Journal) { let www = Name::from_str("www.example.com.").unwrap(); let mut record = Record::from_rdata(www, 0, RData::A(A::from(Ipv4Addr::LOCALHOST))); // test that this message can be inserted let conn = Connection::open_in_memory().expect("could not create in memory DB"); let mut journal = Journal::new(conn).unwrap(); journal.schema_up().unwrap(); // insert the message journal.insert_record(0, &record).unwrap(); // insert another... record.set_data(RData::A(A::from(Ipv4Addr::new(127, 0, 1, 1)))); journal.insert_record(0, &record).unwrap(); (record, journal) } #[test] fn test_insert_and_select_record() { let (mut record, journal) = create_test_journal(); // select the record let (row_id, journal_record) = journal .select_record(0) .expect("persistence error") .expect("none"); record.set_data(RData::A(A::from(Ipv4Addr::LOCALHOST))); assert_eq!(journal_record, record); // test another let (row_id, journal_record) = journal .select_record(row_id + 1) .expect("persistence error") .expect("none"); record.set_data(RData::A(A::from(Ipv4Addr::new(127, 0, 1, 1)))); assert_eq!(journal_record, record); // check that we get nothing for id over row_id let option_none = journal .select_record(row_id + 1) .expect("persistence error"); assert!(option_none.is_none()); } #[test] fn test_iterator() { let (mut record, journal) = create_test_journal(); let mut iter = journal.iter(); assert_eq!( record.set_data(RData::A(A::from(Ipv4Addr::LOCALHOST))), &iter.next().unwrap() ); assert_eq!( record.set_data(RData::A(A::from(Ipv4Addr::new(127, 0, 1, 1)))), &iter.next().unwrap() ); assert_eq!(None, iter.next()); } hickory-dns-0.25.2/tests/integration/store_file_tests.rs000064400000000000000000000064071046102023000215630ustar 00000000000000use std::{ path::{Path, PathBuf}, str::FromStr, }; use hickory_proto::rr::{LowerName, Name, RecordType, RrKey}; use hickory_server::authority::{Authority, LookupOptions, ZoneType}; #[cfg(feature = "__dnssec")] use hickory_server::dnssec::NxProofKind; use hickory_server::store::file::{FileAuthority, FileConfig}; use test_support::subscribe; fn file(master_file_path: &Path, _module: &str, _test_name: &str) -> FileAuthority { let config = FileConfig { zone_file_path: master_file_path.to_owned(), }; FileAuthority::try_from_config( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .expect("failed to load file") } basic_battery!(file, crate::store_file_tests::file); #[cfg(feature = "__dnssec")] dnssec_battery!(file, crate::store_file_tests::file); #[test] fn test_all_lines_are_loaded() { subscribe(); let config = FileConfig { zone_file_path: PathBuf::from("../tests/test-data/test_configs/default/nonewline.zone"), }; let mut authority = FileAuthority::try_from_config( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .expect("failed to load"); let rrkey = RrKey { record_type: RecordType::A, name: LowerName::from(Name::from_ascii("ensure.nonewline.").unwrap()), }; assert!(authority.records_get_mut().get(&rrkey).is_some()) } #[test] fn test_implicit_in_class() { subscribe(); let config = FileConfig { zone_file_path: PathBuf::from("../tests/test-data/test_configs/default/implicitclass.zone"), }; let authority = FileAuthority::try_from_config( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ); assert!(authority.is_ok()); } #[tokio::test] async fn test_ttl_wildcard() { subscribe(); let config = FileConfig { zone_file_path: PathBuf::from("../tests/test-data/test_configs/default/test.local.zone"), }; let zone_name = LowerName::from_str("test.local.").unwrap(); let mut authority = FileAuthority::try_from_config( Name::from(zone_name.clone()), ZoneType::Primary, false, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .unwrap(); // This one pass. let rrkey = RrKey { record_type: RecordType::A, name: LowerName::from(Name::from_ascii("simple.test.local.").unwrap()), }; assert_eq!(authority.records_get_mut().get(&rrkey).unwrap().ttl(), 120); // // This one related to a wildcard don't pass around $TTL let name = LowerName::from(Name::from_ascii("x.wc.test.local.").unwrap()); let rr = authority .lookup(&name, RecordType::A, LookupOptions::default()) .await .unwrap(); let data = rr .into_iter() .next() .expect("A record not found in authority"); assert_eq!(data.record_type(), RecordType::A); assert_eq!(data.ttl(), 120); } hickory-dns-0.25.2/tests/integration/store_sqlite_tests.rs000064400000000000000000000045161046102023000221440ustar 00000000000000#![cfg(feature = "sqlite")] use std::path::PathBuf; use std::str::FromStr; use std::{fs, path::Path}; use futures_executor::block_on; use hickory_proto::rr::Name; #[cfg(feature = "__dnssec")] use hickory_server::dnssec::NxProofKind; use hickory_server::{ authority::ZoneType, store::sqlite::{SqliteAuthority, SqliteConfig}, }; fn sqlite(master_file_path: &Path, module: &str, test_name: &str) -> SqliteAuthority { let journal_path = PathBuf::from("target/tests") .join(module.replace("::", "_")) .join(test_name) .join("authority_battery.jrnl"); fs::create_dir_all(journal_path.parent().unwrap()).ok(); // cleanup anything from previous test fs::remove_file(&journal_path).ok(); let config = SqliteConfig { zone_file_path: master_file_path.to_owned(), journal_file_path: journal_path.to_str().unwrap().to_string(), allow_update: true, }; block_on(SqliteAuthority::try_from_config( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, true, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), )) .expect("failed to load file") } #[allow(unused)] fn sqlite_update(master_file_path: &Path, module: &str, test_name: &str) -> SqliteAuthority { let journal_path = PathBuf::from("target/tests") .join(module.replace("::", "_")) .join(test_name) .join("authority_battery.jrnl"); fs::create_dir_all(journal_path.parent().unwrap()).ok(); // cleanup anything from previous test fs::remove_file(&journal_path).ok(); let config = SqliteConfig { zone_file_path: master_file_path.to_owned(), journal_file_path: journal_path.to_str().unwrap().to_string(), allow_update: true, }; block_on(SqliteAuthority::try_from_config( Name::from_str("example.com.").unwrap(), ZoneType::Primary, false, true, None, &config, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), )) .expect("failed to load file") } basic_battery!(sqlite, crate::store_sqlite_tests::sqlite); #[cfg(feature = "__dnssec")] dnssec_battery!(sqlite, crate::store_sqlite_tests::sqlite); #[cfg(feature = "__dnssec")] dynamic_update!(sqlite_update, crate::store_sqlite_tests::sqlite_update); hickory-dns-0.25.2/tests/integration/txt_tests.rs000064400000000000000000000372001046102023000202420ustar 00000000000000use std::str::FromStr; use futures_executor::block_on; use hickory_proto::rr::rdata::{A, AAAA, tlsa::*}; use hickory_proto::rr::*; use hickory_proto::serialize::txt::*; use hickory_server::authority::{Authority, LookupOptions, ZoneType}; #[cfg(feature = "__dnssec")] use hickory_server::dnssec::NxProofKind; use hickory_server::store::in_memory::InMemoryAuthority; use test_support::subscribe; // TODO: split this test up to test each thing separately #[test] #[allow(clippy::cognitive_complexity)] fn test_zone() { subscribe(); const ZONE: &str = r#" @ IN SOA venera action\.domains ( 20 ; SERIAL 7200 ; REFRESH 600 ; RETRY 3600000; EXPIRE 60) ; MINIMUM NS a.isi.edu. NS venera NS vaxa MX 10 venera MX 20 vaxa a A 26.3.0.103 TXT I am a txt record TXT I am another txt record TXT "I am a different" "txt record" TXT key=val aaaa AAAA 4321:0:1:2:3:4:567:89ab alias CNAME a 103.0.3.26.IN-ADDR.ARPA. PTR a b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.IP6.ARPA. PTR aaaa _ldap._tcp.service SRV 1 2 3 short rust-❤️-🦀 A 192.0.2.1 short 70 A 26.3.0.104 venera A 10.1.0.52 A 128.9.0.32 nocerts CAA 0 issue ";" certs CAA 0 issuewild "example.net" _443._tcp.www.example.com. IN TLSA ( 0 0 1 d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971) tech. 3600 in soa ns0.centralnic.net. hostmaster.centralnic.net. 271851 900 1800 6048000 3600 "#; let records = Parser::new(ZONE, None, Some(Name::from_str("isi.edu.").unwrap())).parse(); if records.is_err() { panic!("failed to parse: {:?}", records.err()) } let (origin, records) = records.unwrap(); let authority = InMemoryAuthority::new( origin, records, ZoneType::Primary, false, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .unwrap(); // not validating everything, just one of each... // SOA let soa_record = block_on(authority.soa()) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!(RecordType::SOA, soa_record.record_type()); assert_eq!(&Name::from_str("isi.edu.").unwrap(), soa_record.name()); // i.e. the origin or domain assert_eq!(3_600_000, soa_record.ttl()); assert_eq!(DNSClass::IN, soa_record.dns_class()); if let RData::SOA(soa) = soa_record.data() { // this should all be lowercased assert_eq!(&Name::from_str("venera.isi.edu.").unwrap(), soa.mname()); assert_eq!( &Name::from_str("action\\.domains.isi.edu.").unwrap(), soa.rname() ); assert_eq!(20, soa.serial()); assert_eq!(7200, soa.refresh()); assert_eq!(600, soa.retry()); assert_eq!(3_600_000, soa.expire()); assert_eq!(60, soa.minimum()); } else { panic!("Not an SOA record!!!") // valid panic, test code } let lowercase_record = block_on(authority.lookup( &Name::from_str("tech.").unwrap().into(), RecordType::SOA, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!(&Name::from_str("tech.").unwrap(), lowercase_record.name()); assert_eq!(DNSClass::IN, lowercase_record.dns_class()); if let RData::SOA(lower_soa) = lowercase_record.data() { assert_eq!( &Name::from_str("ns0.centralnic.net.").unwrap(), lower_soa.mname() ); assert_eq!( &Name::from_str("hostmaster.centralnic.net.").unwrap(), lower_soa.rname() ); assert_eq!(271851, lower_soa.serial()); assert_eq!(900, lower_soa.refresh()); assert_eq!(1800, lower_soa.retry()); assert_eq!(6_048_000, lower_soa.expire()); assert_eq!(3_600, lower_soa.minimum()); } else { panic!("Not an SOA record!!!") // valid panic, test code } // NS let mut ns_records: Vec = block_on(authority.lookup( &Name::from_str("isi.edu.").unwrap().into(), RecordType::NS, LookupOptions::default(), )) .unwrap() .iter() .cloned() .collect(); let mut compare = vec![ // this is cool, zip up the expected results... works as long as the order is good. Name::from_str("a.isi.edu.").unwrap(), Name::from_str("venera.isi.edu.").unwrap(), Name::from_str("vaxa.isi.edu.").unwrap(), ]; compare.sort(); ns_records.sort(); let compare = ns_records.iter().zip(compare); for (record, name) in compare { assert_eq!(&Name::from_str("isi.edu.").unwrap(), record.name()); assert_eq!(60, record.ttl()); // TODO: should this be minimum or expire? assert_eq!(DNSClass::IN, record.dns_class()); assert_eq!(RecordType::NS, record.record_type()); if let RData::NS(nsdname) = record.data() { assert_eq!(name, nsdname.0); } else { panic!("Not an NS record!!!") // valid panic, test code } } // MX let mut mx_records: Vec = block_on(authority.lookup( &Name::from_str("isi.edu.").unwrap().into(), RecordType::MX, LookupOptions::default(), )) .unwrap() .iter() .cloned() .collect(); let mut compare = vec![ (10, Name::from_str("venera.isi.edu.").unwrap()), (20, Name::from_str("vaxa.isi.edu.").unwrap()), ]; compare.sort(); mx_records.sort(); let compare = mx_records.iter().zip(compare); for (record, (num, name)) in compare { assert_eq!(&Name::from_str("isi.edu.").unwrap(), record.name()); assert_eq!(60, record.ttl()); // TODO: should this be minimum or expire? assert_eq!(DNSClass::IN, record.dns_class()); assert_eq!(RecordType::MX, record.record_type()); if let RData::MX(rdata) = record.data() { assert_eq!(num, rdata.preference()); assert_eq!(&name, rdata.exchange()); } else { panic!("Not an NS record!!!") // valid panic, test code } } // A let a_record: Record = block_on(authority.lookup( &Name::from_str("a.isi.edu.").unwrap().into(), RecordType::A, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!(&Name::from_str("a.isi.edu.").unwrap(), a_record.name()); assert_eq!(60, a_record.ttl()); // TODO: should this be minimum or expire? assert_eq!(DNSClass::IN, a_record.dns_class()); assert_eq!(RecordType::A, a_record.record_type()); if let RData::A(address) = a_record.data() { assert_eq!(&A::new(26u8, 3u8, 0u8, 103u8), address); } else { panic!("Not an A record!!!") // valid panic, test code } // AAAA let aaaa_record: Record = block_on(authority.lookup( &Name::from_str("aaaa.isi.edu.").unwrap().into(), RecordType::AAAA, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!( &Name::from_str("aaaa.isi.edu.").unwrap(), aaaa_record.name() ); if let RData::AAAA(address) = aaaa_record.data() { assert_eq!(&AAAA::from_str("4321:0:1:2:3:4:567:89ab").unwrap(), address); } else { panic!("Not a AAAA record!!!") // valid panic, test code } // SHORT let short_record: Record = block_on(authority.lookup( &Name::from_str("short.isi.edu.").unwrap().into(), RecordType::A, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!( &Name::from_str("short.isi.edu.").unwrap(), short_record.name() ); assert_eq!(70, short_record.ttl()); if let RData::A(address) = short_record.data() { assert_eq!(&A::new(26u8, 3u8, 0u8, 104u8), address); } else { panic!("Not an A record!!!") // valid panic, test code } // TXT let mut txt_records: Vec = block_on(authority.lookup( &Name::from_str("a.isi.edu.").unwrap().into(), RecordType::TXT, LookupOptions::default(), )) .unwrap() .iter() .cloned() .collect(); let compare: Vec>> = vec![ vec![b"I" as &[u8], b"am", b"a", b"txt", b"record"] .into_iter() .map(Box::from) .collect(), vec![b"I" as &[u8], b"am", b"another", b"txt", b"record"] .into_iter() .map(Box::from) .collect(), vec![b"key=val" as &[u8]] .into_iter() .map(Box::from) .collect(), vec![b"I am a different" as &[u8], b"txt record"] .into_iter() .map(Box::from) .collect(), ]; txt_records.sort(); println!("compare: {compare:#?}"); println!("txt_records: {txt_records:#?}"); let compare = txt_records.iter().zip(compare); for (record, vector) in compare { if let RData::TXT(rdata) = record.data() { assert_eq!(&vector as &[Box<[u8]>], rdata.txt_data()); } else { panic!("Not a TXT record!!!") // valid panic, test code } } // PTR let ptr_record: Record = block_on(authority.lookup( &Name::from_str("103.0.3.26.in-addr.arpa.").unwrap().into(), RecordType::PTR, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); if let RData::PTR(ptrdname) = ptr_record.data() { assert_eq!(Name::from_str("a.isi.edu.").unwrap(), ptrdname.0); } else { panic!("Not a PTR record!!!") // valid panic, test code } // SRV let srv_record: Record = block_on( authority.lookup( &Name::from_str("_ldap._tcp.service.isi.edu.") .unwrap() .into(), RecordType::SRV, LookupOptions::default(), ), ) .unwrap() .iter() .next() .cloned() .unwrap(); if let RData::SRV(rdata) = srv_record.data() { assert_eq!(rdata.priority(), 1); assert_eq!(rdata.weight(), 2); assert_eq!(rdata.port(), 3); assert_eq!(rdata.target(), &Name::from_str("short.isi.edu.").unwrap()); } else { panic!("Not an SRV record!!!") // valid panic, test code } // IDNA name: rust-❤️-🦀 A 192.0.2.1 let idna_record: Record = block_on(authority.lookup( &Name::from_str("rust-❤️-🦀.isi.edu.").unwrap().into(), RecordType::A, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .unwrap(); assert_eq!( &Name::from_str("rust-❤️-🦀.isi.edu.").unwrap(), idna_record.name() ); if let RData::A(address) = idna_record.data() { assert_eq!(&A::new(192u8, 0u8, 2u8, 1u8), address); } else { panic!("Not an A record!!!") // valid panic, test code } // CAA let caa_record: Record = block_on(authority.lookup( &Name::parse("nocerts.isi.edu.", None).unwrap().into(), RecordType::CAA, LookupOptions::default(), )) .unwrap() .iter() .next() .cloned() .expect("nocerts not found"); if let RData::CAA(rdata) = caa_record.data() { assert!(!rdata.issuer_critical()); assert!(rdata.tag().is_issue()); #[allow(deprecated)] let value = rdata.value(); assert!(value.is_issuer()); } else { panic!(); } // TLSA let tlsa_record: Record = block_on( authority.lookup( &Name::parse("_443._tcp.www.example.com.", None) .unwrap() .into(), RecordType::TLSA, LookupOptions::default(), ), ) .unwrap() .iter() .next() .cloned() .expect("tlsa record not found"); if let RData::TLSA(rdata) = tlsa_record.data() { assert_eq!(rdata.cert_usage(), CertUsage::PkixTa); assert_eq!(rdata.selector(), Selector::Full); assert_eq!(rdata.matching(), Matching::Sha256); assert_eq!( rdata.cert_data(), &[ 210, 171, 222, 36, 13, 124, 211, 238, 107, 75, 40, 197, 77, 240, 52, 185, 121, 131, 161, 209, 110, 138, 65, 14, 69, 97, 203, 16, 102, 24, 233, 113 ] ); } else { panic!(); } } #[test] #[allow(clippy::cognitive_complexity)] fn test_bad_cname_at_soa() { subscribe(); const ZONE: &str = r" @ IN SOA venera action\.domains ( 20 ; SERIAL 7200 ; REFRESH 600 ; RETRY 3600000; EXPIRE 60) ; MINIMUM CNAME a a A 127.0.0.1 "; let records = Parser::new(ZONE, None, Some(Name::from_str("isi.edu").unwrap())).parse(); if records.is_err() { panic!("failed to parse: {:?}", records.err()) } let (origin, records) = records.unwrap(); assert!( InMemoryAuthority::new( origin, records, ZoneType::Primary, false, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .is_err() ); } #[test] fn test_bad_cname_at_a() { subscribe(); const ZONE: &str = r" @ IN SOA venera action\.domains ( 20 ; SERIAL 7200 ; REFRESH 600 ; RETRY 3600000; EXPIRE 60) ; MINIMUM a CNAME b a A 127.0.0.1 b A 127.0.0.2 "; let records = Parser::new(ZONE, None, Some(Name::from_str("isi.edu").unwrap())).parse(); if records.is_err() { panic!("failed to parse: {:?}", records.err()) } let (origin, records) = records.unwrap(); assert!( InMemoryAuthority::new( origin, records, ZoneType::Primary, false, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .is_err() ); } #[test] fn test_aname_at_soa() { subscribe(); const ZONE: &str = r" @ IN SOA venera action\.domains ( 20 ; SERIAL 7200 ; REFRESH 600 ; RETRY 3600000; EXPIRE 60) ; MINIMUM ANAME a a A 127.0.0.1 "; let records = Parser::new(ZONE, None, Some(Name::from_str("isi.edu").unwrap())).parse(); if records.is_err() { panic!("failed to parse: {:?}", records.err()) } let (origin, records) = records.unwrap(); assert!( InMemoryAuthority::new( origin, records, ZoneType::Primary, false, #[cfg(feature = "__dnssec")] Some(NxProofKind::Nsec), ) .is_ok() ); } #[test] fn test_named_root() { subscribe(); const ZONE: &str = r" . 3600000 NS A.ROOT-SERVERS.NET. "; let records = Parser::new(ZONE, None, Some(Name::root())).parse(); if records.is_err() { panic!("failed to parse: {:?}", records.err()) } let (_, records) = records.unwrap(); let key = RrKey::new(LowerName::from(Name::root()), RecordType::NS); assert!(records.contains_key(&key)); assert_eq!(records[&key].dns_class(), DNSClass::IN) } hickory-dns-0.25.2/tests/test-data/rsa-2048-pkcs1.pem000064400000000000000000000032171046102023000201010ustar 00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA0YOH2yD8AmqKpIjU6r9tibBnqfnMXc2issle97nGZR+I7eQp Y+w5mL2IxZ6Wx9oxIxxTjtenTCaIR0GAI19ljI7mFe6U+VwiITjeN+Wp4ttnm9jb dJyT39vmisbpEtrBeY5FFfDKo1yCHzw9W5oGQXxfpTFOEYxM0dIoSLXRwWWQNovV agew7QZZQFhZagyx2rCzK6v+vngSG1WJeoh5h1f3BhGAILX+Uz6nU9GcBDVWivKt fcRoh+pPxv1KxM3NPdlZwt5ZOS4uy8lsJVT2GgjTa2i/VaPj4YHGKNlOCqAk6HBQ 0fVpbLm4H+lzpZVsYU9j35E+601UsD82T8DaBwIDAQABAoIBAQDHNqS4JC3bckiv 1/dz2l8iG90FzyKWUtAP5c9qB3fwcUK2yEre2x+M7zMcjQXbs8kjFG0TFlTZ4I2W C172w1+AfeOjGP8H96XwJu2NAtA9h9eFZXaN88/bfHFCpY26wsxSc4eXLCTRnkWP w6yn69Vk8QjUvwP3RYtJuuVY9aEjnGAguKZrtQ1iNDyR2hH/OeQfzSP3QcamVIKA eD3RG9NSTdhPjg8KkdkKR5S4uRN1N3ieKgmm6sY1RKrjFpR5omRCM+WaWQkqrd7H kUNG2Gc3v8DA5lpDK5nxmk+UMoU39n4z3JEBwY1Y1u8aK8pJS5g0pW9V+6fvEGPh HffYaNXhAoGBAPksjcbGzKvMFXTmrjbgoVeW+CD5C7hpiQmGkIy/aQhBpkTJpjh5 3NpNJUmHNdLxJ97pYIxN6r6PgHOrzBaVT3hl+YJ2TcQ2nTC5QILNuMO9UJbMpPwn sTY9t7LmrAksZNVb2AYAiUdyIsffs5SKAsFnEB7s0IK5PgNb770uZWVxAoGBANdA 12P9zA7py2sUw5Xovc9lzRrIw5KKtGxPl926tZkavAJhZjIV3ycOHI712ed5Pk5I tFsAUSyF/lk95WGe8stJi+l/SiqE+99h9SudV+cElVTxIPatXw8t05wJoKpf6C1l iAZEmb/TOVvjZ0dKUOkd13sYoEO7rYG5uaDQRpr3AoGAIK+khX/Ml3i1E9my3sYG xCusrv6cd/t4G4TIb34odAnQmtNs95mczBI0ovmWqO4zkb1BigYqlgbTDdlqc0fw 3dVarq+AMg4L/rk+aio8Y4jxCmcb+WvnhjsAXvp9dNKbA5YBt3/YgkPJpjiFISYd iGtRpQzhqVRNezlTMJjmejECgYBwEniSvS+jjCK3TAZRZO5+r1hM277H66PoyMAf V/j5ECxXaPLWnQCkyvofjO25US0ZgU02HEInVlSJjmqBm0p6gX5xQi8RWE/zjIMk BDYiOAawRSOOi9H0iz2j/ndCfdDa4gQij1ElyQoSR6IcQhtCZKEunDHFUlASPOrj Sc2ZHQKBgQCQL9gxWuUaiIDhaP5zIRk3a7DFfxLTy39c1sag1d9Yb4hwn84yf63s jDrozDXwDsplCBdk0vTBFAYzl3BcsMgudJ4mYXiYpcPvmqwzZE7ZQZdwXwHiEZ+D POzUa7yf+Hf8v7MvwIRSxzc5UbzNkO7N01cnFuwsiKqEYi6Xfbjuuw== -----END RSA PRIVATE KEY----- hickory-dns-0.25.2/tests/test-data/rsa-2048-pkcs1.pem.pub000064400000000000000000000006121046102023000206620ustar 00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRg4fbIPwCaoqkiNTqv22JsGep+cxdzaKyyV73ucZlH4jt5Clj7DmYvYjFnpbH2jEjHFOO16dMJohHQYAjX2WMjuYV7pT5XCIhON435ani22eb2Nt0nJPf2+aKxukS2sF5jkUV8MqjXIIfPD1bmgZBfF+lMU4RjEzR0ihItdHBZZA2i9VqB7DtBllAWFlqDLHasLMrq/6+eBIbVYl6iHmHV/cGEYAgtf5TPqdT0ZwENVaK8q19xGiH6k/G/UrEzc092VnC3lk5Li7LyWwlVPYaCNNraL9Vo+PhgcYo2U4KoCTocFDR9Wlsubgf6XOllWxhT2PfkT7rTVSwPzZPwNoH djc@djc-2021 hickory-dns-0.25.2/tests/test-data/rsa-2048-pkcs8.pem000064400000000000000000000032501046102023000201050ustar 00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCU6pWvsP1hBFCu nZslJSKQYTG9uuxRcshOno76p9vCx35wcF7TkOQIC264ldGgqKGUiXomy3qDF3kC jENIpjyI/MYquNoy1FdqoEsb0E8N7x73G9HKlR+DsYQTEiyEynSehUAK0CeBBrqK +ZjHsBBn/rquRG6mjQHqwn/UhsyONll6PxNFU8xnJruuWdQibUAtC045b5j9d2/r f4S79kU2Hp9C1i/y9MXrC7+aDklxuYccl9zFl8GlgfaLYaQ5AQnoSq2ExpzsWg/u yQfvWfYxig7BFP7LAMwoWhDvNpSuBsW5t430OOBKwJA+nmxC82oofg50Tu5+X0Vq w7hsHBznAgMBAAECggEAI9lpWZdRGCbyyWaVr9aFwJ41XEiAlYb9Y7fWEc+RHw3p iWXz3hi2XJ1r9THT+ko+Q8TozCgWATMIZa2gkP+7+PppNmWz2aK2vKuECVkXfvI+ 0Rw4X/2QTaLRecnZP8qBUpwfc7ryl6QoCkbWGdRvzqlLBaLhxGSTvpE+NsqQufdL gB304tzF7mfHYeUl7Y3Fsmib/9M77SNcGMBrrt+zj0KZWfMZPkMzJL2ou71qK0+f kgsd8HilUhIpn11tDZ478V+28AJNz5vy5H4Hy95tLjAbR2TqGm28DYqZMsE4IqIy NxUOUaptoF5gNZfK9VteYC+CBNEiBHC5eCpOemV5+QKBgQDH+u2ibw/AxO9o4iig tL6Ker10GnWebP9Qtp1kDXTBrDOJ62AXuCpAU/Vmp/i8sSJFpmhziBqdBk0oL/Qc pSdlaekqn7uIDkA2S83t1CiB/bmfPg8ikCJEFXwGeVg2dhQI1XLKdVIDtKExsyBQ UEWCPB64K/Mk6fR1tkCo8JF/0wKBgQC+ob4bdTkD15rXhEDH5w8WynW6szslo4Ob MFTjX94kUwXMRDGNYGmQYayLlLuTyF3VUdwjhemzCx5ao8/acZu7DexJ3UA1d6MA bRx9n3Ls9rOs3vkt+MQ8w36io8gn/j4cXKE64ENH9Un0HjnFmy6+9aOxyvoC5b80 fM+rgHmWHQKBgQCc3Fi1xsPmgxL/hBY2KFAlMy2T+oUz5W9HElbWei+REs/CaRwQ vuZ1WuXsxei3qYGmfGcF6EZn0N+InESwi2MhizfXtT9V80euzzsSSA4WGL0XCYi5 pbEbEnccZOYqoegJHdRStj/EpFSkNUstdClxhFLcR60K0XBtPK3vVfPq/QKBgCJZ gKjv8WsY0YTeBtI+d0CTEK2omBLLKO6wwcsKvjtrXlMVQpyH792dS/sGvNn/M7E5 g+I+wgKKEVkhxVVGPVpyQs54eJZpzUxJXOXh8lJyjaOrtdirx6hvTYU+39UdAdIY etDiEh2IPGAphh1uPfkYx/tEaNJW1aI+jykHCGGdAoGADgtYFoJK3YKd+iRtgv3e E6nw/aHVmU9YmREBdJ+b6IHy/ea1KNHc3V/Lksi2ZERJAtYBrdOroRc1TdSyVttJ bM/NQEAS8JUP0ilGVApuyUw6JthwsynPspzaVp2P88zFx5WDePOdrQii8ZnzTE+z RRvFE3L879esS6Ii1ig9YD4= -----END PRIVATE KEY-----