From 898d8d8f1b4883933a39ee46fee85e09c5f4ed3b Mon Sep 17 00:00:00 2001 From: Gal Ovadia Date: Wed, 4 Feb 2026 00:23:24 -0500 Subject: [PATCH 1/4] . Signed-off-by: Gal Ovadia . Signed-off-by: Gal Ovadia rename Signed-off-by: Gal Ovadia , Signed-off-by: Gal Ovadia work Signed-off-by: Gal Ovadia Signed-off-by: Gal Ovadia clean Signed-off-by: Gal Ovadia Signed-off-by: Gal Ovadia cleanuP Signed-off-by: Gal Ovadia update to latest sdk Signed-off-by: Gal Ovadia cleanup Signed-off-by: Gal Ovadia cleanup Signed-off-by: Gal Ovadia style match lib.rs Signed-off-by: Gal Ovadia add tests, readme, Signed-off-by: Gal Ovadia clean Signed-off-by: Gal Ovadia Update lib.rs Signed-off-by: Gal Ovadia Update README.md Signed-off-by: Gal Ovadia --- rust/Cargo.lock | 768 ++++++++++++++++++++++- rust/Cargo.toml | 10 +- rust/build.rs | 11 + rust/src/dns_gateway/README.md | 330 ++++++++++ rust/src/dns_gateway/cache_lookup.rs | 81 +++ rust/src/dns_gateway/diagram.png | Bin 0 -> 165656 bytes rust/src/dns_gateway/dns_gateway.proto | 22 + rust/src/dns_gateway/mod.rs | 506 +++++++++++++++ rust/src/dns_gateway/proto.rs | 1 + rust/src/dns_gateway/virtual_ip_cache.rs | 299 +++++++++ rust/src/lib.rs | 59 +- 11 files changed, 2075 insertions(+), 12 deletions(-) create mode 100644 rust/build.rs create mode 100644 rust/src/dns_gateway/README.md create mode 100644 rust/src/dns_gateway/cache_lookup.rs create mode 100644 rust/src/dns_gateway/diagram.png create mode 100644 rust/src/dns_gateway/dns_gateway.proto create mode 100644 rust/src/dns_gateway/mod.rs create mode 100644 rust/src/dns_gateway/proto.rs create mode 100644 rust/src/dns_gateway/virtual_ip_cache.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c3713de..98e1ef7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -17,6 +17,23 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +[[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 = "bindgen" version = "0.70.1" @@ -43,6 +60,12 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cexpr" version = "0.6.0" @@ -69,6 +92,43 @@ dependencies = [ "libloading", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[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,10 +141,22 @@ 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-proxy-dynamic-modules-rust-sdk" version = "0.1.0" -source = "git+https://github.com/envoyproxy/envoy?rev=6d9bb7d9a85d616b220d1f8fe67b61f82bbdb8d3#6d9bb7d9a85d616b220d1f8fe67b61f82bbdb8d3" +source = "git+https://github.com/envoyproxy/envoy?rev=f0e51db62b58196f012f93f20899d86ec81c63e6#f0e51db62b58196f012f93f20899d86ec81c63e6" dependencies = [ "bindgen", "mockall", @@ -94,14 +166,26 @@ dependencies = [ name = "envoy-proxy-dynamic-modules-rust-sdk-examples" version = "0.1.0" dependencies = [ + "dashmap", "envoy-proxy-dynamic-modules-rust-sdk", + "hickory-proto", "matchers", - "rand", + "once_cell", + "parking_lot", + "prost", + "prost-build", + "rand 0.9.1", "serde", "serde_json", "tempfile", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.13" @@ -118,12 +202,78 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -133,7 +283,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -142,6 +292,166 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itertools" version = "0.13.0" @@ -170,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] @@ -179,6 +489,21 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[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.27" @@ -206,6 +531,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + [[package]] name = "mockall" version = "0.13.1" @@ -232,6 +568,12 @@ dependencies = [ "syn", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nom" version = "7.1.3" @@ -248,6 +590,66 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[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 = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -302,6 +704,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -317,14 +771,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -334,7 +809,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -343,7 +827,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", ] [[package]] @@ -400,6 +893,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -438,6 +937,34 @@ 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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "syn" version = "2.0.104" @@ -449,6 +976,17 @@ dependencies = [ "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 = "tempfile" version = "3.20.0" @@ -456,7 +994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -468,12 +1006,126 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -483,6 +1135,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -501,6 +1159,15 @@ dependencies = [ "windows-targets 0.53.2", ] +[[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" @@ -638,6 +1305,35 @@ dependencies = [ "bitflags", ] +[[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.26" @@ -657,3 +1353,57 @@ dependencies = [ "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 = "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", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7fc60b0..bbfd19b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,11 +7,19 @@ repository = "https://github.com/envoyproxy/dynamic-modules-example" [dependencies] # The SDK version must match the Envoy version due to the strict compatibility requirements. -envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "6d9bb7d9a85d616b220d1f8fe67b61f82bbdb8d3" } +envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "f0e51db62b58196f012f93f20899d86ec81c63e6" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rand = "0.9.0" matchers = "0.2.0" +dashmap = "6.1.0" +once_cell = "1.20.2" +hickory-proto = "0.24" +parking_lot = "0.12" +prost = "0.13" + +[build-dependencies] +prost-build = "0.13" [dev-dependencies] tempfile = "3.16.0" diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 0000000..82cf053 --- /dev/null +++ b/rust/build.rs @@ -0,0 +1,11 @@ +fn main() { + let mut config = prost_build::Config::new(); + config.type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]"); + config.field_attribute(".", "#[serde(default)]"); + config + .compile_protos( + &["src/dns_gateway/dns_gateway.proto"], + &["src/dns_gateway/"], + ) + .unwrap(); +} diff --git a/rust/src/dns_gateway/README.md b/rust/src/dns_gateway/README.md new file mode 100644 index 0000000..c2ad25c --- /dev/null +++ b/rust/src/dns_gateway/README.md @@ -0,0 +1,330 @@ +# DNS Gateway + +Envoy dynamic module filters that intercept DNS queries and route TCP connections to external +domains via virtual IP allocation. + +![DNS Gateway diagram](diagram.png) + +## Prerequisites + +Requires iptables/nftables rules to redirect application traffic to Envoy: + +- **DNS**: UDP port 53 redirected to Envoy's DNS listener (e.g. port 15053) +- **TCP**: Outbound connections redirected to Envoy's TCP listener (e.g. port 15001) + +See [`connectivity-iptables`](../../../../../connectivity-iptables) for setup scripts. + +## How it works + +1. **`dns_gateway`** (UDP listener filter) — Intercepts DNS queries. If the queried domain matches + a configured pattern, allocates a virtual IP from a private subnet and responds with an A record. + Caches the mapping from virtual IP to domain and metadata. Non-matching queries pass through. + +2. **`cache_lookup`** (network filter) — On new TCP connections, looks up the destination virtual IP + in the shared cache and sets the resolved domain and metadata as Envoy + [filter state](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/data_sharing_between_filters#primitives) + for use in routing. + +``` + Application + | DNS query: "bucket-1.aws.com" + v + dns_gateway + | matches "*.aws.com", allocates 10.10.0.1, responds with A record + v + Application + | TCP connect to 10.10.0.1:443 + v + cache_lookup + | resolves 10.10.0.1 -> domain="bucket-1.aws.com", metadata.cluster="aws" + v + tcp_proxy + | routes to upstream cluster using filter state + v + External service (bucket-1.aws.com) +``` + +## Filter state + +`cache_lookup` sets the following keys, accessible via `%FILTER_STATE(...)%`: + +| Key | Example | +| ---------------------------------- | -------------------------------- | +| `envoy.dns_gateway.domain` | `bucket-1.aws.com` | +| `envoy.dns_gateway.metadata.` | value from matched domain config | + +Usage in Envoy config: + +- `%FILTER_STATE(envoy.dns_gateway.domain:PLAIN)%` +- `%FILTER_STATE(envoy.dns_gateway.metadata.cluster:PLAIN)%` +- `%FILTER_STATE(envoy.dns_gateway.metadata.auth_token:PLAIN)%` + +## Domain matching + +- **Exact**: `"example.com"` — matches only `example.com` +- **Wildcard**: `"*.aws.com"` — matches any subdomain (e.g. `bucket-1.aws.com`, + `sub.api.aws.com`) but not `aws.com` itself + +## Configuration reference + +### `dns_gateway` + +| Field | Type | Description | +| -------------------- | ------- | ---------------------------------------------------------------- | +| `base_ip` | string | Base IPv4 address for virtual IP allocation (e.g. `"10.10.0.0"`) | +| `prefix_len` | integer | CIDR prefix length (1-32). A `/24` gives 256 IPs. | +| `domains` | array | Domain matchers | +| `domains[].domain` | string | Exact (`"example.com"`) or wildcard (`"*.example.com"`) pattern | +| `domains[].metadata` | object | String key-value pairs exposed via filter state | + +### `cache_lookup` + +No configuration. Use `filter_config: {}`. + +## Manual testing + +End-to-end test with docker-compose. I recommend using a clean linux VM for this. + +Create the following files: + +**docker-compose.yml**: + +```yaml +services: + envoy: + image: + network_mode: host + volumes: + - ./envoy.yaml:/etc/envoy/envoy.yaml + command: ["envoy", "-c", "/etc/envoy/envoy.yaml", "-l", "debug"] + +upstream-1: + image: python:3.12-slim + network_mode: host + volumes: + - ./upstream_1.py:/app/server.py + command: ["python3", "/app/server.py"] + + upstream-2: + image: python:3.12-slim + network_mode: host + volumes: + - ./upstream_2.py:/app/server.py + command: ["python3", "/app/server.py"] +``` + +**upstream_1.py** (port 18001): + +```python +from http.server import HTTPServer, BaseHTTPRequestHandler + +class Handler(BaseHTTPRequestHandler): + def do_CONNECT(self): + print(f"\nCONNECT {self.path}") + for key, value in self.headers.items(): + print(f" {key}: {value}") + + self.send_response(200) + self.end_headers() + + request = self.connection.recv(4096) + + body = f"cluster_1\nCONNECT: {self.path}\n" + for key, value in self.headers.items(): + body += f"{key}: {value}\n" + + resp = f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nX-Upstream: cluster_1\r\nContent-Length: {len(body)}\r\n\r\n{body}" + self.connection.sendall(resp.encode()) + +HTTPServer(("0.0.0.0", 18001), Handler).serve_forever() +``` + +**upstream_2.py** (port 18002): + +```python +from http.server import HTTPServer, BaseHTTPRequestHandler + +class Handler(BaseHTTPRequestHandler): + def do_CONNECT(self): + print(f"\nCONNECT {self.path}") + for key, value in self.headers.items(): + print(f" {key}: {value}") + + self.send_response(200) + self.end_headers() + + request = self.connection.recv(4096) + + body = f"cluster_2\nCONNECT: {self.path}\n" + for key, value in self.headers.items(): + body += f"{key}: {value}\n" + + resp = f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nX-Upstream: cluster_2\r\nContent-Length: {len(body)}\r\n\r\n{body}" + self.connection.sendall(resp.encode()) + +HTTPServer(("0.0.0.0", 18002), Handler).serve_forever() +``` + +**envoy.yaml**: + +```yaml +static_resources: + listeners: + - name: dns_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 15053 + protocol: UDP + listener_filters: + - name: envoy.filters.udp_listener.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.udp.dynamic_modules.v3.DynamicModuleUdpListenerFilter + dynamic_module_config: + name: connectivity_envoy_module + do_not_close: true + filter_name: dns_gateway + filter_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + base_ip: "10.10.0.0" + prefix_len: 24 + domains: + - domain: "*.aws.com" + metadata: + cluster: cluster_1 + auth_token: "abc123" + - domain: "example.com" + metadata: + cluster: cluster_2 + auth_token: "def456" + - name: envoy.filters.udp_listener.dns_filter + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.udp.dns_filter.v3.DnsFilterConfig + stat_prefix: dns_fallback + client_config: + max_pending_lookups: 256 + dns_resolution_config: + resolvers: + - socket_address: + protocol: TCP + address: 172.20.0.10 + port_value: 53 + dns_resolver_options: + no_default_search_domain: true + use_tcp_for_dns_lookups: true + server_config: + inline_dns_table: {} + + - name: tcp_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 15001 + listener_filters: + - name: envoy.filters.listener.original_dst + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst + filter_chains: + - filters: + - name: envoy.filters.network.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.dynamic_modules.v3.DynamicModuleNetworkFilter + dynamic_module_config: + name: connectivity_envoy_module + do_not_close: true + filter_name: cache_lookup + filter_config: {} + # IMPORTANT! Setting an upstream cluster directly in TCP proxy config with FILTER_STATE(...) + # is not supported. Instead, write the value of FILTER_STATE(...) to 'envoy.tcp_proxy.cluster' + - name: envoy.filters.network.set_filter_state + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config + on_new_connection: + - object_key: envoy.tcp_proxy.cluster + format_string: + text_format_source: + inline_string: "%FILTER_STATE(envoy.dns_gateway.metadata.cluster:PLAIN)%" + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: egress + cluster: default + tunneling_config: + hostname: "%FILTER_STATE(envoy.dns_gateway.domain:PLAIN)%" + headers_to_add: + - header: + key: "X-Auth-Token" + value: "%FILTER_STATE(envoy.dns_gateway.metadata.auth_token:PLAIN)%" + + clusters: + - name: cluster_1 + type: STATIC + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 18001 + + - name: cluster_2 + type: STATIC + load_assignment: + cluster_name: cluster_2 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 18002 +``` + +### 2. Start + +```bash +docker-compose up +``` + +### 3. Set up iptables redirect + +```bash +# Redirect DNS (UDP 53) to Envoy's DNS listener +sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:15053 + +# Redirect TCP to virtual IPs (10.10.0.0/24) to Envoy's TCP listener +sudo iptables -t nat -A OUTPUT -p tcp -d 10.10.0.0/24 -j DNAT --to-destination 127.0.0.1:15001 +``` + +### 4. Test + +```bash +# Will allocate sequentially increasing virtual IPs +dig one.s3.aws.com +dig two.s3.aws.com +dig example.com + +# Unmatched domain, will defer to external DNS +dig github.com + +# Will reach cluster_1 +curl http://s3.aws.com./ +# Note that the trailing dot is necessary, Coder might try and append extra parts to the domain because of ndots funkiness + +# Will reach cluster_2 +curl http://example.com./ + +# See logs for upstream-1 and upstream-2 +docker-compose logs upstream-1 +docker-compose logs upstream-2 +``` + +### 5. Clean up iptables + +```bash +sudo iptables -t nat -D OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:15053 +sudo iptables -t nat -D OUTPUT -p tcp -d 10.10.0.0/24 -j DNAT --to-destination 127.0.0.1:15001 +``` diff --git a/rust/src/dns_gateway/cache_lookup.rs b/rust/src/dns_gateway/cache_lookup.rs new file mode 100644 index 0000000..f69bc31 --- /dev/null +++ b/rust/src/dns_gateway/cache_lookup.rs @@ -0,0 +1,81 @@ +//! A cache lookup filter that should be used in conjunction with the DNS gateway filter. +//! +//! The filter looks up the destination virtual IP in the shared cache and sets filter state +//! with the matched domain and metadata for downstream filters. + +use envoy_proxy_dynamic_modules_rust_sdk::*; +use std::net::Ipv4Addr; + +use super::virtual_ip_cache::get_cache; + +/// The filter configuration that implements +/// [`envoy_proxy_dynamic_modules_rust_sdk::NetworkFilterConfig`]. +pub struct CacheLookupFilterConfig; + +impl CacheLookupFilterConfig { + /// Creates a new cache lookup filter configuration. + pub fn new(_config: &[u8]) -> Self { + envoy_log_info!("Filter initialized"); + CacheLookupFilterConfig + } +} + +impl NetworkFilterConfig for CacheLookupFilterConfig { + fn new_network_filter(&self, _envoy: &mut ENF) -> Box> { + Box::new(CacheLookupFilter) + } +} + +/// The cache lookup filter that implements +/// [`envoy_proxy_dynamic_modules_rust_sdk::NetworkFilter`]. +/// +/// Looks up the destination virtual IP in the shared cache and sets filter state +/// with the matched domain and metadata. +struct CacheLookupFilter; + +impl NetworkFilter for CacheLookupFilter { + fn on_new_connection( + &mut self, + envoy_filter: &mut ENF, + ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { + let (ip_str, port) = envoy_filter.get_local_address(); + envoy_log_debug!("New connection, local_address={}:{}", ip_str, port); + + let ip: Ipv4Addr = match ip_str.parse() { + Ok(ip) => ip, + Err(_) => { + envoy_log_warn!("Failed to parse destination IP: {}", ip_str); + return abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue; + } + }; + + let destination = match get_cache().lookup(ip) { + Some(d) => d, + None => { + envoy_log_warn!("No destination found for virtual IP: {} (cache miss)", ip); + return abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue; + } + }; + + envoy_filter + .set_filter_state_bytes(b"envoy.dns_gateway.domain", destination.domain().as_bytes()); + for (key, value) in destination.metadata() { + envoy_filter.set_filter_state_bytes( + format!("envoy.dns_gateway.metadata.{}", key).as_bytes(), + value.as_bytes(), + ); + } + + abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_config_creation() { + let _config = CacheLookupFilterConfig::new(b""); + } +} diff --git a/rust/src/dns_gateway/diagram.png b/rust/src/dns_gateway/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b2aa7900e31a6c9370494e289c498e55f258c3c4 GIT binary patch literal 165656 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 =@7RUnt=;=|r6)G}tlC!By-QCKFR#F#LGZ zM>a-USwH1rToCe;?5Hy-Gm%)StYPyb{c<%l5w|Yw99lf&btAafguCY9b>hyjy%H05 z11bq~I=kkdnh8*u!Ny0oZVra!4r3w-ZB~)Y1vuU!yE(3CIfU^sjlej&*zv6lK`FsN zBSRBKR`c=Y3?;#%ungbOI0{Zmfp{O~ZVvYW=YlB3)F)O)BGaMM0K?PI8vA@Y=jr&F z-eCvq4UG?lY@k}ANO2f9rS{_r>DM&R;-^{OD1WrbrmvIlov%e?;1v$uZfeP2}dje{Qkuu)Xc2Uyh4mpHx~@dL;a zd_N-O)w}whu?^IqrF(1^y|_+UECFbYTSQLt=)<~6qEPREW4VHCP+9Z7zjP>hmEa#C zNgD0aL*iIYg;c%GN$oOBrln&$UhaOx*+SpU_1B%3P&rdxJeOVmPR|5}E88 z4XT6&K}tjv%n0`5tB9e_6QMCqyRVOELBYB#7ssbKyhWvy?$M-?i^j^amgV`%Ofw}N z5%+zT9VR97QR6|R9N$V#lf4KnKD!Yd=2?aP7k$q5b6wY!1lz47`|U+Xlq99_1?LVv z7amQ!hGN_#8P71Sfmkg`0(qA zNK0pmjJxqFB4Mtbpv;kOU=82FQDBbERBdBuZ0hF>CRLMW?T2*VQT$33nZ{o2#+fM? zKD0!mp!0|bw}dxW?zv-0>-B{iEaUREnQgay5w%Tm}bwkJ}mKURh` z%qdI2ZbnK~dcW(HARMXFuy`sB34BTg00US6l5VOOfK8I&z<={OC|&-+nL_;dm7Lw$ zoi3J3ixla8_=>DOWYrV3n5ky5bDEVKz%O(tqcCJ2uWgs6BAAx5_7zCDsQ4wWG6&{p zg>R(#8D>@JSZW*W0sz52?1$cJl|&^sWNl-(=JBKvBDI}*uN*ZUxiXZxO5~LE%T%x( zt#-WEg(_y}Dtz(Vf;=1#ZmcU3$#GRfki)JzLjhf2HS00QTpWsp*K6SWBaANQCshO) zIg}i-t^d{5`u%MHgyAR`8EtMgNwMA{baa0cJsx3j=8#3CCpYxL^d64=95PW-2+U8o zqUdFP-W#OCtBG|;{Ug-H(#9rQ-?4xCMd&4V$ZkRy)74W}xoas?>N}(it(v{8fd}*C z70GF(b#o2eG3~`^C9g-5MwrI70-Ihu@OAo{APFriFJ4WMy+)xZf}A0Bge&3US{(UeL>&;j5Sxg-1+7hpopzlx4&iKV!SO4J zeoYp|k*FKVRzYAaVKQx5pwyGtaB$xUbLHY)u<%10+xw?SDPs+`c{r|~Eu^Re0#>~k zYsOF!BJLsG3sa)8}Iy0^g)E^KFa8CTCOXmrn3l{AwTaDAv4#i z zC-dd3pKEAlWW9IHG^a|U?I6V%EdUEmmt^_f(PM9Y0joL8pF#+q;;=~eDGkjMpN&d4 zk*ux~|5IqefyAWbD(3osXzU2{i%q51rF%zu$t;L9$w z?8bsBgY|O1KQW*z-~3o?;5fhoidHAA@M&1S@54;IT@Nq^quf~qt~%X63pW`4uug;E z@lOER?bZIQsVl+&=lx>ryF=M6;P@wW@z*zo6~J4)D`KrXQ_j2d)1Pw-aN-R%P9zDR*J|>uuWjloyIW zwSW*=&;qg~R^c^}PX4^u`s7*^)((SJOP)QN1L5Wfz;WuN9dkCUe@=sail4(d-=)Lf zl%_{)+muCiGpMiG%G(eIp!0x1908DRYbTBZvGcfEGapxDQrivH{>gVckfY6k(4uEk z(S?jb$PI`@mWK6Qz!F~qX6E|l$;H`C%Rx60_`f}w%8%BL){|*#5TW`35%zN>*|4_5 zDj?k_tEE;cxxn)VkdP-nn$ibOL|-;krA5UCX)o-PrXwCCnHH{sUVR zv61Ee9(ME{A|9{ziJBMZL%s&zAEq6GMglJy(_-I3{iGF3O{VJ*2f zO`AC}yao_Wlw)f}S^Q?+Yrqi_$xW!W0*q!*_;f~liy7eM+AHWpyd}lR^QbK_Q1iHU z-scy`(L+EIPnjimT`t3T71qRQOYXvrV{F3el?j0M%am~k0rW3JDHMR0%kM%+8*Z+D zVINzh+w#syEv=T;zfJZ)=LXzbt$8PMUWDlN&9$%etfwJ{$4S!PCfBA7+q}#kI?A`3 z*WZOgEZ0fdEH{i6;;{4uAcnEtBcK$Rq~XJNi7)hlFce5Ku`&&)bpSELP01zDOQf1T^+ejWt(~GxoN4-+BbMu- zESSX|P$cOk6Uyc)Pk0<(C*b`iV!QA)s4{z4jt|}^J>LZQ{G()CWj+dqHD91h7&#WS znuJ{RFVP+}CoWBpF!{#md;D~6to%a^VYE$?qbG9GB4}~D&&9H_$+aH$WElmZRnFx2 ziKFn65Dc3ZN04pZMO_J<6}=dVmTi1xY7FNqd9?47=7;Cb2d@`tmX6b-^fStC#~)G5YC4;%5liUjgDi=OgD_VW?h;9>G}7KWvWA%z~n^Q-%+nZ zxLpFdH5t@KqOV3oNs^3fR#=6f*dA5V);Yo|f{lXgmZq;FX1q^L$jlbn{CC)k0b$?C z&AHZ0V!rZcFn+EGpop$E66M=`*WMqFq3s;s*fBPllDTb}`1b zp@Rqvng-mQu?rhJW4qO5oC524JC@IVS7>>}Czb?<^G`lMnQYoSo-Yf|pJSe^r`y)* zaMy!aYW3Y*DVdZ4V1m6n8DBMZOY>4hKKTN^>jfs)Am58@m0&bQj;;qBkqt@WJM0o` zeFINxLiK)-N&tJO`(mWn>*$!tIE2MI5+(pY4 zdO-T4ckI9kR8|vU;q=|rE2|D>qvSa!m|Twzflroc$kwO_74WRnm=ej9u_F^SoAJQ1PrbgDGy3SDi(l_C_FL4w)qMG#MN|oIbc4V&o8O_bGgW1$YDs`HrMUG8ty<6 zxmeHJwg*0D$RY4cLAFntD{$I<%<<_>fN#p(&+k0zoKbS3GcVV$M6+biCaz6=dh1XO?f9pfmol+TRlUuDCV{q~J}-K!}2u97oz)hoNS+);X)>L*Fx#1O*xL(n9289!b~?&bA0vWIaAQLfKsD`n^ar{ zMujq7%mS&A$AUk~#6t@@AaB`bxu+jF6io6^zBD5ROXQ545-Mb(?6EwR=acwMbEB|0 zgMD|7q)RoLxUZFyoJ@3{(ABPSr#4jQMI;YnJoEQe@sgtYWY?+)DLx`1J<2nOLh}i& zyO!azpX+GJ-i^Auqfxq;9Jbg01Z0m%uR-@Xi|;`fUW_=(nwxqS>Y8iRyy;FU;8bep zf?O=SCP-Kr&z&XTrIH!hiiS?-Nk@Je2_hrEbce3A$aWt!zQCpVhWs6k_!uHG0EABN z>+eF8Is2#yQiEn@-yw5aWY{AY^seg9(PA7`iu-4N)OVfb(Ug6|Qa9rOQxNW~k?8-J zHwU_E1oylGOP~k_s?$4|YB; zMe)l&h~a~!C(Pnc5(?skwwh0-i_tsU2<=~k!cxk^05~d$?ri8MZo5@Z`^0IcjqkDT zku$01-)frQ7fx$imvJ)(Dnyd%VNZ$8(vt7+L6o8(_w`^zjanbh54X{2ze4w4J9T-( zpX=n0qDbH2Xd1*JWftaT%_etXiFys^h~szdvmo5;r!u6gc6XWsW|;YzKKj(TD?rIE zfv$yrvoP;WGiQ{VRZzdeS=Y?WJo($#60VZkw%vQ#CY|5#-IKGw?k{#6>AorK2}VUE z^QrX(nw%+KF(yNh`v35}=aUujUHgnSSkK{XncnHyw>CP&$~IqhACMQWnmrI}*!Yls zDD|ZN*_iG1)VTs%bEisJU523+i0f4Ahl1PX9U!^PH2yQ=O``xH4m!B>1ImL+;yb&!iOGo>_&TJ7 zVNM3}#S9rF_MO;8iF-gPq`+K(gsTlSUwOr#c;TsW7IV`MjFz83`|BSx?~1~ zC`YFZNbG`jl@6ER6Rjm^tx{6FyXAYO0AFsIdRG6Z7m5`))@hJZ9;4S#;TK8e zwYmD)J9t1O0t_!@21SVd$qQtoXm+Rd;c-ZxU-!F!ZT$z$5$|I7XVt=X+=>#pkizj< znTqgmgNqcVBxv=ha@ACDg?gZGUNVl{w|5TJq(4L5ij|hHqhB58Do%jPl@Bz{0tFay zW?6;a+}E=d>E!FxrQ}*7xkDfld?nLtJn66)gx5Vgof#JOT+PsGU`0^AzKOR_!1F~> z=bP9p4XVLLGm4c%d38sEj#KyzxH0W#Y`SW@>)4FOi3*Rfj_C55#)F{v<&Jp5r@-u( z8WG-WM&(2_grh=py#XKw3JX0SP$oQt`HB=*EPQr9sJW&MzlqF>;v!vO<16&NQ1>Kg zAh(rIetpxyTQVUx_Er-;`{7EUhKXqheQ3UTG|a7X*^gD1$f6;$5VKvc{9{A3g*aEdM7P6bs&BzxC!CH^YhukXJQ4Au3e?aV*-9ji zUrCzsKA$`cTX_9IZV!_&l~mOKb>chRm+=OXEyQqgF+PIG74z{iHzjilH@>eoPKi z|B*V=8WomL*LRyLrFekOlbgwC)IR;9^-;Qt#ZCTFyWdN$8hBCjI5Dw?7w5K`! zaY9+qX`AP4J_q-Z+7tHg7eR@iRcchB3Alw%=ZCWavcP={RLtH<2eD#~Lki4YRS_{- z);LcZ=;iXT1Mdf`bb*xV^Tc!Oca2gsSkD@~`nksxKWD@*XE{nsQ0uz1UY*>t3nym1 z3ldUToLV`2T5Oq!74@wLpdQb@V^xw%AE~v10?t*=dJt*u&HbyGFycFhyVf%K$X5x% z$alGCOyiX{yG_kjbqZc6&V@f=c=;h&ndUL9M zjAOtxoE|UlwnB?{S5kL5Ry=fgaM)$9TEpP4gx{7~{?stn0FP&#s(mZ( zg^9LGADySjP*OcC{aC%)OFiSZP%iPVKh_UlP?CmqF;zSKqpnBk{kHnFNoGmv&1Vl@ zi_*{XzXCJ+beIy(QsnL>3k?@K8A!r8&YSZ6raY#NUmRql!xKnMaOu?1|FXQ>&Y~U> zdsA5SaG;Se8<9t+S8{_hlefm);J!xb&L*fip*=HRM+4S~gyW{+_U8>@Dp*Mme4)y2 zd)m!17*UTj6gi~a>uNKi{i=j&@e|>eeS{A3;!JIABpBf%*#te_beH~kWv=_pd^dg} z+m2%Xp8GHf{|R-4-dbkaq`A3v_coZ5<**mtxNL7*-A>m=yS`Hg9N}WPG(pf^5hjs) zF}WfLg|sWP5bXRB*$*Tv*xt+|@KBepX<|6Io6pNI`4oH}TMkUtcYdmTMZ%N)^eXC0 zZj0w-)et1CW&9{Utno4#!!?b*0A7u^`fvY4Jq#1a{MqoJM;_zDkW!dG8Y0M*tDBbE z=@=J864dY6RZnx7RwSd@F+!;x9k0Of^TS!yoy8QJHc7=|irumg3R1o2ScZ1)@CEx8dXdPRx-l09j!sakt}-1iCx zX)+6j+{@H`;q-l^h{62!c=w_XFT1;6bj=c1^joZHT1}X8ER|`v1kDYk%j-ToA%ZkI zLqV5;?06OW=oXcgwh?_*{nrWru6^5iCvGMaJ)9#qCqI_{yk6)9{c<=96O>aWc6HUV zX>KE&#q*_V7Et6au{QG57#D5yJSLlrkeuV@becXM3s(^yg=Y|;I1m!n?%f^A(y;-F zq^9|>1zk;}3^y7_Cc-0bVwCX59w%d;W3J+d=0@<)rzB3#ERKL$+&>&L9$F7Q>pCA6E1FE1%ms% zu~cg46ajuP$W$mZfaA&HaVvS(Vz^qbW@v$0pfD6@f73O;r^3K_(WN?Wqfo$S=!E zzX^iaB7X-tPBwxWwV@<2+73$HrLNeplisw%D`YWjzdINn9#)XV)a50*In*{@0R#AC zr? z-}nClT0#;^=_C5660V${nm_B*L_+-fq1mE%rpzcTY3L!m7f!pz8@#*4>yKN{mB#5t zqcm+!=0BHr6Ix5ceG)l+lN&q`am;o%Rit&_j|GRf=rm#wq4x!gd%7ZL+L|Y`*{;0u z1=y;1?dK$Dl%CHotG>1)yoGbc`x$Q>QpA%O$|u>q3S!K8Qobj^C;bsM2yyY*Zf+X7?dNV)JYonh7~rcJ zy-3xoq*YD}ddHEYw+y;L?%#<(mga>W@h>e7X1ZAlTmF0n=D1|_LD%wx0tiJlk~n_)i;Zh}m$!$b_*2c#Mbc8^@} zAYNXSYEsw`$0;N2Y$#%I(Kc@1^Dku$P6?~&$gI#hu}OT9CyFfxhI(Bcdh4Ll7|6Jo zrnM1TmU&2Q&Hr+kJl|zd+^oF-7c;@_-dT#wk+PP}4!yH=d5exiA59>m1UYNGUDP8L zd7l=;&5;)Ss}CWwF5g)Uaetl*eC|?4{w_;Dy;vP3fz2p-_ZLtrVT-VA+nJ?#DFzEy zX@|SN71yA&$d0&S3w!@1x`ESOL@*SiHcC^fzO9m_)wZIAklh zXJplBMv8j@n5%Amzj9R_{={hZAFl+OovInnKHU{$z zMSPB1TquMk|9$y|)V@3cN7``4%npSc{ElzG#akvPFu7rpOmqxXcaBTCD@K%D*hPs zdy1Gx`{qkideEIm+T@U$zO($}A0NdDJ&}PX#PD#K-$jKB3WStB6vq|ysCOPd9$08_c#}L_+!y9!Vv=CjMtH`4d zV9}9Yf*vrDGxG(!qD{y-+{|w*P>R$RN4&mj=_pd5eD}Lqp)^#A1 z*s1RWLb&Oa5TuDu&t8?qeZv@`BPU*wQ_j}(IG8-D6a@;0!7cKjP?D3lrz}oVQ8a&_ zs%I@WB~g~ua~a#Jv2?r~RmYrRPjh#|pb}FDt;B^dX57#`Rx2P~ry`Lyl8qoakRz3S zFO#QVy~rz6mA6L(l_#Sdl8l1gQM~pzMP${iW13UIq29CoqYIuEhXuPLU=;Y^KuDMRx7LpBHpnzWPu?ncKKM9qm^ zT#69B^TQ#9KL-7+7V_ZuB~4B3A^zu*1)>gdUwXgZ&-6<#W8cKOH-EXy6m$9BRVjv&>!PSXb130@bxGL}8 z5cRj&shBSAaAQ-cVRZZ-xb7bm`#*m5p8y$EdYJa`?O6bJ7#1?IeAyY> zc$;BD0B4sjZqxs#F%#fs-!Fdh@!t~J|K_eQF#t9@@!I9Ve;Q*AZf1Msk&*E?3iaP+ zv^p3R4%U2v;s0q254c&akFfGBefF0Y;GZt`Tmn#RJQ*ZX|3Psvod#~E+e%7zEARWK zEBy7Er7&2Na4?GU-`(Y}F}6zJX8$iH^?zpce`fSQZ;}5qqyK+}^v(Am*0!#nZC+8@^B4OX#*MzsO zy*jE5oH*kR6dYurLgQF|prKgNL+a^dcLqq>^>3}$Jhq=e2Le?Tp9x^=*9}B#bDL_cVUZy8rt7yx~}^EcP0b(1Fdoc6)=mL!b>KGkQC zf6ep?VCWa)8Y(MhDh~|+sSgu_c{;3w_mO;#yY9I8IeF8H1gYlOu zU_lNL?lH&Q2c21n$LqcT_0iGL)dn~k3V4dB7&)3*k1Ud)S=685Hrr3w>1*zs~KvNgvD8tllAfID~qzQC%lN=tasTK}Y zA1!2Bw=1EOWVu7I?$DF5m@E?L+&`~u74+A4qEq+3s^F{Sf1D(N6^*QKwKCocaJI-A z2NQ5mLoE;v3kddo0b0PC{V(@=R-C`-gBodQrF8e9YBQDbCD;-=rJBlzzmRYhRljqE zyRV*1%+;7@P=L{FR-h5j#k*jz>VNQDp^~?gu@3UU(=88uqTReD)FhpMiF1;cIXfMYXmt0C5HRQh=^; z0BGFAJI97#?fyG;uwP;(28P-##w|M^g!f z#H0B{(6<|ZBqS3_C}rh7Q<^>%2|Wid^~RN-Bfl)3Xzu;?^C;G)_1w4n>OINz8Hi@; z-|D8!i5^@QvSHzsN;=3zdC8t{6a0N(Wv?0iKNZ=E^+Jr&|)U}o1Hfx>tDV9;973sAyUILzD6SKj~A5f)C-VvYH^PIf1?x98-DAq?CXlZ5SU zszV@L#GvedvtK|RawfSjQTkgPf7j_27omnbA$Tj>lj`^rx|iV}xFQ_uM8 z7W%%`qD0r}hmZ}J6z76Jn8oxA)`~otHt)IDoXN{=WJBRc-vqQpD>hT_EXfoe`Ml() zFi36z*#PhkdGqFp@}3OvE=E zXvW=({w2R8A;@2UoS>onj^YR%1>K|2*?w^XxQZx0gA+UV654X$RQ6-x=^!G2dxV`< z;|Gs8y=?D50y{=XSnxd%|8D}t=p~T0Tmx{l{IKc>f=99a6egW=|Mmv~?1$7K4=km) zGvc=6#b}!Ph1E7sdW?*C)L1SNa>#U zo~PM>99`y{0{Ip8QBJrmw0z&r_R30iX-XKu*w-*?6o|oC*WiQ}nHMDxZZQvb^vm8H zpsUyd2=tU2d8#Woo}x6xD8@v{q%#z-ihDqZHW+Y^HT0cQaxfP@0=4SnIc5Uxm%~7A z_4QSWI%_sKbg6-(4w)&W0})gBco^CHkOx^I7Nc|}Nw-W~dr5cOpWR1ic@G>sqmZ^G!ZKEFyyybXz9`F5ni9L&yz9^DpqIcsS7Jsb$H3Yw#V z4Aum>`N;Q1iRbIn%vyZ_EmFbszO);Ssf(08pyl|oNAy~p)>DtNx)k)n0cqDEP&}(- z*FYFTh$p^6Mz+AA7`BpD%-@(8UHIL%1!J1{c^>E!g*kpmHK&ww9{?OXXrTfUze>(H zPX6sq0BrO3u1t2pQ8ic9D>MkZ?2jyiwU_P?2C@q%=_GHPXLHGhG{HfpkcWCi>-m74 z!(HnorMIt+=ik$weW}Wob(BUlhSxCvJrV`kpAz)kK_SF6Oa@;|7&EE0n0Wj5Z zZEhP^-ymuvf493<$H5_(;eRy3HaN`~a^UPuqgG1hNx*Ib89_XW>kP+CACoIoVLCWQ zDUDHjZKbr^>{@H;84z!GV#_3$a4W?ixXydKlD<=PrAdLn&a6Np|3X*K0)6be@nDiW^KwTbf{V0+6 zFTDa{jOnu6Ox#m{UUCmxC{J#=$)gEHTg8d;I|#(JfZ4vYc!48YXTF-0scPhFk{8MQ zvFQL6$f5)x(XwY42JS0~CDr?`Pv~#He)37we-=^@)f z{R56@qLoMO{cE6A!53%%`}CCcuZiZ*qM8T|_S9C`AyV(j((=4v$L!RY(BB_+WIAB3 zeO{V@pLXtmq56595#`)9K4QA`XtpC2dC`(*Af(;6M<^N>)SJeLc{~FC$-;DHhL!@`6(H z+fLp5;8Pvvg9oW_q{)e`_>as6V8YB5(B1tQwKSt~qjPk5rc%Pi5C|D8x?CS_r!L~) z!rN$=Q3svvAqHwVw%NABQt`x>Z5|g1mkoH?I_GfC#Z`rZ*O=l9u<>%vyS}cXnzrHE%JQ#c+ zzWZcYRrl@~a~|0^l?$bhc_K|C=h+MM%it{LE`H1Sh98`$CR|O@y^k%J>CPzQT-t3< zCJT`fxkxmO(?HKEF6%fau#N5^Q)6e9>ZBx)MmVNVwk+R}w}*D(+C^RJw0>!_wULHc zMvj3f&v`}&+$+u_doY6F0SDYJ+9yjI0ho`#_{exAl5XoN4R^C{hDh=cPJ!T zHux%q)5IB;=5!U&JuPt^UKlSKAx!6fIuQj&hi|LscHHt|WI+St5o?b8Z_yfrr$TTdklghXq zg^9a~C&Wy;PY^FygYkg=RoBypMj@C5eH{ za<&&yv}VQ4Y(2%Q&woMRZuZcr;p78=|E1V_Lm$+cYQ^F~$zM%FH&gs-{_t<@##xE=t&J1fVeY*N4J+o+53n6{y z1q!`6S*0^&-uG$W!d4fjl~~EL3EYQs+Zzgu4 zjMww5gcd^(nvTYEn_yAh>vQW=Bx{nOry+`ozuF|o<@~iuAx`sq%IxA`ZjB+9CAp%= ze8_GUVvm*HFP|Ew_EzJ9gwNG{-WDqE)j^w<}25Rq4}xJoEI{4V?-0M!!o_N&@|o0l1AD&PEluc|6Hj!r6%SYgz4@= z$`rllKh?&?tltYXdO`Lg>#~c%?On9BpGwWeYW_}_V>|Y>D&D?u!G6EBcqZj;_wLT} zi8FhA-IR>{+!0|=8geZ4`0t9u2&~w7e9Hj)jrVew zLeaUivB=+0S7?YzRV;-wl+obE;&lm4)toyuCK|aT;J6PdT|S0utm6~BV1wkc7FT8p zVU_JxMh3GhKl;wlYrOd(TQF6hs>9Gn(awG=#^{EqxDcZ_$Ljq7D%0d_1;Nl5VYR69 znJ%;KQJ$}NKm!tI!c~(U6zfLRk)3KxTjkQgU3s#UTmP%Y;sWyu+f6gklI~j(Dycrs z-^U|H8N58(P1ga%Y?h6=OZL6rPhK^` zOAWpM3wr@viWEITIL_{PLEkC{U7+|Z*3=vbj3W%_*iL9fE$-(2X4pH+;O|*Di-Tjs=P((wZ}(2)cr)A@ekt#xg-?DJ`XlqNX}aH_Y^O8&jbPe z*GbQwf{$WQ*KMf}S9Ly26cx#eG@VMhYDlELLDD(OcXddeUh9K+)0F{DaXbg7V%94c zs$L{sP_|GVUd$Y+1SHl!7l{)1RTf+@l-pHNd@fAyjc!bYu?m8&h%2RZW^T zK#-ok(;#)0J|VzPo4p#nW9*bq_pQD|%C1R7@`B~!n~Y! zvJ|4np0~bP8Vl=6%-vCs+uL>xV%J&fDt>F(J?7FpdOx;GKWXgyR;R2FK3zlKK9*QB zPwG92I&>Ah9p4U4YxKbFlrpm-v-`xt`&S1|r@yw2tq0 zJ@uT!_!4d{NFOtWvnre_ErMGtelk3GCOj%5ug)$0>@8K6!^w!1L^8h)M@utI>tDJq z`HZ_F)8bj&gESm|UV*dP4@OKo=a$a)aQpNz(*68F5>TSjs$8wU+j@x6_BA^~N{l7Y z4^!DrX+wn|H#LIv=i0?u{eJt9W>to9rp{uXxc8LQCrL)ADcvn>%GVPq>quucXd$=T z?9e>H!@yqPZX1h^FR{uhy>m&JzKP<7J&%jM2|ba!i~G)JZ`x&-&dhk>0Un#_QO6bc1~QPmo{FTF z`v;Klmtv7I;M_@R*W<^Zbq>GPZTKmlOEN<{X6O7*wut;^&s@+#vY5i$HdP94#~YX` zzZ=qmGA<9@9Kq8JbkFad-LAmpKclSiVkqyanz3-w%Caf3q31zGg0?*n#)wyN9aQ`N;eDiJnq-BGX z>318YIXug5Cq7K^nMO2OBLy6^#V=fD+(~-gJ&C~mENbakG3UI%uGQRAtVpVlVaP?a z{^h+E%gXVB_Qh-9MZU>lERF(TGK?_q^wLDzbf2zW|I1p$3RK>uaj*M_u8ie8)m*L8 zycvrC1*4uGj31ffs;P}2=Pr0=q^@krcCUT$Zmq^(Ds_o3*fh%*DD+2AnMV6uq6(Kv z+5!|HVgDvj1^J)#`sq$SN^IvQxza78N>Y&0lnL^!bk`?J&Mo(u?cZQ!e1b2sUfSNAf20)v- zjg0pJ7-ugQzV(_dUpY*XQqka)^L!3ItyJobCAY?;tSCoKW(qbj4<1&MiS~;Nx}(1h zVfbonZv9f0^0+m3HQ?ek=Q{7;L|04G(KXob35zIi~qCYXYOY-gC17U*Lt;tgUa->wJ1oFBz22_Q{9# z{)sf-x#r@jDEqE6%Y$Pc)IKi>d-(6Z!(i9NyD;1$0cK1-HH{1GK4t4qZtexBI&Yby z2QJNZb_{GhxKGHaWN{p<1bqNmghcGe(AhuKd4?kv9dklKCsUFxi9J&nQr{pMC3KC1 z2ZvbTkOYh9Ob8^~`1BRguWv|^7%YIli#o1oQGr_gN!-r-{9)e6xv=jSSY=uZLi3Y6 z6e97|DFhz-jXodR3cQ+3`q}fm^NM;Zr6TX^ZLn*)_-!z3#I%tj-yfHr|NhC4l-+WCGOIg<_LwT zGr;WfmzC_9-bpl3kjSV`i0Mp!4p@!O4W$XYA(YV>Bq6}SCE*l<|Bxb+mb|=N)!O_% z*OKe13I6F*w5nvuF=Js|T~lG39eQ_~LLTP)%JIMb_@c^^b&s(elXA>bpM;ZF6`*rw zsx=gDI>E2hqw!e?OihV8MlrIxjREHuqrVSIC#)X+P`oS`HnS2zHeE zRmjxhI)VA%(xXSlMyXDii(gJY-cuSE5>bz zc=%ISj3w@0V$A>YVgN_)jv>?Oce9u2OL|q#KYaUMQKrp^LXvY_^J|bI^MZxh5`hZyR%}Ret~4uH$dX*KaV1fb#8=eZ2h?vcstLz| zjL9aO(0vGNL-vK@^fDWNy@Pys8qQNl!NkoR>~WMWqt8ure-5e4U#)rn7R~aROO7=I zr=)pOBUZYIf8@BK_g8jF6WQ>sPC+Pgk6Js0&OT-Op{HL0+r8t=EBhOrbNs|KxU60i z*iF~w;}cv}j9rWb$aP2)<@0n!eFz8&7RQIz+1sWU8s>>3?z_=`X06cD@Ogc`w?9+9 ztB3N+uaSbr&H7aNSWi2JQnZmf&-JiAoKCo4lt%fPe%W}aq9Vj$WRyNeQgFYkgu^mu z{$?KUi@{E{BlL&PpPv<~&F68xqhNFYlAsf$o zs|czXyy}7Y2Xx$SLZ`lF%%OZQUU*R#3wt0=`pk*vPyIh12uYYW$x#!{Ozk!i5>Rsp&44Q z=e>fsnc#o|*IJoxyM+F(mu=Tw-wQQ?Ii}Vw>3i@ZASA}bPpV7%S4Fc*Mhl`C!Wo-F z@oc5K672{l|d6`xvb!(TKy*`z9HWlt%t9iS+J%xb~`Qo}w`6 zH4tGhD~p<4d?$km86(9zIDwgL`S#RW&vtrFMC<+r_YC<>02i%jgmE{WIuR9pimgBp zKhr)BaSD$>El!6P&Q%FTh+UE1_q z7wHE>jys!+18J=ho((Wrb--I$NCQX+3>7#)_zm5Ll1tC7e+}p^GFsi}9g#8phV=nzlTRxW-FN2|BMVEQIZr!1AdC&Ss+Uif5SR}{Jj_;eVjn(^@f zCQ_!Jf$tq;zr(L+h9CWrtwVJoHYNhk=#?TKDx2+9Sam z!Ik2R&PmWXVmXWI9{g(U20jw8RVw%)`JrkbHGPP1&t#c_4{4Yq>DDtmg4%{CMc=n9#WjH- zp$aB=3|yCM#U7^}GZzrjf|%V?R|)kJ{Wmcspxz$vURp6%8$ZvZH_sQuK2f6RBxm!N;it8JRch>Z;tLAMkgz$5*r)n3;BGqV|p zbxTi~z)@|;f!rkAEi>BbfQ?5;AH1oT*w9-~orwS=6KuF=rKRWhGKHqwWM zK%4}>zyl+2M{6ZB_;9HI5Ht?&y|ahQjsaK55t(9bnS=Tf6q9bg(xgSF4GQ)Ifrl8b za7G-4Gx`kVe7AhZJTg7(gudjoMD=iWk!lsB<8Oo4_8EUfI^xX+8jBdsTElhe3yjL) z%M)zJ$Y7#HeRvtkuJ=cB0-Kv?h`|uxZ@>3f{OWZqzrxxLZ}R|eg3)cy-MQ{ax|u4Z zO6Vn;OtfZ2?F)Nv-nt#j5pS2`g|jEQT3pZ>_xw1(o7C(;5R?ToiC;tn4t280)yyv+ zE5qPMP85M5JSNl{p(Q4h^b*RQRi6eJ*hV5Qge92`4KWC9jhe9M_b1^$gG1K8*AE-f z6eC;X;+N+aqI!6{K6bZh(;?Cf)<=795MuilMBle0J2N*#1*TV_L3hwBZY{U5{>t=6G&phJ7y682S(+0 zPK@SPrTFc{(3GVqq>^l7Kc{knxouY4I=83e#G{zQ>>cH^H=TEX5qE!3GQ#XQv8e= z+ahH=ZQi}E`&q=l#QnZ?M6dHh3UzqXVL^xWvBI`i9$0|ppME=6qK*;Q^b^k4geOcN zQ3L--9KN3l^hf=6?Me@b3=l#XignJNEHI7JDE+QyGzGa29@(F;dQ zH5}j;O$ZzUmLdq|Z65r?GU4?UHWapc=x+(%_^pLDmw5_`vgJQsyr~kCg@}THVcAjuoVJ0+pVio+IVku;M|z4~YoQDa5^4n^+(ZJ84I_HI=}A6t4R`~@szZ;+13S%L#qgd>Fx zR>vR29Gs!Sjw5KoAGI!QPWtCP^?DpSej1%Vho@>bde*yF`fv}MyZo{O;ylXg?h#rNBZvXa4N!lb$+;hliHYBOJc1ViBc8- zuGbI3I{H<>RzlIS`ZR=I>R&RpdKCFS8IZ};U=(mA?$;cF3Bt=aw34+_K@+V2KUp*sX(AVM(BnYM?Vu)qT6BMn%tgCq+K{$@dq#ch7s8P2=QA6>61 zePfa&O^@_k*gqWc-jMm6w|gipuhOV|DcRu)IR}q`VUjgWWcwX?g|~*$_>a5I-&h8R zDt3|xaLPHw(MJh{H=^@)0Wq)NMetDUeb+BS%&*sAk< z`RR8R>8%f;0Vh^Nlb+q1l?WJFI)rj)GQtXUNfQ0^V#8R~@&oYvc=1x)Q%p7?Y83rK z;*R{&enjY&Z1*yAV_A&Ok2nQ?OUq}P7_sE33y9Q`Ep6`DhtRAyRY3b|qWq2c@>cC< z?LS0QVQ1ipr@eUj_VMCRFt8AL?b*4Ylx(w&hzF1sVT-3Zgt0^1Zau*je6-R97H5sS z*83JZKA-T!7BgVGv%3UE+F055FGqf&QBvQ$18Ts~y1A(08qtBh7g*2o{K5&(d4q~j zp4nRKWo5xAZF-@I4O%-!S=q%HX1L0Xore#*sEAQV4g^L;L~t*}CC;JN{UQsR$_KU( zwtCEK53Xm6D6dd{b1yIjfBfuPczDDW4!pu{(fOTz&;=nvU7;MsIq&J_kn6^tmTG5t z&+%WLaID|w0dv6H1TFE>6bd9r@1FYI53fF+lN=z;Q%|}w&}o}P?q`89jfna-g;*v0 z;NoRT-Gftr?5e#qT%I8%Ms6dP^&4p`1Sd4d>`o67T?tD~NDyTsS`3wl4PF+Asj8j~ z!0x0P#7Wm0EttJnm$RXrk$PDe-JxoZl)}*VS=_A($4I;fIM-&ZJL&uh%7t>k^6ZXIm4VoKSvJ4&3& z{C&z60j62`0T=r%58zy@>*RPbYdPLrZu`F9B09F;+$1(j*|7zo!Sw$OH6DdoI!&_TTu^Ws%t^%F6D?aE#8RdCf;UpXS z@DG;ZxBQ52eO6D%eACi;I{(YY(`~+iiiaJh6Np#W_4(M2tc~R)CmQ*211nVE2hl5z zs!T+UETKNi3eum4MTU;sS@6nFXm}OE7N2LByLa9FHaS(AY%az^77+swi9)szPbqN} z6{(Ope>HoAVTmRQ$2mZ7mg87$0V_CvX?? z)Zumn1su4uB#}fYp@z8%YNlSS2m1WP?QQK3pXv5;<8)>!M~ZijBOag+$p^qR@9|BS z`N)Ri6s^W7tPG_t`S-jlSqJi^-Sirh!dVuIoUG|aC%4c!=H%PDv7QTE=(mjT!)h6N zczGL?!yaH)qfo>1<#Yz&7LMDfEvuEFrH+8wFP!f&;L8K<2Kzn?y}zF6;*m4mE}waI z@4Ma2-N6K@?W(@G6lU0fgf*d>orZJ)T2CcJ%hwmt%Yy9OwpfX7;}@GDI?%^2JIBY} zzogl6cj+LCh9_px%59W-vfa~o^Blo0PiHv zA$6aYb#PV3IZVbq#^t$TKw0!svhn*TJ#*aMq%s^WU2(#>5Ic|?-|nyN}SqC zLHgm%qZ!G7p*SxmTdk%719k*{US0$%4;4=UqwF5Ke)8Yd5Hd<{Z}B2pcmVc?vobVg#i&~Q`jPvN9P@G zeOVqk@d(S3Kd;nqgkR1AG0e(hcXb;?^vmz&LC|hvk^4kb%AsFD%`Whxfq{38?YmEc zXblCF@3nt_l&k9Cw|T@a5TKWg(mUlrfRf@|o7fo-(RQYk|( zYbFy>LvK;Av9e&xMaIYXCedlxx-M$wQR1`^6k?rcj!zQqXtVNvGYMHE`5BiV>zAh! zvMWN&)M_e z9b}W&>VH?}Ga^+EU0mF;ExdMv6mfI~wUOp9EU{08R^P>O?}56ikt*GvHL~VfijA|r zmCoX5m1k3K3#1%`_C%wG!oDEum@VWNyLvsBOpwZFDw~X8*r|g@4X5V*@&h*@W(B!7_;K4=( z!<`iN-nxq2D=NC`X|A7~(+|3;wkk!CWY~KxTzNg}l7viF;>4^8^t?STHFjrm~_?=qALuk33y- zLQ-3B=m%9Eg9Ai+p43nF=>)8g;p#%o`1cm&qa4-+@rM4*aG{zTtCj`594r0s9SNg%y9*^T+U%n|3WC&sDH>dpBkh} z;7T%iEu`>+Mb?K3*)r~;+OeQaN1r%7oB@8<4QIzf(BTF}I(w4W-+XQ6OS^+tdNm8D zT6rg?QI2D3p%;T0910|Tt$*d3Gt)XItrjtr{Rr$nmXO#WHGYDzTqq+JnFsKX|> z{3WUmoDOz0iLOi{am39{0ne68oGt0OtJ3_(j8KyUUx;g`|#A$}appm^&=GRnexm$bXqQ={h#NIrG8oogna?8|c%koz8 zJo?x?|4XDnZg=GUq`^w~ibete*J!@(q_!RT5#qKJ;Z(LK~jpX;S!-%4dqE-8AAmg15wy zVEK?+inY8$ou9EA`}^eYsOj5sjIF6nwi_YKg*hy0EN$kxV^bZo#UHt7v8J0D-wpMf zLpQ=RweP?BP%E&aErGMX#z$w}5^wQZI8)19{f8FP zK-KC!L$#Wh-P;~s0nRC? zL+Ro5(C;ok${`|UVh|7q!;&9$pn3Vrj9$)YoAU_1*(!$K!OlFh?m!}6%itR$^xWaJ zV{TB~kEu6ujMPL1PxAPr+8+c@2RkGEPrkWIx%Mk~iR7FxD3DveqjV{6!TIbWRhK?{ zdO~oZHjS)c%QvA?x{r5vmkz%jGBDh-F65u;9?@*APuMkD8dC2T$(+-sQ4}HBaDCm5 z;os+Unw>cc4@3(5&UukmdDs_%C5!C#I~g49k1TY2e-c*1Ds{l zFb=-eKQwr5Yl@m7rvAGaS2Q$}66teBQ?!%FN2t-6q$u>c&pcUB%E2NDQy68>kjOI5 z3^oE>eR0L+p`Kq(ZW6apjLHTnQ;n(*r!f`Tt-SZAq(%!Ety7(ub1T-HLqjz)rF+Es z*M&jY=%s1MosKbnU81L~z%{#9#LK}_oj-b$zl}<#H8g@&`YGCC*%W#7X?DGbbQ^>x zG|L6v2oOon_#|c62y3WyE_EFUspO=o#b%nbjq1~Uj#SY~6T3iEfLZn-io$EmjAwBb za7*fNwg-0~UAyQ$u&YMWP3JTgJg-2kZ1g~Tx-jHcMAzPJXZ?67;ozThH0nRXE&5$Y zDGc%89>!fmF^LG!1 z1=;FF;YO}Z zFALjFkWHS8zkLg$z)+ztosr`kZ4U%btiD%fu=Wd2r>KmL>~;Cfb%T%oA7~I!)^I?W zryHVg{0|5VQGMiFVVd{+II+3q2YSGHFbXPK@PrWap5k)W)iXU+ZbHNsLHH$Ytwn(# z&zM{TcZZlWB;Om1tr5RXn*^IzP;)`B(_YP1>6FmR3l^CbS{-adqc>(POgz3u50~bB z3=(b&#B`b?mKs45Klw}{W$CJwHof3g(hC09v{|RrZ5arax@kcqEzgqMDg60%;AEG~ zx)WI=lE#p8P&)9Dm#D?9uQn}D$%KUJ=AP(@=U<w(0@Su{Qm9#o)6!&Xv|8WpXY{XRU`+N{{7L#u_h4v+&42EfW;^ z+rA{(=Pj2MS`QCNqsm|kA8vr7*sD%QCk(0^{zg9qPBix{As3{6dIv)BHxlH z+qdU0UQ&eRHPa?EuPlV07j^Xh{CRjd_{Lh%B|8=##4)**TN;%yBm9jDK=EOG_Nu6f z(qxUCCxQ+LXG_`Nx;zfD$zWf}B|JOxjz;W)cZr23$_rqQjOOH1&+ZvOrqo#c-|G3c z87&t(iS~KiNtTmAdQyp;Bzm_AML3~mfxcN&R?QdvyMc)pGo63nZLK2i<&bC|t6{ln zo9rCxE*R!Pu@vg8ctSBjYg|w*KIAcvG}Mx{jBgUrw@bq~tj{S?XEA+d(k7C>v$@N4 zf;|3Y$)HN#(yw

Adjqv(9IZR_Br4#jzy!fglhb{q^JguT5m+y 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

FHZUKLT6+vo;eabHh{tqz+T2fx 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 z z0rZ^-AdWl=%WOS!d=_i0wf?t0_IF4fpT$Os`qPl_ql1Ff4_7+H=8 zz}0||k^4N}O;h5A%hCG*Fr?Hw+J|f$m|s%<-&>E*9}(utS}RL%O%B~UqAdR9`@_?- z8wkjY{r?_k`|1tU_xtg={M`m-=XZ;aK0No3W%Ku+kgtq;0i)(#0N1=e+zXGh0FUef zDsr+X4CTvuwDAAY$T0#&eWcUz>G%pc$|f8c4HRVhB7(;INSo{^jep!we?%6x*`sL= z`R^X)ATM7k>Q1A7bnMYZH+f*x5kAoKqyO1I`eqzV=6`euG(-N`WrsJ7hybI`!56;# zhhKH%tiKx8p#af_gNst3^S^s|4j5G-h|>1ye@+}n&iad?PDdt~f+H40{&M-a*Z#ES g$Ypu|%SE?;$F^MM{ib&91_kiZLmO+=XgGxb8^75W!2kdN literal 0 HcmV?d00001 diff --git a/rust/src/dns_gateway/dns_gateway.proto b/rust/src/dns_gateway/dns_gateway.proto new file mode 100644 index 0000000..93c8ea6 --- /dev/null +++ b/rust/src/dns_gateway/dns_gateway.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package dns_gateway; + +message DnsGatewayConfig { + // Base IPv4 address for virtual IP allocation (e.g. "10.10.0.0"). + string base_ip = 1; + + // CIDR prefix length (1-32). A /24 gives 256 IPs. + uint32 prefix_len = 2; + + // Each entry defines a domain pattern and associated metadata. + repeated DomainMatcher domains = 3; +} + +message DomainMatcher { + // Exact domain ("example.com") or wildcard pattern ("*.example.com"). + string domain = 1; + + // String key-value pairs exposed in Envoy filter state as `envoy.dns_gateway.metadata..` + map metadata = 2; +} diff --git a/rust/src/dns_gateway/mod.rs b/rust/src/dns_gateway/mod.rs new file mode 100644 index 0000000..32a6422 --- /dev/null +++ b/rust/src/dns_gateway/mod.rs @@ -0,0 +1,506 @@ +//! A DNS gateway filter that intercepts DNS queries and returns virtual IPs for matched domains. +//! +//! This filter demonstrates: +//! 1. UDP listener filter structure with `UdpListenerFilterConfig` and `UdpListenerFilter` traits. +//! 2. DNS query parsing and response. +//! 3. Using protobof for configuration. +//! +//! See dns_gateway.proto for the protobuf definitions of the config. + +pub mod cache_lookup; +mod proto; +mod virtual_ip_cache; + +use virtual_ip_cache::{get_cache, init_cache, Destination}; +use envoy_proxy_dynamic_modules_rust_sdk::*; +use hickory_proto::op::{Message, MessageType, ResponseCode}; +use hickory_proto::rr::{Name, RData, Record, RecordType}; +use hickory_proto::serialize::binary::{BinDecodable, BinDecoder}; +use std::collections::HashMap; +use std::net::Ipv4Addr; +use std::sync::Arc; + +#[derive(Clone)] +struct DomainMatcher { + domain_pattern: String, + metadata: HashMap, +} + +impl DomainMatcher { + /// Matches a domain against this matcher's pattern. + /// Supports exact matches and wildcard patterns like "*.aws.com". + fn matches(&self, domain: &str) -> bool { + let Some(base_domain) = self.domain_pattern.strip_prefix("*.") else { + return domain == self.domain_pattern; + }; + + // For "*.aws.com", base_domain is "aws.com". + // Domain must end with ".aws.com" and have at least one label before it. + let Some(prefix) = domain.strip_suffix(base_domain) else { + return false; + }; + prefix.ends_with('.') && prefix.len() > 1 + } +} + +/// The filter configuration that implements +/// [`envoy_proxy_dynamic_modules_rust_sdk::UdpListenerFilterConfig`]. +/// +/// This configuration is shared across all UDP listener filter instances. +pub struct DnsGatewayFilterConfig { + domains: Arc<[DomainMatcher]>, +} + +impl DnsGatewayFilterConfig { + /// Creates a new DNS gateway filter configuration from the raw config bytes. + /// + /// The config arrives as a JSON-serialized google.protobuf.Struct + /// wrapped in an Any: `{"@type":"...Struct", "value":{"base_ip":"...", ...}}`. + pub fn new(config: &[u8]) -> Option { + let config_str = match std::str::from_utf8(config) { + Ok(s) => s, + Err(err) => { + eprintln!("Error parsing config as UTF-8: {err}"); + return None; + } + }; + + let config_json: serde_json::Value = match serde_json::from_str(config_str) { + Ok(v) => v, + Err(err) => { + eprintln!("Error parsing config JSON: {err}"); + return None; + } + }; + let value = &config_json["value"]; + + let proto_config: proto::DnsGatewayConfig = match serde_json::from_value(value.clone()) { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("Error parsing DnsGatewayConfig: {err}"); + return None; + } + }; + + if proto_config.base_ip.is_empty() { + eprintln!("base_ip is required for DNS gateway"); + return None; + } + let base_ip: Ipv4Addr = match proto_config.base_ip.parse() { + Ok(ip) => ip, + Err(err) => { + eprintln!("Invalid base_ip: {err}"); + return None; + } + }; + + let prefix_len = match u8::try_from(proto_config.prefix_len) { + Ok(v) => v, + Err(err) => { + eprintln!("Invalid prefix_len: {err}"); + return None; + } + }; + if !(1..=32).contains(&prefix_len) { + eprintln!("prefix_len must be between 1 and 32, got {prefix_len}"); + return None; + } + + init_cache(u32::from(base_ip), prefix_len); + + let domains: Arc<[DomainMatcher]> = proto_config + .domains + .into_iter() + .map(|d| DomainMatcher { + domain_pattern: d.domain, + metadata: d.metadata.into_iter().collect(), + }) + .collect::>() + .into(); + + envoy_log_info!("Initialized with {} domains", domains.len()); + + Some(DnsGatewayFilterConfig { domains }) + } +} + +impl UdpListenerFilterConfig for DnsGatewayFilterConfig { + fn new_udp_listener_filter(&self, _envoy: &mut ELF) -> Box> { + Box::new(DnsGatewayFilter { + domains: Arc::clone(&self.domains), + }) + } +} + +/// The DNS gateway filter that implements +/// [`envoy_proxy_dynamic_modules_rust_sdk::UdpListenerFilter`]. +/// +/// Intercepts DNS queries and returns virtual IPs for domains matching configured matchers. +struct DnsGatewayFilter { + domains: Arc<[DomainMatcher]>, +} + +impl UdpListenerFilter for DnsGatewayFilter { + fn on_data( + &mut self, + envoy_filter: &mut ELF, + ) -> abi::envoy_dynamic_module_type_on_udp_listener_filter_status { + let (chunks, total_length) = envoy_filter.get_datagram_data(); + envoy_log_debug!( + "Received UDP datagram, {} bytes, {} chunks", + total_length, + chunks.len() + ); + let data: Vec = chunks.iter().flat_map(|c| c.as_slice()).copied().collect(); + + // From the perspective of DNS gateway, the peer is the client that sent the DNS query. + let peer = envoy_filter.get_peer_address(); + envoy_log_debug!("Peer address: {:?}", peer); + + let mut decoder = BinDecoder::new(&data); + let query_message = match Message::read(&mut decoder) { + Ok(msg) => msg, + Err(e) => { + envoy_log_warn!("Failed to parse DNS query: {}", e); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + }; + + envoy_log_debug!( + "Parsed DNS message id={}, type={:?}, queries={}", + query_message.id(), + query_message.message_type(), + query_message.queries().len() + ); + + if query_message.message_type() != MessageType::Query { + envoy_log_warn!("Received non-query DNS message"); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + + let question = match query_message.queries().first() { + Some(q) => q, + None => { + envoy_log_warn!("DNS query has no questions"); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + }; + + let domain_raw = question.name().to_utf8(); + // DNS names are fully qualified with a trailing dot (e.g. "api.aws.com."). + // Strip it so our wildcard patterns like "*.aws.com" match correctly. + let domain = domain_raw.strip_suffix('.').unwrap_or(&domain_raw); + + envoy_log_debug!( + "{:?} record query for domain: {} (raw: {})", + question.query_type(), + domain, + domain_raw + ); + + let matcher = match self.domains.iter().find(|m| m.matches(domain)) { + Some(m) => m, + None => { + envoy_log_info!("No matcher for domain: {}", domain); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + }; + + envoy_log_info!( + "Matched pattern '{}' for domain '{}'", + matcher.domain_pattern, + domain + ); + + let response_result = match question.query_type() { + RecordType::A => { + let destination = Destination::new(domain.to_string(), matcher.metadata.clone()); + let virtual_ip = match get_cache().allocate(destination) { + Some(ip) => ip, + None => { + envoy_log_error!("IP exhaustion, cannot allocate for {}", domain); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + }; + envoy_log_info!("Allocated virtual IP {} for domain {}", virtual_ip, domain); + build_dns_response(&query_message, question.name(), virtual_ip) + } + other => { + envoy_log_info!( + "Returning NODATA for {:?} query (only A records supported)", + other + ); + build_nodata_response(&query_message) + } + }; + + let response_bytes = match response_result { + Ok(bytes) => bytes, + Err(e) => { + envoy_log_error!("Failed to craft DNS response: {}", e); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue; + } + }; + + let (peer_addr, peer_port) = match peer { + Some(p) => p, + None => { + envoy_log_error!("No peer address available, cannot send response"); + return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::StopIteration; + } + }; + + envoy_log_debug!( + "Sending {} byte response to {}:{}", + response_bytes.len(), + peer_addr, + peer_port + ); + if !envoy_filter.send_datagram(&response_bytes, &peer_addr, peer_port) { + envoy_log_error!("Failed to send datagram to {}:{}", peer_addr, peer_port); + } + + abi::envoy_dynamic_module_type_on_udp_listener_filter_status::StopIteration + } +} + +fn build_dns_response( + query_message: &Message, + name: &Name, + ip: Ipv4Addr, +) -> Result, Box> { + let mut response = query_message.clone(); + + response.set_message_type(MessageType::Response); + response.set_response_code(ResponseCode::NoError); + response.set_recursion_available(true); + response.set_authoritative(true); + + let record = Record::from_rdata(name.clone(), 600, RData::A(ip.into())); + + response.add_answer(record); + + let bytes = response.to_vec()?; + Ok(bytes) +} + +fn build_nodata_response(query_message: &Message) -> Result, Box> { + let mut response = query_message.clone(); + + response.set_message_type(MessageType::Response); + response.set_response_code(ResponseCode::NoError); + response.set_recursion_available(true); + response.set_authoritative(true); + + let bytes = response.to_vec()?; + Ok(bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_domain_matcher_wildcard() { + let matcher = DomainMatcher { + domain_pattern: "*.aws.com".to_string(), + metadata: HashMap::new(), + }; + + assert!(matcher.matches("api.aws.com")); + assert!(matcher.matches("s3.aws.com")); + assert!(matcher.matches("lambda.aws.com")); + assert!(matcher.matches("sub.api.aws.com")); + + assert!(!matcher.matches("aws.com")); + assert!(!matcher.matches("xaws.com")); + assert!(!matcher.matches("aws.com.evil.com")); + assert!(!matcher.matches("api.aws.org")); + } + + #[test] + fn test_domain_matcher_exact() { + let matcher = DomainMatcher { + domain_pattern: "api.example.com".to_string(), + metadata: HashMap::new(), + }; + + assert!(matcher.matches("api.example.com")); + + assert!(!matcher.matches("www.api.example.com")); + assert!(!matcher.matches("example.com")); + assert!(!matcher.matches("api.example.org")); + } + + #[test] + fn test_config_parsing_valid_struct() { + let config = r#"{ + "@type": "type.googleapis.com/google.protobuf.Struct", + "value": { + "base_ip": "10.10.0.0", + "prefix_len": 24, + "domains": [ + { + "domain": "*.aws.com", + "metadata": { + "cluster": "aws_cluster", + "region": "us-east-1" + } + } + ] + } + }"#; + + let config = DnsGatewayFilterConfig::new(config.as_bytes()).unwrap(); + assert_eq!(config.domains.len(), 1); + assert_eq!(config.domains[0].domain_pattern, "*.aws.com"); + assert_eq!( + config.domains[0].metadata.get("cluster").unwrap(), + "aws_cluster" + ); + assert_eq!( + config.domains[0].metadata.get("region").unwrap(), + "us-east-1" + ); + } + + #[test] + fn test_config_parsing_multiple_domains() { + let config = r#"{ + "@type": "type.googleapis.com/google.protobuf.Struct", + "value": { + "base_ip": "10.10.0.0", + "prefix_len": 16, + "domains": [ + {"domain": "*.aws.com", "metadata": {"cluster": "aws"}}, + {"domain": "*.google.com", "metadata": {"cluster": "google"}}, + {"domain": "exact.example.com", "metadata": {"cluster": "exact"}} + ] + } + }"#; + + let config = DnsGatewayFilterConfig::new(config.as_bytes()).unwrap(); + assert_eq!(config.domains.len(), 3); + } + + #[test] + fn test_config_parsing_missing_base_ip() { + let config = r#"{ + "value": { + "prefix_len": 24, + "domains": [] + } + }"#; + + assert!(DnsGatewayFilterConfig::new(config.as_bytes()).is_none()); + } + + #[test] + fn test_config_parsing_missing_prefix_len() { + // proto3 defaults missing uint32 to 0, which fails the 1..=32 range check. + let config = r#"{ + "value": { + "base_ip": "10.10.0.0", + "domains": [] + } + }"#; + + assert!(DnsGatewayFilterConfig::new(config.as_bytes()).is_none()); + } + + #[test] + fn test_config_parsing_invalid_prefix_len() { + let config = r#"{ + "value": { + "base_ip": "10.10.0.0", + "prefix_len": 33, + "domains": [] + } + }"#; + + assert!(DnsGatewayFilterConfig::new(config.as_bytes()).is_none()); + } + + #[test] + fn test_config_parsing_invalid_json() { + assert!(DnsGatewayFilterConfig::new(b"invalid json {").is_none()); + } + + #[test] + fn test_config_parsing_non_string_metadata_value() { + let config = r#"{ + "value": { + "base_ip": "10.10.0.0", + "prefix_len": 24, + "domains": [ + {"domain": "*.aws.com", "metadata": {"count": 42}} + ] + } + }"#; + + assert!(DnsGatewayFilterConfig::new(config.as_bytes()).is_none()); + } + + #[test] + fn test_domain_stripping_trailing_dot() { + let domain_raw = "api.aws.com."; + let domain = domain_raw.strip_suffix('.').unwrap_or(domain_raw); + assert_eq!(domain, "api.aws.com"); + } + + #[test] + fn test_domain_without_trailing_dot() { + let domain_raw = "api.aws.com"; + let domain = domain_raw.strip_suffix('.').unwrap_or(domain_raw); + assert_eq!(domain, "api.aws.com"); + } + + #[test] + fn test_dns_response_building() { + let mut query = Message::new(); + query.set_id(12345); + query.set_message_type(MessageType::Query); + query.set_recursion_desired(true); + + let name = Name::from_utf8("test.example.com").unwrap(); + let ip = Ipv4Addr::new(10, 10, 0, 1); + + let result = build_dns_response(&query, &name, ip); + assert!(result.is_ok()); + + let response_bytes = result.unwrap(); + assert!(!response_bytes.is_empty()); + + let mut decoder = BinDecoder::new(&response_bytes); + let response = Message::read(&mut decoder).unwrap(); + + assert_eq!(response.id(), 12345); + assert_eq!(response.message_type(), MessageType::Response); + assert_eq!(response.response_code(), ResponseCode::NoError); + assert!(response.recursion_available()); + assert_eq!(response.answers().len(), 1); + } + + #[test] + fn test_nodata_response_building() { + let mut query = Message::new(); + query.set_id(54321); + query.set_message_type(MessageType::Query); + query.set_recursion_desired(false); + + let result = build_nodata_response(&query); + assert!(result.is_ok()); + + let response_bytes = result.unwrap(); + assert!(!response_bytes.is_empty()); + + let mut decoder = BinDecoder::new(&response_bytes); + let response = Message::read(&mut decoder).unwrap(); + + assert_eq!(response.id(), 54321); + assert_eq!(response.message_type(), MessageType::Response); + assert_eq!(response.response_code(), ResponseCode::NoError); + assert!(response.recursion_available()); + assert_eq!(response.answers().len(), 0); + } +} diff --git a/rust/src/dns_gateway/proto.rs b/rust/src/dns_gateway/proto.rs new file mode 100644 index 0000000..ba4feff --- /dev/null +++ b/rust/src/dns_gateway/proto.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/dns_gateway.rs")); diff --git a/rust/src/dns_gateway/virtual_ip_cache.rs b/rust/src/dns_gateway/virtual_ip_cache.rs new file mode 100644 index 0000000..1038b86 --- /dev/null +++ b/rust/src/dns_gateway/virtual_ip_cache.rs @@ -0,0 +1,299 @@ +//! Virtual IP cache for mapping domains to synthetic IPv4 addresses. +//! +//! This module provides a thread-safe cache that allocates sequential virtual IPs from a +//! configured subnet. The DNS gateway filter populates this cache, and the cache lookup +//! network filter reads from it. + +use dashmap::mapref::entry::Entry; +use dashmap::DashMap; +use envoy_proxy_dynamic_modules_rust_sdk::*; +use parking_lot::Mutex; +use std::collections::HashMap; +use std::net::Ipv4Addr; +use std::sync::{Arc, OnceLock}; + +/// A destination entry in the virtual IP cache. +/// +/// Maps a fully qualified domain name (e.g. "bucket-1.aws.com") to its +/// associated metadata (e.g. the upstream cluster to use). +#[derive(Clone, Debug)] +pub struct Destination { + domain: String, + metadata: HashMap, +} + +impl Destination { + pub fn new(domain: String, metadata: HashMap) -> Self { + Self { domain, metadata } + } + + pub fn domain(&self) -> &str { + &self.domain + } + + pub fn metadata(&self) -> &HashMap { + &self.metadata + } +} + +/// Thread-safe cache for virtual IP allocation and lookup. +/// +/// Allocates sequential IPs from a configured base address within a CIDR subnet. +/// Deduplicates allocations by domain name. +pub struct VirtualIpCache { + base_ip: u32, + capacity: u32, + alloc_offset: Mutex, + ip_to_destination: DashMap, + domain_to_ip: DashMap, +} + +impl VirtualIpCache { + pub fn new(base_ip: u32, prefix_len: u8) -> Self { + assert!( + (1..=32).contains(&prefix_len), + "prefix_len must be between 1 and 32" + ); + let subnet_size = 1u32 << (32 - prefix_len); + let host_mask = subnet_size - 1; + let capacity = subnet_size - (base_ip & host_mask); + envoy_log_info!( + "Creating cache with prefix_len={}, capacity={}", + prefix_len, + capacity + ); + Self { + base_ip, + capacity, + alloc_offset: Mutex::new(0), + ip_to_destination: DashMap::new(), + domain_to_ip: DashMap::new(), + } + } + + /// Allocates a virtual IP for the given destination. + /// + /// Returns the same IP if the domain was previously allocated. + /// Returns `None` if the subnet is exhausted. + pub fn allocate(&self, destination: Destination) -> Option { + if let Some(ip) = self.domain_to_ip.get(&destination.domain) { + return Some(*ip); + } + + match self.domain_to_ip.entry(destination.domain.clone()) { + Entry::Occupied(entry) => Some(*entry.get()), + Entry::Vacant(entry) => { + let mut offset = self.alloc_offset.lock(); + + if *offset >= self.capacity { + envoy_log_error!( + "IP allocation exhausted, tried to allocate #{} but max is {}", + *offset, self.capacity + ); + return None; + } + + let ip = Ipv4Addr::from(self.base_ip + *offset); + *offset += 1; + + envoy_log_info!( + "Allocated virtual IP {} for domain {}", + ip, + destination.domain + ); + + self.ip_to_destination.insert(ip, destination); + entry.insert(ip); + + Some(ip) + } + } + } + + /// Looks up the destination for a given virtual IP. + pub fn lookup(&self, ip: Ipv4Addr) -> Option { + self.ip_to_destination.get(&ip).as_deref().cloned() + } +} + +static VIRTUAL_IP_CACHE: OnceLock> = OnceLock::new(); + +/// Initializes the global virtual IP cache. First call wins; subsequent calls are ignored. +pub fn init_cache(base_ip: u32, prefix_len: u8) { + let cache = Arc::new(VirtualIpCache::new(base_ip, prefix_len)); + + if VIRTUAL_IP_CACHE.set(cache).is_err() { + envoy_log_warn!("Cache already initialized, ignoring duplicate init"); + return; + } + + envoy_log_info!( + "Initialized with base IP {}, prefix_len {}", + Ipv4Addr::from(base_ip), + prefix_len + ); +} + +pub fn get_cache() -> &'static Arc { + VIRTUAL_IP_CACHE + .get() + .expect("cache not initialized, dns_gateway must be configured first") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cache_new() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); // 10.10.0.0/24 + assert_eq!(cache.base_ip, 0x0A0A0000); + assert_eq!(cache.capacity, 256); + } + + #[test] + fn test_prefix_len_calculations() { + let cache_24 = VirtualIpCache::new(0, 24); + assert_eq!(cache_24.capacity, 256); // 2^(32-24) = 256 + + let cache_16 = VirtualIpCache::new(0, 16); + assert_eq!(cache_16.capacity, 65536); // 2^(32-16) = 65536 + + let cache_32 = VirtualIpCache::new(0, 32); + assert_eq!(cache_32.capacity, 1); // 2^(32-32) = 1 + + let cache_8 = VirtualIpCache::new(0, 8); + assert_eq!(cache_8.capacity, 16777216); // 2^(32-8) = 16777216 + } + + #[test] + #[should_panic(expected = "prefix_len must be between 1 and 32")] + fn test_invalid_prefix_len_zero() { + VirtualIpCache::new(0, 0); + } + + #[test] + #[should_panic(expected = "prefix_len must be between 1 and 32")] + fn test_invalid_prefix_len_too_large() { + VirtualIpCache::new(0, 33); + } + + #[test] + fn test_unaligned_base_ip_caps_capacity() { + // 10.10.0.200/24 — only 56 IPs remain in the subnet (200..=255) + let cache = VirtualIpCache::new(u32::from(Ipv4Addr::new(10, 10, 0, 200)), 24); + assert_eq!(cache.capacity, 56); + } + + #[test] + fn test_unaligned_base_ip_exhaustion() { + // 10.10.0.252/24 — only 4 IPs remain (252, 253, 254, 255) + let base = u32::from(Ipv4Addr::new(10, 10, 0, 252)); + let cache = VirtualIpCache::new(base, 24); + assert_eq!(cache.capacity, 4); + + for i in 0..4 { + let dest = Destination::new(format!("domain{}.com", i), HashMap::new()); + assert!(cache.allocate(dest).is_some()); + } + + let ip_last = cache.allocate(Destination::new("last-ok.com".to_string(), HashMap::new())); + assert!(ip_last.is_none()); + + let first = cache.lookup(Ipv4Addr::new(10, 10, 0, 252)).unwrap(); + assert_eq!(first.domain(), "domain0.com"); + let last = cache.lookup(Ipv4Addr::new(10, 10, 0, 255)).unwrap(); + assert_eq!(last.domain(), "domain3.com"); + } + + #[test] + fn test_aligned_base_ip_full_subnet() { + let cache = VirtualIpCache::new(u32::from(Ipv4Addr::new(10, 10, 0, 0)), 24); + assert_eq!(cache.capacity, 256); + } + + #[test] + fn test_allocate_sequential_ips() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); // 10.10.0.0/24 + + let dest1 = Destination::new("api.aws.com".to_string(), HashMap::new()); + let dest2 = Destination::new("s3.aws.com".to_string(), HashMap::new()); + + let ip1 = cache.allocate(dest1).unwrap(); + let ip2 = cache.allocate(dest2).unwrap(); + + assert_eq!(ip1, Ipv4Addr::new(10, 10, 0, 0)); + assert_eq!(ip2, Ipv4Addr::new(10, 10, 0, 1)); + } + + #[test] + fn test_allocate_same_domain_returns_same_ip() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); + + let dest = Destination::new("api.aws.com".to_string(), HashMap::new()); + + let ip1 = cache.allocate(dest.clone()).unwrap(); + let ip2 = cache.allocate(dest.clone()).unwrap(); + + assert_eq!(ip1, ip2); + } + + #[test] + fn test_lookup_allocated_ip() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); + + let mut metadata = HashMap::new(); + metadata.insert("cluster".to_string(), "aws_cluster".to_string()); + + let dest = Destination::new("api.aws.com".to_string(), metadata); + + let ip = cache.allocate(dest).unwrap(); + + let result = cache.lookup(ip).unwrap(); + assert_eq!(result.domain(), "api.aws.com"); + assert_eq!(result.metadata().get("cluster").unwrap(), "aws_cluster"); + } + + #[test] + fn test_lookup_unallocated_ip() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); + let unallocated_ip = Ipv4Addr::new(10, 10, 0, 100); + + assert!(cache.lookup(unallocated_ip).is_none()); + } + + #[test] + fn test_allocation_exhaustion_returns_none() { + let cache = VirtualIpCache::new(0x0A0A0000, 30); // 4 IPs available (2^(32-30)) + + for i in 0..4 { + let dest = Destination::new(format!("domain{}.com", i), HashMap::new()); + assert!( + cache.allocate(dest).is_some(), + "allocation {} should succeed", + i + ); + } + + let overflow = Destination::new("overflow.com".to_string(), HashMap::new()); + assert!(cache.allocate(overflow).is_none()); + } + + #[test] + fn test_metadata_preserved() { + let cache = VirtualIpCache::new(0x0A0A0000, 24); + + let mut metadata = HashMap::new(); + metadata.insert("key1".to_string(), "value1".to_string()); + metadata.insert("key2".to_string(), "value2".to_string()); + + let dest = Destination::new("test.com".to_string(), metadata); + + let ip = cache.allocate(dest).unwrap(); + let result = cache.lookup(ip).unwrap(); + + assert_eq!(result.metadata().len(), 2); + assert_eq!(result.metadata().get("key1").unwrap(), "value1"); + assert_eq!(result.metadata().get("key2").unwrap(), "value2"); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d87865d..638ae09 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -60,7 +60,15 @@ pub mod listener_ip_allowlist; pub mod listener_sni_router; pub mod listener_tls_detector; -declare_init_functions!(init, new_http_filter_config_fn); +// DNS gateway module example. +pub mod dns_gateway; + +declare_all_init_functions!( + init, + http: new_http_filter_config_fn, + network: new_network_filter_config_fn, + udp_listener: new_udp_listener_filter_config_fn, +); /// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::ProgramInitFunction`]. /// @@ -99,6 +107,53 @@ fn new_http_filter_config_fn( .map(|config| Box::new(config) as Box>), "metrics" => http_metrics::FilterConfig::new(filter_config, envoy_filter_config) .map(|config| Box::new(config) as Box>), - _ => panic!("Unknown filter name: {filter_name}"), + _ => panic!("Unknown HTTP filter name: {filter_name}"), + } +} + + + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::NewNetworkFilterConfigFunction`]. +/// +/// This is the entrypoint every time a new Network filter is created via the DynamicModuleNetworkFilter config. +/// +/// Each argument matches the corresponding argument in the Envoy config here: +/// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig +/// +/// Returns None if the filter name or config is determined to be invalid by each filter's `new` function. +fn new_network_filter_config_fn( + _envoy_filter_config: &mut EC, + filter_name: &str, + filter_config: &[u8], +) -> Option>> { + match filter_name { + "cache_lookup" => { + Some(Box::new(dns_gateway::cache_lookup::CacheLookupFilterConfig::new(filter_config)) + as Box>) + } + _ => panic!("Unknown network filter name: {filter_name}"), + } +} + +/// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::NewUdpListenerFilterConfigFunction`]. +/// +/// This is the entrypoint every time a new UDP Listener filter is created via the DynamicModuleUdpListenerFilter config. +/// +/// Each argument matches the corresponding argument in the Envoy config here: +/// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/dynamic_modules/v3/dynamic_modules.proto#envoy-v3-api-msg-extensions-dynamic-modules-v3-dynamicmoduleconfig +/// +/// Returns None if the filter name or config is determined to be invalid by each filter's `new` function. +fn new_udp_listener_filter_config_fn< + EC: EnvoyUdpListenerFilterConfig, + ELF: EnvoyUdpListenerFilter, +>( + _envoy_filter_config: &mut EC, + filter_name: &str, + filter_config: &[u8], +) -> Option>> { + match filter_name { + "dns_gateway" => dns_gateway::DnsGatewayFilterConfig::new(filter_config) + .map(|config| Box::new(config) as Box>), + _ => panic!("Unknown UDP listener filter name: {filter_name}"), } } From a754a4124c167d74e959e2d398d74ff6531a5cc4 Mon Sep 17 00:00:00 2001 From: Gal Ovadia Date: Wed, 4 Mar 2026 17:41:57 -0500 Subject: [PATCH 2/4] fmt Signed-off-by: Gal Ovadia --- rust/src/dns_gateway/mod.rs | 2 +- rust/src/dns_gateway/virtual_ip_cache.rs | 3 ++- rust/src/lib.rs | 11 +++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/src/dns_gateway/mod.rs b/rust/src/dns_gateway/mod.rs index 32a6422..3e97cfe 100644 --- a/rust/src/dns_gateway/mod.rs +++ b/rust/src/dns_gateway/mod.rs @@ -11,7 +11,6 @@ pub mod cache_lookup; mod proto; mod virtual_ip_cache; -use virtual_ip_cache::{get_cache, init_cache, Destination}; use envoy_proxy_dynamic_modules_rust_sdk::*; use hickory_proto::op::{Message, MessageType, ResponseCode}; use hickory_proto::rr::{Name, RData, Record, RecordType}; @@ -19,6 +18,7 @@ use hickory_proto::serialize::binary::{BinDecodable, BinDecoder}; use std::collections::HashMap; use std::net::Ipv4Addr; use std::sync::Arc; +use virtual_ip_cache::{get_cache, init_cache, Destination}; #[derive(Clone)] struct DomainMatcher { diff --git a/rust/src/dns_gateway/virtual_ip_cache.rs b/rust/src/dns_gateway/virtual_ip_cache.rs index 1038b86..ebc4cdd 100644 --- a/rust/src/dns_gateway/virtual_ip_cache.rs +++ b/rust/src/dns_gateway/virtual_ip_cache.rs @@ -88,7 +88,8 @@ impl VirtualIpCache { if *offset >= self.capacity { envoy_log_error!( "IP allocation exhausted, tried to allocate #{} but max is {}", - *offset, self.capacity + *offset, + self.capacity ); return None; } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 638ae09..679f179 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -111,8 +111,6 @@ fn new_http_filter_config_fn( } } - - /// This implements the [`envoy_proxy_dynamic_modules_rust_sdk::NewNetworkFilterConfigFunction`]. /// /// This is the entrypoint every time a new Network filter is created via the DynamicModuleNetworkFilter config. @@ -127,10 +125,11 @@ fn new_network_filter_config_fn Option>> { match filter_name { - "cache_lookup" => { - Some(Box::new(dns_gateway::cache_lookup::CacheLookupFilterConfig::new(filter_config)) - as Box>) - } + "cache_lookup" => Some( + Box::new(dns_gateway::cache_lookup::CacheLookupFilterConfig::new( + filter_config, + )) as Box>, + ), _ => panic!("Unknown network filter name: {filter_name}"), } } From c5589270de9fdfbec2411955c0dffe1dafcfce88 Mon Sep 17 00:00:00 2001 From: Gal Ovadia Date: Wed, 4 Mar 2026 17:48:27 -0500 Subject: [PATCH 3/4] readme cleanup Signed-off-by: Gal Ovadia --- rust/src/dns_gateway/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/src/dns_gateway/README.md b/rust/src/dns_gateway/README.md index c2ad25c..8f89a65 100644 --- a/rust/src/dns_gateway/README.md +++ b/rust/src/dns_gateway/README.md @@ -83,7 +83,7 @@ No configuration. Use `filter_config: {}`. ## Manual testing -End-to-end test with docker-compose. I recommend using a clean linux VM for this. +End-to-end test with docker-compose. Create the following files: @@ -235,7 +235,7 @@ static_resources: do_not_close: true filter_name: cache_lookup filter_config: {} - # IMPORTANT! Setting an upstream cluster directly in TCP proxy config with FILTER_STATE(...) + # Setting an upstream cluster directly in the TCP proxy tunneling config with FILTER_STATE(...) # is not supported. Instead, write the value of FILTER_STATE(...) to 'envoy.tcp_proxy.cluster' - name: envoy.filters.network.set_filter_state typed_config: @@ -312,7 +312,6 @@ dig github.com # Will reach cluster_1 curl http://s3.aws.com./ -# Note that the trailing dot is necessary, Coder might try and append extra parts to the domain because of ndots funkiness # Will reach cluster_2 curl http://example.com./ From 286860bb1dd2f47a3efa0611e4c1376ea1b9c32f Mon Sep 17 00:00:00 2001 From: Gal Ovadia Date: Wed, 4 Mar 2026 19:02:10 -0500 Subject: [PATCH 4/4] remove protos Signed-off-by: Gal Ovadia --- rust/Cargo.lock | 106 +------------------------ rust/Cargo.toml | 4 - rust/build.rs | 11 --- rust/src/dns_gateway/config.rs | 24 ++++++ rust/src/dns_gateway/dns_gateway.proto | 22 ----- rust/src/dns_gateway/mod.rs | 17 ++-- rust/src/dns_gateway/proto.rs | 1 - 7 files changed, 32 insertions(+), 153 deletions(-) delete mode 100644 rust/build.rs create mode 100644 rust/src/dns_gateway/config.rs delete mode 100644 rust/src/dns_gateway/dns_gateway.proto delete mode 100644 rust/src/dns_gateway/proto.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 98e1ef7..fe873ff 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -17,12 +17,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" -[[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" @@ -106,7 +100,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown 0.14.5", + "hashbrown", "lock_api", "once_cell", "parking_lot_core", @@ -172,20 +166,12 @@ dependencies = [ "matchers", "once_cell", "parking_lot", - "prost", - "prost-build", "rand 0.9.1", "serde", "serde_json", "tempfile", ] -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.13" @@ -202,12 +188,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -298,12 +278,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "heck" version = "0.5.0" @@ -436,16 +410,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -568,12 +532,6 @@ dependencies = [ "syn", ] -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - [[package]] name = "nom" version = "7.1.3" @@ -619,16 +577,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -704,58 +652,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" -dependencies = [ - "prost", -] - [[package]] name = "quote" version = "1.0.40" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index bbfd19b..c964fac 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,10 +16,6 @@ dashmap = "6.1.0" once_cell = "1.20.2" hickory-proto = "0.24" parking_lot = "0.12" -prost = "0.13" - -[build-dependencies] -prost-build = "0.13" [dev-dependencies] tempfile = "3.16.0" diff --git a/rust/build.rs b/rust/build.rs deleted file mode 100644 index 82cf053..0000000 --- a/rust/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let mut config = prost_build::Config::new(); - config.type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]"); - config.field_attribute(".", "#[serde(default)]"); - config - .compile_protos( - &["src/dns_gateway/dns_gateway.proto"], - &["src/dns_gateway/"], - ) - .unwrap(); -} diff --git a/rust/src/dns_gateway/config.rs b/rust/src/dns_gateway/config.rs new file mode 100644 index 0000000..77f7dd1 --- /dev/null +++ b/rust/src/dns_gateway/config.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +#[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DnsGatewayConfig { + /// Base IPv4 address for virtual IP allocation (e.g. "10.10.0.0"). + #[serde(default)] + pub base_ip: String, + /// CIDR prefix length (1-32). A /24 gives 256 IPs. + #[serde(default)] + pub prefix_len: u32, + /// Each entry defines a domain pattern and associated metadata. + #[serde(default)] + pub domains: Vec, +} + +#[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DomainMatcher { + /// Exact domain ("example.com") or wildcard pattern ("*.example.com"). + #[serde(default)] + pub domain: String, + /// String key-value pairs exposed in Envoy filter state as `envoy.dns_gateway.metadata..` + #[serde(default)] + pub metadata: HashMap, +} diff --git a/rust/src/dns_gateway/dns_gateway.proto b/rust/src/dns_gateway/dns_gateway.proto deleted file mode 100644 index 93c8ea6..0000000 --- a/rust/src/dns_gateway/dns_gateway.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package dns_gateway; - -message DnsGatewayConfig { - // Base IPv4 address for virtual IP allocation (e.g. "10.10.0.0"). - string base_ip = 1; - - // CIDR prefix length (1-32). A /24 gives 256 IPs. - uint32 prefix_len = 2; - - // Each entry defines a domain pattern and associated metadata. - repeated DomainMatcher domains = 3; -} - -message DomainMatcher { - // Exact domain ("example.com") or wildcard pattern ("*.example.com"). - string domain = 1; - - // String key-value pairs exposed in Envoy filter state as `envoy.dns_gateway.metadata..` - map metadata = 2; -} diff --git a/rust/src/dns_gateway/mod.rs b/rust/src/dns_gateway/mod.rs index 3e97cfe..85f42ca 100644 --- a/rust/src/dns_gateway/mod.rs +++ b/rust/src/dns_gateway/mod.rs @@ -3,12 +3,9 @@ //! This filter demonstrates: //! 1. UDP listener filter structure with `UdpListenerFilterConfig` and `UdpListenerFilter` traits. //! 2. DNS query parsing and response. -//! 3. Using protobof for configuration. -//! -//! See dns_gateway.proto for the protobuf definitions of the config. pub mod cache_lookup; -mod proto; +mod config; mod virtual_ip_cache; use envoy_proxy_dynamic_modules_rust_sdk::*; @@ -74,7 +71,7 @@ impl DnsGatewayFilterConfig { }; let value = &config_json["value"]; - let proto_config: proto::DnsGatewayConfig = match serde_json::from_value(value.clone()) { + let gateway_config: config::DnsGatewayConfig = match serde_json::from_value(value.clone()) { Ok(cfg) => cfg, Err(err) => { eprintln!("Error parsing DnsGatewayConfig: {err}"); @@ -82,11 +79,11 @@ impl DnsGatewayFilterConfig { } }; - if proto_config.base_ip.is_empty() { + if gateway_config.base_ip.is_empty() { eprintln!("base_ip is required for DNS gateway"); return None; } - let base_ip: Ipv4Addr = match proto_config.base_ip.parse() { + let base_ip: Ipv4Addr = match gateway_config.base_ip.parse() { Ok(ip) => ip, Err(err) => { eprintln!("Invalid base_ip: {err}"); @@ -94,7 +91,7 @@ impl DnsGatewayFilterConfig { } }; - let prefix_len = match u8::try_from(proto_config.prefix_len) { + let prefix_len = match u8::try_from(gateway_config.prefix_len) { Ok(v) => v, Err(err) => { eprintln!("Invalid prefix_len: {err}"); @@ -108,7 +105,7 @@ impl DnsGatewayFilterConfig { init_cache(u32::from(base_ip), prefix_len); - let domains: Arc<[DomainMatcher]> = proto_config + let domains: Arc<[DomainMatcher]> = gateway_config .domains .into_iter() .map(|d| DomainMatcher { @@ -397,7 +394,7 @@ mod tests { #[test] fn test_config_parsing_missing_prefix_len() { - // proto3 defaults missing uint32 to 0, which fails the 1..=32 range check. + // serde defaults missing uint32 to 0, which fails the 1..=32 range check. let config = r#"{ "value": { "base_ip": "10.10.0.0", diff --git a/rust/src/dns_gateway/proto.rs b/rust/src/dns_gateway/proto.rs deleted file mode 100644 index ba4feff..0000000 --- a/rust/src/dns_gateway/proto.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/dns_gateway.rs"));