diff --git a/CODEOWNERS b/CODEOWNERS index ea0baee827312..cf7aef6e613ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -283,6 +283,7 @@ extensions/upstreams/tcp @ggreenway @mattklein123 /*/extensions/network/dns_resolver/cares @yanavlasov @mattklein123 /*/extensions/network/dns_resolver/apple @yanavlasov @mattklein123 /*/extensions/network/dns_resolver/getaddrinfo @fredyw @mattklein123 +/*/extensions/network/dns_resolver/hickory @agrawroh @yanavlasov @wbpcode # compression code /*/extensions/filters/http/decompressor @kbaichoo @mattklein123 /*/extensions/filters/http/compressor @kbaichoo @mattklein123 diff --git a/Cargo.lock b/Cargo.lock index 5408ae0149a37..da4d0b6c8f063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,29 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "bindgen" version = "0.70.1" @@ -43,6 +66,28 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -69,6 +114,62 @@ dependencies = [ "libloading", ] +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +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 = "downcast" version = "0.11.0" @@ -81,6 +182,31 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "envoy-dynamic-modules-builtin-extensions" +version = "0.1.0" +dependencies = [ + "envoy-proxy-dynamic-modules-rust-sdk", + "hickory-resolver", + "serde", + "serde_json", + "time", + "tokio", + "url", +] + [[package]] name = "envoy-proxy-dynamic-modules-rust-sdk" version = "0.1.0" @@ -89,6 +215,39 @@ dependencies = [ "mockall", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fragile" version = "2.0.1" @@ -96,217 +255,1522 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" [[package]] -name = "glob" -version = "0.3.3" +name = "futures-channel" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] [[package]] -name = "itertools" -version = "0.13.0" +name = "futures-core" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "either", + "futures-core", + "futures-task", + "pin-project-lite", + "slab", ] [[package]] -name = "libc" -version = "0.2.183" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] -name = "libloading" -version = "0.8.9" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "windows-link", + "libc", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "log" -version = "0.4.29" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] [[package]] -name = "memchr" -version = "2.8.0" +name = "glob" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "h2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] -name = "mockall" -version = "0.13.1" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", + "foldhash", ] [[package]] -name = "mockall_derive" -version = "0.13.1" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ + "async-trait", + "bitflags", + "bytes", "cfg-if", - "proc-macro2", - "quote", - "syn", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "http", + "idna", + "ipnet", + "once_cell", + "rand", + "ring", + "rustls", + "rustls-pki-types", + "thiserror", + "time", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", + "webpki-roots 0.26.11", ] [[package]] -name = "nom" -version = "7.1.3" +name = "hickory-resolver" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ - "memchr", - "minimal-lexical", + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "rustls", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls", + "tracing", + "webpki-roots 0.26.11", ] [[package]] -name = "predicates" -version = "3.1.4" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "anstyle", - "predicates-core", + "bytes", + "itoa", ] [[package]] -name = "predicates-core" -version = "1.0.10" +name = "icu_collections" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "predicates-tree" -version = "1.0.13" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ - "predicates-core", - "termtree", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "icu_normalizer" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "proc-macro2", - "syn", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "icu_normalizer_data" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "unicode-ident", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "quote" -version = "1.0.45" +name = "icu_properties_data" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ - "proc-macro2", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "regex" -version = "1.12.3" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "regex-syntax" -version = "0.8.10" +name = "indexmap" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "ipconfig" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] [[package]] -name = "shlex" -version = "1.3.0" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "syn" -version = "2.0.117" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "either", ] [[package]] -name = "termtree" -version = "0.5.1" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "test-programs" -version = "0.1.0" +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ - "envoy-proxy-dynamic-modules-rust-sdk", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[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 = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[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", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[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.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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 = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test-programs" +version = "0.1.0" +dependencies = [ + "envoy-proxy-dynamic-modules-rust-sdk", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 7997a7ba7af34..3729abf5cbc67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["source/extensions/dynamic_modules/sdk/rust", "test/extensions/dynamic_modules/test_data/rust"] +members = ["source/extensions/dynamic_modules/sdk/rust", "source/extensions/dynamic_modules/builtin_extensions", "test/extensions/dynamic_modules/test_data/rust"] resolver = "2" diff --git a/api/BUILD b/api/BUILD index 0d01e3dc6e1c7..72b62a1359e22 100644 --- a/api/BUILD +++ b/api/BUILD @@ -366,6 +366,7 @@ proto_library( "//envoy/extensions/network/dns_resolver/apple/v3:pkg", "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", + "//envoy/extensions/network/dns_resolver/hickory/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", diff --git a/api/envoy/extensions/network/dns_resolver/hickory/v3/BUILD b/api/envoy/extensions/network/dns_resolver/hickory/v3/BUILD new file mode 100644 index 0000000000000..504c6c70514ac --- /dev/null +++ b/api/envoy/extensions/network/dns_resolver/hickory/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.proto b/api/envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.proto new file mode 100644 index 0000000000000..4680cdec5e022 --- /dev/null +++ b/api/envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; + +package envoy.extensions.network.dns_resolver.hickory.v3; + +import "envoy/config/core/v3/address.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.network.dns_resolver.hickory.v3"; +option java_outer_classname = "HickoryDnsResolverProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/hickory/v3;hickoryv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Hickory DNS resolver] +// [#extension: envoy.network.dns_resolver.hickory] + +// Configuration for DNS-over-TLS (DoT) servers. +message DnsOverTlsConfig { + // DNS-over-TLS server addresses. The port should typically be 853. + repeated config.core.v3.Address servers = 1; + + // The SNI hostname to use for TLS verification. Required when servers are specified. + string tls_server_name = 2 [(validate.rules).string = {min_len: 1}]; +} + +// Configuration for DNS-over-HTTPS (DoH) servers. +message DnsOverHttpsConfig { + // DNS-over-HTTPS endpoint URLs (e.g., ``https://dns.google/dns-query``). + repeated string server_urls = 1 [(validate.rules).repeated = {items {string {min_len: 1}}}]; +} + +// Configuration for the Hickory DNS resolver. This resolver uses the Hickory DNS library, +// a pure Rust DNS implementation, for DNS resolution. It supports standard DNS (UDP/TCP), +// DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), and ``DNSSEC`` validation. +// +// The resolver runs asynchronously on its own ``Tokio`` runtime threads, separate from Envoy's +// event loop threads. Results are delivered back to the calling dispatcher thread. +// [#next-free-field: 10] +message HickoryDnsResolverConfig { + // A list of DNS resolver addresses for standard UDP/TCP resolution. + // If not specified and ``use_system_config`` is true (the default), the system configuration + // (``/etc/resolv.conf`` on Unix) will be used. + repeated config.core.v3.Address resolvers = 1; + + // Configuration for DNS-over-TLS (DoT). When specified, queries will be sent over TLS + // to the configured servers. + DnsOverTlsConfig dns_over_tls = 2; + + // Configuration for DNS-over-HTTPS (DoH). When specified, queries will be sent over + // HTTPS to the configured endpoints. + DnsOverHttpsConfig dns_over_https = 3; + + // Enable ``DNSSEC`` validation for DNS responses. When enabled, the resolver will validate + // ``DNSSEC`` signatures and reject responses that fail validation. + // + // Defaults to false. + bool enable_dnssec = 4; + + // Maximum number of entries in the DNS response cache. The cache uses an LRU eviction + // policy and supports negative caching (caching of NXDOMAIN/NODATA responses). + // + // Defaults to 1024. + google.protobuf.UInt32Value cache_size = 5; + + // Number of threads in the ``Tokio`` runtime used for asynchronous DNS resolution. + // Each resolver instance runs its own ``Tokio`` runtime. + // + // Defaults to 2. Maximum is 16. + google.protobuf.UInt32Value num_resolver_threads = 6 [(validate.rules).uint32 = {lte: 16 gte: 1}]; + + // If true, read the system DNS configuration (``/etc/resolv.conf`` on Unix) for name server + // addresses and search domains. When ``resolvers`` are also specified, they take precedence + // over the system configuration. + // + // Defaults to true when no ``resolvers``, ``dns_over_tls``, or ``dns_over_https`` are specified. + bool use_system_config = 7; + + // Timeout for each individual DNS query attempt. + // + // Defaults to 5 seconds. + google.protobuf.Duration query_timeout = 8 [(validate.rules).duration = {gte {nanos: 1000000}}]; + + // Maximum number of query attempts before the resolver gives up. Each attempt may use + // a different name server. + // + // Defaults to 3. + google.protobuf.UInt32Value query_tries = 9 [(validate.rules).uint32 = {gte: 1}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index ac41190d2598c..bdc350453a95d 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -306,6 +306,7 @@ proto_library( "//envoy/extensions/network/dns_resolver/apple/v3:pkg", "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", + "//envoy/extensions/network/dns_resolver/hickory/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", diff --git a/bazel/dependency_imports.bzl b/bazel/dependency_imports.bzl index ae18f54c570f9..37571d5bef98e 100644 --- a/bazel/dependency_imports.bzl +++ b/bazel/dependency_imports.bzl @@ -257,9 +257,5 @@ def crates_repositories(): # * https://github.com/bazelbuild/rules_rust/pull/3866 # # lockfile = Label("@envoy//:Cargo.Bazel.lock"), - manifests = [ - "@envoy//source/extensions/dynamic_modules/sdk/rust:Cargo.toml", - "@envoy//test/extensions/dynamic_modules/test_data/rust:Cargo.toml", - "@envoy//:Cargo.toml", - ], + manifests = ["@envoy//:Cargo.toml"], ) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 60e15c4832e60..eed2aed8ed379 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -1290,6 +1290,7 @@ rules_rust: - dataplane_core - dataplane_ext extensions: + - envoy.network.dns_resolver.hickory - envoy.wasm.runtime.wasmtime license: "Apache-2.0" license_url: "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt" @@ -1348,6 +1349,7 @@ toolchains_llvm: use_category: - build - dataplane_core + - dataplane_ext - controlplane implied_untracked_deps: - llvm_toolchain_llvm diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cbf24fa9f6f0b..819fe0389a2f6 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -313,6 +313,11 @@ new_features: operators to configure the timer interval (``timer_interval``, minimum 1s, default 10s) and the memory release threshold (``max_unfreed_memory_bytes``, default 100MB) passed to ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. +- area: dns_resolver + change: | + Added :ref:`HickoryDnsResolverConfig + `, a new DNS + resolver using the `Hickory DNS `_ library. - area: dynamic_modules change: | Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform diff --git a/docs/root/intro/arch_overview/upstream/dns_resolution.rst b/docs/root/intro/arch_overview/upstream/dns_resolution.rst index 1f280f6b547f6..8f6b4f7d36a79 100644 --- a/docs/root/intro/arch_overview/upstream/dns_resolution.rst +++ b/docs/root/intro/arch_overview/upstream/dns_resolution.rst @@ -13,7 +13,7 @@ Envoy uses `c-ares `_ as a third party DNS res On Apple OSes Envoy additionally offers resolution using Apple specific APIs via the ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime feature. -Envoy provides DNS resolution through extensions, and contains 3 built-in extensions: +Envoy provides DNS resolution through extensions, and contains 4 built-in extensions: 1) c-ares: :ref:`CaresDnsResolverConfig` @@ -21,6 +21,10 @@ Envoy provides DNS resolution through extensions, and contains 3 built-in extens 3) getaddrinfo: :ref:`GetAddrInfoDnsResolverConfig ` +4) Hickory DNS: :ref:`HickoryDnsResolverConfig ` + A pure Rust DNS resolver supporting standard DNS (UDP/TCP), DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), + and DNSSEC validation. It runs on its own Tokio runtime threads via the dynamic modules framework. + For an example of a built-in DNS typed configuration see the :ref:`HTTP filter configuration documentation `. The c-ares based DNS Resolver emits the following stats rooted in the ``dns.cares`` stats tree: diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index 708ad49d61fc2..71d30fb9460e1 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -161,6 +161,7 @@ envoy_cc_library( "@envoy_api//envoy/extensions/network/dns_resolver/apple/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/extensions/network/dns_resolver/cares/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/network/dns_resolver/hickory/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/extensions/path/match/uri_template/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/extensions/path/rewrite/uri_template/v3:pkg_cc_proto_descriptor", diff --git a/source/common/protobuf/create_reflectable_message.cc b/source/common/protobuf/create_reflectable_message.cc index c4acbd1ba9178..fdbcea7b9db32 100644 --- a/source/common/protobuf/create_reflectable_message.cc +++ b/source/common/protobuf/create_reflectable_message.cc @@ -112,6 +112,7 @@ Protobuf::ReflectableMessage createReflectableMessage(const Protobuf::Message& m #include "envoy/extensions/network/dns_resolver/apple/v3/apple_dns_resolver_descriptor.pb.h" #include "envoy/extensions/network/dns_resolver/cares/v3/cares_dns_resolver_descriptor.pb.h" #include "envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_descriptor.pb.h" +#include "envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver_descriptor.pb.h" #include "envoy/extensions/network/socket_interface/v3/default_socket_interface_descriptor.pb.h" #include "envoy/extensions/path/match/uri_template/v3/uri_template_match_descriptor.pb.h" #include "envoy/extensions/path/rewrite/uri_template/v3/uri_template_rewrite_descriptor.pb.h" @@ -338,6 +339,8 @@ std::unique_ptr createTranscoder() { protobuf::reflection:: envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver:: kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_network_dns_resolver_hickory_v3_hickory_dns_resolver:: + kFileDescriptorInfo, protobuf::reflection::envoy_extensions_network_socket_interface_v3_default_socket_interface:: kFileDescriptorInfo, protobuf::reflection::envoy_extensions_path_match_uri_template_v3_uri_template_match:: diff --git a/source/extensions/dynamic_modules/builtin_extensions/BUILD b/source/extensions/dynamic_modules/builtin_extensions/BUILD new file mode 100644 index 0000000000000..1e8d33452d59d --- /dev/null +++ b/source/extensions/dynamic_modules/builtin_extensions/BUILD @@ -0,0 +1,45 @@ +load("@envoy_rust_crate_index//:defs.bzl", "all_crate_deps") +load("@rules_rust//rust:defs.bzl", "rust_clippy", "rust_static_library", "rustfmt_test") +load("//bazel:envoy_build_system.bzl", "envoy_extension_package") +load("//source/extensions/dynamic_modules:dynamic_modules.bzl", "envoy_dynamic_module_prefix_symbols") + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +exports_files(["Cargo.toml"]) + +rust_static_library( + name = "builtin_extensions_static_lib", + srcs = glob(["*.rs"]), + crate_root = "lib.rs", + edition = "2021", + deps = [ + "//source/extensions/dynamic_modules/sdk/rust:envoy_proxy_dynamic_modules_rust_sdk", + ] + all_crate_deps( + normal = True, + ), +) + +envoy_dynamic_module_prefix_symbols( + name = "hickory_dns_static", + archive = ":builtin_extensions_static_lib", + module_name = "hickory_dns_static", + visibility = [ + "//source/extensions/network/dns_resolver/hickory:__pkg__", + ], +) + +rustfmt_test( + name = "fmt_builtin_extensions", + testonly = True, + tags = ["nocoverage"], + targets = [":builtin_extensions_static_lib"], +) + +rust_clippy( + name = "clippy_builtin_extensions", + testonly = True, + tags = ["nocoverage"], + deps = [":builtin_extensions_static_lib"], +) diff --git a/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml b/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml new file mode 100644 index 0000000000000..24be54f8e6f35 --- /dev/null +++ b/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "envoy-dynamic-modules-builtin-extensions" +version = "0.1.0" +edition = "2021" +authors = ["Envoy Proxy Authors "] +description = "Builtin Rust extensions for Envoy dynamic modules" +license = "Apache-2.0" +repository = "https://github.com/envoyproxy/envoy" + +[dependencies] +envoy-proxy-dynamic-modules-rust-sdk = { path = "../sdk/rust" } +hickory-resolver = { version = "0.25", features = ["system-config", "tokio", "tls-ring", "https-ring", "dnssec-ring", "webpki-roots"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +url = "2" + +# Pin time to a version compatible with stable Rust. +time = ">=0.3.0, <0.3.37" + +[lib] +name = "envoy_dynamic_modules_builtin_extensions" +path = "lib.rs" +crate-type = ["staticlib"] diff --git a/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs new file mode 100644 index 0000000000000..0b6aec37c98bd --- /dev/null +++ b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs @@ -0,0 +1,433 @@ +//! Hickory DNS resolver dynamic module. +//! +//! This module implements a DNS resolver using the Hickory DNS library, a pure Rust DNS +//! implementation. It supports standard DNS (UDP/TCP), DNS-over-TLS, DNS-over-HTTPS, and +//! DNSSEC validation. The resolver runs on its own Tokio runtime, delivering results back +//! to Envoy's dispatcher thread via the dynamic module ABI. + +use envoy_proxy_dynamic_modules_rust_sdk::*; +use std::net::{SocketAddr, ToSocketAddrs}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +fn program_init() -> bool { + true +} + +fn new_dns_resolver_config( + _name: &str, + config: &[u8], + _envoy_dns_resolver_config: Arc, +) -> Option> { + let config_str = std::str::from_utf8(config).ok()?; + let config: HickoryConfig = serde_json::from_str(config_str).ok()?; + Some(Box::new(HickoryDnsResolverConfigImpl { config })) +} + +declare_dns_resolver_init_functions!(program_init, new_dns_resolver_config); + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct HickoryConfig { + #[serde(default)] + resolvers: Vec, + #[serde(default)] + dns_over_tls: Option, + #[serde(default)] + dns_over_https: Option, + #[serde(default)] + enable_dnssec: bool, + #[serde(default)] + cache_size: Option, + #[serde(default)] + num_resolver_threads: Option, + #[serde(default)] + use_system_config: bool, + #[serde(default)] + query_timeout: Option, + #[serde(default)] + query_tries: Option, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct ResolverAddress { + socket_address: Option, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct SocketAddressJson { + address: String, + port_value: Option, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct DnsOverTlsJsonConfig { + #[serde(default)] + servers: Vec, + #[serde(default)] + tls_server_name: String, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct DnsOverHttpsJsonConfig { + #[serde(default)] + server_urls: Vec, +} + +impl HickoryConfig { + fn effective_cache_size(&self) -> usize { + self.cache_size.unwrap_or(1024) as usize + } + + fn effective_num_threads(&self) -> usize { + self.num_resolver_threads.unwrap_or(2).clamp(1, 16) as usize + } + + fn effective_query_timeout(&self) -> std::time::Duration { + self + .query_timeout + .as_ref() + .and_then(|s| parse_proto_duration(s)) + .unwrap_or(std::time::Duration::from_secs(5)) + } + + fn effective_query_tries(&self) -> usize { + self.query_tries.unwrap_or(3).max(1) as usize + } + + fn should_use_system_config(&self) -> bool { + if self.use_system_config { + return true; + } + self.resolvers.is_empty() && self.dns_over_tls.is_none() && self.dns_over_https.is_none() + } +} + +/// Parse a protobuf Duration JSON string (e.g., "5s", "1.500s"). +fn parse_proto_duration(s: &str) -> Option { + let s = s.trim(); + if let Some(stripped) = s.strip_suffix('s') { + if let Some((whole, frac)) = stripped.split_once('.') { + let secs: u64 = whole.parse().ok()?; + let nanos: u32 = format!("{:0<9}", frac)[.. 9].parse().ok()?; + Some(std::time::Duration::new(secs, nanos)) + } else { + let secs: u64 = stripped.parse().ok()?; + Some(std::time::Duration::from_secs(secs)) + } + } else { + None + } +} + +struct HickoryDnsResolverConfigImpl { + config: HickoryConfig, +} + +// SAFETY: The config is immutable after construction and contains only owned data. +unsafe impl Send for HickoryDnsResolverConfigImpl {} +unsafe impl Sync for HickoryDnsResolverConfigImpl {} + +impl DnsResolverConfig for HickoryDnsResolverConfigImpl { + fn new_resolver( + &self, + envoy_callback: Arc, + ) -> Box { + let resolver = HickoryDnsResolverImpl::new(&self.config, envoy_callback); + Box::new(resolver) + } +} + +type TokioResolver = + hickory_resolver::Resolver; + +struct HickoryDnsResolverImpl { + runtime: Option, + resolver: Arc, + envoy_callback: Arc, + /// Set to true during drop to prevent spawned tasks from calling back into C++. + shutting_down: Arc, +} + +// SAFETY: All fields are Arc-wrapped or owned and thread-safe. +unsafe impl Send for HickoryDnsResolverImpl {} +unsafe impl Sync for HickoryDnsResolverImpl {} + +impl Drop for HickoryDnsResolverImpl { + fn drop(&mut self) { + // Signal all spawned tasks to skip the callback. This must happen before + // shutting down the runtime so that tasks completing during shutdown do not + // attempt to call into the C++ resolver which is being destroyed. + self.shutting_down.store(true, Ordering::Release); + + if let Some(rt) = self.runtime.take() { + rt.shutdown_timeout(std::time::Duration::from_secs(5)); + } + } +} + +impl HickoryDnsResolverImpl { + fn new(config: &HickoryConfig, envoy_callback: Arc) -> Self { + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(config.effective_num_threads()) + .thread_name("hickory-dns") + .enable_all() + .build() + .expect("failed to create Tokio runtime for Hickory DNS"); + + let resolver = runtime.block_on(async { build_resolver(config) }); + + HickoryDnsResolverImpl { + runtime: Some(runtime), + resolver: Arc::new(resolver), + envoy_callback, + shutting_down: Arc::new(AtomicBool::new(false)), + } + } +} + +fn build_resolver(config: &HickoryConfig) -> TokioResolver { + use hickory_resolver::config::*; + use hickory_resolver::name_server::TokioConnectionProvider; + use hickory_resolver::proto::xfer::Protocol; + + let mut resolver_config = if config.should_use_system_config() { + let (sys_config, _) = hickory_resolver::system_conf::read_system_conf() + .unwrap_or_else(|_| (ResolverConfig::default(), ResolverOpts::default())); + sys_config + } else { + ResolverConfig::new() + }; + + for resolver_addr in &config.resolvers { + if let Some(ref sa) = resolver_addr.socket_address { + let port = sa.port_value.unwrap_or(53) as u16; + if let Ok(ip) = sa.address.parse::() { + let socket_addr = SocketAddr::new(ip, port); + resolver_config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Udp)); + resolver_config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Tcp)); + } + } + } + + if let Some(ref dot_config) = config.dns_over_tls { + for server in &dot_config.servers { + if let Some(ref sa) = server.socket_address { + let port = sa.port_value.unwrap_or(853) as u16; + if let Ok(ip) = sa.address.parse::() { + let socket_addr = SocketAddr::new(ip, port); + let mut ns = NameServerConfig::new(socket_addr, Protocol::Tls); + ns.tls_dns_name = Some(dot_config.tls_server_name.clone()); + resolver_config.add_name_server(ns); + } + } + } + } + + if let Some(ref doh_config) = config.dns_over_https { + for url_str in &doh_config.server_urls { + if let Ok(parsed_url) = url::Url::parse(url_str) { + let host = match parsed_url.host_str() { + Some(h) => h, + None => continue, + }; + let port = parsed_url.port().unwrap_or(443); + let path = parsed_url.path(); + let ip = if let Ok(ip) = host.parse::() { + ip + } else { + // Resolve the hostname to an IP via a blocking system DNS lookup. + // This is acceptable during initialization. + match (host, port).to_socket_addrs() { + Ok(mut addrs) => match addrs.next() { + Some(addr) => addr.ip(), + None => continue, + }, + Err(_) => continue, + } + }; + let socket_addr = SocketAddr::new(ip, port); + let mut ns = NameServerConfig::new(socket_addr, Protocol::Https); + ns.tls_dns_name = Some(host.to_string()); + if path != "/" && !path.is_empty() { + ns.http_endpoint = Some(path.to_string()); + } + resolver_config.add_name_server(ns); + } + } + } + + let mut opts = ResolverOpts::default(); + opts.timeout = config.effective_query_timeout(); + opts.attempts = config.effective_query_tries(); + opts.cache_size = config.effective_cache_size(); + opts.validate = config.enable_dnssec; + + let provider = TokioConnectionProvider::default(); + let mut builder = hickory_resolver::Resolver::builder_with_config(resolver_config, provider); + *builder.options_mut() = opts; + builder.build() +} + +impl DnsResolverInstance for HickoryDnsResolverImpl { + fn resolve( + &self, + dns_name: &str, + lookup_family: DnsLookupFamily, + query_id: u64, + ) -> Option> { + let cancelled = Arc::new(AtomicBool::new(false)); + let resolver = Arc::clone(&self.resolver); + let envoy_callback = Arc::clone(&self.envoy_callback); + let dns_name_owned = dns_name.to_string(); + let cancelled_clone = Arc::clone(&cancelled); + let shutting_down = Arc::clone(&self.shutting_down); + + // The runtime is always available during normal operation. It is only taken + // during Drop, after which resolve() cannot be called. + let runtime = self.runtime.as_ref().expect("runtime unavailable"); + + runtime.spawn(async move { + let result = perform_lookup(&resolver, &dns_name_owned, lookup_family).await; + + // Check both per-query cancellation and resolver-level shutdown. The shutdown + // flag prevents calling back into the C++ resolver during destruction. + if cancelled_clone.load(Ordering::Acquire) || shutting_down.load(Ordering::Acquire) { + return; + } + + match result { + Ok(addresses) => { + envoy_callback.resolve_complete( + query_id, + DnsResolutionStatus::Completed, + "resolved", + &addresses, + ); + }, + Err(details) => { + envoy_callback.resolve_complete(query_id, DnsResolutionStatus::Failure, &details, &[]); + }, + } + }); + + Some(Box::new(HickoryActiveQuery { cancelled })) + } + + fn reset_networking(&self) { + self.resolver.clear_cache(); + } +} + +async fn perform_lookup( + resolver: &TokioResolver, + dns_name: &str, + lookup_family: DnsLookupFamily, +) -> Result, String> { + use hickory_resolver::proto::rr::RecordType; + + // If the input is already an IP address, return it directly without DNS lookup. + // This matches the behavior of getaddrinfo and c-ares resolvers. + if let Ok(ip) = dns_name.parse::() { + return Ok(resolve_ip_address_directly(ip, lookup_family)); + } + + let mut addresses = Vec::new(); + + let lookup_a = matches!( + lookup_family, + DnsLookupFamily::V4Only + | DnsLookupFamily::Auto + | DnsLookupFamily::V4Preferred + | DnsLookupFamily::All + ); + let lookup_aaaa = matches!( + lookup_family, + DnsLookupFamily::V6Only + | DnsLookupFamily::Auto + | DnsLookupFamily::V4Preferred + | DnsLookupFamily::All + ); + + let mut errors = Vec::new(); + + if lookup_a { + match resolver.lookup(dns_name, RecordType::A).await { + Ok(response) => { + for record in response.records() { + if let Some(a) = record.data().as_a() { + addresses.push(DnsAddress { + address: format!("{}:0", a.0), + ttl_seconds: record.ttl(), + }); + } + } + }, + Err(e) => errors.push(format!("A lookup failed: {e}")), + } + } + + if lookup_aaaa { + match resolver.lookup(dns_name, RecordType::AAAA).await { + Ok(response) => { + for record in response.records() { + if let Some(aaaa) = record.data().as_aaaa() { + addresses.push(DnsAddress { + address: format!("[{}]:0", aaaa.0), + ttl_seconds: record.ttl(), + }); + } + } + }, + Err(e) => errors.push(format!("AAAA lookup failed: {e}")), + } + } + + if lookup_family == DnsLookupFamily::V4Preferred { + addresses.sort_by_key(|a| u8::from(a.address.starts_with('['))); + } + + if addresses.is_empty() && !errors.is_empty() { + return Err(errors.join("; ")); + } + + Ok(addresses) +} + +/// Handles the case where the DNS name is already an IP address by returning it +/// directly, respecting the requested lookup family. +fn resolve_ip_address_directly( + ip: std::net::IpAddr, + lookup_family: DnsLookupFamily, +) -> Vec { + const SYNTHETIC_TTL: u32 = 60; + if matches!( + (&ip, lookup_family), + (std::net::IpAddr::V4(_), DnsLookupFamily::V6Only) + | (std::net::IpAddr::V6(_), DnsLookupFamily::V4Only) + ) { + return Vec::new(); + } + let address = match ip { + std::net::IpAddr::V4(v4) => format!("{v4}:0"), + std::net::IpAddr::V6(v6) => format!("[{v6}]:0"), + }; + vec![DnsAddress { + address, + ttl_seconds: SYNTHETIC_TTL, + }] +} + +struct HickoryActiveQuery { + cancelled: Arc, +} + +impl DnsActiveQuery for HickoryActiveQuery { + fn cancel(&mut self) { + self.cancelled.store(true, Ordering::Release); + } +} diff --git a/source/extensions/dynamic_modules/builtin_extensions/lib.rs b/source/extensions/dynamic_modules/builtin_extensions/lib.rs new file mode 100644 index 0000000000000..98b14058ae02f --- /dev/null +++ b/source/extensions/dynamic_modules/builtin_extensions/lib.rs @@ -0,0 +1 @@ +mod hickory_dns; diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 48588f88c038c..f85307988880c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -537,6 +537,8 @@ EXTENSIONS = { "envoy.network.dns_resolver.apple": "//source/extensions/network/dns_resolver/apple:config", # getaddrinfo DNS resolver extension can be used when the system resolver is desired (e.g., Android) "envoy.network.dns_resolver.getaddrinfo": "//source/extensions/network/dns_resolver/getaddrinfo:config", + # Hickory DNS resolver extension uses a Rust-based DNS library with support for DoT, DoH, and `DNSSEC`. + "envoy.network.dns_resolver.hickory": "//source/extensions/network/dns_resolver/hickory:config", # # Address Resolvers diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 1d340be700ba4..1529ac8b66e9e 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1764,6 +1764,13 @@ envoy.network.dns_resolver.getaddrinfo: status: stable type_urls: - envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig +envoy.network.dns_resolver.hickory: + categories: + - envoy.network.dns_resolver + security_posture: robust_to_untrusted_downstream_and_upstream + status: alpha + type_urls: + - envoy.extensions.network.dns_resolver.hickory.v3.HickoryDnsResolverConfig envoy.resolvers.reverse_connection: categories: - envoy.resolvers diff --git a/source/extensions/network/dns_resolver/hickory/BUILD b/source/extensions/network/dns_resolver/hickory/BUILD new file mode 100644 index 0000000000000..857345201610c --- /dev/null +++ b/source/extensions/network/dns_resolver/hickory/BUILD @@ -0,0 +1,33 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "hickory_dns_lib", + srcs = ["hickory_dns_impl.cc"], + hdrs = ["hickory_dns_impl.h"], + deps = [ + "//envoy/event:dispatcher_interface", + "//envoy/network:dns_interface", + "//envoy/network:dns_resolver_interface", + "//envoy/registry", + "//source/common/network:utility_lib", + "//source/extensions/dynamic_modules:dynamic_modules_lib", + "@envoy_api//envoy/extensions/network/dns_resolver/hickory/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + deps = [ + ":hickory_dns_lib", + "//source/extensions/dynamic_modules/builtin_extensions:hickory_dns_static", + ], +) diff --git a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc new file mode 100644 index 0000000000000..95f4187e79854 --- /dev/null +++ b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc @@ -0,0 +1,258 @@ +// NOLINT(namespace-envoy) + +#include "source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h" + +#include "source/common/network/utility.h" + +namespace Envoy { +namespace Network { + +namespace { + +// The static module name for the Hickory DNS Rust module. +constexpr absl::string_view HickoryModuleName = "hickory_dns_static"; + +std::string serializeConfigToJson( + const envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig& + proto_config) { + std::string json; + auto status = Protobuf::util::MessageToJsonString(proto_config, &json); + ASSERT(status.ok()); + return json; +} + +} // namespace + +// -- HickoryDnsResolverConfig ------------------------------------------------- + +std::shared_ptr HickoryDnsResolverConfig::create( + const envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig& + proto_config) { + auto config = std::shared_ptr(new HickoryDnsResolverConfig()); + + // The Hickory DNS module is statically linked and always available. + auto module_or = + Extensions::DynamicModules::newDynamicModuleByName(HickoryModuleName, /*do_not_close=*/true); + RELEASE_ASSERT(module_or.ok(), std::string(module_or.status().message())); + config->dynamic_module_ = std::move(*module_or); + + // All symbols are guaranteed to be present in the statically linked module. +#define RESOLVE_SYMBOL(field, symbol) \ + { \ + auto fn = config->dynamic_module_->getFunctionPointerfield)>(symbol); \ + RELEASE_ASSERT(fn.ok(), std::string(fn.status().message())); \ + config->field = *fn; \ + } + + RESOLVE_SYMBOL(on_dns_resolver_config_new_, "envoy_dynamic_module_on_dns_resolver_config_new"); + RESOLVE_SYMBOL(on_dns_resolver_config_destroy_, + "envoy_dynamic_module_on_dns_resolver_config_destroy"); + RESOLVE_SYMBOL(on_dns_resolver_new_, "envoy_dynamic_module_on_dns_resolver_new"); + RESOLVE_SYMBOL(on_dns_resolver_destroy_, "envoy_dynamic_module_on_dns_resolver_destroy"); + RESOLVE_SYMBOL(on_dns_resolve_, "envoy_dynamic_module_on_dns_resolve"); + RESOLVE_SYMBOL(on_dns_resolve_cancel_, "envoy_dynamic_module_on_dns_resolve_cancel"); + RESOLVE_SYMBOL(on_dns_resolver_reset_networking_, + "envoy_dynamic_module_on_dns_resolver_reset_networking"); + +#undef RESOLVE_SYMBOL + + const std::string config_json = serializeConfigToJson(proto_config); + + envoy_dynamic_module_type_envoy_buffer name_buf; + name_buf.ptr = HickoryModuleName.data(); + name_buf.length = HickoryModuleName.size(); + + envoy_dynamic_module_type_envoy_buffer config_buf; + config_buf.ptr = config_json.c_str(); + config_buf.length = config_json.size(); + + config->in_module_config_ = config->on_dns_resolver_config_new_( + static_cast(config.get()), name_buf, + config_buf); + RELEASE_ASSERT(config->in_module_config_ != nullptr, + "Hickory DNS module rejected the configuration."); + + return config; +} + +HickoryDnsResolverConfig::~HickoryDnsResolverConfig() { + on_dns_resolver_config_destroy_(in_module_config_); +} + +// -- HickoryPendingResolution ------------------------------------------------- + +HickoryPendingResolution::HickoryPendingResolution(HickoryDnsResolver& parent, + DnsResolver::ResolveCb callback, + uint64_t query_id, const std::string& dns_name) + : callback_(std::move(callback)), query_id_(query_id), dns_name_(dns_name), parent_(parent) {} + +void HickoryPendingResolution::cancel(CancelReason) { + cancelled_ = true; + parent_.config_->on_dns_resolve_cancel_(parent_.resolver_module_ptr_, query_module_ptr_); +} + +// -- HickoryDnsResolver ------------------------------------------------------- + +HickoryDnsResolver::HickoryDnsResolver(HickoryDnsResolverConfigSharedPtr config, + Event::Dispatcher& dispatcher) + : config_(std::move(config)), dispatcher_(dispatcher) { + resolver_module_ptr_ = + config_->on_dns_resolver_new_(config_->in_module_config_, static_cast(this)); + ASSERT(resolver_module_ptr_ != nullptr); +} + +HickoryDnsResolver::~HickoryDnsResolver() { + // Step 1: Set the C++ shutdown flag so the ABI callback called from `Tokio` threads + // will not post to the dispatcher. This provides TSAN-visible synchronization since + // the Rust code is not instrumented by TSAN. + shutting_down_.store(true, std::memory_order_release); + + // Step 2: Destroy the module resolver, which shuts down the `Tokio` runtime and + // blocks until all worker threads have exited. + config_->on_dns_resolver_destroy_(resolver_module_ptr_); + + // Step 3: Free Rust-side query objects for all remaining pending queries, and delete + // the C++ pending resolution objects. Already-cancelled queries have their Rust-side + // objects freed by the cancel() call, so only free non-cancelled ones. + for (auto& [id, pending] : pending_queries_) { + if (!pending->cancelled_) { + // Safe: on_dns_resolve_cancel_ does not dereference the resolver pointer. + config_->on_dns_resolve_cancel_(resolver_module_ptr_, pending->query_module_ptr_); + } + delete pending; + } + pending_queries_.clear(); +} + +envoy_dynamic_module_type_dns_lookup_family +HickoryDnsResolver::toLookupFamily(DnsLookupFamily dns_lookup_family) { + switch (dns_lookup_family) { + case DnsLookupFamily::V4Only: + return envoy_dynamic_module_type_dns_lookup_family_V4Only; + case DnsLookupFamily::V6Only: + return envoy_dynamic_module_type_dns_lookup_family_V6Only; + case DnsLookupFamily::Auto: + return envoy_dynamic_module_type_dns_lookup_family_Auto; + case DnsLookupFamily::V4Preferred: + return envoy_dynamic_module_type_dns_lookup_family_V4Preferred; + case DnsLookupFamily::All: + return envoy_dynamic_module_type_dns_lookup_family_All; + } + PANIC_DUE_TO_CORRUPT_ENUM; +} + +ActiveDnsQuery* HickoryDnsResolver::resolve(const std::string& dns_name, + DnsLookupFamily dns_lookup_family, ResolveCb callback) { + ENVOY_LOG(trace, "resolving [{}] via Hickory DNS", dns_name); + + const uint64_t query_id = next_query_id_++; + auto* pending = new HickoryPendingResolution(*this, std::move(callback), query_id, dns_name); + pending_queries_[query_id] = pending; + + envoy_dynamic_module_type_envoy_buffer name_buf; + name_buf.ptr = dns_name.c_str(); + name_buf.length = dns_name.size(); + + pending->query_module_ptr_ = config_->on_dns_resolve_( + resolver_module_ptr_, name_buf, toLookupFamily(dns_lookup_family), query_id); + ASSERT(pending->query_module_ptr_ != nullptr); + return pending; +} + +void HickoryDnsResolver::resetNetworking() { + config_->on_dns_resolver_reset_networking_(resolver_module_ptr_); +} + +void HickoryDnsResolver::onResolveComplete(uint64_t query_id, + envoy_dynamic_module_type_dns_resolution_status status, + absl::string_view details, + std::list&& response) { + auto it = pending_queries_.find(query_id); + if (it == pending_queries_.end()) { + return; + } + + HickoryPendingResolution* pending = it->second; + pending_queries_.erase(it); + + if (pending->cancelled_) { + ENVOY_LOG(trace, "dropping cancelled query [{}]", pending->dns_name_); + delete pending; + return; + } + + const auto envoy_status = status == envoy_dynamic_module_type_dns_resolution_status_Completed + ? ResolutionStatus::Completed + : ResolutionStatus::Failure; + + ENVOY_LOG(trace, "Hickory DNS resolution complete for [{}]: status={}", pending->dns_name_, + static_cast(envoy_status)); + + // Free the Rust-side query object. The cancel ABI function takes ownership and drops it. + // Calling cancel on an already-completed query is harmless (it only sets the AtomicBool). + config_->on_dns_resolve_cancel_(resolver_module_ptr_, pending->query_module_ptr_); + pending->query_module_ptr_ = nullptr; + + pending->callback_(envoy_status, std::string(details), std::move(response)); + delete pending; +} + +// -- HickoryDnsResolverFactory ------------------------------------------------ + +absl::StatusOr HickoryDnsResolverFactory::createDnsResolver( + Event::Dispatcher& dispatcher, Api::Api&, + const envoy::config::core::v3::TypedExtensionConfig& typed_config) const { + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig proto_config; + RETURN_IF_NOT_OK(Envoy::MessageUtil::unpackTo(typed_config.typed_config(), proto_config)); + + auto config = HickoryDnsResolverConfig::create(proto_config); + return std::make_shared(std::move(config), dispatcher); +} + +REGISTER_FACTORY(HickoryDnsResolverFactory, DnsResolverFactory); + +} // namespace Network +} // namespace Envoy + +// -- ABI Callback Implementation ---------------------------------------------- +// This callback may be called from any thread by the Rust module. It copies all +// buffer data synchronously and posts the results to the Envoy dispatcher thread. +void envoy_dynamic_module_callback_dns_resolve_complete( + envoy_dynamic_module_type_dns_resolver_envoy_ptr resolver_envoy_ptr, uint64_t query_id, + envoy_dynamic_module_type_dns_resolution_status status, + envoy_dynamic_module_type_module_buffer details, + const envoy_dynamic_module_type_dns_address* addresses, size_t num_addresses) { + + // const_cast is safe here: the resolver passed itself as const void* during creation, + // and we need the mutable reference to post to its dispatcher. + auto* resolver = const_cast( + static_cast(resolver_envoy_ptr)); + + // Check the C++ shutdown flag before accessing any resolver state. This provides + // TSAN-visible synchronization for accesses from `Tokio` worker threads. + if (resolver->shutting_down_.load(std::memory_order_acquire)) { + return; + } + + const std::string details_str = (details.ptr != nullptr && details.length > 0) + ? std::string(details.ptr, details.length) + : std::string(); + std::list response; + for (size_t i = 0; i < num_addresses; i++) { + const auto& addr = addresses[i]; + if (addr.address_ptr == nullptr || addr.address_length == 0) { + continue; + } + std::string addr_str(addr.address_ptr, addr.address_length); + auto address = Envoy::Network::Utility::parseInternetAddressAndPortNoThrow(addr_str); + if (address != nullptr) { + response.emplace_back( + Envoy::Network::DnsResponse(address, std::chrono::seconds(addr.ttl_seconds))); + } + } + + resolver->dispatcher_.post([resolver, query_id, status, details = std::move(details_str), + response = std::move(response)]() mutable { + resolver->onResolveComplete(query_id, status, details, std::move(response)); + }); +} diff --git a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h new file mode 100644 index 0000000000000..a69f3c7baeeba --- /dev/null +++ b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include + +#include "envoy/event/dispatcher.h" +#include "envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.pb.h" +#include "envoy/network/dns.h" +#include "envoy/network/dns_resolver.h" +#include "envoy/registry/registry.h" + +#include "source/common/common/logger.h" +#include "source/extensions/dynamic_modules/abi/abi.h" +#include "source/extensions/dynamic_modules/dynamic_modules.h" + +#include "absl/container/flat_hash_map.h" + +namespace Envoy { +namespace Network { + +class HickoryDnsResolver; + +// Function pointer types for the DNS resolver ABI event hooks. +using OnDnsResolverConfigNewType = decltype(&envoy_dynamic_module_on_dns_resolver_config_new); +using OnDnsResolverConfigDestroyType = + decltype(&envoy_dynamic_module_on_dns_resolver_config_destroy); +using OnDnsResolverNewType = decltype(&envoy_dynamic_module_on_dns_resolver_new); +using OnDnsResolverDestroyType = decltype(&envoy_dynamic_module_on_dns_resolver_destroy); +using OnDnsResolveType = decltype(&envoy_dynamic_module_on_dns_resolve); +using OnDnsResolveCancelType = decltype(&envoy_dynamic_module_on_dns_resolve_cancel); +using OnDnsResolverResetNetworkingType = + decltype(&envoy_dynamic_module_on_dns_resolver_reset_networking); + +/** + * Configuration for the Hickory DNS resolver. Loads the dynamic module, resolves ABI function + * pointers, and holds the in-module configuration. Shared across all resolver instances created + * from this config. + */ +class HickoryDnsResolverConfig { +public: + static std::shared_ptr + create(const envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig& + proto_config); + + ~HickoryDnsResolverConfig(); + + // Resolved function pointers from the dynamic module. + OnDnsResolverConfigNewType on_dns_resolver_config_new_ = nullptr; + OnDnsResolverConfigDestroyType on_dns_resolver_config_destroy_ = nullptr; + OnDnsResolverNewType on_dns_resolver_new_ = nullptr; + OnDnsResolverDestroyType on_dns_resolver_destroy_ = nullptr; + OnDnsResolveType on_dns_resolve_ = nullptr; + OnDnsResolveCancelType on_dns_resolve_cancel_ = nullptr; + OnDnsResolverResetNetworkingType on_dns_resolver_reset_networking_ = nullptr; + + // The in-module configuration pointer. + envoy_dynamic_module_type_dns_resolver_config_module_ptr in_module_config_ = nullptr; + +private: + HickoryDnsResolverConfig() = default; + + Extensions::DynamicModules::DynamicModulePtr dynamic_module_; +}; + +using HickoryDnsResolverConfigSharedPtr = std::shared_ptr; + +/** + * Active DNS query that delegates to the in-module query via the ABI. + */ +class HickoryPendingResolution : public ActiveDnsQuery { +public: + HickoryPendingResolution(HickoryDnsResolver& parent, DnsResolver::ResolveCb callback, + uint64_t query_id, const std::string& dns_name); + + // ActiveDnsQuery + void cancel(CancelReason reason) override; + void addTrace(uint8_t) override {} + std::string getTraces() override { return {}; } + + // Set by the parent after the ABI call returns. + envoy_dynamic_module_type_dns_query_module_ptr query_module_ptr_ = nullptr; + + DnsResolver::ResolveCb callback_; + const uint64_t query_id_; + const std::string dns_name_; + bool cancelled_ = false; + HickoryDnsResolver& parent_; +}; + +/** + * DNS resolver implementation that delegates to a Rust module via the Dynamic Modules ABI. + * This is a thread-safe resolver: resolve() is called on the dispatcher thread, and the + * module may deliver results from any thread. The shell posts results back to the correct + * dispatcher thread. + */ +class HickoryDnsResolver : public DnsResolver, protected Logger::Loggable { +public: + HickoryDnsResolver(HickoryDnsResolverConfigSharedPtr config, Event::Dispatcher& dispatcher); + ~HickoryDnsResolver() override; + + // DnsResolver + ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family, + ResolveCb callback) override; + void resetNetworking() override; + + /** + * Called by the ABI callback (from any thread) to deliver resolution results. + * Posts to the dispatcher thread for thread safety. + */ + void onResolveComplete(uint64_t query_id, envoy_dynamic_module_type_dns_resolution_status status, + absl::string_view details, std::list&& response); + +private: + friend class HickoryPendingResolution; + friend void ::envoy_dynamic_module_callback_dns_resolve_complete( + envoy_dynamic_module_type_dns_resolver_envoy_ptr, uint64_t, + envoy_dynamic_module_type_dns_resolution_status, envoy_dynamic_module_type_module_buffer, + const envoy_dynamic_module_type_dns_address*, size_t); + + static envoy_dynamic_module_type_dns_lookup_family + toLookupFamily(DnsLookupFamily dns_lookup_family); + + HickoryDnsResolverConfigSharedPtr config_; + Event::Dispatcher& dispatcher_; + envoy_dynamic_module_type_dns_resolver_module_ptr resolver_module_ptr_; + uint64_t next_query_id_ = 1; + absl::flat_hash_map pending_queries_; + // Checked by the ABI callback from `Tokio` threads to avoid posting to the dispatcher + // after the resolver begins destruction. + std::atomic shutting_down_{false}; +}; + +/** + * Factory for creating HickoryDnsResolver instances. + */ +class HickoryDnsResolverFactory : public DnsResolverFactory, + public Logger::Loggable { +public: + std::string name() const override { return {"envoy.network.dns_resolver.hickory"}; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return ProtobufTypes::MessagePtr{ + new envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig()}; + } + + absl::StatusOr createDnsResolver( + Event::Dispatcher& dispatcher, Api::Api& api, + const envoy::config::core::v3::TypedExtensionConfig& typed_config) const override; +}; + +DECLARE_FACTORY(HickoryDnsResolverFactory); + +} // namespace Network +} // namespace Envoy diff --git a/test/extensions/network/dns_resolver/hickory/BUILD b/test/extensions/network/dns_resolver/hickory/BUILD new file mode 100644 index 0000000000000..1d0c84f48a060 --- /dev/null +++ b/test/extensions/network/dns_resolver/hickory/BUILD @@ -0,0 +1,69 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +# Common deps for linking the Hickory DNS Rust static library. The Rust static library +# includes the full SDK, which references ABI callbacks from all dynamic module extension +# types. These abi_impl deps provide the callback implementations needed at link time. +HICKORY_ABI_IMPL_DEPS = [ + "//source/extensions/access_loggers/dynamic_modules:access_log_lib", + "//source/extensions/bootstrap/dynamic_modules:abi_impl", + "//source/extensions/clusters/dynamic_modules:cluster_lib", + "//source/extensions/dynamic_modules:abi_impl", + "//source/extensions/filters/http/dynamic_modules:abi_impl", + "//source/extensions/filters/listener/dynamic_modules:filter_lib", + "//source/extensions/filters/network/dynamic_modules:filter_lib", + "//source/extensions/filters/udp/dynamic_modules:filter_lib", + "//source/extensions/load_balancing_policies/dynamic_modules:abi_impl", + "//source/extensions/matching/input_matchers/dynamic_modules:matcher_lib", + "//source/extensions/upstreams/http/dynamic_modules:abi_impl", +] + +envoy_extension_cc_test( + name = "hickory_dns_impl_test", + srcs = ["hickory_dns_impl_test.cc"], + extension_names = ["envoy.network.dns_resolver.hickory"], + rbe_pool = "6gig", + tags = [ + # It is a known issue that TSAN detects a false positive in uninstrumented Rust code: + # https://github.com/rust-lang/rust/issues/39608 + # The Rust standard library and Tokio runtime are not compiled with -fsanitize=thread, + # so TSAN cannot see their internal atomic synchronization. Building Rust with + # -Zsanitizer=thread requires nightly and causes symbol conflicts with Bazel's sanitizer, + # and -Zbuild-std is not supported by rules_rust: + # https://github.com/bazelbuild/rules_rust/issues/2068 + # ASAN works without any issue. + "no_tsan", + ], + deps = [ + "//source/extensions/network/dns_resolver/hickory:config", + "//test/mocks/api:api_mocks", + "//test/test_common:test_runtime_lib", + "@envoy_api//envoy/extensions/network/dns_resolver/hickory/v3:pkg_cc_proto", + ] + HICKORY_ABI_IMPL_DEPS, +) + +envoy_extension_cc_test( + name = "hickory_dns_integration_test", + srcs = ["hickory_dns_integration_test.cc"], + extension_names = ["envoy.network.dns_resolver.hickory"], + rbe_pool = "6gig", + tags = [ + # See comment on hickory_dns_impl_test above. + "no_tsan", + ], + deps = [ + "//source/extensions/clusters/dns:dns_cluster_lib", + "//source/extensions/network/dns_resolver/hickory:config", + "//test/integration:http_integration_lib", + ] + HICKORY_ABI_IMPL_DEPS, +) diff --git a/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc new file mode 100644 index 0000000000000..99bf21bf49e0c --- /dev/null +++ b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc @@ -0,0 +1,487 @@ +#include "envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.pb.h" + +#include "source/common/network/dns_resolver/dns_factory_util.h" +#include "source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h" + +#include "test/mocks/api/mocks.h" +#include "test/test_common/test_runtime.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Network { +namespace { + +// These tests exercise the C++ shell of the Hickory DNS resolver extension by directly +// testing the factory, resolver, pending resolution, and ABI callback logic using the +// statically linked Rust module. + +class HickoryDnsImplTest : public testing::Test { +public: + HickoryDnsImplTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + + void + initialize(const envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig& + config = {}) { + envoy::config::core::v3::TypedExtensionConfig typed_dns_resolver_config; + typed_dns_resolver_config.mutable_typed_config()->PackFrom(config); + typed_dns_resolver_config.set_name(std::string("envoy.network.dns_resolver.hickory")); + + Network::DnsResolverFactory& dns_resolver_factory = + createDnsResolverFactoryFromTypedConfig(typed_dns_resolver_config); + auto resolver_or = + dns_resolver_factory.createDnsResolver(*dispatcher_, *api_, typed_dns_resolver_config); + ASSERT_TRUE(resolver_or.ok()) << resolver_or.status().message(); + resolver_ = std::move(*resolver_or); + } + + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + DnsResolverSharedPtr resolver_; + TestScopedRuntime scoped_runtime_; +}; + +TEST_F(HickoryDnsImplTest, FactoryRegistration) { + envoy::config::core::v3::TypedExtensionConfig typed_dns_resolver_config; + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig config; + typed_dns_resolver_config.mutable_typed_config()->PackFrom(config); + typed_dns_resolver_config.set_name(std::string("envoy.network.dns_resolver.hickory")); + + Network::DnsResolverFactory& factory = + createDnsResolverFactoryFromTypedConfig(typed_dns_resolver_config); + EXPECT_EQ(factory.name(), "envoy.network.dns_resolver.hickory"); + EXPECT_NE(factory.createEmptyConfigProto(), nullptr); +} + +TEST_F(HickoryDnsImplTest, CreateResolverDefaultConfig) { + initialize(); + EXPECT_NE(resolver_, nullptr); +} + +TEST_F(HickoryDnsImplTest, CreateResolverWithExplicitNameservers) { + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig config; + auto* resolver_addr = config.add_resolvers(); + auto* sa = resolver_addr->mutable_socket_address(); + sa->set_address("8.8.8.8"); + sa->set_port_value(53); + + initialize(config); + EXPECT_NE(resolver_, nullptr); +} + +TEST_F(HickoryDnsImplTest, CreateResolverWithCustomOptions) { + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig config; + config.mutable_cache_size()->set_value(2048); + config.mutable_num_resolver_threads()->set_value(4); + config.mutable_query_timeout()->set_seconds(10); + config.mutable_query_tries()->set_value(5); + config.set_enable_dnssec(true); + + initialize(config); + EXPECT_NE(resolver_, nullptr); +} + +TEST_F(HickoryDnsImplTest, ResolveLocalhost) { + initialize(); + + bool callback_called = false; + auto* query = + resolver_->resolve("localhost", DnsLookupFamily::All, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, std::list&&) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveLocalhostV4Only) { + initialize(); + + bool callback_called = false; + auto* query = resolver_->resolve("localhost", DnsLookupFamily::V4Only, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, + std::list&& response) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + for (const auto& resp : response) { + EXPECT_NE(resp.addrInfo().address_->ip()->ipv4(), nullptr); + } + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveLocalhostV6Only) { + initialize(); + + bool callback_called = false; + auto* query = resolver_->resolve("localhost", DnsLookupFamily::V6Only, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, + std::list&& response) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + for (const auto& resp : response) { + EXPECT_NE(resp.addrInfo().address_->ip()->ipv6(), nullptr); + } + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveLocalhostAuto) { + initialize(); + + bool callback_called = false; + auto* query = + resolver_->resolve("localhost", DnsLookupFamily::Auto, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, std::list&&) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveLocalhostV4Preferred) { + initialize(); + + bool callback_called = false; + auto* query = + resolver_->resolve("localhost", DnsLookupFamily::V4Preferred, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, std::list&&) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveNonExistentDomain) { + initialize(); + + bool callback_called = false; + auto* query = resolver_->resolve( + "this-domain-does-not-exist-at-all.invalid", DnsLookupFamily::All, + [this, &callback_called](DnsResolver::ResolutionStatus status, absl::string_view, + std::list&& response) { + callback_called = true; + // Non-existent domain should fail or return empty results. + if (status == DnsResolver::ResolutionStatus::Failure) { + EXPECT_TRUE(response.empty()); + } + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, CancelQuery) { + initialize(); + + auto* query = resolver_->resolve( + "localhost", DnsLookupFamily::All, + [](DnsResolver::ResolutionStatus, absl::string_view, std::list&&) { + // Should not be called after cancel. + }); + + EXPECT_NE(query, nullptr); + query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); + + // Resolve a second query to verify the resolver is still functional after cancel. + bool second_callback_called = false; + auto* query2 = resolver_->resolve( + "localhost", DnsLookupFamily::All, + [this, &second_callback_called](DnsResolver::ResolutionStatus status, absl::string_view, + std::list&&) { + second_callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + dispatcher_->exit(); + }); + + EXPECT_NE(query2, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(second_callback_called); +} + +TEST_F(HickoryDnsImplTest, ResetNetworking) { + initialize(); + + // resetNetworking should not crash. It clears the resolver cache. + resolver_->resetNetworking(); + + // Verify the resolver still works after reset. + bool callback_called = false; + resolver_->resolve("localhost", DnsLookupFamily::All, + [this, &callback_called](DnsResolver::ResolutionStatus status, + absl::string_view, std::list&&) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + dispatcher_->exit(); + }); + + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, MultipleSimultaneousQueries) { + initialize(); + + int callbacks_received = 0; + constexpr int num_queries = 5; + + for (int i = 0; i < num_queries; i++) { + resolver_->resolve("localhost", DnsLookupFamily::All, + [this, &callbacks_received](DnsResolver::ResolutionStatus status, + absl::string_view, std::list&&) { + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + callbacks_received++; + if (callbacks_received == num_queries) { + dispatcher_->exit(); + } + }); + } + + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_EQ(callbacks_received, num_queries); +} + +TEST_F(HickoryDnsImplTest, ActiveDnsQueryTracing) { + initialize(); + + auto* query = resolver_->resolve("localhost", DnsLookupFamily::All, + [this](DnsResolver::ResolutionStatus, absl::string_view, + std::list&&) { dispatcher_->exit(); }); + + // The Hickory resolver does not support tracing, so these should be no-ops. + EXPECT_NE(query, nullptr); + query->addTrace(0); + EXPECT_TRUE(query->getTraces().empty()); + + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); +} + +// -- Tests for the ABI callback and internal error paths -- + +TEST_F(HickoryDnsImplTest, OnResolveCompleteWithUnknownQueryId) { + initialize(); + + // Call onResolveComplete with a query ID that does not exist in pending_queries_. + // This exercises the early return on line 164-166. + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + // This should silently return without crashing. + hickory_resolver->onResolveComplete( + 999999, envoy_dynamic_module_type_dns_resolution_status_Completed, "should_be_ignored", {}); +} + +TEST_F(HickoryDnsImplTest, AbiCallbackWithNullAddress) { + initialize(); + + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + envoy_dynamic_module_type_dns_address null_addr; + null_addr.address_ptr = nullptr; + null_addr.address_length = 0; + null_addr.ttl_seconds = 60; + + envoy_dynamic_module_type_module_buffer details_buf; + details_buf.ptr = nullptr; + details_buf.length = 0; + + // Call the ABI callback directly with a null address entry. The query ID does not match + // any pending query, so onResolveComplete will early-return after the post. + envoy_dynamic_module_callback_dns_resolve_complete( + static_cast(hickory_resolver), 0, + envoy_dynamic_module_type_dns_resolution_status_Completed, details_buf, &null_addr, 1); + + // Run the dispatcher to process the posted lambda. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); +} + +TEST_F(HickoryDnsImplTest, AbiCallbackWithEmptyAddress) { + initialize(); + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + // Address with valid pointer but zero length should also be skipped. + const char* dummy = "1.2.3.4:0"; + envoy_dynamic_module_type_dns_address empty_addr; + empty_addr.address_ptr = dummy; + empty_addr.address_length = 0; + empty_addr.ttl_seconds = 60; + + envoy_dynamic_module_type_module_buffer details_buf; + details_buf.ptr = nullptr; + details_buf.length = 0; + + envoy_dynamic_module_callback_dns_resolve_complete( + static_cast(hickory_resolver), 0, + envoy_dynamic_module_type_dns_resolution_status_Completed, details_buf, &empty_addr, 1); + + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); +} + +TEST_F(HickoryDnsImplTest, AbiCallbackWithInvalidAddress) { + initialize(); + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + // An address string that cannot be parsed should be silently skipped. + const std::string bad_addr = "not-an-address"; + envoy_dynamic_module_type_dns_address invalid_addr; + invalid_addr.address_ptr = bad_addr.c_str(); + invalid_addr.address_length = bad_addr.size(); + invalid_addr.ttl_seconds = 60; + + envoy_dynamic_module_type_module_buffer details_buf; + details_buf.ptr = nullptr; + details_buf.length = 0; + + envoy_dynamic_module_callback_dns_resolve_complete( + static_cast(hickory_resolver), 0, + envoy_dynamic_module_type_dns_resolution_status_Completed, details_buf, &invalid_addr, 1); + + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); +} + +TEST_F(HickoryDnsImplTest, CancelledQueryResultIsDropped) { + initialize(); + + // Issue a resolve to a domain that takes some time, then cancel it. + bool callback_called = false; + auto* query = + resolver_->resolve("localhost", DnsLookupFamily::All, + [&callback_called](DnsResolver::ResolutionStatus, absl::string_view, + std::list&&) { callback_called = true; }); + + EXPECT_NE(query, nullptr); + query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); + + // Issue a second query and wait for it to complete. By the time it finishes, the + // first query's async result has also arrived and been dropped by the dispatcher. + bool second_callback_called = false; + resolver_->resolve("localhost", DnsLookupFamily::All, + [this, &second_callback_called](DnsResolver::ResolutionStatus, + absl::string_view, std::list&&) { + second_callback_called = true; + dispatcher_->exit(); + }); + + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(second_callback_called); + EXPECT_FALSE(callback_called); +} + +TEST_F(HickoryDnsImplTest, OnResolveCompleteForCancelledQuery) { + initialize(); + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + // Issue a query and immediately cancel it. The query remains in pending_queries_ with + // cancelled_ set to true. + bool callback_called = false; + auto* query = + resolver_->resolve("localhost", DnsLookupFamily::All, + [&callback_called](DnsResolver::ResolutionStatus, absl::string_view, + std::list&&) { callback_called = true; }); + EXPECT_NE(query, nullptr); + query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); + + // Simulate the `Tokio` task delivering a result for the cancelled query. The query ID + // is 1 (first query issued on a fresh resolver). This exercises the cancelled path in + // onResolveComplete where the result is dropped without invoking the callback. + hickory_resolver->onResolveComplete(1, envoy_dynamic_module_type_dns_resolution_status_Completed, + "resolved", {}); + + EXPECT_FALSE(callback_called); +} + +TEST_F(HickoryDnsImplTest, DestroyWithPendingQueries) { + initialize(); + + // Issue multiple queries and then destroy the resolver without waiting for results. + // This exercises the destructor cleanup path. + for (int i = 0; i < 3; i++) { + resolver_->resolve( + "localhost", DnsLookupFamily::All, + [](DnsResolver::ResolutionStatus, absl::string_view, std::list&&) {}); + } + + // Destroy the resolver immediately. The destructor should cancel all pending queries + // and shut down the `Tokio` runtime cleanly. + resolver_.reset(); +} + +TEST_F(HickoryDnsImplTest, ResolveIpAddressV4) { + initialize(); + + bool callback_called = false; + auto* query = resolver_->resolve( + "127.0.0.1", DnsLookupFamily::All, + [this, &callback_called](DnsResolver::ResolutionStatus status, absl::string_view, + std::list&& response) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + EXPECT_FALSE(response.empty()); + if (!response.empty()) { + EXPECT_NE(response.front().addrInfo().address_->ip()->ipv4(), nullptr); + } + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +TEST_F(HickoryDnsImplTest, ResolveIpAddressV6) { + initialize(); + + bool callback_called = false; + auto* query = resolver_->resolve( + "::1", DnsLookupFamily::All, + [this, &callback_called](DnsResolver::ResolutionStatus status, absl::string_view, + std::list&& response) { + callback_called = true; + EXPECT_EQ(status, DnsResolver::ResolutionStatus::Completed); + EXPECT_FALSE(response.empty()); + if (!response.empty()) { + EXPECT_NE(response.front().addrInfo().address_->ip()->ipv6(), nullptr); + } + dispatcher_->exit(); + }); + + EXPECT_NE(query, nullptr); + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + EXPECT_TRUE(callback_called); +} + +} // namespace +} // namespace Network +} // namespace Envoy diff --git a/test/extensions/network/dns_resolver/hickory/hickory_dns_integration_test.cc b/test/extensions/network/dns_resolver/hickory/hickory_dns_integration_test.cc new file mode 100644 index 0000000000000..b8172c00e35c4 --- /dev/null +++ b/test/extensions/network/dns_resolver/hickory/hickory_dns_integration_test.cc @@ -0,0 +1,67 @@ +#include "envoy/extensions/network/dns_resolver/hickory/v3/hickory_dns_resolver.pb.h" + +#include "test/integration/http_integration.h" + +namespace Envoy { +namespace Network { +namespace { + +class HickoryDnsIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + HickoryDnsIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP2, GetParam()) {} +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, HickoryDnsIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(HickoryDnsIntegrationTest, LogicalDnsWithHickoryResolver) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() == 1, ""); + auto& cluster = *bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster.set_type(envoy::config::cluster::v3::Cluster::LOGICAL_DNS); + cluster.set_dns_lookup_family(envoy::config::cluster::v3::Cluster::ALL); + auto* typed_dns_resolver_config = cluster.mutable_typed_dns_resolver_config(); + typed_dns_resolver_config->set_name("envoy.network.dns_resolver.hickory"); + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig hickory_config; + typed_dns_resolver_config->mutable_typed_config()->PackFrom(hickory_config); + }); + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* route = hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0); + route->mutable_route()->mutable_auto_host_rewrite()->set_value(true); + }); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + +TEST_P(HickoryDnsIntegrationTest, StrictDnsWithHickoryResolver) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() == 1, ""); + auto& cluster = *bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster.set_type(envoy::config::cluster::v3::Cluster::STRICT_DNS); + cluster.set_dns_lookup_family(envoy::config::cluster::v3::Cluster::ALL); + auto* typed_dns_resolver_config = cluster.mutable_typed_dns_resolver_config(); + typed_dns_resolver_config->set_name("envoy.network.dns_resolver.hickory"); + envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig hickory_config; + typed_dns_resolver_config->mutable_typed_config()->PackFrom(hickory_config); + }); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + +} // namespace +} // namespace Network +} // namespace Envoy