From 2135ff20051afaefa064f7e515d96ccbc06302e6 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Tue, 14 Nov 2023 21:41:17 +0100 Subject: [PATCH 1/2] initial commit for DNS local resolver --- Cargo.lock | 582 ++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + src/config.rs | 6 +- src/config/settings.json | 38 ++- src/dns.rs | 217 +++++++++++++ src/dns/cache.rs | 165 ++++++++++ src/dns/local.rs | 168 ++++++++++ src/dns/remote.rs | 113 +++++++ src/html5/parser/document.rs | 2 +- src/lib.rs | 2 + src/types.rs | 17 +- 11 files changed, 1293 insertions(+), 20 deletions(-) create mode 100644 src/dns.rs create mode 100644 src/dns/cache.rs create mode 100644 src/dns/local.rs create mode 100644 src/dns/remote.rs diff --git a/Cargo.lock b/Cargo.lock index 6a11d47fb..24ed4027f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -58,7 +67,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -68,7 +77,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -77,18 +86,50 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" @@ -107,6 +148,12 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + [[package]] name = "cast" version = "0.3.0" @@ -232,7 +279,7 @@ checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ "is-terminal", "lazy_static", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -340,6 +387,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -353,12 +415,30 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "domain-lookup-tree" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c82ba176db747c3408e87fe4cc88b7277801a15597915b3e593b876b49cd0b" + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "errno" version = "0.3.4" @@ -367,7 +447,7 @@ checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -399,6 +479,46 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -410,6 +530,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "gosub-engine" version = "0.1.0" @@ -420,6 +546,8 @@ dependencies = [ "colored", "criterion", "derive_more", + "domain-lookup-tree", + "hickory-resolver", "lazy_static", "log", "nom", @@ -429,6 +557,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "simple_logger", "sqlite", "test-case", "textwrap", @@ -458,6 +587,62 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hickory-proto" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "idna" version = "0.4.0" @@ -468,6 +653,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "is-terminal" version = "0.4.9" @@ -476,7 +679,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -515,18 +718,49 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "memchr" version = "2.6.4" @@ -557,6 +791,17 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -587,6 +832,34 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -599,6 +872,29 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -647,6 +943,18 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.27" @@ -681,6 +989,18 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -714,6 +1034,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -729,6 +1055,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", ] @@ -737,6 +1075,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rayon" @@ -758,6 +1099,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -787,6 +1137,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + [[package]] name = "ring" version = "0.16.20" @@ -802,6 +1162,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -817,11 +1183,11 @@ version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -914,18 +1280,55 @@ dependencies = [ "serde", ] +[[package]] +name = "simple_logger" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.42.0", +] + [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -1065,6 +1468,37 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1090,6 +1524,53 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -1269,6 +1750,12 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "wildmatch" version = "2.1.1" @@ -1306,6 +1793,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1321,53 +1823,105 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml index 91c32c9e8..c0ea606e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,9 @@ clap = { version = "4.4.7", features = ["derive"] } cli-table = "0.4.7" textwrap = "0.16.0" log = "0.4.20" +domain-lookup-tree = "0.1" +hickory-resolver = "0.24.0" +simple_logger = "4.2.0" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/src/config.rs b/src/config.rs index beb54d667..ffa80f7b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -177,11 +177,11 @@ mod test { let mut store = ConfigStore::from_storage(Box::new(MemoryStorageAdapter::new()), true).unwrap(); let setting = store.get("dns.local_resolver.enabled"); - assert_eq!(setting, Setting::Bool(false)); + assert_eq!(setting, Setting::Bool(true)); - store.set("dns.local_resolver.enabled", Setting::Bool(true)); + store.set("dns.local_resolver.enabled", Setting::Bool(false)); let setting = store.get("dns.local_resolver.enabled"); - assert_eq!(setting, Setting::Bool(true)); + assert_eq!(setting, Setting::Bool(false)); } #[test] diff --git a/src/config/settings.json b/src/config/settings.json index 573a0e5b9..bfb925202 100644 --- a/src/config/settings.json +++ b/src/config/settings.json @@ -1,16 +1,52 @@ { "dns": [ + { + "key": "cache.max_entries", + "type": "u", + "default": "u:1000", + "description": "This setting defines the maximum number of entries that may be stored in the DNS cache before the oldest entry is evicted." + }, { "key": "local_resolver.enabled", "type": "b", + "default": "b:true", + "description": "This setting enables the local DNS override table. When enabled, Gosub will return any IP address that is defined in the local DNS override table." + }, + { + "key": "local_resolver.table", + "type": "m", + "default": "m:''", + "description": "This setting defines the local DNS override table." + }, + { + "key": "resolve.ttl.override.enabled", + "type": "b", "default": "b:false", - "description": "This setting enables the local DNS resolver. When enabled, GosuB will use the local DNS resolver to resolve DNS queries. When disabled, GosuB will use the DNS resolver configured in the operating system." + "description": "When enabled, the TTL of each entry will be overridden with the value defined in resolve.ttl.override." + }, + { + "key": "resolve.ttl.override.seconds", + "type": "u", + "default": "u:0", + "description": "Number of seconds to override the TTL with. When set to 0, the TTL will expire directly" }, { "key": "doh.enabled", "type": "b", "default": "b:false", "description": "This setting enabled DNS over HTTPS. A secure way of communicating with DNS servers." + }, + { + "key": "dot.enabled", + "type": "b", + "default": "b:false", + "description": "This setting enabled DNS over TLS. A secure way of communicating with DNS servers." + }, + { + "key": "resolver.ips", + "type": "m", + "default": "m:''", + "description": "Any resolvers defined here will be used for DNS lookups. If no resolvers are defined, the system resolvers will be used." } ], "useragent": [ diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 000000000..36b6a6dcb --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,217 @@ +mod cache; +mod local; +mod remote; + +use crate::types; +use crate::types::Result; +use core::str::FromStr; +use derive_more::Display; +use log::{debug, info}; +use std::net::IpAddr; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// A DNS entry is a mapping of a domain to zero or more IP address mapping +#[derive(Default, Clone, Debug, PartialEq)] +struct DnsEntry { + // domain name + domain: String, + // // Ip type that is stored in this entry (could be Ipv4, IPv6 or Both) + // ip_type: ResolveType, + // List of addresses for this domain + ips: Vec, + + /// True when the ips list has ipv4 addresses + has_ipv4: bool, + /// True when the ips list has ipv6 addresses + has_ipv6: bool, + + // Internal iterator pointer + iter: usize, + /// expiry time after epoch + expires: u64, +} + +impl DnsEntry { + /// Instantiate a new domain name entry with set of ips + pub(crate) fn new(domain: &str, ips: Vec<&str>) -> DnsEntry { + let mut entry = DnsEntry { + domain: domain.to_string(), + ..Default::default() + }; + + for ip in ips { + if let Ok(ip) = IpAddr::from_str(ip) { + if ip.is_ipv4() { + entry.has_ipv4 = true; + } + if ip.is_ipv6() { + entry.has_ipv6 = true; + } + entry.ips.push(ip); + } + } + + entry + } + + /// Returns true if the dns entry has expired + pub fn expired(&self) -> bool { + self.expires + < SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + fn ipv4(&self) -> Vec { + self.ips.iter().filter(|x| x.is_ipv4()).copied().collect() + } + + fn ipv6(&self) -> Vec { + self.ips.iter().filter(|x| x.is_ipv6()).copied().collect() + } +} + +impl Iterator for DnsEntry { + type Item = IpAddr; + + /// With next() you can simply iterate over all the ips in the list + fn next(&mut self) -> Option { + if self.iter >= self.ips.len() { + // reset iterator at the end + self.iter = 0; + + return None; + } + + let ip = self.ips[self.iter]; + self.iter += 1; + + Some(ip) + } +} + +/// Type of DNS resolution +#[derive(Display, Debug, PartialEq, Clone)] +pub enum ResolveType { + /// Only resolve IPV4 addresses (A) + Ipv4, + /// Only resolve IPV6 addresses (AAAA) + Ipv6, + /// Resolve both IPV4 and IPV6 addresses + Both, +} + +trait DnsResolver { + /// Resolves a domain name for a given resolver_type + fn resolve(&mut self, domain: &str, resolve_type: ResolveType) -> Result; + /// Announces the resolved dns entry for the domain to a resolver + fn announce(&mut self, domain: &str, entry: &DnsEntry); + + // name for debugging purposes + fn name(&self) -> &'static str; +} + +// impl fmt::Debug for dyn DnsResolver { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// write!(f, "dns resolver") +// } +// } + +trait DnsCache { + /// Flush all domains + fn flush_all(&mut self); + /// Flush a single domain + fn flush_entry(&mut self, domain: &str); +} + +struct Dns { + resolvers: Vec>, +} + +impl Dns { + pub fn new() -> Self { + let mut resolvers: Vec> = vec![]; + resolvers.push(Box::new(cache::CacheResolver::new(1000))); + resolvers.push(Box::new(local::LocalTableResolver::new())); + + let opts = remote::RemoteResolverOptions::default(); + resolvers.push(Box::new(remote::RemoteResolver::new(opts))); + + Dns { resolvers } + } + + /// Resolves a domain name to a set of IP addresses based on the resolve_type. + /// It can resolve either Ipv4, ipv6 or both addresses. + /// + /// Each request will be resolved by the resolvers in the order they are added. + /// The first resolver is usually the cache resolver, which caches any entries (according to their TTL) + /// The second resolver is usually the local table resolver, which resolves any local overrides + /// The third resolver is usually the remote resolver, which resolves any remote entries by querying external DNS server(s) + /// + pub fn resolve(&mut self, domain: &str, resolve_type: ResolveType) -> Result { + let mut entry = None; + + info!("Resolving {} for {:?}", domain, resolve_type); + + for resolver in self.resolvers.iter_mut() { + debug!("Trying resolver: {}", resolver.name()); + + if let Ok(e) = resolver.resolve(domain, resolve_type.clone()) { + debug!("Found entry {:?}", e); + entry = Some(e); + break; + } + } + + if entry.is_none() { + return Err(types::Error::DnsDomainNotFound); + } + + // Iterate all resolvers and add to all cache systems (normally, this is only the first resolver) + for resolver in self.resolvers.iter_mut() { + resolver.announce(domain, &entry.clone().unwrap().clone()); + } + + Ok(entry.unwrap().clone()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use simple_logger::SimpleLogger; + use std::time::Instant; + + #[test] + fn resolver() { + SimpleLogger::new().init().unwrap(); + + let mut dns = Dns::new(); + + let now = Instant::now(); + let e = dns.resolve("example.org", ResolveType::Ipv4).unwrap(); + let elapsed_time = now.elapsed(); + e.ipv4().iter().for_each(|x| println!("ipv4: {}", x)); + println!("Took {} microseconds.", elapsed_time.as_micros()); + + let now = Instant::now(); + let e = dns.resolve("example.org", ResolveType::Ipv6).unwrap(); + let elapsed_time = now.elapsed(); + e.ipv6().iter().for_each(|x| println!("ipv6: {}", x)); + println!("Took {} microseconds.", elapsed_time.as_micros()); + + let now = Instant::now(); + let e = dns.resolve("example.org", ResolveType::Ipv4).unwrap(); + let elapsed_time = now.elapsed(); + e.ipv4().iter().for_each(|x| println!("ipv4: {}", x)); + println!("Took {} microseconds.", elapsed_time.as_micros()); + + let now = Instant::now(); + let e = dns.resolve("example.org", ResolveType::Both).unwrap(); + let elapsed_time = now.elapsed(); + e.ipv4().iter().for_each(|x| println!("ipv4: {}", x)); + e.ipv6().iter().for_each(|x| println!("ipv6: {}", x)); + println!("Took {} microseconds.", elapsed_time.as_micros()); + } +} diff --git a/src/dns/cache.rs b/src/dns/cache.rs new file mode 100644 index 000000000..a8405aae2 --- /dev/null +++ b/src/dns/cache.rs @@ -0,0 +1,165 @@ +use crate::dns::{DnsCache, DnsEntry, DnsResolver, ResolveType}; +use crate::types::Result; +use log::trace; +use std::collections::{HashMap, VecDeque}; + +pub(crate) struct CacheResolver { + values: HashMap, + max_entries: usize, + lru: VecDeque, +} + +impl DnsResolver for CacheResolver { + fn resolve(&mut self, domain: &str, resolve_type: ResolveType) -> Result { + if let Some(entry) = self.values.get(domain) { + if !entry.has_ipv4 && !entry.has_ipv6 && resolve_type == ResolveType::Both { + trace!("{}: no addresses found in entry", domain); + return Err(crate::types::Error::DnsNoIpAddressFound); + } + if !entry.has_ipv4 && resolve_type == ResolveType::Ipv4 { + trace!("{}: no ipv4 addresses found in entry", domain); + return Err(crate::types::Error::DnsNoIpAddressFound); + } + if !entry.has_ipv6 && resolve_type == ResolveType::Ipv6 { + trace!("{}: no ipv6 addresses found in entry", domain); + return Err(crate::types::Error::DnsNoIpAddressFound); + } + + trace!("{}: found in cache with correct resolve type", domain); + self.lru.retain(|x| x != domain); + self.lru.push_back(domain.to_string()); + + return Ok(entry.clone()); + } + + Err(crate::types::Error::DnsNoIpAddressFound) + } + + /// When a domain is resolved, it will be announced to all resolvers. This cache resolver + /// will store it into the cache. + fn announce(&mut self, domain: &str, entry: &DnsEntry) { + trace!("{}: announcing to cache", domain); + + self.lru.retain(|x| x != domain); + self.lru.push_back(domain.to_string()); + + if let Some(current_entry) = self.values.get_mut(domain) { + trace!("{}: updating existing entry to cache", domain); + + trace!("current entries: {:?}", current_entry.ips); + trace!("new entries: {:?}", entry.ips); + current_entry.has_ipv4 |= entry.has_ipv4; + current_entry.has_ipv6 |= entry.has_ipv6; + + for ip in entry.ips.iter() { + if current_entry.ips.iter().any(|x| x == ip) { + continue; + } + current_entry.ips.push(*ip); + } + trace!("merged entries: {:?}", current_entry.ips); + } else { + trace!("adding new entry to cache"); + + // Clear out if we have too many entries + if self.values.len() >= self.max_entries { + if let Some(key) = self.lru.pop_front() { + self.values.remove(&key); + } + } + + self.values.insert(domain.to_string(), entry.clone()); + } + } + + fn name(&self) -> &'static str { + "cache resolver" + } +} + +impl DnsCache for CacheResolver { + fn flush_all(&mut self) { + self.values.clear(); + self.lru.clear(); + } + + fn flush_entry(&mut self, domain: &str) { + self.values.remove(domain); + self.lru.retain(|x| x != domain); + } +} + +impl CacheResolver { + pub(crate) fn new(max_entries: usize) -> CacheResolver { + CacheResolver { + values: HashMap::with_capacity(max_entries), + max_entries, + lru: VecDeque::with_capacity(max_entries), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::dns::DnsEntry; + + #[test] + fn test_cache() { + let mut cache = CacheResolver::new(3); + + cache.announce( + "example.com", + &DnsEntry::new("example.com", vec!["127.0.0.1"]), + ); + cache.announce( + "example.org", + &DnsEntry::new("example.org", vec!["127.0.0.1"]), + ); + + assert_eq!(cache.values.len(), 2); + assert_eq!(cache.lru.len(), 2); + assert_eq!(cache.lru.capacity(), 3); + assert_eq!(cache.lru[0], "example.com"); + assert_eq!(cache.lru[1], "example.org"); + + cache.announce( + "example.net", + &DnsEntry::new("example.net", vec!["127.0.0.1"]), + ); + assert_eq!(cache.values.len(), 3); + assert_eq!(cache.lru.len(), 3); + assert_eq!(cache.lru[0], "example.com"); + assert_eq!(cache.lru[1], "example.org"); + assert_eq!(cache.lru[2], "example.net"); + + println!("lru: {:?}", cache.lru); + let _ = cache.resolve("example.org", ResolveType::Both); + println!("lru: {:?}", cache.lru); + assert_eq!(cache.lru[0], "example.com"); + assert_eq!(cache.lru[1], "example.net"); + assert_eq!(cache.lru[2], "example.org"); + + cache.announce( + "example.net", + &DnsEntry::new("example.net", vec!["127.0.0.1"]), + ); + assert_eq!(cache.values.len(), 3); + assert_eq!(cache.lru.len(), 3); + assert_eq!(cache.lru[0], "example.com"); + assert_eq!(cache.lru[1], "example.org"); + assert_eq!(cache.lru[2], "example.net"); + + let _ = cache.resolve("example.com", ResolveType::Both); + assert_eq!(cache.lru[0], "example.org"); + assert_eq!(cache.lru[1], "example.net"); + assert_eq!(cache.lru[2], "example.com"); + + cache.announce("new.com", &DnsEntry::new("new.com", vec!["127.0.0.1"])); + assert_eq!(cache.values.len(), 3); + assert_eq!(cache.lru.len(), 3); + assert_eq!(cache.lru[0], "example.net"); + assert_eq!(cache.lru[1], "example.com"); + assert_eq!(cache.lru[2], "new.com"); + } +} diff --git a/src/dns/local.rs b/src/dns/local.rs new file mode 100644 index 000000000..95ec6c87c --- /dev/null +++ b/src/dns/local.rs @@ -0,0 +1,168 @@ +use crate::dns::{DnsCache, DnsEntry, DnsResolver, ResolveType}; +use crate::types; +use core::fmt; +use domain_lookup_tree::DomainLookupTree; +use log::trace; +use std::collections::HashMap; + +/// Local override table that can be used instead of using /etc/hosts or similar 3rd party dns system. +pub struct LocalTableResolver { + /// Entries in the local override table. + entries: HashMap, + /// Domaintree is a hierarchical lookup tree for quick scanning of (wildcard) domains + tree: DomainLookupTree, +} + +impl Default for LocalTableResolver { + fn default() -> Self { + LocalTableResolver { + entries: HashMap::new(), + tree: DomainLookupTree::new(), + } + } +} + +impl DnsResolver for LocalTableResolver { + fn resolve( + &mut self, + domain: &str, + _resolve_type: ResolveType, + ) -> Result { + let domain_entry = match self.tree.lookup(domain) { + Some(domain_entry) => domain_entry, + None => { + trace!("{}: not found in local table", domain); + return Err(types::Error::DnsDomainNotFound); + } + }; + + trace!("{}: found in local tree", domain_entry); + + // domain_entry could be "com" if you ask for just "com" and it's part of the tree (it normally is). So in + // that case we still have to check if the domain is actually in the entries list. + if let Some(entry) = self.entries.get(&domain_entry) { + return Ok(entry.clone()); + } + + trace!("{}: not found in local table", domain); + Err(types::Error::DnsDomainNotFound) + } + + fn announce(&mut self, _domain: &str, _entry: &DnsEntry) { + // Do nothing. + } + + fn name(&self) -> &'static str { + "local table resolver" + } +} + +impl DnsCache for LocalTableResolver { + fn flush_all(&mut self) { + // flushing the local table means reloading the entries + self.reload_table_entries(); + } + + fn flush_entry(&mut self, domain: &str) { + self.reload_table_entry(domain); + } +} + +impl LocalTableResolver { + /// Instantiates a new local override table + pub fn new() -> LocalTableResolver { + let mut table = LocalTableResolver { + entries: HashMap::new(), + tree: DomainLookupTree::new(), + }; + + table.reload_table_entries(); + table + } + + /// Helper function to add an entry to the local override table. It will figure out which + /// elements are ipv4 and ipv6 and add them accordingly + pub fn add_entry(&mut self, domain: &str, ips: Vec<&str>) { + let entry = DnsEntry::new(domain, ips); + + self.entries.insert(domain.to_string(), entry); + self.tree.insert(domain); + } + + /// Regenerates the new entries table + pub fn reload_table_entries(&mut self) { + // @todo: this should reload all table entries from the configuration into the self.entries list + } + + pub fn reload_table_entry(&mut self, _domain: &str) { + // @todo: this should reload a single entry from the configuration into the self.entries + } +} + +impl fmt::Debug for LocalTableResolver { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "local table resolver") + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::str::FromStr; + use std::net::IpAddr; + + #[test] + fn test_local_override() { + let mut table = LocalTableResolver::new(); + + table.add_entry("example.com", vec!["1.2.3.4"]); + table.add_entry("foo.example.com", vec!["2.3.4.5"]); + table.add_entry(".wildcard.com", vec!["6.6.6.6"]); + table.add_entry("specific.wildcard.com", vec!["8.8.8.8"]); + table.add_entry("ipv6.com", vec!["2002::1", "2002::2", "200.200.200.200"]); + + // Simple resolve + let e = table.resolve("example.com", ResolveType::Ipv4).unwrap(); + assert_eq!( + &IpAddr::from_str("1.2.3.4").unwrap(), + e.ips.first().unwrap() + ); + assert!(table.resolve("xample.com", ResolveType::Ipv4).is_err()); + assert!(table.resolve("com", ResolveType::Ipv4).is_err()); + assert!(table.resolve("example", ResolveType::Ipv4).is_err()); + + // Wildcard + let e = table + .resolve("specific.wildcard.com", ResolveType::Ipv4) + .unwrap(); + assert_eq!( + &IpAddr::from_str("8.8.8.8").unwrap(), + e.ips.first().unwrap() + ); + let e = table + .resolve("something.wildcard.com", ResolveType::Ipv4) + .unwrap(); + assert_eq!( + &IpAddr::from_str("6.6.6.6").unwrap(), + e.ips.first().unwrap() + ); + let e = table + .resolve("foobar.wildcard.com", ResolveType::Ipv4) + .unwrap(); + assert_eq!( + &IpAddr::from_str("6.6.6.6").unwrap(), + e.ips.first().unwrap() + ); + assert!(table.resolve("foo.custom.com", ResolveType::Ipv4).is_err()); + assert!(table + .resolve("too.specific.wildcard.com", ResolveType::Ipv4) + .is_err()); + assert!(table.resolve("custom.com", ResolveType::Ipv4).is_err()); + + // round robin on both ipv4 and ipv6 + let e = table.resolve("ipv6.com", ResolveType::Ipv4).unwrap(); + assert_eq!(3, e.ips.len()); + assert_eq!(1, e.ipv4().len()); + assert_eq!(2, e.ipv6().len()); + } +} diff --git a/src/dns/remote.rs b/src/dns/remote.rs new file mode 100644 index 000000000..20e27abe9 --- /dev/null +++ b/src/dns/remote.rs @@ -0,0 +1,113 @@ +use crate::dns::{DnsEntry, DnsResolver, ResolveType}; +use crate::types; +use core::str::FromStr; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::Resolver; +use log::trace; +use std::net::IpAddr; + +pub struct RemoteResolver { + hickory: Resolver, +} + +impl DnsResolver for RemoteResolver { + fn resolve( + &mut self, + domain: &str, + resolve_type: ResolveType, + ) -> Result { + let mut entry = DnsEntry::new(domain, vec![]); + + let mut ip_types = Vec::new(); + match resolve_type { + ResolveType::Ipv4 => { + ip_types.push("ipv4"); + } + ResolveType::Ipv6 => { + ip_types.push("ipv6"); + } + ResolveType::Both => { + ip_types.push("ipv6"); + ip_types.push("ipv4"); + } + } + + trace!("{}: resolving with {:?}", domain, ip_types); + + for ip_type in ip_types.iter() { + match *ip_type { + "ipv4" => { + let e = self.hickory.ipv4_lookup(domain); + if e.is_err() { + continue; + } + e.unwrap().iter().for_each(|ip| { + trace!("{}: found ipv4 address {}", domain, ip); + entry + .ips + .push(IpAddr::from_str(ip.to_string().as_str()).unwrap()); + entry.has_ipv4 = true; + }); + } + "ipv6" => { + let e = self.hickory.ipv6_lookup(domain); + if e.is_err() { + continue; + } + e.unwrap().iter().for_each(|ip| { + trace!("{}: found ipv6 address {}", domain, ip); + entry + .ips + .push(IpAddr::from_str(ip.to_string().as_str()).unwrap()); + entry.has_ipv6 = true; + }); + } + _ => {} + } + } + + if !entry.has_ipv4 && !entry.has_ipv6 { + return Err(types::Error::DnsNoIpAddressFound); + } + + Ok(entry) + } + + fn announce(&mut self, _domain: &str, _entry: &DnsEntry) { + // Do nothing. + } + + fn name(&self) -> &'static str { + "remote resolver" + } +} + +impl RemoteResolver { + /// Instantiates a new local override table + pub fn new(_opts: RemoteResolverOptions) -> RemoteResolver { + // @todo: do something with the options + let config = ResolverConfig::default(); + let opts = ResolverOpts::default(); + + RemoteResolver { + hickory: Resolver::new(config, opts).unwrap(), + } + } +} + +/// Options for the remote resolver +pub struct RemoteResolverOptions { + pub timeout: u32, + pub retries: u32, + pub nameservers: Vec, +} + +impl Default for RemoteResolverOptions { + fn default() -> Self { + RemoteResolverOptions { + timeout: 5, + retries: 3, + nameservers: vec![], + } + } +} diff --git a/src/html5/parser/document.rs b/src/html5/parser/document.rs index 42ffb4508..b0b4f6e87 100755 --- a/src/html5/parser/document.rs +++ b/src/html5/parser/document.rs @@ -1375,7 +1375,7 @@ mod tests { if let Err(err) = found_ids { assert_eq!( err.to_string(), - "query error: Query predicate is uninitialized" + "query: generic error: Query predicate is uninitialized" ); } else { panic!() diff --git a/src/lib.rs b/src/lib.rs index e81244136..962508f70 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,5 @@ pub mod types; #[allow(dead_code)] pub mod config; +#[allow(dead_code)] +mod dns; diff --git a/src/types.rs b/src/types.rs index 35fec7ffb..8e77176f9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -44,8 +44,23 @@ pub enum Error { #[error("document task error: {0}")] DocumentTask(String), - #[error("query error: {0}")] + #[error("query: generic error: {0}")] Query(String), + + #[error("dns: generic error: {0}")] + DnsGeneric(String), + + #[error("dns: no ipv6 address found")] + DnsNoIpv6Found, + + #[error("dns: no ipv4 address found")] + DnsNoIpv4Found, + + #[error("dns: no ip address found")] + DnsNoIpAddressFound, + + #[error("dns: domain not found")] + DnsDomainNotFound, } /// Result that can be returned which holds either T or an Error From 2e435cb3e26ae29e4c6016fd1455663e9d3c02b4 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sun, 19 Nov 2023 11:42:19 +0100 Subject: [PATCH 2/2] fixed some suggestions --- src/dns.rs | 28 +++------------------------- src/dns/local.rs | 4 ---- src/dns/remote.rs | 16 ++++++---------- 3 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/dns.rs b/src/dns.rs index 36b6a6dcb..e17ff0393 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -70,24 +70,9 @@ impl DnsEntry { fn ipv6(&self) -> Vec { self.ips.iter().filter(|x| x.is_ipv6()).copied().collect() } -} - -impl Iterator for DnsEntry { - type Item = IpAddr; - - /// With next() you can simply iterate over all the ips in the list - fn next(&mut self) -> Option { - if self.iter >= self.ips.len() { - // reset iterator at the end - self.iter = 0; - - return None; - } - - let ip = self.ips[self.iter]; - self.iter += 1; - Some(ip) + fn iter(&self) -> impl Iterator { + self.ips.iter() } } @@ -106,18 +91,11 @@ trait DnsResolver { /// Resolves a domain name for a given resolver_type fn resolve(&mut self, domain: &str, resolve_type: ResolveType) -> Result; /// Announces the resolved dns entry for the domain to a resolver - fn announce(&mut self, domain: &str, entry: &DnsEntry); - + fn announce(&mut self, _domain: &str, _entry: &DnsEntry) {} // name for debugging purposes fn name(&self) -> &'static str; } -// impl fmt::Debug for dyn DnsResolver { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "dns resolver") -// } -// } - trait DnsCache { /// Flush all domains fn flush_all(&mut self); diff --git a/src/dns/local.rs b/src/dns/local.rs index 95ec6c87c..ff7852844 100644 --- a/src/dns/local.rs +++ b/src/dns/local.rs @@ -48,10 +48,6 @@ impl DnsResolver for LocalTableResolver { Err(types::Error::DnsDomainNotFound) } - fn announce(&mut self, _domain: &str, _entry: &DnsEntry) { - // Do nothing. - } - fn name(&self) -> &'static str { "local table resolver" } diff --git a/src/dns/remote.rs b/src/dns/remote.rs index 20e27abe9..4d1effa78 100644 --- a/src/dns/remote.rs +++ b/src/dns/remote.rs @@ -21,14 +21,14 @@ impl DnsResolver for RemoteResolver { let mut ip_types = Vec::new(); match resolve_type { ResolveType::Ipv4 => { - ip_types.push("ipv4"); + ip_types.push(ResolveType::Ipv4); } ResolveType::Ipv6 => { - ip_types.push("ipv6"); + ip_types.push(ResolveType::Ipv6); } ResolveType::Both => { - ip_types.push("ipv6"); - ip_types.push("ipv4"); + ip_types.push(ResolveType::Ipv6); + ip_types.push(ResolveType::Ipv4); } } @@ -36,7 +36,7 @@ impl DnsResolver for RemoteResolver { for ip_type in ip_types.iter() { match *ip_type { - "ipv4" => { + ResolveType::Ipv4 => { let e = self.hickory.ipv4_lookup(domain); if e.is_err() { continue; @@ -49,7 +49,7 @@ impl DnsResolver for RemoteResolver { entry.has_ipv4 = true; }); } - "ipv6" => { + ResolveType::Ipv6 => { let e = self.hickory.ipv6_lookup(domain); if e.is_err() { continue; @@ -73,10 +73,6 @@ impl DnsResolver for RemoteResolver { Ok(entry) } - fn announce(&mut self, _domain: &str, _entry: &DnsEntry) { - // Do nothing. - } - fn name(&self) -> &'static str { "remote resolver" }